's default outline respectively. */
-.library-empty-hint {
- padding: 1.5rem 1rem;
- color: #9299a2;
- font-size: 0.85rem;
- text-align: center;
-}
-
-.library-load-more {
- display: flex;
- align-items: center;
- justify-content: center;
- width: min(100%, 52rem);
- height: auto;
- margin: 0 auto;
- padding: 0.8rem;
- border: 1px solid #eceef1;
- border-radius: 0.4rem;
- box-shadow: none;
- color: #567;
- background: #fff;
- font-size: 0.85rem;
- text-transform: none;
-}
-
-.library-favorite-item {
- display: flex;
- gap: 0.7rem;
- padding: 0.9rem 1rem;
- border-bottom: 1px solid #eceef1;
- background: #fff;
-}
-
-.library-favorite-item.selecting {
- align-items: center;
-}
-
-.library-favorite-content {
- min-width: 0;
- flex: 1 1 auto;
-}
-
-.library-favorite-content > footer {
- display: grid;
- grid-template-columns: minmax(0, 1fr) auto;
- align-items: center;
- gap: 0.5rem;
- margin-top: 0.65rem;
- color: #9299a2;
- font-size: 0.72rem;
-}
-
-.library-favorite-content > footer span {
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.library-favorite-content > footer button {
- width: 2rem;
- min-width: 2rem;
- height: 2rem;
- min-height: 2rem;
- margin: 0;
- padding: 0;
- border: 0;
- box-shadow: none;
- color: #7d858e;
- background: transparent;
-}
-
-.library-text-content {
- margin: 0;
- color: #343941;
- font-size: 0.94rem;
- line-height: 1.65;
-}
-
-/* 需求7.4"长文本折叠、展开和复制":clamped 限定默认显示高度,展开态(FavoriteText 的
- expanded=true) 去掉这个 class 即可显示全文;library-text-actions 放"展开/收起"(仅溢出
- 时才渲染)和"复制"两个按钮,颜色沿用应用里其它文字按钮的强调色。
- 没有用 -webkit-line-clamp:实测这个环境里它对这个 不生效——getComputedStyle 能读到
- display/-webkit-line-clamp/overflow 都是设置的值,但视觉上完全不裁切,scrollHeight 也跟
- clientHeight 相等,是那套盒模型本身在当前渲染环境下没有实际生效,不是 CSS 写错了。改用
- .library-text-content 已有的 line-height:1.65 反推的 max-height(5 行 = 5*1.65em)+
- overflow:hidden,是普通块级盒子,不依赖任何厂商前缀的特殊渲染路径,行为可预期。 */
-.library-text-content.clamped {
- max-height: 8.25em;
- overflow: hidden;
-}
-
-/* library-text-wrap 故意不用 display:flex——曾经这样写过,结果 .clamped 的
变成了 flex
- item:flex/grid item 的 specified display 会被 UA 按 CSS Display 规范"blockify",实测这里的
- -webkit-box 被换成了 flow-root(getComputedStyle 验证过),-webkit-line-clamp 整个失效,折叠
- 彻底不生效。改回普通块级 + margin 做间距,让 .clamped 保持自己声明的 -webkit-box 不被更外层
- 的 flex 容器改写。 */
-.library-text-wrap {
- display: block;
-}
-
-.library-text-actions {
- display: flex;
- align-items: center;
- gap: 1.1rem;
- margin-top: 0.35rem;
-}
-
-.library-text-actions button {
- display: inline-flex;
- align-items: center;
- gap: 0.2rem;
- margin: 0;
- padding: 0;
- border: 0;
- box-shadow: none;
- background: transparent;
- color: #3b82c4;
- font-size: 0.82rem;
-}
-
-.library-text-actions button .material-icons {
- font-size: 1rem;
-}
-
-.library-file-card {
- display: grid;
- grid-template-columns: 3rem minmax(0, 1fr) auto;
- align-items: center;
- gap: 0.8rem;
- width: 100%;
- min-height: 4.8rem;
- margin: 0;
- padding: 0.7rem;
- border: 1px solid #eceef1;
- border-radius: 0.4rem;
- box-shadow: none;
- color: #30353c;
- background: #fafbfc;
- text-align: left;
- text-transform: none;
-}
-
-.library-file-card > span,
-.library-download-icon {
- display: grid;
- place-items: center;
- width: 3rem;
- height: 3.4rem;
- border-radius: 0.3rem;
- color: #fff;
- background: #e54b68;
- font-size: 0.7rem;
- font-weight: 700;
-}
-
-.library-file-card > div {
- display: flex;
- min-width: 0;
- flex-direction: column;
- /* .library-file-card is a , and the global `button{line-height:2.2rem}` reset (meant for
- a single line of centered action-button text) inherits straight down into this /
- pair -- getComputedStyle confirmed the line box is 29.33px tall for 14px text, so its
- own bottom edge sits ~15px below the actual glyphs even before 's 0.3rem margin-top,
- making the gap between filename and size look much bigger than that nominal margin. Reset once
- here so both children (which don't set their own line-height) inherit something sane instead. */
- line-height: 1.3;
-}
-
-.library-file-card strong {
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.library-file-card small {
- margin-top: 0.3rem;
- color: #9299a2;
- font-size: 0.74rem;
-}
-
-.library-file-card.removed {
- opacity: 0.65;
-}
-
-.library-file-card em {
- color: #d74a53;
- font-size: 0.74rem;
- font-style: normal;
-}
-
-.library-link-text {
- display: flex;
- align-items: center;
- gap: 0.6rem;
- width: 100%;
- min-width: 0;
- height: auto;
- min-height: 2.8rem;
- margin: 0;
- padding: 0.6rem 0.8rem;
- border: 1px solid #eceef1;
- border-radius: 0.4rem;
- box-shadow: none;
- color: var(--clr-primary-accent);
- background: #fafbfc;
- text-align: left;
- text-transform: none;
- line-height: 1.3;
-}
-
-.library-link-text span {
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-/* Shared by VoiceCard/LinkText/MediaThumb/text-favorite's "removed" state (content-library-view.jsx)
- -- same opacity + red label language as .library-file-card.removed/em above, but these don't share
- its grid layout (voice/media assume an icon+waveform or an image backdrop that isn't there once
- the source message is gone), so the removed state uses a simple centered flex row instead.
- .library-text-content.removed covers a recalled/deleted *text* favorite or history-search hit --
- plain text has no dedicated wrapper component of its own (FavoriteItem/History render it inline),
- so the same modifier class is applied straight to the existing
- rather than introducing a whole new component just for this one state. */
-.library-voice-card.removed,
-.library-link-text.removed,
-.library-media-thumb.removed,
-.library-text-content.removed {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 0.4rem;
- opacity: 0.65;
-}
-
-.library-voice-card.removed em,
-.library-link-text.removed em,
-.library-media-thumb.removed em,
-.library-text-content.removed em {
- color: #d74a53;
- font-size: 0.74rem;
- font-style: normal;
-}
-
-.library-voice-card {
- display: flex;
- align-items: center;
- gap: 0.6rem;
- width: min(100%, 20rem);
- min-height: 3.2rem;
- margin: 0;
- padding: 0 0.8rem;
- border: 0;
- border-radius: 0.4rem;
- box-shadow: none;
- color: #343a42;
- background: #eef5ff;
-}
-
-.library-voice-card.playing {
- color: #fff;
- background: #398afa;
-}
-
-.library-voice-card .material-icons {
- font-size: 1.6rem;
-}
-
-.library-wave {
- height: 1rem;
- opacity: 0.55;
- background: repeating-linear-gradient(90deg, currentColor 0 2px, transparent 2px 5px);
-}
-
-.library-link-card {
- display: grid;
- grid-template-columns: 5.2rem minmax(0, 1fr);
- gap: 0.8rem;
- width: 100%;
- min-height: 5.8rem;
- margin: 0;
- padding: 0.7rem;
- border: 1px solid #eceef1;
- border-radius: 0.4rem;
- box-shadow: none;
- color: #30353c;
- background: #fafbfc;
- text-align: left;
- text-transform: none;
-}
-
-.library-link-card > img {
- width: 5.2rem;
- height: 4.5rem;
- border-radius: 0.3rem;
- object-fit: cover;
-}
-
-.library-link-card > div {
- min-width: 0;
-}
-
-.library-link-card strong,
-.library-link-card p {
- display: block;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.library-link-card p {
- margin: 0.35rem 0;
- color: #7e858f;
- font-size: 0.76rem;
-}
-
-.library-link-card small {
- display: flex;
- align-items: center;
- color: #9aa0a8;
-}
-
-.library-link-card small .material-icons {
- margin-right: 0.25rem;
- font-size: 0.95rem;
-}
-
-.library-media-thumb {
- position: relative;
- width: 11rem;
- height: auto;
- aspect-ratio: 4 / 3;
- min-height: 0;
- margin: 0;
- padding: 0;
- overflow: hidden;
- border: 0;
- border-radius: 0.4rem;
- box-shadow: none;
- background: #dfe3e8;
-}
-
-.library-media-thumb img {
- width: 100%;
- height: 100%;
- object-fit: cover;
-}
-
-.library-media-thumb .material-icons {
- position: absolute;
- top: 50%;
- left: 50%;
- color: #fff;
- font-size: 2.5rem;
- filter: drop-shadow(0 2px 4px rgba(0,0,0,0.35));
- transform: translate(-50%, -50%);
-}
-
-.library-media-thumb small {
- position: absolute;
- right: 0.4rem;
- bottom: 0.3rem;
- color: #fff;
-}
-
-.library-check {
- display: grid;
- place-items: center;
- width: 1.4rem;
- min-width: 1.4rem;
- height: 1.4rem;
- min-height: 1.4rem;
- margin: 0;
- padding: 0;
- border: 1px solid #c8cdd3;
- border-radius: 50%;
- box-shadow: none;
- color: #fff;
- background: #fff;
-}
-
-.library-check.checked {
- border-color: #398afa;
- background: #398afa;
-}
-
-.library-check .material-icons {
- font-size: 0.95rem;
-}
-
-.library-empty {
- display: flex;
- align-items: center;
- justify-content: center;
- min-height: 20rem;
- color: #969da6;
- flex-direction: column;
-}
-
-.library-empty .material-icons {
- margin-bottom: 0.7rem;
- color: #b5bbc2;
- font-size: 3.5rem;
-}
-
-.library-batch-bar {
- display: grid;
- grid-template-columns: repeat(2, 1fr);
- flex: none;
- min-height: 4.5rem;
- padding-bottom: env(safe-area-inset-bottom);
- border-top: 1px solid #e2e5e9;
- background: #fff;
-}
-
-.library-batch-bar button {
- display: flex;
- align-items: center;
- justify-content: center;
- min-height: 4.2rem;
- margin: 0;
- padding: 0;
- border: 0;
- box-shadow: none;
- color: #555e68;
- background: transparent;
- flex-direction: column;
- text-transform: none;
-}
-
-.library-batch-bar button[disabled] {
- color: #b6bbc2;
-}
-
-.library-batch-bar span {
- margin-top: 0.2rem;
- font-size: 0.72rem;
-}
-
-/* Same generic confirm-sheet look this app already uses in personal-settings-view.jsx
- (.personal-sheet-backdrop/.personal-confirm-sheet, 46a98782) and group-management-view.jsx
- (.group-sheet-backdrop/.group-confirm-sheet, 4773ee5f) -- each view file keeps its own
- identically-styled copy under its own class prefix rather than a shared cross-file component. */
-.library-sheet-backdrop {
- display: grid;
- position: absolute;
- z-index: 70;
- inset: 0;
- place-items: center;
- padding: 1rem;
- background: rgba(22,26,31,0.52);
-}
-
-.library-confirm-sheet {
- width: min(100%, 25rem);
- padding: 1.25rem;
- border-radius: 0.5rem;
- background: #fff;
- text-align: center;
-}
-
-.library-confirm-sheet > .material-icons {
- color: #4d73aa;
- font-size: 2.8rem;
-}
-
-.library-confirm-sheet h2 {
- margin: 0.65rem 0;
- font-size: 1.15rem;
-}
-
-.library-confirm-sheet p {
- margin: 0.7rem 0 1rem;
- color: #5c646e;
- line-height: 1.6;
- text-align: left;
-}
-
-.library-confirm-sheet button {
- width: 100%;
- min-height: 2.9rem;
- margin: 0.45rem 0 0;
- padding: 0 1rem;
- border: 0;
- border-radius: 0.35rem;
- box-shadow: none;
- color: #4d5660;
- background: #f0f2f4;
-}
-
-.library-confirm-sheet button.danger {
- color: #fff;
- background: #d84950;
-}
-
-.library-section-title {
- width: min(100%, 52rem);
- margin: 0 auto;
- padding: 0.55rem 1rem;
- color: #8a929b;
- font-size: 0.78rem;
-}
-
-.library-target-list {
- width: min(100%, 52rem);
- min-height: 0;
- margin: 0 auto;
- overflow-y: auto;
- flex: 1 1 auto;
- background: #fff;
-}
-
-.library-target-list > button {
- display: grid;
- grid-template-columns: 3rem minmax(0, 1fr) 1.5rem;
- align-items: center;
- gap: 0.8rem;
- width: 100%;
- min-height: 4.5rem;
- margin: 0;
- padding: 0.6rem 1rem;
- border: 0;
- border-bottom: 1px solid #eceef1;
- border-radius: 0;
- box-shadow: none;
- color: #30353c;
- background: #fff;
- text-align: left;
- text-transform: none;
-}
-
-.library-target-list > button > span:nth-child(2) {
- display: flex;
- min-width: 0;
- flex-direction: column;
-}
-
-.library-target-list small {
- margin-top: 0.2rem;
- color: #9299a2;
-}
-
-.library-next-bar {
- display: flex;
- align-items: center;
- justify-content: space-between;
- flex: none;
- min-height: 4.3rem;
- padding: 0.6rem max(1rem, calc((100% - 50rem) / 2));
- border-top: 1px solid #e2e5e9;
- color: #777f89;
- background: #fff;
-}
-
-.library-next-bar button,
-.library-share-confirm > button,
-.library-primary {
- width: auto;
- min-width: 5rem;
- min-height: 2.8rem;
- margin: 0;
- padding: 0 1rem;
- border: 0;
- border-radius: 0.35rem;
- box-shadow: none;
- color: #fff;
- background: #398afa;
- text-transform: none;
-}
-
-.library-next-bar button[disabled] {
- background: #b7c9df;
-}
-
-.library-share-confirm {
- width: min(100%, 42rem);
- margin: 0 auto;
- padding: 1.2rem;
-}
-
-.library-share-confirm h2 {
- margin: 0 0 0.8rem;
- font-size: 0.92rem;
-}
-
-.library-target-summary {
- display: flex;
- gap: 1rem;
- padding: 1rem;
- overflow-x: auto;
- background: #fff;
-}
-
-.library-target-summary > span {
- display: flex;
- align-items: center;
- width: 4rem;
- flex: 0 0 4rem;
- flex-direction: column;
-}
-
-.library-target-summary small {
- width: 100%;
- margin-top: 0.3rem;
- overflow: hidden;
- text-align: center;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.library-share-confirm > section {
- margin-top: 1rem;
- padding: 1rem;
- background: #fff;
-}
-
-.library-share-confirm > section p {
- margin-bottom: 0;
- color: #858d96;
- font-size: 0.76rem;
- line-height: 1.5;
-}
-
-.library-share-confirm textarea {
- width: 100%;
- min-height: 5rem;
- margin: 1rem 0;
- padding: 0.8rem;
- border: 0;
- border-radius: 0.35rem;
- box-shadow: none;
- background: #fff;
- resize: vertical;
-}
-
-.library-share-confirm > button {
- width: 100%;
-}
-
-.library-download {
- width: min(100%, 36rem);
- margin: 0 auto;
- padding: 2.5rem 1.2rem;
- text-align: center;
-}
-
-.library-download-icon {
- width: 5rem;
- height: 5.8rem;
- margin: 0 auto 1rem;
- font-size: 1rem;
-}
-
-.library-download h2 {
- margin: 0;
- overflow-wrap: anywhere;
- font-size: 1.1rem;
-}
-
-.library-download > p {
- color: #8a929b;
-}
-
-.library-download dl {
- margin: 1.5rem 0;
- padding: 0 1rem;
- background: #fff;
- text-align: left;
-}
-
-.library-download dl > div {
- display: flex;
- justify-content: space-between;
- padding: 0.8rem 0;
- border-bottom: 1px solid #eceef1;
-}
-
-.library-download dt,
-.library-download dd {
- margin: 0;
-}
-
-.library-download dd {
- color: #777f89;
-}
-
-.library-primary {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 100%;
-}
-
-.library-primary .material-icons {
- margin-right: 0.4rem;
-}
-
-.library-download-progress {
- margin: 1.5rem 0;
- padding: 1rem;
- background: #fff;
- text-align: left;
-}
-
-.library-download-progress > div {
- display: flex;
- justify-content: space-between;
-}
-
-.library-download-progress progress {
- width: 100%;
- height: 0.45rem;
- margin: 0.8rem 0 0.4rem;
- accent-color: #398afa;
-}
-
-.library-download-progress small {
- color: #9299a2;
-}
-
-/* DownloadPage's loading ("取消") and done ("用其他应用打开") states each render exactly one button
- here -- never two side by side -- so the 2nd 1fr column sat permanently empty, pinning the
- button to the left half with the right half dead space. */
-.library-download-actions {
- display: grid;
- grid-template-columns: 1fr;
- gap: 0.8rem;
-}
-
-.library-download-actions button {
- min-height: 2.9rem;
- margin: 0;
- border: 1px solid #dce0e5;
- border-radius: 0.35rem;
- box-shadow: none;
- color: #555e68;
- background: #fff;
- text-transform: none;
-}
-
-.library-download-actions button.primary {
- border-color: #398afa;
- color: #fff;
- background: #398afa;
-}
-
-.library-download-actions .material-icons {
- margin-right: 0.35rem;
- font-size: 1.1rem;
- vertical-align: middle;
-}
-
-.library-complete {
- display: flex;
- align-items: center;
- gap: 0.7rem;
- margin: 1.5rem 0;
- padding: 1rem;
- color: #257c58;
- background: #eaf8f1;
- text-align: left;
-}
-
-.library-complete > .material-icons {
- font-size: 2rem;
-}
-
-.library-complete > span {
- display: flex;
- flex-direction: column;
-}
-
-.library-complete small {
- margin-top: 0.2rem;
- color: #65917d;
-}
-
-.library-message-results > button {
- display: grid;
- grid-template-columns: 3rem minmax(0, 1fr) 1.5rem;
- align-items: start;
- gap: 0.8rem;
- width: 100%;
- height: auto;
- min-height: 5.5rem;
- margin: 0;
- padding: 0.8rem 1rem;
- border: 0;
- border-bottom: 1px solid #eceef1;
- border-radius: 0;
- box-shadow: none;
- color: #30353c;
- background: #fff;
- text-align: left;
- text-transform: none;
-}
-
-.library-message-results > button > span {
- min-width: 0;
-}
-
-/* The global button{} reset's height:2.2rem clamps up to this row's own min-height (5.5rem) but
- stays a DEFINITE height rather than becoming auto -- so a snippet long enough to wrap past what
- 5.5rem holds (any real search hit, not just short mock text) doesn't grow the row to fit; it
- overflows past the row's own border-bottom into the next entry's space instead (confirmed live:
- this row's own trailing timestamp rendered overlapping the NEXT row's sender name).
- height:auto above lets the row grow past min-height like a plain block would. Same reset also
- sets white-space:nowrap, which the snippet
inherits -- with no wrapping at all, anything past
- the column width (a long link, not just long plain text) runs off the right edge instead of
- wrapping. overflow-wrap:anywhere matches .bubble div.message-content's own handling of the same
- kind of long, space-free URL text. */
-.library-message-results p {
- margin: 0.3rem 0;
- color: #666e78;
- line-height: 1.5;
- white-space: normal;
- overflow-wrap: anywhere;
-}
-
-.library-message-results mark {
- padding: 0 0.08rem;
- color: #1f66b8;
- background: #dcecff;
-}
-
-/* Same class of bug as .library-file-card/.library-record-header above: this is a and
- the global `button{line-height:2.2rem}` reset inherits into the sender-name and date
- , inflating the gap around the search-result snippet in between. */
-.library-message-results strong,
-.library-message-results small {
- line-height: 1.3;
-}
-
-.library-message-results small {
- color: #969da5;
-}
-
-.library-record-files,
-.library-record-links {
- padding: 0.8rem;
- background: #fff;
-}
-
-.library-record-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 0.6rem;
- width: 100%;
- min-width: 0;
- height: auto;
- margin: 0 0 0.4rem;
- padding: 0;
- border: 0;
- box-shadow: none;
- color: inherit;
- background: transparent;
- text-align: left;
- text-transform: none;
- /* Same fix as .library-file-card > div above: this is used as both a plain and a
- (see the comment on the JSX call sites) -- only the usage inherits the global
- `button{line-height:2.2rem}` reset, so without resetting it here the two usages render this
- row at two different heights/vertical rhythms depending on which element it happens to be. */
- line-height: 1.3;
-}
-
-.library-record-header strong {
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- font-weight: 600;
-}
-
-.library-record-header small {
- flex: none;
- color: #9299a2;
- font-size: 0.74rem;
-}
-
-.library-record-files .library-file-card,
-.library-record-links .library-link-text {
- margin-bottom: 0.7rem;
-}
-
-.library-date-title {
- margin: 0;
- padding: 0.7rem 0.8rem;
- color: #828a94;
- font-size: 0.8rem;
-}
-
-.library-media-grid {
- display: grid;
- grid-template-columns: repeat(4, 1fr);
- gap: 0.35rem;
- padding: 0 0.8rem 1rem;
-}
-
-.library-media-grid button {
- position: relative;
- width: 100%;
- height: auto;
- min-height: 0;
- aspect-ratio: 1;
- margin: 0;
- padding: 0;
- overflow: hidden;
- border: 0;
- border-radius: 0.25rem;
- box-shadow: none;
- background: #dfe3e8;
-}
-
-.library-media-grid img {
- width: 100%;
- height: 100%;
- object-fit: cover;
-}
-
-.library-media-grid .material-icons {
- position: absolute;
- top: 50%;
- left: 50%;
- color: #fff;
- font-size: 2.2rem;
- filter: drop-shadow(0 2px 4px rgba(0,0,0,0.35));
- transform: translate(-50%, -50%);
-}
-
-.library-media-grid small {
- position: absolute;
- right: 0.3rem;
- bottom: 0.2rem;
- color: #fff;
- font-size: 0.65rem;
-}
-
-/* Android call experience and call records. */
-#sidepanel.call-experience-panel {
- width: 100%;
- min-width: 0;
- flex: 1 1 auto;
- color-scheme: light;
- background: #11151a;
-}
-
-.call-experience-panel * {
- box-sizing: border-box;
- letter-spacing: 0;
-}
-
-.call-history-page,
-.call-minimized-page {
- display: flex;
- position: relative;
- width: 100%;
- min-height: 100%;
- overflow: hidden;
- color: #252a31;
- background: #f5f6f8;
- flex-direction: column;
-}
-
-.call-history-page > header,
-.call-chat-preview-header {
- display: grid;
- grid-template-columns: 3.8rem minmax(0, 1fr) 3.8rem;
- align-items: center;
- flex: none;
- min-height: 4.5rem;
- padding-top: env(safe-area-inset-top);
- border-bottom: 1px solid #e8eaed;
- background: #fff;
-}
-
-.call-history-page > header h1 {
- margin: 0;
- font-size: 1.15rem;
- text-align: center;
-}
-
-.call-history-page > header button,
-.call-chat-preview-header button {
- display: grid;
- place-items: center;
- width: 100%;
- min-width: 0;
- height: 3.2rem;
- min-height: 3.2rem;
- margin: 0;
- padding: 0;
- border: 0;
- box-shadow: none;
- color: #30353c;
- background: transparent;
-}
-
-.call-history-page > main {
- width: min(100%, 48rem);
- min-height: 0;
- margin: 0 auto;
- overflow-y: auto;
- flex: 1 1 auto;
- background: #fff;
-}
-
-.call-record {
- display: grid;
- grid-template-columns: 2.5rem minmax(0, 1fr) auto 1.5rem;
- align-items: center;
- gap: 0.7rem;
- width: 100%;
- min-height: 4.5rem;
- margin: 0;
- padding: 0.65rem 1rem;
- border: 0;
- border-bottom: 1px solid #eceef1;
- border-radius: 0;
- box-shadow: none;
- color: #30353c;
- background: #fff;
- text-align: left;
- text-transform: none;
-}
-
-.call-record > .material-icons:first-child {
- display: grid;
- place-items: center;
- width: 2.5rem;
- height: 2.5rem;
- border-radius: 50%;
- color: #257a58;
- background: #e9f7f0;
-}
-
-.call-record.missed > .material-icons:first-child,
-.call-record.declined > .material-icons:first-child,
-.call-record.busy > .material-icons:first-child {
- color: #d84850;
- background: #fdecee;
-}
-
-.call-record > span {
- display: flex;
- min-width: 0;
- flex-direction: column;
-}
-
-.call-record small {
- margin-top: 0.2rem;
- color: #858d96;
-}
-
-.call-record.missed small,
-.call-record.declined small,
-.call-record.busy small {
- color: #d84850;
-}
-
-.call-record time {
- color: #969da5;
- font-size: 0.72rem;
-}
-
-.call-record > .material-icons:last-child {
- color: #8d949d;
- font-size: 1rem;
-}
-
-.call-history-page > footer {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 0.8rem;
- width: min(100%, 48rem);
- min-height: 4.8rem;
- margin: 0 auto;
- padding: 0.7rem 1rem max(0.7rem, env(safe-area-inset-bottom));
- border-top: 1px solid #e2e5e9;
- background: #fff;
-}
-
-.call-history-page > footer button {
- display: flex;
- align-items: center;
- justify-content: center;
- min-height: 3rem;
- margin: 0;
- border: 0;
- border-radius: 0.35rem;
- box-shadow: none;
- color: #fff;
- background: #277f5a;
- text-transform: none;
-}
-
-.call-history-page > footer button:last-child {
- background: #347fd7;
-}
-
-.call-history-page > footer .material-icons {
- margin-right: 0.4rem;
- font-size: 1.2rem;
-}
-
-.call-fullscreen {
- display: flex;
- position: relative;
- width: 100%;
- min-height: 100%;
- overflow: hidden;
- color: #fff;
- background: #171c22;
- flex-direction: column;
- isolation: isolate;
-}
-
-.call-audio-surface {
- background: #26323b;
-}
-
-.call-ambient {
- position: absolute;
- z-index: -2;
- inset: -2rem;
- background: linear-gradient(160deg, #34444f 0%, #202931 52%, #151a20 100%);
-}
-
-.call-audio-surface::after {
- content: '';
- position: absolute;
- z-index: -1;
- inset: 0;
- opacity: 0.12;
- background-image: url('../img/bkg/p09.jpg');
- background-position: center;
- background-size: cover;
- filter: blur(24px);
- transform: scale(1.12);
-}
-
-.call-fullscreen.incoming-call-overlay {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- /* Must clear every other in-app overlay (headers/toasts go up to z-index:100) so a call always covers the screen. */
- z-index: 200;
-}
-
-.incoming-call-photo-blur {
- position: absolute;
- z-index: -1;
- inset: 0;
- opacity: 0.12;
- background-image: var(--incoming-call-photo);
- background-position: center;
- background-size: cover;
- filter: blur(24px);
- transform: scale(1.12);
-}
-
-.call-product-header {
- display: grid;
- z-index: 4;
- grid-template-columns: 4rem minmax(0, 1fr) 4rem;
- align-items: center;
- flex: none;
- min-height: 4.8rem;
- padding-top: env(safe-area-inset-top);
-}
-
-.call-product-header > button {
- display: grid;
- place-items: center;
- width: 100%;
- min-width: 0;
- height: 3.4rem;
- min-height: 3.4rem;
- margin: 0;
- padding: 0;
- border: 0;
- box-shadow: none;
- color: rgba(255,255,255,0.92);
- background: transparent;
-}
-
-.call-product-header > span {
- color: rgba(255,255,255,0.7);
- font-size: 0.74rem;
- text-align: center;
-}
-
-.call-peer-card {
- display: flex;
- z-index: 2;
- align-items: center;
- justify-content: center;
- min-height: 0;
- padding: 1rem;
- flex: 1 1 auto;
- flex-direction: column;
-}
-
-.call-product-avatar {
- display: block;
- width: 7.5rem;
- height: 7.5rem;
- overflow: hidden;
- border: 2px solid rgba(255,255,255,0.4);
- border-radius: 50%;
- box-shadow: 0 1rem 2.5rem rgba(0,0,0,0.25);
-}
-
-.call-product-avatar.small {
- width: 3rem;
- height: 3rem;
-}
-
-.call-product-avatar img {
- width: 100%;
- height: 100%;
- object-fit: cover;
-}
-
-/* Name + trusted badge used to share one nowrap/ellipsis box -- when the name alone was long
- enough to need truncating, the badge (last in source order) got truncated away with it,
- entirely outside the visible area instead of just following a shortened name. Name now
- truncates in its own flex child; the badge is a non-shrinking sibling so it always stays
- on-screen right after whatever of the name fit. */
-.call-peer-card h1 {
- display: flex;
- align-items: center;
- margin: 1.3rem 0 0.45rem;
- max-width: 100%;
- color: #fff;
- font-size: 1.55rem;
-}
-
-.call-peer-card h1 > span {
- min-width: 0;
- overflow-x: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.call-peer-card h1 .trusted-badge {
- flex: none;
-}
-
-.call-peer-card p {
- margin: 0;
- color: rgba(255,255,255,0.78);
- font-size: 0.92rem;
-}
-
-.call-peer-card .call-network {
- display: flex;
- align-items: center;
- margin-top: 0.65rem;
- color: rgba(255,255,255,0.62);
-}
-
-.call-network i {
- width: 0.45rem;
- height: 0.45rem;
- margin-right: 0.35rem;
- border-radius: 50%;
- background: #43d38c;
-}
-
-.call-ringing-dots {
- display: flex;
- gap: 0.35rem;
- margin-top: 1rem;
-}
-
-.call-ringing-dots i {
- width: 0.4rem;
- height: 0.4rem;
- border-radius: 50%;
- background: rgba(255,255,255,0.65);
- animation: call-dot 1.2s infinite ease-in-out;
-}
-
-.call-ringing-dots i:nth-child(2) {
- animation-delay: 0.15s;
-}
-
-.call-ringing-dots i:nth-child(3) {
- animation-delay: 0.3s;
-}
-
-@keyframes call-dot {
- 0%, 70%, 100% { transform: translateY(0); opacity: 0.45; }
- 35% { transform: translateY(-0.35rem); opacity: 1; }
-}
-
-.call-incoming-actions,
-.call-active-controls,
-.call-single-action,
-.call-video-controls {
- display: flex;
- z-index: 4;
- align-items: flex-start;
- justify-content: space-evenly;
- flex: none;
- min-height: 8rem;
- padding: 1rem max(1rem, calc((100% - 34rem) / 2)) max(1.5rem, env(safe-area-inset-bottom));
-}
-
-.call-single-action {
- justify-content: center;
-}
-
-.call-control {
- display: flex;
- align-items: center;
- width: 5rem;
- min-width: 5rem;
- height: auto;
- min-height: 0;
- margin: 0;
- padding: 0;
- border: 0;
- box-shadow: none;
- color: rgba(255,255,255,0.78);
- background: transparent;
- flex-direction: column;
- text-transform: none;
-}
-
-.call-control > span {
- display: grid;
- place-items: center;
- width: 3.7rem;
- height: 3.7rem;
- border-radius: 50%;
- color: #fff;
- background: rgba(255,255,255,0.16);
- backdrop-filter: blur(8px);
-}
-
-.call-control.active > span {
- color: #22272d;
- background: #fff;
-}
-
-.call-control.danger > span {
- color: #fff;
- background: #e3464f;
-}
-
-.call-control .material-icons {
- font-size: 1.55rem;
-}
-
-.call-control small {
- width: 100%;
- margin-top: 0.5rem;
- color: inherit;
- font-size: 0.7rem;
- line-height: 1.2;
- text-align: center;
-}
-
-.call-incoming-actions .call-control:last-child > span {
- background: #2ab56f;
-}
-
-.call-video-surface {
- background: #11151a;
-}
-
-.call-video-backdrop,
-.call-video-off {
- position: absolute;
- z-index: -2;
- inset: 0;
- width: 100%;
- height: 100%;
-}
-
-.call-video-backdrop {
- object-fit: cover;
-}
-
-.call-video-off {
- display: flex;
- align-items: center;
- justify-content: center;
- background: #252f37;
- flex-direction: column;
-}
-
-.call-video-off .call-product-avatar {
- width: 7rem;
- height: 7rem;
-}
-
-.call-video-off strong {
- margin-top: 1.2rem;
-}
-
-.call-video-off small {
- margin-top: 0.35rem;
- color: rgba(255,255,255,0.68);
-}
-
-.call-video-off.local {
- background: #20272e;
-}
-
-.call-video-shade {
- position: absolute;
- z-index: -1;
- inset: 0;
- background: linear-gradient(180deg, rgba(0,0,0,0.42), transparent 30%, transparent 62%, rgba(0,0,0,0.58));
-}
-
-.call-video-status {
- z-index: 2;
- text-align: center;
-}
-
-.call-video-status h1 {
- margin: 0.4rem 0 0.25rem;
- color: #fff;
- font-size: 1.25rem;
-}
-
-.call-video-status p {
- margin: 0;
- color: rgba(255,255,255,0.8);
-}
-
-.call-video-controls {
- margin-top: auto;
-}
-
-.call-local-preview {
- position: absolute;
- z-index: 3;
- top: max(6.2rem, calc(env(safe-area-inset-top) + 5rem));
- right: 1rem;
- width: 7rem;
- min-width: 7rem;
- height: auto;
- min-height: 0;
- aspect-ratio: 3 / 4;
- margin: 0;
- padding: 0;
- overflow: hidden;
- border: 1px solid rgba(255,255,255,0.35);
- border-radius: 0.4rem;
- box-shadow: 0 0.5rem 1.2rem rgba(0,0,0,0.3);
- background: #252c33;
-}
-
-.call-local-preview img {
- width: 100%;
- height: 100%;
- object-fit: cover;
-}
-
-.call-local-preview .material-icons {
- position: absolute;
- top: 50%;
- left: 50%;
- color: #fff;
- transform: translate(-50%, -50%);
-}
-
-.call-toast {
- position: fixed;
- z-index: 50;
- bottom: max(1.5rem, env(safe-area-inset-bottom));
- left: 50%;
- max-width: calc(100% - 2rem);
- padding: 0.7rem 1rem;
- border-radius: 0.3rem;
- color: #fff;
- background: rgba(24,28,34,0.94);
- text-align: center;
- transform: translateX(-50%);
-}
-
-.call-chat-preview-header > div {
- display: flex;
- align-items: center;
- justify-content: center;
- flex-direction: column;
-}
-
-.call-chat-preview-header small {
- margin-top: 0.15rem;
- color: #37a66d;
-}
-
-.call-minimized-page > main {
- min-height: 0;
- padding: 1.5rem max(1rem, calc((100% - 52rem) / 2));
- overflow-y: auto;
- flex: 1 1 auto;
-}
-
-.call-chat-preview-message {
- width: fit-content;
- max-width: 70%;
- padding: 0.7rem 0.9rem;
- border-radius: 0.4rem;
- background: #fff;
- line-height: 1.5;
-}
-
-.call-chat-composer {
- display: grid;
- grid-template-columns: minmax(0, 1fr) 3rem;
- align-items: center;
- flex: none;
- min-height: 4rem;
- padding: 0.5rem max(0.7rem, calc((100% - 52rem) / 2));
- border-top: 1px solid #e2e5e9;
- background: #fff;
-}
-
-.call-chat-composer input {
- height: 2.7rem;
- margin: 0;
- padding: 0 0.7rem;
- border: 1px solid #e1e4e8;
- border-radius: 0.35rem;
- box-shadow: none;
- background: #f7f8f9;
-}
-
-.call-chat-composer button {
- width: 3rem;
- min-width: 3rem;
- height: 2.7rem;
- min-height: 2.7rem;
- margin: 0;
- padding: 0;
- border: 0;
- box-shadow: none;
- background: transparent;
-}
-
-.call-floating-window {
- position: fixed;
- z-index: 20;
- top: 0;
- left: 0;
- width: 7.4rem;
- min-width: 7.4rem;
- height: auto;
- min-height: 0;
- aspect-ratio: 3 / 4;
- margin: 0;
- padding: 0;
- overflow: hidden;
- touch-action: none;
- border: 1px solid rgba(255,255,255,0.6);
- border-radius: 0.45rem;
- box-shadow: 0 0.5rem 1.5rem rgba(0,0,0,0.28);
- background: #20262c;
-}
-
-.call-floating-window img {
- width: 100%;
- height: 100%;
- pointer-events: none;
- object-fit: cover;
-}
-
-.call-floating-window span {
- position: absolute;
- right: 0.4rem;
- bottom: 0.35rem;
- display: flex;
- align-items: center;
- color: #fff;
- font-size: 0.68rem;
- text-shadow: 0 1px 2px rgba(0,0,0,0.8);
-}
-
-.call-floating-window span i {
- width: 0.4rem;
- height: 0.4rem;
- margin-right: 0.25rem;
- border-radius: 50%;
- background: #41d48d;
-}
-
-.call-mini-hangup {
- position: fixed;
- z-index: 21;
- right: 1rem;
- bottom: 5.2rem;
- display: grid;
- place-items: center;
- width: 3rem;
- min-width: 3rem;
- height: 3rem;
- min-height: 3rem;
- margin: 0;
- padding: 0;
- border: 0;
- border-radius: 50%;
- box-shadow: 0 0.4rem 1rem rgba(0,0,0,0.2);
- color: #fff;
- background: #e3464f;
-}
-
-.compose-target-footer {
- justify-content: space-between;
- margin-top: auto;
- color: #757d87;
- font-size: 0.82rem;
-}
-
-.compose-forward-confirm {
- width: min(100%, 40rem);
- margin: 0 auto;
- padding: 1.2rem;
-}
-
-.compose-forward-confirm > section,
-.compose-forward-confirm textarea {
- width: 100%;
- margin-bottom: 1rem;
- padding: 1rem;
- border: 0;
- border-radius: 0.45rem;
- background: #fff;
-}
-
-.compose-target-summary {
- display: flex;
- gap: 1rem;
- overflow-x: auto;
-}
-
-.compose-target-summary > span {
- display: flex;
- align-items: center;
- width: 4rem;
- flex: 0 0 4rem;
- flex-direction: column;
-}
-
-.compose-target-summary small {
- width: 100%;
- margin-top: 0.35rem;
- overflow: hidden;
- text-align: center;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.compose-forward-preview h2 {
- margin-top: 0;
-}
-
-.compose-forward-preview p {
- margin: 0.45rem 0;
- overflow: hidden;
- color: #757c85;
- font-size: 0.82rem;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.compose-forward-confirm textarea {
- min-height: 5rem;
- resize: vertical;
-}
-
-.compose-forward-mode {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 0.7rem;
- margin-bottom: 1rem;
-}
-
-.compose-forward-mode button {
- min-height: 2.8rem;
- margin: 0;
- border: 1px solid #dce0e5;
- border-radius: 0.35rem;
- box-shadow: none;
- color: #646c76;
- background: #fff;
- text-transform: none;
-}
-
-.compose-forward-mode button.active {
- border-color: #398afa;
- color: #287fe5;
- background: #eef6ff;
-}
-
-.compose-confirm-send {
- width: 100%;
- min-height: 3rem;
- margin: 0;
-}
-
-@keyframes fadein {
- from {
- bottom: 0;
- opacity: 0;
- }
- to {
- bottom: 2rem;
- opacity: 1;
- }
-}
-
-@keyframes fadeout {
- from {
- bottom: 2rem;
- opacity: 1;
- }
- to {
- bottom: 0;
- opacity: 0;
- }
-}
-
-/*
- Very wide monitor.
- */
-@media (min-width: 1440px){
- #app-container {
- max-width: 114rem;
- }
-}
-
-/*
- Mobile styles.
- */
-
-@media (max-width: 960px) {
- #mountPoint {
- padding: 0;
- }
-
- .pinned {
- width: 16rem;
- }
-}
-
-@media (max-width: 640px) {
- #app-container {
- width: 100%;
- border-radius: 0;
- box-shadow: none;
- }
-
- #sidepanel {
- width: 100%;
- }
-
- #sidepanel.message-center-panel,
- #sidepanel.message-center-panel.full-page {
- width: 100%;
- min-width: 0;
- flex-basis: 100%;
- }
-
- #sidepanel.direct-message-panel {
- width: 100%;
- min-width: 0;
- }
-
- #sidepanel.message-composer-panel {
- width: 100%;
- min-width: 0;
- }
-
- #sidepanel.contact-management-panel,
- #sidepanel.group-management-panel {
- width: 100%;
- min-width: 0;
- }
-
- #sidepanel.content-library-panel {
- width: 100%;
- min-width: 0;
- }
-
- #sidepanel.call-experience-panel {
- width: 100%;
- min-width: 0;
- }
-
- .call-active-controls,
- .call-video-controls {
- padding-right: 0.35rem;
- padding-left: 0.35rem;
- }
-
- .call-control {
- width: 4.5rem;
- min-width: 4.5rem;
- }
-
- .call-control > span {
- width: 3.4rem;
- height: 3.4rem;
- }
-
- .library-media-grid {
- grid-template-columns: repeat(3, 1fr);
- }
-
- .library-link-card {
- grid-template-columns: 4.3rem minmax(0, 1fr);
- }
-
- .library-link-card > img {
- width: 4.3rem;
- height: 4.3rem;
- }
-
- .people-profile-actions {
- grid-template-columns: 1fr;
- }
-
- .compose-emoji-panel > div {
- grid-template-columns: repeat(7, 1fr);
- }
-
- .compose-picker-header {
- grid-template-columns: 4rem minmax(0, 1fr) 4.5rem;
- }
-
- .compose-upload-page > main,
- .compose-forward-confirm {
- padding: 1rem;
- }
-
- .chat-product-timeline {
- padding: 1.25rem 1rem;
- }
-
- .chat-message-stack {
- max-width: calc(100% - 4rem);
- }
-
- .chat-bubble {
- font-size: 0.94rem;
- }
-
- .chat-file-card {
- min-width: min(17rem, calc(100vw - 6rem));
- }
-
- .chat-contact-card {
- min-width: min(16rem, calc(100vw - 6rem));
- }
-
- .chat-composer {
- padding-right: 0.4rem;
- padding-left: 0.4rem;
- }
-
- .chat-attachment-panel {
- grid-template-columns: repeat(4, 1fr);
- gap: 0.7rem;
- padding: 1rem 0.75rem max(1rem, env(safe-area-inset-bottom));
- }
-
- .chat-attachment-panel button > span {
- width: 3.3rem;
- height: 3.3rem;
- }
-
- .messages-conversation {
- grid-template-columns: 3.55rem minmax(0, 1fr) auto;
- min-height: 4.8rem;
- padding-right: 1rem;
- padding-left: 1rem;
- }
-
- .messages-frequent-scroll {
- gap: 1rem;
- padding-right: 1rem;
- padding-left: 1rem;
- }
-
- .messages-frequent h2 {
- margin-left: 1rem;
- }
-
- .messages-search {
- margin-right: 1rem;
- margin-left: 1rem;
- }
-
- .system-notification-list {
- padding: 1rem;
- }
-
- .auth-landing {
- padding-right: 1.25rem;
- padding-left: 1.25rem;
- }
-
- .auth-brand {
- margin-bottom: 3rem;
- }
-
- .auth-content {
- padding: 1.25rem;
- }
-
- .auth-reset-content {
- padding: 1rem 0;
- }
-
- .auth-list-form {
- min-height: calc(100vh - 6.25rem);
- }
-
- .auth-list-form label {
- grid-template-columns: 6.4rem 1fr auto;
- }
-
- .auth-code-button {
- min-width: 5.8rem;
- padding: 0 0.4rem;
- }
-
- .auth-realname-hero {
- min-height: 12rem;
- padding: 1.5rem;
- }
-
- .auth-realname-hero strong {
- font-size: 2rem;
- }
-
- .auth-realname-hero > .material-icons {
- font-size: 6rem;
- }
-
- .auth-realname-content form {
- padding: 1.25rem;
- }
-
- .auth-realname-content label {
- grid-template-columns: 6.2rem 1fr;
- }
-
- .auth-screenshot-grid {
- grid-template-columns: repeat(3, 1fr);
- }
-
- #info-view {
- width: 100%;
- }
-
- /* Bottom sheet rather than a dialog */
- .forward-dialog {
- border-radius: 0.3rem 0.3rem 0 0;
- margin: auto 0 0 0;
- width: 100%;
- }
-
- /* Must make it a bit visible on mobile because there is no :hover */
- .menuTrigger {
- opacity: 0.3;
- }
-
- #send-message-input {
- /* Just an indicator that the screen is in mobile mode */
- transition-property: -tinode-mobile;
- }
-
- #pinned-wrapper {
- height: 3.5rem;
- margin: 0.25rem;
- }
-
- .pinned {
- width: unset;
- }
-
- /* :not(.audio-minimized) -- same specificity (1,1,0) as #video-container.audio-minimized (4149
- above), and this rule comes later in the file so it was winning even for a voice call's pill,
- stretching it into the same full-width box meant for the video floating window. */
- #video-container.minimized:not(.audio-minimized) {
- flex-direction: row;
- left: 0;
- right: auto;
- top: var(--caption-panel-height);
- width: 100%;
- height: 11rem;
- border-radius: unset;
- box-shadow: 0 0 0.5rem var(--clr-shadow-gray-strong);
- }
-
- #controls.minimized {
- flex-direction: column;
- row-gap: 0.5rem;
- bottom: auto;
- column-gap: unset;
- right: 1rem;
- }
-
- .minimized .call-party {
- border-radius: unset;
- box-shadow: unset;
- }
-}
-
-/* .call-party.self's fullscreen self-preview (bottom-right corner) overlaps #controls' centered
- button row somewhere around 641px-750px: narrower than this and the row above already switches to
- the cramped mobile layout (with its own bottom:8.5rem clearance built in below); wider and #controls
- has enough room to stay clear of the preview on its own. 768px is comfortably past where the overlap
- was verified to stop, and is a common breakpoint already used elsewhere in this file. Kept as its own
- query (rather than folded into the 640px block above) since the rest of that block also restructures
- #video-container/#controls into an entirely different mobile layout that shouldn't kick in this early. */
-@media (max-width: 768px) {
- .call-party.self {
- bottom: 8.5rem;
- border-radius: unset;
- }
-}
-
-/* Android personal profile, settings, privacy, youth mode and permissions. */
-#sidepanel.personal-settings-panel {
- width: 100%;
- min-width: 0;
- flex: 1 1 auto;
- color-scheme: light;
- background: #f5f6f8;
-}
-
-.personal-settings-panel * {
- box-sizing: border-box;
- letter-spacing: 0;
-}
-
-.personal-page {
- display: flex;
- position: relative;
- width: 100%;
- min-height: 100%;
- overflow: hidden;
- color: #262b31;
- background: #f5f6f8;
- flex-direction: column;
-}
-
-.personal-page > .personal-page {
- min-height: 0;
- flex: 1 1 auto;
-}
-
-.personal-page button,
-.personal-page input,
-.personal-page textarea {
- font: inherit;
- letter-spacing: 0;
-}
-
-.personal-page button {
- text-transform: none;
-}
-
-.personal-page > main {
- min-height: 0;
- overflow-y: auto;
- flex: 1 1 auto;
-}
-
-.personal-header {
- display: grid;
- grid-template-columns: 3.7rem minmax(0, 1fr) 3.7rem;
- align-items: center;
- min-height: 4.5rem;
- padding-top: env(safe-area-inset-top);
- border-bottom: 1px solid #e7e9ec;
- background: #fff;
- flex: none;
-}
-
-.personal-header h1 {
- min-width: 0;
- margin: 0;
- overflow: hidden;
- font-size: 1.15rem;
- font-weight: 650;
- text-align: center;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.personal-header button {
- display: grid;
- place-items: center;
- width: 3.7rem;
- min-width: 3.7rem;
- height: 3.7rem;
- min-height: 3.7rem;
- margin: 0;
- padding: 0;
- border: 0;
- box-shadow: none;
- color: #252a30;
- background: transparent;
-}
-
-.personal-header button.text {
- color: #2873d0;
- font-size: 0.9rem;
-}
-
-.personal-header .material-icons {
- font-size: 1.55rem;
-}
-
-.personal-toast {
- position: fixed;
- z-index: 90;
- bottom: max(1.5rem, env(safe-area-inset-bottom));
- left: 50%;
- max-width: calc(100% - 2rem);
- padding: 0.7rem 1rem;
- border-radius: 0.3rem;
- color: #fff;
- background: rgba(25,29,34,0.94);
- text-align: center;
- transform: translateX(-50%);
-}
-
-.personal-center-page > main {
- padding: 0.85rem 0;
-}
-
-.personal-center-page section,
-.personal-menu-section,
-.android-settings-list section,
-.privacy-list section {
- margin-bottom: 0.75rem;
- background: #fff;
-}
-
-.personal-identity {
- display: grid;
- grid-template-columns: 4.5rem minmax(0, 1fr) 1.5rem;
- align-items: center;
- gap: 0.8rem;
- width: calc(100% - 1.6rem);
- min-height: 7rem;
- margin: 0 0.8rem 0.8rem;
- padding: 0.9rem;
- border: 0;
- border-radius: 0.45rem;
- box-shadow: none;
- color: #2e3339;
- background: #fff;
- text-align: left;
-}
-
-.personal-identity img {
- width: 4.5rem;
- height: 4.5rem;
- border-radius: 0.55rem;
- object-fit: cover;
-}
-
-/* No-avatar fallback: docs/images/我的.png only shows the real-photo state, so this is sized/shaped
- to match `.personal-identity img` above rather than copied from a reference. Specificity has to
- beat `.personal-identity span` below (class+element vs. this rule's two classes) or that rule's
- `display: block` wins and silently drops the centering, leaving a tiny uncentered icon glyph --
- same bug, and same fix, as .workbench-avatar-fallback. */
-.personal-identity .personal-avatar-fallback {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 4.5rem;
- height: 4.5rem;
- border-radius: 0.55rem;
- color: #fff;
- background: #b0b6bd;
-}
-
-.personal-identity .personal-avatar-fallback .material-icons {
- font-size: 2rem;
-}
-
-.personal-identity span,
-.personal-identity strong,
-.personal-identity small {
- display: block;
- min-width: 0;
-}
-
-/* Same class of bug as .library-file-card above: .personal-identity is a (头像+昵称+企业名
- 身份卡片, "我的" 页首屏第一眼看到的内容), and the global `button{line-height:2.2rem}` reset
- inherits into these three stacked lines, inflating the gaps between nickname/企业名/ID. */
-.personal-identity strong {
- margin-bottom: 0.35rem;
- overflow: hidden;
- font-size: 1.25rem;
- line-height: 1.3;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.personal-identity small {
- margin-top: 0.15rem;
- color: #8a919a;
- line-height: 1.3;
-}
-
-.personal-menu-row {
- display: grid;
- grid-template-columns: minmax(0, 1fr) auto;
- align-items: center;
- gap: 0.65rem;
- width: 100%;
- min-height: 4rem;
- margin: 0;
- padding: 0.65rem 1rem;
- border: 0;
- border-bottom: 1px solid #eceef1;
- border-radius: 0;
- box-shadow: none;
- color: #30353b;
- background: #fff;
- text-align: left;
-}
-
-.personal-menu-row:has(> .material-icons:first-child) {
- grid-template-columns: 2.3rem minmax(0, 1fr) auto;
-}
-
-.personal-menu-row:last-child {
- border-bottom: 0;
-}
-
-/* The generic button:hover/button:active rule (base.css ~2027) forces color to --clr-bright (white)
- on every , assuming a dark hover background -- true for .primary/.danger/.positive (already
- white text) but not for a plain white row like this one, where it silently turns the label invisible
- (white text on the still-white/light-gray hover background). Restate the row's own text color on
- hover/active, and add a visible (if subtle) background shift so hovering isn't a no-op. */
-.personal-menu-row:hover:not([disabled]),
-.personal-menu-row:active:not([disabled]) {
- color: #30353b;
- background: var(--clr-menu-item-hover-bg);
-}
-
-/* Overrides the generic button.danger (solid red background, meant for standalone confirm/cancel
- buttons) that this row would otherwise inherit just for reusing the "danger" class as a semantic
- marker -- the design calls for a plain row (same background as every other row) with only the
- icon and label tinted red, not an alarming solid-red block. */
-.personal-menu-row.danger,
-.personal-menu-row.danger:hover:not([disabled]),
-.personal-menu-row.danger:active:not([disabled]) {
- color: #d93f48;
- background: #fff;
-}
-.personal-menu-row.danger:hover:not([disabled]),
-.personal-menu-row.danger:active:not([disabled]) {
- background: var(--clr-menu-item-hover-bg);
-}
-
-.personal-menu-row.danger > .material-icons:first-child {
- color: #d93f48;
-}
-
-.personal-menu-row > .material-icons:first-child {
- color: #59636e;
- font-size: 1.55rem;
-}
-
-.personal-menu-row > .material-icons:last-child {
- color: #a8aeb5;
-}
-
-.personal-menu-row span {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 0.7rem;
- min-width: 0;
-}
-
-.personal-menu-row strong {
- min-width: 0;
- font-weight: 500;
-}
-
-.personal-menu-row small {
- max-width: 62%;
- overflow: hidden;
- color: #969da5;
- text-align: right;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.personal-profile-list {
- padding-top: 0.7rem;
- background: #fff;
-}
-
-.personal-profile-list > button:first-child {
- display: grid;
- grid-template-columns: minmax(0, 1fr) 3.8rem 1.5rem;
- align-items: center;
- width: 100%;
- min-height: 5rem;
- margin: 0;
- padding: 0.6rem 1rem;
- border: 0;
- border-bottom: 1px solid #eceef1;
- border-radius: 0;
- box-shadow: none;
- color: #30353b;
- background: #fff;
- text-align: left;
-}
-
-.personal-profile-list > button:first-child img {
- width: 3.5rem;
- height: 3.5rem;
- border-radius: 0.45rem;
- object-fit: cover;
-}
-
-.gender-option-list {
- padding-top: 0.7rem;
- background: #fff;
-}
-
-.gender-option-list button {
- display: grid;
- grid-template-columns: minmax(0, 1fr) 1.5rem;
- align-items: center;
- width: 100%;
- min-height: 3.6rem;
- margin: 0;
- padding: 0.6rem 1rem;
- border: 0;
- border-bottom: 1px solid #eceef1;
- border-radius: 0;
- box-shadow: none;
- color: #30353b;
- background: #fff;
- text-align: left;
-}
-
-.gender-option-list button strong {
- font-weight: 400;
-}
-
-.gender-option-list button .material-icons {
- color: var(--clr-primary, #3b82f6);
-}
-
-.profile-editor-page > main {
- padding: 1rem;
-}
-
-.profile-editor-page > main > label {
- display: block;
- position: relative;
-}
-
-.profile-editor-page label input,
-.profile-editor-page label textarea {
- width: 100%;
- margin: 0;
- padding: 0.75rem 3rem 0.75rem 0.75rem;
- border: 1px solid #dbe0e5;
- border-radius: 0.35rem;
- box-shadow: none;
- background: #fff;
-}
-
-.profile-editor-page label input:not([type='date']) {
- height: 3.2rem;
-}
-
-.profile-editor-page label textarea {
- height: 11rem;
- padding-bottom: 2rem;
- resize: vertical;
- line-height: 1.5;
-}
-
-.profile-editor-page > main > label > small {
- position: absolute;
- right: 0.75rem;
- bottom: 0.65rem;
- color: #9ba2aa;
-}
-
-.profile-editor-page > main > label > button {
- position: absolute;
- top: 0.1rem;
- right: 2.7rem;
- display: grid;
- place-items: center;
- width: 2.8rem;
- min-width: 2.8rem;
- height: 3rem;
- min-height: 3rem;
- margin: 0;
- padding: 0;
- border: 0;
- box-shadow: none;
- color: #a3a9b0;
- background: transparent;
-}
-
-.profile-editor-page > main > p {
- margin: 0.65rem 0 0;
- color: #8a929b;
- font-size: 0.8rem;
-}
-
-.birth-date-editor {
- display: flex;
- align-items: center;
- padding-top: 4rem !important;
- flex-direction: column;
-}
-
-.birth-date-editor > .material-icons {
- color: #d66372;
- font-size: 4rem;
-}
-
-.birth-date-editor label {
- width: min(100%, 26rem);
- margin-top: 1.5rem;
-}
-
-.birth-date-editor label span {
- display: block;
- margin-bottom: 0.5rem;
- font-weight: 600;
-}
-
-.birth-date-editor label input {
- height: 3.2rem;
- padding: 0 0.75rem;
-}
-
-.change-password-fields label {
- display: block;
- width: min(100%, 26rem);
- margin-top: 1.5rem;
-}
-
-.change-password-fields label span {
- display: block;
- margin-bottom: 0.5rem;
- font-weight: 600;
-}
-
-.change-password-fields label input {
- height: 3.2rem;
- padding: 0 0.75rem;
-}
-
-.personal-qr-page > main {
- padding: 1rem;
-}
-
-.personal-qr-page main > section {
- width: min(100%, 27rem);
- margin: 0 auto;
- padding: 1.2rem;
- border-radius: 0.45rem;
- background: #fff;
- text-align: center;
-}
-
-.personal-qr-profile {
- display: flex;
- align-items: center;
- gap: 0.7rem;
- text-align: left;
-}
-
-.personal-qr-profile img {
- width: 3.3rem;
- height: 3.3rem;
- border-radius: 0.4rem;
- object-fit: cover;
-}
-
-.personal-qr-profile span,
-.personal-qr-profile strong,
-.personal-qr-profile small {
- display: block;
-}
-
-.personal-qr-profile small {
- margin-top: 0.2rem;
- color: #858d96;
-}
-
-.personal-qrcode {
- display: inline-block;
- width: 226px;
- height: 226px;
- margin: 1.5rem auto 0.8rem;
- padding: 8px;
- border: 1px solid #eceef1;
- background: #fff;
-}
-
-.personal-qrcode img,
-.personal-qrcode canvas {
- width: 210px !important;
- height: 210px !important;
-}
-
-.personal-qr-page section > p {
- margin: 0.5rem 0;
-}
-
-.personal-qr-page section > small {
- color: #8d949d;
-}
-
-.personal-qr-actions {
- display: grid;
- grid-template-columns: repeat(2, 1fr);
- width: min(100%, 27rem);
- margin: 0.8rem auto 0;
- background: #fff;
-}
-
-.personal-qr-actions button {
- display: flex;
- align-items: center;
- justify-content: center;
- min-height: 3.4rem;
- margin: 0;
- padding: 0;
- border: 0;
- box-shadow: none;
- color: #3476cd;
- background: transparent;
-}
-
-.personal-qr-actions button + button {
- border-left: 1px solid #eceef1;
-}
-
-.personal-qr-actions .material-icons {
- margin-right: 0.3rem;
-}
-
-.android-settings-list {
- padding: 0.75rem 0 1.5rem;
-}
-
-.personal-logout {
- width: 100%;
- min-height: 3.8rem;
- margin: 0;
- padding: 0;
- border: 0;
- box-shadow: none;
- color: #d93f48;
- background: #fff;
-}
-
-.personal-sheet-backdrop,
-.update-backdrop,
-.permission-overlay {
- display: grid;
- position: absolute;
- z-index: 70;
- inset: 0;
- place-items: center;
- padding: 1rem;
- background: rgba(22,26,31,0.52);
-}
-
-.personal-confirm-sheet {
- width: min(100%, 25rem);
- padding: 1.25rem;
- border-radius: 0.5rem;
- background: #fff;
- text-align: center;
-}
-
-.personal-confirm-sheet > .material-icons {
- color: #4d73aa;
- font-size: 2.8rem;
-}
-
-.personal-confirm-sheet h2 {
- margin: 0.65rem 0;
- font-size: 1.15rem;
-}
-
-.personal-confirm-sheet p {
- margin: 0.7rem 0 1rem;
- color: #5c646e;
- line-height: 1.6;
- text-align: left;
-}
-
-.personal-confirm-sheet input {
- width: 100%;
- height: 3rem;
- margin: 0 0 0.8rem;
- padding: 0 0.75rem;
- border: 1px solid #dce0e5;
- border-radius: 0.35rem;
- box-shadow: none;
- text-align: center;
-}
-
-.personal-confirm-sheet button {
- width: 100%;
- min-height: 2.9rem;
- margin: 0.45rem 0 0;
- padding: 0 1rem;
- border: 0;
- border-radius: 0.35rem;
- box-shadow: none;
- color: #4d5660;
- background: #f0f2f4;
-}
-
-.personal-confirm-sheet button.primary {
- color: #fff;
- background: #347bd7;
-}
-
-.personal-confirm-sheet button.danger {
- color: #fff;
- background: #d84950;
-}
-
-.app-version-page > main {
- padding: 2rem 1rem;
-}
-
-.app-brand {
- margin-bottom: 2rem;
- text-align: center;
-}
-
-.app-brand > span {
- display: grid;
- place-items: center;
- width: 6.5rem;
- height: 6.5rem;
- margin: 0 auto 0.8rem;
- border-radius: 1.2rem;
- color: #fff;
- background: #2878d6;
-}
-
-.app-brand .material-icons {
- font-size: 4.2rem;
-}
-
-.app-brand h2,
-.app-brand p {
- margin: 0;
-}
-
-.app-brand h2 {
- font-size: 1.25rem;
-}
-
-.app-brand p {
- margin-top: 0.25rem;
- color: #777f89;
-}
-
-.update-dialog {
- width: min(100%, 25rem);
- padding: 1.3rem;
- border-radius: 0.55rem;
- background: #fff;
- text-align: center;
-}
-
-.update-dialog > span {
- display: grid;
- place-items: center;
- width: 5rem;
- height: 5rem;
- margin: 0 auto 0.8rem;
- border-radius: 50%;
- color: #fff;
- background: #4e74b7;
-}
-
-.update-dialog > span .material-icons {
- font-size: 3.2rem;
-}
-
-.update-dialog h2 {
- margin: 0;
- font-size: 1.35rem;
-}
-
-.update-dialog > strong {
- display: block;
- margin-top: 0.25rem;
- color: #69717b;
-}
-
-.update-dialog ul {
- margin: 1rem 0;
- padding-left: 1.25rem;
- color: #4e5660;
- line-height: 1.65;
- text-align: left;
-}
-
-.update-dialog button {
- width: 100%;
- min-height: 3rem;
- margin: 0.45rem 0 0;
- padding: 0 1rem;
- border: 0;
- border-radius: 0.35rem;
- box-shadow: none;
- color: #777f88;
- background: transparent;
-}
-
-.update-dialog button:first-of-type {
- color: #fff;
- background: #347bd7;
-}
-
-.privacy-list {
- padding-top: 0.75rem;
-}
-
-.privacy-list section > div {
- display: grid;
- grid-template-columns: minmax(0, 1fr) auto;
- align-items: center;
- gap: 0.8rem;
- min-height: 4.6rem;
- padding: 0.7rem 1rem;
- border-bottom: 1px solid #eceef1;
-}
-
-.privacy-list section > div:last-child {
- border-bottom: 0;
-}
-
-.privacy-list section > div span,
-.privacy-list section > div strong,
-.privacy-list section > div small {
- display: block;
- min-width: 0;
-}
-
-.privacy-list section > div small {
- margin-top: 0.25rem;
- color: #8b929b;
- line-height: 1.35;
-}
-
-.personal-switch {
- position: relative;
- width: 3.25rem;
- min-width: 3.25rem;
- height: 1.9rem;
- min-height: 1.9rem;
- margin: 0;
- padding: 0;
- border: 0;
- border-radius: 1rem;
- box-shadow: none;
- background: #d8dce1;
-}
-
-/* 企业强制策略锁死的项(后端 privacy.editable 里为 false)要看得出是不可改的,
- 否则用户只会觉得开关点不动。 */
-.personal-switch:disabled {
- opacity: 0.45;
- cursor: not-allowed;
-}
-
-.personal-switch i {
- position: absolute;
- top: 0.15rem;
- left: 0.15rem;
- width: 1.6rem;
- height: 1.6rem;
- border-radius: 50%;
- background: #fff;
- box-shadow: 0 1px 4px rgba(0,0,0,0.2);
- transition: transform 160ms ease;
-}
-
-.personal-switch.checked {
- background: #347bd7;
-}
-
-.personal-switch.checked i {
- transform: translateX(1.35rem);
-}
-
-.privacy-note {
- margin: 0;
- padding: 0 1rem 0.75rem;
- color: #7e8690;
- font-size: 0.82rem;
- line-height: 1.5;
-}
-
-.youth-mode-page {
- background: #fff;
-}
-
-.youth-mode-page > main {
- padding: 3.2rem 1.5rem 1rem;
- text-align: center;
-}
-
-.youth-mode-page > main > .material-icons {
- color: #347bd7;
- font-size: 5rem;
-}
-
-.youth-mode-page h2 {
- margin: 1rem 0 1.5rem;
- font-size: 1.45rem;
-}
-
-.youth-mode-page ul {
- margin: 0;
- padding: 0;
- list-style: none;
- text-align: left;
-}
-
-.youth-mode-page li {
- position: relative;
- margin-bottom: 1.2rem;
- padding-left: 1.7rem;
- line-height: 1.55;
-}
-
-.youth-mode-page li::before {
- position: absolute;
- top: 0.55rem;
- left: 0.2rem;
- width: 0.55rem;
- height: 0.55rem;
- border-radius: 50%;
- background: #347bd7;
- content: '';
-}
-
-.youth-usage {
- margin-top: 1.8rem;
- color: #868e97;
-}
-
-.youth-mode-page > footer {
- padding: 0.8rem 1rem max(1rem, env(safe-area-inset-bottom));
- background: #fff;
- text-align: center;
- flex: none;
-}
-
-.youth-mode-page > footer button {
- width: 100%;
- min-height: 3.2rem;
- margin: 0;
- padding: 0 1rem;
- border: 0;
- border-radius: 0.35rem;
- box-shadow: none;
- color: #fff;
- background: #347bd7;
-}
-
-.youth-mode-page > footer button.outline {
- border: 1px solid #347bd7;
- color: #347bd7;
- background: #fff;
-}
-
-.youth-mode-page > footer small {
- display: block;
- margin-top: 0.65rem;
- color: #9aa1a9;
-}
-
-.permission-guide-page {
- background: #fff;
-}
-
-.permission-preview {
- padding: 1.2rem 1rem;
- opacity: 0.72;
-}
-
-.permission-preview > header {
- display: flex;
- align-items: center;
- justify-content: space-between;
-}
-
-.permission-preview h1 {
- margin: 0;
- font-size: 1.35rem;
-}
-
-.permission-preview > label {
- display: flex;
- align-items: center;
- justify-content: center;
- min-height: 3rem;
- margin: 1rem 0;
- border-radius: 0.35rem;
- color: #8d949c;
- background: #f0f2f4;
-}
-
-.permission-preview > label .material-icons {
- margin-right: 0.35rem;
-}
-
-.permission-preview > div {
- display: grid;
- grid-template-columns: 3.4rem minmax(0, 1fr);
- align-items: center;
- gap: 0.7rem;
- min-height: 4.6rem;
- border-bottom: 1px solid #eceef1;
-}
-
-.permission-preview > div > span:last-child,
-.permission-preview > div strong,
-.permission-preview > div small {
- display: block;
- min-width: 0;
-}
-
-.permission-preview > div small {
- margin-top: 0.2rem;
- color: #8b929b;
-}
-
-.permission-avatar {
- display: grid;
- place-items: center;
- width: 3.1rem;
- height: 3.1rem;
- border-radius: 0.45rem;
- color: #fff;
- background: #5a77a9;
-}
-
-.permission-avatar.tone-0 { background: #d18128; }
-.permission-avatar.tone-1 { background: #4673bd; }
-.permission-avatar.tone-2 { background: #398258; }
-.permission-avatar.tone-3 { background: #9c5f7a; }
-
-.permission-overlay section {
- width: min(100%, 24rem);
- padding: 1.3rem;
- border-radius: 0.5rem;
- background: #fff;
- text-align: center;
-}
-
-.permission-overlay section > span {
- display: grid;
- place-items: center;
- width: 4.6rem;
- height: 4.6rem;
- margin: 0 auto 0.8rem;
- border-radius: 50%;
- color: #fff;
- background: #347bd7;
-}
-
-.permission-overlay section > span .material-icons {
- font-size: 2.7rem;
-}
-
-.permission-overlay h2 {
- margin: 0;
- font-size: 1.2rem;
-}
-
-.permission-overlay p {
- margin: 0.8rem 0 1rem;
- color: #555d67;
- line-height: 1.6;
- text-align: left;
-}
-
-.permission-overlay button {
- width: 100%;
- min-height: 3rem;
- margin: 0.4rem 0 0;
- padding: 0 1rem;
- border: 0;
- border-radius: 0.35rem;
- box-shadow: none;
- color: #8a919a;
- background: transparent;
-}
-
-.permission-overlay button:first-of-type {
- color: #fff;
- background: #347bd7;
-}
-
-@media screen and (max-width: 480px) {
- .personal-menu-row {
- min-height: 3.8rem;
- }
-
- .personal-menu-row small {
- max-width: 55%;
- }
-
- .personal-qrcode {
- margin-top: 1rem;
- }
-
- .youth-mode-page > main {
- padding-top: 2rem;
- }
-}
-
-/* Android anti-fraud warnings, abuse reports and user feedback. */
-#sidepanel.security-support-panel {
- width: 100%;
- min-width: 0;
- flex: 1 1 auto;
- color-scheme: light;
- background: #f5f6f8;
-}
-
-.security-support-panel * {
- box-sizing: border-box;
- letter-spacing: 0;
-}
-
-.support-page {
- display: flex;
- position: relative;
- width: 100%;
- min-height: 100%;
- overflow: hidden;
- color: #272c32;
- background: #f5f6f8;
- flex-direction: column;
-}
-
-.support-page button,
-.support-page input,
-.support-page textarea {
- font: inherit;
- letter-spacing: 0;
-}
-
-.support-page button {
- text-transform: none;
-}
-
-.support-page > main {
- min-height: 0;
- overflow-y: auto;
- flex: 1 1 auto;
-}
-
-.support-header {
- display: grid;
- grid-template-columns: 3.7rem minmax(0, 1fr) 3.7rem;
- align-items: center;
- min-height: 4.5rem;
- padding-top: env(safe-area-inset-top);
- border-bottom: 1px solid #e6e9ec;
- background: #fff;
- flex: none;
-}
-
-.support-header h1 {
- min-width: 0;
- margin: 0;
- overflow: hidden;
- font-size: 1.15rem;
- font-weight: 650;
- text-align: center;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.support-header button {
- display: grid;
- place-items: center;
- width: 3.7rem;
- min-width: 3.7rem;
- height: 3.7rem;
- min-height: 3.7rem;
- margin: 0;
- padding: 0;
- border: 0;
- box-shadow: none;
- color: #272c32;
- background: transparent;
-}
-
-.support-toast {
- position: fixed;
- z-index: 100;
- bottom: max(1.5rem, env(safe-area-inset-bottom));
- left: 50%;
- max-width: calc(100% - 2rem);
- padding: 0.7rem 1rem;
- border-radius: 0.3rem;
- color: #fff;
- background: rgba(25,29,34,0.94);
- text-align: center;
- transform: translateX(-50%);
-}
-
-.risk-warning-page {
- background: #fff;
-}
-
-.risk-conversation-preview {
- min-height: 100%;
- opacity: 0.78;
-}
-
-.risk-conversation-preview > header {
- display: grid;
- grid-template-columns: 3.5rem minmax(0, 1fr) 3.5rem;
- align-items: center;
- min-height: 4.5rem;
- padding-top: env(safe-area-inset-top);
- border-bottom: 1px solid #e7e9ec;
- background: #fff;
- text-align: center;
-}
-
-.risk-conversation-preview > header button {
- display: grid;
- place-items: center;
- width: 3.5rem;
- height: 3.5rem;
- margin: 0;
- padding: 0;
- border: 0;
- box-shadow: none;
- background: transparent;
-}
-
-.risk-conversation-preview > header span,
-.risk-conversation-preview > header strong,
-.risk-conversation-preview > header small {
- display: block;
-}
-
-.risk-conversation-preview > header small {
- margin-top: 0.15rem;
- color: #3d9a68;
-}
-
-.risk-conversation-preview > main {
- min-height: calc(100vh - 8.8rem);
- padding: 1rem;
- background: #eef0f3;
-}
-
-.risk-conversation-preview > main > p {
- margin: 0 0 1rem;
- color: #8b929b;
- font-size: 0.75rem;
- text-align: center;
-}
-
-.risk-conversation-preview main > div {
- width: fit-content;
- max-width: 78%;
- margin-bottom: 0.65rem;
- padding: 0.7rem 0.85rem;
- border-radius: 0.35rem;
- background: #fff;
- line-height: 1.5;
-}
-
-.risk-conversation-preview main > div.mine {
- margin-left: auto;
- background: #bde7a9;
-}
-
-.risk-conversation-preview > footer {
- display: flex;
- align-items: center;
- justify-content: space-between;
- min-height: 4.3rem;
- padding: 0.6rem 1rem max(0.6rem, env(safe-area-inset-bottom));
- border-top: 1px solid #e0e3e7;
- color: #959ca5;
- background: #fff;
-}
-
-.risk-conversation-preview > footer span {
- width: calc(100% - 2.7rem);
- padding: 0.65rem 0.75rem;
- border: 1px solid #e0e3e7;
- border-radius: 0.3rem;
- background: #f7f8f9;
-}
-
-.risk-overlay {
- display: grid;
- position: absolute;
- z-index: 70;
- inset: 0;
- place-items: center;
- padding: 1rem;
- background: rgba(19,23,28,0.55);
-}
-
-.risk-dialog {
- width: min(100%, 25rem);
- max-height: calc(100% - 2rem);
- padding: 1.15rem;
- overflow-y: auto;
- border-radius: 0.55rem;
- background: #fff;
- text-align: center;
-}
-
-/* .risk-dialog was designed as a modal card, centered by .risk-overlay's place-items:center wrapper
- (RiskConfirmDialog). FraudAwareness reuses the same class directly as .support-page's (no
- overlay wrapper, no explicit height on the page unlike a modal) to pick up its ul/li/aside/button
- styling -- but .support-page is a plain flex column with no centering of its own, so the capped
- width left the whole page pinned to the left edge on anything wider than ~25rem instead of centered
- like every other support-page/group-page/people-page in the app. */
-.risk-info-page {
- margin: 0 auto;
-}
-
-.risk-symbol {
- display: grid;
- place-items: center;
- width: 5rem;
- height: 5rem;
- margin: 0 auto 0.6rem;
- border-radius: 50%;
- color: #fff;
- background: #3979cd;
-}
-
-.risk-dialog.secondary .risk-symbol {
- color: #9b5e08;
- background: #fff0d5;
-}
-
-.risk-symbol .material-icons {
- font-size: 3.2rem;
-}
-
-.risk-dialog h2 {
- margin: 0;
- font-size: 1.35rem;
-}
-
-.risk-dialog > p {
- margin: 0.75rem 0;
- color: #4d555e;
- line-height: 1.65;
- text-align: left;
-}
-
-.risk-dialog > p.risk-emphasis {
- color: #b1363f;
- font-weight: 650;
- text-align: center;
-}
-
-.risk-dialog ul {
- margin: 0.9rem 0;
- padding: 0.25rem 0.75rem;
- border: 1px solid #d9e5f4;
- border-radius: 0.4rem;
- list-style: none;
-}
-
-.risk-dialog li {
- display: flex;
- align-items: center;
- min-height: 2.7rem;
- border-bottom: 1px solid #e7edf4;
- line-height: 1.35;
- text-align: left;
-}
-
-.risk-dialog li:last-child {
- border-bottom: 0;
-}
-
-.risk-dialog li .material-icons {
- margin-right: 0.55rem;
- color: #347bd7;
- font-size: 1.35rem;
-}
-
-.risk-dialog aside {
- display: flex;
- align-items: flex-start;
- gap: 0.5rem;
- margin: 0.8rem 0;
- padding: 0.7rem;
- border-radius: 0.35rem;
- color: #9e333b;
- background: #fff0f1;
- line-height: 1.45;
- text-align: left;
-}
-
-.risk-dialog button {
- width: 100%;
- min-height: 3rem;
- margin: 0.45rem 0 0;
- padding: 0 0.75rem;
- border: 0;
- border-radius: 0.35rem;
- box-shadow: none;
- color: #7f8790;
- background: transparent;
-}
-
-.risk-dialog button.primary {
- color: #fff;
- background: #347bd7;
-}
-
-.risk-dialog button.danger {
- color: #fff;
- background: #cc3d46;
-}
-
-.report-reasons {
- padding-bottom: 1rem;
-}
-
-.report-target {
- display: grid;
- grid-template-columns: 3.5rem minmax(0, 1fr) 2rem;
- align-items: center;
- gap: 0.75rem;
- min-height: 5.3rem;
- padding: 0.8rem 1rem;
- background: #fff;
-}
-
-.report-target img {
- width: 3.5rem;
- height: 3.5rem;
- border-radius: 0.45rem;
- object-fit: cover;
-}
-
-.report-target span,
-.report-target strong,
-.report-target small {
- display: block;
- min-width: 0;
-}
-
-.report-target small {
- margin-top: 0.25rem;
- color: #818a94;
-}
-
-.report-target > .material-icons {
- color: #3b8661;
-}
-
-.report-reasons > h2 {
- margin: 0;
- padding: 1rem;
- color: #6e7781;
- font-size: 0.85rem;
- font-weight: 500;
-}
-
-.report-loading {
- margin: 0;
- padding: 1rem;
- color: #6e7781;
- font-size: 0.85rem;
-}
-
-.report-reason-list {
- background: #fff;
-}
-
-.report-reason-list button {
- display: grid;
- grid-template-columns: minmax(0, 1fr) 1.5rem;
- align-items: center;
- gap: 0.55rem;
- width: 100%;
- min-height: 4.3rem;
- margin: 0;
- padding: 0.65rem 1rem;
- border: 0;
- border-bottom: 1px solid #eceef1;
- border-radius: 0;
- box-shadow: none;
- color: #30353c;
- background: #fff;
- text-align: left;
-}
-
-/* ReportReasons (security-support-view.jsx) renders each row as a single `label
- ` followed by the trailing chevron -- there is no separate icon element, and
- the reference design (docs/images/举报.png) confirms this list was never meant to carry a
- per-row icon badge. This rule used to target that lone span as `span:first-child` with a fixed
- 2.5rem blue square + white text (sized/colored for an icon glyph, not a 4-character label) --
- the 3-column grid above reserved a matching 2.8rem first column for it. With no icon in the
- DOM, the real label text got force-fit into that narrow column: white-on-blue where it
- overlapped the badge box, white-on-white (invisible) past its edge, so only a garbled sliver of
- each reason's label was ever legible. Folded into the plain block/min-width:0 treatment below
- (and the button grid down to 2 columns) so the label gets the full flexible column instead. */
-.report-reason-list button > span:first-child,
-.report-reason-list strong,
-.report-reason-list small {
- display: block;
- min-width: 0;
-}
-
-.report-reason-list small {
- margin-top: 0.2rem;
- overflow: hidden;
- color: #858d97;
- font-size: 0.76rem;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.report-reason-list button > .material-icons:last-child {
- color: #a8aeb5;
-}
-
-.report-details,
-.feedback-page > main {
- padding-bottom: 1rem;
-}
-
-/* Same collision as .report-reason-list above: ReportDetails renders this section as just
- `{"举报原因"/reasonLabel} 修改 ` -- two children, no leading icon --
- but the grid reserved a 2.7rem first column for one anyway (matching the now-unused
- `.material-icons` rule below, presumably meant for some future/removed icon). With no icon
- present, the label span itself was squeezed into that 36px column, wrapping "举报原因"/the
- reason text onto multiple lines instead of the single flexible column it needs. */
-.report-selected-reason {
- display: grid;
- grid-template-columns: minmax(0, 1fr) auto;
- align-items: center;
- gap: 0.7rem;
- padding: 0.9rem 1rem;
- background: #fff;
-}
-
-.report-selected-reason > .material-icons {
- color: #b14650;
- font-size: 2rem;
-}
-
-.report-selected-reason span,
-.report-selected-reason strong,
-.report-selected-reason small {
- display: block;
- min-width: 0;
-}
-
-.report-selected-reason small {
- margin-bottom: 0.2rem;
- color: #858d96;
-}
-
-.report-selected-reason button {
- min-height: 2.5rem;
- margin: 0;
- padding: 0 0.5rem;
- border: 0;
- box-shadow: none;
- color: #3476ce;
- background: transparent;
-}
-
-.support-textarea {
- display: block;
- position: relative;
- margin-top: 0.7rem;
- padding: 1rem;
- background: #fff;
-}
-
-.support-textarea > span {
- display: block;
- margin-bottom: 0.6rem;
- font-weight: 600;
-}
-
-.support-textarea b {
- margin-left: 0.2rem;
- color: #d6424b;
-}
-
-.support-textarea textarea {
- display: block;
- width: 100%;
- height: 10.5rem;
- margin: 0;
- padding: 0.75rem 0.8rem 2rem;
- resize: vertical;
- border: 0;
- box-shadow: none;
- background: #fff;
- line-height: 1.5;
-}
-
-.support-textarea > small {
- position: absolute;
- right: 1.8rem;
- bottom: 1.6rem;
- color: #9ba2ab;
-}
-
-.evidence-section,
-.feedback-upload {
- margin-top: 0.7rem;
- padding: 1rem;
- background: #fff;
-}
-
-.evidence-section > header,
-.feedback-upload > header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 0.6rem;
- margin-bottom: 0.8rem;
-}
-
-.evidence-section header span,
-.evidence-section header strong,
-.evidence-section header small,
-.feedback-upload header span,
-.feedback-upload header strong,
-.feedback-upload header small {
- display: block;
- min-width: 0;
-}
-
-.evidence-section header small,
-.feedback-upload header small {
- margin-top: 0.2rem;
- color: #858d96;
-}
-
-.evidence-section header em,
-.feedback-upload header em {
- color: #858d96;
- font-style: normal;
-}
-
-.evidence-section article {
- display: grid;
- grid-template-columns: 2.5rem minmax(0, 1fr) 2.5rem;
- align-items: center;
- min-height: 3.5rem;
- border-bottom: 1px solid #eceef1;
-}
-
-.evidence-section article > .material-icons {
- color: #5273a8;
-}
-
-.evidence-section article > span {
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.evidence-section article button {
- display: grid;
- place-items: center;
- width: 2.5rem;
- height: 2.5rem;
- margin: 0;
- padding: 0;
- border: 0;
- box-shadow: none;
- color: #989fa7;
- background: transparent;
-}
-
-.evidence-upload {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 100%;
- min-height: 4.5rem;
- margin: 0;
- padding: 0.7rem;
- border: 1px dashed #cbd1d8;
- border-radius: 0.35rem;
- box-shadow: none;
- color: #3476ce;
- background: #f8f9fa;
- flex-direction: column;
-}
-
-.feedback-upload > button {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 7.5rem;
- height: 7.5rem;
- margin: 0;
- padding: 0.7rem;
- border: 0;
- border-radius: 0.35rem;
- box-shadow: none;
- color: #858d96;
- background: #fff;
- flex-direction: column;
-}
-
-.evidence-upload .material-icons,
-.feedback-upload > button .material-icons {
- margin-bottom: 0.25rem;
- font-size: 1.8rem;
-}
-
-.report-privacy-note {
- display: flex;
- align-items: flex-start;
- gap: 0.5rem;
- margin: 0.8rem 1rem 0;
- color: #74808b;
- font-size: 0.78rem;
- line-height: 1.45;
-}
-
-.report-privacy-note .material-icons {
- color: #4b8065;
- font-size: 1.05rem;
-}
-
-.support-submit-bar {
- padding: 0.75rem 1rem max(0.8rem, env(safe-area-inset-bottom));
- border-top: 1px solid #e1e4e8;
- background: #fff;
- flex: none;
-}
-
-.support-submit-bar button,
-.support-success > button {
- width: 100%;
- min-height: 3.1rem;
- margin: 0;
- padding: 0 1rem;
- border: 0;
- border-radius: 0.35rem;
- box-shadow: none;
- color: #fff;
- background: #347bd7;
- font-weight: 600;
-}
-
-.support-success {
- display: flex;
- align-items: center;
- padding: 4rem 1.2rem 1.5rem;
- text-align: center;
- flex-direction: column;
-}
-
-.support-success > .material-icons {
- color: #3a9365;
- font-size: 5rem;
-}
-
-.support-success h2 {
- margin: 1rem 0 0.55rem;
- font-size: 1.35rem;
-}
-
-.support-success > p {
- max-width: 26rem;
- margin: 0;
- color: #66707a;
- line-height: 1.6;
-}
-
-.support-success dl {
- width: min(100%, 26rem);
- margin: 1.5rem 0;
- padding: 0.5rem 1rem;
- border: 1px solid #e0e4e8;
- border-radius: 0.4rem;
- background: #fff;
-}
-
-.support-success dl > div {
- display: flex;
- align-items: center;
- justify-content: space-between;
- min-height: 3rem;
- border-bottom: 1px solid #eceef1;
-}
-
-.support-success dl > div:last-child {
- border-bottom: 0;
-}
-
-.support-success dt,
-.support-success dd {
- margin: 0;
-}
-
-.support-success dt {
- color: #7c858f;
-}
-
-.support-success > button {
- width: min(100%, 26rem);
-}
-
-.feedback-categories {
- padding: 1rem;
- background: #fff;
-}
-
-.feedback-categories h2 {
- margin: 0 0 0.8rem;
- font-size: 1rem;
-}
-
-.feedback-categories > div {
- display: grid;
- grid-template-columns: repeat(4, minmax(0, 1fr));
- gap: 0.55rem;
-}
-
-.feedback-categories button {
- /* docs/images/意见反馈.png has no category selector at all (server/im_http.go's `category` field
- is real and required, but the design was never drawn for it -- same "no reference, add the
- simplest reasonable UI" call as Ch.6's own-group-nickname editor). This card layout (flex column,
- tall min-height) was carried over from an icon-above-label grid pattern used elsewhere, but this
- button only ever renders a single line of plain text -- with no `justify-content` set, `column`
- direction defaults the main axis to `flex-start`, pinning that one line to the top of a much
- taller box instead of centering it, which is the "why does this look off" of it. Simplified to a
- plain centered pill sized for the text it actually holds. */
- display: flex;
- align-items: center;
- justify-content: center;
- min-width: 0;
- min-height: 2.75rem;
- margin: 0;
- padding: 0.45rem;
- border: 1px solid #e0e4e8;
- border-radius: 0.35rem;
- box-shadow: none;
- color: #666f79;
- background: #fff;
-}
-
-.feedback-categories button.active {
- border-color: #347bd7;
- color: #2f70c7;
- background: #eef5ff;
-}
-
-.feedback-categories .material-icons {
- margin-bottom: 0.25rem;
- font-size: 1.45rem;
-}
-
-.feedback-categories button span {
- width: 100%;
- overflow-wrap: anywhere;
- font-size: 0.76rem;
- line-height: 1.25;
- text-align: center;
-}
-
-.feedback-contact {
- display: block;
- padding: 1rem;
- background: #fff;
-}
-
-.feedback-contact > span {
- display: block;
- margin-bottom: 0.55rem;
- font-weight: 600;
-}
-
-.feedback-contact input {
- width: 100%;
- height: 3rem;
- margin: 0;
- padding: 0 0.75rem;
- border: 1px solid #dfe3e7;
- border-radius: 0.35rem;
- box-shadow: none;
- background: #fff;
-}
-
-.diagnostics-consent {
- display: grid;
- grid-template-columns: 1.8rem minmax(0, 1fr);
- align-items: flex-start;
- gap: 0.5rem;
- margin-top: 0.7rem;
- padding: 1rem;
- background: #fff;
-}
-
-.diagnostics-consent input {
- width: 1.25rem;
- height: 1.25rem;
- margin: 0.1rem 0 0;
-}
-
-.diagnostics-consent span,
-.diagnostics-consent strong,
-.diagnostics-consent small {
- display: block;
- min-width: 0;
-}
-
-.diagnostics-consent small {
- margin-top: 0.3rem;
- color: #7e8791;
- line-height: 1.45;
-}
-
-@media screen and (max-width: 480px) {
- .risk-dialog {
- padding: 1rem;
- }
-
- .risk-symbol {
- width: 4.3rem;
- height: 4.3rem;
- }
-
- .risk-symbol .material-icons {
- font-size: 2.8rem;
- }
-
- .feedback-categories > div {
- gap: 0.35rem;
- }
-}
-
-/* QR scanning ("扫一扫"): full-screen camera view + the post-scan group-join confirm sheet.
- No reference screenshot covers the scanning viewfinder itself (docs/images/ only has the
- generate-a-code screens) -- z-index 200 matches the call overlays (widgets/call-panel.jsx),
- the only other feature that also needs to sit above every other full-screen surface. */
-.qr-scan-view {
- position: fixed;
- z-index: 200;
- inset: 0;
- overflow: hidden;
- background: #000;
-}
-
-.qr-scan-video {
- position: absolute;
- inset: 0;
- width: 100%;
- height: 100%;
- object-fit: cover;
-}
-
-.qr-scan-mask {
- position: absolute;
- inset: 0;
- display: grid;
- place-items: center;
-}
-
-.qr-scan-frame {
- position: relative;
- width: 62vw;
- max-width: 17rem;
- aspect-ratio: 1;
- border-radius: 0.5rem;
- box-shadow: 0 0 0 999rem rgba(0,0,0,0.5);
-}
-
-.qr-scan-corner {
- position: absolute;
- width: 1.6rem;
- height: 1.6rem;
- border: 0.22rem solid #4d8dff;
- font-style: normal;
-}
-
-.qr-scan-corner-tl { top: -0.11rem; left: -0.11rem; border-right: 0; border-bottom: 0; border-radius: 0.4rem 0 0 0; }
-.qr-scan-corner-tr { top: -0.11rem; right: -0.11rem; border-left: 0; border-bottom: 0; border-radius: 0 0.4rem 0 0; }
-.qr-scan-corner-bl { bottom: -0.11rem; left: -0.11rem; border-right: 0; border-top: 0; border-radius: 0 0 0 0.4rem; }
-.qr-scan-corner-br { bottom: -0.11rem; right: -0.11rem; border-left: 0; border-top: 0; border-radius: 0 0 0.4rem 0; }
-
-.qr-scan-header {
- position: absolute;
- z-index: 2;
- top: 0;
- left: 0;
- right: 0;
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 0.9rem 0.5rem;
- background: linear-gradient(rgba(0,0,0,0.45), transparent);
-}
-
-.qr-scan-header button {
- width: 2.6rem;
- height: 2.6rem;
- border: 0;
- box-shadow: none;
- color: #fff;
- background: transparent;
-}
-
-.qr-scan-header h1 {
- margin: 0;
- color: #fff;
- font-size: 1.05rem;
- font-weight: 500;
-}
-
-.qr-scan-header span {
- width: 2.6rem;
-}
-
-.qr-scan-hint {
- position: absolute;
- left: 0;
- right: 0;
- bottom: 14%;
- margin: 0;
- color: #e7ebf0;
- font-size: 0.9rem;
- text-align: center;
-}
-
-.qr-scan-error {
- position: absolute;
- inset: 0;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- gap: 0.6rem;
- padding: 0 2rem;
- color: #fff;
- text-align: center;
-}
-
-.qr-scan-error .material-icons {
- font-size: 2.6rem;
- color: #f2b53d;
-}
-
-.qr-scan-error button {
- margin-top: 0.6rem;
- padding: 0 1.4rem;
- min-height: 2.6rem;
- border: 0;
- border-radius: 1.4rem;
- box-shadow: none;
- color: #fff;
- background: #347bd7;
-}
-
-.qr-join-backdrop {
- position: fixed;
- z-index: 200;
- inset: 0;
- display: grid;
- place-items: center;
- padding: 1rem;
- background: rgba(22,26,31,0.52);
-}
-
-.qr-join-sheet {
- width: min(100%, 25rem);
- padding: 1.4rem 1.25rem 1.25rem;
- border-radius: 0.5rem;
- background: #fff;
- text-align: center;
-}
-
-.qr-join-avatar {
- display: grid;
- place-items: center;
- width: 4rem;
- height: 4rem;
- margin: 0 auto 0.6rem;
- border-radius: 50%;
- overflow: hidden;
- color: #9aa3ad;
- background: #eef1f4;
-}
-
-.qr-join-avatar img {
- width: 100%;
- height: 100%;
- object-fit: cover;
-}
-
-.qr-join-avatar .material-icons {
- font-size: 1.9rem;
-}
-
-.qr-join-sheet h2 {
- margin: 0.2rem 0;
- font-size: 1.1rem;
-}
-
-.qr-join-sheet > p {
- margin: 0 0 0.9rem;
- color: #757d87;
-}
-
-.qr-join-sheet input {
- width: 100%;
- height: 2.9rem;
- margin: 0 0 0.8rem;
- padding: 0 0.75rem;
- border: 1px solid #dce0e5;
- border-radius: 0.35rem;
- box-shadow: none;
- text-align: center;
-}
-
-.qr-join-sheet button {
- width: 100%;
- min-height: 2.9rem;
- margin: 0.45rem 0 0;
- border: 0;
- border-radius: 0.35rem;
- box-shadow: none;
- color: #4d5660;
- background: #f0f2f4;
-}
-
-.qr-join-sheet button.primary {
- color: #fff;
- background: #347bd7;
-}
-
-/* 合并转发卡片(widgets/forward-bundle.jsx,第4章 4.20)。
- 服务端 mode="bundle" 生成的消息不是 Drafty,单独渲染成一张可展开的"聊天记录"卡片。 */
-.forward-bundle {
- min-width: 11rem;
- max-width: 18rem;
-}
-
-.forward-bundle-title {
- margin-bottom: 0.35rem;
- font-weight: 500;
-}
-
-.forward-bundle-lines {
- margin: 0;
- padding: 0;
- list-style: none;
-}
-
-.forward-bundle-lines li {
- overflow: hidden;
- margin: 0.15rem 0;
- color: var(--clr-text-secondary);
- font-size: 0.85rem;
- line-height: 1.35;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.forward-bundle-lines li b {
- font-weight: 500;
-}
-
-.forward-bundle-more,
-.forward-bundle-count {
- display: block;
- width: 100%;
- margin: 0.4rem 0 0;
- padding: 0.35rem 0 0;
- border: 0;
- border-top: 1px solid var(--clr-divider, #e7e9ec);
- border-radius: 0;
- box-shadow: none;
- background: none;
- color: var(--clr-text-secondary);
- font-size: 0.8rem;
- line-height: 1.3;
- text-align: left;
- text-transform: none;
-}
-
-.forward-bundle-more {
- color: #347bd7;
- cursor: pointer;
-}
-
-.forward-bundle-comment {
- margin-top: 0.4rem;
- color: var(--clr-text-primary);
- font-size: 0.85rem;
-}
diff --git a/css/base.min.css b/css/base.min.css
deleted file mode 100644
index 7ee52021..00000000
--- a/css/base.min.css
+++ /dev/null
@@ -1 +0,0 @@
-:root{color-scheme:light dark;--message-text-size:10pt;--caption-panel-height:4rem;--wallpaper-url:url(../img/bkg/a00.png);--wallpaper-repeat:repeat;--wallpaper-blur:0px;--wallpaper-invert:0;--wallpaper-brightness:1;--wallpaper-size:contain;--wallpaper-position:center;--clr-bright:light-dark(#fff,#181818);--clr-dark:light-dark(#000,#fff);--clr-transparent:transparent;--clr-std-gray:gray;--clr-std-ltgray:light-dark(silver,#3f3f3f);--clr-std-teal:teal;--clr-std-grey:grey;--clr-std-orange:orange;--clr-primary-accent:light-dark(#2094f3,#0e6dba);--clr-primary-accent-hover:light-dark(#0a6ebd,#116ab4);--clr-primary-accent-focus-shadow:light-dark(rgba(33,150,243,.25),rgba(3,7,12,.25));--clr-primary-accent-transparent-medium:light-dark(rgba(33,150,243,.3),rgba(20,70,120,.3));--clr-primary-action:light-dark(#1e89e6,#1571c1);--clr-primary-action-hover:light-dark(#2962ff,#0037b8);--clr-secondary-accent:light-dark(#0097a8,#006570);--clr-primary-light-blue:light-dark(#bcdefb,#78a0be);--clr-light-blue-accent:light-dark(#0398e2,#0379b5);--clr-body-background:var(--clr-std-ltgray);--clr-text-primary:light-dark(#666,#999);--clr-text-secondary:light-dark(#757575,#8a8a8a);--clr-text-tertiary:light-dark(#999,#787878);--clr-text-tertiary-activated:#888;--clr-text-placeholder:light-dark(#bbb,#555);--clr-text-disabled:light-dark(#888,#777);--clr-border-light:light-dark(#ccc,#555);--clr-border-muted:light-dark(#ddd,#444);--clr-background-off-white:light-dark(#fafafa,#222);--clr-background-light-gray:light-dark(#f0f0f0,#0f0f0f);--clr-background-extra-light-gray:light-dark(#eee,#222);--clr-background-blue-gray-50:light-dark(#edf0f2,#202c33);--clr-background-blue-gray-75:light-dark(#e1e8eb,#182124);--clr-background-blue-gray-100:light-dark(#cfd8dc,#20272a);--clr-background-blue-gray-300:light-dark(#b0bec5,#354146);--clr-blue-wash-light:light-dark(#f0f0ff,#22222d);--clr-blue-wash-medium:light-dark(#d8e8ff,#123550);--clr-primary-wash:light-dark(#e3f2fd,#072f45);--clr-indigo-50:light-dark(#e8eaf6,#1a2c44);--clr-danger-text:light-dark(#d33131,#931f1f);--clr-danger-text-hover:light-dark(#f44034,#c10);--clr-error-text-muted:light-dark(#c66,#933);--clr-error-bg-light:light-dark(#ffccd1,#ff6675);--clr-error-border-light:light-dark(#ffbdc6,#ff334e);--clr-error-text-dark:light-dark(#b51c1c,#de2323);--clr-error-icon:light-dark(#ef5552,#d81b0e);--clr-warning-accent:light-dark(#ffc107,#808020);--clr-warning-bg-light:light-dark(#fff9c4,#00060c);--clr-warning-border-light:light-dark(#fff59d,#000a2b);--clr-warning-text-dark:light-dark(#6d4c41,#a08356);--clr-warning-icon:light-dark(#fbc02d,#b48204);--clr-info-bg-light:light-dark(#e1f5fe,#010a0e);--clr-info-border-light:light-dark(#b3e5fc,#001824);--clr-info-text-dark:light-dark(#0288d1,#00151f);--clr-header-stripe-bg:light-dark(#3949ab,#12152f);--clr-scrollbar-thumb:light-dark(rgba(0,0,0,.3),hsla(0,0%,100%,.3));--clr-scrollbar-track:light-dark(hsla(0,0%,100%,.1),rgba(0,0,0,.1));--clr-menu-item-hover-bg:light-dark(#f5f5f5,#0a0a0a);--clr-alert-overlay-bg:light-dark(hsla(0,0%,94%,.85),hsla(0,0%,6%,.85));--clr-verified-accent:light-dark(#02a6f2,#027db6);--clr-staff-accent:light-dark(#689f38,#598830);--clr-badge-inv:light-dark(#fffd,#cccd);--clr-cyan-50:light-dark(#e0f7fa,#2f3e43);--clr-overlay-white-strong:light-dark(hsla(0,0%,100%,.9),rgba(0,0,0,.9));--clr-gray-medium-bg:light-dark(#777,#444);--clr-toast-bg:#333;--clr-toast-text:light-dark(#eee,#aaa);--clr-online:#4caf50;--clr-offline:light-dark(#ccc,#999);--clr-shadow-input-underline:rgba(0,0,0,.1);--clr-shadow-hr-inset:rgba(0,0,0,.15);--clr-shadow-scrollbar-track-webkit:rgba(0,0,0,.2);--clr-shadow-button:rgba(0,0,0,.25);--clr-shadow-button-inset-active:rgba(0,0,0,.125);--clr-shadow-dialog:rgba(0,0,0,.45);--clr-shadow-panel-divider:rgba(0,0,0,.1);--clr-shadow-gray-strong:light-dark(var(--clr-std-gray),#333);--clr-shadow-silver-subtle:light-dark(var(--clr-std-ltgray),#333);--clr-shadow-bubble-drop:rgba(0,0,0,.3);--clr-button-primary-bg:var(--clr-primary-action);--clr-button-primary-text:var(--clr-bright);--clr-button-primary-hover-bg:var(--clr-primary-action-hover);--clr-button-secondary-bg:var(--clr-bright);--clr-button-secondary-text:var(--clr-primary-accent);--clr-button-secondary-hover-bg:var(--clr-primary-wash);--clr-button-outline-text:var(--clr-primary-accent);--clr-button-outline-border:var(--clr-std-ltgray);--clr-button-outline-hover-bg:var(--clr-indigo-50);--clr-button-danger-bg:light-dark(#ec645f,#d74742);--clr-button-danger-text:var(--clr-bright);--clr-button-danger-hover-bg:light-dark(#f77,#a55);--clr-button-positive-bg:light-dark(#71c46f,#3f983e);--clr-button-positive-text:var(--clr-bright);--clr-button-positive-hover-bg:light-dark(#7fed7f,#6ec26e);--clr-input-text:var(--clr-text-primary);--clr-input-border:var(--clr-border-light);--clr-input-border-focus:var(--clr-primary-accent);--clr-input-shadow:var(--clr-shadow-input-underline);--clr-input-shadow-focus:var(--clr-primary-accent-focus-shadow);--clr-input-invalid-border:light-dark(#e57171,#561010);--clr-input-invalid-shadow:light-dark(#e5737340,#1a0c0c40);--clr-input-placeholder-text:var(--clr-text-placeholder);--clr-bubble-left-bg:light-dark(#f0f0f0,#2c2c2e);--clr-bubble-left-bg-flash:light-dark(#d8d8d8,#3a3a3c);--clr-bubble-right-bg:var(--clr-primary-accent);--clr-bubble-right-bg-flash:var(--clr-primary-accent-hover);--clr-bubble-text:light-dark(#212121,#bbc);--clr-bubble-right-text:#fff;--clr-bubble-meta-text:var(--clr-text-primary);--clr-bubble-meta-deleted-bg:var(--clr-primary-wash);--clr-bubble-meta-date-bg:transparent;--clr-bubble-timestamp-text:light-dark(#777,#888);--clr-bubble-author-text:light-dark(#777,#666);--clr-reply-quote-border:var(--clr-light-blue-accent);--clr-reply-quote-text:var(--clr-text-primary);--clr-reply-quote-left-bg:light-dark(hsla(0,0%,100%,.4),rgba(0,0,0,.4));--clr-reply-quote-right-bg:light-dark(#8080801f,#c4c4c42e);--clr-reply-quote-preview-bg:var(--clr-background-off-white);--clr-badge-default-bg:light-dark(#e8e8e8,#171717);--clr-badge-default-text:var(--clr-text-primary);--clr-badge-default-border:var(--clr-std-ltgray);--clr-badge-green-bg:light-dark(#efe,#131);--clr-badge-green-text:light-dark(#383,#363);--clr-badge-green-border:light-dark(#aca,#242);--clr-badge-yellow-bg:light-dark(#ffe,#331);--clr-badge-yellow-text:light-dark(#660,#660);--clr-badge-yellow-border:light-dark(#cc9,#552);--clr-badge-blue-bg:light-dark(#f0f0ff,#224);--clr-badge-blue-text:light-dark(#339,#447);--clr-badge-blue-border:light-dark(#aad,#336);--clr-badge-red-bg:light-dark(#fee,#522);--clr-badge-red-text:light-dark(#933,#744);--clr-badge-red-border:light-dark(#c99,#633);--clr-badge-magenta-bg:light-dark(#fef,#535);--clr-badge-magenta-text:light-dark(#939,#747);--clr-badge-magenta-border:light-dark(#c9c,#636);--clr-chip-bg:var(--clr-background-extra-light-gray);--clr-chip-invalid-text:var(--clr-error-text-muted);--clr-chip-invalid-bg:var(--clr-badge-red-bg);--clr-chip-remove-icon-bg:light-dark(#aaa,#555);--clr-chip-remove-icon-text:var(--clr-background-extra-light-gray);--clr-chip-remove-icon-hover-bg:var(--clr-text-placeholder);--clr-chip-remove-icon-hover-text:var(--clr-bright);--clr-tabbar-bg:var(--clr-background-extra-light-gray);--clr-tabbar-inactive-text:light-dark(rgba(33,150,243,.6),#165aa0);--clr-tabbar-active-text:var(--clr-primary-accent);--clr-tabbar-inactive-border:var(--clr-border-light);--clr-tabbar-active-border:var(--clr-primary-accent);--clr-highlight-text:var(--clr-std-teal);--clr-highlight-preview-text:light-dark(#5f9ea0,#205e60);--clr-spinner-track:light-dark(#f3f3f3,#0c0c0c);--clr-spinner-active:var(--clr-primary-action);--clr-cropper-bounding-box-border:var(--clr-primary-action);--clr-cropper-bounding-box-bg:var(--clr-text-tertiary);--clr-cropper-cutout-border:var(--clr-text-tertiary);--clr-cropper-cutout-shadow:light-dark(rgba(230,246,255,.66),rgba(25,10,0,.66));--clr-audio-bg:var(--clr-background-extra-light-gray);--clr-audio-icon:light-dark(hsla(180,7%,43%,.8),hsla(0,7%,57%,.8));--clr-audio-icon-disabled:light-dark(hsla(0,0%,60%,.75),hsla(0,0%,40%,.75));--clr-audio-timer-text:light-dark(hsla(0,0%,40%,.75),hsla(0,0%,60%,.75));--clr-pinned-menu-bg:light-dark(#f8f8f8,#1a1a1a);--clr-drag-n-drop-border:var(--clr-std-grey);--clr-drag-n-drop-text:var(--clr-std-grey);--clr-drag-n-drop-bg:light-dark(hsla(0,0%,100%,.95),rgba(0,0,0,.95));--clr-drag-n-drop-outline:light-dark(hsla(0,0%,100%,.95),rgba(0,0,0,.95));--clr-lettertile-default-text:light-dark(#fafafacc,#dcdcdccc);--clr-lettertile-hover-text:var(--clr-bright);--clr-lt-bg-0:#ef9a9a;--clr-lt-bg-1:#90caf9;--clr-lt-bg-2:#b0bec4;--clr-lt-bg-3:#b49edb;--clr-lt-bg-4:#ffa98f;--clr-lt-bg-5:#a4d5a6;--clr-lt-bg-6:#ededed;--clr-lt-bg-7:#e6ee9b;--clr-lt-bg-8:#c4e0a3;--clr-lt-bg-9:#f0e57f;--clr-lt-bg-10:#f490b1;--clr-lt-bg-11:#a0a8da;--clr-lt-bg-12:#ffdf80;--clr-lt-bg-13:#bcaaa4;--clr-lt-bg-14:#80deea;--clr-lt-bg-15:#cd92d8;--clr-dk-bg-0:#c72929;--clr-dk-bg-1:#ad1457;--clr-dk-bg-2:#681b98;--clr-dk-bg-3:#4527a0;--clr-dk-bg-4:#293594;--clr-dk-bg-5:#1565c1;--clr-dk-bg-6:#0277bb;--clr-dk-bg-7:#00838f;--clr-dk-bg-8:#006b5f;--clr-dk-bg-9:#2f7f33;--clr-dk-bg-10:#54892f;--clr-dk-bg-11:#9e9e24;--clr-dk-bg-12:#f9a824;--clr-dk-bg-13:#ff9100;--clr-dk-bg-14:#f06c00;--clr-dk-bg-15:#d54215;--clr-lt-fg-0:var(--clr-lt-bg-0);--clr-lt-fg-1:var(--clr-lt-bg-1);--clr-lt-fg-2:var(--clr-lt-bg-2);--clr-lt-fg-3:var(--clr-lt-bg-3);--clr-lt-fg-4:var(--clr-lt-bg-4);--clr-lt-fg-5:var(--clr-lt-bg-5);--clr-lt-fg-6:var(--clr-lt-bg-6);--clr-lt-fg-7:var(--clr-lt-bg-7);--clr-lt-fg-8:var(--clr-lt-bg-8);--clr-lt-fg-9:var(--clr-lt-bg-9);--clr-lt-fg-10:var(--clr-lt-bg-10);--clr-lt-fg-11:var(--clr-lt-bg-11);--clr-lt-fg-12:var(--clr-lt-bg-12);--clr-lt-fg-13:var(--clr-lt-bg-13);--clr-lt-fg-14:var(--clr-lt-bg-14);--clr-lt-fg-15:var(--clr-lt-bg-15);--clr-dk-fg-0:var(--clr-dk-bg-0);--clr-dk-fg-1:var(--clr-dk-bg-1);--clr-dk-fg-2:var(--clr-dk-bg-2);--clr-dk-fg-3:var(--clr-dk-bg-3);--clr-dk-fg-4:var(--clr-dk-bg-4);--clr-dk-fg-5:var(--clr-dk-bg-5);--clr-dk-fg-6:var(--clr-dk-bg-6);--clr-dk-fg-7:var(--clr-dk-bg-7);--clr-dk-fg-8:var(--clr-dk-bg-8);--clr-dk-fg-9:var(--clr-dk-bg-9);--clr-dk-fg-10:var(--clr-dk-bg-10);--clr-dk-fg-11:var(--clr-dk-bg-11);--clr-dk-fg-12:var(--clr-dk-bg-12);--clr-dk-fg-13:var(--clr-dk-bg-13);--clr-dk-fg-14:var(--clr-dk-bg-14);--clr-dk-fg-15:var(--clr-dk-bg-15)}#sidepanel.workplace-collaboration-panel{width:100%;min-width:0;flex:1 1 auto;color-scheme:light;background:#f4f5f7}.workplace-collaboration-panel *{box-sizing:border-box;letter-spacing:0}.workplace-page{display:flex;position:relative;width:100%;min-height:100%;overflow:hidden;color:#242930;background:#f4f5f7;flex-direction:column}.workplace-page button,.workplace-page input,.workplace-page textarea{font:inherit;letter-spacing:0}.workplace-page button{text-transform:none}.workplace-page>main{min-height:0;overflow-y:auto;flex:1 1 auto}.workplace-header{display:grid;grid-template-columns:3.6rem minmax(0,1fr) 3.6rem;align-items:center;min-height:4.4rem;padding-top:env(safe-area-inset-top);border-bottom:1px solid #e6e8eb;background:#fff;flex:none}.workplace-header h1{min-width:0;margin:0;overflow:hidden;font-size:1.15rem;font-weight:650;text-align:center;text-overflow:ellipsis;white-space:nowrap}.workplace-header button{display:grid;place-items:center;width:3.6rem;min-width:3.6rem;height:3.6rem;min-height:3.6rem;margin:0;padding:0;border:0;box-shadow:none;color:#242930;background:transparent}.workplace-header button:last-child{color:#246fce}.workplace-header .material-icons{font-size:1.6rem}.workplace-toast{position:fixed;z-index:80;bottom:max(1.5rem,env(safe-area-inset-bottom));left:50%;max-width:calc(100% - 2rem);padding:.7rem 1rem;border-radius:.3rem;color:#fff;background:rgba(29,33,39,.94);text-align:center;transform:translateX(-50%)}.workbench-page{background:#fff}.workbench-page>main{padding:max(1.4rem,env(safe-area-inset-top)) 1.1rem 1.5rem}.workbench-profile{display:grid;grid-template-columns:3.4rem minmax(0,1fr) 3rem;align-items:center;gap:.75rem;min-height:4.8rem}.workbench-profile img{width:3.4rem;height:3.4rem;border-radius:.45rem;object-fit:cover}.workbench-profile small,.workbench-profile span,.workbench-profile strong{display:block;min-width:0}.workbench-profile strong{overflow:hidden;font-size:1.25rem;text-overflow:ellipsis;white-space:nowrap}.workbench-profile small{margin-top:.25rem;color:#767e88}.workbench-profile button{display:grid;place-items:center;width:3rem;min-width:3rem;height:3rem;min-height:3rem;margin:0;padding:0;border:0;box-shadow:none;color:#6c737c;background:transparent}.workbench-banner{display:grid;grid-template-columns:minmax(0,1fr) 5.5rem;align-items:center;min-height:9.5rem;margin:1.25rem 0 1.6rem;padding:1.35rem;overflow:hidden;border-radius:.5rem;color:#fff;background:#3d7ce5}.workbench-banner small,.workbench-banner strong{display:block}.workbench-banner strong{max-width:13rem;font-size:1.35rem;line-height:1.45}.workbench-banner small{margin-top:.8rem;color:hsla(0,0%,100%,.85)}.workbench-banner>.material-icons{color:#d9e7ff;font-size:5rem}.workbench-apps{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:1.4rem .7rem}.workbench-apps button{display:flex;align-items:center;width:100%;min-width:0;min-height:5.8rem;margin:0;padding:0;border:0;box-shadow:none;color:#30353c;background:transparent;flex-direction:column}.workbench-apps button>span{display:grid;position:relative;place-items:center;width:3.7rem;height:3.7rem;border-radius:.7rem;color:#fff}.workbench-apps span.tone-violet{background:#7659d6}.workbench-apps span.tone-orange{background:#f07835}.workbench-apps span.tone-red{background:#df4e46}.workbench-apps span.tone-blue{background:#3e78d6}.workbench-apps span.tone-green{background:#35a96b}.workbench-apps .material-icons{font-size:2rem}.workbench-apps em{position:absolute;top:-.35rem;right:-.35rem;display:grid;place-items:center;min-width:1.25rem;height:1.25rem;padding:0 .25rem;border:2px solid #fff;border-radius:.7rem;color:#fff;background:#e33f4b;font-size:.68rem;font-style:normal}.workbench-apps strong{width:100%;margin-top:.55rem;overflow-wrap:anywhere;font-size:.85rem;font-weight:500;line-height:1.25;text-align:center}.workplace-bottom-nav{display:grid;grid-template-columns:repeat(2,1fr);min-height:calc(4.4rem + env(safe-area-inset-bottom));padding-bottom:env(safe-area-inset-bottom);border-top:1px solid #e5e7ea;background:#fff;flex:none}.workplace-bottom-nav button{display:flex;align-items:center;justify-content:center;min-width:0;min-height:4.4rem;margin:0;padding:0;border:0;box-shadow:none;color:#9aa1aa;background:transparent;flex-direction:column}.workplace-bottom-nav button.active{color:#2d73d2}.workplace-bottom-nav .material-icons{font-size:1.65rem}.workplace-bottom-nav span{margin-top:.15rem;font-size:.72rem}.workplace-search{display:grid;grid-template-columns:2.4rem minmax(0,1fr);align-items:center;min-height:3rem;margin:.8rem;padding:0 .65rem;border:1px solid #e4e7ea;border-radius:.35rem;background:#fff}.workplace-search .material-icons{color:#9ba2aa}.workplace-search input{width:100%;height:2.8rem;margin:0;padding:0;border:0;box-shadow:none;background:transparent}.organization-content{background:#f4f5f7}.organization-tree{margin-top:.7rem;background:#fff}.organization-tree button{display:grid;grid-template-columns:2.5rem minmax(0,1fr) 2rem;align-items:center;gap:.4rem;width:100%;min-height:4rem;margin:0;padding:.55rem 1rem;border:0;border-bottom:1px solid #eceef0;border-radius:0;box-shadow:none;color:#30353c;background:#fff;text-align:left}.organization-tree button small,.organization-tree button strong,.organization-tree button>span{display:block;min-width:0}.organization-tree button strong{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.organization-tree button small{margin-top:.2rem;color:#8b929b}.organization-members img{width:2.5rem;height:2.5rem;border-radius:50%;object-fit:cover}.meeting-list{padding-bottom:1rem}.workplace-tabs{display:grid;grid-template-columns:repeat(3,1fr);min-height:3.2rem;border-bottom:1px solid #e7e9ec;background:#fff}.workplace-tabs button{position:relative;min-width:0;min-height:3.2rem;margin:0;padding:0;border:0;box-shadow:none;color:#7b838d;background:transparent}.workplace-tabs button.active{color:#286fcf;font-weight:600}.workplace-tabs button.active:after{position:absolute;bottom:0;left:30%;width:40%;height:.18rem;border-radius:.1rem;background:#347bd7;content:""}.meeting-card,.meeting-draft,.meeting-status{margin:.8rem;border:1px solid #e7e9ec;border-radius:.45rem;background:#fff}.meeting-card{padding:1rem}.meeting-card>header{display:flex;align-items:flex-start;justify-content:space-between;gap:.8rem;padding-bottom:.85rem;border-bottom:1px solid #eceef1}.meeting-card>header small,.meeting-card>header span,.meeting-card>header strong{display:block}.meeting-card>header small{margin-bottom:.25rem;color:#3575cf}.meeting-card>header strong{font-size:1.08rem}.meeting-card>header em{flex:none;padding:.3rem .5rem;border-radius:.25rem;color:#b26e10;background:#fff3df;font-size:.75rem;font-style:normal}.meeting-card>header em.accepted{color:#28784f;background:#e5f5ec}.meeting-card>header em.declined{color:#ad3d45;background:#fbe9eb}.meeting-card dl{margin:.8rem 0 0}.meeting-card dl>div{display:grid;grid-template-columns:6.5rem minmax(0,1fr);align-items:start;min-height:2.1rem}.meeting-card dd,.meeting-card dt{margin:0}.meeting-card dt{display:flex;align-items:center;color:#757d87}.meeting-card dt .material-icons{margin-right:.35rem;font-size:1.15rem}.meeting-card dd{overflow-wrap:anywhere}.meeting-card>footer{display:flex;justify-content:flex-end;gap:.55rem;margin-top:.8rem}.meeting-card>footer button{min-width:5.4rem;min-height:2.5rem;margin:0;padding:0 .8rem;border:1px solid #d8dce1;border-radius:.3rem;box-shadow:none;color:#3073ce;background:#fff}.meeting-card>footer button.primary,.meeting-card>footer button:last-child{border-color:#347bd7;color:#fff;background:#347bd7}.meeting-card>footer button .material-icons{margin-right:.3rem;font-size:1.1rem;vertical-align:middle}.meeting-response{display:grid!important;grid-template-columns:repeat(3,minmax(0,1fr))}.meeting-response button{min-width:0!important;padding:0 .3rem!important}.meeting-draft{display:grid;grid-template-columns:2.8rem minmax(0,1fr) auto;align-items:center;gap:.6rem;padding:.8rem}.meeting-draft>.material-icons{color:#347bd7;font-size:2rem}.meeting-draft small,.meeting-draft span,.meeting-draft strong{display:block;min-width:0}.meeting-draft small{margin-top:.2rem;color:#838b95}.meeting-draft button{min-height:2.4rem;margin:0;padding:0 .6rem;border:0;box-shadow:none;color:#347bd7;background:transparent}.meeting-status{padding:1rem}.meeting-status h2{margin:0 0 .8rem;font-size:1rem}.meeting-status>div,.report-statistics>div{display:grid;grid-template-columns:repeat(3,1fr)}.meeting-status>div{grid-template-columns:repeat(4,1fr)}.meeting-status span,.report-statistics>div span{display:flex;align-items:center;min-width:0;flex-direction:column}.meeting-status b,.report-statistics>div b{font-size:1.35rem}.meeting-status small,.report-statistics>div small{margin-top:.25rem;color:#7c848e}.report-editor-page>main{padding-bottom:1rem}.report-period{display:flex;align-items:center;gap:.7rem;padding:.9rem 1rem;border-bottom:1px solid #e8eaed;background:#fff}.report-period>.material-icons{color:#347bd7}.report-period small,.report-period span,.report-period strong{display:block}.report-period small{margin-top:.2rem;color:#6b927d}.report-field{display:block;position:relative;margin-top:.7rem;padding:1rem;background:#fff}.report-field>span{display:block;margin-bottom:.65rem;font-weight:600}.report-field>span b{margin-right:.2rem;color:#d8414c}.report-field textarea{display:block;width:100%;height:11rem;margin:0;padding:.75rem .8rem 2rem;resize:vertical;border:1px solid #dfe3e7;border-radius:.3rem;box-shadow:none;background:#fff;line-height:1.5}.report-field>small{position:absolute;right:1.8rem;bottom:1.6rem;color:#9ba2ac}.report-attachment{display:grid;grid-template-columns:2.5rem minmax(0,1fr) 2rem;align-items:center;gap:.5rem;width:calc(100% - 2rem);min-height:4.2rem;margin:.8rem 1rem;padding:.6rem .8rem;border:1px dashed #cad0d7;border-radius:.35rem;box-shadow:none;color:#343a42;background:#fff;text-align:left}.report-attachment>.material-icons:first-child{color:#347bd7}.report-attachment small,.report-attachment span,.report-attachment strong{display:block;min-width:0}.report-attachment small{margin-top:.2rem;color:#888f98}.workplace-submit-bar{padding:.75rem 1rem max(.75rem,env(safe-area-inset-bottom));border-top:1px solid #e4e7ea;background:#fff;flex:none}.workplace-primary,.workplace-submit-bar button{width:100%;min-height:3rem;margin:0;padding:0 1rem;border:0;border-radius:.35rem;box-shadow:none;color:#fff;background:#347bd7;font-weight:600}.report-list-page>main{padding-bottom:1rem}.report-role-switch{display:grid;grid-template-columns:repeat(2,1fr);margin:0 .8rem .8rem;border:1px solid #dce0e5;border-radius:.3rem;background:#fff}.report-role-switch button{min-height:2.6rem;margin:0;padding:0;border:0;box-shadow:none;color:#737b85;background:transparent}.report-role-switch button.active{color:#fff;background:#347bd7}.report-cards{margin:0 .8rem}.report-cards article{margin-bottom:.7rem;padding:.9rem;border:1px solid #e6e9ec;border-radius:.4rem;background:#fff}.report-cards article>header{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:.6rem}.report-cards article>header img{width:2.6rem;height:2.6rem;border-radius:50%;object-fit:cover}.report-cards article>header small,.report-cards article>header span,.report-cards article>header strong{display:block;min-width:0}.report-cards article>header small{margin-top:.15rem;color:#8b929c}.report-cards article>header em{padding:.3rem .45rem;border-radius:.25rem;color:#b76811;background:#fff2df;font-size:.72rem;font-style:normal}.report-cards article>header em.submitted{color:#28784f;background:#e5f5ec}.report-cards article>header em.draft{color:#ad6c11;background:#fff2dd}.report-cards article p{display:-webkit-box;margin:.8rem 0;overflow:hidden;line-height:1.55;-webkit-box-orient:vertical;-webkit-line-clamp:3}.report-cards article>footer{display:flex;align-items:center;justify-content:space-between;gap:.5rem;padding-top:.65rem;border-top:1px solid #eceef1}.report-cards article>footer span{display:flex;align-items:center;min-width:0;color:#7e8690;font-size:.78rem}.report-cards article>footer span .material-icons{margin-right:.25rem;font-size:1rem}.report-cards article>footer button{display:flex;align-items:center;flex:none;min-height:2.2rem;margin:0;padding:0 .1rem 0 .6rem;border:0;box-shadow:none;color:#347bd7;background:transparent}.report-cards article>footer button .material-icons{font-size:1.15rem}.report-cards>button{display:grid;grid-template-columns:3.2rem minmax(0,1fr) auto 1.5rem;align-items:center;gap:.65rem;width:100%;min-height:5.2rem;margin:0 0 .7rem;padding:.7rem;border:1px solid #e6e9ec;border-radius:.4rem;box-shadow:none;color:#30353c;background:#fff;text-align:left}.report-month{display:grid;place-items:center;width:3.2rem;height:3.2rem;border-radius:.35rem;color:#fff;background:#5577b9}.report-cards>button small,.report-cards>button strong,.report-cards>button>span:nth-child(2){display:block;min-width:0}.report-cards>button small,.report-cards>button strong{line-height:1.3}.report-cards>button small{margin-top:.25rem;color:#858d97}.report-cards>button em{padding:.25rem .4rem;border-radius:.25rem;font-size:.72rem;font-style:normal}.report-cards>button em.submitted{color:#28784f;background:#e5f5ec}.report-cards>button em.draft{color:#ad6c11;background:#fff2dd}.report-statistics{margin-bottom:.7rem;padding:1rem;background:#fff}.report-statistics>header{display:flex;align-items:center;justify-content:space-between;gap:.5rem;margin-bottom:1rem}.report-statistics>header small,.report-statistics>header span,.report-statistics>header strong{display:block}.report-statistics>header small{margin-bottom:.2rem;color:#808892}.report-statistics>button,.report-statistics>header button{min-height:2.4rem;margin:0;padding:0 .7rem;border:1px solid #dce0e5;border-radius:.3rem;box-shadow:none;color:#347bd7;background:#fff}.report-statistics>button{display:flex;align-items:center;justify-content:center;width:100%;margin-top:1rem}.report-statistics>button .material-icons{margin-right:.35rem;font-size:1.15rem}.report-detail{padding-bottom:1.5rem}.report-detail>section{margin-bottom:.65rem;padding:1rem;background:#fff}.report-author{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:.7rem}.report-author img{width:3rem;height:3rem;border-radius:50%;object-fit:cover}.report-author small,.report-author span,.report-author strong{display:block;min-width:0}.report-author small{margin-top:.2rem;color:#858d96}.report-author em{font-style:normal}.report-author em.submitted{color:#28784f}.report-author em.draft{color:#ad6c11}.report-detail h2,.report-detail h3,.report-detail p{margin:0}.report-detail h2{font-size:1.05rem}.report-detail h2+small{display:block;margin-top:.4rem;color:#89919b}.report-detail h3{margin-bottom:.6rem;font-size:.98rem}.report-detail section p{line-height:1.7;white-space:pre-wrap}.report-file{display:grid;grid-template-columns:2.5rem minmax(0,1fr) 2rem;align-items:center;gap:.6rem}.report-file>.material-icons:first-child{color:#4f74ad;font-size:2rem}.report-file small,.report-file span,.report-file strong{display:block}.report-file small{margin-top:.2rem;color:#8b929b}.report-updated{display:flex;align-items:center;justify-content:center;color:#8a929c;font-size:.78rem}.report-updated .material-icons{margin-right:.25rem;font-size:1rem}.mailbox-page>main{padding:.8rem 1rem 1.5rem;background:#fff}.mailbox-page label{display:block;position:relative;margin-top:1rem}.mailbox-page label>span:first-child{display:block;margin-bottom:.55rem;font-weight:600}.mailbox-page input[type=text],.mailbox-page label>input:not([type]),.mailbox-page label>textarea{width:100%;margin:0;border:1px solid #dfe3e7;border-radius:.3rem;box-shadow:none;background:#fff}.mailbox-page label>input:not([type]){height:3rem;padding:0 .75rem}.mailbox-page label>textarea{height:12rem;padding:.75rem .75rem 2rem;resize:vertical;line-height:1.5}.mailbox-content>small{position:absolute;right:.75rem;bottom:.65rem;color:#9ca3ac}.mailbox-notice,.mailbox-upload{display:grid;grid-template-columns:2.5rem minmax(0,1fr) 1.5rem;align-items:center;gap:.5rem;width:100%;min-height:4.2rem;margin:0;padding:.65rem .75rem;border:1px solid #dfe4e9;border-radius:.35rem;box-shadow:none;color:#343a42;background:#f7f9fb;text-align:left}.mailbox-notice>.material-icons:first-child{color:#2e7954}.mailbox-notice small,.mailbox-notice span,.mailbox-notice strong{display:block;min-width:0}.mailbox-notice small{margin-top:.2rem;color:#78818b}.mailbox-upload{grid-template-columns:2.5rem minmax(0,1fr) auto;margin-top:1rem;border-style:dashed}.mailbox-upload>.material-icons{color:#347bd7}.mailbox-upload small{color:#888f99}.mailbox-anonymous{display:grid!important;grid-template-columns:2rem minmax(0,1fr);align-items:center;gap:.5rem;margin:1rem 0!important;padding:.8rem;border:1px solid #e4e7ea;border-radius:.35rem}.mailbox-anonymous input{width:1.25rem;height:1.25rem;margin:0}.mailbox-anonymous small,.mailbox-anonymous span,.mailbox-anonymous strong{display:block;min-width:0}.mailbox-anonymous small{margin-top:.2rem;color:#858d96}.mailbox-notice-backdrop{display:grid;position:absolute;z-index:60;inset:0;place-items:center;padding:1rem;background:rgba(20,24,29,.52)}.mailbox-notice-dialog{width:min(100%,25rem);padding:1.25rem;border-radius:.5rem;background:#fff;text-align:center}.mailbox-notice-dialog>.material-icons{color:#2f7854;font-size:2.7rem}.mailbox-notice-dialog h2{margin:.6rem 0 .8rem;font-size:1.15rem}.mailbox-notice-dialog p{margin:.65rem 0;color:#555d67;line-height:1.65;text-align:left}.mailbox-notice-dialog small{display:block;margin:.8rem 0;color:#898f98}.mailbox-notice-dialog button{width:100%;min-height:2.9rem;margin:.5rem 0 0;padding:0 1rem;border:0;border-radius:.35rem;box-shadow:none;color:#fff;background:#347bd7}.organization-loading{padding:2rem 1rem;color:#8b929c;text-align:center}.organization-department{display:flex;align-items:center;gap:.6rem;width:100%;min-height:3.4rem;margin:0;padding:0 1rem;border:0;border-bottom:1px solid #eceef1;border-radius:0;box-shadow:none;color:#30353c;background:#fff;text-align:left;text-transform:none}.organization-department>.material-icons:first-child{color:#8b929c;font-size:1.3rem}.organization-department.me,.organization-department.me>.material-icons:first-child{color:#2094f3}.organization-department>span{flex:1 1 auto;min-width:0}.organization-department>span strong{display:block;overflow:hidden;font-weight:500;text-overflow:ellipsis;white-space:nowrap}.organization-department>.material-icons:last-child{color:#b0b6bd}.organization-person{display:flex;align-items:center;gap:.6rem;width:100%;min-height:3.2rem;margin:0;padding:0 1rem;border:0;border-bottom:1px solid #eceef1;border-radius:0;box-shadow:none;color:#30353c;background:#fafbfc;text-align:left;text-transform:none}.organization-person>.material-icons:first-child{color:#b0b6bd;font-size:1.1rem}.organization-person>span{flex:1 1 auto;min-width:0}.organization-person>span strong{overflow:hidden;font-weight:400;text-overflow:ellipsis;white-space:nowrap}.organization-person.me{color:#2094f3}.organization-person.me>span strong{color:#2094f3;font-weight:500}.organization-person>.material-icons:last-child{color:#b0b6bd}.meeting-more{display:flex;align-items:center;flex:none;margin:0;padding:0;border:0;box-shadow:none;color:#b0b6bd;background:transparent;font-size:.85rem}.meeting-attendees{color:#347bd7}.meeting-responded{width:100%;min-height:2.4rem;margin:0;padding:0 1rem;border:0;border-radius:.3rem;box-shadow:none;color:#8b929c;background:#f2f3f5}.meeting-participant-list{margin:.8rem 0 0;padding:.6rem 0 0;border-top:1px solid #eceef1;list-style:none}.meeting-participant-list li{display:flex;align-items:center;justify-content:space-between;min-height:2.3rem}.meeting-participant-list em{padding:.15rem .5rem;border-radius:.25rem;color:#8b929c;background:#f2f3f5;font-size:.72rem;font-style:normal}.meeting-participant-list em.accepted{color:#28784f;background:#e5f5ec}.meeting-participant-list em.declined{color:#ad3d45;background:#fbe9eb}.meeting-participant-list em.tentative{color:#ad6c11;background:#fff2dd}.meeting-status footer{display:flex;gap:.6rem;margin-top:.8rem}.meeting-status footer button{flex:1 1 auto;min-height:2.6rem;margin:0;border:0;border-radius:.3rem;box-shadow:none;color:#347bd7;background:#eaf2fc}.meeting-status footer button.danger{color:#ad3d45;background:#fbe9eb}.meeting-time-range{display:flex;align-items:center;gap:.5rem}.meeting-time-range .report-field{flex:1 1 0}.meeting-time-range span{color:#8b929c}.meeting-selected-attendees{display:flex;flex-wrap:wrap;gap:.4rem;margin:-.4rem 0 .8rem}.meeting-selected-attendees span{display:flex;align-items:center;gap:.25rem;padding:.3rem .5rem;border-radius:999px;background:#eaf2fc;color:#347bd7;font-size:.82rem}.meeting-selected-attendees .material-icons{font-size:.9rem;cursor:pointer}.daily-report-block{margin-bottom:1rem;padding-bottom:1rem;border-bottom:1px solid #eceef1}.daily-report-block-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:.6rem}.daily-report-block-header .material-icons{color:#8b929c;cursor:pointer}.daily-report-date{display:block;margin:-.3rem 0 .5rem;color:#8b929c}.daily-report-add-block{display:flex;align-items:center;justify-content:center;gap:.3rem;width:100%;min-height:2.6rem;margin:0 0 1rem;border:1px dashed #d5d9dd;border-radius:.3rem;box-shadow:none;color:#347bd7;background:#fff}.report-draft-status{margin:0;color:#8b929c;font-size:.82rem;text-align:right}.report-author .role-badge,.report-cards article>header .role-badge{display:inline-block;flex-shrink:0;margin-left:.4rem;padding:.1rem .4rem;border-radius:.25rem;color:#347bd7;background:#eaf2fc;font-size:.72rem;white-space:nowrap}.report-author>span>strong,.report-cards article>header>span>strong{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.workplace-error{padding:.8rem 1rem;color:#ad3d45;text-align:center}.workbench-profile .workbench-avatar-fallback{display:flex;align-items:center;justify-content:center;width:3rem;height:3rem;border-radius:50%;color:#fff;background:#b0b6bd}.mailbox-anonymous-choice{display:flex;gap:1.2rem;justify-content:center;margin-top:.8rem}.mailbox-anonymous-choice label{display:flex;align-items:center;gap:.35rem;color:#58606b;font-size:.88rem}.mailbox-attachment-list{margin:.5rem 0 0;padding:0;list-style:none}.mailbox-attachment-list li{display:flex;align-items:center;justify-content:space-between;min-height:2.2rem;color:#58606b}.mailbox-attachment-list .material-icons{color:#8b929c;cursor:pointer;font-size:1.1rem}@media screen and (max-width:480px){.workbench-apps{grid-template-columns:repeat(4,minmax(0,1fr));gap:1.2rem .35rem}.workbench-banner{grid-template-columns:minmax(0,1fr) 4rem;padding:1rem}.workbench-banner>.material-icons{font-size:3.8rem}.meeting-card dl>div{grid-template-columns:5.8rem minmax(0,1fr)}.report-cards>button{grid-template-columns:3.2rem minmax(0,1fr) auto 1.2rem;gap:.45rem}}*{box-sizing:border-box;padding:0;margin:0;font-family:Roboto,Arial,sans-serif;text-rendering:optimizeLegibility;scrollbar-width:thin;scrollbar-color:var(--clr-scrollbar-thumb) var(--clr-scrollbar-track)}body,html{height:100%;width:100%;height:100dvh;width:100dvw;font-size:10pt;color:var(--clr-text-primary);overflow:hidden;background-color:var(--clr-body-background);box-shadow:inset 0 10rem var(--clr-header-stripe-bg)}tt{font-family:Roboto Mono,Courier,monospace}a{color:var(--clr-primary-accent);text-decoration:none}a:focus,a:hover{color:var(--clr-primary-accent-hover);text-decoration:underline}a.danger{color:var(--clr-danger-text)}a.danger:focus,a.danger:hover{color:var(--clr-danger-text-hover)}a.gray:focus,a.gray:hover{color:var(--clr-text-tertiary)}form{padding:.5rem .75rem;line-height:1.5}form,label{font-size:1.1rem}label{margin-right:auto}label.small{font-size:.95rem}label.large,label.small{color:var(--clr-primary-action)}label.large{font-size:1.1rem}label.invalid{color:var(--clr-danger-text)}img.avatar{width:100%;height:100%;border-radius:50%}img.avatar.self{padding:7%}button{display:inline-block;font-family:inherit;font-size:1.05rem;height:2.2rem;line-height:2.2rem;font-weight:400;text-align:center;vertical-align:middle;touch-action:manipulation;cursor:pointer;border:1px solid var(--clr-transparent);white-space:nowrap;padding-left:1.2rem;padding-right:1.2rem;border-radius:4px;box-shadow:0 1px 3px 0 var(--clr-shadow-button);text-transform:uppercase;min-width:5rem;margin:0 .35rem}button[disabled],html input[disabled]{cursor:default;opacity:.65;box-shadow:none;filter:grayscale(50%)}button.round{border-radius:50%;width:3rem;height:3rem;padding:0;margin:0;line-height:3.5rem;outline:none;box-shadow:0 2px .5rem 0 var(--clr-shadow-button);z-index:2}button.round.small{width:2rem;height:2rem;line-height:2rem;box-shadow:0 2px .35rem 0 var(--clr-shadow-button)}button.danger:active:not([disabled]),button.danger:hover:not([disabled]),button.positive:active:not([disabled]),button.positive:hover:not([disabled]),button.primary:active:not([disabled]),button.primary:hover:not([disabled]){color:var(--clr-bright)}button:active{box-shadow:inset 0 3px .5rem var(--clr-shadow-button-inset-active)}button:active.round.small{box-shadow:inset 0 2px .35rem var(--clr-shadow-button-inset-active)}button.primary{background-color:var(--clr-button-primary-bg);color:var(--clr-button-primary-text)}button.primary:active:not([disabled]),button.primary:hover:not([disabled]){background-color:var(--clr-button-primary-hover-bg)}button.secondary{background-color:var(--clr-button-secondary-bg);color:var(--clr-button-secondary-text)}button.secondary:active:not([disabled]),button.secondary:hover:not([disabled]){color:var(--clr-button-secondary-text);background-color:var(--clr-button-secondary-hover-bg)}button.outline{background-color:var(--clr-transparent);color:var(--clr-button-outline-text);border:1px solid var(--clr-button-outline-border);box-shadow:none}button.outline:active:not([disabled]),button.outline:hover:not([disabled]){color:var(--clr-button-outline-text);background-color:var(--clr-button-outline-hover-bg)}button.danger{background-color:var(--clr-button-danger-bg);color:var(--clr-button-danger-text)}button.danger:active:not([disabled]),button.danger:hover:not([disabled]){background-color:var(--clr-button-danger-hover-bg)}button.positive{background-color:var(--clr-button-positive-bg);color:var(--clr-button-positive-text)}button.positive:active:not([disabled]),button.positive:hover:not([disabled]){background-color:var(--clr-button-positive-hover-bg)}input,textarea{font-family:inherit;font-size:1.1rem;background:var(--clr-transparent);border:none;margin:.25rem 0;padding:0 .125rem;outline:none;color:var(--clr-input-text)}input[type=date],input[type=email],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],textarea{height:2.5rem;border-bottom:1px solid var(--clr-input-border);box-shadow:0 1px 0 var(--clr-input-shadow);transition:all .1s ease-in-out,height 0s}input[type=date],input[type=email],input[type=password],input[type=tel],input[type=text],input[type=time],textarea{width:100%}input[type=date]:focus,input[type=email]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,textarea:focus{border-bottom:1px solid var(--clr-input-border-focus);box-shadow:0 1px 0 var(--clr-input-shadow-focus)}input[type=email].invalid,input[type=password].invalid,input[type=search].invalid,input[type=tel].invalid,input[type=text].invalid,textarea.invalid{border-bottom:1px solid var(--clr-input-invalid-border);box-shadow:0 1px 0 var(--clr-input-invalid-shadow)}input[type=email]::placeholder,input[type=password]::placeholder,input[type=search]::placeholder,input[type=tel]::placeholder,input[type=text]::placeholder,textarea::placeholder{color:var(--clr-input-placeholder-text);font-weight:lighter}input[type=password].with-icon-right,input[type=text].with-icon-right{margin-right:-2rem;padding-right:2rem}input[type=password].with-icon-left,input[type=text].with-icon-left{margin-left:-1.45rem;padding-left:1.75rem}textarea{resize:none;padding-top:.45rem}input[type=range]{width:90%}option{padding:0}datalist{display:flex;flex-direction:row;justify-content:space-between;width:100%}::-webkit-scrollbar{width:.6rem}::-webkit-scrollbar-track{-webkit-box-shadow:inset 0 0 6px var(--clr-shadow-scrollbar-track-webkit);box-shadow:inset 0 0 6px var(--clr-shadow-scrollbar-track-webkit);background-color:var(--clr-scrollbar-track)}::-webkit-scrollbar-thumb{background-color:var(--clr-scrollbar-thumb)}ul.menu{position:absolute;list-style:none;width:12rem;border-radius:.2rem;padding-top:.35rem;padding-bottom:.35rem;box-shadow:0 1px 6px var(--clr-shadow-dialog);background-color:var(--clr-bright);z-index:4}ul.menu>li{padding:.75rem .75rem .75rem 1.25rem;cursor:pointer}ul.menu>li:hover{background-color:var(--clr-menu-item-hover-bg)}ul.menu>li.separator{padding:0;margin:2px;height:1px}ul.menu>li.separator,ul.menu>li.separator:hover{background-color:var(--clr-border-light)}ul.menu>li.disabled{pointer-events:none;opacity:.6}.menuTrigger{position:absolute;right:.25rem;width:1.4rem;height:1.4rem;line-height:1.4rem;border-radius:.7rem;text-align:center;opacity:0;background-color:inherit;user-select:none}.menuTrigger a{color:var(--clr-text-secondary)}li .menuTrigger{top:.25rem}li:hover .menuTrigger{opacity:.9}li:hover .menuTrigger a{color:var(--clr-text-secondary)}div.alert-container{position:absolute;display:flex;padding:1rem;justify-content:center;align-items:center;width:100%;height:100%;overflow:hidden;background:var(--clr-alert-overlay-bg);z-index:3}div.alert{position:relative;background-color:var(--clr-bright);box-shadow:.15rem .15rem 1.5rem var(--clr-shadow-dialog);padding:1rem;border-radius:.3rem;margin:auto 2rem;width:fit-content;height:min-content;min-width:16rem;max-width:24rem;max-height:40rem;overflow:hidden}div.alert-container .title{position:relative;color:var(--clr-primary-accent);font-size:1.2rem;font-weight:500;width:100%;padding:0 0 .35rem}div.alert-container .title.with-control{position:relative;display:flex;padding:.5rem .5rem 0;align-items:center;flex:none;justify-content:space-between}div.alert-container .content{position:relative;padding:.5rem 0}div.trusted-badge{margin:.25rem 0}.blue{color:var(--clr-primary-accent)!important}.lt-blue{color:var(--clr-lt-fg-1)!important}.orange{color:var(--clr-std-orange)!important}.verified-color{color:var(--clr-verified-accent)!important}.staff-color{color:var(--clr-staff-accent)!important}.danger-color{color:var(--clr-warning-accent)!important}.green{color:light-dark(#388e3c,#1a9a1d)}.white{color:var(--clr-bright)!important}.red{color:var(--clr-danger-text)!important}.badge-inv{color:var(--clr-badge-inv)!important}.gray{color:var(--clr-text-secondary)!important}.light-gray{color:var(--clr-text-placeholder)!important}.large{font-size:120%}.small{font-size:90%}.float-right{position:absolute;right:.5rem}.hr{display:block;width:100%;height:.45rem;min-height:.3rem;background-color:var(--clr-background-light-gray);box-shadow:inset 0 .25rem .25rem -.25rem var(--clr-shadow-hr-inset),inset 0 -.25rem .25rem -.25rem var(--clr-shadow-hr-inset)}.hr.thin{height:.015rem}.divider{display:inline-block;width:1px;margin:0 .25rem;border-left:1.5px groove var(--clr-border-muted)}.ellipsized{overflow-x:hidden;white-space:nowrap;text-overflow:ellipsis}.flat-button{display:flex;align-items:center;margin:.15rem .35rem;height:2em;line-height:2em;font-weight:400;text-align:center;vertical-align:middle;touch-action:manipulation;white-space:nowrap;text-transform:uppercase;background-color:var(--clr-bright);z-index:1}.flat-button:focus,.flat-button:hover{text-decoration:none}.flat-button.disabled,.flat-button.disabled:focus,.flat-button.disabled:hover{color:var(--clr-primary-accent);cursor:default;opacity:.75;filter:grayscale(75%)}.flat-button.faux{display:inline-block;height:1.4em;line-height:1.4em;text-transform:none;padding:0 .35rem;margin:.15rem;border:1px solid var(--clr-text-tertiary);border-radius:3px;background-color:var(--clr-background-extra-light-gray)}.action-button{display:block;position:absolute;color:var(--clr-text-secondary);font-size:1.8rem;background-color:var(--clr-background-off-white);border-radius:50%;width:3.25rem;height:3.25rem;min-width:unset;min-height:unset;padding:0;margin:0;outline:none;box-shadow:0 2px .5rem 0 var(--clr-shadow-button);transition:all .15s;z-index:2}.action-button:active:not([disabled]),.action-button:focus,.action-button:hover,.action-button:hover:not([disabled]){color:var(--clr-text-tertiary-activated);background-color:var(--clr-primary-action-hover)}.action-button:active{color:var(--clr-text-tertiary-activated);background-color:var(--clr-primary-action)}.action-button.hidden{width:1px;height:1px;font-size:1px;line-height:1px;right:2.625rem;bottom:2.625rem;opacity:0}.group-focus{outline:none}#mountPoint{height:100%;padding-top:1rem;padding-bottom:1rem}#noscript-message{position:relative;display:flex;flex-direction:column;max-width:30rem;overflow:hidden;margin-top:5rem;margin-left:auto;margin-right:auto;border-radius:.25rem;box-shadow:1px 2px .3rem var(--clr-shadow-gray-strong);background-color:var(--clr-bright)}#noscript-title{padding:1rem;font-size:120%;font-weight:bolder;background-color:var(--clr-primary-accent);color:var(--clr-primary-light-blue)}#noscript-content{padding:1rem}#app-container{position:relative;display:flex;height:100%;max-width:84rem;overflow:hidden;margin-left:auto;margin-right:auto;border-radius:.25rem;box-shadow:1px 2px .3rem var(--clr-shadow-gray-strong);background-color:var(--clr-bright)}#sidepanel{flex-grow:0;flex-shrink:0.3;width:24rem;min-width:18rem}#sidepanel,#topic-view{position:relative;display:flex;flex-direction:column;height:100%}#topic-view{overflow-x:hidden;width:100%}#info-view{position:relative;display:flex;flex-direction:column;flex-grow:0;flex-shrink:0.3;width:24rem;min-width:18rem;height:100%}.center-medium-text{width:100%;height:100%;display:flex;align-items:center;justify-content:center;text-align:center;font-size:1.4rem}.nodisplay{display:none!important}.quoted{margin-left:1rem}.space-right{margin-right:.25rem}.rounded-container{position:relative;background-color:var(--clr-background-off-white);border-radius:.1rem;padding:.25rem;margin:.25rem}div.in-place-edit,span.in-place-edit{display:inline-block;font-family:inherit;font-size:1rem;margin:.25rem .125rem 0;width:100%;vertical-align:baseline;border-bottom:1px dashed var(--clr-border-muted);cursor:pointer}.in-place-edit.short{display:inline-flex;align-items:center;font-size:1.1rem;height:2.5rem;line-height:2.25;white-space:nowrap}.in-place-edit.disabled{border-bottom:none;cursor:default;color:var(--clr-text-disabled)}.in-place-edit.placeholder{color:var(--clr-text-placeholder)}.in-place-edit span{display:inline-block}.in-place-edit.short span{white-space:nowrap;text-overflow:ellipsis;overflow-x:hidden;width:100%}.in-place-edit .material-icons{display:inline-block}.material-icons.invalid{color:var(--clr-error-text-muted)}textarea.in-place-edit{height:unset;font-size:unset}input.in-place-edit.invalid{color:var(--clr-error-text-muted)}table.permission-editor{width:100%}table.permission-editor td{margin:.35rem 0;font-size:1.1rem;vertical-align:middle}table.permission-editor td.checkbox{text-align:center;color:var(--clr-text-tertiary)}.permission-editor .material-icons{font-size:1.3rem;line-height:1.3rem}.material-icons.clickable{cursor:pointer;border-bottom:none}.clickable{border-bottom:1px dashed var(--clr-border-muted)}.clean-clickable,.clickable,.image-clickable{cursor:pointer}.panel-title{font-size:1.4rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.panel-title.deleted{text-decoration:line-through}.caption-panel{flex:none;height:var(--caption-panel-height);white-space:nowrap;box-shadow:0 1px 2px var(--clr-shadow-gray-strong);overflow-x:hidden;z-index:1}.caption-panel,.dialog-buttons{display:flex;align-items:center;padding:.5rem}.dialog-buttons{justify-content:flex-end}.dialog-buttons button{margin:0 .5rem}.avatar-box{position:relative;flex-grow:0;flex-shrink:0}.bot-form{display:flex;flex-direction:column;overflow:hidden;min-width:8rem;line-height:2rem}.bot-form div{min-height:2rem;vertical-align:middle}.bot-form button{display:inline-block;margin:.15rem;background-color:var(--clr-blue-wash-light);color:var(--clr-primary-accent);font-size:inherit;text-transform:none;height:2rem;line-height:2rem}.bot-form>div>button{width:auto}.bot-form button:active,.bot-form button:focus,.bot-form button:hover{outline:0;color:var(--clr-dk-fg-5);background-color:var(--clr-primary-wash)}#side-caption-panel{background-color:var(--clr-primary-accent);color:var(--clr-primary-light-blue)}#side-caption-panel a{color:var(--clr-primary-light-blue);padding-left:.5rem;line-height:1}#side-caption-panel :hover{color:var(--clr-bright)}#sidepanel-title{margin-right:auto;margin-left:.5rem}#self-avatar{width:3rem;min-width:3rem;height:3rem;flex:none;border-radius:50%}#self-avatar.avatar-box{font-size:1.8rem}#settings-form ul{list-style:none}#settings-form li{vertical-align:middle}#settings-form label{display:inline-block;padding-left:.5rem}input#host-name{width:100%}input.search{text-indent:1.4rem}i.search{position:absolute;left:.6rem}.info-box{position:relative;display:none;overflow:hidden;padding:1rem 1.5rem .75rem .5rem}.info-box>span{line-height:1.35rem}.info-box .cancel{position:absolute;right:.25rem;top:.5rem}.info-box.error{display:flex;background-color:var(--clr-error-bg-light);border-bottom:1px solid var(--clr-error-border-light);color:var(--clr-error-text-dark)}.info-box.warning{display:flex;background-color:var(--clr-warning-bg-light);border-bottom:1px solid var(--clr-warning-border-light);color:var(--clr-warning-text-dark)}.info-box.info{display:flex;background-color:var(--clr-info-bg-light);border-bottom:1px solid var(--clr-info-border-light);color:var(--clr-info-text-dark)}.info-box .cancel .material-icons{font-size:1rem;color:var(--clr-text-secondary)}.info-box.error .cancel .material-icons{color:var(--clr-error-text-dark)}.info-box .icon{display:block;margin-right:.5rem}.info-box .icon .material-icons{font-size:1.5rem}.info-box.warning .icon .material-icons{color:var(--clr-warning-icon)}.info-box.error .icon .material-icons{color:var(--clr-error-icon)}.info-box.info .icon .material-icons{color:var(--clr-info-text-dark)}.flex-column{display:flex;flex-direction:column;height:100%;overflow-x:hidden}.flex-column.narrow{height:auto}.scrollable-panel{overflow-x:hidden;overflow-y:auto;margin-top:.25rem;margin-bottom:auto;height:100%;scroll-behavior:smooth}.contact-box{list-style:none;overflow-x:hidden}.contact-box>li{position:relative;display:flex;padding:.35rem .25rem .5rem .35rem;min-height:3.75rem;flex-grow:0;flex-shrink:0;background:linear-gradient(to right,var(--clr-transparent) 3.5rem,var(--clr-background-extra-light-gray) 1px,var(--clr-background-extra-light-gray) 100%) 0 100% /100% 1px no-repeat,var(--clr-bright);overflow-x:hidden;cursor:pointer}.contact-box.small>li{padding:.25rem .25rem .35rem .35rem;min-height:2.5rem}.contact-box>li:last-child{background:var(--clr-bright)}.contact-box>li:hover{background-color:var(--clr-background-blue-gray-50)}.contact-box>li.selected{background-color:var(--clr-background-blue-gray-100)}.contact-box>li.tpinned{border-right:1.5px solid var(--clr-secondary-accent);background-color:var(--clr-background-blue-gray-75)}.contact-box>li.action{min-height:2.5rem;border-bottom:none}.contact-box .text-box{overflow-x:hidden}.contact-box .contact-title{font-size:1.05rem;text-overflow:ellipsis;overflow-x:hidden;white-space:nowrap}.contact-box .contact-title.deleted{opacity:.66;text-decoration:line-through;text-decoration-thickness:.5px}.contact-box .contact-comment{font-weight:lighter}.contact-box .contact-comment,.contact-box .contact-comment>span{text-overflow:ellipsis;overflow-x:hidden;white-space:nowrap}.contact-box .contact-comment b{font-weight:bolder}.contact-box .contact-comment .material-icons{margin-bottom:.2rem;color:light-dark(#777,#666)}.contact-box .avatar-box{position:relative;margin-right:.35rem;width:3rem;min-width:3rem;height:3rem;min-height:3rem;font-size:1.8rem;overflow:hidden;padding:0}.contact-box .avatar{width:3rem;height:3rem;border-radius:50%}.contact-box.small .avatar-box{width:2rem;min-width:2rem;height:2rem;min-height:2rem;font-size:1.2rem}.contact-box.small .avatar{width:2rem;height:2rem}.avatar.deleted{opacity:.75;filter:grayscale(1)}.contact-box .avatar-box>.material-icons{display:flex;flex-direction:column;justify-content:space-around;font-size:2.4rem;line-height:3rem}.contact-box .avatar-box>.checkmark.material-icons{line-height:unset}.contact-box.small .avatar-box>.material-icons{font-size:1.6rem;line-height:2rem}.contact-box .text-box{display:flex;flex-direction:column;justify-content:space-around}.contact-box .text-box>div{display:flex;flex-direction:row;align-items:center}.contact-box .action-text{text-align:center;font-size:.95rem;line-height:2.5rem;margin:0 auto;cursor:pointer}img.channel{width:1.25rem;height:1.25rem;vertical-align:middle;margin-left:.3rem}.large img.channel{width:1.45rem;height:1.45rem;margin-left:.5rem;margin-bottom:.15rem}.avatar-box .offline,.avatar-box .online{position:absolute;width:.75rem;min-width:.75rem;height:.75rem;min-height:.75rem;flex-shrink:0;border-radius:50%;right:0;bottom:.1rem}.avatar-box .online{background-color:var(--clr-online)}.avatar-box .offline{background-color:var(--clr-offline)}.online.typing{animation:typing .5s infinite}@keyframes typing{0%,to{transform:translateX(0)}50%{transform:translateX(-.3rem)}}.contact-box .checkmark.material-icons{color:var(--clr-secondary-accent);overflow:visible;border:0 solid var(--clr-transparent);padding-left:.5px}.contact-box .checkmark.material-icons,.contact-box .deleted.material-icons{width:1.5rem;height:1.5rem;font-size:1.5rem;position:absolute;right:0;bottom:0;background-color:var(--clr-bright)}.contact-box .deleted.material-icons{color:var(--clr-text-tertiary)}#add-topic{position:absolute;right:1rem;bottom:1rem}.panel-form{display:flex;position:relative;flex-direction:column;padding:0}.panel-form-row{align-items:center;flex-shrink:0}.panel-form-column,.panel-form-row{display:flex;position:relative;padding:.5rem .75rem;line-height:1.5;flex-grow:0}.panel-form-column{flex-direction:column;overflow-x:clip}.panel-form-column>.panel-form-column,.panel-form-row>.panel-form-column{padding:0!important;flex-grow:1}.panel-form-column>.panel-form-row,.panel-form-row>.panel-form-row{padding:0!important}.panel-form-column>.group{margin-top:.25rem;margin-bottom:.25rem}.avatar-upload{position:relative;display:flex;flex-direction:column;flex-shrink:0;width:10rem;height:10.5rem;font-size:4.5rem;padding:0;margin:.25rem}.avatar-upload.read-only{height:auto}.avatar-upload .clear-avatar{position:absolute;font-size:1rem;top:0;right:0;opacity:0;background-image:radial-gradient(var(--clr-bright) 40%,var(--clr-transparent) 75%)}.avatar-upload:hover .clear-avatar{opacity:1}.avatar-upload .avatar-box{position:relative;width:10rem;height:10rem}.avatar-upload .avatar-box>.material-icons{position:relative;font-size:4rem;line-height:10rem;vertical-align:middle}.avatar-upload:after{content:".";display:block;height:0;clear:both;visibility:hidden}.avatar-upload .blank{text-align:center;vertical-align:middle;border:1px dashed var(--clr-text-tertiary);font-size:1rem;flex-shrink:0;color:var(--clr-text-tertiary);line-height:10rem}.avatar-upload .blank,.avatar-upload img.preview{width:10rem;height:10rem;overflow:hidden;border-radius:50%}.avatar-upload img.preview:before{position:absolute;content:" ";display:flex;font-size:.9rem;font-weight:lighter;color:var(--clr-text-primary);top:0;left:0;width:10rem;height:10rem;justify-content:center;background:50% no-repeat url(../img/broken_image.png);border-radius:50%}.avatar-upload .inputfile.hidden{width:.1px;height:.1px;opacity:0;overflow:hidden;position:absolute}.avatar-upload .inputfile+label.round{display:flex;position:absolute;align-items:center;justify-content:center;cursor:pointer;width:3rem;height:3rem;line-height:3rem;border-radius:50%;box-shadow:0 1px 4px var(--clr-shadow-dialog);bottom:0;right:0;background-color:var(--clr-background-off-white);z-index:1}.avatar-upload .inputfile:focus+label.round{outline:none}.avatar-upload .inputfile+label:hover,.avatar-upload .inputfile:focus+label{background-color:var(--clr-primary-wash);color:var(--clr-primary-accent)}#topic-caption-panel{background-color:var(--clr-background-blue-gray-300)}#topic-caption-panel a{color:var(--clr-text-secondary);text-decoration:none}#topic-caption-panel :hover{color:var(--clr-dark)}#topic-caption-panel .avatar-box{position:relative;width:3rem;min-width:3rem;height:3rem;font-size:1.8rem;margin-right:.35rem}#topic-caption-panel .avatar-box>.material-icons{font-size:2.4rem;line-height:3rem}#topic-title-group{overflow-x:hidden}#hide-message-view{display:block;margin-right:.35rem}#topic-users{margin-left:auto;margin-right:1rem;width:min-content}#topic-users .avatar-box{display:inline-block;width:1.8rem;min-width:1.8rem;height:1.8rem;min-height:1.8rem;font-size:1rem;margin-right:.2rem}#topic-users .avatar-box>.material-icons{font-size:1.4rem;line-height:1.8rem}#topic-users>span{line-height:2rem;vertical-align:top}.qr-code{align-self:center}#messages-container{overflow-y:hidden;height:100%}#messages-container:before{content:"";position:absolute;top:0;left:0;height:100%;width:100%;background-color:var(--clr-background-blue-gray-50);background-image:var(--wallpaper-url);background-repeat:var(--wallpaper-repeat);background-size:var(--wallpaper-size);background-position:var(--wallpaper-position);filter:blur(var(--wallpaper-blur)) invert(var(--wallpaper-invert)) brightness(var(--wallpaper-brightness));pointer-events:none;z-index:0}#messages-panel{position:relative;padding:.75rem;overflow-y:scroll;height:100%}#write-only-background{position:absolute;top:0;left:0;height:100%;width:100%;background-image:repeating-linear-gradient(45deg,hsla(0,0%,100%,.5),hsla(0,0%,100%,.5) .5rem,hsla(0,0%,100%,.8) 0,hsla(0,0%,100%,.8) 1rem)}#write-only-note{top:50%;transform:translate(-50%,-50%);height:auto;width:auto}#peer-messaging-disabled-note,#write-only-note{position:absolute;left:50%;background-color:var(--clr-bright);color:var(--clr-text-primary);line-height:2rem;text-align:center;padding:.75rem 1.25rem;border-radius:.5rem;box-shadow:0 1px .5px var(--clr-shadow-button-inset-active)}#peer-messaging-disabled-note{bottom:4.3rem;transform:translate(-50%);width:fit-content}#relationship-status-note{text-align:center;color:var(--clr-text-secondary);font-size:.85rem;padding:.5rem 1.25rem;flex:none}#relationship-status-note a{color:var(--clr-primary-accent)}.chat-box .author .role-badge{display:inline-block;font-style:normal;font-size:.7rem;line-height:1.3;padding:0 .4em;border-radius:.25rem;margin-left:.35em;color:#fff;vertical-align:middle}.chat-box .author .role-badge.owner{background-color:#e0a82e}.chat-box .author .role-badge.admin{background-color:#3ba55c}#group-announcement-banner,#group-join-request-banner{display:flex;align-items:center;gap:.5rem;margin:.5rem .75rem 0;padding:.6rem 1rem;background-color:var(--clr-bright);border-radius:.5rem;box-shadow:0 1px 3px rgba(0,0,0,.12);color:var(--clr-bubble-text);flex:none}#group-announcement-banner i:first-child,#group-join-request-banner i:first-child{color:var(--clr-warning-text,#e0a82e)}#group-announcement-banner span,#group-join-request-banner span{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.9rem}#group-announcement-banner i:last-child,#group-join-request-banner i:last-child{color:var(--clr-text-secondary)}.message-fail-icon{align-self:flex-end;margin:0 .25rem .6rem 0}.message-fail-reason{font-size:.8rem;color:var(--clr-text-secondary);text-align:right;margin-top:.25rem}.message-fail-reason a{color:var(--clr-primary-accent)}.message-group-invitation button{display:block;margin-top:.4rem;padding:.3rem .9rem;border:0;border-radius:1rem;color:#fff;background:var(--clr-primary-accent);font-size:.85rem}.message-group-invitation button:disabled{opacity:.6}.message-group-invitation em{display:block;margin-top:.4rem;color:var(--clr-text-secondary);font-style:normal;font-size:.85rem}.message-group-invitation-error{display:block;margin-top:.25rem;color:#df4e46;font-size:.78rem}.select-checkbox{align-self:center;margin:0 .5rem 0 .25rem;color:var(--clr-text-secondary);cursor:pointer;flex:none}.select-checkbox.blue{color:var(--clr-primary-accent)}.chat-box li.right .select-checkbox{margin-right:auto}#messages-container{position:relative}#messages-container div.toast{bottom:.75rem}#batch-action-bar{display:flex;flex-direction:column;flex:none;background-color:var(--clr-background-blue-gray-50);padding:.5rem .6rem}#batch-action-count{text-align:center;color:var(--clr-text-secondary);font-size:.85rem;padding-bottom:.4rem}#batch-action-buttons{display:flex;justify-content:space-around}#batch-action-buttons a{display:flex;flex-direction:column;align-items:center;font-size:.75rem;color:var(--clr-text-primary)}#batch-action-buttons a.disabled{color:var(--clr-text-secondary);opacity:.5;pointer-events:none}#batch-action-cancel{text-align:center;margin-top:.5rem;color:var(--clr-text-secondary);font-size:.85rem}.batch-forward-confirm{max-width:24rem}.forward-dialog.batch-forward-confirm{height:auto}.batch-forward-target{display:flex;align-items:center;justify-content:space-between;padding:.6rem 0}.contact-box>li.batch-forward-target{padding:.6rem 0;background:linear-gradient(to right,var(--clr-background-extra-light-gray),var(--clr-background-extra-light-gray)) 0 100% /100% 1px no-repeat,var(--clr-bright)}.batch-forward-comment{box-sizing:border-box;width:100%;margin:.5rem 0;padding:.6rem;border:0;border-radius:.4rem;color:var(--clr-text-primary);background-color:var(--clr-background-blue-gray-50);font:inherit}.batch-forward-comment::placeholder{color:var(--clr-text-secondary)}.batch-forward-actions{display:flex;gap:.5rem;margin-top:.5rem}.batch-forward-actions button{flex:1;padding:.6rem;border-radius:.4rem;border:none;background-color:var(--clr-background-blue-gray-50);color:var(--clr-text-primary)}.batch-forward-actions button.primary{background-color:var(--clr-primary-accent);color:var(--clr-bright)}#send-message-panel{display:flex;align-items:center;flex:none;padding:.4rem .4rem .4rem .6rem;background-color:var(--clr-background-blue-gray-50);z-index:2;min-height:4rem}#send-message-panel #writing-disabled{width:100%;text-align:center;font-size:1.2rem}#send-message-wrapper{position:relative;background-color:var(--clr-background-blue-gray-50);box-shadow:0 -2px 3px -1px var(--clr-shadow-panel-divider);z-index:2}#mention-picker{position:absolute;left:.5rem;right:.5rem;bottom:100%;margin-bottom:.3rem;max-height:12rem;overflow-y:auto;background:#fff;border-radius:.4rem;box-shadow:0 -.2rem .6rem rgba(0,0,0,.15);z-index:3}.mention-picker-row{padding:.6rem .9rem;cursor:pointer}.mention-picker-row.active,.mention-picker-row:hover{background:var(--clr-background-blue-gray-50)}.mention-picker-empty{padding:.6rem .9rem;color:var(--clr-text-tertiary)}#attach-panel,#emoji-panel{flex:none;background:#fff;border-top:1px solid var(--clr-shadow-panel-divider);max-height:12rem;overflow-y:auto}#emoji-panel-tabs{display:flex;padding:.4rem .6rem 0}#emoji-panel-tabs a{padding:.3rem .8rem;font-size:1.3rem;border-bottom:2px solid transparent;text-decoration:none}#emoji-panel-tabs a.active{border-bottom-color:var(--clr-primary-accent)}#emoji-panel-grid{display:flex;flex-wrap:wrap;padding:.4rem}#emoji-panel-grid a{width:2.4rem;height:2.4rem;display:flex;align-items:center;justify-content:center;font-size:1.4rem;text-decoration:none}#emoji-panel-footer{display:flex;align-items:center;justify-content:space-between;padding:.4rem .8rem .6rem;border-top:1px solid var(--clr-shadow-panel-divider)}#emoji-panel-footer a{display:flex;align-items:center;justify-content:center;width:2.4rem;height:2.4rem;text-decoration:none;color:var(--clr-text-tertiary)}#emoji-panel-footer a.emoji-panel-send{color:light-dark(#90caf9,#4a7ba6)}#emoji-panel-footer a.emoji-panel-send.active{color:var(--clr-primary-accent)}.emoji-panel-empty{width:100%;padding:1rem;text-align:center;color:var(--clr-text-tertiary)}#attach-panel{display:grid;grid-template-columns:repeat(4,1fr);gap:.6rem;padding:1rem .6rem}#attach-panel a{display:flex;flex-direction:column;align-items:center;gap:.3rem;font-size:.8rem;color:var(--clr-text-primary);text-decoration:none}#attach-panel a .material-icons{width:3rem;height:3rem;display:flex;align-items:center;justify-content:center;border-radius:50%;background:var(--clr-background-blue-gray-50);font-size:1.5rem}#attach-panel a.disabled{opacity:.4;pointer-events:none}.send-message-picker-backdrop{position:fixed;inset:0;z-index:20;display:flex;align-items:flex-end;background:rgba(0,0,0,.35)}.send-message-picker-backdrop>section{width:100%;max-height:70vh;display:flex;flex-direction:column;background:#fff;border-radius:.6rem .6rem 0 0}.send-message-picker-backdrop header{display:flex;align-items:center;justify-content:space-between;padding:.9rem 1rem;font-weight:600;border-bottom:1px solid var(--clr-shadow-panel-divider)}.send-message-picker-backdrop header a{color:var(--clr-text-tertiary)}.send-message-picker-list{overflow-y:auto;padding:.4rem 0}.send-message-picker-list a{display:block;padding:.75rem 1rem;color:var(--clr-text-primary);text-decoration:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.send-message-picker-empty{padding:1.5rem;text-align:center;color:var(--clr-text-tertiary)}#sendMessage{overflow:hidden;overflow-wrap:break-word}#send-message-panel .material-icons{min-width:2rem;font-size:1.6rem}#send-message-panel .material-icons.secondary,i.material-icons.disabled{color:var(--clr-text-tertiary)!important}#send-message-panel .material-icons.secondary:hover{color:var(--clr-primary-accent)!important}#send-message-input{overflow:hidden auto;min-height:2.5rem;max-height:6rem}.accept-invite-panel{background-color:var(--clr-background-blue-gray-50);box-shadow:0 -2px 3px -1px var(--clr-shadow-panel-divider);width:100%;z-index:1}.accept-invite-panel .title{font-size:1.2rem;padding:.75rem}.accept-invite-panel .footer{background-color:var(--clr-background-off-white);white-space:nowrap;padding-bottom:.35rem;text-align:center}.accept-invite-panel button{font-size:1rem;height:2em;line-height:2em;border:1px solid var(--clr-transparent);margin:.25rem;box-shadow:0 1px 2px 0 var(--clr-shadow-button)}#dummy-view{width:100%;height:100%;display:flex;align-items:center;justify-content:center;background-color:var(--clr-cyan-50);text-align:center}#dummy-view-icon{display:grid;place-items:center;width:6.5rem;height:6.5rem;margin:0 auto .8rem;border-radius:1.2rem;color:#fff;background:#2878d6}#dummy-view-icon .material-icons{font-size:4.2rem}#dummy-view h2{margin:0;font-size:1.25rem}#dummy-view p{margin:.25rem 0 0;color:#777f89}#image-preview{position:absolute;display:flex;flex-direction:column;top:0;left:0;width:100%;height:100%;z-index:2;background-color:var(--clr-overlay-white-strong)}#preview-caption-panel{display:flex;justify-content:space-between;background-color:var(--clr-background-blue-gray-50);box-shadow:0 1px 2px var(--clr-shadow-silver-subtle);line-height:2rem;flex-shrink:0;flex-grow:0;height:var(--caption-panel-height);padding:1rem .75rem 0;font-size:120%;z-index:3}#preview-caption-panel a{color:var(--clr-text-secondary);text-decoration:none}#preview-caption-panel a:hover{color:var(--clr-dark)}#image-preview-container{display:flex;justify-content:space-around;align-items:center;overflow:hidden;background-color:var(--clr-gray-medium-bg);width:100%;height:100%}#image-preview-footer{display:flex;justify-content:space-around;height:3.2rem;line-height:1.5rem;background-color:var(--clr-background-blue-gray-50);box-shadow:0 -2px 3px -1px var(--clr-shadow-panel-divider);flex-grow:0;flex-shrink:0;z-index:1}#image-preview-container .material-icons{font-size:6rem}#image-preview-container .doc-card{display:flex;flex-direction:column;overflow-x:hidden;background-color:var(--clr-std-ltgray);padding:1rem;border-radius:.5rem;max-width:30rem}.doc-card .avatar-box{width:10rem;height:10rem;font-size:4.5rem}.doc-card .avatar-box .material-icons{line-height:10rem}#video-container{position:absolute;display:flex;flex-direction:column;justify-content:space-between;top:0;left:0;width:100%;height:100%;background-color:var(--clr-overlay-white-strong);z-index:200}#video-container.minimized{left:auto;top:1rem;right:1rem;width:18rem;height:18rem;background-color:unset;user-select:none;cursor:grab;box-shadow:.15rem .15rem 1.5rem var(--clr-shadow-dialog);border-radius:.5rem}#video-container.audio-minimized{width:auto;height:auto;background-color:unset;box-shadow:none}.call-pill{display:flex;align-items:center;gap:.5rem;background-color:var(--clr-bright);border-radius:999px;padding:.5rem .75rem .5rem 1rem;box-shadow:.15rem .15rem 1.5rem var(--clr-shadow-dialog);cursor:grab}.call-pill>.material-icons{color:var(--clr-badge-green-text);font-size:1.3rem}.call-pill-duration{color:var(--clr-text-primary);font-variant-numeric:tabular-nums;font-size:.9rem;white-space:nowrap}.call-pill-row{display:flex;align-items:center;gap:.5rem}.audio-minimized button.full-screen{position:static;width:1.5rem;height:1.5rem}.audio-minimized button.full-screen .material-icons{font-size:1.1rem}.call-duration{position:absolute;top:1.5rem;left:50%;transform:translateX(-50%);z-index:2;color:#fff;background-color:var(--clr-shadow-dialog);padding:.3rem 1rem;border-radius:999px;font-variant-numeric:tabular-nums;font-size:.95rem}#controls{position:absolute;display:flex;column-gap:1rem;align-items:center;align-self:center;justify-content:center;bottom:2.5rem;z-index:1}#controls.minimized{column-gap:.5rem;bottom:1.75rem}#controls button{position:relative;display:flex;align-items:center;justify-content:center;border-radius:50%;width:4rem;height:4rem;min-width:auto;box-shadow:0 3px 6px 0 var(--clr-shadow-button)}#controls.minimized button{width:2.75rem;height:2.75rem;box-shadow:0 2px 4px 0 var(--clr-shadow-button)}#controls button .material-icons{font-size:1.8rem}#controls.minimized button .material-icons{font-size:1.4rem}#controls button.off{background-color:rgba(0,0,0,.55);color:#fff}#controls button.off:active:not([disabled]),#controls button.off:hover:not([disabled]){background-color:rgba(0,0,0,.7)}button.full-screen{position:absolute;top:2rem;left:2rem;z-index:1;width:2.5rem;height:2.5rem;border-radius:.5rem;padding:0;box-shadow:none;background-color:transparent;min-width:unset}.minimized:not(.audio-minimized) button.full-screen{top:1rem;left:1rem;width:2rem;height:2rem}button.full-screen:hover:not([disabled]),button:active:not([disabled]){color:var(--clr-text-secondary)}button.full-screen .material-icons{font-size:x-large}.minimized:not(.audio-minimized) button.full-screen .material-icons{font-size:large}#video-container-panel{position:relative;height:100%;width:100%}.call-party{background-color:var(--clr-background-extra-light-gray);border-radius:.5rem;border:2px solid var(--clr-border-light);overflow:hidden}.call-party:hover{border-color:var(--clr-primary-accent)}.minimized .call-party{border-width:1px}.call-party.peer{position:absolute;top:1rem;left:1rem;right:1rem;bottom:1rem;z-index:1}.minimized .call-party.peer{top:0;left:0;right:0;bottom:0}.call-party.self{position:absolute;right:.5rem;bottom:.5rem;width:10rem;height:10rem;z-index:2;cursor:grab}.call-party.self[disabled]{cursor:unset;display:none}video{width:100%;height:100%;object-fit:contain}.call-party.self video{transform:scaleX(-1)}.call-party[disabled] video{display:none}.caller-card{position:absolute;top:0;left:0;width:100%;height:100%;display:flex;align-items:center;justify-content:center;flex-direction:column}.caller-card .avatar-box{width:10rem;height:10rem;font-size:6rem}.caller-card.pulse .lettertile,.caller-card.pulse img.avatar{animation:active-call 1.5s linear infinite}.caller-card .avatar-box .material-icons{font-size:6rem;line-height:1.6}.minimized .caller-card .avatar-box{width:6rem;height:6rem;font-size:3.6rem}.call-party .caller-name{max-width:12rem;padding:.15rem .5rem;border-radius:.35rem;z-index:1;text-shadow:0 0 1px var(--clr-bright);background-color:var(--clr-background-off-white);text-overflow:ellipsis;overflow-x:hidden;white-space:nowrap}.call-party.self .caller-name.inactive{position:absolute;bottom:.25rem;right:.25rem}.caller-card .caller-name{font-size:larger;margin-top:.5rem}.call-peer-card h1 .trusted-badge,.caller-card .trusted-badge{display:inline-block}.caller-card .caller-status{margin-top:.5rem;color:var(--clr-bright);font-size:.9rem;opacity:.8}.call-party.peer .caller-name.inactive{position:absolute;font-size:larger;left:1rem;bottom:.5rem;margin:0 auto}.minimized .caller-name{padding:.1rem .35rem;border-radius:.25rem;font-weight:lighter}.call-message{display:flex;flex-direction:row}.call-message .duration{font-weight:lighter}#go-to-latest{right:1.5rem;bottom:1rem}@keyframes active-call{0%{box-shadow:0 0 0 .1rem var(--clr-primary-accent-transparent-medium),0 0 0 .3rem var(--clr-primary-accent-transparent-medium),0 0 0 .5rem var(--clr-primary-accent-transparent-medium)}to{box-shadow:0 0 0 .4rem var(--clr-primary-accent-transparent-medium),0 0 0 2rem rgba(33,150,243,0),0 0 0 3rem rgba(33,150,243,0)}}.chat-box{list-style:none;padding:0 0 3.5rem;margin:0 .5rem;scroll-behavior:smooth}.chat-box.group{margin-left:-.4rem}.chat-box li{display:flex;margin:0;padding:0;align-items:flex-end}.chat-box .right{justify-content:flex-end}.voice-unread-dot{position:absolute;top:-.15rem;right:-.15rem;width:.55rem;height:.55rem;border-radius:50%;background:#e3444d;border:1.5px solid var(--clr-background-primary,#fff);z-index:1}.bubble{position:relative;display:flex;max-width:36rem;min-width:9rem;height:auto;padding:.75rem .5rem;margin:0;text-align:left;color:var(--clr-bubble-text);border-radius:.5rem;transition:background-color .5s ease-out;transition-delay:.3s;filter:drop-shadow(.5px .5px .7px var(--clr-shadow-bubble-drop));-webkit-touch-callout:none}.chat-box li.single{margin-top:.25rem;margin-bottom:.75rem}.chat-box li.first{margin-top:.25rem;margin-bottom:.15rem}.chat-box li.middle{margin-top:.15rem;margin-bottom:.15rem}.chat-box li.last{margin-top:.15rem;margin-bottom:.75rem}.left .bubble{background-color:var(--clr-bubble-left-bg);margin-right:.5rem}.left.flash .bubble{background-color:var(--clr-bubble-left-bg-flash)}.left .bubble.tip{border-bottom-left-radius:0}.right .bubble{background-color:var(--clr-bubble-right-bg);color:var(--clr-bubble-right-text);margin-left:1.75rem}.right.flash .bubble{background-color:var(--clr-bubble-right-bg-flash)}.right .bubble a,.right .bubble a:focus,.right .bubble a:hover{color:var(--clr-bubble-right-text);text-decoration:underline}.right .bubble.tip{border-bottom-right-radius:0}.chat-box .meta{justify-content:center;margin-top:.5rem;margin-bottom:.5rem}.chat-box .meta .bubble{color:var(--clr-bubble-meta-text);min-width:unset}.meta .bubble.deleted{background-color:var(--clr-bubble-meta-deleted-bg);padding:.75rem .75rem .5rem}.meta .bubble.date{font-size:smaller;padding:.5rem;background-color:var(--clr-bubble-meta-date-bg);text-transform:uppercase;user-select:none}.meta .bubble.system{max-width:90%;padding:.3rem .5rem;background-color:transparent;font-size:.8rem;text-align:center}.left .bubble.tip:before{content:"";position:absolute;width:.85rem;height:.85rem;left:-.8rem;bottom:0;background-color:var(--clr-bubble-left-bg);clip-path:polygon(100% 0,0 110%,100% 110%);-webkit-clip-path:polygon(100% 0,0 110%,100% 110%);border-bottom-left-radius:.3rem;transition:background-color .5s ease-out;transition-delay:.3s}.left.flash .bubble.tip:before{background-color:var(--clr-bubble-left-bg-flash)}.right .bubble.tip:before{content:"";position:absolute;width:.85rem;height:.85rem;left:99.8%;bottom:0;background-color:var(--clr-bubble-right-bg);clip-path:polygon(0 0,100% 110%,0 110%);-webkit-clip-path:polygon(0 0,100% 110%,0 110%);border-bottom-right-radius:.3rem;transition:background-color .5s ease-out;transition-delay:.3s}.right.flash .bubble.tip:before{background-color:var(--clr-bubble-right-bg-flash)}.chat-box .avatar-box{position:relative;width:1.4rem;min-width:1.4rem;height:1.4rem;min-height:1.4rem;font-size:.8rem;margin-bottom:.8rem;margin-right:.8rem}.bubble div.content-meta{width:100%}.bubble div.message-content{position:relative;margin:0;width:100%;max-width:36rem;overflow-wrap:anywhere;padding:0 .75rem;font-size:var(--message-text-size)}.bubble div.message-content.emoji-1{font-size:300%}.bubble div.message-content.emoji-2{font-size:241%}.bubble div.message-content.emoji-3{font-size:193%}.bubble div.message-content.emoji-4{font-size:155%}.bubble div.message-content.emoji-5{font-size:125%}.bubble div.message-content:after{content:".";display:block;height:0;clear:both;visibility:hidden}.chat-box .timestamp-padding{display:inline-block;width:4rem;height:0}.chat-box .timestamp{position:relative;font-size:80%;color:var(--clr-bubble-timestamp-text);top:.35rem;float:right;white-space:nowrap;user-select:none}.right .bubble .timestamp{color:hsla(0,0%,100%,.85)}.right .bubble .timestamp .blue{color:#fff!important}.chat-box .author{font-size:80%;color:var(--clr-bubble-author-text);max-width:12em;text-overflow:ellipsis;white-space:nowrap;overflow-x:hidden}.image-preview,.inline-image{position:relative;overflow:hidden;border-radius:.1rem}.inline-image{max-width:36rem;max-height:24rem}.inline-image>.rounded-container{position:relative;margin:.25rem auto;bottom:2rem;border-left:.25rem solid var(--clr-transparent);border-right:.25rem solid var(--clr-transparent)}img.image-preview:before,img.inline-image:before{position:absolute;display:flex;font-size:.9rem;font-weight:lighter;color:var(--clr-text-primary);top:0;left:0;width:100%;height:100%;justify-content:center;background:center no-repeat var(--clr-border-light) url(../img/broken_image.png)}img.inline-image:before{content:attr(alt);background-size:3rem 3rem}img.image-preview:before{content:" ";background-size:10rem 10rem}.inline-image.video>img.inline-image:before,img.image-preview.video:before{background:center no-repeat var(--clr-border-light) url(../img/broken_video.png)}.inline-image.video>img.inline-image:before{background-size:3rem 3rem}.inline-video{display:flex;position:relative;align-items:center;justify-content:center}.inline-video>img.inline-image:before{background:var(--clr-border-light)}.inline-video .duration{padding:.25rem .5rem;color:var(--clr-toast-bg);background-color:hsla(0,0%,100%,.5);position:absolute;bottom:.25rem;left:.25rem;font-size:80%;border-radius:.3rem}.inline-video .play-control{display:flex;position:absolute;align-items:center;justify-content:center;width:4rem;height:4rem;background-color:rgba(0,0,0,.4);border-radius:50%;pointer-events:none;cursor:pointer}.attachment{display:flex}.attachment:last-of-type{margin-bottom:-.45rem}#info-caption-panel{background-color:var(--clr-background-blue-gray-300)}#info-caption-panel a{color:var(--clr-text-secondary);line-height:1;text-decoration:none}#info-caption-panel :hover{color:var(--clr-dark)}#info-title{margin-right:auto}#group-manager{position:relative;display:flex;flex-direction:column;height:100%;overflow-y:hidden}#group-manager-buttons{display:flex;background-color:var(--clr-background-blue-gray-50);box-shadow:0 -1px var(--clr-shadow-panel-divider);z-index:2;justify-content:space-around;padding:.4rem;height:4rem;border-left:1px solid var(--clr-border-light)}ul.tabbar{display:flex;flex-grow:0;flex-shrink:0;background:var(--clr-tabbar-bg);list-style:none;margin:0;width:100%;overflow:hidden;padding:0 0 0 1rem}ul.tabbar>li{position:relative;height:2.4rem;box-shadow:inset 0 -2px 2.5px -1px var(--clr-shadow-button)}ul.tabbar li.active{background:var(--clr-bright)}ul.tabbar li:after{height:.1rem;width:100%;content:"";bottom:0;left:0;position:absolute;background:var(--clr-tabbar-inactive-border)}ul.tabbar li.active:after{height:.15rem;content:"";bottom:0;left:0;position:absolute;background:var(--clr-tabbar-active-border);animation:tabbar-border-expand .2s cubic-bezier(.4,0,.4,1) 0s alternate forwards}@keyframes tabbar-border-expand{0%{opacity:0;width:0}to{opacity:1;width:100%}}ul.tabbar>li>a{position:relative;padding-left:1rem;padding-right:1rem;text-align:center;line-height:2.4rem;font-weight:400;font-size:1.3rem;font-variant:small-caps;text-decoration:none;color:var(--clr-tabbar-inactive-text);overflow:hidden}ul.tabbar li.active>a{color:var(--clr-tabbar-active-text)}.unread{display:inline-block;font-size:.75rem;font-weight:bolder;background-color:var(--clr-secondary-accent);color:var(--clr-bright);border-radius:50%;width:1.35rem;height:1.35rem;margin-left:.5rem;line-height:1.45rem;text-align:center;vertical-align:middle}.material-icons.as-badge{padding-left:.2rem;font-size:1.15rem;color:var(--clr-chip-remove-icon-bg)}.badge{font-family:Roboto Mono,Courier,monospace,sans-serif;font-size:.8rem;border:1px solid var(--clr-badge-default-border);background-color:var(--clr-badge-default-bg);color:var(--clr-badge-default-text);padding:.05rem .25rem;margin:.05rem .1rem;border-radius:.2rem;display:inline-block;align-self:flex-start}.badge.green{border-color:var(--clr-badge-green-border);background-color:var(--clr-badge-green-bg);color:var(--clr-badge-green-text)}.badge.yellow{border-color:var(--clr-badge-yellow-border);background-color:var(--clr-badge-yellow-bg);color:var(--clr-badge-yellow-text)}.badge.blue{border-color:var(--clr-badge-blue-border);background-color:var(--clr-badge-blue-bg);color:var(--clr-badge-blue-text)}.badge.red{border-color:var(--clr-badge-red-border);background-color:var(--clr-badge-red-bg);color:var(--clr-badge-red-text)}.badge.magenta{border-color:var(--clr-badge-magenta-border);background-color:var(--clr-badge-magenta-bg);color:var(--clr-badge-magenta-text)}.lettertile{width:100%;padding-bottom:100%;border-radius:50%;user-select:none}.lettertile>div{position:absolute;display:flex;flex-shrink:0;flex-grow:0;top:0;bottom:0;left:0;right:0;text-transform:uppercase;align-items:center;justify-content:center;text-align:center;border-radius:50%;line-height:1;padding-top:.1em;color:var(--clr-lettertile-default-text)}.lettertile:hover{color:var(--clr-lettertile-hover-text)}.lettertile.disabled{opacity:.5}.lt-bg-0{background-color:var(--clr-lt-bg-0)}.lt-bg-1{background-color:var(--clr-lt-bg-1)}.lt-bg-2{background-color:var(--clr-lt-bg-2)}.lt-bg-3{background-color:var(--clr-lt-bg-3)}.lt-bg-4{background-color:var(--clr-lt-bg-4)}.lt-bg-5{background-color:var(--clr-lt-bg-5)}.lt-bg-6{background-color:var(--clr-lt-bg-6)}.lt-bg-7{background-color:var(--clr-lt-bg-7)}.lt-bg-8{background-color:var(--clr-lt-bg-8)}.lt-bg-9{background-color:var(--clr-lt-bg-9)}.lt-bg-10{background-color:var(--clr-lt-bg-10)}.lt-bg-11{background-color:var(--clr-lt-bg-11)}.lt-bg-12{background-color:var(--clr-lt-bg-12)}.lt-bg-13{background-color:var(--clr-lt-bg-13)}.lt-bg-14{background-color:var(--clr-lt-bg-14)}.lt-bg-15{background-color:var(--clr-lt-bg-15)}.dk-bg-0{background-color:var(--clr-dk-bg-0)}.dk-bg-1{background-color:var(--clr-dk-bg-1)}.dk-bg-2{background-color:var(--clr-dk-bg-2)}.dk-bg-3{background-color:var(--clr-dk-bg-3)}.dk-bg-4{background-color:var(--clr-dk-bg-4)}.dk-bg-5{background-color:var(--clr-dk-bg-5)}.dk-bg-6{background-color:var(--clr-dk-bg-6)}.dk-bg-7{background-color:var(--clr-dk-bg-7)}.dk-bg-8{background-color:var(--clr-dk-bg-8)}.dk-bg-9{background-color:var(--clr-dk-bg-9)}.dk-bg-10{background-color:var(--clr-dk-bg-10)}.dk-bg-11{background-color:var(--clr-dk-bg-11)}.dk-bg-12{background-color:var(--clr-dk-bg-12)}.dk-bg-13{background-color:var(--clr-dk-bg-13)}.dk-bg-14{background-color:var(--clr-dk-bg-14)}.dk-bg-15{background-color:var(--clr-dk-bg-15)}.lt-fg-0{color:var(--clr-lt-fg-0)!important}.lt-fg-1{color:var(--clr-lt-fg-1)!important}.lt-fg-2{color:var(--clr-lt-fg-2)!important}.lt-fg-3{color:var(--clr-lt-fg-3)!important}.lt-fg-4{color:var(--clr-lt-fg-4)!important}.lt-fg-5{color:var(--clr-lt-fg-5)!important}.lt-fg-6{color:var(--clr-lt-fg-6)!important}.lt-fg-7{color:var(--clr-lt-fg-7)!important}.lt-fg-8{color:var(--clr-lt-fg-8)!important}.lt-fg-9{color:var(--clr-lt-fg-9)!important}.lt-fg-10{color:var(--clr-lt-fg-10)!important}.lt-fg-11{color:var(--clr-lt-fg-11)!important}.lt-fg-12{color:var(--clr-lt-fg-12)!important}.lt-fg-13{color:var(--clr-lt-fg-13)!important}.lt-fg-14{color:var(--clr-lt-fg-14)!important}.lt-fg-15{color:var(--clr-lt-fg-15)!important}.dk-fg-0{color:var(--clr-dk-fg-0)!important}.dk-fg-1{color:var(--clr-dk-fg-1)!important}.dk-fg-2{color:var(--clr-dk-fg-2)!important}.dk-fg-3{color:var(--clr-dk-fg-3)!important}.dk-fg-4{color:var(--clr-dk-fg-4)!important}.dk-fg-5{color:var(--clr-dk-fg-5)!important}.dk-fg-6{color:var(--clr-dk-fg-6)!important}.dk-fg-7{color:var(--clr-dk-fg-7)!important}.dk-fg-8{color:var(--clr-dk-fg-8)!important}.dk-fg-9{color:var(--clr-dk-fg-9)!important}.dk-fg-10{color:var(--clr-dk-fg-10)!important}.dk-fg-11{color:var(--clr-dk-fg-11)!important}.dk-fg-12{color:var(--clr-dk-fg-12)!important}.dk-fg-13{color:var(--clr-dk-fg-13)!important}.dk-fg-14{color:var(--clr-dk-fg-14)!important}.dk-fg-15{color:var(--clr-dk-fg-15)!important}.material-icons{font-size:1.4rem;line-height:1;vertical-align:middle;text-align:center;user-select:none}.material-icons.small{font-size:1rem}.material-icons.medium{font-size:1.2rem}.material-icons.large{font-size:1.6rem}.material-icons.big{font-size:2.2rem}.material-icons.x-big{font-size:3rem}.material-icons.huge{font-size:4rem}.composed-material{display:inline-block;position:relative}.composed-material .second{position:absolute;font-size:85%;top:7%;left:39%;font-weight:700}.composed-material .second-small{position:absolute;font-size:62%;top:14%;left:30%;font-weight:700}.material-icons.outline{position:relative;text-shadow:-1px 1px var(--clr-bright),1px 1px var(--clr-bright),1px -1px var(--clr-bright),-1px -1px var(--clr-bright)}#self-avatar .material-icons{font-size:2.4rem;line-height:3rem}.avatar-box .material-icons{color:var(--clr-lettertile-default-text);border-radius:50%;width:inherit;height:inherit}.chip-input{display:flex;flex-wrap:wrap;flex-grow:1;cursor:text;font-size:1.1rem;font-weight:400;background:var(--clr-transparent);border-bottom:1px solid var(--clr-input-border);box-shadow:0 1px 0 var(--clr-input-shadow);transition:all .1s ease-in-out;max-height:10rem;min-height:4rem;overflow-y:scroll;margin:.25rem 0;outline:none;color:var(--clr-input-text);align-items:center;justify-content:flex-start;text-align:center}.chip-input.focused{border-bottom:1px solid var(--clr-input-border-focus);box-shadow:0 1px 0 var(--clr-input-shadow-focus)}.chip-input>input[type=text]{display:inline-block;width:auto;border:none;box-shadow:none;min-width:4rem;flex:1 0 auto}.chip,.chip-input>input[type=text]{line-height:1.8;height:1.8rem;margin:.125rem}.chip{display:flex;flex:0 0 auto;align-items:center;border-radius:.9rem;background-color:var(--clr-chip-bg);font-size:90%;max-width:12rem;overflow:hidden}.chip,.chip>span{white-space:nowrap;text-overflow:ellipsis}.chip>span{overflow-x:hidden}.chip.invalid{color:var(--clr-chip-invalid-text);background-color:var(--clr-chip-invalid-bg)}.chip>a{display:inline-block;color:var(--clr-chip-remove-icon-text);background-color:var(--clr-chip-remove-icon-bg);border-radius:50%;margin:.3rem;width:1.175rem;min-width:1.175rem;height:1.175rem;line-height:1.15;text-decoration:none}.chip>a:focus,.chip>a:hover{text-decoration:none;color:var(--clr-chip-remove-icon-hover-text);background-color:var(--clr-chip-remove-icon-hover-bg)}.chip .avatar-box{position:relative;width:1.8rem;min-width:1.8rem;height:1.8rem;min-height:1.8rem;font-size:1rem;margin-right:.2rem}.chip .avatar-box .material-icons{font-size:1.4rem;line-height:1.8rem}.chip .spacer{display:inline-block;margin:.3rem;width:.25rem;height:1.175rem}.load-spinner-box{--size-small:2.5rem;--size-large:4rem;position:absolute;box-shadow:.1rem .1rem .15rem 1px var(--clr-shadow-button);border-radius:50%;padding:.25rem;width:var(--size-small);height:var(--size-small);left:0;right:0;top:5rem;margin-left:auto;margin-right:auto;background-color:var(--clr-bright);z-index:2}.load-spinner-box.large{width:var(--size-large);height:var(--size-large)}.load-spinner-box.clear{box-shadow:.1rem .1rem .15rem 1px var(--clr-shadow-button-inset-active);background-color:hsla(0,0%,100%,.5)}.load-spinner-box.centered{top:calc(var(--size-small)/2 - .25rem)}.load-spinner-box.large.centered{top:calc(var(--size-large)/2 - .25rem)}.loader-spinner{border-top:.35rem solid var(--clr-spinner-track);border-radius:50%;border:.35rem solid var(--clr-spinner-track);border-top-color:var(--clr-spinner-active);width:2rem;height:2rem;animation:spin 1.5s linear infinite}.load-spinner-box.large .loader-spinner{width:3.5rem;height:3.5rem}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.uploader{display:flex}.uploader>div{position:relative;height:.6rem;padding:.15rem;margin:.35rem;width:6rem;line-height:0;border-radius:.1rem;background-color:var(--clr-background-blue-gray-100)}.uploader>div>span{display:inline-block;height:.3rem;background-color:var(--clr-primary-action);border-radius:.08rem}.highlight{color:var(--clr-highlight-text);font-weight:bolder}.highlight .preview{color:var(--clr-highlight-preview-text);font-weight:inherit}.cropper{height:21rem;margin:1rem auto auto;align-self:center;transform:translateZ(0)}.cropper,.cropper .bounding-box{position:relative;width:18rem;border-radius:.25rem}.cropper .bounding-box{height:18rem;overflow:hidden;z-index:1;border:2px solid var(--clr-cropper-bounding-box-border);box-shadow:1px 1px 3px var(--clr-shadow-cropper-gray);background-color:var(--clr-cropper-bounding-box-bg)}.cropper .preview{position:absolute;max-height:none;max-width:none;z-index:-1}.cropper .cutout{position:absolute;width:17.8rem;height:17.8rem;border:1px dashed var(--clr-cropper-cutout-border);margin:auto;top:0;bottom:0;right:0;left:0;box-shadow:0 0 150rem 150rem var(--clr-cropper-cutout-shadow);z-index:0}.cropper .cutout.circle{border-radius:50%}.cropper .overlay{position:absolute;cursor:move;touch-action:none;z-index:1}.cropper .zoom-wrapper{height:2rem;width:80%;margin:1rem auto;text-align:center}.cropper .zoom{box-shadow:none;pointer-events:none;z-index:2}.cropper .zoom:focus{outline:none}.reply-quote{position:relative;padding:.15rem .5rem .25rem;border-left:.25rem solid var(--clr-reply-quote-border);line-height:1.5em;font-weight:lighter;border-radius:.25rem;color:var(--clr-reply-quote-text);cursor:pointer}.chat-box .bubble .reply-quote{margin:.15rem -.75rem .3rem}.chat-box .bubble .reply-quote:first-child{margin-top:-.25rem}.chat-box .left .reply-quote{background-color:var(--clr-reply-quote-left-bg)}.chat-box .right .reply-quote{background-color:var(--clr-reply-quote-right-bg)}.reply-quote .inline-image{vertical-align:middle;margin-bottom:.1rem}.reply-quote img.inline-image:before{content:""}#reply-quote-preview{display:flex;align-items:center}#reply-quote-preview .reply-quote{width:100%;background-color:var(--clr-reply-quote-preview-bg);margin:.35rem .5rem 0 0;cursor:auto}#reply-quote-preview .cancel{min-width:2.4rem;text-align:center}#reply-quote-preview .inline-image{background-color:var(--clr-border-light)}#reply-quote-preview img.inline-image:before{content:""}#send-message-panel #reply-quote-preview{width:100%}#send-message-panel #reply-quote-preview .reply-quote{margin-bottom:.35rem}.forwarding-quote{font-size:200%;line-height:200%}.mention{font-weight:500}.forward-dialog{position:relative;overflow:hidden;background-color:var(--clr-bright);box-shadow:.15rem .15rem 1.5rem var(--clr-shadow-dialog);padding:.5rem;border-radius:.3rem;margin:auto 1rem;width:24rem;height:80%}.forward-dialog .scrollable-panel{height:80%}.audio{display:flex;margin-left:auto;align-items:center;flex:none;background-color:var(--clr-audio-bg);height:3rem;border-radius:1.5rem}.audio canvas{width:14rem;height:2.6rem;margin-right:2rem}.audio canvas.playback{margin-right:0}.compose-record-overlay canvas{width:6rem;height:1.8rem;margin-top:.4rem}.audio .duration{font-size:large}.audio-player{display:flex;align-items:center}.audio-player .material-icons.large{font-size:2.4rem;color:var(--clr-audio-icon)}.audio-player .material-icons.disabled{color:var(--clr-audio-icon-disabled);cursor:default}.audio-player .playback{width:14rem;height:2.6rem}.audio-player canvas.playback{display:block}.audio-player .playback-row{display:flex;align-items:center;justify-content:center;color:var(--clr-audio-icon);font-size:small}.audio-player .timer{font-size:smaller;color:var(--clr-audio-timer-text)}#pinned-wrapper{margin-right:.5rem}#pinned-wrapper,.pinned{position:relative;display:flex;align-items:center;height:100%}.pinned{padding:.15rem .5rem .25rem;background-color:var(--clr-background-off-white);background-position:50%;font-weight:lighter;border-radius:0 .25rem .25rem 0;color:var(--clr-text-primary);width:24rem;min-width:0;cursor:pointer;transition:background .4s}.pinned>p{overflow-x:hidden;text-overflow:ellipsis;white-space:nowrap}.pinned:hover{background-color:var(--clr-bright)}.pinned:active{background-color:var(--clr-blue-wash-medium);transition:background 0s}.pinned-scroll{position:relative;display:flex;flex-direction:column;align-items:center;border-radius:.25rem 0 0 .25rem;background-color:var(--clr-background-extra-light-gray);justify-content:space-evenly;height:100%;padding:.2rem;margin-left:.35rem;user-select:none}.pinned-scroll .dot{width:.3rem;height:.3rem;border-radius:50%;background-color:var(--clr-text-tertiary);user-select:none}.pinned-scroll .adot{width:.45rem;height:.45rem;border-radius:50%;background-color:var(--clr-light-blue-accent);user-select:none}#pinned-wrapper:hover .menuTrigger{opacity:.9}#pinned-wrapper:hover .menuTrigger a{color:var(--clr-text-secondary)}.pinned-menu{position:absolute;display:flex;flex-direction:column;background-color:var(--clr-pinned-menu-bg);border-radius:0 .25rem .25rem 0;justify-content:space-evenly;top:0;bottom:0;right:0}.pinned-menu .menuTrigger{position:relative;right:0}.contact-card{position:relative;background-color:var(--clr-background-off-white);border-radius:.5rem;display:flex;flex-direction:column;min-width:14rem}.contact-card .avatar-box{position:relative;width:1.8rem;height:1.8rem;font-size:1rem;margin-right:.75rem;margin-bottom:0}.contact-card .contact-body{align-items:center;background-color:var(--clr-background-blue-gray-50);display:flex;padding:.75rem;border-radius:.5rem .5rem 0 0;border-bottom:none}.contact-card .contact-body .name-box{display:flex;flex-direction:column;overflow:hidden}.contact-card .contact-body .name-box .name{color:var(--clr-text-primary);font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.contact-card .contact-body .name-box .org{color:var(--clr-text-secondary);font-size:90%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:300}.contact-card .contact-actions{display:flex;justify-content:space-evenly;align-items:center;padding:0 .5rem;border-radius:0 0 .5rem .5rem;border-top:none;background-color:var(--clr-bright)}.contact-card .contact-actions a{cursor:pointer}.contact-card .contact-actions .divider{height:1.8rem;opacity:.5}.drag-n-drop{display:flex;border:.15rem dashed var(--clr-drag-n-drop-border);border-radius:1rem;background-color:var(--clr-drag-n-drop-bg);position:absolute;top:1.5rem;bottom:1.5rem;left:1.5rem;right:1.5rem;z-index:3;justify-content:center;align-items:center;font-size:2rem;color:var(--clr-drag-n-drop-text);outline:.5rem solid var(--clr-drag-n-drop-outline)}.country-flag{display:inline-block;font-size:larger}.dial-code{white-space:nowrap;display:inline-block;cursor:pointer;vertical-align:middle;margin-bottom:.15rem}ul.phone-country-selector{list-style:none;overflow-x:hidden}ul.phone-country-selector>li{position:relative;font-weight:lighter;padding:.25rem .5rem .15rem;cursor:pointer;width:100%}ul.phone-country-selector>li:hover{background-color:var(--clr-background-blue-gray-50)}ul.phone-country-selector>li.selected{background-color:var(--clr-background-blue-gray-100)}ul.phone-country-selector .dial-code{position:absolute;right:.5rem;color:var(--clr-primary-action);font-weight:400}ul.phone-country-selector span{display:inline-block}ul.phone-country-selector span.country{text-overflow:ellipsis;overflow-x:hidden}.image-grid{position:relative;display:grid;grid-template-columns:repeat(auto-fill,minmax(7rem,1fr));gap:.35rem;padding:.25rem}.image-grid-cell{padding:0;margin:0;width:100%;height:7rem;overflow:hidden;border-radius:.25rem;border:1.5px solid transparent;cursor:pointer;box-shadow:0 0 .2rem var( --clr-shadow-hr-inset)}.image-grid-cell img{object-fit:cover}.image-grid-cell.selected{border:1.5px solid var(--clr-secondary-accent)}img.inverted{filter:invert(1)}div.toast{visibility:hidden;display:flex;align-items:center;justify-content:center;position:absolute;min-width:10rem;padding:.5rem 1rem;height:3rem;z-index:3;font-weight:lighter;color:var(--clr-toast-text);background-color:var(--clr-toast-bg);border-radius:.5rem;box-shadow:1px 2px .3rem var(--clr-shadow-gray-strong);bottom:2rem;left:50%;translate:-50%}div.toast.show{visibility:visible;animation:fadein .5s,fadeout .5s 2.5s}#sidepanel.auth-onboarding-panel{width:100%;min-width:0;flex:1 1 auto;color-scheme:light;background:#f7f9fc;color:#20242c}.auth-onboarding-panel *{box-sizing:border-box;letter-spacing:0}.auth-landing,.auth-page{position:relative;width:100%;min-height:100%;overflow-x:hidden;overflow-y:auto;background:#fff}.auth-soft-bg{background:#f6f7f9}.auth-landing{display:flex;flex-direction:column;align-items:center;padding:max(1.5rem,env(safe-area-inset-top)) 2rem max(1.5rem,env(safe-area-inset-bottom));background:radial-gradient(ellipse 40% 25% at 90% 0,rgba(68,151,255,.14),transparent 72%),#fff}.auth-landing-form{width:min(100%,28rem);margin:auto 0}.auth-brand{display:flex;flex-direction:column;align-items:center;margin-bottom:4rem}.auth-logo{display:grid;place-items:center;width:5.5rem;height:5.5rem;border-radius:.75rem;background:#fff;box-shadow:0 .75rem 2rem rgba(35,74,125,.14)}.auth-logo span{display:grid;place-items:center;width:3.75rem;height:3.75rem;border-radius:50% 50% 50% 18%;color:#fff;background:#1677ff;font-size:2.2rem;font-weight:700;transform:rotate(-18deg)}.auth-brand h1{margin:1.3rem 0 0;color:#14171c;font-size:2rem;font-weight:700}.auth-back-link,.auth-bottom-link,.auth-remove-file,.auth-switch button,.auth-text-button,.auth-top-link{width:auto;min-width:0;height:auto;min-height:0;padding:0;border:0;box-shadow:none;color:#1677ff;background:transparent;font-size:.95rem;text-transform:none}.auth-top-link{position:absolute;top:max(1.5rem,env(safe-area-inset-top));right:2rem}.auth-bottom-link{margin-top:auto;padding-top:2rem}.auth-back-link{position:absolute;top:max(1.5rem,env(safe-area-inset-top));left:2rem;display:flex;align-items:center;color:#616975}.auth-back-link .material-icons{margin-right:.15rem;font-size:1rem}.auth-field-wrap{margin-bottom:1rem}.auth-field{display:flex;align-items:center;min-height:3.8rem;border:1px solid transparent;border-radius:.5rem;background:#f2f4f7;transition:border-color .15s,box-shadow .15s}.auth-field:focus-within{border-color:#4091ff;box-shadow:0 0 0 3px rgba(22,119,255,.1)}.auth-field.invalid{border-color:#d94b4b}.auth-field input{min-width:0;width:100%;height:3.7rem;margin:0;padding:0 1rem;border:0;box-shadow:none;color:#252931;background:transparent;font-size:1rem}.auth-field input:focus{box-shadow:none}.auth-field input::placeholder,.auth-form textarea::placeholder,.auth-list-form input::placeholder,.auth-realname-content input::placeholder{color:#b2b7bf}.auth-field-icon{flex:none;margin-left:1rem;color:#4091ff;font-size:1.35rem}.auth-field-icon-button{flex:none;display:flex;margin:0 0 0 1rem;padding:0;border:0;background:transparent;color:#4091ff}.auth-field-icon-button .material-icons{font-size:1.35rem}.auth-icon-button,.auth-inline-icon{display:grid;place-items:center;flex:none;width:3.25rem;min-width:3.25rem;height:3.25rem;min-height:3.25rem;margin:0;padding:0;border:0;box-shadow:none;color:#a7adb6;background:transparent}.auth-inline-icon .material-icons{font-size:1.25rem}.auth-field-error{margin-top:.4rem;color:#cf3f3f;font-size:.82rem}.auth-primary{width:100%;height:3.8rem;margin:1rem 0 0;border:0;border-radius:.5rem;color:#fff;background:#368bfa;box-shadow:0 .4rem 1rem rgba(54,139,250,.2);font-size:1.08rem;font-weight:500;text-transform:none}.auth-primary:focus:not([disabled]),.auth-primary:hover:not([disabled]){background:#1677e8}.auth-primary[disabled]{color:#fff;background:#b8d5fa;box-shadow:none;cursor:default}.auth-row{display:flex;align-items:center}.auth-row-end{justify-content:flex-end;min-height:1.5rem;margin-top:-.35rem}.auth-switch{margin:1.3rem 0 0;color:#4a4f57;text-align:center}.auth-agreement{display:flex;align-items:flex-start;max-width:32rem;margin-top:auto;color:#454b54;font-size:.9rem;line-height:1.5}.auth-agreement input{width:1.1rem;height:1.1rem;margin:.12rem .55rem 0 0;accent-color:#368bfa}.auth-agreement a{color:#1677ff}.auth-form-alert{display:flex;align-items:center;margin:0 0 1rem;padding:.75rem .9rem;border-radius:.4rem;color:#bd3434;background:#fff1f1;font-size:.9rem}.auth-form-alert .material-icons{margin-right:.45rem;font-size:1.15rem}.auth-header{position:sticky;top:0;z-index:2;display:grid;grid-template-columns:3.5rem 1fr 3.5rem;align-items:center;height:4.25rem;padding-top:env(safe-area-inset-top);border-bottom:1px solid #edf0f4;background:hsla(0,0%,100%,.96)}.auth-header h1{margin:0;color:#252931;font-size:1.2rem;text-align:center}.auth-header .auth-icon-button{color:#252931}.auth-header .material-icons{font-size:1.25rem}.auth-content{width:100%;min-height:calc(100% - 4.25rem);margin:0 auto;padding:2rem}.auth-content-narrow{max-width:40rem}.auth-steps{display:grid;grid-template-columns:repeat(3,1fr);margin:0 0 2.25rem;padding:0;list-style:none}.auth-steps li{position:relative;display:flex;flex-direction:column;align-items:center;color:#b4b9c1;text-align:center}.auth-steps li:not(:last-child):after{content:"";position:absolute;top:1rem;left:calc(50% + 1.25rem);width:calc(100% - 2.5rem);height:2px;background:#d9dde3}.auth-steps li.active:not(:last-child):after{background:#4091ff}.auth-steps span{display:grid;place-items:center;width:2rem;height:2rem;border:1px solid #aeb4bd;border-radius:50%;background:#fff}.auth-steps b{margin-top:.55rem;font-size:.86rem;font-weight:500}.auth-steps .active{color:#368bfa}.auth-steps .active span{border-color:#368bfa;color:#fff;background:#368bfa}.auth-label{display:block;margin:1.25rem 0 .55rem;color:#292d34;font-size:1rem;font-weight:500}.auth-label span{margin-left:.35rem;color:#87909c;font-size:.78rem;font-weight:400}.auth-help,.auth-review-time{margin:-.45rem 0 0;color:#9ba1aa;font-size:.84rem}.auth-upload{display:flex;position:relative;flex-direction:column;align-items:center;justify-content:center;min-height:12rem;overflow:hidden;border:1px dashed #c7ccd4;border-radius:.5rem;background:#fafafa;cursor:pointer}.auth-upload.invalid{border-color:#d94b4b}.auth-add-screenshot input,.auth-upload input{display:none}.auth-upload .material-icons{color:#4091ff;font-size:3.4rem}.auth-upload img{width:100%;height:8rem;object-fit:contain}.auth-upload strong{max-width:90%;margin-top:.65rem;overflow:hidden;color:#252931;font-size:.95rem;text-overflow:ellipsis;white-space:nowrap}.auth-upload small{margin-top:.35rem;color:#a4aab2}.auth-remove-file{margin-top:.5rem;color:#cf3f3f}.auth-review-time{display:flex;align-items:center;justify-content:center;margin-top:.85rem}.auth-review-time .material-icons{margin-right:.35rem;font-size:1rem}.auth-form textarea{width:100%;min-height:10rem;resize:vertical;padding:1rem;border:1px solid #e1e4e9;border-radius:.5rem;box-shadow:none;color:#252931;background:#fff;font:inherit;line-height:1.6}.auth-form textarea:focus{border-color:#4091ff;outline:none;box-shadow:0 0 0 3px rgba(22,119,255,.1)}.auth-form textarea.invalid{border-color:#d94b4b}.auth-counter{margin:-1.7rem .75rem .8rem 0;color:#a0a6af;font-size:.8rem;text-align:right}.auth-screenshot-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:.75rem}.auth-add-screenshot,.auth-screenshot{position:relative;aspect-ratio:1;overflow:hidden;border:1px solid #dce0e5;border-radius:.4rem;background:#fff}.auth-screenshot img{width:100%;height:100%;object-fit:cover}.auth-screenshot button{position:absolute;top:.2rem;right:.2rem;display:grid;place-items:center;width:1.4rem;min-width:1.4rem;height:1.4rem;min-height:1.4rem;margin:0;padding:0;border:0;border-radius:50%;color:#fff;background:rgba(0,0,0,.58)}.auth-screenshot button .material-icons{font-size:.9rem}.auth-add-screenshot{display:grid;place-items:center;color:#87909c;cursor:pointer}.auth-code-button{flex:none;width:auto;min-width:6.8rem;height:2.7rem;min-height:2.7rem;margin:0 .55rem 0 0;padding:0 .8rem;border:0;border-radius:.4rem;box-shadow:none;color:#1677ff;background:transparent;font-size:.9rem;text-transform:none}.auth-code-button[disabled]{color:#a7adb6}.auth-register-form .auth-brand{margin-bottom:2rem}.auth-register-page .auth-agreement{margin-top:2rem}.auth-list-form{display:flex;flex-direction:column;min-height:calc(100vh - 8.25rem)}.auth-list-form label{display:grid;grid-template-columns:7rem 1fr auto;align-items:center;width:100%;min-height:4.5rem;padding:0 1rem;border-bottom:1px solid #e4e7eb;background:#fff;box-sizing:border-box}.auth-list-form label span{color:#30343b;font-size:1rem}.auth-list-form input{min-width:0;width:100%;height:4rem;margin:0;border:0;box-shadow:none;color:#252931;background:transparent;font-size:1rem}.auth-list-form input:focus{box-shadow:none}.auth-list-gap{height:1rem;background:#f6f7f9}.auth-sticky-action{margin-top:auto}.auth-identity-check-page .auth-sticky-action{margin-top:2.5rem}.auth-realname-page{background:#f5f6f8}.auth-realname-hero{display:flex;align-items:center;justify-content:space-between;min-height:14rem;padding:2rem max(2rem,calc(50% - 22rem));color:#12357b;background:#cfe1fa}.auth-realname-hero div{display:flex;flex-direction:column}.auth-realname-hero strong{font-size:2.5rem;font-weight:800}.auth-realname-hero span{margin-top:.75rem;color:#53799c;font-size:1.2rem}.auth-realname-hero>.material-icons{color:#4a82ee;font-size:9rem;opacity:.9}.auth-realname-content{width:min(calc(100% - 2rem),44rem);margin:-1.5rem auto 0;padding:0 0 2rem}.auth-realname-content h2{position:relative;z-index:1;margin:0;padding:1.5rem 2rem;border:1px solid #fff;border-radius:.5rem .5rem 0 0;background:#e8f1fd;font-size:1.15rem}.auth-realname-content form{padding:2rem;background:#fff}.auth-realname-content label{display:grid;grid-template-columns:8rem 1fr;align-items:center;min-height:4.5rem;margin-bottom:1rem;padding:0 1rem;border-radius:.5rem;background:#f6f6f7}.auth-realname-content input{min-width:0;width:100%;height:3.8rem;margin:0;border:0;box-shadow:none;color:#30343b;background:transparent;font-size:1rem}.auth-privacy-note{display:flex;margin:1rem 0 0;padding:1rem;border-radius:.4rem;color:#a0a6af;background:#fff;line-height:1.5}.auth-privacy-note .material-icons{margin-right:.5rem;font-size:1.15rem}.auth-reminder-demo{overflow:hidden;background:#f5f6f8}.auth-demo-chat{width:min(100%,42rem);margin:0 auto;padding:2rem}.auth-demo-chat h1{text-align:center}.auth-demo-search{display:flex;align-items:center;justify-content:center;height:2.8rem;margin-bottom:1rem;border-radius:.5rem;color:#969ca5;background:#e9ecf0}.auth-demo-row{display:flex;align-items:center;min-height:4.8rem;border-bottom:1px solid #e3e6ea}.auth-demo-row>span{display:grid;place-items:center;width:3.2rem;height:3.2rem;margin-right:.8rem;border-radius:.5rem;color:#fff;background:#368bfa}.auth-demo-row div{display:flex;flex-direction:column}.auth-demo-row small{margin-top:.3rem;color:#969ca5}.auth-modal-backdrop{position:absolute;inset:0;z-index:3;display:grid;place-items:center;padding:1.5rem;background:rgba(0,0,0,.38)}.auth-modal{width:min(100%,30rem);padding:2rem;border-radius:.5rem;background:#fff;box-shadow:0 1rem 3rem rgba(0,0,0,.18);text-align:center;position:relative}.auth-modal-close{position:absolute;top:.6rem;right:.6rem;min-width:0;height:auto;margin:0;padding:.25rem;color:#9aa1ab;background:none;border:0;box-shadow:none}.auth-modal h2{margin:0 0 1rem;font-size:1.45rem}.auth-modal p{color:#858c96;line-height:1.6;text-align:left}.auth-modal .auth-text-button{margin-top:1rem;color:#7c838d}.auth-pin-content{display:flex;position:relative;flex-direction:column;align-items:center;width:min(100%,32rem);margin:0 auto;padding:6rem 2rem 2rem}.auth-pin-content h1{margin:0 0 3.5rem;font-size:1.8rem}.youth-password-gate{display:flex;position:relative;flex-direction:column;align-items:center;width:min(100%,32rem);margin:0 auto;padding:3rem 2rem 2rem}.youth-password-gate .auth-primary{width:min(100%,24rem);margin-top:3rem}.youth-password-gate .auth-text-button{margin-top:1.2rem}.auth-pin-wrap{position:relative;display:inline-block}.auth-pin-boxes{display:grid;grid-template-columns:repeat(4,4rem);gap:.8rem}.auth-pin-boxes span{display:grid;place-items:center;aspect-ratio:1;border-radius:.35rem;color:#30343b;background:#f2f3f5}.auth-pin-input{position:absolute;inset:0;margin:0;opacity:0}.auth-pin-wrap .auth-pin-input{width:100%;height:100%}.auth-pin-content .auth-primary{width:min(100%,24rem);margin-top:3rem}.auth-toast{position:fixed;z-index:20;bottom:max(2rem,env(safe-area-inset-bottom));left:50%;display:flex;align-items:center;max-width:calc(100% - 2rem);padding:.8rem 1.1rem;border-radius:.4rem;color:#fff;background:rgba(31,35,42,.92);box-shadow:0 .5rem 1.5rem rgba(0,0,0,.18);transform:translateX(-50%);animation:auth-toast-in .18s ease-out}.auth-toast .material-icons{margin-right:.5rem;color:#63d68b;font-size:1.15rem}.security-product-links button.flat-button{display:grid;grid-template-columns:2rem 1fr 2rem;align-items:center;width:100%;margin:0;color:var(--clr-text-primary);text-align:left;text-transform:none}.security-product-links .trailing{color:var(--clr-text-tertiary)}@keyframes auth-toast-in{0%{opacity:0;transform:translate(-50%,.5rem)}to{opacity:1;transform:translate(-50%)}}#sidepanel.message-center-panel{width:24rem;min-width:20rem;flex:0 0 24rem;color-scheme:light;background:#fff;color:#242830}#sidepanel.message-center-panel.full-page{width:100%;min-width:0;flex:1 1 auto}.message-center-panel *{box-sizing:border-box;letter-spacing:0}.message-actions-page,.messages-product-page,.system-messages-page{position:relative;display:flex;flex-direction:column;width:100%;min-height:100%;overflow:hidden;background:#fff}.messages-home-header,.messages-sub-header{display:grid;position:relative;z-index:5;grid-template-columns:3.5rem 1fr 3.5rem;align-items:center;flex:none;min-height:4.25rem;padding-top:env(safe-area-inset-top);border-bottom:1px solid #f0f1f3;background:#fff}.messages-home-header h1,.messages-sub-header h1{grid-column:2;margin:0;overflow:hidden;color:#22262d;font-size:1.25rem;font-weight:600;text-align:center;text-overflow:ellipsis;white-space:nowrap}.messages-menu-button,.messages-sub-header button{display:grid;place-items:center;width:3.25rem;min-width:3.25rem;height:3.25rem;min-height:3.25rem;margin:0;padding:0;border:0;box-shadow:none;color:#272b32;background:transparent}.messages-menu-button{grid-column:3}.messages-menu-button .material-icons{font-size:1.75rem}.messages-sub-header button .material-icons{font-size:1.2rem}.messages-connection{grid-column:2;display:flex;align-items:center;justify-content:center;font-size:1.2rem;font-weight:600}.messages-spinner{width:1.25rem;height:1.25rem;margin-right:.55rem;border:3px dotted #aab0b8;border-radius:50%;animation:messages-spin .85s linear infinite}@keyframes messages-spin{to{transform:rotate(1turn)}}.messages-home-content{width:100%;min-height:0;overflow-x:hidden;overflow-y:auto;flex:1 1 auto;padding-bottom:1rem}.message-center-panel.full-page .messages-home-content,.message-center-panel.full-page .messages-home-header>*{--messages-content-width:52rem}.messages-search{display:flex;align-items:center;height:2.8rem;margin:.75rem;border:1px solid transparent;border-radius:.5rem;color:#a5abb4;background:#f1f2f4}.message-center-panel.full-page .messages-conversations,.message-center-panel.full-page .messages-frequent,.message-center-panel.full-page .messages-search{width:min(calc(100% - 2rem),52rem);margin-right:auto;margin-left:auto}.messages-search:focus-within{border-color:#4091ff;box-shadow:0 0 0 3px rgba(22,119,255,.08)}.messages-search .material-icons{margin-left:.9rem;font-size:1.35rem}.messages-search input{min-width:0;width:100%;height:2.7rem;margin:0;padding:0 .85rem;border:0;box-shadow:none;color:#252931;background:transparent;font-size:.95rem}.messages-search input:focus{box-shadow:none}.messages-search input::placeholder{color:#a5abb4}.messages-frequent{padding:.3rem 0 .8rem}.messages-frequent h2{margin:0 .75rem .65rem;color:#9399a2;font-size:.88rem;font-weight:400}.messages-frequent-scroll{display:flex;gap:.75rem;overflow-x:auto;padding:0 .75rem .3rem;scrollbar-width:none}.messages-frequent-scroll::-webkit-scrollbar{display:none}.messages-frequent-scroll button{display:flex;flex:0 0 4.4rem;flex-direction:column;align-items:center;width:4.4rem;min-width:4.4rem;height:auto;min-height:0;margin:0;padding:0;border:0;box-shadow:none;color:#656b74;background:transparent;text-transform:none}.messages-frequent-scroll button>span{width:100%;margin-top:.4rem;overflow:hidden;font-size:.78rem;line-height:1.3;text-overflow:ellipsis;white-space:nowrap}.messages-conversations{display:flex;flex-direction:column}.messages-conversation{display:grid;grid-template-columns:3.4rem minmax(0,1fr) auto;align-items:center;width:100%;min-width:0;min-height:4.55rem;margin:0;padding:.45rem .75rem;border:0;border-radius:0;border-bottom:1px solid #f1f2f4;box-shadow:none;color:#252931;background:#fff;text-align:left;text-transform:none}.messages-conversation:focus,.messages-conversation:hover{background:#f6f8fb}.messages-conversation.pinned{background:#f4f6f8}.messages-avatar{display:grid;position:relative;place-items:center;width:3rem;height:3rem;overflow:visible;border-radius:.55rem;color:#fff;background:#3c8df6}.messages-avatar.small{width:3.2rem;height:3.2rem;border-radius:.65rem}.messages-avatar.tone-orange{color:#ff951c;background:#fff5e5}.messages-avatar.tone-green{color:#16ae54;background:#eefaf3}.messages-avatar img{width:100%;height:100%;border-radius:inherit;object-fit:cover}.messages-avatar>.material-icons{font-size:1.8rem}.messages-avatar .offline,.messages-avatar .online{position:absolute;right:-.18rem;bottom:-.12rem;width:.72rem;min-width:.72rem;height:.72rem;min-height:.72rem;border:2px solid #fff;border-radius:50%;background:#1fb36a}.messages-avatar .offline{background:#aeb4bd}.messages-conversation-copy{display:flex;min-width:0;flex-direction:column;justify-content:center;padding:0 .55rem}.messages-conversation-copy small,.messages-conversation-copy strong{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.messages-conversation-copy strong{color:#272b31;font-size:1rem;font-weight:500;line-height:1.3}.messages-conversation-copy small{margin-top:.2rem;color:#969ca5;font-size:.84rem;font-weight:400;line-height:1.3}.messages-conversation-meta{display:flex;align-self:stretch;min-width:3.4rem;flex-direction:column;align-items:flex-end;justify-content:space-around}.messages-conversation-meta time{color:#9ca2aa;font-size:.72rem;white-space:nowrap}.messages-conversation-meta b{display:grid;place-items:center;min-width:1.25rem;height:1.25rem;padding:0 .3rem;border-radius:.7rem;color:#fff;background:#f34b46;font-size:.7rem;font-weight:500;line-height:1}.messages-conversation-meta .material-icons{color:#a5abb3;font-size:1.1rem}.messages-empty{display:flex;flex-direction:column;align-items:center;padding:4rem 1rem;color:#9ca2aa}.messages-empty .material-icons{font-size:2.4rem}.messages-bottom-nav{display:grid;grid-template-columns:repeat(2,1fr);flex:none;min-height:4.8rem;padding-bottom:env(safe-area-inset-bottom);border-top:1px solid #eceef1;background:#fff}.messages-bottom-nav button{display:flex;flex-direction:column;align-items:center;justify-content:center;width:100%;height:4.8rem;margin:0;padding:0;border:0;box-shadow:none;color:#9ca2ab;background:transparent;text-transform:none}.messages-bottom-nav button.active{color:#297fea}.messages-bottom-nav .material-icons{font-size:1.55rem}.messages-bottom-nav span{margin-top:.25rem;font-size:.76rem}.message-action-backdrop,.messages-menu-backdrop{position:absolute;inset:0;z-index:10;background:rgba(0,0,0,.43)}.messages-quick-menu{position:absolute;top:calc(3.6rem + env(safe-area-inset-top));right:.65rem;width:12rem;overflow:hidden;border-radius:.4rem;background:#4b4c4f;box-shadow:0 .8rem 2rem rgba(0,0,0,.24)}.messages-quick-menu:before{content:"";position:absolute;top:-.45rem;right:1rem;width:.9rem;height:.9rem;background:#4b4c4f;transform:rotate(45deg)}.messages-quick-menu button{display:grid;position:relative;grid-template-columns:2.2rem 1fr;align-items:center;width:100%;min-height:3.2rem;margin:0;padding:0 1rem;border:0;border-radius:0;box-shadow:none;color:#fff;background:transparent;text-align:left;text-transform:none}.messages-quick-menu button:focus,.messages-quick-menu button:hover{background:hsla(0,0%,100%,.09)}.messages-quick-menu .material-icons{font-size:1.35rem}.messages-quick-menu span{font-size:.98rem}.messages-toast{position:fixed;z-index:30;bottom:max(1.5rem,env(safe-area-inset-bottom));left:50%;max-width:calc(100% - 2rem);padding:.75rem 1.1rem;border-radius:.4rem;color:#fff;background:rgba(34,38,45,.94);box-shadow:0 .5rem 1.5rem rgba(0,0,0,.18);text-align:center;transform:translateX(-50%)}.message-actions-page{background:#e8eaee}.message-actions-chat{flex:1 1 auto;min-height:0;overflow-y:auto;padding:1.5rem max(1rem,calc(50% - 25rem));background:#edf0f3 url(../img/bkg/a00.png) repeat}.message-time{margin:0 0 1.5rem;color:#8d949d;font-size:.78rem;text-align:center}.message-demo-row{display:flex;align-items:flex-start;gap:.65rem;margin-bottom:1rem}.message-demo-row.mine{justify-content:flex-end}.message-demo-row .messages-avatar{width:2.7rem;height:2.7rem;flex:0 0 2.7rem}.message-demo-bubble{width:auto;max-width:min(72%,28rem);min-height:2.7rem;height:auto;margin:0;padding:.75rem .9rem;border:0;border-radius:.35rem;box-shadow:0 1px 2px rgba(0,0,0,.08);color:#252931;background:#fff;font-size:.95rem;font-weight:400;line-height:1.5;text-align:left;text-transform:none}.message-demo-bubble.mine{background:#b9eb8f}.message-operation-result{color:#777f89;font-size:.8rem;text-align:center}.message-demo-composer{display:grid;grid-template-columns:2.5rem minmax(0,1fr) 2.5rem 2.5rem;align-items:center;flex:none;min-height:4rem;padding:.5rem max(.75rem,calc(50% - 26rem));background:#f7f8fa}.message-demo-composer span{min-width:0;height:2.7rem;padding:0 .8rem;overflow:hidden;border-radius:.35rem;color:#a2a8b0;background:#fff;line-height:2.7rem;white-space:nowrap}.message-demo-composer .material-icons{color:#555c65;font-size:1.45rem;text-align:center}.message-action-backdrop{display:grid;place-items:center}.message-action-menu{width:min(calc(100% - 3rem),23rem);overflow:hidden;border-radius:.5rem;background:#4b4c4f;box-shadow:0 1rem 3rem rgba(0,0,0,.24)}.message-action-menu button{display:grid;grid-template-columns:1fr 2rem;align-items:center;width:calc(100% - 2rem);min-height:3.1rem;margin:0 1rem;padding:0;border:0;border-radius:0;border-bottom:1px solid hsla(0,0%,100%,.1);box-shadow:none;color:#fff;background:transparent;text-align:left;text-transform:none}.message-action-menu button:last-child{border-bottom:0}.message-action-menu button:focus,.message-action-menu button:hover{background:hsla(0,0%,100%,.08)}.message-action-menu span{font-size:.98rem}.message-action-menu .material-icons{font-size:1.3rem}.system-messages-page{background:#f4f5f7}.system-notification-list{width:100%;max-width:52rem;margin:0 auto;overflow-y:auto;padding:1.5rem}.system-notification-list article{margin-bottom:1.5rem}.system-notification-list article>time{display:block;margin-bottom:.75rem;color:#a2a8b0;font-size:.82rem;text-align:center}.system-notification-card{padding:1.3rem;box-shadow:0 .2rem .8rem rgba(37,49,65,.04)}.system-notification-card,.system-notification-load-more{border:1px solid #eceef1;border-radius:.5rem;background:#fff}.system-notification-load-more{display:flex;align-items:center;justify-content:center;width:100%;height:auto;margin:0;padding:.7rem;box-shadow:none;color:#567;font-size:.85rem;text-transform:none}.system-notification-list article.read .system-notification-card{opacity:.82}.system-notification-card header{display:flex;align-items:center;justify-content:space-between}.system-notification-card h2{margin:0;color:#20242b;font-size:1.05rem}.system-notification-card header>span{padding:.15rem .4rem;border-radius:.25rem;color:#fff;background:#f34b46;font-size:.7rem}.system-notification-card p{margin:.85rem 0;color:#555c66;line-height:1.55}.system-notification-card dl{margin:0}.system-notification-card dl div{display:flex;margin:.35rem 0}.system-notification-card dt{color:#242830;font-weight:600}.system-notification-card dd{margin:0;color:#555c66}.system-security-link{display:flex;align-items:center;width:auto;min-width:0;height:auto;min-height:0;margin:.6rem 0 0;padding:0;border:0;box-shadow:none;color:#1683f8;background:transparent;font-size:.9rem;text-transform:none}.system-security-link .material-icons{font-size:1.1rem}.system-notification-card footer{margin-top:1.1rem;color:#767e88;line-height:1.6}#sidepanel.direct-message-panel{width:100%;min-width:0;flex:1 1 auto;color-scheme:light;background:#f4f6f8;color:#252931}.direct-message-panel *{box-sizing:border-box;letter-spacing:0}.chat-product-page{display:flex;position:relative;flex-direction:column;width:100%;min-height:100%;overflow:hidden;background:#f4f6f8}.chat-product-header{display:grid;position:relative;z-index:4;grid-template-columns:3.5rem minmax(0,1fr) 3.5rem;align-items:center;flex:none;min-height:4.5rem;padding-top:env(safe-area-inset-top);border-bottom:1px solid #eceef1;background:#fff}.chat-product-header>button{display:grid;place-items:center;width:3.25rem;min-width:3.25rem;height:3.25rem;min-height:3.25rem;margin:0;padding:0;border:0;box-shadow:none;color:#292d34;background:transparent}.chat-product-header>button .material-icons{font-size:1.25rem}.chat-product-header>div{min-width:0;text-align:center}.chat-product-header h1{margin:0;overflow:hidden;color:#23272e;font-size:1.2rem;text-overflow:ellipsis;white-space:nowrap}.chat-product-header span{display:flex;align-items:center;justify-content:center;margin-top:.15rem;color:#7d848e;font-size:.78rem}.chat-product-header span i{width:.55rem;height:.55rem;margin-right:.3rem;border-radius:50%;background:#16bf78}.chat-product-timeline{width:100%;min-height:0;overflow-x:hidden;overflow-y:auto;flex:1 1 auto;padding:1.5rem max(1rem,calc(50% - 27rem));scroll-behavior:smooth}.chat-time{margin:.3rem 0 1.4rem;color:#9299a2;font-size:.8rem;text-align:center}.chat-message-row{display:flex;align-items:flex-start;gap:.65rem;margin:0 0 1rem}.chat-message-row.mine{justify-content:flex-end}.chat-avatar{width:2.8rem;height:2.8rem;overflow:hidden;flex:0 0 2.8rem;border-radius:.5rem;background:#dfe3e8}.chat-avatar img{width:100%;height:100%;object-fit:cover}.chat-message-stack{display:flex;min-width:0;max-width:min(72%,32rem);flex-direction:column}.chat-message-row.mine .chat-message-stack{align-items:flex-end}.chat-bubble{position:relative;min-width:2.8rem;min-height:2.8rem;padding:.72rem .9rem;border-radius:.45rem;color:#282c33;background:#fff;box-shadow:0 1px 2px rgba(31,39,50,.06);font-size:.98rem;line-height:1.5;overflow-wrap:anywhere}.chat-bubble:before{content:"";position:absolute;top:.85rem;left:-.35rem;width:.7rem;height:.7rem;background:inherit;transform:rotate(45deg)}.chat-bubble.mine{color:#fff;background:#398afa}.chat-bubble.mine:before{right:-.35rem;left:auto}.chat-bubble.image,.chat-bubble.voice{padding:0;background:transparent;box-shadow:none}.chat-bubble.image:before,.chat-bubble.voice:before{display:none}.chat-message-status{display:flex;align-items:center;justify-content:flex-end;min-height:1.2rem;margin-top:.25rem;color:#8d949e;font-size:.72rem}.chat-message-status.failed{color:#df3838}.chat-message-status .material-icons{margin-right:.25rem;font-size:1.15rem}.chat-relation-notice{display:flex;align-items:center;justify-content:center;min-height:1.8rem;margin:.4rem auto 1.5rem;color:#939aa3;font-size:.82rem;line-height:1.5;text-align:center}.chat-relation-notice button{width:auto;min-width:0;height:auto;min-height:0;margin:0 0 0 .5rem;padding:0;border:0;box-shadow:none;color:#2683f5;background:transparent;font-size:inherit;text-transform:none}.chat-request-actions{display:flex;justify-content:center;gap:.75rem;margin:2rem auto}.chat-request-actions button{width:auto;min-width:6rem;height:2.7rem;min-height:2.7rem;margin:0;padding:0 1.2rem;border:1px solid #d9dde3;border-radius:.4rem;box-shadow:none;color:#5d646e;background:#fff;font-size:.9rem;text-transform:none}.chat-request-actions button.primary{border-color:#398afa;color:#fff;background:#398afa}.chat-image-message{position:relative;width:10rem;aspect-ratio:3/4;overflow:hidden;border-radius:.5rem;background:#e2e5e9}.chat-image-message>img{width:100%;height:100%;object-fit:cover}.chat-upload-progress{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;color:#fff;background:rgba(20,24,30,.4)}.chat-upload-progress progress{width:4rem;height:.35rem;border:0;accent-color:#fff}.chat-upload-progress strong{margin-top:.5rem;font-size:.85rem}.chat-voice-message{display:grid;grid-template-columns:1.5rem auto 2.2rem;align-items:center;min-width:8rem;min-height:3rem;padding:0 .85rem;border-radius:.45rem;color:#30343b;background:#fff;box-shadow:0 1px 2px rgba(31,39,50,.06)}.chat-message-row.mine .chat-voice-message{color:#fff;background:#398afa}.chat-voice-message .material-icons{font-size:1.3rem}.chat-voice-message strong{font-size:1rem;font-weight:500}.chat-voice-message span{height:1rem;background:repeating-linear-gradient(90deg,currentColor 0 2px,transparent 2px 5px);opacity:.55}.chat-file-card{display:grid;grid-template-columns:minmax(0,1fr) 3.4rem;gap:1rem;width:min(100%,24rem);min-width:17rem;padding:1rem;border-radius:.5rem;background:#fff;box-shadow:0 1px 2px rgba(31,39,50,.06)}.chat-file-card>div{display:flex;min-width:0;flex-direction:column}.chat-file-card strong{overflow:hidden;color:#30343b;line-height:1.45;text-overflow:ellipsis}.chat-file-card small{margin-top:auto;color:#a1a7af}.chat-file-icon{display:grid;place-items:center;width:3.4rem;height:3.4rem;border-radius:.35rem;color:#fff;background:#f13e67;font-size:2rem;font-weight:600}.chat-contact-card{display:flex;width:min(100%,22rem);min-width:16rem;height:auto;min-height:0;margin:0;padding:0;overflow:hidden;flex-direction:column;border:0;border-radius:.5rem;box-shadow:0 1px 2px rgba(31,39,50,.06);color:#30343b;background:#fff;text-align:left;text-transform:none}.chat-contact-card>span{display:flex;align-items:center;width:100%;padding:.85rem}.chat-contact-card img{width:3rem;height:3rem;margin-right:.75rem;border-radius:.45rem;object-fit:cover}.chat-contact-card strong{overflow:hidden;font-size:1rem;text-overflow:ellipsis;white-space:nowrap}.chat-contact-card small{width:calc(100% - 1.7rem);margin:0 .85rem;padding:.65rem 0;border-top:1px solid #eceef1;color:#9da3ac}.chat-permission-banner{display:flex;align-items:center;justify-content:center;flex:none;min-height:2.5rem;padding:.55rem 1rem;color:#7e858e;background:#fff7e8;font-size:.82rem;text-align:center}.chat-permission-banner .material-icons{margin-right:.4rem;color:#dc9221;font-size:1.05rem}.chat-composer{display:grid;grid-template-columns:2.7rem minmax(0,1fr) 2.7rem auto;align-items:center;flex:none;min-height:4.2rem;padding:.55rem max(.75rem,calc(50% - 27rem));border-top:1px solid #e8eaee;background:#f8f9fa}.chat-composer>button{display:grid;place-items:center;width:2.5rem;min-width:2.5rem;height:2.5rem;min-height:2.5rem;margin:0;padding:0;border:0;box-shadow:none;color:#555c65;background:transparent}.chat-composer>button[disabled]{color:#b7bcc3}.chat-composer>button .material-icons{font-size:1.45rem}.chat-composer input{min-width:0;width:100%;height:2.8rem;margin:0;padding:0 .8rem;border:1px solid #eceef1;border-radius:.4rem;box-shadow:none;color:#252931;background:#fff;font-size:.93rem}.chat-composer input:focus{border-color:#4091ff;box-shadow:0 0 0 3px rgba(22,119,255,.08)}.chat-composer input[disabled]{color:#9da3ab;background:#f1f2f4}.chat-composer .chat-send-button{width:auto;min-width:3.8rem;margin-left:.25rem;padding:0 .75rem;border-radius:.35rem;color:#fff;background:#398afa;font-size:.82rem;text-transform:none}.chat-attachment-panel{display:grid;grid-template-columns:repeat(4,minmax(4rem,1fr));gap:1rem;flex:none;min-height:13rem;padding:1rem max(1rem,calc(50% - 24rem)) max(1rem,env(safe-area-inset-bottom));border-top:1px solid #e3e6ea;background:#f1f2f4}.chat-attachment-panel button{display:flex;flex-direction:column;align-items:center;justify-content:flex-start;width:100%;height:auto;min-height:5.2rem;margin:0;padding:0;border:0;box-shadow:none;color:#6d747e;background:transparent;text-transform:none}.chat-attachment-panel button>span{display:grid;place-items:center;width:3.8rem;height:3.8rem;border-radius:.5rem;color:#4d535b;background:#fff}.chat-attachment-panel .material-icons{font-size:1.55rem}.chat-attachment-panel small{margin-top:.45rem;font-size:.78rem}.chat-toast{position:fixed;z-index:30;bottom:max(1.5rem,env(safe-area-inset-bottom));left:50%;max-width:calc(100% - 2rem);padding:.75rem 1.1rem;border-radius:.4rem;color:#fff;background:rgba(34,38,45,.94);box-shadow:0 .5rem 1.5rem rgba(0,0,0,.18);text-align:center;transform:translateX(-50%)}#sidepanel.message-composer-panel{width:100%;min-width:0;flex:1 1 auto;color-scheme:light;color:#252931;background:#f4f6f8}.message-composer-panel *{box-sizing:border-box;letter-spacing:0}.message-composer-page .chat-product-header span{margin-top:.1rem}.compose-timeline{padding-bottom:2rem}.compose-avatar{display:grid;place-items:center;width:2.7rem;height:2.7rem;overflow:hidden;flex:0 0 2.7rem;border-radius:.45rem;color:#fff;background:#4789dc}.compose-avatar img{width:100%;height:100%;object-fit:cover}.compose-avatar .material-icons{font-size:1.35rem}.compose-message-row{display:flex;align-items:flex-start;gap:.65rem;width:100%;margin-bottom:1rem}.compose-message-row.mine{justify-content:flex-end}.compose-text-bubble{position:relative;max-width:min(70%,32rem);min-height:2.7rem;padding:.7rem .9rem;border-radius:.45rem;background:#fff;box-shadow:0 1px 2px rgba(31,39,50,.06);line-height:1.5;overflow-wrap:anywhere}.compose-text-bubble.mine{color:#fff;background:#398afa}.compose-mention{color:#1976d2;font-weight:500}.compose-voice-bubble{display:grid;grid-template-columns:1.5rem minmax(3rem,8rem) auto;align-items:center;gap:.4rem;width:auto;min-width:10rem;height:3rem;min-height:3rem;margin:0;padding:0 .85rem;border:0;border-radius:.45rem;box-shadow:0 1px 2px rgba(31,39,50,.06);color:#30343b;background:#fff}.compose-voice-bubble.mine{color:#fff;background:#398afa}.compose-voice-bubble>i:last-child{position:absolute;width:.45rem;height:.45rem;margin-left:10.3rem;border-radius:50%;background:#ef3f4d}.compose-wave{height:1rem;opacity:.55;background:repeating-linear-gradient(90deg,currentColor 0 2px,transparent 2px 5px)}.compose-media-card,.compose-video-card{position:relative;width:11rem;aspect-ratio:4/3;overflow:hidden;border:0;border-radius:.45rem;background:#dfe3e8}.compose-video-card{height:auto;min-height:0;margin:0;padding:0;box-shadow:none}.compose-media-card>img,.compose-video-card>img{width:100%;height:100%;object-fit:cover}.compose-video-card>.material-icons{position:absolute;top:50%;left:50%;color:#fff;font-size:2.8rem;filter:drop-shadow(0 2px 5px rgba(0,0,0,.3));transform:translate(-50%,-50%)}.compose-video-card>small{position:absolute;right:.45rem;bottom:.35rem;padding:.1rem .3rem;border-radius:.2rem;color:#fff;background:rgba(0,0,0,.55)}.compose-media-state{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;flex-direction:column;color:#fff;background:rgba(25,29,35,.52)}.compose-media-state .material-icons{font-size:1.7rem}.compose-media-state strong{margin:.3rem 0;font-size:.75rem}.compose-media-state progress{width:5rem;height:.3rem;accent-color:#fff}.compose-file-card{display:grid;grid-template-columns:3rem minmax(0,1fr) 1.7rem;align-items:center;gap:.8rem;width:min(25rem,calc(100vw - 8rem));min-height:5rem;padding:.8rem;border-radius:.45rem;background:#fff;box-shadow:0 1px 2px rgba(31,39,50,.06)}.compose-file-type{display:grid;place-items:center;width:3rem;height:3.4rem;border-radius:.3rem;color:#fff;background:#e74664;font-size:.72rem;font-weight:700}.compose-file-card>div,.compose-upload-list>div>div{display:flex;min-width:0;flex-direction:column}.compose-file-card strong,.compose-upload-list strong{overflow:hidden;color:#30343b;text-overflow:ellipsis;white-space:nowrap}.compose-file-card small,.compose-upload-list small{margin-top:.35rem;color:#9097a0}.compose-file-card progress,.compose-upload-list progress{width:100%;height:.3rem;margin-top:.35rem;accent-color:#398afa}.compose-toolbar{display:grid;grid-template-columns:2.8rem minmax(0,1fr) 2.8rem auto;align-items:center;min-height:4.2rem;padding:.55rem max(.6rem,calc(50% - 27rem));border-top:1px solid #e4e7eb;background:#f8f9fa}.compose-toolbar>button{display:grid;place-items:center;width:2.6rem;min-width:2.6rem;height:2.6rem;min-height:2.6rem;margin:0;padding:0;border:0;box-shadow:none;color:#555c65;background:transparent}.compose-toolbar input{width:100%;min-width:0;height:2.8rem;margin:0;padding:0 .8rem;border:1px solid #e4e7eb;border-radius:.35rem;box-shadow:none;color:#252931;background:#fff}.compose-confirm-send,.compose-target-footer button,.compose-toolbar .compose-send,.compose-upload-page footer button{width:auto;min-width:3.8rem;padding:0 .85rem;border:0;border-radius:.35rem;color:#fff;background:#398afa;text-transform:none}.compose-toolbar .compose-hold-button{width:100%;color:#30343b;border:1px solid #e1e4e8;background:#fff;font-size:.92rem}.compose-emoji-panel{position:relative;flex:none;min-height:14rem;padding:0 1rem max(1rem,env(safe-area-inset-bottom));border-top:1px solid #e2e5e9;background:#f4f5f7}.compose-emoji-panel nav{display:flex;gap:.25rem;height:3rem;border-bottom:1px solid #e1e4e8}.compose-emoji-delete,.compose-emoji-panel nav button{width:3rem;min-width:3rem;height:3rem;min-height:3rem;margin:0;padding:0;border:0;box-shadow:none;color:#7d848d;background:transparent}.compose-emoji-panel nav button.active{color:#398afa;border-bottom:2px solid #398afa}.compose-emoji-panel>div{display:grid;grid-template-columns:repeat(9,minmax(2.5rem,1fr));gap:.35rem;max-width:52rem;padding:.8rem 0;margin:auto}.compose-emoji-panel>div button{display:grid;place-items:center;width:100%;min-width:2.5rem;height:2.5rem;min-height:2.5rem;margin:0;padding:0;border:0;box-shadow:none;background:transparent;font-size:1.4rem}.compose-emoji-delete{position:absolute;right:1rem;bottom:max(.5rem,env(safe-area-inset-bottom));border-radius:.3rem;background:#fff}.compose-record-overlay{position:fixed;z-index:20;top:50%;left:50%;display:flex;align-items:center;justify-content:center;width:12rem;min-height:11rem;padding:1.2rem;border-radius:.5rem;color:#fff;background:rgba(34,39,47,.9);box-shadow:0 1rem 3rem rgba(0,0,0,.24);flex-direction:column;transform:translate(-50%,-50%)}.compose-record-overlay.cancel{background:rgba(204,55,62,.94)}.compose-record-overlay>.material-icons{font-size:3rem}.compose-record-overlay strong{margin-top:.65rem;font-size:.8rem}.compose-record-overlay small{margin-top:.35rem;opacity:.8}.compose-meter{display:flex;align-items:center;gap:.18rem;height:1.8rem;margin-top:.4rem}.compose-meter i{width:.18rem;border-radius:.1rem;background:#fff}.compose-forward-page,.compose-picker-page,.compose-upload-page{display:flex;width:100%;min-height:100%;flex-direction:column;color:#252931;background:#f6f7f9}.compose-picker-header{display:grid;grid-template-columns:4.5rem minmax(0,1fr) 4.5rem;align-items:center;min-height:4.5rem;padding-top:env(safe-area-inset-top);border-bottom:1px solid #ebedf0;background:#fff}.compose-picker-header h1{margin:0;font-size:1.12rem;text-align:center}.compose-picker-header button{width:100%;min-width:0;height:3.2rem;min-height:3.2rem;margin:0;padding:0;border:0;box-shadow:none;color:#30343b;background:transparent}.compose-picker-header button.text{color:#287fe5;font-size:.88rem;text-transform:none}.compose-picker-header button[disabled]{color:#b4bac1}.compose-search{display:flex;align-items:center;height:2.8rem;margin:.8rem max(1rem,calc(50% - 24rem));padding:0 .75rem;border-radius:.35rem;color:#949ba4;background:#fff}.compose-search .material-icons{font-size:1.25rem}.compose-search input{width:100%;min-width:0;height:100%;margin:0;padding:0 .55rem;border:0;box-shadow:none;background:transparent}.compose-section-title{width:min(100%,48rem);margin:0 auto;padding:.5rem 1rem;color:#8d949d;font-size:.78rem}.compose-picker-list{width:min(100%,48rem);margin:0 auto;background:#fff}.compose-picker-list>button{display:grid;grid-template-columns:3rem minmax(0,1fr) 2rem;align-items:center;gap:.75rem;width:100%;min-width:0;min-height:4.4rem;margin:0;padding:.55rem 1rem;border:0;border-bottom:1px solid #eef0f2;border-radius:0;box-shadow:none;color:#252931;background:#fff;text-align:left;text-transform:none}.compose-picker-list>button>span:nth-child(2){display:flex;min-width:0;flex-direction:column}.compose-picker-list strong{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.compose-picker-list small{margin-top:.2rem;color:#969da6;font-size:.76rem}.compose-check,.compose-checkbox{display:grid;place-items:center;width:1.4rem;height:1.4rem;border:1px solid #c9ced4;border-radius:50%;color:#fff;background:#fff}.compose-check.checked,.compose-checkbox.checked{border-color:#398afa;background:#398afa}.compose-check .material-icons,.compose-checkbox .material-icons{font-size:.95rem}.compose-select-row{display:flex;align-items:flex-start;width:100%}.compose-select-row.selectable{gap:.7rem}.compose-select-row.selectable .compose-message-row{width:calc(100% - 2.2rem)}.compose-check{flex:0 0 1.4rem;min-width:1.4rem;min-height:1.4rem;margin:.65rem 0 0;padding:0;box-shadow:none}.compose-selection-bar{display:grid;grid-template-columns:4rem 1fr 5rem;align-items:center;min-height:3.2rem;border-bottom:1px solid #e6e9ed;background:#fff;text-align:center}.compose-selection-bar button{width:100%;min-width:0;min-height:2.8rem;margin:0;padding:0;border:0;box-shadow:none;color:#398afa;background:transparent;font-size:.78rem;text-transform:none}.compose-batch-actions{display:grid;grid-template-columns:repeat(3,1fr);flex:none;min-height:4.6rem;padding-bottom:env(safe-area-inset-bottom);border-top:1px solid #e3e6ea;background:#fff}.compose-batch-actions button{display:flex;align-items:center;justify-content:center;width:100%;min-width:0;min-height:4.2rem;margin:0;padding:0;border:0;box-shadow:none;color:#515862;background:transparent;flex-direction:column;text-transform:none}.compose-batch-actions button[disabled]{color:#b8bdc4}.compose-batch-actions span{margin-top:.25rem;font-size:.72rem}.compose-upload-page>main{width:min(100%,48rem);min-height:0;margin:0 auto;padding:1.2rem;overflow-y:auto;flex:1 1 auto}.compose-upload-drop{display:flex;align-items:center;justify-content:center;min-height:11rem;border:1px dashed #b9c4d1;border-radius:.45rem;color:#4f5863;background:#fff;cursor:pointer;flex-direction:column}.compose-upload-drop input{position:absolute;width:1px;height:1px;opacity:0}.compose-upload-drop .material-icons{margin-bottom:.5rem;color:#398afa;font-size:2.6rem}.compose-upload-drop small{margin-top:.35rem;color:#9299a2}.compose-forward-confirm h2,.compose-upload-list h2{margin:1.4rem 0 .7rem;font-size:.92rem}.compose-upload-list>div{display:grid;grid-template-columns:3rem minmax(0,1fr) 2rem;align-items:center;gap:.8rem;padding:.8rem;border-bottom:1px solid #eceef1;background:#fff}.compose-upload-list button{width:2rem;min-width:2rem;height:2rem;min-height:2rem;margin:0;padding:0;border:0;box-shadow:none;background:transparent}.compose-target-footer,.compose-upload-page>footer{display:flex;align-items:center;justify-content:flex-end;min-height:4.5rem;padding:.6rem max(1rem,calc(50% - 24rem));border-top:1px solid #e3e6ea;background:#fff}.compose-target-footer button,.compose-upload-page footer button{height:2.8rem;min-height:2.8rem;margin:0}#sidepanel.contact-management-panel,#sidepanel.group-management-panel{width:100%;min-width:0;flex:1 1 auto;color-scheme:light;color:#252931;background:#f5f6f8}.contact-management-panel *,.group-management-panel *{box-sizing:border-box;letter-spacing:0}.group-page,.people-page{display:flex;position:relative;width:100%;min-height:100%;overflow:hidden;flex-direction:column;background:#f5f6f8}.group-chat-header,.group-header,.people-header{display:grid;z-index:3;grid-template-columns:4.5rem minmax(0,1fr) 4.5rem;align-items:center;flex:none;min-height:4.5rem;padding-top:env(safe-area-inset-top);border-bottom:1px solid #e9ebee;background:#fff}.group-chat-header h1,.group-header h1,.people-header h1{margin:0;overflow:hidden;font-size:1.15rem;text-align:center;text-overflow:ellipsis;white-space:nowrap}.group-chat-header>button,.group-header>button,.people-header>button{display:grid;place-items:center;width:100%;min-width:0;height:3.2rem;min-height:3.2rem;margin:0;padding:0;border:0;box-shadow:none;color:#2e333a;background:transparent}.group-header>button.text,.people-header>button.text{color:#247ee6;font-size:.86rem;text-transform:none}.group-search,.people-search{display:flex;align-items:center;flex:none;height:2.8rem;margin:.8rem max(1rem,calc(50% - 25rem));padding:0 .75rem;border-radius:.35rem;color:#9299a2;background:#fff}.group-search .material-icons,.people-search .material-icons{font-size:1.2rem}.group-search input,.people-search input{width:100%;min-width:0;height:100%;margin:0;padding:0 .5rem;border:0;box-shadow:none;background:transparent}.group-avatar,.people-avatar{display:grid;place-items:center;align-content:center;justify-content:center;width:3rem;height:3rem;overflow:hidden;flex:0 0 3rem;border-radius:.45rem;color:#fff;background:#4389df}.group-avatar img,.people-avatar img{width:100%;height:100%;object-fit:cover}.group-avatar .material-icons,.people-avatar .material-icons{font-size:1.45rem}.group-page>main,.people-page>main,.people-scroll{min-height:0;overflow-y:auto;flex:1 1 auto}.group-governance>section,.group-settings>section,.people-add-actions,.people-entry-list,.people-group,.people-settings>section{width:min(100%,50rem);margin:0 auto .8rem;background:#fff}.people-entry-list>button{display:grid;grid-template-columns:3rem minmax(0,1fr) auto 1.5rem;align-items:center;gap:.75rem;width:100%;min-height:4.3rem;margin:0;padding:.55rem 1rem;border:0;border-bottom:1px solid #eef0f2;border-radius:0;box-shadow:none;color:#2d3239;background:#fff;text-align:left;text-transform:none}.people-entry-list>button>.material-icons:last-child{grid-column:4}.people-entry-list em{display:grid;place-items:center;min-width:1.2rem;height:1.2rem;padding:0 .35rem;border-radius:.6rem;color:#fff;background:#ef4653;font-size:.7rem;font-style:normal;line-height:1}.people-group h2{margin:0;padding:.45rem 1rem;color:#868d97;background:#f5f6f8;font-size:.78rem}.people-group>button{display:flex;align-items:center;gap:.8rem;width:100%;min-height:4.2rem;margin:0;padding:.55rem 1rem;border:0;border-bottom:1px solid #eef0f2;border-radius:0;box-shadow:none;color:#2d3239;background:#fff;text-align:left;text-transform:none}.group-admin-page article>span:nth-child(2),.group-list button>span:nth-child(2),.group-member-picker>button>span:nth-child(2),.group-muted-list article>span:nth-child(2),.group-request-list article>div,.people-add-actions button>span,.people-block-list article>span:nth-child(2),.people-group>button>span:nth-child(2),.people-request-list article>div,.people-search-result button>span:nth-child(2){display:flex;min-width:0;flex-direction:column;flex:1 1 auto}.group-admin-page strong,.group-list strong,.group-member-picker strong,.group-muted-list strong,.group-request-list strong,.people-add-actions strong,.people-block-list strong,.people-group strong,.people-request-list strong,.people-search-result strong{line-height:1.3}.group-admin-page small,.group-list small,.group-member-picker small,.group-muted-list small,.group-request-list small,.people-add-actions small,.people-block-list small,.people-group small,.people-request-list small,.people-search-result small{margin-top:.2rem;color:#9299a2;font-size:.75rem;line-height:1.3}.people-index{position:absolute;z-index:2;top:27.5rem;right:.35rem;display:flex;gap:.2rem;flex-direction:column}.people-index a{color:#6a727d;font-size:.68rem;text-decoration:none}.group-admin-page,.group-muted-list,.group-request-list,.people-block-list,.people-request-list{width:min(100%,50rem);margin:0 auto;background:#fff}.group-admin-page article,.group-muted-list article,.group-request-list article,.people-block-list article,.people-request-list article{display:flex;align-items:center;gap:.75rem;min-height:5.2rem;padding:.7rem 1rem;border-bottom:1px solid #eceef1}.people-request-list article[role=button]{cursor:pointer}.group-request-list p,.people-request-list p{max-width:24rem;margin:.2rem 0;overflow:hidden;color:#69717b;font-size:.8rem;text-overflow:ellipsis;white-space:nowrap}.group-request-list article>span,.people-request-list article>span{display:flex;gap:.4rem}.group-admin-page article button,.group-muted-list button,.group-request-list button,.people-block-list button,.people-request-list button{width:auto;min-width:3.3rem;height:2.1rem;min-height:2.1rem;margin:0;padding:0 .65rem;border:1px solid #d8dce1;border-radius:.3rem;box-shadow:none;color:#69717b;background:#fff;font-size:.76rem;text-transform:none}.group-request-list button.primary,.people-request-list button.primary{border-color:#398afa;color:#fff;background:#398afa}.group-request-list em,.people-request-list em{color:#969da5;font-size:.78rem;font-style:normal}.people-add-actions{margin-top:.5rem}.people-add-actions button{display:grid;grid-template-columns:2.7rem minmax(0,1fr) 1.5rem;align-items:center;gap:.7rem;width:100%;min-height:4.4rem;margin:0;padding:.65rem 1rem;border:0;border-bottom:1px solid #eceef1;border-radius:0;box-shadow:none;color:#2d3239;background:#fff;text-align:left;text-transform:none}.people-add-actions button>.material-icons:first-child{color:#398afa;font-size:1.7rem}.people-my-account{color:#9299a2;font-size:.8rem;text-align:center}.people-search-result{width:min(100%,50rem);margin:0 auto}.people-search-result h2{padding:0 1rem;color:#8b929b;font-size:.78rem}.people-search-result button{display:flex;align-items:center;gap:.8rem;width:100%;min-height:4.7rem;margin:0;padding:.7rem 1rem;border:0;box-shadow:none;color:#2d3239;background:#fff;text-align:left;text-transform:none}.group-qr-page main,.people-qr-page main{display:flex;align-items:center;justify-content:center;padding:2rem 1rem;flex-direction:column}.group-qr-card,.people-qr-card{width:min(100%,23rem);padding:1.4rem;border-radius:.45rem;background:#fff;box-shadow:0 .8rem 2.2rem rgba(35,43,54,.1);text-align:center}.group-qr-card>div:first-child,.people-qr-profile{display:flex;align-items:center;gap:.75rem;text-align:left}.group-qr-card>div:first-child>span:nth-child(2),.people-qr-profile>span:nth-child(2){display:flex;flex-direction:column}.group-qr-card small,.people-qr-profile small{margin-top:.2rem;color:#8f969f}.group-qrcode,.people-qrcode{width:190px;height:190px;margin:1.5rem auto 1rem}.group-qrcode img,.people-qrcode img{width:100%;height:100%}.group-qr-card p,.people-qr-card p{margin:.6rem 0 0}.group-qr-actions,.people-qr-actions{display:flex;gap:1rem;margin-top:1.5rem}.group-qr-actions button,.people-qr-actions button{display:flex;align-items:center;justify-content:center;width:8.5rem;min-width:0;min-height:2.8rem;margin:0;border:0;border-radius:.35rem;box-shadow:none;color:#3b424b;background:#fff;text-transform:none}.group-qr-actions .material-icons,.people-qr-actions .material-icons{margin-right:.4rem;font-size:1.2rem}.people-profile,.people-verify{width:min(100%,42rem);margin:0 auto;padding:2rem 1rem;text-align:center}.people-verify>.people-avatar{width:4.5rem;height:4.5rem;margin:0 auto}.people-verify h2{margin:.8rem 0 1.8rem}.people-verify label{display:flex;padding:1rem;background:#fff;text-align:left;flex-direction:column}.people-verify textarea{min-height:7rem;margin:.7rem 0;padding:.7rem;border:1px solid #e3e6ea;border-radius:.35rem;box-shadow:none;resize:vertical}.people-verify label small{color:#989fa7;text-align:right}.people-verify-actions{display:flex;gap:.6rem;margin-top:1.2rem}.people-verify-actions button{flex:1;min-height:3rem;border:0;border-radius:.35rem;box-shadow:none;background:var(--clr-background-blue-gray-50);text-transform:none}.people-verify-actions .people-primary{margin:0}.people-section-title{margin:.6rem 1rem 0;color:var(--clr-text-secondary);font-size:.85rem;font-weight:400}.people-empty-hint{padding:1.5rem 1rem;text-align:center;color:var(--clr-text-secondary)}.people-remark-edit{display:flex;gap:.5rem;padding:.6rem 1rem}.people-remark-edit input{flex:1;border:1px solid #e3e6ea;border-radius:.35rem;padding:.4rem .6rem}.people-profile dd.muted,.people-search-result em{color:var(--clr-text-secondary)}.people-search-result em{flex:none;padding:.35rem .9rem;border-radius:1rem;font-style:normal;font-size:.85rem;background:var(--clr-background-blue-gray-50)}.people-search-result em.people-add-cta{color:var(--clr-primary-accent)}.people-primary{display:flex;align-items:center;justify-content:center;width:100%;min-height:3rem;margin:1.2rem 0 0;border:0;border-radius:.35rem;box-shadow:none;color:#fff;background:#398afa;text-transform:none}.people-primary .material-icons{margin-right:.4rem}.people-profile>section:first-child{display:grid;grid-template-columns:4.5rem minmax(0,1fr) 2rem;align-items:center;gap:1rem;padding:1.2rem;background:#fff;text-align:left}.people-profile>section:first-child .people-avatar{width:4.5rem;height:4.5rem}.people-profile h2,.people-profile p{margin:.2rem 0}.people-profile p{color:#858c95;font-size:.82rem}.people-profile dl{margin:.8rem 0;padding:0 1rem;background:#fff;text-align:left}.people-profile dl>div{display:grid;grid-template-columns:6rem 1fr;padding:.9rem 0;border-bottom:1px solid #eef0f2}.people-profile dd,.people-profile dt{margin:0}.people-profile dd{color:#707883}.people-profile-actions{display:grid;grid-template-columns:1fr 1fr;gap:.8rem}.people-profile-actions button{min-height:3rem;margin:0;border:1px solid #dce0e5;border-radius:.35rem;box-shadow:none;color:#398afa;background:#fff;text-transform:none}.people-profile-actions button.people-primary{border-color:#398afa;color:#fff;background:#398afa}.group-governance,.group-settings,.people-settings{padding:.8rem 0 2rem}.people-settings-profile{display:flex;align-items:center;gap:.8rem;padding:.8rem 1rem}.group-governance section>button,.group-governance section>div,.group-profile>button,.group-profile>label,.group-settings section>button,.group-settings section>div,.people-profile-qr-row,.people-settings section>button,.people-settings section>div{display:flex;align-items:center;width:100%;min-height:3.7rem;margin:0;padding:.65rem 1rem;border:0;border-bottom:1px solid #eceef1;border-radius:0;box-shadow:none;color:#30353c;background:#fff;text-align:left;text-transform:none}.group-governance section>button>span,.group-settings section>button>span,.group-settings section>div>span,.people-settings section>button>span,.people-settings section>div>span{flex:1 1 auto}.group-settings section small,.people-settings section small{min-width:0;margin-left:auto;overflow:hidden;color:#949ba4;text-overflow:ellipsis;white-space:nowrap}.group-switch,.people-switch{position:relative;width:2.8rem;min-width:2.8rem;height:1.55rem;min-height:1.55rem;margin:0;padding:0;border:0;border-radius:.8rem;box-shadow:none;background:#c7ccd2}.group-switch i,.people-switch i{position:absolute;top:.15rem;left:.15rem;width:1.25rem;height:1.25rem;border-radius:50%;background:#fff;transition:transform .18s ease}.group-switch.checked,.people-switch.checked{background:#398afa}.group-switch.checked i,.people-switch.checked i{transform:translateX(1.25rem)}.group-danger,.people-danger{display:block;width:min(calc(100% - 2rem),48rem);min-height:3rem;margin:1rem auto;border:0;border-radius:.35rem;box-shadow:none;color:#df3f48;background:#fff;text-transform:none}.group-empty,.people-empty{display:flex;align-items:center;justify-content:center;min-height:23rem;color:#8d949d;flex-direction:column;text-align:center}.group-empty>.material-icons,.people-empty .material-icons{margin-bottom:.8rem;color:#b3bac2;font-size:4rem}.group-modal-backdrop,.group-sheet-backdrop,.people-sheet-backdrop{position:absolute;z-index:20;inset:0;display:flex;align-items:flex-end;justify-content:center;background:rgba(28,33,40,.44)}.group-confirm-sheet,.group-source-sheet,.people-confirm-sheet{width:min(100%,36rem);padding:1.2rem;border-radius:.5rem .5rem 0 0;background:#fff;text-align:center}.people-confirm-sheet>i{display:block;width:2.5rem;height:.25rem;margin:0 auto 1rem;border-radius:.2rem;background:#d6dae0}.group-confirm-sheet h2,.group-source-sheet h2,.people-confirm-sheet h2{margin:.2rem 0 .8rem;font-size:1.08rem}.group-confirm-sheet p,.people-confirm-sheet p{color:#757d87;line-height:1.6}.group-password-input{width:100%;height:2.75rem;margin-top:.6rem;padding:0 .9rem;border:1px solid #dde1e6;border-radius:.35rem;font-size:1rem;text-align:left}.group-confirm-sheet button,.group-source-sheet button,.people-confirm-sheet button{width:100%;min-height:3rem;margin:.45rem 0 0;border:0;border-radius:.35rem;box-shadow:none;color:#31363d;background:#f2f3f5;text-transform:none}.group-confirm-sheet button.danger,.people-confirm-sheet button.danger{color:#fff;background:#e3444d}.group-source-sheet button{display:flex;align-items:center;justify-content:center}.group-source-sheet button .material-icons{margin-right:.5rem}.group-tabs{display:grid;grid-template-columns:repeat(3,1fr);width:min(100%,50rem);margin:0 auto;border-bottom:1px solid #e4e7eb;background:#fff}.group-tabs button{min-height:2.8rem;margin:0;padding:0;border:0;border-bottom:2px solid transparent;border-radius:0;box-shadow:none;color:#747c86;background:transparent;font-size:.8rem;text-transform:none}.group-tabs button.active{border-bottom-color:#398afa;color:#247ee6}.group-list{width:min(100%,50rem);margin:0 auto;background:#fff}.group-settings-header{display:grid;grid-template-columns:3.6rem minmax(0,1fr) 1.5rem;align-items:center;gap:.8rem;width:100%;min-height:5rem;margin:0;padding:.8rem 1rem;border:0;border-bottom:1px solid #eceef1;border-radius:0;box-shadow:none;color:#30353c;background:#fff;text-align:left;text-transform:none}.group-settings-header .group-avatar{width:3.6rem;height:3.6rem}.group-settings-header>span:not(.group-avatar){display:flex;flex-direction:column;gap:.15rem;min-width:0}.group-settings-header strong{line-height:1.3}.group-settings-header small{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#9299a2;line-height:1.3}.group-list>button{display:grid;grid-template-columns:3.2rem minmax(0,1fr) 1.5rem;align-items:center;gap:.8rem;width:100%;min-height:4.7rem;margin:0;padding:.7rem 1rem;border:0;border-bottom:1px solid #eceef1;border-radius:0;box-shadow:none;color:#30353c;background:#fff;text-align:left;text-transform:none}.group-empty p{max-width:19rem;margin:.5rem auto 1rem;line-height:1.5}.group-empty button{width:auto;min-height:2.7rem;margin:0;padding:0 1rem;border:0;border-radius:.35rem;box-shadow:none;color:#fff;background:#398afa;text-transform:none}.group-modal-backdrop{align-items:center}.group-modal{width:min(calc(100% - 2rem),28rem);padding:1.2rem;border-radius:.5rem;background:#fff}.group-modal h2{margin:0 0 1rem;font-size:1.1rem}.group-modal label{display:flex;flex-direction:column}.group-modal input{height:2.8rem;margin:.5rem 0;padding:0 .7rem;border:1px solid #dce0e5;border-radius:.35rem;box-shadow:none}.group-modal p{color:#858c96;font-size:.78rem}.group-modal footer{display:flex;justify-content:flex-end;gap:.6rem}.group-modal footer button{width:auto;min-width:4.5rem;min-height:2.6rem;margin:0;border:0;border-radius:.3rem;box-shadow:none;color:#59616b;background:#eef0f2;text-transform:none}.group-modal footer button.primary{color:#fff;background:#398afa}.group-chat-header{grid-template-columns:3.5rem minmax(0,1fr) 3.5rem}.group-chat-header>div{text-align:center}.group-chat-header small{color:#8c939c}.group-top-notice{display:grid;grid-template-columns:2.5rem minmax(0,1fr) 1.5rem;align-items:center;gap:.6rem;width:min(100%,54rem);min-height:3.7rem;margin:0 auto;padding:.5rem 1rem;border:0;border-bottom:1px solid #e8eaed;border-radius:0;box-shadow:none;color:#30353c;background:#fff;text-align:left;text-transform:none}.group-top-notice>.material-icons:first-child{color:#398afa}.group-top-notice.announcement>.material-icons:first-child{color:#f29b38}.group-top-notice span{display:flex;min-width:0;flex-direction:column}.group-top-notice small{margin-top:.15rem;overflow:hidden;color:#8d949d;font-size:.75rem;text-overflow:ellipsis;white-space:nowrap}.group-chat-timeline{width:100%;min-height:0;padding:1.5rem max(1rem,calc(50% - 27rem));overflow-y:auto;flex:1 1 auto}.group-system-event{width:fit-content;max-width:80%;margin:.8rem auto;padding:.35rem .6rem;border-radius:.3rem;color:#878e97;background:#e8eaed;font-size:.75rem;text-align:center}.group-chat-message{display:flex;gap:.7rem;margin-top:1rem}.group-chat-message>div{max-width:70%}.group-chat-message>div>span{color:#7d858f;font-size:.75rem}.group-chat-message em{margin-left:.35rem;padding:.08rem .3rem;border-radius:.2rem;color:#247ee6;background:#eaf3ff;font-size:.65rem;font-style:normal}.group-chat-message.owner em{color:#d57b17;background:#fff1df}.group-chat-message p{margin:.3rem 0 0;padding:.7rem .9rem;border-radius:.4rem;background:#fff;line-height:1.5}.group-chat-composer{display:grid;grid-template-columns:2.6rem minmax(0,1fr) 2.6rem 2.6rem;align-items:center;flex:none;min-height:4rem;padding:.5rem max(.5rem,calc(50% - 27rem));border-top:1px solid #e2e5e9;background:#f8f9fa}.group-chat-composer button{width:2.5rem;min-width:2.5rem;height:2.5rem;min-height:2.5rem;margin:0;padding:0;border:0;box-shadow:none;background:transparent}.group-chat-composer input{height:2.7rem;margin:0;padding:0 .7rem;border:1px solid #e1e4e8;border-radius:.35rem;box-shadow:none;background:#fff}.group-invite-card{width:min(100%,23rem);margin:3rem auto;padding:1rem;border-radius:.45rem;background:#fff;box-shadow:0 .5rem 1.5rem rgba(34,42,52,.08)}.group-invite-card>div{display:flex;align-items:center;gap:.8rem}.group-invite-card>div>span{display:flex;flex-direction:column}.group-invite-card p,.group-invite-card small{color:#858c95}.group-invite-card button{width:100%;min-height:2.8rem;margin:.8rem 0 0;border:0;border-radius:.35rem;box-shadow:none;color:#fff;background:#398afa;text-transform:none}.group-invite-card button[disabled]{color:#7e858e;background:#e8eaed}.group-announcement-detail{width:min(100%,42rem);margin:0 auto;padding:2rem 1.2rem;background:#fff}.group-announcement-detail>.material-icons{color:#f19b38;font-size:2.5rem}.group-announcement-detail h2{margin:.7rem 0 1.2rem}.group-announcement-detail>p{color:#555d67;line-height:1.8}.group-announcement-detail>small{display:block;margin-top:1rem;color:#3a8aec}.group-settings section>.group-member-section-title{min-height:2.8rem;padding:.75rem 1rem .35rem;border-bottom:0;gap:.5rem}.group-member-section-title>strong{color:#30353c;font-size:1rem;line-height:1.3}.group-member-section-title>small{margin-left:auto;color:#949ba4;line-height:1.3}.group-settings section>.group-member-grid{display:flex;flex-wrap:wrap;align-items:flex-start;gap:.9rem .5rem;min-height:0;padding:.25rem 1rem 1rem;border-bottom:0}.group-member-grid>button{display:flex;flex-direction:column;align-items:center;gap:.35rem;width:4.4rem;min-width:0;height:auto;min-height:0;margin:0;padding:0;border:0;box-shadow:none;color:#68707a;background:transparent;line-height:1.3;text-transform:none}.group-member-grid>button>small{width:100%;overflow:hidden;color:#68707a;text-align:center;text-overflow:ellipsis;white-space:nowrap}.group-member-grid .group-avatar,.group-member-tile-action{width:3.6rem;height:3.6rem;flex:0 0 3.6rem}.group-member-tile-action{display:grid;place-items:center;width:3.6rem;height:3.6rem;border-radius:.45rem;color:#98a0aa;background:#f0f2f5}.group-member-tile-action>.material-icons{font-size:1.5rem}.group-profile{width:min(100%,50rem);margin:0 auto;padding-top:.8rem}.group-profile>button,.group-profile>label{gap:.7rem}.group-profile>button>span:not(.group-avatar),.group-profile>label>span{min-width:6rem}.group-profile>button>small,.group-profile>label>input{min-width:0;margin-left:auto;overflow:hidden;color:#818892;text-overflow:ellipsis;white-space:nowrap}.group-profile>label>input{width:100%;height:2.5rem;margin:0;padding:0 .5rem;border:0;box-shadow:none;text-align:right}.group-profile-avatar .group-avatar{width:3.5rem;height:3.5rem}.group-profile-avatar>i:last-child,.group-profile-qr>i:first-of-type{margin-left:auto}.group-edit-text{width:min(100%,42rem);margin:0 auto;padding:1rem}.group-edit-text input,.group-edit-text textarea{width:100%;margin:0;padding:.8rem;border:1px solid #e1e4e8;border-radius:.35rem;box-shadow:none;background:#fff}.group-edit-text input{height:3rem}.group-edit-text textarea{min-height:10rem;resize:vertical}.group-edit-text>small{display:block;margin-top:.4rem;color:#969da6;text-align:right}.group-edit-text p{color:#878e98;font-size:.78rem}.group-governance section>div>span{display:flex;flex-direction:column;flex:1 1 auto}.group-governance section>div>span small{margin-top:.2rem;color:#9299a2;font-size:.72rem}.group-admin-page{padding-bottom:1rem}.group-admin-page aside{display:flex;gap:.6rem;margin:0;padding:.8rem 1rem;color:#7a828c;background:#eef5ff;font-size:.78rem;line-height:1.5}.group-admin-page aside .material-icons{color:#398afa;font-size:1.2rem}.group-admin-page aside p{margin:0}.group-admin-page h2{margin:0;padding:.7rem 1rem;color:#8a929b;background:#f5f6f8;font-size:.78rem}.group-inline-empty{padding:2rem;color:#969da5;text-align:center}.group-inline-note{margin:0;padding:.9rem 1rem 1.4rem;color:#969da5;font-size:.85rem;text-align:center}.group-section-title{margin:0;padding:.7rem 1rem;color:#8a929b;background:#f5f6f8;font-size:.78rem}.group-inline-hint{padding:.3rem 1rem .7rem;color:#969da5;font-size:.78rem}.group-toast-center{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);z-index:30;display:flex;flex-direction:column;align-items:center;gap:.4rem;padding:1.2rem 1.6rem;border-radius:.6rem;color:#fff;background:rgba(34,38,45,.94);box-shadow:0 .5rem 1.5rem rgba(0,0,0,.18)}.group-toast-center .material-icons{font-size:2rem}.group-upload-menu{position:absolute;top:3.4rem;right:max(1rem,calc(50% - 24rem));z-index:20;display:flex;flex-direction:column;min-width:9rem;padding:.4rem 0;border-radius:.5rem;background:rgba(34,38,45,.94);box-shadow:0 .5rem 1.5rem rgba(0,0,0,.18)}.group-upload-menu button{display:flex;align-items:center;gap:.6rem;padding:.6rem 1rem;border:0;border-radius:0;box-shadow:none;color:#fff;background:transparent;text-align:left;text-transform:none;font-size:.85rem}.group-upload-menu .material-icons{font-size:1.1rem}.group-member-picker{width:min(100%,50rem);margin:0 auto;background:#fff}.group-member-picker>button{display:grid;grid-template-columns:3rem minmax(0,1fr) 1.5rem;align-items:center;gap:.8rem;width:100%;min-height:4.5rem;margin:0;padding:.6rem 1rem;border:0;border-bottom:1px solid #eceef1;border-radius:0;box-shadow:none;color:#30353c;background:#fff;text-align:left;text-transform:none}.group-check{display:grid;place-items:center;width:1.4rem;height:1.4rem;border:1px solid #c8cdd3;border-radius:50%;color:#fff}.group-check.checked{border-color:#398afa;background:#398afa}.group-check .material-icons{font-size:.95rem}.group-file-list{width:min(100%,54rem);margin:0 auto;background:#fff}.group-file-list article{display:grid;grid-template-columns:3rem minmax(0,1fr) 2.5rem;align-items:center;gap:.8rem;min-height:5rem;padding:.7rem 1rem;border-bottom:1px solid #eceef1}.group-file-type{display:grid;place-items:center;width:3rem;height:3.4rem;border-radius:.3rem;color:#fff;background:#e54b68;font-size:.7rem;font-weight:700}.group-file-list article>div{display:flex;min-width:0;flex-direction:column}.group-file-list strong{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.group-file-list small{margin-top:.3rem;color:#9299a2;font-size:.74rem}.group-file-list progress{width:100%;height:.3rem;margin-top:.4rem;accent-color:#398afa}.group-file-list button{width:2.5rem;min-width:2.5rem;height:2.5rem;min-height:2.5rem;margin:0;padding:0;border:0;box-shadow:none;color:#5f6872;background:transparent}.group-upload-sources{display:grid;grid-template-columns:repeat(3,1fr);flex:none;min-height:6rem;padding:.7rem max(1rem,calc(50% - 18rem)) max(.7rem,env(safe-area-inset-bottom));border-top:1px solid #e2e5e9;background:#fff}.group-upload-sources button{display:flex;align-items:center;justify-content:center;width:100%;min-width:0;margin:0;padding:0;border:0;box-shadow:none;color:#5c6570;background:transparent;flex-direction:column;text-transform:none}.group-upload-sources .material-icons{margin-bottom:.35rem;color:#398afa;font-size:1.7rem}#sidepanel.content-library-panel{width:100%;min-width:0;flex:1 1 auto;color-scheme:light;color:#292e35;background:#f5f6f8}.content-library-panel *{box-sizing:border-box;letter-spacing:0}.library-page{display:flex;position:relative;width:100%;min-height:100%;overflow:hidden;flex-direction:column;background:#f5f6f8}.library-header{display:grid;z-index:3;grid-template-columns:4.5rem minmax(0,1fr) 4.5rem;align-items:center;flex:none;min-height:4.5rem;padding-top:env(safe-area-inset-top);border-bottom:1px solid #e9ebee;background:#fff}.library-header h1{margin:0;overflow:hidden;font-size:1.15rem;text-align:center;text-overflow:ellipsis;white-space:nowrap}.library-header>button{display:grid;place-items:center;width:100%;min-width:0;height:3.2rem;min-height:3.2rem;margin:0;padding:0;border:0;box-shadow:none;color:#2e333a;background:transparent}.library-header>button.text{color:#247ee6;font-size:.86rem;text-transform:none}.library-search{display:flex;align-items:center;flex:none;height:2.8rem;margin:.8rem max(1rem,calc(50% - 26rem));padding:0 .75rem;border-radius:.35rem;color:#9299a2;background:#fff}.library-search .material-icons{font-size:1.2rem}.library-search input{width:100%;min-width:0;height:100%;margin:0;padding:0 .5rem;border:0;box-shadow:none;background:transparent}.library-avatar{display:grid;place-items:center;width:3rem;height:3rem;overflow:hidden;flex:0 0 3rem;border-radius:.45rem;color:#fff;background:#4389df}.library-avatar img{width:100%;height:100%;object-fit:cover}.library-history-tabs,.library-type-tabs{display:flex;width:min(100%,52rem);min-height:2.9rem;margin:0 auto;overflow-x:auto;flex:none;border-bottom:1px solid #e4e7eb;background:#fff;scrollbar-width:none}.library-history-tabs button,.library-type-tabs button{min-width:4.3rem;min-height:2.9rem;margin:0;padding:0 .8rem;border:0;border-bottom:2px solid transparent;border-radius:0;box-shadow:none;color:#747c86;background:transparent;font-size:.8rem;text-transform:none}.library-history-tabs button.active,.library-type-tabs button.active{border-bottom-color:#398afa;color:#247ee6}.library-favorite-list,.library-history-content{width:min(100%,52rem);min-height:0;margin:0 auto;overflow-y:auto;flex:1 1 auto}.library-empty-hint{padding:1.5rem 1rem;color:#9299a2;font-size:.85rem;text-align:center}.library-load-more{display:flex;align-items:center;justify-content:center;width:min(100%,52rem);height:auto;margin:0 auto;padding:.8rem;border:1px solid #eceef1;border-radius:.4rem;box-shadow:none;color:#567;background:#fff;font-size:.85rem;text-transform:none}.library-favorite-item{display:flex;gap:.7rem;padding:.9rem 1rem;border-bottom:1px solid #eceef1;background:#fff}.library-favorite-item.selecting{align-items:center}.library-favorite-content{min-width:0;flex:1 1 auto}.library-favorite-content>footer{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:.5rem;margin-top:.65rem;color:#9299a2;font-size:.72rem}.library-favorite-content>footer span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.library-favorite-content>footer button{width:2rem;min-width:2rem;height:2rem;min-height:2rem;margin:0;padding:0;border:0;box-shadow:none;color:#7d858e;background:transparent}.library-text-content{margin:0;color:#343941;font-size:.94rem;line-height:1.65}.library-text-content.clamped{max-height:8.25em;overflow:hidden}.library-text-wrap{display:block}.library-text-actions{display:flex;align-items:center;gap:1.1rem;margin-top:.35rem}.library-text-actions button{display:inline-flex;align-items:center;gap:.2rem;margin:0;padding:0;border:0;box-shadow:none;background:transparent;color:#3b82c4;font-size:.82rem}.library-text-actions button .material-icons{font-size:1rem}.library-file-card{display:grid;grid-template-columns:3rem minmax(0,1fr) auto;align-items:center;gap:.8rem;width:100%;min-height:4.8rem;margin:0;padding:.7rem;border:1px solid #eceef1;border-radius:.4rem;box-shadow:none;color:#30353c;background:#fafbfc;text-align:left;text-transform:none}.library-download-icon,.library-file-card>span{display:grid;place-items:center;width:3rem;height:3.4rem;border-radius:.3rem;color:#fff;background:#e54b68;font-size:.7rem;font-weight:700}.library-file-card>div{display:flex;min-width:0;flex-direction:column;line-height:1.3}.library-file-card strong{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.library-file-card small{margin-top:.3rem;color:#9299a2;font-size:.74rem}.library-file-card.removed{opacity:.65}.library-file-card em{color:#d74a53;font-size:.74rem;font-style:normal}.library-link-text{display:flex;align-items:center;gap:.6rem;width:100%;min-width:0;height:auto;min-height:2.8rem;margin:0;padding:.6rem .8rem;border:1px solid #eceef1;border-radius:.4rem;box-shadow:none;color:var(--clr-primary-accent);background:#fafbfc;text-align:left;text-transform:none;line-height:1.3}.library-link-text span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.library-link-text.removed,.library-media-thumb.removed,.library-text-content.removed,.library-voice-card.removed{display:flex;align-items:center;justify-content:center;gap:.4rem;opacity:.65}.library-link-text.removed em,.library-media-thumb.removed em,.library-text-content.removed em,.library-voice-card.removed em{color:#d74a53;font-size:.74rem;font-style:normal}.library-voice-card{display:flex;align-items:center;gap:.6rem;width:min(100%,20rem);min-height:3.2rem;margin:0;padding:0 .8rem;border:0;border-radius:.4rem;box-shadow:none;color:#343a42;background:#eef5ff}.library-voice-card.playing{color:#fff;background:#398afa}.library-voice-card .material-icons{font-size:1.6rem}.library-wave{height:1rem;opacity:.55;background:repeating-linear-gradient(90deg,currentColor 0 2px,transparent 2px 5px)}.library-link-card{display:grid;grid-template-columns:5.2rem minmax(0,1fr);gap:.8rem;width:100%;min-height:5.8rem;margin:0;padding:.7rem;border:1px solid #eceef1;border-radius:.4rem;box-shadow:none;color:#30353c;background:#fafbfc;text-align:left;text-transform:none}.library-link-card>img{width:5.2rem;height:4.5rem;border-radius:.3rem;object-fit:cover}.library-link-card>div{min-width:0}.library-link-card p,.library-link-card strong{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.library-link-card p{margin:.35rem 0;color:#7e858f;font-size:.76rem}.library-link-card small{display:flex;align-items:center;color:#9aa0a8}.library-link-card small .material-icons{margin-right:.25rem;font-size:.95rem}.library-media-thumb{position:relative;width:11rem;height:auto;aspect-ratio:4/3;min-height:0;margin:0;padding:0;overflow:hidden;border:0;border-radius:.4rem;box-shadow:none;background:#dfe3e8}.library-media-thumb img{width:100%;height:100%;object-fit:cover}.library-media-thumb .material-icons{position:absolute;top:50%;left:50%;color:#fff;font-size:2.5rem;filter:drop-shadow(0 2px 4px rgba(0,0,0,.35));transform:translate(-50%,-50%)}.library-media-thumb small{position:absolute;right:.4rem;bottom:.3rem;color:#fff}.library-check{display:grid;place-items:center;width:1.4rem;min-width:1.4rem;height:1.4rem;min-height:1.4rem;margin:0;padding:0;border:1px solid #c8cdd3;border-radius:50%;box-shadow:none;color:#fff;background:#fff}.library-check.checked{border-color:#398afa;background:#398afa}.library-check .material-icons{font-size:.95rem}.library-empty{display:flex;align-items:center;justify-content:center;min-height:20rem;color:#969da6;flex-direction:column}.library-empty .material-icons{margin-bottom:.7rem;color:#b5bbc2;font-size:3.5rem}.library-batch-bar{display:grid;grid-template-columns:repeat(2,1fr);flex:none;min-height:4.5rem;padding-bottom:env(safe-area-inset-bottom);border-top:1px solid #e2e5e9;background:#fff}.library-batch-bar button{display:flex;align-items:center;justify-content:center;min-height:4.2rem;margin:0;padding:0;border:0;box-shadow:none;color:#555e68;background:transparent;flex-direction:column;text-transform:none}.library-batch-bar button[disabled]{color:#b6bbc2}.library-batch-bar span{margin-top:.2rem;font-size:.72rem}.library-sheet-backdrop{display:grid;position:absolute;z-index:70;inset:0;place-items:center;padding:1rem;background:rgba(22,26,31,.52)}.library-confirm-sheet{width:min(100%,25rem);padding:1.25rem;border-radius:.5rem;background:#fff;text-align:center}.library-confirm-sheet>.material-icons{color:#4d73aa;font-size:2.8rem}.library-confirm-sheet h2{margin:.65rem 0;font-size:1.15rem}.library-confirm-sheet p{margin:.7rem 0 1rem;color:#5c646e;line-height:1.6;text-align:left}.library-confirm-sheet button{width:100%;min-height:2.9rem;margin:.45rem 0 0;padding:0 1rem;border:0;border-radius:.35rem;box-shadow:none;color:#4d5660;background:#f0f2f4}.library-confirm-sheet button.danger{color:#fff;background:#d84950}.library-section-title{width:min(100%,52rem);margin:0 auto;padding:.55rem 1rem;color:#8a929b;font-size:.78rem}.library-target-list{width:min(100%,52rem);min-height:0;margin:0 auto;overflow-y:auto;flex:1 1 auto;background:#fff}.library-target-list>button{display:grid;grid-template-columns:3rem minmax(0,1fr) 1.5rem;align-items:center;gap:.8rem;width:100%;min-height:4.5rem;margin:0;padding:.6rem 1rem;border:0;border-bottom:1px solid #eceef1;border-radius:0;box-shadow:none;color:#30353c;background:#fff;text-align:left;text-transform:none}.library-target-list>button>span:nth-child(2){display:flex;min-width:0;flex-direction:column}.library-target-list small{margin-top:.2rem;color:#9299a2}.library-next-bar{display:flex;align-items:center;justify-content:space-between;flex:none;min-height:4.3rem;padding:.6rem max(1rem,calc(50% - 25rem));border-top:1px solid #e2e5e9;color:#777f89;background:#fff}.library-next-bar button,.library-primary,.library-share-confirm>button{width:auto;min-width:5rem;min-height:2.8rem;margin:0;padding:0 1rem;border:0;border-radius:.35rem;box-shadow:none;color:#fff;background:#398afa;text-transform:none}.library-next-bar button[disabled]{background:#b7c9df}.library-share-confirm{width:min(100%,42rem);margin:0 auto;padding:1.2rem}.library-share-confirm h2{margin:0 0 .8rem;font-size:.92rem}.library-target-summary{display:flex;gap:1rem;padding:1rem;overflow-x:auto;background:#fff}.library-target-summary>span{display:flex;align-items:center;width:4rem;flex:0 0 4rem;flex-direction:column}.library-target-summary small{width:100%;margin-top:.3rem;overflow:hidden;text-align:center;text-overflow:ellipsis;white-space:nowrap}.library-share-confirm>section{margin-top:1rem;padding:1rem;background:#fff}.library-share-confirm>section p{margin-bottom:0;color:#858d96;font-size:.76rem;line-height:1.5}.library-share-confirm textarea{width:100%;min-height:5rem;margin:1rem 0;padding:.8rem;border:0;border-radius:.35rem;box-shadow:none;background:#fff;resize:vertical}.library-share-confirm>button{width:100%}.library-download{width:min(100%,36rem);margin:0 auto;padding:2.5rem 1.2rem;text-align:center}.library-download-icon{width:5rem;height:5.8rem;margin:0 auto 1rem;font-size:1rem}.library-download h2{margin:0;overflow-wrap:anywhere;font-size:1.1rem}.library-download>p{color:#8a929b}.library-download dl{margin:1.5rem 0;padding:0 1rem;background:#fff;text-align:left}.library-download dl>div{display:flex;justify-content:space-between;padding:.8rem 0;border-bottom:1px solid #eceef1}.library-download dd,.library-download dt{margin:0}.library-download dd{color:#777f89}.library-primary{display:flex;align-items:center;justify-content:center;width:100%}.library-primary .material-icons{margin-right:.4rem}.library-download-progress{margin:1.5rem 0;padding:1rem;background:#fff;text-align:left}.library-download-progress>div{display:flex;justify-content:space-between}.library-download-progress progress{width:100%;height:.45rem;margin:.8rem 0 .4rem;accent-color:#398afa}.library-download-progress small{color:#9299a2}.library-download-actions{display:grid;grid-template-columns:1fr;gap:.8rem}.library-download-actions button{min-height:2.9rem;margin:0;border:1px solid #dce0e5;border-radius:.35rem;box-shadow:none;color:#555e68;background:#fff;text-transform:none}.library-download-actions button.primary{border-color:#398afa;color:#fff;background:#398afa}.library-download-actions .material-icons{margin-right:.35rem;font-size:1.1rem;vertical-align:middle}.library-complete{display:flex;align-items:center;gap:.7rem;margin:1.5rem 0;padding:1rem;color:#257c58;background:#eaf8f1;text-align:left}.library-complete>.material-icons{font-size:2rem}.library-complete>span{display:flex;flex-direction:column}.library-complete small{margin-top:.2rem;color:#65917d}.library-message-results>button{display:grid;grid-template-columns:3rem minmax(0,1fr) 1.5rem;align-items:start;gap:.8rem;width:100%;height:auto;min-height:5.5rem;margin:0;padding:.8rem 1rem;border:0;border-bottom:1px solid #eceef1;border-radius:0;box-shadow:none;color:#30353c;background:#fff;text-align:left;text-transform:none}.library-message-results>button>span{min-width:0}.library-message-results p{margin:.3rem 0;color:#666e78;line-height:1.5;white-space:normal;overflow-wrap:anywhere}.library-message-results mark{padding:0 .08rem;color:#1f66b8;background:#dcecff}.library-message-results small,.library-message-results strong{line-height:1.3}.library-message-results small{color:#969da5}.library-record-files,.library-record-links{padding:.8rem;background:#fff}.library-record-header{display:flex;align-items:center;justify-content:space-between;gap:.6rem;width:100%;min-width:0;height:auto;margin:0 0 .4rem;padding:0;border:0;box-shadow:none;color:inherit;background:transparent;text-align:left;text-transform:none;line-height:1.3}.library-record-header strong{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:600}.library-record-header small{flex:none;color:#9299a2;font-size:.74rem}.library-record-files .library-file-card,.library-record-links .library-link-text{margin-bottom:.7rem}.library-date-title{margin:0;padding:.7rem .8rem;color:#828a94;font-size:.8rem}.library-media-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:.35rem;padding:0 .8rem 1rem}.library-media-grid button{position:relative;width:100%;height:auto;min-height:0;aspect-ratio:1;margin:0;padding:0;overflow:hidden;border:0;border-radius:.25rem;box-shadow:none;background:#dfe3e8}.library-media-grid img{width:100%;height:100%;object-fit:cover}.library-media-grid .material-icons{position:absolute;top:50%;left:50%;color:#fff;font-size:2.2rem;filter:drop-shadow(0 2px 4px rgba(0,0,0,.35));transform:translate(-50%,-50%)}.library-media-grid small{position:absolute;right:.3rem;bottom:.2rem;color:#fff;font-size:.65rem}#sidepanel.call-experience-panel{width:100%;min-width:0;flex:1 1 auto;color-scheme:light;background:#11151a}.call-experience-panel *{box-sizing:border-box;letter-spacing:0}.call-history-page,.call-minimized-page{display:flex;position:relative;width:100%;min-height:100%;overflow:hidden;color:#252a31;background:#f5f6f8;flex-direction:column}.call-chat-preview-header,.call-history-page>header{display:grid;grid-template-columns:3.8rem minmax(0,1fr) 3.8rem;align-items:center;flex:none;min-height:4.5rem;padding-top:env(safe-area-inset-top);border-bottom:1px solid #e8eaed;background:#fff}.call-history-page>header h1{margin:0;font-size:1.15rem;text-align:center}.call-chat-preview-header button,.call-history-page>header button{display:grid;place-items:center;width:100%;min-width:0;height:3.2rem;min-height:3.2rem;margin:0;padding:0;border:0;box-shadow:none;color:#30353c;background:transparent}.call-history-page>main{width:min(100%,48rem);min-height:0;margin:0 auto;overflow-y:auto;flex:1 1 auto;background:#fff}.call-record{display:grid;grid-template-columns:2.5rem minmax(0,1fr) auto 1.5rem;align-items:center;gap:.7rem;width:100%;min-height:4.5rem;margin:0;padding:.65rem 1rem;border:0;border-bottom:1px solid #eceef1;border-radius:0;box-shadow:none;color:#30353c;background:#fff;text-align:left;text-transform:none}.call-record>.material-icons:first-child{display:grid;place-items:center;width:2.5rem;height:2.5rem;border-radius:50%;color:#257a58;background:#e9f7f0}.call-record.busy>.material-icons:first-child,.call-record.declined>.material-icons:first-child,.call-record.missed>.material-icons:first-child{color:#d84850;background:#fdecee}.call-record>span{display:flex;min-width:0;flex-direction:column}.call-record small{margin-top:.2rem;color:#858d96}.call-record.busy small,.call-record.declined small,.call-record.missed small{color:#d84850}.call-record time{color:#969da5;font-size:.72rem}.call-record>.material-icons:last-child{color:#8d949d;font-size:1rem}.call-history-page>footer{display:grid;grid-template-columns:1fr 1fr;gap:.8rem;width:min(100%,48rem);min-height:4.8rem;margin:0 auto;padding:.7rem 1rem max(.7rem,env(safe-area-inset-bottom));border-top:1px solid #e2e5e9;background:#fff}.call-history-page>footer button{display:flex;align-items:center;justify-content:center;min-height:3rem;margin:0;border:0;border-radius:.35rem;box-shadow:none;color:#fff;background:#277f5a;text-transform:none}.call-history-page>footer button:last-child{background:#347fd7}.call-history-page>footer .material-icons{margin-right:.4rem;font-size:1.2rem}.call-fullscreen{display:flex;position:relative;width:100%;min-height:100%;overflow:hidden;color:#fff;background:#171c22;flex-direction:column;isolation:isolate}.call-audio-surface{background:#26323b}.call-ambient{position:absolute;z-index:-2;inset:-2rem;background:linear-gradient(160deg,#34444f,#202931 52%,#151a20)}.call-audio-surface:after{content:"";position:absolute;z-index:-1;inset:0;opacity:.12;background-image:url(../img/bkg/p09.jpg);background-position:50%;background-size:cover;filter:blur(24px);transform:scale(1.12)}.call-fullscreen.incoming-call-overlay{position:absolute;top:0;left:0;width:100%;height:100%;z-index:200}.incoming-call-photo-blur{position:absolute;z-index:-1;inset:0;opacity:.12;background-image:var(--incoming-call-photo);background-position:50%;background-size:cover;filter:blur(24px);transform:scale(1.12)}.call-product-header{display:grid;z-index:4;grid-template-columns:4rem minmax(0,1fr) 4rem;align-items:center;flex:none;min-height:4.8rem;padding-top:env(safe-area-inset-top)}.call-product-header>button{display:grid;place-items:center;width:100%;min-width:0;height:3.4rem;min-height:3.4rem;margin:0;padding:0;border:0;box-shadow:none;color:hsla(0,0%,100%,.92);background:transparent}.call-product-header>span{color:hsla(0,0%,100%,.7);font-size:.74rem;text-align:center}.call-peer-card{display:flex;z-index:2;align-items:center;justify-content:center;min-height:0;padding:1rem;flex:1 1 auto;flex-direction:column}.call-product-avatar{display:block;width:7.5rem;height:7.5rem;overflow:hidden;border:2px solid hsla(0,0%,100%,.4);border-radius:50%;box-shadow:0 1rem 2.5rem rgba(0,0,0,.25)}.call-product-avatar.small{width:3rem;height:3rem}.call-product-avatar img{width:100%;height:100%;object-fit:cover}.call-peer-card h1{display:flex;align-items:center;margin:1.3rem 0 .45rem;max-width:100%;color:#fff;font-size:1.55rem}.call-peer-card h1>span{min-width:0;overflow-x:hidden;text-overflow:ellipsis;white-space:nowrap}.call-peer-card h1 .trusted-badge{flex:none}.call-peer-card p{margin:0;color:hsla(0,0%,100%,.78);font-size:.92rem}.call-peer-card .call-network{display:flex;align-items:center;margin-top:.65rem;color:hsla(0,0%,100%,.62)}.call-network i{width:.45rem;height:.45rem;margin-right:.35rem;border-radius:50%;background:#43d38c}.call-ringing-dots{display:flex;gap:.35rem;margin-top:1rem}.call-ringing-dots i{width:.4rem;height:.4rem;border-radius:50%;background:hsla(0,0%,100%,.65);animation:call-dot 1.2s ease-in-out infinite}.call-ringing-dots i:nth-child(2){animation-delay:.15s}.call-ringing-dots i:nth-child(3){animation-delay:.3s}@keyframes call-dot{0%,70%,to{transform:translateY(0);opacity:.45}35%{transform:translateY(-.35rem);opacity:1}}.call-active-controls,.call-incoming-actions,.call-single-action,.call-video-controls{display:flex;z-index:4;align-items:flex-start;justify-content:space-evenly;flex:none;min-height:8rem;padding:1rem max(1rem,calc(50% - 17rem)) max(1.5rem,env(safe-area-inset-bottom))}.call-single-action{justify-content:center}.call-control{display:flex;align-items:center;width:5rem;min-width:5rem;height:auto;min-height:0;margin:0;padding:0;border:0;box-shadow:none;color:hsla(0,0%,100%,.78);background:transparent;flex-direction:column;text-transform:none}.call-control>span{display:grid;place-items:center;width:3.7rem;height:3.7rem;border-radius:50%;color:#fff;background:hsla(0,0%,100%,.16);backdrop-filter:blur(8px)}.call-control.active>span{color:#22272d;background:#fff}.call-control.danger>span{color:#fff;background:#e3464f}.call-control .material-icons{font-size:1.55rem}.call-control small{width:100%;margin-top:.5rem;color:inherit;font-size:.7rem;line-height:1.2;text-align:center}.call-incoming-actions .call-control:last-child>span{background:#2ab56f}.call-video-surface{background:#11151a}.call-video-backdrop,.call-video-off{position:absolute;z-index:-2;inset:0;width:100%;height:100%}.call-video-backdrop{object-fit:cover}.call-video-off{display:flex;align-items:center;justify-content:center;background:#252f37;flex-direction:column}.call-video-off .call-product-avatar{width:7rem;height:7rem}.call-video-off strong{margin-top:1.2rem}.call-video-off small{margin-top:.35rem;color:hsla(0,0%,100%,.68)}.call-video-off.local{background:#20272e}.call-video-shade{position:absolute;z-index:-1;inset:0;background:linear-gradient(180deg,rgba(0,0,0,.42),transparent 30%,transparent 62%,rgba(0,0,0,.58))}.call-video-status{z-index:2;text-align:center}.call-video-status h1{margin:.4rem 0 .25rem;color:#fff;font-size:1.25rem}.call-video-status p{margin:0;color:hsla(0,0%,100%,.8)}.call-video-controls{margin-top:auto}.call-local-preview{position:absolute;z-index:3;top:max(6.2rem,calc(env(safe-area-inset-top) + 5rem));right:1rem;width:7rem;min-width:7rem;height:auto;min-height:0;aspect-ratio:3/4;margin:0;padding:0;overflow:hidden;border:1px solid hsla(0,0%,100%,.35);border-radius:.4rem;box-shadow:0 .5rem 1.2rem rgba(0,0,0,.3);background:#252c33}.call-local-preview img{width:100%;height:100%;object-fit:cover}.call-local-preview .material-icons{position:absolute;top:50%;left:50%;color:#fff;transform:translate(-50%,-50%)}.call-toast{position:fixed;z-index:50;bottom:max(1.5rem,env(safe-area-inset-bottom));left:50%;max-width:calc(100% - 2rem);padding:.7rem 1rem;border-radius:.3rem;color:#fff;background:rgba(24,28,34,.94);text-align:center;transform:translateX(-50%)}.call-chat-preview-header>div{display:flex;align-items:center;justify-content:center;flex-direction:column}.call-chat-preview-header small{margin-top:.15rem;color:#37a66d}.call-minimized-page>main{min-height:0;padding:1.5rem max(1rem,calc(50% - 26rem));overflow-y:auto;flex:1 1 auto}.call-chat-preview-message{width:fit-content;max-width:70%;padding:.7rem .9rem;border-radius:.4rem;background:#fff;line-height:1.5}.call-chat-composer{display:grid;grid-template-columns:minmax(0,1fr) 3rem;align-items:center;flex:none;min-height:4rem;padding:.5rem max(.7rem,calc(50% - 26rem));border-top:1px solid #e2e5e9;background:#fff}.call-chat-composer input{height:2.7rem;margin:0;padding:0 .7rem;border:1px solid #e1e4e8;border-radius:.35rem;box-shadow:none;background:#f7f8f9}.call-chat-composer button{width:3rem;min-width:3rem;height:2.7rem;min-height:2.7rem;margin:0;padding:0;border:0;box-shadow:none;background:transparent}.call-floating-window{position:fixed;z-index:20;top:0;left:0;width:7.4rem;min-width:7.4rem;height:auto;min-height:0;aspect-ratio:3/4;margin:0;padding:0;overflow:hidden;touch-action:none;border:1px solid hsla(0,0%,100%,.6);border-radius:.45rem;box-shadow:0 .5rem 1.5rem rgba(0,0,0,.28);background:#20262c}.call-floating-window img{width:100%;height:100%;pointer-events:none;object-fit:cover}.call-floating-window span{position:absolute;right:.4rem;bottom:.35rem;display:flex;align-items:center;color:#fff;font-size:.68rem;text-shadow:0 1px 2px rgba(0,0,0,.8)}.call-floating-window span i{width:.4rem;height:.4rem;margin-right:.25rem;border-radius:50%;background:#41d48d}.call-mini-hangup{position:fixed;z-index:21;right:1rem;bottom:5.2rem;display:grid;place-items:center;width:3rem;min-width:3rem;height:3rem;min-height:3rem;margin:0;padding:0;border:0;border-radius:50%;box-shadow:0 .4rem 1rem rgba(0,0,0,.2);color:#fff;background:#e3464f}.compose-target-footer{justify-content:space-between;margin-top:auto;color:#757d87;font-size:.82rem}.compose-forward-confirm{width:min(100%,40rem);margin:0 auto;padding:1.2rem}.compose-forward-confirm textarea,.compose-forward-confirm>section{width:100%;margin-bottom:1rem;padding:1rem;border:0;border-radius:.45rem;background:#fff}.compose-target-summary{display:flex;gap:1rem;overflow-x:auto}.compose-target-summary>span{display:flex;align-items:center;width:4rem;flex:0 0 4rem;flex-direction:column}.compose-target-summary small{width:100%;margin-top:.35rem;overflow:hidden;text-align:center;text-overflow:ellipsis;white-space:nowrap}.compose-forward-preview h2{margin-top:0}.compose-forward-preview p{margin:.45rem 0;overflow:hidden;color:#757c85;font-size:.82rem;text-overflow:ellipsis;white-space:nowrap}.compose-forward-confirm textarea{min-height:5rem;resize:vertical}.compose-forward-mode{display:grid;grid-template-columns:1fr 1fr;gap:.7rem;margin-bottom:1rem}.compose-forward-mode button{min-height:2.8rem;margin:0;border:1px solid #dce0e5;border-radius:.35rem;box-shadow:none;color:#646c76;background:#fff;text-transform:none}.compose-forward-mode button.active{border-color:#398afa;color:#287fe5;background:#eef6ff}.compose-confirm-send{width:100%;min-height:3rem;margin:0}@keyframes fadein{0%{bottom:0;opacity:0}to{bottom:2rem;opacity:1}}@keyframes fadeout{0%{bottom:2rem;opacity:1}to{bottom:0;opacity:0}}@media (min-width:1440px){#app-container{max-width:114rem}}@media (max-width:960px){#mountPoint{padding:0}.pinned{width:16rem}}@media (max-width:640px){#app-container{width:100%;border-radius:0;box-shadow:none}#sidepanel{width:100%}#sidepanel.message-center-panel,#sidepanel.message-center-panel.full-page{width:100%;min-width:0;flex-basis:100%}#sidepanel.call-experience-panel,#sidepanel.contact-management-panel,#sidepanel.content-library-panel,#sidepanel.direct-message-panel,#sidepanel.group-management-panel,#sidepanel.message-composer-panel{width:100%;min-width:0}.call-active-controls,.call-video-controls{padding-right:.35rem;padding-left:.35rem}.call-control{width:4.5rem;min-width:4.5rem}.call-control>span{width:3.4rem;height:3.4rem}.library-media-grid{grid-template-columns:repeat(3,1fr)}.library-link-card{grid-template-columns:4.3rem minmax(0,1fr)}.library-link-card>img{width:4.3rem;height:4.3rem}.people-profile-actions{grid-template-columns:1fr}.compose-emoji-panel>div{grid-template-columns:repeat(7,1fr)}.compose-picker-header{grid-template-columns:4rem minmax(0,1fr) 4.5rem}.compose-forward-confirm,.compose-upload-page>main{padding:1rem}.chat-product-timeline{padding:1.25rem 1rem}.chat-message-stack{max-width:calc(100% - 4rem)}.chat-bubble{font-size:.94rem}.chat-file-card{min-width:min(17rem,calc(100vw - 6rem))}.chat-contact-card{min-width:min(16rem,calc(100vw - 6rem))}.chat-composer{padding-right:.4rem;padding-left:.4rem}.chat-attachment-panel{grid-template-columns:repeat(4,1fr);gap:.7rem;padding:1rem .75rem max(1rem,env(safe-area-inset-bottom))}.chat-attachment-panel button>span{width:3.3rem;height:3.3rem}.messages-conversation{grid-template-columns:3.55rem minmax(0,1fr) auto;min-height:4.8rem;padding-right:1rem;padding-left:1rem}.messages-frequent-scroll{gap:1rem;padding-right:1rem;padding-left:1rem}.messages-frequent h2{margin-left:1rem}.messages-search{margin-right:1rem;margin-left:1rem}.system-notification-list{padding:1rem}.auth-landing{padding-right:1.25rem;padding-left:1.25rem}.auth-brand{margin-bottom:3rem}.auth-content{padding:1.25rem}.auth-reset-content{padding:1rem 0}.auth-list-form{min-height:calc(100vh - 6.25rem)}.auth-list-form label{grid-template-columns:6.4rem 1fr auto}.auth-code-button{min-width:5.8rem;padding:0 .4rem}.auth-realname-hero{min-height:12rem;padding:1.5rem}.auth-realname-hero strong{font-size:2rem}.auth-realname-hero>.material-icons{font-size:6rem}.auth-realname-content form{padding:1.25rem}.auth-realname-content label{grid-template-columns:6.2rem 1fr}.auth-screenshot-grid{grid-template-columns:repeat(3,1fr)}#info-view{width:100%}.forward-dialog{border-radius:.3rem .3rem 0 0;margin:auto 0 0;width:100%}.menuTrigger{opacity:.3}#send-message-input{transition-property:-tinode-mobile}#pinned-wrapper{height:3.5rem;margin:.25rem}.pinned{width:unset}#video-container.minimized:not(.audio-minimized){flex-direction:row;left:0;right:auto;top:var(--caption-panel-height);width:100%;height:11rem;border-radius:unset;box-shadow:0 0 .5rem var(--clr-shadow-gray-strong)}#controls.minimized{flex-direction:column;row-gap:.5rem;bottom:auto;column-gap:unset;right:1rem}.minimized .call-party{border-radius:unset;box-shadow:unset}}@media (max-width:768px){.call-party.self{bottom:8.5rem;border-radius:unset}}#sidepanel.personal-settings-panel{width:100%;min-width:0;flex:1 1 auto;color-scheme:light;background:#f5f6f8}.personal-settings-panel *{box-sizing:border-box;letter-spacing:0}.personal-page{display:flex;position:relative;width:100%;min-height:100%;overflow:hidden;color:#262b31;background:#f5f6f8;flex-direction:column}.personal-page>.personal-page{min-height:0;flex:1 1 auto}.personal-page button,.personal-page input,.personal-page textarea{font:inherit;letter-spacing:0}.personal-page button{text-transform:none}.personal-page>main{min-height:0;overflow-y:auto;flex:1 1 auto}.personal-header{display:grid;grid-template-columns:3.7rem minmax(0,1fr) 3.7rem;align-items:center;min-height:4.5rem;padding-top:env(safe-area-inset-top);border-bottom:1px solid #e7e9ec;background:#fff;flex:none}.personal-header h1{min-width:0;margin:0;overflow:hidden;font-size:1.15rem;font-weight:650;text-align:center;text-overflow:ellipsis;white-space:nowrap}.personal-header button{display:grid;place-items:center;width:3.7rem;min-width:3.7rem;height:3.7rem;min-height:3.7rem;margin:0;padding:0;border:0;box-shadow:none;color:#252a30;background:transparent}.personal-header button.text{color:#2873d0;font-size:.9rem}.personal-header .material-icons{font-size:1.55rem}.personal-toast{position:fixed;z-index:90;bottom:max(1.5rem,env(safe-area-inset-bottom));left:50%;max-width:calc(100% - 2rem);padding:.7rem 1rem;border-radius:.3rem;color:#fff;background:rgba(25,29,34,.94);text-align:center;transform:translateX(-50%)}.personal-center-page>main{padding:.85rem 0}.android-settings-list section,.personal-center-page section,.personal-menu-section,.privacy-list section{margin-bottom:.75rem;background:#fff}.personal-identity{display:grid;grid-template-columns:4.5rem minmax(0,1fr) 1.5rem;align-items:center;gap:.8rem;width:calc(100% - 1.6rem);min-height:7rem;margin:0 .8rem .8rem;padding:.9rem;border:0;border-radius:.45rem;box-shadow:none;color:#2e3339;background:#fff;text-align:left}.personal-identity img{width:4.5rem;height:4.5rem;border-radius:.55rem;object-fit:cover}.personal-identity .personal-avatar-fallback{display:flex;align-items:center;justify-content:center;width:4.5rem;height:4.5rem;border-radius:.55rem;color:#fff;background:#b0b6bd}.personal-identity .personal-avatar-fallback .material-icons{font-size:2rem}.personal-identity small,.personal-identity span,.personal-identity strong{display:block;min-width:0}.personal-identity strong{margin-bottom:.35rem;overflow:hidden;font-size:1.25rem;line-height:1.3;text-overflow:ellipsis;white-space:nowrap}.personal-identity small{margin-top:.15rem;color:#8a919a;line-height:1.3}.personal-menu-row{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:.65rem;width:100%;min-height:4rem;margin:0;padding:.65rem 1rem;border:0;border-bottom:1px solid #eceef1;border-radius:0;box-shadow:none;color:#30353b;background:#fff;text-align:left}.personal-menu-row:has(>.material-icons:first-child){grid-template-columns:2.3rem minmax(0,1fr) auto}.personal-menu-row:last-child{border-bottom:0}.personal-menu-row:active:not([disabled]),.personal-menu-row:hover:not([disabled]){color:#30353b;background:var(--clr-menu-item-hover-bg)}.personal-menu-row.danger,.personal-menu-row.danger:active:not([disabled]),.personal-menu-row.danger:hover:not([disabled]){color:#d93f48;background:#fff}.personal-menu-row.danger:active:not([disabled]),.personal-menu-row.danger:hover:not([disabled]){background:var(--clr-menu-item-hover-bg)}.personal-menu-row.danger>.material-icons:first-child{color:#d93f48}.personal-menu-row>.material-icons:first-child{color:#59636e;font-size:1.55rem}.personal-menu-row>.material-icons:last-child{color:#a8aeb5}.personal-menu-row span{display:flex;align-items:center;justify-content:space-between;gap:.7rem;min-width:0}.personal-menu-row strong{min-width:0;font-weight:500}.personal-menu-row small{max-width:62%;overflow:hidden;color:#969da5;text-align:right;text-overflow:ellipsis;white-space:nowrap}.personal-profile-list{padding-top:.7rem;background:#fff}.personal-profile-list>button:first-child{display:grid;grid-template-columns:minmax(0,1fr) 3.8rem 1.5rem;align-items:center;width:100%;min-height:5rem;margin:0;padding:.6rem 1rem;border:0;border-bottom:1px solid #eceef1;border-radius:0;box-shadow:none;color:#30353b;background:#fff;text-align:left}.personal-profile-list>button:first-child img{width:3.5rem;height:3.5rem;border-radius:.45rem;object-fit:cover}.gender-option-list{padding-top:.7rem;background:#fff}.gender-option-list button{display:grid;grid-template-columns:minmax(0,1fr) 1.5rem;align-items:center;width:100%;min-height:3.6rem;margin:0;padding:.6rem 1rem;border:0;border-bottom:1px solid #eceef1;border-radius:0;box-shadow:none;color:#30353b;background:#fff;text-align:left}.gender-option-list button strong{font-weight:400}.gender-option-list button .material-icons{color:var(--clr-primary,#3b82f6)}.profile-editor-page>main{padding:1rem}.profile-editor-page>main>label{display:block;position:relative}.profile-editor-page label input,.profile-editor-page label textarea{width:100%;margin:0;padding:.75rem 3rem .75rem .75rem;border:1px solid #dbe0e5;border-radius:.35rem;box-shadow:none;background:#fff}.profile-editor-page label input:not([type=date]){height:3.2rem}.profile-editor-page label textarea{height:11rem;padding-bottom:2rem;resize:vertical;line-height:1.5}.profile-editor-page>main>label>small{position:absolute;right:.75rem;bottom:.65rem;color:#9ba2aa}.profile-editor-page>main>label>button{position:absolute;top:.1rem;right:2.7rem;display:grid;place-items:center;width:2.8rem;min-width:2.8rem;height:3rem;min-height:3rem;margin:0;padding:0;border:0;box-shadow:none;color:#a3a9b0;background:transparent}.profile-editor-page>main>p{margin:.65rem 0 0;color:#8a929b;font-size:.8rem}.birth-date-editor{display:flex;align-items:center;padding-top:4rem!important;flex-direction:column}.birth-date-editor>.material-icons{color:#d66372;font-size:4rem}.birth-date-editor label{width:min(100%,26rem);margin-top:1.5rem}.birth-date-editor label span{display:block;margin-bottom:.5rem;font-weight:600}.birth-date-editor label input{height:3.2rem;padding:0 .75rem}.change-password-fields label{display:block;width:min(100%,26rem);margin-top:1.5rem}.change-password-fields label span{display:block;margin-bottom:.5rem;font-weight:600}.change-password-fields label input{height:3.2rem;padding:0 .75rem}.personal-qr-page>main{padding:1rem}.personal-qr-page main>section{width:min(100%,27rem);margin:0 auto;padding:1.2rem;border-radius:.45rem;background:#fff;text-align:center}.personal-qr-profile{display:flex;align-items:center;gap:.7rem;text-align:left}.personal-qr-profile img{width:3.3rem;height:3.3rem;border-radius:.4rem;object-fit:cover}.personal-qr-profile small,.personal-qr-profile span,.personal-qr-profile strong{display:block}.personal-qr-profile small{margin-top:.2rem;color:#858d96}.personal-qrcode{display:inline-block;width:226px;height:226px;margin:1.5rem auto .8rem;padding:8px;border:1px solid #eceef1;background:#fff}.personal-qrcode canvas,.personal-qrcode img{width:210px!important;height:210px!important}.personal-qr-page section>p{margin:.5rem 0}.personal-qr-page section>small{color:#8d949d}.personal-qr-actions{display:grid;grid-template-columns:repeat(2,1fr);width:min(100%,27rem);margin:.8rem auto 0;background:#fff}.personal-qr-actions button{display:flex;align-items:center;justify-content:center;min-height:3.4rem;margin:0;padding:0;border:0;box-shadow:none;color:#3476cd;background:transparent}.personal-qr-actions button+button{border-left:1px solid #eceef1}.personal-qr-actions .material-icons{margin-right:.3rem}.android-settings-list{padding:.75rem 0 1.5rem}.personal-logout{width:100%;min-height:3.8rem;margin:0;padding:0;border:0;box-shadow:none;color:#d93f48;background:#fff}.permission-overlay,.personal-sheet-backdrop,.update-backdrop{display:grid;position:absolute;z-index:70;inset:0;place-items:center;padding:1rem;background:rgba(22,26,31,.52)}.personal-confirm-sheet{width:min(100%,25rem);padding:1.25rem;border-radius:.5rem;background:#fff;text-align:center}.personal-confirm-sheet>.material-icons{color:#4d73aa;font-size:2.8rem}.personal-confirm-sheet h2{margin:.65rem 0;font-size:1.15rem}.personal-confirm-sheet p{margin:.7rem 0 1rem;color:#5c646e;line-height:1.6;text-align:left}.personal-confirm-sheet input{width:100%;height:3rem;margin:0 0 .8rem;padding:0 .75rem;border:1px solid #dce0e5;border-radius:.35rem;box-shadow:none;text-align:center}.personal-confirm-sheet button{width:100%;min-height:2.9rem;margin:.45rem 0 0;padding:0 1rem;border:0;border-radius:.35rem;box-shadow:none;color:#4d5660;background:#f0f2f4}.personal-confirm-sheet button.primary{color:#fff;background:#347bd7}.personal-confirm-sheet button.danger{color:#fff;background:#d84950}.app-version-page>main{padding:2rem 1rem}.app-brand{margin-bottom:2rem;text-align:center}.app-brand>span{display:grid;place-items:center;width:6.5rem;height:6.5rem;margin:0 auto .8rem;border-radius:1.2rem;color:#fff;background:#2878d6}.app-brand .material-icons{font-size:4.2rem}.app-brand h2,.app-brand p{margin:0}.app-brand h2{font-size:1.25rem}.app-brand p{margin-top:.25rem;color:#777f89}.update-dialog{width:min(100%,25rem);padding:1.3rem;border-radius:.55rem;background:#fff;text-align:center}.update-dialog>span{display:grid;place-items:center;width:5rem;height:5rem;margin:0 auto .8rem;border-radius:50%;color:#fff;background:#4e74b7}.update-dialog>span .material-icons{font-size:3.2rem}.update-dialog h2{margin:0;font-size:1.35rem}.update-dialog>strong{display:block;margin-top:.25rem;color:#69717b}.update-dialog ul{margin:1rem 0;padding-left:1.25rem;color:#4e5660;line-height:1.65;text-align:left}.update-dialog button{width:100%;min-height:3rem;margin:.45rem 0 0;padding:0 1rem;border:0;border-radius:.35rem;box-shadow:none;color:#777f88;background:transparent}.update-dialog button:first-of-type{color:#fff;background:#347bd7}.privacy-list{padding-top:.75rem}.privacy-list section>div{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:.8rem;min-height:4.6rem;padding:.7rem 1rem;border-bottom:1px solid #eceef1}.privacy-list section>div:last-child{border-bottom:0}.privacy-list section>div small,.privacy-list section>div span,.privacy-list section>div strong{display:block;min-width:0}.privacy-list section>div small{margin-top:.25rem;color:#8b929b;line-height:1.35}.personal-switch{position:relative;width:3.25rem;min-width:3.25rem;height:1.9rem;min-height:1.9rem;margin:0;padding:0;border:0;border-radius:1rem;box-shadow:none;background:#d8dce1}.personal-switch:disabled{opacity:.45;cursor:not-allowed}.personal-switch i{position:absolute;top:.15rem;left:.15rem;width:1.6rem;height:1.6rem;border-radius:50%;background:#fff;box-shadow:0 1px 4px rgba(0,0,0,.2);transition:transform .16s ease}.personal-switch.checked{background:#347bd7}.personal-switch.checked i{transform:translateX(1.35rem)}.privacy-note{margin:0;padding:0 1rem .75rem;color:#7e8690;font-size:.82rem;line-height:1.5}.youth-mode-page{background:#fff}.youth-mode-page>main{padding:3.2rem 1.5rem 1rem;text-align:center}.youth-mode-page>main>.material-icons{color:#347bd7;font-size:5rem}.youth-mode-page h2{margin:1rem 0 1.5rem;font-size:1.45rem}.youth-mode-page ul{margin:0;padding:0;list-style:none;text-align:left}.youth-mode-page li{position:relative;margin-bottom:1.2rem;padding-left:1.7rem;line-height:1.55}.youth-mode-page li:before{position:absolute;top:.55rem;left:.2rem;width:.55rem;height:.55rem;border-radius:50%;background:#347bd7;content:""}.youth-usage{margin-top:1.8rem;color:#868e97}.youth-mode-page>footer{padding:.8rem 1rem max(1rem,env(safe-area-inset-bottom));background:#fff;text-align:center;flex:none}.youth-mode-page>footer button{width:100%;min-height:3.2rem;margin:0;padding:0 1rem;border:0;border-radius:.35rem;box-shadow:none;color:#fff;background:#347bd7}.youth-mode-page>footer button.outline{border:1px solid #347bd7;color:#347bd7;background:#fff}.youth-mode-page>footer small{display:block;margin-top:.65rem;color:#9aa1a9}.permission-guide-page{background:#fff}.permission-preview{padding:1.2rem 1rem;opacity:.72}.permission-preview>header{display:flex;align-items:center;justify-content:space-between}.permission-preview h1{margin:0;font-size:1.35rem}.permission-preview>label{display:flex;align-items:center;justify-content:center;min-height:3rem;margin:1rem 0;border-radius:.35rem;color:#8d949c;background:#f0f2f4}.permission-preview>label .material-icons{margin-right:.35rem}.permission-preview>div{display:grid;grid-template-columns:3.4rem minmax(0,1fr);align-items:center;gap:.7rem;min-height:4.6rem;border-bottom:1px solid #eceef1}.permission-preview>div small,.permission-preview>div strong,.permission-preview>div>span:last-child{display:block;min-width:0}.permission-preview>div small{margin-top:.2rem;color:#8b929b}.permission-avatar{display:grid;place-items:center;width:3.1rem;height:3.1rem;border-radius:.45rem;color:#fff;background:#5a77a9}.permission-avatar.tone-0{background:#d18128}.permission-avatar.tone-1{background:#4673bd}.permission-avatar.tone-2{background:#398258}.permission-avatar.tone-3{background:#9c5f7a}.permission-overlay section{width:min(100%,24rem);padding:1.3rem;border-radius:.5rem;background:#fff;text-align:center}.permission-overlay section>span{display:grid;place-items:center;width:4.6rem;height:4.6rem;margin:0 auto .8rem;border-radius:50%;color:#fff;background:#347bd7}.permission-overlay section>span .material-icons{font-size:2.7rem}.permission-overlay h2{margin:0;font-size:1.2rem}.permission-overlay p{margin:.8rem 0 1rem;color:#555d67;line-height:1.6;text-align:left}.permission-overlay button{width:100%;min-height:3rem;margin:.4rem 0 0;padding:0 1rem;border:0;border-radius:.35rem;box-shadow:none;color:#8a919a;background:transparent}.permission-overlay button:first-of-type{color:#fff;background:#347bd7}@media screen and (max-width:480px){.personal-menu-row{min-height:3.8rem}.personal-menu-row small{max-width:55%}.personal-qrcode{margin-top:1rem}.youth-mode-page>main{padding-top:2rem}}#sidepanel.security-support-panel{width:100%;min-width:0;flex:1 1 auto;color-scheme:light;background:#f5f6f8}.security-support-panel *{box-sizing:border-box;letter-spacing:0}.support-page{display:flex;position:relative;width:100%;min-height:100%;overflow:hidden;color:#272c32;background:#f5f6f8;flex-direction:column}.support-page button,.support-page input,.support-page textarea{font:inherit;letter-spacing:0}.support-page button{text-transform:none}.support-page>main{min-height:0;overflow-y:auto;flex:1 1 auto}.support-header{display:grid;grid-template-columns:3.7rem minmax(0,1fr) 3.7rem;align-items:center;min-height:4.5rem;padding-top:env(safe-area-inset-top);border-bottom:1px solid #e6e9ec;background:#fff;flex:none}.support-header h1{min-width:0;margin:0;overflow:hidden;font-size:1.15rem;font-weight:650;text-align:center;text-overflow:ellipsis;white-space:nowrap}.support-header button{display:grid;place-items:center;width:3.7rem;min-width:3.7rem;height:3.7rem;min-height:3.7rem;margin:0;padding:0;border:0;box-shadow:none;color:#272c32;background:transparent}.support-toast{position:fixed;z-index:100;bottom:max(1.5rem,env(safe-area-inset-bottom));left:50%;max-width:calc(100% - 2rem);padding:.7rem 1rem;border-radius:.3rem;color:#fff;background:rgba(25,29,34,.94);text-align:center;transform:translateX(-50%)}.risk-warning-page{background:#fff}.risk-conversation-preview{min-height:100%;opacity:.78}.risk-conversation-preview>header{display:grid;grid-template-columns:3.5rem minmax(0,1fr) 3.5rem;align-items:center;min-height:4.5rem;padding-top:env(safe-area-inset-top);border-bottom:1px solid #e7e9ec;background:#fff;text-align:center}.risk-conversation-preview>header button{display:grid;place-items:center;width:3.5rem;height:3.5rem;margin:0;padding:0;border:0;box-shadow:none;background:transparent}.risk-conversation-preview>header small,.risk-conversation-preview>header span,.risk-conversation-preview>header strong{display:block}.risk-conversation-preview>header small{margin-top:.15rem;color:#3d9a68}.risk-conversation-preview>main{min-height:calc(100vh - 8.8rem);padding:1rem;background:#eef0f3}.risk-conversation-preview>main>p{margin:0 0 1rem;color:#8b929b;font-size:.75rem;text-align:center}.risk-conversation-preview main>div{width:fit-content;max-width:78%;margin-bottom:.65rem;padding:.7rem .85rem;border-radius:.35rem;background:#fff;line-height:1.5}.risk-conversation-preview main>div.mine{margin-left:auto;background:#bde7a9}.risk-conversation-preview>footer{display:flex;align-items:center;justify-content:space-between;min-height:4.3rem;padding:.6rem 1rem max(.6rem,env(safe-area-inset-bottom));border-top:1px solid #e0e3e7;color:#959ca5;background:#fff}.risk-conversation-preview>footer span{width:calc(100% - 2.7rem);padding:.65rem .75rem;border:1px solid #e0e3e7;border-radius:.3rem;background:#f7f8f9}.risk-overlay{display:grid;position:absolute;z-index:70;inset:0;place-items:center;padding:1rem;background:rgba(19,23,28,.55)}.risk-dialog{width:min(100%,25rem);max-height:calc(100% - 2rem);padding:1.15rem;overflow-y:auto;border-radius:.55rem;background:#fff;text-align:center}.risk-info-page{margin:0 auto}.risk-symbol{display:grid;place-items:center;width:5rem;height:5rem;margin:0 auto .6rem;border-radius:50%;color:#fff;background:#3979cd}.risk-dialog.secondary .risk-symbol{color:#9b5e08;background:#fff0d5}.risk-symbol .material-icons{font-size:3.2rem}.risk-dialog h2{margin:0;font-size:1.35rem}.risk-dialog>p{margin:.75rem 0;color:#4d555e;line-height:1.65;text-align:left}.risk-dialog>p.risk-emphasis{color:#b1363f;font-weight:650;text-align:center}.risk-dialog ul{margin:.9rem 0;padding:.25rem .75rem;border:1px solid #d9e5f4;border-radius:.4rem;list-style:none}.risk-dialog li{display:flex;align-items:center;min-height:2.7rem;border-bottom:1px solid #e7edf4;line-height:1.35;text-align:left}.risk-dialog li:last-child{border-bottom:0}.risk-dialog li .material-icons{margin-right:.55rem;color:#347bd7;font-size:1.35rem}.risk-dialog aside{display:flex;align-items:flex-start;gap:.5rem;margin:.8rem 0;padding:.7rem;border-radius:.35rem;color:#9e333b;background:#fff0f1;line-height:1.45;text-align:left}.risk-dialog button{width:100%;min-height:3rem;margin:.45rem 0 0;padding:0 .75rem;border:0;border-radius:.35rem;box-shadow:none;color:#7f8790;background:transparent}.risk-dialog button.primary{color:#fff;background:#347bd7}.risk-dialog button.danger{color:#fff;background:#cc3d46}.report-reasons{padding-bottom:1rem}.report-target{display:grid;grid-template-columns:3.5rem minmax(0,1fr) 2rem;align-items:center;gap:.75rem;min-height:5.3rem;padding:.8rem 1rem;background:#fff}.report-target img{width:3.5rem;height:3.5rem;border-radius:.45rem;object-fit:cover}.report-target small,.report-target span,.report-target strong{display:block;min-width:0}.report-target small{margin-top:.25rem;color:#818a94}.report-target>.material-icons{color:#3b8661}.report-reasons>h2{font-weight:500}.report-loading,.report-reasons>h2{margin:0;padding:1rem;color:#6e7781;font-size:.85rem}.report-reason-list{background:#fff}.report-reason-list button{display:grid;grid-template-columns:minmax(0,1fr) 1.5rem;align-items:center;gap:.55rem;width:100%;min-height:4.3rem;margin:0;padding:.65rem 1rem;border:0;border-bottom:1px solid #eceef1;border-radius:0;box-shadow:none;color:#30353c;background:#fff;text-align:left}.report-reason-list button>span:first-child,.report-reason-list small,.report-reason-list strong{display:block;min-width:0}.report-reason-list small{margin-top:.2rem;overflow:hidden;color:#858d97;font-size:.76rem;text-overflow:ellipsis;white-space:nowrap}.report-reason-list button>.material-icons:last-child{color:#a8aeb5}.feedback-page>main,.report-details{padding-bottom:1rem}.report-selected-reason{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:.7rem;padding:.9rem 1rem;background:#fff}.report-selected-reason>.material-icons{color:#b14650;font-size:2rem}.report-selected-reason small,.report-selected-reason span,.report-selected-reason strong{display:block;min-width:0}.report-selected-reason small{margin-bottom:.2rem;color:#858d96}.report-selected-reason button{min-height:2.5rem;margin:0;padding:0 .5rem;border:0;box-shadow:none;color:#3476ce;background:transparent}.support-textarea{display:block;position:relative;margin-top:.7rem;padding:1rem;background:#fff}.support-textarea>span{display:block;margin-bottom:.6rem;font-weight:600}.support-textarea b{margin-left:.2rem;color:#d6424b}.support-textarea textarea{display:block;width:100%;height:10.5rem;margin:0;padding:.75rem .8rem 2rem;resize:vertical;border:0;box-shadow:none;background:#fff;line-height:1.5}.support-textarea>small{position:absolute;right:1.8rem;bottom:1.6rem;color:#9ba2ab}.evidence-section,.feedback-upload{margin-top:.7rem;padding:1rem;background:#fff}.evidence-section>header,.feedback-upload>header{display:flex;align-items:center;justify-content:space-between;gap:.6rem;margin-bottom:.8rem}.evidence-section header small,.evidence-section header span,.evidence-section header strong,.feedback-upload header small,.feedback-upload header span,.feedback-upload header strong{display:block;min-width:0}.evidence-section header small,.feedback-upload header small{margin-top:.2rem;color:#858d96}.evidence-section header em,.feedback-upload header em{color:#858d96;font-style:normal}.evidence-section article{display:grid;grid-template-columns:2.5rem minmax(0,1fr) 2.5rem;align-items:center;min-height:3.5rem;border-bottom:1px solid #eceef1}.evidence-section article>.material-icons{color:#5273a8}.evidence-section article>span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.evidence-section article button{display:grid;place-items:center;width:2.5rem;height:2.5rem;margin:0;padding:0;border:0;box-shadow:none;color:#989fa7;background:transparent}.evidence-upload{width:100%;min-height:4.5rem;border:1px dashed #cbd1d8;border-radius:.35rem;color:#3476ce;background:#f8f9fa}.evidence-upload,.feedback-upload>button{display:flex;align-items:center;justify-content:center;margin:0;padding:.7rem;box-shadow:none;flex-direction:column}.feedback-upload>button{width:7.5rem;height:7.5rem;border:0;border-radius:.35rem;color:#858d96;background:#fff}.evidence-upload .material-icons,.feedback-upload>button .material-icons{margin-bottom:.25rem;font-size:1.8rem}.report-privacy-note{display:flex;align-items:flex-start;gap:.5rem;margin:.8rem 1rem 0;color:#74808b;font-size:.78rem;line-height:1.45}.report-privacy-note .material-icons{color:#4b8065;font-size:1.05rem}.support-submit-bar{padding:.75rem 1rem max(.8rem,env(safe-area-inset-bottom));border-top:1px solid #e1e4e8;background:#fff;flex:none}.support-submit-bar button,.support-success>button{width:100%;min-height:3.1rem;margin:0;padding:0 1rem;border:0;border-radius:.35rem;box-shadow:none;color:#fff;background:#347bd7;font-weight:600}.support-success{display:flex;align-items:center;padding:4rem 1.2rem 1.5rem;text-align:center;flex-direction:column}.support-success>.material-icons{color:#3a9365;font-size:5rem}.support-success h2{margin:1rem 0 .55rem;font-size:1.35rem}.support-success>p{max-width:26rem;margin:0;color:#66707a;line-height:1.6}.support-success dl{width:min(100%,26rem);margin:1.5rem 0;padding:.5rem 1rem;border:1px solid #e0e4e8;border-radius:.4rem;background:#fff}.support-success dl>div{display:flex;align-items:center;justify-content:space-between;min-height:3rem;border-bottom:1px solid #eceef1}.support-success dl>div:last-child{border-bottom:0}.support-success dd,.support-success dt{margin:0}.support-success dt{color:#7c858f}.support-success>button{width:min(100%,26rem)}.feedback-categories{padding:1rem;background:#fff}.feedback-categories h2{margin:0 0 .8rem;font-size:1rem}.feedback-categories>div{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:.55rem}.feedback-categories button{display:flex;align-items:center;justify-content:center;min-width:0;min-height:2.75rem;margin:0;padding:.45rem;border:1px solid #e0e4e8;border-radius:.35rem;box-shadow:none;color:#666f79;background:#fff}.feedback-categories button.active{border-color:#347bd7;color:#2f70c7;background:#eef5ff}.feedback-categories .material-icons{margin-bottom:.25rem;font-size:1.45rem}.feedback-categories button span{width:100%;overflow-wrap:anywhere;font-size:.76rem;line-height:1.25;text-align:center}.feedback-contact{display:block;padding:1rem;background:#fff}.feedback-contact>span{display:block;margin-bottom:.55rem;font-weight:600}.feedback-contact input{width:100%;height:3rem;margin:0;padding:0 .75rem;border:1px solid #dfe3e7;border-radius:.35rem;box-shadow:none;background:#fff}.diagnostics-consent{display:grid;grid-template-columns:1.8rem minmax(0,1fr);align-items:flex-start;gap:.5rem;margin-top:.7rem;padding:1rem;background:#fff}.diagnostics-consent input{width:1.25rem;height:1.25rem;margin:.1rem 0 0}.diagnostics-consent small,.diagnostics-consent span,.diagnostics-consent strong{display:block;min-width:0}.diagnostics-consent small{margin-top:.3rem;color:#7e8791;line-height:1.45}@media screen and (max-width:480px){.risk-dialog{padding:1rem}.risk-symbol{width:4.3rem;height:4.3rem}.risk-symbol .material-icons{font-size:2.8rem}.feedback-categories>div{gap:.35rem}}.qr-scan-view{position:fixed;z-index:200;inset:0;overflow:hidden;background:#000}.qr-scan-video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}.qr-scan-mask{position:absolute;inset:0;display:grid;place-items:center}.qr-scan-frame{position:relative;width:62vw;max-width:17rem;aspect-ratio:1;border-radius:.5rem;box-shadow:0 0 0 999rem rgba(0,0,0,.5)}.qr-scan-corner{position:absolute;width:1.6rem;height:1.6rem;border:.22rem solid #4d8dff;font-style:normal}.qr-scan-corner-tl{top:-.11rem;left:-.11rem;border-right:0;border-bottom:0;border-radius:.4rem 0 0 0}.qr-scan-corner-tr{top:-.11rem;right:-.11rem;border-left:0;border-bottom:0;border-radius:0 .4rem 0 0}.qr-scan-corner-bl{bottom:-.11rem;left:-.11rem;border-right:0;border-top:0;border-radius:0 0 0 .4rem}.qr-scan-corner-br{bottom:-.11rem;right:-.11rem;border-left:0;border-top:0;border-radius:0 0 .4rem 0}.qr-scan-header{position:absolute;z-index:2;top:0;left:0;right:0;display:flex;align-items:center;justify-content:space-between;padding:.9rem .5rem;background:linear-gradient(rgba(0,0,0,.45),transparent)}.qr-scan-header button{width:2.6rem;height:2.6rem;border:0;box-shadow:none;color:#fff;background:transparent}.qr-scan-header h1{margin:0;color:#fff;font-size:1.05rem;font-weight:500}.qr-scan-header span{width:2.6rem}.qr-scan-hint{left:0;right:0;bottom:14%;margin:0;color:#e7ebf0;font-size:.9rem}.qr-scan-error,.qr-scan-hint{position:absolute;text-align:center}.qr-scan-error{inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.6rem;padding:0 2rem;color:#fff}.qr-scan-error .material-icons{font-size:2.6rem;color:#f2b53d}.qr-scan-error button{margin-top:.6rem;padding:0 1.4rem;min-height:2.6rem;border:0;border-radius:1.4rem;box-shadow:none;color:#fff;background:#347bd7}.qr-join-backdrop{position:fixed;z-index:200;inset:0;display:grid;place-items:center;padding:1rem;background:rgba(22,26,31,.52)}.qr-join-sheet{width:min(100%,25rem);padding:1.4rem 1.25rem 1.25rem;border-radius:.5rem;background:#fff;text-align:center}.qr-join-avatar{display:grid;place-items:center;width:4rem;height:4rem;margin:0 auto .6rem;border-radius:50%;overflow:hidden;color:#9aa3ad;background:#eef1f4}.qr-join-avatar img{width:100%;height:100%;object-fit:cover}.qr-join-avatar .material-icons{font-size:1.9rem}.qr-join-sheet h2{margin:.2rem 0;font-size:1.1rem}.qr-join-sheet>p{margin:0 0 .9rem;color:#757d87}.qr-join-sheet input{width:100%;height:2.9rem;margin:0 0 .8rem;padding:0 .75rem;border:1px solid #dce0e5;border-radius:.35rem;box-shadow:none;text-align:center}.qr-join-sheet button{width:100%;min-height:2.9rem;margin:.45rem 0 0;border:0;border-radius:.35rem;box-shadow:none;color:#4d5660;background:#f0f2f4}.qr-join-sheet button.primary{color:#fff;background:#347bd7}.forward-bundle{min-width:11rem;max-width:18rem}.forward-bundle-title{margin-bottom:.35rem;font-weight:500}.forward-bundle-lines{margin:0;padding:0;list-style:none}.forward-bundle-lines li{overflow:hidden;margin:.15rem 0;color:var(--clr-text-secondary);font-size:.85rem;line-height:1.35;text-overflow:ellipsis;white-space:nowrap}.forward-bundle-lines li b{font-weight:500}.forward-bundle-count,.forward-bundle-more{display:block;width:100%;margin:.4rem 0 0;padding:.35rem 0 0;border:0;border-top:1px solid var(--clr-divider,#e7e9ec);border-radius:0;box-shadow:none;background:none;color:var(--clr-text-secondary);font-size:.8rem;line-height:1.3;text-align:left;text-transform:none}.forward-bundle-more{color:#347bd7;cursor:pointer}.forward-bundle-comment{margin-top:.4rem;color:var(--clr-text-primary);font-size:.85rem}
\ No newline at end of file
diff --git a/firebase-init.js b/firebase-init.js
deleted file mode 100644
index ce3e5759..00000000
--- a/firebase-init.js
+++ /dev/null
@@ -1,15 +0,0 @@
-// Get these values for your project from the https://console.firebase.google.com/
-const FIREBASE_INIT = {
- // Set up a web application in console then get the config object.
- // The following keys are copied from the object:
- apiKey: "AIzaSyD6X4ULR-RUsobvs1zZ2bHdJuPz39q2tbQ",
- messagingSenderId: "114126160546",
- projectId: "tinode-1000",
- appId: "1:114126160546:web:aca6ea2981feb81fb44dfb",
- // Project Settings -> Cloud Messaging -> Web configuration -> Web Push certificates.
- // This value IS NOT included in the generated js config you get from
- // the firebase console. It needs to be added separately.
- messagingVapidKey: "BOgQVPOMzIMXUpsYGpbVkZoEBc0ifKY_f2kSU5DNDGYI6i6CoKqqxDd7w7PJ3FaGRBgVGJffldETumOx831jl58",
- // Google analytics tag. Uncomment and use one you got from https://analytics.google.com/.
- // measurementId: "G-ABCDEF1234"
-};
diff --git a/img/badge96.png b/img/badge96.png
deleted file mode 100755
index 586da9b0..00000000
Binary files a/img/badge96.png and /dev/null differ
diff --git a/img/bkg/a00.png b/img/bkg/a00.png
deleted file mode 100755
index d4eeb9f1..00000000
Binary files a/img/bkg/a00.png and /dev/null differ
diff --git a/img/bkg/a01.jpg b/img/bkg/a01.jpg
deleted file mode 100644
index d84f4da0..00000000
Binary files a/img/bkg/a01.jpg and /dev/null differ
diff --git a/img/bkg/a02.jpg b/img/bkg/a02.jpg
deleted file mode 100644
index 11f30be5..00000000
Binary files a/img/bkg/a02.jpg and /dev/null differ
diff --git a/img/bkg/a03.jpg b/img/bkg/a03.jpg
deleted file mode 100644
index f2d52121..00000000
Binary files a/img/bkg/a03.jpg and /dev/null differ
diff --git a/img/bkg/a04.png b/img/bkg/a04.png
deleted file mode 100644
index 5220feb2..00000000
Binary files a/img/bkg/a04.png and /dev/null differ
diff --git a/img/bkg/a05.jpg b/img/bkg/a05.jpg
deleted file mode 100644
index b102aecb..00000000
Binary files a/img/bkg/a05.jpg and /dev/null differ
diff --git a/img/bkg/a06.jpg b/img/bkg/a06.jpg
deleted file mode 100644
index 1898bac1..00000000
Binary files a/img/bkg/a06.jpg and /dev/null differ
diff --git a/img/bkg/a07.jpg b/img/bkg/a07.jpg
deleted file mode 100644
index b2d56f0a..00000000
Binary files a/img/bkg/a07.jpg and /dev/null differ
diff --git a/img/bkg/a08.jpg b/img/bkg/a08.jpg
deleted file mode 100644
index 58f2da90..00000000
Binary files a/img/bkg/a08.jpg and /dev/null differ
diff --git a/img/bkg/a09.jpg b/img/bkg/a09.jpg
deleted file mode 100644
index 45793d91..00000000
Binary files a/img/bkg/a09.jpg and /dev/null differ
diff --git a/img/bkg/a10.jpg b/img/bkg/a10.jpg
deleted file mode 100644
index 3c672c70..00000000
Binary files a/img/bkg/a10.jpg and /dev/null differ
diff --git a/img/bkg/a11.png b/img/bkg/a11.png
deleted file mode 100644
index 3506ad80..00000000
Binary files a/img/bkg/a11.png and /dev/null differ
diff --git a/img/bkg/index.json b/img/bkg/index.json
deleted file mode 100644
index 60177ea9..00000000
--- a/img/bkg/index.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "patt": [
- {"name": "a00.png", "size": 200}, {"name": "a01.jpg", "size": 384}, {"name": "a02.jpg", "size": 384},
- {"name": "a03.jpg", "size": 384}, {"name": "a04.png", "size": 400}, {"name": "a05.jpg", "size": 400},
- {"name": "a06.jpg", "size": 256}, {"name": "a07.jpg", "size": 384}, {"name": "a08.jpg", "size": 384},
- {"name": "a09.jpg", "size": 400}, {"name": "a10.jpg", "size": 256}, {"name": "a11.png", "size": 200}
- ],
- "img": [
- {"name": "w01.jpg", "pr": "p01.jpg"}, {"name": "w02.jpg", "pr": "p02.jpg"}, {"name": "w03.jpg", "pr": "p03.jpg"},
- {"name": "w04.jpg", "pr": "p04.jpg"}, {"name": "w05.jpg", "pr": "p05.jpg"}, {"name": "w06.jpg", "pr": "p06.jpg"},
- {"name": "w07.jpg", "pr": "p07.jpg"}, {"name": "w08.jpg", "pr": "p08.jpg"}, {"name": "w09.jpg", "pr": "p09.jpg"},
- {"name": "w10.jpg", "pr": "p10.jpg"}, {"name": "w11.jpg", "pr": "p11.jpg"}, {"name": "w12.jpg", "pr": "p12.jpg"},
- {"name": "w13.jpg", "pr": "p13.jpg"}, {"name": "w14.jpg", "pr": "p14.jpg"}, {"name": "w15.jpg", "pr": "p15.jpg"},
- {"name": "w16.jpg", "pr": "p16.jpg"}, {"name": "w17.jpg", "pr": "p17.jpg"}, {"name": "w18.jpg", "pr": "p18.jpg"},
- {"name": "w19.jpg", "pr": "p19.jpg"}, {"name": "w20.jpg", "pr": "p20.jpg"}, {"name": "w21.jpg", "pr": "p21.jpg"},
- {"name": "w22.jpg", "pr": "p22.jpg"}, {"name": "w23.jpg", "pr": "p23.jpg"}, {"name": "w24.jpg", "pr": "p24.jpg"}
- ]
-}
diff --git a/img/bkg/p01.jpg b/img/bkg/p01.jpg
deleted file mode 100644
index e28d42f9..00000000
Binary files a/img/bkg/p01.jpg and /dev/null differ
diff --git a/img/bkg/p02.jpg b/img/bkg/p02.jpg
deleted file mode 100644
index 66cdb743..00000000
Binary files a/img/bkg/p02.jpg and /dev/null differ
diff --git a/img/bkg/p03.jpg b/img/bkg/p03.jpg
deleted file mode 100644
index 33f0d7ad..00000000
Binary files a/img/bkg/p03.jpg and /dev/null differ
diff --git a/img/bkg/p04.jpg b/img/bkg/p04.jpg
deleted file mode 100644
index 30fd1995..00000000
Binary files a/img/bkg/p04.jpg and /dev/null differ
diff --git a/img/bkg/p05.jpg b/img/bkg/p05.jpg
deleted file mode 100644
index 4d371b4c..00000000
Binary files a/img/bkg/p05.jpg and /dev/null differ
diff --git a/img/bkg/p06.jpg b/img/bkg/p06.jpg
deleted file mode 100644
index 6f2cb4bd..00000000
Binary files a/img/bkg/p06.jpg and /dev/null differ
diff --git a/img/bkg/p07.jpg b/img/bkg/p07.jpg
deleted file mode 100644
index 4ca596db..00000000
Binary files a/img/bkg/p07.jpg and /dev/null differ
diff --git a/img/bkg/p08.jpg b/img/bkg/p08.jpg
deleted file mode 100644
index 1f17fcdd..00000000
Binary files a/img/bkg/p08.jpg and /dev/null differ
diff --git a/img/bkg/p09.jpg b/img/bkg/p09.jpg
deleted file mode 100644
index bf66e307..00000000
Binary files a/img/bkg/p09.jpg and /dev/null differ
diff --git a/img/bkg/p10.jpg b/img/bkg/p10.jpg
deleted file mode 100644
index e509235f..00000000
Binary files a/img/bkg/p10.jpg and /dev/null differ
diff --git a/img/bkg/p11.jpg b/img/bkg/p11.jpg
deleted file mode 100644
index b438bd57..00000000
Binary files a/img/bkg/p11.jpg and /dev/null differ
diff --git a/img/bkg/p12.jpg b/img/bkg/p12.jpg
deleted file mode 100644
index c580deca..00000000
Binary files a/img/bkg/p12.jpg and /dev/null differ
diff --git a/img/bkg/p13.jpg b/img/bkg/p13.jpg
deleted file mode 100644
index 382a797b..00000000
Binary files a/img/bkg/p13.jpg and /dev/null differ
diff --git a/img/bkg/p14.jpg b/img/bkg/p14.jpg
deleted file mode 100644
index 4566c172..00000000
Binary files a/img/bkg/p14.jpg and /dev/null differ
diff --git a/img/bkg/p15.jpg b/img/bkg/p15.jpg
deleted file mode 100644
index 45212c9b..00000000
Binary files a/img/bkg/p15.jpg and /dev/null differ
diff --git a/img/bkg/p16.jpg b/img/bkg/p16.jpg
deleted file mode 100644
index fc7f2a11..00000000
Binary files a/img/bkg/p16.jpg and /dev/null differ
diff --git a/img/bkg/p17.jpg b/img/bkg/p17.jpg
deleted file mode 100644
index 1e8197d5..00000000
Binary files a/img/bkg/p17.jpg and /dev/null differ
diff --git a/img/bkg/p18.jpg b/img/bkg/p18.jpg
deleted file mode 100644
index 772bef88..00000000
Binary files a/img/bkg/p18.jpg and /dev/null differ
diff --git a/img/bkg/p19.jpg b/img/bkg/p19.jpg
deleted file mode 100644
index 23419081..00000000
Binary files a/img/bkg/p19.jpg and /dev/null differ
diff --git a/img/bkg/p20.jpg b/img/bkg/p20.jpg
deleted file mode 100644
index 5bf50c9e..00000000
Binary files a/img/bkg/p20.jpg and /dev/null differ
diff --git a/img/bkg/p21.jpg b/img/bkg/p21.jpg
deleted file mode 100644
index 55622225..00000000
Binary files a/img/bkg/p21.jpg and /dev/null differ
diff --git a/img/bkg/p22.jpg b/img/bkg/p22.jpg
deleted file mode 100644
index be685dd4..00000000
Binary files a/img/bkg/p22.jpg and /dev/null differ
diff --git a/img/bkg/p23.jpg b/img/bkg/p23.jpg
deleted file mode 100644
index e55e9201..00000000
Binary files a/img/bkg/p23.jpg and /dev/null differ
diff --git a/img/bkg/p24.jpg b/img/bkg/p24.jpg
deleted file mode 100644
index 4943c623..00000000
Binary files a/img/bkg/p24.jpg and /dev/null differ
diff --git a/img/bkg/w01.jpg b/img/bkg/w01.jpg
deleted file mode 100644
index 865eb0c5..00000000
Binary files a/img/bkg/w01.jpg and /dev/null differ
diff --git a/img/bkg/w02.jpg b/img/bkg/w02.jpg
deleted file mode 100644
index 8c230b77..00000000
Binary files a/img/bkg/w02.jpg and /dev/null differ
diff --git a/img/bkg/w03.jpg b/img/bkg/w03.jpg
deleted file mode 100644
index abc3bfc1..00000000
Binary files a/img/bkg/w03.jpg and /dev/null differ
diff --git a/img/bkg/w04.jpg b/img/bkg/w04.jpg
deleted file mode 100644
index a2f7f6f2..00000000
Binary files a/img/bkg/w04.jpg and /dev/null differ
diff --git a/img/bkg/w05.jpg b/img/bkg/w05.jpg
deleted file mode 100644
index 8e9bd39f..00000000
Binary files a/img/bkg/w05.jpg and /dev/null differ
diff --git a/img/bkg/w06.jpg b/img/bkg/w06.jpg
deleted file mode 100644
index 18c6c472..00000000
Binary files a/img/bkg/w06.jpg and /dev/null differ
diff --git a/img/bkg/w07.jpg b/img/bkg/w07.jpg
deleted file mode 100644
index d2ac8a2a..00000000
Binary files a/img/bkg/w07.jpg and /dev/null differ
diff --git a/img/bkg/w08.jpg b/img/bkg/w08.jpg
deleted file mode 100644
index 79dd0e93..00000000
Binary files a/img/bkg/w08.jpg and /dev/null differ
diff --git a/img/bkg/w09.jpg b/img/bkg/w09.jpg
deleted file mode 100644
index f64c518c..00000000
Binary files a/img/bkg/w09.jpg and /dev/null differ
diff --git a/img/bkg/w10.jpg b/img/bkg/w10.jpg
deleted file mode 100644
index 584f2823..00000000
Binary files a/img/bkg/w10.jpg and /dev/null differ
diff --git a/img/bkg/w11.jpg b/img/bkg/w11.jpg
deleted file mode 100644
index 8700cf92..00000000
Binary files a/img/bkg/w11.jpg and /dev/null differ
diff --git a/img/bkg/w12.jpg b/img/bkg/w12.jpg
deleted file mode 100644
index 5dc14ea6..00000000
Binary files a/img/bkg/w12.jpg and /dev/null differ
diff --git a/img/bkg/w13.jpg b/img/bkg/w13.jpg
deleted file mode 100644
index 24633093..00000000
Binary files a/img/bkg/w13.jpg and /dev/null differ
diff --git a/img/bkg/w14.jpg b/img/bkg/w14.jpg
deleted file mode 100644
index e9b3f3a5..00000000
Binary files a/img/bkg/w14.jpg and /dev/null differ
diff --git a/img/bkg/w15.jpg b/img/bkg/w15.jpg
deleted file mode 100644
index 6fe10159..00000000
Binary files a/img/bkg/w15.jpg and /dev/null differ
diff --git a/img/bkg/w16.jpg b/img/bkg/w16.jpg
deleted file mode 100644
index d7ae60f2..00000000
Binary files a/img/bkg/w16.jpg and /dev/null differ
diff --git a/img/bkg/w17.jpg b/img/bkg/w17.jpg
deleted file mode 100644
index bd9c0b96..00000000
Binary files a/img/bkg/w17.jpg and /dev/null differ
diff --git a/img/bkg/w18.jpg b/img/bkg/w18.jpg
deleted file mode 100644
index cf671c34..00000000
Binary files a/img/bkg/w18.jpg and /dev/null differ
diff --git a/img/bkg/w19.jpg b/img/bkg/w19.jpg
deleted file mode 100644
index 1788183b..00000000
Binary files a/img/bkg/w19.jpg and /dev/null differ
diff --git a/img/bkg/w20.jpg b/img/bkg/w20.jpg
deleted file mode 100644
index 818bbc31..00000000
Binary files a/img/bkg/w20.jpg and /dev/null differ
diff --git a/img/bkg/w21.jpg b/img/bkg/w21.jpg
deleted file mode 100644
index 93ff1613..00000000
Binary files a/img/bkg/w21.jpg and /dev/null differ
diff --git a/img/bkg/w22.jpg b/img/bkg/w22.jpg
deleted file mode 100644
index 7ed9cd67..00000000
Binary files a/img/bkg/w22.jpg and /dev/null differ
diff --git a/img/bkg/w23.jpg b/img/bkg/w23.jpg
deleted file mode 100644
index 99691ce0..00000000
Binary files a/img/bkg/w23.jpg and /dev/null differ
diff --git a/img/bkg/w24.jpg b/img/bkg/w24.jpg
deleted file mode 100644
index 8904b363..00000000
Binary files a/img/bkg/w24.jpg and /dev/null differ
diff --git a/img/blankimg.png b/img/blankimg.png
deleted file mode 100644
index 6a637297..00000000
Binary files a/img/blankimg.png and /dev/null differ
diff --git a/img/blankvid.png b/img/blankvid.png
deleted file mode 100644
index 8d2eb0c8..00000000
Binary files a/img/blankvid.png and /dev/null differ
diff --git a/img/broken_image.png b/img/broken_image.png
deleted file mode 100644
index 44f8f58c..00000000
Binary files a/img/broken_image.png and /dev/null differ
diff --git a/img/broken_video.png b/img/broken_video.png
deleted file mode 100644
index 9226893c..00000000
Binary files a/img/broken_video.png and /dev/null differ
diff --git a/img/logo-unread.svg b/img/logo-unread.svg
deleted file mode 100644
index 3a01524e..00000000
--- a/img/logo-unread.svg
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
- M
-
-
-
diff --git a/img/logo.svg b/img/logo.svg
deleted file mode 100644
index 7b453084..00000000
--- a/img/logo.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
- M
-
diff --git a/img/logo192.png b/img/logo192.png
deleted file mode 100644
index 137183cd..00000000
Binary files a/img/logo192.png and /dev/null differ
diff --git a/img/logo32x32.png b/img/logo32x32.png
deleted file mode 100644
index a11b1cd1..00000000
Binary files a/img/logo32x32.png and /dev/null differ
diff --git a/img/logo32x32a.png b/img/logo32x32a.png
deleted file mode 100644
index 9f8c7dad..00000000
Binary files a/img/logo32x32a.png and /dev/null differ
diff --git a/img/logo96.png b/img/logo96.png
deleted file mode 100644
index 9d625d61..00000000
Binary files a/img/logo96.png and /dev/null differ
diff --git a/img/og-logo.jpeg b/img/og-logo.jpeg
deleted file mode 100644
index 621cb3dc..00000000
Binary files a/img/og-logo.jpeg and /dev/null differ
diff --git a/img/og-logo.svg b/img/og-logo.svg
deleted file mode 100644
index d57ee764..00000000
--- a/img/og-logo.svg
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
- M
-
- MeetingIM
- 即时通讯
-
diff --git a/img/routine.svg b/img/routine.svg
deleted file mode 100644
index 4b7cd778..00000000
--- a/img/routine.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/index-dev.html b/index-dev.html
deleted file mode 100644
index 060ba8e9..00000000
--- a/index-dev.html
+++ /dev/null
@@ -1,61 +0,0 @@
-
-
-
-
- MeetingIM
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Javascript support is required.
-
It appears JavaScript is turned off or is not supported by your browser.
- MeetingIM does not work without JavaScript. Please enable JavaScript by changing your browser options,
- then
try again .
-
-
-
-
-
-
-
-
diff --git a/index.html b/index.html
deleted file mode 100644
index 7122a70d..00000000
--- a/index.html
+++ /dev/null
@@ -1,48 +0,0 @@
-
-
-
-MeetingIM
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-Javascript support is required.
-
It appears JavaScript is turned off or is not supported by your browser.
-MeetingIM does not work without JavaScript. Please enable JavaScript by changing your browser options,
-then
try again .
-
-
-
-
-
-
diff --git a/manifest.json b/manifest.json
deleted file mode 100644
index 409a038a..00000000
--- a/manifest.json
+++ /dev/null
@@ -1,34 +0,0 @@
-{
- "name": "MeetingIM",
- "short_name": "MeetingIM",
- "description": "MeetingIM instant messenger.",
- "categories": ["chat", "communication", "productivity"],
- "icons": [
- {
- "src":"img/logo96.png",
- "sizes": "96x96",
- "type": "image/png"
- },
- {
- "src":"img/logo192.png",
- "sizes": "192x192",
- "type": "image/png"
- },
- {
- "src": "img/logo.svg",
- "type": "image/svg+xml",
- "sizes": "512x512"
- }
- ],
- "start_url": "/",
- "background_color": "silver",
- "theme_color": "#3949AB",
- "display": "standalone",
- "gcm_sender_id": "103953800507",
- "prefer_related_applications": false,
- "permissions": {
- "audio-capture": {
- "description": "Required for recording voice messages"
- }
- }
-}
diff --git a/package-lock.json b/package-lock.json
deleted file mode 100644
index 41c0a402..00000000
--- a/package-lock.json
+++ /dev/null
@@ -1,10826 +0,0 @@
-{
- "name": "tinode-webapp",
- "version": "0.25.3",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "name": "tinode-webapp",
- "version": "0.25.3",
- "license": "Apache-2.0",
- "dependencies": {
- "firebase": "^12.15.0",
- "jsqr": "^1.4.0",
- "libphonenumber-js": "^1.10.40",
- "pinyin-pro": "^3.29.3",
- "qrcodejs": "^1.0.0",
- "react": "^18.3.0",
- "react-dom": "^18.3.0",
- "react-intl": "^7.0.0",
- "tinode-sdk": "^0.25.3",
- "webm-duration-fix": "^1.0.4"
- },
- "devDependencies": {
- "@babel/core": "^8.0.0",
- "@babel/preset-env": "^8.0.0",
- "@babel/preset-react": "^8.0.0",
- "@formatjs/cli": "^6.1.3",
- "babel-loader": "^10.0.0",
- "babel-plugin-formatjs": "^11.0.0",
- "browserslist": "^4.21.10",
- "copy-webpack-plugin": "^14.0.0",
- "cssnano": "^8.0.0",
- "glob": "^13.0.0",
- "jest": "^30.0.0",
- "postcss": "^8.4.27",
- "postcss-cli": "^11.0.0",
- "webpack": "^5.88.2",
- "webpack-cli": "^7.0.0"
- }
- },
- "node_modules/@babel/code-frame": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-8.0.0.tgz",
- "integrity": "sha512-dYYg153EyN2Ekbqw2zAsbd6/JR+9N2SEoC7YV2GyyqMM7x9bLDTjBD6XBhSMLH0wtIVyJj03jWNriQhaN+eoCw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-validator-identifier": "^8.0.0",
- "js-tokens": "^10.0.0"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- }
- },
- "node_modules/@babel/compat-data": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-8.0.0.tgz",
- "integrity": "sha512-DOjnob/cXOUgDOozCDeq/aK2p5y8dUIVdf6tNhEV1HQRd6I8aQ4f4fbtHRVEvb6lP3BGomrKHiS8ICAASSVQSw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- }
- },
- "node_modules/@babel/core": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-8.0.1.tgz",
- "integrity": "sha512-5FgxM4dLQpMJHSiVATk8foW263dVHQHBVpXYiimNECVWG01f4nFyEbQixeT6Mwvg7TayREJ2gpKl3o2RoMdnqw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^8.0.0",
- "@babel/generator": "^8.0.0",
- "@babel/helper-compilation-targets": "^8.0.0",
- "@babel/helpers": "^8.0.0",
- "@babel/parser": "^8.0.0",
- "@babel/template": "^8.0.0",
- "@babel/traverse": "^8.0.0",
- "@babel/types": "^8.0.0",
- "@types/gensync": "^1.0.5",
- "convert-source-map": "^2.0.0",
- "empathic": "^2.0.1",
- "gensync": "^1.0.0-beta.2",
- "import-meta-resolve": "^4.2.0",
- "json5": "^2.2.3",
- "obug": "^2.1.1",
- "semver": "^7.7.3"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/babel"
- }
- },
- "node_modules/@babel/generator": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-8.0.0.tgz",
- "integrity": "sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/parser": "^8.0.0",
- "@babel/types": "^8.0.0",
- "@jridgewell/gen-mapping": "^0.3.12",
- "@jridgewell/trace-mapping": "^0.3.28",
- "@types/jsesc": "^2.5.0",
- "jsesc": "^3.0.2"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- }
- },
- "node_modules/@babel/helper-annotate-as-pure": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-8.0.0.tgz",
- "integrity": "sha512-NSpMkMsvvZqzThJ0p1B02cbtA2ObEyfBvq950bmNkyxsxvcxwhvvCB036rKhlEnuBBo30bOrk13u3FzlKSoRrw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^8.0.0"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- }
- },
- "node_modules/@babel/helper-compilation-targets": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-8.0.0.tgz",
- "integrity": "sha512-JwculLABZvyPvyLBpwU/E/IbH2uM3mnxNtIJpxnIfb24y1PrdVxK5Dqjle4DpgqpGRnwgC7G8IkzPdSXZrO1Ew==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/compat-data": "^8.0.0",
- "@babel/helper-validator-option": "^8.0.0",
- "browserslist": "^4.24.0",
- "lru-cache": "^11.0.0",
- "semver": "^7.7.3"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- }
- },
- "node_modules/@babel/helper-create-class-features-plugin": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-8.0.1.tgz",
- "integrity": "sha512-++t3ZktzlLmASAxIlxeXQK9Z2YwUafYGYcvGBFevqOqt16HozVHStUoQvWD09fzAZOb/uJGpUTBuGK41AJAuOA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-annotate-as-pure": "^8.0.0",
- "@babel/helper-member-expression-to-functions": "^8.0.0",
- "@babel/helper-optimise-call-expression": "^8.0.0",
- "@babel/helper-replace-supers": "^8.0.1",
- "@babel/helper-skip-transparent-expression-wrappers": "^8.0.0",
- "@babel/traverse": "^8.0.0",
- "semver": "^7.7.3"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/helper-create-regexp-features-plugin": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-8.0.1.tgz",
- "integrity": "sha512-PydTbcVTiIfVweHMeY1u3MslaD/ZzvnaTNhJp+7ghofelLWshF66Ckc/ZsjStfvRQIKQ4uVG0yEJucyDtyrWgw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-annotate-as-pure": "^8.0.0",
- "regexpu-core": "^6.3.1",
- "semver": "^7.7.3"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/helper-define-polyfill-provider": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-1.0.0.tgz",
- "integrity": "sha512-9jzVaTeZyXRDKTgUnNzcPQMO8y0ga3o+Z4fKjNet9Fcx7slgKa83qRbz0EwROSd6qO6CoEe/HQszqSPKb5lhkw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-compilation-targets": "^8.0.0",
- "@babel/helper-plugin-utils": "^8.0.0",
- "lodash.debounce": "^4.0.8"
- },
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.4.0 || ^8.0.0"
- }
- },
- "node_modules/@babel/helper-globals": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-8.0.0.tgz",
- "integrity": "sha512-lLozHOM6sWWlxNo8CYqHy4MBZeTvHXNgVPBfPOGsjPKUzHC2Az9QwB6gxdQmpwHl6GlQtbGgS+lj5887guDiLw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- }
- },
- "node_modules/@babel/helper-member-expression-to-functions": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-8.0.0.tgz",
- "integrity": "sha512-xkXrMbtk87Gk7+oKBVmBc6EORg/Qwx++AHESldmHkpvG8wgccdhJJFwrzqlF382Fk8wfXhJHWE/g/43QvEGNPQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/traverse": "^8.0.0",
- "@babel/types": "^8.0.0"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- }
- },
- "node_modules/@babel/helper-module-imports": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-8.0.0.tgz",
- "integrity": "sha512-NZ7mSS93o4ndX4KrbD7W8Sf3QT8Qe24PrnFyUcuOPDzK6faqDFKjY9RG7he7+I7FdiQ4llpnosFqzrXa+Vy3Ew==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/traverse": "^8.0.0",
- "@babel/types": "^8.0.0"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- }
- },
- "node_modules/@babel/helper-module-transforms": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-8.0.1.tgz",
- "integrity": "sha512-UgAhl1kqiW5ciE0yCXqqvnb4H2n3IELJ7lIIQRezwDPilPEZX5i+Rvbja9MFTkwUn2biEiSMeV31aUzR4Lwakw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-module-imports": "^8.0.0",
- "@babel/helper-validator-identifier": "^8.0.0",
- "@babel/traverse": "^8.0.0"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/helper-optimise-call-expression": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-8.0.0.tgz",
- "integrity": "sha512-3W6satvtPuCUkUx63S2jMoW9EQNYkADgs1HTfufmL7gCmAulHMKupA/12WNz4A0GMMFn/YnWWwqOT9IZrJHQjg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^8.0.0"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- }
- },
- "node_modules/@babel/helper-plugin-utils": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-8.0.1.tgz",
- "integrity": "sha512-3PKFgjTyPlhFhorfP+SjKQxLViIL++zWjFOO4hGriYU+Bsm983DxEM1JmDRJVWXV0O9npu+xXRqz7Pbd3mh70g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/helper-remap-async-to-generator": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-8.0.1.tgz",
- "integrity": "sha512-baAKuLEMmu6BCSY3tuiU7qglM1qOZt6F1SrFScA241oNqksxkxfEZEKztlGRmoVns9AQ5UgArH7RsUEjxWnzgQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-annotate-as-pure": "^8.0.0",
- "@babel/helper-wrap-function": "^8.0.0",
- "@babel/traverse": "^8.0.0"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/helper-replace-supers": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-8.0.1.tgz",
- "integrity": "sha512-B1SZADIcy3tmH8CmWvj4SHi/oAPom4UL3uknTc2QRNsPVLFk/sPnZvQL/8kj7Y5omvjMqie0vklvs6XM4OLW5Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-member-expression-to-functions": "^8.0.0",
- "@babel/helper-optimise-call-expression": "^8.0.0",
- "@babel/traverse": "^8.0.0"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/helper-skip-transparent-expression-wrappers": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-8.0.0.tgz",
- "integrity": "sha512-xmCA9kP3IhySsqhzwIdWGlDN/1A4cCKNBO/uwZx/3YzmDoMePwno2Q5/Bq0q+tYaKbeF940YiKV/kaW8Mzvpjw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/traverse": "^8.0.0",
- "@babel/types": "^8.0.0"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- }
- },
- "node_modules/@babel/helper-string-parser": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz",
- "integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- }
- },
- "node_modules/@babel/helper-validator-identifier": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.2.tgz",
- "integrity": "sha512-9Fr9QeyCAyi1BR1jKZ6uYQ24EIhQUx5ReHfQU7drOE+TPOb+w11/dsqLkMOT2U29OdCT71XajrOT8xDc1C7orA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- }
- },
- "node_modules/@babel/helper-validator-option": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-8.0.0.tgz",
- "integrity": "sha512-U4Dybxh4WESWHt5XhBeExi4DrY0/DNK1aHpQbsrQXCUbFHuMweT0TpLEWKvaraV2Y6fS+ZXunsZ8zIuZIgvF2Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- }
- },
- "node_modules/@babel/helper-wrap-function": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-8.0.0.tgz",
- "integrity": "sha512-Qpm8+wi5xfDkBfollanwriCcKniFfBmMmaKB01GVM6VGzKXo1fdxosZp04qEr5HM+LKhwr3hG1yRy8+ORsficA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/template": "^8.0.0",
- "@babel/traverse": "^8.0.0",
- "@babel/types": "^8.0.0"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- }
- },
- "node_modules/@babel/helpers": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-8.0.0.tgz",
- "integrity": "sha512-wfbi91pM3py96oIiJEz7qIpyXDytgr9zQC1HEWwlGNVRAEmItuU/0a41ZUKu1sJGyhhOIpc4t5vk4PYzt8wpsg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/template": "^8.0.0",
- "@babel/types": "^8.0.0"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- }
- },
- "node_modules/@babel/parser": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.0.tgz",
- "integrity": "sha512-aLxAE+imI9bCcyaPrUDjBv3uSkWieifjLe0kuFOZF0zli0L6GCsTmsePnTr55adbIAgYz2zhN1vnFimCBUYcRQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^8.0.0"
- },
- "bin": {
- "parser": "bin/babel-parser.js"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- }
- },
- "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-8.0.1.tgz",
- "integrity": "sha512-Ytgjjne4RnG3Oig7ik+NfY4ebRY30BPptVkkyu1f72eINJXRM3/bkU++tIc5aPvyLmo4KH20avq0xJ2o+9aEnw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-8.0.1.tgz",
- "integrity": "sha512-X7pAMBhuKluA7UfwZNvKN0XVVu/AGeo84Z75eJl85rcb8J2aBzLK92btahM1X5h0oi0QIrbe0qIMA/0+4Buk7w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-8.0.1.tgz",
- "integrity": "sha512-DJviKTxYfH0hFwnMiW4dnPyMGzS3Hrr4zUfXl1zwQ0QiGlGlNYklLoPSYEQr8S7nau0/K7NdQjTh0qbYuyFjCA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array/-/plugin-bugfix-safari-rest-destructuring-rhs-array-8.0.1.tgz",
- "integrity": "sha512-DmR/N+B9+4PbURFj4+zdnWj49/PFAnK2bn8+E4ZAmwn3J5QCxnbG7Ep6aRfz9M8Aw+rBro0kIJQycvzFpl4buQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1",
- "@babel/helper-skip-transparent-expression-wrappers": "^8.0.0"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-8.0.1.tgz",
- "integrity": "sha512-x8bi0LFVD2xkULjfNn+hCMg16yAFHAM9fS/ThSFeYBi+0MP9K6qcY2BZb4urUwC7PYtEy5wPe6TKjOEjXrCGFA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1",
- "@babel/helper-skip-transparent-expression-wrappers": "^8.0.0",
- "@babel/plugin-transform-optional-chaining": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-8.0.1.tgz",
- "integrity": "sha512-P8+RN2n7ts2s1vnE+lXdHYf+dhnmcGSen/kWzBsVluT9Sey5AqmcRXYWlHqgQxaNlKTD5YMa1tf5z4d1v8W88w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-syntax-async-generators": {
- "version": "7.8.4",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz",
- "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.8.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-syntax-async-generators/node_modules/@babel/helper-plugin-utils": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
- "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/plugin-syntax-bigint": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz",
- "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.8.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-syntax-bigint/node_modules/@babel/helper-plugin-utils": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
- "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/plugin-syntax-class-properties": {
- "version": "7.12.13",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz",
- "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.12.13"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-syntax-class-properties/node_modules/@babel/helper-plugin-utils": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
- "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/plugin-syntax-class-static-block": {
- "version": "7.14.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz",
- "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.14.5"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-syntax-class-static-block/node_modules/@babel/helper-plugin-utils": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
- "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/plugin-syntax-import-attributes": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.29.7.tgz",
- "integrity": "sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-syntax-import-attributes/node_modules/@babel/helper-plugin-utils": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
- "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/plugin-syntax-import-meta": {
- "version": "7.10.4",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz",
- "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.10.4"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-syntax-import-meta/node_modules/@babel/helper-plugin-utils": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
- "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/plugin-syntax-json-strings": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz",
- "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.8.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-syntax-json-strings/node_modules/@babel/helper-plugin-utils": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
- "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/plugin-syntax-jsx": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-8.0.1.tgz",
- "integrity": "sha512-n0jtCOxEovhU7METqSQjcZO9pX53nu9uNIjMS+hEt+Nt9jA7oOZoBIgbCxhhASmF6T6rPDGge5UAvh6Z4eFz/g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-syntax-logical-assignment-operators": {
- "version": "7.10.4",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz",
- "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.10.4"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-syntax-logical-assignment-operators/node_modules/@babel/helper-plugin-utils": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
- "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz",
- "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.8.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-syntax-nullish-coalescing-operator/node_modules/@babel/helper-plugin-utils": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
- "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/plugin-syntax-numeric-separator": {
- "version": "7.10.4",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz",
- "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.10.4"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-syntax-numeric-separator/node_modules/@babel/helper-plugin-utils": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
- "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/plugin-syntax-object-rest-spread": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz",
- "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.8.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-syntax-object-rest-spread/node_modules/@babel/helper-plugin-utils": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
- "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/plugin-syntax-optional-catch-binding": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz",
- "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.8.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-syntax-optional-catch-binding/node_modules/@babel/helper-plugin-utils": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
- "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/plugin-syntax-optional-chaining": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz",
- "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.8.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-syntax-optional-chaining/node_modules/@babel/helper-plugin-utils": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
- "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/plugin-syntax-private-property-in-object": {
- "version": "7.14.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz",
- "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.14.5"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-syntax-private-property-in-object/node_modules/@babel/helper-plugin-utils": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
- "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/plugin-syntax-top-level-await": {
- "version": "7.14.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz",
- "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.14.5"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-syntax-top-level-await/node_modules/@babel/helper-plugin-utils": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
- "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/plugin-syntax-typescript": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz",
- "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-syntax-typescript/node_modules/@babel/helper-plugin-utils": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
- "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/plugin-transform-arrow-functions": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-8.0.1.tgz",
- "integrity": "sha512-o/gr7kRlq3PKLLuYth4udOsrC7geBerti+QtwPeyxMOsEQO1d8kDHqk9r2PtMx2y9i8FG7tzyTerfv1yMLSMsQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-async-generator-functions": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-8.0.1.tgz",
- "integrity": "sha512-kqnSMF1YHBzuiQrl68675i5Ma1oljvo+SJsNEZFZVBu5BUrVIZm9KId3ui2PdtLK2sv2zM8sJnjPDfgLxQlEqQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1",
- "@babel/helper-remap-async-to-generator": "^8.0.1",
- "@babel/traverse": "^8.0.0"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-async-to-generator": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-8.0.1.tgz",
- "integrity": "sha512-e1jmmEU4p2Lx64sA1+EF8e8/RxPuegzbXcEbmFp5alDyLE+f2ViUpZ77bRWMXzihTwgVVmn/TOpqDbAuS5g1Ew==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-module-imports": "^8.0.0",
- "@babel/helper-plugin-utils": "^8.0.1",
- "@babel/helper-remap-async-to-generator": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-block-scoped-functions": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-8.0.1.tgz",
- "integrity": "sha512-0V97/gcf7LIgPieEiK1YT0eXa18XJFSLOTZjzEZhA9SJIqZhD/IwGUrCitBzXSmnGCP7hchwC6svHtJ/Eidcpg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-block-scoping": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-8.0.1.tgz",
- "integrity": "sha512-HxiQvKsSCs2jOmMhjDrooHaZYOy6W8bqwXp/zjdgPjsNrda6tK9/CH3a/cVIeg6ge3hSS02ALqvqgIo4rTsuSg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-class-properties": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-8.0.1.tgz",
- "integrity": "sha512-tORnYiVhIHnKj90TgbSZXrO24f9oEpA6MgFxpIDSKKlHv7AzBIRhkMlYevanueLNYaQXqZWarfCgXM4bWTfNiw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-create-class-features-plugin": "^8.0.1",
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-class-static-block": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-8.0.1.tgz",
- "integrity": "sha512-NEVK+L0Le8h8tJ+IK0CGS5y9Yi1ZHxLj6M5PeanhMFuq9aSo0XI+Wtmbuyop6fTNukOm7ORNntf/kwid891vqQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-create-class-features-plugin": "^8.0.1",
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-classes": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-8.0.1.tgz",
- "integrity": "sha512-phwyCES8kIMAdVOFw25ztmgAvkl2G+TvUv7azUYyrlR1Qoo3eLJC/MU3MGUKFZ4BWtsJ1NTJM1lKRLzKbswg7w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-annotate-as-pure": "^8.0.0",
- "@babel/helper-compilation-targets": "^8.0.0",
- "@babel/helper-globals": "^8.0.0",
- "@babel/helper-plugin-utils": "^8.0.1",
- "@babel/helper-replace-supers": "^8.0.1",
- "@babel/traverse": "^8.0.0"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-computed-properties": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-8.0.1.tgz",
- "integrity": "sha512-i4l3OGLO8DUDcwdnyraOvILbhqdUf4QgfzhVxSOSzRy49XKXrY7pwaSg9gDSKmhZfNPrEMciBSJSciQh/CjB1A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-destructuring": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-8.0.1.tgz",
- "integrity": "sha512-RtR8uLDl0QcCmqMNIkM8gmDeYZ3rS0ZH+sa+I6sfc09yFoqfp9AEPgBstq9KyfVb0lFCVSRFfJXCI70FIl5ccw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-dotall-regex": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-8.0.1.tgz",
- "integrity": "sha512-czOUoSaZljJ92yu+bYlXqb/UBN8K9daNCob/B6/7nthSvfGP6YhCnfqD64XWfyb2dN4ypxALNplApoJrsMd4fw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-create-regexp-features-plugin": "^8.0.1",
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-duplicate-keys": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-8.0.1.tgz",
- "integrity": "sha512-kNnVLkxFUEcTtCyB5PFVQ5Xoy88Bk1lU/ZgDu97CW8eNhRH2Wsiy8Sq5l5dFnwtIUYjzsXHU77jUy1W5AtGSIw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-8.0.1.tgz",
- "integrity": "sha512-Tv43P47o6fuHgBL7HLHQg3WKXohW9CEUGjLtnCDW27yJLK0zKUdTTqREbZbycNHA83hewMjde5tF6ekrHu9bAA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-create-regexp-features-plugin": "^8.0.1",
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-dynamic-import": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-8.0.1.tgz",
- "integrity": "sha512-AS9GlgKc43tJNRu7yOvLaTko4qmdOb+8M69uNS8i421WLO20eVez7LdG5khKdi8E0LIQpYzzzdGIrdXWnO753g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-explicit-resource-management": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-8.0.1.tgz",
- "integrity": "sha512-VzDIYwBlLCpV6mJfloRdJm8HmYnMqs7O+bGha8yfg2kP7jAdxeCw6yZBVBeaKKQUThtSU52iy+3lB7DhYsbOBA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1",
- "@babel/plugin-transform-destructuring": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-exponentiation-operator": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-8.0.1.tgz",
- "integrity": "sha512-DsZvUUklUmDQ7d2vp+VjqgUWD51mGxhZZ1FPdPP9Hcj0vsgGUKX+zEBGp/vzB1O5PZUxWT/Euq5fu39M9dm9wg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-export-namespace-from": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-8.0.1.tgz",
- "integrity": "sha512-bFzznm46bvWGaTYKle3iolbBJ+oPBfUjwCPesxlFE3SQ7DaY9EHf/8Y5ZzrodKJi8JDdcAyaVWaDUSVyhULh0g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-for-of": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-8.0.1.tgz",
- "integrity": "sha512-rpeXtgELjpIBQH/+YmyFlD9timPEVCyqY+TNednzoeoTYvXSBEeUvYnYE+BK8rB8m6hHiNK7aL9QWKhGifEJCw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1",
- "@babel/helper-skip-transparent-expression-wrappers": "^8.0.0"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-function-name": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-8.0.1.tgz",
- "integrity": "sha512-H1L/JfPf3CqmubuaiZaquXKQ8MRs4YWSsgRllkTviM8TafcCNnlvc4/fJZ3rXP8HmFM+/Bg+TlsPehUI9BtDFA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-compilation-targets": "^8.0.0",
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-json-strings": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-8.0.1.tgz",
- "integrity": "sha512-Mowp8X0J6p7ZehLU82B5e65te2uuSeDHyxrEROwEAS2VKXNXssfw5ZMqhY7k9iXTsOv1Xs/49G3lDCj9Vvw8qQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-literals": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-8.0.1.tgz",
- "integrity": "sha512-ai7kfPRcfyUV1EszXoF1PvL3IuJoCuH08WSEPoRcJTWfZZ55VL/rcfvbVY16QLA3jjbzzSneQSoCtD3L6OyUjw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-logical-assignment-operators": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-8.0.1.tgz",
- "integrity": "sha512-Emvtr5zkEGyCNAmt+qKD5EUh8G0RbxV9EZWrDdX0LuVy5tBq1B3fOIslvVF9aCJmpnwS/AvAT53b9LxAZyXlng==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-member-expression-literals": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-8.0.1.tgz",
- "integrity": "sha512-3Axi9abnyGsm/hh6DsKPZ1Cr9fTtKqS7w0Ig5g12mU269YclpH8pV3xMln2vPLexXgUp6S6L+I06d9/YOLfRKA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-modules-amd": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-8.0.1.tgz",
- "integrity": "sha512-FDdhET8y1YFDNRuoynqSf23WTzbBBpbIB2oRrlFX7YYm9uWtFvJDSD1r/epBSjfPkOjeaaLgRW9xNnt3JGx46A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-module-transforms": "^8.0.1",
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-modules-commonjs": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-8.0.1.tgz",
- "integrity": "sha512-PMuzulWrrzFNmY3lXSk/tV9NRb7y0eZZLJY4UEo2TKszroxvUZHAPPi+T9FDyrQhod+TQA+t+8/QYaaMpiEuhA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-module-transforms": "^8.0.1",
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-modules-systemjs": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-8.0.1.tgz",
- "integrity": "sha512-0NEHanXmnFEnfT2dLKTXnu7m8GXFsnxRgteBC2aH21hYMBwAgxu5dcTdi/Eg+ToI1HbZe0CHwz4XRLgRNQhYoQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-module-transforms": "^8.0.1",
- "@babel/helper-plugin-utils": "^8.0.1",
- "@babel/helper-validator-identifier": "^8.0.0"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-modules-umd": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-8.0.1.tgz",
- "integrity": "sha512-XKTa2J2MdkmbVEeChq9f7Or0VYcsF0NyVBgytRyeN9F+J+ETAB2SHhfkG4toz/ssuU0i+h/QgJ6ddo5YakSQcA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-module-transforms": "^8.0.1",
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-named-capturing-groups-regex": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-8.0.1.tgz",
- "integrity": "sha512-zCHu+Jr2gTdJE48lN9SV/kXueCW2M79mKtKJc/ttfzzr/jvgdQdCd17RADMqFRQc/25MLxdtjTmlD0HSAMOlIQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-create-regexp-features-plugin": "^8.0.1",
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-new-target": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-8.0.1.tgz",
- "integrity": "sha512-QSQxVg1x4PuOuhWUs4Y9u+x9Y+ER8z6G3tC+bDLBzvoOrNLJrEBQLRnwrTP8e5klihAw6Z+e9X5RjdAKcAGapA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-nullish-coalescing-operator": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-8.0.1.tgz",
- "integrity": "sha512-AgCJAmQLF7+PtsK79wJqr4xJ2StHCXlz7JL5CVFP4HejJx25Tk6yl1ZrXvi0cKh3VGDVnfVxefxnrpsBirgpyQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-numeric-separator": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-8.0.1.tgz",
- "integrity": "sha512-it2DmUyLIA1GQUXlFDEnI+/G89mTgxndnAiZYpW8xYR6LboblfirMqiWJeTna5uypQJg7viTT4D1iEURRtFcfw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-object-rest-spread": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-8.0.1.tgz",
- "integrity": "sha512-VmxkDu6bBdbxRzqn6E93hYucug4OVa6svSO19W//vVzNUGAmQzk3QRyHyyEtfcjSLR3NWfRsWwVM9zExLmd+2w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-compilation-targets": "^8.0.0",
- "@babel/helper-plugin-utils": "^8.0.1",
- "@babel/plugin-transform-destructuring": "^8.0.1",
- "@babel/plugin-transform-parameters": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-object-super": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-8.0.1.tgz",
- "integrity": "sha512-fDkPXRTRKGm25bAq01q82UM4ypPqdVXCwphUUm4t1dL01fGIG0v8KRvT+4BjhMAtRxtPuI34t5Vs7yjRgs3ZgQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1",
- "@babel/helper-replace-supers": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-optional-catch-binding": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-8.0.1.tgz",
- "integrity": "sha512-b2OQ74uGliyATcasTjxGy2O/86UI/n+EN4juB4EMfEwTi9j9uq70PuP0L8fW77vfRY66gO/YoTo/WbIdQ/Si1g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-optional-chaining": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-8.0.1.tgz",
- "integrity": "sha512-WtRS1c94lZGpGHxYLXMEWeoMVcuv8nkiyr8BTs6OYZv7N3Y9xVE8nbdFIl4lDJH6aH8/pLhqAQOL69d/WI9WdA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1",
- "@babel/helper-skip-transparent-expression-wrappers": "^8.0.0"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-parameters": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-8.0.1.tgz",
- "integrity": "sha512-IIwRqroW0CYQwR6+3pnmu27z+H98poScWdnov8z6osumMeEsFxAFBBsDS2CFk2jFpPlGqVr89jK/HXO6i5DzxQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-private-methods": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-8.0.1.tgz",
- "integrity": "sha512-TrFCGcXaVDh6S5IRhmLSRTY9H80VTCMQWnZtzBRg4RWg3KCLmdmsmj4M15kZAPZfoPkWL/SJb4em3Py/vOiX8g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-create-class-features-plugin": "^8.0.1",
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-private-property-in-object": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-8.0.1.tgz",
- "integrity": "sha512-e+yfOqSYBZaf3PARpiQkjZrpWYgmcFLhK+1tevh2CpHR1O9/36IdyPnAZusESX5nzVV/XZTDAtQBRLa8HPT5Dw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-annotate-as-pure": "^8.0.0",
- "@babel/helper-create-class-features-plugin": "^8.0.1",
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-property-literals": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-8.0.1.tgz",
- "integrity": "sha512-Z/qx4cxUtYR1nt7XWRutObPxDks98fEYsjWbVeKEqZH6y3AGknmgzCqmHf2FHWZCl1DfoPeuJY+3hZ+35D+2tg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-react-display-name": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-8.0.1.tgz",
- "integrity": "sha512-soLishXlkyu6jcICPyO3HEP7A3GCzKEnn7XfvYrImuWEOwFAz93qShmWSYPf5ww0ZkO4By0zsN2bVIDF54fSdA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-react-jsx": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-8.0.1.tgz",
- "integrity": "sha512-NgkoF7Uq+30TmOPDdNUimT0Nta02uVjqJRFNlVWKrbOCu/CkzfHa4aMnIs0lMpkMmZmWA1e42Va+F04i/pY1zw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-annotate-as-pure": "^8.0.0",
- "@babel/helper-module-imports": "^8.0.0",
- "@babel/helper-plugin-utils": "^8.0.1",
- "@babel/plugin-syntax-jsx": "^8.0.1",
- "@babel/types": "^8.0.0"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-react-jsx-development": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-8.0.1.tgz",
- "integrity": "sha512-Hb+HUZpV9KFHjm+F+P3aLDMi8QXU9l3ROCQv20z18Me2sGyW5nNNR5YTevNlgHvCpFek3BnAwhDGq/BRndXViw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/plugin-transform-react-jsx": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-react-pure-annotations": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-8.0.1.tgz",
- "integrity": "sha512-7/8UwU8hoPBurXa9tUiTTC8aACTRy5tCqLUtqikHp2eGiWoEB57AduOdbQ71OOMTEvawKrGhv3WfzkDpI+/oSg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-annotate-as-pure": "^8.0.0",
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-regenerator": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-8.0.2.tgz",
- "integrity": "sha512-aFfsjCRYducRV4dPnpsBbdRkLjboca9FVDg6HZCgy0Ahvk2ZQ/2exmCRC5qS9P6rsWwrmIheNaIM6A1j2F8KMA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-regexp-modifiers": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-8.0.1.tgz",
- "integrity": "sha512-02ITRDBesPdTYU0oShAzERwEPzozOUQSXlz3qrt8JGuhalBJQv9z5NjgHJPC9sS3Fsam8gDtfAEpBnqZwUIdjQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-create-regexp-features-plugin": "^8.0.1",
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-reserved-words": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-8.0.1.tgz",
- "integrity": "sha512-+aykZi7ZP3U84veqfJXm3HhPZGddWFi64g7jr0ni6tb1zel+1ey+SL+IRKPoZXFyFqvYEsoqrmx4PyEJRlHl/Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-shorthand-properties": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-8.0.1.tgz",
- "integrity": "sha512-JddANd9yPVH8dYgVoNkqAH5BftnsDxFpG51Zas7sc6F3poz5QWcejHNGO8a/57IX5ByjGSzEmYk9Z7ZMa5MWaw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-spread": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-8.0.1.tgz",
- "integrity": "sha512-O9Bw9FyxlSw1SlMg3S82/GKNZ0x77RPbHezotEy1JTlIM/vk6WO8jW1iF+iTiKLOXNvi+b+LZ9t77Gi+Q0FhGg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1",
- "@babel/helper-skip-transparent-expression-wrappers": "^8.0.0"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-sticky-regex": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-8.0.1.tgz",
- "integrity": "sha512-IsVP6WrZZQdaG2zLmeKwWiI+ua2NB5L1+f77C2/8z2NCDz7uxlIA/lnwocYOJk9PXcOC2sZgRls3LN4XpNduzQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-template-literals": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-8.0.1.tgz",
- "integrity": "sha512-JXvtj5+BJA9Qv3prDzW2z2DkGTJNmG0BObTdUD03STiu1Jr4fNQkQy3hYZgPL46a2RjcuhwBMYf49BOuJ98gnA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-typeof-symbol": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-8.0.1.tgz",
- "integrity": "sha512-+wJoxgxP2gtey0UMUOMhzMMji2XHO/Uu6MXUh/r5Yhc2jngKzK/wFxY2WNe4UCaRcMvCb4gcnB8wIgFXJsocXg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-unicode-escapes": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-8.0.1.tgz",
- "integrity": "sha512-TAXJepIJ6vZphytTwcf+LuXi2M2ZWI43VCqNw+1ZZLPP/38Z1A8j4Mahvg8kqDgMOSM/cakk+hedTJCiw3jQuQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-unicode-property-regex": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-8.0.1.tgz",
- "integrity": "sha512-zjBN9tSMSuomNDfurL69Gf7+v4D2t5uI1mSZaYJDo88SKpbduhCXqtxH7Tx66iCF6caWYwnBzSM0tnCozmQq5Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-create-regexp-features-plugin": "^8.0.1",
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-unicode-regex": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-8.0.1.tgz",
- "integrity": "sha512-v0oO83cvT5lwbcIVRShpx4vaHD8AvM9IBowsQuTeP+kGmhh3recJQs33Bl6dlo3/2g9amlznLbFGn4VJbPCJqA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-create-regexp-features-plugin": "^8.0.1",
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-unicode-sets-regex": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-8.0.1.tgz",
- "integrity": "sha512-MlQeyS0K7gh0XNeLBMS/3Z07HjDOKhA7xm2L18GyxOXyiFHI9E+ZuQ4mFYmcLjluXsE/Wf6dABIqZvKpKw0Z3w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-create-regexp-features-plugin": "^8.0.1",
- "@babel/helper-plugin-utils": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/preset-env": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-8.0.2.tgz",
- "integrity": "sha512-CUGLn9hNBCF/eXnwdFAWERbniCcXCRvnKwLV9fegeUEIqv7YlU2MepsWMMM54GcILx5XYMnRh+JAL+K5G+mK6g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/compat-data": "^8.0.0",
- "@babel/helper-compilation-targets": "^8.0.0",
- "@babel/helper-plugin-utils": "^8.0.1",
- "@babel/helper-validator-option": "^8.0.0",
- "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^8.0.1",
- "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^8.0.1",
- "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^8.0.1",
- "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": "^8.0.1",
- "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^8.0.1",
- "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^8.0.1",
- "@babel/plugin-transform-arrow-functions": "^8.0.1",
- "@babel/plugin-transform-async-generator-functions": "^8.0.1",
- "@babel/plugin-transform-async-to-generator": "^8.0.1",
- "@babel/plugin-transform-block-scoped-functions": "^8.0.1",
- "@babel/plugin-transform-block-scoping": "^8.0.1",
- "@babel/plugin-transform-class-properties": "^8.0.1",
- "@babel/plugin-transform-class-static-block": "^8.0.1",
- "@babel/plugin-transform-classes": "^8.0.1",
- "@babel/plugin-transform-computed-properties": "^8.0.1",
- "@babel/plugin-transform-destructuring": "^8.0.1",
- "@babel/plugin-transform-dotall-regex": "^8.0.1",
- "@babel/plugin-transform-duplicate-keys": "^8.0.1",
- "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^8.0.1",
- "@babel/plugin-transform-dynamic-import": "^8.0.1",
- "@babel/plugin-transform-explicit-resource-management": "^8.0.1",
- "@babel/plugin-transform-exponentiation-operator": "^8.0.1",
- "@babel/plugin-transform-export-namespace-from": "^8.0.1",
- "@babel/plugin-transform-for-of": "^8.0.1",
- "@babel/plugin-transform-function-name": "^8.0.1",
- "@babel/plugin-transform-json-strings": "^8.0.1",
- "@babel/plugin-transform-literals": "^8.0.1",
- "@babel/plugin-transform-logical-assignment-operators": "^8.0.1",
- "@babel/plugin-transform-member-expression-literals": "^8.0.1",
- "@babel/plugin-transform-modules-amd": "^8.0.1",
- "@babel/plugin-transform-modules-commonjs": "^8.0.1",
- "@babel/plugin-transform-modules-systemjs": "^8.0.1",
- "@babel/plugin-transform-modules-umd": "^8.0.1",
- "@babel/plugin-transform-named-capturing-groups-regex": "^8.0.1",
- "@babel/plugin-transform-new-target": "^8.0.1",
- "@babel/plugin-transform-nullish-coalescing-operator": "^8.0.1",
- "@babel/plugin-transform-numeric-separator": "^8.0.1",
- "@babel/plugin-transform-object-rest-spread": "^8.0.1",
- "@babel/plugin-transform-object-super": "^8.0.1",
- "@babel/plugin-transform-optional-catch-binding": "^8.0.1",
- "@babel/plugin-transform-optional-chaining": "^8.0.1",
- "@babel/plugin-transform-parameters": "^8.0.1",
- "@babel/plugin-transform-private-methods": "^8.0.1",
- "@babel/plugin-transform-private-property-in-object": "^8.0.1",
- "@babel/plugin-transform-property-literals": "^8.0.1",
- "@babel/plugin-transform-regenerator": "^8.0.2",
- "@babel/plugin-transform-regexp-modifiers": "^8.0.1",
- "@babel/plugin-transform-reserved-words": "^8.0.1",
- "@babel/plugin-transform-shorthand-properties": "^8.0.1",
- "@babel/plugin-transform-spread": "^8.0.1",
- "@babel/plugin-transform-sticky-regex": "^8.0.1",
- "@babel/plugin-transform-template-literals": "^8.0.1",
- "@babel/plugin-transform-typeof-symbol": "^8.0.1",
- "@babel/plugin-transform-unicode-escapes": "^8.0.1",
- "@babel/plugin-transform-unicode-property-regex": "^8.0.1",
- "@babel/plugin-transform-unicode-regex": "^8.0.1",
- "@babel/plugin-transform-unicode-sets-regex": "^8.0.1",
- "@babel/preset-modules": "^0.2.0",
- "babel-plugin-polyfill-corejs3": "^1.0.0",
- "core-js-compat": "^3.48.0",
- "semver": "^7.7.3"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/preset-modules": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.2.0.tgz",
- "integrity": "sha512-yz0RBN2fx4fjCeFcTWsWgL7PxSRltvTa0Qg14HkWCU3qS8MO7ZSJlBVbGceynd5C9NsJwwUHNQD3dc6tYO+jqQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1",
- "@babel/plugin-transform-dotall-regex": "^8.0.1",
- "@babel/plugin-transform-unicode-property-regex": "^8.0.1",
- "@babel/types": "^8.0.0",
- "esutils": "^2.0.2"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/preset-react": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-8.0.1.tgz",
- "integrity": "sha512-jrFuPp/pTddFZbtmWhdLNAYc6UMcpboeUPnw0BBrm4nOmcAko/1TRcFi1PzWCeOFRU+VaSiKmat87W1HvR7mIg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^8.0.1",
- "@babel/helper-validator-option": "^8.0.0",
- "@babel/plugin-transform-react-display-name": "^8.0.1",
- "@babel/plugin-transform-react-jsx": "^8.0.1",
- "@babel/plugin-transform-react-jsx-development": "^8.0.1",
- "@babel/plugin-transform-react-pure-annotations": "^8.0.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- },
- "peerDependencies": {
- "@babel/core": "^8.0.0"
- }
- },
- "node_modules/@babel/template": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-8.0.0.tgz",
- "integrity": "sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^8.0.0",
- "@babel/parser": "^8.0.0",
- "@babel/types": "^8.0.0"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- }
- },
- "node_modules/@babel/traverse": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-8.0.0.tgz",
- "integrity": "sha512-bxTj/W2VclGE6CctlfQOpxg8MPDzXArRqkOBePw8EHfebcjF7fETWSS3BriEECo+UiU/Yblq+xUtSImFu7cTbw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^8.0.0",
- "@babel/generator": "^8.0.0",
- "@babel/helper-globals": "^8.0.0",
- "@babel/parser": "^8.0.0",
- "@babel/template": "^8.0.0",
- "@babel/types": "^8.0.0",
- "obug": "^2.1.1"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- }
- },
- "node_modules/@babel/types": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.0.tgz",
- "integrity": "sha512-K8ponJDxBwDHigkeFqaqT5wLGl4bTlwMafR8k7b5CPxr6Ww+UG9ls8Yx6Tcpboxu97eeGVEEyKcHmEyOwN1vSw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-string-parser": "^8.0.0",
- "@babel/helper-validator-identifier": "^8.0.0"
- },
- "engines": {
- "node": "^22.18.0 || >=24.11.0"
- }
- },
- "node_modules/@bcoe/v8-coverage": {
- "version": "0.2.3",
- "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
- "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@colordx/core": {
- "version": "5.5.0",
- "resolved": "https://registry.npmjs.org/@colordx/core/-/core-5.5.0.tgz",
- "integrity": "sha512-3PxTH8itZzltK0U9jTwVVnjLXvnDYuq3m+QXsHkENxWiPRh4WaoLcs1SQjqgZ55kS+QyirpH5BVwzP2gMVG6EQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@discoveryjs/json-ext": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-1.1.0.tgz",
- "integrity": "sha512-Xc3VhU02wqZ1HvHRJUwL09HkZSTvidqY5Ya0NXBSYOxAp+Ln9dcJr9fySI+CkONzP3PekQo9WdzCv0PGER/mOA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=14.17.0"
- }
- },
- "node_modules/@emnapi/core": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
- "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@emnapi/wasi-threads": "1.2.1",
- "tslib": "^2.4.0"
- }
- },
- "node_modules/@emnapi/runtime": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
- "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "tslib": "^2.4.0"
- }
- },
- "node_modules/@emnapi/wasi-threads": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
- "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "tslib": "^2.4.0"
- }
- },
- "node_modules/@firebase/ai": {
- "version": "2.13.1",
- "resolved": "https://registry.npmjs.org/@firebase/ai/-/ai-2.13.1.tgz",
- "integrity": "sha512-RhT/VViTPBSplhQSuEp62HhLvfsV+LowMh8ZUo5MMRDzG7oFtSget4Kmg5oHP50hDVyWQuQj6to9iPFEZk08Tw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/app-check-interop-types": "0.3.4",
- "@firebase/component": "0.7.3",
- "@firebase/logger": "0.5.1",
- "@firebase/util": "1.15.1",
- "tslib": "^2.1.0"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "@firebase/app": "0.x",
- "@firebase/app-types": "0.x"
- }
- },
- "node_modules/@firebase/analytics": {
- "version": "0.10.22",
- "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.22.tgz",
- "integrity": "sha512-8BSaq/QRGU1+xyi8L2PTLTJU7MH9aMA72RQdIxrbhWFauOZY9OXo8f2YDN/972xA8d588tlnNVEQ2Mo69pT9Ow==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/component": "0.7.3",
- "@firebase/installations": "0.6.22",
- "@firebase/logger": "0.5.1",
- "@firebase/util": "1.15.1",
- "tslib": "^2.1.0"
- },
- "peerDependencies": {
- "@firebase/app": "0.x"
- }
- },
- "node_modules/@firebase/analytics-compat": {
- "version": "0.2.28",
- "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.28.tgz",
- "integrity": "sha512-lIAlqUUbBu93FJMlQfslryQtBwwzdzvp23ePC6FNgymXk6Ook5v4Uvc0vdutvoIeqmyA3LfP0ZeRFK8+11kOOQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/analytics": "0.10.22",
- "@firebase/analytics-types": "0.8.4",
- "@firebase/component": "0.7.3",
- "@firebase/util": "1.15.1",
- "tslib": "^2.1.0"
- },
- "peerDependencies": {
- "@firebase/app-compat": "0.x"
- }
- },
- "node_modules/@firebase/analytics-types": {
- "version": "0.8.4",
- "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.4.tgz",
- "integrity": "sha512-zQ+XTgkwH6CY/eUSHJRP7e4LxM30RCxlCmob5sy2axs25GE3Ny0XdgpDscMTHHQIGqWkxPXad4w2Mw9sCgT8zQ==",
- "license": "Apache-2.0"
- },
- "node_modules/@firebase/app": {
- "version": "0.15.0",
- "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.15.0.tgz",
- "integrity": "sha512-soIskolmGgbpi0K/MfrjtdpO1220qRCbXA4Z8Qx3lM+fVwA3q40m+OM+7zBHd2nuQCrLXb33L6Oc1aBH3Y26AQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/component": "0.7.3",
- "@firebase/logger": "0.5.1",
- "@firebase/util": "1.15.1",
- "idb": "7.1.1",
- "tslib": "^2.1.0"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@firebase/app-check": {
- "version": "0.12.0",
- "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.12.0.tgz",
- "integrity": "sha512-wMeT6HLWRAuW7Cp/5UjWBGKgjPNxWNOoNf4PRIv0weljoGMZVeqbUY7wNBWTI2/31cX1NlXx8gQruDLsUShB3Q==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/component": "0.7.3",
- "@firebase/logger": "0.5.1",
- "@firebase/util": "1.15.1",
- "tslib": "^2.1.0"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "@firebase/app": "0.x"
- }
- },
- "node_modules/@firebase/app-check-compat": {
- "version": "0.4.5",
- "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.4.5.tgz",
- "integrity": "sha512-JI17mVcZs34zO6ZeSCrw4U2iohqy+n6GIzkbmsA+TbVjmvFLkUKt3bs5M+qRBteQm/0IWzqSHYFzEQLzDTQebg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/app-check": "0.12.0",
- "@firebase/app-check-types": "0.5.4",
- "@firebase/component": "0.7.3",
- "@firebase/logger": "0.5.1",
- "@firebase/util": "1.15.1",
- "tslib": "^2.1.0"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "@firebase/app-compat": "0.x"
- }
- },
- "node_modules/@firebase/app-check-interop-types": {
- "version": "0.3.4",
- "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.4.tgz",
- "integrity": "sha512-zz3i6e13B8BfWiLy8MABtTh8aGIACgKbf9UVnyHcWs+yQzJXgQcl8A46b0zfaiJHdQ+niF0ouAfcpuf+3LMPQg==",
- "license": "Apache-2.0"
- },
- "node_modules/@firebase/app-check-types": {
- "version": "0.5.4",
- "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.4.tgz",
- "integrity": "sha512-xV7JsIyzVr15aA7f3Pi0rB9gdBuVubs89FGA8VkRYA4g0l78poADgdfrScgf7NndSg9mm7cR7PJyY0+t22KaGw==",
- "license": "Apache-2.0"
- },
- "node_modules/@firebase/app-compat": {
- "version": "0.5.14",
- "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.5.14.tgz",
- "integrity": "sha512-rgFmiofYsdS9ZG/Bht3OBxJtPD3zWE1cffShWubEm+4+qZeyzCbmtb1q6jOEjN9fB7uufe4rQmWOPXouR3758Q==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/app": "0.15.0",
- "@firebase/component": "0.7.3",
- "@firebase/logger": "0.5.1",
- "@firebase/util": "1.15.1",
- "tslib": "^2.1.0"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@firebase/app-types": {
- "version": "0.9.5",
- "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.5.tgz",
- "integrity": "sha512-YevqTjvo7Iujsa9Dwowmd6dSoElhzmD63ZSrq6bzjvQ6POjYgNjOFHLmNIgJs48eNO093NCERibuFnxbfOvU7A==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/logger": "0.5.1"
- }
- },
- "node_modules/@firebase/auth-compat": {
- "version": "0.6.8",
- "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.6.8.tgz",
- "integrity": "sha512-llcBREUC4iSNKZ6rvwud7Oz9Q7aAWU6KuQLa6pdu7Q+QAQsy4JLw6yFgxwtmzabsgznHmmcsX2UjHLLzqUxi3Q==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/auth": "1.13.3",
- "@firebase/auth-types": "0.13.1",
- "@firebase/component": "0.7.3",
- "@firebase/util": "1.15.1",
- "tslib": "^2.1.0"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "@firebase/app-compat": "0.x"
- }
- },
- "node_modules/@firebase/auth-compat/node_modules/@firebase/auth": {
- "version": "1.13.3",
- "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.13.3.tgz",
- "integrity": "sha512-bqiq4uubDN2YyQkdvSWPQeJyXAv2O76ImF41En9b6UhV5JuBVYDoHYrrrE3NzIuGkpFMKagfhMRP4Vz6t+yQSQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/component": "0.7.3",
- "@firebase/logger": "0.5.1",
- "@firebase/util": "1.15.1",
- "tslib": "^2.1.0"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "@firebase/app": "0.x",
- "@react-native-async-storage/async-storage": "^2.2.0 || ^3.0.0"
- },
- "peerDependenciesMeta": {
- "@react-native-async-storage/async-storage": {
- "optional": true
- }
- }
- },
- "node_modules/@firebase/auth-interop-types": {
- "version": "0.2.5",
- "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.5.tgz",
- "integrity": "sha512-1Li/YuBDBAXcKv7BzY4U28gontUmAaw53sYiqbaVOMCFb2lFKK/c3CGMUWqtwe7+TXrl3poWnTCL5umYBg85Eg==",
- "license": "Apache-2.0"
- },
- "node_modules/@firebase/auth-types": {
- "version": "0.13.1",
- "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.13.1.tgz",
- "integrity": "sha512-0c1Mnid0uMDfGJHeUS4zfvBa4/CedJXotGy/n/NZJnBjwiJawt0ZYU+wH2VAVLiRCEfG2ncCkAX3yd1/2nrB7g==",
- "license": "Apache-2.0",
- "peerDependencies": {
- "@firebase/app-types": "0.x",
- "@firebase/util": "1.x"
- }
- },
- "node_modules/@firebase/component": {
- "version": "0.7.3",
- "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.7.3.tgz",
- "integrity": "sha512-wFofIaa2879ogD/WvkjYXJxRmfnL0scen6ORgaC3na1FNOR9ASIUANQdhqQcmWu/h77/pVHY7ch5flewa5Bcew==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/util": "1.15.1",
- "tslib": "^2.1.0"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@firebase/data-connect": {
- "version": "0.7.1",
- "resolved": "https://registry.npmjs.org/@firebase/data-connect/-/data-connect-0.7.1.tgz",
- "integrity": "sha512-2LbUU8mmSA63HknxQMmWHjpzuNLBKflvVwQc2tpoVKg0biWleNEJX031ELks0vzFs+dDjOUkCJR72RP6mQHFOg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/auth-interop-types": "0.2.5",
- "@firebase/component": "0.7.3",
- "@firebase/logger": "0.5.1",
- "@firebase/util": "1.15.1",
- "tslib": "^2.1.0"
- },
- "peerDependencies": {
- "@firebase/app": "0.x"
- }
- },
- "node_modules/@firebase/database": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.1.3.tgz",
- "integrity": "sha512-XwWCa+E4TvNGpGwXrycLRNfdogADwFcvuhyow6wDWma9W54roaQIhe+4PM0KiLsIftBdSCGI7OKCXrdSRHbIhw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/app-check-interop-types": "0.3.4",
- "@firebase/auth-interop-types": "0.2.5",
- "@firebase/component": "0.7.3",
- "@firebase/logger": "0.5.1",
- "@firebase/util": "1.15.1",
- "faye-websocket": "0.11.4",
- "tslib": "^2.1.0"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@firebase/database-compat": {
- "version": "2.1.4",
- "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.1.4.tgz",
- "integrity": "sha512-3pK35F1MAgmqFJQlf2nhQl44vtAXQO1uaCaQOEUI9kCRtLFqi7N+QRKR7lFZPg+xIZIyubgxQaxY69YgfZRZWg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/component": "0.7.3",
- "@firebase/database": "1.1.3",
- "@firebase/database-types": "1.0.20",
- "@firebase/logger": "0.5.1",
- "@firebase/util": "1.15.1",
- "tslib": "^2.1.0"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@firebase/database-types": {
- "version": "1.0.20",
- "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.20.tgz",
- "integrity": "sha512-kegbOk/w8iU64pr0q6k2ItyNGjnQBMHFhwS7ohdWI4W+pc0/zhhdGXTdFj6X1oxItRjPoYOsSQmERgBkn/ihxw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/app-types": "0.9.5",
- "@firebase/util": "1.15.1"
- }
- },
- "node_modules/@firebase/firestore": {
- "version": "4.16.0",
- "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.16.0.tgz",
- "integrity": "sha512-qdHMHMvMr0nRMuZyWNR/ArWa0YlPE3C4eAbmxTASJMYXAesKPL0Y54p70moggrNPzaK7MSIIq5RDJJyntQyIYA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/component": "0.7.3",
- "@firebase/logger": "0.5.1",
- "@firebase/util": "1.15.1",
- "@firebase/webchannel-wrapper": "1.0.6",
- "@grpc/grpc-js": "~1.9.0",
- "@grpc/proto-loader": "^0.7.8",
- "re2js": "^0.4.2",
- "tslib": "^2.1.0"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "@firebase/app": "0.x"
- }
- },
- "node_modules/@firebase/firestore-compat": {
- "version": "0.4.11",
- "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.4.11.tgz",
- "integrity": "sha512-W7o1WdwWq5aABK5Up2ncSvTQs/QGLR/fy7cVpFBNqhsXtxoMtflHf2xBIG6+aoptcuGAobddq4g2Sq27wqHaYw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/component": "0.7.3",
- "@firebase/firestore": "4.16.0",
- "@firebase/firestore-types": "3.0.4",
- "@firebase/util": "1.15.1",
- "tslib": "^2.1.0"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "@firebase/app-compat": "0.x"
- }
- },
- "node_modules/@firebase/firestore-types": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.4.tgz",
- "integrity": "sha512-jGn+JSS4X9zZsrfu7Yw66v5YRdOLD1oyQh4USR0xWl4CUqV/DA6bNIXRPpxH/cUl3iVTNiP6MN7g+EL42A4qfA==",
- "license": "Apache-2.0",
- "peerDependencies": {
- "@firebase/app-types": "0.x",
- "@firebase/util": "1.x"
- }
- },
- "node_modules/@firebase/functions": {
- "version": "0.13.5",
- "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.13.5.tgz",
- "integrity": "sha512-bWCx713f4kE/uFV7gdFOLBS7lDoiZj48MRkbAqe35gkXcCeWF4QjRNO07Jhmve7EJIoQOBczL29y2r8VRuN1kw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/app-check-interop-types": "0.3.4",
- "@firebase/auth-interop-types": "0.2.5",
- "@firebase/component": "0.7.3",
- "@firebase/messaging-interop-types": "0.2.5",
- "@firebase/util": "1.15.1",
- "tslib": "^2.1.0"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "@firebase/app": "0.x"
- }
- },
- "node_modules/@firebase/functions-compat": {
- "version": "0.4.5",
- "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.4.5.tgz",
- "integrity": "sha512-10qlUXGY25G5/1g9UihqksPp2po+ZqSE7LEizsrdUP7vrTmkysXxGSZCDyojSEp6mQe/ecRDdDDI+z4XRdb4wQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/component": "0.7.3",
- "@firebase/functions": "0.13.5",
- "@firebase/functions-types": "0.6.4",
- "@firebase/util": "1.15.1",
- "tslib": "^2.1.0"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "@firebase/app-compat": "0.x"
- }
- },
- "node_modules/@firebase/functions-types": {
- "version": "0.6.4",
- "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.4.tgz",
- "integrity": "sha512-zV6kgqtduR4rUAdC/ilS7kmb93XD7bEZoJDlVBZqlOw2uGGGCNBQBuleww2rr0Ulr3L9o2TDjumEt68/l1f9DQ==",
- "license": "Apache-2.0"
- },
- "node_modules/@firebase/installations": {
- "version": "0.6.22",
- "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.22.tgz",
- "integrity": "sha512-ef6nn3GGQTdReCfotRMG77PJZu8CqEbiK5pEoBnM0gTu/Z9v0i/az2p3HABsa/1beQmmyh1OsOjf7P5+pgwdZw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/component": "0.7.3",
- "@firebase/util": "1.15.1",
- "idb": "7.1.1",
- "tslib": "^2.1.0"
- },
- "peerDependencies": {
- "@firebase/app": "0.x"
- }
- },
- "node_modules/@firebase/installations-compat": {
- "version": "0.2.22",
- "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.22.tgz",
- "integrity": "sha512-C/zpAuTP5S9OgKSPvXRupw3hoY/JZSlA1wFjD/Sb7LIQE0FNbcMdO8Y4KXVEkjVzma/DDDDIAzxEXqKMAzc88w==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/component": "0.7.3",
- "@firebase/installations": "0.6.22",
- "@firebase/installations-types": "0.5.4",
- "@firebase/util": "1.15.1",
- "tslib": "^2.1.0"
- },
- "peerDependencies": {
- "@firebase/app-compat": "0.x"
- }
- },
- "node_modules/@firebase/installations-types": {
- "version": "0.5.4",
- "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.4.tgz",
- "integrity": "sha512-U2eFapdHwjb43Vx9o+Pmj4dFfvcHEK1IirEFLqMtWrTHvmdrS3gBpBD1kmJk/9HjsOtoHZxJ2Paoe79e+L1ZPg==",
- "license": "Apache-2.0",
- "peerDependencies": {
- "@firebase/app-types": "0.x"
- }
- },
- "node_modules/@firebase/logger": {
- "version": "0.5.1",
- "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.5.1.tgz",
- "integrity": "sha512-vZKLsqE1ABOy8OjQiE7cUTFn4gvaqlk88yp8N94Pk/sDpq61YqZGqmVFZTvOyflTwuYFcWirBdYGoJgbDaXKYQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.1.0"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@firebase/messaging": {
- "version": "0.13.0",
- "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.13.0.tgz",
- "integrity": "sha512-GZoo0uGRvEbszo83xcgbjJp4FpkmBEr4l8Z4hi8gl+P1Spn/MTK3HapanMzSX4yUHuTEiF5hasWRxOaz+o5sxQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/component": "0.7.3",
- "@firebase/installations": "0.6.22",
- "@firebase/messaging-interop-types": "0.2.5",
- "@firebase/util": "1.15.1",
- "idb": "7.1.1",
- "tslib": "^2.1.0"
- },
- "peerDependencies": {
- "@firebase/app": "0.x"
- }
- },
- "node_modules/@firebase/messaging-compat": {
- "version": "0.2.27",
- "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.27.tgz",
- "integrity": "sha512-JNOiu1PPgdHzEPEtoFiNxQuu0x9bm4bfETSQCpGfcTlgWkhlSK7uh7nlsjC10TQLUNgYetLmuutaYTh8aeYLVA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/component": "0.7.3",
- "@firebase/messaging": "0.13.0",
- "@firebase/util": "1.15.1",
- "tslib": "^2.1.0"
- },
- "peerDependencies": {
- "@firebase/app-compat": "0.x"
- }
- },
- "node_modules/@firebase/messaging-interop-types": {
- "version": "0.2.5",
- "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.5.tgz",
- "integrity": "sha512-tUEKnaAP2Y/MNIqgnriPpV6e5l13Vs/+p2yrd6NGlncPJT9O3a8muYZtdnWe+IJ4fgKLHJVC79n/asxk/N5Msw==",
- "license": "Apache-2.0"
- },
- "node_modules/@firebase/performance": {
- "version": "0.7.12",
- "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.7.12.tgz",
- "integrity": "sha512-fe7nV8teUU3OBHlMUZ9Lw4gLhCW2k4m5Uc3pfWGV+fl8uwJQBGp9Q3lqsJ+HSrFu3Q2pJyLAgrClPGSKyDeYgQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/component": "0.7.3",
- "@firebase/installations": "0.6.22",
- "@firebase/logger": "0.5.1",
- "@firebase/util": "1.15.1",
- "tslib": "^2.1.0",
- "web-vitals": "^4.2.4"
- },
- "peerDependencies": {
- "@firebase/app": "0.x"
- }
- },
- "node_modules/@firebase/performance-compat": {
- "version": "0.2.25",
- "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.25.tgz",
- "integrity": "sha512-q6NjTXpIPoFuUmCmMN/maCdTgzT6aExs9xZo+PxfVLj6uLVGvpyAD6XWjmcrb7jChsFBYbq7E5dyNDF7Zhy9kA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/component": "0.7.3",
- "@firebase/logger": "0.5.1",
- "@firebase/performance": "0.7.12",
- "@firebase/performance-types": "0.2.4",
- "@firebase/util": "1.15.1",
- "tslib": "^2.1.0"
- },
- "peerDependencies": {
- "@firebase/app-compat": "0.x"
- }
- },
- "node_modules/@firebase/performance-types": {
- "version": "0.2.4",
- "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.4.tgz",
- "integrity": "sha512-kJSEk7b0uhpcPRyL4SQ/GPujLqk52XNKcXlnsKDbWGAb9vugcLvOU3u6zfEdwd+d8hWJb5S5ZizV1JFFI0nkKg==",
- "license": "Apache-2.0"
- },
- "node_modules/@firebase/remote-config": {
- "version": "0.8.5",
- "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.8.5.tgz",
- "integrity": "sha512-zb+7CDGFP2wYVF1LXQoYIFdoESIQM3p0+uiW1welw8+zvDxAL50K75PKTXXtunJADUrksTVpV7mD0pn54vzJRA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/component": "0.7.3",
- "@firebase/installations": "0.6.22",
- "@firebase/logger": "0.5.1",
- "@firebase/util": "1.15.1",
- "tslib": "^2.1.0"
- },
- "peerDependencies": {
- "@firebase/app": "0.x"
- }
- },
- "node_modules/@firebase/remote-config-compat": {
- "version": "0.2.26",
- "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.26.tgz",
- "integrity": "sha512-uC57Tc7GYYOCnMgLkGIVf999XlaYaPDONoa54c93YTKDctlvCZI89z0zQ2RbhGR8Zf+QuCbQHs/99vqoE84a7g==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/component": "0.7.3",
- "@firebase/logger": "0.5.1",
- "@firebase/remote-config": "0.8.5",
- "@firebase/remote-config-types": "0.5.1",
- "@firebase/util": "1.15.1",
- "tslib": "^2.1.0"
- },
- "peerDependencies": {
- "@firebase/app-compat": "0.x"
- }
- },
- "node_modules/@firebase/remote-config-types": {
- "version": "0.5.1",
- "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.5.1.tgz",
- "integrity": "sha512-cX/1LT6KQwkXzck2eSzeKnuvXZCyr8qaPpDcikoJs7jmI+oBOXixpDLeDtWj1U6GNMkIoXrEDNoyT2Ypcyp5/A==",
- "license": "Apache-2.0"
- },
- "node_modules/@firebase/storage": {
- "version": "0.14.3",
- "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.14.3.tgz",
- "integrity": "sha512-YX4/YL6P6/fufSSeGnVhjWddcIXbFq2cWIhMKFTZo1E/Rtcl2mJj/BYUQTwJfcE1Tl8un1FOya4L05jcSLN/Eg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/component": "0.7.3",
- "@firebase/util": "1.15.1",
- "tslib": "^2.1.0"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "@firebase/app": "0.x"
- }
- },
- "node_modules/@firebase/storage-compat": {
- "version": "0.4.3",
- "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.4.3.tgz",
- "integrity": "sha512-gruVqjtUGX8tEoeNbaWXZm0Zfcfcb7fvmDmBxV8yPAbWvExRnZYLO2+qw9idxNE7BvPXt5csyjSYHy//dAizxw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/component": "0.7.3",
- "@firebase/storage": "0.14.3",
- "@firebase/storage-types": "0.8.4",
- "@firebase/util": "1.15.1",
- "tslib": "^2.1.0"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "@firebase/app-compat": "0.x"
- }
- },
- "node_modules/@firebase/storage-types": {
- "version": "0.8.4",
- "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.4.tgz",
- "integrity": "sha512-BT7cwxJOx8SWwlQfrlC+bD/Sk3Cw+1odCi8UZNFNWTVZoPsBnA5W+mqtZzVnvsdJpXCFGSGQ7R7vOR6dtM/BRA==",
- "license": "Apache-2.0",
- "peerDependencies": {
- "@firebase/app-types": "0.x",
- "@firebase/util": "1.x"
- }
- },
- "node_modules/@firebase/util": {
- "version": "1.15.1",
- "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.15.1.tgz",
- "integrity": "sha512-LUdM4Wg7YM9Pq/49nGYySJA0CSQEKnGffFzWV8+6gXN7mGxn+FL1IqvFbuZUtAQcfZgHYDwCE1wwlK7rB7gl2g==",
- "hasInstallScript": true,
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.1.0"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@firebase/webchannel-wrapper": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.6.tgz",
- "integrity": "sha512-Vr/Mqu79dMwGRAyGbJ4uN4+BtXB3/mRTdzetD1daWNeG8QaWuzhhbG77GltO5c0yYmYls8i250iX73624GJd7Q==",
- "license": "Apache-2.0"
- },
- "node_modules/@formatjs/cli": {
- "version": "6.16.11",
- "resolved": "https://registry.npmjs.org/@formatjs/cli/-/cli-6.16.11.tgz",
- "integrity": "sha512-O3tTFavGK3zOhW4i2/ONWfMUgbU7fapxNbIsdFuF9eTMtQZFnKFdoBDdiBHj7d2W4ULVyeWd4b3qw2XHYpmuBg==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "formatjs": "bin/formatjs"
- },
- "engines": {
- "node": ">= 20.12.0"
- },
- "optionalDependencies": {
- "@formatjs/cli-native-darwin-arm64": "1.1.5",
- "@formatjs/cli-native-linux-arm64": "1.2.5",
- "@formatjs/cli-native-linux-arm64-musl": "1.0.3",
- "@formatjs/cli-native-linux-x64": "1.1.5",
- "@formatjs/cli-native-linux-x64-musl": "1.0.3",
- "@formatjs/cli-native-win32-x64": "1.1.6"
- },
- "peerDependencies": {
- "@glimmer/syntax": "^0.84.3 || ^0.95.0",
- "@vue/compiler-core": "^3.5.0",
- "content-tag": "^4.1.0",
- "vue": "^3.5.0"
- },
- "peerDependenciesMeta": {
- "@glimmer/env": {
- "optional": true
- },
- "@glimmer/reference": {
- "optional": true
- },
- "@glimmer/syntax": {
- "optional": true
- },
- "@glimmer/validator": {
- "optional": true
- },
- "@vue/compiler-core": {
- "optional": true
- },
- "content-tag": {
- "optional": true
- },
- "vue": {
- "optional": true
- }
- }
- },
- "node_modules/@formatjs/cli-native-darwin-arm64": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@formatjs/cli-native-darwin-arm64/-/cli-native-darwin-arm64-1.1.5.tgz",
- "integrity": "sha512-zGt7OenQakyh8a+eAgCx/zUInbQXYT0UNzeOaYlnaKmU9yr78wsVAGGhPc85gchnQ7gIXSWOKhwIzvfSMFGU7w==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@formatjs/cli-native-linux-arm64": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/@formatjs/cli-native-linux-arm64/-/cli-native-linux-arm64-1.2.5.tgz",
- "integrity": "sha512-G1Ou5CFDM2hfqmD2jfXJJpHL7DL7f7OxlMXf6VPWvUYu+jcvpEj0KdEtmz5poh8rWzfHbOf4MfqC6WNhExJD5A==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@formatjs/cli-native-linux-arm64-musl": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/@formatjs/cli-native-linux-arm64-musl/-/cli-native-linux-arm64-musl-1.0.3.tgz",
- "integrity": "sha512-1qq9EZIY87cnptTd2PywI41s/DlkJBzNID6AofHCYm8sunXfj+vYAIQOSZnctNm7/u1ZlSJ/VjxHSS1NIC4tTw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@formatjs/cli-native-linux-x64": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@formatjs/cli-native-linux-x64/-/cli-native-linux-x64-1.1.5.tgz",
- "integrity": "sha512-v/EtDIMC7C3cOpYpRJDvUImQFHBveFP+PTggZJbi88zXCeMMEL6eMwbMlJezUIwvYjgH1FWQargCgwOnI3lVdg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@formatjs/cli-native-linux-x64-musl": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/@formatjs/cli-native-linux-x64-musl/-/cli-native-linux-x64-musl-1.0.3.tgz",
- "integrity": "sha512-wb90O1S7cad8QKmHQ2I5glN9O9m+y+V29mrvvxFbKAVmVbKB7oyjhoau/jtuHQ/Yv2tfah3CYCj+1iaDNlJmYg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@formatjs/cli-native-win32-x64": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/@formatjs/cli-native-win32-x64/-/cli-native-win32-x64-1.1.6.tgz",
- "integrity": "sha512-fsPOUB4JePy6VJfQV+oorLfozbb9IfYRAncGZ54VZg77Uj2aOP2h/KLhQmgippNlgi0Q77EB8QDMzNQfaKpVAA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@formatjs/ecma402-abstract": {
- "version": "2.3.6",
- "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.6.tgz",
- "integrity": "sha512-HJnTFeRM2kVFVr5gr5kH1XP6K0JcJtE7Lzvtr3FS/so5f1kpsqqqxy5JF+FRaO6H2qmcMfAUIox7AJteieRtVw==",
- "license": "MIT",
- "dependencies": {
- "@formatjs/fast-memoize": "2.2.7",
- "@formatjs/intl-localematcher": "0.6.2",
- "decimal.js": "^10.4.3",
- "tslib": "^2.8.0"
- }
- },
- "node_modules/@formatjs/fast-memoize": {
- "version": "2.2.7",
- "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.7.tgz",
- "integrity": "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==",
- "license": "MIT",
- "dependencies": {
- "tslib": "^2.8.0"
- }
- },
- "node_modules/@formatjs/icu-messageformat-parser": {
- "version": "3.5.12",
- "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.12.tgz",
- "integrity": "sha512-YyzzxVgYJ8DELmmkhn0Yr0rUj0dTJFf9Jp628K3S0ysInBWxLVDOS8i3RP91cCp4DMK4WYb4cVMhWA9i4knSJg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@formatjs/icu-skeleton-parser": "2.1.10"
- }
- },
- "node_modules/@formatjs/icu-skeleton-parser": {
- "version": "2.1.10",
- "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.10.tgz",
- "integrity": "sha512-XuSva+8ZGawk8VnD5VD6UeH8KarQ/Z022zgjHDoHmlNiAewstXuuzXc0Hk5pGFSdG+nNw5bfJKXqj1ZXHn9yUA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@formatjs/intl": {
- "version": "3.1.8",
- "resolved": "https://registry.npmjs.org/@formatjs/intl/-/intl-3.1.8.tgz",
- "integrity": "sha512-LWXgwI5zTMatvR8w8kCNh/priDTOF/ZssokMBHJ7ZWXFoYLVOYo0EJERD9Eajv+xsfQO1QkuAt77KWQ1OI4mOQ==",
- "license": "MIT",
- "dependencies": {
- "@formatjs/ecma402-abstract": "2.3.6",
- "@formatjs/fast-memoize": "2.2.7",
- "@formatjs/icu-messageformat-parser": "2.11.4",
- "intl-messageformat": "10.7.18",
- "tslib": "^2.8.0"
- },
- "peerDependencies": {
- "typescript": "^5.6.0"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
- }
- },
- "node_modules/@formatjs/intl-localematcher": {
- "version": "0.6.2",
- "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.6.2.tgz",
- "integrity": "sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA==",
- "license": "MIT",
- "dependencies": {
- "tslib": "^2.8.0"
- }
- },
- "node_modules/@formatjs/intl/node_modules/@formatjs/icu-messageformat-parser": {
- "version": "2.11.4",
- "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.11.4.tgz",
- "integrity": "sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw==",
- "license": "MIT",
- "dependencies": {
- "@formatjs/ecma402-abstract": "2.3.6",
- "@formatjs/icu-skeleton-parser": "1.8.16",
- "tslib": "^2.8.0"
- }
- },
- "node_modules/@formatjs/intl/node_modules/@formatjs/icu-skeleton-parser": {
- "version": "1.8.16",
- "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.16.tgz",
- "integrity": "sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ==",
- "license": "MIT",
- "dependencies": {
- "@formatjs/ecma402-abstract": "2.3.6",
- "tslib": "^2.8.0"
- }
- },
- "node_modules/@formatjs/ts-transformer": {
- "version": "4.4.14",
- "resolved": "https://registry.npmjs.org/@formatjs/ts-transformer/-/ts-transformer-4.4.14.tgz",
- "integrity": "sha512-k3zaGIAUpQIS5tpifOJ8Jzsqy+j1YV916b1yhwd6US+kRQHdSyxtk76zad8NabalOWel5ouyzGohD0ZlApKkdg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@formatjs/icu-messageformat-parser": "3.5.12",
- "@types/babel__core": "^7.20.5",
- "@types/node": "22 || 24",
- "json-stable-stringify": "^1.3.0",
- "typescript": "^5.6 || 6"
- },
- "engines": {
- "node": ">= 20.12.0"
- },
- "peerDependencies": {
- "ts-jest": "^29"
- },
- "peerDependenciesMeta": {
- "ts-jest": {
- "optional": true
- }
- }
- },
- "node_modules/@grpc/grpc-js": {
- "version": "1.9.16",
- "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.16.tgz",
- "integrity": "sha512-wE4Ut/olIzfKqp631XrG+wbF0v1vWFN4YL9FyXC2LJiG33DsV7PLzURjrCvY/6je2ntdRkeLpPDluzSRGaVltQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@grpc/proto-loader": "^0.7.8",
- "@types/node": ">=12.12.47"
- },
- "engines": {
- "node": "^8.13.0 || >=10.10.0"
- }
- },
- "node_modules/@grpc/proto-loader": {
- "version": "0.7.15",
- "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz",
- "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "lodash.camelcase": "^4.3.0",
- "long": "^5.0.0",
- "protobufjs": "^7.2.5",
- "yargs": "^17.7.2"
- },
- "bin": {
- "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/@isaacs/cliui": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
- "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "string-width": "^5.1.2",
- "string-width-cjs": "npm:string-width@^4.2.0",
- "strip-ansi": "^7.0.1",
- "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
- "wrap-ansi": "^8.1.0",
- "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/ansi-regex": {
- "version": "6.2.2",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
- "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-regex?sponsor=1"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/ansi-styles": {
- "version": "6.2.3",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
- "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/emoji-regex": {
- "version": "9.2.2",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
- "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@isaacs/cliui/node_modules/string-width": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
- "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "eastasianwidth": "^0.2.0",
- "emoji-regex": "^9.2.2",
- "strip-ansi": "^7.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/strip-ansi": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
- "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^6.2.2"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/strip-ansi?sponsor=1"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
- "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^6.1.0",
- "string-width": "^5.0.1",
- "strip-ansi": "^7.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
- "node_modules/@istanbuljs/load-nyc-config": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
- "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "camelcase": "^5.3.1",
- "find-up": "^4.1.0",
- "get-package-type": "^0.1.0",
- "js-yaml": "^3.13.1",
- "resolve-from": "^5.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": {
- "version": "1.0.10",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
- "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "sprintf-js": "~1.0.2"
- }
- },
- "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
- "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "locate-path": "^5.0.0",
- "path-exists": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": {
- "version": "3.15.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.0.tgz",
- "integrity": "sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "argparse": "^1.0.7",
- "esprima": "^4.0.0"
- },
- "bin": {
- "js-yaml": "bin/js-yaml.js"
- }
- },
- "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
- "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-locate": "^4.1.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
- "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-try": "^2.0.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
- "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-limit": "^2.2.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/@istanbuljs/schema": {
- "version": "0.1.6",
- "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz",
- "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/@jest/console": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.4.1.tgz",
- "integrity": "sha512-v3bhyxUh9Hgmo5p6hAOXe14/R3ZxZDOsvHleh4B07z3m/x4/ngPUXEm9XwK4sF4u+f+P2ORb0Ge+MgpaqRMVDA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/types": "30.4.1",
- "@types/node": "*",
- "chalk": "^4.1.2",
- "jest-message-util": "30.4.1",
- "jest-util": "30.4.1",
- "slash": "^3.0.0"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/@jest/core": {
- "version": "30.4.2",
- "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.4.2.tgz",
- "integrity": "sha512-TZJA6cPJUFxoWhxaLo8t0VX/MZX2wPWr0uIDvLSHIvN4gu9h02vSzqI2kBADG1ExqQlC+cY09xKMSreivvrChQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/console": "30.4.1",
- "@jest/pattern": "30.4.0",
- "@jest/reporters": "30.4.1",
- "@jest/test-result": "30.4.1",
- "@jest/transform": "30.4.1",
- "@jest/types": "30.4.1",
- "@types/node": "*",
- "ansi-escapes": "^4.3.2",
- "chalk": "^4.1.2",
- "ci-info": "^4.2.0",
- "exit-x": "^0.2.2",
- "fast-json-stable-stringify": "^2.1.0",
- "graceful-fs": "^4.2.11",
- "jest-changed-files": "30.4.1",
- "jest-config": "30.4.2",
- "jest-haste-map": "30.4.1",
- "jest-message-util": "30.4.1",
- "jest-regex-util": "30.4.0",
- "jest-resolve": "30.4.1",
- "jest-resolve-dependencies": "30.4.2",
- "jest-runner": "30.4.2",
- "jest-runtime": "30.4.2",
- "jest-snapshot": "30.4.1",
- "jest-util": "30.4.1",
- "jest-validate": "30.4.1",
- "jest-watcher": "30.4.1",
- "pretty-format": "30.4.1",
- "slash": "^3.0.0"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- },
- "peerDependencies": {
- "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
- },
- "peerDependenciesMeta": {
- "node-notifier": {
- "optional": true
- }
- }
- },
- "node_modules/@jest/diff-sequences": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.4.0.tgz",
- "integrity": "sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/@jest/environment": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.4.1.tgz",
- "integrity": "sha512-AK9yNRqgKxiabqMoe4oW+3/TSSeV8vkdC7BGaxZdU0AFXfOpofTLqdru2GXKZghP3sdgwE9XXpnVwfZ8JnFV4w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/fake-timers": "30.4.1",
- "@jest/types": "30.4.1",
- "@types/node": "*",
- "jest-mock": "30.4.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/@jest/expect": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.4.1.tgz",
- "integrity": "sha512-ginrj6TMgh2GshLUGCjO94Ptx9HhdZA/I6A9iUfyeLKFtdAjnKzHDgzgP9HYQgbxM1lbXScQ2eUBz2lGeVDPWA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "expect": "30.4.1",
- "jest-snapshot": "30.4.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/@jest/expect-utils": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.4.1.tgz",
- "integrity": "sha512-ZBn5CglH8fBsQsvs4VWNzD4aWfUYks+IdOOQU3MEK71ol/BcVm+P+rtb1KpiFBpSWSCE27uOahyyf1vfqOVbcQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/get-type": "30.1.0"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/@jest/fake-timers": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.4.1.tgz",
- "integrity": "sha512-iW5umdmfPeWzehrVhugFQZqCchSCud5S1l2YT0O9ZhjRR0ExclANDZkiSBwzqtnlOn0J1JXvO+HZ6rkuyOVOgQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/types": "30.4.1",
- "@sinonjs/fake-timers": "^15.4.0",
- "@types/node": "*",
- "jest-message-util": "30.4.1",
- "jest-mock": "30.4.1",
- "jest-util": "30.4.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/@jest/get-type": {
- "version": "30.1.0",
- "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz",
- "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/@jest/globals": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.4.1.tgz",
- "integrity": "sha512-ZbuY4cmXC8DkxYjfvT2DbcHWL2T6vmsMhXCDcmTB2T0y0gaezBI77ufq5ZAIdcRkYZ7NEQEDg1xFeKbxUJ5v5Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/environment": "30.4.1",
- "@jest/expect": "30.4.1",
- "@jest/types": "30.4.1",
- "jest-mock": "30.4.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/@jest/pattern": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz",
- "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "jest-regex-util": "30.4.0"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/@jest/reporters": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.4.1.tgz",
- "integrity": "sha512-/SnkPCzEQpUaBH81kjdEdDdo2WZl5hxw+BmLDGWjRkm8o7XlhjwsU36cqwe5PGBE5WYpBvDzRSdXx9rbGuJtNA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@bcoe/v8-coverage": "^0.2.3",
- "@jest/console": "30.4.1",
- "@jest/test-result": "30.4.1",
- "@jest/transform": "30.4.1",
- "@jest/types": "30.4.1",
- "@jridgewell/trace-mapping": "^0.3.25",
- "@types/node": "*",
- "chalk": "^4.1.2",
- "collect-v8-coverage": "^1.0.2",
- "exit-x": "^0.2.2",
- "glob": "^10.5.0",
- "graceful-fs": "^4.2.11",
- "istanbul-lib-coverage": "^3.0.0",
- "istanbul-lib-instrument": "^6.0.0",
- "istanbul-lib-report": "^3.0.0",
- "istanbul-lib-source-maps": "^5.0.0",
- "istanbul-reports": "^3.1.3",
- "jest-message-util": "30.4.1",
- "jest-util": "30.4.1",
- "jest-worker": "30.4.1",
- "slash": "^3.0.0",
- "string-length": "^4.0.2",
- "v8-to-istanbul": "^9.0.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- },
- "peerDependencies": {
- "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
- },
- "peerDependenciesMeta": {
- "node-notifier": {
- "optional": true
- }
- }
- },
- "node_modules/@jest/reporters/node_modules/balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@jest/reporters/node_modules/brace-expansion": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
- "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
- "node_modules/@jest/reporters/node_modules/glob": {
- "version": "10.5.0",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
- "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
- "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "foreground-child": "^3.1.0",
- "jackspeak": "^3.1.2",
- "minimatch": "^9.0.4",
- "minipass": "^7.1.2",
- "package-json-from-dist": "^1.0.0",
- "path-scurry": "^1.11.1"
- },
- "bin": {
- "glob": "dist/esm/bin.mjs"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/@jest/reporters/node_modules/lru-cache": {
- "version": "10.4.3",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
- "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/@jest/reporters/node_modules/minimatch": {
- "version": "9.0.9",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
- "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.2"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/@jest/reporters/node_modules/path-scurry": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
- "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "lru-cache": "^10.2.0",
- "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
- },
- "engines": {
- "node": ">=16 || 14 >=14.18"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/@jest/schemas": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz",
- "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@sinclair/typebox": "^0.34.0"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/@jest/snapshot-utils": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.4.1.tgz",
- "integrity": "sha512-ObY4ljvQ95mt6iwKtVLetR/4yXiAgl3H4nJxhztr0MTjrN97TwDYrnCp/kF60Ec9HdhkWTHSu+Hg05aXfngpOA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/types": "30.4.1",
- "chalk": "^4.1.2",
- "graceful-fs": "^4.2.11",
- "natural-compare": "^1.4.0"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/@jest/source-map": {
- "version": "30.0.1",
- "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz",
- "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/trace-mapping": "^0.3.25",
- "callsites": "^3.1.0",
- "graceful-fs": "^4.2.11"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/@jest/test-result": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.4.1.tgz",
- "integrity": "sha512-/ZG7pgEiOmmWkN9TplKbOu4id2N5lh7FHwRwlkgBVAzGdRH+OkkQ8wX/kIxg4zmd3ZQvAL1RwL2yWsvNYYECTw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/console": "30.4.1",
- "@jest/types": "30.4.1",
- "@types/istanbul-lib-coverage": "^2.0.6",
- "collect-v8-coverage": "^1.0.2"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/@jest/test-sequencer": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.4.1.tgz",
- "integrity": "sha512-PeYE+4td5rKjoRPxztObrXU+H8hsjZfxKMXOcmrr34JerSyB/ROOxbbicz8B7A5j9R9VayDnVPvBmedqCsFCdw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/test-result": "30.4.1",
- "graceful-fs": "^4.2.11",
- "jest-haste-map": "30.4.1",
- "slash": "^3.0.0"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/@jest/transform": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.4.1.tgz",
- "integrity": "sha512-Wz0LyktlTvRefoymh+n64hQ84KNXsRGcwdoZ8CSa0Ea+fgYcHZlnk+hDP7v2MS7il2bQ5uTEIxf4/NNfhMN4KQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/core": "^7.27.4",
- "@jest/types": "30.4.1",
- "@jridgewell/trace-mapping": "^0.3.25",
- "babel-plugin-istanbul": "^7.0.1",
- "chalk": "^4.1.2",
- "convert-source-map": "^2.0.0",
- "fast-json-stable-stringify": "^2.1.0",
- "graceful-fs": "^4.2.11",
- "jest-haste-map": "30.4.1",
- "jest-regex-util": "30.4.0",
- "jest-util": "30.4.1",
- "pirates": "^4.0.7",
- "slash": "^3.0.0",
- "write-file-atomic": "^5.0.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/@jest/types": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz",
- "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/pattern": "30.4.0",
- "@jest/schemas": "30.4.1",
- "@types/istanbul-lib-coverage": "^2.0.6",
- "@types/istanbul-reports": "^3.0.4",
- "@types/node": "*",
- "@types/yargs": "^17.0.33",
- "chalk": "^4.1.2"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.13",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
- "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/sourcemap-codec": "^1.5.0",
- "@jridgewell/trace-mapping": "^0.3.24"
- }
- },
- "node_modules/@jridgewell/resolve-uri": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
- "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/source-map": {
- "version": "0.3.11",
- "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
- "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/gen-mapping": "^0.3.5",
- "@jridgewell/trace-mapping": "^0.3.25"
- }
- },
- "node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
- "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.31",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
- "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/resolve-uri": "^3.1.0",
- "@jridgewell/sourcemap-codec": "^1.4.14"
- }
- },
- "node_modules/@napi-rs/wasm-runtime": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz",
- "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@tybys/wasm-util": "^0.10.3"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/Brooooooklyn"
- },
- "peerDependencies": {
- "@emnapi/core": "^1.7.1",
- "@emnapi/runtime": "^1.7.1"
- }
- },
- "node_modules/@pkgjs/parseargs": {
- "version": "0.11.0",
- "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
- "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/@pkgr/core": {
- "version": "0.3.6",
- "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.3.6.tgz",
- "integrity": "sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^14.18.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/pkgr"
- }
- },
- "node_modules/@protobufjs/aspromise": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
- "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@protobufjs/base64": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
- "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@protobufjs/codegen": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz",
- "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@protobufjs/eventemitter": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz",
- "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@protobufjs/fetch": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz",
- "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==",
- "license": "BSD-3-Clause",
- "dependencies": {
- "@protobufjs/aspromise": "^1.1.1"
- }
- },
- "node_modules/@protobufjs/float": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
- "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@protobufjs/path": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
- "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@protobufjs/pool": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
- "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@protobufjs/utf8": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz",
- "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@sinclair/typebox": {
- "version": "0.34.49",
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz",
- "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@sinonjs/commons": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz",
- "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "type-detect": "4.0.8"
- }
- },
- "node_modules/@sinonjs/fake-timers": {
- "version": "15.4.0",
- "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.4.0.tgz",
- "integrity": "sha512-DsG+8/LscQIQg68J6Ef3dv10u6nVyetYn923s3/sus5eaGfTo1of5WMZSLf0UJc9KDuKPilPH0UDJCjvNbDNCA==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@sinonjs/commons": "^3.0.1"
- }
- },
- "node_modules/@tybys/wasm-util": {
- "version": "0.10.3",
- "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz",
- "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "tslib": "^2.4.0"
- }
- },
- "node_modules/@types/babel__core": {
- "version": "7.20.5",
- "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
- "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.20.7",
- "@babel/types": "^7.20.7",
- "@types/babel__generator": "*",
- "@types/babel__template": "*",
- "@types/babel__traverse": "*"
- }
- },
- "node_modules/@types/babel__core/node_modules/@babel/helper-string-parser": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
- "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@types/babel__core/node_modules/@babel/helper-validator-identifier": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
- "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@types/babel__core/node_modules/@babel/parser": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
- "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.29.7"
- },
- "bin": {
- "parser": "bin/babel-parser.js"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@types/babel__core/node_modules/@babel/types": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
- "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-string-parser": "^7.29.7",
- "@babel/helper-validator-identifier": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@types/babel__generator": {
- "version": "7.27.0",
- "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
- "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.0.0"
- }
- },
- "node_modules/@types/babel__generator/node_modules/@babel/helper-string-parser": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
- "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@types/babel__generator/node_modules/@babel/helper-validator-identifier": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
- "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@types/babel__generator/node_modules/@babel/types": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
- "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-string-parser": "^7.29.7",
- "@babel/helper-validator-identifier": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@types/babel__helper-plugin-utils": {
- "version": "7.10.3",
- "resolved": "https://registry.npmjs.org/@types/babel__helper-plugin-utils/-/babel__helper-plugin-utils-7.10.3.tgz",
- "integrity": "sha512-FcLBBPXInqKfULB2nvOBskQPcnSMZ0s1Y2q76u9H1NPPWaLcTeq38xBeKfF/RBUECK333qeaqRdYoPSwW7rTNQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/babel__core": "*"
- }
- },
- "node_modules/@types/babel__template": {
- "version": "7.4.4",
- "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
- "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.1.0",
- "@babel/types": "^7.0.0"
- }
- },
- "node_modules/@types/babel__template/node_modules/@babel/helper-string-parser": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
- "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@types/babel__template/node_modules/@babel/helper-validator-identifier": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
- "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@types/babel__template/node_modules/@babel/parser": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
- "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.29.7"
- },
- "bin": {
- "parser": "bin/babel-parser.js"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@types/babel__template/node_modules/@babel/types": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
- "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-string-parser": "^7.29.7",
- "@babel/helper-validator-identifier": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@types/babel__traverse": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
- "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.28.2"
- }
- },
- "node_modules/@types/babel__traverse/node_modules/@babel/helper-string-parser": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
- "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@types/babel__traverse/node_modules/@babel/helper-validator-identifier": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
- "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@types/babel__traverse/node_modules/@babel/types": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
- "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-string-parser": "^7.29.7",
- "@babel/helper-validator-identifier": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@types/estree": {
- "version": "1.0.9",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
- "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/gensync": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/@types/gensync/-/gensync-1.0.5.tgz",
- "integrity": "sha512-MbsRCT7mTikHwKZ0X+LVUTLRrZZRLipTuXEO9qOYO+zmjMVk81axyClMROf6uoPD9MRVu46bx8zoR0Ad9q3NAg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/hoist-non-react-statics": {
- "version": "3.3.7",
- "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.7.tgz",
- "integrity": "sha512-PQTyIulDkIDro8P+IHbKCsw7U2xxBYflVzW/FgWdCAePD9xGSidgA76/GeJ6lBKoblyhf9pBY763gbrN+1dI8g==",
- "license": "MIT",
- "dependencies": {
- "hoist-non-react-statics": "^3.3.0"
- },
- "peerDependencies": {
- "@types/react": "*"
- }
- },
- "node_modules/@types/istanbul-lib-coverage": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
- "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/istanbul-lib-report": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz",
- "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/istanbul-lib-coverage": "*"
- }
- },
- "node_modules/@types/istanbul-reports": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz",
- "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/istanbul-lib-report": "*"
- }
- },
- "node_modules/@types/jsesc": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/@types/jsesc/-/jsesc-2.5.1.tgz",
- "integrity": "sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/json-schema": {
- "version": "7.0.15",
- "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
- "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/node": {
- "version": "24.13.2",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz",
- "integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==",
- "license": "MIT",
- "dependencies": {
- "undici-types": "~7.18.0"
- }
- },
- "node_modules/@types/react": {
- "version": "19.2.17",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz",
- "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==",
- "license": "MIT",
- "dependencies": {
- "csstype": "^3.2.2"
- }
- },
- "node_modules/@types/stack-utils": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
- "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/yargs": {
- "version": "17.0.35",
- "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz",
- "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/yargs-parser": "*"
- }
- },
- "node_modules/@types/yargs-parser": {
- "version": "21.0.3",
- "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
- "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@ungap/structured-clone": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.2.tgz",
- "integrity": "sha512-5jsZFwgR5rTdKwidH9Qmat75RKwqfpKlWWB1frDkljN127mwqBu8K0PYo7/hFpF03IEJpfVPpCQDY/eDx3iHvA==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/@unrs/resolver-binding-android-arm-eabi": {
- "version": "1.12.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz",
- "integrity": "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@unrs/resolver-binding-android-arm64": {
- "version": "1.12.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.12.2.tgz",
- "integrity": "sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@unrs/resolver-binding-darwin-arm64": {
- "version": "1.12.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.12.2.tgz",
- "integrity": "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@unrs/resolver-binding-darwin-x64": {
- "version": "1.12.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.12.2.tgz",
- "integrity": "sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@unrs/resolver-binding-freebsd-x64": {
- "version": "1.12.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.12.2.tgz",
- "integrity": "sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ]
- },
- "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": {
- "version": "1.12.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.12.2.tgz",
- "integrity": "sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": {
- "version": "1.12.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.12.2.tgz",
- "integrity": "sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@unrs/resolver-binding-linux-arm64-gnu": {
- "version": "1.12.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.12.2.tgz",
- "integrity": "sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@unrs/resolver-binding-linux-arm64-musl": {
- "version": "1.12.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.12.2.tgz",
- "integrity": "sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@unrs/resolver-binding-linux-loong64-gnu": {
- "version": "1.12.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.12.2.tgz",
- "integrity": "sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@unrs/resolver-binding-linux-loong64-musl": {
- "version": "1.12.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.12.2.tgz",
- "integrity": "sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": {
- "version": "1.12.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.12.2.tgz",
- "integrity": "sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": {
- "version": "1.12.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.12.2.tgz",
- "integrity": "sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@unrs/resolver-binding-linux-riscv64-musl": {
- "version": "1.12.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.12.2.tgz",
- "integrity": "sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@unrs/resolver-binding-linux-s390x-gnu": {
- "version": "1.12.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.12.2.tgz",
- "integrity": "sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@unrs/resolver-binding-linux-x64-gnu": {
- "version": "1.12.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.12.2.tgz",
- "integrity": "sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@unrs/resolver-binding-linux-x64-musl": {
- "version": "1.12.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.12.2.tgz",
- "integrity": "sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@unrs/resolver-binding-openharmony-arm64": {
- "version": "1.12.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.12.2.tgz",
- "integrity": "sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openharmony"
- ]
- },
- "node_modules/@unrs/resolver-binding-wasm32-wasi": {
- "version": "1.12.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.12.2.tgz",
- "integrity": "sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==",
- "cpu": [
- "wasm32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@emnapi/core": "1.10.0",
- "@emnapi/runtime": "1.10.0",
- "@napi-rs/wasm-runtime": "^1.1.4"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@unrs/resolver-binding-win32-arm64-msvc": {
- "version": "1.12.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz",
- "integrity": "sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@unrs/resolver-binding-win32-ia32-msvc": {
- "version": "1.12.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.12.2.tgz",
- "integrity": "sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@unrs/resolver-binding-win32-x64-msvc": {
- "version": "1.12.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.12.2.tgz",
- "integrity": "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@webassemblyjs/ast": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz",
- "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@webassemblyjs/helper-numbers": "1.13.2",
- "@webassemblyjs/helper-wasm-bytecode": "1.13.2"
- }
- },
- "node_modules/@webassemblyjs/floating-point-hex-parser": {
- "version": "1.13.2",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz",
- "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@webassemblyjs/helper-api-error": {
- "version": "1.13.2",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz",
- "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@webassemblyjs/helper-buffer": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz",
- "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@webassemblyjs/helper-numbers": {
- "version": "1.13.2",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz",
- "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@webassemblyjs/floating-point-hex-parser": "1.13.2",
- "@webassemblyjs/helper-api-error": "1.13.2",
- "@xtuc/long": "4.2.2"
- }
- },
- "node_modules/@webassemblyjs/helper-wasm-bytecode": {
- "version": "1.13.2",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz",
- "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@webassemblyjs/helper-wasm-section": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz",
- "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@webassemblyjs/ast": "1.14.1",
- "@webassemblyjs/helper-buffer": "1.14.1",
- "@webassemblyjs/helper-wasm-bytecode": "1.13.2",
- "@webassemblyjs/wasm-gen": "1.14.1"
- }
- },
- "node_modules/@webassemblyjs/ieee754": {
- "version": "1.13.2",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz",
- "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@xtuc/ieee754": "^1.2.0"
- }
- },
- "node_modules/@webassemblyjs/leb128": {
- "version": "1.13.2",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz",
- "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@xtuc/long": "4.2.2"
- }
- },
- "node_modules/@webassemblyjs/utf8": {
- "version": "1.13.2",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz",
- "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@webassemblyjs/wasm-edit": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz",
- "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@webassemblyjs/ast": "1.14.1",
- "@webassemblyjs/helper-buffer": "1.14.1",
- "@webassemblyjs/helper-wasm-bytecode": "1.13.2",
- "@webassemblyjs/helper-wasm-section": "1.14.1",
- "@webassemblyjs/wasm-gen": "1.14.1",
- "@webassemblyjs/wasm-opt": "1.14.1",
- "@webassemblyjs/wasm-parser": "1.14.1",
- "@webassemblyjs/wast-printer": "1.14.1"
- }
- },
- "node_modules/@webassemblyjs/wasm-gen": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz",
- "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@webassemblyjs/ast": "1.14.1",
- "@webassemblyjs/helper-wasm-bytecode": "1.13.2",
- "@webassemblyjs/ieee754": "1.13.2",
- "@webassemblyjs/leb128": "1.13.2",
- "@webassemblyjs/utf8": "1.13.2"
- }
- },
- "node_modules/@webassemblyjs/wasm-opt": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz",
- "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@webassemblyjs/ast": "1.14.1",
- "@webassemblyjs/helper-buffer": "1.14.1",
- "@webassemblyjs/wasm-gen": "1.14.1",
- "@webassemblyjs/wasm-parser": "1.14.1"
- }
- },
- "node_modules/@webassemblyjs/wasm-parser": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz",
- "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@webassemblyjs/ast": "1.14.1",
- "@webassemblyjs/helper-api-error": "1.13.2",
- "@webassemblyjs/helper-wasm-bytecode": "1.13.2",
- "@webassemblyjs/ieee754": "1.13.2",
- "@webassemblyjs/leb128": "1.13.2",
- "@webassemblyjs/utf8": "1.13.2"
- }
- },
- "node_modules/@webassemblyjs/wast-printer": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz",
- "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@webassemblyjs/ast": "1.14.1",
- "@xtuc/long": "4.2.2"
- }
- },
- "node_modules/@xtuc/ieee754": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
- "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
- "dev": true,
- "license": "BSD-3-Clause"
- },
- "node_modules/@xtuc/long": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
- "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
- "dev": true,
- "license": "Apache-2.0"
- },
- "node_modules/acorn": {
- "version": "8.17.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz",
- "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "acorn": "bin/acorn"
- },
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/acorn-import-phases": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz",
- "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10.13.0"
- },
- "peerDependencies": {
- "acorn": "^8.14.0"
- }
- },
- "node_modules/ajv": {
- "version": "8.20.0",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
- "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fast-deep-equal": "^3.1.3",
- "fast-uri": "^3.0.1",
- "json-schema-traverse": "^1.0.0",
- "require-from-string": "^2.0.2"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
- }
- },
- "node_modules/ajv-formats": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
- "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ajv": "^8.0.0"
- },
- "peerDependencies": {
- "ajv": "^8.0.0"
- },
- "peerDependenciesMeta": {
- "ajv": {
- "optional": true
- }
- }
- },
- "node_modules/ajv-keywords": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
- "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fast-deep-equal": "^3.1.3"
- },
- "peerDependencies": {
- "ajv": "^8.8.2"
- }
- },
- "node_modules/ansi-escapes": {
- "version": "4.3.2",
- "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
- "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "type-fest": "^0.21.3"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "license": "MIT",
- "dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/anymatch": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
- "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "normalize-path": "^3.0.0",
- "picomatch": "^2.0.4"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/anymatch/node_modules/picomatch": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
- "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/babel-jest": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.4.1.tgz",
- "integrity": "sha512-fATAbM8piYxkiXQp3RBXmZHxZVNJZAVXXfyeyCN2Tida3+qJ8ea9UxhiJ2y4fLO90ZImKt6k9FlcH2+rLkJGhw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/transform": "30.4.1",
- "@types/babel__core": "^7.20.5",
- "babel-plugin-istanbul": "^7.0.1",
- "babel-preset-jest": "30.4.0",
- "chalk": "^4.1.2",
- "graceful-fs": "^4.2.11",
- "slash": "^3.0.0"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.11.0 || ^8.0.0-0"
- }
- },
- "node_modules/babel-loader": {
- "version": "10.1.1",
- "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-10.1.1.tgz",
- "integrity": "sha512-JwKSzk2kjIe7mgPK+/lyZ2QAaJcpahNAdM+hgR2HI8D0OJVkdj8Rl6J3kaLYki9pwF7P2iWnD8qVv80Lq1ABtg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "find-up": "^5.0.0"
- },
- "engines": {
- "node": "^18.20.0 || ^20.10.0 || >=22.0.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.12.0 || ^8.0.0-beta.1",
- "@rspack/core": "^1.0.0 || ^2.0.0-0",
- "webpack": ">=5.61.0"
- },
- "peerDependenciesMeta": {
- "@rspack/core": {
- "optional": true
- },
- "webpack": {
- "optional": true
- }
- }
- },
- "node_modules/babel-plugin-formatjs": {
- "version": "11.3.14",
- "resolved": "https://registry.npmjs.org/babel-plugin-formatjs/-/babel-plugin-formatjs-11.3.14.tgz",
- "integrity": "sha512-tyM0QGOCUi8PgfQVYLo7BTIkaS4pXFh9bw4p1FU5J2P9kFyxC7TLhWoIMLPZubzcG4F9W++eJ0nONf0r6aH68g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/core": "^7.28.5",
- "@babel/helper-plugin-utils": "^7.27.1",
- "@babel/plugin-syntax-jsx": "^7.27.1",
- "@babel/traverse": "^7.28.5",
- "@babel/types": "^7.29.0",
- "@formatjs/icu-messageformat-parser": "3.5.12",
- "@formatjs/ts-transformer": "4.4.14",
- "@types/babel__core": "^7.20.5",
- "@types/babel__helper-plugin-utils": "^7.10.3",
- "@types/babel__traverse": "^7.28.0"
- }
- },
- "node_modules/babel-plugin-formatjs/node_modules/@babel/code-frame": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
- "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-validator-identifier": "^7.29.7",
- "js-tokens": "^4.0.0",
- "picocolors": "^1.1.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/babel-plugin-formatjs/node_modules/@babel/generator": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz",
- "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.29.7",
- "@babel/types": "^7.29.7",
- "@jridgewell/gen-mapping": "^0.3.12",
- "@jridgewell/trace-mapping": "^0.3.28",
- "jsesc": "^3.0.2"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/babel-plugin-formatjs/node_modules/@babel/helper-globals": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
- "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/babel-plugin-formatjs/node_modules/@babel/helper-plugin-utils": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
- "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/babel-plugin-formatjs/node_modules/@babel/helper-string-parser": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
- "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/babel-plugin-formatjs/node_modules/@babel/helper-validator-identifier": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
- "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/babel-plugin-formatjs/node_modules/@babel/parser": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
- "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.29.7"
- },
- "bin": {
- "parser": "bin/babel-parser.js"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/babel-plugin-formatjs/node_modules/@babel/plugin-syntax-jsx": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz",
- "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/babel-plugin-formatjs/node_modules/@babel/template": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
- "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.29.7",
- "@babel/parser": "^7.29.7",
- "@babel/types": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/babel-plugin-formatjs/node_modules/@babel/traverse": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz",
- "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.29.7",
- "@babel/generator": "^7.29.7",
- "@babel/helper-globals": "^7.29.7",
- "@babel/parser": "^7.29.7",
- "@babel/template": "^7.29.7",
- "@babel/types": "^7.29.7",
- "debug": "^4.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/babel-plugin-formatjs/node_modules/@babel/types": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
- "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-string-parser": "^7.29.7",
- "@babel/helper-validator-identifier": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/babel-plugin-formatjs/node_modules/js-tokens": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/babel-plugin-istanbul": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz",
- "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==",
- "dev": true,
- "license": "BSD-3-Clause",
- "workspaces": [
- "test/babel-8"
- ],
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.0.0",
- "@istanbuljs/load-nyc-config": "^1.0.0",
- "@istanbuljs/schema": "^0.1.3",
- "istanbul-lib-instrument": "^6.0.2",
- "test-exclude": "^6.0.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/babel-plugin-istanbul/node_modules/@babel/helper-plugin-utils": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
- "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/babel-plugin-jest-hoist": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.4.0.tgz",
- "integrity": "sha512-9EdtWM/sSfXLOGLwSn+GS6pIXyBnL07/8gyJlwFXjWy4DxMOyItqyUT29d4lQiS380EZwYlX7/At4PgBS+m2aA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/babel__core": "^7.20.5"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/babel-plugin-polyfill-corejs3": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-1.0.0.tgz",
- "integrity": "sha512-yIkslVjbmml2Xjb6XhFW7lISXHsqk6cesxTdDsXoMom4Lnb99DbD3OQbSOoM5Z+ASh8YXYaLAsRQrU2Jeh3Qig==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-define-polyfill-provider": "^1.0.0",
- "core-js-compat": "^3.48.0"
- },
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.4.0 || ^8.0.0"
- }
- },
- "node_modules/babel-preset-current-node-syntax": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz",
- "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/plugin-syntax-async-generators": "^7.8.4",
- "@babel/plugin-syntax-bigint": "^7.8.3",
- "@babel/plugin-syntax-class-properties": "^7.12.13",
- "@babel/plugin-syntax-class-static-block": "^7.14.5",
- "@babel/plugin-syntax-import-attributes": "^7.24.7",
- "@babel/plugin-syntax-import-meta": "^7.10.4",
- "@babel/plugin-syntax-json-strings": "^7.8.3",
- "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4",
- "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
- "@babel/plugin-syntax-numeric-separator": "^7.10.4",
- "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
- "@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
- "@babel/plugin-syntax-optional-chaining": "^7.8.3",
- "@babel/plugin-syntax-private-property-in-object": "^7.14.5",
- "@babel/plugin-syntax-top-level-await": "^7.14.5"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0 || ^8.0.0-0"
- }
- },
- "node_modules/babel-preset-jest": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.4.0.tgz",
- "integrity": "sha512-lBY4jxsNmCnSiu7kquw8ZC9F4+XLMOKypT3RnNHPvU2Kpd4W0xaPuLr5ZkRyOsvLYAY4yaW1ZwTW4xB7NIiZzg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "babel-plugin-jest-hoist": "30.4.0",
- "babel-preset-current-node-syntax": "^1.2.0"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.11.0 || ^8.0.0-beta.1"
- }
- },
- "node_modules/balanced-match": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
- "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "18 || 20 || >=22"
- }
- },
- "node_modules/base64-js": {
- "version": "1.5.1",
- "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
- "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
- "node_modules/baseline-browser-mapping": {
- "version": "2.10.41",
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.41.tgz",
- "integrity": "sha512-WwS7MHhqGHHlaVsqRZnhvCEMS0owDX+SxRlve7JkuH7My1Ara3ZriTmCQupPfYjxMZ8I/tgxtJYr2t7taHaH4A==",
- "dev": true,
- "license": "Apache-2.0",
- "bin": {
- "baseline-browser-mapping": "dist/cli.cjs"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/binary-extensions": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
- "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/boolbase": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
- "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/brace-expansion": {
- "version": "5.0.7",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz",
- "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^4.0.2"
- },
- "engines": {
- "node": "18 || 20 || >=22"
- }
- },
- "node_modules/braces": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
- "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fill-range": "^7.1.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/browserslist": {
- "version": "4.28.4",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz",
- "integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/browserslist"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "baseline-browser-mapping": "^2.10.38",
- "caniuse-lite": "^1.0.30001799",
- "electron-to-chromium": "^1.5.376",
- "node-releases": "^2.0.48",
- "update-browserslist-db": "^1.2.3"
- },
- "bin": {
- "browserslist": "cli.js"
- },
- "engines": {
- "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
- }
- },
- "node_modules/bser": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
- "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "node-int64": "^0.4.0"
- }
- },
- "node_modules/buffer": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
- "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "base64-js": "^1.3.1",
- "ieee754": "^1.2.1"
- }
- },
- "node_modules/buffer-from": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
- "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/call-bind": {
- "version": "1.0.9",
- "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz",
- "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.2",
- "es-define-property": "^1.0.1",
- "get-intrinsic": "^1.3.0",
- "set-function-length": "^1.2.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/call-bind-apply-helpers": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
- "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "function-bind": "^1.1.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/call-bound": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
- "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.2",
- "get-intrinsic": "^1.3.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/callsites": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
- "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/camelcase": {
- "version": "5.3.1",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
- "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/caniuse-api": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-4.0.0.tgz",
- "integrity": "sha512-B0hQ1OLyJuHTQSOWXvwibWqM6DCoqJdvBA6X1S/53bd4XU7LJ1yurIPlrsouol3mw1jh9pGI4ivubSpmJeIqCA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "browserslist": "^4.0.0",
- "caniuse-lite": "^1.0.0"
- }
- },
- "node_modules/caniuse-lite": {
- "version": "1.0.30001800",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001800.tgz",
- "integrity": "sha512-MMHtuAz9Ys840zAY5F4k6fV5GaivZ9sPk+nz0mY+GYVzRBnYkN0mpqkSR92oWRQ19yQWo4HvBV/FnC16AJX8MA==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "CC-BY-4.0"
- },
- "node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
- }
- },
- "node_modules/char-regex": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
- "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/chokidar": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
- "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "anymatch": "~3.1.2",
- "braces": "~3.0.2",
- "glob-parent": "~5.1.2",
- "is-binary-path": "~2.1.0",
- "is-glob": "~4.0.1",
- "normalize-path": "~3.0.0",
- "readdirp": "~3.6.0"
- },
- "engines": {
- "node": ">= 8.10.0"
- },
- "funding": {
- "url": "https://paulmillr.com/funding/"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.2"
- }
- },
- "node_modules/chokidar/node_modules/glob-parent": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
- "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/chrome-trace-event": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz",
- "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.0"
- }
- },
- "node_modules/ci-info": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz",
- "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/sibiraj-s"
- }
- ],
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/cjs-module-lexer": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz",
- "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/cliui": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
- "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
- "license": "ISC",
- "dependencies": {
- "string-width": "^4.2.0",
- "strip-ansi": "^6.0.1",
- "wrap-ansi": "^7.0.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/clone-deep": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
- "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-plain-object": "^2.0.4",
- "kind-of": "^6.0.2",
- "shallow-clone": "^3.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/co": {
- "version": "4.6.0",
- "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
- "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "iojs": ">= 1.0.0",
- "node": ">= 0.12.0"
- }
- },
- "node_modules/collect-v8-coverage": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz",
- "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "license": "MIT",
- "dependencies": {
- "color-name": "~1.1.4"
- },
- "engines": {
- "node": ">=7.0.0"
- }
- },
- "node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "license": "MIT"
- },
- "node_modules/commander": {
- "version": "11.1.0",
- "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
- "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=16"
- }
- },
- "node_modules/concat-map": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/convert-source-map": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
- "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/copy-webpack-plugin": {
- "version": "14.0.0",
- "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-14.0.0.tgz",
- "integrity": "sha512-3JLW90aBGeaTLpM7mYQKpnVdgsUZRExY55giiZgLuX/xTQRUs1dOCwbBnWnvY6Q6rfZoXMNwzOQJCSZPppfqXA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "glob-parent": "^6.0.1",
- "normalize-path": "^3.0.0",
- "schema-utils": "^4.2.0",
- "serialize-javascript": "^7.0.3",
- "tinyglobby": "^0.2.12"
- },
- "engines": {
- "node": ">= 20.9.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
- },
- "peerDependencies": {
- "webpack": "^5.1.0"
- }
- },
- "node_modules/core-js-compat": {
- "version": "3.49.0",
- "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz",
- "integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "browserslist": "^4.28.1"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/core-js"
- }
- },
- "node_modules/cross-spawn": {
- "version": "7.0.6",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
- "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "path-key": "^3.1.0",
- "shebang-command": "^2.0.0",
- "which": "^2.0.1"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/css-select": {
- "version": "5.2.2",
- "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
- "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "boolbase": "^1.0.0",
- "css-what": "^6.1.0",
- "domhandler": "^5.0.2",
- "domutils": "^3.0.1",
- "nth-check": "^2.0.1"
- },
- "funding": {
- "url": "https://github.com/sponsors/fb55"
- }
- },
- "node_modules/css-tree": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz",
- "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "mdn-data": "2.27.1",
- "source-map-js": "^1.2.1"
- },
- "engines": {
- "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
- }
- },
- "node_modules/css-what": {
- "version": "6.2.2",
- "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz",
- "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==",
- "dev": true,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">= 6"
- },
- "funding": {
- "url": "https://github.com/sponsors/fb55"
- }
- },
- "node_modules/cssesc": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
- "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "cssesc": "bin/cssesc"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/cssnano": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-8.0.2.tgz",
- "integrity": "sha512-K+a76gA1v0/CsYgcsE95HGGyIuPKxpQSetwSwz4nHEM8fFXqSkzq2JzEXFL8v5+CCjxzVVVhPcTK3Oo8SaF/xA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "cssnano-preset-default": "^8.0.2",
- "lilconfig": "^3.1.3"
- },
- "engines": {
- "node": "^22.11.0 || ^24.11.0 || >=26.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/cssnano"
- },
- "peerDependencies": {
- "postcss": "^8.5.15"
- }
- },
- "node_modules/cssnano-preset-default": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-8.0.2.tgz",
- "integrity": "sha512-+jQAqIKCqMmBjZs7741XkilU93ITZ/EW8gjAkMmujdCzfDkfjrDBv2VqkSu29Fzeig/0rZ3S9IAwfPLlmXEUfQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "browserslist": "^4.28.2",
- "cssnano-utils": "^6.0.1",
- "postcss-calc": "^10.1.1",
- "postcss-colormin": "^8.0.1",
- "postcss-convert-values": "^8.0.1",
- "postcss-discard-comments": "^8.0.1",
- "postcss-discard-duplicates": "^8.0.1",
- "postcss-discard-empty": "^8.0.1",
- "postcss-discard-overridden": "^8.0.1",
- "postcss-merge-longhand": "^8.0.1",
- "postcss-merge-rules": "^8.0.1",
- "postcss-minify-font-values": "^8.0.1",
- "postcss-minify-gradients": "^8.0.1",
- "postcss-minify-params": "^8.0.1",
- "postcss-minify-selectors": "^8.0.2",
- "postcss-normalize-charset": "^8.0.1",
- "postcss-normalize-display-values": "^8.0.1",
- "postcss-normalize-positions": "^8.0.1",
- "postcss-normalize-repeat-style": "^8.0.1",
- "postcss-normalize-string": "^8.0.1",
- "postcss-normalize-timing-functions": "^8.0.1",
- "postcss-normalize-unicode": "^8.0.1",
- "postcss-normalize-url": "^8.0.1",
- "postcss-normalize-whitespace": "^8.0.1",
- "postcss-ordered-values": "^8.0.1",
- "postcss-reduce-initial": "^8.0.1",
- "postcss-reduce-transforms": "^8.0.1",
- "postcss-svgo": "^8.0.1",
- "postcss-unique-selectors": "^8.0.1"
- },
- "engines": {
- "node": "^22.11.0 || ^24.11.0 || >=26.0"
- },
- "peerDependencies": {
- "postcss": "^8.5.15"
- }
- },
- "node_modules/cssnano-utils": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-6.0.1.tgz",
- "integrity": "sha512-zk65GIxA8tCjqVk7nTm1mE+ZKxtnxAvU5JSUaBLXbAr3ZF7IOvz3fbPOnEDvZKhnS7GOIitXTS5BgehLzNoc8Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^22.11.0 || ^24.11.0 || >=26.0"
- },
- "peerDependencies": {
- "postcss": "^8.5.15"
- }
- },
- "node_modules/csso": {
- "version": "5.0.5",
- "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz",
- "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "css-tree": "~2.2.0"
- },
- "engines": {
- "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0",
- "npm": ">=7.0.0"
- }
- },
- "node_modules/csso/node_modules/css-tree": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz",
- "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "mdn-data": "2.0.28",
- "source-map-js": "^1.0.1"
- },
- "engines": {
- "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0",
- "npm": ">=7.0.0"
- }
- },
- "node_modules/csso/node_modules/mdn-data": {
- "version": "2.0.28",
- "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz",
- "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==",
- "dev": true,
- "license": "CC0-1.0"
- },
- "node_modules/csstype": {
- "version": "3.2.3",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
- "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
- "license": "MIT"
- },
- "node_modules/debug": {
- "version": "4.4.3",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
- "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/decimal.js": {
- "version": "10.6.0",
- "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
- "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
- "license": "MIT"
- },
- "node_modules/dedent": {
- "version": "1.7.2",
- "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz",
- "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "babel-plugin-macros": "^3.1.0"
- },
- "peerDependenciesMeta": {
- "babel-plugin-macros": {
- "optional": true
- }
- }
- },
- "node_modules/deepmerge": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
- "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/define-data-property": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
- "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "es-define-property": "^1.0.0",
- "es-errors": "^1.3.0",
- "gopd": "^1.0.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/dependency-graph": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-1.0.0.tgz",
- "integrity": "sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/detect-newline": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
- "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/dom-serializer": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
- "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "domelementtype": "^2.3.0",
- "domhandler": "^5.0.2",
- "entities": "^4.2.0"
- },
- "funding": {
- "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
- }
- },
- "node_modules/domelementtype": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
- "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fb55"
- }
- ],
- "license": "BSD-2-Clause"
- },
- "node_modules/domhandler": {
- "version": "5.0.3",
- "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
- "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "domelementtype": "^2.3.0"
- },
- "engines": {
- "node": ">= 4"
- },
- "funding": {
- "url": "https://github.com/fb55/domhandler?sponsor=1"
- }
- },
- "node_modules/domutils": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
- "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "dom-serializer": "^2.0.0",
- "domelementtype": "^2.3.0",
- "domhandler": "^5.0.3"
- },
- "funding": {
- "url": "https://github.com/fb55/domutils?sponsor=1"
- }
- },
- "node_modules/dunder-proto": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
- "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.1",
- "es-errors": "^1.3.0",
- "gopd": "^1.2.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/eastasianwidth": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
- "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/ebml-block": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/ebml-block/-/ebml-block-1.1.2.tgz",
- "integrity": "sha512-HgNlIsRFP6D9VKU5atCeHRJY7XkJP8bOe8yEhd8NB7B3b4++VWTyauz6g650iiPmLfPLGlVpoJmGSgMfXDYusg==",
- "license": "MIT"
- },
- "node_modules/electron-to-chromium": {
- "version": "1.5.387",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.387.tgz",
- "integrity": "sha512-TaxwufTFDufvPEoXdhwVrA3UdFWBeWGkYoJ1K8ldF1xe6gKfth6iRNS5lTQ5JPNOHdGQm8PT1QYKUqFLCiUefQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/emittery": {
- "version": "0.13.1",
- "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz",
- "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sindresorhus/emittery?sponsor=1"
- }
- },
- "node_modules/emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "license": "MIT"
- },
- "node_modules/empathic": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.1.tgz",
- "integrity": "sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/enhanced-resolve": {
- "version": "5.24.1",
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.1.tgz",
- "integrity": "sha512-7DdUaTjmNwMcH2gLr1qycesKII3BK4RLy/mdAb7x10Lq7bR4aNKHt1BR1ZALSv0rPM/hF5wYF0PhGop/rJm8vw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "graceful-fs": "^4.2.4",
- "tapable": "^2.3.3"
- },
- "engines": {
- "node": ">=10.13.0"
- }
- },
- "node_modules/entities": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
- "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
- "dev": true,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=0.12"
- },
- "funding": {
- "url": "https://github.com/fb55/entities?sponsor=1"
- }
- },
- "node_modules/envinfo": {
- "version": "7.21.0",
- "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.21.0.tgz",
- "integrity": "sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "envinfo": "dist/cli.js"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/error-ex": {
- "version": "1.3.4",
- "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
- "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-arrayish": "^0.2.1"
- }
- },
- "node_modules/es-define-property": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
- "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-errors": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
- "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-module-lexer": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.0.tgz",
- "integrity": "sha512-KLdwQm2NvGLDkQDCGvmiQrhkd0JbMzXthwQAUgWjQuQdBLFa3eiBP5arXZyA+f8x+x7OXgud6bq2rxjGtHV2tw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/es-object-atoms": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
- "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/escalade": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
- "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/escape-string-regexp": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
- "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/eslint-scope": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
- "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "esrecurse": "^4.3.0",
- "estraverse": "^4.1.1"
- },
- "engines": {
- "node": ">=8.0.0"
- }
- },
- "node_modules/esprima": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
- "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
- "dev": true,
- "license": "BSD-2-Clause",
- "bin": {
- "esparse": "bin/esparse.js",
- "esvalidate": "bin/esvalidate.js"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/esrecurse": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
- "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "estraverse": "^5.2.0"
- },
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/esrecurse/node_modules/estraverse": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
- "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
- "dev": true,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/estraverse": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
- "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
- "dev": true,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/esutils": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
- "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
- "dev": true,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/events": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
- "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
- "license": "MIT",
- "engines": {
- "node": ">=0.8.x"
- }
- },
- "node_modules/execa": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
- "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "cross-spawn": "^7.0.3",
- "get-stream": "^6.0.0",
- "human-signals": "^2.1.0",
- "is-stream": "^2.0.0",
- "merge-stream": "^2.0.0",
- "npm-run-path": "^4.0.1",
- "onetime": "^5.1.2",
- "signal-exit": "^3.0.3",
- "strip-final-newline": "^2.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sindresorhus/execa?sponsor=1"
- }
- },
- "node_modules/exit-x": {
- "version": "0.2.2",
- "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz",
- "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/expect": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/expect/-/expect-30.4.1.tgz",
- "integrity": "sha512-PMARsyh/JtqC20HoGqlFcIlQAyqUtW4PlI1rup1uhYJtKuwAjbvWi3GQMAn+STdHum/dk8xrKfUM1+5SAwpolA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/expect-utils": "30.4.1",
- "@jest/get-type": "30.1.0",
- "jest-matcher-utils": "30.4.1",
- "jest-message-util": "30.4.1",
- "jest-mock": "30.4.1",
- "jest-util": "30.4.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fast-json-stable-stringify": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
- "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fast-uri": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz",
- "integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fastify"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fastify"
- }
- ],
- "license": "BSD-3-Clause"
- },
- "node_modules/faye-websocket": {
- "version": "0.11.4",
- "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz",
- "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==",
- "license": "Apache-2.0",
- "dependencies": {
- "websocket-driver": ">=0.5.1"
- },
- "engines": {
- "node": ">=0.8.0"
- }
- },
- "node_modules/fb-watchman": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
- "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "bser": "2.1.1"
- }
- },
- "node_modules/fdir": {
- "version": "6.5.0",
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
- "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12.0.0"
- },
- "peerDependencies": {
- "picomatch": "^3 || ^4"
- },
- "peerDependenciesMeta": {
- "picomatch": {
- "optional": true
- }
- }
- },
- "node_modules/fill-range": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
- "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "to-regex-range": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/find-up": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
- "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "locate-path": "^6.0.0",
- "path-exists": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/firebase": {
- "version": "12.15.0",
- "resolved": "https://registry.npmjs.org/firebase/-/firebase-12.15.0.tgz",
- "integrity": "sha512-p0YTLcRSTiBXMx9sGr4ZNSfLjc/RVBEw4C/TXjVMtw65+6E1Pbm47UY3F4/AqRoDobEcNX3gsbPGy7jPjxbgSQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/ai": "2.13.1",
- "@firebase/analytics": "0.10.22",
- "@firebase/analytics-compat": "0.2.28",
- "@firebase/app": "0.15.0",
- "@firebase/app-check": "0.12.0",
- "@firebase/app-check-compat": "0.4.5",
- "@firebase/app-compat": "0.5.14",
- "@firebase/app-types": "0.9.5",
- "@firebase/auth": "1.13.3",
- "@firebase/auth-compat": "0.6.8",
- "@firebase/data-connect": "0.7.1",
- "@firebase/database": "1.1.3",
- "@firebase/database-compat": "2.1.4",
- "@firebase/firestore": "4.16.0",
- "@firebase/firestore-compat": "0.4.11",
- "@firebase/functions": "0.13.5",
- "@firebase/functions-compat": "0.4.5",
- "@firebase/installations": "0.6.22",
- "@firebase/installations-compat": "0.2.22",
- "@firebase/messaging": "0.13.0",
- "@firebase/messaging-compat": "0.2.27",
- "@firebase/performance": "0.7.12",
- "@firebase/performance-compat": "0.2.25",
- "@firebase/remote-config": "0.8.5",
- "@firebase/remote-config-compat": "0.2.26",
- "@firebase/storage": "0.14.3",
- "@firebase/storage-compat": "0.4.3",
- "@firebase/util": "1.15.1"
- }
- },
- "node_modules/firebase/node_modules/@firebase/auth": {
- "version": "1.13.3",
- "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.13.3.tgz",
- "integrity": "sha512-bqiq4uubDN2YyQkdvSWPQeJyXAv2O76ImF41En9b6UhV5JuBVYDoHYrrrE3NzIuGkpFMKagfhMRP4Vz6t+yQSQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/component": "0.7.3",
- "@firebase/logger": "0.5.1",
- "@firebase/util": "1.15.1",
- "tslib": "^2.1.0"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "@firebase/app": "0.x",
- "@react-native-async-storage/async-storage": "^2.2.0 || ^3.0.0"
- },
- "peerDependenciesMeta": {
- "@react-native-async-storage/async-storage": {
- "optional": true
- }
- }
- },
- "node_modules/flat": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
- "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
- "dev": true,
- "license": "BSD-3-Clause",
- "bin": {
- "flat": "cli.js"
- }
- },
- "node_modules/foreground-child": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
- "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "cross-spawn": "^7.0.6",
- "signal-exit": "^4.0.1"
- },
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/foreground-child/node_modules/signal-exit": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
- "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/fs-extra": {
- "version": "11.3.6",
- "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.6.tgz",
- "integrity": "sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "graceful-fs": "^4.2.0",
- "jsonfile": "^6.0.1",
- "universalify": "^2.0.0"
- },
- "engines": {
- "node": ">=14.14"
- }
- },
- "node_modules/fs.realpath": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
- "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/fsevents": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
- "node_modules/function-bind": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
- "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/gensync": {
- "version": "1.0.0-beta.2",
- "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
- "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/get-caller-file": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
- "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
- "license": "ISC",
- "engines": {
- "node": "6.* || 8.* || >= 10.*"
- }
- },
- "node_modules/get-intrinsic": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
- "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.2",
- "es-define-property": "^1.0.1",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.1.1",
- "function-bind": "^1.1.2",
- "get-proto": "^1.0.1",
- "gopd": "^1.2.0",
- "has-symbols": "^1.1.0",
- "hasown": "^2.0.2",
- "math-intrinsics": "^1.1.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/get-package-type": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
- "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8.0.0"
- }
- },
- "node_modules/get-proto": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
- "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "dunder-proto": "^1.0.1",
- "es-object-atoms": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/get-stream": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
- "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/glob": {
- "version": "13.0.6",
- "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz",
- "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "minimatch": "^10.2.2",
- "minipass": "^7.1.3",
- "path-scurry": "^2.0.2"
- },
- "engines": {
- "node": "18 || 20 || >=22"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/glob-parent": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
- "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.3"
- },
- "engines": {
- "node": ">=10.13.0"
- }
- },
- "node_modules/gopd": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
- "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/graceful-fs": {
- "version": "4.2.11",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
- "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/has-property-descriptors": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
- "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "es-define-property": "^1.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/has-symbols": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
- "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/hasown": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
- "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "function-bind": "^1.1.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/hoist-non-react-statics": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
- "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
- "license": "BSD-3-Clause",
- "dependencies": {
- "react-is": "^16.7.0"
- }
- },
- "node_modules/html-escaper": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
- "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/http-parser-js": {
- "version": "0.5.10",
- "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz",
- "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==",
- "license": "MIT"
- },
- "node_modules/human-signals": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
- "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=10.17.0"
- }
- },
- "node_modules/idb": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz",
- "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==",
- "license": "ISC"
- },
- "node_modules/ieee754": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
- "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "BSD-3-Clause"
- },
- "node_modules/import-local": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz",
- "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "pkg-dir": "^4.2.0",
- "resolve-cwd": "^3.0.0"
- },
- "bin": {
- "import-local-fixture": "fixtures/cli.js"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/import-meta-resolve": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz",
- "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
- }
- },
- "node_modules/imurmurhash": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
- "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.8.19"
- }
- },
- "node_modules/inflight": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
- "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
- "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "once": "^1.3.0",
- "wrappy": "1"
- }
- },
- "node_modules/inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/int64-buffer": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/int64-buffer/-/int64-buffer-1.1.0.tgz",
- "integrity": "sha512-94smTCQOvigN4d/2R/YDjz8YVG0Sufvv2aAh8P5m42gwhCsDAJqnbNOrxJsrADuAFAA69Q/ptGzxvNcNuIJcvw==",
- "license": "MIT"
- },
- "node_modules/interpret": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz",
- "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10.13.0"
- }
- },
- "node_modules/intl-messageformat": {
- "version": "10.7.18",
- "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.18.tgz",
- "integrity": "sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g==",
- "license": "BSD-3-Clause",
- "dependencies": {
- "@formatjs/ecma402-abstract": "2.3.6",
- "@formatjs/fast-memoize": "2.2.7",
- "@formatjs/icu-messageformat-parser": "2.11.4",
- "tslib": "^2.8.0"
- }
- },
- "node_modules/intl-messageformat/node_modules/@formatjs/icu-messageformat-parser": {
- "version": "2.11.4",
- "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.11.4.tgz",
- "integrity": "sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw==",
- "license": "MIT",
- "dependencies": {
- "@formatjs/ecma402-abstract": "2.3.6",
- "@formatjs/icu-skeleton-parser": "1.8.16",
- "tslib": "^2.8.0"
- }
- },
- "node_modules/intl-messageformat/node_modules/@formatjs/icu-skeleton-parser": {
- "version": "1.8.16",
- "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.16.tgz",
- "integrity": "sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ==",
- "license": "MIT",
- "dependencies": {
- "@formatjs/ecma402-abstract": "2.3.6",
- "tslib": "^2.8.0"
- }
- },
- "node_modules/is-arrayish": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
- "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/is-binary-path": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
- "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "binary-extensions": "^2.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/is-core-module": {
- "version": "2.16.2",
- "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz",
- "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "hasown": "^2.0.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-extglob": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
- "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-fullwidth-code-point": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/is-generator-fn": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz",
- "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/is-glob": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
- "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-extglob": "^2.1.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-number": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
- "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.12.0"
- }
- },
- "node_modules/is-plain-object": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
- "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "isobject": "^3.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-stream": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
- "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/isarray": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
- "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/isexe": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
- "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/isobject": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
- "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/istanbul-lib-coverage": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
- "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/istanbul-lib-instrument": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz",
- "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@babel/core": "^7.23.9",
- "@babel/parser": "^7.23.9",
- "@istanbuljs/schema": "^0.1.3",
- "istanbul-lib-coverage": "^3.2.0",
- "semver": "^7.5.4"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/istanbul-lib-instrument/node_modules/@babel/helper-string-parser": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
- "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/istanbul-lib-instrument/node_modules/@babel/helper-validator-identifier": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
- "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/istanbul-lib-instrument/node_modules/@babel/parser": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
- "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.29.7"
- },
- "bin": {
- "parser": "bin/babel-parser.js"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/istanbul-lib-instrument/node_modules/@babel/types": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
- "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-string-parser": "^7.29.7",
- "@babel/helper-validator-identifier": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/istanbul-lib-report": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
- "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "istanbul-lib-coverage": "^3.0.0",
- "make-dir": "^4.0.0",
- "supports-color": "^7.1.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/istanbul-lib-source-maps": {
- "version": "5.0.6",
- "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz",
- "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@jridgewell/trace-mapping": "^0.3.23",
- "debug": "^4.1.1",
- "istanbul-lib-coverage": "^3.0.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/istanbul-reports": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
- "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "html-escaper": "^2.0.0",
- "istanbul-lib-report": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/jackspeak": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
- "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "@isaacs/cliui": "^8.0.2"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- },
- "optionalDependencies": {
- "@pkgjs/parseargs": "^0.11.0"
- }
- },
- "node_modules/jest": {
- "version": "30.4.2",
- "resolved": "https://registry.npmjs.org/jest/-/jest-30.4.2.tgz",
- "integrity": "sha512-Yi1jqNC/Oq0N4hBgNH/YvBpP1P57QqundgytzYqy3yqAa7NZPNjSoi4SGbRAXDMdBzNE6xBCi5U7RgfrvMEUVQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/core": "30.4.2",
- "@jest/types": "30.4.1",
- "import-local": "^3.2.0",
- "jest-cli": "30.4.2"
- },
- "bin": {
- "jest": "bin/jest.js"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- },
- "peerDependencies": {
- "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
- },
- "peerDependenciesMeta": {
- "node-notifier": {
- "optional": true
- }
- }
- },
- "node_modules/jest-changed-files": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.4.1.tgz",
- "integrity": "sha512-IuctmYrxi21iOSOaIXpJWalHyPAsVv0GeBHKDn8C1CA4W5htHn7INL+wdnL4Bo0+olEndvAFkmb++tIQJG+vvg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "execa": "^5.1.1",
- "jest-util": "30.4.1",
- "p-limit": "^3.1.0"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-circus": {
- "version": "30.4.2",
- "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.4.2.tgz",
- "integrity": "sha512-rvHH7VlY6LgbJXJTQ87GW62g1FntOtbhh0zT+v04kC+pgL6aBKyYINXxWukCpj3dcIBMw5/XUbtDS9dU9JTXeQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/environment": "30.4.1",
- "@jest/expect": "30.4.1",
- "@jest/test-result": "30.4.1",
- "@jest/types": "30.4.1",
- "@types/node": "*",
- "chalk": "^4.1.2",
- "co": "^4.6.0",
- "dedent": "^1.6.0",
- "is-generator-fn": "^2.1.0",
- "jest-each": "30.4.1",
- "jest-matcher-utils": "30.4.1",
- "jest-message-util": "30.4.1",
- "jest-runtime": "30.4.2",
- "jest-snapshot": "30.4.1",
- "jest-util": "30.4.1",
- "p-limit": "^3.1.0",
- "pretty-format": "30.4.1",
- "pure-rand": "^7.0.0",
- "slash": "^3.0.0",
- "stack-utils": "^2.0.6"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-cli": {
- "version": "30.4.2",
- "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.4.2.tgz",
- "integrity": "sha512-jfA2ocvVHMXS2QijrJ0d31ektP+d/W0T5RpcTX2Pq+3sVqHlsXVCM2+FmwpL+bdY8OfHpIg9xMxLF17Zg0U49Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/core": "30.4.2",
- "@jest/test-result": "30.4.1",
- "@jest/types": "30.4.1",
- "chalk": "^4.1.2",
- "exit-x": "^0.2.2",
- "import-local": "^3.2.0",
- "jest-config": "30.4.2",
- "jest-util": "30.4.1",
- "jest-validate": "30.4.1",
- "yargs": "^17.7.2"
- },
- "bin": {
- "jest": "bin/jest.js"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- },
- "peerDependencies": {
- "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
- },
- "peerDependenciesMeta": {
- "node-notifier": {
- "optional": true
- }
- }
- },
- "node_modules/jest-config": {
- "version": "30.4.2",
- "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.4.2.tgz",
- "integrity": "sha512-rNHAShJQqQwFNoL0hbf3BphSBOWnpOUAKvidLS/AjNVLPfoj5mSf4jQMfW3cYOs6hXeZC7nF7mDHaBnbxELOzg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/core": "^7.27.4",
- "@jest/get-type": "30.1.0",
- "@jest/pattern": "30.4.0",
- "@jest/test-sequencer": "30.4.1",
- "@jest/types": "30.4.1",
- "babel-jest": "30.4.1",
- "chalk": "^4.1.2",
- "ci-info": "^4.2.0",
- "deepmerge": "^4.3.1",
- "glob": "^10.5.0",
- "graceful-fs": "^4.2.11",
- "jest-circus": "30.4.2",
- "jest-docblock": "30.4.0",
- "jest-environment-node": "30.4.1",
- "jest-regex-util": "30.4.0",
- "jest-resolve": "30.4.1",
- "jest-runner": "30.4.2",
- "jest-util": "30.4.1",
- "jest-validate": "30.4.1",
- "parse-json": "^5.2.0",
- "pretty-format": "30.4.1",
- "slash": "^3.0.0",
- "strip-json-comments": "^3.1.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- },
- "peerDependencies": {
- "@types/node": "*",
- "esbuild-register": ">=3.4.0",
- "ts-node": ">=9.0.0"
- },
- "peerDependenciesMeta": {
- "@types/node": {
- "optional": true
- },
- "esbuild-register": {
- "optional": true
- },
- "ts-node": {
- "optional": true
- }
- }
- },
- "node_modules/jest-config/node_modules/balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/jest-config/node_modules/brace-expansion": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
- "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
- "node_modules/jest-config/node_modules/glob": {
- "version": "10.5.0",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
- "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
- "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "foreground-child": "^3.1.0",
- "jackspeak": "^3.1.2",
- "minimatch": "^9.0.4",
- "minipass": "^7.1.2",
- "package-json-from-dist": "^1.0.0",
- "path-scurry": "^1.11.1"
- },
- "bin": {
- "glob": "dist/esm/bin.mjs"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/jest-config/node_modules/lru-cache": {
- "version": "10.4.3",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
- "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/jest-config/node_modules/minimatch": {
- "version": "9.0.9",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
- "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.2"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/jest-config/node_modules/path-scurry": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
- "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "lru-cache": "^10.2.0",
- "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
- },
- "engines": {
- "node": ">=16 || 14 >=14.18"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/jest-diff": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.4.1.tgz",
- "integrity": "sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/diff-sequences": "30.4.0",
- "@jest/get-type": "30.1.0",
- "chalk": "^4.1.2",
- "pretty-format": "30.4.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-docblock": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.4.0.tgz",
- "integrity": "sha512-ZPMabUZCx5MpbZ2eBYSvZ0J8fvo3dR9oM+eeUpb3aKNQFuS2tu3Duw1TNlMoP8k3WQgKGJuhcMFvwcVuq6T7oA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "detect-newline": "^3.1.0"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-each": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.4.1.tgz",
- "integrity": "sha512-/8MJbH6fuj48TstjrMf+u/pd06Qezz5xOXvZA6442heNOWr8bdeoGZX2d9fCn028CoMgYmroH9//zky5GfyYmA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/get-type": "30.1.0",
- "@jest/types": "30.4.1",
- "chalk": "^4.1.2",
- "jest-util": "30.4.1",
- "pretty-format": "30.4.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-environment-node": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.4.1.tgz",
- "integrity": "sha512-4FZYVOk85hz2AyT6BbarKy9u37g6DbrDyCdFhsnDdXqyrueYQvB+0zO4f/kqLCRD0BsPRXPMNJeQwihKZV8naw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/environment": "30.4.1",
- "@jest/fake-timers": "30.4.1",
- "@jest/types": "30.4.1",
- "@types/node": "*",
- "jest-mock": "30.4.1",
- "jest-util": "30.4.1",
- "jest-validate": "30.4.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-haste-map": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.4.1.tgz",
- "integrity": "sha512-rFrcONd8jeFsyw+Z9CrScJgglRf2+NFmNam8dKu7n+SoHqNYT47mn0DdEcVUZJpvh7Iz6/si7f7yUH7GJHVgnw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/types": "30.4.1",
- "@types/node": "*",
- "anymatch": "^3.1.3",
- "fb-watchman": "^2.0.2",
- "graceful-fs": "^4.2.11",
- "jest-regex-util": "30.4.0",
- "jest-util": "30.4.1",
- "jest-worker": "30.4.1",
- "picomatch": "^4.0.3",
- "walker": "^1.0.8"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- },
- "optionalDependencies": {
- "fsevents": "^2.3.3"
- }
- },
- "node_modules/jest-leak-detector": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.4.1.tgz",
- "integrity": "sha512-IpmyiioeHxiWDhesHnUFmOxcTzwCwKpgACgWajtAP+nYQXiY7DakTxB6Bx9JFiRMljr0AX1PvnQdaU1KFoz6NQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/get-type": "30.1.0",
- "pretty-format": "30.4.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-matcher-utils": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.4.1.tgz",
- "integrity": "sha512-zvYfX5CaeEkFrrLS9suWe9rvJrm9J1Iv3ua8kIBv9GEPzcnsfBf0bob37la7s67fs0nlBC3EuvkOLnXQKxtx4A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/get-type": "30.1.0",
- "chalk": "^4.1.2",
- "jest-diff": "30.4.1",
- "pretty-format": "30.4.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-message-util": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.4.1.tgz",
- "integrity": "sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.27.1",
- "@jest/types": "30.4.1",
- "@types/stack-utils": "^2.0.3",
- "chalk": "^4.1.2",
- "graceful-fs": "^4.2.11",
- "jest-util": "30.4.1",
- "picomatch": "^4.0.3",
- "pretty-format": "30.4.1",
- "slash": "^3.0.0",
- "stack-utils": "^2.0.6"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-message-util/node_modules/@babel/code-frame": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
- "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-validator-identifier": "^7.29.7",
- "js-tokens": "^4.0.0",
- "picocolors": "^1.1.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/jest-message-util/node_modules/@babel/helper-validator-identifier": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
- "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/jest-message-util/node_modules/js-tokens": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/jest-mock": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.4.1.tgz",
- "integrity": "sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/types": "30.4.1",
- "@types/node": "*",
- "jest-util": "30.4.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-pnp-resolver": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz",
- "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- },
- "peerDependencies": {
- "jest-resolve": "*"
- },
- "peerDependenciesMeta": {
- "jest-resolve": {
- "optional": true
- }
- }
- },
- "node_modules/jest-regex-util": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz",
- "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-resolve": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.4.1.tgz",
- "integrity": "sha512-Zry8Yq/yJcNAZ7dJ5F2heic8AheXvbFZ7XI5V+h28nrYZ7Qoyy4dItq8OodjnYD270mvX+ZudmrNV9cysqhW5Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "chalk": "^4.1.2",
- "graceful-fs": "^4.2.11",
- "jest-haste-map": "30.4.1",
- "jest-pnp-resolver": "^1.2.3",
- "jest-util": "30.4.1",
- "jest-validate": "30.4.1",
- "slash": "^3.0.0",
- "unrs-resolver": "^1.7.11"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-resolve-dependencies": {
- "version": "30.4.2",
- "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.4.2.tgz",
- "integrity": "sha512-gDiVh1I+GxYzz9oXlyw+1wv6VOYX1WYxMOfjsA3iGKePV2oxmbHhwxfkALxNxYy1ciw6APWwkW2zZONwP97aEQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "jest-regex-util": "30.4.0",
- "jest-snapshot": "30.4.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-runner": {
- "version": "30.4.2",
- "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.4.2.tgz",
- "integrity": "sha512-2dw0PslVYXxffXGpLo+Ejad+KcI1Qkjn7f4X4619gf21oCUmL+SPfjqIa/losUem3yEOvfNZe/F1HWUcNpODcg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/console": "30.4.1",
- "@jest/environment": "30.4.1",
- "@jest/test-result": "30.4.1",
- "@jest/transform": "30.4.1",
- "@jest/types": "30.4.1",
- "@types/node": "*",
- "chalk": "^4.1.2",
- "emittery": "^0.13.1",
- "exit-x": "^0.2.2",
- "graceful-fs": "^4.2.11",
- "jest-docblock": "30.4.0",
- "jest-environment-node": "30.4.1",
- "jest-haste-map": "30.4.1",
- "jest-leak-detector": "30.4.1",
- "jest-message-util": "30.4.1",
- "jest-resolve": "30.4.1",
- "jest-runtime": "30.4.2",
- "jest-util": "30.4.1",
- "jest-watcher": "30.4.1",
- "jest-worker": "30.4.1",
- "p-limit": "^3.1.0",
- "source-map-support": "0.5.13"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-runtime": {
- "version": "30.4.2",
- "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.4.2.tgz",
- "integrity": "sha512-3/5e8iPz2k/VLqlr8DgTftYyLUv8Su3FkCAO2/Od81UsUTpSxOrS6O5x5KkoQwyUjmpYyDJKeyAvg2T2nvpNkQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/environment": "30.4.1",
- "@jest/fake-timers": "30.4.1",
- "@jest/globals": "30.4.1",
- "@jest/source-map": "30.0.1",
- "@jest/test-result": "30.4.1",
- "@jest/transform": "30.4.1",
- "@jest/types": "30.4.1",
- "@types/node": "*",
- "chalk": "^4.1.2",
- "cjs-module-lexer": "^2.1.0",
- "collect-v8-coverage": "^1.0.2",
- "glob": "^10.5.0",
- "graceful-fs": "^4.2.11",
- "jest-haste-map": "30.4.1",
- "jest-message-util": "30.4.1",
- "jest-mock": "30.4.1",
- "jest-regex-util": "30.4.0",
- "jest-resolve": "30.4.1",
- "jest-snapshot": "30.4.1",
- "jest-util": "30.4.1",
- "slash": "^3.0.0",
- "strip-bom": "^4.0.0"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-runtime/node_modules/balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/jest-runtime/node_modules/brace-expansion": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
- "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
- "node_modules/jest-runtime/node_modules/glob": {
- "version": "10.5.0",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
- "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
- "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "foreground-child": "^3.1.0",
- "jackspeak": "^3.1.2",
- "minimatch": "^9.0.4",
- "minipass": "^7.1.2",
- "package-json-from-dist": "^1.0.0",
- "path-scurry": "^1.11.1"
- },
- "bin": {
- "glob": "dist/esm/bin.mjs"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/jest-runtime/node_modules/lru-cache": {
- "version": "10.4.3",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
- "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/jest-runtime/node_modules/minimatch": {
- "version": "9.0.9",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
- "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.2"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/jest-runtime/node_modules/path-scurry": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
- "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "lru-cache": "^10.2.0",
- "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
- },
- "engines": {
- "node": ">=16 || 14 >=14.18"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/jest-snapshot": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.4.1.tgz",
- "integrity": "sha512-tEOkkfOMppUyeiHwjZswOQ3lcnoTnws/q5FnGIaeIh/jmoU0ZlgMYRR8sTlTj+nNGCoJ0RDq6SfxGxCsyMTPmw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/core": "^7.27.4",
- "@babel/generator": "^7.27.5",
- "@babel/plugin-syntax-jsx": "^7.27.1",
- "@babel/plugin-syntax-typescript": "^7.27.1",
- "@babel/types": "^7.27.3",
- "@jest/expect-utils": "30.4.1",
- "@jest/get-type": "30.1.0",
- "@jest/snapshot-utils": "30.4.1",
- "@jest/transform": "30.4.1",
- "@jest/types": "30.4.1",
- "babel-preset-current-node-syntax": "^1.2.0",
- "chalk": "^4.1.2",
- "expect": "30.4.1",
- "graceful-fs": "^4.2.11",
- "jest-diff": "30.4.1",
- "jest-matcher-utils": "30.4.1",
- "jest-message-util": "30.4.1",
- "jest-util": "30.4.1",
- "pretty-format": "30.4.1",
- "semver": "^7.7.2",
- "synckit": "^0.11.8"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-snapshot/node_modules/@babel/generator": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz",
- "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.29.7",
- "@babel/types": "^7.29.7",
- "@jridgewell/gen-mapping": "^0.3.12",
- "@jridgewell/trace-mapping": "^0.3.28",
- "jsesc": "^3.0.2"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/jest-snapshot/node_modules/@babel/helper-plugin-utils": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
- "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/jest-snapshot/node_modules/@babel/helper-string-parser": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
- "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/jest-snapshot/node_modules/@babel/helper-validator-identifier": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
- "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/jest-snapshot/node_modules/@babel/parser": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
- "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.29.7"
- },
- "bin": {
- "parser": "bin/babel-parser.js"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/jest-snapshot/node_modules/@babel/plugin-syntax-jsx": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz",
- "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/jest-snapshot/node_modules/@babel/types": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
- "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-string-parser": "^7.29.7",
- "@babel/helper-validator-identifier": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/jest-util": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz",
- "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/types": "30.4.1",
- "@types/node": "*",
- "chalk": "^4.1.2",
- "ci-info": "^4.2.0",
- "graceful-fs": "^4.2.11",
- "picomatch": "^4.0.3"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-validate": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.4.1.tgz",
- "integrity": "sha512-PDWi4SOwLnwqNDfHZjOcsEFyZ4fc/2W2gVL3DEoyqnB6jCQMLRtfBong8s6omIw3lI0HWOus12xfnFmQtjW3fw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/get-type": "30.1.0",
- "@jest/types": "30.4.1",
- "camelcase": "^6.3.0",
- "chalk": "^4.1.2",
- "leven": "^3.1.0",
- "pretty-format": "30.4.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-validate/node_modules/camelcase": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
- "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/jest-watcher": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.4.1.tgz",
- "integrity": "sha512-/l9UonmvCwjHH7d2h3iAwIloLc1H0S8mJZ/LNK3i86hqwPAz8otUJjP9MfYtz9Tt77Su5FD2xGjZn8d31IZHlw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/test-result": "30.4.1",
- "@jest/types": "30.4.1",
- "@types/node": "*",
- "ansi-escapes": "^4.3.2",
- "chalk": "^4.1.2",
- "emittery": "^0.13.1",
- "jest-util": "30.4.1",
- "string-length": "^4.0.2"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-worker": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.4.1.tgz",
- "integrity": "sha512-SHynN/q/QD++iNyvMdy+WMmbCGk8jIsNcRxycXbWubSOhvo6T+j2afcfUSl+3hYsiBebOTo0cT7c2H7CXugu1g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "@ungap/structured-clone": "^1.3.0",
- "jest-util": "30.4.1",
- "merge-stream": "^2.0.0",
- "supports-color": "^8.1.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-worker/node_modules/supports-color": {
- "version": "8.1.1",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
- "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/supports-color?sponsor=1"
- }
- },
- "node_modules/js-tokens": {
- "version": "10.0.0",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz",
- "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/jsesc": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
- "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "jsesc": "bin/jsesc"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/json-parse-even-better-errors": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
- "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/json-schema-traverse": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
- "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/json-stable-stringify": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.3.0.tgz",
- "integrity": "sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.4",
- "isarray": "^2.0.5",
- "jsonify": "^0.0.1",
- "object-keys": "^1.1.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/json5": {
- "version": "2.2.3",
- "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
- "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "json5": "lib/cli.js"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/jsonfile": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz",
- "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "universalify": "^2.0.0"
- },
- "optionalDependencies": {
- "graceful-fs": "^4.1.6"
- }
- },
- "node_modules/jsonify": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz",
- "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==",
- "dev": true,
- "license": "Public Domain",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/jsqr": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/jsqr/-/jsqr-1.4.0.tgz",
- "integrity": "sha512-dxLob7q65Xg2DvstYkRpkYtmKm2sPJ9oFhrhmudT1dZvNFFTlroai3AWSpLey/w5vMcLBXRgOJsbXpdN9HzU/A==",
- "license": "Apache-2.0"
- },
- "node_modules/kind-of": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
- "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/leven": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
- "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/libphonenumber-js": {
- "version": "1.13.8",
- "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.13.8.tgz",
- "integrity": "sha512-80xal1m93rADejw2pMp2MSzFhHCPLEspjHxnH2UtqI+DgAmElsbmLMiqk9niwH9NWAfjsRtaJI+qBrOEmRx9nQ==",
- "license": "MIT"
- },
- "node_modules/lilconfig": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
- "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/antonk52"
- }
- },
- "node_modules/lines-and-columns": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
- "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/loader-runner": {
- "version": "4.3.2",
- "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.2.tgz",
- "integrity": "sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.11.5"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
- }
- },
- "node_modules/locate-path": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
- "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-locate": "^5.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/lodash.camelcase": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
- "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
- "license": "MIT"
- },
- "node_modules/lodash.debounce": {
- "version": "4.0.8",
- "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
- "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/long": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
- "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
- "license": "Apache-2.0"
- },
- "node_modules/loose-envify": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
- "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
- "license": "MIT",
- "dependencies": {
- "js-tokens": "^3.0.0 || ^4.0.0"
- },
- "bin": {
- "loose-envify": "cli.js"
- }
- },
- "node_modules/loose-envify/node_modules/js-tokens": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "license": "MIT"
- },
- "node_modules/lru-cache": {
- "version": "11.5.1",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz",
- "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "engines": {
- "node": "20 || >=22"
- }
- },
- "node_modules/make-dir": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
- "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "semver": "^7.5.3"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/makeerror": {
- "version": "1.0.12",
- "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz",
- "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "tmpl": "1.0.5"
- }
- },
- "node_modules/math-intrinsics": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
- "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/mdn-data": {
- "version": "2.27.1",
- "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz",
- "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==",
- "dev": true,
- "license": "CC0-1.0"
- },
- "node_modules/merge-stream": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
- "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/mime-db": {
- "version": "1.54.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
- "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/mimic-fn": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
- "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/minimatch": {
- "version": "10.2.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
- "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "brace-expansion": "^5.0.5"
- },
- "engines": {
- "node": "18 || 20 || >=22"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/minimizer-webpack-plugin": {
- "version": "5.6.1",
- "resolved": "https://registry.npmjs.org/minimizer-webpack-plugin/-/minimizer-webpack-plugin-5.6.1.tgz",
- "integrity": "sha512-DoeAZz8Q1C1znwsUzej1fdoi4jCf7/+Em27ouLqfK/+3m8G+D7yDhUwrc3CNhjSzGUN1kn7Iv4sWmjflQHenpw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/trace-mapping": "^0.3.25",
- "jest-worker": "^27.4.5",
- "schema-utils": "^4.3.0",
- "terser": "^5.31.1"
- },
- "engines": {
- "node": ">= 10.13.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
- },
- "peerDependencies": {
- "webpack": "^5.1.0"
- },
- "peerDependenciesMeta": {
- "@minify-html/node": {
- "optional": true
- },
- "@swc/core": {
- "optional": true
- },
- "@swc/css": {
- "optional": true
- },
- "@swc/html": {
- "optional": true
- },
- "clean-css": {
- "optional": true
- },
- "cssnano": {
- "optional": true
- },
- "csso": {
- "optional": true
- },
- "esbuild": {
- "optional": true
- },
- "html-minifier-terser": {
- "optional": true
- },
- "lightningcss": {
- "optional": true
- },
- "postcss": {
- "optional": true
- },
- "uglify-js": {
- "optional": true
- }
- }
- },
- "node_modules/minimizer-webpack-plugin/node_modules/jest-worker": {
- "version": "27.5.1",
- "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
- "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "merge-stream": "^2.0.0",
- "supports-color": "^8.0.0"
- },
- "engines": {
- "node": ">= 10.13.0"
- }
- },
- "node_modules/minimizer-webpack-plugin/node_modules/supports-color": {
- "version": "8.1.1",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
- "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/supports-color?sponsor=1"
- }
- },
- "node_modules/minipass": {
- "version": "7.1.3",
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
- "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "engines": {
- "node": ">=16 || 14 >=14.17"
- }
- },
- "node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/nanoid": {
- "version": "3.3.15",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz",
- "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "bin": {
- "nanoid": "bin/nanoid.cjs"
- },
- "engines": {
- "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
- }
- },
- "node_modules/napi-postinstall": {
- "version": "0.3.4",
- "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz",
- "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "napi-postinstall": "lib/cli.js"
- },
- "engines": {
- "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/napi-postinstall"
- }
- },
- "node_modules/natural-compare": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
- "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/neo-async": {
- "version": "2.6.2",
- "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
- "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/node-int64": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
- "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/node-releases": {
- "version": "2.0.50",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz",
- "integrity": "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/normalize-path": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
- "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/npm-run-path": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
- "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "path-key": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/nth-check": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
- "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "boolbase": "^1.0.0"
- },
- "funding": {
- "url": "https://github.com/fb55/nth-check?sponsor=1"
- }
- },
- "node_modules/object-keys": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
- "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/obug": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz",
- "integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==",
- "dev": true,
- "funding": [
- "https://github.com/sponsors/sxzz",
- "https://opencollective.com/debug"
- ],
- "license": "MIT",
- "engines": {
- "node": ">=12.20.0"
- }
- },
- "node_modules/once": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
- "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "wrappy": "1"
- }
- },
- "node_modules/onetime": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
- "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "mimic-fn": "^2.1.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/p-limit": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
- "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "yocto-queue": "^0.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/p-locate": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
- "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-limit": "^3.0.2"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/p-try": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
- "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/package-json-from-dist": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
- "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
- "dev": true,
- "license": "BlueOak-1.0.0"
- },
- "node_modules/parse-json": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
- "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.0.0",
- "error-ex": "^1.3.1",
- "json-parse-even-better-errors": "^2.3.0",
- "lines-and-columns": "^1.1.6"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/parse-json/node_modules/@babel/code-frame": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
- "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-validator-identifier": "^7.29.7",
- "js-tokens": "^4.0.0",
- "picocolors": "^1.1.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/parse-json/node_modules/@babel/helper-validator-identifier": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
- "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/parse-json/node_modules/js-tokens": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/path-exists": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
- "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/path-is-absolute": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
- "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/path-key": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
- "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/path-parse": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
- "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/path-scurry": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz",
- "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "lru-cache": "^11.0.0",
- "minipass": "^7.1.2"
- },
- "engines": {
- "node": "18 || 20 || >=22"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/picocolors": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
- "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/picomatch": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
- "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/pify": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
- "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/pinyin-pro": {
- "version": "3.29.3",
- "resolved": "https://registry.npmjs.org/pinyin-pro/-/pinyin-pro-3.29.3.tgz",
- "integrity": "sha512-+UU9bx6vfDw8amOJGHm0TE0rdQl8VPylsDWviQ5OOQ3e+on1xRP4OqDbiDuMT5OISgvfl/Y6ez1BBRaIP80GLQ==",
- "license": "MIT"
- },
- "node_modules/pirates": {
- "version": "4.0.7",
- "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
- "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/pkg-dir": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
- "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "find-up": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/pkg-dir/node_modules/find-up": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
- "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "locate-path": "^5.0.0",
- "path-exists": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/pkg-dir/node_modules/locate-path": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
- "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-locate": "^4.1.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/pkg-dir/node_modules/p-limit": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
- "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-try": "^2.0.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/pkg-dir/node_modules/p-locate": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
- "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-limit": "^2.2.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/postcss": {
- "version": "8.5.16",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz",
- "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/postcss"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "nanoid": "^3.3.12",
- "picocolors": "^1.1.1",
- "source-map-js": "^1.2.1"
- },
- "engines": {
- "node": "^10 || ^12 || >=14"
- }
- },
- "node_modules/postcss-calc": {
- "version": "10.1.1",
- "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-10.1.1.tgz",
- "integrity": "sha512-NYEsLHh8DgG/PRH2+G9BTuUdtf9ViS+vdoQ0YA5OQdGsfN4ztiwtDWNtBl9EKeqNMFnIu8IKZ0cLxEQ5r5KVMw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "postcss-selector-parser": "^7.0.0",
- "postcss-value-parser": "^4.2.0"
- },
- "engines": {
- "node": "^18.12 || ^20.9 || >=22.0"
- },
- "peerDependencies": {
- "postcss": "^8.4.38"
- }
- },
- "node_modules/postcss-cli": {
- "version": "11.0.1",
- "resolved": "https://registry.npmjs.org/postcss-cli/-/postcss-cli-11.0.1.tgz",
- "integrity": "sha512-0UnkNPSayHKRe/tc2YGW6XnSqqOA9eqpiRMgRlV1S6HdGi16vwJBx7lviARzbV1HpQHqLLRH3o8vTcB0cLc+5g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "chokidar": "^3.3.0",
- "dependency-graph": "^1.0.0",
- "fs-extra": "^11.0.0",
- "picocolors": "^1.0.0",
- "postcss-load-config": "^5.0.0",
- "postcss-reporter": "^7.0.0",
- "pretty-hrtime": "^1.0.3",
- "read-cache": "^1.0.0",
- "slash": "^5.0.0",
- "tinyglobby": "^0.2.12",
- "yargs": "^17.0.0"
- },
- "bin": {
- "postcss": "index.js"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "postcss": "^8.0.0"
- }
- },
- "node_modules/postcss-cli/node_modules/slash": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz",
- "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=14.16"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/postcss-colormin": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-8.0.1.tgz",
- "integrity": "sha512-qBY4ABQ6d8/mk5RRZHwMllrZMxeMey3azVY2dZUEk+RgiUC4ARdPR3/AITzNqqKTbvW/3y/MJKinDrzwqn8RDQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@colordx/core": "^5.4.3",
- "browserslist": "^4.28.2",
- "caniuse-api": "^4.0.0",
- "postcss-value-parser": "^4.2.0"
- },
- "engines": {
- "node": "^22.11.0 || ^24.11.0 || >=26.0"
- },
- "peerDependencies": {
- "postcss": "^8.5.15"
- }
- },
- "node_modules/postcss-convert-values": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-8.0.1.tgz",
- "integrity": "sha512-IdOSIX3BzfMvCc1TAHIha2gfy17xnb5vfML8e2BIKARnFOghksESfaSAB/3CXgyLfMozZAbTRPVQF5dbuKOidw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "browserslist": "^4.28.2",
- "postcss-value-parser": "^4.2.0"
- },
- "engines": {
- "node": "^22.11.0 || ^24.11.0 || >=26.0"
- },
- "peerDependencies": {
- "postcss": "^8.5.15"
- }
- },
- "node_modules/postcss-discard-comments": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-8.0.1.tgz",
- "integrity": "sha512-FDvzm3tXlEsQBO2XQgnta5ugsAqwBrgWH+j5QgXpegEIDYA0VPnZg2aP7LtmWtC49POskeIhXesFiU/k3NyFHA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "postcss-selector-parser": "^7.1.2"
- },
- "engines": {
- "node": "^22.11.0 || ^24.11.0 || >=26.0"
- },
- "peerDependencies": {
- "postcss": "^8.5.15"
- }
- },
- "node_modules/postcss-discard-duplicates": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-8.0.1.tgz",
- "integrity": "sha512-stTDXkI8YkCUfADurQhp03oq5ynsgSx6Qrw5B1swds6oTHtAeOZ9I0SHGK8cY/VpWUsIYFDWMs3IWf9jIEfFvA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^22.11.0 || ^24.11.0 || >=26.0"
- },
- "peerDependencies": {
- "postcss": "^8.5.15"
- }
- },
- "node_modules/postcss-discard-empty": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-8.0.1.tgz",
- "integrity": "sha512-Zv4fM1Yfhk71tbt6gfiptbL6jDHi+7apSnaMeaO9n1uET+1embrXQw5m93Zp5x28UyQSuv+AVkFY193jdwZ33w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^22.11.0 || ^24.11.0 || >=26.0"
- },
- "peerDependencies": {
- "postcss": "^8.5.15"
- }
- },
- "node_modules/postcss-discard-overridden": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-8.0.1.tgz",
- "integrity": "sha512-ykt4fvrC7yYGzbxKyqBVjDCbsjF/11JgWK8enrdkobRyqqEtb/uDUCbKOGdvrK8X7BrShW8Lv5cCRNbdkNHGkQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^22.11.0 || ^24.11.0 || >=26.0"
- },
- "peerDependencies": {
- "postcss": "^8.5.15"
- }
- },
- "node_modules/postcss-load-config": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-5.1.0.tgz",
- "integrity": "sha512-G5AJ+IX0aD0dygOE0yFZQ/huFFMSNneyfp0e3/bT05a8OfPC5FUoZRPfGijUdGOJNMewJiwzcHJXFafFzeKFVA==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "lilconfig": "^3.1.1",
- "yaml": "^2.4.2"
- },
- "engines": {
- "node": ">= 18"
- },
- "peerDependencies": {
- "jiti": ">=1.21.0",
- "postcss": ">=8.0.9",
- "tsx": "^4.8.1"
- },
- "peerDependenciesMeta": {
- "jiti": {
- "optional": true
- },
- "postcss": {
- "optional": true
- },
- "tsx": {
- "optional": true
- }
- }
- },
- "node_modules/postcss-merge-longhand": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-8.0.1.tgz",
- "integrity": "sha512-huTfSYgQ13O81SFvAuOi7GWnO48vvybjj3xF+X3qUoPjzvvaLpJH5DcUqqXcwOEulZUcvaV4s0V9WtWs+IAQPA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "postcss-value-parser": "^4.2.0",
- "stylehacks": "^8.0.1"
- },
- "engines": {
- "node": "^22.11.0 || ^24.11.0 || >=26.0"
- },
- "peerDependencies": {
- "postcss": "^8.5.15"
- }
- },
- "node_modules/postcss-merge-rules": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-8.0.1.tgz",
- "integrity": "sha512-o3rk4UpnPNg469tklYwbR/NtvKc/f/wJiVDTnNQ/EFPw/LeiPOHUCvV1GIBQIZHGrBAYdPjToK6a+ojYprsrxQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "browserslist": "^4.28.2",
- "caniuse-api": "^4.0.0",
- "cssnano-utils": "^6.0.1",
- "postcss-selector-parser": "^7.1.2"
- },
- "engines": {
- "node": "^22.11.0 || ^24.11.0 || >=26.0"
- },
- "peerDependencies": {
- "postcss": "^8.5.15"
- }
- },
- "node_modules/postcss-minify-font-values": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-8.0.1.tgz",
- "integrity": "sha512-L8Nzs/PRlBSPrLdY/7rAiU5ZN5800+2J/4LRbfyG8SJnPljmgMaXVmQiCklvRS+yObfVRNtvmk/Ean/eoYcSeg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "postcss-value-parser": "^4.2.0"
- },
- "engines": {
- "node": "^22.11.0 || ^24.11.0 || >=26.0"
- },
- "peerDependencies": {
- "postcss": "^8.5.15"
- }
- },
- "node_modules/postcss-minify-gradients": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-8.0.1.tgz",
- "integrity": "sha512-qf+4s/hZMqTwpWN2teqf6+1yvR/SZK5HgHqXYuACeJXV7ABe7AXtBEomgxagUzcN4bSnmqBh5vnIml0dYqykYg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@colordx/core": "^5.4.3",
- "cssnano-utils": "^6.0.1",
- "postcss-value-parser": "^4.2.0"
- },
- "engines": {
- "node": "^22.11.0 || ^24.11.0 || >=26.0"
- },
- "peerDependencies": {
- "postcss": "^8.5.15"
- }
- },
- "node_modules/postcss-minify-params": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-8.0.1.tgz",
- "integrity": "sha512-L0h3H59deFfFg0wQN1NVaS/8E/LfGvaMuZKGO7siwlG995zo3OshtQyRkqKdVqcBwAORBvZ1nDZrKPLRapYkQw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "browserslist": "^4.28.2",
- "cssnano-utils": "^6.0.1",
- "postcss-value-parser": "^4.2.0"
- },
- "engines": {
- "node": "^22.11.0 || ^24.11.0 || >=26.0"
- },
- "peerDependencies": {
- "postcss": "^8.5.15"
- }
- },
- "node_modules/postcss-minify-selectors": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-8.0.2.tgz",
- "integrity": "sha512-3icdxc/zght5UAizdwqZBDE2KOWHf1jMQCxET6iLACeNlRxfTPyXS0/COpGk8CQ2cECyaEKTRUd/i/k8Gxmz4g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "browserslist": "^4.28.1",
- "caniuse-api": "^4.0.0",
- "cssesc": "^3.0.0",
- "postcss-selector-parser": "^7.1.2"
- },
- "engines": {
- "node": "^22.11.0 || ^24.11.0 || >=26.0"
- },
- "peerDependencies": {
- "postcss": "^8.5.15"
- }
- },
- "node_modules/postcss-normalize-charset": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-8.0.1.tgz",
- "integrity": "sha512-xzqr36F8UeIZOvOHsf3aul+RVJCADvSwuwpMLgizqKjisHZpBfztgW0XFLBfJvz9pJgaStaOXAtGb0zLqT6B0w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^22.11.0 || ^24.11.0 || >=26.0"
- },
- "peerDependencies": {
- "postcss": "^8.5.15"
- }
- },
- "node_modules/postcss-normalize-display-values": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-8.0.1.tgz",
- "integrity": "sha512-ZDWOijOK1FFMlpgiQCUO9fCNKd7HJ9L7z9HWEq4iyubnUFWzdTSwm/LcrMbNW6iZ1oAtqeLYA0WA3xHszOI08g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "postcss-value-parser": "^4.2.0"
- },
- "engines": {
- "node": "^22.11.0 || ^24.11.0 || >=26.0"
- },
- "peerDependencies": {
- "postcss": "^8.5.15"
- }
- },
- "node_modules/postcss-normalize-positions": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-8.0.1.tgz",
- "integrity": "sha512-uuivan2poSqbE48ST4do20dGaFUeXey9/H8rhHzoyVHB2I6BmkoVLZ/C9+BRjUlpaAFYVOoDY7epkiidzaYbvA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "postcss-value-parser": "^4.2.0"
- },
- "engines": {
- "node": "^22.11.0 || ^24.11.0 || >=26.0"
- },
- "peerDependencies": {
- "postcss": "^8.5.15"
- }
- },
- "node_modules/postcss-normalize-repeat-style": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-8.0.1.tgz",
- "integrity": "sha512-q2hq5fmKxk29K6DjKA3nZ17Q2dtjhLYFNmFweKALmooUqx6UWAHF1bBoWTu/EqlJ88josb82A/J0Atj9LJUmpQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "postcss-value-parser": "^4.2.0"
- },
- "engines": {
- "node": "^22.11.0 || ^24.11.0 || >=26.0"
- },
- "peerDependencies": {
- "postcss": "^8.5.15"
- }
- },
- "node_modules/postcss-normalize-string": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-8.0.1.tgz",
- "integrity": "sha512-+Wf+kQJhm1WgSGEAuUaswE9rdpR9QbrKRVemcVHs6rhOoOTVIdAbgaicftfYA6vLM346P8onRzkEVbFN29ktKQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "postcss-value-parser": "^4.2.0"
- },
- "engines": {
- "node": "^22.11.0 || ^24.11.0 || >=26.0"
- },
- "peerDependencies": {
- "postcss": "^8.5.15"
- }
- },
- "node_modules/postcss-normalize-timing-functions": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-8.0.1.tgz",
- "integrity": "sha512-W8/tvwRlm3T+yjGkg0IRTF4bvHj0vILYr/LOogCrJKHz2ey2HFRwfsAA8Bk9N4BGR7z7WmmDu/KzzwhJ6FoGPQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "postcss-value-parser": "^4.2.0"
- },
- "engines": {
- "node": "^22.11.0 || ^24.11.0 || >=26.0"
- },
- "peerDependencies": {
- "postcss": "^8.5.15"
- }
- },
- "node_modules/postcss-normalize-unicode": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-8.0.1.tgz",
- "integrity": "sha512-Ad0YHNRBp4WHEOYUM/4wL/8MoL2fimEF8se/0q+Rt/owMzYpbxsypC1P8fN/oluwoRmRKdNVX7X2oycEobPWcQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "browserslist": "^4.28.2",
- "postcss-value-parser": "^4.2.0"
- },
- "engines": {
- "node": "^22.11.0 || ^24.11.0 || >=26.0"
- },
- "peerDependencies": {
- "postcss": "^8.5.15"
- }
- },
- "node_modules/postcss-normalize-url": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-8.0.1.tgz",
- "integrity": "sha512-tkYcip6pCDY806xuxpJYqMW2M3/623jzGFJmz3m5Us47q8P28+gbRZxaea3Rr/CmwwLUiVlh+BTGYwQ6gvaP8A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "postcss-value-parser": "^4.2.0"
- },
- "engines": {
- "node": "^22.11.0 || ^24.11.0 || >=26.0"
- },
- "peerDependencies": {
- "postcss": "^8.5.15"
- }
- },
- "node_modules/postcss-normalize-whitespace": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-8.0.1.tgz",
- "integrity": "sha512-XzORadNfSrKWDZZpgAEHPKINKx8r9r9RIfE9c70g/HThdpbmPHhDYCodHSVESDxmKeySAYw1p4liuBCf7j6LyA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "postcss-value-parser": "^4.2.0"
- },
- "engines": {
- "node": "^22.11.0 || ^24.11.0 || >=26.0"
- },
- "peerDependencies": {
- "postcss": "^8.5.15"
- }
- },
- "node_modules/postcss-ordered-values": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-8.0.1.tgz",
- "integrity": "sha512-OLXq5lR1yk3KWQ1FPK6aWjFFdktHE9f9kb8cnt4LmIw7w30DnzgD9+sOVYJc5HenkWCX8i1MJhhFwmqc/GYqLg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "cssnano-utils": "^6.0.1",
- "postcss-value-parser": "^4.2.0"
- },
- "engines": {
- "node": "^22.11.0 || ^24.11.0 || >=26.0"
- },
- "peerDependencies": {
- "postcss": "^8.5.15"
- }
- },
- "node_modules/postcss-reduce-initial": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-8.0.1.tgz",
- "integrity": "sha512-+aQsR6+61KRoIfcFNLP3v9RM7+0iYOTtPnjl1wr6JqMW1zx6S+t2ktHRefXwacFdHIDj5+ETG0KY7K3+SGQ4Nw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "browserslist": "^4.28.2",
- "caniuse-api": "^4.0.0"
- },
- "engines": {
- "node": "^22.11.0 || ^24.11.0 || >=26.0"
- },
- "peerDependencies": {
- "postcss": "^8.5.15"
- }
- },
- "node_modules/postcss-reduce-transforms": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-8.0.1.tgz",
- "integrity": "sha512-x71slHVykiFi5RuKEXM0wgYpY2PngC78x6R8TnZhHF3lhqt+u/w3MGwYLX+2t5O87ssRiMfEAhQH+3J4QwVzCw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "postcss-value-parser": "^4.2.0"
- },
- "engines": {
- "node": "^22.11.0 || ^24.11.0 || >=26.0"
- },
- "peerDependencies": {
- "postcss": "^8.5.15"
- }
- },
- "node_modules/postcss-reporter": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-7.1.0.tgz",
- "integrity": "sha512-/eoEylGWyy6/DOiMP5lmFRdmDKThqgn7D6hP2dXKJI/0rJSO1ADFNngZfDzxL0YAxFvws+Rtpuji1YIHj4mySA==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "picocolors": "^1.0.0",
- "thenby": "^1.3.4"
- },
- "engines": {
- "node": ">=10"
- },
- "peerDependencies": {
- "postcss": "^8.1.0"
- }
- },
- "node_modules/postcss-selector-parser": {
- "version": "7.1.4",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz",
- "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "cssesc": "^3.0.0",
- "util-deprecate": "^1.0.2"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/postcss-svgo": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-8.0.1.tgz",
- "integrity": "sha512-HpnvWii7W0/FPrsejJa6ZTi0kNtTJP/Iba7CUMPX0xPV6QpnndOp+SDP74tFtgjA2cYKYNWJPOlmLXMsvi/9yA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "postcss-value-parser": "^4.2.0",
- "svgo": "^4.0.1"
- },
- "engines": {
- "node": "^22.11.0 || ^24.11.0 || >=26.0"
- },
- "peerDependencies": {
- "postcss": "^8.5.15"
- }
- },
- "node_modules/postcss-unique-selectors": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-8.0.1.tgz",
- "integrity": "sha512-+xvKI5+/Cl8yYQwxDV39Uhuc4WV951xngFvPPjiPj2NIbIfm6vbbRTXblyw0FioLkIoGlw+7qUcY1h2YhaZYgw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "postcss-selector-parser": "^7.1.2"
- },
- "engines": {
- "node": "^22.11.0 || ^24.11.0 || >=26.0"
- },
- "peerDependencies": {
- "postcss": "^8.5.15"
- }
- },
- "node_modules/postcss-value-parser": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
- "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/pretty-format": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz",
- "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/schemas": "30.4.1",
- "ansi-styles": "^5.2.0",
- "react-is-18": "npm:react-is@^18.3.1",
- "react-is-19": "npm:react-is@^19.2.5"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/pretty-format/node_modules/ansi-styles": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
- "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/pretty-hrtime": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz",
- "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/protobufjs": {
- "version": "7.6.5",
- "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.5.tgz",
- "integrity": "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==",
- "hasInstallScript": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@protobufjs/aspromise": "^1.1.2",
- "@protobufjs/base64": "^1.1.2",
- "@protobufjs/codegen": "^2.0.5",
- "@protobufjs/eventemitter": "^1.1.1",
- "@protobufjs/fetch": "^1.1.1",
- "@protobufjs/float": "^1.0.2",
- "@protobufjs/path": "^1.1.2",
- "@protobufjs/pool": "^1.1.0",
- "@protobufjs/utf8": "^1.1.1",
- "@types/node": ">=13.7.0",
- "long": "^5.3.2"
- },
- "engines": {
- "node": ">=12.0.0"
- }
- },
- "node_modules/pure-rand": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz",
- "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==",
- "dev": true,
- "funding": [
- {
- "type": "individual",
- "url": "https://github.com/sponsors/dubzzz"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fast-check"
- }
- ],
- "license": "MIT"
- },
- "node_modules/qrcodejs": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/qrcodejs/-/qrcodejs-1.0.0.tgz",
- "integrity": "sha512-67rj3mMBhSBepaD57qENnltO+r8rSYlqM7HGThks/BiyDAkc86sLvkKqjkqPS5v13f7tvnt6dbEf3qt7zq+BCg=="
- },
- "node_modules/re2js": {
- "version": "0.4.3",
- "resolved": "https://registry.npmjs.org/re2js/-/re2js-0.4.3.tgz",
- "integrity": "sha512-EuNmh7jurhHEE8Ge/lBo9JuMLb3qf866Xjjfyovw3wPc7+hlqDkZq4LwhrCQMEI+ARWfrKrHozEndzlpNT0WDg==",
- "license": "MIT"
- },
- "node_modules/react": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
- "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
- "license": "MIT",
- "dependencies": {
- "loose-envify": "^1.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/react-dom": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
- "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
- "license": "MIT",
- "dependencies": {
- "loose-envify": "^1.1.0",
- "scheduler": "^0.23.2"
- },
- "peerDependencies": {
- "react": "^18.3.1"
- }
- },
- "node_modules/react-intl": {
- "version": "7.1.14",
- "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-7.1.14.tgz",
- "integrity": "sha512-VE/0Wi/lHJlBC7APQpCzLUdIt3GB5B0GZrRW8Q+ACbkHI4j+Wwgg9J1TniN6zmLHmPH5gxXcMy+fkSPfw5p1WQ==",
- "license": "BSD-3-Clause",
- "dependencies": {
- "@formatjs/ecma402-abstract": "2.3.6",
- "@formatjs/icu-messageformat-parser": "2.11.4",
- "@formatjs/intl": "3.1.8",
- "@types/hoist-non-react-statics": "^3.3.1",
- "@types/react": "16 || 17 || 18 || 19",
- "hoist-non-react-statics": "^3.3.2",
- "intl-messageformat": "10.7.18",
- "tslib": "^2.8.0"
- },
- "peerDependencies": {
- "react": "16 || 17 || 18 || 19",
- "typescript": "^5.6.0"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
- }
- },
- "node_modules/react-intl/node_modules/@formatjs/icu-messageformat-parser": {
- "version": "2.11.4",
- "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.11.4.tgz",
- "integrity": "sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw==",
- "license": "MIT",
- "dependencies": {
- "@formatjs/ecma402-abstract": "2.3.6",
- "@formatjs/icu-skeleton-parser": "1.8.16",
- "tslib": "^2.8.0"
- }
- },
- "node_modules/react-intl/node_modules/@formatjs/icu-skeleton-parser": {
- "version": "1.8.16",
- "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.16.tgz",
- "integrity": "sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ==",
- "license": "MIT",
- "dependencies": {
- "@formatjs/ecma402-abstract": "2.3.6",
- "tslib": "^2.8.0"
- }
- },
- "node_modules/react-is": {
- "version": "16.13.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
- "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
- "license": "MIT"
- },
- "node_modules/react-is-18": {
- "name": "react-is",
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
- "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/react-is-19": {
- "name": "react-is",
- "version": "19.2.7",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.7.tgz",
- "integrity": "sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/read-cache": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
- "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "pify": "^2.3.0"
- }
- },
- "node_modules/readdirp": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
- "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "picomatch": "^2.2.1"
- },
- "engines": {
- "node": ">=8.10.0"
- }
- },
- "node_modules/readdirp/node_modules/picomatch": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
- "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/rechoir": {
- "version": "0.8.0",
- "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz",
- "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "resolve": "^1.20.0"
- },
- "engines": {
- "node": ">= 10.13.0"
- }
- },
- "node_modules/regenerate": {
- "version": "1.4.2",
- "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
- "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/regenerate-unicode-properties": {
- "version": "10.2.2",
- "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz",
- "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "regenerate": "^1.4.2"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/regexpu-core": {
- "version": "6.4.0",
- "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz",
- "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "regenerate": "^1.4.2",
- "regenerate-unicode-properties": "^10.2.2",
- "regjsgen": "^0.8.0",
- "regjsparser": "^0.13.0",
- "unicode-match-property-ecmascript": "^2.0.0",
- "unicode-match-property-value-ecmascript": "^2.2.1"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/regjsgen": {
- "version": "0.8.0",
- "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz",
- "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/regjsparser": {
- "version": "0.13.2",
- "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.2.tgz",
- "integrity": "sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "jsesc": "~3.1.0"
- },
- "bin": {
- "regjsparser": "bin/parser"
- }
- },
- "node_modules/require-directory": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
- "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/require-from-string": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
- "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/resolve": {
- "version": "1.22.12",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz",
- "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "is-core-module": "^2.16.1",
- "path-parse": "^1.0.7",
- "supports-preserve-symlinks-flag": "^1.0.0"
- },
- "bin": {
- "resolve": "bin/resolve"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/resolve-cwd": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz",
- "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "resolve-from": "^5.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/resolve-from": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
- "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/safe-buffer": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
- "node_modules/sax": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz",
- "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "engines": {
- "node": ">=11.0.0"
- }
- },
- "node_modules/scheduler": {
- "version": "0.23.2",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
- "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
- "license": "MIT",
- "dependencies": {
- "loose-envify": "^1.1.0"
- }
- },
- "node_modules/schema-utils": {
- "version": "4.3.3",
- "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz",
- "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/json-schema": "^7.0.9",
- "ajv": "^8.9.0",
- "ajv-formats": "^2.1.1",
- "ajv-keywords": "^5.1.0"
- },
- "engines": {
- "node": ">= 10.13.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
- }
- },
- "node_modules/semver": {
- "version": "7.8.5",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
- "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/serialize-javascript": {
- "version": "7.0.7",
- "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.7.tgz",
- "integrity": "sha512-YAy8Od6KV+uuwUuU50np8fGB/Aues6Y0nAhA9y/hId74PlKUcme4pXcBD46NWKr1Q4osN/iseZ17YqO1XfmI8g==",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/set-function-length": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
- "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "define-data-property": "^1.1.4",
- "es-errors": "^1.3.0",
- "function-bind": "^1.1.2",
- "get-intrinsic": "^1.2.4",
- "gopd": "^1.0.1",
- "has-property-descriptors": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/shallow-clone": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz",
- "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "kind-of": "^6.0.2"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/shebang-command": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
- "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "shebang-regex": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/shebang-regex": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
- "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/signal-exit": {
- "version": "3.0.7",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
- "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/slash": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
- "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/source-map-js": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
- "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/source-map-support": {
- "version": "0.5.13",
- "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
- "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "buffer-from": "^1.0.0",
- "source-map": "^0.6.0"
- }
- },
- "node_modules/sprintf-js": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
- "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
- "dev": true,
- "license": "BSD-3-Clause"
- },
- "node_modules/stack-utils": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
- "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "escape-string-regexp": "^2.0.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/string-length": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
- "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "char-regex": "^1.0.2",
- "strip-ansi": "^6.0.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/string-width-cjs": {
- "name": "string-width",
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/strip-ansi-cjs": {
- "name": "strip-ansi",
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/strip-bom": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz",
- "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/strip-final-newline": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
- "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/strip-json-comments": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
- "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/stylehacks": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-8.0.1.tgz",
- "integrity": "sha512-Gv095oTD0N+BdJALNFDsxZpETHZLTxbOl5RyIO7y6VAE6sR3z0MnV3Nix7N0IATNldNTrkvSASp2KR1Yt526HA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "browserslist": "^4.28.2",
- "postcss-selector-parser": "^7.1.2"
- },
- "engines": {
- "node": "^22.11.0 || ^24.11.0 || >=26.0"
- },
- "peerDependencies": {
- "postcss": "^8.5.15"
- }
- },
- "node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/supports-preserve-symlinks-flag": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
- "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/svgo": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.1.tgz",
- "integrity": "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "commander": "^11.1.0",
- "css-select": "^5.1.0",
- "css-tree": "^3.0.1",
- "css-what": "^6.1.0",
- "csso": "^5.0.5",
- "picocolors": "^1.1.1",
- "sax": "^1.5.0"
- },
- "bin": {
- "svgo": "bin/svgo.js"
- },
- "engines": {
- "node": ">=16"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/svgo"
- }
- },
- "node_modules/synckit": {
- "version": "0.11.13",
- "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.13.tgz",
- "integrity": "sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@pkgr/core": "^0.3.6"
- },
- "engines": {
- "node": "^14.18.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/synckit"
- }
- },
- "node_modules/tapable": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz",
- "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
- }
- },
- "node_modules/terser": {
- "version": "5.48.0",
- "resolved": "https://registry.npmjs.org/terser/-/terser-5.48.0.tgz",
- "integrity": "sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "@jridgewell/source-map": "^0.3.3",
- "acorn": "^8.15.0",
- "commander": "^2.20.0",
- "source-map-support": "~0.5.20"
- },
- "bin": {
- "terser": "bin/terser"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/terser/node_modules/commander": {
- "version": "2.20.3",
- "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
- "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/terser/node_modules/source-map-support": {
- "version": "0.5.21",
- "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
- "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "buffer-from": "^1.0.0",
- "source-map": "^0.6.0"
- }
- },
- "node_modules/test-exclude": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
- "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "@istanbuljs/schema": "^0.1.2",
- "glob": "^7.1.4",
- "minimatch": "^3.0.4"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/test-exclude/node_modules/balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/test-exclude/node_modules/brace-expansion": {
- "version": "1.1.15",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
- "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/test-exclude/node_modules/glob": {
- "version": "7.2.3",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
- "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
- "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.1.1",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- },
- "engines": {
- "node": "*"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/test-exclude/node_modules/minimatch": {
- "version": "3.1.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
- "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/thenby": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/thenby/-/thenby-1.4.1.tgz",
- "integrity": "sha512-D5a/bO0KdalOE3q8MlrRmSxjbKZHT3MQmXkJP+r97Vw8MMwOZKOwUSEyTtK7eSMj2y0kyAjpYMRMZmmLw1FtNQ==",
- "dev": true,
- "license": "Apache-2.0"
- },
- "node_modules/tinode-sdk": {
- "version": "0.25.3",
- "resolved": "https://registry.npmjs.org/tinode-sdk/-/tinode-sdk-0.25.3.tgz",
- "integrity": "sha512-5pf6dYU1d5aIkU2t1a7VebLWFZlVLI3vIIoQIm4LbQ9LL1PMaTOlTFHAezQHBQ8UZ6yW1nj4Y3IuLqp5hSfK6w==",
- "license": "Apache-2.0"
- },
- "node_modules/tinyglobby": {
- "version": "0.2.17",
- "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
- "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fdir": "^6.5.0",
- "picomatch": "^4.0.4"
- },
- "engines": {
- "node": ">=12.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/SuperchupuDev"
- }
- },
- "node_modules/tmpl": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
- "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
- "dev": true,
- "license": "BSD-3-Clause"
- },
- "node_modules/to-regex-range": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
- "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-number": "^7.0.0"
- },
- "engines": {
- "node": ">=8.0"
- }
- },
- "node_modules/tslib": {
- "version": "2.8.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
- "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
- "license": "0BSD"
- },
- "node_modules/type-detect": {
- "version": "4.0.8",
- "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
- "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/type-fest": {
- "version": "0.21.3",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
- "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
- "dev": true,
- "license": "(MIT OR CC0-1.0)",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/typescript": {
- "version": "5.9.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
- "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
- "devOptional": true,
- "license": "Apache-2.0",
- "bin": {
- "tsc": "bin/tsc",
- "tsserver": "bin/tsserver"
- },
- "engines": {
- "node": ">=14.17"
- }
- },
- "node_modules/undici-types": {
- "version": "7.18.2",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
- "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
- "license": "MIT"
- },
- "node_modules/unicode-canonical-property-names-ecmascript": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz",
- "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/unicode-match-property-ecmascript": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz",
- "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "unicode-canonical-property-names-ecmascript": "^2.0.0",
- "unicode-property-aliases-ecmascript": "^2.0.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/unicode-match-property-value-ecmascript": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz",
- "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/unicode-property-aliases-ecmascript": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz",
- "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/universalify": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
- "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 10.0.0"
- }
- },
- "node_modules/unrs-resolver": {
- "version": "1.12.2",
- "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.12.2.tgz",
- "integrity": "sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "dependencies": {
- "napi-postinstall": "^0.3.4"
- },
- "funding": {
- "url": "https://opencollective.com/unrs-resolver"
- },
- "optionalDependencies": {
- "@unrs/resolver-binding-android-arm-eabi": "1.12.2",
- "@unrs/resolver-binding-android-arm64": "1.12.2",
- "@unrs/resolver-binding-darwin-arm64": "1.12.2",
- "@unrs/resolver-binding-darwin-x64": "1.12.2",
- "@unrs/resolver-binding-freebsd-x64": "1.12.2",
- "@unrs/resolver-binding-linux-arm-gnueabihf": "1.12.2",
- "@unrs/resolver-binding-linux-arm-musleabihf": "1.12.2",
- "@unrs/resolver-binding-linux-arm64-gnu": "1.12.2",
- "@unrs/resolver-binding-linux-arm64-musl": "1.12.2",
- "@unrs/resolver-binding-linux-loong64-gnu": "1.12.2",
- "@unrs/resolver-binding-linux-loong64-musl": "1.12.2",
- "@unrs/resolver-binding-linux-ppc64-gnu": "1.12.2",
- "@unrs/resolver-binding-linux-riscv64-gnu": "1.12.2",
- "@unrs/resolver-binding-linux-riscv64-musl": "1.12.2",
- "@unrs/resolver-binding-linux-s390x-gnu": "1.12.2",
- "@unrs/resolver-binding-linux-x64-gnu": "1.12.2",
- "@unrs/resolver-binding-linux-x64-musl": "1.12.2",
- "@unrs/resolver-binding-openharmony-arm64": "1.12.2",
- "@unrs/resolver-binding-wasm32-wasi": "1.12.2",
- "@unrs/resolver-binding-win32-arm64-msvc": "1.12.2",
- "@unrs/resolver-binding-win32-ia32-msvc": "1.12.2",
- "@unrs/resolver-binding-win32-x64-msvc": "1.12.2"
- }
- },
- "node_modules/update-browserslist-db": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
- "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/browserslist"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "escalade": "^3.2.0",
- "picocolors": "^1.1.1"
- },
- "bin": {
- "update-browserslist-db": "cli.js"
- },
- "peerDependencies": {
- "browserslist": ">= 4.21.0"
- }
- },
- "node_modules/util-deprecate": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/v8-to-istanbul": {
- "version": "9.3.0",
- "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
- "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "@jridgewell/trace-mapping": "^0.3.12",
- "@types/istanbul-lib-coverage": "^2.0.1",
- "convert-source-map": "^2.0.0"
- },
- "engines": {
- "node": ">=10.12.0"
- }
- },
- "node_modules/walker": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
- "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "makeerror": "1.0.12"
- }
- },
- "node_modules/watchpack": {
- "version": "2.5.2",
- "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.2.tgz",
- "integrity": "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "graceful-fs": "^4.1.2"
- },
- "engines": {
- "node": ">=10.13.0"
- }
- },
- "node_modules/web-vitals": {
- "version": "4.2.4",
- "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz",
- "integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==",
- "license": "Apache-2.0"
- },
- "node_modules/webm-duration-fix": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/webm-duration-fix/-/webm-duration-fix-1.0.4.tgz",
- "integrity": "sha512-kvhmSmEnuohtK+j+mJswqCCM2ViKb9W8Ch0oAxcaeUvpok5CsMORQLnea+CYKDXPG6JH12H0CbRK85qhfeZLew==",
- "license": "ISC",
- "dependencies": {
- "buffer": "^6.0.3",
- "ebml-block": "^1.1.2",
- "events": "^3.3.0",
- "int64-buffer": "^1.0.1"
- }
- },
- "node_modules/webpack": {
- "version": "5.108.3",
- "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.108.3.tgz",
- "integrity": "sha512-hOpaCHmQVVY66IVTjofnH14IgSdmod2aquSGHGuYig/OIdWge01Hk2Wt988DZcwXumFUT4+FvJY5N+ikl8o/ww==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/estree": "^1.0.8",
- "@types/json-schema": "^7.0.15",
- "@webassemblyjs/ast": "^1.14.1",
- "@webassemblyjs/wasm-edit": "^1.14.1",
- "@webassemblyjs/wasm-parser": "^1.14.1",
- "acorn": "^8.16.0",
- "acorn-import-phases": "^1.0.3",
- "browserslist": "^4.28.1",
- "chrome-trace-event": "^1.0.2",
- "enhanced-resolve": "^5.22.2",
- "es-module-lexer": "^2.1.0",
- "eslint-scope": "5.1.1",
- "events": "^3.2.0",
- "graceful-fs": "^4.2.11",
- "loader-runner": "^4.3.2",
- "mime-db": "^1.54.0",
- "minimizer-webpack-plugin": "^5.6.1",
- "neo-async": "^2.6.2",
- "schema-utils": "^4.3.3",
- "tapable": "^2.3.0",
- "watchpack": "^2.5.2",
- "webpack-sources": "^3.5.0"
- },
- "bin": {
- "webpack": "bin/webpack.js"
- },
- "engines": {
- "node": ">=10.13.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
- },
- "peerDependenciesMeta": {
- "webpack-cli": {
- "optional": true
- }
- }
- },
- "node_modules/webpack-cli": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-7.1.0.tgz",
- "integrity": "sha512-pSJ5p5PkXRD88sfCq5Wo+coc42QykwRu5Md0DyESj0rT6PPPA2wTNabpHPKgqH8EMkfTDo3IWx3iiNXMu8XDBQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@discoveryjs/json-ext": "^1.1.0",
- "commander": "^14.0.3",
- "cross-spawn": "^7.0.6",
- "envinfo": "^7.21.0",
- "import-local": "^3.2.0",
- "interpret": "^3.1.1",
- "rechoir": "^0.8.0",
- "webpack-merge": "^6.0.1"
- },
- "bin": {
- "webpack-cli": "bin/cli.js"
- },
- "engines": {
- "node": ">=20.9.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
- },
- "peerDependencies": {
- "js-yaml": "^4.0.0 || ^5.0.0",
- "json5": "^2.2.3",
- "toml": "^3.0.0 || ^4.0.0",
- "webpack": "^5.101.0",
- "webpack-bundle-analyzer": "^4.0.0 || ^5.0.0",
- "webpack-dev-server": "^5.0.0"
- },
- "peerDependenciesMeta": {
- "js-yaml": {
- "optional": true
- },
- "json5": {
- "optional": true
- },
- "toml": {
- "optional": true
- },
- "webpack-bundle-analyzer": {
- "optional": true
- },
- "webpack-dev-server": {
- "optional": true
- }
- }
- },
- "node_modules/webpack-cli/node_modules/commander": {
- "version": "14.0.3",
- "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz",
- "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=20"
- }
- },
- "node_modules/webpack-merge": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz",
- "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "clone-deep": "^4.0.1",
- "flat": "^5.0.2",
- "wildcard": "^2.0.1"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/webpack-sources": {
- "version": "3.5.0",
- "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.5.0.tgz",
- "integrity": "sha512-HPuy+uuoTCaaoEoI1LQ3JN9+vrPBvEesnnX1jADHy728cHSMlq4wUc4afYqahq2B1mhQVZxCXOkNTnXltr+2vQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10.13.0"
- }
- },
- "node_modules/websocket-driver": {
- "version": "0.7.5",
- "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.5.tgz",
- "integrity": "sha512-ZL2+3c7kMBdIRCMz6l8jQMHyGVxj+UL+xVk74Ombiciboca8rHa15L86B19E5oh1pL9Ii/uj54gtsIrZGMo6zA==",
- "license": "Apache-2.0",
- "dependencies": {
- "http-parser-js": ">=0.5.1",
- "safe-buffer": ">=5.1.0",
- "websocket-extensions": ">=0.1.1"
- },
- "engines": {
- "node": ">=0.8.0"
- }
- },
- "node_modules/websocket-extensions": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz",
- "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=0.8.0"
- }
- },
- "node_modules/which": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
- "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "isexe": "^2.0.0"
- },
- "bin": {
- "node-which": "bin/node-which"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/wildcard": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz",
- "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/wrap-ansi": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
- "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
- "node_modules/wrap-ansi-cjs": {
- "name": "wrap-ansi",
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
- "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
- "node_modules/wrappy": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/write-file-atomic": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz",
- "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "imurmurhash": "^0.1.4",
- "signal-exit": "^4.0.1"
- },
- "engines": {
- "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
- }
- },
- "node_modules/write-file-atomic/node_modules/signal-exit": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
- "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/y18n": {
- "version": "5.0.8",
- "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
- "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
- "license": "ISC",
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/yaml": {
- "version": "2.9.0",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
- "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "yaml": "bin.mjs"
- },
- "engines": {
- "node": ">= 14.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/eemeli"
- }
- },
- "node_modules/yargs": {
- "version": "17.7.3",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz",
- "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==",
- "license": "MIT",
- "dependencies": {
- "cliui": "^8.0.1",
- "escalade": "^3.1.1",
- "get-caller-file": "^2.0.5",
- "require-directory": "^2.1.1",
- "string-width": "^4.2.3",
- "y18n": "^5.0.5",
- "yargs-parser": "^21.1.1"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/yargs-parser": {
- "version": "21.1.1",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
- "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
- "license": "ISC",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/yocto-queue": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
- "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- }
- }
-}
diff --git a/package.json b/package.json
deleted file mode 100644
index e26da8cb..00000000
--- a/package.json
+++ /dev/null
@@ -1,115 +0,0 @@
-{
- "name": "tinode-webapp",
- "description": "Tinode messenger for the web",
- "version": "0.25.3",
- "repository": {
- "type": "git",
- "url": "git+https://github.com/tinode/webapp.git"
- },
- "scripts": {
- "start": "echo 'Read installation instructions'",
- "build": "npm run vers && npm run build:css && npm run extract && npm run build:i18n && npm run build:prod && npm run build:dev",
- "clean": "rm umd/* && rm css/base.min.css && rm src/i18n.min/*",
- "build:css": "postcss css/base.css > css/base.min.css",
- "build:dev": "webpack --mode development",
- "build:prod": "webpack --mode production",
- "build:i18n": "node scripts/i18n.js",
- "i18n:missing": "node scripts/i18n.js missing",
- "i18n:obsolete": "node scripts/i18n.js obsolete",
- "vers": "node scripts/gen-version.js",
- "extract": "formatjs extract \"src/**/*.{js,jsx}\" --out-file src/i18n/ex/base-en.json --id-interpolation-pattern '[sha512:contenthash:base64:6]'",
- "compile": "formatjs compile",
- "test": "jest"
- },
- "browserslist": [
- "defaults"
- ],
- "overrides": {
- "@babel/core": "^8.0.0"
- },
- "files": [
- "umd/*.js",
- "umd/*.map",
- "audio/*.m4a",
- "css/base.css",
- "css/base.min.css",
- "img/*.png",
- "img/*.svg",
- "img/*.jpeg",
- "img/bkg/*.jpg",
- "img/bkg/*.png",
- "img/bkg/*.json",
- "index-dev.html",
- "index.html",
- "manifest.json",
- "service-worker.js",
- "version.js"
- ],
- "keywords": [
- "chat",
- "instant-messenger",
- "message",
- "messenger",
- "react",
- "web"
- ],
- "locales": [
- "ar",
- "de",
- "en",
- "es",
- "fr",
- "it",
- "ko",
- "ro",
- "ru",
- "th",
- "uk",
- "vi",
- "zh",
- "zh-TW"
- ],
- "main": "umd/index.prod.js",
- "bugs": {
- "url": "https://github.com/tinode/webapp/issues"
- },
- "homepage": "https://github.com/tinode/chat",
- "email": "info@tinode.co",
- "author": "Tinode Authors ",
- "license": "Apache-2.0",
- "dependencies": {
- "firebase": "^12.15.0",
- "jsqr": "^1.4.0",
- "libphonenumber-js": "^1.10.40",
- "pinyin-pro": "^3.29.3",
- "qrcodejs": "^1.0.0",
- "react": "^18.3.0",
- "react-dom": "^18.3.0",
- "react-intl": "^7.0.0",
- "tinode-sdk": "^0.25.3",
- "webm-duration-fix": "^1.0.4"
- },
- "devDependencies": {
- "@babel/core": "^8.0.0",
- "@babel/preset-env": "^8.0.0",
- "@babel/preset-react": "^8.0.0",
- "@formatjs/cli": "^6.1.3",
- "babel-loader": "^10.0.0",
- "babel-plugin-formatjs": "^11.0.0",
- "browserslist": "^4.21.10",
- "copy-webpack-plugin": "^14.0.0",
- "cssnano": "^8.0.0",
- "glob": "^13.0.0",
- "jest": "^30.0.0",
- "postcss": "^8.4.27",
- "postcss-cli": "^11.0.0",
- "webpack": "^5.88.2",
- "webpack-cli": "^7.0.0"
- },
- "allowScripts": {
- "@firebase/util@1.15.1": true,
- "fsevents@2.3.3": true,
- "protobufjs@7.6.5": true,
- "unrs-resolver@1.12.2": true
- }
-}
diff --git a/postcss.config.js b/postcss.config.js
deleted file mode 100644
index ffb2269a..00000000
--- a/postcss.config.js
+++ /dev/null
@@ -1,7 +0,0 @@
-module.exports = {
- plugins: [
- require('cssnano')({
- preset: 'default',
- }),
- ],
-};
diff --git a/push.md b/push.md
deleted file mode 100644
index 3e71e57a..00000000
--- a/push.md
+++ /dev/null
@@ -1,11 +0,0 @@
-# Push notifications
-
-If you want to use the app with your own server and want web push notification to work you have to set them up:
-
-* Register at https://firebase.google.com/, set up the project if you have not done so already.
-* Follow instructions to create a web application https://support.google.com/firebase/answer/9326094 in your project.
-* Follow instructions at https://support.google.com/firebase/answer/7015592 to get a Firebase configuration object ("Firebase SDK snippet").
-* Locate `firebase-init.js` in the root folder of your copy of TinodeWeb app. Copy-paste the following keys from the configuration object to `firebase-init.js`: `apiKey`, `messagingSenderId`, `projectId`, `appId` (you may copy all keys).
-* Copy `Web Push certificate` (Project Settings -> Cloud Messaging -> Web configuration -> Web Push certificates) to `messagingVapidKey` field in `firebase-init.js`.
-* Double check that `firebase-init.js` contains the following keys: `apiKey`, `messagingSenderId`, `projectId`, `appId`, `messagingVapidKey`. The file may contain other optional keys.
-* Copy Google-provided server key to `tinode.conf`, see details [here](https://github.com/tinode/chat/blob/master/docs/faq.md#q-how-to-setup-fcm-push-notifications).
diff --git a/scripts/gen-version.js b/scripts/gen-version.js
deleted file mode 100644
index 45eb47e2..00000000
--- a/scripts/gen-version.js
+++ /dev/null
@@ -1,18 +0,0 @@
-const fs = require('fs');
-const version = require('../package.json').version;
-
-function writeVersion(fileName, useExport) {
- fs.writeFile(fileName,
- '// This is a generated file. Don\'t edit.\n\n'+
- `${useExport ? 'export ' : ''}const PACKAGE_VERSION = "${version}";\n`,
- { flag: 'w' },
- (err) => {
- if (err) {
- return console.error(err);
- }
- }
- );
-}
-
-writeVersion('./src/version.js', true);
-writeVersion('./version.js', false);
diff --git a/scripts/i18n.js b/scripts/i18n.js
deleted file mode 100644
index 9c5eb73d..00000000
--- a/scripts/i18n.js
+++ /dev/null
@@ -1,127 +0,0 @@
-// 1. Updates translations files with new messages, mark no longer needed translations as obsolete.
-// 2. Takes all translations and groups them into a single file.
-// 3. When called with parameters 'missing' or 'obsolete' writes missing and obsolete translations to console.
-
-const fs = require('fs');
-const path = require('path');
-const globSync = require('glob').sync;
-
-const EXTRACTED_STRINGS = './src/i18n/ex/base-en.json';
-const BASE_LANG = 'en';
-const LANG_PATTERN = './src/i18n/*.json';
-const LANG_PATTERN_ONE = './src/i18n/%s.json';
-const OUTPUT_PATTERN = './src/i18n.min/%s.json';
-
-const args = process.argv.slice(2);
-
-function getLocaleFromFileName(filename) {
- const parts = filename.split(path.sep);
- return parts[parts.length - 1].split('.json')[0];
-}
-
-function printf(str) {
- const args = [].slice.call(arguments, 1);
- let i = 0;
- return str.replace(/%s/g, () => args[i++]);
-}
-
-// Called with one or two parameters?
-if (args[0] == 'missing' || args[0] == 'obsolete') {
- // Extract missing or obsolete translations as requested.
- // Read either all or just one translation.
- const pattern = args[1] ? printf(LANG_PATTERN_ONE, args[1]) : LANG_PATTERN;
- const extracted = globSync(pattern)
- .map((filename) => {
- const locale = getLocaleFromFileName(filename);
- const translated = JSON.parse(fs.readFileSync(filename, 'utf8'));
-
- const dest = {};
- // Copy messages while skipping missing and obsolete ones.
- for (let key in translated) {
- if ((args[0] == 'missing' && translated[key].missing) ||
- (args[0] == 'obsolete' && translated[key].obsolete)) {
- dest[key] = translated[key];
- }
- }
-
- return { [locale]: dest };
- })
- .reduce((collection, descriptors) => {
- return { ...collection, ...descriptors };
- }, {});
-
- console.log(JSON.stringify(extracted, null, 2)+"\n");
- return;
-}
-
-// The formatjs/cli saves extracted messages into a file {'message_id': {defaultMessage: 'default message text', description: '...'}, ...}.
-// Rewrite them into a single file en.json {'message_id': 'default message text', }.
-
-// Read master-set of messages in default language.
-const baseMessages = JSON.parse(fs.readFileSync(EXTRACTED_STRINGS, 'utf8'));
-
-// Find messages present in baseMessages but missing in translations and vice versa.
-// Mark missing or obsolete messages as such.
-globSync(LANG_PATTERN)
- .map((filename) => {
- const translated = JSON.parse(fs.readFileSync(filename, 'utf8'));
- const locale = getLocaleFromFileName(filename);
-
- // Refresh existing entries with possible new descriptions and default messages, add missing.
- Object.entries(baseMessages).forEach((ent) => {
- const [key, value] = ent;
- let translation = translated[key] ? translated[key].translation : "";
- if (!translation && locale == BASE_LANG) {
- translation = value.defaultMessage;
- }
-
- translated[key] = {
- translation: translation,
- defaultMessage: value.defaultMessage,
- description: value.description,
- missing: !translation,
- obsolete: false
- };
- });
-
- // Find no longer needed messages.
- Object.entries(translated).forEach((ent) => {
- const [key, value] = ent;
- if (!baseMessages[key] && translated[key]) {
- // Obsolete, no longer missing.
- translated[key].missing = !translated[key].translation;
- translated[key].obsolete = true;
- }
- });
-
- fs.writeFileSync(filename, JSON.stringify(translated, null, 2)+"\n");
- });
-
-// Legacy: Combine all translations into a single json {"lang_1": {...}, "lang_2: {...}"}
-// while removing messages marked as missing or obsolete.
-// New way: Emit one file per language with all unnecessary data stripped.
-
-// Read all translations.
-const messages = globSync(LANG_PATTERN)
- .map((filename) => {
- const locale = getLocaleFromFileName(filename);
- const translated = JSON.parse(fs.readFileSync(filename, 'utf8'));
-
- const dest = {};
- // Copy messages while skipping missing and obsolete ones.
- for (let key in translated) {
- if (!translated[key].missing && !translated[key].obsolete) {
- dest[key] = translated[key].translation;
- }
- }
-
- return { [locale]: dest };
- })
- .reduce((collection, descriptors) => {
- return { ...collection, ...descriptors };
- }, {});
-
-// Write minified translation files, one per language.
-for (let lang in messages) {
- fs.writeFileSync(printf(OUTPUT_PATTERN, lang), JSON.stringify(messages[lang]));
-}
diff --git a/service-worker.js b/service-worker.js
deleted file mode 100644
index e6068a12..00000000
--- a/service-worker.js
+++ /dev/null
@@ -1,210 +0,0 @@
-// Must be located at the root.
-importScripts('https://cdn.jsdelivr.net/npm/firebase@9.14.0/firebase-app-compat.js');
-importScripts('https://cdn.jsdelivr.net/npm/firebase@9.14.0/firebase-messaging-compat.js');
-importScripts('firebase-init.js');
-importScripts('version.js');
-
-// Channel to notify the webapp. There is no BroadcastChannel in Safari.
-const webAppChannel = (typeof BroadcastChannel == 'function') ?
- new BroadcastChannel('tinode-sw') : null;
-
-// Basic internationalization.
-const i18n = {
- 'ar': {
- 'new_message': "رسالة جديدة",
- 'new_chat': "محادثة جديدة",
- },
- 'de': {
- 'new_message': "Neue Nachricht",
- 'new_chat': "Neuer Chat",
- },
- 'en': {
- 'new_message': "New message",
- 'new_chat': "New chat",
- },
- 'fr': {
- 'new_message': "Nouveau message",
- 'new_chat': "Nouvelle conversation",
- },
- 'es': {
- 'new_message': "Nuevo mensaje",
- 'new_chat': "Nueva conversación",
- },
- 'it': {
- 'new_message': "Nuovo Messaggio",
- 'new_chat': "Nuova Chat",
- },
- 'ko': {
- 'new_message': "새로운 메시지",
- 'new_chat': "새로운 채팅",
- },
- 'pt': {
- 'new_message': "Nova Mensagem",
- 'new_chat': "Nova Conversa",
- },
- 'ro': {
- 'new_message': "Mesaj nou",
- 'new_chat': "Chat nou",
- },
- 'ru': {
- 'new_message': "Новое сообщение",
- 'new_chat': "Новый чат",
- },
- 'th': {
- 'new_message': "ข้อความใหม่",
- 'new_chat': "แชทใหม่",
- },
- 'uk': {
- 'new_message': "Нове повідомлення",
- 'new_chat': "Новий чат",
- },
- 'vi': {
- 'new_message': "Tin nhắn mới",
- 'new_chat': "Cuộc trò chuyện mới",
- },
- 'zh': {
- 'new_message': "新讯息",
- 'new_chat': "新聊天",
- },
- 'zh-TW': {
- 'new_message': "新訊息",
- 'new_chat': "新聊天",
- }
-};
-
-self.i18nMessage = function (id) {
- if (!id) {
- return null;
- }
- // Choose translations: given something like 'de-CH', try 'de-CH' then 'de' then 'en'.
- const lang = i18n[self.locale] || i18n[self.baseLocale] || i18n['en'];
- // Try finding string by id in the specified language, if missing try English, otherwise use the id itself
- // as the last resort.
- return lang[id] || i18n['en'][id] || id;
-}
-
-firebase.initializeApp(FIREBASE_INIT);
-const fbMessaging = firebase.messaging();
-
-// This method shows the push notifications while the window is in background.
-fbMessaging.onBackgroundMessage(payload => {
- // Notify webapp that a message was received.
- if (webAppChannel) {
- webAppChannel.postMessage(payload.data);
- }
-
- if (payload.data.silent == 'true') {
- // No need to show anything.
- return;
- }
-
- const titles = { 'msg': 'new_message', 'sub': 'new_chat' };
- const pushType = payload.data.what || 'msg';
- const title = payload.data.title || self.i18nMessage(titles[pushType]);
- if (title) {
- const options = {
- body: payload.data.content || '', // TODO: content for 'sub' should be topic's or user's title.
- icon: '/img/logo96.png', // TODO: use topic's or user's avatar (would have to fetch for 'sub', read from db for 'msg').
- badge: '/img/badge96.png',
- tag: payload.data.topic || undefined,
- data: {
- topic: payload.data.topic
- }
- };
- return self.registration.showNotification(title, options);
- }
-});
-
-// Update service worker immediately for both the current client
-// and all other active clients.
-self.addEventListener('install', _ => {
- self.skipWaiting();
-});
-
-// This code handles a click on notification: takes
-// the user to the browser tab with the chat or opens a new tab.
-self.addEventListener('notificationclick', event => {
- const data = event.notification.data;
- event.notification.close();
- if (!data) {
- console.info("Missing 'data' in notification", event.notification);
- return;
- }
-
- // '_' mirrors the placeholder Navigation.composeUrl (src/lib/navigation.js) substitutes for an
- // empty path[0] ("no sidepanel view, just open this topic") -- a real empty first segment would
- // make the path start with '//', which pushState/URL resolve as a protocol-relative reference
- // (host becomes "data.topic") rather than a same-origin path. This file runs outside the webpack
- // bundle (importScripts, not an ES module) so it can't import the constant; kept in sync by hand.
- const urlPath = '/_/' + data.topic;
-
- event.waitUntil(self.clients.matchAll({
- type: 'window',
- includeUncontrolled: true
- })
- .then(windowClients => {
- let anyClient = null;
- for (let i = 0; i < windowClients.length; i++) {
- const url = new URL(windowClients[i].url);
- if (url.pathname.includes(data.topic)) {
- // Found the Tinode tab with the right topic open.
- return windowClients[i].focus();
- } else {
- // This will be the least recently used tab.
- anyClient = windowClients[i];
- }
- }
-
- // Found tab with Tinode on a different topic,
- // navigate to the right topic.
- if (anyClient) {
- const url = new URL(anyClient.url);
- url.pathname = urlPath;
- return anyClient.focus().then(thisClient => {
- return thisClient.navigate(url);
- });
- }
-
- // Did not find a Tinode browser tab. Open one.
- const url = new URL(self.location.origin);
- url.pathname = urlPath;
- return clients.openWindow(url);
- }));
-});
-
-// This is needed for 'Add to Home Screen'.
-self.addEventListener('fetch', event => {
- if (event.request.method != 'GET') {
- return;
- }
-
- event.respondWith((async _ => {
- // Try to find the response in the cache.
- const cache = await caches.open(PACKAGE_VERSION);
-
- const reqUrl = new URL(event.request.url);
- // Using ignoreSearch=true to read cached images and docs despite different auth signatures.
- const cachedResponse = await cache.match(event.request, { ignoreSearch: (self.location.origin == reqUrl.origin) });
- if (cachedResponse) {
- return cachedResponse;
- }
- // Not found in cache.
- const response = await fetch(event.request);
- if (!response || response.status != 200 || response.type != 'basic') {
- return response;
- }
- if (reqUrl.protocol == 'http:' || reqUrl.protocol == 'https:') {
- await cache.put(event.request, response.clone());
- }
- return response;
- })());
-});
-
-// This code gets the human language from the webapp.
-self.addEventListener('message', event => {
- const data = JSON.parse(event.data);
-
- // The locale is used for selecting strings in an appropriate language.
- self.locale = data.locale || '';
- self.baseLocale = self.locale.toLowerCase().split(/[-_]/)[0];
-});
diff --git a/src/config.js b/src/config.js
deleted file mode 100644
index c1cdaf1a..00000000
--- a/src/config.js
+++ /dev/null
@@ -1,167 +0,0 @@
-import { PACKAGE_VERSION } from './version.js';
-
-// Version-independent name of this application, used for Firebase app name and shown to users
-// (about page, youth-mode copy).
-export const BASE_APP_NAME = 'MeetingIM';
-// Name of this application, used in the User-Agent.
-export const APP_NAME = BASE_APP_NAME + '/' + (PACKAGE_VERSION || '0.25');
-
-// API key. Use https://github.com/tinode/chat/tree/master/keygen to generate your own.
-// Injected at build time via webpack DefinePlugin from the TINODE_API_KEY env var
-// (see webpack.config.js and .env.local). Falls back to the public sample key when unset,
-// which only works against a backend still using the matching sample API_KEY_SALT.
-export const API_KEY = process.env.TINODE_API_KEY || 'AQEAAAABAAD_rAp4DJh05a1HAwFT3A6K';
-
-// The array of possible hosts to connect to.
-export const KNOWN_HOSTS = {hosted: 'web.118im.com', local: 'localhost:6060'};
-
-// Default host name and port to connect to.
-export const DEFAULT_HOST = KNOWN_HOSTS.hosted;
-
-// Enable console logging.
-export const LOGGING_ENABLED = true;
-
-// Minimum time between two keypress notifications, milliseconds.
-export const KEYPRESS_DELAY = 3_000;
-// Delay before sending a read notification, milliseconds.
-export const READ_DELAY = 1_500;
-
-// The default shortest allowed tag length. Matches the value on the server.
-export const MIN_TAG_LENGTH = 2;
-// The default greatest allowed tag length. Matches the value on the server.
-export const MAX_TAG_LENGTH = 96;
-// The default maximum number of tags allowed. Matches the value on the server.
-export const MAX_TAG_COUNT = 16;
-
-// Access mode for P2P subscriptions initiated by the current user.
-export const DEFAULT_P2P_ACCESS_MODE = 'JRWPS';
-// Access mode for new group topics created by the current user.
-export const NEW_GRP_ACCESS_MODE = 'JRWPSAO';
-// Access mode for a channel.
-export const CHANNEL_ACCESS_MODE = 'JR';
-
-// Access mode for no access.
-export const NO_ACCESS_MODE = 'N';
-
-// Mediaquery breakpoint between desktop and mobile, in px. Should match the value
-// in @media (max-size: 640px) in base.css
-export const MEDIA_BREAKPOINT = 640;
-// Size of css 'rem' unit in pixels. Default 1rem = 10pt = 13px.
-export const REM_SIZE = 13;
-
-// Size of the avatar image: when image dimensions are greater or the image is not square, it's resized to
-// a square of this size or less.
-export const AVATAR_SIZE = 384;
-
-// Maximum size of an avatar in bytes for sending in-band. Bigger avatars will be sent out of band (as uploads).
-export const MAX_AVATAR_BYTES = 4096;
-
-// Size of the broken_image shown in MessagesView
-export const BROKEN_IMAGE_SIZE = 32;
-
-// Size of video preview when dimensions are missing.
-export const NO_DIMENSIONS_VIDEO = 128;
-
-// Number of chat messages to fetch in one call.
-export const MESSAGES_PAGE = 24;
-
-// Default maximum in-band (included directly into the message) attachment size which fits into
-// a message of 256K in size. Used when the server-provided value is unavailable. The actual
-// binary size of the attachment should be smaller due to base64 encoding expansion and some overhead,
-// for instance 1024 bytes.
-// Increase this limit to a greater value in production, if desired. Also increase
-// max_message_size in the server config.
-export const MAX_INBAND_ATTACHMENT_SIZE = 262_144;
-
-// Default absolute maximum attachment size to be used with the server = 8MB.
-// Used when the server-provided value is unavailable. Increase to something like 100MB in production.
-export const MAX_EXTERN_ATTACHMENT_SIZE = 1 << 23;
-
-// Maximum allowed linear dimension (pixels) of an image sent inline. Larger images will be shrunk
-// to make the larger dimension fit under this size. You may want to adjust it to 1600 or
-// 2400 for production.
-export const MAX_IMAGE_DIM = 1024;
-
-// Linear dimensions of image preview: shrink image under this size for previews.
-export const IMAGE_PREVIEW_DIM = 64;
-
-// Linear dimensions of video screencapture: shrink image under this size for previews.
-export const VIDEO_PREVIEW_DIM = 96;
-
-// Linear dimensions of image thumbnail: shrink image under this size for thumbnails in reply quote previews.
-export const IMAGE_THUMBNAIL_DIM = 36;
-// Width of video thumbnail: allow it to be wider, but not taller. Use IMAGE_THUMBNAIL_DIM for height.
-export const VIDEO_THUMBNAIL_WIDTH = 48;
-
-// Maximum number of online users to be shown in a topic title bar. Others will be
-// hidden under "+X more"
-export const MAX_ONLINE_IN_TOPIC = 4;
-
-// Maximum length of user name, topic title, and private comment.
-export const MAX_TITLE_LENGTH = 60;
-// Maximum length of topic description.
-export const MAX_TOPIC_DESCRIPTION_LENGTH = 360;
-// Maximum length of user name when displaying a video call.
-export const MAX_PEER_TITLE_LENGTH = 24;
-
-// Length of message previews in chat list, in characters.
-export const MESSAGE_PREVIEW_LENGTH = 80;
-
-// Length of a quote in quoted reply (for outgoing messages).
-export const QUOTED_REPLY_LENGTH = 30;
-// Length of a preview of a forwarded message.
-export const FORWARDED_PREVIEW_LENGTH = 84;
-// Length of preview for a message being edited.
-export const EDIT_PREVIEW_LENGTH = 48;
-
-// Minimum duration of an audio recording in milliseconds (2 sec).
-export const MIN_DURATION = 2_000;
-// Maximum duration of an audio recording in milliseconds (10 min).
-export const MAX_DURATION = 600_000;
-
-// No real destination exists for these (this is a private multi-tenant product, not the open-source
-// Tinode project) -- null rather than the upstream project's own contact/legal pages, which acc-support-view.jsx
-// renders as inert text instead of a link (same treatment auth-onboarding-view.jsx already gives its
-// own placeholder 用户协议/隐私政策 links).
-export const LINK_CONTACT_US = null;
-export const LINK_PRIVACY_POLICY = null;
-export const LINK_TERMS_OF_SERVICE = null;
-
-// When user starts a P2P topic, automatic subscription does not
-// happen until the first message. Setting this option to true enables
-// immediate subscription.
-export const IMMEDIATE_P2P_SUBSCRIPTION = false;
-
-// URL schemes permitted to appear in links (i.e. no javascript: scheme).
-export const CLICKABLE_URL_SCHEMES = ['http', 'https', 'ftp', 'ftps'];
-
-// Size of the QR code in the app.
-export const QRCODE_SIZE = 128;
-
-// How much time has to pass to consider computer awoken from sleep (80 sec).
-// Chrome throttles ticker events in inactive tabs to once per minute. This
-// value ensures that these once-a-minute events do not trigger a reconnect.
-export const WAKE_UP_TIMEOUT = 80000;
-// Timer interval between checks if computer woke up from sleep (1 sec).
-export const WAKE_UP_TICK = 1000;
-
-// Swipe distance to register as a gesture, in pixels.
-export const MIN_SWIPE_DISTANCE = REM_SIZE * 3;
-
-// Self-topic avatar as an SVG icon encoded as data URI
-export const SELF_AVATAR_URI = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48c3ZnIHZpZXdCb3g9IjAgMCAxNyAxNyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMy41NjIgMXYxNS40NTlsNC42ODYtMy4yNyA0Ljc1MiAzLjI2di0xNS40NDloLTkuNDM4ek0xMiAxNC41NTFsLTMuNzU2LTIuNTc4LTMuNjgxIDIuNTY4di0xMi41NDFoNy40Mzd2MTIuNTUxeiIgZmlsbD0iIzU1NTU1NSIgLz48L3N2Zz4=' // eslint-disable-line max-len
-
-// Toast display duration, milliseconds.
-export const TOAST_DURATION = 3_000;
-
-// Initial/default color scheme.
-export const DEFAULT_COLOR_SCHEME = 'auto';
-
-// Default text size in points.
-export const DEFAULT_TEXT_SIZE = 10;
-
-// MIME type for the legacy Drafty form-response data.
-export const DRAFTY_FR_MIME_TYPE_LEGACY = 'application/json'; // Remove in 2026.
-
-// Path to wallpaper images.
-export const WALLPAPER_DEFAULTS = {type: 'patt', index: 0, path:'img/bkg/'};
diff --git a/src/constants.js b/src/constants.js
deleted file mode 100644
index b0015dea..00000000
--- a/src/constants.js
+++ /dev/null
@@ -1,9 +0,0 @@
-// Video call states.
-export const CALL_STATE_NONE = 0;
-export const CALL_STATE_OUTGOING_INITATED = 1;
-export const CALL_STATE_INCOMING_RECEIVED = 2;
-export const CALL_STATE_IN_PROGRESS = 3;
-
-// Video call message headers representing call state.
-export const CALL_HEAD_STARTED = 'started';
-
diff --git a/src/dcodes.json b/src/dcodes.json
deleted file mode 100644
index 63482f57..00000000
--- a/src/dcodes.json
+++ /dev/null
@@ -1 +0,0 @@
-[{"dial":"93","code":"AF"},{"dial":"355","code":"AL"},{"dial":"213","code":"DZ"},{"dial":"1-684","code":"AS"},{"dial":"376","code":"AD"},{"dial":"244","code":"AO"},{"dial":"1-264","code":"AI"},{"dial":"672","code":"AQ"},{"dial":"1-268","code":"AG"},{"dial":"54","code":"AR"},{"dial":"374","code":"AM"},{"dial":"297","code":"AW"},{"dial":"61","code":"AU"},{"dial":"43","code":"AT"},{"dial":"994","code":"AZ"},{"dial":"1-242","code":"BS"},{"dial":"973","code":"BH"},{"dial":"880","code":"BD"},{"dial":"1-246","code":"BB"},{"dial":"375","code":"BY"},{"dial":"32","code":"BE"},{"dial":"501","code":"BZ"},{"dial":"229","code":"BJ"},{"dial":"1-441","code":"BM"},{"dial":"975","code":"BT"},{"dial":"591","code":"BO"},{"dial":"387","code":"BA"},{"dial":"267","code":"BW"},{"dial":"55","code":"BR"},{"dial":"246","code":"IO"},{"dial":"1-284","code":"VG"},{"dial":"673","code":"BN"},{"dial":"359","code":"BG"},{"dial":"226","code":"BF"},{"dial":"257","code":"BI"},{"dial":"855","code":"KH"},{"dial":"237","code":"CM"},{"dial":"1","code":"CA"},{"dial":"238","code":"CV"},{"dial":"1-345","code":"KY"},{"dial":"236","code":"CF"},{"dial":"235","code":"TD"},{"dial":"56","code":"CL"},{"dial":"86","code":"CN"},{"dial":"61","code":"CX"},{"dial":"61","code":"CC"},{"dial":"57","code":"CO"},{"dial":"269","code":"KM"},{"dial":"682","code":"CK"},{"dial":"506","code":"CR"},{"dial":"385","code":"HR"},{"dial":"53","code":"CU"},{"dial":"599","code":"CW"},{"dial":"357","code":"CY"},{"dial":"420","code":"CZ"},{"dial":"243","code":"CD"},{"dial":"45","code":"DK"},{"dial":"253","code":"DJ"},{"dial":"1-767","code":"DM"},{"dial":"1-809, 1-829, 1-849","code":"DO"},{"dial":"670","code":"TL"},{"dial":"593","code":"EC"},{"dial":"20","code":"EG"},{"dial":"503","code":"SV"},{"dial":"240","code":"GQ"},{"dial":"291","code":"ER"},{"dial":"372","code":"EE"},{"dial":"251","code":"ET"},{"dial":"500","code":"FK"},{"dial":"298","code":"FO"},{"dial":"679","code":"FJ"},{"dial":"358","code":"FI"},{"dial":"33","code":"FR"},{"dial":"689","code":"PF"},{"dial":"241","code":"GA"},{"dial":"220","code":"GM"},{"dial":"995","code":"GE"},{"dial":"49","code":"DE"},{"dial":"233","code":"GH"},{"dial":"350","code":"GI"},{"dial":"30","code":"GR"},{"dial":"299","code":"GL"},{"dial":"1-473","code":"GD"},{"dial":"1-671","code":"GU"},{"dial":"502","code":"GT"},{"dial":"44-1481","code":"GG"},{"dial":"224","code":"GN"},{"dial":"245","code":"GW"},{"dial":"592","code":"GY"},{"dial":"509","code":"HT"},{"dial":"504","code":"HN"},{"dial":"852","code":"HK"},{"dial":"36","code":"HU"},{"dial":"354","code":"IS"},{"dial":"91","code":"IN"},{"dial":"62","code":"ID"},{"dial":"98","code":"IR"},{"dial":"964","code":"IQ"},{"dial":"353","code":"IE"},{"dial":"44-1624","code":"IM"},{"dial":"972","code":"IL"},{"dial":"39","code":"IT"},{"dial":"225","code":"CI"},{"dial":"1-876","code":"JM"},{"dial":"81","code":"JP"},{"dial":"44-1534","code":"JE"},{"dial":"962","code":"JO"},{"dial":"7, 997","code":"KZ"},{"dial":"254","code":"KE"},{"dial":"686","code":"KI"},{"dial":"383","code":"XK"},{"dial":"965","code":"KW"},{"dial":"996","code":"KG"},{"dial":"856","code":"LA"},{"dial":"371","code":"LV"},{"dial":"961","code":"LB"},{"dial":"266","code":"LS"},{"dial":"231","code":"LR"},{"dial":"218","code":"LY"},{"dial":"423","code":"LI"},{"dial":"370","code":"LT"},{"dial":"352","code":"LU"},{"dial":"853","code":"MO"},{"dial":"389","code":"MK"},{"dial":"261","code":"MG"},{"dial":"265","code":"MW"},{"dial":"60","code":"MY"},{"dial":"960","code":"MV"},{"dial":"223","code":"ML"},{"dial":"356","code":"MT"},{"dial":"692","code":"MH"},{"dial":"222","code":"MR"},{"dial":"230","code":"MU"},{"dial":"262","code":"YT"},{"dial":"52","code":"MX"},{"dial":"691","code":"FM"},{"dial":"373","code":"MD"},{"dial":"377","code":"MC"},{"dial":"976","code":"MN"},{"dial":"382","code":"ME"},{"dial":"1-664","code":"MS"},{"dial":"212","code":"MA"},{"dial":"258","code":"MZ"},{"dial":"95","code":"MM"},{"dial":"264","code":"NA"},{"dial":"674","code":"NR"},{"dial":"977","code":"NP"},{"dial":"31","code":"NL"},{"dial":"599","code":"CW"},{"dial":"687","code":"NC"},{"dial":"64","code":"NZ"},{"dial":"505","code":"NI"},{"dial":"227","code":"NE"},{"dial":"234","code":"NG"},{"dial":"683","code":"NU"},{"dial":"850","code":"KP"},{"dial":"1-670","code":"MP"},{"dial":"47","code":"NO"},{"dial":"968","code":"OM"},{"dial":"92","code":"PK"},{"dial":"680","code":"PW"},{"dial":"970","code":"PS"},{"dial":"507","code":"PA"},{"dial":"675","code":"PG"},{"dial":"595","code":"PY"},{"dial":"51","code":"PE"},{"dial":"63","code":"PH"},{"dial":"64","code":"PN"},{"dial":"48","code":"PL"},{"dial":"351","code":"PT"},{"dial":"1-787, 1-939","code":"PR"},{"dial":"974","code":"QA"},{"dial":"242","code":"CG"},{"dial":"262","code":"RE"},{"dial":"40","code":"RO"},{"dial":"7","code":"RU"},{"dial":"250","code":"RW"},{"dial":"590","code":"BL"},{"dial":"290","code":"SH"},{"dial":"1-869","code":"KN"},{"dial":"1-758","code":"LC"},{"dial":"590","code":"MF"},{"dial":"508","code":"PM"},{"dial":"1-784","code":"VC"},{"dial":"685","code":"WS"},{"dial":"378","code":"SM"},{"dial":"239","code":"ST"},{"dial":"966","code":"SA"},{"dial":"221","code":"SN"},{"dial":"381","code":"RS"},{"dial":"248","code":"SC"},{"dial":"232","code":"SL"},{"dial":"65","code":"SG"},{"dial":"1-721","code":"SX"},{"dial":"421","code":"SK"},{"dial":"386","code":"SI"},{"dial":"677","code":"SB"},{"dial":"252","code":"SO"},{"dial":"27","code":"ZA"},{"dial":"82","code":"KR"},{"dial":"211","code":"SS"},{"dial":"34","code":"ES"},{"dial":"94","code":"LK"},{"dial":"249","code":"SD"},{"dial":"597","code":"SR"},{"dial":"47","code":"SJ"},{"dial":"268","code":"SZ"},{"dial":"46","code":"SE"},{"dial":"41","code":"CH"},{"dial":"963","code":"SY"},{"dial":"886","code":"TW"},{"dial":"992","code":"TJ"},{"dial":"255","code":"TZ"},{"dial":"66","code":"TH"},{"dial":"228","code":"TG"},{"dial":"690","code":"TK"},{"dial":"676","code":"TO"},{"dial":"1-868","code":"TT"},{"dial":"216","code":"TN"},{"dial":"90","code":"TR"},{"dial":"993","code":"TM"},{"dial":"1-649","code":"TC"},{"dial":"688","code":"TV"},{"dial":"1-340","code":"VI"},{"dial":"256","code":"UG"},{"dial":"380","code":"UA"},{"dial":"971","code":"AE"},{"dial":"44","code":"GB"},{"dial":"1","code":"US"},{"dial":"598","code":"UY"},{"dial":"998","code":"UZ"},{"dial":"678","code":"VU"},{"dial":"379","code":"VA"},{"dial":"58","code":"VE"},{"dial":"84","code":"VN"},{"dial":"681","code":"WF"},{"dial":"212","code":"EH"},{"dial":"967","code":"YE"},{"dial":"260","code":"ZM"},{"dial":"263","code":"ZW"}]
diff --git a/src/i18n.min/ar.json b/src/i18n.min/ar.json
deleted file mode 100644
index 538625f6..00000000
--- a/src/i18n.min/ar.json
+++ /dev/null
@@ -1 +0,0 @@
-{"action_block_contact":"حظر جهة الاتصال","action_cancel":"إلغاء","action_clear_messages":"مسح الرسائل","action_delete_messages":"مسح الرسائل للجميع","action_leave_chat":"مغادرة المحادثة","action_report_chat":"الإبلاغ عن المحادثة","archived_contacts":"جهات الاتصال المؤرشفة ({count})","badge_danger":"غير موثوق به","badge_owner":"مالك","badge_staff":"إدارة الموقع","badge_verified":"موثق/رسمي","badge_you":"أنت","block_contact_warning":"هل أنت متأكد من أنك تريد حظر جهة الاتصال هذه؟","blocked_contacts_link":"جهات الاتصال المحظورة ({count})","button_add_members":"إضافة أعضاء","button_cancel":"إلغاء","button_confirm":"تأكيد","button_create":"إنشاء","button_delete_account":"حذف الحساب","button_edit":"تعديل","button_logout":"تسجيل الخروج","button_ok":"موافق","button_reset":"إعادة تعيين","button_send_request":"إرسال طلب","button_sign_in":"تسجيل الدخول","button_sign_up":"إنشاء حساب","button_subscribe":"اشتراك","button_update":"تحديث","cannot_initiate_file_upload":"لا يمكن بدء تحميل الملف","channel":"قناة","channel_prompt":"هذه قناة","chat_invitation":"أنت مدعو لبدء محادثة جديدة. ماذا تريد أن تفعل؟","chat_invitation_accept":"قبول","chat_invitation_block":"حظر","chat_invitation_ignore":"تجاهل","clear_messages_warning":"هل أنت متأكد من أنك تريد مسح جميع الرسائل؟ لا يمكن التراجع عن هذا الإجراء.","code_doesnot_match":"الرمز غير متطابق","contacts_not_found":"ليس لديك محادثات\n¯∖_(ツ)_/¯","contacts_not_found_short":"لا توجد جهات اتصال تطابق ''{query}''","credential_email_prompt":"البريد الإلكتروني للتسجيل","delete_account":"حذف الحساب","delete_account_warning":"هل أنت متأكد من أنك تريد حذف حسابك؟ لا يمكن التراجع عن هذا الإجراء.","delete_messages_warning":"هل أنت متأكد من أنك تريد حذف جميع الرسائل للجميع؟ لا يمكن التراجع عن هذا الإجراء.","download_action":"تحميل","drafty_attachment":"مرفق","drafty_form":"نموذج:","drafty_image":"صورة","email_dative":"البريد الإلكتروني","email_prompt":"البريد الإلكتروني، مثال: jdoe@example.com","enable_peers_messaging":"تفعيل","enter_confirmation_code_prompt":"رمز التأكيد","error_invalid_id":"معرف غير صالح","file_attachment_too_large":"حجم الملف {size} يتجاوز الحد {limit}.","forgot_password_link":"نسيت كلمة المرور؟","full_name_prompt":"الاسم الكامل، مثال: جون دو","granted_permissions":"ممنوح","group_has_no_members":"لا يوجد أعضاء","group_user_id_prompt":"معرف المجموعة أو المستخدم","image_caption_prompt":"وصف الصورة","invalid_content":"محتوى غير صالح","invalid_security_token":"رمز الأمان غير صالح","label_client":"العميل:","label_content_type":"نوع المحتوى:","label_default_access_mode":"وضع الوصول الافتراضي:","label_file_name":"اسم الملف:","label_group_members":"أعضاء المجموعة:","label_incognito_mode":"وضع التخفي:","label_message_sound":"صوت الرسالة:","label_muting_topic":"صامت:","label_other_user":"آخر","label_password":"كلمة المرور","label_permissions":"الصلاحيات:","label_private":"تعليق خاص","label_push_notifications":"تنبيهات الإشعارات:","label_push_notifications_disabled":"تنبيهات الإشعارات (يتطلب HTTPS):","label_reset_password":"إرسال بريد إلكتروني لإعادة تعيين كلمة المرور:","label_sdk":"SDK:","label_server":"الخادم:","label_server_address":"عنوان الخادم:","label_server_to_use":"الخادم المراد استخدامه:","label_size":"الحجم:","label_topic_name":"الاسم","label_user_contacts":"جهات الاتصال","label_user_id":"المعرف:","label_wire_transport":"نقل البيانات:","label_you":"أنت:","label_your_name":"اسمك","label_your_permissions":"صلاحياتك:","last_seen_timestamp":"آخر ظهور","leave_chat_warning":"هل أنت متأكد من أنك تريد مغادرة هذه المحادثة؟","link_contact_us":"اتصل بنا","link_privacy_policy":"سياسة الخصوصية","link_terms_of_service":"شروط الخدمة","login_prompt":"تسجيل الدخول","menu_item_archive_topic":"أرشفة","menu_item_block":"حظر","menu_item_clear_messages":"مسح الرسائل","menu_item_clear_messages_for_all":"مسح للجميع","menu_item_delete":"حذف","menu_item_delete_for_all":"حذف للجميع","menu_item_delete_topic":"حذف","menu_item_edit_permissions":"تعديل الصلاحيات","menu_item_info":"معلومات","menu_item_member_delete":"إزالة","menu_item_mute":"كتم","menu_item_reply":"رد","menu_item_restore_topic":"استعادة","menu_item_send_retry":"إعادة المحاولة","menu_item_unblock":"إلغاء الحظر","menu_item_unmute":"إلغاء الكتم","message_sending":"جاري الإرسال...","message_sending_failed":"فشل","messages_not_readable":"لا يمكن الوصول إلى الرسائل","messaging_disabled_prompt":"المراسلة معطلة","more_online_members":"+{overflow} آخرون","new_message_prompt":"رسالة جديدة","new_password_placeholder":"أدخل كلمة المرور الجديدة","no_connection":"لا يوجد اتصال","no_contacts":"ليس لديك جهات اتصال :-(","numeric_confirmation_code_prompt":"أرقام فقط","online_now":"متصل الآن","password_prompt":"كلمة المرور","password_unchanged_prompt":"لم يتغير","peers_messaging_disabled":"المراسلة معطلة للطرف الآخر","permission_admin":"الموافقة ({val})","permission_delete":"حذف ({val})","permission_join":"انضمام ({val})","permission_owner":"مالك ({val})","permission_pres":"تلقي الإشعارات ({val})","permission_read":"قراءة ({val})","permission_share":"مشاركة ({val})","permission_write":"كتابة ({val})","phone_dative":"هاتف","private_editing_placeholder":"مرئي لك فقط","push_init_failed":"فشل في تهيئة الإشعارات","reconnect_countdown":"تم قطع الاتصال. إعادة الاتصال خلال {seconds}...","reconnect_now":"حاول الآن","reload_update":"إعادة تحميل","report_chat_warning":"هل أنت متأكد من أنك تريد حظر والإبلاغ عن هذه المحادثة؟","requested_permissions":"مطلوب","save_attachment":"حفظ","search_for_contacts":"استخدم البحث للعثور على جهات الاتصال","search_no_results":"لم يسفر البحث عن أي نتائج","search_placeholder":"قائمة مثل alice@example.com، +17025550003...","sidepanel_title_acc_notifications":"الإشعارات","sidepanel_title_acc_support":"الدعم","sidepanel_title_account_settings":"إعدادات الحساب","sidepanel_title_archive":"المحادثات المؤرشفة","sidepanel_title_blocked":"المحادثات المحظورة","sidepanel_title_cred":"تأكيد بيانات الاعتماد","sidepanel_title_login":"تسجيل الدخول","sidepanel_title_newtpk":"بدء محادثة جديدة","sidepanel_title_register":"إنشاء حساب","sidepanel_title_reset":"إعادة تعيين كلمة المرور","sidepanel_title_settings":"الإعدادات","stay_logged_in":"البقاء متصلاً","tabtitle_find_user":"بحث","tabtitle_group_by_id":"بواسطة المعرف","tabtitle_new_group":"مجموعة جديدة","tags_editor_no_tags":"أضف بعض الوسوم","tags_not_found":"لا توجد وسوم محددة. أضف بعضها.","title_all_contacts":"جميع جهات الاتصال","title_group_members":"أعضاء المجموعة","title_manage_tags":"إدارة","title_not_found":"غير موجود","title_permissions":"الصلاحيات","title_tag_manager":"الوسوم (البحث والاكتشاف)","topic_block_warning":"هل أنت متأكد من أنك تريد حظر هذه المحادثة؟","topic_delete_warning":"هل أنت متأكد من أنك تريد حذف هذه المحادثة؟ لا يمكن التراجع عن هذا الإجراء.","topic_name_editing_placeholder":"اسم حر للمجموعة","unnamed_topic":"بدون اسم","update_available":"يتوفر تحديث.","upload_finishing":"جاري الإنهاء...","user_not_found":"غير موجود","description_editing_placeholder":"وصف اختياري","label_description":"الوصف","button_security":"الأمان","panel_title_crop":"اسحب للتعديل","panel_title_general":"عام","panel_title_members":"الأعضاء","panel_title_security":"الأمان","panel_title_info":"معلومات","permissions_anonymous":"مجهول","permissions_authenticated":"مصادق عليه","topic_delete":"حذف المحادثة","permissions_user":"صلاحيات المستخدم","password_reset_email_sent":"تم إرسال بريد إلكتروني يحتوي على رمز الأمان.","label_unarchive_topic":"مؤرشف:","menu_item_forward":"إعادة توجيه","forward_to":"إعادة توجيه إلى","forward_to_search_placeholder":"البحث عن جهات الاتصال","label_new_password":"كلمة المرور الجديدة","drafty_unknown":"غير مدعوم","calls_incoming":"مكالمة واردة","calls_outgoing":"مكالمة صادرة","calls_you_label":"أنت","menu_item_video_call":"مكالمة فيديو","already_in_call":"أنت بالفعل في مكالمة جارية!","call_cancelled":"تم الإلغاء","call_missed":"فائتة","call_declined":"تم الرفض","call_disconnected":"تم قطع الاتصال","label_use_secure_connection":"استخدام اتصال آمن","cred_confirmed_successfully":"تم التأكيد بنجاح","menu_item_edit":"تعديل","editing_message":"جاري التعديل","message_edited_marker":"، تم التعديل","icon_title_add_image":"إضافة صورة","icon_title_attach_file":"إرفاق ملف","icon_title_delete":"حذف التسجيل","icon_title_pause":"إيقاف مؤقت","icon_title_play":"تشغيل","icon_title_record_voice":"تسجيل رسالة صوتية","icon_title_resume":"استئناف","icon_title_send":"إرسال رسالة","drag_file":"اسحب الملف هنا","drafty_video":"تسجيل فيديو","call_in_progress":"جارية","menu_item_audio_call":"مكالمة","unrecognized_video_format":"تنسيق هذا الفيديو غير معروف","loading_note":"جاري التحميل...","password_i_have_code":"لدي رمز","label_reset_password_tel":"إرسال رسالة نصية لإعادة تعيين كلمة المرور:","mobile_phone_number":"رقم الهاتف المحمول","password_reset_sms_sent":"تم إرسال رسالة نصية تحتوي على رمز الأمان.","mobile_number_required":"رقم الهاتف المحمول مطلوب","current_email":"البريد الإلكتروني الحالي","current_phone":"رقم الهاتف الحالي","new_email":"بريد إلكتروني جديد","new_phone_number":"رقم هاتف جديد","change_email":"تغيير البريد الإلكتروني","change_phone":"تغيير رقم الهاتف","call_busy":"مشغول","failed_to_init_audio":"فشل في تهيئة تسجيل الصوت","password_reset_success":"تم إعادة تعيين كلمة المرور بنجاح","scan_qr_code":"مسح رمز QR","alias_already_taken":"(مُستخدم بالفعل)","alias_editing_placeholder":"الاسم المستعار (اختياري)","alias_invalid":"(غير صالح)","label_alias":"الاسم المستعار:","label_alias_edit":"الاسم المستعار","message_deleted":"تم حذف الرسالة","message_not_found":"لم يتم العثور على الرسالة","pin_message":"تثبيت","select_country":"اختر الدولة","self_topic_comment":"ملاحظات ورسائل وروابط وملفات محفوظة للمستقبل","self_topic_name":"الرسائل المحفوظة","text_copied":"تم النسخ إلى الحافظة","unpin_message":"إلغاء التثبيت","color_schema_dark":"داكن","color_schema_light":"فاتح","color_schema_system":"افتراضي النظام","label_color_schema":"المظهر:","label_text_size":"حجم النص:","incognito_mode_description":"إخفاء حالة اتصالك ومؤشرات الكتابة.","send_command":"إرسال بـ {key}","send_command_explained":"اضغط Enter لسطر جديد","send_plain":"إرسال بـ Enter","send_plain_explained":"اضغط Shift + Enter لسطر جديد","sidepanel_title_acc_general":"عام","label_keyboard":"لوحة المفاتيح:","pin_chat":"تثبيت","unpin_chat":"إلغاء التثبيت","subscriber_count":"{count, plural, zero {{count, number} مشترك} one {{count, number} مشترك} two {{count, number} مشتركان} few {{count, number} مشتركين} many {{count, number} مشتركًا} other {{count, number} مشترك}}","label_member_count":"الأعضاء:","label_subscriber_count":"المشتركون:","member_count":"{count, plural, zero {{count, number} عضو} one {{count, number} عضو} two {{count, number} عضوان} few {{count, number} أعضاء} many {{count, number} عضوًا} other {{count, number} عضو}}","tabtitle_image":"صورة","tabtitle_pattern":"نمط","button_restore":"استعادة الافتراضي","wallpapers":"الخلفيات","label_blur_wallpaper":"ضبابية:","label_scan_id":"مسح معرفي:","unknown_name":"غير معروف","save_action":"حفظ","cannot_parse_vcard":"تعذّر تحليل ملف vCard.","chat_now":"محادثة","find_user":"بحث","label_contact_card":"بطاقة الاتصال","label_contacts":"جهات الاتصال","add_members_prompt":"إضافة أعضاء"}
\ No newline at end of file
diff --git a/src/i18n.min/de.json b/src/i18n.min/de.json
deleted file mode 100644
index 10677fa8..00000000
--- a/src/i18n.min/de.json
+++ /dev/null
@@ -1 +0,0 @@
-{"action_block_contact":"Kontakt blockieren","action_cancel":"Abbrechen","action_clear_messages":"Nachrichten leeren","action_delete_messages":"Nachrichten für alle löschen","action_leave_chat":"Unterhaltung verlassen","action_report_chat":"Unterhaltung melden","archived_contacts":"Archivierte Kontakte ({count})","badge_danger":"Verdächtig","badge_owner":"Besitzer","badge_staff":"Verwaltung","badge_verified":"Verifiziert","badge_you":"Sie","block_contact_warning":"Möchten Sie diesen Kontakt wirklich blockieren?","blocked_contacts_link":"Blockierte Kontakte ({count})","button_add_members":"Mitglieder hinzufügen","button_cancel":"Abbrechen","button_confirm":"Bestätigen","button_create":"Erstellen","button_delete_account":"Konto löschen","button_edit":"Bearbeiten","button_logout":"Abmelden","button_ok":"OK","button_reset":"Zurücksetzen","button_send_request":"Anfrage senden","button_sign_in":"Anmelden","button_sign_up":"Anmelden","button_subscribe":"Abbonieren","button_update":"Aktualisieren","cannot_initiate_file_upload":"Datei kann nicht hochgeladen werden.","channel":"Sender","channel_prompt":"Dies ist ein Sender","chat_invitation":"Sie sind eingeladen, an einem neuen Chat teilzunehmen. Was möchten Sie tun?","chat_invitation_accept":"Akzeptieren","chat_invitation_block":"Blockieren","chat_invitation_ignore":"Ignorieren","clear_messages_warning":"Sind Sie sicher, dass Sie diese Unterhaltung für alle leeren wollen? Das kann nicht rückgängig gemacht werden.","code_doesnot_match":"Code stimmt nicht überein","contacts_not_found":"Sie haben keine Unterhaltungen\n¯∖_(ツ)_/¯","contacts_not_found_short":"Keine Kontakte für ''{query}''","credential_email_prompt":"E-Mail Adresse für Registrierung","delete_account":"Konto löschen","delete_account_warning":"Möchten Sie das Konto wirklich löschen? Das kann nicht rückgängig gemacht werden","delete_messages_warning":"Sind Sie sicher, dass Sie diese Unterhaltung für alle löschen wollen? Das kann nicht rückgängig gemacht werden.","download_action":"herunterladen","drafty_attachment":"Dateianhang","drafty_form":"Formular:","drafty_image":"Bild","email_dative":"E-Mail","email_prompt":"E-Mail, z.B. lisam@beispiel.de","enable_peers_messaging":"Aktivieren","enter_confirmation_code_prompt":"Geben Sie den Bestätigungscode ein, der per {method} geschickt wurde:","error_invalid_id":"ungültige ID","file_attachment_too_large":"Die Dateigröße {size} überschreitet das Limit von {limit}.","forgot_password_link":"Passwort vergessen?","full_name_prompt":"Vollständiger Name, z.B. Lisa Musterfrau","granted_permissions":"Erteilt","group_has_no_members":"Keine Mitglieder","group_user_id_prompt":"Gruppe oder Benutzer ID","image_caption_prompt":"Bildunterschrift","invalid_content":"ungültiger Inhalt","invalid_security_token":"Ungültiger Sicherheitsschlüssel","label_client":"Client:","label_content_type":"Inhaltsart:","label_default_access_mode":"Standard Zugriffsmodus:","label_file_name":"Dateiname:","label_group_members":"Gruppenmitglieder:","label_incognito_mode":"inkognito-Modus:","label_message_sound":"Benachrichtigungston:","label_muting_topic":"Stumm geschaltet:","label_other_user":"Andere","label_password":"Passwort","label_permissions":"Berechtigungen:","label_private":"Privater Kommentar","label_push_notifications":"Benachrichtigungsmeldungen:","label_push_notifications_disabled":"Benachrichtigungsmeldungen (erfordert HTTPS):","label_reset_password":"Passwort per E-Mail wiederherstellen:","label_sdk":"SDK:","label_server":"Server:","label_server_address":"Server Adresse:","label_server_to_use":"Server verwenden:","label_size":"Größe:","label_topic_name":"Name","label_user_contacts":"Kontakte","label_user_id":"ID:","label_wire_transport":"Übertragung per Kabel:","label_you":"Sie:","label_your_name":"Ihr Name","label_your_permissions":"Ihre Berechtigungen:","last_seen_timestamp":"Zuletzt gesehen","leave_chat_warning":"Möchten Sie diese Unterhaltung wirklich verlassen?","link_contact_us":"Kontakt","link_privacy_policy":"Datenschutzerklärung","link_terms_of_service":"Nutzungsbedingungen","login_prompt":"Anmelden","menu_item_archive_topic":"Archivieren","menu_item_block":"Blockieren","menu_item_clear_messages":"Nachrichten leeren","menu_item_clear_messages_for_all":"Für alle leeren","menu_item_delete":"Löschen","menu_item_delete_for_all":"Für alle löschen","menu_item_delete_topic":"Entfernen","menu_item_edit_permissions":"Berechtigungen ändern","menu_item_info":"Info","menu_item_member_delete":"Entfernen","menu_item_mute":"Unterhaltung stumm schalten","menu_item_restore_topic":"Wiederherstellen","menu_item_send_retry":"Wiederholen","menu_item_unblock":"Blockierung aufheben","menu_item_unmute":"Stumm schalten beenden","message_sending":"wird gesendet...","message_sending_failed":"fehlgeschlagen","messages_not_readable":"Neue Nachrichten können nicht gelesen werden","messaging_disabled_prompt":"Nachrichtenübermittlung deaktiviert","more_online_members":"+{overflow} mehr","new_message_prompt":"Neue Nachricht","new_password_placeholder":"Geben Sie ein neues Passwort ein","no_connection":"Keine Verbindung","no_contacts":"Sie haben keine Kontakte :-(","numeric_confirmation_code_prompt":"Nur zahlen","online_now":"jetzt online","password_prompt":"Passwort","password_unchanged_prompt":"unverändert","peers_messaging_disabled":"Gruppennachrichten sind deaktiviert","permission_admin":"Bestätigen ({val})","permission_delete":"Entfernen ({val})","permission_join":"Beitreten ({val})","permission_owner":"Besitzer ({val})","permission_pres":"Benachrichtigt werden ({val})","permission_read":"Lesen ({val})","permission_share":"Teilen ({val})","permission_write":"Schreiben ({val})","phone_dative":"Telefon","private_editing_placeholder":"Nur für Sie sichtbar","push_init_failed":"Initialisierung von Push-Benachrichtigungen fehlgeschlagen","reconnect_countdown":"Getrennt. Wiederverbinden in {seconds}…","reconnect_now":"Jetzt probieren","reload_update":"Neu laden","report_chat_warning":"Möchten Sie diese Unterhaltung wirklich blockieren und melden?","requested_permissions":"Angefordert","save_attachment":"Speichern","search_for_contacts":"Nutzen Sie die Suche um Kontakte zu finden","search_no_results":"Die Suche hatte keine Ergebnisse","search_placeholder":"Liste: email:lisa@beispiel.de, tel:17025550003...","sidepanel_title_acc_notifications":"Benachrichtigungen","sidepanel_title_acc_support":"Unterstützung","sidepanel_title_account_settings":"Konto-Einstellungen","sidepanel_title_archive":"Archivierte Unterhaltungen","sidepanel_title_blocked":"Blockierte Unterhaltungen","sidepanel_title_cred":"Anmeldeinformationen bestätigen","sidepanel_title_login":"Anmelden","sidepanel_title_newtpk":"Neue Unterhaltung starten","sidepanel_title_register":"Konto erstellen","sidepanel_title_reset":"Passwort zurücksetzen","sidepanel_title_settings":"Einstellungen","stay_logged_in":"Angemeldet bleiben","tabtitle_find_user":"Suchen","tabtitle_group_by_id":"nach ID","tabtitle_new_group":"Neue Gruppe","tags_editor_no_tags":"Schlagworte hinzufügen","tags_not_found":"Keine Schlagworte definiert. Erstellen Sie welche.","title_all_contacts":"Alle Kontakte","title_group_members":"Gruppenmitglieder","panel_title_info":"Info","title_manage_tags":"Verwalten","title_not_found":"Nicht gefunden","title_permissions":"Berechtigungen","title_tag_manager":"Schlagworte (Nutzer entdecken)","topic_block_warning":"Möchten Sie diese Unterhaltung wirklich blockieren?","topic_delete_warning":"Möchten Sie diese Unterhaltung wirklich löschen?","topic_name_editing_placeholder":"Freiform Name der Gruppe","unnamed_topic":"Unbenannt","update_available":"Aktualisierung verfügbar.","upload_finishing":"wird abgeschlossen...","user_not_found":"Nicht gefunden","description_editing_placeholder":"Beschreibung (optional)","label_description":"Beschreibung","button_security":"Sicherheit","panel_title_general":"Allgemein","panel_title_security":"Sicherheit","panel_title_crop":"Zum Anpassen ziehen","panel_title_members":"Mitglieder","permissions_anonymous":"Anonym","permissions_authenticated":"Authentifiziert","topic_delete":"Chat löschen","permissions_user":"Benutzerberechtigungen","password_reset_email_sent":"Eine E-Mail mit Sicherheitscode wurde gesendet.","label_unarchive_topic":"Archiviert:","menu_item_reply":"Antwort","menu_item_forward":"Nach vorne","forward_to":"Weiterleiten","forward_to_search_placeholder":"Kontakte durchsuchen","label_new_password":"Neues Kennwort","drafty_unknown":"Nicht unterstützt","calls_incoming":"Eingehender Anruf","calls_outgoing":"Ausgehender Anruf","calls_you_label":"Du","menu_item_video_call":"Videoanruf","already_in_call":"Du redest schon!","call_cancelled":"abgebrochen","call_missed":"verpasster","call_declined":"abgelehnt","call_disconnected":"getrennt","label_use_secure_connection":"Verwenden Sie eine sichere Verbindung","cred_confirmed_successfully":"Erfolgreich bestätigt","menu_item_edit":"Bearbeiten","editing_message":"Bearbeitung","message_edited_marker":", bearbeitet","icon_title_add_image":"Bild hinzufügen","icon_title_attach_file":"Datei anhängen","icon_title_delete":"Aufnahme löschen","icon_title_pause":"Wiedergabe pausieren","icon_title_play":"Aufnahme abspielen","icon_title_record_voice":"Sprachnachricht aufnehmen","icon_title_resume":"Wiedergabe fortsetzen","icon_title_send":"Nachricht senden","drag_file":"Datei hierher ziehen","drafty_video":"Videoaufnahme","call_in_progress":"in Arbeit","menu_item_audio_call":"Anruf","unrecognized_video_format":"Das Format dieses Videos wird nicht erkannt","loading_note":"Laden...","password_i_have_code":"Ich habe Code","label_reset_password_tel":"SMS zum Zurücksetzen des Passworts senden","mobile_phone_number":"Mobiltelefonnummer","password_reset_sms_sent":"Eine SMS mit einem Sicherheitscode wurde gesendet.","mobile_number_required":"Handynummer erforderlich","current_email":"Aktuelle E-Mail","current_phone":"Aktuelle Telefonnummer","new_email":"Neue E-Mail","new_phone_number":"Neue Telefonnummer","change_email":"E-Mail ändern","change_phone":"Telefonnummer ändern","call_busy":"besetzt","failed_to_init_audio":"Audioaufnahme konnte nicht initialisiert werden","pin_message":"Anpinnen","unpin_message":"Lösen","password_reset_success":"Passwort erfolgreich zurückgesetzt","scan_qr_code":"QR-Code scannen","self_topic_comment":"Notizen, Nachrichten, Links, Dateien für die Nachwelt gespeichert","self_topic_name":"Gespeicherte Nachrichten","message_not_found":"Nachricht nicht gefunden","message_deleted":"Nachricht gelöscht","alias_editing_placeholder":"Alias (optional)","label_alias":"Alias:","alias_already_taken":"(bereits vergeben)","alias_invalid":"(ungültig)","label_alias_edit":"Alias","select_country":"Land auswählen","text_copied":"In die Zwischenablage kopiert","color_schema_dark":"Dunkel","color_schema_light":"Hell","color_schema_system":"Systemstandard","label_color_schema":"Thema:","label_text_size":"Textgröße:","incognito_mode_description":"Online-Status und Tipp-Indikatoren verbergen.","send_command":"Senden mit {key}","send_command_explained":"Drücken Sie Enter für eine neue Zeile","send_plain":"Senden mit Enter","send_plain_explained":"Drücken Sie Shift + Enter für eine neue Zeile","sidepanel_title_acc_general":"Allgemein","label_keyboard":"Tastatur:","pin_chat":"Anheften","unpin_chat":"Loslösen","subscriber_count":"{count, plural, one {{count, number} Abonnent} other {{count, number} Abonnenten}}","label_member_count":"Mitglieder:","label_subscriber_count":"Abonnenten:","member_count":"{count, plural, one {{count, number} Mitglied} other {{count, number} Mitglieder}}","tabtitle_image":"Bild","tabtitle_pattern":"Muster","button_restore":"Standard wiederherstellen","wallpapers":"Hintergrundbilder","label_blur_wallpaper":"Unschärfe:","label_scan_id":"Meine ID scannen:","unknown_name":"Unbekannt","save_action":"Speichern","cannot_parse_vcard":"vCard-Datei konnte nicht verarbeitet werden.","chat_now":"Chatten","find_user":"Suchen","label_contact_card":"Kontaktkarte","label_contacts":"Kontakte","add_members_prompt":"Mitglieder hinzufügen"}
\ No newline at end of file
diff --git a/src/i18n.min/en.json b/src/i18n.min/en.json
deleted file mode 100644
index 017eea22..00000000
--- a/src/i18n.min/en.json
+++ /dev/null
@@ -1 +0,0 @@
-{"action_block_contact":"Block Contact","action_cancel":"cancel","action_clear_messages":"Clear Messages","action_delete_messages":"Clear Messages for All","action_leave_chat":"Leave Conversation","action_report_chat":"Report Conversation","archived_contacts":"Archived contacts ({count})","badge_danger":"Untrustworthy","badge_owner":"owner","badge_staff":"Staff-managed","badge_verified":"Verified/official","badge_you":"you","block_contact_warning":"Are you sure you want to block this contact?","blocked_contacts_link":"Blocked contacts ({count})","button_add_members":"Add members","button_cancel":"Cancel","button_confirm":"Confirm","button_create":"Create","button_delete_account":"Delete account","button_edit":"Edit","button_logout":"Logout","button_ok":"OK","button_reset":"Reset","button_send_request":"Send request","button_sign_in":"Sign in","button_sign_up":"Sign up","button_subscribe":"Subscribe","button_update":"Update","cannot_initiate_file_upload":"Cannot initiate file upload.","channel":"channel","channel_prompt":"This is a channel","chat_invitation":"You are invited to start a new chat. What would you like to do?","chat_invitation_accept":"Accept","chat_invitation_block":"Block","chat_invitation_ignore":"Ignore","clear_messages_warning":"Are you sure you want to clear all messages? It cannot be undone.","code_doesnot_match":"Code does not match","contacts_not_found":"You have no chats\n¯∖_(ツ)_/¯","contacts_not_found_short":"No contacts match ''{query}''","credential_email_prompt":"Your registration email","delete_account":"Delete account","delete_account_warning":"Are you sure you want to delete your account? It cannot be undone.","delete_messages_warning":"Are you sure you want to delete all messages for everyone? It cannot be undone.","download_action":"download","drafty_attachment":"Attachment","drafty_form":"Form:","drafty_image":"Picture","email_dative":"email","email_prompt":"Email, e.g. jdoe@example.com","enable_peers_messaging":"Enable","enter_confirmation_code_prompt":"Confirmation code","error_invalid_id":"Invalid ID","file_attachment_too_large":"The file size {size} exceeds the {limit} limit.","forgot_password_link":"Forgot password?","full_name_prompt":"Full name, e.g. John Doe","granted_permissions":"Granted","group_has_no_members":"No members","group_user_id_prompt":"Group or User ID","image_caption_prompt":"Image caption","invalid_content":"invalid content","invalid_security_token":"Invalid security token","label_client":"Client:","label_content_type":"Content type:","label_default_access_mode":"Default access mode:","label_file_name":"File name:","label_group_members":"Group members:","label_incognito_mode":"Incognito mode:","label_message_sound":"Message sound:","label_muting_topic":"Muted:","label_other_user":"Other","label_password":"Password","label_permissions":"Permissions:","label_private":"Private comment","label_push_notifications":"Notification alerts:","label_push_notifications_disabled":"Notification alerts (requires HTTPS):","label_reset_password":"Send a password reset email:","label_sdk":"SDK:","label_server":"Server:","label_server_address":"Server address:","label_server_to_use":"Server to use:","label_size":"Size:","label_topic_name":"Name","label_user_contacts":"Contacts","label_user_id":"ID:","label_wire_transport":"Wire transport:","label_you":"You:","label_your_name":"Your name","label_your_permissions":"Your permissions:","last_seen_timestamp":"Last seen: {timestamp}","leave_chat_warning":"Are you sure you want to leave this conversation?","link_contact_us":"Contact Us","link_privacy_policy":"Privacy Policy","link_terms_of_service":"Terms of Service","login_prompt":"Login","menu_item_archive_topic":"Archive","menu_item_block":"Block","menu_item_clear_messages":"Clear messages","menu_item_clear_messages_for_all":"Clear for All","menu_item_delete":"Delete","menu_item_delete_for_all":"Delete for All","menu_item_delete_topic":"Delete","menu_item_edit_permissions":"Edit permissions","menu_item_info":"Info","menu_item_member_delete":"Remove","menu_item_mute":"Mute","menu_item_reply":"Reply","menu_item_restore_topic":"Restore","menu_item_send_retry":"Retry","menu_item_unblock":"Unblock","menu_item_unmute":"Unmute","message_sending":"sending...","message_sending_failed":"failed","messages_not_readable":"no access to messages","messaging_disabled_prompt":"Messaging disabled","more_online_members":"+{overflow} more","new_message_prompt":"New message","new_password_placeholder":"Enter new password","no_connection":"No connection","no_contacts":"You have no contacts :-(","numeric_confirmation_code_prompt":"Numbers only","online_now":"online now","password_prompt":"Password","password_unchanged_prompt":"Unchanged","peers_messaging_disabled":"Peer's messaging is disabled.","permission_admin":"Approve ({val})","permission_delete":"Delete ({val})","permission_join":"Join ({val})","permission_owner":"Owner ({val})","permission_pres":"Get notified ({val})","permission_read":"Read ({val})","permission_share":"Share ({val})","permission_write":"Write ({val})","phone_dative":"phone","private_editing_placeholder":"Visible to you only","push_init_failed":"Failed to initialize push notifications","reconnect_countdown":"Disconnected. Reconnecting in {seconds}…","reconnect_now":"Try now","reload_update":"Reload","report_chat_warning":"Are you sure you want to block and report this conversation?","requested_permissions":"Requested","save_attachment":"save","search_for_contacts":"Use search to find contacts","search_no_results":"Search returned no results","search_placeholder":"List like email:alice@example.com, tel:17025550003...","sidepanel_title_acc_notifications":"Notifications","sidepanel_title_acc_support":"Support","sidepanel_title_account_settings":"Account Settings","sidepanel_title_archive":"Archived Chats","sidepanel_title_blocked":"Blocked Chats","sidepanel_title_cred":"Confirm Credentials","sidepanel_title_login":"Sign In","sidepanel_title_newtpk":"Start New Chat","sidepanel_title_register":"Create Account","sidepanel_title_reset":"Reset Password","sidepanel_title_settings":"Settings","stay_logged_in":"Stay logged in","tabtitle_find_user":"find","tabtitle_group_by_id":"by id","tabtitle_new_group":"new group","tags_editor_no_tags":"Add some tags","tags_not_found":"No tags defined. Add some.","title_all_contacts":"All Contacts","title_group_members":"Group Members","title_manage_tags":"Manage","title_not_found":"Not found","title_permissions":"Permissions","title_tag_manager":"Tags (search & discovery)","topic_block_warning":"Are you sure you want to block this conversation?","topic_delete_warning":"Are you sure you want to delete this conversation?","topic_name_editing_placeholder":"Freeform name of the group","unnamed_topic":"Unnamed","update_available":"Update available.","upload_finishing":"finishing...","user_not_found":"Not found","description_editing_placeholder":"Optional description","label_description":"Description","button_security":"Security","panel_title_crop":"Drag to Adjust","panel_title_general":"General","panel_title_members":"Members","panel_title_security":"Security","panel_title_info":"Info","permissions_anonymous":"Anonymous","permissions_authenticated":"Authenticated","topic_delete":"Delete Conversation","permissions_user":"User's Permissions","password_reset_email_sent":"An email with security code has been sent.","label_unarchive_topic":"Archived:","menu_item_forward":"Forward","forward_to":"Forward to","forward_to_search_placeholder":"Search contacts","label_new_password":"New password","drafty_unknown":"Unsupported","calls_incoming":"Incoming call","calls_outgoing":"Outgoing call","calls_you_label":"You","menu_item_video_call":"Video call","already_in_call":"You already in an ongoing call!","call_cancelled":"cancelled","call_missed":"missed","call_declined":"declined","call_disconnected":"disconnected","label_use_secure_connection":"Use secure connection","cred_confirmed_successfully":"Confirmed successfully","menu_item_edit":"Edit","editing_message":"Editing","message_edited_marker":", edited","icon_title_add_image":"Add image","icon_title_attach_file":"Attach file","icon_title_delete":"Delete","icon_title_pause":"Pause","icon_title_play":"Play","icon_title_record_voice":"Record voice message","icon_title_resume":"Resume","icon_title_send":"Send message","drag_file":"Drag file here","drafty_video":"Video recording","call_in_progress":"in progress","menu_item_audio_call":"Call","unrecognized_video_format":"Format of this video is not recognized","loading_note":"Loading...","password_i_have_code":"I have code","label_reset_password_tel":"Send a password reset SMS:","mobile_phone_number":"Mobile phone number","password_reset_sms_sent":"A text message with security code has been sent.","mobile_number_required":"Mobile phone number required","current_email":"Current email","current_phone":"Current phone number","new_email":"New email","new_phone_number":"New phone number","change_email":"Change email","change_phone":"Change phone number","call_busy":"busy","failed_to_init_audio":"Failed to initialize audio recording","pin_message":"Pin","unpin_message":"Unpin","password_reset_success":"Password reset successfully","scan_qr_code":"Scan QR Code","self_topic_comment":"Notes, messages, links, files saved for posterity","self_topic_name":"Saved messages","message_not_found":"message not found","message_deleted":"message deleted","alias_editing_placeholder":"Alias (optional)","label_alias":"Alias:","alias_already_taken":"(already taken)","alias_invalid":"(invalid)","label_alias_edit":"Alias","select_country":"Select country","text_copied":"Copied to clipboard","color_schema_dark":"Dark","color_schema_light":"Light","color_schema_system":"System default","label_color_schema":"Theme:","label_text_size":"Text size:","incognito_mode_description":"Hide your online status and typing indicators.","send_command":"Send on {key}","send_command_explained":"Press Enter for new line","send_plain":"Send on Enter","send_plain_explained":"Press Shift + Enter for new line","sidepanel_title_acc_general":"General","label_keyboard":"Keyboard:","pin_chat":"Pin","unpin_chat":"Unpin","subscriber_count":"{count, plural, one {{count, number} subscriber} other {{count, number} subscribers}}","label_member_count":"Members:","label_subscriber_count":"Subscribers:","member_count":"{count, plural, one {{count, number} member} other {{count, number} members}}","tabtitle_image":"image","tabtitle_pattern":"pattern","button_restore":"Restore default","wallpapers":"Wallpapers","label_blur_wallpaper":"Blur:","label_scan_id":"Scan my ID:","unknown_name":"Unknown","save_action":"Save","cannot_parse_vcard":"Cannot parse vCard file.","chat_now":"Chat","find_user":"Find","label_contact_card":"Contact Card","label_contacts":"Contacts","add_members_prompt":"add members"}
\ No newline at end of file
diff --git a/src/i18n.min/es.json b/src/i18n.min/es.json
deleted file mode 100644
index da5743de..00000000
--- a/src/i18n.min/es.json
+++ /dev/null
@@ -1 +0,0 @@
-{"action_block_contact":"Bloquear contacto","action_cancel":"cancelar","action_clear_messages":"Borrar mensajes","action_delete_messages":"Borrar mensajes para todos","action_leave_chat":"Dejar conversación","action_report_chat":"Reportar conversación","archived_contacts":"Contactos archivados ({count})","badge_danger":"Suspicaz","badge_owner":"propietario","badge_staff":"Administración","badge_verified":"Verificado","badge_you":"tú","block_contact_warning":"¿Estás seguro de que quieres bloquear a este contacto?","blocked_contacts_link":"Contactos bloqueados ({count})","button_add_members":"Añadir miembros","button_cancel":"Cancelar","button_confirm":"Confirmar","button_create":"Crear","button_delete_account":"Eliminar cuenta","button_edit":"Editar","button_logout":"Cerrar sesión","button_ok":"OK","button_reset":"Restablecer","button_send_request":"Enviar petición","button_sign_in":"Entrar","button_sign_up":"Regístrate","button_subscribe":"Suscribirse","button_update":"Actualizar","cannot_initiate_file_upload":"No se pudo iniciar la carga del archivo.","channel":"canal","channel_prompt":"Este es un canal","chat_invitation":"Estás invitado a participar en un nuevo chat. ¿Qué te gustaría hacer?","chat_invitation_accept":"Aceptar","chat_invitation_block":"Bloquear","chat_invitation_ignore":"Ignorar","clear_messages_warning":"¿Estás seguro de que quieres eliminar todos los mensajes? Esta acción es irreversible.","code_doesnot_match":"El código no coincide","contacts_not_found":"No tienes chats\n¯∖_(ツ)_/¯","contacts_not_found_short":"Ningún contacto coincide con ''{query}''","credential_email_prompt":"Tu correo electrónico de registro","delete_account":"Eliminar cuenta","delete_account_warning":"¿Estás seguro de que deseas eliminar permanentemente tu cuenta? Esta acción es irreversible.","delete_messages_warning":"¿Estás seguro de que quieres eliminar todos los mensajes para todos? Esta acción es irreversible.","download_action":"descargar","drafty_attachment":"Archivo","drafty_form":"Formulario:","drafty_image":"Imagen","email_dative":"correo electrónico","email_prompt":"Correo electrónico, p.ej. juan@example.com","enable_peers_messaging":"Habilitar","enter_confirmation_code_prompt":"Introduzca el código de confirmación enviado a tu {method}:","error_invalid_id":"ID inválido","file_attachment_too_large":"El tamaño del archivo {size} excede el límite de {limit}.","forgot_password_link":"¿Olvidaste tu contraseña?","full_name_prompt":"Nombre completo, p.ej. Juan González Hernández","granted_permissions":"Otorgados","group_has_no_members":"No hay miembros","group_user_id_prompt":"ID del grupo o usuario","image_caption_prompt":"Añade un comentario","invalid_content":"contenido inválido","invalid_security_token":"Token de seguridad inválido","label_client":"Cliente:","label_content_type":"Tipo de contenido:","label_default_access_mode":"Modo de acceso predeterminado:","label_file_name":"Nombre del archivo:","label_group_members":"Miembros del grupo:","label_incognito_mode":"Modo incógnito:","label_message_sound":"Sonido de mensaje:","label_muting_topic":"Silenciado:","label_other_user":"Otros","label_password":"Contraseña","label_permissions":"Permisos:","label_private":"Comentario privado","label_push_notifications":"Alertas de notificaciones:","label_push_notifications_disabled":"Alertas de notificaciones (requiere HTTPS):","label_reset_password":"Enviar un correo electrónico de restablecimiento de contraseña:","label_sdk":"SDK:","label_server":"Servidor:","label_server_address":"Dirección del servidor:","label_server_to_use":"Servidor para usar:","label_size":"Tamaño:","label_topic_name":"Nombre del tema","label_user_contacts":"Contactos","label_user_id":"ID:","label_wire_transport":"Transporte de alambre:","label_you":"Tú:","label_your_name":"Tu nombre","label_your_permissions":"Tus permisos:","last_seen_timestamp":"Últ. vez","leave_chat_warning":"¿Estás seguro de que quieres dejar esta conversación?","link_contact_us":"Contáctanos","link_privacy_policy":"Política de privacidad","link_terms_of_service":"Términos de uso","login_prompt":"Nombre de usuario","menu_item_archive_topic":"Archivar","menu_item_block":"Bloquear","menu_item_clear_messages":"Borrar mensajes","menu_item_clear_messages_for_all":"Borrar para todos","menu_item_delete":"Eliminar","menu_item_delete_for_all":"Eliminar para todos","menu_item_delete_topic":"Eliminar","menu_item_edit_permissions":"Editar permisos","menu_item_info":"Información","menu_item_member_delete":"Eliminar","menu_item_mute":"Silenciar","menu_item_restore_topic":"Restaurar","menu_item_send_retry":"Inténtalo de nuevo","menu_item_unblock":"Desbloquear","menu_item_unmute":"Anular el silencio","message_sending":"enviando...","message_sending_failed":"no se pudo enviar el mensaje","messages_not_readable":"sin acceso a mensajes","messaging_disabled_prompt":"El envío de mensajes está deshabilitado","more_online_members":"+{overflow} más","new_message_prompt":"Nuevo mensaje","new_password_placeholder":"Introduzca una nueva contraseña","no_connection":"Sin conexión","no_contacts":"No tienes contactos :-(","numeric_confirmation_code_prompt":"Sólo números","online_now":"en línea","password_prompt":"Contraseña","password_unchanged_prompt":"Sin cambios","peers_messaging_disabled":"La mensajería Peer está deshabilitada.","permission_admin":"Approbar ({val})","permission_delete":"Eliminar ({val})","permission_join":"Unirse ({val})","permission_owner":"Propietario ({val})","permission_pres":"Ser notificado ({val})","permission_read":"Leer ({val})","permission_share":"Compartir ({val})","permission_write":"Escribir ({val})","phone_dative":"teléfono","private_editing_placeholder":"Sólo visible para tí","push_init_failed":"Error al inicializar las notificaciones push","reconnect_countdown":"Desconectado. Reconectando en {seconds}…","reconnect_now":"Reintentar","reload_update":"Recargar","report_chat_warning":"¿Estás seguro de que quieres bloquear y reportar a esta conversación?","requested_permissions":"Solicitados","save_attachment":"guardar","search_for_contacts":"Usa la búsqueda para encontrar contactos","search_no_results":"La búsqueda no arrojó resultados","search_placeholder":"Ej. email:alice@example.com, tel:17025550003...","sidepanel_title_acc_notifications":"Notificaciones","sidepanel_title_acc_support":"Soporte","sidepanel_title_account_settings":"Ajustes de la cuenta","sidepanel_title_archive":"Chats archivados","sidepanel_title_blocked":"Chats bloqueados","sidepanel_title_cred":"Confirmar credenciales","sidepanel_title_login":"Iniciar sesión","sidepanel_title_newtpk":"Iniciar un nuevo chat","sidepanel_title_register":"Crear cuenta","sidepanel_title_reset":"Restablecer contraseña","sidepanel_title_settings":"Ajustes","stay_logged_in":"Permanecer conectado","tabtitle_find_user":"encontrar","tabtitle_group_by_id":"por ID","tabtitle_new_group":"nuevo grupo","tags_editor_no_tags":"Añadir etiquetas","tags_not_found":"No hay etiquetas definidas. Añade unas.","title_all_contacts":"Todos los contactos","title_group_members":"Miembros del grupo","title_manage_tags":"Gestionar","title_not_found":"No encontrado","title_permissions":"Permisos","title_tag_manager":"Etiquetas (descubrimiento de usuarios)","topic_block_warning":"¿Estás seguro de que quieres bloquear esta conversación","topic_delete_warning":"¿Estás seguro de que quieres eliminar esta conversación?","topic_name_editing_placeholder":"Nombre del grupo","unnamed_topic":"Sin nombre","update_available":"Actualización disponible.","upload_finishing":"terminando...","user_not_found":"Usuario no encontrado","description_editing_placeholder":"Descripción (opcional)","label_description":"Descripción","button_security":"Seguridad","panel_title_crop":"Arrastra para ajustar","panel_title_general":"General","panel_title_members":"Miembros","panel_title_security":"Seguridad","panel_title_info":"Información","permissions_anonymous":"Anónimo","permissions_authenticated":"Autenticado","topic_delete":"Eliminar chat","permissions_user":"Permisos del usuario","password_reset_email_sent":"Se ha enviado un correo electrónico con el código de seguridad.","label_unarchive_topic":"Archivado:","menu_item_reply":"Respuesta","menu_item_forward":"Reenviar","forward_to":"Reenviar a","forward_to_search_placeholder":"Buscar contactos","label_new_password":"Nueva contraseña","drafty_unknown":"No soportado","calls_incoming":"Llamada entrante","calls_outgoing":"Llamada saliente","calls_you_label":"Tú","menu_item_video_call":"Videollamada","already_in_call":"¡Ya estás hablando!","call_cancelled":"cancelada","call_missed":"perdida","call_declined":"rechazada","call_disconnected":"desconectada","label_use_secure_connection":"Usar conexión segura","cred_confirmed_successfully":"Confirmado con éxito","menu_item_edit":"Editar","editing_message":"Edición","message_edited_marker":", editado","icon_title_add_image":"Añadir imagen","icon_title_attach_file":"Adjuntar archivo","icon_title_delete":"Eliminar grabación","icon_title_pause":"Pausar reproducción","icon_title_play":"Reproducir grabación","icon_title_record_voice":"Grabar mensaje de voz","icon_title_resume":"Reanudar la reproducción","icon_title_send":"Enviar mensaje","drag_file":"Arrastra el archivo aquí","drafty_video":"Grabación de vídeo","call_in_progress":"en progreso","menu_item_audio_call":"Llamada","unrecognized_video_format":"No se reconoce el formato de este video","loading_note":"Cargando...","password_i_have_code":"Tengo código","label_reset_password_tel":"Enviar SMS para restablecer la contraseña","mobile_phone_number":"Número de teléfono móvil","password_reset_sms_sent":"Se ha enviado un mensaje de texto con un código de seguridad.","mobile_number_required":"Número de teléfono móvil requerido","current_email":"Correo electrónico actual","current_phone":"Número de teléfono actual","new_email":"Nuevo correo electrónico","new_phone_number":"Nuevo número de teléfono","change_email":"Cambiar correo electrónico","change_phone":"Cambiar número de teléfono","call_busy":"ocupado","pin_message":"Fijar","unpin_message":"Desanclar","failed_to_init_audio":"Error al inicializar la grabación de audio","password_reset_success":"Restablecimiento de contraseña con éxito","scan_qr_code":"Escanear código QR","self_topic_comment":"Notas, mensajes, enlaces, archivos guardados para la posteridad","self_topic_name":"Mensajes guardados","message_not_found":"Mensaje no encontrado","message_deleted":"Mensaje eliminado","alias_editing_placeholder":"Alias (opcional)","label_alias":"Alias:","alias_already_taken":"(ya tomado)","alias_invalid":"(inválido)","label_alias_edit":"Alias","select_country":"Seleccionar país","text_copied":"Copiado al portapapeles","color_schema_dark":"Oscuro","color_schema_light":"Claro","color_schema_system":"Predeterminado del sistema","label_color_schema":"Tema:","label_text_size":"Tamaño del texto:","incognito_mode_description":"Ocultar tu estado en línea e indicadores de escritura.","send_command":"Enviar con {key}","send_command_explained":"Presiona Enter para nueva línea","send_plain":"Enviar con Enter","send_plain_explained":"Presiona Shift + Enter para nueva línea","sidepanel_title_acc_general":"General","label_keyboard":"Teclado:","pin_chat":"Fijar","unpin_chat":"Desfijar","subscriber_count":"{count, plural, one {{count, number} suscriptor} other {{count, number} suscriptores}}","label_member_count":"Miembros:","label_subscriber_count":"Suscriptores:","member_count":"{count, plural, one {{count, number} miembro} other {{count, number} miembros}}","tabtitle_image":"imagen","tabtitle_pattern":"patrón","button_restore":"Restaurar predeterminado","wallpapers":"Fondos de pantalla","label_blur_wallpaper":"Desenfoque:","label_scan_id":"Escanear mi ID:","unknown_name":"Desconocido","save_action":"Guardar","cannot_parse_vcard":"No se puede analizar el archivo vCard.","chat_now":"Chatear","find_user":"Buscar","label_contact_card":"Tarjeta de contacto","label_contacts":"Contactos","add_members_prompt":"añadir miembros"}
\ No newline at end of file
diff --git a/src/i18n.min/fr.json b/src/i18n.min/fr.json
deleted file mode 100644
index 42977fb6..00000000
--- a/src/i18n.min/fr.json
+++ /dev/null
@@ -1 +0,0 @@
-{"action_block_contact":"Bloquer le Contact","action_cancel":"annuler","action_clear_messages":"Effacer les Messages","action_delete_messages":"Effacer les Messages pour Tous","action_leave_chat":"Quitter la Conversation","action_report_chat":"Signaler la Conversation","archived_contacts":"Contacts archivés ({count})","badge_danger":"Non fiable","badge_owner":"propriétaire","badge_staff":"Géré par le Staff","badge_verified":"Vérifié / Officiel","badge_you":"vous","block_contact_warning":"Êtes-vous sûr de bloquer le contact ?","blocked_contacts_link":"Contacts bloqués ({count})","button_add_members":"Ajouter des membres","button_cancel":"Annuler","button_confirm":"Confirmer","button_create":"Créer","button_delete_account":"Supprimer le compte","button_edit":"Modifier","button_logout":"Déconnexion","button_ok":"OK","button_reset":"Remettre à Zéro","button_send_request":"Envoyer une demande","button_sign_in":"Connexion","button_sign_up":"Créer un compte","button_subscribe":"S'abonner","button_update":"Mettre à jour","cannot_initiate_file_upload":"Impossible de démarrer l'envoi.","channel":"canal","channel_prompt":"C'est un canal","chat_invitation":"Vous êtes invité à démarrer une conversation. Que voulez vous faire ?","chat_invitation_accept":"Accepter","chat_invitation_block":"Bloquer","chat_invitation_ignore":"Ignorer","clear_messages_warning":"Êtes-vous sûr de vouloir effacer tous les messages ? Aucun retour possible.","code_doesnot_match":"Le code ne correspond pas","contacts_not_found":"Vous n'avez aucune conversation\n¯∖_(ツ)_/¯","contacts_not_found_short":"Aucun contact ne correspond à ''{query}''","credential_email_prompt":"Votre email d'enregistrement","delete_account":"Supprimer le compte","delete_account_warning":"Êtes-vous sûr de vouloir supprimer votre compte ? Aucun retour possible.","delete_messages_warning":"Êtes-vous sûr de vouloir supprimer tous les messages pour tous ? Aucun retour possible.","download_action":"télécharger","drafty_attachment":"Pièce(s) jointe(s)","drafty_form":"Formulaire:","drafty_image":"Image","email_dative":"email","email_prompt":"Email, par exemple jdoe@exemple.com","enable_peers_messaging":"Activer","enter_confirmation_code_prompt":"Entrez le code de confirmation envoyé par {method}:","error_invalid_id":"Identifiant invalide","file_attachment_too_large":"Le fichier de taille {size} dépasse la limite de {limit}.","forgot_password_link":"Mot de passe oublié ?","full_name_prompt":"Nom complet, par exemple John Doe","granted_permissions":"Accordé","group_has_no_members":"Aucun membre","group_user_id_prompt":"Identifiant d'utilisateur ou de groupe","image_caption_prompt":"Description de l'image","invalid_content":"contenu invalide","invalid_security_token":"Token de sécurité invalide","label_client":"Client :","label_content_type":"Type de contenu :","label_default_access_mode":"Mode d'accès par défaut :","label_file_name":"Nom du fichier :","label_group_members":"Membres du groupe :","label_incognito_mode":"Mode incognito :","label_message_sound":"Son du message :","label_muting_topic":"Mué :","label_other_user":"Autre","label_password":"Mot de passe","label_permissions":"Permissions :","label_private":"Commentaire privé","label_push_notifications":"Notifications :","label_push_notifications_disabled":"Notifications (requière HTTPS):","label_reset_password":"Envoyer un mail de remise à zéro de mot de passe :","label_sdk":"SDK :","label_server":"Serveur :","label_server_address":"Adresse du serveur :","label_server_to_use":"Serveur à utiliser :","label_size":"Taille :","label_topic_name":"Nom","label_user_contacts":"Contacts","label_user_id":"ID :","label_wire_transport":"Transport par cable :","label_you":"Vous :","label_your_name":"Votre nom","label_your_permissions":"Vos permissions :","last_seen_timestamp":"Vu pour la dernière fois","leave_chat_warning":"Êtes-vous sûr de vouloir quitter cette conversation ?","link_contact_us":"Contactez Nous","link_privacy_policy":"Politique de confidentialité","link_terms_of_service":"Conditions d'utilisation","login_prompt":"Connexion","menu_item_archive_topic":"Archiver","menu_item_block":"Bloquer","menu_item_clear_messages":"Effacer les messages","menu_item_clear_messages_for_all":"Effacer pour Tous","menu_item_delete":"Supprimer","menu_item_delete_for_all":"Supprimer pour Tous","menu_item_delete_topic":"Supprimer","menu_item_edit_permissions":"Modifier les permissions","menu_item_info":"Informations","menu_item_member_delete":"Supprimer","menu_item_mute":"Rendre muer","menu_item_reply":"Répondre","menu_item_restore_topic":"Restorer","menu_item_send_retry":"Réessayer","menu_item_unblock":"Débloquer","menu_item_unmute":"Recevoir à nouveau","message_sending":"envoi en cours...","message_sending_failed":"échoué","messages_not_readable":"aucun accès aux messages","messaging_disabled_prompt":"Discussion désactivée","more_online_members":"+{overflow} de plus","new_message_prompt":"Nouveau message","new_password_placeholder":"Entrez un nouveau mot de passe","no_connection":"Aucune connexion","no_contacts":"Vous n'avez pas de contacts :-(","numeric_confirmation_code_prompt":"Nombres seulement","online_now":"en ligne","password_prompt":"Mot de passe","password_unchanged_prompt":"Non changé","peers_messaging_disabled":"La messagerie point à point est désactivée.","permission_admin":"Approver ({val})","permission_delete":"Supprimer ({val})","permission_join":"Rejoindre ({val})","permission_owner":"Propriétaire ({val})","permission_pres":"Recevoir une notification ({val})","permission_read":"Lire ({val})","permission_share":"Partager ({val})","permission_write":"Ecrire ({val})","phone_dative":"téléphone","private_editing_placeholder":"Visible par vous seulement","push_init_failed":"Impossible d'initialiser les notifications push","reconnect_countdown":"Déconnecté. Reconnexion dans {seconds}…","reconnect_now":"Essayer maintenant","reload_update":"Recharger","report_chat_warning":"Êtes-vous sûr de vouloir bloquer et signaler cette conversation ?","requested_permissions":"Demandé","save_attachment":"sauvegarder","search_for_contacts":"Utilisez la recherche pour trouver des contacts","search_no_results":"La recherche n'a donné aucun résultat","search_placeholder":"Liste comme email:alice@exemple.com, tel:17025550003...","sidepanel_title_acc_notifications":"Notifications","sidepanel_title_acc_support":"Support","sidepanel_title_account_settings":"Paramètres du compte","sidepanel_title_archive":"Conversations Archivées","sidepanel_title_blocked":"Conversations Bloquées","sidepanel_title_cred":"Confirmer les identifiants","sidepanel_title_login":"Se Connecter","sidepanel_title_newtpk":"Démarrer une nouvelle Conversation","sidepanel_title_register":"Créer un compte","sidepanel_title_reset":"Remettre à Zéro le Mot de Passe","sidepanel_title_settings":"Paramètres","stay_logged_in":"Rester connecté","tabtitle_find_user":"trouver","tabtitle_group_by_id":"par identifiant","tabtitle_new_group":"nouveau groupe","tags_editor_no_tags":"Ajouter des étiquettes","tags_not_found":"Aucune étiquette définie. Ajoutez en.","title_all_contacts":"Tous les Contacts","title_group_members":"Membres du Groupe","title_manage_tags":"Gérer","title_not_found":"Pas trouvé","title_permissions":"Permissions","title_tag_manager":"Etiquettes (recherche & découverte)","topic_block_warning":"Êtes-vous sûr de vouloir bloquer cette conversation ?","topic_delete_warning":"Êtes-vous sûr de vouloir supprimer cette conversation ?","topic_name_editing_placeholder":"Nom libre du groupe","unnamed_topic":"Non nommé","update_available":"Mise à jour disponible.","upload_finishing":"fin...","user_not_found":"Pas trouvé","description_editing_placeholder":"Description facultative","label_description":"Description","button_security":"Sécurité","panel_title_crop":"Déplacez pour ajuster","panel_title_general":"Général","panel_title_members":"Membres","panel_title_security":"Sécurité","panel_title_info":"Informations","permissions_anonymous":"Anonyme","permissions_authenticated":"Authentifié","topic_delete":"Supprimer la Conversation","permissions_user":"Permissions d'Utilisateur","password_reset_email_sent":"Un e-mail avec code de sécurité a été envoyé.","label_unarchive_topic":"Archivé:","menu_item_forward":"Transférer","forward_to":"Transférer à","forward_to_search_placeholder":"Rechercher des contacts","label_new_password":"Nouveau mot de passe","drafty_unknown":"Non supporté","already_in_call":"Vous êtes déjà dans un appel en cours!","call_cancelled":"annulé","call_declined":"refusé","call_missed":"manqué","calls_incoming":"Appel entrant","calls_outgoing":"Appel sortant","calls_you_label":"Tu","menu_item_video_call":"Appel vidéo","call_disconnected":"débranché","label_use_secure_connection":"Utiliser une connexion sécurisée","cred_confirmed_successfully":"Confirmé avec succès","menu_item_edit":"Modifier","editing_message":"Édition","message_edited_marker":", édité","icon_title_add_image":"Ajouter une image","icon_title_attach_file":"Pièce jointe","icon_title_delete":"Supprimer l'enregistrement","icon_title_pause":"Interrompre la lecture","icon_title_play":"Lire l'enregistrement","icon_title_record_voice":"Enregistrer un message vocal","icon_title_resume":"Reprendre la lecture","icon_title_send":"Envoyer le message","drag_file":"Faites glisser le fichier ici","drafty_video":"Enregistrement video","call_in_progress":"en cours","menu_item_audio_call":"Appel","unrecognized_video_format":"Le format de cette vidéo n'est pas reconnu","loading_note":"Chargement...","password_i_have_code":"J'ai du code","label_reset_password_tel":"Envoyer un SMS pour réinitialiser le mot de passe","mobile_phone_number":"Numéro de téléphone mobile","password_reset_sms_sent":"Un message texte avec un code de sécurité a été envoyé.","mobile_number_required":"Numéro de téléphone portable requis","current_email":"Courriel actuel","current_phone":"Numéro de téléphone actuel","new_email":"Nouvel e-mail","new_phone_number":"Nouveau numéro de téléphone","change_email":"Modifier l'e-mail","change_phone":"Modifier le numéro de téléphone","call_busy":"occupé","pin_message":"Épingler","unpin_message":"Détacher","failed_to_init_audio":"Échec de l'initialisation de l'enregistrement audio","password_reset_success":"Mot de passe réinitialisé avec succès","scan_qr_code":"Scanner le code QR","self_topic_comment":"Notes, messages, liens, fichiers sauvegardés","self_topic_name":"Messages sauvegardés","message_not_found":"message introuvable","message_deleted":"message supprimé","alias_editing_placeholder":"Alias (facultatif)","label_alias":"Alias:","alias_already_taken":"(déjà utilisé)","alias_invalid":"(invalide)","label_alias_edit":"Alias","select_country":"Sélectionner un pays","text_copied":"Copié dans le presse-papiers","color_schema_dark":"Sombre","color_schema_light":"Clair","color_schema_system":"Thème du système","label_color_schema":"Thème :","label_text_size":"Taille du texte :","incognito_mode_description":"Masquer votre statut en ligne et les indicateurs de frappe.","send_command":"Envoyer avec {key}","send_command_explained":"Appuyez sur Entrée pour une nouvelle ligne","send_plain":"Envoyer avec Entrée","send_plain_explained":"Appuyez sur Shift + Entrée pour une nouvelle ligne","sidepanel_title_acc_general":"Général","label_keyboard":"Clavier :","pin_chat":"Épingler","unpin_chat":"Désépingler","subscriber_count":"{count, plural, one {{count, number} abonné} other {{count, number} abonnés}}","label_member_count":"Membres :","label_subscriber_count":"Abonnés :","member_count":"{count, plural, one {{count, number} membre} other {{count, number} membres}}","tabtitle_image":"image","tabtitle_pattern":"motif","button_restore":"Restaurer par défaut","wallpapers":"Fonds d'écran","label_blur_wallpaper":"Flou :","label_scan_id":"Scanner mon ID :","unknown_name":"Inconnu","save_action":"Enregistrer","cannot_parse_vcard":"Impossible d'analyser le fichier vCard.","chat_now":"Discuter","find_user":"Rechercher","label_contact_card":"Carte de contact","label_contacts":"Contacts","add_members_prompt":"ajouter des membres"}
\ No newline at end of file
diff --git a/src/i18n.min/it.json b/src/i18n.min/it.json
deleted file mode 100644
index 3a4d818f..00000000
--- a/src/i18n.min/it.json
+++ /dev/null
@@ -1 +0,0 @@
-{"action_block_contact":"Bloccare contatto","action_cancel":"cancella","action_clear_messages":"Cancella messaggi","action_delete_messages":"Cancella messaggi per tutti","action_leave_chat":"Lascia la conversazione","action_report_chat":"Report conversazione","archived_contacts":"Contatti archiviati ({count})","badge_danger":"Sospetto","badge_owner":"propietario","badge_staff":"Amministrazione","badge_verified":"Verificato","badge_you":"tu","block_contact_warning":"Sei sicuro di bloccare questo contatto?","blocked_contacts_link":"Contatti bloccati ({count})","button_add_members":"Aggiungi membri","button_cancel":"Cancella","button_confirm":"Conferma","button_create":"Crea","button_delete_account":"Elimina account","button_edit":"Editare","button_logout":"Fine sessione","button_ok":"OK","button_reset":"Reset","button_send_request":"Invia richiesta","button_sign_in":"Entrare","button_sign_up":"Registrazione","button_subscribe":"Sottoscrivi","button_update":"Aggiorna","cannot_initiate_file_upload":"Non è possibile iniziare il caricamento dell'archivio.","channel":"canale","channel_prompt":"Questo è un canale","chat_invitation":"Sei invitato a partecipare ad una nuova chat. Vuoi farlo?","chat_invitation_accept":"Accettare","chat_invitation_block":"Bloccare","chat_invitation_ignore":"Ignorare","clear_messages_warning":"Sei sicuro di voler cancellare tutti i messaggi? Questa azione è irreversibile.","code_doesnot_match":"Il codice non conicide","contacts_not_found":"Non hai chats\n¯∖_(ツ)_/¯","contacts_not_found_short":"Nessun contatto coincide con ''{query}''","credential_email_prompt":"La tua mail di registrazione","delete_account":"Elimina l'account","delete_account_warning":"Sei sicuro di voler cancellare il tuo account? Questa azione è irreversibile.","delete_messages_warning":"Sei sicuro di voler cancellare tutti i messaggi per tutti? Questa azione è irreversibile.","download_action":"Scarica","drafty_attachment":"Allegato","drafty_form":"Form:","drafty_image":"Immagine","email_dative":"posta elettronica","email_prompt":"Posta elettronica, per es. mario@example.com","enable_peers_messaging":"Abilitare","enter_confirmation_code_prompt":"Inserire il codice di conferma {method}:","error_invalid_id":"ID non valido","file_attachment_too_large":"La grandezza del file {size} eccede il limite di {limit}.","forgot_password_link":"Hai dimenticato la password?","full_name_prompt":"Nome completo, per es. Mario Rossi","granted_permissions":"Garantito","group_has_no_members":"Non hai membri","group_user_id_prompt":"ID del gruppo o dell'utente","image_caption_prompt":"Aggiungi un'immagine","invalid_content":"contenuto non valido","invalid_security_token":"Token di sicurezza invalido","label_client":"Cliente:","label_content_type":"Tipo di contenuto:","label_default_access_mode":"Modalità di accesso predefinito:","label_file_name":"Nome del file:","label_group_members":"Membri del gruppo:","label_incognito_mode":"Modalità incognito:","label_message_sound":"Suono del messaggio:","label_muting_topic":"Silenziato:","label_other_user":"Altri","label_password":"Password","label_permissions":"Permessi:","label_private":"Commento privato","label_push_notifications":"Allarmi di notifica:","label_push_notifications_disabled":"Allarmi di notifica (richiede HTTPS):","label_reset_password":"Invia una mail per il reset della password:","label_sdk":"SDK:","label_server":"Server:","label_server_address":"Indirizzo del server:","label_server_to_use":"Server da usare:","label_size":"Grandezza:","label_topic_name":"Nome dell'argomento","label_user_contacts":"Contatti","label_user_id":"ID:","label_wire_transport":"Tipo di Trasporto:","label_you":"Tu:","label_your_name":"Il tuo nome","label_your_permissions":"I tuoi permessi:","last_seen_timestamp":"Ultimo visto","leave_chat_warning":"Sei sicuro di voler lasciare questa conversazione?","link_contact_us":"Contattaci","link_privacy_policy":"Política della privacy","link_terms_of_service":"Termini d'uso del servizio","login_prompt":"Nome dell'utente","menu_item_archive_topic":"Archivia","menu_item_block":"Bloccare","menu_item_clear_messages":"Cancella i messaggi","menu_item_clear_messages_for_all":"Cancella per tutti","menu_item_delete":"Elimina","menu_item_delete_for_all":"Elimina per tutti","menu_item_delete_topic":"Eliminare","menu_item_edit_permissions":"Modifica permessi","menu_item_info":"Informazioni","menu_item_member_delete":"Eliminare","menu_item_mute":"Silenziare","menu_item_restore_topic":"Ripristinare","menu_item_send_retry":"Riprova","menu_item_unblock":"Sblocca","menu_item_unmute":"Riattiva le notifiche","message_sending":"invio...","message_sending_failed":"non è possibile inviare il messaggio","messages_not_readable":"nessun accesso ai messaggi","messaging_disabled_prompt":"L'invio dei messaggi è disabilitato","more_online_members":"+{overflow} e oltre","new_message_prompt":"Nuovo messaggio","new_password_placeholder":"Inserire nuova password","no_connection":"Nessuna connessione","no_contacts":"Non ci sono contatti :-(","numeric_confirmation_code_prompt":"Solo numeri","online_now":"in linea","password_prompt":"Password","password_unchanged_prompt":"Non è cambiata","peers_messaging_disabled":"I messaggi punto punto sono disabilitati.","permission_admin":"Approvare ({val})","permission_delete":"Eliminare ({val})","permission_join":"Unisci ({val})","permission_owner":"Propietario ({val})","permission_pres":"Notifica arrivata ({val})","permission_read":"Leggi ({val})","permission_share":"Condividi ({val})","permission_write":"Scrivi ({val})","phone_dative":"telefono","private_editing_placeholder":"Visibile solo a te","push_init_failed":"Non è possibile inizializzare le notifiche push","reconnect_countdown":"Disconnesso. Riconnessione in {seconds}…","reconnect_now":"Riprovare","reload_update":"Ricaricare","report_chat_warning":"Sei sicuro di bloccare e denunciare questa conversazione?","requested_permissions":"Sollecito","save_attachment":"salva","search_for_contacts":"Usa la ricerca per trovare i contatti","search_no_results":"La ricerca non ha trovato nessun risultato","search_placeholder":"Elenca come: email:mario@example.com, tel:17025550003...","sidepanel_title_acc_notifications":"Notifiche","sidepanel_title_acc_support":"Supporto","sidepanel_title_account_settings":"Impostazioni dell'account","sidepanel_title_archive":"Chats archiviate","sidepanel_title_blocked":"Chats bloccate","sidepanel_title_cred":"Conferma le credenziali","sidepanel_title_login":"Entra","sidepanel_title_newtpk":"Comincia una Nuova Chat","sidepanel_title_register":"Creare Account","sidepanel_title_reset":"Resetta la Password","sidepanel_title_settings":"Impostazioni","stay_logged_in":"Rimani loggato","tabtitle_find_user":"cerca","tabtitle_group_by_id":"per ID","tabtitle_new_group":"nuovo gruppo","tags_editor_no_tags":"Aggiungi alcune etichette","tags_not_found":"Non ci sono etichette definite. Aggiungine alcune.","title_all_contacts":"Tutti i contatti","title_group_members":"Membri del gruppo","title_manage_tags":"Gestisci","title_not_found":"Non trovato","title_permissions":"Permessi","title_tag_manager":"Etichette (trova e scopri)","topic_block_warning":"Sei sicuro di voler bloccare questa conversazione?","topic_delete_warning":"Sei sicuro di voler eliminare questa conversazione?","topic_name_editing_placeholder":"Nome del gruppo","unnamed_topic":"Senza nome","update_available":"Aggiornamento disponibile.","upload_finishing":"sto terminando...","user_not_found":"Non trovato","description_editing_placeholder":"Descrizione (opzionale)","label_description":"Descrizione","button_security":"Sicurezza","panel_title_crop":"Trascina per aggiustare","panel_title_general":"Generale","panel_title_members":"Membri","panel_title_security":"Sicurezza","panel_title_info":"Informazione","permissions_anonymous":"Anonimo","permissions_authenticated":"Autenticato","topic_delete":"Elimina la conversazione","permissions_user":"Permessi dell'utente","password_reset_email_sent":"Una mail con il codice di sicurezza è stata inviata.","label_unarchive_topic":"Archiviato:","menu_item_reply":"Replica","menu_item_forward":"Reinvia","forward_to":"Reinvia a","forward_to_search_placeholder":"Cerca i contatti","label_new_password":"Nuova password","drafty_unknown":"Non supportato","calls_incoming":"Chiamata in arrivo","calls_outgoing":"Chiamata in uscita","calls_you_label":"Tu","menu_item_video_call":"Videochiamata","already_in_call":"Sei già in una chiamata in uscita!","call_cancelled":"cancellata","call_missed":"persa","call_declined":"rifiutata","call_disconnected":"disconnessa","label_use_secure_connection":"Usa una connessione sicura","cred_confirmed_successfully":"Confermato con successo","menu_item_edit":"Modifica","editing_message":"Edita","message_edited_marker":", editando","icon_title_add_image":"Aggiungi immagine","icon_title_attach_file":"Allega un file","icon_title_delete":"Eliminare la registrazione","icon_title_pause":"Riproduzione in pausa","icon_title_play":"Riproduci la registrazione","icon_title_record_voice":"Registra un messaggio vocale","icon_title_resume":"Riprendi la riproduzione","icon_title_send":"Inviare il messaggio","drag_file":"Trascina il file qui","drafty_video":"Registrazione video","call_in_progress":"avanzamento","menu_item_audio_call":"Chiamata","unrecognized_video_format":"Il formato di questo video non è riconosciuto","loading_note":"Caricando...","password_i_have_code":"Ho un codice","label_reset_password_tel":"Invia un SMS per resettare la password","mobile_phone_number":"Numero di telefono cellulare","password_reset_sms_sent":"Un SMS con il codice di sicurezza è stato inviato.","mobile_number_required":"E' richiesto il numero di telefono cellulare","current_email":"Email attuale","current_phone":"Numero di telefono attuale","new_email":"Nuova email","new_phone_number":"Nuovo numero di telefono","change_email":"Cambiare l'indirizzo di posta elettronica","change_phone":"Cambiare numero di telefono","call_busy":"occupato","pin_message":"Fissare","unpin_message":"Rimuovere","failed_to_init_audio":"Non è possibile cominciare la registrazione audio","password_reset_success":"Il reset della password è avvenuto con successo","scan_qr_code":"Scansionare il codice QR","self_topic_comment":"Note, messaggi, links e archivi, sono salvati per i posteri","self_topic_name":"Messaggi salvati","message_not_found":"Messaggi non trovati","message_deleted":"Messaggi eliminati","alias_editing_placeholder":"Alias (opzionale)","label_alias":"Alias:","alias_already_taken":"(già utilizzato)","alias_invalid":"(invalido)","label_alias_edit":"Alias","select_country":"Seleziona la nazione","text_copied":"Copia nella clipboard","incognito_mode_description":"Nascondi il tuo stato online e gli indicatori di digitazione.","send_command":"Invia con {key}","send_command_explained":"Premi Invio per una nuova riga","send_plain":"Invia con Invio","send_plain_explained":"Premi Shift + Invio per una nuova riga","sidepanel_title_acc_general":"Generale","label_keyboard":"Tastiera:","color_schema_dark":"Scuro","color_schema_light":"Chiaro","color_schema_system":"Predefinito del sistema","label_color_schema":"Tema:","label_text_size":"Dimensione testo:","pin_chat":"Fissa","unpin_chat":"Sblocca","subscriber_count":"{count, plural, one {{count, number} iscritto} other {{count, number} iscritti}}","label_member_count":"Membri:","label_subscriber_count":"Iscritti:","member_count":"{count, plural, one {{count, number} membro} other {{count, number} membri}}","tabtitle_image":"immagine","tabtitle_pattern":"motivo","button_restore":"Ripristina predefinito","wallpapers":"Sfondi","label_blur_wallpaper":"Sfocatura:","label_scan_id":"Scansiona il mio ID:","unknown_name":"Sconosciuto","save_action":"Salva","cannot_parse_vcard":"Impossibile analizzare il file vCard.","chat_now":"Chatta","find_user":"Cerca","label_contact_card":"Scheda contatto","label_contacts":"Contatti","add_members_prompt":"aggiungi membri"}
\ No newline at end of file
diff --git a/src/i18n.min/ko.json b/src/i18n.min/ko.json
deleted file mode 100644
index 47b44073..00000000
--- a/src/i18n.min/ko.json
+++ /dev/null
@@ -1 +0,0 @@
-{"action_block_contact":"연락차단","action_cancel":"취소","action_clear_messages":"메시지지우기","action_delete_messages":"모든메시지지우기","action_leave_chat":"대화나누기","action_report_chat":"대화기록","archived_contacts":"보관된연락처({수})","badge_danger":"의심 많은","badge_owner":"소유자","badge_staff":"직원 관리하에","badge_verified":"확인 됨","badge_you":"당신","block_contact_warning":"이연락처를차단하시겠습니까?","blocked_contacts_link":"차단된연락처({수})","button_add_members":"회원추가","button_cancel":"취소","button_confirm":"확인","button_create":"작성","button_delete_account":"계정삭제","button_edit":"편집","button_logout":"로그아웃","button_ok":"OK","button_reset":"재설정","button_send_request":"요청보내기","button_sign_in":"로그인","button_sign_up":"가입","button_subscribe":"구독","button_update":"업데이트","cannot_initiate_file_upload":"파일업로드를시작할수없습니다.","channel":"채널","channel_prompt":"이 채널입니다","chat_invitation":"새로운대화를시작하도록초대되었습니다.무엇을하시겠습니까?","chat_invitation_accept":"수락","chat_invitation_block":"차단","chat_invitation_ignore":"무시","clear_messages_warning":"모든메시지를지우시겠습니까?실행취소할수없습니다.","code_doesnot_match":"코드가일치하지않습니다","contacts_not_found":"채팅이없습니다\n¯∖_(ツ)_/¯","contacts_not_found_short":"'{문의}'와일치하는연락처가없습니다.","credential_email_prompt":"등록이메일","delete_account":"계정삭제","delete_account_warning":"계정을삭제하시겠습니까?실행취소할수없습니다.","delete_messages_warning":"모든사람의모든메시지를삭제하시겠습니까?실행취소할수없습니다.","download_action":"다운로드","drafty_attachment":"부착","drafty_form":"형태:","drafty_image":"이미지","email_dative":"이메일","email_prompt":"이메일(예:hong@example.com)","enable_peers_messaging":"활성화","enter_confirmation_code_prompt":"{방법}으로보낸확인코드를입력하십시오:","error_invalid_id":"잘못된ID","file_attachment_too_large":"파일크기{크기}이(가){제한}제한을초과합니다.","forgot_password_link":"비밀번호를잊으셨습니까?","full_name_prompt":"전체이름(예:홍길동)","granted_permissions":"승낙하다","group_has_no_members":"회원없음","group_user_id_prompt":"그룹또는사용자ID","image_caption_prompt":"이미지설명","invalid_content":"잘못된내용","invalid_security_token":"유효하지않은보안토큰","label_client":"클라이언트: ","label_content_type":"컨텐츠유형: ","label_default_access_mode":"기본액세스모드: ","label_file_name":"파일이름: ","label_group_members":"그룹회원: ","label_incognito_mode":"시크릿모드: ","label_message_sound":"메시지소리: ","label_muting_topic":"음소거: ","label_other_user":"기타","label_password":"비밀번호","label_permissions":"권한: ","label_private":"개인코멘트","label_push_notifications":"통지경보: ","label_push_notifications_disabled":"통지경보(HTTPS필요): ","label_reset_password":"비밀번호재설정이메일보내기: ","label_sdk":"SDK:","label_server":"서버: ","label_server_address":"서버주소: ","label_server_to_use":"사용할서버:","label_size":"크기: ","label_topic_name":"이름","label_user_contacts":"연락처","label_user_id":"ID:","label_wire_transport":"와이어수송:","label_you":"당신: ","label_your_name":"이름","label_your_permissions":"권한: ","last_seen_timestamp":"마지막으로본","leave_chat_warning":"이대화를나가시겠습니까?","link_contact_us":"문의처","link_privacy_policy":"개인정보보호정책","link_terms_of_service":"서비스약관","login_prompt":"로그인","menu_item_archive_topic":"보관","menu_item_block":"차단","menu_item_clear_messages":"메시지지우기","menu_item_clear_messages_for_all":"모두지우기","menu_item_delete":"삭제","menu_item_delete_for_all":"모두삭제","menu_item_delete_topic":"삭제","menu_item_edit_permissions":"편집권한","menu_item_info":"정보","menu_item_member_delete":"제거","menu_item_mute":"음소거","menu_item_restore_topic":"복원","menu_item_send_retry":"다시시도","menu_item_unblock":"차단해제","menu_item_unmute":"음소거해제","message_sending":"보내기...","message_sending_failed":"실패","messages_not_readable":"메시지에 액세스 할 수 없습니다","messaging_disabled_prompt":"메시지비활성화","more_online_members":"+{넘침}더보기","new_message_prompt":"새메시지","new_password_placeholder":"새비밀번호입력","no_connection":"연결없음","no_contacts":"연락처가없습니다 (._.)","numeric_confirmation_code_prompt":"숫자만","online_now":"현재접속중","password_prompt":"비밀번호","password_unchanged_prompt":"변경되지않음","peers_messaging_disabled":"동료의메시지가비활성화되었습니다.","permission_admin":"승인({val})","permission_delete":"삭제({val})","permission_join":"참여({val})","permission_owner":"소유자({val})","permission_pres":"알림받기({val})","permission_read":"읽기({val})","permission_share":"공유({val})","permission_write":"쓰기({val})","phone_dative":"전화","private_editing_placeholder":"나만볼수있습니다","push_init_failed":"푸시알림을초기화하지못했습니다","reconnect_countdown":"연결이끊어졌습니다.다시연결하는중{초}…","reconnect_now":"지금시도","reload_update":"재로드","report_chat_warning":"이대화를차단하고기록하시겠습니까?","requested_permissions":"요청","save_attachment":"저장","search_for_contacts":"검색을사용하여연락처찾기","search_no_results":"검색결과가없습니다","search_placeholder":"email:alice@example.com,tel:17025550003...와같은목록","sidepanel_title_acc_notifications":"알림","sidepanel_title_acc_support":"지원","sidepanel_title_account_settings":"계정설정","sidepanel_title_archive":"보관된채팅","sidepanel_title_blocked":"차단된채팅","sidepanel_title_cred":"자격증명확인","sidepanel_title_login":"로그인","sidepanel_title_newtpk":"새채팅시작","sidepanel_title_register":"계정만들기","sidepanel_title_reset":"비밀번호재설정","sidepanel_title_settings":"설정","stay_logged_in":"로그인상태유지","tabtitle_find_user":"찾기","tabtitle_group_by_id":"id로","tabtitle_new_group":"새그룹","tags_editor_no_tags":"일부태그추가","tags_not_found":"태그가정의되지않았습니다.일부를추가하십시오.","title_all_contacts":"모든연락처","title_group_members":"그룹구성원","title_manage_tags":"관리","title_not_found":"찾을수없음","title_permissions":"권한","title_tag_manager":"태그(사용자검색)","topic_block_warning":"이대화를차단하시겠습니까?","topic_delete_warning":"이대화를삭제하시겠습니까?","topic_name_editing_placeholder":"그룹의자유형이름","unnamed_topic":"이름없는","update_available":"업데이트가능.","upload_finishing":"마무리...","user_not_found":"찾을수없음","description_editing_placeholder":"설명(선택 사항)","label_description":"설명","button_security":"보안","panel_title_crop":"드래그하여 조정","panel_title_general":"일반","panel_title_members":"구성원","panel_title_security":"보안","panel_title_info":"정보","permissions_anonymous":"익명","permissions_authenticated":"인증됨","topic_delete":"채팅 삭제","permissions_user":"사용자 권한","password_reset_email_sent":"보안 코드가 포함된 이메일이 전송되었습니다.","label_unarchive_topic":"보관됨:","menu_item_reply":"회신하다","menu_item_forward":"리디렉션","forward_to":"메시지를 리디렉션","forward_to_search_placeholder":"연락처 검색","label_new_password":"새 비밀번호","drafty_unknown":"지원되지 않음","calls_incoming":"수신 전화","calls_outgoing":"발신 전화","calls_you_label":"너","menu_item_video_call":"영상 통화","already_in_call":"당신은 이미 말하고 있습니다!","call_cancelled":"취소 된","call_missed":"부재중","call_declined":"통화 거부","call_disconnected":"연결 끊김","label_use_secure_connection":"보안 연결 사용","cred_confirmed_successfully":"성공적으로 확인됨","menu_item_edit":"편집","editing_message":"교정하다","message_edited_marker":" 수정됨","icon_title_add_image":"이미지 추가","icon_title_attach_file":"파일을 첨부","icon_title_delete":"녹음 삭제","icon_title_pause":"재생 일시 중지","icon_title_play":"녹음 재생","icon_title_record_voice":"음성 메시지 녹음","icon_title_resume":"재생 재개","icon_title_send":"문자 보내","drag_file":"여기로 파일 드래그","drafty_video":"녹화","call_in_progress":"통화 중","menu_item_audio_call":"전화","unrecognized_video_format":"이 동영상의 형식을 인식할 수 없습니다.","loading_note":"로드 중...","password_i_have_code":"코드가 있습니다","label_reset_password_tel":"비밀번호 재설정을 위해 SMS 보내기","mobile_phone_number":"휴대전화 번호","password_reset_sms_sent":"보안 코드가 포함된 문자 메시지가 전송되었습니다.","mobile_number_required":"휴대전화 번호 필요","current_email":"현재 이메일","current_phone":"현재 전화번호","new_email":"새 이메일 주소","new_phone_number":"새 전화번호","change_email":"이메일 변경","change_phone":"전화번호 변경","call_busy":"바쁘다","pin_message":"고정","unpin_message":"고정 해제","failed_to_init_audio":"오디오 녹음 초기화 실패","password_reset_success":"비밀번호 재설정 성공","scan_qr_code":"QR 코드 스캔","self_topic_comment":"후세를 위해 저장된 메모, 메시지, 링크, 파일","self_topic_name":"저장된 메시지","message_not_found":"메시지를 찾을 수 없음","message_deleted":"메시지 삭제됨","alias_editing_placeholder":"별칭(선택 사항)","label_alias":"별칭:","alias_already_taken":"(이미 취함)","alias_invalid":"(잘못된)","label_alias_edit":"별칭","select_country":"국가 선택","text_copied":"클립보드에 복사됨","color_schema_dark":"어둡게","color_schema_light":"밝게","color_schema_system":"시스템 기본값","label_color_schema":"테마:","label_text_size":"텍스트 크기:","incognito_mode_description":"온라인 상태와 입력 표시기를 숨깁니다.","send_command":"{key}로 전송","send_command_explained":"새 줄로 이동하려면 Enter를 누르세요","send_plain":"Enter로 전송","send_plain_explained":"새 줄로 이동하려면 Shift + Enter를 누르세요","sidepanel_title_acc_general":"일반","label_keyboard":"키보드:","pin_chat":"고정","unpin_chat":"고정 해제","subscriber_count":"{count, plural, other {구독자 {count, number}명}}","label_member_count":"멤버:","label_subscriber_count":"구독자:","member_count":"{count, plural, other {멤버 {count, number}명}}","tabtitle_image":"이미지","tabtitle_pattern":"패턴","button_restore":"기본값 복원","wallpapers":"배경화면","label_blur_wallpaper":"흐림:","label_scan_id":"내 ID 스캔:","unknown_name":"알 수 없음","save_action":"저장","cannot_parse_vcard":"vCard 파일을 파싱할 수 없습니다.","chat_now":"채팅","find_user":"찾기","label_contact_card":"연락처 카드","label_contacts":"연락처","add_members_prompt":"멤버 추가"}
\ No newline at end of file
diff --git a/src/i18n.min/pt.json b/src/i18n.min/pt.json
deleted file mode 100644
index 5bb2d9e4..00000000
--- a/src/i18n.min/pt.json
+++ /dev/null
@@ -1 +0,0 @@
-{"action_block_contact":"Bloquear Contato","action_cancel":"Cancelar","action_clear_messages":"Limpar Mensagens","action_delete_messages":"Limpar Mensagens para Todos","action_leave_chat":"Sair da Conversa","action_report_chat":"Reportar Conversa","archived_contacts":"Contatos arquivados ({count})","badge_danger":"Não confiável","badge_owner":"dono","badge_staff":"Gerenciado pela equipe","badge_verified":"Verificado/oficial","badge_you":"você","block_contact_warning":"Tem certeza de que deseja bloquear este contato?","blocked_contacts_link":"Contatos bloqueados ({count})","button_add_members":"Adicionar membros","button_cancel":"Cancelar","button_confirm":"Confirmar","button_create":"Criar","button_delete_account":"Excluir conta","button_edit":"Editar","button_logout":"Sair","button_ok":"OK","button_reset":"Reset","button_send_request":"Enviar solicitação","button_sign_in":"Entrar","button_sign_up":"Cadastrar","button_subscribe":"Assinar","button_update":"Atualizar","cannot_initiate_file_upload":"Não posso iniciar o upload do arquivo.","channel":"canal","channel_prompt":"Isto é um canal","chat_invitation":"Você foi convidado para iniciar uma nova conversa. O que você gostaria de fazer?","chat_invitation_accept":"Aceitar","chat_invitation_block":"Bloquear","chat_invitation_ignore":"Ignorar","clear_messages_warning":"Tem certeza de quer apagarar todas as mensagens? Esta ação não pode ser desfeita.","code_doesnot_match":"Código não corresponde","contacts_not_found":"Você não tem conversas\n¯∖_(ツ)_/¯","contacts_not_found_short":"Nenhum contato corresponde a ''{query}''","credential_email_prompt":"Seu email de registro","delete_account":"Excluir conta","delete_account_warning":"Tem certeza de que deseja excluir sua conta? Isso não pode ser desfeito.","delete_messages_warning":"Tem certeza de que deseja excluir todas as mensagens para todos? Isso não pode ser desfeito.","download_action":"baixar","drafty_attachment":"Anexo","drafty_form":"Form:","drafty_image":"Imagem","email_dative":"email","email_prompt":"Email, ex. fulanodetal@exemplo.com","enable_peers_messaging":"Ativar","enter_confirmation_code_prompt":"Código de confirmação","error_invalid_id":"ID Inválido","file_attachment_too_large":"O tamanho do arquivo {size} excede o limite de {limit}.","forgot_password_link":"Esqueceu a senha?","full_name_prompt":"Nome completo, ex. Fulano de Tal","granted_permissions":"Concedidas","group_has_no_members":"Não há membros","group_user_id_prompt":"ID de Grupo ou ID de Usuário","image_caption_prompt":"Legenda da imagem","invalid_content":"conteúdo inválido","invalid_security_token":"Token de segurança inválido","label_client":"Cliente:","label_content_type":"Tipo de conteúdo:","label_default_access_mode":"Modo de acesso padrão:","label_file_name":"Nome do arquivo:","label_group_members":"Membros do grupo:","label_incognito_mode":"Modo incógnito:","label_message_sound":"Som das mensagens:","label_muting_topic":"Mudo:","label_other_user":"Outro","label_password":"Senha","label_permissions":"Permissões:","label_private":"Comentário privado","label_push_notifications":"Alertas de notificação:","label_push_notifications_disabled":"Alertas de notificação (requer HTTPS):","label_reset_password":"Envie um email para redefinir a senha:","label_sdk":"SDK:","label_server":"Servidor:","label_server_address":"Endereço do servidor:","label_server_to_use":"Servidor a ser usado:","label_size":"Tamanho:","label_topic_name":"Nome","label_user_contacts":"Contatos","label_user_id":"ID:","label_wire_transport":"Tipo de transporte:","label_you":"Você:","label_your_name":"Seu nome","label_your_permissions":"Suas permissões:","last_seen_timestamp":"Última vez visto","leave_chat_warning":"Tem certeza de que quer sair desta conversa?","link_contact_us":"Contacte-nos","link_privacy_policy":"Política de Privacidade","link_terms_of_service":"Termos de Serviço","login_prompt":"Login","menu_item_archive_topic":"Arquivar","menu_item_block":"Bloquear","menu_item_clear_messages":"Limpar mensagens","menu_item_clear_messages_for_all":"Limpar Mensagens para Todos","menu_item_delete":"Excluir","menu_item_delete_for_all":"Excluir para Todos","menu_item_delete_topic":"Excluir","menu_item_edit_permissions":"Editar permissões","menu_item_info":"Info","menu_item_member_delete":"Remover","menu_item_mute":"Silenciar","menu_item_reply":"Responder","menu_item_restore_topic":"Restaurar","menu_item_send_retry":"Tentar novamente","menu_item_unblock":"Desbloquear","menu_item_unmute":"Ativar notificações","message_sending":"a enviar...","message_sending_failed":"falhou","messages_not_readable":"sem acesso às mensagens","messaging_disabled_prompt":"Mensageria desativadas","more_online_members":"+{overflow} mais","new_message_prompt":"Nova mensagem","new_password_placeholder":"Entre a nova senha","no_connection":"Sem conexão","no_contacts":"Você não tem contatos :-(","numeric_confirmation_code_prompt":"Somente números","online_now":"online agora","password_prompt":"Senha","password_unchanged_prompt":"Sem alterações","peers_messaging_disabled":"Mensagens do peer desativadas.","permission_admin":"Approvar ({val})","permission_delete":"Excluir ({val})","permission_join":"Unir-se ({val})","permission_owner":"Proprietário ({val})","permission_pres":"Ser notificado ({val})","permission_read":"Ler ({val})","permission_share":"Compartilhar ({val})","permission_write":"Escrever ({val})","phone_dative":"fone","private_editing_placeholder":"Visível apenas para você","push_init_failed":"Falha em inicializar notificações push","reconnect_countdown":"Desconectado. Reconectando em {seconds}…","reconnect_now":"Tentar agora","reload_update":"Recarregar","report_chat_warning":"Tem certeza de que deseja bloquear e relatar esta conversa?","requested_permissions":"Solicitados","save_attachment":"guardar","search_for_contacts":"Use a busca para encontrar contatos","search_no_results":"A busca não retornou resultados","search_placeholder":"Liste como email:ciclanae@exemplo.com, tel:+351000555000...","sidepanel_title_acc_notifications":"Notificações","sidepanel_title_acc_support":"Suporte","sidepanel_title_account_settings":"Configurações da Conta","sidepanel_title_archive":"Conversas Arquivadas","sidepanel_title_blocked":"Conversas Bloqueadas","sidepanel_title_cred":"Confirme as Credenciais","sidepanel_title_login":"Entrar","sidepanel_title_newtpk":"Começar Nova Conversa","sidepanel_title_register":"Criar Conta","sidepanel_title_reset":"Redefinir Senha","sidepanel_title_settings":"Configurações","stay_logged_in":"Manter-se logado","tabtitle_find_user":"procurar","tabtitle_group_by_id":"por id","tabtitle_new_group":"novo grupo","tags_editor_no_tags":"Adicione algumas tags","tags_not_found":"Nenhuma tag definida. Adicione algumas.","title_all_contacts":"Todos os Contatos","title_group_members":"Membros do Grupo","title_manage_tags":"Gerenciar","title_not_found":"Não encontrado","title_permissions":"Permissões","title_tag_manager":"Tags (pesquisar & descobrir)","topic_block_warning":"Você tem certeza que deseja bloquear esta conversa?","topic_delete_warning":"Você tem certeza que deseja excluir esta conversa? Não pode ser desfeito.","topic_name_editing_placeholder":"Nome livre do grupo","unnamed_topic":"Sem nome","update_available":"Atualização disponível.","upload_finishing":"finalizando...","user_not_found":"Não encontrado","description_editing_placeholder":"Descrição opcional","label_description":"Descrição","button_security":"Segurança","panel_title_crop":"Arraste para Ajustar","panel_title_general":"Geral","panel_title_members":"Membros","panel_title_security":"Segurança","panel_title_info":"Info","permissions_anonymous":"Anônimo","permissions_authenticated":"Autenticado","topic_delete":"Apagar Tópico","permissions_user":"Permissões do Usuário","password_reset_email_sent":"Um email com o código de segurança foi enviado.","label_unarchive_topic":"Arquivado:","menu_item_forward":"Encaminhar","forward_to":"Encaminhar para","forward_to_search_placeholder":"Pesquisar contatos","label_new_password":"Nova senha","drafty_unknown":"Não suportado","calls_incoming":"Chamada recebida","calls_outgoing":"Chamada saída","calls_you_label":"Você","menu_item_video_call":"Chamada de vídeo","already_in_call":"Você já está em uma chamada!","call_cancelled":"cancelada","call_missed":"perdida","call_declined":"recusada","call_disconnected":"desconectada","label_use_secure_connection":"Use uma conexão segura","cred_confirmed_successfully":"Confirmado com sucesso","menu_item_edit":"Editar","editing_message":"Editando","message_edited_marker":", editado","icon_title_add_image":"Adicionar imagem","icon_title_attach_file":"Anexar arquivo","icon_title_delete":"Excluir","icon_title_pause":"Pausar","icon_title_play":"Reproduzir","icon_title_record_voice":"Gravar mensagem de voz","icon_title_resume":"Retomar","icon_title_send":"Enviar mensagem","drag_file":"Arraste o arquivo aqui","drafty_video":"Gravação de vídeo","call_in_progress":"em andamento","menu_item_audio_call":"Chamada","unrecognized_video_format":"O formato deste vídeo não é reconhecido","loading_note":"Carregando...","password_i_have_code":"Tenho o código","label_reset_password_tel":"Enviar SMS para redefinir senha:","mobile_phone_number":"Número do celular/telemóvel","password_reset_sms_sent":"Uma mensagem de texto com o código de segurança foi enviada.","mobile_number_required":"Número do celular/telemóvel é obrigatório","current_email":"Email atual","current_phone":"Número do telefone atual","new_email":"Novo email","new_phone_number":"Novo número de telefone","change_email":"Trocar email","change_phone":"Trocar número de telefone","call_busy":"ocupado","failed_to_init_audio":"Falha ao inicializar a gravação de áudio","pin_message":"Fixar","unpin_message":"Desafixar","password_reset_success":"Senha redefinida com sucesso","scan_qr_code":"Escanear código QR","self_topic_comment":"Anotações, mensagens, links, arquivos salvos para posteridade","self_topic_name":"Mensagens salvas","message_not_found":"mensagem não encontrada","message_deleted":"mensagem excluída","alias_editing_placeholder":"Nome alternativo (optional)","label_alias":"Nome alternativo:","alias_already_taken":"(já está em uso)","alias_invalid":"(inválido)","label_alias_edit":"Nome alternativo","select_country":"Selecione o país","text_copied":"Texto copiado para a área de transferência","color_schema_dark":"Escuro","color_schema_light":"Claro","color_schema_system":"Padrão do sistema","label_color_schema":"Tema:","label_text_size":"Tamanho do texto:","incognito_mode_description":"Oculta seu status online e indicadores de digitação.","send_command":"Envia com {key}","send_command_explained":"Pressione Enter para nova linha","send_plain":"Envia com Enter","send_plain_explained":"Pressione Shift + Enter para nova linha","sidepanel_title_acc_general":"Geral","label_keyboard":"Teclado:","pin_chat":"Afixar","unpin_chat":"Desafixar","subscriber_count":"{count, plural, one {{count, number} subscriber} outro {{count, number} subscribers}}","label_member_count":"Membros:","label_subscriber_count":"Assinantes:","member_count":"{count, plural, one {{count, number} member} outro {{count, number} members}}","tabtitle_image":"imagem","tabtitle_pattern":"padrão","button_restore":"Restaurar para o padrão","wallpapers":"Wallpapers","label_blur_wallpaper":"Blur:","label_scan_id":"Leia meu ID:","unknown_name":"Desconhecido","save_action":"Guardar","cannot_parse_vcard":"Não é possível analisar o arquivo vCard.","chat_now":"Conversar","find_user":"Encontrar","label_contact_card":"Cartão de Contato","label_contacts":"Contatos","add_members_prompt":"adicionar membros"}
\ No newline at end of file
diff --git a/src/i18n.min/ro.json b/src/i18n.min/ro.json
deleted file mode 100644
index dd8886c6..00000000
--- a/src/i18n.min/ro.json
+++ /dev/null
@@ -1 +0,0 @@
-{"action_block_contact":"Blochează contact","action_cancel":"anulează","action_clear_messages":"Șterge mesajele","action_delete_messages":"Șterge toate mesajele","action_leave_chat":"Ieși din conversație","action_report_chat":"Raportează conversația","archived_contacts":"Contacte arhivate ({count})","badge_danger":"Suspicios","badge_owner":"owner","badge_staff":"Administrare","badge_verified":"Verificat","badge_you":"tu","block_contact_warning":"Ești sigur că dorești să blochezi acest contact?","blocked_contacts_link":"Contact blocate ({count})","button_add_members":"Adaugă persoane","button_cancel":"Anulează","button_confirm":"Confirmă","button_create":"Creează","button_delete_account":"Șterge cont","button_edit":"Editează","button_logout":"Deconectare","button_ok":"OK","button_reset":"Resetează","button_send_request":"Trimite cerere","button_sign_in":"Conectează-te","button_sign_up":"Creează cont","button_subscribe":"Subscribe","button_update":"Actualizează","cannot_initiate_file_upload":"Nu se poate face încărcarea.","channel":"canal","channel_prompt":"Acesta este un canal","chat_invitation":"Ai primit invitație într-o conversație. Accepți?","chat_invitation_accept":"Da, accept","chat_invitation_block":"Blochează","chat_invitation_ignore":"Ignoră","clear_messages_warning":"Ești sigur că dorești să ștergi mesajele? Acestea nu pot fi recuperate.","code_doesnot_match":"Codul furnizat nu este valid","contacts_not_found":"Nu ai contacte salvate\n¯∖_(ツ)_/¯","contacts_not_found_short":"Niciun contact găsit după criteriile ''{query}''","credential_email_prompt":"Adresa de e-mail","delete_account":"Șterge contul","delete_account_warning":"Ești sigur că dorești să ștergi contul? Acesta nu va putea fi recuperat.","delete_messages_warning":"Ești sigur că dorești să șterge mesajele din toate conversațiile? Acestea nu pot fi recuperate.","download_action":"download","drafty_attachment":"Atasament","drafty_form":"Formă:","drafty_image":"Imagine","email_dative":"email","email_prompt":"Adresa de e-mail","enable_peers_messaging":"Activează","enter_confirmation_code_prompt":"Introu codul de confirmare trimis pe {method}:","error_invalid_id":"ID Invalid","file_attachment_too_large":"Dimensiunea fișiterului {size} depășește limita de {limit}.","forgot_password_link":"Ți-ai uitat parola?","full_name_prompt":"Numele tău","granted_permissions":"Oferite","group_has_no_members":"În acest grup nu se află persoane","group_user_id_prompt":"Grup sau user ID","image_caption_prompt":"Titlul imaginii","invalid_content":"conținut invalid","invalid_security_token":"Codul de securitate este invalid","label_client":"Client:","label_content_type":"Tip conținut:","label_default_access_mode":"Default access mode:","label_file_name":"Nume fișier:","label_group_members":"Membrii grupului:","label_incognito_mode":"Mod incognito:","label_message_sound":"Sunet de mesaj:","label_muting_topic":"Mod silențios (muted):","label_other_user":"Altele","label_password":"Parola","label_permissions":"Permisiuni:","label_private":"Comentariu privat","label_push_notifications":"Alerte de notificare:","label_push_notifications_disabled":"Alerte de notificare (necesită HTTPS):","label_reset_password":"Resetează parola:","label_sdk":"SDK:","label_server":"Server:","label_server_address":"Adresa serverului:","label_server_to_use":"Server de utilizat:","label_size":"Size:","label_topic_name":"Nume","label_user_contacts":"Contacte","label_user_id":"ID:","label_wire_transport":"Transportul legat:","label_you":"Tu:","label_your_name":"Numele tău","label_your_permissions":"Permisiuniile tale:","last_seen_timestamp":"Văzut ultima dată","leave_chat_warning":"Ești sigur că dorești să ieși din conersație?","link_contact_us":"Contactează-ne","link_privacy_policy":"Politica de Confidențialitate","link_terms_of_service":"Termenii Serviciului","login_prompt":"Conectează-te","menu_item_archive_topic":"Arhivează","menu_item_block":"Blochează","menu_item_clear_messages":"Șterge mesajele","menu_item_clear_messages_for_all":"Ștergele pe toate","menu_item_delete":"Șterge","menu_item_delete_for_all":"Ștergele pe toate","menu_item_delete_topic":"Șterge","menu_item_edit_permissions":"Editează permisiuni","menu_item_info":"Info","menu_item_member_delete":"Șterge persoană","menu_item_mute":"Mute","menu_item_restore_topic":"Restabili","menu_item_send_retry":"Reîncearcă","menu_item_unblock":"Deblochează","menu_item_unmute":"Unmute","message_sending":"se trimite...","message_sending_failed":"eroare","messages_not_readable":"Mesajele nu pot fi citite","messaging_disabled_prompt":"Mesageria este dezactivată","more_online_members":"+{overflow} mai mult","new_message_prompt":"Mesaj nou","new_password_placeholder":"Introdu parolă","no_connection":"Nu există conexiune","no_contacts":"Nu ai contacte adăugate :-(","numeric_confirmation_code_prompt":"Doar cifre","online_now":"online acum","password_prompt":"Parola","password_unchanged_prompt":"Neschimbată","peers_messaging_disabled":"Mesageria de tip Peer este dezactivată.","permission_admin":"Aprobare ({val})","permission_delete":"Ștergere ({val})","permission_join":"Participare ({val})","permission_owner":"Deținător ({val})","permission_pres":"Notificare ({val})","permission_read":"Citire ({val})","permission_share":"Distribuire ({val})","permission_write":"Scriere ({val})","phone_dative":"telefon","private_editing_placeholder":"Vizibil doar ție","push_init_failed":"Nu s-a reușit inițializarea tip push notifications","reconnect_countdown":"Deconectat. Se încearcă conectarea în {seconds}…","reconnect_now":"Încearcă din nou","reload_update":"Reîncarcă","report_chat_warning":"Ești sigur că dorești să blochezi și să raportezi această conversație?","requested_permissions":"Necesare","save_attachment":"salvează","search_for_contacts":"Caută în contacte","search_no_results":"Nu s-au găsit rezultate","search_placeholder":"Caută după e-mail sau număr telefon...","sidepanel_title_acc_notifications":"Notificări","sidepanel_title_acc_support":"Ajutor","sidepanel_title_account_settings":"Setările Contului","sidepanel_title_archive":"Conversații Arhivate","sidepanel_title_blocked":"Conversații Blocate","sidepanel_title_cred":"Confirmă credențiale","sidepanel_title_login":"Conectează-te","sidepanel_title_newtpk":"Creeză un nou Chat","sidepanel_title_register":"Creează Cont","sidepanel_title_reset":"Resetează Parola","sidepanel_title_settings":"Setări","stay_logged_in":"Rămâi conectat","tabtitle_find_user":"caută","tabtitle_group_by_id":"după id","tabtitle_new_group":"grup nou","tags_editor_no_tags":"Adaugă tag-uri","tags_not_found":"Niciun tag definit. Adaugă.","title_all_contacts":"Toate Contactele","title_group_members":"Persoanele din grup","title_manage_tags":"Administrează","title_not_found":"Nu a fost găsit","title_permissions":"Permisiuni","title_tag_manager":"Tag-uri (user discovery)","topic_block_warning":"Ești sigur că dorești să blochezi această conversație?","topic_delete_warning":"Ești sigur că dorești să șterge această conversație?","topic_name_editing_placeholder":"Numele grupului","unnamed_topic":"Nedenumit","update_available":"Actualizare disponibilă.","upload_finishing":"se încarcă...","user_not_found":"Utilizatorul nu a fost găsit","description_editing_placeholder":"Descriere (opțional)","label_description":"Descriere","button_security":"Securitate","panel_title_crop":"Trageți pentru a ajusta","panel_title_general":"Generale","panel_title_members":"Membri","panel_title_security":"Securitate","panel_title_info":"Info","permissions_anonymous":"Anonim","permissions_authenticated":"Autentificat","topic_delete":"Ștergeți Chat","permissions_user":"Permisiunile utilizatorului","password_reset_email_sent":"A fost trimis un e-mail cu cod de securitate.","label_unarchive_topic":"Arhivat:","menu_item_reply":"A raspunde","menu_item_forward":"Redirecţiona","forward_to":"Redirecționați către","forward_to_search_placeholder":"Căutați contacte","label_new_password":"Parolă Nouă","drafty_unknown":"Neacceptat","calls_incoming":"Apel primit","calls_outgoing":"Apel efectuat","calls_you_label":"Tu","menu_item_video_call":"Apel video","already_in_call":"Deja vorbesti!","call_cancelled":"anulat","call_missed":"pierdut","call_declined":"refuzat","call_disconnected":"deconectat","label_use_secure_connection":"Utilizați conexiune securizată","cred_confirmed_successfully":"Confirmat cu succes","menu_item_edit":"Editează","editing_message":"Editare","message_edited_marker":", editat","icon_title_add_image":"Adauga imagine","icon_title_attach_file":"Ataseaza fisier","icon_title_delete":"Ștergeți înregistrarea","icon_title_pause":"Întrerupeți redarea","icon_title_play":"Redați înregistrarea","icon_title_record_voice":"Înregistrați mesajul vocal","icon_title_resume":"Reluați redarea","icon_title_send":"Trimite mesaj","drag_file":"Trageți fișierul aici","drafty_video":"Înregistrare video","call_in_progress":"în curs","menu_item_audio_call":"Apel","unrecognized_video_format":"Formatul acestui videoclip nu este recunoscut","loading_note":"Se încarcă...","password_i_have_code":"Am cod","label_reset_password_tel":"Trimite SMS pentru a reseta parola","mobile_phone_number":"Număr de telefon mobil","password_reset_sms_sent":"A fost trimis un mesaj text cu cod de securitate.","mobile_number_required":"Este necesar un număr de telefon mobil","current_email":"E-mail curent","current_phone":"Număr de telefon actual","new_email":"E-mail nou","new_phone_number":"Număr de telefon nou","change_email":"Schimbați adresa de e-mail","change_phone":"Schimbați numărul de telefon","call_busy":"ocupat","pin_message":"Fixați","unpin_message":"Anulați fixarea","failed_to_init_audio":"Inițializarea înregistrării audio a eșuat","password_reset_success":"Resetarea parolei cu succes","scan_qr_code":"Scanați codul QR","self_topic_comment":"Note, mesaje, linkuri, fișiere salvate pentru posteritate","self_topic_name":"Mesaje salvate","message_not_found":"mesajul nu a fost găsit","message_deleted":"mesaj șters","alias_editing_placeholder":"Alias (opțional)","label_alias":"Alias:","alias_already_taken":"(deja luat)","alias_invalid":"(nevalid)","label_alias_edit":"Alias","select_country":"Selectați țara","text_copied":"Copiat în clipboard","color_schema_dark":"Întunecat","color_schema_light":"Luminos","color_schema_system":"Implicit sistem","label_color_schema":"Temă:","label_text_size":"Dimensiune text:","incognito_mode_description":"Ascunde statusul tău online și indicatorii de tastare.","send_command":"Trimite cu {key}","send_command_explained":"Apasă Enter pentru linie nouă","send_plain":"Trimite cu Enter","send_plain_explained":"Apasă Shift + Enter pentru linie nouă","sidepanel_title_acc_general":"General","label_keyboard":"Tastatură:","pin_chat":"Fixează","unpin_chat":"Anulează fixarea","subscriber_count":"{count, plural, one {{count, number} abonat} few {{count, number} abonați} other {{count, number} de abonați}}","label_member_count":"Membri:","label_subscriber_count":"Abonați:","member_count":"{count, plural, one {{count, number} membru} few {{count, number} membri} other {{count, number} de membri}}","tabtitle_image":"imagine","tabtitle_pattern":"model","button_restore":"Restabilește implicit","wallpapers":"Imagini de fundal","label_blur_wallpaper":"Estompare:","label_scan_id":"Scanează ID-ul meu:","unknown_name":"Necunoscut","save_action":"Salvează","cannot_parse_vcard":"Nu se poate procesa fișierul vCard.","chat_now":"Conversație","find_user":"Caută","label_contact_card":"Card de contact","label_contacts":"Contacte","add_members_prompt":"adaugă membri"}
\ No newline at end of file
diff --git a/src/i18n.min/ru.json b/src/i18n.min/ru.json
deleted file mode 100644
index 1dafd027..00000000
--- a/src/i18n.min/ru.json
+++ /dev/null
@@ -1 +0,0 @@
-{"action_block_contact":"Заблокировать контакт","action_cancel":"отменить","action_clear_messages":"Удалить сообщения","action_delete_messages":"Удалить сообщения","action_leave_chat":"Уйти из чата","action_report_chat":"Сообщить о нарушении","archived_contacts":"Чаты в архиве ({count})","badge_danger":"Подозрительный","badge_owner":"влад.","badge_staff":"Администрация","badge_verified":"Верифицированный","badge_you":"вы","block_contact_warning":"Вы действительно заблокировать этот контакт?","blocked_contacts_link":"Заблокированные ({count})","button_add_members":"Добавить","button_cancel":"Отменить","button_confirm":"Подтвердить","button_create":"Создать","button_delete_account":"Удалить аккаунт","button_edit":"Изменить","button_logout":"Выйти","button_ok":"OK","button_reset":"Изменить","button_send_request":"Отправить","button_sign_in":"Войти","button_sign_up":"Создать аккаунт","button_subscribe":"Подписаться","button_update":"Применить","cannot_initiate_file_upload":"Ошибка загрузки файла.","channel":"канал","channel_prompt":"Создать канал","chat_invitation":"Вас пригласили начать новый чат. Как вы хотите поступить?","chat_invitation_accept":"Принять","chat_invitation_block":"Заблокировать","chat_invitation_ignore":"Игнорировать","clear_messages_warning":"Вы действительно хотите удалить все сообщения в чате? Их будет невозможно восстановить.","code_doesnot_match":"Код не совпадает","contacts_not_found":"Чатов нет\n¯∖_(ツ)_/¯","contacts_not_found_short":"Нет контактов для запроса ''{query}''","credential_email_prompt":"Регистрационный емейл","delete_account":"Удалить аккаунт","delete_account_warning":"Вы уверены, что ходите удалить свой аккаунт? Его невозможно будет восстановить.","delete_messages_warning":"Вы действительно хотите удалить все сообщения?","download_action":"скачать","drafty_attachment":"Аттачмент","drafty_form":"Форма:","drafty_image":"Картинка","email_dative":"емейлу","email_prompt":"Email, напр. ivan@example.com","enable_peers_messaging":"Разблокировать.","enter_confirmation_code_prompt":"Код подтверждения, полученный по {method}:","error_invalid_id":"Неверный ID","file_attachment_too_large":"Размер файла {size} превышает {limit} лимит.","forgot_password_link":"Напомнить пароль","full_name_prompt":"Полное имя, напр. Иван Петров","granted_permissions":"Получены","group_has_no_members":"Нет участников","group_user_id_prompt":"ID чата или пользователя","image_caption_prompt":"Подпись к фото","invalid_content":"сообщение не читается","invalid_security_token":"Токен некорректен","label_client":"Клиент:","label_content_type":"Тип:","label_default_access_mode":"Доступ по умолчанию:","label_file_name":"Имя файла:","label_group_members":"Участники чата:","label_incognito_mode":"Режим инкогнито:","label_message_sound":"Звук нового сообщения:","label_muting_topic":"Без уведомлений","label_other_user":"Второй","label_password":"Пароль","label_permissions":"Права доступа:","label_private":"Комментарий","label_push_notifications":"Уведомления:","label_push_notifications_disabled":"Уведомления (требуют HTTPS):","label_reset_password":"Отправить емейл для смены пароля:","label_sdk":"SDK:","label_server":"Сервер:","label_server_address":"Адрес сервера:","label_server_to_use":"Использовать сервер:","label_size":"Размер:","label_topic_name":"Название","label_user_contacts":"Конакты","label_user_id":"ID:","label_wire_transport":"Соединение:","label_you":"Вы:","label_your_name":"Ваше имя","label_your_permissions":"Ваши права доступа:","last_seen_timestamp":"Был активен","leave_chat_warning":"Вы действительно хотите покинуть этот чат?","link_contact_us":"Связаться с нами","link_privacy_policy":"Политика конфиденциальности","link_terms_of_service":"Условия сервиса","login_prompt":"Логин","menu_item_archive_topic":"В архив","menu_item_block":"Заблокировать","menu_item_clear_messages":"Удалить сообщения","menu_item_clear_messages_for_all":"Удалить для всех","menu_item_delete":"Удалить","menu_item_delete_for_all":"Удалить для всех","menu_item_delete_topic":"Удалить чат","menu_item_edit_permissions":"Права доступа","menu_item_info":"Информация","menu_item_member_delete":"Отписать","menu_item_mute":"Не уведомлять","menu_item_restore_topic":"Разархивировать","menu_item_send_retry":"Отправить заново","menu_item_unblock":"Разблокировать","menu_item_unmute":"Уведомлять","message_sending":"в пути...","message_sending_failed":"ошибка","messages_not_readable":"нет доступа к сообщениям","messaging_disabled_prompt":"Отправка недоступна","more_online_members":"+еще {overflow}","new_message_prompt":"Новое сообщение","new_password_placeholder":"Введите новый пароль","no_connection":"Нет связи","no_contacts":"Ничего нет :-(","numeric_confirmation_code_prompt":"Только цифры","online_now":"онлайн","password_prompt":"Пароль","password_unchanged_prompt":"Не изменен","peers_messaging_disabled":"Чат заблокирован у корреспондента.","permission_admin":"Подтверждать ({val})","permission_delete":"Удалять ({val})","permission_join":"Подписываться ({val})","permission_owner":"Владелец ({val})","permission_pres":"Уведомлять ({val})","permission_read":"Читать ({val})","permission_share":"Приглашать ({val})","permission_write":"Писать ({val})","phone_dative":"телефону","private_editing_placeholder":"Виден только вам","push_init_failed":"Ошибка инициализации пуш уведомлений","reconnect_countdown":"Нет связи. Подключение через {seconds}…","reconnect_now":"Подключить сейчас.","reload_update":"Обновить","report_chat_warning":"Вы действительно хотите сообщить о нарушении и заблокировать этот чат?","requested_permissions":"Требуются","save_attachment":"сохранить","search_for_contacts":"Поиск контактов","search_no_results":"Ничего не найдено","search_placeholder":"Список, напр. email:alice@example.com, tel:+17025550003...","sidepanel_title_acc_notifications":"Уведомления","sidepanel_title_acc_support":"Поддержка","sidepanel_title_account_settings":"Настройки аккаунта","sidepanel_title_archive":"Архив чатов","sidepanel_title_blocked":"Заблокированные чаты","sidepanel_title_cred":"Подтвердить","sidepanel_title_login":"Авторизация","sidepanel_title_newtpk":"Новый чат","sidepanel_title_register":"Зарегистрироваться","sidepanel_title_reset":"Сменить пароль","sidepanel_title_settings":"Настройки","stay_logged_in":"Запомнить","tabtitle_find_user":"найти","tabtitle_group_by_id":"по id","tabtitle_new_group":"создать","tags_editor_no_tags":"Добавьте теги","tags_not_found":"Тегов нет. Добавьте.","title_all_contacts":"Все контакты","title_group_members":"Участники","title_manage_tags":"Редактировать","title_not_found":"Не найден","title_permissions":"Права доступа","title_tag_manager":"Теги для поиска","topic_block_warning":"Вы действительно хотите заблокировать этот чат?","topic_delete_warning":"Вы действительно хотите удалить этот чат?","topic_name_editing_placeholder":"Название чата","unnamed_topic":"Без названия","update_available":"Есть новая версия приложения.","upload_finishing":"завершение...","user_not_found":"Не найден","description_editing_placeholder":"Описание (не обязательно)","label_description":"Описание","button_security":"Безопасность","panel_title_crop":"Обрезать картинку","panel_title_general":"Общие настройки","panel_title_members":"Участники","panel_title_security":"Безопасность","panel_title_info":"Подробности","permissions_anonymous":"Анонимный","permissions_authenticated":"Авторизованный","topic_delete":"Удалить чат","permissions_user":"Права доступа","password_reset_email_sent":"Сообщение было отправлено на указанный email.","label_unarchive_topic":"Архивирован:","menu_item_reply":"Ответить","menu_item_forward":"Переслать","forward_to":"Переслать","forward_to_search_placeholder":"Поиск контактов","label_new_password":"Новый пароль","drafty_unknown":"Не поддерживается","calls_incoming":"Входящий звонок","calls_outgoing":"Исходящий звонок","calls_you_label":"Вы","menu_item_video_call":"Видеозвонок","already_in_call":"Вы уже звоните кому-то!","call_cancelled":"отменен","call_missed":"пропущен","call_declined":"отклонен","call_disconnected":"разъединен","label_use_secure_connection":"Безопасное соединение","cred_confirmed_successfully":"Подтверждено успешно","menu_item_edit":"Изменить","editing_message":"Редактирование","message_edited_marker":", изменено","icon_title_add_image":"Добавить изображение","icon_title_attach_file":"Прикрепить файл","icon_title_delete":"Удалить запись","icon_title_pause":"Пауза воспроизведения","icon_title_play":"Воспроизвести запись","icon_title_record_voice":"Записать голосовое сообщение","icon_title_resume":"Продолжить","icon_title_send":"Отправить сообщение","drag_file":"Перетащите файл сюда","drafty_video":"Видеозапись","call_in_progress":"в процессе","menu_item_audio_call":"Позвонить","unrecognized_video_format":"Неизвестный формат видео файла","loading_note":"Загружается...","password_i_have_code":"У меня есть код","label_reset_password_tel":"Отправить SMS для сброса пароля","mobile_phone_number":"Номер мобильного телефона","password_reset_sms_sent":"SMS с защитным кодом отправлен.","mobile_number_required":"Номер мобильного телефона","current_email":"Текущий e-mail","current_phone":"Текущий номер телефона","new_email":"Новый e-mail","new_phone_number":"Новый номер телефона","change_email":"Изменить e-mail","change_phone":"Изменить номер телефона","call_busy":"занято","pin_message":"Закрепить","unpin_message":"Открепить","failed_to_init_audio":"Не удалось инициализировать аудиозапись","password_reset_success":"Пароль успешно изменен","scan_qr_code":"Отсканируйте QR код","self_topic_comment":"Заметки, сообщения, ссылки, файлы, сохраненные для потомков","self_topic_name":"Сохраненные сообщения","message_not_found":"сообщение не найдено","message_deleted":"сообщение удалено","alias_editing_placeholder":"Псевдоним (необязательно)","label_alias":"Псевдоним:","alias_already_taken":"(уже занято)","alias_invalid":"(некорректно)","label_alias_edit":"Псевдоним","select_country":"Выберите страну","text_copied":"Скопировано в буфер обмена","color_schema_dark":"Тёмная","color_schema_light":"Светлая","color_schema_system":"Системная","label_color_schema":"Тема:","label_text_size":"Размер текста:","incognito_mode_description":"Скрывать онлайн статус и индикаторы набора текста.","send_command":"Отправлять на {key}","send_command_explained":"Нажмать Enter для новой строки","send_plain":"Отправлять на Enter","send_plain_explained":"Нажимать Shift + Enter для новой строки","sidepanel_title_acc_general":"Основные","label_keyboard":"Клавиатура:","pin_chat":"Закрепить","unpin_chat":"Открепить","subscriber_count":"{count, plural, one {{count, number} подписчик} few {{count, number} подписчика} many {{count, number} подписчиков} other {{count, number} подписчиков}}","label_member_count":"Участники:","label_subscriber_count":"Подписчики:","member_count":"{count, plural, one {{count, number} участник} few {{count, number} участника} many {{count, number} участников} other {{count, number} участников}}","tabtitle_image":"картинка","tabtitle_pattern":"узор","button_restore":"Восстановить по умолчанию","wallpapers":"Обои","label_blur_wallpaper":"Размытие:","label_scan_id":"Отсканируйте мой ID:","unknown_name":"Неизвестный","save_action":"Сохранить","cannot_parse_vcard":"Не удалось обработать файл vCard.","chat_now":"Написать","find_user":"Найти","label_contact_card":"Карточка контакта","label_contacts":"Контакты","add_members_prompt":"добавить участников"}
\ No newline at end of file
diff --git a/src/i18n.min/th.json b/src/i18n.min/th.json
deleted file mode 100644
index e2da1dc6..00000000
--- a/src/i18n.min/th.json
+++ /dev/null
@@ -1 +0,0 @@
-{"action_block_contact":"บล็อครายชื่อ","action_cancel":"ยกเลิก","action_clear_messages":"ล้างข้อความ","action_delete_messages":"ล้างข้อความทั้งหมด","action_leave_chat":"ออกจากการสนทนา","action_report_chat":"รายงานการสนทนา","archived_contacts":"รายชื่อที่สำเร็จ ({count})","badge_danger":"ไม่น่าไว้วางใจ","badge_owner":"เจ้าของ","badge_staff":"ผู้ดูแล","badge_verified":"ยืนยันแล้ว/ทางการ","badge_you":"คุณ","block_contact_warning":"คุณแน่ใจหรือไม่? คุณต้องการบล็อครายชื่อนี้","blocked_contacts_link":"รายชื่อที่ถูกบล็อก ({count})","button_add_members":"เพิ่มสมาชิก","button_cancel":"ยกเลิก","button_confirm":"ยืนยัน","button_create":"สร้าง","button_delete_account":"ลบบัญชี","button_edit":"แก้ไข","button_logout":"ลงชื่อออก","button_ok":"ตกลง","button_reset":"รีเซ็ต","button_send_request":"ส่งคำขอ","button_sign_in":"ลงชื่อเข้าใช้","button_sign_up":"สร้างบัญชี","button_subscribe":"ติดตาม","button_update":"อัพเดต","cannot_initiate_file_upload":"ไม่สามารถอัพโหลดไฟล์","channel":"ช่อง","channel_prompt":"นี่คือช่อง","chat_invitation":"คุณถูกเชิญให้เข้าร่วมแชทใหม่ คุณต้องการทำอะไร?","chat_invitation_accept":"ยอมรับ","chat_invitation_block":"บล็อค","chat_invitation_ignore":"ไม่สนใจ","clear_messages_warning":"คุณแน่ใจหรือไม่ที่จะล้างข้อความทั้งหมด? ส่ิงนี้ไม่สามารถเลิกทำได้","code_doesnot_match":"โค้ด ไม่ถูกต้อง","contacts_not_found":"ไม่พบแชท \n¯∖_(ツ)_/¯","contacts_not_found_short":"ไม่พบรายชื่อ ''{query}''","credential_email_prompt":"อีเมลที่ลงทะเบียนของคุณ","delete_account":"ลบบัญชี","delete_account_warning":"คุณแน่ใจหรือไม่ที่จะลบบัญชีผู้ใช้ของคุณ? สิ่งนี้ไม่สามารถเลิกทำได้.","delete_messages_warning":"คุณแน่ใจหรือไม่ที่จะลบข้อความทั้งหมดสำหรับทุกคน? ส่ิงนี้ไม่สามารถเลิกทำได้","download_action":"ดาวน์โหลด","drafty_attachment":"สิ่งที่แนบมา","drafty_form":"ฟอร์ม:","drafty_image":"รูปภาพ","email_dative":"อีเมล์","email_prompt":"อีเมล, เช่น jdoe@example.com","enable_peers_messaging":"เปิดใช้งาน","enter_confirmation_code_prompt":"รหัสยืนยัน","error_invalid_id":"ID ไม่ถูกต้อง","file_attachment_too_large":"ขนาดไฟล์ {size} เกิน {limit} ที่กำหนด.","forgot_password_link":"ลืมรหัสผ่าน?","full_name_prompt":"ชื่อเต็ม, เช่น John Doe","granted_permissions":"ยินยอม","group_has_no_members":"ไม่มีสมาชิก","group_user_id_prompt":"กลุ่ม หรือ ID ผู้ใช้","image_caption_prompt":"คำอธิบายภาพ","invalid_content":"เนื้อหาไม่ถูกต้อง","invalid_security_token":"security token ไม่ถูกต้อง","label_client":"Client:","label_content_type":"ชนิดของเนื้อหา:","label_default_access_mode":"โหมดการเข้าถึงเริ่มต้น:","label_file_name":"ชื่อไฟล์:","label_group_members":"สมาชิกกลุ่ม:","label_incognito_mode":"โหมดไม่ระบุตัวตน:","label_message_sound":"เสียงข้อความ:","label_muting_topic":"ไม่สนใจ:","label_other_user":"อื่นๆ","label_password":"รหัสผ่าน","label_permissions":"สิทธิ์:","label_private":"ความคิดเห็นส่วนตัว","label_push_notifications":"การแจ้งเตือน:","label_push_notifications_disabled":"การแจ้งเตือน (requires HTTPS):","label_reset_password":"ส่งรหัสรีเซ็ตอีเมล:","label_sdk":"SDK:","label_server":"เซิร์ฟเวอร์:","label_server_address":"ที่อยู่เซิร์ฟเวอร์:","label_server_to_use":"เลือกเซิร์ฟเวอร์:","label_size":"ขนาด:","label_topic_name":"ชื่อ","label_user_contacts":"รายชื่อ","label_user_id":"ID:","label_wire_transport":"Wire transport:","label_you":"คุณ:","label_your_name":"ชื่อของคุณ","label_your_permissions":"สิทธิของคุณ:","last_seen_timestamp":"ล่าสุดเมื่อ","leave_chat_warning":"คุณแน่ใจหรือไม่ที่จะออกจากการสนทนานี้?","link_contact_us":"ติดต่อเรา","link_privacy_policy":"ข้อกำหนดความเป็นส่วนตัว","link_terms_of_service":"ข้อกำหนดการใช้งาน","login_prompt":"ล็อกอิน","menu_item_archive_topic":"สำเร็จ","menu_item_block":"บล็อค","menu_item_clear_messages":"ล้างข้อความ","menu_item_clear_messages_for_all":"ล้างทั้งหมด","menu_item_delete":"ลบ","menu_item_delete_for_all":"ลบทั้งหมด","menu_item_delete_topic":"ลบ","menu_item_edit_permissions":"แก้ไขสิทธิ","menu_item_info":"ข้อมูล","menu_item_member_delete":"ลบ","menu_item_mute":"ปิดการแจ้งเตือน","menu_item_reply":"ตอบกลับ","menu_item_restore_topic":"เรื่มใหม่","menu_item_send_retry":"ลองอีกครั้ง","menu_item_unblock":"ปลดบล็อค","menu_item_unmute":"เปิดการแจ้งเตือน","message_sending":"กำลังส่ง...","message_sending_failed":"ล้มเหลว","messages_not_readable":"ไม่มีสิทธิเข้าถึงข้อความ","messaging_disabled_prompt":"ปิดการส่งข้อความ","more_online_members":"+{overflow} เพิ่มเติม","new_message_prompt":"ข้อความใหม่","new_password_placeholder":"ใส่รหัสใหม่","no_connection":"ไม่มีการเชื่อมต่อ","no_contacts":"คุณไม่มีรายชื่อ :-(","numeric_confirmation_code_prompt":"ตัวเลขเท่านั้น","online_now":"ออนไลน์อยู่","password_prompt":"รหัสผ่าน","password_unchanged_prompt":"ไม่เปลี่ยนแปลง","peers_messaging_disabled":"ปิดใช้งาน การส่งข้อความ Peer's","permission_admin":"ยอมรับ ({val})","permission_delete":"ลบ ({val})","permission_join":"เข้าร่วม ({val})","permission_owner":"เจ้าของ ({val})","permission_pres":"รับการแจ้งเตือน ({val})","permission_read":"อ่าน ({val})","permission_share":"แชร์ ({val})","permission_write":"เขียน ({val})","phone_dative":"มือถือ","private_editing_placeholder":"แค่คุณที่เห็นสิ่งนี้","push_init_failed":"การแจ้งเตือนล้มเหลว","reconnect_countdown":"ถูกตัดการเชื่อมต่อ. จะเชื่อมต่อใหม่ใน {seconds}…","reconnect_now":"ลองเลย","reload_update":"โหลดใหม่","report_chat_warning":"คุณแน่ใจหรือไม่ที่จะบล็อคและรายงานการสนทนานี้","requested_permissions":"ต้องการ","save_attachment":"บันทึก","search_for_contacts":"ใช้ค้นหารายชื่อ","search_no_results":"ไม่พบผลลัพธ์","search_placeholder":"เช่น email:alice@example.com, tel:17025550003...","sidepanel_title_acc_notifications":"การแจ้งเตือน","sidepanel_title_acc_support":"สนับสนุน","sidepanel_title_account_settings":"การตั้งค่าบัญชี","sidepanel_title_archive":"แชทที่สำเร็จ","sidepanel_title_blocked":"แชทที่ถูกบล็อค","sidepanel_title_cred":"ยืนยันตัวตน","sidepanel_title_login":"ลงชื่อเข้าใช้","sidepanel_title_newtpk":"เริ่มแชทใหม่","sidepanel_title_register":"สร้างบัญชี","sidepanel_title_reset":"รีเซ็ตรหัสผ่าน","sidepanel_title_settings":"ตั้งค่า","stay_logged_in":"คงอยู่ในระบบ","tabtitle_find_user":"หา","tabtitle_group_by_id":"ด้วย ID","tabtitle_new_group":"กลุ่มใหม่","tags_editor_no_tags":"เพิ่มแท็ก","tags_not_found":"ไม่มีการกำหนดแท็ก เพิ่มแท็กเลย","title_all_contacts":"รายชื่อทั้งหมด","title_group_members":"สมาชิกกลุ่ม","title_manage_tags":"จัดการ","title_not_found":"ไม่พบ","title_permissions":"การอนุญาต","title_tag_manager":"แท็ก (ค้นหา & ค้นพบ)","topic_block_warning":"คุณแน่ใจหรือไม่ที่ต้องการบล็อกการสนทนานี้?","topic_delete_warning":"คุณแน่ใจหรือไม่ที่ต้องการลบการสนทนานี้? สิ่งนี้ไม่สามารถเลิกทำได้","topic_name_editing_placeholder":"ชื่อกลุ่มอิสระ","unnamed_topic":"ไม่มีชื่อ","update_available":"มีการอัพเดตพร้อมใช้งาน","upload_finishing":"กำลังจบ...","user_not_found":"ไม่พบ","description_editing_placeholder":"คำอธิบาย (ไม่บังคับ)","label_description":"คำอธิบาย","button_security":"ความปลอดภัย","panel_title_crop":"ลากเพื่อปรับ","panel_title_general":"ทั่วไป","panel_title_members":"สมาชิก","panel_title_security":"ความปลอดภัย","panel_title_info":"ข้อมูล","permissions_anonymous":"ไม่ระบุตัวตน","permissions_authenticated":"รับรองความถูกต้องแล้ว","topic_delete":"ลบการสนทนา","permissions_user":"การอนุญาตของผู้ใช้","password_reset_email_sent":"รหัสความปลอดภัยถูกส่งไปยังอีเมลแล้ว","label_unarchive_topic":"สำเร็จ:","menu_item_forward":"ส่งต่อ","forward_to":"ส่งต่อไปยัง","forward_to_search_placeholder":"ค้นหารายชื่อ","label_new_password":"รหัสผ่านใหม่","drafty_unknown":"ไม่รองรับ","calls_incoming":"สายโทรเข้า","calls_outgoing":"สายโทรออก","calls_you_label":"คุณ","menu_item_video_call":"วีดีโอคอล","already_in_call":"คุณกำลังอยู่ในการสนทนา!","call_cancelled":"ยกเลิกแล้ว","call_missed":"ไม่ได้รับสาย","call_declined":"ไม่รับสาย","call_disconnected":"ตัดการเชื่อมต่อแล้ว","label_use_secure_connection":"ใช้การเชื่อมต่อที่ปลอดภัย","cred_confirmed_successfully":"การยืนยันสำเร็จ","menu_item_edit":"แก้ไข","editing_message":"กำลังแก้ไข","message_edited_marker":", ถูกแก้ไข","icon_title_add_image":"เพิ่มรูปภาพ","icon_title_attach_file":"แนบไฟล์","icon_title_delete":"ลบ","icon_title_pause":"หยุด","icon_title_play":"เล่น","icon_title_record_voice":"บันทึกข้อความเสียง","icon_title_resume":"ต่อ","icon_title_send":"ส่งข้อความ","drag_file":"ลากไฟล์มาที่นี่","drafty_video":"กำลังบันทึกวิดีโอ","call_in_progress":"กำลังดำเนินการ","menu_item_audio_call":"โทร","unrecognized_video_format":"ไม่รู้จักรูปแบบของวิดีโอนี้","loading_note":"กำลังโหลด...","password_i_have_code":"ฉันมีรหัส","label_reset_password_tel":"ส่ง SMS เพื่อรีเซ็ตรหัสผ่าน:","mobile_phone_number":"หมายเลขโทรศัพท์มือถือ","password_reset_sms_sent":"ส่งข้อความพร้อมรหัสความปลอดภัยแล้ว","mobile_number_required":"จำเป็นต้องระบุหมายเลขโทรศัพท์มือถือ","current_email":"อีเมลปัจจุบัน","current_phone":"หมายเลขโทรศัพท์ปัจจุบัน","new_email":"อีเมลใหม่","new_phone_number":"หมายเลขโทรศัพท์ใหม่","change_email":"เปลี่ยนอีเมล","change_phone":"เปลี่ยนหมายเลขโทรศัพท์","call_busy":"ยุ่งอยู่","failed_to_init_audio":"ไม่สามารถเริ่มต้นการบันทึกเสียงได้","password_reset_success":"รีเซ็ตรหัสผ่านสำเร็จ","scan_qr_code":"สแกนรหัส QR","pin_message":"พิน","unpin_message":"ปลดหมุด","self_topic_comment":"บันทึก ข้อความ ลิงก์ ไฟล์ที่บันทึกไว้เพื่ออนาคต","self_topic_name":"ข้อความที่บันทึกไว้","message_not_found":"ไม่พบข้อความ","message_deleted":"ข้อความถูกลบ","alias_editing_placeholder":"นามแฝง (ไม่บังคับ)","label_alias":"นามแฝง:","alias_already_taken":"(ใช้ไปแล้ว)","alias_invalid":"(ไม่ถูกต้อง)","label_alias_edit":"นามแฝง","select_country":"เลือกประเทศ","text_copied":"คัดลอกไปยังคลิปบอร์ดแล้ว","color_schema_dark":"มืด","color_schema_light":"สว่าง","color_schema_system":"ค่าเริ่มต้นของระบบ","label_color_schema":"ธีม:","label_text_size":"ขนาดตัวอักษร:","incognito_mode_description":"ซ่อนสถานะออนไลน์และตัวบ่งชี้การพิมพ์ของคุณ","send_command":"ส่งด้วย {key}","send_command_explained":"กด Enter เพื่อขึ้นบรรทัดใหม่","send_plain":"ส่งด้วย Enter","send_plain_explained":"กด Shift + Enter เพื่อขึ้นบรรทัดใหม่","sidepanel_title_acc_general":"ทั่วไป","label_keyboard":"แป้นพิมพ์:","pin_chat":"ปักหมุด","unpin_chat":"เลิกปักหมุด","subscriber_count":"{count, plural, other {{count, number} ผู้ติดตาม}}","label_member_count":"สมาชิก:","label_subscriber_count":"ผู้ติดตาม:","member_count":"{count, plural, other {{count, number} สมาชิก}}","tabtitle_image":"รูปภาพ","tabtitle_pattern":"ลายลวดลาย","button_restore":"คืนค่าเริ่มต้น","wallpapers":"วอลเปเปอร์","label_blur_wallpaper":"เบลอ:","label_scan_id":"สแกน ID ของฉัน:","unknown_name":"ไม่ทราบ","save_action":"บันทึก","cannot_parse_vcard":"ไม่สามารถอ่านไฟล์ vCard ได้","chat_now":"แชท","find_user":"ค้นหา","label_contact_card":"นามบัตร","label_contacts":"ผู้ติดต่อ","add_members_prompt":"เพิ่มสมาชิก"}
\ No newline at end of file
diff --git a/src/i18n.min/uk.json b/src/i18n.min/uk.json
deleted file mode 100644
index 34b33084..00000000
--- a/src/i18n.min/uk.json
+++ /dev/null
@@ -1 +0,0 @@
-{"action_block_contact":"Заблокувати контакт","action_cancel":"Скасувати","action_clear_messages":"Очистити повідомлення","action_delete_messages":"Очистити повідомлення для всіх","action_leave_chat":"Покинути чат","action_report_chat":"Сповістити про порушення","archived_contacts":"Чати в архіві ({count})","badge_danger":"Підозрільний","badge_owner":"власник","badge_staff":"Адміністрація","badge_verified":"Верифікований","badge_you":"ви","block_contact_warning":"Ви дійсно бажаєте заблокувати цей контакт?","blocked_contacts_link":"Заблоковані контакти ({count})","button_add_members":"Додати учасників","button_cancel":"Скасувати","button_confirm":"Підтвердити","button_create":"Створити","button_delete_account":"Видалити акаунт","button_edit":"Змінити","button_logout":"Вийти","button_ok":"OK","button_reset":"Змінити","button_send_request":"Надіслати","button_sign_in":"Увійти","button_sign_up":"Створити акаунт","button_subscribe":"Підписатися","button_update":"Оновити","cannot_initiate_file_upload":"Помилка завантаження файлу.","channel":"канал","channel_prompt":"Створити канал","chat_invitation":"Вас запросили розпочати новий чат. Як ви хочете вчинити?","chat_invitation_accept":"Прийняти","chat_invitation_block":"Заблокувати","chat_invitation_ignore":"Ігнорувати","clear_messages_warning":"Ви дійсно бажаєте видалити всі повідомлення в чаті? Їх неможливо буде відновити.","code_doesnot_match":"Код не збігається","contacts_not_found":"Чатів немає\n¯∖_(ツ)_/¯","contacts_not_found_short":"Немає контактів для запиту ''{query}''","credential_email_prompt":"Реєстраційний емейл","delete_account":"Видалити акаунт","delete_account_warning":"Ви впевнені, що хочете видалити свій акаунт? Його неможливо буде відновити.","delete_messages_warning":"Ви впевнені, що бажаєте видалити всі повідомлення для всіх? Їх неможливо буде відновити.","download_action":"завантажити","drafty_attachment":"Аттачмент","drafty_form":"Форма:","drafty_image":"Картинка","email_dative":"емейлу","email_prompt":"Емейл, напр. ivan@example.com","enable_peers_messaging":"Розблокувати.","enter_confirmation_code_prompt":"Код підтвердження, отриманий по {method}:","error_invalid_id":"Невірний ID","file_attachment_too_large":"Розмір файла {size} перевищує {limit} ліміт.","forgot_password_link":"Нагадати пароль","full_name_prompt":"Повне ім'я, напр. Дмитро Соколов","granted_permissions":"Отримано","group_has_no_members":"Нема учасників","group_user_id_prompt":"ID чату або користувача","image_caption_prompt":"Підпис до фото","invalid_content":"повідомлення не читається","invalid_security_token":"Токен некоректний","label_client":"Клієнт:","label_content_type":"Тип:","label_default_access_mode":"Доступу по замовчуванню:","label_file_name":"Ім'я файлу:","label_group_members":"Учасники чату:","label_incognito_mode":"Режим інкогніто:","label_message_sound":"Звук нового повідомлення:","label_muting_topic":"Без сповіщень","label_other_user":"Інший","label_password":"Пароль","label_permissions":"Права доступу:","label_private":"Коментарій","label_push_notifications":"Сповіщення:","label_push_notifications_disabled":"Сповіщення (потребують HTTPS):","label_reset_password":"Надіслати емейл для зміни пароля:","label_sdk":"SDK:","label_server":"Сервер:","label_server_address":"Адреса сервера:","label_server_to_use":"Використання сервера:","label_size":"Розмір:","label_topic_name":"Назва","label_user_contacts":"Конакти","label_user_id":"ID:","label_wire_transport":"З'єднання:","label_you":"Ви:","label_your_name":"Ваше ім'я","label_your_permissions":"Ваші права доступу:","last_seen_timestamp":"Був активний","leave_chat_warning":"Ви дійсно бажаєте покинути цей чат?","link_contact_us":"Зв'язатися з нами","link_privacy_policy":"Політика конфіденційності","link_terms_of_service":"Умови сервісу","login_prompt":"Логін","menu_item_archive_topic":"В архів","menu_item_block":"Заблокувати","menu_item_clear_messages":"Очистити усі повідомлення","menu_item_clear_messages_for_all":"Очистити для всіх","menu_item_delete":"Видалити повідомлення","menu_item_delete_for_all":"Видалити для всіх","menu_item_delete_topic":"Видалити чат","menu_item_edit_permissions":"Права доступу","menu_item_info":"Інформація","menu_item_member_delete":"Відписати","menu_item_mute":"Не повідомляти","menu_item_restore_topic":"Розархівувати","menu_item_send_retry":"Надіслати заново","menu_item_unblock":"Разблокувати","menu_item_unmute":"Повідомляти","message_sending":"відправлення...","message_sending_failed":"помилка","messages_not_readable":"немає доступу до повідомлень","messaging_disabled_prompt":"Відправка недоступна","more_online_members":"+ще {overflow}","new_message_prompt":"Нове повідомлення","new_password_placeholder":"Введіть новий пароль","no_connection":"Немає зв'язку","no_contacts":"Ви не маєте контактів :-(","numeric_confirmation_code_prompt":"Тільки цифри","online_now":"онлайн","password_prompt":"Пароль","password_unchanged_prompt":"Не змінено","peers_messaging_disabled":"Чат заблоковано у кореспондента.","permission_admin":"Підтверджувати ({val})","permission_delete":"Видаляти ({val})","permission_join":"Підписуватись ({val})","permission_owner":"Власник ({val})","permission_pres":"Повідомляти ({val})","permission_read":"Читати ({val})","permission_share":"Запрошувати ({val})","permission_write":"Писати ({val})","phone_dative":"телефону","private_editing_placeholder":"Видно тільки вам","push_init_failed":"Помилка ініціалізації пуш сповіщень","reconnect_countdown":"Немає зв'язку. Підключення через {seconds}…","reconnect_now":"Підключити зараз.","reload_update":"Оновити","report_chat_warning":"Ви дійсно бажаєте повідомити про порушення і заблокувати цей?","requested_permissions":"Потрібні","save_attachment":"зберегти","search_for_contacts":"Пошук контактів","search_no_results":"Нічого не знайдено","search_placeholder":"Список, напр. email:alice@example.com, tel:+17025550003...","sidepanel_title_acc_notifications":"Сповіщення","sidepanel_title_acc_support":"Підтримка","sidepanel_title_account_settings":"Налаштування акаунту","sidepanel_title_archive":"Архів чатів","sidepanel_title_blocked":"Заблоковані чати","sidepanel_title_cred":"Підтвердити","sidepanel_title_login":"Авторизація","sidepanel_title_newtpk":"Новий чат","sidepanel_title_register":"Зареєструватися","sidepanel_title_reset":"Змінити пароль","sidepanel_title_settings":"Налаштування","stay_logged_in":"Запам'ятати","tabtitle_find_user":"знайти","tabtitle_group_by_id":"по id","tabtitle_new_group":"створити","tags_editor_no_tags":"Додайте теги","tags_not_found":"Теґів немає. Додайте.","title_all_contacts":"Всі контакти","title_group_members":"Учасники","title_manage_tags":"Редагувати","title_not_found":"Не знайдено","title_permissions":"Права доступу","title_tag_manager":"Теги для пошуку","topic_block_warning":"Ви дійсно бажаєте заблокувати цей чат?","topic_delete_warning":"Ви дійсно бажаєте видалити цей чат?","topic_name_editing_placeholder":"Назва чата","unnamed_topic":"Без назви","update_available":"Є нова версія програми.","upload_finishing":"завершення...","user_not_found":"Не найдено","description_editing_placeholder":"Опис (не обов'язково)","label_description":"Опис","button_security":"Безпека","panel_title_crop":"Обрізати картинку","panel_title_general":"Загальні налаштування","panel_title_members":"Учасники","panel_title_security":"Безпека","panel_title_info":"Деталі","permissions_anonymous":"Анонімний","permissions_authenticated":"Авторизований","topic_delete":"Видалити чат","permissions_user":"Права доступу","password_reset_email_sent":"Повідомлення було надіслано на вказаний email.","label_unarchive_topic":"Архівований:","menu_item_reply":"Відповісти","menu_item_forward":"Переслати","forward_to":"Переслати","forward_to_search_placeholder":"Пошук контактів","label_new_password":"Новий пароль","drafty_unknown":"Не підтримується","calls_incoming":"Вхідний дзвінок","calls_outgoing":"Вихідний дзвінок","calls_you_label":"Ви","menu_item_video_call":"Відеодзвінок","already_in_call":"Ви вже дзвоните комусь!","call_cancelled":"скасовано","call_missed":"пропущений","call_declined":"відхилений","call_disconnected":"роз'єднаний","label_use_secure_connection":"Безпечне з'єднання","cred_confirmed_successfully":"Підтверджено успішно","menu_item_edit":"Змінити","editing_message":"Редагування","message_edited_marker":", змінено","icon_title_add_image":"Додати зображення","icon_title_attach_file":"Прикріпити файл","icon_title_delete":"Видалити запис","icon_title_pause":"Пауза програвання","icon_title_play":"Програти запис","icon_title_record_voice":"Записати голосове повідомлення","icon_title_resume":"Продовжити","icon_title_send":"Надіслати повідомлення","drag_file":"Перетягніть файл сюди","drafty_video":"Відеозапис","call_in_progress":"в процесі","menu_item_audio_call":"Подзвонити","unrecognized_video_format":"Невідомий формат відео файлу","loading_note":"Завантажується...","password_i_have_code":"У мене є код","label_reset_password_tel":"Надіслати СМС для скидання пароля","mobile_phone_number":"Номер мобільного телефону","password_reset_sms_sent":"СМС із захисним кодом відправлено.","mobile_number_required":"Номер мобільного телефону","current_email":"Поточний e-mail","current_phone":"Поточний номер телефону","new_email":"Новий e-mail","new_phone_number":"Новий номер телефону","change_email":"Змінити e-mail","change_phone":"Змінити номер телефону","call_busy":"зайнято","failed_to_init_audio":"Не вдалося ініціалізувати аудіозапис","password_reset_success":"Пароль змінено успішно","scan_qr_code":"Відскануйте QR-код","pin_message":"закріпити","unpin_message":"відкріпити","self_topic_comment":"Нотатки, повідомлення, посилання, файли збережені для нащадків","self_topic_name":"Збережені повідомлення","message_not_found":"повідомлення не знайдено","message_deleted":"повідомлення видалено","alias_editing_placeholder":"Псевдонім (необов'язково)","label_alias":"Псевдонім:","alias_already_taken":"(вже зайнято)","alias_invalid":"(недійсний)","label_alias_edit":"Псевдонім","select_country":"Виберіть країну","text_copied":"Скопійовано в буфер обміну","color_schema_dark":"Темна","color_schema_light":"Світла","color_schema_system":"Системна","label_color_schema":"Тема:","label_text_size":"Розмір тексту:","incognito_mode_description":"Приховати ваш статус онлайн та індикатори набору тексту.","send_command":"Надіслати на {key}","send_command_explained":"Натисніть Enter для нового рядка","send_plain":"Надіслати на Enter","send_plain_explained":"Натисніть Shift + Enter для нового рядка","sidepanel_title_acc_general":"Загальні","label_keyboard":"Клавіатура:","pin_chat":"Закріпити","unpin_chat":"Відкріпити","subscriber_count":"{count, plural, one {{count, number} підписник} few {{count, number} підписники} many {{count, number} підписників} other {{count, number} підписників}}","label_member_count":"Учасники:","label_subscriber_count":"Підписники:","member_count":"{count, plural, one {{count, number} учасник} few {{count, number} учасники} many {{count, number} учасників} other {{count, number} учасників}}","tabtitle_image":"зображення","tabtitle_pattern":"візерунок","button_restore":"Відновити типові","wallpapers":"Шпалери","label_blur_wallpaper":"Розмиття:","label_scan_id":"Сканувати мій ID:","unknown_name":"Невідомий","save_action":"Зберегти","cannot_parse_vcard":"Не вдалося обробити файл vCard.","chat_now":"Написати","find_user":"Знайти","label_contact_card":"Картка контакту","label_contacts":"Контакти","add_members_prompt":"додати учасників"}
\ No newline at end of file
diff --git a/src/i18n.min/vi.json b/src/i18n.min/vi.json
deleted file mode 100644
index bab493d5..00000000
--- a/src/i18n.min/vi.json
+++ /dev/null
@@ -1 +0,0 @@
-{"action_block_contact":"Chặn liên hệ","action_cancel":"hủy","action_clear_messages":"Xóa tin nhắn","action_delete_messages":"Xóa tin nhắn cho tất cả","action_leave_chat":"Rời cuộc trò chuyện","action_report_chat":"Báo cáo cuộc trò chuyện","archived_contacts":"Liên hệ đã lưu trữ ({count})","badge_danger":"Không đáng tin","badge_owner":"chủ sở hữu","badge_staff":"Được quản lý bởi nhân viên","badge_verified":"Đã xác minh/chính thức","badge_you":"bạn","block_contact_warning":"Bạn có chắc muốn chặn liên hệ này?","blocked_contacts_link":"Liên hệ bị chặn ({count})","button_add_members":"Thêm thành viên","button_cancel":"Hủy","button_confirm":"Xác nhận","button_create":"Tạo","button_delete_account":"Xóa tài khoản","button_edit":"Sửa","button_logout":"Đăng xuất","button_ok":"OK","button_reset":"Đặt lại","button_send_request":"Gửi yêu cầu","button_sign_in":"Đăng nhập","button_sign_up":"Đăng ký","button_subscribe":"Theo dõi","button_update":"Cập nhật","cannot_initiate_file_upload":"Không thể bắt đầu tải tệp lên.","channel":"kênh","channel_prompt":"Đây là một kênh","chat_invitation":"Bạn được mời bắt đầu cuộc trò chuyện mới. Bạn muốn làm gì?","chat_invitation_accept":"Chấp nhận","chat_invitation_block":"Chặn","chat_invitation_ignore":"Bỏ qua","clear_messages_warning":"Bạn có chắc muốn xóa tất cả tin nhắn? Hành động này không thể hoàn tác.","code_doesnot_match":"Mã không khớp","contacts_not_found":"Bạn chưa có cuộc trò chuyện nào\n¯∖_(ツ)_/¯","contacts_not_found_short":"Không có liên hệ nào khớp với ''{query}''","credential_email_prompt":"Email đăng ký của bạn","delete_account":"Xóa tài khoản","delete_account_warning":"Bạn có chắc muốn xóa tài khoản? Hành động này không thể hoàn tác.","delete_messages_warning":"Bạn có chắc muốn xóa tất cả tin nhắn cho mọi người? Hành động này không thể hoàn tác.","download_action":"tải xuống","drafty_attachment":"Tệp đính kèm","drafty_form":"Biểu mẫu:","drafty_image":"Hình ảnh","email_dative":"email","email_prompt":"Email, ví dụ jdoe@example.com","enable_peers_messaging":"Bật","enter_confirmation_code_prompt":"Mã xác nhận","error_invalid_id":"ID không hợp lệ","file_attachment_too_large":"Kích thước tệp {size} vượt quá giới hạn {limit}.","forgot_password_link":"Quên mật khẩu?","full_name_prompt":"Họ và tên, ví dụ John Doe","granted_permissions":"Đã cấp","group_has_no_members":"Không có thành viên","group_user_id_prompt":"ID nhóm hoặc người dùng","image_caption_prompt":"Chú thích hình ảnh","invalid_content":"nội dung không hợp lệ","invalid_security_token":"Mã bảo mật không hợp lệ","label_client":"Client:","label_content_type":"Kiểu nội dung:","label_default_access_mode":"Chế độ truy cập mặc định:","label_file_name":"Tên tệp:","label_group_members":"Thành viên nhóm:","label_incognito_mode":"Chế độ ẩn danh:","label_message_sound":"Âm thanh tin nhắn:","label_muting_topic":"Đã tắt:","label_other_user":"Khác","label_password":"Mật khẩu","label_permissions":"Quyền:","label_private":"Chỉ bạn nhìn thấy","label_push_notifications":"Cảnh báo thông báo:","label_push_notifications_disabled":"Cảnh báo thông báo (yêu cầu HTTPS):","label_reset_password":"Gửi email đặt lại mật khẩu:","label_sdk":"SDK:","label_server":"Máy chủ:","label_server_address":"Địa chỉ máy chủ:","label_server_to_use":"Máy chủ sử dụng:","label_size":"Kích thước:","label_topic_name":"Tên","label_user_contacts":"Danh bạ","label_user_id":"ID:","label_wire_transport":"Giao thức truyền tải:","label_you":"Bạn:","label_your_name":"Tên của bạn","label_your_permissions":"Quyền của bạn:","last_seen_timestamp":"Lần cuối hoạt động: {timestamp}","leave_chat_warning":"Bạn có chắc muốn rời cuộc trò chuyện này?","link_contact_us":"Liên hệ chúng tôi","link_privacy_policy":"Chính sách quyền riêng tư","link_terms_of_service":"Điều khoản dịch vụ","login_prompt":"Đăng nhập","menu_item_archive_topic":"Lưu trữ","menu_item_block":"Chặn","menu_item_clear_messages":"Xóa tin nhắn","menu_item_clear_messages_for_all":"Xóa cho tất cả","menu_item_delete":"Xóa","menu_item_delete_for_all":"Xóa cho tất cả","menu_item_delete_topic":"Xóa","menu_item_edit_permissions":"Sửa quyền","menu_item_info":"Thông tin","menu_item_member_delete":"Xóa","menu_item_mute":"Tắt thông báo","menu_item_reply":"Trả lời","menu_item_restore_topic":"Khôi phục","menu_item_send_retry":"Thử lại","menu_item_unblock":"Bỏ chặn","menu_item_unmute":"Bật thông báo","message_sending":"đang gửi...","message_sending_failed":"thất bại","messages_not_readable":"không có quyền xem tin nhắn","messaging_disabled_prompt":"Tin nhắn bị tắt","more_online_members":"+{overflow} nữa","new_message_prompt":"Tin nhắn mới","new_password_placeholder":"Nhập mật khẩu mới","no_connection":"Không có kết nối","no_contacts":"Bạn chưa có liên hệ nào :-(","numeric_confirmation_code_prompt":"Chỉ số","online_now":"đang online","password_prompt":"Mật khẩu","password_unchanged_prompt":"Không thay đổi","peers_messaging_disabled":"Tin nhắn của người kia đã bị tắt.","permission_admin":"Phê duyệt ({val})","permission_delete":"Xóa ({val})","permission_join":"Tham gia ({val})","permission_owner":"Chủ sở hữu ({val})","permission_pres":"Nhận thông báo ({val})","permission_read":"Đọc ({val})","permission_share":"Chia sẻ ({val})","permission_write":"Ghi ({val})","phone_dative":"điện thoại","private_editing_placeholder":"Chỉ mình bạn nhìn thấy","push_init_failed":"Khởi tạo thông báo đẩy thất bại","reconnect_countdown":"Mất kết nối. Kết nối lại sau {seconds}…","reconnect_now":"Thử ngay","reload_update":"Tải lại","report_chat_warning":"Bạn có chắc muốn chặn và báo cáo cuộc trò chuyện này?","requested_permissions":"Đã yêu cầu","save_attachment":"lưu","search_for_contacts":"Dùng tìm kiếm để tìm liên hệ","search_no_results":"Không có kết quả nào","search_placeholder":"Liệt kê như email:alice@example.com, tel:17025550003...","sidepanel_title_acc_notifications":"Thông báo","sidepanel_title_acc_support":"Hỗ trợ","sidepanel_title_account_settings":"Cài đặt tài khoản","sidepanel_title_archive":"Cuộc trò chuyện đã lưu trữ","sidepanel_title_blocked":"Cuộc trò chuyện bị chặn","sidepanel_title_cred":"Xác nhận thông tin","sidepanel_title_login":"Đăng nhập","sidepanel_title_newtpk":"Bắt đầu cuộc trò chuyện mới","sidepanel_title_register":"Tạo tài khoản","sidepanel_title_reset":"Đặt lại mật khẩu","sidepanel_title_settings":"Cài đặt","stay_logged_in":"Giữ tôi đăng nhập","tabtitle_find_user":"tìm","tabtitle_group_by_id":"theo id","tabtitle_new_group":"nhóm mới","tags_editor_no_tags":"Thêm vài thẻ","tags_not_found":"Chưa có thẻ nào. Hãy thêm một vài thẻ.","title_all_contacts":"Tất cả liên hệ","title_group_members":"Thành viên nhóm","title_manage_tags":"Quản lý","title_not_found":"Không tìm thấy","title_permissions":"Quyền","title_tag_manager":"Thẻ (tìm kiếm & khám phá)","topic_block_warning":"Bạn có chắc muốn chặn cuộc trò chuyện này?","topic_delete_warning":"Bạn có chắc muốn xóa cuộc trò chuyện này? Hành động này không thể hoàn tác.","topic_name_editing_placeholder":"Tên nhóm tự do","unnamed_topic":"Chưa đặt tên","update_available":"Có bản cập nhật.","upload_finishing":"đang hoàn tất...","user_not_found":"Không tìm thấy","description_editing_placeholder":"Mô tả tùy chọn","label_description":"Mô tả","button_security":"Bảo mật","panel_title_crop":"Kéo để điều chỉnh","panel_title_general":"Chung","panel_title_members":"Thành viên","panel_title_security":"Bảo mật","panel_title_info":"Thông tin","permissions_anonymous":"Ẩn danh","permissions_authenticated":"Đã xác thực","topic_delete":"Xóa cuộc trò chuyện","permissions_user":"Quyền của người dùng","password_reset_email_sent":"Email chứa mã bảo mật đã được gửi.","label_unarchive_topic":"Đã lưu trữ:","menu_item_forward":"Chuyển tiếp","forward_to":"Chuyển tiếp đến","forward_to_search_placeholder":"Tìm kiếm liên hệ","label_new_password":"Mật khẩu mới","drafty_unknown":"Không hỗ trợ","calls_incoming":"Cuộc gọi đến","calls_outgoing":"Cuộc gọi đi","calls_you_label":"Bạn","menu_item_video_call":"Gọi video","already_in_call":"Bạn đang có một cuộc gọi đang diễn ra!","call_cancelled":"đã hủy","call_missed":"bỏ lỡ","call_declined":"từ chối","call_disconnected":"mất kết nối","label_use_secure_connection":"Sử dụng kết nối bảo mật","cred_confirmed_successfully":"Xác nhận thành công","menu_item_edit":"Sửa","editing_message":"Đang sửa","message_edited_marker":", đã sửa","icon_title_add_image":"Thêm hình ảnh","icon_title_attach_file":"Đính kèm tệp","icon_title_delete":"Xóa","icon_title_pause":"Tạm dừng","icon_title_play":"Phát","icon_title_record_voice":"Ghi âm tin nhắn","icon_title_resume":"Tiếp tục","icon_title_send":"Gửi tin nhắn","drag_file":"Kéo tệp vào đây","drafty_video":"Video","call_in_progress":"đang diễn ra","menu_item_audio_call":"Gọi","unrecognized_video_format":"Không nhận dạng được định dạng video này","loading_note":"Đang tải...","password_i_have_code":"Tôi có mã","label_reset_password_tel":"Gửi SMS đặt lại mật khẩu:","mobile_phone_number":"Số điện thoại di động","password_reset_sms_sent":"Tin nhắn chứa mã bảo mật đã được gửi.","mobile_number_required":"Cần số điện thoại di động","current_email":"Email hiện tại","current_phone":"Số điện thoại hiện tại","new_email":"Email mới","new_phone_number":"Số điện thoại mới","change_email":"Đổi email","change_phone":"Đổi số điện thoại","call_busy":"bận","failed_to_init_audio":"Không thể khởi tạo ghi âm","password_reset_success":"Đặt lại mật khẩu thành công","scan_qr_code":"Quét mã QR","add_members_prompt":"thêm thành viên","alias_already_taken":"(đã được sử dụng)","alias_editing_placeholder":"Bí danh (tùy chọn)","alias_invalid":"(không hợp lệ)","button_restore":"Khôi phục mặc định","cannot_parse_vcard":"Không thể đọc tệp vCard.","chat_now":"Nhắn tin","color_schema_dark":"Tối","color_schema_light":"Sáng","color_schema_system":"Mặc định hệ thống","find_user":"Tìm kiếm","incognito_mode_description":"Ẩn trạng thái trực tuyến và chỉ báo đang nhập.","label_alias":"Bí danh:","label_alias_edit":"Bí danh","label_blur_wallpaper":"Làm mờ:","label_color_schema":"Giao diện:","label_contact_card":"Danh thiếp","label_contacts":"Danh bạ","label_keyboard":"Bàn phím:","label_member_count":"Thành viên:","label_scan_id":"Quét mã của tôi:","label_subscriber_count":"Người theo dõi:","label_text_size":"Cỡ chữ:","member_count":"{count, plural, one {{count, number} thành viên} other {{count, number} thành viên}}","message_deleted":"tin nhắn đã xóa","message_not_found":"không tìm thấy tin nhắn","pin_chat":"Ghim","pin_message":"Ghim","save_action":"Lưu","select_country":"Chọn quốc gia","self_topic_comment":"Ghi chú, tin nhắn, liên kết, tệp lưu lại","self_topic_name":"Tin nhắn đã lưu","send_command":"Gửi bằng {key}","send_command_explained":"Nhấn Enter để xuống dòng","send_plain":"Gửi bằng Enter","send_plain_explained":"Nhấn Shift + Enter để xuống dòng","sidepanel_title_acc_general":"Chung","subscriber_count":"{count, plural, one {{count, number} người theo dõi} other {{count, number} người theo dõi}}","tabtitle_image":"hình ảnh","tabtitle_pattern":"hoa văn","text_copied":"Đã sao chép vào clipboard","unknown_name":"Không rõ","unpin_chat":"Bỏ ghim","unpin_message":"Bỏ ghim","wallpapers":"Hình nền"}
\ No newline at end of file
diff --git a/src/i18n.min/zh-TW.json b/src/i18n.min/zh-TW.json
deleted file mode 100644
index 40689c81..00000000
--- a/src/i18n.min/zh-TW.json
+++ /dev/null
@@ -1 +0,0 @@
-{"action_block_contact":"封鎖聯絡人","action_cancel":"取消","action_clear_messages":"清除訊息","action_delete_messages":"為所有人清除訊息","action_leave_chat":"離開對話","action_report_chat":"檢舉對話","archived_contacts":"已封存的聯絡人 ({count})","badge_danger":"不可信","badge_owner":"擁有者","badge_staff":"員工管理","badge_verified":"已驗證/官方","badge_you":"您","block_contact_warning":"您確定要封鎖此聯絡人嗎?","blocked_contacts_link":"已封鎖的聯絡人 ({count})","button_add_members":"新增成員","button_cancel":"取消","button_confirm":"確認","button_create":"建立","button_delete_account":"刪除帳號","button_edit":"編輯","button_logout":"登出","button_ok":"OK","button_reset":"重設","button_send_request":"傳送要求","button_sign_in":"登入","button_sign_up":"註冊","button_subscribe":"訂閱","button_update":"更新","cannot_initiate_file_upload":"無法為檔案上傳初始化。","channel":"頻道","channel_prompt":"這是頻道","chat_invitation":"您已被邀請開始新對話。您接下來要做什麼呢?","chat_invitation_accept":"接受","chat_invitation_block":"封鎖","chat_invitation_ignore":"忽略","clear_messages_warning":"您確定要清除所有訊息嗎?此操作無法復原。","code_doesnot_match":"代碼不相符","contacts_not_found":"您沒有任何對話\n¯∖_(ツ)_/¯","contacts_not_found_short":"沒有符合 ''{query}'' 的聯絡人","credential_email_prompt":"您的註冊電子郵件地址","delete_account":"刪除帳號","delete_account_warning":"您確定要刪除您的帳戶嗎?此操作無法撤消。","delete_messages_warning":"您確定要為所有人刪除所有訊息嗎?此操作無法復原。","download_action":"下載","drafty_attachment":"附加檔案","drafty_form":"表單:","drafty_image":"圖片","email_dative":"電子郵件地址","email_prompt":"電子郵件地址,例如:jdoe@example.com","enable_peers_messaging":"啟用","enter_confirmation_code_prompt":"輸入透過 {method} 傳送給您的確認碼:","error_invalid_id":"ID 無效","file_attachment_too_large":"檔案大小 {size} 超出 {limit} 限制。","forgot_password_link":"忘記密碼?","full_name_prompt":"全名,例如:王小明","granted_permissions":"已授權","group_has_no_members":"無成員","group_user_id_prompt":"群組或使用者 ID","image_caption_prompt":"圖片說明","invalid_content":"內容無效","invalid_security_token":"安全權杖無效","label_client":"客戶端:","label_content_type":"類型:","label_default_access_mode":"預設存取模式:","label_file_name":"檔名:","label_group_members":"群組成員:","label_incognito_mode":"無痕模式:","label_message_sound":"訊息提示聲:","label_muting_topic":"靜音:","label_other_user":"其他","label_password":"密碼","label_permissions":"權限:","label_private":"私人留言","label_push_notifications":"通知:","label_push_notifications_disabled":"通知 (需要 HTTPS):","label_reset_password":"傳送重設密碼郵件:","label_sdk":"SDK:","label_server":"伺服器:","label_server_address":"伺服器位址:","label_server_to_use":"使用的伺服器:","label_size":"大小:","label_topic_name":"名稱","label_user_contacts":"聯絡人","label_user_id":"ID:","label_wire_transport":"線路傳輸:","label_you":"您:","label_your_name":"您的名字","label_your_permissions":"您的權限:","last_seen_timestamp":"最後上線","leave_chat_warning":"您確定要離開此對話嗎?","link_contact_us":"聯絡我們","link_privacy_policy":"隱私權政策","link_terms_of_service":"服務條款","login_prompt":"登入","menu_item_archive_topic":"封存","menu_item_block":"封鎖","menu_item_clear_messages":"清除訊息","menu_item_clear_messages_for_all":"為所有人清除訊息","menu_item_delete":"刪除","menu_item_delete_for_all":"為所有人刪除","menu_item_delete_topic":"刪除","menu_item_edit_permissions":"編輯權限","menu_item_info":"Info","menu_item_member_delete":"移除","menu_item_mute":"靜音","menu_item_restore_topic":"恢復","menu_item_send_retry":"重試","menu_item_unblock":"解除封鎖","menu_item_unmute":"解除靜音","message_sending":"正在傳送...","message_sending_failed":"失敗","messages_not_readable":"沒有存取訊息的權限","messaging_disabled_prompt":"停用訊息","more_online_members":"+{overflow}","new_message_prompt":"新訊息","new_password_placeholder":"輸入新的密碼","no_connection":"無連線","no_contacts":"您沒有任何聯絡人 :-(","numeric_confirmation_code_prompt":"僅數字","online_now":"上線中","password_prompt":"密碼","password_unchanged_prompt":"未修改","peers_messaging_disabled":"Peer 已停用訊息。","permission_admin":"核可 ({val})","permission_delete":"刪除 ({val})","permission_join":"加入 ({val})","permission_owner":"擁有者 ({val})","permission_pres":"收到通知 ({val})","permission_read":"讀取 ({val})","permission_share":"分享 ({val})","permission_write":"撰寫 ({val})","phone_dative":"phone","private_editing_placeholder":"僅您可見","push_init_failed":"初始化通知失敗。","reconnect_countdown":"已斷線。將在 {seconds} 秒後重新連線…","reconnect_now":"現在重試","reload_update":"重新載入","report_chat_warning":"您確定要封鎖並檢舉此對話嗎?","requested_permissions":"重新請求","save_attachment":"儲存","search_for_contacts":"使用搜尋來尋找聯絡人","search_no_results":"搜尋無結果","search_placeholder":"例如 email:alice@example.com, tel:17025550003... 的清單","sidepanel_title_acc_notifications":"通知","sidepanel_title_acc_support":"支援","sidepanel_title_account_settings":"帳號設定","sidepanel_title_archive":"已封存的對話","sidepanel_title_blocked":"已封鎖的對話","sidepanel_title_cred":"確認認證","sidepanel_title_login":"登入","sidepanel_title_newtpk":"開始新對話","sidepanel_title_register":"建立帳號","sidepanel_title_reset":"重設密碼","sidepanel_title_settings":"設定","stay_logged_in":"保持登入","tabtitle_find_user":"尋找","tabtitle_group_by_id":"以 id","tabtitle_new_group":"新群組","tags_editor_no_tags":"新增一些標籤","tags_not_found":"未定義任何標籤。請新增一些標籤。","title_all_contacts":"所有聯絡人","title_group_members":"群組成員","title_manage_tags":"管理","title_not_found":"找不到","title_permissions":"權限","title_tag_manager":"標籤 (使用者探索)","topic_block_warning":"您確定要封鎖此對話嗎?","topic_delete_warning":"您確定要刪除此對話嗎?","topic_name_editing_placeholder":"群組的任意名稱","unnamed_topic":"未命名","update_available":"有可用的更新。","upload_finishing":"正在完成...","user_not_found":"找不到","description_editing_placeholder":"說明(可選)","label_description":"說明","button_security":"安全性","panel_title_crop":"拖動調整","panel_title_general":"一般","panel_title_members":"成員","panel_title_security":"安全性","panel_title_info":"資訊","permissions_anonymous":"匿名","permissions_authenticated":"已認證","topic_delete":"刪除聊天","permissions_user":"用戶權限","password_reset_email_sent":"已發送一封帶有安全代碼的電子郵件。","label_unarchive_topic":"存檔聊天:","menu_item_reply":"回答","menu_item_forward":"重定向","forward_to":"重定向消息","forward_to_search_placeholder":"搜索聯繫人","label_new_password":"新密碼","drafty_unknown":"不支持","calls_incoming":"來電","calls_outgoing":"撥出電話","calls_you_label":"你","menu_item_video_call":"視頻電話","already_in_call":"你已經在說話了!","call_cancelled":"取消","call_missed":"未接","call_declined":"被拒","call_disconnected":"斷開連接","label_use_secure_connection":"使用安全連接","cred_confirmed_successfully":"確認成功","menu_item_edit":"編輯","editing_message":"編輯","message_edited_marker":",編輯","icon_title_add_image":"添加圖片","icon_title_attach_file":"附加文件","icon_title_delete":"刪除錄音","icon_title_pause":"暫停播放","icon_title_play":"播放錄音","icon_title_record_voice":"錄製語音消息","icon_title_resume":"恢復播放","icon_title_send":"發信息","drag_file":"將文件拖到這裡","drafty_video":"視頻錄製","call_in_progress":"通話中","menu_item_audio_call":"語音電話","unrecognized_video_format":"無法識別此視頻的格式","loading_note":"加載中...","password_i_have_code":"我有代碼","label_reset_password_tel":"發送短信重置密碼","mobile_phone_number":"手機號碼","password_reset_sms_sent":"已發送帶有安全代碼的短信。","mobile_number_required":"需要手機號","current_email":"當前郵箱","current_phone":"當前電話號碼","new_email":"新電子郵件地址","new_phone_number":"新電話號碼","change_email":"更改郵箱","change_phone":"更改電話號碼","call_busy":"忙碌的","pin_message":"置頂消息","unpin_message":"取消置頂消息","failed_to_init_audio":"初始化錄音失敗","password_reset_success":"密碼重置成功","scan_qr_code":"掃描二維碼","self_topic_comment":"為後代所儲存的註解、訊息、連結、檔案","self_topic_name":"已儲存的訊息","message_not_found":"未找到訊息","message_deleted":"訊息已刪除","alias_editing_placeholder":"別名(可選)","label_alias":"別名:","alias_already_taken":"(已採取)","alias_invalid":"(無效)","label_alias_edit":"別名","select_country":"選擇國家","text_copied":"已複製到剪貼簿","color_schema_dark":"深色","color_schema_light":"淺色","color_schema_system":"系統預設","label_color_schema":"主題:","label_text_size":"文字大小:","incognito_mode_description":"隱藏您的線上狀態和正在輸入指示器。","send_command":"按 {key} 傳送","send_command_explained":"按 Enter 換行","send_plain":"按 Enter 傳送","send_plain_explained":"按 Shift + Enter 換行","sidepanel_title_acc_general":"一般","label_keyboard":"鍵盤:","pin_chat":"置頂","unpin_chat":"取消置頂","subscriber_count":"{count, plural, other {{count, number} 位訂閱者}}","label_member_count":"成員:","label_subscriber_count":"訂閱者:","member_count":"{count, plural, other {{count, number} 位成員}}","tabtitle_image":"圖片","tabtitle_pattern":"圖案","button_restore":"恢復預設","wallpapers":"桌布","label_blur_wallpaper":"模糊:","label_scan_id":"掃描我的 ID:","unknown_name":"未知","save_action":"儲存","cannot_parse_vcard":"無法解析 vCard 檔案。","chat_now":"聊天","find_user":"尋找","label_contact_card":"聯絡人卡片","label_contacts":"聯絡人","add_members_prompt":"新增成員"}
\ No newline at end of file
diff --git a/src/i18n.min/zh.json b/src/i18n.min/zh.json
deleted file mode 100644
index a1011c12..00000000
--- a/src/i18n.min/zh.json
+++ /dev/null
@@ -1 +0,0 @@
-{"action_block_contact":"屏蔽联系人","action_cancel":"取消","action_clear_messages":"删除讯息","action_delete_messages":"删除所有帖子","action_leave_chat":"离开","action_report_chat":"检举垃圾邮件","archived_contacts":"已归档联系人 ({count})","badge_danger":"可疑的","badge_owner":"所有者","badge_staff":"在员工管理下","badge_verified":"值得信赖","badge_you":"你","block_contact_warning":"您确定要阻止此联系人吗?","blocked_contacts_link":"封锁的联络人 ({count})","button_add_members":"添加成员","button_cancel":"取消","button_confirm":"确认","button_create":"创建","button_delete_account":"删除帐户","button_edit":"编辑","button_logout":"登出","button_ok":"好","button_reset":"重置","button_send_request":"发送请求","button_sign_in":"登录","button_sign_up":"注册","button_subscribe":"订阅","button_update":"更新","cannot_initiate_file_upload":"无法初始化文件上传。","channel":"频道","channel_prompt":"这是一个频道","chat_invitation":"你受邀开始新会话。你想怎么做?","chat_invitation_accept":"接受","chat_invitation_block":"屏蔽","chat_invitation_ignore":"忽略","clear_messages_warning":"您确定要清除所有消息吗?无法撤消。","code_doesnot_match":"代码不匹配","contacts_not_found":"你尚无会话\n¯∖_(ツ)_/¯","contacts_not_found_short":"无联系人匹配''{query}''","credential_email_prompt":"你的注册邮箱","delete_account":"删除帐户","delete_account_warning":"您确定要删除您的帐户吗?无法撤消。","delete_messages_warning":"您确定要删除所有消息吗?无法撤消。","download_action":"下载","drafty_attachment":"附件","drafty_form":"形式:","drafty_image":"图像","email_dative":"电子邮件","email_prompt":"电子邮件,例如 zhang@example.com","enable_peers_messaging":"启用","enter_confirmation_code_prompt":"输入通过{method}发送的验证码:","error_invalid_id":"无效 ID","file_attachment_too_large":"文件大小 {size} 超过 {limit} 限制。","forgot_password_link":"忘记密码?","full_name_prompt":"全名,例如张伟","granted_permissions":"已授予","group_has_no_members":"无成员","group_user_id_prompt":"群组或用户 ID","image_caption_prompt":"图片标题","invalid_content":"无效内容","invalid_security_token":"无效的安全令牌","label_client":"客户端:","label_content_type":"内容类型:","label_default_access_mode":"默认访问模式:","label_file_name":"文件名:","label_group_members":"群组成员:","label_incognito_mode":"无痕模式:","label_message_sound":"消息提示音:","label_muting_topic":"已静音:","label_other_user":"其他","label_password":"密码","label_permissions":"权限:","label_private":"私人评论","label_push_notifications":"通知提醒:","label_push_notifications_disabled":"通知提醒(需要 HTTPS):","label_reset_password":"发送密码重置邮件:","label_sdk":"开发包:","label_server":"服务器:","label_server_address":"服务器地址:","label_server_to_use":"使用的服务器:","label_size":"大小:","label_topic_name":"名称","label_user_contacts":"往来","label_user_id":"地址:","label_wire_transport":"线路传输:","label_you":"你:","label_your_name":"你的姓名","label_your_permissions":"你的权限:","last_seen_timestamp":"最后在线:{timestamp}","leave_chat_warning":"您确定要退出此对话吗?","link_contact_us":"联系我们","link_privacy_policy":"隐私政策","link_terms_of_service":"条款和条件","login_prompt":"登录","menu_item_archive_topic":"归档","menu_item_block":"屏蔽","menu_item_clear_messages":"清空消息","menu_item_clear_messages_for_all":"全部清除","menu_item_delete":"删除","menu_item_delete_for_all":"全部删除","menu_item_delete_topic":"删除","menu_item_edit_permissions":"编辑权限","menu_item_info":"信息","menu_item_member_delete":"移除","menu_item_mute":"静音","menu_item_restore_topic":"从存档中恢复","menu_item_send_retry":"重试","menu_item_unblock":"取消屏蔽","menu_item_unmute":"取消静音","message_sending":"正在发送...","message_sending_failed":"发送失败","messages_not_readable":"无消息访问权限","messaging_disabled_prompt":"消息已禁用","more_online_members":"还有{overflow}个","new_message_prompt":"新消息","new_password_placeholder":"输入新密码","no_connection":"无连接","no_contacts":"你尚无联系人 (._.)","numeric_confirmation_code_prompt":"仅数字","online_now":"在线","password_prompt":"密码","password_unchanged_prompt":"未改变","peers_messaging_disabled":"对方已关闭消息。","permission_admin":"批准 ({val})","permission_delete":"删除 ({val})","permission_join":"加入 ({val})","permission_owner":"所有者 ({val})","permission_pres":"获取通知 ({val})","permission_read":"读取 ({val})","permission_share":"分享 ({val})","permission_write":"写入 ({val})","phone_dative":"电话","private_editing_placeholder":"仅自己可见","push_init_failed":"初始化推送通知失败","reconnect_countdown":"连接已断开。{seconds} 秒后重新连接…","reconnect_now":"立即尝试","reload_update":"重新载入","report_chat_warning":"您确定要停止并报告此对话吗?","requested_permissions":"已请求","save_attachment":"保存","search_for_contacts":"使用搜索寻找联系人","search_no_results":"搜索无结果","search_placeholder":"列表如 email:alice@example.com, tel:+17025550003...","sidepanel_title_acc_notifications":"通知","sidepanel_title_acc_support":"支持","sidepanel_title_account_settings":"帐号设定","sidepanel_title_archive":"已存档会话","sidepanel_title_blocked":"被阻止的聊天","sidepanel_title_cred":"确认凭据","sidepanel_title_login":"登录","sidepanel_title_newtpk":"开始新会话","sidepanel_title_register":"创建账户","sidepanel_title_reset":"重置密码","sidepanel_title_settings":"设置","stay_logged_in":"保持登录","tabtitle_find_user":"搜索","tabtitle_group_by_id":"通过 id","tabtitle_new_group":"新群组","tags_editor_no_tags":"添加一些标签","tags_not_found":"尚未定义标签。添加一些。","title_all_contacts":"全部联系人","title_group_members":"群组成员","title_manage_tags":"管理标签","title_not_found":"无法找到","title_permissions":"权限","title_tag_manager":"标签(用户发现)","topic_block_warning":"您确定要阻止此对话吗?","topic_delete_warning":"您确定要删除此对话吗?","topic_name_editing_placeholder":"群组自由格式名称","unnamed_topic":"未命名","update_available":"更新可用。","upload_finishing":"正在结束...","user_not_found":"未找到","description_editing_placeholder":"说明(可选)","label_description":"说明","button_security":"安全","panel_title_crop":"拖动调整","panel_title_general":"常用设定","panel_title_members":"成员","panel_title_security":"安全","panel_title_info":"信息","permissions_anonymous":"匿名","permissions_authenticated":"已认证","topic_delete":"删除聊天","permissions_user":"用户权限","password_reset_email_sent":"已发送一封带有安全代码的电子邮件。","label_unarchive_topic":"存档:","menu_item_reply":"回复","menu_item_forward":"转发","forward_to":"转发给","forward_to_search_placeholder":"搜索联系人","label_new_password":"新密码","drafty_unknown":"不支持","calls_incoming":"来电","calls_outgoing":"拨出电话","calls_you_label":"你","menu_item_video_call":"视频电话","already_in_call":"你已经在说话了!","call_cancelled":"取消","call_missed":"未接","call_declined":"被拒","call_disconnected":"断开连接","label_use_secure_connection":"使用安全连接","cred_confirmed_successfully":"确认成功","menu_item_edit":"编辑","editing_message":"编辑","message_edited_marker":",编辑","icon_title_add_image":"添加图片","icon_title_attach_file":"附加文件","icon_title_delete":"删除录音","icon_title_pause":"暂停播放","icon_title_play":"播放录音","icon_title_record_voice":"录制语音信息","icon_title_resume":"恢复播放","icon_title_send":"发送消息","drag_file":"将文件拖到这里","drafty_video":"视频录制","call_in_progress":"通话中","menu_item_audio_call":"语音电话","unrecognized_video_format":"无法识别此视频的格式","loading_note":"加载中...","password_i_have_code":"我有代码","label_reset_password_tel":"发送短信重置密码","mobile_phone_number":"手机号","password_reset_sms_sent":"已发送带有安全代码的短信。","mobile_number_required":"需要手机号","current_email":"当前邮箱","current_phone":"当前电话号码","new_email":"新电子邮件地址","new_phone_number":"新电话号码","change_email":"更改邮箱","change_phone":"更改电话号码","call_busy":"忙碌的","pin_message":"置顶消息","unpin_message":"取消置顶消息","failed_to_init_audio":"初始化录音失败","password_reset_success":"密码重置成功","scan_qr_code":"扫描二维码","self_topic_comment":"为后人保存的注释、消息、链接、文件","self_topic_name":"已保存的消息","message_not_found":"未找到消息","message_deleted":"消息已删除","alias_editing_placeholder":"别名(可选)","label_alias":"别名:","alias_already_taken":"(已占用)","alias_invalid":"(无效)","label_alias_edit":"别名","select_country":"选择国家","text_copied":"已复制到剪贴板","color_schema_dark":"深色","color_schema_light":"浅色","color_schema_system":"系统默认","label_color_schema":"主题:","label_text_size":"文字大小:","incognito_mode_description":"隐藏您的在线状态和正在输入指示器。","send_command":"按 {key} 发送","send_command_explained":"按 Enter 换行","send_plain":"按 Enter 发送","send_plain_explained":"按 Shift + Enter 换行","sidepanel_title_acc_general":"常规","label_keyboard":"键盘:","pin_chat":"置顶","unpin_chat":"取消置顶","subscriber_count":"{count, plural, other {{count, number} 位订阅者}}","label_member_count":"成员:","label_subscriber_count":"订阅者:","member_count":"{count, plural, other {{count, number} 位成员}}","tabtitle_image":"图片","tabtitle_pattern":"图案","button_restore":"恢复默认","wallpapers":"壁纸","label_blur_wallpaper":"模糊:","label_scan_id":"扫描我的 ID:","unknown_name":"未知","save_action":"保存","cannot_parse_vcard":"无法解析 vCard 文件。","chat_now":"聊天","find_user":"查找","label_contact_card":"联系人卡片","label_contacts":"联系人","add_members_prompt":"添加成员"}
\ No newline at end of file
diff --git a/src/i18n/ar.json b/src/i18n/ar.json
deleted file mode 100644
index 1eff0178..00000000
--- a/src/i18n/ar.json
+++ /dev/null
@@ -1,1966 +0,0 @@
-{
- "action_block_contact": {
- "translation": "حظر جهة الاتصال",
- "defaultMessage": "Block Contact",
- "description": "Flat button [Block Contact]",
- "missing": false,
- "obsolete": false
- },
- "action_cancel": {
- "translation": "إلغاء",
- "defaultMessage": "cancel",
- "description": "Call to action [cancel]",
- "missing": false,
- "obsolete": false
- },
- "action_clear_messages": {
- "translation": "مسح الرسائل",
- "defaultMessage": "Clear Messages",
- "description": "Flat button [Clear Messages] (soft-delete messages)",
- "missing": false,
- "obsolete": false
- },
- "action_delete_messages": {
- "translation": "مسح الرسائل للجميع",
- "defaultMessage": "Clear Messages for All",
- "description": "Flat button [Clear for All] (hard-delete all messages)",
- "missing": false,
- "obsolete": false
- },
- "action_leave_chat": {
- "translation": "مغادرة المحادثة",
- "defaultMessage": "Leave Conversation",
- "description": "Flat button [Leave Conversation]",
- "missing": false,
- "obsolete": false
- },
- "action_report_chat": {
- "translation": "الإبلاغ عن المحادثة",
- "defaultMessage": "Report Conversation",
- "description": "Flat button [Report Group]",
- "missing": false,
- "obsolete": false
- },
- "archived_contacts": {
- "translation": "جهات الاتصال المؤرشفة ({count})",
- "defaultMessage": "Archived contacts ({count})",
- "description": "Label for archived chats",
- "missing": false,
- "obsolete": false
- },
- "badge_danger": {
- "translation": "غير موثوق به",
- "defaultMessage": "Untrustworthy",
- "description": "Suspicious or untrustworthy account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_owner": {
- "translation": "مالك",
- "defaultMessage": "owner",
- "description": "Badge for indicating the owner",
- "missing": false,
- "obsolete": false
- },
- "badge_staff": {
- "translation": "إدارة الموقع",
- "defaultMessage": "Staff-managed",
- "description": "Explanation of a staff-managed account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_verified": {
- "translation": "موثق/رسمي",
- "defaultMessage": "Verified/official",
- "description": "Explanation of a verified account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_you": {
- "translation": "أنت",
- "defaultMessage": "you",
- "description": "Badge for indicating the current user",
- "missing": false,
- "obsolete": false
- },
- "block_contact_warning": {
- "translation": "هل أنت متأكد من أنك تريد حظر جهة الاتصال هذه؟",
- "defaultMessage": "Are you sure you want to block this contact?",
- "description": "Alert dialog warning when blocking a contact.",
- "missing": false,
- "obsolete": false
- },
- "blocked_contacts_link": {
- "translation": "جهات الاتصال المحظورة ({count})",
- "defaultMessage": "Blocked contacts ({count})",
- "description": "Blocked contacts link",
- "missing": false,
- "obsolete": false
- },
- "button_add_members": {
- "translation": "إضافة أعضاء",
- "defaultMessage": "Add members",
- "description": "Flat button [Add members] (to topic)",
- "missing": false,
- "obsolete": false
- },
- "button_cancel": {
- "translation": "إلغاء",
- "defaultMessage": "Cancel",
- "description": "Button [Cancel]",
- "missing": false,
- "obsolete": false
- },
- "button_confirm": {
- "translation": "تأكيد",
- "defaultMessage": "Confirm",
- "description": "Button [Confirm]",
- "missing": false,
- "obsolete": false
- },
- "button_create": {
- "translation": "إنشاء",
- "defaultMessage": "Create",
- "description": "Button [Create]",
- "missing": false,
- "obsolete": false
- },
- "button_delete_account": {
- "translation": "حذف الحساب",
- "defaultMessage": "Delete account",
- "description": "Button [Delete account]",
- "missing": false,
- "obsolete": false
- },
- "button_edit": {
- "translation": "تعديل",
- "defaultMessage": "Edit",
- "description": "Call to action [Edit]",
- "missing": false,
- "obsolete": false
- },
- "button_logout": {
- "translation": "تسجيل الخروج",
- "defaultMessage": "Logout",
- "description": "Button [Logout]",
- "missing": false,
- "obsolete": false
- },
- "button_ok": {
- "translation": "موافق",
- "defaultMessage": "OK",
- "description": "Button [OK]",
- "missing": false,
- "obsolete": false
- },
- "button_reset": {
- "translation": "إعادة تعيين",
- "defaultMessage": "Reset",
- "description": "Button [Reset]",
- "missing": false,
- "obsolete": false
- },
- "button_send_request": {
- "translation": "إرسال طلب",
- "defaultMessage": "Send request",
- "description": "Button [Send request]",
- "missing": false,
- "obsolete": false
- },
- "button_sign_in": {
- "translation": "تسجيل الدخول",
- "defaultMessage": "Sign in",
- "description": "Button [Sign In]",
- "missing": false,
- "obsolete": false
- },
- "button_sign_up": {
- "translation": "إنشاء حساب",
- "defaultMessage": "Sign up",
- "description": "Create account button [Sign Up]",
- "missing": false,
- "obsolete": false
- },
- "button_subscribe": {
- "translation": "اشتراك",
- "defaultMessage": "Subscribe",
- "description": "Button [Subscribe]",
- "missing": false,
- "obsolete": false
- },
- "button_update": {
- "translation": "تحديث",
- "defaultMessage": "Update",
- "description": "Button [Update]",
- "missing": false,
- "obsolete": false
- },
- "cannot_initiate_file_upload": {
- "translation": "لا يمكن بدء تحميل الملف",
- "defaultMessage": "Cannot initiate file upload.",
- "description": "Generic error messagewhen attachment fails",
- "missing": false,
- "obsolete": false
- },
- "channel": {
- "translation": "قناة",
- "defaultMessage": "channel",
- "description": "Subtitle shown for channels in MessagesView instead of last seen",
- "missing": false,
- "obsolete": false
- },
- "channel_prompt": {
- "translation": "هذه قناة",
- "defaultMessage": "This is a channel",
- "description": "Checkbox label when creating a channel",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation": {
- "translation": "أنت مدعو لبدء محادثة جديدة. ماذا تريد أن تفعل؟",
- "defaultMessage": "You are invited to start a new chat. What would you like to do?",
- "description": "New chat invitation message: [Accept] [Ignore] [Block].",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_accept": {
- "translation": "قبول",
- "defaultMessage": "Accept",
- "description": "Action [Accept] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_block": {
- "translation": "حظر",
- "defaultMessage": "Block",
- "description": "Action [Block] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_ignore": {
- "translation": "تجاهل",
- "defaultMessage": "Ignore",
- "description": "Action [Ignore] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "clear_messages_warning": {
- "translation": "هل أنت متأكد من أنك تريد مسح جميع الرسائل؟ لا يمكن التراجع عن هذا الإجراء.",
- "defaultMessage": "Are you sure you want to clear all messages? It cannot be undone.",
- "description": "Alert dialog warning when deleting all messages.",
- "missing": false,
- "obsolete": false
- },
- "code_doesnot_match": {
- "translation": "الرمز غير متطابق",
- "defaultMessage": "Code does not match",
- "description": "Error message when the credential validation code is incorrect.",
- "missing": false,
- "obsolete": false
- },
- "contacts_not_found": {
- "translation": "ليس لديك محادثات\n¯∖_(ツ)_/¯",
- "defaultMessage": "You have no chats\\n¯∖_(ツ)_/¯",
- "description": "HTML message shown in ContactList when no contacts are found",
- "missing": false,
- "obsolete": false
- },
- "contacts_not_found_short": {
- "translation": "لا توجد جهات اتصال تطابق ''{query}''",
- "defaultMessage": "No contacts match ''{query}''",
- "description": "Shown in ContactsView when search returned no results",
- "missing": false,
- "obsolete": false
- },
- "credential_email_prompt": {
- "translation": "البريد الإلكتروني للتسجيل",
- "defaultMessage": "Your registration email",
- "description": "Placeholder for entering email",
- "missing": false,
- "obsolete": false
- },
- "delete_account": {
- "translation": "حذف الحساب",
- "defaultMessage": "Delete account",
- "description": "Title for delete account warning",
- "missing": false,
- "obsolete": false
- },
- "delete_account_warning": {
- "translation": "هل أنت متأكد من أنك تريد حذف حسابك؟ لا يمكن التراجع عن هذا الإجراء.",
- "defaultMessage": "Are you sure you want to delete your account? It cannot be undone.",
- "description": "Warning message when deleting an account",
- "missing": false,
- "obsolete": false
- },
- "delete_messages_warning": {
- "translation": "هل أنت متأكد من أنك تريد حذف جميع الرسائل للجميع؟ لا يمكن التراجع عن هذا الإجراء.",
- "defaultMessage": "Are you sure you want to delete all messages for everyone? It cannot be undone.",
- "description": "Alert dialog warning when hard-deleting all messages.",
- "missing": false,
- "obsolete": false
- },
- "download_action": {
- "translation": "تحميل",
- "defaultMessage": "download",
- "description": "Call to action [download]",
- "missing": false,
- "obsolete": false
- },
- "drafty_attachment": {
- "translation": "مرفق",
- "defaultMessage": "Attachment",
- "description": "Comment for attachment in Drafty",
- "missing": false,
- "obsolete": false
- },
- "drafty_form": {
- "translation": "نموذج:",
- "defaultMessage": "Form:",
- "description": "Comment for form in Drafty",
- "missing": false,
- "obsolete": false
- },
- "drafty_image": {
- "translation": "صورة",
- "defaultMessage": "Picture",
- "description": "Comment for embedded images in Drafty",
- "missing": false,
- "obsolete": false
- },
- "email_dative": {
- "translation": "البريد الإلكتروني",
- "defaultMessage": "email",
- "description": "Dative case of 'email', i.e. 'email' in 'by email'",
- "missing": false,
- "obsolete": false
- },
- "email_prompt": {
- "translation": "البريد الإلكتروني، مثال: jdoe@example.com",
- "defaultMessage": "Email, e.g. jdoe@example.com",
- "description": "Input placeholder for email entry",
- "missing": false,
- "obsolete": false
- },
- "enable_peers_messaging": {
- "translation": "تفعيل",
- "defaultMessage": "Enable",
- "description": "Call to action to enable peer's messaging",
- "missing": false,
- "obsolete": false
- },
- "enter_confirmation_code_prompt": {
- "translation": "رمز التأكيد",
- "defaultMessage": "Confirmation code",
- "description": "Request to enter confirmation code",
- "missing": false,
- "obsolete": false
- },
- "error_invalid_id": {
- "translation": "معرف غير صالح",
- "defaultMessage": "Invalid ID",
- "description": "Error message",
- "missing": false,
- "obsolete": false
- },
- "file_attachment_too_large": {
- "translation": "حجم الملف {size} يتجاوز الحد {limit}.",
- "defaultMessage": "The file size {size} exceeds the {limit} limit.",
- "description": "Error message when attachment is too large",
- "missing": false,
- "obsolete": false
- },
- "forgot_password_link": {
- "translation": "نسيت كلمة المرور؟",
- "defaultMessage": "Forgot password?",
- "description": "Link to Reset password form",
- "missing": false,
- "obsolete": false
- },
- "full_name_prompt": {
- "translation": "الاسم الكامل، مثال: جون دو",
- "defaultMessage": "Full name, e.g. John Doe",
- "description": "Input placeholder for person's full name",
- "missing": false,
- "obsolete": false
- },
- "granted_permissions": {
- "translation": "ممنوح",
- "defaultMessage": "Granted",
- "description": "Title for permissions",
- "missing": false,
- "obsolete": false
- },
- "group_has_no_members": {
- "translation": "لا يوجد أعضاء",
- "defaultMessage": "No members",
- "description": "Shown in place of group members",
- "missing": false,
- "obsolete": false
- },
- "group_user_id_prompt": {
- "translation": "معرف المجموعة أو المستخدم",
- "defaultMessage": "Group or User ID",
- "description": "Prompt for entering user or group ID",
- "missing": false,
- "obsolete": false
- },
- "image_caption_prompt": {
- "translation": "وصف الصورة",
- "defaultMessage": "Image caption",
- "description": "Prompt in SendMessage for attached image",
- "missing": false,
- "obsolete": false
- },
- "invalid_content": {
- "translation": "محتوى غير صالح",
- "defaultMessage": "invalid content",
- "description": "Shown when the message is unreadable",
- "missing": false,
- "obsolete": false
- },
- "invalid_security_token": {
- "translation": "رمز الأمان غير صالح",
- "defaultMessage": "Invalid security token",
- "description": "Error message when resetting password.",
- "missing": false,
- "obsolete": false
- },
- "label_client": {
- "translation": "العميل:",
- "defaultMessage": "Client:",
- "description": "Label for a client version",
- "missing": false,
- "obsolete": false
- },
- "label_content_type": {
- "translation": "نوع المحتوى:",
- "defaultMessage": "Content type:",
- "description": "Label for file content type (mime)",
- "missing": false,
- "obsolete": false
- },
- "label_default_access_mode": {
- "translation": "وضع الوصول الافتراضي:",
- "defaultMessage": "Default access mode:",
- "description": "Label for default access mode",
- "missing": false,
- "obsolete": false
- },
- "label_file_name": {
- "translation": "اسم الملف:",
- "defaultMessage": "File name:",
- "description": "Label for a file name",
- "missing": false,
- "obsolete": false
- },
- "label_group_members": {
- "translation": "أعضاء المجموعة:",
- "defaultMessage": "Group members:",
- "description": "Section title or label",
- "missing": false,
- "obsolete": false
- },
- "label_incognito_mode": {
- "translation": "وضع التخفي:",
- "defaultMessage": "Incognito mode:",
- "description": "Label for incognito mode toggle",
- "missing": false,
- "obsolete": false
- },
- "label_message_sound": {
- "translation": "صوت الرسالة:",
- "defaultMessage": "Message sound:",
- "description": "Label for message sounds toggle",
- "missing": false,
- "obsolete": false
- },
- "label_muting_topic": {
- "translation": "صامت:",
- "defaultMessage": "Muted:",
- "description": "Label for Muting/unmuting the topic",
- "missing": false,
- "obsolete": false
- },
- "label_other_user": {
- "translation": "آخر",
- "defaultMessage": "Other",
- "description": "Label for the other user when the user is unnamed",
- "missing": false,
- "obsolete": false
- },
- "label_password": {
- "translation": "كلمة المرور",
- "defaultMessage": "Password",
- "description": "Label for password editing",
- "missing": false,
- "obsolete": false
- },
- "label_permissions": {
- "translation": "الصلاحيات:",
- "defaultMessage": "Permissions:",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "label_private": {
- "translation": "تعليق خاص",
- "defaultMessage": "Private comment",
- "description": "Label for editing 'private'",
- "missing": false,
- "obsolete": false
- },
- "label_push_notifications": {
- "translation": "تنبيهات الإشعارات:",
- "defaultMessage": "Notification alerts:",
- "description": "Label for push notifications switch",
- "missing": false,
- "obsolete": false
- },
- "label_push_notifications_disabled": {
- "translation": "تنبيهات الإشعارات (يتطلب HTTPS):",
- "defaultMessage": "Notification alerts (requires HTTPS):",
- "description": "Label for push notifications switch",
- "missing": false,
- "obsolete": false
- },
- "label_reset_password": {
- "translation": "إرسال بريد إلكتروني لإعادة تعيين كلمة المرور:",
- "defaultMessage": "Send a password reset email",
- "description": "Label for password reset field",
- "missing": false,
- "obsolete": false
- },
- "label_sdk": {
- "translation": "SDK:",
- "defaultMessage": "SDK:",
- "missing": false,
- "obsolete": false
- },
- "label_server": {
- "translation": "الخادم:",
- "defaultMessage": "Server:",
- "description": "Label for a server version",
- "missing": false,
- "obsolete": false
- },
- "label_server_address": {
- "translation": "عنوان الخادم:",
- "defaultMessage": "Server address:",
- "missing": false,
- "obsolete": false
- },
- "label_server_to_use": {
- "translation": "الخادم المراد استخدامه:",
- "defaultMessage": "Server to use:",
- "description": "Label for server selector in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "label_size": {
- "translation": "الحجم:",
- "defaultMessage": "Size:",
- "description": "Label for file size",
- "missing": false,
- "obsolete": false
- },
- "label_topic_name": {
- "translation": "الاسم",
- "defaultMessage": "Name",
- "description": "Label for editing topic name",
- "missing": false,
- "obsolete": false
- },
- "label_user_contacts": {
- "translation": "جهات الاتصال",
- "defaultMessage": "Contacts",
- "description": "Label for user contacts",
- "missing": false,
- "obsolete": false
- },
- "label_user_id": {
- "translation": "المعرف:",
- "defaultMessage": "ID:",
- "description": "Label for user address (ID)",
- "missing": false,
- "obsolete": false
- },
- "label_wire_transport": {
- "translation": "نقل البيانات:",
- "defaultMessage": "Wire transport:",
- "description": "Label for wire transport selection in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "label_you": {
- "translation": "أنت:",
- "defaultMessage": "You:",
- "description": "Label for the current user",
- "missing": false,
- "obsolete": false
- },
- "label_your_name": {
- "translation": "اسمك",
- "defaultMessage": "Your name",
- "description": "Label for full name editing",
- "missing": false,
- "obsolete": false
- },
- "label_your_permissions": {
- "translation": "صلاحياتك:",
- "defaultMessage": "Your permissions:",
- "description": "Label for current user permissions",
- "missing": false,
- "obsolete": false
- },
- "last_seen_timestamp": {
- "translation": "آخر ظهور",
- "defaultMessage": "Last seen: {timestamp}",
- "description": "Label for the timestamp of when the user or topic was last online",
- "missing": false,
- "obsolete": false
- },
- "leave_chat_warning": {
- "translation": "هل أنت متأكد من أنك تريد مغادرة هذه المحادثة؟",
- "defaultMessage": "Are you sure you want to leave this conversation?",
- "description": "Alert dialog warning when unsubscribing from a chat.",
- "missing": false,
- "obsolete": false
- },
- "link_contact_us": {
- "translation": "اتصل بنا",
- "defaultMessage": "Contact Us",
- "description": "Ancor text for contacting us by email",
- "missing": false,
- "obsolete": false
- },
- "link_privacy_policy": {
- "translation": "سياسة الخصوصية",
- "defaultMessage": "Privacy Policy",
- "description": "Ancor text for privacy policy link",
- "missing": false,
- "obsolete": false
- },
- "link_terms_of_service": {
- "translation": "شروط الخدمة",
- "defaultMessage": "Terms of Service",
- "description": "Ancor text for terms of service link",
- "missing": false,
- "obsolete": false
- },
- "login_prompt": {
- "translation": "تسجيل الدخول",
- "defaultMessage": "Login",
- "description": "Placeholer for username/login",
- "missing": false,
- "obsolete": false
- },
- "menu_item_archive_topic": {
- "translation": "أرشفة",
- "defaultMessage": "Archive",
- "description": "Move topic from the list of active chats to archive",
- "missing": false,
- "obsolete": false
- },
- "menu_item_block": {
- "translation": "حظر",
- "defaultMessage": "Block",
- "description": "Block topic or user",
- "missing": false,
- "obsolete": false
- },
- "menu_item_clear_messages": {
- "translation": "مسح الرسائل",
- "defaultMessage": "Clear messages",
- "description": "Delete all messages",
- "missing": false,
- "obsolete": false
- },
- "menu_item_clear_messages_for_all": {
- "translation": "مسح للجميع",
- "defaultMessage": "Clear for All",
- "description": "Delete all message(s) for all members",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete": {
- "translation": "حذف",
- "defaultMessage": "Delete",
- "description": "Delete selected messages",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete_for_all": {
- "translation": "حذف للجميع",
- "defaultMessage": "Delete for All",
- "description": "Delete selected message(s) for all members",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete_topic": {
- "translation": "حذف",
- "defaultMessage": "Delete",
- "description": "Delete entire topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_edit_permissions": {
- "translation": "تعديل الصلاحيات",
- "defaultMessage": "Edit permissions",
- "description": "Menu item [Edit permissions]",
- "missing": false,
- "obsolete": false
- },
- "menu_item_info": {
- "translation": "معلومات",
- "defaultMessage": "Info",
- "description": "Show extended topic information",
- "missing": false,
- "obsolete": false
- },
- "menu_item_member_delete": {
- "translation": "إزالة",
- "defaultMessage": "Remove",
- "description": "Remove user from topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_mute": {
- "translation": "كتم",
- "defaultMessage": "Mute",
- "description": "Turn off notifications",
- "missing": false,
- "obsolete": false
- },
- "menu_item_reply": {
- "translation": "رد",
- "defaultMessage": "Reply",
- "description": "Reply to message",
- "missing": false,
- "obsolete": false
- },
- "menu_item_restore_topic": {
- "translation": "استعادة",
- "defaultMessage": "Restore",
- "description": "Restore topic from archive",
- "missing": false,
- "obsolete": false
- },
- "menu_item_send_retry": {
- "translation": "إعادة المحاولة",
- "defaultMessage": "Retry",
- "description": "Retry sending message",
- "missing": false,
- "obsolete": false
- },
- "menu_item_unblock": {
- "translation": "إلغاء الحظر",
- "defaultMessage": "Unblock",
- "description": "Unblock topic or user",
- "missing": false,
- "obsolete": false
- },
- "menu_item_unmute": {
- "translation": "إلغاء الكتم",
- "defaultMessage": "Unmute",
- "description": "Turn notifications on",
- "missing": false,
- "obsolete": false
- },
- "message_sending": {
- "translation": "جاري الإرسال...",
- "defaultMessage": "sending...",
- "description": "Message being sent, in place of time stamp",
- "missing": false,
- "obsolete": false
- },
- "message_sending_failed": {
- "translation": "فشل",
- "defaultMessage": "failed",
- "description": "Failed to send message, in place of time stamp",
- "missing": false,
- "obsolete": false
- },
- "messages_not_readable": {
- "translation": "لا يمكن الوصول إلى الرسائل",
- "defaultMessage": "no access to messages",
- "description": "Message shown in topic without the read access",
- "missing": false,
- "obsolete": false
- },
- "messaging_disabled_prompt": {
- "translation": "المراسلة معطلة",
- "defaultMessage": "Messaging disabled",
- "description": "Prompt in SendMessage in read-only topic",
- "missing": false,
- "obsolete": false
- },
- "more_online_members": {
- "translation": "+{overflow} آخرون",
- "defaultMessage": "+{overflow} more",
- "description": "Shown in MessagesView title bar when the number of online subscribers exceeds MAX_ONLINE_IN_TOPIC",
- "missing": false,
- "obsolete": false
- },
- "new_message_prompt": {
- "translation": "رسالة جديدة",
- "defaultMessage": "New message",
- "description": "Prompt in send message field",
- "missing": false,
- "obsolete": false
- },
- "new_password_placeholder": {
- "translation": "أدخل كلمة المرور الجديدة",
- "defaultMessage": "Enter new password",
- "description": "Placeholder for entering new password",
- "missing": false,
- "obsolete": false
- },
- "no_connection": {
- "translation": "لا يوجد اتصال",
- "defaultMessage": "No connection",
- "description": "Warning that the user is offline.",
- "missing": false,
- "obsolete": false
- },
- "no_contacts": {
- "translation": "ليس لديك جهات اتصال :-(",
- "defaultMessage": "You have no contacts :-(",
- "description": "Shown in ContactsView when the user has no contacts",
- "missing": false,
- "obsolete": false
- },
- "numeric_confirmation_code_prompt": {
- "translation": "أرقام فقط",
- "defaultMessage": "Numbers only",
- "description": "Prompt for numeric conformation code",
- "missing": false,
- "obsolete": false
- },
- "online_now": {
- "translation": "متصل الآن",
- "defaultMessage": "online now",
- "description": "Indicator that the user or topic is currently online",
- "missing": false,
- "obsolete": false
- },
- "password_prompt": {
- "translation": "كلمة المرور",
- "defaultMessage": "Password",
- "description": "Placeholder/prompt for entering password",
- "missing": false,
- "obsolete": false
- },
- "password_unchanged_prompt": {
- "translation": "لم يتغير",
- "defaultMessage": "Unchanged",
- "description": "Message in editor while password is unchanged",
- "missing": false,
- "obsolete": false
- },
- "peers_messaging_disabled": {
- "translation": "المراسلة معطلة للطرف الآخر",
- "defaultMessage": "Peer's messaging is disabled.",
- "description": "Shown when the p2p peer's messaging is disabled",
- "missing": false,
- "obsolete": false
- },
- "permission_admin": {
- "translation": "الموافقة ({val})",
- "defaultMessage": "Approve ({val})",
- "description": "Name of A permission",
- "missing": false,
- "obsolete": false
- },
- "permission_delete": {
- "translation": "حذف ({val})",
- "defaultMessage": "Delete ({val})",
- "description": "Name of D permission",
- "missing": false,
- "obsolete": false
- },
- "permission_join": {
- "translation": "انضمام ({val})",
- "defaultMessage": "Join ({val})",
- "description": "Name of J permission",
- "missing": false,
- "obsolete": false
- },
- "permission_owner": {
- "translation": "مالك ({val})",
- "defaultMessage": "Owner ({val})",
- "description": "Name of O permission",
- "missing": false,
- "obsolete": false
- },
- "permission_pres": {
- "translation": "تلقي الإشعارات ({val})",
- "defaultMessage": "Get notified ({val})",
- "description": "Name of P permission",
- "missing": false,
- "obsolete": false
- },
- "permission_read": {
- "translation": "قراءة ({val})",
- "defaultMessage": "Read ({val})",
- "description": "Name of R permission",
- "missing": false,
- "obsolete": false
- },
- "permission_share": {
- "translation": "مشاركة ({val})",
- "defaultMessage": "Share ({val})",
- "description": "Name of S permission",
- "missing": false,
- "obsolete": false
- },
- "permission_write": {
- "translation": "كتابة ({val})",
- "defaultMessage": "Write ({val})",
- "description": "Name of W permission",
- "missing": false,
- "obsolete": false
- },
- "phone_dative": {
- "translation": "هاتف",
- "defaultMessage": "phone",
- "description": "Dative case of 'phone', i.e. 'phone' in 'by phone'",
- "missing": false,
- "obsolete": false
- },
- "private_editing_placeholder": {
- "translation": "مرئي لك فقط",
- "defaultMessage": "Visible to you only",
- "description": "Placeholder for editing 'private'",
- "missing": false,
- "obsolete": false
- },
- "push_init_failed": {
- "translation": "فشل في تهيئة الإشعارات",
- "defaultMessage": "Failed to initialize push notifications",
- "description": "Error message when push notifications have failed to initialize.",
- "missing": false,
- "obsolete": false
- },
- "reconnect_countdown": {
- "translation": "تم قطع الاتصال. إعادة الاتصال خلال {seconds}...",
- "defaultMessage": "Disconnected. Reconnecting in {seconds}…",
- "description": "Message shown when an app update is available.",
- "missing": false,
- "obsolete": false
- },
- "reconnect_now": {
- "translation": "حاول الآن",
- "defaultMessage": "Try now",
- "description": "Prompt for reconnecting now",
- "missing": false,
- "obsolete": false
- },
- "reload_update": {
- "translation": "إعادة تحميل",
- "defaultMessage": "Reload",
- "description": "Call to action to reload application when update is available.",
- "missing": false,
- "obsolete": false
- },
- "report_chat_warning": {
- "translation": "هل أنت متأكد من أنك تريد حظر والإبلاغ عن هذه المحادثة؟",
- "defaultMessage": "Are you sure you want to block and report this conversation?",
- "description": "Alert dialog warning when reporting a conversation for abuse",
- "missing": false,
- "obsolete": false
- },
- "requested_permissions": {
- "translation": "مطلوب",
- "defaultMessage": "Requested",
- "description": "Title for permissions",
- "missing": false,
- "obsolete": false
- },
- "save_attachment": {
- "translation": "حفظ",
- "defaultMessage": "save",
- "description": "Call to save an attachment",
- "missing": false,
- "obsolete": false
- },
- "search_for_contacts": {
- "translation": "استخدم البحث للعثور على جهات الاتصال",
- "defaultMessage": "Use search to find contacts",
- "description": "Text shown in contacts view when user entered no search query.",
- "missing": false,
- "obsolete": false
- },
- "search_no_results": {
- "translation": "لم يسفر البحث عن أي نتائج",
- "defaultMessage": "Search returned no results",
- "description": "Text shown in contacts view when query returned no results.",
- "missing": false,
- "obsolete": false
- },
- "search_placeholder": {
- "translation": "قائمة مثل alice@example.com، +17025550003...",
- "defaultMessage": "List like alice@example.com, +17025550003...",
- "description": "Placeholder in contacts search field",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_notifications": {
- "translation": "الإشعارات",
- "defaultMessage": "Notifications",
- "description": "Sidepanel title for AccNotificationsView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_support": {
- "translation": "الدعم",
- "defaultMessage": "Support",
- "description": "Sidepanel title for AccSupportView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_account_settings": {
- "translation": "إعدادات الحساب",
- "defaultMessage": "Account Settings",
- "description": "Sidepanel title for AccountSettingsView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_archive": {
- "translation": "المحادثات المؤرشفة",
- "defaultMessage": "Archived Chats",
- "description": "Sidepanel title for ContactsView-Archive.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_blocked": {
- "translation": "المحادثات المحظورة",
- "defaultMessage": "Blocked Chats",
- "description": "Sidepanel title for ContactsView-Blocked.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_cred": {
- "translation": "تأكيد بيانات الاعتماد",
- "defaultMessage": "Confirm Credentials",
- "description": "Sidepanel title for ValidationView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_login": {
- "translation": "تسجيل الدخول",
- "defaultMessage": "Sign In",
- "description": "Sidepanel title for LoginView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_newtpk": {
- "translation": "بدء محادثة جديدة",
- "defaultMessage": "Start New Chat",
- "description": "Sidepanel title for NewTopicView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_register": {
- "translation": "إنشاء حساب",
- "defaultMessage": "Create Account",
- "description": "Sidepanel title for CreateAccountView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_reset": {
- "translation": "إعادة تعيين كلمة المرور",
- "defaultMessage": "Reset Password",
- "description": "Sidepanel title for PasswordResetView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_settings": {
- "translation": "الإعدادات",
- "defaultMessage": "Settings",
- "description": "Sidepanel title for SettingsView.",
- "missing": false,
- "obsolete": false
- },
- "stay_logged_in": {
- "translation": "البقاء متصلاً",
- "defaultMessage": "Stay logged in",
- "description": "Label for a checkbox",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_find_user": {
- "translation": "بحث",
- "defaultMessage": "find",
- "description": "Tab title Find",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_group_by_id": {
- "translation": "بواسطة المعرف",
- "defaultMessage": "by id",
- "description": "Tab title Find topic by ID",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_new_group": {
- "translation": "مجموعة جديدة",
- "defaultMessage": "new group",
- "description": "Tab title New Group",
- "missing": false,
- "obsolete": false
- },
- "tags_editor_no_tags": {
- "translation": "أضف بعض الوسوم",
- "defaultMessage": "Add some tags",
- "description": "Tag editor prompt when no tags are found.",
- "missing": false,
- "obsolete": false
- },
- "tags_not_found": {
- "translation": "لا توجد وسوم محددة. أضف بعضها.",
- "defaultMessage": "No tags defined. Add some.",
- "description": "",
- "missing": false,
- "obsolete": false
- },
- "title_all_contacts": {
- "translation": "جميع جهات الاتصال",
- "defaultMessage": "All Contacts",
- "description": "Section title [All Contacts]",
- "missing": false,
- "obsolete": false
- },
- "title_group_members": {
- "translation": "أعضاء المجموعة",
- "defaultMessage": "Group Members",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "title_manage_tags": {
- "translation": "إدارة",
- "defaultMessage": "Manage",
- "description": "Section title for the list of tags",
- "missing": false,
- "obsolete": false
- },
- "title_not_found": {
- "translation": "غير موجود",
- "defaultMessage": "Not found",
- "description": "Title shown when topic is not found",
- "missing": false,
- "obsolete": false
- },
- "title_permissions": {
- "translation": "الصلاحيات",
- "defaultMessage": "Permissions",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "title_tag_manager": {
- "translation": "الوسوم (البحث والاكتشاف)",
- "defaultMessage": "Tags (search & discovery)",
- "description": "Section title for TagManager",
- "missing": false,
- "obsolete": false
- },
- "topic_block_warning": {
- "translation": "هل أنت متأكد من أنك تريد حظر هذه المحادثة؟",
- "defaultMessage": "Are you sure you want to block this conversation?",
- "description": "Alert warning when blocking a topic.",
- "missing": false,
- "obsolete": false
- },
- "topic_delete_warning": {
- "translation": "هل أنت متأكد من أنك تريد حذف هذه المحادثة؟ لا يمكن التراجع عن هذا الإجراء.",
- "defaultMessage": "Are you sure you want to delete this conversation? It cannot be undone.",
- "description": "Alert warning when deleting entire topic",
- "missing": false,
- "obsolete": false
- },
- "topic_name_editing_placeholder": {
- "translation": "اسم حر للمجموعة",
- "defaultMessage": "Freeform name of the group",
- "description": "Prompt for entering topic name",
- "missing": false,
- "obsolete": false
- },
- "unnamed_topic": {
- "translation": "بدون اسم",
- "defaultMessage": "Unnamed",
- "description": "Title shown when the topic has no name",
- "missing": false,
- "obsolete": false
- },
- "update_available": {
- "translation": "يتوفر تحديث.",
- "defaultMessage": "Update available.",
- "description": "Message shown when an app update is available.",
- "missing": false,
- "obsolete": false
- },
- "upload_finishing": {
- "translation": "جاري الإنهاء...",
- "defaultMessage": "finishing...",
- "description": "Notification that upload is finishing",
- "missing": false,
- "obsolete": false
- },
- "user_not_found": {
- "translation": "غير موجود",
- "defaultMessage": "Not found",
- "description": "In place of a user's full name when the user is not found.",
- "missing": false,
- "obsolete": false
- },
- "description_editing_placeholder": {
- "translation": "وصف اختياري",
- "defaultMessage": "Description (optional)",
- "description": "Placeholder for editing topic description",
- "missing": false,
- "obsolete": false
- },
- "label_description": {
- "translation": "الوصف",
- "defaultMessage": "Description",
- "description": "Label for description of user or topic",
- "missing": false,
- "obsolete": false
- },
- "button_security": {
- "translation": "الأمان",
- "defaultMessage": "Security",
- "description": "Navigaton button for security panel.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_crop": {
- "translation": "اسحب للتعديل",
- "defaultMessage": "Drag to Adjust",
- "description": "Title for AvatarCropView.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_general": {
- "translation": "عام",
- "defaultMessage": "General",
- "description": "Title for TopicCommon.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_members": {
- "translation": "الأعضاء",
- "defaultMessage": "Members",
- "description": "Title for managing group members view.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_security": {
- "translation": "الأمان",
- "defaultMessage": "Security",
- "description": "Title for TopicSecirity and AccSecurity.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_info": {
- "translation": "معلومات",
- "defaultMessage": "Info",
- "description": "Title for InfoView",
- "missing": false,
- "obsolete": false
- },
- "permissions_anonymous": {
- "translation": "مجهول",
- "defaultMessage": "Anonymous",
- "description": "Title for editing default anonymous permissions",
- "missing": false,
- "obsolete": false
- },
- "permissions_authenticated": {
- "translation": "مصادق عليه",
- "defaultMessage": "Authenticated",
- "description": "Title for editing default authenticated permissions",
- "missing": false,
- "obsolete": false
- },
- "topic_delete": {
- "translation": "حذف المحادثة",
- "defaultMessage": "Delete Conversation",
- "description": "Alert title when deleting the topic.",
- "missing": false,
- "obsolete": false
- },
- "permissions_user": {
- "translation": "صلاحيات المستخدم",
- "defaultMessage": "User's Permissions",
- "description": "Title for editing user's permissions",
- "missing": false,
- "obsolete": false
- },
- "password_reset_email_sent": {
- "translation": "تم إرسال بريد إلكتروني يحتوي على رمز الأمان.",
- "defaultMessage": "An email with security code has been sent.",
- "description": "Notification that the email with password reset instructions has been sent",
- "missing": false,
- "obsolete": false
- },
- "label_unarchive_topic": {
- "translation": "مؤرشف:",
- "defaultMessage": "Archived:",
- "description": "Label for unarchiving the topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_forward": {
- "translation": "إعادة توجيه",
- "defaultMessage": "Forward",
- "description": "Forward message",
- "missing": false,
- "obsolete": false
- },
- "forward_to": {
- "translation": "إعادة توجيه إلى",
- "defaultMessage": "Forward to",
- "missing": false,
- "obsolete": false
- },
- "forward_to_search_placeholder": {
- "translation": "البحث عن جهات الاتصال",
- "defaultMessage": "Search contacts",
- "description": "Contact search prompt when forwarding a message.",
- "missing": false,
- "obsolete": false
- },
- "label_new_password": {
- "translation": "كلمة المرور الجديدة",
- "defaultMessage": "New password",
- "description": "Label for entering a new password",
- "missing": false,
- "obsolete": false
- },
- "drafty_unknown": {
- "translation": "غير مدعوم",
- "defaultMessage": "Unsupported",
- "description": "Unsupported entity in drafty",
- "missing": false,
- "obsolete": false
- },
- "calls_incoming": {
- "translation": "مكالمة واردة",
- "defaultMessage": "Incoming call",
- "description": "Incoming call label",
- "missing": false,
- "obsolete": false
- },
- "calls_outgoing": {
- "translation": "مكالمة صادرة",
- "defaultMessage": "Outgoing call",
- "description": "Outgoing call label",
- "missing": false,
- "obsolete": false
- },
- "calls_you_label": {
- "translation": "أنت",
- "defaultMessage": "You",
- "description": "Shown over the local video screen",
- "missing": false,
- "obsolete": false
- },
- "menu_item_video_call": {
- "translation": "مكالمة فيديو",
- "defaultMessage": "Video call",
- "description": "Start video call",
- "missing": false,
- "obsolete": false
- },
- "already_in_call": {
- "translation": "أنت بالفعل في مكالمة جارية!",
- "defaultMessage": "You already in an ongoing call!",
- "description": "Error message when the user tried to accept a new call without finishing pervious one",
- "missing": false,
- "obsolete": false
- },
- "call_cancelled": {
- "translation": "تم الإلغاء",
- "defaultMessage": "cancelled",
- "description": "Label for cancelled outgoing call",
- "missing": false,
- "obsolete": false
- },
- "call_missed": {
- "translation": "فائتة",
- "defaultMessage": "missed",
- "description": "Label for missed incoming call",
- "missing": false,
- "obsolete": false
- },
- "call_declined": {
- "translation": "تم الرفض",
- "defaultMessage": "declined",
- "description": "Label for declined call",
- "missing": false,
- "obsolete": false
- },
- "call_disconnected": {
- "translation": "تم قطع الاتصال",
- "defaultMessage": "disconnected",
- "description": "Label for disconnected call",
- "missing": false,
- "obsolete": false
- },
- "label_use_secure_connection": {
- "translation": "استخدام اتصال آمن",
- "defaultMessage": "Use secure connection",
- "description": "Label for WS/WSS connection type in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "cred_confirmed_successfully": {
- "translation": "تم التأكيد بنجاح",
- "defaultMessage": "Confirmed successfully",
- "description": "Notification message that the credential was successfully validated.",
- "missing": false,
- "obsolete": false
- },
- "menu_item_edit": {
- "translation": "تعديل",
- "defaultMessage": "Edit",
- "description": "Edit message",
- "missing": false,
- "obsolete": false
- },
- "editing_message": {
- "translation": "جاري التعديل",
- "defaultMessage": "Editing",
- "description": "Title over message editing preview",
- "missing": false,
- "obsolete": false
- },
- "message_edited_marker": {
- "translation": "، تم التعديل",
- "defaultMessage": ", edited",
- "description": "Marker indicating that the message was edited",
- "missing": false,
- "obsolete": false
- },
- "icon_title_add_image": {
- "translation": "إضافة صورة",
- "defaultMessage": "Add image",
- "description": "Icon tool tip for attaching an image",
- "missing": false,
- "obsolete": false
- },
- "icon_title_attach_file": {
- "translation": "إرفاق ملف",
- "defaultMessage": "Attach file",
- "description": "Icon tool tip for attaching a file",
- "missing": false,
- "obsolete": false
- },
- "icon_title_delete": {
- "translation": "حذف التسجيل",
- "defaultMessage": "Delete recording",
- "description": "Icon tool tip for deleting recorded audio",
- "missing": false,
- "obsolete": false
- },
- "icon_title_pause": {
- "translation": "إيقاف مؤقت",
- "defaultMessage": "Pause playback",
- "description": "Icon tool tip for pausing audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_play": {
- "translation": "تشغيل",
- "defaultMessage": "Play recording",
- "description": "Icon tool tip for starting audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_record_voice": {
- "translation": "تسجيل رسالة صوتية",
- "defaultMessage": "Record voice message",
- "description": "Icon tool tip for recording a voice message",
- "missing": false,
- "obsolete": false
- },
- "icon_title_resume": {
- "translation": "استئناف",
- "defaultMessage": "Resume playback",
- "description": "Icon tool tip for resuming audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_send": {
- "translation": "إرسال رسالة",
- "defaultMessage": "Send message",
- "description": "Icon tool tip for sending a message",
- "missing": false,
- "obsolete": false
- },
- "drag_file": {
- "translation": "اسحب الملف هنا",
- "defaultMessage": "Drag file here",
- "description": "Prompt on the file drag-n-drop overlay banner",
- "missing": false,
- "obsolete": false
- },
- "drafty_video": {
- "translation": "تسجيل فيديو",
- "defaultMessage": "Video recording",
- "description": "Comment for videos embedded in Drafty",
- "missing": false,
- "obsolete": false
- },
- "call_in_progress": {
- "translation": "جارية",
- "defaultMessage": "in progress",
- "description": "Label for call in progress",
- "missing": false,
- "obsolete": false
- },
- "menu_item_audio_call": {
- "translation": "مكالمة",
- "defaultMessage": "Call",
- "description": "Start audio call",
- "missing": false,
- "obsolete": false
- },
- "unrecognized_video_format": {
- "translation": "تنسيق هذا الفيديو غير معروف",
- "defaultMessage": "Format of this video is not recognized",
- "description": "Error message when uploaded video is invalid",
- "missing": false,
- "obsolete": false
- },
- "loading_note": {
- "translation": "جاري التحميل...",
- "defaultMessage": "Loading...",
- "description": "Message shown when component is loading",
- "missing": false,
- "obsolete": false
- },
- "password_i_have_code": {
- "translation": "لدي رمز",
- "defaultMessage": "I have code",
- "description": "Call to open field to enter password reset code",
- "missing": false,
- "obsolete": false
- },
- "label_reset_password_tel": {
- "translation": "إرسال رسالة نصية لإعادة تعيين كلمة المرور:",
- "defaultMessage": "Send SMS to reset password",
- "description": "Label for password reset field by phone",
- "missing": false,
- "obsolete": false
- },
- "mobile_phone_number": {
- "translation": "رقم الهاتف المحمول",
- "defaultMessage": "Mobile phone number",
- "description": "Prompt for entering a mobile phone number",
- "missing": false,
- "obsolete": false
- },
- "password_reset_sms_sent": {
- "translation": "تم إرسال رسالة نصية تحتوي على رمز الأمان.",
- "defaultMessage": "A text message with security code has been sent.",
- "description": "Notification that the SMS with password reset instructions has been sent",
- "missing": false,
- "obsolete": false
- },
- "mobile_number_required": {
- "translation": "رقم الهاتف المحمول مطلوب",
- "defaultMessage": "Mobile phone number required",
- "description": "Error message",
- "missing": false,
- "obsolete": false
- },
- "current_email": {
- "translation": "البريد الإلكتروني الحالي",
- "defaultMessage": "Current email",
- "description": "Label for old email",
- "missing": false,
- "obsolete": false
- },
- "current_phone": {
- "translation": "رقم الهاتف الحالي",
- "defaultMessage": "Current phone number",
- "description": "Label for old phone",
- "missing": false,
- "obsolete": false
- },
- "new_email": {
- "translation": "بريد إلكتروني جديد",
- "defaultMessage": "New email",
- "description": "Prompt for entering a new email",
- "missing": false,
- "obsolete": false
- },
- "new_phone_number": {
- "translation": "رقم هاتف جديد",
- "defaultMessage": "New phone number",
- "description": "Prompt for entering a new telephone number",
- "missing": false,
- "obsolete": false
- },
- "change_email": {
- "translation": "تغيير البريد الإلكتروني",
- "defaultMessage": "Change email",
- "description": "Prompt to change current email",
- "missing": false,
- "obsolete": false
- },
- "change_phone": {
- "translation": "تغيير رقم الهاتف",
- "defaultMessage": "Change phone number",
- "description": "Prompt to change current phone number",
- "missing": false,
- "obsolete": false
- },
- "call_busy": {
- "translation": "مشغول",
- "defaultMessage": "busy",
- "description": "Label for busy call line",
- "missing": false,
- "obsolete": false
- },
- "failed_to_init_audio": {
- "translation": "فشل في تهيئة تسجيل الصوت",
- "defaultMessage": "Failed to initialize audio recording",
- "description": "Error message when audio is not available",
- "missing": false,
- "obsolete": false
- },
- "password_reset_success": {
- "translation": "تم إعادة تعيين كلمة المرور بنجاح",
- "defaultMessage": "Password reset successfully",
- "description": "Notification message that the password was successfully reset.",
- "missing": false,
- "obsolete": false
- },
- "scan_qr_code": {
- "translation": "مسح رمز QR",
- "defaultMessage": "Scan QR Code",
- "description": "Title for scanning QR code",
- "missing": false,
- "obsolete": false
- },
- "alias_already_taken": {
- "translation": "(مُستخدم بالفعل)",
- "defaultMessage": "(already taken)",
- "description": "Error message for alias already taken",
- "missing": false,
- "obsolete": false
- },
- "alias_editing_placeholder": {
- "translation": "الاسم المستعار (اختياري)",
- "defaultMessage": "Alias (optional)",
- "description": "Placeholder for editing user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "alias_invalid": {
- "translation": "(غير صالح)",
- "defaultMessage": "(invalid)",
- "description": "Error message for invalid alias",
- "missing": false,
- "obsolete": false
- },
- "label_alias": {
- "translation": "الاسم المستعار:",
- "defaultMessage": "Alias:",
- "description": "Label for user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "label_alias_edit": {
- "translation": "الاسم المستعار",
- "defaultMessage": "Alias",
- "description": "Label for editing user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "message_deleted": {
- "translation": "تم حذف الرسالة",
- "defaultMessage": "message deleted",
- "description": "Pinned message is deleted",
- "missing": false,
- "obsolete": false
- },
- "message_not_found": {
- "translation": "لم يتم العثور على الرسالة",
- "defaultMessage": "message not found",
- "description": "Pinned message is not found",
- "missing": false,
- "obsolete": false
- },
- "pin_message": {
- "translation": "تثبيت",
- "defaultMessage": "Pin",
- "description": "Menu item [Pin] for pinning message to the top.",
- "missing": false,
- "obsolete": false
- },
- "select_country": {
- "translation": "اختر الدولة",
- "defaultMessage": "Select country",
- "description": "Placeholder for the country selector",
- "missing": false,
- "obsolete": false
- },
- "self_topic_comment": {
- "translation": "ملاحظات ورسائل وروابط وملفات محفوظة للمستقبل",
- "defaultMessage": "Notes, messages, links, files saved for posterity",
- "description": "Comment for self topic for UI",
- "missing": false,
- "obsolete": false
- },
- "self_topic_name": {
- "translation": "الرسائل المحفوظة",
- "defaultMessage": "Saved messages",
- "description": "Name of self topic for UI",
- "missing": false,
- "obsolete": false
- },
- "text_copied": {
- "translation": "تم النسخ إلى الحافظة",
- "defaultMessage": "Copied to clipboard",
- "description": "Notification that text has been copied to clipboard",
- "missing": false,
- "obsolete": false
- },
- "unpin_message": {
- "translation": "إلغاء التثبيت",
- "defaultMessage": "Unpin",
- "description": "Menu item [Unpin] for un-pinning the message.",
- "missing": false,
- "obsolete": false
- },
- "color_schema_dark": {
- "translation": "داكن",
- "defaultMessage": "Dark",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "color_schema_light": {
- "translation": "فاتح",
- "defaultMessage": "Light",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "color_schema_system": {
- "translation": "افتراضي النظام",
- "defaultMessage": "System default",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "label_color_schema": {
- "translation": "المظهر:",
- "defaultMessage": "Theme:",
- "description": "Label for selecting color scheme (dark, light) in Settings",
- "missing": false,
- "obsolete": false
- },
- "label_text_size": {
- "translation": "حجم النص:",
- "defaultMessage": "Text size:",
- "description": "Label adjusting text size",
- "missing": false,
- "obsolete": false
- },
- "incognito_mode_description": {
- "translation": "إخفاء حالة اتصالك ومؤشرات الكتابة.",
- "defaultMessage": "Hide your online status and typing indicators.",
- "description": "Description for incognito mode toggle",
- "missing": false,
- "obsolete": false
- },
- "send_command": {
- "translation": "إرسال بـ {key}",
- "defaultMessage": "Send on {key}",
- "description": "Config option to send message on hitting CTRL(or Cmd)-Enter",
- "missing": false,
- "obsolete": false
- },
- "send_command_explained": {
- "translation": "اضغط Enter لسطر جديد",
- "defaultMessage": "Press Enter for new line",
- "description": "Explanation how to enter newline when [Send on CTRL-Enter] is enabled",
- "missing": false,
- "obsolete": false
- },
- "send_plain": {
- "translation": "إرسال بـ Enter",
- "defaultMessage": "Send on Enter",
- "description": "Config option to send message on hitting Enter",
- "missing": false,
- "obsolete": false
- },
- "send_plain_explained": {
- "translation": "اضغط Shift + Enter لسطر جديد",
- "defaultMessage": "Press Shift + Enter for new line",
- "description": "Explanation how to enter newline when [Send on Enter] is enabled",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_general": {
- "translation": "عام",
- "defaultMessage": "General",
- "description": "Sidepanel title for AccGeneralView.",
- "missing": false,
- "obsolete": false
- },
- "label_keyboard": {
- "translation": "لوحة المفاتيح:",
- "defaultMessage": "Keyboard:",
- "description": "Label for send on enter settings",
- "missing": false,
- "obsolete": false
- },
- "pin_chat": {
- "translation": "تثبيت",
- "defaultMessage": "Pin",
- "description": "Menu item [Pin] for pinning chat to the top.",
- "missing": false,
- "obsolete": false
- },
- "unpin_chat": {
- "translation": "إلغاء التثبيت",
- "defaultMessage": "Unpin",
- "description": "Menu item [Unpin] for un-pinning the chat.",
- "missing": false,
- "obsolete": false
- },
- "subscriber_count": {
- "translation": "{count, plural, zero {{count, number} مشترك} one {{count, number} مشترك} two {{count, number} مشتركان} few {{count, number} مشتركين} many {{count, number} مشتركًا} other {{count, number} مشترك}}",
- "defaultMessage": "{count, plural, one {{count, number} subscriber} two {{count, number} subscribers} three {{count, number} subscribers} few {{count, number} subscribers} many {{count, number} subscribers} other {{count, number} subscribers}}",
- "description": "Count of channel subscribers",
- "missing": false,
- "obsolete": false
- },
- "label_member_count": {
- "translation": "الأعضاء:",
- "defaultMessage": "Members:",
- "description": "Label for the count of group topic members",
- "missing": false,
- "obsolete": false
- },
- "label_subscriber_count": {
- "translation": "المشتركون:",
- "defaultMessage": "Subscribers:",
- "description": "Label for the count of channel subscribers",
- "missing": false,
- "obsolete": false
- },
- "member_count": {
- "translation": "{count, plural, zero {{count, number} عضو} one {{count, number} عضو} two {{count, number} عضوان} few {{count, number} أعضاء} many {{count, number} عضوًا} other {{count, number} عضو}}",
- "defaultMessage": "{count, plural, one {{count, number} member} two {{count, number} members} three {{count, number} members} few {{count, number} members} many {{count, number} members} other {{count, number} members}}",
- "description": "Count of group topic members",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_image": {
- "translation": "صورة",
- "defaultMessage": "image",
- "description": "Tab title Image",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_pattern": {
- "translation": "نمط",
- "defaultMessage": "pattern",
- "description": "Tab title Pattern",
- "missing": false,
- "obsolete": false
- },
- "button_restore": {
- "translation": "استعادة الافتراضي",
- "defaultMessage": "Restore default",
- "description": "Reset setting to default",
- "missing": false,
- "obsolete": false
- },
- "wallpapers": {
- "translation": "الخلفيات",
- "defaultMessage": "Wallpapers",
- "description": "Link or title for working with wallpapers.",
- "missing": false,
- "obsolete": false
- },
- "label_blur_wallpaper": {
- "translation": "ضبابية:",
- "defaultMessage": "Blur:",
- "description": "Label adjusting blur amount",
- "missing": false,
- "obsolete": false
- },
- "label_scan_id": {
- "translation": "مسح معرفي:",
- "defaultMessage": "Scan my ID:",
- "description": "Label [Scan my ID:]",
- "missing": false,
- "obsolete": false
- },
- "unknown_name": {
- "translation": "غير معروف",
- "defaultMessage": "Unknown",
- "description": "Name to show when the name is missing",
- "missing": false,
- "obsolete": false
- },
- "save_action": {
- "translation": "حفظ",
- "defaultMessage": "Save",
- "description": "Label for Save button",
- "missing": false,
- "obsolete": false
- },
- "cannot_parse_vcard": {
- "translation": "تعذّر تحليل ملف vCard.",
- "defaultMessage": "Cannot parse vCard file.",
- "description": "Error message when vCard file cannot be parsed",
- "missing": false,
- "obsolete": false
- },
- "chat_now": {
- "translation": "محادثة",
- "defaultMessage": "Chat",
- "description": "Label for message button in contact card",
- "missing": false,
- "obsolete": false
- },
- "find_user": {
- "translation": "بحث",
- "defaultMessage": "Find",
- "description": "Label for search button in contact card",
- "missing": false,
- "obsolete": false
- },
- "label_contact_card": {
- "translation": "بطاقة الاتصال",
- "defaultMessage": "Contact Card",
- "description": "Label for contact card preview",
- "missing": false,
- "obsolete": false
- },
- "label_contacts": {
- "translation": "جهات الاتصال",
- "defaultMessage": "Contacts",
- "description": "Label for contact information",
- "missing": false,
- "obsolete": false
- },
- "add_members_prompt": {
- "translation": "إضافة أعضاء",
- "defaultMessage": "add members",
- "description": "Placeholder in group members input field",
- "missing": false,
- "obsolete": false
- }
-}
diff --git a/src/i18n/de.json b/src/i18n/de.json
deleted file mode 100644
index 83a64936..00000000
--- a/src/i18n/de.json
+++ /dev/null
@@ -1,1966 +0,0 @@
-{
- "action_block_contact": {
- "translation": "Kontakt blockieren",
- "defaultMessage": "Block Contact",
- "description": "Flat button [Block Contact]",
- "missing": false,
- "obsolete": false
- },
- "action_cancel": {
- "translation": "Abbrechen",
- "defaultMessage": "cancel",
- "description": "Call to action [cancel]",
- "missing": false,
- "obsolete": false
- },
- "action_clear_messages": {
- "translation": "Nachrichten leeren",
- "defaultMessage": "Clear Messages",
- "description": "Flat button [Clear Messages] (soft-delete messages)",
- "missing": false,
- "obsolete": false
- },
- "action_delete_messages": {
- "translation": "Nachrichten für alle löschen",
- "defaultMessage": "Clear Messages for All",
- "description": "Flat button [Clear for All] (hard-delete all messages)",
- "missing": false,
- "obsolete": false
- },
- "action_leave_chat": {
- "translation": "Unterhaltung verlassen",
- "defaultMessage": "Leave Conversation",
- "description": "Flat button [Leave Conversation]",
- "missing": false,
- "obsolete": false
- },
- "action_report_chat": {
- "translation": "Unterhaltung melden",
- "defaultMessage": "Report Conversation",
- "description": "Flat button [Report Group]",
- "missing": false,
- "obsolete": false
- },
- "archived_contacts": {
- "translation": "Archivierte Kontakte ({count})",
- "defaultMessage": "Archived contacts ({count})",
- "description": "Label for archived chats",
- "missing": false,
- "obsolete": false
- },
- "badge_danger": {
- "translation": "Verdächtig",
- "defaultMessage": "Untrustworthy",
- "description": "Suspicious or untrustworthy account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_owner": {
- "translation": "Besitzer",
- "defaultMessage": "owner",
- "description": "Badge for indicating the owner",
- "missing": false,
- "obsolete": false
- },
- "badge_staff": {
- "translation": "Verwaltung",
- "defaultMessage": "Staff-managed",
- "description": "Explanation of a staff-managed account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_verified": {
- "translation": "Verifiziert",
- "defaultMessage": "Verified/official",
- "description": "Explanation of a verified account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_you": {
- "translation": "Sie",
- "defaultMessage": "you",
- "description": "Badge for indicating the current user",
- "missing": false,
- "obsolete": false
- },
- "block_contact_warning": {
- "translation": "Möchten Sie diesen Kontakt wirklich blockieren?",
- "defaultMessage": "Are you sure you want to block this contact?",
- "description": "Alert dialog warning when blocking a contact.",
- "missing": false,
- "obsolete": false
- },
- "blocked_contacts_link": {
- "translation": "Blockierte Kontakte ({count})",
- "defaultMessage": "Blocked contacts ({count})",
- "description": "Blocked contacts link",
- "missing": false,
- "obsolete": false
- },
- "button_add_members": {
- "translation": "Mitglieder hinzufügen",
- "defaultMessage": "Add members",
- "description": "Flat button [Add members] (to topic)",
- "missing": false,
- "obsolete": false
- },
- "button_cancel": {
- "translation": "Abbrechen",
- "defaultMessage": "Cancel",
- "description": "Button [Cancel]",
- "missing": false,
- "obsolete": false
- },
- "button_confirm": {
- "translation": "Bestätigen",
- "defaultMessage": "Confirm",
- "description": "Button [Confirm]",
- "missing": false,
- "obsolete": false
- },
- "button_create": {
- "translation": "Erstellen",
- "defaultMessage": "Create",
- "description": "Button [Create]",
- "missing": false,
- "obsolete": false
- },
- "button_delete_account": {
- "translation": "Konto löschen",
- "defaultMessage": "Delete account",
- "description": "Button [Delete account]",
- "missing": false,
- "obsolete": false
- },
- "button_edit": {
- "translation": "Bearbeiten",
- "defaultMessage": "Edit",
- "description": "Call to action [Edit]",
- "missing": false,
- "obsolete": false
- },
- "button_logout": {
- "translation": "Abmelden",
- "defaultMessage": "Logout",
- "description": "Button [Logout]",
- "missing": false,
- "obsolete": false
- },
- "button_ok": {
- "translation": "OK",
- "defaultMessage": "OK",
- "description": "Button [OK]",
- "missing": false,
- "obsolete": false
- },
- "button_reset": {
- "translation": "Zurücksetzen",
- "defaultMessage": "Reset",
- "description": "Button [Reset]",
- "missing": false,
- "obsolete": false
- },
- "button_send_request": {
- "translation": "Anfrage senden",
- "defaultMessage": "Send request",
- "description": "Button [Send request]",
- "missing": false,
- "obsolete": false
- },
- "button_sign_in": {
- "translation": "Anmelden",
- "defaultMessage": "Sign in",
- "description": "Button [Sign In]",
- "missing": false,
- "obsolete": false
- },
- "button_sign_up": {
- "translation": "Anmelden",
- "defaultMessage": "Sign up",
- "description": "Create account button [Sign Up]",
- "missing": false,
- "obsolete": false
- },
- "button_subscribe": {
- "translation": "Abbonieren",
- "defaultMessage": "Subscribe",
- "description": "Button [Subscribe]",
- "missing": false,
- "obsolete": false
- },
- "button_update": {
- "translation": "Aktualisieren",
- "defaultMessage": "Update",
- "description": "Button [Update]",
- "missing": false,
- "obsolete": false
- },
- "cannot_initiate_file_upload": {
- "translation": "Datei kann nicht hochgeladen werden.",
- "defaultMessage": "Cannot initiate file upload.",
- "description": "Generic error messagewhen attachment fails",
- "missing": false,
- "obsolete": false
- },
- "channel": {
- "translation": "Sender",
- "defaultMessage": "channel",
- "description": "Subtitle shown for channels in MessagesView instead of last seen",
- "missing": false,
- "obsolete": false
- },
- "channel_prompt": {
- "translation": "Dies ist ein Sender",
- "defaultMessage": "This is a channel",
- "description": "Checkbox label when creating a channel",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation": {
- "translation": "Sie sind eingeladen, an einem neuen Chat teilzunehmen. Was möchten Sie tun?",
- "defaultMessage": "You are invited to start a new chat. What would you like to do?",
- "description": "New chat invitation message: [Accept] [Ignore] [Block].",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_accept": {
- "translation": "Akzeptieren",
- "defaultMessage": "Accept",
- "description": "Action [Accept] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_block": {
- "translation": "Blockieren",
- "defaultMessage": "Block",
- "description": "Action [Block] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_ignore": {
- "translation": "Ignorieren",
- "defaultMessage": "Ignore",
- "description": "Action [Ignore] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "clear_messages_warning": {
- "translation": "Sind Sie sicher, dass Sie diese Unterhaltung für alle leeren wollen? Das kann nicht rückgängig gemacht werden.",
- "defaultMessage": "Are you sure you want to clear all messages? It cannot be undone.",
- "description": "Alert dialog warning when deleting all messages.",
- "missing": false,
- "obsolete": false
- },
- "code_doesnot_match": {
- "translation": "Code stimmt nicht überein",
- "defaultMessage": "Code does not match",
- "description": "Error message when the credential validation code is incorrect.",
- "missing": false,
- "obsolete": false
- },
- "contacts_not_found": {
- "translation": "Sie haben keine Unterhaltungen\n¯∖_(ツ)_/¯",
- "defaultMessage": "You have no chats\\n¯∖_(ツ)_/¯",
- "description": "HTML message shown in ContactList when no contacts are found",
- "missing": false,
- "obsolete": false
- },
- "contacts_not_found_short": {
- "translation": "Keine Kontakte für ''{query}''",
- "defaultMessage": "No contacts match ''{query}''",
- "description": "Shown in ContactsView when search returned no results",
- "missing": false,
- "obsolete": false
- },
- "credential_email_prompt": {
- "translation": "E-Mail Adresse für Registrierung",
- "defaultMessage": "Your registration email",
- "description": "Placeholder for entering email",
- "missing": false,
- "obsolete": false
- },
- "delete_account": {
- "translation": "Konto löschen",
- "defaultMessage": "Delete account",
- "description": "Title for delete account warning",
- "missing": false,
- "obsolete": false
- },
- "delete_account_warning": {
- "translation": "Möchten Sie das Konto wirklich löschen? Das kann nicht rückgängig gemacht werden",
- "defaultMessage": "Are you sure you want to delete your account? It cannot be undone.",
- "description": "Warning message when deleting an account",
- "missing": false,
- "obsolete": false
- },
- "delete_messages_warning": {
- "translation": "Sind Sie sicher, dass Sie diese Unterhaltung für alle löschen wollen? Das kann nicht rückgängig gemacht werden.",
- "defaultMessage": "Are you sure you want to delete all messages for everyone? It cannot be undone.",
- "description": "Alert dialog warning when hard-deleting all messages.",
- "missing": false,
- "obsolete": false
- },
- "download_action": {
- "translation": "herunterladen",
- "defaultMessage": "download",
- "description": "Call to action [download]",
- "missing": false,
- "obsolete": false
- },
- "drafty_attachment": {
- "translation": "Dateianhang",
- "defaultMessage": "Attachment",
- "description": "Comment for attachment in Drafty",
- "missing": false,
- "obsolete": false
- },
- "drafty_form": {
- "translation": "Formular:",
- "defaultMessage": "Form:",
- "description": "Comment for form in Drafty",
- "missing": false,
- "obsolete": false
- },
- "drafty_image": {
- "translation": "Bild",
- "defaultMessage": "Picture",
- "description": "Comment for embedded images in Drafty",
- "missing": false,
- "obsolete": false
- },
- "email_dative": {
- "translation": "E-Mail",
- "defaultMessage": "email",
- "description": "Dative case of 'email', i.e. 'email' in 'by email'",
- "missing": false,
- "obsolete": false
- },
- "email_prompt": {
- "translation": "E-Mail, z.B. lisam@beispiel.de",
- "defaultMessage": "Email, e.g. jdoe@example.com",
- "description": "Input placeholder for email entry",
- "missing": false,
- "obsolete": false
- },
- "enable_peers_messaging": {
- "translation": "Aktivieren",
- "defaultMessage": "Enable",
- "description": "Call to action to enable peer's messaging",
- "missing": false,
- "obsolete": false
- },
- "enter_confirmation_code_prompt": {
- "translation": "Geben Sie den Bestätigungscode ein, der per {method} geschickt wurde:",
- "defaultMessage": "Confirmation code",
- "description": "Request to enter confirmation code",
- "missing": false,
- "obsolete": false
- },
- "error_invalid_id": {
- "translation": "ungültige ID",
- "defaultMessage": "Invalid ID",
- "description": "Error message",
- "missing": false,
- "obsolete": false
- },
- "file_attachment_too_large": {
- "translation": "Die Dateigröße {size} überschreitet das Limit von {limit}.",
- "defaultMessage": "The file size {size} exceeds the {limit} limit.",
- "description": "Error message when attachment is too large",
- "missing": false,
- "obsolete": false
- },
- "forgot_password_link": {
- "translation": "Passwort vergessen?",
- "defaultMessage": "Forgot password?",
- "description": "Link to Reset password form",
- "missing": false,
- "obsolete": false
- },
- "full_name_prompt": {
- "translation": "Vollständiger Name, z.B. Lisa Musterfrau",
- "defaultMessage": "Full name, e.g. John Doe",
- "description": "Input placeholder for person's full name",
- "missing": false,
- "obsolete": false
- },
- "granted_permissions": {
- "translation": "Erteilt",
- "defaultMessage": "Granted",
- "description": "Title for permissions",
- "missing": false,
- "obsolete": false
- },
- "group_has_no_members": {
- "translation": "Keine Mitglieder",
- "defaultMessage": "No members",
- "description": "Shown in place of group members",
- "missing": false,
- "obsolete": false
- },
- "group_user_id_prompt": {
- "translation": "Gruppe oder Benutzer ID",
- "defaultMessage": "Group or User ID",
- "description": "Prompt for entering user or group ID",
- "missing": false,
- "obsolete": false
- },
- "image_caption_prompt": {
- "translation": "Bildunterschrift",
- "defaultMessage": "Image caption",
- "description": "Prompt in SendMessage for attached image",
- "missing": false,
- "obsolete": false
- },
- "invalid_content": {
- "translation": "ungültiger Inhalt",
- "defaultMessage": "invalid content",
- "description": "Shown when the message is unreadable",
- "missing": false,
- "obsolete": false
- },
- "invalid_security_token": {
- "translation": "Ungültiger Sicherheitsschlüssel",
- "defaultMessage": "Invalid security token",
- "description": "Error message when resetting password.",
- "missing": false,
- "obsolete": false
- },
- "label_client": {
- "translation": "Client:",
- "defaultMessage": "Client:",
- "description": "Label for a client version",
- "missing": false,
- "obsolete": false
- },
- "label_content_type": {
- "translation": "Inhaltsart:",
- "defaultMessage": "Content type:",
- "description": "Label for file content type (mime)",
- "missing": false,
- "obsolete": false
- },
- "label_default_access_mode": {
- "translation": "Standard Zugriffsmodus:",
- "defaultMessage": "Default access mode:",
- "description": "Label for default access mode",
- "missing": false,
- "obsolete": false
- },
- "label_file_name": {
- "translation": "Dateiname:",
- "defaultMessage": "File name:",
- "description": "Label for a file name",
- "missing": false,
- "obsolete": false
- },
- "label_group_members": {
- "translation": "Gruppenmitglieder:",
- "defaultMessage": "Group members:",
- "description": "Section title or label",
- "missing": false,
- "obsolete": false
- },
- "label_incognito_mode": {
- "translation": "inkognito-Modus:",
- "defaultMessage": "Incognito mode:",
- "description": "Label for incognito mode toggle",
- "missing": false,
- "obsolete": false
- },
- "label_message_sound": {
- "translation": "Benachrichtigungston:",
- "defaultMessage": "Message sound:",
- "description": "Label for message sounds toggle",
- "missing": false,
- "obsolete": false
- },
- "label_muting_topic": {
- "translation": "Stumm geschaltet:",
- "defaultMessage": "Muted:",
- "description": "Label for Muting/unmuting the topic",
- "missing": false,
- "obsolete": false
- },
- "label_other_user": {
- "translation": "Andere",
- "defaultMessage": "Other",
- "description": "Label for the other user when the user is unnamed",
- "missing": false,
- "obsolete": false
- },
- "label_password": {
- "translation": "Passwort",
- "defaultMessage": "Password",
- "description": "Label for password editing",
- "missing": false,
- "obsolete": false
- },
- "label_permissions": {
- "translation": "Berechtigungen:",
- "defaultMessage": "Permissions:",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "label_private": {
- "translation": "Privater Kommentar",
- "defaultMessage": "Private comment",
- "description": "Label for editing 'private'",
- "missing": false,
- "obsolete": false
- },
- "label_push_notifications": {
- "translation": "Benachrichtigungsmeldungen:",
- "defaultMessage": "Notification alerts:",
- "description": "Label for push notifications switch",
- "missing": false,
- "obsolete": false
- },
- "label_push_notifications_disabled": {
- "translation": "Benachrichtigungsmeldungen (erfordert HTTPS):",
- "defaultMessage": "Notification alerts (requires HTTPS):",
- "description": "Label for push notifications switch",
- "missing": false,
- "obsolete": false
- },
- "label_reset_password": {
- "translation": "Passwort per E-Mail wiederherstellen:",
- "defaultMessage": "Send a password reset email",
- "description": "Label for password reset field",
- "missing": false,
- "obsolete": false
- },
- "label_sdk": {
- "translation": "SDK:",
- "defaultMessage": "SDK:",
- "missing": false,
- "obsolete": false
- },
- "label_server": {
- "translation": "Server:",
- "defaultMessage": "Server:",
- "description": "Label for a server version",
- "missing": false,
- "obsolete": false
- },
- "label_server_address": {
- "translation": "Server Adresse:",
- "defaultMessage": "Server address:",
- "missing": false,
- "obsolete": false
- },
- "label_server_to_use": {
- "translation": "Server verwenden:",
- "defaultMessage": "Server to use:",
- "description": "Label for server selector in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "label_size": {
- "translation": "Größe:",
- "defaultMessage": "Size:",
- "description": "Label for file size",
- "missing": false,
- "obsolete": false
- },
- "label_topic_name": {
- "translation": "Name",
- "defaultMessage": "Name",
- "description": "Label for editing topic name",
- "missing": false,
- "obsolete": false
- },
- "label_user_contacts": {
- "translation": "Kontakte",
- "defaultMessage": "Contacts",
- "description": "Label for user contacts",
- "missing": false,
- "obsolete": false
- },
- "label_user_id": {
- "translation": "ID:",
- "defaultMessage": "ID:",
- "description": "Label for user address (ID)",
- "missing": false,
- "obsolete": false
- },
- "label_wire_transport": {
- "translation": "Übertragung per Kabel:",
- "defaultMessage": "Wire transport:",
- "description": "Label for wire transport selection in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "label_you": {
- "translation": "Sie:",
- "defaultMessage": "You:",
- "description": "Label for the current user",
- "missing": false,
- "obsolete": false
- },
- "label_your_name": {
- "translation": "Ihr Name",
- "defaultMessage": "Your name",
- "description": "Label for full name editing",
- "missing": false,
- "obsolete": false
- },
- "label_your_permissions": {
- "translation": "Ihre Berechtigungen:",
- "defaultMessage": "Your permissions:",
- "description": "Label for current user permissions",
- "missing": false,
- "obsolete": false
- },
- "last_seen_timestamp": {
- "translation": "Zuletzt gesehen",
- "defaultMessage": "Last seen: {timestamp}",
- "description": "Label for the timestamp of when the user or topic was last online",
- "missing": false,
- "obsolete": false
- },
- "leave_chat_warning": {
- "translation": "Möchten Sie diese Unterhaltung wirklich verlassen?",
- "defaultMessage": "Are you sure you want to leave this conversation?",
- "description": "Alert dialog warning when unsubscribing from a chat.",
- "missing": false,
- "obsolete": false
- },
- "link_contact_us": {
- "translation": "Kontakt",
- "defaultMessage": "Contact Us",
- "description": "Ancor text for contacting us by email",
- "missing": false,
- "obsolete": false
- },
- "link_privacy_policy": {
- "translation": "Datenschutzerklärung",
- "defaultMessage": "Privacy Policy",
- "description": "Ancor text for privacy policy link",
- "missing": false,
- "obsolete": false
- },
- "link_terms_of_service": {
- "translation": "Nutzungsbedingungen",
- "defaultMessage": "Terms of Service",
- "description": "Ancor text for terms of service link",
- "missing": false,
- "obsolete": false
- },
- "login_prompt": {
- "translation": "Anmelden",
- "defaultMessage": "Login",
- "description": "Placeholer for username/login",
- "missing": false,
- "obsolete": false
- },
- "menu_item_archive_topic": {
- "translation": "Archivieren",
- "defaultMessage": "Archive",
- "description": "Move topic from the list of active chats to archive",
- "missing": false,
- "obsolete": false
- },
- "menu_item_block": {
- "translation": "Blockieren",
- "defaultMessage": "Block",
- "description": "Block topic or user",
- "missing": false,
- "obsolete": false
- },
- "menu_item_clear_messages": {
- "translation": "Nachrichten leeren",
- "defaultMessage": "Clear messages",
- "description": "Delete all messages",
- "missing": false,
- "obsolete": false
- },
- "menu_item_clear_messages_for_all": {
- "translation": "Für alle leeren",
- "defaultMessage": "Clear for All",
- "description": "Delete all message(s) for all members",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete": {
- "translation": "Löschen",
- "defaultMessage": "Delete",
- "description": "Delete selected messages",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete_for_all": {
- "translation": "Für alle löschen",
- "defaultMessage": "Delete for All",
- "description": "Delete selected message(s) for all members",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete_topic": {
- "translation": "Entfernen",
- "defaultMessage": "Delete",
- "description": "Delete entire topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_edit_permissions": {
- "translation": "Berechtigungen ändern",
- "defaultMessage": "Edit permissions",
- "description": "Menu item [Edit permissions]",
- "missing": false,
- "obsolete": false
- },
- "menu_item_info": {
- "translation": "Info",
- "defaultMessage": "Info",
- "description": "Show extended topic information",
- "missing": false,
- "obsolete": false
- },
- "menu_item_member_delete": {
- "translation": "Entfernen",
- "defaultMessage": "Remove",
- "description": "Remove user from topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_mute": {
- "translation": "Unterhaltung stumm schalten",
- "defaultMessage": "Mute",
- "description": "Turn off notifications",
- "missing": false,
- "obsolete": false
- },
- "menu_item_restore_topic": {
- "translation": "Wiederherstellen",
- "defaultMessage": "Restore",
- "description": "Restore topic from archive",
- "missing": false,
- "obsolete": false
- },
- "menu_item_send_retry": {
- "translation": "Wiederholen",
- "defaultMessage": "Retry",
- "description": "Retry sending message",
- "missing": false,
- "obsolete": false
- },
- "menu_item_unblock": {
- "translation": "Blockierung aufheben",
- "defaultMessage": "Unblock",
- "description": "Unblock topic or user",
- "missing": false,
- "obsolete": false
- },
- "menu_item_unmute": {
- "translation": "Stumm schalten beenden",
- "defaultMessage": "Unmute",
- "description": "Turn notifications on",
- "missing": false,
- "obsolete": false
- },
- "message_sending": {
- "translation": "wird gesendet...",
- "defaultMessage": "sending...",
- "description": "Message being sent, in place of time stamp",
- "missing": false,
- "obsolete": false
- },
- "message_sending_failed": {
- "translation": "fehlgeschlagen",
- "defaultMessage": "failed",
- "description": "Failed to send message, in place of time stamp",
- "missing": false,
- "obsolete": false
- },
- "messages_not_readable": {
- "translation": "Neue Nachrichten können nicht gelesen werden",
- "defaultMessage": "no access to messages",
- "description": "Message shown in topic without the read access",
- "missing": false,
- "obsolete": false
- },
- "messaging_disabled_prompt": {
- "translation": "Nachrichtenübermittlung deaktiviert",
- "defaultMessage": "Messaging disabled",
- "description": "Prompt in SendMessage in read-only topic",
- "missing": false,
- "obsolete": false
- },
- "more_online_members": {
- "translation": "+{overflow} mehr",
- "defaultMessage": "+{overflow} more",
- "description": "Shown in MessagesView title bar when the number of online subscribers exceeds MAX_ONLINE_IN_TOPIC",
- "missing": false,
- "obsolete": false
- },
- "new_message_prompt": {
- "translation": "Neue Nachricht",
- "defaultMessage": "New message",
- "description": "Prompt in send message field",
- "missing": false,
- "obsolete": false
- },
- "new_password_placeholder": {
- "translation": "Geben Sie ein neues Passwort ein",
- "defaultMessage": "Enter new password",
- "description": "Placeholder for entering new password",
- "missing": false,
- "obsolete": false
- },
- "no_connection": {
- "translation": "Keine Verbindung",
- "defaultMessage": "No connection",
- "description": "Warning that the user is offline.",
- "missing": false,
- "obsolete": false
- },
- "no_contacts": {
- "translation": "Sie haben keine Kontakte :-(",
- "defaultMessage": "You have no contacts :-(",
- "description": "Shown in ContactsView when the user has no contacts",
- "missing": false,
- "obsolete": false
- },
- "numeric_confirmation_code_prompt": {
- "translation": "Nur zahlen",
- "defaultMessage": "Numbers only",
- "description": "Prompt for numeric conformation code",
- "missing": false,
- "obsolete": false
- },
- "online_now": {
- "translation": "jetzt online",
- "defaultMessage": "online now",
- "description": "Indicator that the user or topic is currently online",
- "missing": false,
- "obsolete": false
- },
- "password_prompt": {
- "translation": "Passwort",
- "defaultMessage": "Password",
- "description": "Placeholder/prompt for entering password",
- "missing": false,
- "obsolete": false
- },
- "password_unchanged_prompt": {
- "translation": "unverändert",
- "defaultMessage": "Unchanged",
- "description": "Message in editor while password is unchanged",
- "missing": false,
- "obsolete": false
- },
- "peers_messaging_disabled": {
- "translation": "Gruppennachrichten sind deaktiviert",
- "defaultMessage": "Peer's messaging is disabled.",
- "description": "Shown when the p2p peer's messaging is disabled",
- "missing": false,
- "obsolete": false
- },
- "permission_admin": {
- "translation": "Bestätigen ({val})",
- "defaultMessage": "Approve ({val})",
- "description": "Name of A permission",
- "missing": false,
- "obsolete": false
- },
- "permission_delete": {
- "translation": "Entfernen ({val})",
- "defaultMessage": "Delete ({val})",
- "description": "Name of D permission",
- "missing": false,
- "obsolete": false
- },
- "permission_join": {
- "translation": "Beitreten ({val})",
- "defaultMessage": "Join ({val})",
- "description": "Name of J permission",
- "missing": false,
- "obsolete": false
- },
- "permission_owner": {
- "translation": "Besitzer ({val})",
- "defaultMessage": "Owner ({val})",
- "description": "Name of O permission",
- "missing": false,
- "obsolete": false
- },
- "permission_pres": {
- "translation": "Benachrichtigt werden ({val})",
- "defaultMessage": "Get notified ({val})",
- "description": "Name of P permission",
- "missing": false,
- "obsolete": false
- },
- "permission_read": {
- "translation": "Lesen ({val})",
- "defaultMessage": "Read ({val})",
- "description": "Name of R permission",
- "missing": false,
- "obsolete": false
- },
- "permission_share": {
- "translation": "Teilen ({val})",
- "defaultMessage": "Share ({val})",
- "description": "Name of S permission",
- "missing": false,
- "obsolete": false
- },
- "permission_write": {
- "translation": "Schreiben ({val})",
- "defaultMessage": "Write ({val})",
- "description": "Name of W permission",
- "missing": false,
- "obsolete": false
- },
- "phone_dative": {
- "translation": "Telefon",
- "defaultMessage": "phone",
- "description": "Dative case of 'phone', i.e. 'phone' in 'by phone'",
- "missing": false,
- "obsolete": false
- },
- "private_editing_placeholder": {
- "translation": "Nur für Sie sichtbar",
- "defaultMessage": "Visible to you only",
- "description": "Placeholder for editing 'private'",
- "missing": false,
- "obsolete": false
- },
- "push_init_failed": {
- "translation": "Initialisierung von Push-Benachrichtigungen fehlgeschlagen",
- "defaultMessage": "Failed to initialize push notifications",
- "description": "Error message when push notifications have failed to initialize.",
- "missing": false,
- "obsolete": false
- },
- "reconnect_countdown": {
- "translation": "Getrennt. Wiederverbinden in {seconds}…",
- "defaultMessage": "Disconnected. Reconnecting in {seconds}…",
- "description": "Message shown when an app update is available.",
- "missing": false,
- "obsolete": false
- },
- "reconnect_now": {
- "translation": "Jetzt probieren",
- "defaultMessage": "Try now",
- "description": "Prompt for reconnecting now",
- "missing": false,
- "obsolete": false
- },
- "reload_update": {
- "translation": "Neu laden",
- "defaultMessage": "Reload",
- "description": "Call to action to reload application when update is available.",
- "missing": false,
- "obsolete": false
- },
- "report_chat_warning": {
- "translation": "Möchten Sie diese Unterhaltung wirklich blockieren und melden?",
- "defaultMessage": "Are you sure you want to block and report this conversation?",
- "description": "Alert dialog warning when reporting a conversation for abuse",
- "missing": false,
- "obsolete": false
- },
- "requested_permissions": {
- "translation": "Angefordert",
- "defaultMessage": "Requested",
- "description": "Title for permissions",
- "missing": false,
- "obsolete": false
- },
- "save_attachment": {
- "translation": "Speichern",
- "defaultMessage": "save",
- "description": "Call to save an attachment",
- "missing": false,
- "obsolete": false
- },
- "search_for_contacts": {
- "translation": "Nutzen Sie die Suche um Kontakte zu finden",
- "defaultMessage": "Use search to find contacts",
- "description": "Text shown in contacts view when user entered no search query.",
- "missing": false,
- "obsolete": false
- },
- "search_no_results": {
- "translation": "Die Suche hatte keine Ergebnisse",
- "defaultMessage": "Search returned no results",
- "description": "Text shown in contacts view when query returned no results.",
- "missing": false,
- "obsolete": false
- },
- "search_placeholder": {
- "translation": "Liste: email:lisa@beispiel.de, tel:17025550003...",
- "defaultMessage": "List like alice@example.com, +17025550003...",
- "description": "Placeholder in contacts search field",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_notifications": {
- "translation": "Benachrichtigungen",
- "defaultMessage": "Notifications",
- "description": "Sidepanel title for AccNotificationsView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_support": {
- "translation": "Unterstützung",
- "defaultMessage": "Support",
- "description": "Sidepanel title for AccSupportView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_account_settings": {
- "translation": "Konto-Einstellungen",
- "defaultMessage": "Account Settings",
- "description": "Sidepanel title for AccountSettingsView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_archive": {
- "translation": "Archivierte Unterhaltungen",
- "defaultMessage": "Archived Chats",
- "description": "Sidepanel title for ContactsView-Archive.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_blocked": {
- "translation": "Blockierte Unterhaltungen",
- "defaultMessage": "Blocked Chats",
- "description": "Sidepanel title for ContactsView-Blocked.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_cred": {
- "translation": "Anmeldeinformationen bestätigen",
- "defaultMessage": "Confirm Credentials",
- "description": "Sidepanel title for ValidationView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_login": {
- "translation": "Anmelden",
- "defaultMessage": "Sign In",
- "description": "Sidepanel title for LoginView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_newtpk": {
- "translation": "Neue Unterhaltung starten",
- "defaultMessage": "Start New Chat",
- "description": "Sidepanel title for NewTopicView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_register": {
- "translation": "Konto erstellen",
- "defaultMessage": "Create Account",
- "description": "Sidepanel title for CreateAccountView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_reset": {
- "translation": "Passwort zurücksetzen",
- "defaultMessage": "Reset Password",
- "description": "Sidepanel title for PasswordResetView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_settings": {
- "translation": "Einstellungen",
- "defaultMessage": "Settings",
- "description": "Sidepanel title for SettingsView.",
- "missing": false,
- "obsolete": false
- },
- "stay_logged_in": {
- "translation": "Angemeldet bleiben",
- "defaultMessage": "Stay logged in",
- "description": "Label for a checkbox",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_find_user": {
- "translation": "Suchen",
- "defaultMessage": "find",
- "description": "Tab title Find",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_group_by_id": {
- "translation": "nach ID",
- "defaultMessage": "by id",
- "description": "Tab title Find topic by ID",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_new_group": {
- "translation": "Neue Gruppe",
- "defaultMessage": "new group",
- "description": "Tab title New Group",
- "missing": false,
- "obsolete": false
- },
- "tags_editor_no_tags": {
- "translation": "Schlagworte hinzufügen",
- "defaultMessage": "Add some tags",
- "description": "Tag editor prompt when no tags are found.",
- "missing": false,
- "obsolete": false
- },
- "tags_not_found": {
- "translation": "Keine Schlagworte definiert. Erstellen Sie welche.",
- "defaultMessage": "No tags defined. Add some.",
- "description": "",
- "missing": false,
- "obsolete": false
- },
- "title_all_contacts": {
- "translation": "Alle Kontakte",
- "defaultMessage": "All Contacts",
- "description": "Section title [All Contacts]",
- "missing": false,
- "obsolete": false
- },
- "title_group_members": {
- "translation": "Gruppenmitglieder",
- "defaultMessage": "Group Members",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "panel_title_info": {
- "translation": "Info",
- "defaultMessage": "Info",
- "description": "Title for InfoView",
- "missing": false,
- "obsolete": false
- },
- "title_manage_tags": {
- "translation": "Verwalten",
- "defaultMessage": "Manage",
- "description": "Section title for the list of tags",
- "missing": false,
- "obsolete": false
- },
- "title_not_found": {
- "translation": "Nicht gefunden",
- "defaultMessage": "Not found",
- "description": "Title shown when topic is not found",
- "missing": false,
- "obsolete": false
- },
- "title_permissions": {
- "translation": "Berechtigungen",
- "defaultMessage": "Permissions",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "title_tag_manager": {
- "translation": "Schlagworte (Nutzer entdecken)",
- "defaultMessage": "Tags (search & discovery)",
- "description": "Section title for TagManager",
- "missing": false,
- "obsolete": false
- },
- "topic_block_warning": {
- "translation": "Möchten Sie diese Unterhaltung wirklich blockieren?",
- "defaultMessage": "Are you sure you want to block this conversation?",
- "description": "Alert warning when blocking a topic.",
- "missing": false,
- "obsolete": false
- },
- "topic_delete_warning": {
- "translation": "Möchten Sie diese Unterhaltung wirklich löschen?",
- "defaultMessage": "Are you sure you want to delete this conversation? It cannot be undone.",
- "description": "Alert warning when deleting entire topic",
- "missing": false,
- "obsolete": false
- },
- "topic_name_editing_placeholder": {
- "translation": "Freiform Name der Gruppe",
- "defaultMessage": "Freeform name of the group",
- "description": "Prompt for entering topic name",
- "missing": false,
- "obsolete": false
- },
- "unnamed_topic": {
- "translation": "Unbenannt",
- "defaultMessage": "Unnamed",
- "description": "Title shown when the topic has no name",
- "missing": false,
- "obsolete": false
- },
- "update_available": {
- "translation": "Aktualisierung verfügbar.",
- "defaultMessage": "Update available.",
- "description": "Message shown when an app update is available.",
- "missing": false,
- "obsolete": false
- },
- "upload_finishing": {
- "translation": "wird abgeschlossen...",
- "defaultMessage": "finishing...",
- "description": "Notification that upload is finishing",
- "missing": false,
- "obsolete": false
- },
- "user_not_found": {
- "translation": "Nicht gefunden",
- "defaultMessage": "Not found",
- "description": "In place of a user's full name when the user is not found.",
- "missing": false,
- "obsolete": false
- },
- "description_editing_placeholder": {
- "translation": "Beschreibung (optional)",
- "defaultMessage": "Description (optional)",
- "description": "Placeholder for editing topic description",
- "missing": false,
- "obsolete": false
- },
- "label_description": {
- "translation": "Beschreibung",
- "defaultMessage": "Description",
- "description": "Label for description of user or topic",
- "missing": false,
- "obsolete": false
- },
- "button_security": {
- "translation": "Sicherheit",
- "defaultMessage": "Security",
- "description": "Navigaton button for security panel.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_general": {
- "translation": "Allgemein",
- "defaultMessage": "General",
- "description": "Title for TopicCommon.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_security": {
- "translation": "Sicherheit",
- "defaultMessage": "Security",
- "description": "Title for TopicSecirity and AccSecurity.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_crop": {
- "translation": "Zum Anpassen ziehen",
- "defaultMessage": "Drag to Adjust",
- "description": "Title for AvatarCropView.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_members": {
- "translation": "Mitglieder",
- "defaultMessage": "Members",
- "description": "Title for managing group members view.",
- "missing": false,
- "obsolete": false
- },
- "permissions_anonymous": {
- "translation": "Anonym",
- "defaultMessage": "Anonymous",
- "description": "Title for editing default anonymous permissions",
- "missing": false,
- "obsolete": false
- },
- "permissions_authenticated": {
- "translation": "Authentifiziert",
- "defaultMessage": "Authenticated",
- "description": "Title for editing default authenticated permissions",
- "missing": false,
- "obsolete": false
- },
- "topic_delete": {
- "translation": "Chat löschen",
- "defaultMessage": "Delete Conversation",
- "description": "Alert title when deleting the topic.",
- "missing": false,
- "obsolete": false
- },
- "permissions_user": {
- "translation": "Benutzerberechtigungen",
- "defaultMessage": "User's Permissions",
- "description": "Title for editing user's permissions",
- "missing": false,
- "obsolete": false
- },
- "password_reset_email_sent": {
- "translation": "Eine E-Mail mit Sicherheitscode wurde gesendet.",
- "defaultMessage": "An email with security code has been sent.",
- "description": "Notification that the email with password reset instructions has been sent",
- "missing": false,
- "obsolete": false
- },
- "label_unarchive_topic": {
- "translation": "Archiviert:",
- "defaultMessage": "Archived:",
- "description": "Label for unarchiving the topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_reply": {
- "translation": "Antwort",
- "defaultMessage": "Reply",
- "description": "Reply to message",
- "missing": false,
- "obsolete": false
- },
- "menu_item_forward": {
- "translation": "Nach vorne",
- "defaultMessage": "Forward",
- "description": "Forward message",
- "missing": false,
- "obsolete": false
- },
- "forward_to": {
- "translation": "Weiterleiten",
- "defaultMessage": "Forward to",
- "missing": false,
- "obsolete": false
- },
- "forward_to_search_placeholder": {
- "translation": "Kontakte durchsuchen",
- "defaultMessage": "Search contacts",
- "description": "Contact search prompt when forwarding a message.",
- "missing": false,
- "obsolete": false
- },
- "label_new_password": {
- "translation": "Neues Kennwort",
- "defaultMessage": "New password",
- "description": "Label for entering a new password",
- "missing": false,
- "obsolete": false
- },
- "drafty_unknown": {
- "translation": "Nicht unterstützt",
- "defaultMessage": "Unsupported",
- "description": "Unsupported entity in drafty",
- "missing": false,
- "obsolete": false
- },
- "calls_incoming": {
- "translation": "Eingehender Anruf",
- "defaultMessage": "Incoming call",
- "description": "Incoming call label",
- "missing": false,
- "obsolete": false
- },
- "calls_outgoing": {
- "translation": "Ausgehender Anruf",
- "defaultMessage": "Outgoing call",
- "description": "Outgoing call label",
- "missing": false,
- "obsolete": false
- },
- "calls_you_label": {
- "translation": "Du",
- "defaultMessage": "You",
- "description": "Shown over the local video screen",
- "missing": false,
- "obsolete": false
- },
- "menu_item_video_call": {
- "translation": "Videoanruf",
- "defaultMessage": "Video call",
- "description": "Start video call",
- "missing": false,
- "obsolete": false
- },
- "already_in_call": {
- "translation": "Du redest schon!",
- "defaultMessage": "You already in an ongoing call!",
- "description": "Error message when the user tried to accept a new call without finishing pervious one",
- "missing": false,
- "obsolete": false
- },
- "call_cancelled": {
- "translation": "abgebrochen",
- "defaultMessage": "cancelled",
- "description": "Label for cancelled outgoing call",
- "missing": false,
- "obsolete": false
- },
- "call_missed": {
- "translation": "verpasster",
- "defaultMessage": "missed",
- "description": "Label for missed incoming call",
- "missing": false,
- "obsolete": false
- },
- "call_declined": {
- "translation": "abgelehnt",
- "defaultMessage": "declined",
- "description": "Label for declined call",
- "missing": false,
- "obsolete": false
- },
- "call_disconnected": {
- "translation": "getrennt",
- "defaultMessage": "disconnected",
- "description": "Label for disconnected call",
- "missing": false,
- "obsolete": false
- },
- "label_use_secure_connection": {
- "translation": "Verwenden Sie eine sichere Verbindung",
- "defaultMessage": "Use secure connection",
- "description": "Label for WS/WSS connection type in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "cred_confirmed_successfully": {
- "translation": "Erfolgreich bestätigt",
- "defaultMessage": "Confirmed successfully",
- "description": "Notification message that the credential was successfully validated.",
- "missing": false,
- "obsolete": false
- },
- "menu_item_edit": {
- "translation": "Bearbeiten",
- "defaultMessage": "Edit",
- "description": "Edit message",
- "missing": false,
- "obsolete": false
- },
- "editing_message": {
- "translation": "Bearbeitung",
- "defaultMessage": "Editing",
- "description": "Title over message editing preview",
- "missing": false,
- "obsolete": false
- },
- "message_edited_marker": {
- "translation": ", bearbeitet",
- "defaultMessage": ", edited",
- "description": "Marker indicating that the message was edited",
- "missing": false,
- "obsolete": false
- },
- "icon_title_add_image": {
- "translation": "Bild hinzufügen",
- "defaultMessage": "Add image",
- "description": "Icon tool tip for attaching an image",
- "missing": false,
- "obsolete": false
- },
- "icon_title_attach_file": {
- "translation": "Datei anhängen",
- "defaultMessage": "Attach file",
- "description": "Icon tool tip for attaching a file",
- "missing": false,
- "obsolete": false
- },
- "icon_title_delete": {
- "translation": "Aufnahme löschen",
- "defaultMessage": "Delete recording",
- "description": "Icon tool tip for deleting recorded audio",
- "missing": false,
- "obsolete": false
- },
- "icon_title_pause": {
- "translation": "Wiedergabe pausieren",
- "defaultMessage": "Pause playback",
- "description": "Icon tool tip for pausing audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_play": {
- "translation": "Aufnahme abspielen",
- "defaultMessage": "Play recording",
- "description": "Icon tool tip for starting audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_record_voice": {
- "translation": "Sprachnachricht aufnehmen",
- "defaultMessage": "Record voice message",
- "description": "Icon tool tip for recording a voice message",
- "missing": false,
- "obsolete": false
- },
- "icon_title_resume": {
- "translation": "Wiedergabe fortsetzen",
- "defaultMessage": "Resume playback",
- "description": "Icon tool tip for resuming audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_send": {
- "translation": "Nachricht senden",
- "defaultMessage": "Send message",
- "description": "Icon tool tip for sending a message",
- "missing": false,
- "obsolete": false
- },
- "drag_file": {
- "translation": "Datei hierher ziehen",
- "defaultMessage": "Drag file here",
- "description": "Prompt on the file drag-n-drop overlay banner",
- "missing": false,
- "obsolete": false
- },
- "drafty_video": {
- "translation": "Videoaufnahme",
- "defaultMessage": "Video recording",
- "description": "Comment for videos embedded in Drafty",
- "missing": false,
- "obsolete": false
- },
- "call_in_progress": {
- "translation": "in Arbeit",
- "defaultMessage": "in progress",
- "description": "Label for call in progress",
- "missing": false,
- "obsolete": false
- },
- "menu_item_audio_call": {
- "translation": "Anruf",
- "defaultMessage": "Call",
- "description": "Start audio call",
- "missing": false,
- "obsolete": false
- },
- "unrecognized_video_format": {
- "translation": "Das Format dieses Videos wird nicht erkannt",
- "defaultMessage": "Format of this video is not recognized",
- "description": "Error message when uploaded video is invalid",
- "missing": false,
- "obsolete": false
- },
- "loading_note": {
- "translation": "Laden...",
- "defaultMessage": "Loading...",
- "description": "Message shown when component is loading",
- "missing": false,
- "obsolete": false
- },
- "password_i_have_code": {
- "translation": "Ich habe Code",
- "defaultMessage": "I have code",
- "description": "Call to open field to enter password reset code",
- "missing": false,
- "obsolete": false
- },
- "label_reset_password_tel": {
- "translation": "SMS zum Zurücksetzen des Passworts senden",
- "defaultMessage": "Send SMS to reset password",
- "description": "Label for password reset field by phone",
- "missing": false,
- "obsolete": false
- },
- "mobile_phone_number": {
- "translation": "Mobiltelefonnummer",
- "defaultMessage": "Mobile phone number",
- "description": "Prompt for entering a mobile phone number",
- "missing": false,
- "obsolete": false
- },
- "password_reset_sms_sent": {
- "translation": "Eine SMS mit einem Sicherheitscode wurde gesendet.",
- "defaultMessage": "A text message with security code has been sent.",
- "description": "Notification that the SMS with password reset instructions has been sent",
- "missing": false,
- "obsolete": false
- },
- "mobile_number_required": {
- "translation": "Handynummer erforderlich",
- "defaultMessage": "Mobile phone number required",
- "description": "Error message",
- "missing": false,
- "obsolete": false
- },
- "current_email": {
- "translation": "Aktuelle E-Mail",
- "defaultMessage": "Current email",
- "description": "Label for old email",
- "missing": false,
- "obsolete": false
- },
- "current_phone": {
- "translation": "Aktuelle Telefonnummer",
- "defaultMessage": "Current phone number",
- "description": "Label for old phone",
- "missing": false,
- "obsolete": false
- },
- "new_email": {
- "translation": "Neue E-Mail",
- "defaultMessage": "New email",
- "description": "Prompt for entering a new email",
- "missing": false,
- "obsolete": false
- },
- "new_phone_number": {
- "translation": "Neue Telefonnummer",
- "defaultMessage": "New phone number",
- "description": "Prompt for entering a new telephone number",
- "missing": false,
- "obsolete": false
- },
- "change_email": {
- "translation": "E-Mail ändern",
- "defaultMessage": "Change email",
- "description": "Prompt to change current email",
- "missing": false,
- "obsolete": false
- },
- "change_phone": {
- "translation": "Telefonnummer ändern",
- "defaultMessage": "Change phone number",
- "description": "Prompt to change current phone number",
- "missing": false,
- "obsolete": false
- },
- "call_busy": {
- "translation": "besetzt",
- "defaultMessage": "busy",
- "description": "Label for busy call line",
- "missing": false,
- "obsolete": false
- },
- "failed_to_init_audio": {
- "translation": "Audioaufnahme konnte nicht initialisiert werden",
- "defaultMessage": "Failed to initialize audio recording",
- "description": "Error message when audio is not available",
- "missing": false,
- "obsolete": false
- },
- "pin_message": {
- "translation": "Anpinnen",
- "defaultMessage": "Pin",
- "description": "Menu item [Pin] for pinning message to the top.",
- "missing": false,
- "obsolete": false
- },
- "unpin_message": {
- "translation": "Lösen",
- "defaultMessage": "Unpin",
- "description": "Menu item [Unpin] for un-pinning the message.",
- "missing": false,
- "obsolete": false
- },
- "password_reset_success": {
- "translation": "Passwort erfolgreich zurückgesetzt",
- "defaultMessage": "Password reset successfully",
- "description": "Notification message that the password was successfully reset.",
- "missing": false,
- "obsolete": false
- },
- "scan_qr_code": {
- "translation": "QR-Code scannen",
- "defaultMessage": "Scan QR Code",
- "description": "Title for scanning QR code",
- "missing": false,
- "obsolete": false
- },
- "self_topic_comment": {
- "translation": "Notizen, Nachrichten, Links, Dateien für die Nachwelt gespeichert",
- "defaultMessage": "Notes, messages, links, files saved for posterity",
- "description": "Comment for self topic for UI",
- "missing": false,
- "obsolete": false
- },
- "self_topic_name": {
- "translation": "Gespeicherte Nachrichten",
- "defaultMessage": "Saved messages",
- "description": "Name of self topic for UI",
- "missing": false,
- "obsolete": false
- },
- "message_not_found": {
- "translation": "Nachricht nicht gefunden",
- "defaultMessage": "message not found",
- "description": "Pinned message is not found",
- "missing": false,
- "obsolete": false
- },
- "message_deleted": {
- "translation": "Nachricht gelöscht",
- "defaultMessage": "message deleted",
- "description": "Pinned message is deleted",
- "missing": false,
- "obsolete": false
- },
- "alias_editing_placeholder": {
- "translation": "Alias (optional)",
- "defaultMessage": "Alias (optional)",
- "description": "Placeholder for editing user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "label_alias": {
- "translation": "Alias:",
- "defaultMessage": "Alias:",
- "description": "Label for user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "alias_already_taken": {
- "translation": "(bereits vergeben)",
- "defaultMessage": "(already taken)",
- "description": "Error message for alias already taken",
- "missing": false,
- "obsolete": false
- },
- "alias_invalid": {
- "translation": "(ungültig)",
- "defaultMessage": "(invalid)",
- "description": "Error message for invalid alias",
- "missing": false,
- "obsolete": false
- },
- "label_alias_edit": {
- "translation": "Alias",
- "defaultMessage": "Alias",
- "description": "Label for editing user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "select_country": {
- "translation": "Land auswählen",
- "defaultMessage": "Select country",
- "description": "Placeholder for the country selector",
- "missing": false,
- "obsolete": false
- },
- "text_copied": {
- "translation": "In die Zwischenablage kopiert",
- "defaultMessage": "Copied to clipboard",
- "description": "Notification that text has been copied to clipboard",
- "missing": false,
- "obsolete": false
- },
- "color_schema_dark": {
- "translation": "Dunkel",
- "defaultMessage": "Dark",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "color_schema_light": {
- "translation": "Hell",
- "defaultMessage": "Light",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "color_schema_system": {
- "translation": "Systemstandard",
- "defaultMessage": "System default",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "label_color_schema": {
- "translation": "Thema:",
- "defaultMessage": "Theme:",
- "description": "Label for selecting color scheme (dark, light) in Settings",
- "missing": false,
- "obsolete": false
- },
- "label_text_size": {
- "translation": "Textgröße:",
- "defaultMessage": "Text size:",
- "description": "Label adjusting text size",
- "missing": false,
- "obsolete": false
- },
- "incognito_mode_description": {
- "translation": "Online-Status und Tipp-Indikatoren verbergen.",
- "defaultMessage": "Hide your online status and typing indicators.",
- "description": "Description for incognito mode toggle",
- "missing": false,
- "obsolete": false
- },
- "send_command": {
- "translation": "Senden mit {key}",
- "defaultMessage": "Send on {key}",
- "description": "Config option to send message on hitting CTRL(or Cmd)-Enter",
- "missing": false,
- "obsolete": false
- },
- "send_command_explained": {
- "translation": "Drücken Sie Enter für eine neue Zeile",
- "defaultMessage": "Press Enter for new line",
- "description": "Explanation how to enter newline when [Send on CTRL-Enter] is enabled",
- "missing": false,
- "obsolete": false
- },
- "send_plain": {
- "translation": "Senden mit Enter",
- "defaultMessage": "Send on Enter",
- "description": "Config option to send message on hitting Enter",
- "missing": false,
- "obsolete": false
- },
- "send_plain_explained": {
- "translation": "Drücken Sie Shift + Enter für eine neue Zeile",
- "defaultMessage": "Press Shift + Enter for new line",
- "description": "Explanation how to enter newline when [Send on Enter] is enabled",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_general": {
- "translation": "Allgemein",
- "defaultMessage": "General",
- "description": "Sidepanel title for AccGeneralView.",
- "missing": false,
- "obsolete": false
- },
- "label_keyboard": {
- "translation": "Tastatur:",
- "defaultMessage": "Keyboard:",
- "description": "Label for send on enter settings",
- "missing": false,
- "obsolete": false
- },
- "pin_chat": {
- "translation": "Anheften",
- "defaultMessage": "Pin",
- "description": "Menu item [Pin] for pinning chat to the top.",
- "missing": false,
- "obsolete": false
- },
- "unpin_chat": {
- "translation": "Loslösen",
- "defaultMessage": "Unpin",
- "description": "Menu item [Unpin] for un-pinning the chat.",
- "missing": false,
- "obsolete": false
- },
- "subscriber_count": {
- "translation": "{count, plural, one {{count, number} Abonnent} other {{count, number} Abonnenten}}",
- "defaultMessage": "{count, plural, one {{count, number} subscriber} two {{count, number} subscribers} three {{count, number} subscribers} few {{count, number} subscribers} many {{count, number} subscribers} other {{count, number} subscribers}}",
- "description": "Count of channel subscribers",
- "missing": false,
- "obsolete": false
- },
- "label_member_count": {
- "translation": "Mitglieder:",
- "defaultMessage": "Members:",
- "description": "Label for the count of group topic members",
- "missing": false,
- "obsolete": false
- },
- "label_subscriber_count": {
- "translation": "Abonnenten:",
- "defaultMessage": "Subscribers:",
- "description": "Label for the count of channel subscribers",
- "missing": false,
- "obsolete": false
- },
- "member_count": {
- "translation": "{count, plural, one {{count, number} Mitglied} other {{count, number} Mitglieder}}",
- "defaultMessage": "{count, plural, one {{count, number} member} two {{count, number} members} three {{count, number} members} few {{count, number} members} many {{count, number} members} other {{count, number} members}}",
- "description": "Count of group topic members",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_image": {
- "translation": "Bild",
- "defaultMessage": "image",
- "description": "Tab title Image",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_pattern": {
- "translation": "Muster",
- "defaultMessage": "pattern",
- "description": "Tab title Pattern",
- "missing": false,
- "obsolete": false
- },
- "button_restore": {
- "translation": "Standard wiederherstellen",
- "defaultMessage": "Restore default",
- "description": "Reset setting to default",
- "missing": false,
- "obsolete": false
- },
- "wallpapers": {
- "translation": "Hintergrundbilder",
- "defaultMessage": "Wallpapers",
- "description": "Link or title for working with wallpapers.",
- "missing": false,
- "obsolete": false
- },
- "label_blur_wallpaper": {
- "translation": "Unschärfe:",
- "defaultMessage": "Blur:",
- "description": "Label adjusting blur amount",
- "missing": false,
- "obsolete": false
- },
- "label_scan_id": {
- "translation": "Meine ID scannen:",
- "defaultMessage": "Scan my ID:",
- "description": "Label [Scan my ID:]",
- "missing": false,
- "obsolete": false
- },
- "unknown_name": {
- "translation": "Unbekannt",
- "defaultMessage": "Unknown",
- "description": "Name to show when the name is missing",
- "missing": false,
- "obsolete": false
- },
- "save_action": {
- "translation": "Speichern",
- "defaultMessage": "Save",
- "description": "Label for Save button",
- "missing": false,
- "obsolete": false
- },
- "cannot_parse_vcard": {
- "translation": "vCard-Datei konnte nicht verarbeitet werden.",
- "defaultMessage": "Cannot parse vCard file.",
- "description": "Error message when vCard file cannot be parsed",
- "missing": false,
- "obsolete": false
- },
- "chat_now": {
- "translation": "Chatten",
- "defaultMessage": "Chat",
- "description": "Label for message button in contact card",
- "missing": false,
- "obsolete": false
- },
- "find_user": {
- "translation": "Suchen",
- "defaultMessage": "Find",
- "description": "Label for search button in contact card",
- "missing": false,
- "obsolete": false
- },
- "label_contact_card": {
- "translation": "Kontaktkarte",
- "defaultMessage": "Contact Card",
- "description": "Label for contact card preview",
- "missing": false,
- "obsolete": false
- },
- "label_contacts": {
- "translation": "Kontakte",
- "defaultMessage": "Contacts",
- "description": "Label for contact information",
- "missing": false,
- "obsolete": false
- },
- "add_members_prompt": {
- "translation": "Mitglieder hinzufügen",
- "defaultMessage": "add members",
- "description": "Placeholder in group members input field",
- "missing": false,
- "obsolete": false
- }
-}
diff --git a/src/i18n/en.json b/src/i18n/en.json
deleted file mode 100644
index cec3af1a..00000000
--- a/src/i18n/en.json
+++ /dev/null
@@ -1,1966 +0,0 @@
-{
- "action_block_contact": {
- "translation": "Block Contact",
- "defaultMessage": "Block Contact",
- "description": "Flat button [Block Contact]",
- "missing": false,
- "obsolete": false
- },
- "action_cancel": {
- "translation": "cancel",
- "defaultMessage": "cancel",
- "description": "Call to action [cancel]",
- "missing": false,
- "obsolete": false
- },
- "action_clear_messages": {
- "translation": "Clear Messages",
- "defaultMessage": "Clear Messages",
- "description": "Flat button [Clear Messages] (soft-delete messages)",
- "missing": false,
- "obsolete": false
- },
- "action_delete_messages": {
- "translation": "Clear Messages for All",
- "defaultMessage": "Clear Messages for All",
- "description": "Flat button [Clear for All] (hard-delete all messages)",
- "missing": false,
- "obsolete": false
- },
- "action_leave_chat": {
- "translation": "Leave Conversation",
- "defaultMessage": "Leave Conversation",
- "description": "Flat button [Leave Conversation]",
- "missing": false,
- "obsolete": false
- },
- "action_report_chat": {
- "translation": "Report Conversation",
- "defaultMessage": "Report Conversation",
- "description": "Flat button [Report Group]",
- "missing": false,
- "obsolete": false
- },
- "archived_contacts": {
- "translation": "Archived contacts ({count})",
- "defaultMessage": "Archived contacts ({count})",
- "description": "Label for archived chats",
- "missing": false,
- "obsolete": false
- },
- "badge_danger": {
- "translation": "Untrustworthy",
- "defaultMessage": "Untrustworthy",
- "description": "Suspicious or untrustworthy account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_owner": {
- "translation": "owner",
- "defaultMessage": "owner",
- "description": "Badge for indicating the owner",
- "missing": false,
- "obsolete": false
- },
- "badge_staff": {
- "translation": "Staff-managed",
- "defaultMessage": "Staff-managed",
- "description": "Explanation of a staff-managed account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_verified": {
- "translation": "Verified/official",
- "defaultMessage": "Verified/official",
- "description": "Explanation of a verified account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_you": {
- "translation": "you",
- "defaultMessage": "you",
- "description": "Badge for indicating the current user",
- "missing": false,
- "obsolete": false
- },
- "block_contact_warning": {
- "translation": "Are you sure you want to block this contact?",
- "defaultMessage": "Are you sure you want to block this contact?",
- "description": "Alert dialog warning when blocking a contact.",
- "missing": false,
- "obsolete": false
- },
- "blocked_contacts_link": {
- "translation": "Blocked contacts ({count})",
- "defaultMessage": "Blocked contacts ({count})",
- "description": "Blocked contacts link",
- "missing": false,
- "obsolete": false
- },
- "button_add_members": {
- "translation": "Add members",
- "defaultMessage": "Add members",
- "description": "Flat button [Add members] (to topic)",
- "missing": false,
- "obsolete": false
- },
- "button_cancel": {
- "translation": "Cancel",
- "defaultMessage": "Cancel",
- "description": "Button [Cancel]",
- "missing": false,
- "obsolete": false
- },
- "button_confirm": {
- "translation": "Confirm",
- "defaultMessage": "Confirm",
- "description": "Button [Confirm]",
- "missing": false,
- "obsolete": false
- },
- "button_create": {
- "translation": "Create",
- "defaultMessage": "Create",
- "description": "Button [Create]",
- "missing": false,
- "obsolete": false
- },
- "button_delete_account": {
- "translation": "Delete account",
- "defaultMessage": "Delete account",
- "description": "Button [Delete account]",
- "missing": false,
- "obsolete": false
- },
- "button_edit": {
- "translation": "Edit",
- "defaultMessage": "Edit",
- "description": "Call to action [Edit]",
- "missing": false,
- "obsolete": false
- },
- "button_logout": {
- "translation": "Logout",
- "defaultMessage": "Logout",
- "description": "Button [Logout]",
- "missing": false,
- "obsolete": false
- },
- "button_ok": {
- "translation": "OK",
- "defaultMessage": "OK",
- "description": "Button [OK]",
- "missing": false,
- "obsolete": false
- },
- "button_reset": {
- "translation": "Reset",
- "defaultMessage": "Reset",
- "description": "Button [Reset]",
- "missing": false,
- "obsolete": false
- },
- "button_send_request": {
- "translation": "Send request",
- "defaultMessage": "Send request",
- "description": "Button [Send request]",
- "missing": false,
- "obsolete": false
- },
- "button_sign_in": {
- "translation": "Sign in",
- "defaultMessage": "Sign in",
- "description": "Button [Sign In]",
- "missing": false,
- "obsolete": false
- },
- "button_sign_up": {
- "translation": "Sign up",
- "defaultMessage": "Sign up",
- "description": "Create account button [Sign Up]",
- "missing": false,
- "obsolete": false
- },
- "button_subscribe": {
- "translation": "Subscribe",
- "defaultMessage": "Subscribe",
- "description": "Button [Subscribe]",
- "missing": false,
- "obsolete": false
- },
- "button_update": {
- "translation": "Update",
- "defaultMessage": "Update",
- "description": "Button [Update]",
- "missing": false,
- "obsolete": false
- },
- "cannot_initiate_file_upload": {
- "translation": "Cannot initiate file upload.",
- "defaultMessage": "Cannot initiate file upload.",
- "description": "Generic error messagewhen attachment fails",
- "missing": false,
- "obsolete": false
- },
- "channel": {
- "translation": "channel",
- "defaultMessage": "channel",
- "description": "Subtitle shown for channels in MessagesView instead of last seen",
- "missing": false,
- "obsolete": false
- },
- "channel_prompt": {
- "translation": "This is a channel",
- "defaultMessage": "This is a channel",
- "description": "Checkbox label when creating a channel",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation": {
- "translation": "You are invited to start a new chat. What would you like to do?",
- "defaultMessage": "You are invited to start a new chat. What would you like to do?",
- "description": "New chat invitation message: [Accept] [Ignore] [Block].",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_accept": {
- "translation": "Accept",
- "defaultMessage": "Accept",
- "description": "Action [Accept] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_block": {
- "translation": "Block",
- "defaultMessage": "Block",
- "description": "Action [Block] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_ignore": {
- "translation": "Ignore",
- "defaultMessage": "Ignore",
- "description": "Action [Ignore] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "clear_messages_warning": {
- "translation": "Are you sure you want to clear all messages? It cannot be undone.",
- "defaultMessage": "Are you sure you want to clear all messages? It cannot be undone.",
- "description": "Alert dialog warning when deleting all messages.",
- "missing": false,
- "obsolete": false
- },
- "code_doesnot_match": {
- "translation": "Code does not match",
- "defaultMessage": "Code does not match",
- "description": "Error message when the credential validation code is incorrect.",
- "missing": false,
- "obsolete": false
- },
- "contacts_not_found": {
- "translation": "You have no chats\n¯∖_(ツ)_/¯",
- "defaultMessage": "You have no chats\\n¯∖_(ツ)_/¯",
- "description": "HTML message shown in ContactList when no contacts are found",
- "missing": false,
- "obsolete": false
- },
- "contacts_not_found_short": {
- "translation": "No contacts match ''{query}''",
- "defaultMessage": "No contacts match ''{query}''",
- "description": "Shown in ContactsView when search returned no results",
- "missing": false,
- "obsolete": false
- },
- "credential_email_prompt": {
- "translation": "Your registration email",
- "defaultMessage": "Your registration email",
- "description": "Placeholder for entering email",
- "missing": false,
- "obsolete": false
- },
- "delete_account": {
- "translation": "Delete account",
- "defaultMessage": "Delete account",
- "description": "Title for delete account warning",
- "missing": false,
- "obsolete": false
- },
- "delete_account_warning": {
- "translation": "Are you sure you want to delete your account? It cannot be undone.",
- "defaultMessage": "Are you sure you want to delete your account? It cannot be undone.",
- "description": "Warning message when deleting an account",
- "missing": false,
- "obsolete": false
- },
- "delete_messages_warning": {
- "translation": "Are you sure you want to delete all messages for everyone? It cannot be undone.",
- "defaultMessage": "Are you sure you want to delete all messages for everyone? It cannot be undone.",
- "description": "Alert dialog warning when hard-deleting all messages.",
- "missing": false,
- "obsolete": false
- },
- "download_action": {
- "translation": "download",
- "defaultMessage": "download",
- "description": "Call to action [download]",
- "missing": false,
- "obsolete": false
- },
- "drafty_attachment": {
- "translation": "Attachment",
- "defaultMessage": "Attachment",
- "description": "Comment for attachment in Drafty",
- "missing": false,
- "obsolete": false
- },
- "drafty_form": {
- "translation": "Form:",
- "defaultMessage": "Form:",
- "description": "Comment for form in Drafty",
- "missing": false,
- "obsolete": false
- },
- "drafty_image": {
- "translation": "Picture",
- "defaultMessage": "Picture",
- "description": "Comment for embedded images in Drafty",
- "missing": false,
- "obsolete": false
- },
- "email_dative": {
- "translation": "email",
- "defaultMessage": "email",
- "description": "Dative case of 'email', i.e. 'email' in 'by email'",
- "missing": false,
- "obsolete": false
- },
- "email_prompt": {
- "translation": "Email, e.g. jdoe@example.com",
- "defaultMessage": "Email, e.g. jdoe@example.com",
- "description": "Input placeholder for email entry",
- "missing": false,
- "obsolete": false
- },
- "enable_peers_messaging": {
- "translation": "Enable",
- "defaultMessage": "Enable",
- "description": "Call to action to enable peer's messaging",
- "missing": false,
- "obsolete": false
- },
- "enter_confirmation_code_prompt": {
- "translation": "Confirmation code",
- "defaultMessage": "Confirmation code",
- "description": "Request to enter confirmation code",
- "missing": false,
- "obsolete": false
- },
- "error_invalid_id": {
- "translation": "Invalid ID",
- "defaultMessage": "Invalid ID",
- "description": "Error message",
- "missing": false,
- "obsolete": false
- },
- "file_attachment_too_large": {
- "translation": "The file size {size} exceeds the {limit} limit.",
- "defaultMessage": "The file size {size} exceeds the {limit} limit.",
- "description": "Error message when attachment is too large",
- "missing": false,
- "obsolete": false
- },
- "forgot_password_link": {
- "translation": "Forgot password?",
- "defaultMessage": "Forgot password?",
- "description": "Link to Reset password form",
- "missing": false,
- "obsolete": false
- },
- "full_name_prompt": {
- "translation": "Full name, e.g. John Doe",
- "defaultMessage": "Full name, e.g. John Doe",
- "description": "Input placeholder for person's full name",
- "missing": false,
- "obsolete": false
- },
- "granted_permissions": {
- "translation": "Granted",
- "defaultMessage": "Granted",
- "description": "Title for permissions",
- "missing": false,
- "obsolete": false
- },
- "group_has_no_members": {
- "translation": "No members",
- "defaultMessage": "No members",
- "description": "Shown in place of group members",
- "missing": false,
- "obsolete": false
- },
- "group_user_id_prompt": {
- "translation": "Group or User ID",
- "defaultMessage": "Group or User ID",
- "description": "Prompt for entering user or group ID",
- "missing": false,
- "obsolete": false
- },
- "image_caption_prompt": {
- "translation": "Image caption",
- "defaultMessage": "Image caption",
- "description": "Prompt in SendMessage for attached image",
- "missing": false,
- "obsolete": false
- },
- "invalid_content": {
- "translation": "invalid content",
- "defaultMessage": "invalid content",
- "description": "Shown when the message is unreadable",
- "missing": false,
- "obsolete": false
- },
- "invalid_security_token": {
- "translation": "Invalid security token",
- "defaultMessage": "Invalid security token",
- "description": "Error message when resetting password.",
- "missing": false,
- "obsolete": false
- },
- "label_client": {
- "translation": "Client:",
- "defaultMessage": "Client:",
- "description": "Label for a client version",
- "missing": false,
- "obsolete": false
- },
- "label_content_type": {
- "translation": "Content type:",
- "defaultMessage": "Content type:",
- "description": "Label for file content type (mime)",
- "missing": false,
- "obsolete": false
- },
- "label_default_access_mode": {
- "translation": "Default access mode:",
- "defaultMessage": "Default access mode:",
- "description": "Label for default access mode",
- "missing": false,
- "obsolete": false
- },
- "label_file_name": {
- "translation": "File name:",
- "defaultMessage": "File name:",
- "description": "Label for a file name",
- "missing": false,
- "obsolete": false
- },
- "label_group_members": {
- "translation": "Group members:",
- "defaultMessage": "Group members:",
- "description": "Section title or label",
- "missing": false,
- "obsolete": false
- },
- "label_incognito_mode": {
- "translation": "Incognito mode:",
- "defaultMessage": "Incognito mode:",
- "description": "Label for incognito mode toggle",
- "missing": false,
- "obsolete": false
- },
- "label_message_sound": {
- "translation": "Message sound:",
- "defaultMessage": "Message sound:",
- "description": "Label for message sounds toggle",
- "missing": false,
- "obsolete": false
- },
- "label_muting_topic": {
- "translation": "Muted:",
- "defaultMessage": "Muted:",
- "description": "Label for Muting/unmuting the topic",
- "missing": false,
- "obsolete": false
- },
- "label_other_user": {
- "translation": "Other",
- "defaultMessage": "Other",
- "description": "Label for the other user when the user is unnamed",
- "missing": false,
- "obsolete": false
- },
- "label_password": {
- "translation": "Password",
- "defaultMessage": "Password",
- "description": "Label for password editing",
- "missing": false,
- "obsolete": false
- },
- "label_permissions": {
- "translation": "Permissions:",
- "defaultMessage": "Permissions:",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "label_private": {
- "translation": "Private comment",
- "defaultMessage": "Private comment",
- "description": "Label for editing 'private'",
- "missing": false,
- "obsolete": false
- },
- "label_push_notifications": {
- "translation": "Notification alerts:",
- "defaultMessage": "Notification alerts:",
- "description": "Label for push notifications switch",
- "missing": false,
- "obsolete": false
- },
- "label_push_notifications_disabled": {
- "translation": "Notification alerts (requires HTTPS):",
- "defaultMessage": "Notification alerts (requires HTTPS):",
- "description": "Label for push notifications switch",
- "missing": false,
- "obsolete": false
- },
- "label_reset_password": {
- "translation": "Send a password reset email:",
- "defaultMessage": "Send a password reset email",
- "description": "Label for password reset field",
- "missing": false,
- "obsolete": false
- },
- "label_sdk": {
- "translation": "SDK:",
- "defaultMessage": "SDK:",
- "missing": false,
- "obsolete": false
- },
- "label_server": {
- "translation": "Server:",
- "defaultMessage": "Server:",
- "description": "Label for a server version",
- "missing": false,
- "obsolete": false
- },
- "label_server_address": {
- "translation": "Server address:",
- "defaultMessage": "Server address:",
- "missing": false,
- "obsolete": false
- },
- "label_server_to_use": {
- "translation": "Server to use:",
- "defaultMessage": "Server to use:",
- "description": "Label for server selector in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "label_size": {
- "translation": "Size:",
- "defaultMessage": "Size:",
- "description": "Label for file size",
- "missing": false,
- "obsolete": false
- },
- "label_topic_name": {
- "translation": "Name",
- "defaultMessage": "Name",
- "description": "Label for editing topic name",
- "missing": false,
- "obsolete": false
- },
- "label_user_contacts": {
- "translation": "Contacts",
- "defaultMessage": "Contacts",
- "description": "Label for user contacts",
- "missing": false,
- "obsolete": false
- },
- "label_user_id": {
- "translation": "ID:",
- "defaultMessage": "ID:",
- "description": "Label for user address (ID)",
- "missing": false,
- "obsolete": false
- },
- "label_wire_transport": {
- "translation": "Wire transport:",
- "defaultMessage": "Wire transport:",
- "description": "Label for wire transport selection in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "label_you": {
- "translation": "You:",
- "defaultMessage": "You:",
- "description": "Label for the current user",
- "missing": false,
- "obsolete": false
- },
- "label_your_name": {
- "translation": "Your name",
- "defaultMessage": "Your name",
- "description": "Label for full name editing",
- "missing": false,
- "obsolete": false
- },
- "label_your_permissions": {
- "translation": "Your permissions:",
- "defaultMessage": "Your permissions:",
- "description": "Label for current user permissions",
- "missing": false,
- "obsolete": false
- },
- "last_seen_timestamp": {
- "translation": "Last seen: {timestamp}",
- "defaultMessage": "Last seen: {timestamp}",
- "description": "Label for the timestamp of when the user or topic was last online",
- "missing": false,
- "obsolete": false
- },
- "leave_chat_warning": {
- "translation": "Are you sure you want to leave this conversation?",
- "defaultMessage": "Are you sure you want to leave this conversation?",
- "description": "Alert dialog warning when unsubscribing from a chat.",
- "missing": false,
- "obsolete": false
- },
- "link_contact_us": {
- "translation": "Contact Us",
- "defaultMessage": "Contact Us",
- "description": "Ancor text for contacting us by email",
- "missing": false,
- "obsolete": false
- },
- "link_privacy_policy": {
- "translation": "Privacy Policy",
- "defaultMessage": "Privacy Policy",
- "description": "Ancor text for privacy policy link",
- "missing": false,
- "obsolete": false
- },
- "link_terms_of_service": {
- "translation": "Terms of Service",
- "defaultMessage": "Terms of Service",
- "description": "Ancor text for terms of service link",
- "missing": false,
- "obsolete": false
- },
- "login_prompt": {
- "translation": "Login",
- "defaultMessage": "Login",
- "description": "Placeholer for username/login",
- "missing": false,
- "obsolete": false
- },
- "menu_item_archive_topic": {
- "translation": "Archive",
- "defaultMessage": "Archive",
- "description": "Move topic from the list of active chats to archive",
- "missing": false,
- "obsolete": false
- },
- "menu_item_block": {
- "translation": "Block",
- "defaultMessage": "Block",
- "description": "Block topic or user",
- "missing": false,
- "obsolete": false
- },
- "menu_item_clear_messages": {
- "translation": "Clear messages",
- "defaultMessage": "Clear messages",
- "description": "Delete all messages",
- "missing": false,
- "obsolete": false
- },
- "menu_item_clear_messages_for_all": {
- "translation": "Clear for All",
- "defaultMessage": "Clear for All",
- "description": "Delete all message(s) for all members",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete": {
- "translation": "Delete",
- "defaultMessage": "Delete",
- "description": "Delete selected messages",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete_for_all": {
- "translation": "Delete for All",
- "defaultMessage": "Delete for All",
- "description": "Delete selected message(s) for all members",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete_topic": {
- "translation": "Delete",
- "defaultMessage": "Delete",
- "description": "Delete entire topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_edit_permissions": {
- "translation": "Edit permissions",
- "defaultMessage": "Edit permissions",
- "description": "Menu item [Edit permissions]",
- "missing": false,
- "obsolete": false
- },
- "menu_item_info": {
- "translation": "Info",
- "defaultMessage": "Info",
- "description": "Show extended topic information",
- "missing": false,
- "obsolete": false
- },
- "menu_item_member_delete": {
- "translation": "Remove",
- "defaultMessage": "Remove",
- "description": "Remove user from topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_mute": {
- "translation": "Mute",
- "defaultMessage": "Mute",
- "description": "Turn off notifications",
- "missing": false,
- "obsolete": false
- },
- "menu_item_reply": {
- "translation": "Reply",
- "defaultMessage": "Reply",
- "description": "Reply to message",
- "missing": false,
- "obsolete": false
- },
- "menu_item_restore_topic": {
- "translation": "Restore",
- "defaultMessage": "Restore",
- "description": "Restore topic from archive",
- "missing": false,
- "obsolete": false
- },
- "menu_item_send_retry": {
- "translation": "Retry",
- "defaultMessage": "Retry",
- "description": "Retry sending message",
- "missing": false,
- "obsolete": false
- },
- "menu_item_unblock": {
- "translation": "Unblock",
- "defaultMessage": "Unblock",
- "description": "Unblock topic or user",
- "missing": false,
- "obsolete": false
- },
- "menu_item_unmute": {
- "translation": "Unmute",
- "defaultMessage": "Unmute",
- "description": "Turn notifications on",
- "missing": false,
- "obsolete": false
- },
- "message_sending": {
- "translation": "sending...",
- "defaultMessage": "sending...",
- "description": "Message being sent, in place of time stamp",
- "missing": false,
- "obsolete": false
- },
- "message_sending_failed": {
- "translation": "failed",
- "defaultMessage": "failed",
- "description": "Failed to send message, in place of time stamp",
- "missing": false,
- "obsolete": false
- },
- "messages_not_readable": {
- "translation": "no access to messages",
- "defaultMessage": "no access to messages",
- "description": "Message shown in topic without the read access",
- "missing": false,
- "obsolete": false
- },
- "messaging_disabled_prompt": {
- "translation": "Messaging disabled",
- "defaultMessage": "Messaging disabled",
- "description": "Prompt in SendMessage in read-only topic",
- "missing": false,
- "obsolete": false
- },
- "more_online_members": {
- "translation": "+{overflow} more",
- "defaultMessage": "+{overflow} more",
- "description": "Shown in MessagesView title bar when the number of online subscribers exceeds MAX_ONLINE_IN_TOPIC",
- "missing": false,
- "obsolete": false
- },
- "new_message_prompt": {
- "translation": "New message",
- "defaultMessage": "New message",
- "description": "Prompt in send message field",
- "missing": false,
- "obsolete": false
- },
- "new_password_placeholder": {
- "translation": "Enter new password",
- "defaultMessage": "Enter new password",
- "description": "Placeholder for entering new password",
- "missing": false,
- "obsolete": false
- },
- "no_connection": {
- "translation": "No connection",
- "defaultMessage": "No connection",
- "description": "Warning that the user is offline.",
- "missing": false,
- "obsolete": false
- },
- "no_contacts": {
- "translation": "You have no contacts :-(",
- "defaultMessage": "You have no contacts :-(",
- "description": "Shown in ContactsView when the user has no contacts",
- "missing": false,
- "obsolete": false
- },
- "numeric_confirmation_code_prompt": {
- "translation": "Numbers only",
- "defaultMessage": "Numbers only",
- "description": "Prompt for numeric conformation code",
- "missing": false,
- "obsolete": false
- },
- "online_now": {
- "translation": "online now",
- "defaultMessage": "online now",
- "description": "Indicator that the user or topic is currently online",
- "missing": false,
- "obsolete": false
- },
- "password_prompt": {
- "translation": "Password",
- "defaultMessage": "Password",
- "description": "Placeholder/prompt for entering password",
- "missing": false,
- "obsolete": false
- },
- "password_unchanged_prompt": {
- "translation": "Unchanged",
- "defaultMessage": "Unchanged",
- "description": "Message in editor while password is unchanged",
- "missing": false,
- "obsolete": false
- },
- "peers_messaging_disabled": {
- "translation": "Peer's messaging is disabled.",
- "defaultMessage": "Peer's messaging is disabled.",
- "description": "Shown when the p2p peer's messaging is disabled",
- "missing": false,
- "obsolete": false
- },
- "permission_admin": {
- "translation": "Approve ({val})",
- "defaultMessage": "Approve ({val})",
- "description": "Name of A permission",
- "missing": false,
- "obsolete": false
- },
- "permission_delete": {
- "translation": "Delete ({val})",
- "defaultMessage": "Delete ({val})",
- "description": "Name of D permission",
- "missing": false,
- "obsolete": false
- },
- "permission_join": {
- "translation": "Join ({val})",
- "defaultMessage": "Join ({val})",
- "description": "Name of J permission",
- "missing": false,
- "obsolete": false
- },
- "permission_owner": {
- "translation": "Owner ({val})",
- "defaultMessage": "Owner ({val})",
- "description": "Name of O permission",
- "missing": false,
- "obsolete": false
- },
- "permission_pres": {
- "translation": "Get notified ({val})",
- "defaultMessage": "Get notified ({val})",
- "description": "Name of P permission",
- "missing": false,
- "obsolete": false
- },
- "permission_read": {
- "translation": "Read ({val})",
- "defaultMessage": "Read ({val})",
- "description": "Name of R permission",
- "missing": false,
- "obsolete": false
- },
- "permission_share": {
- "translation": "Share ({val})",
- "defaultMessage": "Share ({val})",
- "description": "Name of S permission",
- "missing": false,
- "obsolete": false
- },
- "permission_write": {
- "translation": "Write ({val})",
- "defaultMessage": "Write ({val})",
- "description": "Name of W permission",
- "missing": false,
- "obsolete": false
- },
- "phone_dative": {
- "translation": "phone",
- "defaultMessage": "phone",
- "description": "Dative case of 'phone', i.e. 'phone' in 'by phone'",
- "missing": false,
- "obsolete": false
- },
- "private_editing_placeholder": {
- "translation": "Visible to you only",
- "defaultMessage": "Visible to you only",
- "description": "Placeholder for editing 'private'",
- "missing": false,
- "obsolete": false
- },
- "push_init_failed": {
- "translation": "Failed to initialize push notifications",
- "defaultMessage": "Failed to initialize push notifications",
- "description": "Error message when push notifications have failed to initialize.",
- "missing": false,
- "obsolete": false
- },
- "reconnect_countdown": {
- "translation": "Disconnected. Reconnecting in {seconds}…",
- "defaultMessage": "Disconnected. Reconnecting in {seconds}…",
- "description": "Message shown when an app update is available.",
- "missing": false,
- "obsolete": false
- },
- "reconnect_now": {
- "translation": "Try now",
- "defaultMessage": "Try now",
- "description": "Prompt for reconnecting now",
- "missing": false,
- "obsolete": false
- },
- "reload_update": {
- "translation": "Reload",
- "defaultMessage": "Reload",
- "description": "Call to action to reload application when update is available.",
- "missing": false,
- "obsolete": false
- },
- "report_chat_warning": {
- "translation": "Are you sure you want to block and report this conversation?",
- "defaultMessage": "Are you sure you want to block and report this conversation?",
- "description": "Alert dialog warning when reporting a conversation for abuse",
- "missing": false,
- "obsolete": false
- },
- "requested_permissions": {
- "translation": "Requested",
- "defaultMessage": "Requested",
- "description": "Title for permissions",
- "missing": false,
- "obsolete": false
- },
- "save_attachment": {
- "translation": "save",
- "defaultMessage": "save",
- "description": "Call to save an attachment",
- "missing": false,
- "obsolete": false
- },
- "search_for_contacts": {
- "translation": "Use search to find contacts",
- "defaultMessage": "Use search to find contacts",
- "description": "Text shown in contacts view when user entered no search query.",
- "missing": false,
- "obsolete": false
- },
- "search_no_results": {
- "translation": "Search returned no results",
- "defaultMessage": "Search returned no results",
- "description": "Text shown in contacts view when query returned no results.",
- "missing": false,
- "obsolete": false
- },
- "search_placeholder": {
- "translation": "List like email:alice@example.com, tel:17025550003...",
- "defaultMessage": "List like alice@example.com, +17025550003...",
- "description": "Placeholder in contacts search field",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_notifications": {
- "translation": "Notifications",
- "defaultMessage": "Notifications",
- "description": "Sidepanel title for AccNotificationsView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_support": {
- "translation": "Support",
- "defaultMessage": "Support",
- "description": "Sidepanel title for AccSupportView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_account_settings": {
- "translation": "Account Settings",
- "defaultMessage": "Account Settings",
- "description": "Sidepanel title for AccountSettingsView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_archive": {
- "translation": "Archived Chats",
- "defaultMessage": "Archived Chats",
- "description": "Sidepanel title for ContactsView-Archive.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_blocked": {
- "translation": "Blocked Chats",
- "defaultMessage": "Blocked Chats",
- "description": "Sidepanel title for ContactsView-Blocked.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_cred": {
- "translation": "Confirm Credentials",
- "defaultMessage": "Confirm Credentials",
- "description": "Sidepanel title for ValidationView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_login": {
- "translation": "Sign In",
- "defaultMessage": "Sign In",
- "description": "Sidepanel title for LoginView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_newtpk": {
- "translation": "Start New Chat",
- "defaultMessage": "Start New Chat",
- "description": "Sidepanel title for NewTopicView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_register": {
- "translation": "Create Account",
- "defaultMessage": "Create Account",
- "description": "Sidepanel title for CreateAccountView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_reset": {
- "translation": "Reset Password",
- "defaultMessage": "Reset Password",
- "description": "Sidepanel title for PasswordResetView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_settings": {
- "translation": "Settings",
- "defaultMessage": "Settings",
- "description": "Sidepanel title for SettingsView.",
- "missing": false,
- "obsolete": false
- },
- "stay_logged_in": {
- "translation": "Stay logged in",
- "defaultMessage": "Stay logged in",
- "description": "Label for a checkbox",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_find_user": {
- "translation": "find",
- "defaultMessage": "find",
- "description": "Tab title Find",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_group_by_id": {
- "translation": "by id",
- "defaultMessage": "by id",
- "description": "Tab title Find topic by ID",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_new_group": {
- "translation": "new group",
- "defaultMessage": "new group",
- "description": "Tab title New Group",
- "missing": false,
- "obsolete": false
- },
- "tags_editor_no_tags": {
- "translation": "Add some tags",
- "defaultMessage": "Add some tags",
- "description": "Tag editor prompt when no tags are found.",
- "missing": false,
- "obsolete": false
- },
- "tags_not_found": {
- "translation": "No tags defined. Add some.",
- "defaultMessage": "No tags defined. Add some.",
- "description": "",
- "missing": false,
- "obsolete": false
- },
- "title_all_contacts": {
- "translation": "All Contacts",
- "defaultMessage": "All Contacts",
- "description": "Section title [All Contacts]",
- "missing": false,
- "obsolete": false
- },
- "title_group_members": {
- "translation": "Group Members",
- "defaultMessage": "Group Members",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "title_manage_tags": {
- "translation": "Manage",
- "defaultMessage": "Manage",
- "description": "Section title for the list of tags",
- "missing": false,
- "obsolete": false
- },
- "title_not_found": {
- "translation": "Not found",
- "defaultMessage": "Not found",
- "description": "Title shown when topic is not found",
- "missing": false,
- "obsolete": false
- },
- "title_permissions": {
- "translation": "Permissions",
- "defaultMessage": "Permissions",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "title_tag_manager": {
- "translation": "Tags (search & discovery)",
- "defaultMessage": "Tags (search & discovery)",
- "description": "Section title for TagManager",
- "missing": false,
- "obsolete": false
- },
- "topic_block_warning": {
- "translation": "Are you sure you want to block this conversation?",
- "defaultMessage": "Are you sure you want to block this conversation?",
- "description": "Alert warning when blocking a topic.",
- "missing": false,
- "obsolete": false
- },
- "topic_delete_warning": {
- "translation": "Are you sure you want to delete this conversation?",
- "defaultMessage": "Are you sure you want to delete this conversation? It cannot be undone.",
- "description": "Alert warning when deleting entire topic",
- "missing": false,
- "obsolete": false
- },
- "topic_name_editing_placeholder": {
- "translation": "Freeform name of the group",
- "defaultMessage": "Freeform name of the group",
- "description": "Prompt for entering topic name",
- "missing": false,
- "obsolete": false
- },
- "unnamed_topic": {
- "translation": "Unnamed",
- "defaultMessage": "Unnamed",
- "description": "Title shown when the topic has no name",
- "missing": false,
- "obsolete": false
- },
- "update_available": {
- "translation": "Update available.",
- "defaultMessage": "Update available.",
- "description": "Message shown when an app update is available.",
- "missing": false,
- "obsolete": false
- },
- "upload_finishing": {
- "translation": "finishing...",
- "defaultMessage": "finishing...",
- "description": "Notification that upload is finishing",
- "missing": false,
- "obsolete": false
- },
- "user_not_found": {
- "translation": "Not found",
- "defaultMessage": "Not found",
- "description": "In place of a user's full name when the user is not found.",
- "missing": false,
- "obsolete": false
- },
- "description_editing_placeholder": {
- "translation": "Optional description",
- "defaultMessage": "Description (optional)",
- "description": "Placeholder for editing topic description",
- "missing": false,
- "obsolete": false
- },
- "label_description": {
- "translation": "Description",
- "defaultMessage": "Description",
- "description": "Label for description of user or topic",
- "missing": false,
- "obsolete": false
- },
- "button_security": {
- "translation": "Security",
- "defaultMessage": "Security",
- "description": "Navigaton button for security panel.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_crop": {
- "translation": "Drag to Adjust",
- "defaultMessage": "Drag to Adjust",
- "description": "Title for AvatarCropView.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_general": {
- "translation": "General",
- "defaultMessage": "General",
- "description": "Title for TopicCommon.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_members": {
- "translation": "Members",
- "defaultMessage": "Members",
- "description": "Title for managing group members view.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_security": {
- "translation": "Security",
- "defaultMessage": "Security",
- "description": "Title for TopicSecirity and AccSecurity.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_info": {
- "translation": "Info",
- "defaultMessage": "Info",
- "description": "Title for InfoView",
- "missing": false,
- "obsolete": false
- },
- "permissions_anonymous": {
- "translation": "Anonymous",
- "defaultMessage": "Anonymous",
- "description": "Title for editing default anonymous permissions",
- "missing": false,
- "obsolete": false
- },
- "permissions_authenticated": {
- "translation": "Authenticated",
- "defaultMessage": "Authenticated",
- "description": "Title for editing default authenticated permissions",
- "missing": false,
- "obsolete": false
- },
- "topic_delete": {
- "translation": "Delete Conversation",
- "defaultMessage": "Delete Conversation",
- "description": "Alert title when deleting the topic.",
- "missing": false,
- "obsolete": false
- },
- "permissions_user": {
- "translation": "User's Permissions",
- "defaultMessage": "User's Permissions",
- "description": "Title for editing user's permissions",
- "missing": false,
- "obsolete": false
- },
- "password_reset_email_sent": {
- "translation": "An email with security code has been sent.",
- "defaultMessage": "An email with security code has been sent.",
- "description": "Notification that the email with password reset instructions has been sent",
- "missing": false,
- "obsolete": false
- },
- "label_unarchive_topic": {
- "translation": "Archived:",
- "defaultMessage": "Archived:",
- "description": "Label for unarchiving the topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_forward": {
- "translation": "Forward",
- "defaultMessage": "Forward",
- "description": "Forward message",
- "missing": false,
- "obsolete": false
- },
- "forward_to": {
- "translation": "Forward to",
- "defaultMessage": "Forward to",
- "missing": false,
- "obsolete": false
- },
- "forward_to_search_placeholder": {
- "translation": "Search contacts",
- "defaultMessage": "Search contacts",
- "description": "Contact search prompt when forwarding a message.",
- "missing": false,
- "obsolete": false
- },
- "label_new_password": {
- "translation": "New password",
- "defaultMessage": "New password",
- "description": "Label for entering a new password",
- "missing": false,
- "obsolete": false
- },
- "drafty_unknown": {
- "translation": "Unsupported",
- "defaultMessage": "Unsupported",
- "description": "Unsupported entity in drafty",
- "missing": false,
- "obsolete": false
- },
- "calls_incoming": {
- "translation": "Incoming call",
- "defaultMessage": "Incoming call",
- "description": "Incoming call label",
- "missing": false,
- "obsolete": false
- },
- "calls_outgoing": {
- "translation": "Outgoing call",
- "defaultMessage": "Outgoing call",
- "description": "Outgoing call label",
- "missing": false,
- "obsolete": false
- },
- "calls_you_label": {
- "translation": "You",
- "defaultMessage": "You",
- "description": "Shown over the local video screen",
- "missing": false,
- "obsolete": false
- },
- "menu_item_video_call": {
- "translation": "Video call",
- "defaultMessage": "Video call",
- "description": "Start video call",
- "missing": false,
- "obsolete": false
- },
- "already_in_call": {
- "translation": "You already in an ongoing call!",
- "defaultMessage": "You already in an ongoing call!",
- "description": "Error message when the user tried to accept a new call without finishing pervious one",
- "missing": false,
- "obsolete": false
- },
- "call_cancelled": {
- "translation": "cancelled",
- "defaultMessage": "cancelled",
- "description": "Label for cancelled outgoing call",
- "missing": false,
- "obsolete": false
- },
- "call_missed": {
- "translation": "missed",
- "defaultMessage": "missed",
- "description": "Label for missed incoming call",
- "missing": false,
- "obsolete": false
- },
- "call_declined": {
- "translation": "declined",
- "defaultMessage": "declined",
- "description": "Label for declined call",
- "missing": false,
- "obsolete": false
- },
- "call_disconnected": {
- "translation": "disconnected",
- "defaultMessage": "disconnected",
- "description": "Label for disconnected call",
- "missing": false,
- "obsolete": false
- },
- "label_use_secure_connection": {
- "translation": "Use secure connection",
- "defaultMessage": "Use secure connection",
- "description": "Label for WS/WSS connection type in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "cred_confirmed_successfully": {
- "translation": "Confirmed successfully",
- "defaultMessage": "Confirmed successfully",
- "description": "Notification message that the credential was successfully validated.",
- "missing": false,
- "obsolete": false
- },
- "menu_item_edit": {
- "translation": "Edit",
- "defaultMessage": "Edit",
- "description": "Edit message",
- "missing": false,
- "obsolete": false
- },
- "editing_message": {
- "translation": "Editing",
- "defaultMessage": "Editing",
- "description": "Title over message editing preview",
- "missing": false,
- "obsolete": false
- },
- "message_edited_marker": {
- "translation": ", edited",
- "defaultMessage": ", edited",
- "description": "Marker indicating that the message was edited",
- "missing": false,
- "obsolete": false
- },
- "icon_title_add_image": {
- "translation": "Add image",
- "defaultMessage": "Add image",
- "description": "Icon tool tip for attaching an image",
- "missing": false,
- "obsolete": false
- },
- "icon_title_attach_file": {
- "translation": "Attach file",
- "defaultMessage": "Attach file",
- "description": "Icon tool tip for attaching a file",
- "missing": false,
- "obsolete": false
- },
- "icon_title_delete": {
- "translation": "Delete",
- "defaultMessage": "Delete recording",
- "description": "Icon tool tip for deleting recorded audio",
- "missing": false,
- "obsolete": false
- },
- "icon_title_pause": {
- "translation": "Pause",
- "defaultMessage": "Pause playback",
- "description": "Icon tool tip for pausing audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_play": {
- "translation": "Play",
- "defaultMessage": "Play recording",
- "description": "Icon tool tip for starting audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_record_voice": {
- "translation": "Record voice message",
- "defaultMessage": "Record voice message",
- "description": "Icon tool tip for recording a voice message",
- "missing": false,
- "obsolete": false
- },
- "icon_title_resume": {
- "translation": "Resume",
- "defaultMessage": "Resume playback",
- "description": "Icon tool tip for resuming audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_send": {
- "translation": "Send message",
- "defaultMessage": "Send message",
- "description": "Icon tool tip for sending a message",
- "missing": false,
- "obsolete": false
- },
- "drag_file": {
- "translation": "Drag file here",
- "defaultMessage": "Drag file here",
- "description": "Prompt on the file drag-n-drop overlay banner",
- "missing": false,
- "obsolete": false
- },
- "drafty_video": {
- "translation": "Video recording",
- "defaultMessage": "Video recording",
- "description": "Comment for videos embedded in Drafty",
- "missing": false,
- "obsolete": false
- },
- "call_in_progress": {
- "translation": "in progress",
- "defaultMessage": "in progress",
- "description": "Label for call in progress",
- "missing": false,
- "obsolete": false
- },
- "menu_item_audio_call": {
- "translation": "Call",
- "defaultMessage": "Call",
- "description": "Start audio call",
- "missing": false,
- "obsolete": false
- },
- "unrecognized_video_format": {
- "translation": "Format of this video is not recognized",
- "defaultMessage": "Format of this video is not recognized",
- "description": "Error message when uploaded video is invalid",
- "missing": false,
- "obsolete": false
- },
- "loading_note": {
- "translation": "Loading...",
- "defaultMessage": "Loading...",
- "description": "Message shown when component is loading",
- "missing": false,
- "obsolete": false
- },
- "password_i_have_code": {
- "translation": "I have code",
- "defaultMessage": "I have code",
- "description": "Call to open field to enter password reset code",
- "missing": false,
- "obsolete": false
- },
- "label_reset_password_tel": {
- "translation": "Send a password reset SMS:",
- "defaultMessage": "Send SMS to reset password",
- "description": "Label for password reset field by phone",
- "missing": false,
- "obsolete": false
- },
- "mobile_phone_number": {
- "translation": "Mobile phone number",
- "defaultMessage": "Mobile phone number",
- "description": "Prompt for entering a mobile phone number",
- "missing": false,
- "obsolete": false
- },
- "password_reset_sms_sent": {
- "translation": "A text message with security code has been sent.",
- "defaultMessage": "A text message with security code has been sent.",
- "description": "Notification that the SMS with password reset instructions has been sent",
- "missing": false,
- "obsolete": false
- },
- "mobile_number_required": {
- "translation": "Mobile phone number required",
- "defaultMessage": "Mobile phone number required",
- "description": "Error message",
- "missing": false,
- "obsolete": false
- },
- "current_email": {
- "translation": "Current email",
- "defaultMessage": "Current email",
- "description": "Label for old email",
- "missing": false,
- "obsolete": false
- },
- "current_phone": {
- "translation": "Current phone number",
- "defaultMessage": "Current phone number",
- "description": "Label for old phone",
- "missing": false,
- "obsolete": false
- },
- "new_email": {
- "translation": "New email",
- "defaultMessage": "New email",
- "description": "Prompt for entering a new email",
- "missing": false,
- "obsolete": false
- },
- "new_phone_number": {
- "translation": "New phone number",
- "defaultMessage": "New phone number",
- "description": "Prompt for entering a new telephone number",
- "missing": false,
- "obsolete": false
- },
- "change_email": {
- "translation": "Change email",
- "defaultMessage": "Change email",
- "description": "Prompt to change current email",
- "missing": false,
- "obsolete": false
- },
- "change_phone": {
- "translation": "Change phone number",
- "defaultMessage": "Change phone number",
- "description": "Prompt to change current phone number",
- "missing": false,
- "obsolete": false
- },
- "call_busy": {
- "translation": "busy",
- "defaultMessage": "busy",
- "description": "Label for busy call line",
- "missing": false,
- "obsolete": false
- },
- "failed_to_init_audio": {
- "translation": "Failed to initialize audio recording",
- "defaultMessage": "Failed to initialize audio recording",
- "description": "Error message when audio is not available",
- "missing": false,
- "obsolete": false
- },
- "pin_message": {
- "translation": "Pin",
- "defaultMessage": "Pin",
- "description": "Menu item [Pin] for pinning message to the top.",
- "missing": false,
- "obsolete": false
- },
- "unpin_message": {
- "translation": "Unpin",
- "defaultMessage": "Unpin",
- "description": "Menu item [Unpin] for un-pinning the message.",
- "missing": false,
- "obsolete": false
- },
- "password_reset_success": {
- "translation": "Password reset successfully",
- "defaultMessage": "Password reset successfully",
- "description": "Notification message that the password was successfully reset.",
- "missing": false,
- "obsolete": false
- },
- "scan_qr_code": {
- "translation": "Scan QR Code",
- "defaultMessage": "Scan QR Code",
- "description": "Title for scanning QR code",
- "missing": false,
- "obsolete": false
- },
- "self_topic_comment": {
- "translation": "Notes, messages, links, files saved for posterity",
- "defaultMessage": "Notes, messages, links, files saved for posterity",
- "description": "Comment for self topic for UI",
- "missing": false,
- "obsolete": false
- },
- "self_topic_name": {
- "translation": "Saved messages",
- "defaultMessage": "Saved messages",
- "description": "Name of self topic for UI",
- "missing": false,
- "obsolete": false
- },
- "message_not_found": {
- "translation": "message not found",
- "defaultMessage": "message not found",
- "description": "Pinned message is not found",
- "missing": false,
- "obsolete": false
- },
- "message_deleted": {
- "translation": "message deleted",
- "defaultMessage": "message deleted",
- "description": "Pinned message is deleted",
- "missing": false,
- "obsolete": false
- },
- "alias_editing_placeholder": {
- "translation": "Alias (optional)",
- "defaultMessage": "Alias (optional)",
- "description": "Placeholder for editing user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "label_alias": {
- "translation": "Alias:",
- "defaultMessage": "Alias:",
- "description": "Label for user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "alias_already_taken": {
- "translation": "(already taken)",
- "defaultMessage": "(already taken)",
- "description": "Error message for alias already taken",
- "missing": false,
- "obsolete": false
- },
- "alias_invalid": {
- "translation": "(invalid)",
- "defaultMessage": "(invalid)",
- "description": "Error message for invalid alias",
- "missing": false,
- "obsolete": false
- },
- "label_alias_edit": {
- "translation": "Alias",
- "defaultMessage": "Alias",
- "description": "Label for editing user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "select_country": {
- "translation": "Select country",
- "defaultMessage": "Select country",
- "description": "Placeholder for the country selector",
- "missing": false,
- "obsolete": false
- },
- "text_copied": {
- "translation": "Copied to clipboard",
- "defaultMessage": "Copied to clipboard",
- "description": "Notification that text has been copied to clipboard",
- "missing": false,
- "obsolete": false
- },
- "color_schema_dark": {
- "translation": "Dark",
- "defaultMessage": "Dark",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "color_schema_light": {
- "translation": "Light",
- "defaultMessage": "Light",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "color_schema_system": {
- "translation": "System default",
- "defaultMessage": "System default",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "label_color_schema": {
- "translation": "Theme:",
- "defaultMessage": "Theme:",
- "description": "Label for selecting color scheme (dark, light) in Settings",
- "missing": false,
- "obsolete": false
- },
- "label_text_size": {
- "translation": "Text size:",
- "defaultMessage": "Text size:",
- "description": "Label adjusting text size",
- "missing": false,
- "obsolete": false
- },
- "incognito_mode_description": {
- "translation": "Hide your online status and typing indicators.",
- "defaultMessage": "Hide your online status and typing indicators.",
- "description": "Description for incognito mode toggle",
- "missing": false,
- "obsolete": false
- },
- "send_command": {
- "translation": "Send on {key}",
- "defaultMessage": "Send on {key}",
- "description": "Config option to send message on hitting CTRL(or Cmd)-Enter",
- "missing": false,
- "obsolete": false
- },
- "send_command_explained": {
- "translation": "Press Enter for new line",
- "defaultMessage": "Press Enter for new line",
- "description": "Explanation how to enter newline when [Send on CTRL-Enter] is enabled",
- "missing": false,
- "obsolete": false
- },
- "send_plain": {
- "translation": "Send on Enter",
- "defaultMessage": "Send on Enter",
- "description": "Config option to send message on hitting Enter",
- "missing": false,
- "obsolete": false
- },
- "send_plain_explained": {
- "translation": "Press Shift + Enter for new line",
- "defaultMessage": "Press Shift + Enter for new line",
- "description": "Explanation how to enter newline when [Send on Enter] is enabled",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_general": {
- "translation": "General",
- "defaultMessage": "General",
- "description": "Sidepanel title for AccGeneralView.",
- "missing": false,
- "obsolete": false
- },
- "label_keyboard": {
- "translation": "Keyboard:",
- "defaultMessage": "Keyboard:",
- "description": "Label for send on enter settings",
- "missing": false,
- "obsolete": false
- },
- "pin_chat": {
- "translation": "Pin",
- "defaultMessage": "Pin",
- "description": "Menu item [Pin] for pinning chat to the top.",
- "missing": false,
- "obsolete": false
- },
- "unpin_chat": {
- "translation": "Unpin",
- "defaultMessage": "Unpin",
- "description": "Menu item [Unpin] for un-pinning the chat.",
- "missing": false,
- "obsolete": false
- },
- "subscriber_count": {
- "translation": "{count, plural, one {{count, number} subscriber} other {{count, number} subscribers}}",
- "defaultMessage": "{count, plural, one {{count, number} subscriber} two {{count, number} subscribers} three {{count, number} subscribers} few {{count, number} subscribers} many {{count, number} subscribers} other {{count, number} subscribers}}",
- "description": "Count of channel subscribers",
- "missing": false,
- "obsolete": false
- },
- "label_member_count": {
- "translation": "Members:",
- "defaultMessage": "Members:",
- "description": "Label for the count of group topic members",
- "missing": false,
- "obsolete": false
- },
- "label_subscriber_count": {
- "translation": "Subscribers:",
- "defaultMessage": "Subscribers:",
- "description": "Label for the count of channel subscribers",
- "missing": false,
- "obsolete": false
- },
- "member_count": {
- "translation": "{count, plural, one {{count, number} member} other {{count, number} members}}",
- "defaultMessage": "{count, plural, one {{count, number} member} two {{count, number} members} three {{count, number} members} few {{count, number} members} many {{count, number} members} other {{count, number} members}}",
- "description": "Count of group topic members",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_image": {
- "translation": "image",
- "defaultMessage": "image",
- "description": "Tab title Image",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_pattern": {
- "translation": "pattern",
- "defaultMessage": "pattern",
- "description": "Tab title Pattern",
- "missing": false,
- "obsolete": false
- },
- "button_restore": {
- "translation": "Restore default",
- "defaultMessage": "Restore default",
- "description": "Reset setting to default",
- "missing": false,
- "obsolete": false
- },
- "wallpapers": {
- "translation": "Wallpapers",
- "defaultMessage": "Wallpapers",
- "description": "Link or title for working with wallpapers.",
- "missing": false,
- "obsolete": false
- },
- "label_blur_wallpaper": {
- "translation": "Blur:",
- "defaultMessage": "Blur:",
- "description": "Label adjusting blur amount",
- "missing": false,
- "obsolete": false
- },
- "label_scan_id": {
- "translation": "Scan my ID:",
- "defaultMessage": "Scan my ID:",
- "description": "Label [Scan my ID:]",
- "missing": false,
- "obsolete": false
- },
- "unknown_name": {
- "translation": "Unknown",
- "defaultMessage": "Unknown",
- "description": "Name to show when the name is missing",
- "missing": false,
- "obsolete": false
- },
- "save_action": {
- "translation": "Save",
- "defaultMessage": "Save",
- "description": "Label for Save button",
- "missing": false,
- "obsolete": false
- },
- "cannot_parse_vcard": {
- "translation": "Cannot parse vCard file.",
- "defaultMessage": "Cannot parse vCard file.",
- "description": "Error message when vCard file cannot be parsed",
- "missing": false,
- "obsolete": false
- },
- "chat_now": {
- "translation": "Chat",
- "defaultMessage": "Chat",
- "description": "Label for message button in contact card",
- "missing": false,
- "obsolete": false
- },
- "find_user": {
- "translation": "Find",
- "defaultMessage": "Find",
- "description": "Label for search button in contact card",
- "missing": false,
- "obsolete": false
- },
- "label_contact_card": {
- "translation": "Contact Card",
- "defaultMessage": "Contact Card",
- "description": "Label for contact card preview",
- "missing": false,
- "obsolete": false
- },
- "label_contacts": {
- "translation": "Contacts",
- "defaultMessage": "Contacts",
- "description": "Label for contact information",
- "missing": false,
- "obsolete": false
- },
- "add_members_prompt": {
- "translation": "add members",
- "defaultMessage": "add members",
- "description": "Placeholder in group members input field",
- "missing": false,
- "obsolete": false
- }
-}
diff --git a/src/i18n/es.json b/src/i18n/es.json
deleted file mode 100644
index d0f4a5d7..00000000
--- a/src/i18n/es.json
+++ /dev/null
@@ -1,1966 +0,0 @@
-{
- "action_block_contact": {
- "translation": "Bloquear contacto",
- "defaultMessage": "Block Contact",
- "description": "Flat button [Block Contact]",
- "missing": false,
- "obsolete": false
- },
- "action_cancel": {
- "translation": "cancelar",
- "defaultMessage": "cancel",
- "description": "Call to action [cancel]",
- "missing": false,
- "obsolete": false
- },
- "action_clear_messages": {
- "translation": "Borrar mensajes",
- "defaultMessage": "Clear Messages",
- "description": "Flat button [Clear Messages] (soft-delete messages)",
- "missing": false,
- "obsolete": false
- },
- "action_delete_messages": {
- "translation": "Borrar mensajes para todos",
- "defaultMessage": "Clear Messages for All",
- "description": "Flat button [Clear for All] (hard-delete all messages)",
- "missing": false,
- "obsolete": false
- },
- "action_leave_chat": {
- "translation": "Dejar conversación",
- "defaultMessage": "Leave Conversation",
- "description": "Flat button [Leave Conversation]",
- "missing": false,
- "obsolete": false
- },
- "action_report_chat": {
- "translation": "Reportar conversación",
- "defaultMessage": "Report Conversation",
- "description": "Flat button [Report Group]",
- "missing": false,
- "obsolete": false
- },
- "archived_contacts": {
- "translation": "Contactos archivados ({count})",
- "defaultMessage": "Archived contacts ({count})",
- "description": "Label for archived chats",
- "missing": false,
- "obsolete": false
- },
- "badge_danger": {
- "translation": "Suspicaz",
- "defaultMessage": "Untrustworthy",
- "description": "Suspicious or untrustworthy account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_owner": {
- "translation": "propietario",
- "defaultMessage": "owner",
- "description": "Badge for indicating the owner",
- "missing": false,
- "obsolete": false
- },
- "badge_staff": {
- "translation": "Administración",
- "defaultMessage": "Staff-managed",
- "description": "Explanation of a staff-managed account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_verified": {
- "translation": "Verificado",
- "defaultMessage": "Verified/official",
- "description": "Explanation of a verified account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_you": {
- "translation": "tú",
- "defaultMessage": "you",
- "description": "Badge for indicating the current user",
- "missing": false,
- "obsolete": false
- },
- "block_contact_warning": {
- "translation": "¿Estás seguro de que quieres bloquear a este contacto?",
- "defaultMessage": "Are you sure you want to block this contact?",
- "description": "Alert dialog warning when blocking a contact.",
- "missing": false,
- "obsolete": false
- },
- "blocked_contacts_link": {
- "translation": "Contactos bloqueados ({count})",
- "defaultMessage": "Blocked contacts ({count})",
- "description": "Blocked contacts link",
- "missing": false,
- "obsolete": false
- },
- "button_add_members": {
- "translation": "Añadir miembros",
- "defaultMessage": "Add members",
- "description": "Flat button [Add members] (to topic)",
- "missing": false,
- "obsolete": false
- },
- "button_cancel": {
- "translation": "Cancelar",
- "defaultMessage": "Cancel",
- "description": "Button [Cancel]",
- "missing": false,
- "obsolete": false
- },
- "button_confirm": {
- "translation": "Confirmar",
- "defaultMessage": "Confirm",
- "description": "Button [Confirm]",
- "missing": false,
- "obsolete": false
- },
- "button_create": {
- "translation": "Crear",
- "defaultMessage": "Create",
- "description": "Button [Create]",
- "missing": false,
- "obsolete": false
- },
- "button_delete_account": {
- "translation": "Eliminar cuenta",
- "defaultMessage": "Delete account",
- "description": "Button [Delete account]",
- "missing": false,
- "obsolete": false
- },
- "button_edit": {
- "translation": "Editar",
- "defaultMessage": "Edit",
- "description": "Call to action [Edit]",
- "missing": false,
- "obsolete": false
- },
- "button_logout": {
- "translation": "Cerrar sesión",
- "defaultMessage": "Logout",
- "description": "Button [Logout]",
- "missing": false,
- "obsolete": false
- },
- "button_ok": {
- "translation": "OK",
- "defaultMessage": "OK",
- "description": "Button [OK]",
- "missing": false,
- "obsolete": false
- },
- "button_reset": {
- "translation": "Restablecer",
- "defaultMessage": "Reset",
- "description": "Button [Reset]",
- "missing": false,
- "obsolete": false
- },
- "button_send_request": {
- "translation": "Enviar petición",
- "defaultMessage": "Send request",
- "description": "Button [Send request]",
- "missing": false,
- "obsolete": false
- },
- "button_sign_in": {
- "translation": "Entrar",
- "defaultMessage": "Sign in",
- "description": "Button [Sign In]",
- "missing": false,
- "obsolete": false
- },
- "button_sign_up": {
- "translation": "Regístrate",
- "defaultMessage": "Sign up",
- "description": "Create account button [Sign Up]",
- "missing": false,
- "obsolete": false
- },
- "button_subscribe": {
- "translation": "Suscribirse",
- "defaultMessage": "Subscribe",
- "description": "Button [Subscribe]",
- "missing": false,
- "obsolete": false
- },
- "button_update": {
- "translation": "Actualizar",
- "defaultMessage": "Update",
- "description": "Button [Update]",
- "missing": false,
- "obsolete": false
- },
- "cannot_initiate_file_upload": {
- "translation": "No se pudo iniciar la carga del archivo.",
- "defaultMessage": "Cannot initiate file upload.",
- "description": "Generic error messagewhen attachment fails",
- "missing": false,
- "obsolete": false
- },
- "channel": {
- "translation": "canal",
- "defaultMessage": "channel",
- "description": "Subtitle shown for channels in MessagesView instead of last seen",
- "missing": false,
- "obsolete": false
- },
- "channel_prompt": {
- "translation": "Este es un canal",
- "defaultMessage": "This is a channel",
- "description": "Checkbox label when creating a channel",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation": {
- "translation": "Estás invitado a participar en un nuevo chat. ¿Qué te gustaría hacer?",
- "defaultMessage": "You are invited to start a new chat. What would you like to do?",
- "description": "New chat invitation message: [Accept] [Ignore] [Block].",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_accept": {
- "translation": "Aceptar",
- "defaultMessage": "Accept",
- "description": "Action [Accept] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_block": {
- "translation": "Bloquear",
- "defaultMessage": "Block",
- "description": "Action [Block] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_ignore": {
- "translation": "Ignorar",
- "defaultMessage": "Ignore",
- "description": "Action [Ignore] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "clear_messages_warning": {
- "translation": "¿Estás seguro de que quieres eliminar todos los mensajes? Esta acción es irreversible.",
- "defaultMessage": "Are you sure you want to clear all messages? It cannot be undone.",
- "description": "Alert dialog warning when deleting all messages.",
- "missing": false,
- "obsolete": false
- },
- "code_doesnot_match": {
- "translation": "El código no coincide",
- "defaultMessage": "Code does not match",
- "description": "Error message when the credential validation code is incorrect.",
- "missing": false,
- "obsolete": false
- },
- "contacts_not_found": {
- "translation": "No tienes chats\n¯∖_(ツ)_/¯",
- "defaultMessage": "You have no chats\\n¯∖_(ツ)_/¯",
- "description": "HTML message shown in ContactList when no contacts are found",
- "missing": false,
- "obsolete": false
- },
- "contacts_not_found_short": {
- "translation": "Ningún contacto coincide con ''{query}''",
- "defaultMessage": "No contacts match ''{query}''",
- "description": "Shown in ContactsView when search returned no results",
- "missing": false,
- "obsolete": false
- },
- "credential_email_prompt": {
- "translation": "Tu correo electrónico de registro",
- "defaultMessage": "Your registration email",
- "description": "Placeholder for entering email",
- "missing": false,
- "obsolete": false
- },
- "delete_account": {
- "translation": "Eliminar cuenta",
- "defaultMessage": "Delete account",
- "description": "Title for delete account warning",
- "missing": false,
- "obsolete": false
- },
- "delete_account_warning": {
- "translation": "¿Estás seguro de que deseas eliminar permanentemente tu cuenta? Esta acción es irreversible.",
- "defaultMessage": "Are you sure you want to delete your account? It cannot be undone.",
- "description": "Warning message when deleting an account",
- "missing": false,
- "obsolete": false
- },
- "delete_messages_warning": {
- "translation": "¿Estás seguro de que quieres eliminar todos los mensajes para todos? Esta acción es irreversible.",
- "defaultMessage": "Are you sure you want to delete all messages for everyone? It cannot be undone.",
- "description": "Alert dialog warning when hard-deleting all messages.",
- "missing": false,
- "obsolete": false
- },
- "download_action": {
- "translation": "descargar",
- "defaultMessage": "download",
- "description": "Call to action [download]",
- "missing": false,
- "obsolete": false
- },
- "drafty_attachment": {
- "translation": "Archivo",
- "defaultMessage": "Attachment",
- "description": "Comment for attachment in Drafty",
- "missing": false,
- "obsolete": false
- },
- "drafty_form": {
- "translation": "Formulario:",
- "defaultMessage": "Form:",
- "description": "Comment for form in Drafty",
- "missing": false,
- "obsolete": false
- },
- "drafty_image": {
- "translation": "Imagen",
- "defaultMessage": "Picture",
- "description": "Comment for embedded images in Drafty",
- "missing": false,
- "obsolete": false
- },
- "email_dative": {
- "translation": "correo electrónico",
- "defaultMessage": "email",
- "description": "Dative case of 'email', i.e. 'email' in 'by email'",
- "missing": false,
- "obsolete": false
- },
- "email_prompt": {
- "translation": "Correo electrónico, p.ej. juan@example.com",
- "defaultMessage": "Email, e.g. jdoe@example.com",
- "description": "Input placeholder for email entry",
- "missing": false,
- "obsolete": false
- },
- "enable_peers_messaging": {
- "translation": "Habilitar",
- "defaultMessage": "Enable",
- "description": "Call to action to enable peer's messaging",
- "missing": false,
- "obsolete": false
- },
- "enter_confirmation_code_prompt": {
- "translation": "Introduzca el código de confirmación enviado a tu {method}:",
- "defaultMessage": "Confirmation code",
- "description": "Request to enter confirmation code",
- "missing": false,
- "obsolete": false
- },
- "error_invalid_id": {
- "translation": "ID inválido",
- "defaultMessage": "Invalid ID",
- "description": "Error message",
- "missing": false,
- "obsolete": false
- },
- "file_attachment_too_large": {
- "translation": "El tamaño del archivo {size} excede el límite de {limit}.",
- "defaultMessage": "The file size {size} exceeds the {limit} limit.",
- "description": "Error message when attachment is too large",
- "missing": false,
- "obsolete": false
- },
- "forgot_password_link": {
- "translation": "¿Olvidaste tu contraseña?",
- "defaultMessage": "Forgot password?",
- "description": "Link to Reset password form",
- "missing": false,
- "obsolete": false
- },
- "full_name_prompt": {
- "translation": "Nombre completo, p.ej. Juan González Hernández",
- "defaultMessage": "Full name, e.g. John Doe",
- "description": "Input placeholder for person's full name",
- "missing": false,
- "obsolete": false
- },
- "granted_permissions": {
- "translation": "Otorgados",
- "defaultMessage": "Granted",
- "description": "Title for permissions",
- "missing": false,
- "obsolete": false
- },
- "group_has_no_members": {
- "translation": "No hay miembros",
- "defaultMessage": "No members",
- "description": "Shown in place of group members",
- "missing": false,
- "obsolete": false
- },
- "group_user_id_prompt": {
- "translation": "ID del grupo o usuario",
- "defaultMessage": "Group or User ID",
- "description": "Prompt for entering user or group ID",
- "missing": false,
- "obsolete": false
- },
- "image_caption_prompt": {
- "translation": "Añade un comentario",
- "defaultMessage": "Image caption",
- "description": "Prompt in SendMessage for attached image",
- "missing": false,
- "obsolete": false
- },
- "invalid_content": {
- "translation": "contenido inválido",
- "defaultMessage": "invalid content",
- "description": "Shown when the message is unreadable",
- "missing": false,
- "obsolete": false
- },
- "invalid_security_token": {
- "translation": "Token de seguridad inválido",
- "defaultMessage": "Invalid security token",
- "description": "Error message when resetting password.",
- "missing": false,
- "obsolete": false
- },
- "label_client": {
- "translation": "Cliente:",
- "defaultMessage": "Client:",
- "description": "Label for a client version",
- "missing": false,
- "obsolete": false
- },
- "label_content_type": {
- "translation": "Tipo de contenido:",
- "defaultMessage": "Content type:",
- "description": "Label for file content type (mime)",
- "missing": false,
- "obsolete": false
- },
- "label_default_access_mode": {
- "translation": "Modo de acceso predeterminado:",
- "defaultMessage": "Default access mode:",
- "description": "Label for default access mode",
- "missing": false,
- "obsolete": false
- },
- "label_file_name": {
- "translation": "Nombre del archivo:",
- "defaultMessage": "File name:",
- "description": "Label for a file name",
- "missing": false,
- "obsolete": false
- },
- "label_group_members": {
- "translation": "Miembros del grupo:",
- "defaultMessage": "Group members:",
- "description": "Section title or label",
- "missing": false,
- "obsolete": false
- },
- "label_incognito_mode": {
- "translation": "Modo incógnito:",
- "defaultMessage": "Incognito mode:",
- "description": "Label for incognito mode toggle",
- "missing": false,
- "obsolete": false
- },
- "label_message_sound": {
- "translation": "Sonido de mensaje:",
- "defaultMessage": "Message sound:",
- "description": "Label for message sounds toggle",
- "missing": false,
- "obsolete": false
- },
- "label_muting_topic": {
- "translation": "Silenciado:",
- "defaultMessage": "Muted:",
- "description": "Label for Muting/unmuting the topic",
- "missing": false,
- "obsolete": false
- },
- "label_other_user": {
- "translation": "Otros",
- "defaultMessage": "Other",
- "description": "Label for the other user when the user is unnamed",
- "missing": false,
- "obsolete": false
- },
- "label_password": {
- "translation": "Contraseña",
- "defaultMessage": "Password",
- "description": "Label for password editing",
- "missing": false,
- "obsolete": false
- },
- "label_permissions": {
- "translation": "Permisos:",
- "defaultMessage": "Permissions:",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "label_private": {
- "translation": "Comentario privado",
- "defaultMessage": "Private comment",
- "description": "Label for editing 'private'",
- "missing": false,
- "obsolete": false
- },
- "label_push_notifications": {
- "translation": "Alertas de notificaciones:",
- "defaultMessage": "Notification alerts:",
- "description": "Label for push notifications switch",
- "missing": false,
- "obsolete": false
- },
- "label_push_notifications_disabled": {
- "translation": "Alertas de notificaciones (requiere HTTPS):",
- "defaultMessage": "Notification alerts (requires HTTPS):",
- "description": "Label for push notifications switch",
- "missing": false,
- "obsolete": false
- },
- "label_reset_password": {
- "translation": "Enviar un correo electrónico de restablecimiento de contraseña:",
- "defaultMessage": "Send a password reset email",
- "description": "Label for password reset field",
- "missing": false,
- "obsolete": false
- },
- "label_sdk": {
- "translation": "SDK:",
- "defaultMessage": "SDK:",
- "missing": false,
- "obsolete": false
- },
- "label_server": {
- "translation": "Servidor:",
- "defaultMessage": "Server:",
- "description": "Label for a server version",
- "missing": false,
- "obsolete": false
- },
- "label_server_address": {
- "translation": "Dirección del servidor:",
- "defaultMessage": "Server address:",
- "missing": false,
- "obsolete": false
- },
- "label_server_to_use": {
- "translation": "Servidor para usar:",
- "defaultMessage": "Server to use:",
- "description": "Label for server selector in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "label_size": {
- "translation": "Tamaño:",
- "defaultMessage": "Size:",
- "description": "Label for file size",
- "missing": false,
- "obsolete": false
- },
- "label_topic_name": {
- "translation": "Nombre del tema",
- "defaultMessage": "Name",
- "description": "Label for editing topic name",
- "missing": false,
- "obsolete": false
- },
- "label_user_contacts": {
- "translation": "Contactos",
- "defaultMessage": "Contacts",
- "description": "Label for user contacts",
- "missing": false,
- "obsolete": false
- },
- "label_user_id": {
- "translation": "ID:",
- "defaultMessage": "ID:",
- "description": "Label for user address (ID)",
- "missing": false,
- "obsolete": false
- },
- "label_wire_transport": {
- "translation": "Transporte de alambre:",
- "defaultMessage": "Wire transport:",
- "description": "Label for wire transport selection in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "label_you": {
- "translation": "Tú:",
- "defaultMessage": "You:",
- "description": "Label for the current user",
- "missing": false,
- "obsolete": false
- },
- "label_your_name": {
- "translation": "Tu nombre",
- "defaultMessage": "Your name",
- "description": "Label for full name editing",
- "missing": false,
- "obsolete": false
- },
- "label_your_permissions": {
- "translation": "Tus permisos:",
- "defaultMessage": "Your permissions:",
- "description": "Label for current user permissions",
- "missing": false,
- "obsolete": false
- },
- "last_seen_timestamp": {
- "translation": "Últ. vez",
- "defaultMessage": "Last seen: {timestamp}",
- "description": "Label for the timestamp of when the user or topic was last online",
- "missing": false,
- "obsolete": false
- },
- "leave_chat_warning": {
- "translation": "¿Estás seguro de que quieres dejar esta conversación?",
- "defaultMessage": "Are you sure you want to leave this conversation?",
- "description": "Alert dialog warning when unsubscribing from a chat.",
- "missing": false,
- "obsolete": false
- },
- "link_contact_us": {
- "translation": "Contáctanos",
- "defaultMessage": "Contact Us",
- "description": "Ancor text for contacting us by email",
- "missing": false,
- "obsolete": false
- },
- "link_privacy_policy": {
- "translation": "Política de privacidad",
- "defaultMessage": "Privacy Policy",
- "description": "Ancor text for privacy policy link",
- "missing": false,
- "obsolete": false
- },
- "link_terms_of_service": {
- "translation": "Términos de uso",
- "defaultMessage": "Terms of Service",
- "description": "Ancor text for terms of service link",
- "missing": false,
- "obsolete": false
- },
- "login_prompt": {
- "translation": "Nombre de usuario",
- "defaultMessage": "Login",
- "description": "Placeholer for username/login",
- "missing": false,
- "obsolete": false
- },
- "menu_item_archive_topic": {
- "translation": "Archivar",
- "defaultMessage": "Archive",
- "description": "Move topic from the list of active chats to archive",
- "missing": false,
- "obsolete": false
- },
- "menu_item_block": {
- "translation": "Bloquear",
- "defaultMessage": "Block",
- "description": "Block topic or user",
- "missing": false,
- "obsolete": false
- },
- "menu_item_clear_messages": {
- "translation": "Borrar mensajes",
- "defaultMessage": "Clear messages",
- "description": "Delete all messages",
- "missing": false,
- "obsolete": false
- },
- "menu_item_clear_messages_for_all": {
- "translation": "Borrar para todos",
- "defaultMessage": "Clear for All",
- "description": "Delete all message(s) for all members",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete": {
- "translation": "Eliminar",
- "defaultMessage": "Delete",
- "description": "Delete selected messages",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete_for_all": {
- "translation": "Eliminar para todos",
- "defaultMessage": "Delete for All",
- "description": "Delete selected message(s) for all members",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete_topic": {
- "translation": "Eliminar",
- "defaultMessage": "Delete",
- "description": "Delete entire topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_edit_permissions": {
- "translation": "Editar permisos",
- "defaultMessage": "Edit permissions",
- "description": "Menu item [Edit permissions]",
- "missing": false,
- "obsolete": false
- },
- "menu_item_info": {
- "translation": "Información",
- "defaultMessage": "Info",
- "description": "Show extended topic information",
- "missing": false,
- "obsolete": false
- },
- "menu_item_member_delete": {
- "translation": "Eliminar",
- "defaultMessage": "Remove",
- "description": "Remove user from topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_mute": {
- "translation": "Silenciar",
- "defaultMessage": "Mute",
- "description": "Turn off notifications",
- "missing": false,
- "obsolete": false
- },
- "menu_item_restore_topic": {
- "translation": "Restaurar",
- "defaultMessage": "Restore",
- "description": "Restore topic from archive",
- "missing": false,
- "obsolete": false
- },
- "menu_item_send_retry": {
- "translation": "Inténtalo de nuevo",
- "defaultMessage": "Retry",
- "description": "Retry sending message",
- "missing": false,
- "obsolete": false
- },
- "menu_item_unblock": {
- "translation": "Desbloquear",
- "defaultMessage": "Unblock",
- "description": "Unblock topic or user",
- "missing": false,
- "obsolete": false
- },
- "menu_item_unmute": {
- "translation": "Anular el silencio",
- "defaultMessage": "Unmute",
- "description": "Turn notifications on",
- "missing": false,
- "obsolete": false
- },
- "message_sending": {
- "translation": "enviando...",
- "defaultMessage": "sending...",
- "description": "Message being sent, in place of time stamp",
- "missing": false,
- "obsolete": false
- },
- "message_sending_failed": {
- "translation": "no se pudo enviar el mensaje",
- "defaultMessage": "failed",
- "description": "Failed to send message, in place of time stamp",
- "missing": false,
- "obsolete": false
- },
- "messages_not_readable": {
- "translation": "sin acceso a mensajes",
- "defaultMessage": "no access to messages",
- "description": "Message shown in topic without the read access",
- "missing": false,
- "obsolete": false
- },
- "messaging_disabled_prompt": {
- "translation": "El envío de mensajes está deshabilitado",
- "defaultMessage": "Messaging disabled",
- "description": "Prompt in SendMessage in read-only topic",
- "missing": false,
- "obsolete": false
- },
- "more_online_members": {
- "translation": "+{overflow} más",
- "defaultMessage": "+{overflow} more",
- "description": "Shown in MessagesView title bar when the number of online subscribers exceeds MAX_ONLINE_IN_TOPIC",
- "missing": false,
- "obsolete": false
- },
- "new_message_prompt": {
- "translation": "Nuevo mensaje",
- "defaultMessage": "New message",
- "description": "Prompt in send message field",
- "missing": false,
- "obsolete": false
- },
- "new_password_placeholder": {
- "translation": "Introduzca una nueva contraseña",
- "defaultMessage": "Enter new password",
- "description": "Placeholder for entering new password",
- "missing": false,
- "obsolete": false
- },
- "no_connection": {
- "translation": "Sin conexión",
- "defaultMessage": "No connection",
- "description": "Warning that the user is offline.",
- "missing": false,
- "obsolete": false
- },
- "no_contacts": {
- "translation": "No tienes contactos :-(",
- "defaultMessage": "You have no contacts :-(",
- "description": "Shown in ContactsView when the user has no contacts",
- "missing": false,
- "obsolete": false
- },
- "numeric_confirmation_code_prompt": {
- "translation": "Sólo números",
- "defaultMessage": "Numbers only",
- "description": "Prompt for numeric conformation code",
- "missing": false,
- "obsolete": false
- },
- "online_now": {
- "translation": "en línea",
- "defaultMessage": "online now",
- "description": "Indicator that the user or topic is currently online",
- "missing": false,
- "obsolete": false
- },
- "password_prompt": {
- "translation": "Contraseña",
- "defaultMessage": "Password",
- "description": "Placeholder/prompt for entering password",
- "missing": false,
- "obsolete": false
- },
- "password_unchanged_prompt": {
- "translation": "Sin cambios",
- "defaultMessage": "Unchanged",
- "description": "Message in editor while password is unchanged",
- "missing": false,
- "obsolete": false
- },
- "peers_messaging_disabled": {
- "translation": "La mensajería Peer está deshabilitada.",
- "defaultMessage": "Peer's messaging is disabled.",
- "description": "Shown when the p2p peer's messaging is disabled",
- "missing": false,
- "obsolete": false
- },
- "permission_admin": {
- "translation": "Approbar ({val})",
- "defaultMessage": "Approve ({val})",
- "description": "Name of A permission",
- "missing": false,
- "obsolete": false
- },
- "permission_delete": {
- "translation": "Eliminar ({val})",
- "defaultMessage": "Delete ({val})",
- "description": "Name of D permission",
- "missing": false,
- "obsolete": false
- },
- "permission_join": {
- "translation": "Unirse ({val})",
- "defaultMessage": "Join ({val})",
- "description": "Name of J permission",
- "missing": false,
- "obsolete": false
- },
- "permission_owner": {
- "translation": "Propietario ({val})",
- "defaultMessage": "Owner ({val})",
- "description": "Name of O permission",
- "missing": false,
- "obsolete": false
- },
- "permission_pres": {
- "translation": "Ser notificado ({val})",
- "defaultMessage": "Get notified ({val})",
- "description": "Name of P permission",
- "missing": false,
- "obsolete": false
- },
- "permission_read": {
- "translation": "Leer ({val})",
- "defaultMessage": "Read ({val})",
- "description": "Name of R permission",
- "missing": false,
- "obsolete": false
- },
- "permission_share": {
- "translation": "Compartir ({val})",
- "defaultMessage": "Share ({val})",
- "description": "Name of S permission",
- "missing": false,
- "obsolete": false
- },
- "permission_write": {
- "translation": "Escribir ({val})",
- "defaultMessage": "Write ({val})",
- "description": "Name of W permission",
- "missing": false,
- "obsolete": false
- },
- "phone_dative": {
- "translation": "teléfono",
- "defaultMessage": "phone",
- "description": "Dative case of 'phone', i.e. 'phone' in 'by phone'",
- "missing": false,
- "obsolete": false
- },
- "private_editing_placeholder": {
- "translation": "Sólo visible para tí",
- "defaultMessage": "Visible to you only",
- "description": "Placeholder for editing 'private'",
- "missing": false,
- "obsolete": false
- },
- "push_init_failed": {
- "translation": "Error al inicializar las notificaciones push",
- "defaultMessage": "Failed to initialize push notifications",
- "description": "Error message when push notifications have failed to initialize.",
- "missing": false,
- "obsolete": false
- },
- "reconnect_countdown": {
- "translation": "Desconectado. Reconectando en {seconds}…",
- "defaultMessage": "Disconnected. Reconnecting in {seconds}…",
- "description": "Message shown when an app update is available.",
- "missing": false,
- "obsolete": false
- },
- "reconnect_now": {
- "translation": "Reintentar",
- "defaultMessage": "Try now",
- "description": "Prompt for reconnecting now",
- "missing": false,
- "obsolete": false
- },
- "reload_update": {
- "translation": "Recargar",
- "defaultMessage": "Reload",
- "description": "Call to action to reload application when update is available.",
- "missing": false,
- "obsolete": false
- },
- "report_chat_warning": {
- "translation": "¿Estás seguro de que quieres bloquear y reportar a esta conversación?",
- "defaultMessage": "Are you sure you want to block and report this conversation?",
- "description": "Alert dialog warning when reporting a conversation for abuse",
- "missing": false,
- "obsolete": false
- },
- "requested_permissions": {
- "translation": "Solicitados",
- "defaultMessage": "Requested",
- "description": "Title for permissions",
- "missing": false,
- "obsolete": false
- },
- "save_attachment": {
- "translation": "guardar",
- "defaultMessage": "save",
- "description": "Call to save an attachment",
- "missing": false,
- "obsolete": false
- },
- "search_for_contacts": {
- "translation": "Usa la búsqueda para encontrar contactos",
- "defaultMessage": "Use search to find contacts",
- "description": "Text shown in contacts view when user entered no search query.",
- "missing": false,
- "obsolete": false
- },
- "search_no_results": {
- "translation": "La búsqueda no arrojó resultados",
- "defaultMessage": "Search returned no results",
- "description": "Text shown in contacts view when query returned no results.",
- "missing": false,
- "obsolete": false
- },
- "search_placeholder": {
- "translation": "Ej. email:alice@example.com, tel:17025550003...",
- "defaultMessage": "List like alice@example.com, +17025550003...",
- "description": "Placeholder in contacts search field",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_notifications": {
- "translation": "Notificaciones",
- "defaultMessage": "Notifications",
- "description": "Sidepanel title for AccNotificationsView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_support": {
- "translation": "Soporte",
- "defaultMessage": "Support",
- "description": "Sidepanel title for AccSupportView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_account_settings": {
- "translation": "Ajustes de la cuenta",
- "defaultMessage": "Account Settings",
- "description": "Sidepanel title for AccountSettingsView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_archive": {
- "translation": "Chats archivados",
- "defaultMessage": "Archived Chats",
- "description": "Sidepanel title for ContactsView-Archive.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_blocked": {
- "translation": "Chats bloqueados",
- "defaultMessage": "Blocked Chats",
- "description": "Sidepanel title for ContactsView-Blocked.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_cred": {
- "translation": "Confirmar credenciales",
- "defaultMessage": "Confirm Credentials",
- "description": "Sidepanel title for ValidationView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_login": {
- "translation": "Iniciar sesión",
- "defaultMessage": "Sign In",
- "description": "Sidepanel title for LoginView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_newtpk": {
- "translation": "Iniciar un nuevo chat",
- "defaultMessage": "Start New Chat",
- "description": "Sidepanel title for NewTopicView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_register": {
- "translation": "Crear cuenta",
- "defaultMessage": "Create Account",
- "description": "Sidepanel title for CreateAccountView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_reset": {
- "translation": "Restablecer contraseña",
- "defaultMessage": "Reset Password",
- "description": "Sidepanel title for PasswordResetView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_settings": {
- "translation": "Ajustes",
- "defaultMessage": "Settings",
- "description": "Sidepanel title for SettingsView.",
- "missing": false,
- "obsolete": false
- },
- "stay_logged_in": {
- "translation": "Permanecer conectado",
- "defaultMessage": "Stay logged in",
- "description": "Label for a checkbox",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_find_user": {
- "translation": "encontrar",
- "defaultMessage": "find",
- "description": "Tab title Find",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_group_by_id": {
- "translation": "por ID",
- "defaultMessage": "by id",
- "description": "Tab title Find topic by ID",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_new_group": {
- "translation": "nuevo grupo",
- "defaultMessage": "new group",
- "description": "Tab title New Group",
- "missing": false,
- "obsolete": false
- },
- "tags_editor_no_tags": {
- "translation": "Añadir etiquetas",
- "defaultMessage": "Add some tags",
- "description": "Tag editor prompt when no tags are found.",
- "missing": false,
- "obsolete": false
- },
- "tags_not_found": {
- "translation": "No hay etiquetas definidas. Añade unas.",
- "defaultMessage": "No tags defined. Add some.",
- "description": "",
- "missing": false,
- "obsolete": false
- },
- "title_all_contacts": {
- "translation": "Todos los contactos",
- "defaultMessage": "All Contacts",
- "description": "Section title [All Contacts]",
- "missing": false,
- "obsolete": false
- },
- "title_group_members": {
- "translation": "Miembros del grupo",
- "defaultMessage": "Group Members",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "title_manage_tags": {
- "translation": "Gestionar",
- "defaultMessage": "Manage",
- "description": "Section title for the list of tags",
- "missing": false,
- "obsolete": false
- },
- "title_not_found": {
- "translation": "No encontrado",
- "defaultMessage": "Not found",
- "description": "Title shown when topic is not found",
- "missing": false,
- "obsolete": false
- },
- "title_permissions": {
- "translation": "Permisos",
- "defaultMessage": "Permissions",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "title_tag_manager": {
- "translation": "Etiquetas (descubrimiento de usuarios)",
- "defaultMessage": "Tags (search & discovery)",
- "description": "Section title for TagManager",
- "missing": false,
- "obsolete": false
- },
- "topic_block_warning": {
- "translation": "¿Estás seguro de que quieres bloquear esta conversación",
- "defaultMessage": "Are you sure you want to block this conversation?",
- "description": "Alert warning when blocking a topic.",
- "missing": false,
- "obsolete": false
- },
- "topic_delete_warning": {
- "translation": "¿Estás seguro de que quieres eliminar esta conversación?",
- "defaultMessage": "Are you sure you want to delete this conversation? It cannot be undone.",
- "description": "Alert warning when deleting entire topic",
- "missing": false,
- "obsolete": false
- },
- "topic_name_editing_placeholder": {
- "translation": "Nombre del grupo",
- "defaultMessage": "Freeform name of the group",
- "description": "Prompt for entering topic name",
- "missing": false,
- "obsolete": false
- },
- "unnamed_topic": {
- "translation": "Sin nombre",
- "defaultMessage": "Unnamed",
- "description": "Title shown when the topic has no name",
- "missing": false,
- "obsolete": false
- },
- "update_available": {
- "translation": "Actualización disponible.",
- "defaultMessage": "Update available.",
- "description": "Message shown when an app update is available.",
- "missing": false,
- "obsolete": false
- },
- "upload_finishing": {
- "translation": "terminando...",
- "defaultMessage": "finishing...",
- "description": "Notification that upload is finishing",
- "missing": false,
- "obsolete": false
- },
- "user_not_found": {
- "translation": "Usuario no encontrado",
- "defaultMessage": "Not found",
- "description": "In place of a user's full name when the user is not found.",
- "missing": false,
- "obsolete": false
- },
- "description_editing_placeholder": {
- "translation": "Descripción (opcional)",
- "defaultMessage": "Description (optional)",
- "description": "Placeholder for editing topic description",
- "missing": false,
- "obsolete": false
- },
- "label_description": {
- "translation": "Descripción",
- "defaultMessage": "Description",
- "description": "Label for description of user or topic",
- "missing": false,
- "obsolete": false
- },
- "button_security": {
- "translation": "Seguridad",
- "defaultMessage": "Security",
- "description": "Navigaton button for security panel.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_crop": {
- "translation": "Arrastra para ajustar",
- "defaultMessage": "Drag to Adjust",
- "description": "Title for AvatarCropView.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_general": {
- "translation": "General",
- "defaultMessage": "General",
- "description": "Title for TopicCommon.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_members": {
- "translation": "Miembros",
- "defaultMessage": "Members",
- "description": "Title for managing group members view.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_security": {
- "translation": "Seguridad",
- "defaultMessage": "Security",
- "description": "Title for TopicSecirity and AccSecurity.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_info": {
- "translation": "Información",
- "defaultMessage": "Info",
- "description": "Title for InfoView",
- "missing": false,
- "obsolete": false
- },
- "permissions_anonymous": {
- "translation": "Anónimo",
- "defaultMessage": "Anonymous",
- "description": "Title for editing default anonymous permissions",
- "missing": false,
- "obsolete": false
- },
- "permissions_authenticated": {
- "translation": "Autenticado",
- "defaultMessage": "Authenticated",
- "description": "Title for editing default authenticated permissions",
- "missing": false,
- "obsolete": false
- },
- "topic_delete": {
- "translation": "Eliminar chat",
- "defaultMessage": "Delete Conversation",
- "description": "Alert title when deleting the topic.",
- "missing": false,
- "obsolete": false
- },
- "permissions_user": {
- "translation": "Permisos del usuario",
- "defaultMessage": "User's Permissions",
- "description": "Title for editing user's permissions",
- "missing": false,
- "obsolete": false
- },
- "password_reset_email_sent": {
- "translation": "Se ha enviado un correo electrónico con el código de seguridad.",
- "defaultMessage": "An email with security code has been sent.",
- "description": "Notification that the email with password reset instructions has been sent",
- "missing": false,
- "obsolete": false
- },
- "label_unarchive_topic": {
- "translation": "Archivado:",
- "defaultMessage": "Archived:",
- "description": "Label for unarchiving the topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_reply": {
- "translation": "Respuesta",
- "defaultMessage": "Reply",
- "description": "Reply to message",
- "missing": false,
- "obsolete": false
- },
- "menu_item_forward": {
- "translation": "Reenviar",
- "defaultMessage": "Forward",
- "description": "Forward message",
- "missing": false,
- "obsolete": false
- },
- "forward_to": {
- "translation": "Reenviar a",
- "defaultMessage": "Forward to",
- "missing": false,
- "obsolete": false
- },
- "forward_to_search_placeholder": {
- "translation": "Buscar contactos",
- "defaultMessage": "Search contacts",
- "description": "Contact search prompt when forwarding a message.",
- "missing": false,
- "obsolete": false
- },
- "label_new_password": {
- "translation": "Nueva contraseña",
- "defaultMessage": "New password",
- "description": "Label for entering a new password",
- "missing": false,
- "obsolete": false
- },
- "drafty_unknown": {
- "translation": "No soportado",
- "defaultMessage": "Unsupported",
- "description": "Unsupported entity in drafty",
- "missing": false,
- "obsolete": false
- },
- "calls_incoming": {
- "translation": "Llamada entrante",
- "defaultMessage": "Incoming call",
- "description": "Incoming call label",
- "missing": false,
- "obsolete": false
- },
- "calls_outgoing": {
- "translation": "Llamada saliente",
- "defaultMessage": "Outgoing call",
- "description": "Outgoing call label",
- "missing": false,
- "obsolete": false
- },
- "calls_you_label": {
- "translation": "Tú",
- "defaultMessage": "You",
- "description": "Shown over the local video screen",
- "missing": false,
- "obsolete": false
- },
- "menu_item_video_call": {
- "translation": "Videollamada",
- "defaultMessage": "Video call",
- "description": "Start video call",
- "missing": false,
- "obsolete": false
- },
- "already_in_call": {
- "translation": "¡Ya estás hablando!",
- "defaultMessage": "You already in an ongoing call!",
- "description": "Error message when the user tried to accept a new call without finishing pervious one",
- "missing": false,
- "obsolete": false
- },
- "call_cancelled": {
- "translation": "cancelada",
- "defaultMessage": "cancelled",
- "description": "Label for cancelled outgoing call",
- "missing": false,
- "obsolete": false
- },
- "call_missed": {
- "translation": "perdida",
- "defaultMessage": "missed",
- "description": "Label for missed incoming call",
- "missing": false,
- "obsolete": false
- },
- "call_declined": {
- "translation": "rechazada",
- "defaultMessage": "declined",
- "description": "Label for declined call",
- "missing": false,
- "obsolete": false
- },
- "call_disconnected": {
- "translation": "desconectada",
- "defaultMessage": "disconnected",
- "description": "Label for disconnected call",
- "missing": false,
- "obsolete": false
- },
- "label_use_secure_connection": {
- "translation": "Usar conexión segura",
- "defaultMessage": "Use secure connection",
- "description": "Label for WS/WSS connection type in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "cred_confirmed_successfully": {
- "translation": "Confirmado con éxito",
- "defaultMessage": "Confirmed successfully",
- "description": "Notification message that the credential was successfully validated.",
- "missing": false,
- "obsolete": false
- },
- "menu_item_edit": {
- "translation": "Editar",
- "defaultMessage": "Edit",
- "description": "Edit message",
- "missing": false,
- "obsolete": false
- },
- "editing_message": {
- "translation": "Edición",
- "defaultMessage": "Editing",
- "description": "Title over message editing preview",
- "missing": false,
- "obsolete": false
- },
- "message_edited_marker": {
- "translation": ", editado",
- "defaultMessage": ", edited",
- "description": "Marker indicating that the message was edited",
- "missing": false,
- "obsolete": false
- },
- "icon_title_add_image": {
- "translation": "Añadir imagen",
- "defaultMessage": "Add image",
- "description": "Icon tool tip for attaching an image",
- "missing": false,
- "obsolete": false
- },
- "icon_title_attach_file": {
- "translation": "Adjuntar archivo",
- "defaultMessage": "Attach file",
- "description": "Icon tool tip for attaching a file",
- "missing": false,
- "obsolete": false
- },
- "icon_title_delete": {
- "translation": "Eliminar grabación",
- "defaultMessage": "Delete recording",
- "description": "Icon tool tip for deleting recorded audio",
- "missing": false,
- "obsolete": false
- },
- "icon_title_pause": {
- "translation": "Pausar reproducción",
- "defaultMessage": "Pause playback",
- "description": "Icon tool tip for pausing audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_play": {
- "translation": "Reproducir grabación",
- "defaultMessage": "Play recording",
- "description": "Icon tool tip for starting audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_record_voice": {
- "translation": "Grabar mensaje de voz",
- "defaultMessage": "Record voice message",
- "description": "Icon tool tip for recording a voice message",
- "missing": false,
- "obsolete": false
- },
- "icon_title_resume": {
- "translation": "Reanudar la reproducción",
- "defaultMessage": "Resume playback",
- "description": "Icon tool tip for resuming audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_send": {
- "translation": "Enviar mensaje",
- "defaultMessage": "Send message",
- "description": "Icon tool tip for sending a message",
- "missing": false,
- "obsolete": false
- },
- "drag_file": {
- "translation": "Arrastra el archivo aquí",
- "defaultMessage": "Drag file here",
- "description": "Prompt on the file drag-n-drop overlay banner",
- "missing": false,
- "obsolete": false
- },
- "drafty_video": {
- "translation": "Grabación de vídeo",
- "defaultMessage": "Video recording",
- "description": "Comment for videos embedded in Drafty",
- "missing": false,
- "obsolete": false
- },
- "call_in_progress": {
- "translation": "en progreso",
- "defaultMessage": "in progress",
- "description": "Label for call in progress",
- "missing": false,
- "obsolete": false
- },
- "menu_item_audio_call": {
- "translation": "Llamada",
- "defaultMessage": "Call",
- "description": "Start audio call",
- "missing": false,
- "obsolete": false
- },
- "unrecognized_video_format": {
- "translation": "No se reconoce el formato de este video",
- "defaultMessage": "Format of this video is not recognized",
- "description": "Error message when uploaded video is invalid",
- "missing": false,
- "obsolete": false
- },
- "loading_note": {
- "translation": "Cargando...",
- "defaultMessage": "Loading...",
- "description": "Message shown when component is loading",
- "missing": false,
- "obsolete": false
- },
- "password_i_have_code": {
- "translation": "Tengo código",
- "defaultMessage": "I have code",
- "description": "Call to open field to enter password reset code",
- "missing": false,
- "obsolete": false
- },
- "label_reset_password_tel": {
- "translation": "Enviar SMS para restablecer la contraseña",
- "defaultMessage": "Send SMS to reset password",
- "description": "Label for password reset field by phone",
- "missing": false,
- "obsolete": false
- },
- "mobile_phone_number": {
- "translation": "Número de teléfono móvil",
- "defaultMessage": "Mobile phone number",
- "description": "Prompt for entering a mobile phone number",
- "missing": false,
- "obsolete": false
- },
- "password_reset_sms_sent": {
- "translation": "Se ha enviado un mensaje de texto con un código de seguridad.",
- "defaultMessage": "A text message with security code has been sent.",
- "description": "Notification that the SMS with password reset instructions has been sent",
- "missing": false,
- "obsolete": false
- },
- "mobile_number_required": {
- "translation": "Número de teléfono móvil requerido",
- "defaultMessage": "Mobile phone number required",
- "description": "Error message",
- "missing": false,
- "obsolete": false
- },
- "current_email": {
- "translation": "Correo electrónico actual",
- "defaultMessage": "Current email",
- "description": "Label for old email",
- "missing": false,
- "obsolete": false
- },
- "current_phone": {
- "translation": "Número de teléfono actual",
- "defaultMessage": "Current phone number",
- "description": "Label for old phone",
- "missing": false,
- "obsolete": false
- },
- "new_email": {
- "translation": "Nuevo correo electrónico",
- "defaultMessage": "New email",
- "description": "Prompt for entering a new email",
- "missing": false,
- "obsolete": false
- },
- "new_phone_number": {
- "translation": "Nuevo número de teléfono",
- "defaultMessage": "New phone number",
- "description": "Prompt for entering a new telephone number",
- "missing": false,
- "obsolete": false
- },
- "change_email": {
- "translation": "Cambiar correo electrónico",
- "defaultMessage": "Change email",
- "description": "Prompt to change current email",
- "missing": false,
- "obsolete": false
- },
- "change_phone": {
- "translation": "Cambiar número de teléfono",
- "defaultMessage": "Change phone number",
- "description": "Prompt to change current phone number",
- "missing": false,
- "obsolete": false
- },
- "call_busy": {
- "translation": "ocupado",
- "defaultMessage": "busy",
- "description": "Label for busy call line",
- "missing": false,
- "obsolete": false
- },
- "pin_message": {
- "translation": "Fijar",
- "defaultMessage": "Pin",
- "description": "Menu item [Pin] for pinning message to the top.",
- "missing": false,
- "obsolete": false
- },
- "unpin_message": {
- "translation": "Desanclar",
- "defaultMessage": "Unpin",
- "description": "Menu item [Unpin] for un-pinning the message.",
- "missing": false,
- "obsolete": false
- },
- "failed_to_init_audio": {
- "translation": "Error al inicializar la grabación de audio",
- "defaultMessage": "Failed to initialize audio recording",
- "description": "Error message when audio is not available",
- "missing": false,
- "obsolete": false
- },
- "password_reset_success": {
- "translation": "Restablecimiento de contraseña con éxito",
- "defaultMessage": "Password reset successfully",
- "description": "Notification message that the password was successfully reset.",
- "missing": false,
- "obsolete": false
- },
- "scan_qr_code": {
- "translation": "Escanear código QR",
- "defaultMessage": "Scan QR Code",
- "description": "Title for scanning QR code",
- "missing": false,
- "obsolete": false
- },
- "self_topic_comment": {
- "translation": "Notas, mensajes, enlaces, archivos guardados para la posteridad",
- "defaultMessage": "Notes, messages, links, files saved for posterity",
- "description": "Comment for self topic for UI",
- "missing": false,
- "obsolete": false
- },
- "self_topic_name": {
- "translation": "Mensajes guardados",
- "defaultMessage": "Saved messages",
- "description": "Name of self topic for UI",
- "missing": false,
- "obsolete": false
- },
- "message_not_found": {
- "translation": "Mensaje no encontrado",
- "defaultMessage": "message not found",
- "description": "Pinned message is not found",
- "missing": false,
- "obsolete": false
- },
- "message_deleted": {
- "translation": "Mensaje eliminado",
- "defaultMessage": "message deleted",
- "description": "Pinned message is deleted",
- "missing": false,
- "obsolete": false
- },
- "alias_editing_placeholder": {
- "translation": "Alias (opcional)",
- "defaultMessage": "Alias (optional)",
- "description": "Placeholder for editing user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "label_alias": {
- "translation": "Alias:",
- "defaultMessage": "Alias:",
- "description": "Label for user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "alias_already_taken": {
- "translation": "(ya tomado)",
- "defaultMessage": "(already taken)",
- "description": "Error message for alias already taken",
- "missing": false,
- "obsolete": false
- },
- "alias_invalid": {
- "translation": "(inválido)",
- "defaultMessage": "(invalid)",
- "description": "Error message for invalid alias",
- "missing": false,
- "obsolete": false
- },
- "label_alias_edit": {
- "translation": "Alias",
- "defaultMessage": "Alias",
- "description": "Label for editing user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "select_country": {
- "translation": "Seleccionar país",
- "defaultMessage": "Select country",
- "description": "Placeholder for the country selector",
- "missing": false,
- "obsolete": false
- },
- "text_copied": {
- "translation": "Copiado al portapapeles",
- "defaultMessage": "Copied to clipboard",
- "description": "Notification that text has been copied to clipboard",
- "missing": false,
- "obsolete": false
- },
- "color_schema_dark": {
- "translation": "Oscuro",
- "defaultMessage": "Dark",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "color_schema_light": {
- "translation": "Claro",
- "defaultMessage": "Light",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "color_schema_system": {
- "translation": "Predeterminado del sistema",
- "defaultMessage": "System default",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "label_color_schema": {
- "translation": "Tema:",
- "defaultMessage": "Theme:",
- "description": "Label for selecting color scheme (dark, light) in Settings",
- "missing": false,
- "obsolete": false
- },
- "label_text_size": {
- "translation": "Tamaño del texto:",
- "defaultMessage": "Text size:",
- "description": "Label adjusting text size",
- "missing": false,
- "obsolete": false
- },
- "incognito_mode_description": {
- "translation": "Ocultar tu estado en línea e indicadores de escritura.",
- "defaultMessage": "Hide your online status and typing indicators.",
- "description": "Description for incognito mode toggle",
- "missing": false,
- "obsolete": false
- },
- "send_command": {
- "translation": "Enviar con {key}",
- "defaultMessage": "Send on {key}",
- "description": "Config option to send message on hitting CTRL(or Cmd)-Enter",
- "missing": false,
- "obsolete": false
- },
- "send_command_explained": {
- "translation": "Presiona Enter para nueva línea",
- "defaultMessage": "Press Enter for new line",
- "description": "Explanation how to enter newline when [Send on CTRL-Enter] is enabled",
- "missing": false,
- "obsolete": false
- },
- "send_plain": {
- "translation": "Enviar con Enter",
- "defaultMessage": "Send on Enter",
- "description": "Config option to send message on hitting Enter",
- "missing": false,
- "obsolete": false
- },
- "send_plain_explained": {
- "translation": "Presiona Shift + Enter para nueva línea",
- "defaultMessage": "Press Shift + Enter for new line",
- "description": "Explanation how to enter newline when [Send on Enter] is enabled",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_general": {
- "translation": "General",
- "defaultMessage": "General",
- "description": "Sidepanel title for AccGeneralView.",
- "missing": false,
- "obsolete": false
- },
- "label_keyboard": {
- "translation": "Teclado:",
- "defaultMessage": "Keyboard:",
- "description": "Label for send on enter settings",
- "missing": false,
- "obsolete": false
- },
- "pin_chat": {
- "translation": "Fijar",
- "defaultMessage": "Pin",
- "description": "Menu item [Pin] for pinning chat to the top.",
- "missing": false,
- "obsolete": false
- },
- "unpin_chat": {
- "translation": "Desfijar",
- "defaultMessage": "Unpin",
- "description": "Menu item [Unpin] for un-pinning the chat.",
- "missing": false,
- "obsolete": false
- },
- "subscriber_count": {
- "translation": "{count, plural, one {{count, number} suscriptor} other {{count, number} suscriptores}}",
- "defaultMessage": "{count, plural, one {{count, number} subscriber} two {{count, number} subscribers} three {{count, number} subscribers} few {{count, number} subscribers} many {{count, number} subscribers} other {{count, number} subscribers}}",
- "description": "Count of channel subscribers",
- "missing": false,
- "obsolete": false
- },
- "label_member_count": {
- "translation": "Miembros:",
- "defaultMessage": "Members:",
- "description": "Label for the count of group topic members",
- "missing": false,
- "obsolete": false
- },
- "label_subscriber_count": {
- "translation": "Suscriptores:",
- "defaultMessage": "Subscribers:",
- "description": "Label for the count of channel subscribers",
- "missing": false,
- "obsolete": false
- },
- "member_count": {
- "translation": "{count, plural, one {{count, number} miembro} other {{count, number} miembros}}",
- "defaultMessage": "{count, plural, one {{count, number} member} two {{count, number} members} three {{count, number} members} few {{count, number} members} many {{count, number} members} other {{count, number} members}}",
- "description": "Count of group topic members",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_image": {
- "translation": "imagen",
- "defaultMessage": "image",
- "description": "Tab title Image",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_pattern": {
- "translation": "patrón",
- "defaultMessage": "pattern",
- "description": "Tab title Pattern",
- "missing": false,
- "obsolete": false
- },
- "button_restore": {
- "translation": "Restaurar predeterminado",
- "defaultMessage": "Restore default",
- "description": "Reset setting to default",
- "missing": false,
- "obsolete": false
- },
- "wallpapers": {
- "translation": "Fondos de pantalla",
- "defaultMessage": "Wallpapers",
- "description": "Link or title for working with wallpapers.",
- "missing": false,
- "obsolete": false
- },
- "label_blur_wallpaper": {
- "translation": "Desenfoque:",
- "defaultMessage": "Blur:",
- "description": "Label adjusting blur amount",
- "missing": false,
- "obsolete": false
- },
- "label_scan_id": {
- "translation": "Escanear mi ID:",
- "defaultMessage": "Scan my ID:",
- "description": "Label [Scan my ID:]",
- "missing": false,
- "obsolete": false
- },
- "unknown_name": {
- "translation": "Desconocido",
- "defaultMessage": "Unknown",
- "description": "Name to show when the name is missing",
- "missing": false,
- "obsolete": false
- },
- "save_action": {
- "translation": "Guardar",
- "defaultMessage": "Save",
- "description": "Label for Save button",
- "missing": false,
- "obsolete": false
- },
- "cannot_parse_vcard": {
- "translation": "No se puede analizar el archivo vCard.",
- "defaultMessage": "Cannot parse vCard file.",
- "description": "Error message when vCard file cannot be parsed",
- "missing": false,
- "obsolete": false
- },
- "chat_now": {
- "translation": "Chatear",
- "defaultMessage": "Chat",
- "description": "Label for message button in contact card",
- "missing": false,
- "obsolete": false
- },
- "find_user": {
- "translation": "Buscar",
- "defaultMessage": "Find",
- "description": "Label for search button in contact card",
- "missing": false,
- "obsolete": false
- },
- "label_contact_card": {
- "translation": "Tarjeta de contacto",
- "defaultMessage": "Contact Card",
- "description": "Label for contact card preview",
- "missing": false,
- "obsolete": false
- },
- "label_contacts": {
- "translation": "Contactos",
- "defaultMessage": "Contacts",
- "description": "Label for contact information",
- "missing": false,
- "obsolete": false
- },
- "add_members_prompt": {
- "translation": "añadir miembros",
- "defaultMessage": "add members",
- "description": "Placeholder in group members input field",
- "missing": false,
- "obsolete": false
- }
-}
diff --git a/src/i18n/ex/base-en.json b/src/i18n/ex/base-en.json
deleted file mode 100644
index 326e3915..00000000
--- a/src/i18n/ex/base-en.json
+++ /dev/null
@@ -1,1123 +0,0 @@
-{
- "action_block_contact": {
- "defaultMessage": "Block Contact",
- "description": "Flat button [Block Contact]"
- },
- "action_cancel": {
- "defaultMessage": "cancel",
- "description": "Call to action [cancel]"
- },
- "action_clear_messages": {
- "defaultMessage": "Clear Messages",
- "description": "Flat button [Clear Messages] (soft-delete messages)"
- },
- "action_delete_messages": {
- "defaultMessage": "Clear Messages for All",
- "description": "Flat button [Clear for All] (hard-delete all messages)"
- },
- "action_leave_chat": {
- "defaultMessage": "Leave Conversation",
- "description": "Flat button [Leave Conversation]"
- },
- "action_report_chat": {
- "defaultMessage": "Report Conversation",
- "description": "Flat button [Report Group]"
- },
- "add_members_prompt": {
- "defaultMessage": "add members",
- "description": "Placeholder in group members input field"
- },
- "alias_already_taken": {
- "defaultMessage": "(already taken)",
- "description": "Error message for alias already taken"
- },
- "alias_editing_placeholder": {
- "defaultMessage": "Alias (optional)",
- "description": "Placeholder for editing user or topic alias"
- },
- "alias_invalid": {
- "defaultMessage": "(invalid)",
- "description": "Error message for invalid alias"
- },
- "already_in_call": {
- "defaultMessage": "You already in an ongoing call!",
- "description": "Error message when the user tried to accept a new call without finishing pervious one"
- },
- "archived_contacts": {
- "defaultMessage": "Archived contacts ({count})",
- "description": "Label for archived chats"
- },
- "badge_danger": {
- "defaultMessage": "Untrustworthy",
- "description": "Suspicious or untrustworthy account or topic badge"
- },
- "badge_owner": {
- "defaultMessage": "owner",
- "description": "Badge for indicating the owner"
- },
- "badge_staff": {
- "defaultMessage": "Staff-managed",
- "description": "Explanation of a staff-managed account or topic badge"
- },
- "badge_verified": {
- "defaultMessage": "Verified/official",
- "description": "Explanation of a verified account or topic badge"
- },
- "badge_you": {
- "defaultMessage": "you",
- "description": "Badge for indicating the current user"
- },
- "block_contact_warning": {
- "defaultMessage": "Are you sure you want to block this contact?",
- "description": "Alert dialog warning when blocking a contact."
- },
- "blocked_contacts_link": {
- "defaultMessage": "Blocked contacts ({count})",
- "description": "Blocked contacts link"
- },
- "button_add_members": {
- "defaultMessage": "Add members",
- "description": "Flat button [Add members] (to topic)"
- },
- "button_cancel": {
- "defaultMessage": "Cancel",
- "description": "Button [Cancel]"
- },
- "button_confirm": {
- "defaultMessage": "Confirm",
- "description": "Button [Confirm]"
- },
- "button_create": {
- "defaultMessage": "Create",
- "description": "Button [Create]"
- },
- "button_delete_account": {
- "defaultMessage": "Delete account",
- "description": "Button [Delete account]"
- },
- "button_edit": {
- "defaultMessage": "Edit",
- "description": "Call to action [Edit]"
- },
- "button_logout": {
- "defaultMessage": "Logout",
- "description": "Button [Logout]"
- },
- "button_ok": {
- "defaultMessage": "OK",
- "description": "Button [OK]"
- },
- "button_reset": {
- "defaultMessage": "Reset",
- "description": "Button [Reset]"
- },
- "button_restore": {
- "defaultMessage": "Restore default",
- "description": "Reset setting to default"
- },
- "button_security": {
- "defaultMessage": "Security",
- "description": "Navigaton button for security panel."
- },
- "button_send_request": {
- "defaultMessage": "Send request",
- "description": "Button [Send request]"
- },
- "button_sign_in": {
- "defaultMessage": "Sign in",
- "description": "Button [Sign In]"
- },
- "button_sign_up": {
- "defaultMessage": "Sign up",
- "description": "Create account button [Sign Up]"
- },
- "button_subscribe": {
- "defaultMessage": "Subscribe",
- "description": "Button [Subscribe]"
- },
- "button_update": {
- "defaultMessage": "Update",
- "description": "Button [Update]"
- },
- "call_busy": {
- "defaultMessage": "busy",
- "description": "Label for busy call line"
- },
- "call_cancelled": {
- "defaultMessage": "cancelled",
- "description": "Label for cancelled outgoing call"
- },
- "call_declined": {
- "defaultMessage": "declined",
- "description": "Label for declined call"
- },
- "call_disconnected": {
- "defaultMessage": "disconnected",
- "description": "Label for disconnected call"
- },
- "call_in_progress": {
- "defaultMessage": "in progress",
- "description": "Label for call in progress"
- },
- "call_missed": {
- "defaultMessage": "missed",
- "description": "Label for missed incoming call"
- },
- "calls_incoming": {
- "defaultMessage": "Incoming call",
- "description": "Incoming call label"
- },
- "calls_outgoing": {
- "defaultMessage": "Outgoing call",
- "description": "Outgoing call label"
- },
- "calls_you_label": {
- "defaultMessage": "You",
- "description": "Shown over the local video screen"
- },
- "cannot_initiate_file_upload": {
- "defaultMessage": "Cannot initiate file upload.",
- "description": "Generic error messagewhen attachment fails"
- },
- "cannot_parse_vcard": {
- "defaultMessage": "Cannot parse vCard file.",
- "description": "Error message when vCard file cannot be parsed"
- },
- "change_email": {
- "defaultMessage": "Change email",
- "description": "Prompt to change current email"
- },
- "change_phone": {
- "defaultMessage": "Change phone number",
- "description": "Prompt to change current phone number"
- },
- "channel": {
- "defaultMessage": "channel",
- "description": "Subtitle shown for channels in MessagesView instead of last seen"
- },
- "channel_prompt": {
- "defaultMessage": "This is a channel",
- "description": "Checkbox label when creating a channel"
- },
- "chat_invitation": {
- "defaultMessage": "You are invited to start a new chat. What would you like to do?",
- "description": "New chat invitation message: [Accept] [Ignore] [Block]."
- },
- "chat_invitation_accept": {
- "defaultMessage": "Accept",
- "description": "Action [Accept] for chat invitation."
- },
- "chat_invitation_block": {
- "defaultMessage": "Block",
- "description": "Action [Block] for chat invitation."
- },
- "chat_invitation_ignore": {
- "defaultMessage": "Ignore",
- "description": "Action [Ignore] for chat invitation."
- },
- "chat_now": {
- "defaultMessage": "Chat",
- "description": "Label for message button in contact card"
- },
- "clear_messages_warning": {
- "defaultMessage": "Are you sure you want to clear all messages? It cannot be undone.",
- "description": "Alert dialog warning when deleting all messages."
- },
- "code_doesnot_match": {
- "defaultMessage": "Code does not match",
- "description": "Error message when the credential validation code is incorrect."
- },
- "color_schema_dark": {
- "defaultMessage": "Dark",
- "description": "Name of the color schema"
- },
- "color_schema_light": {
- "defaultMessage": "Light",
- "description": "Name of the color schema"
- },
- "color_schema_system": {
- "defaultMessage": "System default",
- "description": "Name of the color schema"
- },
- "contacts_not_found": {
- "defaultMessage": "You have no chats\\n¯∖_(ツ)_/¯",
- "description": "HTML message shown in ContactList when no contacts are found"
- },
- "contacts_not_found_short": {
- "defaultMessage": "No contacts match ''{query}''",
- "description": "Shown in ContactsView when search returned no results"
- },
- "cred_confirmed_successfully": {
- "defaultMessage": "Confirmed successfully",
- "description": "Notification message that the credential was successfully validated."
- },
- "credential_email_prompt": {
- "defaultMessage": "Your registration email",
- "description": "Placeholder for entering email"
- },
- "current_email": {
- "defaultMessage": "Current email",
- "description": "Label for old email"
- },
- "current_phone": {
- "defaultMessage": "Current phone number",
- "description": "Label for old phone"
- },
- "delete_account": {
- "defaultMessage": "Delete account",
- "description": "Title for delete account warning"
- },
- "delete_account_warning": {
- "defaultMessage": "Are you sure you want to delete your account? It cannot be undone.",
- "description": "Warning message when deleting an account"
- },
- "delete_messages_warning": {
- "defaultMessage": "Are you sure you want to delete all messages for everyone? It cannot be undone.",
- "description": "Alert dialog warning when hard-deleting all messages."
- },
- "description_editing_placeholder": {
- "defaultMessage": "Description (optional)",
- "description": "Placeholder for editing topic description"
- },
- "download_action": {
- "defaultMessage": "download",
- "description": "Call to action [download]"
- },
- "drafty_attachment": {
- "defaultMessage": "Attachment",
- "description": "Comment for attachment in Drafty"
- },
- "drafty_form": {
- "defaultMessage": "Form:",
- "description": "Comment for form in Drafty"
- },
- "drafty_image": {
- "defaultMessage": "Picture",
- "description": "Comment for embedded images in Drafty"
- },
- "drafty_unknown": {
- "defaultMessage": "Unsupported",
- "description": "Unsupported entity in drafty"
- },
- "drafty_video": {
- "defaultMessage": "Video recording",
- "description": "Comment for videos embedded in Drafty"
- },
- "drag_file": {
- "defaultMessage": "Drag file here",
- "description": "Prompt on the file drag-n-drop overlay banner"
- },
- "editing_message": {
- "defaultMessage": "Editing",
- "description": "Title over message editing preview"
- },
- "email_dative": {
- "defaultMessage": "email",
- "description": "Dative case of 'email', i.e. 'email' in 'by email'"
- },
- "email_prompt": {
- "defaultMessage": "Email, e.g. jdoe@example.com",
- "description": "Input placeholder for email entry"
- },
- "enable_peers_messaging": {
- "defaultMessage": "Enable",
- "description": "Call to action to enable peer's messaging"
- },
- "enter_confirmation_code_prompt": {
- "defaultMessage": "Confirmation code",
- "description": "Request to enter confirmation code"
- },
- "error_invalid_id": {
- "defaultMessage": "Invalid ID",
- "description": "Error message"
- },
- "failed_to_init_audio": {
- "defaultMessage": "Failed to initialize audio recording",
- "description": "Error message when audio is not available"
- },
- "file_attachment_too_large": {
- "defaultMessage": "The file size {size} exceeds the {limit} limit.",
- "description": "Error message when attachment is too large"
- },
- "find_user": {
- "defaultMessage": "Find",
- "description": "Label for search button in contact card"
- },
- "forgot_password_link": {
- "defaultMessage": "Forgot password?",
- "description": "Link to Reset password form"
- },
- "forward_to": {
- "defaultMessage": "Forward to"
- },
- "forward_to_search_placeholder": {
- "defaultMessage": "Search contacts",
- "description": "Contact search prompt when forwarding a message."
- },
- "full_name_prompt": {
- "defaultMessage": "Full name, e.g. John Doe",
- "description": "Input placeholder for person's full name"
- },
- "granted_permissions": {
- "defaultMessage": "Granted",
- "description": "Title for permissions"
- },
- "group_has_no_members": {
- "defaultMessage": "No members",
- "description": "Shown in place of group members"
- },
- "group_user_id_prompt": {
- "defaultMessage": "Group or User ID",
- "description": "Prompt for entering user or group ID"
- },
- "icon_title_add_image": {
- "defaultMessage": "Add image",
- "description": "Icon tool tip for attaching an image"
- },
- "icon_title_attach_file": {
- "defaultMessage": "Attach file",
- "description": "Icon tool tip for attaching a file"
- },
- "icon_title_delete": {
- "defaultMessage": "Delete recording",
- "description": "Icon tool tip for deleting recorded audio"
- },
- "icon_title_pause": {
- "defaultMessage": "Pause playback",
- "description": "Icon tool tip for pausing audio playback"
- },
- "icon_title_play": {
- "defaultMessage": "Play recording",
- "description": "Icon tool tip for starting audio playback"
- },
- "icon_title_record_voice": {
- "defaultMessage": "Record voice message",
- "description": "Icon tool tip for recording a voice message"
- },
- "icon_title_resume": {
- "defaultMessage": "Resume playback",
- "description": "Icon tool tip for resuming audio playback"
- },
- "icon_title_send": {
- "defaultMessage": "Send message",
- "description": "Icon tool tip for sending a message"
- },
- "image_caption_prompt": {
- "defaultMessage": "Image caption",
- "description": "Prompt in SendMessage for attached image"
- },
- "incognito_mode_description": {
- "defaultMessage": "Hide your online status and typing indicators.",
- "description": "Description for incognito mode toggle"
- },
- "invalid_content": {
- "defaultMessage": "invalid content",
- "description": "Shown when the message is unreadable"
- },
- "invalid_security_token": {
- "defaultMessage": "Invalid security token",
- "description": "Error message when resetting password."
- },
- "label_alias": {
- "defaultMessage": "Alias:",
- "description": "Label for user or topic alias"
- },
- "label_alias_edit": {
- "defaultMessage": "Alias",
- "description": "Label for editing user or topic alias"
- },
- "label_blur_wallpaper": {
- "defaultMessage": "Blur:",
- "description": "Label adjusting blur amount"
- },
- "label_client": {
- "defaultMessage": "Client:",
- "description": "Label for a client version"
- },
- "label_color_schema": {
- "defaultMessage": "Theme:",
- "description": "Label for selecting color scheme (dark, light) in Settings"
- },
- "label_contact_card": {
- "defaultMessage": "Contact Card",
- "description": "Label for contact card preview"
- },
- "label_contacts": {
- "defaultMessage": "Contacts",
- "description": "Label for contact information"
- },
- "label_content_type": {
- "defaultMessage": "Content type:",
- "description": "Label for file content type (mime)"
- },
- "label_default_access_mode": {
- "defaultMessage": "Default access mode:",
- "description": "Label for default access mode"
- },
- "label_description": {
- "defaultMessage": "Description",
- "description": "Label for description of user or topic"
- },
- "label_file_name": {
- "defaultMessage": "File name:",
- "description": "Label for a file name"
- },
- "label_group_members": {
- "defaultMessage": "Group members:",
- "description": "Section title or label"
- },
- "label_incognito_mode": {
- "defaultMessage": "Incognito mode:",
- "description": "Label for incognito mode toggle"
- },
- "label_keyboard": {
- "defaultMessage": "Keyboard:",
- "description": "Label for send on enter settings"
- },
- "label_member_count": {
- "defaultMessage": "Members:",
- "description": "Label for the count of group topic members"
- },
- "label_message_sound": {
- "defaultMessage": "Message sound:",
- "description": "Label for message sounds toggle"
- },
- "label_muting_topic": {
- "defaultMessage": "Muted:",
- "description": "Label for Muting/unmuting the topic"
- },
- "label_new_password": {
- "defaultMessage": "New password",
- "description": "Label for entering a new password"
- },
- "label_other_user": {
- "defaultMessage": "Other",
- "description": "Label for the other user when the user is unnamed"
- },
- "label_password": {
- "defaultMessage": "Password",
- "description": "Label for password editing"
- },
- "label_permissions": {
- "defaultMessage": "Permissions:",
- "description": "Section title"
- },
- "label_private": {
- "defaultMessage": "Private comment",
- "description": "Label for editing 'private'"
- },
- "label_push_notifications": {
- "defaultMessage": "Notification alerts:",
- "description": "Label for push notifications switch"
- },
- "label_push_notifications_disabled": {
- "defaultMessage": "Notification alerts (requires HTTPS):",
- "description": "Label for push notifications switch"
- },
- "label_reset_password": {
- "defaultMessage": "Send a password reset email",
- "description": "Label for password reset field"
- },
- "label_reset_password_tel": {
- "defaultMessage": "Send SMS to reset password",
- "description": "Label for password reset field by phone"
- },
- "label_scan_id": {
- "defaultMessage": "Scan my ID:",
- "description": "Label [Scan my ID:]"
- },
- "label_sdk": {
- "defaultMessage": "SDK:"
- },
- "label_server": {
- "defaultMessage": "Server:",
- "description": "Label for a server version"
- },
- "label_server_address": {
- "defaultMessage": "Server address:"
- },
- "label_server_to_use": {
- "defaultMessage": "Server to use:",
- "description": "Label for server selector in SettingsView"
- },
- "label_size": {
- "defaultMessage": "Size:",
- "description": "Label for file size"
- },
- "label_subscriber_count": {
- "defaultMessage": "Subscribers:",
- "description": "Label for the count of channel subscribers"
- },
- "label_text_size": {
- "defaultMessage": "Text size:",
- "description": "Label adjusting text size"
- },
- "label_topic_name": {
- "defaultMessage": "Name",
- "description": "Label for editing topic name"
- },
- "label_unarchive_topic": {
- "defaultMessage": "Archived:",
- "description": "Label for unarchiving the topic"
- },
- "label_use_secure_connection": {
- "defaultMessage": "Use secure connection",
- "description": "Label for WS/WSS connection type in SettingsView"
- },
- "label_user_contacts": {
- "defaultMessage": "Contacts",
- "description": "Label for user contacts"
- },
- "label_user_id": {
- "defaultMessage": "ID:",
- "description": "Label for user address (ID)"
- },
- "label_wire_transport": {
- "defaultMessage": "Wire transport:",
- "description": "Label for wire transport selection in SettingsView"
- },
- "label_you": {
- "defaultMessage": "You:",
- "description": "Label for the current user"
- },
- "label_your_name": {
- "defaultMessage": "Your name",
- "description": "Label for full name editing"
- },
- "label_your_permissions": {
- "defaultMessage": "Your permissions:",
- "description": "Label for current user permissions"
- },
- "last_seen_timestamp": {
- "defaultMessage": "Last seen: {timestamp}",
- "description": "Label for the timestamp of when the user or topic was last online"
- },
- "leave_chat_warning": {
- "defaultMessage": "Are you sure you want to leave this conversation?",
- "description": "Alert dialog warning when unsubscribing from a chat."
- },
- "link_contact_us": {
- "defaultMessage": "Contact Us",
- "description": "Ancor text for contacting us by email"
- },
- "link_privacy_policy": {
- "defaultMessage": "Privacy Policy",
- "description": "Ancor text for privacy policy link"
- },
- "link_terms_of_service": {
- "defaultMessage": "Terms of Service",
- "description": "Ancor text for terms of service link"
- },
- "loading_note": {
- "defaultMessage": "Loading...",
- "description": "Message shown when component is loading"
- },
- "login_prompt": {
- "defaultMessage": "Login",
- "description": "Placeholer for username/login"
- },
- "member_count": {
- "defaultMessage": "{count, plural, one {{count, number} member} two {{count, number} members} three {{count, number} members} few {{count, number} members} many {{count, number} members} other {{count, number} members}}",
- "description": "Count of group topic members"
- },
- "menu_item_archive_topic": {
- "defaultMessage": "Archive",
- "description": "Move topic from the list of active chats to archive"
- },
- "menu_item_audio_call": {
- "defaultMessage": "Call",
- "description": "Start audio call"
- },
- "menu_item_block": {
- "defaultMessage": "Block",
- "description": "Block topic or user"
- },
- "menu_item_clear_messages": {
- "defaultMessage": "Clear messages",
- "description": "Delete all messages"
- },
- "menu_item_clear_messages_for_all": {
- "defaultMessage": "Clear for All",
- "description": "Delete all message(s) for all members"
- },
- "menu_item_delete": {
- "defaultMessage": "Delete",
- "description": "Delete selected messages"
- },
- "menu_item_delete_for_all": {
- "defaultMessage": "Delete for All",
- "description": "Delete selected message(s) for all members"
- },
- "menu_item_delete_topic": {
- "defaultMessage": "Delete",
- "description": "Delete entire topic"
- },
- "menu_item_edit": {
- "defaultMessage": "Edit",
- "description": "Edit message"
- },
- "menu_item_edit_permissions": {
- "defaultMessage": "Edit permissions",
- "description": "Menu item [Edit permissions]"
- },
- "menu_item_forward": {
- "defaultMessage": "Forward",
- "description": "Forward message"
- },
- "menu_item_info": {
- "defaultMessage": "Info",
- "description": "Show extended topic information"
- },
- "menu_item_member_delete": {
- "defaultMessage": "Remove",
- "description": "Remove user from topic"
- },
- "menu_item_mute": {
- "defaultMessage": "Mute",
- "description": "Turn off notifications"
- },
- "menu_item_reply": {
- "defaultMessage": "Reply",
- "description": "Reply to message"
- },
- "menu_item_restore_topic": {
- "defaultMessage": "Restore",
- "description": "Restore topic from archive"
- },
- "menu_item_send_retry": {
- "defaultMessage": "Retry",
- "description": "Retry sending message"
- },
- "menu_item_unblock": {
- "defaultMessage": "Unblock",
- "description": "Unblock topic or user"
- },
- "menu_item_unmute": {
- "defaultMessage": "Unmute",
- "description": "Turn notifications on"
- },
- "menu_item_video_call": {
- "defaultMessage": "Video call",
- "description": "Start video call"
- },
- "message_deleted": {
- "defaultMessage": "message deleted",
- "description": "Pinned message is deleted"
- },
- "message_edited_marker": {
- "defaultMessage": ", edited",
- "description": "Marker indicating that the message was edited"
- },
- "message_not_found": {
- "defaultMessage": "message not found",
- "description": "Pinned message is not found"
- },
- "message_sending": {
- "defaultMessage": "sending...",
- "description": "Message being sent, in place of time stamp"
- },
- "message_sending_failed": {
- "defaultMessage": "failed",
- "description": "Failed to send message, in place of time stamp"
- },
- "messages_not_readable": {
- "defaultMessage": "no access to messages",
- "description": "Message shown in topic without the read access"
- },
- "messaging_disabled_prompt": {
- "defaultMessage": "Messaging disabled",
- "description": "Prompt in SendMessage in read-only topic"
- },
- "mobile_number_required": {
- "defaultMessage": "Mobile phone number required",
- "description": "Error message"
- },
- "mobile_phone_number": {
- "defaultMessage": "Mobile phone number",
- "description": "Prompt for entering a mobile phone number"
- },
- "more_online_members": {
- "defaultMessage": "+{overflow} more",
- "description": "Shown in MessagesView title bar when the number of online subscribers exceeds MAX_ONLINE_IN_TOPIC"
- },
- "new_email": {
- "defaultMessage": "New email",
- "description": "Prompt for entering a new email"
- },
- "new_message_prompt": {
- "defaultMessage": "New message",
- "description": "Prompt in send message field"
- },
- "new_password_placeholder": {
- "defaultMessage": "Enter new password",
- "description": "Placeholder for entering new password"
- },
- "new_phone_number": {
- "defaultMessage": "New phone number",
- "description": "Prompt for entering a new telephone number"
- },
- "no_connection": {
- "defaultMessage": "No connection",
- "description": "Warning that the user is offline."
- },
- "no_contacts": {
- "defaultMessage": "You have no contacts :-(",
- "description": "Shown in ContactsView when the user has no contacts"
- },
- "numeric_confirmation_code_prompt": {
- "defaultMessage": "Numbers only",
- "description": "Prompt for numeric conformation code"
- },
- "online_now": {
- "defaultMessage": "online now",
- "description": "Indicator that the user or topic is currently online"
- },
- "panel_title_crop": {
- "defaultMessage": "Drag to Adjust",
- "description": "Title for AvatarCropView."
- },
- "panel_title_general": {
- "defaultMessage": "General",
- "description": "Title for TopicCommon."
- },
- "panel_title_info": {
- "defaultMessage": "Info",
- "description": "Title for InfoView"
- },
- "panel_title_members": {
- "defaultMessage": "Members",
- "description": "Title for managing group members view."
- },
- "panel_title_security": {
- "defaultMessage": "Security",
- "description": "Title for TopicSecirity and AccSecurity."
- },
- "password_i_have_code": {
- "defaultMessage": "I have code",
- "description": "Call to open field to enter password reset code"
- },
- "password_prompt": {
- "defaultMessage": "Password",
- "description": "Placeholder/prompt for entering password"
- },
- "password_reset_email_sent": {
- "defaultMessage": "An email with security code has been sent.",
- "description": "Notification that the email with password reset instructions has been sent"
- },
- "password_reset_sms_sent": {
- "defaultMessage": "A text message with security code has been sent.",
- "description": "Notification that the SMS with password reset instructions has been sent"
- },
- "password_reset_success": {
- "defaultMessage": "Password reset successfully",
- "description": "Notification message that the password was successfully reset."
- },
- "password_unchanged_prompt": {
- "defaultMessage": "Unchanged",
- "description": "Message in editor while password is unchanged"
- },
- "peers_messaging_disabled": {
- "defaultMessage": "Peer's messaging is disabled.",
- "description": "Shown when the p2p peer's messaging is disabled"
- },
- "permission_admin": {
- "defaultMessage": "Approve ({val})",
- "description": "Name of A permission"
- },
- "permission_delete": {
- "defaultMessage": "Delete ({val})",
- "description": "Name of D permission"
- },
- "permission_join": {
- "defaultMessage": "Join ({val})",
- "description": "Name of J permission"
- },
- "permission_owner": {
- "defaultMessage": "Owner ({val})",
- "description": "Name of O permission"
- },
- "permission_pres": {
- "defaultMessage": "Get notified ({val})",
- "description": "Name of P permission"
- },
- "permission_read": {
- "defaultMessage": "Read ({val})",
- "description": "Name of R permission"
- },
- "permission_share": {
- "defaultMessage": "Share ({val})",
- "description": "Name of S permission"
- },
- "permission_write": {
- "defaultMessage": "Write ({val})",
- "description": "Name of W permission"
- },
- "permissions_anonymous": {
- "defaultMessage": "Anonymous",
- "description": "Title for editing default anonymous permissions"
- },
- "permissions_authenticated": {
- "defaultMessage": "Authenticated",
- "description": "Title for editing default authenticated permissions"
- },
- "permissions_user": {
- "defaultMessage": "User's Permissions",
- "description": "Title for editing user's permissions"
- },
- "phone_dative": {
- "defaultMessage": "phone",
- "description": "Dative case of 'phone', i.e. 'phone' in 'by phone'"
- },
- "pin_chat": {
- "defaultMessage": "Pin",
- "description": "Menu item [Pin] for pinning chat to the top."
- },
- "pin_message": {
- "defaultMessage": "Pin",
- "description": "Menu item [Pin] for pinning message to the top."
- },
- "private_editing_placeholder": {
- "defaultMessage": "Visible to you only",
- "description": "Placeholder for editing 'private'"
- },
- "push_init_failed": {
- "defaultMessage": "Failed to initialize push notifications",
- "description": "Error message when push notifications have failed to initialize."
- },
- "reconnect_countdown": {
- "defaultMessage": "Disconnected. Reconnecting in {seconds}…",
- "description": "Message shown when an app update is available."
- },
- "reconnect_now": {
- "defaultMessage": "Try now",
- "description": "Prompt for reconnecting now"
- },
- "reload_update": {
- "defaultMessage": "Reload",
- "description": "Call to action to reload application when update is available."
- },
- "report_chat_warning": {
- "defaultMessage": "Are you sure you want to block and report this conversation?",
- "description": "Alert dialog warning when reporting a conversation for abuse"
- },
- "requested_permissions": {
- "defaultMessage": "Requested",
- "description": "Title for permissions"
- },
- "save_action": {
- "defaultMessage": "Save",
- "description": "Label for Save button"
- },
- "save_attachment": {
- "defaultMessage": "save",
- "description": "Call to save an attachment"
- },
- "scan_qr_code": {
- "defaultMessage": "Scan QR Code",
- "description": "Title for scanning QR code"
- },
- "search_for_contacts": {
- "defaultMessage": "Use search to find contacts",
- "description": "Text shown in contacts view when user entered no search query."
- },
- "search_no_results": {
- "defaultMessage": "Search returned no results",
- "description": "Text shown in contacts view when query returned no results."
- },
- "search_placeholder": {
- "defaultMessage": "List like alice@example.com, +17025550003...",
- "description": "Placeholder in contacts search field"
- },
- "select_country": {
- "defaultMessage": "Select country",
- "description": "Placeholder for the country selector"
- },
- "self_topic_comment": {
- "defaultMessage": "Notes, messages, links, files saved for posterity",
- "description": "Comment for self topic for UI"
- },
- "self_topic_name": {
- "defaultMessage": "Saved messages",
- "description": "Name of self topic for UI"
- },
- "send_command": {
- "defaultMessage": "Send on {key}",
- "description": "Config option to send message on hitting CTRL(or Cmd)-Enter"
- },
- "send_command_explained": {
- "defaultMessage": "Press Enter for new line",
- "description": "Explanation how to enter newline when [Send on CTRL-Enter] is enabled"
- },
- "send_plain": {
- "defaultMessage": "Send on Enter",
- "description": "Config option to send message on hitting Enter"
- },
- "send_plain_explained": {
- "defaultMessage": "Press Shift + Enter for new line",
- "description": "Explanation how to enter newline when [Send on Enter] is enabled"
- },
- "sidepanel_title_acc_general": {
- "defaultMessage": "General",
- "description": "Sidepanel title for AccGeneralView."
- },
- "sidepanel_title_acc_notifications": {
- "defaultMessage": "Notifications",
- "description": "Sidepanel title for AccNotificationsView."
- },
- "sidepanel_title_acc_support": {
- "defaultMessage": "Support",
- "description": "Sidepanel title for AccSupportView."
- },
- "sidepanel_title_account_settings": {
- "defaultMessage": "Account Settings",
- "description": "Sidepanel title for AccountSettingsView."
- },
- "sidepanel_title_archive": {
- "defaultMessage": "Archived Chats",
- "description": "Sidepanel title for ContactsView-Archive."
- },
- "sidepanel_title_blocked": {
- "defaultMessage": "Blocked Chats",
- "description": "Sidepanel title for ContactsView-Blocked."
- },
- "sidepanel_title_cred": {
- "defaultMessage": "Confirm Credentials",
- "description": "Sidepanel title for ValidationView."
- },
- "sidepanel_title_login": {
- "defaultMessage": "Sign In",
- "description": "Sidepanel title for LoginView."
- },
- "sidepanel_title_newtpk": {
- "defaultMessage": "Start New Chat",
- "description": "Sidepanel title for NewTopicView."
- },
- "sidepanel_title_register": {
- "defaultMessage": "Create Account",
- "description": "Sidepanel title for CreateAccountView."
- },
- "sidepanel_title_reset": {
- "defaultMessage": "Reset Password",
- "description": "Sidepanel title for PasswordResetView."
- },
- "sidepanel_title_settings": {
- "defaultMessage": "Settings",
- "description": "Sidepanel title for SettingsView."
- },
- "stay_logged_in": {
- "defaultMessage": "Stay logged in",
- "description": "Label for a checkbox"
- },
- "subscriber_count": {
- "defaultMessage": "{count, plural, one {{count, number} subscriber} two {{count, number} subscribers} three {{count, number} subscribers} few {{count, number} subscribers} many {{count, number} subscribers} other {{count, number} subscribers}}",
- "description": "Count of channel subscribers"
- },
- "tabtitle_find_user": {
- "defaultMessage": "find",
- "description": "Tab title Find"
- },
- "tabtitle_group_by_id": {
- "defaultMessage": "by id",
- "description": "Tab title Find topic by ID"
- },
- "tabtitle_image": {
- "defaultMessage": "image",
- "description": "Tab title Image"
- },
- "tabtitle_new_group": {
- "defaultMessage": "new group",
- "description": "Tab title New Group"
- },
- "tabtitle_pattern": {
- "defaultMessage": "pattern",
- "description": "Tab title Pattern"
- },
- "tags_editor_no_tags": {
- "defaultMessage": "Add some tags",
- "description": "Tag editor prompt when no tags are found."
- },
- "tags_not_found": {
- "defaultMessage": "No tags defined. Add some.",
- "description": ""
- },
- "text_copied": {
- "defaultMessage": "Copied to clipboard",
- "description": "Notification that text has been copied to clipboard"
- },
- "title_all_contacts": {
- "defaultMessage": "All Contacts",
- "description": "Section title [All Contacts]"
- },
- "title_group_members": {
- "defaultMessage": "Group Members",
- "description": "Section title"
- },
- "title_manage_tags": {
- "defaultMessage": "Manage",
- "description": "Section title for the list of tags"
- },
- "title_not_found": {
- "defaultMessage": "Not found",
- "description": "Title shown when topic is not found"
- },
- "title_permissions": {
- "defaultMessage": "Permissions",
- "description": "Section title"
- },
- "title_tag_manager": {
- "defaultMessage": "Tags (search & discovery)",
- "description": "Section title for TagManager"
- },
- "topic_block_warning": {
- "defaultMessage": "Are you sure you want to block this conversation?",
- "description": "Alert warning when blocking a topic."
- },
- "topic_delete": {
- "defaultMessage": "Delete Conversation",
- "description": "Alert title when deleting the topic."
- },
- "topic_delete_warning": {
- "defaultMessage": "Are you sure you want to delete this conversation? It cannot be undone.",
- "description": "Alert warning when deleting entire topic"
- },
- "topic_name_editing_placeholder": {
- "defaultMessage": "Freeform name of the group",
- "description": "Prompt for entering topic name"
- },
- "unknown_name": {
- "defaultMessage": "Unknown",
- "description": "Name to show when the name is missing"
- },
- "unnamed_topic": {
- "defaultMessage": "Unnamed",
- "description": "Title shown when the topic has no name"
- },
- "unpin_chat": {
- "defaultMessage": "Unpin",
- "description": "Menu item [Unpin] for un-pinning the chat."
- },
- "unpin_message": {
- "defaultMessage": "Unpin",
- "description": "Menu item [Unpin] for un-pinning the message."
- },
- "unrecognized_video_format": {
- "defaultMessage": "Format of this video is not recognized",
- "description": "Error message when uploaded video is invalid"
- },
- "update_available": {
- "defaultMessage": "Update available.",
- "description": "Message shown when an app update is available."
- },
- "upload_finishing": {
- "defaultMessage": "finishing...",
- "description": "Notification that upload is finishing"
- },
- "user_not_found": {
- "defaultMessage": "Not found",
- "description": "In place of a user's full name when the user is not found."
- },
- "wallpapers": {
- "defaultMessage": "Wallpapers",
- "description": "Link or title for working with wallpapers."
- }
-}
diff --git a/src/i18n/fr.json b/src/i18n/fr.json
deleted file mode 100644
index dd7da423..00000000
--- a/src/i18n/fr.json
+++ /dev/null
@@ -1,1966 +0,0 @@
-{
- "action_block_contact": {
- "translation": "Bloquer le Contact",
- "defaultMessage": "Block Contact",
- "description": "Flat button [Block Contact]",
- "missing": false,
- "obsolete": false
- },
- "action_cancel": {
- "translation": "annuler",
- "defaultMessage": "cancel",
- "description": "Call to action [cancel]",
- "missing": false,
- "obsolete": false
- },
- "action_clear_messages": {
- "translation": "Effacer les Messages",
- "defaultMessage": "Clear Messages",
- "description": "Flat button [Clear Messages] (soft-delete messages)",
- "missing": false,
- "obsolete": false
- },
- "action_delete_messages": {
- "translation": "Effacer les Messages pour Tous",
- "defaultMessage": "Clear Messages for All",
- "description": "Flat button [Clear for All] (hard-delete all messages)",
- "missing": false,
- "obsolete": false
- },
- "action_leave_chat": {
- "translation": "Quitter la Conversation",
- "defaultMessage": "Leave Conversation",
- "description": "Flat button [Leave Conversation]",
- "missing": false,
- "obsolete": false
- },
- "action_report_chat": {
- "translation": "Signaler la Conversation",
- "defaultMessage": "Report Conversation",
- "description": "Flat button [Report Group]",
- "missing": false,
- "obsolete": false
- },
- "archived_contacts": {
- "translation": "Contacts archivés ({count})",
- "defaultMessage": "Archived contacts ({count})",
- "description": "Label for archived chats",
- "missing": false,
- "obsolete": false
- },
- "badge_danger": {
- "translation": "Non fiable",
- "defaultMessage": "Untrustworthy",
- "description": "Suspicious or untrustworthy account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_owner": {
- "translation": "propriétaire",
- "defaultMessage": "owner",
- "description": "Badge for indicating the owner",
- "missing": false,
- "obsolete": false
- },
- "badge_staff": {
- "translation": "Géré par le Staff",
- "defaultMessage": "Staff-managed",
- "description": "Explanation of a staff-managed account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_verified": {
- "translation": "Vérifié / Officiel",
- "defaultMessage": "Verified/official",
- "description": "Explanation of a verified account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_you": {
- "translation": "vous",
- "defaultMessage": "you",
- "description": "Badge for indicating the current user",
- "missing": false,
- "obsolete": false
- },
- "block_contact_warning": {
- "translation": "Êtes-vous sûr de bloquer le contact ?",
- "defaultMessage": "Are you sure you want to block this contact?",
- "description": "Alert dialog warning when blocking a contact.",
- "missing": false,
- "obsolete": false
- },
- "blocked_contacts_link": {
- "translation": "Contacts bloqués ({count})",
- "defaultMessage": "Blocked contacts ({count})",
- "description": "Blocked contacts link",
- "missing": false,
- "obsolete": false
- },
- "button_add_members": {
- "translation": "Ajouter des membres",
- "defaultMessage": "Add members",
- "description": "Flat button [Add members] (to topic)",
- "missing": false,
- "obsolete": false
- },
- "button_cancel": {
- "translation": "Annuler",
- "defaultMessage": "Cancel",
- "description": "Button [Cancel]",
- "missing": false,
- "obsolete": false
- },
- "button_confirm": {
- "translation": "Confirmer",
- "defaultMessage": "Confirm",
- "description": "Button [Confirm]",
- "missing": false,
- "obsolete": false
- },
- "button_create": {
- "translation": "Créer",
- "defaultMessage": "Create",
- "description": "Button [Create]",
- "missing": false,
- "obsolete": false
- },
- "button_delete_account": {
- "translation": "Supprimer le compte",
- "defaultMessage": "Delete account",
- "description": "Button [Delete account]",
- "missing": false,
- "obsolete": false
- },
- "button_edit": {
- "translation": "Modifier",
- "defaultMessage": "Edit",
- "description": "Call to action [Edit]",
- "missing": false,
- "obsolete": false
- },
- "button_logout": {
- "translation": "Déconnexion",
- "defaultMessage": "Logout",
- "description": "Button [Logout]",
- "missing": false,
- "obsolete": false
- },
- "button_ok": {
- "translation": "OK",
- "defaultMessage": "OK",
- "description": "Button [OK]",
- "missing": false,
- "obsolete": false
- },
- "button_reset": {
- "translation": "Remettre à Zéro",
- "defaultMessage": "Reset",
- "description": "Button [Reset]",
- "missing": false,
- "obsolete": false
- },
- "button_send_request": {
- "translation": "Envoyer une demande",
- "defaultMessage": "Send request",
- "description": "Button [Send request]",
- "missing": false,
- "obsolete": false
- },
- "button_sign_in": {
- "translation": "Connexion",
- "defaultMessage": "Sign in",
- "description": "Button [Sign In]",
- "missing": false,
- "obsolete": false
- },
- "button_sign_up": {
- "translation": "Créer un compte",
- "defaultMessage": "Sign up",
- "description": "Create account button [Sign Up]",
- "missing": false,
- "obsolete": false
- },
- "button_subscribe": {
- "translation": "S'abonner",
- "defaultMessage": "Subscribe",
- "description": "Button [Subscribe]",
- "missing": false,
- "obsolete": false
- },
- "button_update": {
- "translation": "Mettre à jour",
- "defaultMessage": "Update",
- "description": "Button [Update]",
- "missing": false,
- "obsolete": false
- },
- "cannot_initiate_file_upload": {
- "translation": "Impossible de démarrer l'envoi.",
- "defaultMessage": "Cannot initiate file upload.",
- "description": "Generic error messagewhen attachment fails",
- "missing": false,
- "obsolete": false
- },
- "channel": {
- "translation": "canal",
- "defaultMessage": "channel",
- "description": "Subtitle shown for channels in MessagesView instead of last seen",
- "missing": false,
- "obsolete": false
- },
- "channel_prompt": {
- "translation": "C'est un canal",
- "defaultMessage": "This is a channel",
- "description": "Checkbox label when creating a channel",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation": {
- "translation": "Vous êtes invité à démarrer une conversation. Que voulez vous faire ?",
- "defaultMessage": "You are invited to start a new chat. What would you like to do?",
- "description": "New chat invitation message: [Accept] [Ignore] [Block].",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_accept": {
- "translation": "Accepter",
- "defaultMessage": "Accept",
- "description": "Action [Accept] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_block": {
- "translation": "Bloquer",
- "defaultMessage": "Block",
- "description": "Action [Block] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_ignore": {
- "translation": "Ignorer",
- "defaultMessage": "Ignore",
- "description": "Action [Ignore] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "clear_messages_warning": {
- "translation": "Êtes-vous sûr de vouloir effacer tous les messages ? Aucun retour possible.",
- "defaultMessage": "Are you sure you want to clear all messages? It cannot be undone.",
- "description": "Alert dialog warning when deleting all messages.",
- "missing": false,
- "obsolete": false
- },
- "code_doesnot_match": {
- "translation": "Le code ne correspond pas",
- "defaultMessage": "Code does not match",
- "description": "Error message when the credential validation code is incorrect.",
- "missing": false,
- "obsolete": false
- },
- "contacts_not_found": {
- "translation": "Vous n'avez aucune conversation\n¯∖_(ツ)_/¯",
- "defaultMessage": "You have no chats\\n¯∖_(ツ)_/¯",
- "description": "HTML message shown in ContactList when no contacts are found",
- "missing": false,
- "obsolete": false
- },
- "contacts_not_found_short": {
- "translation": "Aucun contact ne correspond à ''{query}''",
- "defaultMessage": "No contacts match ''{query}''",
- "description": "Shown in ContactsView when search returned no results",
- "missing": false,
- "obsolete": false
- },
- "credential_email_prompt": {
- "translation": "Votre email d'enregistrement",
- "defaultMessage": "Your registration email",
- "description": "Placeholder for entering email",
- "missing": false,
- "obsolete": false
- },
- "delete_account": {
- "translation": "Supprimer le compte",
- "defaultMessage": "Delete account",
- "description": "Title for delete account warning",
- "missing": false,
- "obsolete": false
- },
- "delete_account_warning": {
- "translation": "Êtes-vous sûr de vouloir supprimer votre compte ? Aucun retour possible.",
- "defaultMessage": "Are you sure you want to delete your account? It cannot be undone.",
- "description": "Warning message when deleting an account",
- "missing": false,
- "obsolete": false
- },
- "delete_messages_warning": {
- "translation": "Êtes-vous sûr de vouloir supprimer tous les messages pour tous ? Aucun retour possible.",
- "defaultMessage": "Are you sure you want to delete all messages for everyone? It cannot be undone.",
- "description": "Alert dialog warning when hard-deleting all messages.",
- "missing": false,
- "obsolete": false
- },
- "download_action": {
- "translation": "télécharger",
- "defaultMessage": "download",
- "description": "Call to action [download]",
- "missing": false,
- "obsolete": false
- },
- "drafty_attachment": {
- "translation": "Pièce(s) jointe(s)",
- "defaultMessage": "Attachment",
- "description": "Comment for attachment in Drafty",
- "missing": false,
- "obsolete": false
- },
- "drafty_form": {
- "translation": "Formulaire:",
- "defaultMessage": "Form:",
- "description": "Comment for form in Drafty",
- "missing": false,
- "obsolete": false
- },
- "drafty_image": {
- "translation": "Image",
- "defaultMessage": "Picture",
- "description": "Comment for embedded images in Drafty",
- "missing": false,
- "obsolete": false
- },
- "email_dative": {
- "translation": "email",
- "defaultMessage": "email",
- "description": "Dative case of 'email', i.e. 'email' in 'by email'",
- "missing": false,
- "obsolete": false
- },
- "email_prompt": {
- "translation": "Email, par exemple jdoe@exemple.com",
- "defaultMessage": "Email, e.g. jdoe@example.com",
- "description": "Input placeholder for email entry",
- "missing": false,
- "obsolete": false
- },
- "enable_peers_messaging": {
- "translation": "Activer",
- "defaultMessage": "Enable",
- "description": "Call to action to enable peer's messaging",
- "missing": false,
- "obsolete": false
- },
- "enter_confirmation_code_prompt": {
- "translation": "Entrez le code de confirmation envoyé par {method}:",
- "defaultMessage": "Confirmation code",
- "description": "Request to enter confirmation code",
- "missing": false,
- "obsolete": false
- },
- "error_invalid_id": {
- "translation": "Identifiant invalide",
- "defaultMessage": "Invalid ID",
- "description": "Error message",
- "missing": false,
- "obsolete": false
- },
- "file_attachment_too_large": {
- "translation": "Le fichier de taille {size} dépasse la limite de {limit}.",
- "defaultMessage": "The file size {size} exceeds the {limit} limit.",
- "description": "Error message when attachment is too large",
- "missing": false,
- "obsolete": false
- },
- "forgot_password_link": {
- "translation": "Mot de passe oublié ?",
- "defaultMessage": "Forgot password?",
- "description": "Link to Reset password form",
- "missing": false,
- "obsolete": false
- },
- "full_name_prompt": {
- "translation": "Nom complet, par exemple John Doe",
- "defaultMessage": "Full name, e.g. John Doe",
- "description": "Input placeholder for person's full name",
- "missing": false,
- "obsolete": false
- },
- "granted_permissions": {
- "translation": "Accordé",
- "defaultMessage": "Granted",
- "description": "Title for permissions",
- "missing": false,
- "obsolete": false
- },
- "group_has_no_members": {
- "translation": "Aucun membre",
- "defaultMessage": "No members",
- "description": "Shown in place of group members",
- "missing": false,
- "obsolete": false
- },
- "group_user_id_prompt": {
- "translation": "Identifiant d'utilisateur ou de groupe",
- "defaultMessage": "Group or User ID",
- "description": "Prompt for entering user or group ID",
- "missing": false,
- "obsolete": false
- },
- "image_caption_prompt": {
- "translation": "Description de l'image",
- "defaultMessage": "Image caption",
- "description": "Prompt in SendMessage for attached image",
- "missing": false,
- "obsolete": false
- },
- "invalid_content": {
- "translation": "contenu invalide",
- "defaultMessage": "invalid content",
- "description": "Shown when the message is unreadable",
- "missing": false,
- "obsolete": false
- },
- "invalid_security_token": {
- "translation": "Token de sécurité invalide",
- "defaultMessage": "Invalid security token",
- "description": "Error message when resetting password.",
- "missing": false,
- "obsolete": false
- },
- "label_client": {
- "translation": "Client :",
- "defaultMessage": "Client:",
- "description": "Label for a client version",
- "missing": false,
- "obsolete": false
- },
- "label_content_type": {
- "translation": "Type de contenu :",
- "defaultMessage": "Content type:",
- "description": "Label for file content type (mime)",
- "missing": false,
- "obsolete": false
- },
- "label_default_access_mode": {
- "translation": "Mode d'accès par défaut :",
- "defaultMessage": "Default access mode:",
- "description": "Label for default access mode",
- "missing": false,
- "obsolete": false
- },
- "label_file_name": {
- "translation": "Nom du fichier :",
- "defaultMessage": "File name:",
- "description": "Label for a file name",
- "missing": false,
- "obsolete": false
- },
- "label_group_members": {
- "translation": "Membres du groupe :",
- "defaultMessage": "Group members:",
- "description": "Section title or label",
- "missing": false,
- "obsolete": false
- },
- "label_incognito_mode": {
- "translation": "Mode incognito :",
- "defaultMessage": "Incognito mode:",
- "description": "Label for incognito mode toggle",
- "missing": false,
- "obsolete": false
- },
- "label_message_sound": {
- "translation": "Son du message :",
- "defaultMessage": "Message sound:",
- "description": "Label for message sounds toggle",
- "missing": false,
- "obsolete": false
- },
- "label_muting_topic": {
- "translation": "Mué :",
- "defaultMessage": "Muted:",
- "description": "Label for Muting/unmuting the topic",
- "missing": false,
- "obsolete": false
- },
- "label_other_user": {
- "translation": "Autre",
- "defaultMessage": "Other",
- "description": "Label for the other user when the user is unnamed",
- "missing": false,
- "obsolete": false
- },
- "label_password": {
- "translation": "Mot de passe",
- "defaultMessage": "Password",
- "description": "Label for password editing",
- "missing": false,
- "obsolete": false
- },
- "label_permissions": {
- "translation": "Permissions :",
- "defaultMessage": "Permissions:",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "label_private": {
- "translation": "Commentaire privé",
- "defaultMessage": "Private comment",
- "description": "Label for editing 'private'",
- "missing": false,
- "obsolete": false
- },
- "label_push_notifications": {
- "translation": "Notifications :",
- "defaultMessage": "Notification alerts:",
- "description": "Label for push notifications switch",
- "missing": false,
- "obsolete": false
- },
- "label_push_notifications_disabled": {
- "translation": "Notifications (requière HTTPS):",
- "defaultMessage": "Notification alerts (requires HTTPS):",
- "description": "Label for push notifications switch",
- "missing": false,
- "obsolete": false
- },
- "label_reset_password": {
- "translation": "Envoyer un mail de remise à zéro de mot de passe :",
- "defaultMessage": "Send a password reset email",
- "description": "Label for password reset field",
- "missing": false,
- "obsolete": false
- },
- "label_sdk": {
- "translation": "SDK :",
- "defaultMessage": "SDK:",
- "missing": false,
- "obsolete": false
- },
- "label_server": {
- "translation": "Serveur :",
- "defaultMessage": "Server:",
- "description": "Label for a server version",
- "missing": false,
- "obsolete": false
- },
- "label_server_address": {
- "translation": "Adresse du serveur :",
- "defaultMessage": "Server address:",
- "missing": false,
- "obsolete": false
- },
- "label_server_to_use": {
- "translation": "Serveur à utiliser :",
- "defaultMessage": "Server to use:",
- "description": "Label for server selector in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "label_size": {
- "translation": "Taille :",
- "defaultMessage": "Size:",
- "description": "Label for file size",
- "missing": false,
- "obsolete": false
- },
- "label_topic_name": {
- "translation": "Nom",
- "defaultMessage": "Name",
- "description": "Label for editing topic name",
- "missing": false,
- "obsolete": false
- },
- "label_user_contacts": {
- "translation": "Contacts",
- "defaultMessage": "Contacts",
- "description": "Label for user contacts",
- "missing": false,
- "obsolete": false
- },
- "label_user_id": {
- "translation": "ID :",
- "defaultMessage": "ID:",
- "description": "Label for user address (ID)",
- "missing": false,
- "obsolete": false
- },
- "label_wire_transport": {
- "translation": "Transport par cable :",
- "defaultMessage": "Wire transport:",
- "description": "Label for wire transport selection in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "label_you": {
- "translation": "Vous :",
- "defaultMessage": "You:",
- "description": "Label for the current user",
- "missing": false,
- "obsolete": false
- },
- "label_your_name": {
- "translation": "Votre nom",
- "defaultMessage": "Your name",
- "description": "Label for full name editing",
- "missing": false,
- "obsolete": false
- },
- "label_your_permissions": {
- "translation": "Vos permissions :",
- "defaultMessage": "Your permissions:",
- "description": "Label for current user permissions",
- "missing": false,
- "obsolete": false
- },
- "last_seen_timestamp": {
- "translation": "Vu pour la dernière fois",
- "defaultMessage": "Last seen: {timestamp}",
- "description": "Label for the timestamp of when the user or topic was last online",
- "missing": false,
- "obsolete": false
- },
- "leave_chat_warning": {
- "translation": "Êtes-vous sûr de vouloir quitter cette conversation ?",
- "defaultMessage": "Are you sure you want to leave this conversation?",
- "description": "Alert dialog warning when unsubscribing from a chat.",
- "missing": false,
- "obsolete": false
- },
- "link_contact_us": {
- "translation": "Contactez Nous",
- "defaultMessage": "Contact Us",
- "description": "Ancor text for contacting us by email",
- "missing": false,
- "obsolete": false
- },
- "link_privacy_policy": {
- "translation": "Politique de confidentialité",
- "defaultMessage": "Privacy Policy",
- "description": "Ancor text for privacy policy link",
- "missing": false,
- "obsolete": false
- },
- "link_terms_of_service": {
- "translation": "Conditions d'utilisation",
- "defaultMessage": "Terms of Service",
- "description": "Ancor text for terms of service link",
- "missing": false,
- "obsolete": false
- },
- "login_prompt": {
- "translation": "Connexion",
- "defaultMessage": "Login",
- "description": "Placeholer for username/login",
- "missing": false,
- "obsolete": false
- },
- "menu_item_archive_topic": {
- "translation": "Archiver",
- "defaultMessage": "Archive",
- "description": "Move topic from the list of active chats to archive",
- "missing": false,
- "obsolete": false
- },
- "menu_item_block": {
- "translation": "Bloquer",
- "defaultMessage": "Block",
- "description": "Block topic or user",
- "missing": false,
- "obsolete": false
- },
- "menu_item_clear_messages": {
- "translation": "Effacer les messages",
- "defaultMessage": "Clear messages",
- "description": "Delete all messages",
- "missing": false,
- "obsolete": false
- },
- "menu_item_clear_messages_for_all": {
- "translation": "Effacer pour Tous",
- "defaultMessage": "Clear for All",
- "description": "Delete all message(s) for all members",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete": {
- "translation": "Supprimer",
- "defaultMessage": "Delete",
- "description": "Delete selected messages",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete_for_all": {
- "translation": "Supprimer pour Tous",
- "defaultMessage": "Delete for All",
- "description": "Delete selected message(s) for all members",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete_topic": {
- "translation": "Supprimer",
- "defaultMessage": "Delete",
- "description": "Delete entire topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_edit_permissions": {
- "translation": "Modifier les permissions",
- "defaultMessage": "Edit permissions",
- "description": "Menu item [Edit permissions]",
- "missing": false,
- "obsolete": false
- },
- "menu_item_info": {
- "translation": "Informations",
- "defaultMessage": "Info",
- "description": "Show extended topic information",
- "missing": false,
- "obsolete": false
- },
- "menu_item_member_delete": {
- "translation": "Supprimer",
- "defaultMessage": "Remove",
- "description": "Remove user from topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_mute": {
- "translation": "Rendre muer",
- "defaultMessage": "Mute",
- "description": "Turn off notifications",
- "missing": false,
- "obsolete": false
- },
- "menu_item_reply": {
- "translation": "Répondre",
- "defaultMessage": "Reply",
- "description": "Reply to message",
- "missing": false,
- "obsolete": false
- },
- "menu_item_restore_topic": {
- "translation": "Restorer",
- "defaultMessage": "Restore",
- "description": "Restore topic from archive",
- "missing": false,
- "obsolete": false
- },
- "menu_item_send_retry": {
- "translation": "Réessayer",
- "defaultMessage": "Retry",
- "description": "Retry sending message",
- "missing": false,
- "obsolete": false
- },
- "menu_item_unblock": {
- "translation": "Débloquer",
- "defaultMessage": "Unblock",
- "description": "Unblock topic or user",
- "missing": false,
- "obsolete": false
- },
- "menu_item_unmute": {
- "translation": "Recevoir à nouveau",
- "defaultMessage": "Unmute",
- "description": "Turn notifications on",
- "missing": false,
- "obsolete": false
- },
- "message_sending": {
- "translation": "envoi en cours...",
- "defaultMessage": "sending...",
- "description": "Message being sent, in place of time stamp",
- "missing": false,
- "obsolete": false
- },
- "message_sending_failed": {
- "translation": "échoué",
- "defaultMessage": "failed",
- "description": "Failed to send message, in place of time stamp",
- "missing": false,
- "obsolete": false
- },
- "messages_not_readable": {
- "translation": "aucun accès aux messages",
- "defaultMessage": "no access to messages",
- "description": "Message shown in topic without the read access",
- "missing": false,
- "obsolete": false
- },
- "messaging_disabled_prompt": {
- "translation": "Discussion désactivée",
- "defaultMessage": "Messaging disabled",
- "description": "Prompt in SendMessage in read-only topic",
- "missing": false,
- "obsolete": false
- },
- "more_online_members": {
- "translation": "+{overflow} de plus",
- "defaultMessage": "+{overflow} more",
- "description": "Shown in MessagesView title bar when the number of online subscribers exceeds MAX_ONLINE_IN_TOPIC",
- "missing": false,
- "obsolete": false
- },
- "new_message_prompt": {
- "translation": "Nouveau message",
- "defaultMessage": "New message",
- "description": "Prompt in send message field",
- "missing": false,
- "obsolete": false
- },
- "new_password_placeholder": {
- "translation": "Entrez un nouveau mot de passe",
- "defaultMessage": "Enter new password",
- "description": "Placeholder for entering new password",
- "missing": false,
- "obsolete": false
- },
- "no_connection": {
- "translation": "Aucune connexion",
- "defaultMessage": "No connection",
- "description": "Warning that the user is offline.",
- "missing": false,
- "obsolete": false
- },
- "no_contacts": {
- "translation": "Vous n'avez pas de contacts :-(",
- "defaultMessage": "You have no contacts :-(",
- "description": "Shown in ContactsView when the user has no contacts",
- "missing": false,
- "obsolete": false
- },
- "numeric_confirmation_code_prompt": {
- "translation": "Nombres seulement",
- "defaultMessage": "Numbers only",
- "description": "Prompt for numeric conformation code",
- "missing": false,
- "obsolete": false
- },
- "online_now": {
- "translation": "en ligne",
- "defaultMessage": "online now",
- "description": "Indicator that the user or topic is currently online",
- "missing": false,
- "obsolete": false
- },
- "password_prompt": {
- "translation": "Mot de passe",
- "defaultMessage": "Password",
- "description": "Placeholder/prompt for entering password",
- "missing": false,
- "obsolete": false
- },
- "password_unchanged_prompt": {
- "translation": "Non changé",
- "defaultMessage": "Unchanged",
- "description": "Message in editor while password is unchanged",
- "missing": false,
- "obsolete": false
- },
- "peers_messaging_disabled": {
- "translation": "La messagerie point à point est désactivée.",
- "defaultMessage": "Peer's messaging is disabled.",
- "description": "Shown when the p2p peer's messaging is disabled",
- "missing": false,
- "obsolete": false
- },
- "permission_admin": {
- "translation": "Approver ({val})",
- "defaultMessage": "Approve ({val})",
- "description": "Name of A permission",
- "missing": false,
- "obsolete": false
- },
- "permission_delete": {
- "translation": "Supprimer ({val})",
- "defaultMessage": "Delete ({val})",
- "description": "Name of D permission",
- "missing": false,
- "obsolete": false
- },
- "permission_join": {
- "translation": "Rejoindre ({val})",
- "defaultMessage": "Join ({val})",
- "description": "Name of J permission",
- "missing": false,
- "obsolete": false
- },
- "permission_owner": {
- "translation": "Propriétaire ({val})",
- "defaultMessage": "Owner ({val})",
- "description": "Name of O permission",
- "missing": false,
- "obsolete": false
- },
- "permission_pres": {
- "translation": "Recevoir une notification ({val})",
- "defaultMessage": "Get notified ({val})",
- "description": "Name of P permission",
- "missing": false,
- "obsolete": false
- },
- "permission_read": {
- "translation": "Lire ({val})",
- "defaultMessage": "Read ({val})",
- "description": "Name of R permission",
- "missing": false,
- "obsolete": false
- },
- "permission_share": {
- "translation": "Partager ({val})",
- "defaultMessage": "Share ({val})",
- "description": "Name of S permission",
- "missing": false,
- "obsolete": false
- },
- "permission_write": {
- "translation": "Ecrire ({val})",
- "defaultMessage": "Write ({val})",
- "description": "Name of W permission",
- "missing": false,
- "obsolete": false
- },
- "phone_dative": {
- "translation": "téléphone",
- "defaultMessage": "phone",
- "description": "Dative case of 'phone', i.e. 'phone' in 'by phone'",
- "missing": false,
- "obsolete": false
- },
- "private_editing_placeholder": {
- "translation": "Visible par vous seulement",
- "defaultMessage": "Visible to you only",
- "description": "Placeholder for editing 'private'",
- "missing": false,
- "obsolete": false
- },
- "push_init_failed": {
- "translation": "Impossible d'initialiser les notifications push",
- "defaultMessage": "Failed to initialize push notifications",
- "description": "Error message when push notifications have failed to initialize.",
- "missing": false,
- "obsolete": false
- },
- "reconnect_countdown": {
- "translation": "Déconnecté. Reconnexion dans {seconds}…",
- "defaultMessage": "Disconnected. Reconnecting in {seconds}…",
- "description": "Message shown when an app update is available.",
- "missing": false,
- "obsolete": false
- },
- "reconnect_now": {
- "translation": "Essayer maintenant",
- "defaultMessage": "Try now",
- "description": "Prompt for reconnecting now",
- "missing": false,
- "obsolete": false
- },
- "reload_update": {
- "translation": "Recharger",
- "defaultMessage": "Reload",
- "description": "Call to action to reload application when update is available.",
- "missing": false,
- "obsolete": false
- },
- "report_chat_warning": {
- "translation": "Êtes-vous sûr de vouloir bloquer et signaler cette conversation ?",
- "defaultMessage": "Are you sure you want to block and report this conversation?",
- "description": "Alert dialog warning when reporting a conversation for abuse",
- "missing": false,
- "obsolete": false
- },
- "requested_permissions": {
- "translation": "Demandé",
- "defaultMessage": "Requested",
- "description": "Title for permissions",
- "missing": false,
- "obsolete": false
- },
- "save_attachment": {
- "translation": "sauvegarder",
- "defaultMessage": "save",
- "description": "Call to save an attachment",
- "missing": false,
- "obsolete": false
- },
- "search_for_contacts": {
- "translation": "Utilisez la recherche pour trouver des contacts",
- "defaultMessage": "Use search to find contacts",
- "description": "Text shown in contacts view when user entered no search query.",
- "missing": false,
- "obsolete": false
- },
- "search_no_results": {
- "translation": "La recherche n'a donné aucun résultat",
- "defaultMessage": "Search returned no results",
- "description": "Text shown in contacts view when query returned no results.",
- "missing": false,
- "obsolete": false
- },
- "search_placeholder": {
- "translation": "Liste comme email:alice@exemple.com, tel:17025550003...",
- "defaultMessage": "List like alice@example.com, +17025550003...",
- "description": "Placeholder in contacts search field",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_notifications": {
- "translation": "Notifications",
- "defaultMessage": "Notifications",
- "description": "Sidepanel title for AccNotificationsView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_support": {
- "translation": "Support",
- "defaultMessage": "Support",
- "description": "Sidepanel title for AccSupportView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_account_settings": {
- "translation": "Paramètres du compte",
- "defaultMessage": "Account Settings",
- "description": "Sidepanel title for AccountSettingsView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_archive": {
- "translation": "Conversations Archivées",
- "defaultMessage": "Archived Chats",
- "description": "Sidepanel title for ContactsView-Archive.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_blocked": {
- "translation": "Conversations Bloquées",
- "defaultMessage": "Blocked Chats",
- "description": "Sidepanel title for ContactsView-Blocked.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_cred": {
- "translation": "Confirmer les identifiants",
- "defaultMessage": "Confirm Credentials",
- "description": "Sidepanel title for ValidationView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_login": {
- "translation": "Se Connecter",
- "defaultMessage": "Sign In",
- "description": "Sidepanel title for LoginView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_newtpk": {
- "translation": "Démarrer une nouvelle Conversation",
- "defaultMessage": "Start New Chat",
- "description": "Sidepanel title for NewTopicView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_register": {
- "translation": "Créer un compte",
- "defaultMessage": "Create Account",
- "description": "Sidepanel title for CreateAccountView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_reset": {
- "translation": "Remettre à Zéro le Mot de Passe",
- "defaultMessage": "Reset Password",
- "description": "Sidepanel title for PasswordResetView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_settings": {
- "translation": "Paramètres",
- "defaultMessage": "Settings",
- "description": "Sidepanel title for SettingsView.",
- "missing": false,
- "obsolete": false
- },
- "stay_logged_in": {
- "translation": "Rester connecté",
- "defaultMessage": "Stay logged in",
- "description": "Label for a checkbox",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_find_user": {
- "translation": "trouver",
- "defaultMessage": "find",
- "description": "Tab title Find",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_group_by_id": {
- "translation": "par identifiant",
- "defaultMessage": "by id",
- "description": "Tab title Find topic by ID",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_new_group": {
- "translation": "nouveau groupe",
- "defaultMessage": "new group",
- "description": "Tab title New Group",
- "missing": false,
- "obsolete": false
- },
- "tags_editor_no_tags": {
- "translation": "Ajouter des étiquettes",
- "defaultMessage": "Add some tags",
- "description": "Tag editor prompt when no tags are found.",
- "missing": false,
- "obsolete": false
- },
- "tags_not_found": {
- "translation": "Aucune étiquette définie. Ajoutez en.",
- "defaultMessage": "No tags defined. Add some.",
- "description": "",
- "missing": false,
- "obsolete": false
- },
- "title_all_contacts": {
- "translation": "Tous les Contacts",
- "defaultMessage": "All Contacts",
- "description": "Section title [All Contacts]",
- "missing": false,
- "obsolete": false
- },
- "title_group_members": {
- "translation": "Membres du Groupe",
- "defaultMessage": "Group Members",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "title_manage_tags": {
- "translation": "Gérer",
- "defaultMessage": "Manage",
- "description": "Section title for the list of tags",
- "missing": false,
- "obsolete": false
- },
- "title_not_found": {
- "translation": "Pas trouvé",
- "defaultMessage": "Not found",
- "description": "Title shown when topic is not found",
- "missing": false,
- "obsolete": false
- },
- "title_permissions": {
- "translation": "Permissions",
- "defaultMessage": "Permissions",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "title_tag_manager": {
- "translation": "Etiquettes (recherche & découverte)",
- "defaultMessage": "Tags (search & discovery)",
- "description": "Section title for TagManager",
- "missing": false,
- "obsolete": false
- },
- "topic_block_warning": {
- "translation": "Êtes-vous sûr de vouloir bloquer cette conversation ?",
- "defaultMessage": "Are you sure you want to block this conversation?",
- "description": "Alert warning when blocking a topic.",
- "missing": false,
- "obsolete": false
- },
- "topic_delete_warning": {
- "translation": "Êtes-vous sûr de vouloir supprimer cette conversation ?",
- "defaultMessage": "Are you sure you want to delete this conversation? It cannot be undone.",
- "description": "Alert warning when deleting entire topic",
- "missing": false,
- "obsolete": false
- },
- "topic_name_editing_placeholder": {
- "translation": "Nom libre du groupe",
- "defaultMessage": "Freeform name of the group",
- "description": "Prompt for entering topic name",
- "missing": false,
- "obsolete": false
- },
- "unnamed_topic": {
- "translation": "Non nommé",
- "defaultMessage": "Unnamed",
- "description": "Title shown when the topic has no name",
- "missing": false,
- "obsolete": false
- },
- "update_available": {
- "translation": "Mise à jour disponible.",
- "defaultMessage": "Update available.",
- "description": "Message shown when an app update is available.",
- "missing": false,
- "obsolete": false
- },
- "upload_finishing": {
- "translation": "fin...",
- "defaultMessage": "finishing...",
- "description": "Notification that upload is finishing",
- "missing": false,
- "obsolete": false
- },
- "user_not_found": {
- "translation": "Pas trouvé",
- "defaultMessage": "Not found",
- "description": "In place of a user's full name when the user is not found.",
- "missing": false,
- "obsolete": false
- },
- "description_editing_placeholder": {
- "translation": "Description facultative",
- "defaultMessage": "Description (optional)",
- "description": "Placeholder for editing topic description",
- "missing": false,
- "obsolete": false
- },
- "label_description": {
- "translation": "Description",
- "defaultMessage": "Description",
- "description": "Label for description of user or topic",
- "missing": false,
- "obsolete": false
- },
- "button_security": {
- "translation": "Sécurité",
- "defaultMessage": "Security",
- "description": "Navigaton button for security panel.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_crop": {
- "translation": "Déplacez pour ajuster",
- "defaultMessage": "Drag to Adjust",
- "description": "Title for AvatarCropView.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_general": {
- "translation": "Général",
- "defaultMessage": "General",
- "description": "Title for TopicCommon.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_members": {
- "translation": "Membres",
- "defaultMessage": "Members",
- "description": "Title for managing group members view.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_security": {
- "translation": "Sécurité",
- "defaultMessage": "Security",
- "description": "Title for TopicSecirity and AccSecurity.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_info": {
- "translation": "Informations",
- "defaultMessage": "Info",
- "description": "Title for InfoView",
- "missing": false,
- "obsolete": false
- },
- "permissions_anonymous": {
- "translation": "Anonyme",
- "defaultMessage": "Anonymous",
- "description": "Title for editing default anonymous permissions",
- "missing": false,
- "obsolete": false
- },
- "permissions_authenticated": {
- "translation": "Authentifié",
- "defaultMessage": "Authenticated",
- "description": "Title for editing default authenticated permissions",
- "missing": false,
- "obsolete": false
- },
- "topic_delete": {
- "translation": "Supprimer la Conversation",
- "defaultMessage": "Delete Conversation",
- "description": "Alert title when deleting the topic.",
- "missing": false,
- "obsolete": false
- },
- "permissions_user": {
- "translation": "Permissions d'Utilisateur",
- "defaultMessage": "User's Permissions",
- "description": "Title for editing user's permissions",
- "missing": false,
- "obsolete": false
- },
- "password_reset_email_sent": {
- "translation": "Un e-mail avec code de sécurité a été envoyé.",
- "defaultMessage": "An email with security code has been sent.",
- "description": "Notification that the email with password reset instructions has been sent",
- "missing": false,
- "obsolete": false
- },
- "label_unarchive_topic": {
- "translation": "Archivé:",
- "defaultMessage": "Archived:",
- "description": "Label for unarchiving the topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_forward": {
- "translation": "Transférer",
- "defaultMessage": "Forward",
- "description": "Forward message",
- "missing": false,
- "obsolete": false
- },
- "forward_to": {
- "translation": "Transférer à",
- "defaultMessage": "Forward to",
- "missing": false,
- "obsolete": false
- },
- "forward_to_search_placeholder": {
- "translation": "Rechercher des contacts",
- "defaultMessage": "Search contacts",
- "description": "Contact search prompt when forwarding a message.",
- "missing": false,
- "obsolete": false
- },
- "label_new_password": {
- "translation": "Nouveau mot de passe",
- "defaultMessage": "New password",
- "description": "Label for entering a new password",
- "missing": false,
- "obsolete": false
- },
- "drafty_unknown": {
- "translation": "Non supporté",
- "defaultMessage": "Unsupported",
- "description": "Unsupported entity in drafty",
- "missing": false,
- "obsolete": false
- },
- "already_in_call": {
- "translation": "Vous êtes déjà dans un appel en cours!",
- "defaultMessage": "You already in an ongoing call!",
- "description": "Error message when the user tried to accept a new call without finishing pervious one",
- "missing": false,
- "obsolete": false
- },
- "call_cancelled": {
- "translation": "annulé",
- "defaultMessage": "cancelled",
- "description": "Label for cancelled outgoing call",
- "missing": false,
- "obsolete": false
- },
- "call_declined": {
- "translation": "refusé",
- "defaultMessage": "declined",
- "description": "Label for declined call",
- "missing": false,
- "obsolete": false
- },
- "call_missed": {
- "translation": "manqué",
- "defaultMessage": "missed",
- "description": "Label for missed incoming call",
- "missing": false,
- "obsolete": false
- },
- "calls_incoming": {
- "translation": "Appel entrant",
- "defaultMessage": "Incoming call",
- "description": "Incoming call label",
- "missing": false,
- "obsolete": false
- },
- "calls_outgoing": {
- "translation": "Appel sortant",
- "defaultMessage": "Outgoing call",
- "description": "Outgoing call label",
- "missing": false,
- "obsolete": false
- },
- "calls_you_label": {
- "translation": "Tu",
- "defaultMessage": "You",
- "description": "Shown over the local video screen",
- "missing": false,
- "obsolete": false
- },
- "menu_item_video_call": {
- "translation": "Appel vidéo",
- "defaultMessage": "Video call",
- "description": "Start video call",
- "missing": false,
- "obsolete": false
- },
- "call_disconnected": {
- "translation": "débranché",
- "defaultMessage": "disconnected",
- "description": "Label for disconnected call",
- "missing": false,
- "obsolete": false
- },
- "label_use_secure_connection": {
- "translation": "Utiliser une connexion sécurisée",
- "defaultMessage": "Use secure connection",
- "description": "Label for WS/WSS connection type in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "cred_confirmed_successfully": {
- "translation": "Confirmé avec succès",
- "defaultMessage": "Confirmed successfully",
- "description": "Notification message that the credential was successfully validated.",
- "missing": false,
- "obsolete": false
- },
- "menu_item_edit": {
- "translation": "Modifier",
- "defaultMessage": "Edit",
- "description": "Edit message",
- "missing": false,
- "obsolete": false
- },
- "editing_message": {
- "translation": "Édition",
- "defaultMessage": "Editing",
- "description": "Title over message editing preview",
- "missing": false,
- "obsolete": false
- },
- "message_edited_marker": {
- "translation": ", édité",
- "defaultMessage": ", edited",
- "description": "Marker indicating that the message was edited",
- "missing": false,
- "obsolete": false
- },
- "icon_title_add_image": {
- "translation": "Ajouter une image",
- "defaultMessage": "Add image",
- "description": "Icon tool tip for attaching an image",
- "missing": false,
- "obsolete": false
- },
- "icon_title_attach_file": {
- "translation": "Pièce jointe",
- "defaultMessage": "Attach file",
- "description": "Icon tool tip for attaching a file",
- "missing": false,
- "obsolete": false
- },
- "icon_title_delete": {
- "translation": "Supprimer l'enregistrement",
- "defaultMessage": "Delete recording",
- "description": "Icon tool tip for deleting recorded audio",
- "missing": false,
- "obsolete": false
- },
- "icon_title_pause": {
- "translation": "Interrompre la lecture",
- "defaultMessage": "Pause playback",
- "description": "Icon tool tip for pausing audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_play": {
- "translation": "Lire l'enregistrement",
- "defaultMessage": "Play recording",
- "description": "Icon tool tip for starting audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_record_voice": {
- "translation": "Enregistrer un message vocal",
- "defaultMessage": "Record voice message",
- "description": "Icon tool tip for recording a voice message",
- "missing": false,
- "obsolete": false
- },
- "icon_title_resume": {
- "translation": "Reprendre la lecture",
- "defaultMessage": "Resume playback",
- "description": "Icon tool tip for resuming audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_send": {
- "translation": "Envoyer le message",
- "defaultMessage": "Send message",
- "description": "Icon tool tip for sending a message",
- "missing": false,
- "obsolete": false
- },
- "drag_file": {
- "translation": "Faites glisser le fichier ici",
- "defaultMessage": "Drag file here",
- "description": "Prompt on the file drag-n-drop overlay banner",
- "missing": false,
- "obsolete": false
- },
- "drafty_video": {
- "translation": "Enregistrement video",
- "defaultMessage": "Video recording",
- "description": "Comment for videos embedded in Drafty",
- "missing": false,
- "obsolete": false
- },
- "call_in_progress": {
- "translation": "en cours",
- "defaultMessage": "in progress",
- "description": "Label for call in progress",
- "missing": false,
- "obsolete": false
- },
- "menu_item_audio_call": {
- "translation": "Appel",
- "defaultMessage": "Call",
- "description": "Start audio call",
- "missing": false,
- "obsolete": false
- },
- "unrecognized_video_format": {
- "translation": "Le format de cette vidéo n'est pas reconnu",
- "defaultMessage": "Format of this video is not recognized",
- "description": "Error message when uploaded video is invalid",
- "missing": false,
- "obsolete": false
- },
- "loading_note": {
- "translation": "Chargement...",
- "defaultMessage": "Loading...",
- "description": "Message shown when component is loading",
- "missing": false,
- "obsolete": false
- },
- "password_i_have_code": {
- "translation": "J'ai du code",
- "defaultMessage": "I have code",
- "description": "Call to open field to enter password reset code",
- "missing": false,
- "obsolete": false
- },
- "label_reset_password_tel": {
- "translation": "Envoyer un SMS pour réinitialiser le mot de passe",
- "defaultMessage": "Send SMS to reset password",
- "description": "Label for password reset field by phone",
- "missing": false,
- "obsolete": false
- },
- "mobile_phone_number": {
- "translation": "Numéro de téléphone mobile",
- "defaultMessage": "Mobile phone number",
- "description": "Prompt for entering a mobile phone number",
- "missing": false,
- "obsolete": false
- },
- "password_reset_sms_sent": {
- "translation": "Un message texte avec un code de sécurité a été envoyé.",
- "defaultMessage": "A text message with security code has been sent.",
- "description": "Notification that the SMS with password reset instructions has been sent",
- "missing": false,
- "obsolete": false
- },
- "mobile_number_required": {
- "translation": "Numéro de téléphone portable requis",
- "defaultMessage": "Mobile phone number required",
- "description": "Error message",
- "missing": false,
- "obsolete": false
- },
- "current_email": {
- "translation": "Courriel actuel",
- "defaultMessage": "Current email",
- "description": "Label for old email",
- "missing": false,
- "obsolete": false
- },
- "current_phone": {
- "translation": "Numéro de téléphone actuel",
- "defaultMessage": "Current phone number",
- "description": "Label for old phone",
- "missing": false,
- "obsolete": false
- },
- "new_email": {
- "translation": "Nouvel e-mail",
- "defaultMessage": "New email",
- "description": "Prompt for entering a new email",
- "missing": false,
- "obsolete": false
- },
- "new_phone_number": {
- "translation": "Nouveau numéro de téléphone",
- "defaultMessage": "New phone number",
- "description": "Prompt for entering a new telephone number",
- "missing": false,
- "obsolete": false
- },
- "change_email": {
- "translation": "Modifier l'e-mail",
- "defaultMessage": "Change email",
- "description": "Prompt to change current email",
- "missing": false,
- "obsolete": false
- },
- "change_phone": {
- "translation": "Modifier le numéro de téléphone",
- "defaultMessage": "Change phone number",
- "description": "Prompt to change current phone number",
- "missing": false,
- "obsolete": false
- },
- "call_busy": {
- "translation": "occupé",
- "defaultMessage": "busy",
- "description": "Label for busy call line",
- "missing": false,
- "obsolete": false
- },
- "pin_message": {
- "translation": "Épingler",
- "defaultMessage": "Pin",
- "description": "Menu item [Pin] for pinning message to the top.",
- "missing": false,
- "obsolete": false
- },
- "unpin_message": {
- "translation": "Détacher",
- "defaultMessage": "Unpin",
- "description": "Menu item [Unpin] for un-pinning the message.",
- "missing": false,
- "obsolete": false
- },
- "failed_to_init_audio": {
- "translation": "Échec de l'initialisation de l'enregistrement audio",
- "defaultMessage": "Failed to initialize audio recording",
- "description": "Error message when audio is not available",
- "missing": false,
- "obsolete": false
- },
- "password_reset_success": {
- "translation": "Mot de passe réinitialisé avec succès",
- "defaultMessage": "Password reset successfully",
- "description": "Notification message that the password was successfully reset.",
- "missing": false,
- "obsolete": false
- },
- "scan_qr_code": {
- "translation": "Scanner le code QR",
- "defaultMessage": "Scan QR Code",
- "description": "Title for scanning QR code",
- "missing": false,
- "obsolete": false
- },
- "self_topic_comment": {
- "translation": "Notes, messages, liens, fichiers sauvegardés",
- "defaultMessage": "Notes, messages, links, files saved for posterity",
- "description": "Comment for self topic for UI",
- "missing": false,
- "obsolete": false
- },
- "self_topic_name": {
- "translation": "Messages sauvegardés",
- "defaultMessage": "Saved messages",
- "description": "Name of self topic for UI",
- "missing": false,
- "obsolete": false
- },
- "message_not_found": {
- "translation": "message introuvable",
- "defaultMessage": "message not found",
- "description": "Pinned message is not found",
- "missing": false,
- "obsolete": false
- },
- "message_deleted": {
- "translation": "message supprimé",
- "defaultMessage": "message deleted",
- "description": "Pinned message is deleted",
- "missing": false,
- "obsolete": false
- },
- "alias_editing_placeholder": {
- "translation": "Alias (facultatif)",
- "defaultMessage": "Alias (optional)",
- "description": "Placeholder for editing user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "label_alias": {
- "translation": "Alias:",
- "defaultMessage": "Alias:",
- "description": "Label for user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "alias_already_taken": {
- "translation": "(déjà utilisé)",
- "defaultMessage": "(already taken)",
- "description": "Error message for alias already taken",
- "missing": false,
- "obsolete": false
- },
- "alias_invalid": {
- "translation": "(invalide)",
- "defaultMessage": "(invalid)",
- "description": "Error message for invalid alias",
- "missing": false,
- "obsolete": false
- },
- "label_alias_edit": {
- "translation": "Alias",
- "defaultMessage": "Alias",
- "description": "Label for editing user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "select_country": {
- "translation": "Sélectionner un pays",
- "defaultMessage": "Select country",
- "description": "Placeholder for the country selector",
- "missing": false,
- "obsolete": false
- },
- "text_copied": {
- "translation": "Copié dans le presse-papiers",
- "defaultMessage": "Copied to clipboard",
- "description": "Notification that text has been copied to clipboard",
- "missing": false,
- "obsolete": false
- },
- "color_schema_dark": {
- "translation": "Sombre",
- "defaultMessage": "Dark",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "color_schema_light": {
- "translation": "Clair",
- "defaultMessage": "Light",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "color_schema_system": {
- "translation": "Thème du système",
- "defaultMessage": "System default",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "label_color_schema": {
- "translation": "Thème :",
- "defaultMessage": "Theme:",
- "description": "Label for selecting color scheme (dark, light) in Settings",
- "missing": false,
- "obsolete": false
- },
- "label_text_size": {
- "translation": "Taille du texte :",
- "defaultMessage": "Text size:",
- "description": "Label adjusting text size",
- "missing": false,
- "obsolete": false
- },
- "incognito_mode_description": {
- "translation": "Masquer votre statut en ligne et les indicateurs de frappe.",
- "defaultMessage": "Hide your online status and typing indicators.",
- "description": "Description for incognito mode toggle",
- "missing": false,
- "obsolete": false
- },
- "send_command": {
- "translation": "Envoyer avec {key}",
- "defaultMessage": "Send on {key}",
- "description": "Config option to send message on hitting CTRL(or Cmd)-Enter",
- "missing": false,
- "obsolete": false
- },
- "send_command_explained": {
- "translation": "Appuyez sur Entrée pour une nouvelle ligne",
- "defaultMessage": "Press Enter for new line",
- "description": "Explanation how to enter newline when [Send on CTRL-Enter] is enabled",
- "missing": false,
- "obsolete": false
- },
- "send_plain": {
- "translation": "Envoyer avec Entrée",
- "defaultMessage": "Send on Enter",
- "description": "Config option to send message on hitting Enter",
- "missing": false,
- "obsolete": false
- },
- "send_plain_explained": {
- "translation": "Appuyez sur Shift + Entrée pour une nouvelle ligne",
- "defaultMessage": "Press Shift + Enter for new line",
- "description": "Explanation how to enter newline when [Send on Enter] is enabled",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_general": {
- "translation": "Général",
- "defaultMessage": "General",
- "description": "Sidepanel title for AccGeneralView.",
- "missing": false,
- "obsolete": false
- },
- "label_keyboard": {
- "translation": "Clavier :",
- "defaultMessage": "Keyboard:",
- "description": "Label for send on enter settings",
- "missing": false,
- "obsolete": false
- },
- "pin_chat": {
- "translation": "Épingler",
- "defaultMessage": "Pin",
- "description": "Menu item [Pin] for pinning chat to the top.",
- "missing": false,
- "obsolete": false
- },
- "unpin_chat": {
- "translation": "Désépingler",
- "defaultMessage": "Unpin",
- "description": "Menu item [Unpin] for un-pinning the chat.",
- "missing": false,
- "obsolete": false
- },
- "subscriber_count": {
- "translation": "{count, plural, one {{count, number} abonné} other {{count, number} abonnés}}",
- "defaultMessage": "{count, plural, one {{count, number} subscriber} two {{count, number} subscribers} three {{count, number} subscribers} few {{count, number} subscribers} many {{count, number} subscribers} other {{count, number} subscribers}}",
- "description": "Count of channel subscribers",
- "missing": false,
- "obsolete": false
- },
- "label_member_count": {
- "translation": "Membres :",
- "defaultMessage": "Members:",
- "description": "Label for the count of group topic members",
- "missing": false,
- "obsolete": false
- },
- "label_subscriber_count": {
- "translation": "Abonnés :",
- "defaultMessage": "Subscribers:",
- "description": "Label for the count of channel subscribers",
- "missing": false,
- "obsolete": false
- },
- "member_count": {
- "translation": "{count, plural, one {{count, number} membre} other {{count, number} membres}}",
- "defaultMessage": "{count, plural, one {{count, number} member} two {{count, number} members} three {{count, number} members} few {{count, number} members} many {{count, number} members} other {{count, number} members}}",
- "description": "Count of group topic members",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_image": {
- "translation": "image",
- "defaultMessage": "image",
- "description": "Tab title Image",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_pattern": {
- "translation": "motif",
- "defaultMessage": "pattern",
- "description": "Tab title Pattern",
- "missing": false,
- "obsolete": false
- },
- "button_restore": {
- "translation": "Restaurer par défaut",
- "defaultMessage": "Restore default",
- "description": "Reset setting to default",
- "missing": false,
- "obsolete": false
- },
- "wallpapers": {
- "translation": "Fonds d'écran",
- "defaultMessage": "Wallpapers",
- "description": "Link or title for working with wallpapers.",
- "missing": false,
- "obsolete": false
- },
- "label_blur_wallpaper": {
- "translation": "Flou :",
- "defaultMessage": "Blur:",
- "description": "Label adjusting blur amount",
- "missing": false,
- "obsolete": false
- },
- "label_scan_id": {
- "translation": "Scanner mon ID :",
- "defaultMessage": "Scan my ID:",
- "description": "Label [Scan my ID:]",
- "missing": false,
- "obsolete": false
- },
- "unknown_name": {
- "translation": "Inconnu",
- "defaultMessage": "Unknown",
- "description": "Name to show when the name is missing",
- "missing": false,
- "obsolete": false
- },
- "save_action": {
- "translation": "Enregistrer",
- "defaultMessage": "Save",
- "description": "Label for Save button",
- "missing": false,
- "obsolete": false
- },
- "cannot_parse_vcard": {
- "translation": "Impossible d'analyser le fichier vCard.",
- "defaultMessage": "Cannot parse vCard file.",
- "description": "Error message when vCard file cannot be parsed",
- "missing": false,
- "obsolete": false
- },
- "chat_now": {
- "translation": "Discuter",
- "defaultMessage": "Chat",
- "description": "Label for message button in contact card",
- "missing": false,
- "obsolete": false
- },
- "find_user": {
- "translation": "Rechercher",
- "defaultMessage": "Find",
- "description": "Label for search button in contact card",
- "missing": false,
- "obsolete": false
- },
- "label_contact_card": {
- "translation": "Carte de contact",
- "defaultMessage": "Contact Card",
- "description": "Label for contact card preview",
- "missing": false,
- "obsolete": false
- },
- "label_contacts": {
- "translation": "Contacts",
- "defaultMessage": "Contacts",
- "description": "Label for contact information",
- "missing": false,
- "obsolete": false
- },
- "add_members_prompt": {
- "translation": "ajouter des membres",
- "defaultMessage": "add members",
- "description": "Placeholder in group members input field",
- "missing": false,
- "obsolete": false
- }
-}
diff --git a/src/i18n/it.json b/src/i18n/it.json
deleted file mode 100644
index 939a75a3..00000000
--- a/src/i18n/it.json
+++ /dev/null
@@ -1,1966 +0,0 @@
-{
- "action_block_contact": {
- "translation": "Bloccare contatto",
- "defaultMessage": "Block Contact",
- "description": "Flat button [Block Contact]",
- "missing": false,
- "obsolete": false
- },
- "action_cancel": {
- "translation": "cancella",
- "defaultMessage": "cancel",
- "description": "Call to action [cancel]",
- "missing": false,
- "obsolete": false
- },
- "action_clear_messages": {
- "translation": "Cancella messaggi",
- "defaultMessage": "Clear Messages",
- "description": "Flat button [Clear Messages] (soft-delete messages)",
- "missing": false,
- "obsolete": false
- },
- "action_delete_messages": {
- "translation": "Cancella messaggi per tutti",
- "defaultMessage": "Clear Messages for All",
- "description": "Flat button [Clear for All] (hard-delete all messages)",
- "missing": false,
- "obsolete": false
- },
- "action_leave_chat": {
- "translation": "Lascia la conversazione",
- "defaultMessage": "Leave Conversation",
- "description": "Flat button [Leave Conversation]",
- "missing": false,
- "obsolete": false
- },
- "action_report_chat": {
- "translation": "Report conversazione",
- "defaultMessage": "Report Conversation",
- "description": "Flat button [Report Group]",
- "missing": false,
- "obsolete": false
- },
- "archived_contacts": {
- "translation": "Contatti archiviati ({count})",
- "defaultMessage": "Archived contacts ({count})",
- "description": "Label for archived chats",
- "missing": false,
- "obsolete": false
- },
- "badge_danger": {
- "translation": "Sospetto",
- "defaultMessage": "Untrustworthy",
- "description": "Suspicious or untrustworthy account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_owner": {
- "translation": "propietario",
- "defaultMessage": "owner",
- "description": "Badge for indicating the owner",
- "missing": false,
- "obsolete": false
- },
- "badge_staff": {
- "translation": "Amministrazione",
- "defaultMessage": "Staff-managed",
- "description": "Explanation of a staff-managed account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_verified": {
- "translation": "Verificato",
- "defaultMessage": "Verified/official",
- "description": "Explanation of a verified account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_you": {
- "translation": "tu",
- "defaultMessage": "you",
- "description": "Badge for indicating the current user",
- "missing": false,
- "obsolete": false
- },
- "block_contact_warning": {
- "translation": "Sei sicuro di bloccare questo contatto?",
- "defaultMessage": "Are you sure you want to block this contact?",
- "description": "Alert dialog warning when blocking a contact.",
- "missing": false,
- "obsolete": false
- },
- "blocked_contacts_link": {
- "translation": "Contatti bloccati ({count})",
- "defaultMessage": "Blocked contacts ({count})",
- "description": "Blocked contacts link",
- "missing": false,
- "obsolete": false
- },
- "button_add_members": {
- "translation": "Aggiungi membri",
- "defaultMessage": "Add members",
- "description": "Flat button [Add members] (to topic)",
- "missing": false,
- "obsolete": false
- },
- "button_cancel": {
- "translation": "Cancella",
- "defaultMessage": "Cancel",
- "description": "Button [Cancel]",
- "missing": false,
- "obsolete": false
- },
- "button_confirm": {
- "translation": "Conferma",
- "defaultMessage": "Confirm",
- "description": "Button [Confirm]",
- "missing": false,
- "obsolete": false
- },
- "button_create": {
- "translation": "Crea",
- "defaultMessage": "Create",
- "description": "Button [Create]",
- "missing": false,
- "obsolete": false
- },
- "button_delete_account": {
- "translation": "Elimina account",
- "defaultMessage": "Delete account",
- "description": "Button [Delete account]",
- "missing": false,
- "obsolete": false
- },
- "button_edit": {
- "translation": "Editare",
- "defaultMessage": "Edit",
- "description": "Call to action [Edit]",
- "missing": false,
- "obsolete": false
- },
- "button_logout": {
- "translation": "Fine sessione",
- "defaultMessage": "Logout",
- "description": "Button [Logout]",
- "missing": false,
- "obsolete": false
- },
- "button_ok": {
- "translation": "OK",
- "defaultMessage": "OK",
- "description": "Button [OK]",
- "missing": false,
- "obsolete": false
- },
- "button_reset": {
- "translation": "Reset",
- "defaultMessage": "Reset",
- "description": "Button [Reset]",
- "missing": false,
- "obsolete": false
- },
- "button_send_request": {
- "translation": "Invia richiesta",
- "defaultMessage": "Send request",
- "description": "Button [Send request]",
- "missing": false,
- "obsolete": false
- },
- "button_sign_in": {
- "translation": "Entrare",
- "defaultMessage": "Sign in",
- "description": "Button [Sign In]",
- "missing": false,
- "obsolete": false
- },
- "button_sign_up": {
- "translation": "Registrazione",
- "defaultMessage": "Sign up",
- "description": "Create account button [Sign Up]",
- "missing": false,
- "obsolete": false
- },
- "button_subscribe": {
- "translation": "Sottoscrivi",
- "defaultMessage": "Subscribe",
- "description": "Button [Subscribe]",
- "missing": false,
- "obsolete": false
- },
- "button_update": {
- "translation": "Aggiorna",
- "defaultMessage": "Update",
- "description": "Button [Update]",
- "missing": false,
- "obsolete": false
- },
- "cannot_initiate_file_upload": {
- "translation": "Non è possibile iniziare il caricamento dell'archivio.",
- "defaultMessage": "Cannot initiate file upload.",
- "description": "Generic error messagewhen attachment fails",
- "missing": false,
- "obsolete": false
- },
- "channel": {
- "translation": "canale",
- "defaultMessage": "channel",
- "description": "Subtitle shown for channels in MessagesView instead of last seen",
- "missing": false,
- "obsolete": false
- },
- "channel_prompt": {
- "translation": "Questo è un canale",
- "defaultMessage": "This is a channel",
- "description": "Checkbox label when creating a channel",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation": {
- "translation": "Sei invitato a partecipare ad una nuova chat. Vuoi farlo?",
- "defaultMessage": "You are invited to start a new chat. What would you like to do?",
- "description": "New chat invitation message: [Accept] [Ignore] [Block].",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_accept": {
- "translation": "Accettare",
- "defaultMessage": "Accept",
- "description": "Action [Accept] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_block": {
- "translation": "Bloccare",
- "defaultMessage": "Block",
- "description": "Action [Block] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_ignore": {
- "translation": "Ignorare",
- "defaultMessage": "Ignore",
- "description": "Action [Ignore] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "clear_messages_warning": {
- "translation": "Sei sicuro di voler cancellare tutti i messaggi? Questa azione è irreversibile.",
- "defaultMessage": "Are you sure you want to clear all messages? It cannot be undone.",
- "description": "Alert dialog warning when deleting all messages.",
- "missing": false,
- "obsolete": false
- },
- "code_doesnot_match": {
- "translation": "Il codice non conicide",
- "defaultMessage": "Code does not match",
- "description": "Error message when the credential validation code is incorrect.",
- "missing": false,
- "obsolete": false
- },
- "contacts_not_found": {
- "translation": "Non hai chats\n¯∖_(ツ)_/¯",
- "defaultMessage": "You have no chats\\n¯∖_(ツ)_/¯",
- "description": "HTML message shown in ContactList when no contacts are found",
- "missing": false,
- "obsolete": false
- },
- "contacts_not_found_short": {
- "translation": "Nessun contatto coincide con ''{query}''",
- "defaultMessage": "No contacts match ''{query}''",
- "description": "Shown in ContactsView when search returned no results",
- "missing": false,
- "obsolete": false
- },
- "credential_email_prompt": {
- "translation": "La tua mail di registrazione",
- "defaultMessage": "Your registration email",
- "description": "Placeholder for entering email",
- "missing": false,
- "obsolete": false
- },
- "delete_account": {
- "translation": "Elimina l'account",
- "defaultMessage": "Delete account",
- "description": "Title for delete account warning",
- "missing": false,
- "obsolete": false
- },
- "delete_account_warning": {
- "translation": "Sei sicuro di voler cancellare il tuo account? Questa azione è irreversibile.",
- "defaultMessage": "Are you sure you want to delete your account? It cannot be undone.",
- "description": "Warning message when deleting an account",
- "missing": false,
- "obsolete": false
- },
- "delete_messages_warning": {
- "translation": "Sei sicuro di voler cancellare tutti i messaggi per tutti? Questa azione è irreversibile.",
- "defaultMessage": "Are you sure you want to delete all messages for everyone? It cannot be undone.",
- "description": "Alert dialog warning when hard-deleting all messages.",
- "missing": false,
- "obsolete": false
- },
- "download_action": {
- "translation": "Scarica",
- "defaultMessage": "download",
- "description": "Call to action [download]",
- "missing": false,
- "obsolete": false
- },
- "drafty_attachment": {
- "translation": "Allegato",
- "defaultMessage": "Attachment",
- "description": "Comment for attachment in Drafty",
- "missing": false,
- "obsolete": false
- },
- "drafty_form": {
- "translation": "Form:",
- "defaultMessage": "Form:",
- "description": "Comment for form in Drafty",
- "missing": false,
- "obsolete": false
- },
- "drafty_image": {
- "translation": "Immagine",
- "defaultMessage": "Picture",
- "description": "Comment for embedded images in Drafty",
- "missing": false,
- "obsolete": false
- },
- "email_dative": {
- "translation": "posta elettronica",
- "defaultMessage": "email",
- "description": "Dative case of 'email', i.e. 'email' in 'by email'",
- "missing": false,
- "obsolete": false
- },
- "email_prompt": {
- "translation": "Posta elettronica, per es. mario@example.com",
- "defaultMessage": "Email, e.g. jdoe@example.com",
- "description": "Input placeholder for email entry",
- "missing": false,
- "obsolete": false
- },
- "enable_peers_messaging": {
- "translation": "Abilitare",
- "defaultMessage": "Enable",
- "description": "Call to action to enable peer's messaging",
- "missing": false,
- "obsolete": false
- },
- "enter_confirmation_code_prompt": {
- "translation": "Inserire il codice di conferma {method}:",
- "defaultMessage": "Confirmation code",
- "description": "Request to enter confirmation code",
- "missing": false,
- "obsolete": false
- },
- "error_invalid_id": {
- "translation": "ID non valido",
- "defaultMessage": "Invalid ID",
- "description": "Error message",
- "missing": false,
- "obsolete": false
- },
- "file_attachment_too_large": {
- "translation": "La grandezza del file {size} eccede il limite di {limit}.",
- "defaultMessage": "The file size {size} exceeds the {limit} limit.",
- "description": "Error message when attachment is too large",
- "missing": false,
- "obsolete": false
- },
- "forgot_password_link": {
- "translation": "Hai dimenticato la password?",
- "defaultMessage": "Forgot password?",
- "description": "Link to Reset password form",
- "missing": false,
- "obsolete": false
- },
- "full_name_prompt": {
- "translation": "Nome completo, per es. Mario Rossi",
- "defaultMessage": "Full name, e.g. John Doe",
- "description": "Input placeholder for person's full name",
- "missing": false,
- "obsolete": false
- },
- "granted_permissions": {
- "translation": "Garantito",
- "defaultMessage": "Granted",
- "description": "Title for permissions",
- "missing": false,
- "obsolete": false
- },
- "group_has_no_members": {
- "translation": "Non hai membri",
- "defaultMessage": "No members",
- "description": "Shown in place of group members",
- "missing": false,
- "obsolete": false
- },
- "group_user_id_prompt": {
- "translation": "ID del gruppo o dell'utente",
- "defaultMessage": "Group or User ID",
- "description": "Prompt for entering user or group ID",
- "missing": false,
- "obsolete": false
- },
- "image_caption_prompt": {
- "translation": "Aggiungi un'immagine",
- "defaultMessage": "Image caption",
- "description": "Prompt in SendMessage for attached image",
- "missing": false,
- "obsolete": false
- },
- "invalid_content": {
- "translation": "contenuto non valido",
- "defaultMessage": "invalid content",
- "description": "Shown when the message is unreadable",
- "missing": false,
- "obsolete": false
- },
- "invalid_security_token": {
- "translation": "Token di sicurezza invalido",
- "defaultMessage": "Invalid security token",
- "description": "Error message when resetting password.",
- "missing": false,
- "obsolete": false
- },
- "label_client": {
- "translation": "Cliente:",
- "defaultMessage": "Client:",
- "description": "Label for a client version",
- "missing": false,
- "obsolete": false
- },
- "label_content_type": {
- "translation": "Tipo di contenuto:",
- "defaultMessage": "Content type:",
- "description": "Label for file content type (mime)",
- "missing": false,
- "obsolete": false
- },
- "label_default_access_mode": {
- "translation": "Modalità di accesso predefinito:",
- "defaultMessage": "Default access mode:",
- "description": "Label for default access mode",
- "missing": false,
- "obsolete": false
- },
- "label_file_name": {
- "translation": "Nome del file:",
- "defaultMessage": "File name:",
- "description": "Label for a file name",
- "missing": false,
- "obsolete": false
- },
- "label_group_members": {
- "translation": "Membri del gruppo:",
- "defaultMessage": "Group members:",
- "description": "Section title or label",
- "missing": false,
- "obsolete": false
- },
- "label_incognito_mode": {
- "translation": "Modalità incognito:",
- "defaultMessage": "Incognito mode:",
- "description": "Label for incognito mode toggle",
- "missing": false,
- "obsolete": false
- },
- "label_message_sound": {
- "translation": "Suono del messaggio:",
- "defaultMessage": "Message sound:",
- "description": "Label for message sounds toggle",
- "missing": false,
- "obsolete": false
- },
- "label_muting_topic": {
- "translation": "Silenziato:",
- "defaultMessage": "Muted:",
- "description": "Label for Muting/unmuting the topic",
- "missing": false,
- "obsolete": false
- },
- "label_other_user": {
- "translation": "Altri",
- "defaultMessage": "Other",
- "description": "Label for the other user when the user is unnamed",
- "missing": false,
- "obsolete": false
- },
- "label_password": {
- "translation": "Password",
- "defaultMessage": "Password",
- "description": "Label for password editing",
- "missing": false,
- "obsolete": false
- },
- "label_permissions": {
- "translation": "Permessi:",
- "defaultMessage": "Permissions:",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "label_private": {
- "translation": "Commento privato",
- "defaultMessage": "Private comment",
- "description": "Label for editing 'private'",
- "missing": false,
- "obsolete": false
- },
- "label_push_notifications": {
- "translation": "Allarmi di notifica:",
- "defaultMessage": "Notification alerts:",
- "description": "Label for push notifications switch",
- "missing": false,
- "obsolete": false
- },
- "label_push_notifications_disabled": {
- "translation": "Allarmi di notifica (richiede HTTPS):",
- "defaultMessage": "Notification alerts (requires HTTPS):",
- "description": "Label for push notifications switch",
- "missing": false,
- "obsolete": false
- },
- "label_reset_password": {
- "translation": "Invia una mail per il reset della password:",
- "defaultMessage": "Send a password reset email",
- "description": "Label for password reset field",
- "missing": false,
- "obsolete": false
- },
- "label_sdk": {
- "translation": "SDK:",
- "defaultMessage": "SDK:",
- "missing": false,
- "obsolete": false
- },
- "label_server": {
- "translation": "Server:",
- "defaultMessage": "Server:",
- "description": "Label for a server version",
- "missing": false,
- "obsolete": false
- },
- "label_server_address": {
- "translation": "Indirizzo del server:",
- "defaultMessage": "Server address:",
- "missing": false,
- "obsolete": false
- },
- "label_server_to_use": {
- "translation": "Server da usare:",
- "defaultMessage": "Server to use:",
- "description": "Label for server selector in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "label_size": {
- "translation": "Grandezza:",
- "defaultMessage": "Size:",
- "description": "Label for file size",
- "missing": false,
- "obsolete": false
- },
- "label_topic_name": {
- "translation": "Nome dell'argomento",
- "defaultMessage": "Name",
- "description": "Label for editing topic name",
- "missing": false,
- "obsolete": false
- },
- "label_user_contacts": {
- "translation": "Contatti",
- "defaultMessage": "Contacts",
- "description": "Label for user contacts",
- "missing": false,
- "obsolete": false
- },
- "label_user_id": {
- "translation": "ID:",
- "defaultMessage": "ID:",
- "description": "Label for user address (ID)",
- "missing": false,
- "obsolete": false
- },
- "label_wire_transport": {
- "translation": "Tipo di Trasporto:",
- "defaultMessage": "Wire transport:",
- "description": "Label for wire transport selection in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "label_you": {
- "translation": "Tu:",
- "defaultMessage": "You:",
- "description": "Label for the current user",
- "missing": false,
- "obsolete": false
- },
- "label_your_name": {
- "translation": "Il tuo nome",
- "defaultMessage": "Your name",
- "description": "Label for full name editing",
- "missing": false,
- "obsolete": false
- },
- "label_your_permissions": {
- "translation": "I tuoi permessi:",
- "defaultMessage": "Your permissions:",
- "description": "Label for current user permissions",
- "missing": false,
- "obsolete": false
- },
- "last_seen_timestamp": {
- "translation": "Ultimo visto",
- "defaultMessage": "Last seen: {timestamp}",
- "description": "Label for the timestamp of when the user or topic was last online",
- "missing": false,
- "obsolete": false
- },
- "leave_chat_warning": {
- "translation": "Sei sicuro di voler lasciare questa conversazione?",
- "defaultMessage": "Are you sure you want to leave this conversation?",
- "description": "Alert dialog warning when unsubscribing from a chat.",
- "missing": false,
- "obsolete": false
- },
- "link_contact_us": {
- "translation": "Contattaci",
- "defaultMessage": "Contact Us",
- "description": "Ancor text for contacting us by email",
- "missing": false,
- "obsolete": false
- },
- "link_privacy_policy": {
- "translation": "Política della privacy",
- "defaultMessage": "Privacy Policy",
- "description": "Ancor text for privacy policy link",
- "missing": false,
- "obsolete": false
- },
- "link_terms_of_service": {
- "translation": "Termini d'uso del servizio",
- "defaultMessage": "Terms of Service",
- "description": "Ancor text for terms of service link",
- "missing": false,
- "obsolete": false
- },
- "login_prompt": {
- "translation": "Nome dell'utente",
- "defaultMessage": "Login",
- "description": "Placeholer for username/login",
- "missing": false,
- "obsolete": false
- },
- "menu_item_archive_topic": {
- "translation": "Archivia",
- "defaultMessage": "Archive",
- "description": "Move topic from the list of active chats to archive",
- "missing": false,
- "obsolete": false
- },
- "menu_item_block": {
- "translation": "Bloccare",
- "defaultMessage": "Block",
- "description": "Block topic or user",
- "missing": false,
- "obsolete": false
- },
- "menu_item_clear_messages": {
- "translation": "Cancella i messaggi",
- "defaultMessage": "Clear messages",
- "description": "Delete all messages",
- "missing": false,
- "obsolete": false
- },
- "menu_item_clear_messages_for_all": {
- "translation": "Cancella per tutti",
- "defaultMessage": "Clear for All",
- "description": "Delete all message(s) for all members",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete": {
- "translation": "Elimina",
- "defaultMessage": "Delete",
- "description": "Delete selected messages",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete_for_all": {
- "translation": "Elimina per tutti",
- "defaultMessage": "Delete for All",
- "description": "Delete selected message(s) for all members",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete_topic": {
- "translation": "Eliminare",
- "defaultMessage": "Delete",
- "description": "Delete entire topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_edit_permissions": {
- "translation": "Modifica permessi",
- "defaultMessage": "Edit permissions",
- "description": "Menu item [Edit permissions]",
- "missing": false,
- "obsolete": false
- },
- "menu_item_info": {
- "translation": "Informazioni",
- "defaultMessage": "Info",
- "description": "Show extended topic information",
- "missing": false,
- "obsolete": false
- },
- "menu_item_member_delete": {
- "translation": "Eliminare",
- "defaultMessage": "Remove",
- "description": "Remove user from topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_mute": {
- "translation": "Silenziare",
- "defaultMessage": "Mute",
- "description": "Turn off notifications",
- "missing": false,
- "obsolete": false
- },
- "menu_item_restore_topic": {
- "translation": "Ripristinare",
- "defaultMessage": "Restore",
- "description": "Restore topic from archive",
- "missing": false,
- "obsolete": false
- },
- "menu_item_send_retry": {
- "translation": "Riprova",
- "defaultMessage": "Retry",
- "description": "Retry sending message",
- "missing": false,
- "obsolete": false
- },
- "menu_item_unblock": {
- "translation": "Sblocca",
- "defaultMessage": "Unblock",
- "description": "Unblock topic or user",
- "missing": false,
- "obsolete": false
- },
- "menu_item_unmute": {
- "translation": "Riattiva le notifiche",
- "defaultMessage": "Unmute",
- "description": "Turn notifications on",
- "missing": false,
- "obsolete": false
- },
- "message_sending": {
- "translation": "invio...",
- "defaultMessage": "sending...",
- "description": "Message being sent, in place of time stamp",
- "missing": false,
- "obsolete": false
- },
- "message_sending_failed": {
- "translation": "non è possibile inviare il messaggio",
- "defaultMessage": "failed",
- "description": "Failed to send message, in place of time stamp",
- "missing": false,
- "obsolete": false
- },
- "messages_not_readable": {
- "translation": "nessun accesso ai messaggi",
- "defaultMessage": "no access to messages",
- "description": "Message shown in topic without the read access",
- "missing": false,
- "obsolete": false
- },
- "messaging_disabled_prompt": {
- "translation": "L'invio dei messaggi è disabilitato",
- "defaultMessage": "Messaging disabled",
- "description": "Prompt in SendMessage in read-only topic",
- "missing": false,
- "obsolete": false
- },
- "more_online_members": {
- "translation": "+{overflow} e oltre",
- "defaultMessage": "+{overflow} more",
- "description": "Shown in MessagesView title bar when the number of online subscribers exceeds MAX_ONLINE_IN_TOPIC",
- "missing": false,
- "obsolete": false
- },
- "new_message_prompt": {
- "translation": "Nuovo messaggio",
- "defaultMessage": "New message",
- "description": "Prompt in send message field",
- "missing": false,
- "obsolete": false
- },
- "new_password_placeholder": {
- "translation": "Inserire nuova password",
- "defaultMessage": "Enter new password",
- "description": "Placeholder for entering new password",
- "missing": false,
- "obsolete": false
- },
- "no_connection": {
- "translation": "Nessuna connessione",
- "defaultMessage": "No connection",
- "description": "Warning that the user is offline.",
- "missing": false,
- "obsolete": false
- },
- "no_contacts": {
- "translation": "Non ci sono contatti :-(",
- "defaultMessage": "You have no contacts :-(",
- "description": "Shown in ContactsView when the user has no contacts",
- "missing": false,
- "obsolete": false
- },
- "numeric_confirmation_code_prompt": {
- "translation": "Solo numeri",
- "defaultMessage": "Numbers only",
- "description": "Prompt for numeric conformation code",
- "missing": false,
- "obsolete": false
- },
- "online_now": {
- "translation": "in linea",
- "defaultMessage": "online now",
- "description": "Indicator that the user or topic is currently online",
- "missing": false,
- "obsolete": false
- },
- "password_prompt": {
- "translation": "Password",
- "defaultMessage": "Password",
- "description": "Placeholder/prompt for entering password",
- "missing": false,
- "obsolete": false
- },
- "password_unchanged_prompt": {
- "translation": "Non è cambiata",
- "defaultMessage": "Unchanged",
- "description": "Message in editor while password is unchanged",
- "missing": false,
- "obsolete": false
- },
- "peers_messaging_disabled": {
- "translation": "I messaggi punto punto sono disabilitati.",
- "defaultMessage": "Peer's messaging is disabled.",
- "description": "Shown when the p2p peer's messaging is disabled",
- "missing": false,
- "obsolete": false
- },
- "permission_admin": {
- "translation": "Approvare ({val})",
- "defaultMessage": "Approve ({val})",
- "description": "Name of A permission",
- "missing": false,
- "obsolete": false
- },
- "permission_delete": {
- "translation": "Eliminare ({val})",
- "defaultMessage": "Delete ({val})",
- "description": "Name of D permission",
- "missing": false,
- "obsolete": false
- },
- "permission_join": {
- "translation": "Unisci ({val})",
- "defaultMessage": "Join ({val})",
- "description": "Name of J permission",
- "missing": false,
- "obsolete": false
- },
- "permission_owner": {
- "translation": "Propietario ({val})",
- "defaultMessage": "Owner ({val})",
- "description": "Name of O permission",
- "missing": false,
- "obsolete": false
- },
- "permission_pres": {
- "translation": "Notifica arrivata ({val})",
- "defaultMessage": "Get notified ({val})",
- "description": "Name of P permission",
- "missing": false,
- "obsolete": false
- },
- "permission_read": {
- "translation": "Leggi ({val})",
- "defaultMessage": "Read ({val})",
- "description": "Name of R permission",
- "missing": false,
- "obsolete": false
- },
- "permission_share": {
- "translation": "Condividi ({val})",
- "defaultMessage": "Share ({val})",
- "description": "Name of S permission",
- "missing": false,
- "obsolete": false
- },
- "permission_write": {
- "translation": "Scrivi ({val})",
- "defaultMessage": "Write ({val})",
- "description": "Name of W permission",
- "missing": false,
- "obsolete": false
- },
- "phone_dative": {
- "translation": "telefono",
- "defaultMessage": "phone",
- "description": "Dative case of 'phone', i.e. 'phone' in 'by phone'",
- "missing": false,
- "obsolete": false
- },
- "private_editing_placeholder": {
- "translation": "Visibile solo a te",
- "defaultMessage": "Visible to you only",
- "description": "Placeholder for editing 'private'",
- "missing": false,
- "obsolete": false
- },
- "push_init_failed": {
- "translation": "Non è possibile inizializzare le notifiche push",
- "defaultMessage": "Failed to initialize push notifications",
- "description": "Error message when push notifications have failed to initialize.",
- "missing": false,
- "obsolete": false
- },
- "reconnect_countdown": {
- "translation": "Disconnesso. Riconnessione in {seconds}…",
- "defaultMessage": "Disconnected. Reconnecting in {seconds}…",
- "description": "Message shown when an app update is available.",
- "missing": false,
- "obsolete": false
- },
- "reconnect_now": {
- "translation": "Riprovare",
- "defaultMessage": "Try now",
- "description": "Prompt for reconnecting now",
- "missing": false,
- "obsolete": false
- },
- "reload_update": {
- "translation": "Ricaricare",
- "defaultMessage": "Reload",
- "description": "Call to action to reload application when update is available.",
- "missing": false,
- "obsolete": false
- },
- "report_chat_warning": {
- "translation": "Sei sicuro di bloccare e denunciare questa conversazione?",
- "defaultMessage": "Are you sure you want to block and report this conversation?",
- "description": "Alert dialog warning when reporting a conversation for abuse",
- "missing": false,
- "obsolete": false
- },
- "requested_permissions": {
- "translation": "Sollecito",
- "defaultMessage": "Requested",
- "description": "Title for permissions",
- "missing": false,
- "obsolete": false
- },
- "save_attachment": {
- "translation": "salva",
- "defaultMessage": "save",
- "description": "Call to save an attachment",
- "missing": false,
- "obsolete": false
- },
- "search_for_contacts": {
- "translation": "Usa la ricerca per trovare i contatti",
- "defaultMessage": "Use search to find contacts",
- "description": "Text shown in contacts view when user entered no search query.",
- "missing": false,
- "obsolete": false
- },
- "search_no_results": {
- "translation": "La ricerca non ha trovato nessun risultato",
- "defaultMessage": "Search returned no results",
- "description": "Text shown in contacts view when query returned no results.",
- "missing": false,
- "obsolete": false
- },
- "search_placeholder": {
- "translation": "Elenca come: email:mario@example.com, tel:17025550003...",
- "defaultMessage": "List like alice@example.com, +17025550003...",
- "description": "Placeholder in contacts search field",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_notifications": {
- "translation": "Notifiche",
- "defaultMessage": "Notifications",
- "description": "Sidepanel title for AccNotificationsView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_support": {
- "translation": "Supporto",
- "defaultMessage": "Support",
- "description": "Sidepanel title for AccSupportView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_account_settings": {
- "translation": "Impostazioni dell'account",
- "defaultMessage": "Account Settings",
- "description": "Sidepanel title for AccountSettingsView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_archive": {
- "translation": "Chats archiviate",
- "defaultMessage": "Archived Chats",
- "description": "Sidepanel title for ContactsView-Archive.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_blocked": {
- "translation": "Chats bloccate",
- "defaultMessage": "Blocked Chats",
- "description": "Sidepanel title for ContactsView-Blocked.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_cred": {
- "translation": "Conferma le credenziali",
- "defaultMessage": "Confirm Credentials",
- "description": "Sidepanel title for ValidationView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_login": {
- "translation": "Entra",
- "defaultMessage": "Sign In",
- "description": "Sidepanel title for LoginView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_newtpk": {
- "translation": "Comincia una Nuova Chat",
- "defaultMessage": "Start New Chat",
- "description": "Sidepanel title for NewTopicView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_register": {
- "translation": "Creare Account",
- "defaultMessage": "Create Account",
- "description": "Sidepanel title for CreateAccountView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_reset": {
- "translation": "Resetta la Password",
- "defaultMessage": "Reset Password",
- "description": "Sidepanel title for PasswordResetView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_settings": {
- "translation": "Impostazioni",
- "defaultMessage": "Settings",
- "description": "Sidepanel title for SettingsView.",
- "missing": false,
- "obsolete": false
- },
- "stay_logged_in": {
- "translation": "Rimani loggato",
- "defaultMessage": "Stay logged in",
- "description": "Label for a checkbox",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_find_user": {
- "translation": "cerca",
- "defaultMessage": "find",
- "description": "Tab title Find",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_group_by_id": {
- "translation": "per ID",
- "defaultMessage": "by id",
- "description": "Tab title Find topic by ID",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_new_group": {
- "translation": "nuovo gruppo",
- "defaultMessage": "new group",
- "description": "Tab title New Group",
- "missing": false,
- "obsolete": false
- },
- "tags_editor_no_tags": {
- "translation": "Aggiungi alcune etichette",
- "defaultMessage": "Add some tags",
- "description": "Tag editor prompt when no tags are found.",
- "missing": false,
- "obsolete": false
- },
- "tags_not_found": {
- "translation": "Non ci sono etichette definite. Aggiungine alcune.",
- "defaultMessage": "No tags defined. Add some.",
- "description": "",
- "missing": false,
- "obsolete": false
- },
- "title_all_contacts": {
- "translation": "Tutti i contatti",
- "defaultMessage": "All Contacts",
- "description": "Section title [All Contacts]",
- "missing": false,
- "obsolete": false
- },
- "title_group_members": {
- "translation": "Membri del gruppo",
- "defaultMessage": "Group Members",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "title_manage_tags": {
- "translation": "Gestisci",
- "defaultMessage": "Manage",
- "description": "Section title for the list of tags",
- "missing": false,
- "obsolete": false
- },
- "title_not_found": {
- "translation": "Non trovato",
- "defaultMessage": "Not found",
- "description": "Title shown when topic is not found",
- "missing": false,
- "obsolete": false
- },
- "title_permissions": {
- "translation": "Permessi",
- "defaultMessage": "Permissions",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "title_tag_manager": {
- "translation": "Etichette (trova e scopri)",
- "defaultMessage": "Tags (search & discovery)",
- "description": "Section title for TagManager",
- "missing": false,
- "obsolete": false
- },
- "topic_block_warning": {
- "translation": "Sei sicuro di voler bloccare questa conversazione?",
- "defaultMessage": "Are you sure you want to block this conversation?",
- "description": "Alert warning when blocking a topic.",
- "missing": false,
- "obsolete": false
- },
- "topic_delete_warning": {
- "translation": "Sei sicuro di voler eliminare questa conversazione?",
- "defaultMessage": "Are you sure you want to delete this conversation? It cannot be undone.",
- "description": "Alert warning when deleting entire topic",
- "missing": false,
- "obsolete": false
- },
- "topic_name_editing_placeholder": {
- "translation": "Nome del gruppo",
- "defaultMessage": "Freeform name of the group",
- "description": "Prompt for entering topic name",
- "missing": false,
- "obsolete": false
- },
- "unnamed_topic": {
- "translation": "Senza nome",
- "defaultMessage": "Unnamed",
- "description": "Title shown when the topic has no name",
- "missing": false,
- "obsolete": false
- },
- "update_available": {
- "translation": "Aggiornamento disponibile.",
- "defaultMessage": "Update available.",
- "description": "Message shown when an app update is available.",
- "missing": false,
- "obsolete": false
- },
- "upload_finishing": {
- "translation": "sto terminando...",
- "defaultMessage": "finishing...",
- "description": "Notification that upload is finishing",
- "missing": false,
- "obsolete": false
- },
- "user_not_found": {
- "translation": "Non trovato",
- "defaultMessage": "Not found",
- "description": "In place of a user's full name when the user is not found.",
- "missing": false,
- "obsolete": false
- },
- "description_editing_placeholder": {
- "translation": "Descrizione (opzionale)",
- "defaultMessage": "Description (optional)",
- "description": "Placeholder for editing topic description",
- "missing": false,
- "obsolete": false
- },
- "label_description": {
- "translation": "Descrizione",
- "defaultMessage": "Description",
- "description": "Label for description of user or topic",
- "missing": false,
- "obsolete": false
- },
- "button_security": {
- "translation": "Sicurezza",
- "defaultMessage": "Security",
- "description": "Navigaton button for security panel.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_crop": {
- "translation": "Trascina per aggiustare",
- "defaultMessage": "Drag to Adjust",
- "description": "Title for AvatarCropView.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_general": {
- "translation": "Generale",
- "defaultMessage": "General",
- "description": "Title for TopicCommon.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_members": {
- "translation": "Membri",
- "defaultMessage": "Members",
- "description": "Title for managing group members view.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_security": {
- "translation": "Sicurezza",
- "defaultMessage": "Security",
- "description": "Title for TopicSecirity and AccSecurity.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_info": {
- "translation": "Informazione",
- "defaultMessage": "Info",
- "description": "Title for InfoView",
- "missing": false,
- "obsolete": false
- },
- "permissions_anonymous": {
- "translation": "Anonimo",
- "defaultMessage": "Anonymous",
- "description": "Title for editing default anonymous permissions",
- "missing": false,
- "obsolete": false
- },
- "permissions_authenticated": {
- "translation": "Autenticato",
- "defaultMessage": "Authenticated",
- "description": "Title for editing default authenticated permissions",
- "missing": false,
- "obsolete": false
- },
- "topic_delete": {
- "translation": "Elimina la conversazione",
- "defaultMessage": "Delete Conversation",
- "description": "Alert title when deleting the topic.",
- "missing": false,
- "obsolete": false
- },
- "permissions_user": {
- "translation": "Permessi dell'utente",
- "defaultMessage": "User's Permissions",
- "description": "Title for editing user's permissions",
- "missing": false,
- "obsolete": false
- },
- "password_reset_email_sent": {
- "translation": "Una mail con il codice di sicurezza è stata inviata.",
- "defaultMessage": "An email with security code has been sent.",
- "description": "Notification that the email with password reset instructions has been sent",
- "missing": false,
- "obsolete": false
- },
- "label_unarchive_topic": {
- "translation": "Archiviato:",
- "defaultMessage": "Archived:",
- "description": "Label for unarchiving the topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_reply": {
- "translation": "Replica",
- "defaultMessage": "Reply",
- "description": "Reply to message",
- "missing": false,
- "obsolete": false
- },
- "menu_item_forward": {
- "translation": "Reinvia",
- "defaultMessage": "Forward",
- "description": "Forward message",
- "missing": false,
- "obsolete": false
- },
- "forward_to": {
- "translation": "Reinvia a",
- "defaultMessage": "Forward to",
- "missing": false,
- "obsolete": false
- },
- "forward_to_search_placeholder": {
- "translation": "Cerca i contatti",
- "defaultMessage": "Search contacts",
- "description": "Contact search prompt when forwarding a message.",
- "missing": false,
- "obsolete": false
- },
- "label_new_password": {
- "translation": "Nuova password",
- "defaultMessage": "New password",
- "description": "Label for entering a new password",
- "missing": false,
- "obsolete": false
- },
- "drafty_unknown": {
- "translation": "Non supportato",
- "defaultMessage": "Unsupported",
- "description": "Unsupported entity in drafty",
- "missing": false,
- "obsolete": false
- },
- "calls_incoming": {
- "translation": "Chiamata in arrivo",
- "defaultMessage": "Incoming call",
- "description": "Incoming call label",
- "missing": false,
- "obsolete": false
- },
- "calls_outgoing": {
- "translation": "Chiamata in uscita",
- "defaultMessage": "Outgoing call",
- "description": "Outgoing call label",
- "missing": false,
- "obsolete": false
- },
- "calls_you_label": {
- "translation": "Tu",
- "defaultMessage": "You",
- "description": "Shown over the local video screen",
- "missing": false,
- "obsolete": false
- },
- "menu_item_video_call": {
- "translation": "Videochiamata",
- "defaultMessage": "Video call",
- "description": "Start video call",
- "missing": false,
- "obsolete": false
- },
- "already_in_call": {
- "translation": "Sei già in una chiamata in uscita!",
- "defaultMessage": "You already in an ongoing call!",
- "description": "Error message when the user tried to accept a new call without finishing pervious one",
- "missing": false,
- "obsolete": false
- },
- "call_cancelled": {
- "translation": "cancellata",
- "defaultMessage": "cancelled",
- "description": "Label for cancelled outgoing call",
- "missing": false,
- "obsolete": false
- },
- "call_missed": {
- "translation": "persa",
- "defaultMessage": "missed",
- "description": "Label for missed incoming call",
- "missing": false,
- "obsolete": false
- },
- "call_declined": {
- "translation": "rifiutata",
- "defaultMessage": "declined",
- "description": "Label for declined call",
- "missing": false,
- "obsolete": false
- },
- "call_disconnected": {
- "translation": "disconnessa",
- "defaultMessage": "disconnected",
- "description": "Label for disconnected call",
- "missing": false,
- "obsolete": false
- },
- "label_use_secure_connection": {
- "translation": "Usa una connessione sicura",
- "defaultMessage": "Use secure connection",
- "description": "Label for WS/WSS connection type in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "cred_confirmed_successfully": {
- "translation": "Confermato con successo",
- "defaultMessage": "Confirmed successfully",
- "description": "Notification message that the credential was successfully validated.",
- "missing": false,
- "obsolete": false
- },
- "menu_item_edit": {
- "translation": "Modifica",
- "defaultMessage": "Edit",
- "description": "Edit message",
- "missing": false,
- "obsolete": false
- },
- "editing_message": {
- "translation": "Edita",
- "defaultMessage": "Editing",
- "description": "Title over message editing preview",
- "missing": false,
- "obsolete": false
- },
- "message_edited_marker": {
- "translation": ", editando",
- "defaultMessage": ", edited",
- "description": "Marker indicating that the message was edited",
- "missing": false,
- "obsolete": false
- },
- "icon_title_add_image": {
- "translation": "Aggiungi immagine",
- "defaultMessage": "Add image",
- "description": "Icon tool tip for attaching an image",
- "missing": false,
- "obsolete": false
- },
- "icon_title_attach_file": {
- "translation": "Allega un file",
- "defaultMessage": "Attach file",
- "description": "Icon tool tip for attaching a file",
- "missing": false,
- "obsolete": false
- },
- "icon_title_delete": {
- "translation": "Eliminare la registrazione",
- "defaultMessage": "Delete recording",
- "description": "Icon tool tip for deleting recorded audio",
- "missing": false,
- "obsolete": false
- },
- "icon_title_pause": {
- "translation": "Riproduzione in pausa",
- "defaultMessage": "Pause playback",
- "description": "Icon tool tip for pausing audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_play": {
- "translation": "Riproduci la registrazione",
- "defaultMessage": "Play recording",
- "description": "Icon tool tip for starting audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_record_voice": {
- "translation": "Registra un messaggio vocale",
- "defaultMessage": "Record voice message",
- "description": "Icon tool tip for recording a voice message",
- "missing": false,
- "obsolete": false
- },
- "icon_title_resume": {
- "translation": "Riprendi la riproduzione",
- "defaultMessage": "Resume playback",
- "description": "Icon tool tip for resuming audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_send": {
- "translation": "Inviare il messaggio",
- "defaultMessage": "Send message",
- "description": "Icon tool tip for sending a message",
- "missing": false,
- "obsolete": false
- },
- "drag_file": {
- "translation": "Trascina il file qui",
- "defaultMessage": "Drag file here",
- "description": "Prompt on the file drag-n-drop overlay banner",
- "missing": false,
- "obsolete": false
- },
- "drafty_video": {
- "translation": "Registrazione video",
- "defaultMessage": "Video recording",
- "description": "Comment for videos embedded in Drafty",
- "missing": false,
- "obsolete": false
- },
- "call_in_progress": {
- "translation": "avanzamento",
- "defaultMessage": "in progress",
- "description": "Label for call in progress",
- "missing": false,
- "obsolete": false
- },
- "menu_item_audio_call": {
- "translation": "Chiamata",
- "defaultMessage": "Call",
- "description": "Start audio call",
- "missing": false,
- "obsolete": false
- },
- "unrecognized_video_format": {
- "translation": "Il formato di questo video non è riconosciuto",
- "defaultMessage": "Format of this video is not recognized",
- "description": "Error message when uploaded video is invalid",
- "missing": false,
- "obsolete": false
- },
- "loading_note": {
- "translation": "Caricando...",
- "defaultMessage": "Loading...",
- "description": "Message shown when component is loading",
- "missing": false,
- "obsolete": false
- },
- "password_i_have_code": {
- "translation": "Ho un codice",
- "defaultMessage": "I have code",
- "description": "Call to open field to enter password reset code",
- "missing": false,
- "obsolete": false
- },
- "label_reset_password_tel": {
- "translation": "Invia un SMS per resettare la password",
- "defaultMessage": "Send SMS to reset password",
- "description": "Label for password reset field by phone",
- "missing": false,
- "obsolete": false
- },
- "mobile_phone_number": {
- "translation": "Numero di telefono cellulare",
- "defaultMessage": "Mobile phone number",
- "description": "Prompt for entering a mobile phone number",
- "missing": false,
- "obsolete": false
- },
- "password_reset_sms_sent": {
- "translation": "Un SMS con il codice di sicurezza è stato inviato.",
- "defaultMessage": "A text message with security code has been sent.",
- "description": "Notification that the SMS with password reset instructions has been sent",
- "missing": false,
- "obsolete": false
- },
- "mobile_number_required": {
- "translation": "E' richiesto il numero di telefono cellulare",
- "defaultMessage": "Mobile phone number required",
- "description": "Error message",
- "missing": false,
- "obsolete": false
- },
- "current_email": {
- "translation": "Email attuale",
- "defaultMessage": "Current email",
- "description": "Label for old email",
- "missing": false,
- "obsolete": false
- },
- "current_phone": {
- "translation": "Numero di telefono attuale",
- "defaultMessage": "Current phone number",
- "description": "Label for old phone",
- "missing": false,
- "obsolete": false
- },
- "new_email": {
- "translation": "Nuova email",
- "defaultMessage": "New email",
- "description": "Prompt for entering a new email",
- "missing": false,
- "obsolete": false
- },
- "new_phone_number": {
- "translation": "Nuovo numero di telefono",
- "defaultMessage": "New phone number",
- "description": "Prompt for entering a new telephone number",
- "missing": false,
- "obsolete": false
- },
- "change_email": {
- "translation": "Cambiare l'indirizzo di posta elettronica",
- "defaultMessage": "Change email",
- "description": "Prompt to change current email",
- "missing": false,
- "obsolete": false
- },
- "change_phone": {
- "translation": "Cambiare numero di telefono",
- "defaultMessage": "Change phone number",
- "description": "Prompt to change current phone number",
- "missing": false,
- "obsolete": false
- },
- "call_busy": {
- "translation": "occupato",
- "defaultMessage": "busy",
- "description": "Label for busy call line",
- "missing": false,
- "obsolete": false
- },
- "pin_message": {
- "translation": "Fissare",
- "defaultMessage": "Pin",
- "description": "Menu item [Pin] for pinning message to the top.",
- "missing": false,
- "obsolete": false
- },
- "unpin_message": {
- "translation": "Rimuovere",
- "defaultMessage": "Unpin",
- "description": "Menu item [Unpin] for un-pinning the message.",
- "missing": false,
- "obsolete": false
- },
- "failed_to_init_audio": {
- "translation": "Non è possibile cominciare la registrazione audio",
- "defaultMessage": "Failed to initialize audio recording",
- "description": "Error message when audio is not available",
- "missing": false,
- "obsolete": false
- },
- "password_reset_success": {
- "translation": "Il reset della password è avvenuto con successo",
- "defaultMessage": "Password reset successfully",
- "description": "Notification message that the password was successfully reset.",
- "missing": false,
- "obsolete": false
- },
- "scan_qr_code": {
- "translation": "Scansionare il codice QR",
- "defaultMessage": "Scan QR Code",
- "description": "Title for scanning QR code",
- "missing": false,
- "obsolete": false
- },
- "self_topic_comment": {
- "translation": "Note, messaggi, links e archivi, sono salvati per i posteri",
- "defaultMessage": "Notes, messages, links, files saved for posterity",
- "description": "Comment for self topic for UI",
- "missing": false,
- "obsolete": false
- },
- "self_topic_name": {
- "translation": "Messaggi salvati",
- "defaultMessage": "Saved messages",
- "description": "Name of self topic for UI",
- "missing": false,
- "obsolete": false
- },
- "message_not_found": {
- "translation": "Messaggi non trovati",
- "defaultMessage": "message not found",
- "description": "Pinned message is not found",
- "missing": false,
- "obsolete": false
- },
- "message_deleted": {
- "translation": "Messaggi eliminati",
- "defaultMessage": "message deleted",
- "description": "Pinned message is deleted",
- "missing": false,
- "obsolete": false
- },
- "alias_editing_placeholder": {
- "translation": "Alias (opzionale)",
- "defaultMessage": "Alias (optional)",
- "description": "Placeholder for editing user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "label_alias": {
- "translation": "Alias:",
- "defaultMessage": "Alias:",
- "description": "Label for user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "alias_already_taken": {
- "translation": "(già utilizzato)",
- "defaultMessage": "(already taken)",
- "description": "Error message for alias already taken",
- "missing": false,
- "obsolete": false
- },
- "alias_invalid": {
- "translation": "(invalido)",
- "defaultMessage": "(invalid)",
- "description": "Error message for invalid alias",
- "missing": false,
- "obsolete": false
- },
- "label_alias_edit": {
- "translation": "Alias",
- "defaultMessage": "Alias",
- "description": "Label for editing user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "select_country": {
- "translation": "Seleziona la nazione",
- "defaultMessage": "Select country",
- "description": "Placeholder for the country selector",
- "missing": false,
- "obsolete": false
- },
- "text_copied": {
- "translation": "Copia nella clipboard",
- "defaultMessage": "Copied to clipboard",
- "description": "Notification that text has been copied to clipboard",
- "missing": false,
- "obsolete": false
- },
- "incognito_mode_description": {
- "translation": "Nascondi il tuo stato online e gli indicatori di digitazione.",
- "defaultMessage": "Hide your online status and typing indicators.",
- "description": "Description for incognito mode toggle",
- "missing": false,
- "obsolete": false
- },
- "send_command": {
- "translation": "Invia con {key}",
- "defaultMessage": "Send on {key}",
- "description": "Config option to send message on hitting CTRL(or Cmd)-Enter",
- "missing": false,
- "obsolete": false
- },
- "send_command_explained": {
- "translation": "Premi Invio per una nuova riga",
- "defaultMessage": "Press Enter for new line",
- "description": "Explanation how to enter newline when [Send on CTRL-Enter] is enabled",
- "missing": false,
- "obsolete": false
- },
- "send_plain": {
- "translation": "Invia con Invio",
- "defaultMessage": "Send on Enter",
- "description": "Config option to send message on hitting Enter",
- "missing": false,
- "obsolete": false
- },
- "send_plain_explained": {
- "translation": "Premi Shift + Invio per una nuova riga",
- "defaultMessage": "Press Shift + Enter for new line",
- "description": "Explanation how to enter newline when [Send on Enter] is enabled",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_general": {
- "translation": "Generale",
- "defaultMessage": "General",
- "description": "Sidepanel title for AccGeneralView.",
- "missing": false,
- "obsolete": false
- },
- "label_keyboard": {
- "translation": "Tastiera:",
- "defaultMessage": "Keyboard:",
- "description": "Label for send on enter settings",
- "missing": false,
- "obsolete": false
- },
- "color_schema_dark": {
- "translation": "Scuro",
- "defaultMessage": "Dark",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "color_schema_light": {
- "translation": "Chiaro",
- "defaultMessage": "Light",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "color_schema_system": {
- "translation": "Predefinito del sistema",
- "defaultMessage": "System default",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "label_color_schema": {
- "translation": "Tema:",
- "defaultMessage": "Theme:",
- "description": "Label for selecting color scheme (dark, light) in Settings",
- "missing": false,
- "obsolete": false
- },
- "label_text_size": {
- "translation": "Dimensione testo:",
- "defaultMessage": "Text size:",
- "description": "Label adjusting text size",
- "missing": false,
- "obsolete": false
- },
- "pin_chat": {
- "translation": "Fissa",
- "defaultMessage": "Pin",
- "description": "Menu item [Pin] for pinning chat to the top.",
- "missing": false,
- "obsolete": false
- },
- "unpin_chat": {
- "translation": "Sblocca",
- "defaultMessage": "Unpin",
- "description": "Menu item [Unpin] for un-pinning the chat.",
- "missing": false,
- "obsolete": false
- },
- "subscriber_count": {
- "translation": "{count, plural, one {{count, number} iscritto} other {{count, number} iscritti}}",
- "defaultMessage": "{count, plural, one {{count, number} subscriber} two {{count, number} subscribers} three {{count, number} subscribers} few {{count, number} subscribers} many {{count, number} subscribers} other {{count, number} subscribers}}",
- "description": "Count of channel subscribers",
- "missing": false,
- "obsolete": false
- },
- "label_member_count": {
- "translation": "Membri:",
- "defaultMessage": "Members:",
- "description": "Label for the count of group topic members",
- "missing": false,
- "obsolete": false
- },
- "label_subscriber_count": {
- "translation": "Iscritti:",
- "defaultMessage": "Subscribers:",
- "description": "Label for the count of channel subscribers",
- "missing": false,
- "obsolete": false
- },
- "member_count": {
- "translation": "{count, plural, one {{count, number} membro} other {{count, number} membri}}",
- "defaultMessage": "{count, plural, one {{count, number} member} two {{count, number} members} three {{count, number} members} few {{count, number} members} many {{count, number} members} other {{count, number} members}}",
- "description": "Count of group topic members",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_image": {
- "translation": "immagine",
- "defaultMessage": "image",
- "description": "Tab title Image",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_pattern": {
- "translation": "motivo",
- "defaultMessage": "pattern",
- "description": "Tab title Pattern",
- "missing": false,
- "obsolete": false
- },
- "button_restore": {
- "translation": "Ripristina predefinito",
- "defaultMessage": "Restore default",
- "description": "Reset setting to default",
- "missing": false,
- "obsolete": false
- },
- "wallpapers": {
- "translation": "Sfondi",
- "defaultMessage": "Wallpapers",
- "description": "Link or title for working with wallpapers.",
- "missing": false,
- "obsolete": false
- },
- "label_blur_wallpaper": {
- "translation": "Sfocatura:",
- "defaultMessage": "Blur:",
- "description": "Label adjusting blur amount",
- "missing": false,
- "obsolete": false
- },
- "label_scan_id": {
- "translation": "Scansiona il mio ID:",
- "defaultMessage": "Scan my ID:",
- "description": "Label [Scan my ID:]",
- "missing": false,
- "obsolete": false
- },
- "unknown_name": {
- "translation": "Sconosciuto",
- "defaultMessage": "Unknown",
- "description": "Name to show when the name is missing",
- "missing": false,
- "obsolete": false
- },
- "save_action": {
- "translation": "Salva",
- "defaultMessage": "Save",
- "description": "Label for Save button",
- "missing": false,
- "obsolete": false
- },
- "cannot_parse_vcard": {
- "translation": "Impossibile analizzare il file vCard.",
- "defaultMessage": "Cannot parse vCard file.",
- "description": "Error message when vCard file cannot be parsed",
- "missing": false,
- "obsolete": false
- },
- "chat_now": {
- "translation": "Chatta",
- "defaultMessage": "Chat",
- "description": "Label for message button in contact card",
- "missing": false,
- "obsolete": false
- },
- "find_user": {
- "translation": "Cerca",
- "defaultMessage": "Find",
- "description": "Label for search button in contact card",
- "missing": false,
- "obsolete": false
- },
- "label_contact_card": {
- "translation": "Scheda contatto",
- "defaultMessage": "Contact Card",
- "description": "Label for contact card preview",
- "missing": false,
- "obsolete": false
- },
- "label_contacts": {
- "translation": "Contatti",
- "defaultMessage": "Contacts",
- "description": "Label for contact information",
- "missing": false,
- "obsolete": false
- },
- "add_members_prompt": {
- "translation": "aggiungi membri",
- "defaultMessage": "add members",
- "description": "Placeholder in group members input field",
- "missing": false,
- "obsolete": false
- }
-}
diff --git a/src/i18n/ko.json b/src/i18n/ko.json
deleted file mode 100644
index 00b24f3a..00000000
--- a/src/i18n/ko.json
+++ /dev/null
@@ -1,1966 +0,0 @@
-{
- "action_block_contact": {
- "translation": "연락차단",
- "defaultMessage": "Block Contact",
- "description": "Flat button [Block Contact]",
- "missing": false,
- "obsolete": false
- },
- "action_cancel": {
- "translation": "취소",
- "defaultMessage": "cancel",
- "description": "Call to action [cancel]",
- "missing": false,
- "obsolete": false
- },
- "action_clear_messages": {
- "translation": "메시지지우기",
- "defaultMessage": "Clear Messages",
- "description": "Flat button [Clear Messages] (soft-delete messages)",
- "missing": false,
- "obsolete": false
- },
- "action_delete_messages": {
- "translation": "모든메시지지우기",
- "defaultMessage": "Clear Messages for All",
- "description": "Flat button [Clear for All] (hard-delete all messages)",
- "missing": false,
- "obsolete": false
- },
- "action_leave_chat": {
- "translation": "대화나누기",
- "defaultMessage": "Leave Conversation",
- "description": "Flat button [Leave Conversation]",
- "missing": false,
- "obsolete": false
- },
- "action_report_chat": {
- "translation": "대화기록",
- "defaultMessage": "Report Conversation",
- "description": "Flat button [Report Group]",
- "missing": false,
- "obsolete": false
- },
- "archived_contacts": {
- "translation": "보관된연락처({수})",
- "defaultMessage": "Archived contacts ({count})",
- "description": "Label for archived chats",
- "missing": false,
- "obsolete": false
- },
- "badge_danger": {
- "translation": "의심 많은",
- "defaultMessage": "Untrustworthy",
- "description": "Suspicious or untrustworthy account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_owner": {
- "translation": "소유자",
- "defaultMessage": "owner",
- "description": "Badge for indicating the owner",
- "missing": false,
- "obsolete": false
- },
- "badge_staff": {
- "translation": "직원 관리하에",
- "defaultMessage": "Staff-managed",
- "description": "Explanation of a staff-managed account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_verified": {
- "translation": "확인 됨",
- "defaultMessage": "Verified/official",
- "description": "Explanation of a verified account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_you": {
- "translation": "당신",
- "defaultMessage": "you",
- "description": "Badge for indicating the current user",
- "missing": false,
- "obsolete": false
- },
- "block_contact_warning": {
- "translation": "이연락처를차단하시겠습니까?",
- "defaultMessage": "Are you sure you want to block this contact?",
- "description": "Alert dialog warning when blocking a contact.",
- "missing": false,
- "obsolete": false
- },
- "blocked_contacts_link": {
- "translation": "차단된연락처({수})",
- "defaultMessage": "Blocked contacts ({count})",
- "description": "Blocked contacts link",
- "missing": false,
- "obsolete": false
- },
- "button_add_members": {
- "translation": "회원추가",
- "defaultMessage": "Add members",
- "description": "Flat button [Add members] (to topic)",
- "missing": false,
- "obsolete": false
- },
- "button_cancel": {
- "translation": "취소",
- "defaultMessage": "Cancel",
- "description": "Button [Cancel]",
- "missing": false,
- "obsolete": false
- },
- "button_confirm": {
- "translation": "확인",
- "defaultMessage": "Confirm",
- "description": "Button [Confirm]",
- "missing": false,
- "obsolete": false
- },
- "button_create": {
- "translation": "작성",
- "defaultMessage": "Create",
- "description": "Button [Create]",
- "missing": false,
- "obsolete": false
- },
- "button_delete_account": {
- "translation": "계정삭제",
- "defaultMessage": "Delete account",
- "description": "Button [Delete account]",
- "missing": false,
- "obsolete": false
- },
- "button_edit": {
- "translation": "편집",
- "defaultMessage": "Edit",
- "description": "Call to action [Edit]",
- "missing": false,
- "obsolete": false
- },
- "button_logout": {
- "translation": "로그아웃",
- "defaultMessage": "Logout",
- "description": "Button [Logout]",
- "missing": false,
- "obsolete": false
- },
- "button_ok": {
- "translation": "OK",
- "defaultMessage": "OK",
- "description": "Button [OK]",
- "missing": false,
- "obsolete": false
- },
- "button_reset": {
- "translation": "재설정",
- "defaultMessage": "Reset",
- "description": "Button [Reset]",
- "missing": false,
- "obsolete": false
- },
- "button_send_request": {
- "translation": "요청보내기",
- "defaultMessage": "Send request",
- "description": "Button [Send request]",
- "missing": false,
- "obsolete": false
- },
- "button_sign_in": {
- "translation": "로그인",
- "defaultMessage": "Sign in",
- "description": "Button [Sign In]",
- "missing": false,
- "obsolete": false
- },
- "button_sign_up": {
- "translation": "가입",
- "defaultMessage": "Sign up",
- "description": "Create account button [Sign Up]",
- "missing": false,
- "obsolete": false
- },
- "button_subscribe": {
- "translation": "구독",
- "defaultMessage": "Subscribe",
- "description": "Button [Subscribe]",
- "missing": false,
- "obsolete": false
- },
- "button_update": {
- "translation": "업데이트",
- "defaultMessage": "Update",
- "description": "Button [Update]",
- "missing": false,
- "obsolete": false
- },
- "cannot_initiate_file_upload": {
- "translation": "파일업로드를시작할수없습니다.",
- "defaultMessage": "Cannot initiate file upload.",
- "description": "Generic error messagewhen attachment fails",
- "missing": false,
- "obsolete": false
- },
- "channel": {
- "translation": "채널",
- "defaultMessage": "channel",
- "description": "Subtitle shown for channels in MessagesView instead of last seen",
- "missing": false,
- "obsolete": false
- },
- "channel_prompt": {
- "translation": "이 채널입니다",
- "defaultMessage": "This is a channel",
- "description": "Checkbox label when creating a channel",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation": {
- "translation": "새로운대화를시작하도록초대되었습니다.무엇을하시겠습니까?",
- "defaultMessage": "You are invited to start a new chat. What would you like to do?",
- "description": "New chat invitation message: [Accept] [Ignore] [Block].",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_accept": {
- "translation": "수락",
- "defaultMessage": "Accept",
- "description": "Action [Accept] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_block": {
- "translation": "차단",
- "defaultMessage": "Block",
- "description": "Action [Block] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_ignore": {
- "translation": "무시",
- "defaultMessage": "Ignore",
- "description": "Action [Ignore] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "clear_messages_warning": {
- "translation": "모든메시지를지우시겠습니까?실행취소할수없습니다.",
- "defaultMessage": "Are you sure you want to clear all messages? It cannot be undone.",
- "description": "Alert dialog warning when deleting all messages.",
- "missing": false,
- "obsolete": false
- },
- "code_doesnot_match": {
- "translation": "코드가일치하지않습니다",
- "defaultMessage": "Code does not match",
- "description": "Error message when the credential validation code is incorrect.",
- "missing": false,
- "obsolete": false
- },
- "contacts_not_found": {
- "translation": "채팅이없습니다\n¯∖_(ツ)_/¯",
- "defaultMessage": "You have no chats\\n¯∖_(ツ)_/¯",
- "description": "HTML message shown in ContactList when no contacts are found",
- "missing": false,
- "obsolete": false
- },
- "contacts_not_found_short": {
- "translation": "'{문의}'와일치하는연락처가없습니다.",
- "defaultMessage": "No contacts match ''{query}''",
- "description": "Shown in ContactsView when search returned no results",
- "missing": false,
- "obsolete": false
- },
- "credential_email_prompt": {
- "translation": "등록이메일",
- "defaultMessage": "Your registration email",
- "description": "Placeholder for entering email",
- "missing": false,
- "obsolete": false
- },
- "delete_account": {
- "translation": "계정삭제",
- "defaultMessage": "Delete account",
- "description": "Title for delete account warning",
- "missing": false,
- "obsolete": false
- },
- "delete_account_warning": {
- "translation": "계정을삭제하시겠습니까?실행취소할수없습니다.",
- "defaultMessage": "Are you sure you want to delete your account? It cannot be undone.",
- "description": "Warning message when deleting an account",
- "missing": false,
- "obsolete": false
- },
- "delete_messages_warning": {
- "translation": "모든사람의모든메시지를삭제하시겠습니까?실행취소할수없습니다.",
- "defaultMessage": "Are you sure you want to delete all messages for everyone? It cannot be undone.",
- "description": "Alert dialog warning when hard-deleting all messages.",
- "missing": false,
- "obsolete": false
- },
- "download_action": {
- "translation": "다운로드",
- "defaultMessage": "download",
- "description": "Call to action [download]",
- "missing": false,
- "obsolete": false
- },
- "drafty_attachment": {
- "translation": "부착",
- "defaultMessage": "Attachment",
- "description": "Comment for attachment in Drafty",
- "missing": false,
- "obsolete": false
- },
- "drafty_form": {
- "translation": "형태:",
- "defaultMessage": "Form:",
- "description": "Comment for form in Drafty",
- "missing": false,
- "obsolete": false
- },
- "drafty_image": {
- "translation": "이미지",
- "defaultMessage": "Picture",
- "description": "Comment for embedded images in Drafty",
- "missing": false,
- "obsolete": false
- },
- "email_dative": {
- "translation": "이메일",
- "defaultMessage": "email",
- "description": "Dative case of 'email', i.e. 'email' in 'by email'",
- "missing": false,
- "obsolete": false
- },
- "email_prompt": {
- "translation": "이메일(예:hong@example.com)",
- "defaultMessage": "Email, e.g. jdoe@example.com",
- "description": "Input placeholder for email entry",
- "missing": false,
- "obsolete": false
- },
- "enable_peers_messaging": {
- "translation": "활성화",
- "defaultMessage": "Enable",
- "description": "Call to action to enable peer's messaging",
- "missing": false,
- "obsolete": false
- },
- "enter_confirmation_code_prompt": {
- "translation": "{방법}으로보낸확인코드를입력하십시오:",
- "defaultMessage": "Confirmation code",
- "description": "Request to enter confirmation code",
- "missing": false,
- "obsolete": false
- },
- "error_invalid_id": {
- "translation": "잘못된ID",
- "defaultMessage": "Invalid ID",
- "description": "Error message",
- "missing": false,
- "obsolete": false
- },
- "file_attachment_too_large": {
- "translation": "파일크기{크기}이(가){제한}제한을초과합니다.",
- "defaultMessage": "The file size {size} exceeds the {limit} limit.",
- "description": "Error message when attachment is too large",
- "missing": false,
- "obsolete": false
- },
- "forgot_password_link": {
- "translation": "비밀번호를잊으셨습니까?",
- "defaultMessage": "Forgot password?",
- "description": "Link to Reset password form",
- "missing": false,
- "obsolete": false
- },
- "full_name_prompt": {
- "translation": "전체이름(예:홍길동)",
- "defaultMessage": "Full name, e.g. John Doe",
- "description": "Input placeholder for person's full name",
- "missing": false,
- "obsolete": false
- },
- "granted_permissions": {
- "translation": "승낙하다",
- "defaultMessage": "Granted",
- "description": "Title for permissions",
- "missing": false,
- "obsolete": false
- },
- "group_has_no_members": {
- "translation": "회원없음",
- "defaultMessage": "No members",
- "description": "Shown in place of group members",
- "missing": false,
- "obsolete": false
- },
- "group_user_id_prompt": {
- "translation": "그룹또는사용자ID",
- "defaultMessage": "Group or User ID",
- "description": "Prompt for entering user or group ID",
- "missing": false,
- "obsolete": false
- },
- "image_caption_prompt": {
- "translation": "이미지설명",
- "defaultMessage": "Image caption",
- "description": "Prompt in SendMessage for attached image",
- "missing": false,
- "obsolete": false
- },
- "invalid_content": {
- "translation": "잘못된내용",
- "defaultMessage": "invalid content",
- "description": "Shown when the message is unreadable",
- "missing": false,
- "obsolete": false
- },
- "invalid_security_token": {
- "translation": "유효하지않은보안토큰",
- "defaultMessage": "Invalid security token",
- "description": "Error message when resetting password.",
- "missing": false,
- "obsolete": false
- },
- "label_client": {
- "translation": "클라이언트: ",
- "defaultMessage": "Client:",
- "description": "Label for a client version",
- "missing": false,
- "obsolete": false
- },
- "label_content_type": {
- "translation": "컨텐츠유형: ",
- "defaultMessage": "Content type:",
- "description": "Label for file content type (mime)",
- "missing": false,
- "obsolete": false
- },
- "label_default_access_mode": {
- "translation": "기본액세스모드: ",
- "defaultMessage": "Default access mode:",
- "description": "Label for default access mode",
- "missing": false,
- "obsolete": false
- },
- "label_file_name": {
- "translation": "파일이름: ",
- "defaultMessage": "File name:",
- "description": "Label for a file name",
- "missing": false,
- "obsolete": false
- },
- "label_group_members": {
- "translation": "그룹회원: ",
- "defaultMessage": "Group members:",
- "description": "Section title or label",
- "missing": false,
- "obsolete": false
- },
- "label_incognito_mode": {
- "translation": "시크릿모드: ",
- "defaultMessage": "Incognito mode:",
- "description": "Label for incognito mode toggle",
- "missing": false,
- "obsolete": false
- },
- "label_message_sound": {
- "translation": "메시지소리: ",
- "defaultMessage": "Message sound:",
- "description": "Label for message sounds toggle",
- "missing": false,
- "obsolete": false
- },
- "label_muting_topic": {
- "translation": "음소거: ",
- "defaultMessage": "Muted:",
- "description": "Label for Muting/unmuting the topic",
- "missing": false,
- "obsolete": false
- },
- "label_other_user": {
- "translation": "기타",
- "defaultMessage": "Other",
- "description": "Label for the other user when the user is unnamed",
- "missing": false,
- "obsolete": false
- },
- "label_password": {
- "translation": "비밀번호",
- "defaultMessage": "Password",
- "description": "Label for password editing",
- "missing": false,
- "obsolete": false
- },
- "label_permissions": {
- "translation": "권한: ",
- "defaultMessage": "Permissions:",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "label_private": {
- "translation": "개인코멘트",
- "defaultMessage": "Private comment",
- "description": "Label for editing 'private'",
- "missing": false,
- "obsolete": false
- },
- "label_push_notifications": {
- "translation": "통지경보: ",
- "defaultMessage": "Notification alerts:",
- "description": "Label for push notifications switch",
- "missing": false,
- "obsolete": false
- },
- "label_push_notifications_disabled": {
- "translation": "통지경보(HTTPS필요): ",
- "defaultMessage": "Notification alerts (requires HTTPS):",
- "description": "Label for push notifications switch",
- "missing": false,
- "obsolete": false
- },
- "label_reset_password": {
- "translation": "비밀번호재설정이메일보내기: ",
- "defaultMessage": "Send a password reset email",
- "description": "Label for password reset field",
- "missing": false,
- "obsolete": false
- },
- "label_sdk": {
- "translation": "SDK:",
- "defaultMessage": "SDK:",
- "missing": false,
- "obsolete": false
- },
- "label_server": {
- "translation": "서버: ",
- "defaultMessage": "Server:",
- "description": "Label for a server version",
- "missing": false,
- "obsolete": false
- },
- "label_server_address": {
- "translation": "서버주소: ",
- "defaultMessage": "Server address:",
- "missing": false,
- "obsolete": false
- },
- "label_server_to_use": {
- "translation": "사용할서버:",
- "defaultMessage": "Server to use:",
- "description": "Label for server selector in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "label_size": {
- "translation": "크기: ",
- "defaultMessage": "Size:",
- "description": "Label for file size",
- "missing": false,
- "obsolete": false
- },
- "label_topic_name": {
- "translation": "이름",
- "defaultMessage": "Name",
- "description": "Label for editing topic name",
- "missing": false,
- "obsolete": false
- },
- "label_user_contacts": {
- "translation": "연락처",
- "defaultMessage": "Contacts",
- "description": "Label for user contacts",
- "missing": false,
- "obsolete": false
- },
- "label_user_id": {
- "translation": "ID:",
- "defaultMessage": "ID:",
- "description": "Label for user address (ID)",
- "missing": false,
- "obsolete": false
- },
- "label_wire_transport": {
- "translation": "와이어수송:",
- "defaultMessage": "Wire transport:",
- "description": "Label for wire transport selection in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "label_you": {
- "translation": "당신: ",
- "defaultMessage": "You:",
- "description": "Label for the current user",
- "missing": false,
- "obsolete": false
- },
- "label_your_name": {
- "translation": "이름",
- "defaultMessage": "Your name",
- "description": "Label for full name editing",
- "missing": false,
- "obsolete": false
- },
- "label_your_permissions": {
- "translation": "권한: ",
- "defaultMessage": "Your permissions:",
- "description": "Label for current user permissions",
- "missing": false,
- "obsolete": false
- },
- "last_seen_timestamp": {
- "translation": "마지막으로본",
- "defaultMessage": "Last seen: {timestamp}",
- "description": "Label for the timestamp of when the user or topic was last online",
- "missing": false,
- "obsolete": false
- },
- "leave_chat_warning": {
- "translation": "이대화를나가시겠습니까?",
- "defaultMessage": "Are you sure you want to leave this conversation?",
- "description": "Alert dialog warning when unsubscribing from a chat.",
- "missing": false,
- "obsolete": false
- },
- "link_contact_us": {
- "translation": "문의처",
- "defaultMessage": "Contact Us",
- "description": "Ancor text for contacting us by email",
- "missing": false,
- "obsolete": false
- },
- "link_privacy_policy": {
- "translation": "개인정보보호정책",
- "defaultMessage": "Privacy Policy",
- "description": "Ancor text for privacy policy link",
- "missing": false,
- "obsolete": false
- },
- "link_terms_of_service": {
- "translation": "서비스약관",
- "defaultMessage": "Terms of Service",
- "description": "Ancor text for terms of service link",
- "missing": false,
- "obsolete": false
- },
- "login_prompt": {
- "translation": "로그인",
- "defaultMessage": "Login",
- "description": "Placeholer for username/login",
- "missing": false,
- "obsolete": false
- },
- "menu_item_archive_topic": {
- "translation": "보관",
- "defaultMessage": "Archive",
- "description": "Move topic from the list of active chats to archive",
- "missing": false,
- "obsolete": false
- },
- "menu_item_block": {
- "translation": "차단",
- "defaultMessage": "Block",
- "description": "Block topic or user",
- "missing": false,
- "obsolete": false
- },
- "menu_item_clear_messages": {
- "translation": "메시지지우기",
- "defaultMessage": "Clear messages",
- "description": "Delete all messages",
- "missing": false,
- "obsolete": false
- },
- "menu_item_clear_messages_for_all": {
- "translation": "모두지우기",
- "defaultMessage": "Clear for All",
- "description": "Delete all message(s) for all members",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete": {
- "translation": "삭제",
- "defaultMessage": "Delete",
- "description": "Delete selected messages",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete_for_all": {
- "translation": "모두삭제",
- "defaultMessage": "Delete for All",
- "description": "Delete selected message(s) for all members",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete_topic": {
- "translation": "삭제",
- "defaultMessage": "Delete",
- "description": "Delete entire topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_edit_permissions": {
- "translation": "편집권한",
- "defaultMessage": "Edit permissions",
- "description": "Menu item [Edit permissions]",
- "missing": false,
- "obsolete": false
- },
- "menu_item_info": {
- "translation": "정보",
- "defaultMessage": "Info",
- "description": "Show extended topic information",
- "missing": false,
- "obsolete": false
- },
- "menu_item_member_delete": {
- "translation": "제거",
- "defaultMessage": "Remove",
- "description": "Remove user from topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_mute": {
- "translation": "음소거",
- "defaultMessage": "Mute",
- "description": "Turn off notifications",
- "missing": false,
- "obsolete": false
- },
- "menu_item_restore_topic": {
- "translation": "복원",
- "defaultMessage": "Restore",
- "description": "Restore topic from archive",
- "missing": false,
- "obsolete": false
- },
- "menu_item_send_retry": {
- "translation": "다시시도",
- "defaultMessage": "Retry",
- "description": "Retry sending message",
- "missing": false,
- "obsolete": false
- },
- "menu_item_unblock": {
- "translation": "차단해제",
- "defaultMessage": "Unblock",
- "description": "Unblock topic or user",
- "missing": false,
- "obsolete": false
- },
- "menu_item_unmute": {
- "translation": "음소거해제",
- "defaultMessage": "Unmute",
- "description": "Turn notifications on",
- "missing": false,
- "obsolete": false
- },
- "message_sending": {
- "translation": "보내기...",
- "defaultMessage": "sending...",
- "description": "Message being sent, in place of time stamp",
- "missing": false,
- "obsolete": false
- },
- "message_sending_failed": {
- "translation": "실패",
- "defaultMessage": "failed",
- "description": "Failed to send message, in place of time stamp",
- "missing": false,
- "obsolete": false
- },
- "messages_not_readable": {
- "translation": "메시지에 액세스 할 수 없습니다",
- "defaultMessage": "no access to messages",
- "description": "Message shown in topic without the read access",
- "missing": false,
- "obsolete": false
- },
- "messaging_disabled_prompt": {
- "translation": "메시지비활성화",
- "defaultMessage": "Messaging disabled",
- "description": "Prompt in SendMessage in read-only topic",
- "missing": false,
- "obsolete": false
- },
- "more_online_members": {
- "translation": "+{넘침}더보기",
- "defaultMessage": "+{overflow} more",
- "description": "Shown in MessagesView title bar when the number of online subscribers exceeds MAX_ONLINE_IN_TOPIC",
- "missing": false,
- "obsolete": false
- },
- "new_message_prompt": {
- "translation": "새메시지",
- "defaultMessage": "New message",
- "description": "Prompt in send message field",
- "missing": false,
- "obsolete": false
- },
- "new_password_placeholder": {
- "translation": "새비밀번호입력",
- "defaultMessage": "Enter new password",
- "description": "Placeholder for entering new password",
- "missing": false,
- "obsolete": false
- },
- "no_connection": {
- "translation": "연결없음",
- "defaultMessage": "No connection",
- "description": "Warning that the user is offline.",
- "missing": false,
- "obsolete": false
- },
- "no_contacts": {
- "translation": "연락처가없습니다 (._.)",
- "defaultMessage": "You have no contacts :-(",
- "description": "Shown in ContactsView when the user has no contacts",
- "missing": false,
- "obsolete": false
- },
- "numeric_confirmation_code_prompt": {
- "translation": "숫자만",
- "defaultMessage": "Numbers only",
- "description": "Prompt for numeric conformation code",
- "missing": false,
- "obsolete": false
- },
- "online_now": {
- "translation": "현재접속중",
- "defaultMessage": "online now",
- "description": "Indicator that the user or topic is currently online",
- "missing": false,
- "obsolete": false
- },
- "password_prompt": {
- "translation": "비밀번호",
- "defaultMessage": "Password",
- "description": "Placeholder/prompt for entering password",
- "missing": false,
- "obsolete": false
- },
- "password_unchanged_prompt": {
- "translation": "변경되지않음",
- "defaultMessage": "Unchanged",
- "description": "Message in editor while password is unchanged",
- "missing": false,
- "obsolete": false
- },
- "peers_messaging_disabled": {
- "translation": "동료의메시지가비활성화되었습니다.",
- "defaultMessage": "Peer's messaging is disabled.",
- "description": "Shown when the p2p peer's messaging is disabled",
- "missing": false,
- "obsolete": false
- },
- "permission_admin": {
- "translation": "승인({val})",
- "defaultMessage": "Approve ({val})",
- "description": "Name of A permission",
- "missing": false,
- "obsolete": false
- },
- "permission_delete": {
- "translation": "삭제({val})",
- "defaultMessage": "Delete ({val})",
- "description": "Name of D permission",
- "missing": false,
- "obsolete": false
- },
- "permission_join": {
- "translation": "참여({val})",
- "defaultMessage": "Join ({val})",
- "description": "Name of J permission",
- "missing": false,
- "obsolete": false
- },
- "permission_owner": {
- "translation": "소유자({val})",
- "defaultMessage": "Owner ({val})",
- "description": "Name of O permission",
- "missing": false,
- "obsolete": false
- },
- "permission_pres": {
- "translation": "알림받기({val})",
- "defaultMessage": "Get notified ({val})",
- "description": "Name of P permission",
- "missing": false,
- "obsolete": false
- },
- "permission_read": {
- "translation": "읽기({val})",
- "defaultMessage": "Read ({val})",
- "description": "Name of R permission",
- "missing": false,
- "obsolete": false
- },
- "permission_share": {
- "translation": "공유({val})",
- "defaultMessage": "Share ({val})",
- "description": "Name of S permission",
- "missing": false,
- "obsolete": false
- },
- "permission_write": {
- "translation": "쓰기({val})",
- "defaultMessage": "Write ({val})",
- "description": "Name of W permission",
- "missing": false,
- "obsolete": false
- },
- "phone_dative": {
- "translation": "전화",
- "defaultMessage": "phone",
- "description": "Dative case of 'phone', i.e. 'phone' in 'by phone'",
- "missing": false,
- "obsolete": false
- },
- "private_editing_placeholder": {
- "translation": "나만볼수있습니다",
- "defaultMessage": "Visible to you only",
- "description": "Placeholder for editing 'private'",
- "missing": false,
- "obsolete": false
- },
- "push_init_failed": {
- "translation": "푸시알림을초기화하지못했습니다",
- "defaultMessage": "Failed to initialize push notifications",
- "description": "Error message when push notifications have failed to initialize.",
- "missing": false,
- "obsolete": false
- },
- "reconnect_countdown": {
- "translation": "연결이끊어졌습니다.다시연결하는중{초}…",
- "defaultMessage": "Disconnected. Reconnecting in {seconds}…",
- "description": "Message shown when an app update is available.",
- "missing": false,
- "obsolete": false
- },
- "reconnect_now": {
- "translation": "지금시도",
- "defaultMessage": "Try now",
- "description": "Prompt for reconnecting now",
- "missing": false,
- "obsolete": false
- },
- "reload_update": {
- "translation": "재로드",
- "defaultMessage": "Reload",
- "description": "Call to action to reload application when update is available.",
- "missing": false,
- "obsolete": false
- },
- "report_chat_warning": {
- "translation": "이대화를차단하고기록하시겠습니까?",
- "defaultMessage": "Are you sure you want to block and report this conversation?",
- "description": "Alert dialog warning when reporting a conversation for abuse",
- "missing": false,
- "obsolete": false
- },
- "requested_permissions": {
- "translation": "요청",
- "defaultMessage": "Requested",
- "description": "Title for permissions",
- "missing": false,
- "obsolete": false
- },
- "save_attachment": {
- "translation": "저장",
- "defaultMessage": "save",
- "description": "Call to save an attachment",
- "missing": false,
- "obsolete": false
- },
- "search_for_contacts": {
- "translation": "검색을사용하여연락처찾기",
- "defaultMessage": "Use search to find contacts",
- "description": "Text shown in contacts view when user entered no search query.",
- "missing": false,
- "obsolete": false
- },
- "search_no_results": {
- "translation": "검색결과가없습니다",
- "defaultMessage": "Search returned no results",
- "description": "Text shown in contacts view when query returned no results.",
- "missing": false,
- "obsolete": false
- },
- "search_placeholder": {
- "translation": "email:alice@example.com,tel:17025550003...와같은목록",
- "defaultMessage": "List like alice@example.com, +17025550003...",
- "description": "Placeholder in contacts search field",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_notifications": {
- "translation": "알림",
- "defaultMessage": "Notifications",
- "description": "Sidepanel title for AccNotificationsView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_support": {
- "translation": "지원",
- "defaultMessage": "Support",
- "description": "Sidepanel title for AccSupportView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_account_settings": {
- "translation": "계정설정",
- "defaultMessage": "Account Settings",
- "description": "Sidepanel title for AccountSettingsView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_archive": {
- "translation": "보관된채팅",
- "defaultMessage": "Archived Chats",
- "description": "Sidepanel title for ContactsView-Archive.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_blocked": {
- "translation": "차단된채팅",
- "defaultMessage": "Blocked Chats",
- "description": "Sidepanel title for ContactsView-Blocked.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_cred": {
- "translation": "자격증명확인",
- "defaultMessage": "Confirm Credentials",
- "description": "Sidepanel title for ValidationView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_login": {
- "translation": "로그인",
- "defaultMessage": "Sign In",
- "description": "Sidepanel title for LoginView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_newtpk": {
- "translation": "새채팅시작",
- "defaultMessage": "Start New Chat",
- "description": "Sidepanel title for NewTopicView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_register": {
- "translation": "계정만들기",
- "defaultMessage": "Create Account",
- "description": "Sidepanel title for CreateAccountView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_reset": {
- "translation": "비밀번호재설정",
- "defaultMessage": "Reset Password",
- "description": "Sidepanel title for PasswordResetView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_settings": {
- "translation": "설정",
- "defaultMessage": "Settings",
- "description": "Sidepanel title for SettingsView.",
- "missing": false,
- "obsolete": false
- },
- "stay_logged_in": {
- "translation": "로그인상태유지",
- "defaultMessage": "Stay logged in",
- "description": "Label for a checkbox",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_find_user": {
- "translation": "찾기",
- "defaultMessage": "find",
- "description": "Tab title Find",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_group_by_id": {
- "translation": "id로",
- "defaultMessage": "by id",
- "description": "Tab title Find topic by ID",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_new_group": {
- "translation": "새그룹",
- "defaultMessage": "new group",
- "description": "Tab title New Group",
- "missing": false,
- "obsolete": false
- },
- "tags_editor_no_tags": {
- "translation": "일부태그추가",
- "defaultMessage": "Add some tags",
- "description": "Tag editor prompt when no tags are found.",
- "missing": false,
- "obsolete": false
- },
- "tags_not_found": {
- "translation": "태그가정의되지않았습니다.일부를추가하십시오.",
- "defaultMessage": "No tags defined. Add some.",
- "description": "",
- "missing": false,
- "obsolete": false
- },
- "title_all_contacts": {
- "translation": "모든연락처",
- "defaultMessage": "All Contacts",
- "description": "Section title [All Contacts]",
- "missing": false,
- "obsolete": false
- },
- "title_group_members": {
- "translation": "그룹구성원",
- "defaultMessage": "Group Members",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "title_manage_tags": {
- "translation": "관리",
- "defaultMessage": "Manage",
- "description": "Section title for the list of tags",
- "missing": false,
- "obsolete": false
- },
- "title_not_found": {
- "translation": "찾을수없음",
- "defaultMessage": "Not found",
- "description": "Title shown when topic is not found",
- "missing": false,
- "obsolete": false
- },
- "title_permissions": {
- "translation": "권한",
- "defaultMessage": "Permissions",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "title_tag_manager": {
- "translation": "태그(사용자검색)",
- "defaultMessage": "Tags (search & discovery)",
- "description": "Section title for TagManager",
- "missing": false,
- "obsolete": false
- },
- "topic_block_warning": {
- "translation": "이대화를차단하시겠습니까?",
- "defaultMessage": "Are you sure you want to block this conversation?",
- "description": "Alert warning when blocking a topic.",
- "missing": false,
- "obsolete": false
- },
- "topic_delete_warning": {
- "translation": "이대화를삭제하시겠습니까?",
- "defaultMessage": "Are you sure you want to delete this conversation? It cannot be undone.",
- "description": "Alert warning when deleting entire topic",
- "missing": false,
- "obsolete": false
- },
- "topic_name_editing_placeholder": {
- "translation": "그룹의자유형이름",
- "defaultMessage": "Freeform name of the group",
- "description": "Prompt for entering topic name",
- "missing": false,
- "obsolete": false
- },
- "unnamed_topic": {
- "translation": "이름없는",
- "defaultMessage": "Unnamed",
- "description": "Title shown when the topic has no name",
- "missing": false,
- "obsolete": false
- },
- "update_available": {
- "translation": "업데이트가능.",
- "defaultMessage": "Update available.",
- "description": "Message shown when an app update is available.",
- "missing": false,
- "obsolete": false
- },
- "upload_finishing": {
- "translation": "마무리...",
- "defaultMessage": "finishing...",
- "description": "Notification that upload is finishing",
- "missing": false,
- "obsolete": false
- },
- "user_not_found": {
- "translation": "찾을수없음",
- "defaultMessage": "Not found",
- "description": "In place of a user's full name when the user is not found.",
- "missing": false,
- "obsolete": false
- },
- "description_editing_placeholder": {
- "translation": "설명(선택 사항)",
- "defaultMessage": "Description (optional)",
- "description": "Placeholder for editing topic description",
- "missing": false,
- "obsolete": false
- },
- "label_description": {
- "translation": "설명",
- "defaultMessage": "Description",
- "description": "Label for description of user or topic",
- "missing": false,
- "obsolete": false
- },
- "button_security": {
- "translation": "보안",
- "defaultMessage": "Security",
- "description": "Navigaton button for security panel.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_crop": {
- "translation": "드래그하여 조정",
- "defaultMessage": "Drag to Adjust",
- "description": "Title for AvatarCropView.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_general": {
- "translation": "일반",
- "defaultMessage": "General",
- "description": "Title for TopicCommon.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_members": {
- "translation": "구성원",
- "defaultMessage": "Members",
- "description": "Title for managing group members view.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_security": {
- "translation": "보안",
- "defaultMessage": "Security",
- "description": "Title for TopicSecirity and AccSecurity.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_info": {
- "translation": "정보",
- "defaultMessage": "Info",
- "description": "Title for InfoView",
- "missing": false,
- "obsolete": false
- },
- "permissions_anonymous": {
- "translation": "익명",
- "defaultMessage": "Anonymous",
- "description": "Title for editing default anonymous permissions",
- "missing": false,
- "obsolete": false
- },
- "permissions_authenticated": {
- "translation": "인증됨",
- "defaultMessage": "Authenticated",
- "description": "Title for editing default authenticated permissions",
- "missing": false,
- "obsolete": false
- },
- "topic_delete": {
- "translation": "채팅 삭제",
- "defaultMessage": "Delete Conversation",
- "description": "Alert title when deleting the topic.",
- "missing": false,
- "obsolete": false
- },
- "permissions_user": {
- "translation": "사용자 권한",
- "defaultMessage": "User's Permissions",
- "description": "Title for editing user's permissions",
- "missing": false,
- "obsolete": false
- },
- "password_reset_email_sent": {
- "translation": "보안 코드가 포함된 이메일이 전송되었습니다.",
- "defaultMessage": "An email with security code has been sent.",
- "description": "Notification that the email with password reset instructions has been sent",
- "missing": false,
- "obsolete": false
- },
- "label_unarchive_topic": {
- "translation": "보관됨:",
- "defaultMessage": "Archived:",
- "description": "Label for unarchiving the topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_reply": {
- "translation": "회신하다",
- "defaultMessage": "Reply",
- "description": "Reply to message",
- "missing": false,
- "obsolete": false
- },
- "menu_item_forward": {
- "translation": "리디렉션",
- "defaultMessage": "Forward",
- "description": "Forward message",
- "missing": false,
- "obsolete": false
- },
- "forward_to": {
- "translation": "메시지를 리디렉션",
- "defaultMessage": "Forward to",
- "missing": false,
- "obsolete": false
- },
- "forward_to_search_placeholder": {
- "translation": "연락처 검색",
- "defaultMessage": "Search contacts",
- "description": "Contact search prompt when forwarding a message.",
- "missing": false,
- "obsolete": false
- },
- "label_new_password": {
- "translation": "새 비밀번호",
- "defaultMessage": "New password",
- "description": "Label for entering a new password",
- "missing": false,
- "obsolete": false
- },
- "drafty_unknown": {
- "translation": "지원되지 않음",
- "defaultMessage": "Unsupported",
- "description": "Unsupported entity in drafty",
- "missing": false,
- "obsolete": false
- },
- "calls_incoming": {
- "translation": "수신 전화",
- "defaultMessage": "Incoming call",
- "description": "Incoming call label",
- "missing": false,
- "obsolete": false
- },
- "calls_outgoing": {
- "translation": "발신 전화",
- "defaultMessage": "Outgoing call",
- "description": "Outgoing call label",
- "missing": false,
- "obsolete": false
- },
- "calls_you_label": {
- "translation": "너",
- "defaultMessage": "You",
- "description": "Shown over the local video screen",
- "missing": false,
- "obsolete": false
- },
- "menu_item_video_call": {
- "translation": "영상 통화",
- "defaultMessage": "Video call",
- "description": "Start video call",
- "missing": false,
- "obsolete": false
- },
- "already_in_call": {
- "translation": "당신은 이미 말하고 있습니다!",
- "defaultMessage": "You already in an ongoing call!",
- "description": "Error message when the user tried to accept a new call without finishing pervious one",
- "missing": false,
- "obsolete": false
- },
- "call_cancelled": {
- "translation": "취소 된",
- "defaultMessage": "cancelled",
- "description": "Label for cancelled outgoing call",
- "missing": false,
- "obsolete": false
- },
- "call_missed": {
- "translation": "부재중",
- "defaultMessage": "missed",
- "description": "Label for missed incoming call",
- "missing": false,
- "obsolete": false
- },
- "call_declined": {
- "translation": "통화 거부",
- "defaultMessage": "declined",
- "description": "Label for declined call",
- "missing": false,
- "obsolete": false
- },
- "call_disconnected": {
- "translation": "연결 끊김",
- "defaultMessage": "disconnected",
- "description": "Label for disconnected call",
- "missing": false,
- "obsolete": false
- },
- "label_use_secure_connection": {
- "translation": "보안 연결 사용",
- "defaultMessage": "Use secure connection",
- "description": "Label for WS/WSS connection type in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "cred_confirmed_successfully": {
- "translation": "성공적으로 확인됨",
- "defaultMessage": "Confirmed successfully",
- "description": "Notification message that the credential was successfully validated.",
- "missing": false,
- "obsolete": false
- },
- "menu_item_edit": {
- "translation": "편집",
- "defaultMessage": "Edit",
- "description": "Edit message",
- "missing": false,
- "obsolete": false
- },
- "editing_message": {
- "translation": "교정하다",
- "defaultMessage": "Editing",
- "description": "Title over message editing preview",
- "missing": false,
- "obsolete": false
- },
- "message_edited_marker": {
- "translation": " 수정됨",
- "defaultMessage": ", edited",
- "description": "Marker indicating that the message was edited",
- "missing": false,
- "obsolete": false
- },
- "icon_title_add_image": {
- "translation": "이미지 추가",
- "defaultMessage": "Add image",
- "description": "Icon tool tip for attaching an image",
- "missing": false,
- "obsolete": false
- },
- "icon_title_attach_file": {
- "translation": "파일을 첨부",
- "defaultMessage": "Attach file",
- "description": "Icon tool tip for attaching a file",
- "missing": false,
- "obsolete": false
- },
- "icon_title_delete": {
- "translation": "녹음 삭제",
- "defaultMessage": "Delete recording",
- "description": "Icon tool tip for deleting recorded audio",
- "missing": false,
- "obsolete": false
- },
- "icon_title_pause": {
- "translation": "재생 일시 중지",
- "defaultMessage": "Pause playback",
- "description": "Icon tool tip for pausing audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_play": {
- "translation": "녹음 재생",
- "defaultMessage": "Play recording",
- "description": "Icon tool tip for starting audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_record_voice": {
- "translation": "음성 메시지 녹음",
- "defaultMessage": "Record voice message",
- "description": "Icon tool tip for recording a voice message",
- "missing": false,
- "obsolete": false
- },
- "icon_title_resume": {
- "translation": "재생 재개",
- "defaultMessage": "Resume playback",
- "description": "Icon tool tip for resuming audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_send": {
- "translation": "문자 보내",
- "defaultMessage": "Send message",
- "description": "Icon tool tip for sending a message",
- "missing": false,
- "obsolete": false
- },
- "drag_file": {
- "translation": "여기로 파일 드래그",
- "defaultMessage": "Drag file here",
- "description": "Prompt on the file drag-n-drop overlay banner",
- "missing": false,
- "obsolete": false
- },
- "drafty_video": {
- "translation": "녹화",
- "defaultMessage": "Video recording",
- "description": "Comment for videos embedded in Drafty",
- "missing": false,
- "obsolete": false
- },
- "call_in_progress": {
- "translation": "통화 중",
- "defaultMessage": "in progress",
- "description": "Label for call in progress",
- "missing": false,
- "obsolete": false
- },
- "menu_item_audio_call": {
- "translation": "전화",
- "defaultMessage": "Call",
- "description": "Start audio call",
- "missing": false,
- "obsolete": false
- },
- "unrecognized_video_format": {
- "translation": "이 동영상의 형식을 인식할 수 없습니다.",
- "defaultMessage": "Format of this video is not recognized",
- "description": "Error message when uploaded video is invalid",
- "missing": false,
- "obsolete": false
- },
- "loading_note": {
- "translation": "로드 중...",
- "defaultMessage": "Loading...",
- "description": "Message shown when component is loading",
- "missing": false,
- "obsolete": false
- },
- "password_i_have_code": {
- "translation": "코드가 있습니다",
- "defaultMessage": "I have code",
- "description": "Call to open field to enter password reset code",
- "missing": false,
- "obsolete": false
- },
- "label_reset_password_tel": {
- "translation": "비밀번호 재설정을 위해 SMS 보내기",
- "defaultMessage": "Send SMS to reset password",
- "description": "Label for password reset field by phone",
- "missing": false,
- "obsolete": false
- },
- "mobile_phone_number": {
- "translation": "휴대전화 번호",
- "defaultMessage": "Mobile phone number",
- "description": "Prompt for entering a mobile phone number",
- "missing": false,
- "obsolete": false
- },
- "password_reset_sms_sent": {
- "translation": "보안 코드가 포함된 문자 메시지가 전송되었습니다.",
- "defaultMessage": "A text message with security code has been sent.",
- "description": "Notification that the SMS with password reset instructions has been sent",
- "missing": false,
- "obsolete": false
- },
- "mobile_number_required": {
- "translation": "휴대전화 번호 필요",
- "defaultMessage": "Mobile phone number required",
- "description": "Error message",
- "missing": false,
- "obsolete": false
- },
- "current_email": {
- "translation": "현재 이메일",
- "defaultMessage": "Current email",
- "description": "Label for old email",
- "missing": false,
- "obsolete": false
- },
- "current_phone": {
- "translation": "현재 전화번호",
- "defaultMessage": "Current phone number",
- "description": "Label for old phone",
- "missing": false,
- "obsolete": false
- },
- "new_email": {
- "translation": "새 이메일 주소",
- "defaultMessage": "New email",
- "description": "Prompt for entering a new email",
- "missing": false,
- "obsolete": false
- },
- "new_phone_number": {
- "translation": "새 전화번호",
- "defaultMessage": "New phone number",
- "description": "Prompt for entering a new telephone number",
- "missing": false,
- "obsolete": false
- },
- "change_email": {
- "translation": "이메일 변경",
- "defaultMessage": "Change email",
- "description": "Prompt to change current email",
- "missing": false,
- "obsolete": false
- },
- "change_phone": {
- "translation": "전화번호 변경",
- "defaultMessage": "Change phone number",
- "description": "Prompt to change current phone number",
- "missing": false,
- "obsolete": false
- },
- "call_busy": {
- "translation": "바쁘다",
- "defaultMessage": "busy",
- "description": "Label for busy call line",
- "missing": false,
- "obsolete": false
- },
- "pin_message": {
- "translation": "고정",
- "defaultMessage": "Pin",
- "description": "Menu item [Pin] for pinning message to the top.",
- "missing": false,
- "obsolete": false
- },
- "unpin_message": {
- "translation": "고정 해제",
- "defaultMessage": "Unpin",
- "description": "Menu item [Unpin] for un-pinning the message.",
- "missing": false,
- "obsolete": false
- },
- "failed_to_init_audio": {
- "translation": "오디오 녹음 초기화 실패",
- "defaultMessage": "Failed to initialize audio recording",
- "description": "Error message when audio is not available",
- "missing": false,
- "obsolete": false
- },
- "password_reset_success": {
- "translation": "비밀번호 재설정 성공",
- "defaultMessage": "Password reset successfully",
- "description": "Notification message that the password was successfully reset.",
- "missing": false,
- "obsolete": false
- },
- "scan_qr_code": {
- "translation": "QR 코드 스캔",
- "defaultMessage": "Scan QR Code",
- "description": "Title for scanning QR code",
- "missing": false,
- "obsolete": false
- },
- "self_topic_comment": {
- "translation": "후세를 위해 저장된 메모, 메시지, 링크, 파일",
- "defaultMessage": "Notes, messages, links, files saved for posterity",
- "description": "Comment for self topic for UI",
- "missing": false,
- "obsolete": false
- },
- "self_topic_name": {
- "translation": "저장된 메시지",
- "defaultMessage": "Saved messages",
- "description": "Name of self topic for UI",
- "missing": false,
- "obsolete": false
- },
- "message_not_found": {
- "translation": "메시지를 찾을 수 없음",
- "defaultMessage": "message not found",
- "description": "Pinned message is not found",
- "missing": false,
- "obsolete": false
- },
- "message_deleted": {
- "translation": "메시지 삭제됨",
- "defaultMessage": "message deleted",
- "description": "Pinned message is deleted",
- "missing": false,
- "obsolete": false
- },
- "alias_editing_placeholder": {
- "translation": "별칭(선택 사항)",
- "defaultMessage": "Alias (optional)",
- "description": "Placeholder for editing user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "label_alias": {
- "translation": "별칭:",
- "defaultMessage": "Alias:",
- "description": "Label for user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "alias_already_taken": {
- "translation": "(이미 취함)",
- "defaultMessage": "(already taken)",
- "description": "Error message for alias already taken",
- "missing": false,
- "obsolete": false
- },
- "alias_invalid": {
- "translation": "(잘못된)",
- "defaultMessage": "(invalid)",
- "description": "Error message for invalid alias",
- "missing": false,
- "obsolete": false
- },
- "label_alias_edit": {
- "translation": "별칭",
- "defaultMessage": "Alias",
- "description": "Label for editing user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "select_country": {
- "translation": "국가 선택",
- "defaultMessage": "Select country",
- "description": "Placeholder for the country selector",
- "missing": false,
- "obsolete": false
- },
- "text_copied": {
- "translation": "클립보드에 복사됨",
- "defaultMessage": "Copied to clipboard",
- "description": "Notification that text has been copied to clipboard",
- "missing": false,
- "obsolete": false
- },
- "color_schema_dark": {
- "translation": "어둡게",
- "defaultMessage": "Dark",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "color_schema_light": {
- "translation": "밝게",
- "defaultMessage": "Light",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "color_schema_system": {
- "translation": "시스템 기본값",
- "defaultMessage": "System default",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "label_color_schema": {
- "translation": "테마:",
- "defaultMessage": "Theme:",
- "description": "Label for selecting color scheme (dark, light) in Settings",
- "missing": false,
- "obsolete": false
- },
- "label_text_size": {
- "translation": "텍스트 크기:",
- "defaultMessage": "Text size:",
- "description": "Label adjusting text size",
- "missing": false,
- "obsolete": false
- },
- "incognito_mode_description": {
- "translation": "온라인 상태와 입력 표시기를 숨깁니다.",
- "defaultMessage": "Hide your online status and typing indicators.",
- "description": "Description for incognito mode toggle",
- "missing": false,
- "obsolete": false
- },
- "send_command": {
- "translation": "{key}로 전송",
- "defaultMessage": "Send on {key}",
- "description": "Config option to send message on hitting CTRL(or Cmd)-Enter",
- "missing": false,
- "obsolete": false
- },
- "send_command_explained": {
- "translation": "새 줄로 이동하려면 Enter를 누르세요",
- "defaultMessage": "Press Enter for new line",
- "description": "Explanation how to enter newline when [Send on CTRL-Enter] is enabled",
- "missing": false,
- "obsolete": false
- },
- "send_plain": {
- "translation": "Enter로 전송",
- "defaultMessage": "Send on Enter",
- "description": "Config option to send message on hitting Enter",
- "missing": false,
- "obsolete": false
- },
- "send_plain_explained": {
- "translation": "새 줄로 이동하려면 Shift + Enter를 누르세요",
- "defaultMessage": "Press Shift + Enter for new line",
- "description": "Explanation how to enter newline when [Send on Enter] is enabled",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_general": {
- "translation": "일반",
- "defaultMessage": "General",
- "description": "Sidepanel title for AccGeneralView.",
- "missing": false,
- "obsolete": false
- },
- "label_keyboard": {
- "translation": "키보드:",
- "defaultMessage": "Keyboard:",
- "description": "Label for send on enter settings",
- "missing": false,
- "obsolete": false
- },
- "pin_chat": {
- "translation": "고정",
- "defaultMessage": "Pin",
- "description": "Menu item [Pin] for pinning chat to the top.",
- "missing": false,
- "obsolete": false
- },
- "unpin_chat": {
- "translation": "고정 해제",
- "defaultMessage": "Unpin",
- "description": "Menu item [Unpin] for un-pinning the chat.",
- "missing": false,
- "obsolete": false
- },
- "subscriber_count": {
- "translation": "{count, plural, other {구독자 {count, number}명}}",
- "defaultMessage": "{count, plural, one {{count, number} subscriber} two {{count, number} subscribers} three {{count, number} subscribers} few {{count, number} subscribers} many {{count, number} subscribers} other {{count, number} subscribers}}",
- "description": "Count of channel subscribers",
- "missing": false,
- "obsolete": false
- },
- "label_member_count": {
- "translation": "멤버:",
- "defaultMessage": "Members:",
- "description": "Label for the count of group topic members",
- "missing": false,
- "obsolete": false
- },
- "label_subscriber_count": {
- "translation": "구독자:",
- "defaultMessage": "Subscribers:",
- "description": "Label for the count of channel subscribers",
- "missing": false,
- "obsolete": false
- },
- "member_count": {
- "translation": "{count, plural, other {멤버 {count, number}명}}",
- "defaultMessage": "{count, plural, one {{count, number} member} two {{count, number} members} three {{count, number} members} few {{count, number} members} many {{count, number} members} other {{count, number} members}}",
- "description": "Count of group topic members",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_image": {
- "translation": "이미지",
- "defaultMessage": "image",
- "description": "Tab title Image",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_pattern": {
- "translation": "패턴",
- "defaultMessage": "pattern",
- "description": "Tab title Pattern",
- "missing": false,
- "obsolete": false
- },
- "button_restore": {
- "translation": "기본값 복원",
- "defaultMessage": "Restore default",
- "description": "Reset setting to default",
- "missing": false,
- "obsolete": false
- },
- "wallpapers": {
- "translation": "배경화면",
- "defaultMessage": "Wallpapers",
- "description": "Link or title for working with wallpapers.",
- "missing": false,
- "obsolete": false
- },
- "label_blur_wallpaper": {
- "translation": "흐림:",
- "defaultMessage": "Blur:",
- "description": "Label adjusting blur amount",
- "missing": false,
- "obsolete": false
- },
- "label_scan_id": {
- "translation": "내 ID 스캔:",
- "defaultMessage": "Scan my ID:",
- "description": "Label [Scan my ID:]",
- "missing": false,
- "obsolete": false
- },
- "unknown_name": {
- "translation": "알 수 없음",
- "defaultMessage": "Unknown",
- "description": "Name to show when the name is missing",
- "missing": false,
- "obsolete": false
- },
- "save_action": {
- "translation": "저장",
- "defaultMessage": "Save",
- "description": "Label for Save button",
- "missing": false,
- "obsolete": false
- },
- "cannot_parse_vcard": {
- "translation": "vCard 파일을 파싱할 수 없습니다.",
- "defaultMessage": "Cannot parse vCard file.",
- "description": "Error message when vCard file cannot be parsed",
- "missing": false,
- "obsolete": false
- },
- "chat_now": {
- "translation": "채팅",
- "defaultMessage": "Chat",
- "description": "Label for message button in contact card",
- "missing": false,
- "obsolete": false
- },
- "find_user": {
- "translation": "찾기",
- "defaultMessage": "Find",
- "description": "Label for search button in contact card",
- "missing": false,
- "obsolete": false
- },
- "label_contact_card": {
- "translation": "연락처 카드",
- "defaultMessage": "Contact Card",
- "description": "Label for contact card preview",
- "missing": false,
- "obsolete": false
- },
- "label_contacts": {
- "translation": "연락처",
- "defaultMessage": "Contacts",
- "description": "Label for contact information",
- "missing": false,
- "obsolete": false
- },
- "add_members_prompt": {
- "translation": "멤버 추가",
- "defaultMessage": "add members",
- "description": "Placeholder in group members input field",
- "missing": false,
- "obsolete": false
- }
-}
diff --git a/src/i18n/pt.json b/src/i18n/pt.json
deleted file mode 100644
index 6ce52b11..00000000
--- a/src/i18n/pt.json
+++ /dev/null
@@ -1,1966 +0,0 @@
-{
- "action_block_contact": {
- "translation": "Bloquear Contato",
- "defaultMessage": "Block Contact",
- "description": "Flat button [Block Contact]",
- "missing": false,
- "obsolete": false
- },
- "action_cancel": {
- "translation": "Cancelar",
- "defaultMessage": "cancel",
- "description": "Call to action [cancel]",
- "missing": false,
- "obsolete": false
- },
- "action_clear_messages": {
- "translation": "Limpar Mensagens",
- "defaultMessage": "Clear Messages",
- "description": "Flat button [Clear Messages] (soft-delete messages)",
- "missing": false,
- "obsolete": false
- },
- "action_delete_messages": {
- "translation": "Limpar Mensagens para Todos",
- "defaultMessage": "Clear Messages for All",
- "description": "Flat button [Clear for All] (hard-delete all messages)",
- "missing": false,
- "obsolete": false
- },
- "action_leave_chat": {
- "translation": "Sair da Conversa",
- "defaultMessage": "Leave Conversation",
- "description": "Flat button [Leave Conversation]",
- "missing": false,
- "obsolete": false
- },
- "action_report_chat": {
- "translation": "Reportar Conversa",
- "defaultMessage": "Report Conversation",
- "description": "Flat button [Report Group]",
- "missing": false,
- "obsolete": false
- },
- "archived_contacts": {
- "translation": "Contatos arquivados ({count})",
- "defaultMessage": "Archived contacts ({count})",
- "description": "Label for archived chats",
- "missing": false,
- "obsolete": false
- },
- "badge_danger": {
- "translation": "Não confiável",
- "defaultMessage": "Untrustworthy",
- "description": "Suspicious or untrustworthy account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_owner": {
- "translation": "dono",
- "defaultMessage": "owner",
- "description": "Badge for indicating the owner",
- "missing": false,
- "obsolete": false
- },
- "badge_staff": {
- "translation": "Gerenciado pela equipe",
- "defaultMessage": "Staff-managed",
- "description": "Explanation of a staff-managed account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_verified": {
- "translation": "Verificado/oficial",
- "defaultMessage": "Verified/official",
- "description": "Explanation of a verified account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_you": {
- "translation": "você",
- "defaultMessage": "you",
- "description": "Badge for indicating the current user",
- "missing": false,
- "obsolete": false
- },
- "block_contact_warning": {
- "translation": "Tem certeza de que deseja bloquear este contato?",
- "defaultMessage": "Are you sure you want to block this contact?",
- "description": "Alert dialog warning when blocking a contact.",
- "missing": false,
- "obsolete": false
- },
- "blocked_contacts_link": {
- "translation": "Contatos bloqueados ({count})",
- "defaultMessage": "Blocked contacts ({count})",
- "description": "Blocked contacts link",
- "missing": false,
- "obsolete": false
- },
- "button_add_members": {
- "translation": "Adicionar membros",
- "defaultMessage": "Add members",
- "description": "Flat button [Add members] (to topic)",
- "missing": false,
- "obsolete": false
- },
- "button_cancel": {
- "translation": "Cancelar",
- "defaultMessage": "Cancel",
- "description": "Button [Cancel]",
- "missing": false,
- "obsolete": false
- },
- "button_confirm": {
- "translation": "Confirmar",
- "defaultMessage": "Confirm",
- "description": "Button [Confirm]",
- "missing": false,
- "obsolete": false
- },
- "button_create": {
- "translation": "Criar",
- "defaultMessage": "Create",
- "description": "Button [Create]",
- "missing": false,
- "obsolete": false
- },
- "button_delete_account": {
- "translation": "Excluir conta",
- "defaultMessage": "Delete account",
- "description": "Button [Delete account]",
- "missing": false,
- "obsolete": false
- },
- "button_edit": {
- "translation": "Editar",
- "defaultMessage": "Edit",
- "description": "Call to action [Edit]",
- "missing": false,
- "obsolete": false
- },
- "button_logout": {
- "translation": "Sair",
- "defaultMessage": "Logout",
- "description": "Button [Logout]",
- "missing": false,
- "obsolete": false
- },
- "button_ok": {
- "translation": "OK",
- "defaultMessage": "OK",
- "description": "Button [OK]",
- "missing": false,
- "obsolete": false
- },
- "button_reset": {
- "translation": "Reset",
- "defaultMessage": "Reset",
- "description": "Button [Reset]",
- "missing": false,
- "obsolete": false
- },
- "button_send_request": {
- "translation": "Enviar solicitação",
- "defaultMessage": "Send request",
- "description": "Button [Send request]",
- "missing": false,
- "obsolete": false
- },
- "button_sign_in": {
- "translation": "Entrar",
- "defaultMessage": "Sign in",
- "description": "Button [Sign In]",
- "missing": false,
- "obsolete": false
- },
- "button_sign_up": {
- "translation": "Cadastrar",
- "defaultMessage": "Sign up",
- "description": "Create account button [Sign Up]",
- "missing": false,
- "obsolete": false
- },
- "button_subscribe": {
- "translation": "Assinar",
- "defaultMessage": "Subscribe",
- "description": "Button [Subscribe]",
- "missing": false,
- "obsolete": false
- },
- "button_update": {
- "translation": "Atualizar",
- "defaultMessage": "Update",
- "description": "Button [Update]",
- "missing": false,
- "obsolete": false
- },
- "cannot_initiate_file_upload": {
- "translation": "Não posso iniciar o upload do arquivo.",
- "defaultMessage": "Cannot initiate file upload.",
- "description": "Generic error messagewhen attachment fails",
- "missing": false,
- "obsolete": false
- },
- "channel": {
- "translation": "canal",
- "defaultMessage": "channel",
- "description": "Subtitle shown for channels in MessagesView instead of last seen",
- "missing": false,
- "obsolete": false
- },
- "channel_prompt": {
- "translation": "Isto é um canal",
- "defaultMessage": "This is a channel",
- "description": "Checkbox label when creating a channel",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation": {
- "translation": "Você foi convidado para iniciar uma nova conversa. O que você gostaria de fazer?",
- "defaultMessage": "You are invited to start a new chat. What would you like to do?",
- "description": "New chat invitation message: [Accept] [Ignore] [Block].",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_accept": {
- "translation": "Aceitar",
- "defaultMessage": "Accept",
- "description": "Action [Accept] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_block": {
- "translation": "Bloquear",
- "defaultMessage": "Block",
- "description": "Action [Block] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_ignore": {
- "translation": "Ignorar",
- "defaultMessage": "Ignore",
- "description": "Action [Ignore] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "clear_messages_warning": {
- "translation": "Tem certeza de quer apagarar todas as mensagens? Esta ação não pode ser desfeita.",
- "defaultMessage": "Are you sure you want to clear all messages? It cannot be undone.",
- "description": "Alert dialog warning when deleting all messages.",
- "missing": false,
- "obsolete": false
- },
- "code_doesnot_match": {
- "translation": "Código não corresponde",
- "defaultMessage": "Code does not match",
- "description": "Error message when the credential validation code is incorrect.",
- "missing": false,
- "obsolete": false
- },
- "contacts_not_found": {
- "translation": "Você não tem conversas\n¯∖_(ツ)_/¯",
- "defaultMessage": "You have no chats\\n¯∖_(ツ)_/¯",
- "description": "HTML message shown in ContactList when no contacts are found",
- "missing": false,
- "obsolete": false
- },
- "contacts_not_found_short": {
- "translation": "Nenhum contato corresponde a ''{query}''",
- "defaultMessage": "No contacts match ''{query}''",
- "description": "Shown in ContactsView when search returned no results",
- "missing": false,
- "obsolete": false
- },
- "credential_email_prompt": {
- "translation": "Seu email de registro",
- "defaultMessage": "Your registration email",
- "description": "Placeholder for entering email",
- "missing": false,
- "obsolete": false
- },
- "delete_account": {
- "translation": "Excluir conta",
- "defaultMessage": "Delete account",
- "description": "Title for delete account warning",
- "missing": false,
- "obsolete": false
- },
- "delete_account_warning": {
- "translation": "Tem certeza de que deseja excluir sua conta? Isso não pode ser desfeito.",
- "defaultMessage": "Are you sure you want to delete your account? It cannot be undone.",
- "description": "Warning message when deleting an account",
- "missing": false,
- "obsolete": false
- },
- "delete_messages_warning": {
- "translation": "Tem certeza de que deseja excluir todas as mensagens para todos? Isso não pode ser desfeito.",
- "defaultMessage": "Are you sure you want to delete all messages for everyone? It cannot be undone.",
- "description": "Alert dialog warning when hard-deleting all messages.",
- "missing": false,
- "obsolete": false
- },
- "download_action": {
- "translation": "baixar",
- "defaultMessage": "download",
- "description": "Call to action [download]",
- "missing": false,
- "obsolete": false
- },
- "drafty_attachment": {
- "translation": "Anexo",
- "defaultMessage": "Attachment",
- "description": "Comment for attachment in Drafty",
- "missing": false,
- "obsolete": false
- },
- "drafty_form": {
- "translation": "Form:",
- "defaultMessage": "Form:",
- "description": "Comment for form in Drafty",
- "missing": false,
- "obsolete": false
- },
- "drafty_image": {
- "translation": "Imagem",
- "defaultMessage": "Picture",
- "description": "Comment for embedded images in Drafty",
- "missing": false,
- "obsolete": false
- },
- "email_dative": {
- "translation": "email",
- "defaultMessage": "email",
- "description": "Dative case of 'email', i.e. 'email' in 'by email'",
- "missing": false,
- "obsolete": false
- },
- "email_prompt": {
- "translation": "Email, ex. fulanodetal@exemplo.com",
- "defaultMessage": "Email, e.g. jdoe@example.com",
- "description": "Input placeholder for email entry",
- "missing": false,
- "obsolete": false
- },
- "enable_peers_messaging": {
- "translation": "Ativar",
- "defaultMessage": "Enable",
- "description": "Call to action to enable peer's messaging",
- "missing": false,
- "obsolete": false
- },
- "enter_confirmation_code_prompt": {
- "translation": "Código de confirmação",
- "defaultMessage": "Confirmation code",
- "description": "Request to enter confirmation code",
- "missing": false,
- "obsolete": false
- },
- "error_invalid_id": {
- "translation": "ID Inválido",
- "defaultMessage": "Invalid ID",
- "description": "Error message",
- "missing": false,
- "obsolete": false
- },
- "file_attachment_too_large": {
- "translation": "O tamanho do arquivo {size} excede o limite de {limit}.",
- "defaultMessage": "The file size {size} exceeds the {limit} limit.",
- "description": "Error message when attachment is too large",
- "missing": false,
- "obsolete": false
- },
- "forgot_password_link": {
- "translation": "Esqueceu a senha?",
- "defaultMessage": "Forgot password?",
- "description": "Link to Reset password form",
- "missing": false,
- "obsolete": false
- },
- "full_name_prompt": {
- "translation": "Nome completo, ex. Fulano de Tal",
- "defaultMessage": "Full name, e.g. John Doe",
- "description": "Input placeholder for person's full name",
- "missing": false,
- "obsolete": false
- },
- "granted_permissions": {
- "translation": "Concedidas",
- "defaultMessage": "Granted",
- "description": "Title for permissions",
- "missing": false,
- "obsolete": false
- },
- "group_has_no_members": {
- "translation": "Não há membros",
- "defaultMessage": "No members",
- "description": "Shown in place of group members",
- "missing": false,
- "obsolete": false
- },
- "group_user_id_prompt": {
- "translation": "ID de Grupo ou ID de Usuário",
- "defaultMessage": "Group or User ID",
- "description": "Prompt for entering user or group ID",
- "missing": false,
- "obsolete": false
- },
- "image_caption_prompt": {
- "translation": "Legenda da imagem",
- "defaultMessage": "Image caption",
- "description": "Prompt in SendMessage for attached image",
- "missing": false,
- "obsolete": false
- },
- "invalid_content": {
- "translation": "conteúdo inválido",
- "defaultMessage": "invalid content",
- "description": "Shown when the message is unreadable",
- "missing": false,
- "obsolete": false
- },
- "invalid_security_token": {
- "translation": "Token de segurança inválido",
- "defaultMessage": "Invalid security token",
- "description": "Error message when resetting password.",
- "missing": false,
- "obsolete": false
- },
- "label_client": {
- "translation": "Cliente:",
- "defaultMessage": "Client:",
- "description": "Label for a client version",
- "missing": false,
- "obsolete": false
- },
- "label_content_type": {
- "translation": "Tipo de conteúdo:",
- "defaultMessage": "Content type:",
- "description": "Label for file content type (mime)",
- "missing": false,
- "obsolete": false
- },
- "label_default_access_mode": {
- "translation": "Modo de acesso padrão:",
- "defaultMessage": "Default access mode:",
- "description": "Label for default access mode",
- "missing": false,
- "obsolete": false
- },
- "label_file_name": {
- "translation": "Nome do arquivo:",
- "defaultMessage": "File name:",
- "description": "Label for a file name",
- "missing": false,
- "obsolete": false
- },
- "label_group_members": {
- "translation": "Membros do grupo:",
- "defaultMessage": "Group members:",
- "description": "Section title or label",
- "missing": false,
- "obsolete": false
- },
- "label_incognito_mode": {
- "translation": "Modo incógnito:",
- "defaultMessage": "Incognito mode:",
- "description": "Label for incognito mode toggle",
- "missing": false,
- "obsolete": false
- },
- "label_message_sound": {
- "translation": "Som das mensagens:",
- "defaultMessage": "Message sound:",
- "description": "Label for message sounds toggle",
- "missing": false,
- "obsolete": false
- },
- "label_muting_topic": {
- "translation": "Mudo:",
- "defaultMessage": "Muted:",
- "description": "Label for Muting/unmuting the topic",
- "missing": false,
- "obsolete": false
- },
- "label_other_user": {
- "translation": "Outro",
- "defaultMessage": "Other",
- "description": "Label for the other user when the user is unnamed",
- "missing": false,
- "obsolete": false
- },
- "label_password": {
- "translation": "Senha",
- "defaultMessage": "Password",
- "description": "Label for password editing",
- "missing": false,
- "obsolete": false
- },
- "label_permissions": {
- "translation": "Permissões:",
- "defaultMessage": "Permissions:",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "label_private": {
- "translation": "Comentário privado",
- "defaultMessage": "Private comment",
- "description": "Label for editing 'private'",
- "missing": false,
- "obsolete": false
- },
- "label_push_notifications": {
- "translation": "Alertas de notificação:",
- "defaultMessage": "Notification alerts:",
- "description": "Label for push notifications switch",
- "missing": false,
- "obsolete": false
- },
- "label_push_notifications_disabled": {
- "translation": "Alertas de notificação (requer HTTPS):",
- "defaultMessage": "Notification alerts (requires HTTPS):",
- "description": "Label for push notifications switch",
- "missing": false,
- "obsolete": false
- },
- "label_reset_password": {
- "translation": "Envie um email para redefinir a senha:",
- "defaultMessage": "Send a password reset email",
- "description": "Label for password reset field",
- "missing": false,
- "obsolete": false
- },
- "label_sdk": {
- "translation": "SDK:",
- "defaultMessage": "SDK:",
- "missing": false,
- "obsolete": false
- },
- "label_server": {
- "translation": "Servidor:",
- "defaultMessage": "Server:",
- "description": "Label for a server version",
- "missing": false,
- "obsolete": false
- },
- "label_server_address": {
- "translation": "Endereço do servidor:",
- "defaultMessage": "Server address:",
- "missing": false,
- "obsolete": false
- },
- "label_server_to_use": {
- "translation": "Servidor a ser usado:",
- "defaultMessage": "Server to use:",
- "description": "Label for server selector in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "label_size": {
- "translation": "Tamanho:",
- "defaultMessage": "Size:",
- "description": "Label for file size",
- "missing": false,
- "obsolete": false
- },
- "label_topic_name": {
- "translation": "Nome",
- "defaultMessage": "Name",
- "description": "Label for editing topic name",
- "missing": false,
- "obsolete": false
- },
- "label_user_contacts": {
- "translation": "Contatos",
- "defaultMessage": "Contacts",
- "description": "Label for user contacts",
- "missing": false,
- "obsolete": false
- },
- "label_user_id": {
- "translation": "ID:",
- "defaultMessage": "ID:",
- "description": "Label for user address (ID)",
- "missing": false,
- "obsolete": false
- },
- "label_wire_transport": {
- "translation": "Tipo de transporte:",
- "defaultMessage": "Wire transport:",
- "description": "Label for wire transport selection in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "label_you": {
- "translation": "Você:",
- "defaultMessage": "You:",
- "description": "Label for the current user",
- "missing": false,
- "obsolete": false
- },
- "label_your_name": {
- "translation": "Seu nome",
- "defaultMessage": "Your name",
- "description": "Label for full name editing",
- "missing": false,
- "obsolete": false
- },
- "label_your_permissions": {
- "translation": "Suas permissões:",
- "defaultMessage": "Your permissions:",
- "description": "Label for current user permissions",
- "missing": false,
- "obsolete": false
- },
- "last_seen_timestamp": {
- "translation": "Última vez visto",
- "defaultMessage": "Last seen: {timestamp}",
- "description": "Label for the timestamp of when the user or topic was last online",
- "missing": false,
- "obsolete": false
- },
- "leave_chat_warning": {
- "translation": "Tem certeza de que quer sair desta conversa?",
- "defaultMessage": "Are you sure you want to leave this conversation?",
- "description": "Alert dialog warning when unsubscribing from a chat.",
- "missing": false,
- "obsolete": false
- },
- "link_contact_us": {
- "translation": "Contacte-nos",
- "defaultMessage": "Contact Us",
- "description": "Ancor text for contacting us by email",
- "missing": false,
- "obsolete": false
- },
- "link_privacy_policy": {
- "translation": "Política de Privacidade",
- "defaultMessage": "Privacy Policy",
- "description": "Ancor text for privacy policy link",
- "missing": false,
- "obsolete": false
- },
- "link_terms_of_service": {
- "translation": "Termos de Serviço",
- "defaultMessage": "Terms of Service",
- "description": "Ancor text for terms of service link",
- "missing": false,
- "obsolete": false
- },
- "login_prompt": {
- "translation": "Login",
- "defaultMessage": "Login",
- "description": "Placeholer for username/login",
- "missing": false,
- "obsolete": false
- },
- "menu_item_archive_topic": {
- "translation": "Arquivar",
- "defaultMessage": "Archive",
- "description": "Move topic from the list of active chats to archive",
- "missing": false,
- "obsolete": false
- },
- "menu_item_block": {
- "translation": "Bloquear",
- "defaultMessage": "Block",
- "description": "Block topic or user",
- "missing": false,
- "obsolete": false
- },
- "menu_item_clear_messages": {
- "translation": "Limpar mensagens",
- "defaultMessage": "Clear messages",
- "description": "Delete all messages",
- "missing": false,
- "obsolete": false
- },
- "menu_item_clear_messages_for_all": {
- "translation": "Limpar Mensagens para Todos",
- "defaultMessage": "Clear for All",
- "description": "Delete all message(s) for all members",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete": {
- "translation": "Excluir",
- "defaultMessage": "Delete",
- "description": "Delete selected messages",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete_for_all": {
- "translation": "Excluir para Todos",
- "defaultMessage": "Delete for All",
- "description": "Delete selected message(s) for all members",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete_topic": {
- "translation": "Excluir",
- "defaultMessage": "Delete",
- "description": "Delete entire topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_edit_permissions": {
- "translation": "Editar permissões",
- "defaultMessage": "Edit permissions",
- "description": "Menu item [Edit permissions]",
- "missing": false,
- "obsolete": false
- },
- "menu_item_info": {
- "translation": "Info",
- "defaultMessage": "Info",
- "description": "Show extended topic information",
- "missing": false,
- "obsolete": false
- },
- "menu_item_member_delete": {
- "translation": "Remover",
- "defaultMessage": "Remove",
- "description": "Remove user from topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_mute": {
- "translation": "Silenciar",
- "defaultMessage": "Mute",
- "description": "Turn off notifications",
- "missing": false,
- "obsolete": false
- },
- "menu_item_reply": {
- "translation": "Responder",
- "defaultMessage": "Reply",
- "description": "Reply to message",
- "missing": false,
- "obsolete": false
- },
- "menu_item_restore_topic": {
- "translation": "Restaurar",
- "defaultMessage": "Restore",
- "description": "Restore topic from archive",
- "missing": false,
- "obsolete": false
- },
- "menu_item_send_retry": {
- "translation": "Tentar novamente",
- "defaultMessage": "Retry",
- "description": "Retry sending message",
- "missing": false,
- "obsolete": false
- },
- "menu_item_unblock": {
- "translation": "Desbloquear",
- "defaultMessage": "Unblock",
- "description": "Unblock topic or user",
- "missing": false,
- "obsolete": false
- },
- "menu_item_unmute": {
- "translation": "Ativar notificações",
- "defaultMessage": "Unmute",
- "description": "Turn notifications on",
- "missing": false,
- "obsolete": false
- },
- "message_sending": {
- "translation": "a enviar...",
- "defaultMessage": "sending...",
- "description": "Message being sent, in place of time stamp",
- "missing": false,
- "obsolete": false
- },
- "message_sending_failed": {
- "translation": "falhou",
- "defaultMessage": "failed",
- "description": "Failed to send message, in place of time stamp",
- "missing": false,
- "obsolete": false
- },
- "messages_not_readable": {
- "translation": "sem acesso às mensagens",
- "defaultMessage": "no access to messages",
- "description": "Message shown in topic without the read access",
- "missing": false,
- "obsolete": false
- },
- "messaging_disabled_prompt": {
- "translation": "Mensageria desativadas",
- "defaultMessage": "Messaging disabled",
- "description": "Prompt in SendMessage in read-only topic",
- "missing": false,
- "obsolete": false
- },
- "more_online_members": {
- "translation": "+{overflow} mais",
- "defaultMessage": "+{overflow} more",
- "description": "Shown in MessagesView title bar when the number of online subscribers exceeds MAX_ONLINE_IN_TOPIC",
- "missing": false,
- "obsolete": false
- },
- "new_message_prompt": {
- "translation": "Nova mensagem",
- "defaultMessage": "New message",
- "description": "Prompt in send message field",
- "missing": false,
- "obsolete": false
- },
- "new_password_placeholder": {
- "translation": "Entre a nova senha",
- "defaultMessage": "Enter new password",
- "description": "Placeholder for entering new password",
- "missing": false,
- "obsolete": false
- },
- "no_connection": {
- "translation": "Sem conexão",
- "defaultMessage": "No connection",
- "description": "Warning that the user is offline.",
- "missing": false,
- "obsolete": false
- },
- "no_contacts": {
- "translation": "Você não tem contatos :-(",
- "defaultMessage": "You have no contacts :-(",
- "description": "Shown in ContactsView when the user has no contacts",
- "missing": false,
- "obsolete": false
- },
- "numeric_confirmation_code_prompt": {
- "translation": "Somente números",
- "defaultMessage": "Numbers only",
- "description": "Prompt for numeric conformation code",
- "missing": false,
- "obsolete": false
- },
- "online_now": {
- "translation": "online agora",
- "defaultMessage": "online now",
- "description": "Indicator that the user or topic is currently online",
- "missing": false,
- "obsolete": false
- },
- "password_prompt": {
- "translation": "Senha",
- "defaultMessage": "Password",
- "description": "Placeholder/prompt for entering password",
- "missing": false,
- "obsolete": false
- },
- "password_unchanged_prompt": {
- "translation": "Sem alterações",
- "defaultMessage": "Unchanged",
- "description": "Message in editor while password is unchanged",
- "missing": false,
- "obsolete": false
- },
- "peers_messaging_disabled": {
- "translation": "Mensagens do peer desativadas.",
- "defaultMessage": "Peer's messaging is disabled.",
- "description": "Shown when the p2p peer's messaging is disabled",
- "missing": false,
- "obsolete": false
- },
- "permission_admin": {
- "translation": "Approvar ({val})",
- "defaultMessage": "Approve ({val})",
- "description": "Name of A permission",
- "missing": false,
- "obsolete": false
- },
- "permission_delete": {
- "translation": "Excluir ({val})",
- "defaultMessage": "Delete ({val})",
- "description": "Name of D permission",
- "missing": false,
- "obsolete": false
- },
- "permission_join": {
- "translation": "Unir-se ({val})",
- "defaultMessage": "Join ({val})",
- "description": "Name of J permission",
- "missing": false,
- "obsolete": false
- },
- "permission_owner": {
- "translation": "Proprietário ({val})",
- "defaultMessage": "Owner ({val})",
- "description": "Name of O permission",
- "missing": false,
- "obsolete": false
- },
- "permission_pres": {
- "translation": "Ser notificado ({val})",
- "defaultMessage": "Get notified ({val})",
- "description": "Name of P permission",
- "missing": false,
- "obsolete": false
- },
- "permission_read": {
- "translation": "Ler ({val})",
- "defaultMessage": "Read ({val})",
- "description": "Name of R permission",
- "missing": false,
- "obsolete": false
- },
- "permission_share": {
- "translation": "Compartilhar ({val})",
- "defaultMessage": "Share ({val})",
- "description": "Name of S permission",
- "missing": false,
- "obsolete": false
- },
- "permission_write": {
- "translation": "Escrever ({val})",
- "defaultMessage": "Write ({val})",
- "description": "Name of W permission",
- "missing": false,
- "obsolete": false
- },
- "phone_dative": {
- "translation": "fone",
- "defaultMessage": "phone",
- "description": "Dative case of 'phone', i.e. 'phone' in 'by phone'",
- "missing": false,
- "obsolete": false
- },
- "private_editing_placeholder": {
- "translation": "Visível apenas para você",
- "defaultMessage": "Visible to you only",
- "description": "Placeholder for editing 'private'",
- "missing": false,
- "obsolete": false
- },
- "push_init_failed": {
- "translation": "Falha em inicializar notificações push",
- "defaultMessage": "Failed to initialize push notifications",
- "description": "Error message when push notifications have failed to initialize.",
- "missing": false,
- "obsolete": false
- },
- "reconnect_countdown": {
- "translation": "Desconectado. Reconectando em {seconds}…",
- "defaultMessage": "Disconnected. Reconnecting in {seconds}…",
- "description": "Message shown when an app update is available.",
- "missing": false,
- "obsolete": false
- },
- "reconnect_now": {
- "translation": "Tentar agora",
- "defaultMessage": "Try now",
- "description": "Prompt for reconnecting now",
- "missing": false,
- "obsolete": false
- },
- "reload_update": {
- "translation": "Recarregar",
- "defaultMessage": "Reload",
- "description": "Call to action to reload application when update is available.",
- "missing": false,
- "obsolete": false
- },
- "report_chat_warning": {
- "translation": "Tem certeza de que deseja bloquear e relatar esta conversa?",
- "defaultMessage": "Are you sure you want to block and report this conversation?",
- "description": "Alert dialog warning when reporting a conversation for abuse",
- "missing": false,
- "obsolete": false
- },
- "requested_permissions": {
- "translation": "Solicitados",
- "defaultMessage": "Requested",
- "description": "Title for permissions",
- "missing": false,
- "obsolete": false
- },
- "save_attachment": {
- "translation": "guardar",
- "defaultMessage": "save",
- "description": "Call to save an attachment",
- "missing": false,
- "obsolete": false
- },
- "search_for_contacts": {
- "translation": "Use a busca para encontrar contatos",
- "defaultMessage": "Use search to find contacts",
- "description": "Text shown in contacts view when user entered no search query.",
- "missing": false,
- "obsolete": false
- },
- "search_no_results": {
- "translation": "A busca não retornou resultados",
- "defaultMessage": "Search returned no results",
- "description": "Text shown in contacts view when query returned no results.",
- "missing": false,
- "obsolete": false
- },
- "search_placeholder": {
- "translation": "Liste como email:ciclanae@exemplo.com, tel:+351000555000...",
- "defaultMessage": "List like alice@example.com, +17025550003...",
- "description": "Placeholder in contacts search field",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_notifications": {
- "translation": "Notificações",
- "defaultMessage": "Notifications",
- "description": "Sidepanel title for AccNotificationsView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_support": {
- "translation": "Suporte",
- "defaultMessage": "Support",
- "description": "Sidepanel title for AccSupportView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_account_settings": {
- "translation": "Configurações da Conta",
- "defaultMessage": "Account Settings",
- "description": "Sidepanel title for AccountSettingsView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_archive": {
- "translation": "Conversas Arquivadas",
- "defaultMessage": "Archived Chats",
- "description": "Sidepanel title for ContactsView-Archive.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_blocked": {
- "translation": "Conversas Bloqueadas",
- "defaultMessage": "Blocked Chats",
- "description": "Sidepanel title for ContactsView-Blocked.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_cred": {
- "translation": "Confirme as Credenciais",
- "defaultMessage": "Confirm Credentials",
- "description": "Sidepanel title for ValidationView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_login": {
- "translation": "Entrar",
- "defaultMessage": "Sign In",
- "description": "Sidepanel title for LoginView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_newtpk": {
- "translation": "Começar Nova Conversa",
- "defaultMessage": "Start New Chat",
- "description": "Sidepanel title for NewTopicView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_register": {
- "translation": "Criar Conta",
- "defaultMessage": "Create Account",
- "description": "Sidepanel title for CreateAccountView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_reset": {
- "translation": "Redefinir Senha",
- "defaultMessage": "Reset Password",
- "description": "Sidepanel title for PasswordResetView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_settings": {
- "translation": "Configurações",
- "defaultMessage": "Settings",
- "description": "Sidepanel title for SettingsView.",
- "missing": false,
- "obsolete": false
- },
- "stay_logged_in": {
- "translation": "Manter-se logado",
- "defaultMessage": "Stay logged in",
- "description": "Label for a checkbox",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_find_user": {
- "translation": "procurar",
- "defaultMessage": "find",
- "description": "Tab title Find",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_group_by_id": {
- "translation": "por id",
- "defaultMessage": "by id",
- "description": "Tab title Find topic by ID",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_new_group": {
- "translation": "novo grupo",
- "defaultMessage": "new group",
- "description": "Tab title New Group",
- "missing": false,
- "obsolete": false
- },
- "tags_editor_no_tags": {
- "translation": "Adicione algumas tags",
- "defaultMessage": "Add some tags",
- "description": "Tag editor prompt when no tags are found.",
- "missing": false,
- "obsolete": false
- },
- "tags_not_found": {
- "translation": "Nenhuma tag definida. Adicione algumas.",
- "defaultMessage": "No tags defined. Add some.",
- "description": "",
- "missing": false,
- "obsolete": false
- },
- "title_all_contacts": {
- "translation": "Todos os Contatos",
- "defaultMessage": "All Contacts",
- "description": "Section title [All Contacts]",
- "missing": false,
- "obsolete": false
- },
- "title_group_members": {
- "translation": "Membros do Grupo",
- "defaultMessage": "Group Members",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "title_manage_tags": {
- "translation": "Gerenciar",
- "defaultMessage": "Manage",
- "description": "Section title for the list of tags",
- "missing": false,
- "obsolete": false
- },
- "title_not_found": {
- "translation": "Não encontrado",
- "defaultMessage": "Not found",
- "description": "Title shown when topic is not found",
- "missing": false,
- "obsolete": false
- },
- "title_permissions": {
- "translation": "Permissões",
- "defaultMessage": "Permissions",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "title_tag_manager": {
- "translation": "Tags (pesquisar & descobrir)",
- "defaultMessage": "Tags (search & discovery)",
- "description": "Section title for TagManager",
- "missing": false,
- "obsolete": false
- },
- "topic_block_warning": {
- "translation": "Você tem certeza que deseja bloquear esta conversa?",
- "defaultMessage": "Are you sure you want to block this conversation?",
- "description": "Alert warning when blocking a topic.",
- "missing": false,
- "obsolete": false
- },
- "topic_delete_warning": {
- "translation": "Você tem certeza que deseja excluir esta conversa? Não pode ser desfeito.",
- "defaultMessage": "Are you sure you want to delete this conversation? It cannot be undone.",
- "description": "Alert warning when deleting entire topic",
- "missing": false,
- "obsolete": false
- },
- "topic_name_editing_placeholder": {
- "translation": "Nome livre do grupo",
- "defaultMessage": "Freeform name of the group",
- "description": "Prompt for entering topic name",
- "missing": false,
- "obsolete": false
- },
- "unnamed_topic": {
- "translation": "Sem nome",
- "defaultMessage": "Unnamed",
- "description": "Title shown when the topic has no name",
- "missing": false,
- "obsolete": false
- },
- "update_available": {
- "translation": "Atualização disponível.",
- "defaultMessage": "Update available.",
- "description": "Message shown when an app update is available.",
- "missing": false,
- "obsolete": false
- },
- "upload_finishing": {
- "translation": "finalizando...",
- "defaultMessage": "finishing...",
- "description": "Notification that upload is finishing",
- "missing": false,
- "obsolete": false
- },
- "user_not_found": {
- "translation": "Não encontrado",
- "defaultMessage": "Not found",
- "description": "In place of a user's full name when the user is not found.",
- "missing": false,
- "obsolete": false
- },
- "description_editing_placeholder": {
- "translation": "Descrição opcional",
- "defaultMessage": "Description (optional)",
- "description": "Placeholder for editing topic description",
- "missing": false,
- "obsolete": false
- },
- "label_description": {
- "translation": "Descrição",
- "defaultMessage": "Description",
- "description": "Label for description of user or topic",
- "missing": false,
- "obsolete": false
- },
- "button_security": {
- "translation": "Segurança",
- "defaultMessage": "Security",
- "description": "Navigaton button for security panel.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_crop": {
- "translation": "Arraste para Ajustar",
- "defaultMessage": "Drag to Adjust",
- "description": "Title for AvatarCropView.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_general": {
- "translation": "Geral",
- "defaultMessage": "General",
- "description": "Title for TopicCommon.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_members": {
- "translation": "Membros",
- "defaultMessage": "Members",
- "description": "Title for managing group members view.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_security": {
- "translation": "Segurança",
- "defaultMessage": "Security",
- "description": "Title for TopicSecirity and AccSecurity.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_info": {
- "translation": "Info",
- "defaultMessage": "Info",
- "description": "Title for InfoView",
- "missing": false,
- "obsolete": false
- },
- "permissions_anonymous": {
- "translation": "Anônimo",
- "defaultMessage": "Anonymous",
- "description": "Title for editing default anonymous permissions",
- "missing": false,
- "obsolete": false
- },
- "permissions_authenticated": {
- "translation": "Autenticado",
- "defaultMessage": "Authenticated",
- "description": "Title for editing default authenticated permissions",
- "missing": false,
- "obsolete": false
- },
- "topic_delete": {
- "translation": "Apagar Tópico",
- "defaultMessage": "Delete Conversation",
- "description": "Alert title when deleting the topic.",
- "missing": false,
- "obsolete": false
- },
- "permissions_user": {
- "translation": "Permissões do Usuário",
- "defaultMessage": "User's Permissions",
- "description": "Title for editing user's permissions",
- "missing": false,
- "obsolete": false
- },
- "password_reset_email_sent": {
- "translation": "Um email com o código de segurança foi enviado.",
- "defaultMessage": "An email with security code has been sent.",
- "description": "Notification that the email with password reset instructions has been sent",
- "missing": false,
- "obsolete": false
- },
- "label_unarchive_topic": {
- "translation": "Arquivado:",
- "defaultMessage": "Archived:",
- "description": "Label for unarchiving the topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_forward": {
- "translation": "Encaminhar",
- "defaultMessage": "Forward",
- "description": "Forward message",
- "missing": false,
- "obsolete": false
- },
- "forward_to": {
- "translation": "Encaminhar para",
- "defaultMessage": "Forward to",
- "missing": false,
- "obsolete": false
- },
- "forward_to_search_placeholder": {
- "translation": "Pesquisar contatos",
- "defaultMessage": "Search contacts",
- "description": "Contact search prompt when forwarding a message.",
- "missing": false,
- "obsolete": false
- },
- "label_new_password": {
- "translation": "Nova senha",
- "defaultMessage": "New password",
- "description": "Label for entering a new password",
- "missing": false,
- "obsolete": false
- },
- "drafty_unknown": {
- "translation": "Não suportado",
- "defaultMessage": "Unsupported",
- "description": "Unsupported entity in drafty",
- "missing": false,
- "obsolete": false
- },
- "calls_incoming": {
- "translation": "Chamada recebida",
- "defaultMessage": "Incoming call",
- "description": "Incoming call label",
- "missing": false,
- "obsolete": false
- },
- "calls_outgoing": {
- "translation": "Chamada saída",
- "defaultMessage": "Outgoing call",
- "description": "Outgoing call label",
- "missing": false,
- "obsolete": false
- },
- "calls_you_label": {
- "translation": "Você",
- "defaultMessage": "You",
- "description": "Shown over the local video screen",
- "missing": false,
- "obsolete": false
- },
- "menu_item_video_call": {
- "translation": "Chamada de vídeo",
- "defaultMessage": "Video call",
- "description": "Start video call",
- "missing": false,
- "obsolete": false
- },
- "already_in_call": {
- "translation": "Você já está em uma chamada!",
- "defaultMessage": "You already in an ongoing call!",
- "description": "Error message when the user tried to accept a new call without finishing pervious one",
- "missing": false,
- "obsolete": false
- },
- "call_cancelled": {
- "translation": "cancelada",
- "defaultMessage": "cancelled",
- "description": "Label for cancelled outgoing call",
- "missing": false,
- "obsolete": false
- },
- "call_missed": {
- "translation": "perdida",
- "defaultMessage": "missed",
- "description": "Label for missed incoming call",
- "missing": false,
- "obsolete": false
- },
- "call_declined": {
- "translation": "recusada",
- "defaultMessage": "declined",
- "description": "Label for declined call",
- "missing": false,
- "obsolete": false
- },
- "call_disconnected": {
- "translation": "desconectada",
- "defaultMessage": "disconnected",
- "description": "Label for disconnected call",
- "missing": false,
- "obsolete": false
- },
- "label_use_secure_connection": {
- "translation": "Use uma conexão segura",
- "defaultMessage": "Use secure connection",
- "description": "Label for WS/WSS connection type in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "cred_confirmed_successfully": {
- "translation": "Confirmado com sucesso",
- "defaultMessage": "Confirmed successfully",
- "description": "Notification message that the credential was successfully validated.",
- "missing": false,
- "obsolete": false
- },
- "menu_item_edit": {
- "translation": "Editar",
- "defaultMessage": "Edit",
- "description": "Edit message",
- "missing": false,
- "obsolete": false
- },
- "editing_message": {
- "translation": "Editando",
- "defaultMessage": "Editing",
- "description": "Title over message editing preview",
- "missing": false,
- "obsolete": false
- },
- "message_edited_marker": {
- "translation": ", editado",
- "defaultMessage": ", edited",
- "description": "Marker indicating that the message was edited",
- "missing": false,
- "obsolete": false
- },
- "icon_title_add_image": {
- "translation": "Adicionar imagem",
- "defaultMessage": "Add image",
- "description": "Icon tool tip for attaching an image",
- "missing": false,
- "obsolete": false
- },
- "icon_title_attach_file": {
- "translation": "Anexar arquivo",
- "defaultMessage": "Attach file",
- "description": "Icon tool tip for attaching a file",
- "missing": false,
- "obsolete": false
- },
- "icon_title_delete": {
- "translation": "Excluir",
- "defaultMessage": "Delete recording",
- "description": "Icon tool tip for deleting recorded audio",
- "missing": false,
- "obsolete": false
- },
- "icon_title_pause": {
- "translation": "Pausar",
- "defaultMessage": "Pause playback",
- "description": "Icon tool tip for pausing audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_play": {
- "translation": "Reproduzir",
- "defaultMessage": "Play recording",
- "description": "Icon tool tip for starting audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_record_voice": {
- "translation": "Gravar mensagem de voz",
- "defaultMessage": "Record voice message",
- "description": "Icon tool tip for recording a voice message",
- "missing": false,
- "obsolete": false
- },
- "icon_title_resume": {
- "translation": "Retomar",
- "defaultMessage": "Resume playback",
- "description": "Icon tool tip for resuming audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_send": {
- "translation": "Enviar mensagem",
- "defaultMessage": "Send message",
- "description": "Icon tool tip for sending a message",
- "missing": false,
- "obsolete": false
- },
- "drag_file": {
- "translation": "Arraste o arquivo aqui",
- "defaultMessage": "Drag file here",
- "description": "Prompt on the file drag-n-drop overlay banner",
- "missing": false,
- "obsolete": false
- },
- "drafty_video": {
- "translation": "Gravação de vídeo",
- "defaultMessage": "Video recording",
- "description": "Comment for videos embedded in Drafty",
- "missing": false,
- "obsolete": false
- },
- "call_in_progress": {
- "translation": "em andamento",
- "defaultMessage": "in progress",
- "description": "Label for call in progress",
- "missing": false,
- "obsolete": false
- },
- "menu_item_audio_call": {
- "translation": "Chamada",
- "defaultMessage": "Call",
- "description": "Start audio call",
- "missing": false,
- "obsolete": false
- },
- "unrecognized_video_format": {
- "translation": "O formato deste vídeo não é reconhecido",
- "defaultMessage": "Format of this video is not recognized",
- "description": "Error message when uploaded video is invalid",
- "missing": false,
- "obsolete": false
- },
- "loading_note": {
- "translation": "Carregando...",
- "defaultMessage": "Loading...",
- "description": "Message shown when component is loading",
- "missing": false,
- "obsolete": false
- },
- "password_i_have_code": {
- "translation": "Tenho o código",
- "defaultMessage": "I have code",
- "description": "Call to open field to enter password reset code",
- "missing": false,
- "obsolete": false
- },
- "label_reset_password_tel": {
- "translation": "Enviar SMS para redefinir senha:",
- "defaultMessage": "Send SMS to reset password",
- "description": "Label for password reset field by phone",
- "missing": false,
- "obsolete": false
- },
- "mobile_phone_number": {
- "translation": "Número do celular/telemóvel",
- "defaultMessage": "Mobile phone number",
- "description": "Prompt for entering a mobile phone number",
- "missing": false,
- "obsolete": false
- },
- "password_reset_sms_sent": {
- "translation": "Uma mensagem de texto com o código de segurança foi enviada.",
- "defaultMessage": "A text message with security code has been sent.",
- "description": "Notification that the SMS with password reset instructions has been sent",
- "missing": false,
- "obsolete": false
- },
- "mobile_number_required": {
- "translation": "Número do celular/telemóvel é obrigatório",
- "defaultMessage": "Mobile phone number required",
- "description": "Error message",
- "missing": false,
- "obsolete": false
- },
- "current_email": {
- "translation": "Email atual",
- "defaultMessage": "Current email",
- "description": "Label for old email",
- "missing": false,
- "obsolete": false
- },
- "current_phone": {
- "translation": "Número do telefone atual",
- "defaultMessage": "Current phone number",
- "description": "Label for old phone",
- "missing": false,
- "obsolete": false
- },
- "new_email": {
- "translation": "Novo email",
- "defaultMessage": "New email",
- "description": "Prompt for entering a new email",
- "missing": false,
- "obsolete": false
- },
- "new_phone_number": {
- "translation": "Novo número de telefone",
- "defaultMessage": "New phone number",
- "description": "Prompt for entering a new telephone number",
- "missing": false,
- "obsolete": false
- },
- "change_email": {
- "translation": "Trocar email",
- "defaultMessage": "Change email",
- "description": "Prompt to change current email",
- "missing": false,
- "obsolete": false
- },
- "change_phone": {
- "translation": "Trocar número de telefone",
- "defaultMessage": "Change phone number",
- "description": "Prompt to change current phone number",
- "missing": false,
- "obsolete": false
- },
- "call_busy": {
- "translation": "ocupado",
- "defaultMessage": "busy",
- "description": "Label for busy call line",
- "missing": false,
- "obsolete": false
- },
- "failed_to_init_audio": {
- "translation": "Falha ao inicializar a gravação de áudio",
- "defaultMessage": "Failed to initialize audio recording",
- "description": "Error message when audio is not available",
- "missing": false,
- "obsolete": false
- },
- "pin_message": {
- "translation": "Fixar",
- "defaultMessage": "Pin",
- "description": "Menu item [Pin] for pinning message to the top.",
- "missing": false,
- "obsolete": false
- },
- "unpin_message": {
- "translation": "Desafixar",
- "defaultMessage": "Unpin",
- "description": "Menu item [Unpin] for un-pinning the message.",
- "missing": false,
- "obsolete": false
- },
- "password_reset_success": {
- "translation": "Senha redefinida com sucesso",
- "defaultMessage": "Password reset successfully",
- "description": "Notification message that the password was successfully reset.",
- "missing": false,
- "obsolete": false
- },
- "scan_qr_code": {
- "translation": "Escanear código QR",
- "defaultMessage": "Scan QR Code",
- "description": "Title for scanning QR code",
- "missing": false,
- "obsolete": false
- },
- "self_topic_comment": {
- "translation": "Anotações, mensagens, links, arquivos salvos para posteridade",
- "defaultMessage": "Notes, messages, links, files saved for posterity",
- "description": "Comment for self topic for UI",
- "missing": false,
- "obsolete": false
- },
- "self_topic_name": {
- "translation": "Mensagens salvas",
- "defaultMessage": "Saved messages",
- "description": "Name of self topic for UI",
- "missing": false,
- "obsolete": false
- },
- "message_not_found": {
- "translation": "mensagem não encontrada",
- "defaultMessage": "message not found",
- "description": "Pinned message is not found",
- "missing": false,
- "obsolete": false
- },
- "message_deleted": {
- "translation": "mensagem excluída",
- "defaultMessage": "message deleted",
- "description": "Pinned message is deleted",
- "missing": false,
- "obsolete": false
- },
- "alias_editing_placeholder": {
- "translation": "Nome alternativo (optional)",
- "defaultMessage": "Alias (optional)",
- "description": "Placeholder for editing user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "label_alias": {
- "translation": "Nome alternativo:",
- "defaultMessage": "Alias:",
- "description": "Label for user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "alias_already_taken": {
- "translation": "(já está em uso)",
- "defaultMessage": "(already taken)",
- "description": "Error message for alias already taken",
- "missing": false,
- "obsolete": false
- },
- "alias_invalid": {
- "translation": "(inválido)",
- "defaultMessage": "(invalid)",
- "description": "Error message for invalid alias",
- "missing": false,
- "obsolete": false
- },
- "label_alias_edit": {
- "translation": "Nome alternativo",
- "defaultMessage": "Alias",
- "description": "Label for editing user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "select_country": {
- "translation": "Selecione o país",
- "defaultMessage": "Select country",
- "description": "Placeholder for the country selector",
- "missing": false,
- "obsolete": false
- },
- "text_copied": {
- "translation": "Texto copiado para a área de transferência",
- "defaultMessage": "Copied to clipboard",
- "description": "Notification that text has been copied to clipboard",
- "missing": false,
- "obsolete": false
- },
- "color_schema_dark": {
- "translation": "Escuro",
- "defaultMessage": "Dark",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "color_schema_light": {
- "translation": "Claro",
- "defaultMessage": "Light",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "color_schema_system": {
- "translation": "Padrão do sistema",
- "defaultMessage": "System default",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "label_color_schema": {
- "translation": "Tema:",
- "defaultMessage": "Theme:",
- "description": "Label for selecting color scheme (dark, light) in Settings",
- "missing": false,
- "obsolete": false
- },
- "label_text_size": {
- "translation": "Tamanho do texto:",
- "defaultMessage": "Text size:",
- "description": "Label adjusting text size",
- "missing": false,
- "obsolete": false
- },
- "incognito_mode_description": {
- "translation": "Oculta seu status online e indicadores de digitação.",
- "defaultMessage": "Hide your online status and typing indicators.",
- "description": "Description for incognito mode toggle",
- "missing": false,
- "obsolete": false
- },
- "send_command": {
- "translation": "Envia com {key}",
- "defaultMessage": "Send on {key}",
- "description": "Config option to send message on hitting CTRL(or Cmd)-Enter",
- "missing": false,
- "obsolete": false
- },
- "send_command_explained": {
- "translation": "Pressione Enter para nova linha",
- "defaultMessage": "Press Enter for new line",
- "description": "Explanation how to enter newline when [Send on CTRL-Enter] is enabled",
- "missing": false,
- "obsolete": false
- },
- "send_plain": {
- "translation": "Envia com Enter",
- "defaultMessage": "Send on Enter",
- "description": "Config option to send message on hitting Enter",
- "missing": false,
- "obsolete": false
- },
- "send_plain_explained": {
- "translation": "Pressione Shift + Enter para nova linha",
- "defaultMessage": "Press Shift + Enter for new line",
- "description": "Explanation how to enter newline when [Send on Enter] is enabled",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_general": {
- "translation": "Geral",
- "defaultMessage": "General",
- "description": "Sidepanel title for AccGeneralView.",
- "missing": false,
- "obsolete": false
- },
- "label_keyboard": {
- "translation": "Teclado:",
- "defaultMessage": "Keyboard:",
- "description": "Label for send on enter settings",
- "missing": false,
- "obsolete": false
- },
- "pin_chat": {
- "translation": "Afixar",
- "defaultMessage": "Pin",
- "description": "Menu item [Pin] for pinning chat to the top.",
- "missing": false,
- "obsolete": false
- },
- "unpin_chat": {
- "translation": "Desafixar",
- "defaultMessage": "Unpin",
- "description": "Menu item [Unpin] for un-pinning the chat.",
- "missing": false,
- "obsolete": false
- },
- "subscriber_count": {
- "translation": "{count, plural, one {{count, number} subscriber} outro {{count, number} subscribers}}",
- "defaultMessage": "{count, plural, one {{count, number} subscriber} two {{count, number} subscribers} three {{count, number} subscribers} few {{count, number} subscribers} many {{count, number} subscribers} other {{count, number} subscribers}}",
- "description": "Count of channel subscribers",
- "missing": false,
- "obsolete": false
- },
- "label_member_count": {
- "translation": "Membros:",
- "defaultMessage": "Members:",
- "description": "Label for the count of group topic members",
- "missing": false,
- "obsolete": false
- },
- "label_subscriber_count": {
- "translation": "Assinantes:",
- "defaultMessage": "Subscribers:",
- "description": "Label for the count of channel subscribers",
- "missing": false,
- "obsolete": false
- },
- "member_count": {
- "translation": "{count, plural, one {{count, number} member} outro {{count, number} members}}",
- "defaultMessage": "{count, plural, one {{count, number} member} two {{count, number} members} three {{count, number} members} few {{count, number} members} many {{count, number} members} other {{count, number} members}}",
- "description": "Count of group topic members",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_image": {
- "translation": "imagem",
- "defaultMessage": "image",
- "description": "Tab title Image",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_pattern": {
- "translation": "padrão",
- "defaultMessage": "pattern",
- "description": "Tab title Pattern",
- "missing": false,
- "obsolete": false
- },
- "button_restore": {
- "translation": "Restaurar para o padrão",
- "defaultMessage": "Restore default",
- "description": "Reset setting to default",
- "missing": false,
- "obsolete": false
- },
- "wallpapers": {
- "translation": "Wallpapers",
- "defaultMessage": "Wallpapers",
- "description": "Link or title for working with wallpapers.",
- "missing": false,
- "obsolete": false
- },
- "label_blur_wallpaper": {
- "translation": "Blur:",
- "defaultMessage": "Blur:",
- "description": "Label adjusting blur amount",
- "missing": false,
- "obsolete": false
- },
- "label_scan_id": {
- "translation": "Leia meu ID:",
- "defaultMessage": "Scan my ID:",
- "description": "Label [Scan my ID:]",
- "missing": false,
- "obsolete": false
- },
- "unknown_name": {
- "translation": "Desconhecido",
- "defaultMessage": "Unknown",
- "description": "Name to show when the name is missing",
- "missing": false,
- "obsolete": false
- },
- "save_action": {
- "translation": "Guardar",
- "defaultMessage": "Save",
- "description": "Label for Save button",
- "missing": false,
- "obsolete": false
- },
- "cannot_parse_vcard": {
- "translation": "Não é possível analisar o arquivo vCard.",
- "defaultMessage": "Cannot parse vCard file.",
- "description": "Error message when vCard file cannot be parsed",
- "missing": false,
- "obsolete": false
- },
- "chat_now": {
- "translation": "Conversar",
- "defaultMessage": "Chat",
- "description": "Label for message button in contact card",
- "missing": false,
- "obsolete": false
- },
- "find_user": {
- "translation": "Encontrar",
- "defaultMessage": "Find",
- "description": "Label for search button in contact card",
- "missing": false,
- "obsolete": false
- },
- "label_contact_card": {
- "translation": "Cartão de Contato",
- "defaultMessage": "Contact Card",
- "description": "Label for contact card preview",
- "missing": false,
- "obsolete": false
- },
- "label_contacts": {
- "translation": "Contatos",
- "defaultMessage": "Contacts",
- "description": "Label for contact information",
- "missing": false,
- "obsolete": false
- },
- "add_members_prompt": {
- "translation": "adicionar membros",
- "defaultMessage": "add members",
- "description": "Placeholder in group members input field",
- "missing": false,
- "obsolete": false
- }
-}
diff --git a/src/i18n/ro.json b/src/i18n/ro.json
deleted file mode 100644
index 660db13c..00000000
--- a/src/i18n/ro.json
+++ /dev/null
@@ -1,1966 +0,0 @@
-{
- "action_block_contact": {
- "translation": "Blochează contact",
- "defaultMessage": "Block Contact",
- "description": "Flat button [Block Contact]",
- "missing": false,
- "obsolete": false
- },
- "action_cancel": {
- "translation": "anulează",
- "defaultMessage": "cancel",
- "description": "Call to action [cancel]",
- "missing": false,
- "obsolete": false
- },
- "action_clear_messages": {
- "translation": "Șterge mesajele",
- "defaultMessage": "Clear Messages",
- "description": "Flat button [Clear Messages] (soft-delete messages)",
- "missing": false,
- "obsolete": false
- },
- "action_delete_messages": {
- "translation": "Șterge toate mesajele",
- "defaultMessage": "Clear Messages for All",
- "description": "Flat button [Clear for All] (hard-delete all messages)",
- "missing": false,
- "obsolete": false
- },
- "action_leave_chat": {
- "translation": "Ieși din conversație",
- "defaultMessage": "Leave Conversation",
- "description": "Flat button [Leave Conversation]",
- "missing": false,
- "obsolete": false
- },
- "action_report_chat": {
- "translation": "Raportează conversația",
- "defaultMessage": "Report Conversation",
- "description": "Flat button [Report Group]",
- "missing": false,
- "obsolete": false
- },
- "archived_contacts": {
- "translation": "Contacte arhivate ({count})",
- "defaultMessage": "Archived contacts ({count})",
- "description": "Label for archived chats",
- "missing": false,
- "obsolete": false
- },
- "badge_danger": {
- "translation": "Suspicios",
- "defaultMessage": "Untrustworthy",
- "description": "Suspicious or untrustworthy account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_owner": {
- "translation": "owner",
- "defaultMessage": "owner",
- "description": "Badge for indicating the owner",
- "missing": false,
- "obsolete": false
- },
- "badge_staff": {
- "translation": "Administrare",
- "defaultMessage": "Staff-managed",
- "description": "Explanation of a staff-managed account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_verified": {
- "translation": "Verificat",
- "defaultMessage": "Verified/official",
- "description": "Explanation of a verified account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_you": {
- "translation": "tu",
- "defaultMessage": "you",
- "description": "Badge for indicating the current user",
- "missing": false,
- "obsolete": false
- },
- "block_contact_warning": {
- "translation": "Ești sigur că dorești să blochezi acest contact?",
- "defaultMessage": "Are you sure you want to block this contact?",
- "description": "Alert dialog warning when blocking a contact.",
- "missing": false,
- "obsolete": false
- },
- "blocked_contacts_link": {
- "translation": "Contact blocate ({count})",
- "defaultMessage": "Blocked contacts ({count})",
- "description": "Blocked contacts link",
- "missing": false,
- "obsolete": false
- },
- "button_add_members": {
- "translation": "Adaugă persoane",
- "defaultMessage": "Add members",
- "description": "Flat button [Add members] (to topic)",
- "missing": false,
- "obsolete": false
- },
- "button_cancel": {
- "translation": "Anulează",
- "defaultMessage": "Cancel",
- "description": "Button [Cancel]",
- "missing": false,
- "obsolete": false
- },
- "button_confirm": {
- "translation": "Confirmă",
- "defaultMessage": "Confirm",
- "description": "Button [Confirm]",
- "missing": false,
- "obsolete": false
- },
- "button_create": {
- "translation": "Creează",
- "defaultMessage": "Create",
- "description": "Button [Create]",
- "missing": false,
- "obsolete": false
- },
- "button_delete_account": {
- "translation": "Șterge cont",
- "defaultMessage": "Delete account",
- "description": "Button [Delete account]",
- "missing": false,
- "obsolete": false
- },
- "button_edit": {
- "translation": "Editează",
- "defaultMessage": "Edit",
- "description": "Call to action [Edit]",
- "missing": false,
- "obsolete": false
- },
- "button_logout": {
- "translation": "Deconectare",
- "defaultMessage": "Logout",
- "description": "Button [Logout]",
- "missing": false,
- "obsolete": false
- },
- "button_ok": {
- "translation": "OK",
- "defaultMessage": "OK",
- "description": "Button [OK]",
- "missing": false,
- "obsolete": false
- },
- "button_reset": {
- "translation": "Resetează",
- "defaultMessage": "Reset",
- "description": "Button [Reset]",
- "missing": false,
- "obsolete": false
- },
- "button_send_request": {
- "translation": "Trimite cerere",
- "defaultMessage": "Send request",
- "description": "Button [Send request]",
- "missing": false,
- "obsolete": false
- },
- "button_sign_in": {
- "translation": "Conectează-te",
- "defaultMessage": "Sign in",
- "description": "Button [Sign In]",
- "missing": false,
- "obsolete": false
- },
- "button_sign_up": {
- "translation": "Creează cont",
- "defaultMessage": "Sign up",
- "description": "Create account button [Sign Up]",
- "missing": false,
- "obsolete": false
- },
- "button_subscribe": {
- "translation": "Subscribe",
- "defaultMessage": "Subscribe",
- "description": "Button [Subscribe]",
- "missing": false,
- "obsolete": false
- },
- "button_update": {
- "translation": "Actualizează",
- "defaultMessage": "Update",
- "description": "Button [Update]",
- "missing": false,
- "obsolete": false
- },
- "cannot_initiate_file_upload": {
- "translation": "Nu se poate face încărcarea.",
- "defaultMessage": "Cannot initiate file upload.",
- "description": "Generic error messagewhen attachment fails",
- "missing": false,
- "obsolete": false
- },
- "channel": {
- "translation": "canal",
- "defaultMessage": "channel",
- "description": "Subtitle shown for channels in MessagesView instead of last seen",
- "missing": false,
- "obsolete": false
- },
- "channel_prompt": {
- "translation": "Acesta este un canal",
- "defaultMessage": "This is a channel",
- "description": "Checkbox label when creating a channel",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation": {
- "translation": "Ai primit invitație într-o conversație. Accepți?",
- "defaultMessage": "You are invited to start a new chat. What would you like to do?",
- "description": "New chat invitation message: [Accept] [Ignore] [Block].",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_accept": {
- "translation": "Da, accept",
- "defaultMessage": "Accept",
- "description": "Action [Accept] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_block": {
- "translation": "Blochează",
- "defaultMessage": "Block",
- "description": "Action [Block] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_ignore": {
- "translation": "Ignoră",
- "defaultMessage": "Ignore",
- "description": "Action [Ignore] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "clear_messages_warning": {
- "translation": "Ești sigur că dorești să ștergi mesajele? Acestea nu pot fi recuperate.",
- "defaultMessage": "Are you sure you want to clear all messages? It cannot be undone.",
- "description": "Alert dialog warning when deleting all messages.",
- "missing": false,
- "obsolete": false
- },
- "code_doesnot_match": {
- "translation": "Codul furnizat nu este valid",
- "defaultMessage": "Code does not match",
- "description": "Error message when the credential validation code is incorrect.",
- "missing": false,
- "obsolete": false
- },
- "contacts_not_found": {
- "translation": "Nu ai contacte salvate\n¯∖_(ツ)_/¯",
- "defaultMessage": "You have no chats\\n¯∖_(ツ)_/¯",
- "description": "HTML message shown in ContactList when no contacts are found",
- "missing": false,
- "obsolete": false
- },
- "contacts_not_found_short": {
- "translation": "Niciun contact găsit după criteriile ''{query}''",
- "defaultMessage": "No contacts match ''{query}''",
- "description": "Shown in ContactsView when search returned no results",
- "missing": false,
- "obsolete": false
- },
- "credential_email_prompt": {
- "translation": "Adresa de e-mail",
- "defaultMessage": "Your registration email",
- "description": "Placeholder for entering email",
- "missing": false,
- "obsolete": false
- },
- "delete_account": {
- "translation": "Șterge contul",
- "defaultMessage": "Delete account",
- "description": "Title for delete account warning",
- "missing": false,
- "obsolete": false
- },
- "delete_account_warning": {
- "translation": "Ești sigur că dorești să ștergi contul? Acesta nu va putea fi recuperat.",
- "defaultMessage": "Are you sure you want to delete your account? It cannot be undone.",
- "description": "Warning message when deleting an account",
- "missing": false,
- "obsolete": false
- },
- "delete_messages_warning": {
- "translation": "Ești sigur că dorești să șterge mesajele din toate conversațiile? Acestea nu pot fi recuperate.",
- "defaultMessage": "Are you sure you want to delete all messages for everyone? It cannot be undone.",
- "description": "Alert dialog warning when hard-deleting all messages.",
- "missing": false,
- "obsolete": false
- },
- "download_action": {
- "translation": "download",
- "defaultMessage": "download",
- "description": "Call to action [download]",
- "missing": false,
- "obsolete": false
- },
- "drafty_attachment": {
- "translation": "Atasament",
- "defaultMessage": "Attachment",
- "description": "Comment for attachment in Drafty",
- "missing": false,
- "obsolete": false
- },
- "drafty_form": {
- "translation": "Formă:",
- "defaultMessage": "Form:",
- "description": "Comment for form in Drafty",
- "missing": false,
- "obsolete": false
- },
- "drafty_image": {
- "translation": "Imagine",
- "defaultMessage": "Picture",
- "description": "Comment for embedded images in Drafty",
- "missing": false,
- "obsolete": false
- },
- "email_dative": {
- "translation": "email",
- "defaultMessage": "email",
- "description": "Dative case of 'email', i.e. 'email' in 'by email'",
- "missing": false,
- "obsolete": false
- },
- "email_prompt": {
- "translation": "Adresa de e-mail",
- "defaultMessage": "Email, e.g. jdoe@example.com",
- "description": "Input placeholder for email entry",
- "missing": false,
- "obsolete": false
- },
- "enable_peers_messaging": {
- "translation": "Activează",
- "defaultMessage": "Enable",
- "description": "Call to action to enable peer's messaging",
- "missing": false,
- "obsolete": false
- },
- "enter_confirmation_code_prompt": {
- "translation": "Introu codul de confirmare trimis pe {method}:",
- "defaultMessage": "Confirmation code",
- "description": "Request to enter confirmation code",
- "missing": false,
- "obsolete": false
- },
- "error_invalid_id": {
- "translation": "ID Invalid",
- "defaultMessage": "Invalid ID",
- "description": "Error message",
- "missing": false,
- "obsolete": false
- },
- "file_attachment_too_large": {
- "translation": "Dimensiunea fișiterului {size} depășește limita de {limit}.",
- "defaultMessage": "The file size {size} exceeds the {limit} limit.",
- "description": "Error message when attachment is too large",
- "missing": false,
- "obsolete": false
- },
- "forgot_password_link": {
- "translation": "Ți-ai uitat parola?",
- "defaultMessage": "Forgot password?",
- "description": "Link to Reset password form",
- "missing": false,
- "obsolete": false
- },
- "full_name_prompt": {
- "translation": "Numele tău",
- "defaultMessage": "Full name, e.g. John Doe",
- "description": "Input placeholder for person's full name",
- "missing": false,
- "obsolete": false
- },
- "granted_permissions": {
- "translation": "Oferite",
- "defaultMessage": "Granted",
- "description": "Title for permissions",
- "missing": false,
- "obsolete": false
- },
- "group_has_no_members": {
- "translation": "În acest grup nu se află persoane",
- "defaultMessage": "No members",
- "description": "Shown in place of group members",
- "missing": false,
- "obsolete": false
- },
- "group_user_id_prompt": {
- "translation": "Grup sau user ID",
- "defaultMessage": "Group or User ID",
- "description": "Prompt for entering user or group ID",
- "missing": false,
- "obsolete": false
- },
- "image_caption_prompt": {
- "translation": "Titlul imaginii",
- "defaultMessage": "Image caption",
- "description": "Prompt in SendMessage for attached image",
- "missing": false,
- "obsolete": false
- },
- "invalid_content": {
- "translation": "conținut invalid",
- "defaultMessage": "invalid content",
- "description": "Shown when the message is unreadable",
- "missing": false,
- "obsolete": false
- },
- "invalid_security_token": {
- "translation": "Codul de securitate este invalid",
- "defaultMessage": "Invalid security token",
- "description": "Error message when resetting password.",
- "missing": false,
- "obsolete": false
- },
- "label_client": {
- "translation": "Client:",
- "defaultMessage": "Client:",
- "description": "Label for a client version",
- "missing": false,
- "obsolete": false
- },
- "label_content_type": {
- "translation": "Tip conținut:",
- "defaultMessage": "Content type:",
- "description": "Label for file content type (mime)",
- "missing": false,
- "obsolete": false
- },
- "label_default_access_mode": {
- "translation": "Default access mode:",
- "defaultMessage": "Default access mode:",
- "description": "Label for default access mode",
- "missing": false,
- "obsolete": false
- },
- "label_file_name": {
- "translation": "Nume fișier:",
- "defaultMessage": "File name:",
- "description": "Label for a file name",
- "missing": false,
- "obsolete": false
- },
- "label_group_members": {
- "translation": "Membrii grupului:",
- "defaultMessage": "Group members:",
- "description": "Section title or label",
- "missing": false,
- "obsolete": false
- },
- "label_incognito_mode": {
- "translation": "Mod incognito:",
- "defaultMessage": "Incognito mode:",
- "description": "Label for incognito mode toggle",
- "missing": false,
- "obsolete": false
- },
- "label_message_sound": {
- "translation": "Sunet de mesaj:",
- "defaultMessage": "Message sound:",
- "description": "Label for message sounds toggle",
- "missing": false,
- "obsolete": false
- },
- "label_muting_topic": {
- "translation": "Mod silențios (muted):",
- "defaultMessage": "Muted:",
- "description": "Label for Muting/unmuting the topic",
- "missing": false,
- "obsolete": false
- },
- "label_other_user": {
- "translation": "Altele",
- "defaultMessage": "Other",
- "description": "Label for the other user when the user is unnamed",
- "missing": false,
- "obsolete": false
- },
- "label_password": {
- "translation": "Parola",
- "defaultMessage": "Password",
- "description": "Label for password editing",
- "missing": false,
- "obsolete": false
- },
- "label_permissions": {
- "translation": "Permisiuni:",
- "defaultMessage": "Permissions:",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "label_private": {
- "translation": "Comentariu privat",
- "defaultMessage": "Private comment",
- "description": "Label for editing 'private'",
- "missing": false,
- "obsolete": false
- },
- "label_push_notifications": {
- "translation": "Alerte de notificare:",
- "defaultMessage": "Notification alerts:",
- "description": "Label for push notifications switch",
- "missing": false,
- "obsolete": false
- },
- "label_push_notifications_disabled": {
- "translation": "Alerte de notificare (necesită HTTPS):",
- "defaultMessage": "Notification alerts (requires HTTPS):",
- "description": "Label for push notifications switch",
- "missing": false,
- "obsolete": false
- },
- "label_reset_password": {
- "translation": "Resetează parola:",
- "defaultMessage": "Send a password reset email",
- "description": "Label for password reset field",
- "missing": false,
- "obsolete": false
- },
- "label_sdk": {
- "translation": "SDK:",
- "defaultMessage": "SDK:",
- "missing": false,
- "obsolete": false
- },
- "label_server": {
- "translation": "Server:",
- "defaultMessage": "Server:",
- "description": "Label for a server version",
- "missing": false,
- "obsolete": false
- },
- "label_server_address": {
- "translation": "Adresa serverului:",
- "defaultMessage": "Server address:",
- "missing": false,
- "obsolete": false
- },
- "label_server_to_use": {
- "translation": "Server de utilizat:",
- "defaultMessage": "Server to use:",
- "description": "Label for server selector in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "label_size": {
- "translation": "Size:",
- "defaultMessage": "Size:",
- "description": "Label for file size",
- "missing": false,
- "obsolete": false
- },
- "label_topic_name": {
- "translation": "Nume",
- "defaultMessage": "Name",
- "description": "Label for editing topic name",
- "missing": false,
- "obsolete": false
- },
- "label_user_contacts": {
- "translation": "Contacte",
- "defaultMessage": "Contacts",
- "description": "Label for user contacts",
- "missing": false,
- "obsolete": false
- },
- "label_user_id": {
- "translation": "ID:",
- "defaultMessage": "ID:",
- "description": "Label for user address (ID)",
- "missing": false,
- "obsolete": false
- },
- "label_wire_transport": {
- "translation": "Transportul legat:",
- "defaultMessage": "Wire transport:",
- "description": "Label for wire transport selection in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "label_you": {
- "translation": "Tu:",
- "defaultMessage": "You:",
- "description": "Label for the current user",
- "missing": false,
- "obsolete": false
- },
- "label_your_name": {
- "translation": "Numele tău",
- "defaultMessage": "Your name",
- "description": "Label for full name editing",
- "missing": false,
- "obsolete": false
- },
- "label_your_permissions": {
- "translation": "Permisiuniile tale:",
- "defaultMessage": "Your permissions:",
- "description": "Label for current user permissions",
- "missing": false,
- "obsolete": false
- },
- "last_seen_timestamp": {
- "translation": "Văzut ultima dată",
- "defaultMessage": "Last seen: {timestamp}",
- "description": "Label for the timestamp of when the user or topic was last online",
- "missing": false,
- "obsolete": false
- },
- "leave_chat_warning": {
- "translation": "Ești sigur că dorești să ieși din conersație?",
- "defaultMessage": "Are you sure you want to leave this conversation?",
- "description": "Alert dialog warning when unsubscribing from a chat.",
- "missing": false,
- "obsolete": false
- },
- "link_contact_us": {
- "translation": "Contactează-ne",
- "defaultMessage": "Contact Us",
- "description": "Ancor text for contacting us by email",
- "missing": false,
- "obsolete": false
- },
- "link_privacy_policy": {
- "translation": "Politica de Confidențialitate",
- "defaultMessage": "Privacy Policy",
- "description": "Ancor text for privacy policy link",
- "missing": false,
- "obsolete": false
- },
- "link_terms_of_service": {
- "translation": "Termenii Serviciului",
- "defaultMessage": "Terms of Service",
- "description": "Ancor text for terms of service link",
- "missing": false,
- "obsolete": false
- },
- "login_prompt": {
- "translation": "Conectează-te",
- "defaultMessage": "Login",
- "description": "Placeholer for username/login",
- "missing": false,
- "obsolete": false
- },
- "menu_item_archive_topic": {
- "translation": "Arhivează",
- "defaultMessage": "Archive",
- "description": "Move topic from the list of active chats to archive",
- "missing": false,
- "obsolete": false
- },
- "menu_item_block": {
- "translation": "Blochează",
- "defaultMessage": "Block",
- "description": "Block topic or user",
- "missing": false,
- "obsolete": false
- },
- "menu_item_clear_messages": {
- "translation": "Șterge mesajele",
- "defaultMessage": "Clear messages",
- "description": "Delete all messages",
- "missing": false,
- "obsolete": false
- },
- "menu_item_clear_messages_for_all": {
- "translation": "Ștergele pe toate",
- "defaultMessage": "Clear for All",
- "description": "Delete all message(s) for all members",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete": {
- "translation": "Șterge",
- "defaultMessage": "Delete",
- "description": "Delete selected messages",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete_for_all": {
- "translation": "Ștergele pe toate",
- "defaultMessage": "Delete for All",
- "description": "Delete selected message(s) for all members",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete_topic": {
- "translation": "Șterge",
- "defaultMessage": "Delete",
- "description": "Delete entire topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_edit_permissions": {
- "translation": "Editează permisiuni",
- "defaultMessage": "Edit permissions",
- "description": "Menu item [Edit permissions]",
- "missing": false,
- "obsolete": false
- },
- "menu_item_info": {
- "translation": "Info",
- "defaultMessage": "Info",
- "description": "Show extended topic information",
- "missing": false,
- "obsolete": false
- },
- "menu_item_member_delete": {
- "translation": "Șterge persoană",
- "defaultMessage": "Remove",
- "description": "Remove user from topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_mute": {
- "translation": "Mute",
- "defaultMessage": "Mute",
- "description": "Turn off notifications",
- "missing": false,
- "obsolete": false
- },
- "menu_item_restore_topic": {
- "translation": "Restabili",
- "defaultMessage": "Restore",
- "description": "Restore topic from archive",
- "missing": false,
- "obsolete": false
- },
- "menu_item_send_retry": {
- "translation": "Reîncearcă",
- "defaultMessage": "Retry",
- "description": "Retry sending message",
- "missing": false,
- "obsolete": false
- },
- "menu_item_unblock": {
- "translation": "Deblochează",
- "defaultMessage": "Unblock",
- "description": "Unblock topic or user",
- "missing": false,
- "obsolete": false
- },
- "menu_item_unmute": {
- "translation": "Unmute",
- "defaultMessage": "Unmute",
- "description": "Turn notifications on",
- "missing": false,
- "obsolete": false
- },
- "message_sending": {
- "translation": "se trimite...",
- "defaultMessage": "sending...",
- "description": "Message being sent, in place of time stamp",
- "missing": false,
- "obsolete": false
- },
- "message_sending_failed": {
- "translation": "eroare",
- "defaultMessage": "failed",
- "description": "Failed to send message, in place of time stamp",
- "missing": false,
- "obsolete": false
- },
- "messages_not_readable": {
- "translation": "Mesajele nu pot fi citite",
- "defaultMessage": "no access to messages",
- "description": "Message shown in topic without the read access",
- "missing": false,
- "obsolete": false
- },
- "messaging_disabled_prompt": {
- "translation": "Mesageria este dezactivată",
- "defaultMessage": "Messaging disabled",
- "description": "Prompt in SendMessage in read-only topic",
- "missing": false,
- "obsolete": false
- },
- "more_online_members": {
- "translation": "+{overflow} mai mult",
- "defaultMessage": "+{overflow} more",
- "description": "Shown in MessagesView title bar when the number of online subscribers exceeds MAX_ONLINE_IN_TOPIC",
- "missing": false,
- "obsolete": false
- },
- "new_message_prompt": {
- "translation": "Mesaj nou",
- "defaultMessage": "New message",
- "description": "Prompt in send message field",
- "missing": false,
- "obsolete": false
- },
- "new_password_placeholder": {
- "translation": "Introdu parolă",
- "defaultMessage": "Enter new password",
- "description": "Placeholder for entering new password",
- "missing": false,
- "obsolete": false
- },
- "no_connection": {
- "translation": "Nu există conexiune",
- "defaultMessage": "No connection",
- "description": "Warning that the user is offline.",
- "missing": false,
- "obsolete": false
- },
- "no_contacts": {
- "translation": "Nu ai contacte adăugate :-(",
- "defaultMessage": "You have no contacts :-(",
- "description": "Shown in ContactsView when the user has no contacts",
- "missing": false,
- "obsolete": false
- },
- "numeric_confirmation_code_prompt": {
- "translation": "Doar cifre",
- "defaultMessage": "Numbers only",
- "description": "Prompt for numeric conformation code",
- "missing": false,
- "obsolete": false
- },
- "online_now": {
- "translation": "online acum",
- "defaultMessage": "online now",
- "description": "Indicator that the user or topic is currently online",
- "missing": false,
- "obsolete": false
- },
- "password_prompt": {
- "translation": "Parola",
- "defaultMessage": "Password",
- "description": "Placeholder/prompt for entering password",
- "missing": false,
- "obsolete": false
- },
- "password_unchanged_prompt": {
- "translation": "Neschimbată",
- "defaultMessage": "Unchanged",
- "description": "Message in editor while password is unchanged",
- "missing": false,
- "obsolete": false
- },
- "peers_messaging_disabled": {
- "translation": "Mesageria de tip Peer este dezactivată.",
- "defaultMessage": "Peer's messaging is disabled.",
- "description": "Shown when the p2p peer's messaging is disabled",
- "missing": false,
- "obsolete": false
- },
- "permission_admin": {
- "translation": "Aprobare ({val})",
- "defaultMessage": "Approve ({val})",
- "description": "Name of A permission",
- "missing": false,
- "obsolete": false
- },
- "permission_delete": {
- "translation": "Ștergere ({val})",
- "defaultMessage": "Delete ({val})",
- "description": "Name of D permission",
- "missing": false,
- "obsolete": false
- },
- "permission_join": {
- "translation": "Participare ({val})",
- "defaultMessage": "Join ({val})",
- "description": "Name of J permission",
- "missing": false,
- "obsolete": false
- },
- "permission_owner": {
- "translation": "Deținător ({val})",
- "defaultMessage": "Owner ({val})",
- "description": "Name of O permission",
- "missing": false,
- "obsolete": false
- },
- "permission_pres": {
- "translation": "Notificare ({val})",
- "defaultMessage": "Get notified ({val})",
- "description": "Name of P permission",
- "missing": false,
- "obsolete": false
- },
- "permission_read": {
- "translation": "Citire ({val})",
- "defaultMessage": "Read ({val})",
- "description": "Name of R permission",
- "missing": false,
- "obsolete": false
- },
- "permission_share": {
- "translation": "Distribuire ({val})",
- "defaultMessage": "Share ({val})",
- "description": "Name of S permission",
- "missing": false,
- "obsolete": false
- },
- "permission_write": {
- "translation": "Scriere ({val})",
- "defaultMessage": "Write ({val})",
- "description": "Name of W permission",
- "missing": false,
- "obsolete": false
- },
- "phone_dative": {
- "translation": "telefon",
- "defaultMessage": "phone",
- "description": "Dative case of 'phone', i.e. 'phone' in 'by phone'",
- "missing": false,
- "obsolete": false
- },
- "private_editing_placeholder": {
- "translation": "Vizibil doar ție",
- "defaultMessage": "Visible to you only",
- "description": "Placeholder for editing 'private'",
- "missing": false,
- "obsolete": false
- },
- "push_init_failed": {
- "translation": "Nu s-a reușit inițializarea tip push notifications",
- "defaultMessage": "Failed to initialize push notifications",
- "description": "Error message when push notifications have failed to initialize.",
- "missing": false,
- "obsolete": false
- },
- "reconnect_countdown": {
- "translation": "Deconectat. Se încearcă conectarea în {seconds}…",
- "defaultMessage": "Disconnected. Reconnecting in {seconds}…",
- "description": "Message shown when an app update is available.",
- "missing": false,
- "obsolete": false
- },
- "reconnect_now": {
- "translation": "Încearcă din nou",
- "defaultMessage": "Try now",
- "description": "Prompt for reconnecting now",
- "missing": false,
- "obsolete": false
- },
- "reload_update": {
- "translation": "Reîncarcă",
- "defaultMessage": "Reload",
- "description": "Call to action to reload application when update is available.",
- "missing": false,
- "obsolete": false
- },
- "report_chat_warning": {
- "translation": "Ești sigur că dorești să blochezi și să raportezi această conversație?",
- "defaultMessage": "Are you sure you want to block and report this conversation?",
- "description": "Alert dialog warning when reporting a conversation for abuse",
- "missing": false,
- "obsolete": false
- },
- "requested_permissions": {
- "translation": "Necesare",
- "defaultMessage": "Requested",
- "description": "Title for permissions",
- "missing": false,
- "obsolete": false
- },
- "save_attachment": {
- "translation": "salvează",
- "defaultMessage": "save",
- "description": "Call to save an attachment",
- "missing": false,
- "obsolete": false
- },
- "search_for_contacts": {
- "translation": "Caută în contacte",
- "defaultMessage": "Use search to find contacts",
- "description": "Text shown in contacts view when user entered no search query.",
- "missing": false,
- "obsolete": false
- },
- "search_no_results": {
- "translation": "Nu s-au găsit rezultate",
- "defaultMessage": "Search returned no results",
- "description": "Text shown in contacts view when query returned no results.",
- "missing": false,
- "obsolete": false
- },
- "search_placeholder": {
- "translation": "Caută după e-mail sau număr telefon...",
- "defaultMessage": "List like alice@example.com, +17025550003...",
- "description": "Placeholder in contacts search field",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_notifications": {
- "translation": "Notificări",
- "defaultMessage": "Notifications",
- "description": "Sidepanel title for AccNotificationsView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_support": {
- "translation": "Ajutor",
- "defaultMessage": "Support",
- "description": "Sidepanel title for AccSupportView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_account_settings": {
- "translation": "Setările Contului",
- "defaultMessage": "Account Settings",
- "description": "Sidepanel title for AccountSettingsView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_archive": {
- "translation": "Conversații Arhivate",
- "defaultMessage": "Archived Chats",
- "description": "Sidepanel title for ContactsView-Archive.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_blocked": {
- "translation": "Conversații Blocate",
- "defaultMessage": "Blocked Chats",
- "description": "Sidepanel title for ContactsView-Blocked.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_cred": {
- "translation": "Confirmă credențiale",
- "defaultMessage": "Confirm Credentials",
- "description": "Sidepanel title for ValidationView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_login": {
- "translation": "Conectează-te",
- "defaultMessage": "Sign In",
- "description": "Sidepanel title for LoginView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_newtpk": {
- "translation": "Creeză un nou Chat",
- "defaultMessage": "Start New Chat",
- "description": "Sidepanel title for NewTopicView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_register": {
- "translation": "Creează Cont",
- "defaultMessage": "Create Account",
- "description": "Sidepanel title for CreateAccountView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_reset": {
- "translation": "Resetează Parola",
- "defaultMessage": "Reset Password",
- "description": "Sidepanel title for PasswordResetView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_settings": {
- "translation": "Setări",
- "defaultMessage": "Settings",
- "description": "Sidepanel title for SettingsView.",
- "missing": false,
- "obsolete": false
- },
- "stay_logged_in": {
- "translation": "Rămâi conectat",
- "defaultMessage": "Stay logged in",
- "description": "Label for a checkbox",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_find_user": {
- "translation": "caută",
- "defaultMessage": "find",
- "description": "Tab title Find",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_group_by_id": {
- "translation": "după id",
- "defaultMessage": "by id",
- "description": "Tab title Find topic by ID",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_new_group": {
- "translation": "grup nou",
- "defaultMessage": "new group",
- "description": "Tab title New Group",
- "missing": false,
- "obsolete": false
- },
- "tags_editor_no_tags": {
- "translation": "Adaugă tag-uri",
- "defaultMessage": "Add some tags",
- "description": "Tag editor prompt when no tags are found.",
- "missing": false,
- "obsolete": false
- },
- "tags_not_found": {
- "translation": "Niciun tag definit. Adaugă.",
- "defaultMessage": "No tags defined. Add some.",
- "description": "",
- "missing": false,
- "obsolete": false
- },
- "title_all_contacts": {
- "translation": "Toate Contactele",
- "defaultMessage": "All Contacts",
- "description": "Section title [All Contacts]",
- "missing": false,
- "obsolete": false
- },
- "title_group_members": {
- "translation": "Persoanele din grup",
- "defaultMessage": "Group Members",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "title_manage_tags": {
- "translation": "Administrează",
- "defaultMessage": "Manage",
- "description": "Section title for the list of tags",
- "missing": false,
- "obsolete": false
- },
- "title_not_found": {
- "translation": "Nu a fost găsit",
- "defaultMessage": "Not found",
- "description": "Title shown when topic is not found",
- "missing": false,
- "obsolete": false
- },
- "title_permissions": {
- "translation": "Permisiuni",
- "defaultMessage": "Permissions",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "title_tag_manager": {
- "translation": "Tag-uri (user discovery)",
- "defaultMessage": "Tags (search & discovery)",
- "description": "Section title for TagManager",
- "missing": false,
- "obsolete": false
- },
- "topic_block_warning": {
- "translation": "Ești sigur că dorești să blochezi această conversație?",
- "defaultMessage": "Are you sure you want to block this conversation?",
- "description": "Alert warning when blocking a topic.",
- "missing": false,
- "obsolete": false
- },
- "topic_delete_warning": {
- "translation": "Ești sigur că dorești să șterge această conversație?",
- "defaultMessage": "Are you sure you want to delete this conversation? It cannot be undone.",
- "description": "Alert warning when deleting entire topic",
- "missing": false,
- "obsolete": false
- },
- "topic_name_editing_placeholder": {
- "translation": "Numele grupului",
- "defaultMessage": "Freeform name of the group",
- "description": "Prompt for entering topic name",
- "missing": false,
- "obsolete": false
- },
- "unnamed_topic": {
- "translation": "Nedenumit",
- "defaultMessage": "Unnamed",
- "description": "Title shown when the topic has no name",
- "missing": false,
- "obsolete": false
- },
- "update_available": {
- "translation": "Actualizare disponibilă.",
- "defaultMessage": "Update available.",
- "description": "Message shown when an app update is available.",
- "missing": false,
- "obsolete": false
- },
- "upload_finishing": {
- "translation": "se încarcă...",
- "defaultMessage": "finishing...",
- "description": "Notification that upload is finishing",
- "missing": false,
- "obsolete": false
- },
- "user_not_found": {
- "translation": "Utilizatorul nu a fost găsit",
- "defaultMessage": "Not found",
- "description": "In place of a user's full name when the user is not found.",
- "missing": false,
- "obsolete": false
- },
- "description_editing_placeholder": {
- "translation": "Descriere (opțional)",
- "defaultMessage": "Description (optional)",
- "description": "Placeholder for editing topic description",
- "missing": false,
- "obsolete": false
- },
- "label_description": {
- "translation": "Descriere",
- "defaultMessage": "Description",
- "description": "Label for description of user or topic",
- "missing": false,
- "obsolete": false
- },
- "button_security": {
- "translation": "Securitate",
- "defaultMessage": "Security",
- "description": "Navigaton button for security panel.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_crop": {
- "translation": "Trageți pentru a ajusta",
- "defaultMessage": "Drag to Adjust",
- "description": "Title for AvatarCropView.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_general": {
- "translation": "Generale",
- "defaultMessage": "General",
- "description": "Title for TopicCommon.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_members": {
- "translation": "Membri",
- "defaultMessage": "Members",
- "description": "Title for managing group members view.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_security": {
- "translation": "Securitate",
- "defaultMessage": "Security",
- "description": "Title for TopicSecirity and AccSecurity.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_info": {
- "translation": "Info",
- "defaultMessage": "Info",
- "description": "Title for InfoView",
- "missing": false,
- "obsolete": false
- },
- "permissions_anonymous": {
- "translation": "Anonim",
- "defaultMessage": "Anonymous",
- "description": "Title for editing default anonymous permissions",
- "missing": false,
- "obsolete": false
- },
- "permissions_authenticated": {
- "translation": "Autentificat",
- "defaultMessage": "Authenticated",
- "description": "Title for editing default authenticated permissions",
- "missing": false,
- "obsolete": false
- },
- "topic_delete": {
- "translation": "Ștergeți Chat",
- "defaultMessage": "Delete Conversation",
- "description": "Alert title when deleting the topic.",
- "missing": false,
- "obsolete": false
- },
- "permissions_user": {
- "translation": "Permisiunile utilizatorului",
- "defaultMessage": "User's Permissions",
- "description": "Title for editing user's permissions",
- "missing": false,
- "obsolete": false
- },
- "password_reset_email_sent": {
- "translation": "A fost trimis un e-mail cu cod de securitate.",
- "defaultMessage": "An email with security code has been sent.",
- "description": "Notification that the email with password reset instructions has been sent",
- "missing": false,
- "obsolete": false
- },
- "label_unarchive_topic": {
- "translation": "Arhivat:",
- "defaultMessage": "Archived:",
- "description": "Label for unarchiving the topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_reply": {
- "translation": "A raspunde",
- "defaultMessage": "Reply",
- "description": "Reply to message",
- "missing": false,
- "obsolete": false
- },
- "menu_item_forward": {
- "translation": "Redirecţiona",
- "defaultMessage": "Forward",
- "description": "Forward message",
- "missing": false,
- "obsolete": false
- },
- "forward_to": {
- "translation": "Redirecționați către",
- "defaultMessage": "Forward to",
- "missing": false,
- "obsolete": false
- },
- "forward_to_search_placeholder": {
- "translation": "Căutați contacte",
- "defaultMessage": "Search contacts",
- "description": "Contact search prompt when forwarding a message.",
- "missing": false,
- "obsolete": false
- },
- "label_new_password": {
- "translation": "Parolă Nouă",
- "defaultMessage": "New password",
- "description": "Label for entering a new password",
- "missing": false,
- "obsolete": false
- },
- "drafty_unknown": {
- "translation": "Neacceptat",
- "defaultMessage": "Unsupported",
- "description": "Unsupported entity in drafty",
- "missing": false,
- "obsolete": false
- },
- "calls_incoming": {
- "translation": "Apel primit",
- "defaultMessage": "Incoming call",
- "description": "Incoming call label",
- "missing": false,
- "obsolete": false
- },
- "calls_outgoing": {
- "translation": "Apel efectuat",
- "defaultMessage": "Outgoing call",
- "description": "Outgoing call label",
- "missing": false,
- "obsolete": false
- },
- "calls_you_label": {
- "translation": "Tu",
- "defaultMessage": "You",
- "description": "Shown over the local video screen",
- "missing": false,
- "obsolete": false
- },
- "menu_item_video_call": {
- "translation": "Apel video",
- "defaultMessage": "Video call",
- "description": "Start video call",
- "missing": false,
- "obsolete": false
- },
- "already_in_call": {
- "translation": "Deja vorbesti!",
- "defaultMessage": "You already in an ongoing call!",
- "description": "Error message when the user tried to accept a new call without finishing pervious one",
- "missing": false,
- "obsolete": false
- },
- "call_cancelled": {
- "translation": "anulat",
- "defaultMessage": "cancelled",
- "description": "Label for cancelled outgoing call",
- "missing": false,
- "obsolete": false
- },
- "call_missed": {
- "translation": "pierdut",
- "defaultMessage": "missed",
- "description": "Label for missed incoming call",
- "missing": false,
- "obsolete": false
- },
- "call_declined": {
- "translation": "refuzat",
- "defaultMessage": "declined",
- "description": "Label for declined call",
- "missing": false,
- "obsolete": false
- },
- "call_disconnected": {
- "translation": "deconectat",
- "defaultMessage": "disconnected",
- "description": "Label for disconnected call",
- "missing": false,
- "obsolete": false
- },
- "label_use_secure_connection": {
- "translation": "Utilizați conexiune securizată",
- "defaultMessage": "Use secure connection",
- "description": "Label for WS/WSS connection type in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "cred_confirmed_successfully": {
- "translation": "Confirmat cu succes",
- "defaultMessage": "Confirmed successfully",
- "description": "Notification message that the credential was successfully validated.",
- "missing": false,
- "obsolete": false
- },
- "menu_item_edit": {
- "translation": "Editează",
- "defaultMessage": "Edit",
- "description": "Edit message",
- "missing": false,
- "obsolete": false
- },
- "editing_message": {
- "translation": "Editare",
- "defaultMessage": "Editing",
- "description": "Title over message editing preview",
- "missing": false,
- "obsolete": false
- },
- "message_edited_marker": {
- "translation": ", editat",
- "defaultMessage": ", edited",
- "description": "Marker indicating that the message was edited",
- "missing": false,
- "obsolete": false
- },
- "icon_title_add_image": {
- "translation": "Adauga imagine",
- "defaultMessage": "Add image",
- "description": "Icon tool tip for attaching an image",
- "missing": false,
- "obsolete": false
- },
- "icon_title_attach_file": {
- "translation": "Ataseaza fisier",
- "defaultMessage": "Attach file",
- "description": "Icon tool tip for attaching a file",
- "missing": false,
- "obsolete": false
- },
- "icon_title_delete": {
- "translation": "Ștergeți înregistrarea",
- "defaultMessage": "Delete recording",
- "description": "Icon tool tip for deleting recorded audio",
- "missing": false,
- "obsolete": false
- },
- "icon_title_pause": {
- "translation": "Întrerupeți redarea",
- "defaultMessage": "Pause playback",
- "description": "Icon tool tip for pausing audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_play": {
- "translation": "Redați înregistrarea",
- "defaultMessage": "Play recording",
- "description": "Icon tool tip for starting audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_record_voice": {
- "translation": "Înregistrați mesajul vocal",
- "defaultMessage": "Record voice message",
- "description": "Icon tool tip for recording a voice message",
- "missing": false,
- "obsolete": false
- },
- "icon_title_resume": {
- "translation": "Reluați redarea",
- "defaultMessage": "Resume playback",
- "description": "Icon tool tip for resuming audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_send": {
- "translation": "Trimite mesaj",
- "defaultMessage": "Send message",
- "description": "Icon tool tip for sending a message",
- "missing": false,
- "obsolete": false
- },
- "drag_file": {
- "translation": "Trageți fișierul aici",
- "defaultMessage": "Drag file here",
- "description": "Prompt on the file drag-n-drop overlay banner",
- "missing": false,
- "obsolete": false
- },
- "drafty_video": {
- "translation": "Înregistrare video",
- "defaultMessage": "Video recording",
- "description": "Comment for videos embedded in Drafty",
- "missing": false,
- "obsolete": false
- },
- "call_in_progress": {
- "translation": "în curs",
- "defaultMessage": "in progress",
- "description": "Label for call in progress",
- "missing": false,
- "obsolete": false
- },
- "menu_item_audio_call": {
- "translation": "Apel",
- "defaultMessage": "Call",
- "description": "Start audio call",
- "missing": false,
- "obsolete": false
- },
- "unrecognized_video_format": {
- "translation": "Formatul acestui videoclip nu este recunoscut",
- "defaultMessage": "Format of this video is not recognized",
- "description": "Error message when uploaded video is invalid",
- "missing": false,
- "obsolete": false
- },
- "loading_note": {
- "translation": "Se încarcă...",
- "defaultMessage": "Loading...",
- "description": "Message shown when component is loading",
- "missing": false,
- "obsolete": false
- },
- "password_i_have_code": {
- "translation": "Am cod",
- "defaultMessage": "I have code",
- "description": "Call to open field to enter password reset code",
- "missing": false,
- "obsolete": false
- },
- "label_reset_password_tel": {
- "translation": "Trimite SMS pentru a reseta parola",
- "defaultMessage": "Send SMS to reset password",
- "description": "Label for password reset field by phone",
- "missing": false,
- "obsolete": false
- },
- "mobile_phone_number": {
- "translation": "Număr de telefon mobil",
- "defaultMessage": "Mobile phone number",
- "description": "Prompt for entering a mobile phone number",
- "missing": false,
- "obsolete": false
- },
- "password_reset_sms_sent": {
- "translation": "A fost trimis un mesaj text cu cod de securitate.",
- "defaultMessage": "A text message with security code has been sent.",
- "description": "Notification that the SMS with password reset instructions has been sent",
- "missing": false,
- "obsolete": false
- },
- "mobile_number_required": {
- "translation": "Este necesar un număr de telefon mobil",
- "defaultMessage": "Mobile phone number required",
- "description": "Error message",
- "missing": false,
- "obsolete": false
- },
- "current_email": {
- "translation": "E-mail curent",
- "defaultMessage": "Current email",
- "description": "Label for old email",
- "missing": false,
- "obsolete": false
- },
- "current_phone": {
- "translation": "Număr de telefon actual",
- "defaultMessage": "Current phone number",
- "description": "Label for old phone",
- "missing": false,
- "obsolete": false
- },
- "new_email": {
- "translation": "E-mail nou",
- "defaultMessage": "New email",
- "description": "Prompt for entering a new email",
- "missing": false,
- "obsolete": false
- },
- "new_phone_number": {
- "translation": "Număr de telefon nou",
- "defaultMessage": "New phone number",
- "description": "Prompt for entering a new telephone number",
- "missing": false,
- "obsolete": false
- },
- "change_email": {
- "translation": "Schimbați adresa de e-mail",
- "defaultMessage": "Change email",
- "description": "Prompt to change current email",
- "missing": false,
- "obsolete": false
- },
- "change_phone": {
- "translation": "Schimbați numărul de telefon",
- "defaultMessage": "Change phone number",
- "description": "Prompt to change current phone number",
- "missing": false,
- "obsolete": false
- },
- "call_busy": {
- "translation": "ocupat",
- "defaultMessage": "busy",
- "description": "Label for busy call line",
- "missing": false,
- "obsolete": false
- },
- "pin_message": {
- "translation": "Fixați",
- "defaultMessage": "Pin",
- "description": "Menu item [Pin] for pinning message to the top.",
- "missing": false,
- "obsolete": false
- },
- "unpin_message": {
- "translation": "Anulați fixarea",
- "defaultMessage": "Unpin",
- "description": "Menu item [Unpin] for un-pinning the message.",
- "missing": false,
- "obsolete": false
- },
- "failed_to_init_audio": {
- "translation": "Inițializarea înregistrării audio a eșuat",
- "defaultMessage": "Failed to initialize audio recording",
- "description": "Error message when audio is not available",
- "missing": false,
- "obsolete": false
- },
- "password_reset_success": {
- "translation": "Resetarea parolei cu succes",
- "defaultMessage": "Password reset successfully",
- "description": "Notification message that the password was successfully reset.",
- "missing": false,
- "obsolete": false
- },
- "scan_qr_code": {
- "translation": "Scanați codul QR",
- "defaultMessage": "Scan QR Code",
- "description": "Title for scanning QR code",
- "missing": false,
- "obsolete": false
- },
- "self_topic_comment": {
- "translation": "Note, mesaje, linkuri, fișiere salvate pentru posteritate",
- "defaultMessage": "Notes, messages, links, files saved for posterity",
- "description": "Comment for self topic for UI",
- "missing": false,
- "obsolete": false
- },
- "self_topic_name": {
- "translation": "Mesaje salvate",
- "defaultMessage": "Saved messages",
- "description": "Name of self topic for UI",
- "missing": false,
- "obsolete": false
- },
- "message_not_found": {
- "translation": "mesajul nu a fost găsit",
- "defaultMessage": "message not found",
- "description": "Pinned message is not found",
- "missing": false,
- "obsolete": false
- },
- "message_deleted": {
- "translation": "mesaj șters",
- "defaultMessage": "message deleted",
- "description": "Pinned message is deleted",
- "missing": false,
- "obsolete": false
- },
- "alias_editing_placeholder": {
- "translation": "Alias (opțional)",
- "defaultMessage": "Alias (optional)",
- "description": "Placeholder for editing user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "label_alias": {
- "translation": "Alias:",
- "defaultMessage": "Alias:",
- "description": "Label for user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "alias_already_taken": {
- "translation": "(deja luat)",
- "defaultMessage": "(already taken)",
- "description": "Error message for alias already taken",
- "missing": false,
- "obsolete": false
- },
- "alias_invalid": {
- "translation": "(nevalid)",
- "defaultMessage": "(invalid)",
- "description": "Error message for invalid alias",
- "missing": false,
- "obsolete": false
- },
- "label_alias_edit": {
- "translation": "Alias",
- "defaultMessage": "Alias",
- "description": "Label for editing user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "select_country": {
- "translation": "Selectați țara",
- "defaultMessage": "Select country",
- "description": "Placeholder for the country selector",
- "missing": false,
- "obsolete": false
- },
- "text_copied": {
- "translation": "Copiat în clipboard",
- "defaultMessage": "Copied to clipboard",
- "description": "Notification that text has been copied to clipboard",
- "missing": false,
- "obsolete": false
- },
- "color_schema_dark": {
- "translation": "Întunecat",
- "defaultMessage": "Dark",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "color_schema_light": {
- "translation": "Luminos",
- "defaultMessage": "Light",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "color_schema_system": {
- "translation": "Implicit sistem",
- "defaultMessage": "System default",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "label_color_schema": {
- "translation": "Temă:",
- "defaultMessage": "Theme:",
- "description": "Label for selecting color scheme (dark, light) in Settings",
- "missing": false,
- "obsolete": false
- },
- "label_text_size": {
- "translation": "Dimensiune text:",
- "defaultMessage": "Text size:",
- "description": "Label adjusting text size",
- "missing": false,
- "obsolete": false
- },
- "incognito_mode_description": {
- "translation": "Ascunde statusul tău online și indicatorii de tastare.",
- "defaultMessage": "Hide your online status and typing indicators.",
- "description": "Description for incognito mode toggle",
- "missing": false,
- "obsolete": false
- },
- "send_command": {
- "translation": "Trimite cu {key}",
- "defaultMessage": "Send on {key}",
- "description": "Config option to send message on hitting CTRL(or Cmd)-Enter",
- "missing": false,
- "obsolete": false
- },
- "send_command_explained": {
- "translation": "Apasă Enter pentru linie nouă",
- "defaultMessage": "Press Enter for new line",
- "description": "Explanation how to enter newline when [Send on CTRL-Enter] is enabled",
- "missing": false,
- "obsolete": false
- },
- "send_plain": {
- "translation": "Trimite cu Enter",
- "defaultMessage": "Send on Enter",
- "description": "Config option to send message on hitting Enter",
- "missing": false,
- "obsolete": false
- },
- "send_plain_explained": {
- "translation": "Apasă Shift + Enter pentru linie nouă",
- "defaultMessage": "Press Shift + Enter for new line",
- "description": "Explanation how to enter newline when [Send on Enter] is enabled",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_general": {
- "translation": "General",
- "defaultMessage": "General",
- "description": "Sidepanel title for AccGeneralView.",
- "missing": false,
- "obsolete": false
- },
- "label_keyboard": {
- "translation": "Tastatură:",
- "defaultMessage": "Keyboard:",
- "description": "Label for send on enter settings",
- "missing": false,
- "obsolete": false
- },
- "pin_chat": {
- "translation": "Fixează",
- "defaultMessage": "Pin",
- "description": "Menu item [Pin] for pinning chat to the top.",
- "missing": false,
- "obsolete": false
- },
- "unpin_chat": {
- "translation": "Anulează fixarea",
- "defaultMessage": "Unpin",
- "description": "Menu item [Unpin] for un-pinning the chat.",
- "missing": false,
- "obsolete": false
- },
- "subscriber_count": {
- "translation": "{count, plural, one {{count, number} abonat} few {{count, number} abonați} other {{count, number} de abonați}}",
- "defaultMessage": "{count, plural, one {{count, number} subscriber} two {{count, number} subscribers} three {{count, number} subscribers} few {{count, number} subscribers} many {{count, number} subscribers} other {{count, number} subscribers}}",
- "description": "Count of channel subscribers",
- "missing": false,
- "obsolete": false
- },
- "label_member_count": {
- "translation": "Membri:",
- "defaultMessage": "Members:",
- "description": "Label for the count of group topic members",
- "missing": false,
- "obsolete": false
- },
- "label_subscriber_count": {
- "translation": "Abonați:",
- "defaultMessage": "Subscribers:",
- "description": "Label for the count of channel subscribers",
- "missing": false,
- "obsolete": false
- },
- "member_count": {
- "translation": "{count, plural, one {{count, number} membru} few {{count, number} membri} other {{count, number} de membri}}",
- "defaultMessage": "{count, plural, one {{count, number} member} two {{count, number} members} three {{count, number} members} few {{count, number} members} many {{count, number} members} other {{count, number} members}}",
- "description": "Count of group topic members",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_image": {
- "translation": "imagine",
- "defaultMessage": "image",
- "description": "Tab title Image",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_pattern": {
- "translation": "model",
- "defaultMessage": "pattern",
- "description": "Tab title Pattern",
- "missing": false,
- "obsolete": false
- },
- "button_restore": {
- "translation": "Restabilește implicit",
- "defaultMessage": "Restore default",
- "description": "Reset setting to default",
- "missing": false,
- "obsolete": false
- },
- "wallpapers": {
- "translation": "Imagini de fundal",
- "defaultMessage": "Wallpapers",
- "description": "Link or title for working with wallpapers.",
- "missing": false,
- "obsolete": false
- },
- "label_blur_wallpaper": {
- "translation": "Estompare:",
- "defaultMessage": "Blur:",
- "description": "Label adjusting blur amount",
- "missing": false,
- "obsolete": false
- },
- "label_scan_id": {
- "translation": "Scanează ID-ul meu:",
- "defaultMessage": "Scan my ID:",
- "description": "Label [Scan my ID:]",
- "missing": false,
- "obsolete": false
- },
- "unknown_name": {
- "translation": "Necunoscut",
- "defaultMessage": "Unknown",
- "description": "Name to show when the name is missing",
- "missing": false,
- "obsolete": false
- },
- "save_action": {
- "translation": "Salvează",
- "defaultMessage": "Save",
- "description": "Label for Save button",
- "missing": false,
- "obsolete": false
- },
- "cannot_parse_vcard": {
- "translation": "Nu se poate procesa fișierul vCard.",
- "defaultMessage": "Cannot parse vCard file.",
- "description": "Error message when vCard file cannot be parsed",
- "missing": false,
- "obsolete": false
- },
- "chat_now": {
- "translation": "Conversație",
- "defaultMessage": "Chat",
- "description": "Label for message button in contact card",
- "missing": false,
- "obsolete": false
- },
- "find_user": {
- "translation": "Caută",
- "defaultMessage": "Find",
- "description": "Label for search button in contact card",
- "missing": false,
- "obsolete": false
- },
- "label_contact_card": {
- "translation": "Card de contact",
- "defaultMessage": "Contact Card",
- "description": "Label for contact card preview",
- "missing": false,
- "obsolete": false
- },
- "label_contacts": {
- "translation": "Contacte",
- "defaultMessage": "Contacts",
- "description": "Label for contact information",
- "missing": false,
- "obsolete": false
- },
- "add_members_prompt": {
- "translation": "adaugă membri",
- "defaultMessage": "add members",
- "description": "Placeholder in group members input field",
- "missing": false,
- "obsolete": false
- }
-}
diff --git a/src/i18n/ru.json b/src/i18n/ru.json
deleted file mode 100644
index 378f70ff..00000000
--- a/src/i18n/ru.json
+++ /dev/null
@@ -1,1966 +0,0 @@
-{
- "action_block_contact": {
- "translation": "Заблокировать контакт",
- "defaultMessage": "Block Contact",
- "description": "Flat button [Block Contact]",
- "missing": false,
- "obsolete": false
- },
- "action_cancel": {
- "translation": "отменить",
- "defaultMessage": "cancel",
- "description": "Call to action [cancel]",
- "missing": false,
- "obsolete": false
- },
- "action_clear_messages": {
- "translation": "Удалить сообщения",
- "defaultMessage": "Clear Messages",
- "description": "Flat button [Clear Messages] (soft-delete messages)",
- "missing": false,
- "obsolete": false
- },
- "action_delete_messages": {
- "translation": "Удалить сообщения",
- "defaultMessage": "Clear Messages for All",
- "description": "Flat button [Clear for All] (hard-delete all messages)",
- "missing": false,
- "obsolete": false
- },
- "action_leave_chat": {
- "translation": "Уйти из чата",
- "defaultMessage": "Leave Conversation",
- "description": "Flat button [Leave Conversation]",
- "missing": false,
- "obsolete": false
- },
- "action_report_chat": {
- "translation": "Сообщить о нарушении",
- "defaultMessage": "Report Conversation",
- "description": "Flat button [Report Group]",
- "missing": false,
- "obsolete": false
- },
- "archived_contacts": {
- "translation": "Чаты в архиве ({count})",
- "defaultMessage": "Archived contacts ({count})",
- "description": "Label for archived chats",
- "missing": false,
- "obsolete": false
- },
- "badge_danger": {
- "translation": "Подозрительный",
- "defaultMessage": "Untrustworthy",
- "description": "Suspicious or untrustworthy account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_owner": {
- "translation": "влад.",
- "defaultMessage": "owner",
- "description": "Badge for indicating the owner",
- "missing": false,
- "obsolete": false
- },
- "badge_staff": {
- "translation": "Администрация",
- "defaultMessage": "Staff-managed",
- "description": "Explanation of a staff-managed account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_verified": {
- "translation": "Верифицированный",
- "defaultMessage": "Verified/official",
- "description": "Explanation of a verified account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_you": {
- "translation": "вы",
- "defaultMessage": "you",
- "description": "Badge for indicating the current user",
- "missing": false,
- "obsolete": false
- },
- "block_contact_warning": {
- "translation": "Вы действительно заблокировать этот контакт?",
- "defaultMessage": "Are you sure you want to block this contact?",
- "description": "Alert dialog warning when blocking a contact.",
- "missing": false,
- "obsolete": false
- },
- "blocked_contacts_link": {
- "translation": "Заблокированные ({count})",
- "defaultMessage": "Blocked contacts ({count})",
- "description": "Blocked contacts link",
- "missing": false,
- "obsolete": false
- },
- "button_add_members": {
- "translation": "Добавить",
- "defaultMessage": "Add members",
- "description": "Flat button [Add members] (to topic)",
- "missing": false,
- "obsolete": false
- },
- "button_cancel": {
- "translation": "Отменить",
- "defaultMessage": "Cancel",
- "description": "Button [Cancel]",
- "missing": false,
- "obsolete": false
- },
- "button_confirm": {
- "translation": "Подтвердить",
- "defaultMessage": "Confirm",
- "description": "Button [Confirm]",
- "missing": false,
- "obsolete": false
- },
- "button_create": {
- "translation": "Создать",
- "defaultMessage": "Create",
- "description": "Button [Create]",
- "missing": false,
- "obsolete": false
- },
- "button_delete_account": {
- "translation": "Удалить аккаунт",
- "defaultMessage": "Delete account",
- "description": "Button [Delete account]",
- "missing": false,
- "obsolete": false
- },
- "button_edit": {
- "translation": "Изменить",
- "defaultMessage": "Edit",
- "description": "Call to action [Edit]",
- "missing": false,
- "obsolete": false
- },
- "button_logout": {
- "translation": "Выйти",
- "defaultMessage": "Logout",
- "description": "Button [Logout]",
- "missing": false,
- "obsolete": false
- },
- "button_ok": {
- "translation": "OK",
- "defaultMessage": "OK",
- "description": "Button [OK]",
- "missing": false,
- "obsolete": false
- },
- "button_reset": {
- "translation": "Изменить",
- "defaultMessage": "Reset",
- "description": "Button [Reset]",
- "missing": false,
- "obsolete": false
- },
- "button_send_request": {
- "translation": "Отправить",
- "defaultMessage": "Send request",
- "description": "Button [Send request]",
- "missing": false,
- "obsolete": false
- },
- "button_sign_in": {
- "translation": "Войти",
- "defaultMessage": "Sign in",
- "description": "Button [Sign In]",
- "missing": false,
- "obsolete": false
- },
- "button_sign_up": {
- "translation": "Создать аккаунт",
- "defaultMessage": "Sign up",
- "description": "Create account button [Sign Up]",
- "missing": false,
- "obsolete": false
- },
- "button_subscribe": {
- "translation": "Подписаться",
- "defaultMessage": "Subscribe",
- "description": "Button [Subscribe]",
- "missing": false,
- "obsolete": false
- },
- "button_update": {
- "translation": "Применить",
- "defaultMessage": "Update",
- "description": "Button [Update]",
- "missing": false,
- "obsolete": false
- },
- "cannot_initiate_file_upload": {
- "translation": "Ошибка загрузки файла.",
- "defaultMessage": "Cannot initiate file upload.",
- "description": "Generic error messagewhen attachment fails",
- "missing": false,
- "obsolete": false
- },
- "channel": {
- "translation": "канал",
- "defaultMessage": "channel",
- "description": "Subtitle shown for channels in MessagesView instead of last seen",
- "missing": false,
- "obsolete": false
- },
- "channel_prompt": {
- "translation": "Создать канал",
- "defaultMessage": "This is a channel",
- "description": "Checkbox label when creating a channel",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation": {
- "translation": "Вас пригласили начать новый чат. Как вы хотите поступить?",
- "defaultMessage": "You are invited to start a new chat. What would you like to do?",
- "description": "New chat invitation message: [Accept] [Ignore] [Block].",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_accept": {
- "translation": "Принять",
- "defaultMessage": "Accept",
- "description": "Action [Accept] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_block": {
- "translation": "Заблокировать",
- "defaultMessage": "Block",
- "description": "Action [Block] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_ignore": {
- "translation": "Игнорировать",
- "defaultMessage": "Ignore",
- "description": "Action [Ignore] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "clear_messages_warning": {
- "translation": "Вы действительно хотите удалить все сообщения в чате? Их будет невозможно восстановить.",
- "defaultMessage": "Are you sure you want to clear all messages? It cannot be undone.",
- "description": "Alert dialog warning when deleting all messages.",
- "missing": false,
- "obsolete": false
- },
- "code_doesnot_match": {
- "translation": "Код не совпадает",
- "defaultMessage": "Code does not match",
- "description": "Error message when the credential validation code is incorrect.",
- "missing": false,
- "obsolete": false
- },
- "contacts_not_found": {
- "translation": "Чатов нет\n¯∖_(ツ)_/¯",
- "defaultMessage": "You have no chats\\n¯∖_(ツ)_/¯",
- "description": "HTML message shown in ContactList when no contacts are found",
- "missing": false,
- "obsolete": false
- },
- "contacts_not_found_short": {
- "translation": "Нет контактов для запроса ''{query}''",
- "defaultMessage": "No contacts match ''{query}''",
- "description": "Shown in ContactsView when search returned no results",
- "missing": false,
- "obsolete": false
- },
- "credential_email_prompt": {
- "translation": "Регистрационный емейл",
- "defaultMessage": "Your registration email",
- "description": "Placeholder for entering email",
- "missing": false,
- "obsolete": false
- },
- "delete_account": {
- "translation": "Удалить аккаунт",
- "defaultMessage": "Delete account",
- "description": "Title for delete account warning",
- "missing": false,
- "obsolete": false
- },
- "delete_account_warning": {
- "translation": "Вы уверены, что ходите удалить свой аккаунт? Его невозможно будет восстановить.",
- "defaultMessage": "Are you sure you want to delete your account? It cannot be undone.",
- "description": "Warning message when deleting an account",
- "missing": false,
- "obsolete": false
- },
- "delete_messages_warning": {
- "translation": "Вы действительно хотите удалить все сообщения?",
- "defaultMessage": "Are you sure you want to delete all messages for everyone? It cannot be undone.",
- "description": "Alert dialog warning when hard-deleting all messages.",
- "missing": false,
- "obsolete": false
- },
- "download_action": {
- "translation": "скачать",
- "defaultMessage": "download",
- "description": "Call to action [download]",
- "missing": false,
- "obsolete": false
- },
- "drafty_attachment": {
- "translation": "Аттачмент",
- "defaultMessage": "Attachment",
- "description": "Comment for attachment in Drafty",
- "missing": false,
- "obsolete": false
- },
- "drafty_form": {
- "translation": "Форма:",
- "defaultMessage": "Form:",
- "description": "Comment for form in Drafty",
- "missing": false,
- "obsolete": false
- },
- "drafty_image": {
- "translation": "Картинка",
- "defaultMessage": "Picture",
- "description": "Comment for embedded images in Drafty",
- "missing": false,
- "obsolete": false
- },
- "email_dative": {
- "translation": "емейлу",
- "defaultMessage": "email",
- "description": "Dative case of 'email', i.e. 'email' in 'by email'",
- "missing": false,
- "obsolete": false
- },
- "email_prompt": {
- "translation": "Email, напр. ivan@example.com",
- "defaultMessage": "Email, e.g. jdoe@example.com",
- "description": "Input placeholder for email entry",
- "missing": false,
- "obsolete": false
- },
- "enable_peers_messaging": {
- "translation": "Разблокировать.",
- "defaultMessage": "Enable",
- "description": "Call to action to enable peer's messaging",
- "missing": false,
- "obsolete": false
- },
- "enter_confirmation_code_prompt": {
- "translation": "Код подтверждения, полученный по {method}:",
- "defaultMessage": "Confirmation code",
- "description": "Request to enter confirmation code",
- "missing": false,
- "obsolete": false
- },
- "error_invalid_id": {
- "translation": "Неверный ID",
- "defaultMessage": "Invalid ID",
- "description": "Error message",
- "missing": false,
- "obsolete": false
- },
- "file_attachment_too_large": {
- "translation": "Размер файла {size} превышает {limit} лимит.",
- "defaultMessage": "The file size {size} exceeds the {limit} limit.",
- "description": "Error message when attachment is too large",
- "missing": false,
- "obsolete": false
- },
- "forgot_password_link": {
- "translation": "Напомнить пароль",
- "defaultMessage": "Forgot password?",
- "description": "Link to Reset password form",
- "missing": false,
- "obsolete": false
- },
- "full_name_prompt": {
- "translation": "Полное имя, напр. Иван Петров",
- "defaultMessage": "Full name, e.g. John Doe",
- "description": "Input placeholder for person's full name",
- "missing": false,
- "obsolete": false
- },
- "granted_permissions": {
- "translation": "Получены",
- "defaultMessage": "Granted",
- "description": "Title for permissions",
- "missing": false,
- "obsolete": false
- },
- "group_has_no_members": {
- "translation": "Нет участников",
- "defaultMessage": "No members",
- "description": "Shown in place of group members",
- "missing": false,
- "obsolete": false
- },
- "group_user_id_prompt": {
- "translation": "ID чата или пользователя",
- "defaultMessage": "Group or User ID",
- "description": "Prompt for entering user or group ID",
- "missing": false,
- "obsolete": false
- },
- "image_caption_prompt": {
- "translation": "Подпись к фото",
- "defaultMessage": "Image caption",
- "description": "Prompt in SendMessage for attached image",
- "missing": false,
- "obsolete": false
- },
- "invalid_content": {
- "translation": "сообщение не читается",
- "defaultMessage": "invalid content",
- "description": "Shown when the message is unreadable",
- "missing": false,
- "obsolete": false
- },
- "invalid_security_token": {
- "translation": "Токен некорректен",
- "defaultMessage": "Invalid security token",
- "description": "Error message when resetting password.",
- "missing": false,
- "obsolete": false
- },
- "label_client": {
- "translation": "Клиент:",
- "defaultMessage": "Client:",
- "description": "Label for a client version",
- "missing": false,
- "obsolete": false
- },
- "label_content_type": {
- "translation": "Тип:",
- "defaultMessage": "Content type:",
- "description": "Label for file content type (mime)",
- "missing": false,
- "obsolete": false
- },
- "label_default_access_mode": {
- "translation": "Доступ по умолчанию:",
- "defaultMessage": "Default access mode:",
- "description": "Label for default access mode",
- "missing": false,
- "obsolete": false
- },
- "label_file_name": {
- "translation": "Имя файла:",
- "defaultMessage": "File name:",
- "description": "Label for a file name",
- "missing": false,
- "obsolete": false
- },
- "label_group_members": {
- "translation": "Участники чата:",
- "defaultMessage": "Group members:",
- "description": "Section title or label",
- "missing": false,
- "obsolete": false
- },
- "label_incognito_mode": {
- "translation": "Режим инкогнито:",
- "defaultMessage": "Incognito mode:",
- "description": "Label for incognito mode toggle",
- "missing": false,
- "obsolete": false
- },
- "label_message_sound": {
- "translation": "Звук нового сообщения:",
- "defaultMessage": "Message sound:",
- "description": "Label for message sounds toggle",
- "missing": false,
- "obsolete": false
- },
- "label_muting_topic": {
- "translation": "Без уведомлений",
- "defaultMessage": "Muted:",
- "description": "Label for Muting/unmuting the topic",
- "missing": false,
- "obsolete": false
- },
- "label_other_user": {
- "translation": "Второй",
- "defaultMessage": "Other",
- "description": "Label for the other user when the user is unnamed",
- "missing": false,
- "obsolete": false
- },
- "label_password": {
- "translation": "Пароль",
- "defaultMessage": "Password",
- "description": "Label for password editing",
- "missing": false,
- "obsolete": false
- },
- "label_permissions": {
- "translation": "Права доступа:",
- "defaultMessage": "Permissions:",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "label_private": {
- "translation": "Комментарий",
- "defaultMessage": "Private comment",
- "description": "Label for editing 'private'",
- "missing": false,
- "obsolete": false
- },
- "label_push_notifications": {
- "translation": "Уведомления:",
- "defaultMessage": "Notification alerts:",
- "description": "Label for push notifications switch",
- "missing": false,
- "obsolete": false
- },
- "label_push_notifications_disabled": {
- "translation": "Уведомления (требуют HTTPS):",
- "defaultMessage": "Notification alerts (requires HTTPS):",
- "description": "Label for push notifications switch",
- "missing": false,
- "obsolete": false
- },
- "label_reset_password": {
- "translation": "Отправить емейл для смены пароля:",
- "defaultMessage": "Send a password reset email",
- "description": "Label for password reset field",
- "missing": false,
- "obsolete": false
- },
- "label_sdk": {
- "translation": "SDK:",
- "defaultMessage": "SDK:",
- "missing": false,
- "obsolete": false
- },
- "label_server": {
- "translation": "Сервер:",
- "defaultMessage": "Server:",
- "description": "Label for a server version",
- "missing": false,
- "obsolete": false
- },
- "label_server_address": {
- "translation": "Адрес сервера:",
- "defaultMessage": "Server address:",
- "missing": false,
- "obsolete": false
- },
- "label_server_to_use": {
- "translation": "Использовать сервер:",
- "defaultMessage": "Server to use:",
- "description": "Label for server selector in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "label_size": {
- "translation": "Размер:",
- "defaultMessage": "Size:",
- "description": "Label for file size",
- "missing": false,
- "obsolete": false
- },
- "label_topic_name": {
- "translation": "Название",
- "defaultMessage": "Name",
- "description": "Label for editing topic name",
- "missing": false,
- "obsolete": false
- },
- "label_user_contacts": {
- "translation": "Конакты",
- "defaultMessage": "Contacts",
- "description": "Label for user contacts",
- "missing": false,
- "obsolete": false
- },
- "label_user_id": {
- "translation": "ID:",
- "defaultMessage": "ID:",
- "description": "Label for user address (ID)",
- "missing": false,
- "obsolete": false
- },
- "label_wire_transport": {
- "translation": "Соединение:",
- "defaultMessage": "Wire transport:",
- "description": "Label for wire transport selection in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "label_you": {
- "translation": "Вы:",
- "defaultMessage": "You:",
- "description": "Label for the current user",
- "missing": false,
- "obsolete": false
- },
- "label_your_name": {
- "translation": "Ваше имя",
- "defaultMessage": "Your name",
- "description": "Label for full name editing",
- "missing": false,
- "obsolete": false
- },
- "label_your_permissions": {
- "translation": "Ваши права доступа:",
- "defaultMessage": "Your permissions:",
- "description": "Label for current user permissions",
- "missing": false,
- "obsolete": false
- },
- "last_seen_timestamp": {
- "translation": "Был активен",
- "defaultMessage": "Last seen: {timestamp}",
- "description": "Label for the timestamp of when the user or topic was last online",
- "missing": false,
- "obsolete": false
- },
- "leave_chat_warning": {
- "translation": "Вы действительно хотите покинуть этот чат?",
- "defaultMessage": "Are you sure you want to leave this conversation?",
- "description": "Alert dialog warning when unsubscribing from a chat.",
- "missing": false,
- "obsolete": false
- },
- "link_contact_us": {
- "translation": "Связаться с нами",
- "defaultMessage": "Contact Us",
- "description": "Ancor text for contacting us by email",
- "missing": false,
- "obsolete": false
- },
- "link_privacy_policy": {
- "translation": "Политика конфиденциальности",
- "defaultMessage": "Privacy Policy",
- "description": "Ancor text for privacy policy link",
- "missing": false,
- "obsolete": false
- },
- "link_terms_of_service": {
- "translation": "Условия сервиса",
- "defaultMessage": "Terms of Service",
- "description": "Ancor text for terms of service link",
- "missing": false,
- "obsolete": false
- },
- "login_prompt": {
- "translation": "Логин",
- "defaultMessage": "Login",
- "description": "Placeholer for username/login",
- "missing": false,
- "obsolete": false
- },
- "menu_item_archive_topic": {
- "translation": "В архив",
- "defaultMessage": "Archive",
- "description": "Move topic from the list of active chats to archive",
- "missing": false,
- "obsolete": false
- },
- "menu_item_block": {
- "translation": "Заблокировать",
- "defaultMessage": "Block",
- "description": "Block topic or user",
- "missing": false,
- "obsolete": false
- },
- "menu_item_clear_messages": {
- "translation": "Удалить сообщения",
- "defaultMessage": "Clear messages",
- "description": "Delete all messages",
- "missing": false,
- "obsolete": false
- },
- "menu_item_clear_messages_for_all": {
- "translation": "Удалить для всех",
- "defaultMessage": "Clear for All",
- "description": "Delete all message(s) for all members",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete": {
- "translation": "Удалить",
- "defaultMessage": "Delete",
- "description": "Delete selected messages",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete_for_all": {
- "translation": "Удалить для всех",
- "defaultMessage": "Delete for All",
- "description": "Delete selected message(s) for all members",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete_topic": {
- "translation": "Удалить чат",
- "defaultMessage": "Delete",
- "description": "Delete entire topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_edit_permissions": {
- "translation": "Права доступа",
- "defaultMessage": "Edit permissions",
- "description": "Menu item [Edit permissions]",
- "missing": false,
- "obsolete": false
- },
- "menu_item_info": {
- "translation": "Информация",
- "defaultMessage": "Info",
- "description": "Show extended topic information",
- "missing": false,
- "obsolete": false
- },
- "menu_item_member_delete": {
- "translation": "Отписать",
- "defaultMessage": "Remove",
- "description": "Remove user from topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_mute": {
- "translation": "Не уведомлять",
- "defaultMessage": "Mute",
- "description": "Turn off notifications",
- "missing": false,
- "obsolete": false
- },
- "menu_item_restore_topic": {
- "translation": "Разархивировать",
- "defaultMessage": "Restore",
- "description": "Restore topic from archive",
- "missing": false,
- "obsolete": false
- },
- "menu_item_send_retry": {
- "translation": "Отправить заново",
- "defaultMessage": "Retry",
- "description": "Retry sending message",
- "missing": false,
- "obsolete": false
- },
- "menu_item_unblock": {
- "translation": "Разблокировать",
- "defaultMessage": "Unblock",
- "description": "Unblock topic or user",
- "missing": false,
- "obsolete": false
- },
- "menu_item_unmute": {
- "translation": "Уведомлять",
- "defaultMessage": "Unmute",
- "description": "Turn notifications on",
- "missing": false,
- "obsolete": false
- },
- "message_sending": {
- "translation": "в пути...",
- "defaultMessage": "sending...",
- "description": "Message being sent, in place of time stamp",
- "missing": false,
- "obsolete": false
- },
- "message_sending_failed": {
- "translation": "ошибка",
- "defaultMessage": "failed",
- "description": "Failed to send message, in place of time stamp",
- "missing": false,
- "obsolete": false
- },
- "messages_not_readable": {
- "translation": "нет доступа к сообщениям",
- "defaultMessage": "no access to messages",
- "description": "Message shown in topic without the read access",
- "missing": false,
- "obsolete": false
- },
- "messaging_disabled_prompt": {
- "translation": "Отправка недоступна",
- "defaultMessage": "Messaging disabled",
- "description": "Prompt in SendMessage in read-only topic",
- "missing": false,
- "obsolete": false
- },
- "more_online_members": {
- "translation": "+еще {overflow}",
- "defaultMessage": "+{overflow} more",
- "description": "Shown in MessagesView title bar when the number of online subscribers exceeds MAX_ONLINE_IN_TOPIC",
- "missing": false,
- "obsolete": false
- },
- "new_message_prompt": {
- "translation": "Новое сообщение",
- "defaultMessage": "New message",
- "description": "Prompt in send message field",
- "missing": false,
- "obsolete": false
- },
- "new_password_placeholder": {
- "translation": "Введите новый пароль",
- "defaultMessage": "Enter new password",
- "description": "Placeholder for entering new password",
- "missing": false,
- "obsolete": false
- },
- "no_connection": {
- "translation": "Нет связи",
- "defaultMessage": "No connection",
- "description": "Warning that the user is offline.",
- "missing": false,
- "obsolete": false
- },
- "no_contacts": {
- "translation": "Ничего нет :-(",
- "defaultMessage": "You have no contacts :-(",
- "description": "Shown in ContactsView when the user has no contacts",
- "missing": false,
- "obsolete": false
- },
- "numeric_confirmation_code_prompt": {
- "translation": "Только цифры",
- "defaultMessage": "Numbers only",
- "description": "Prompt for numeric conformation code",
- "missing": false,
- "obsolete": false
- },
- "online_now": {
- "translation": "онлайн",
- "defaultMessage": "online now",
- "description": "Indicator that the user or topic is currently online",
- "missing": false,
- "obsolete": false
- },
- "password_prompt": {
- "translation": "Пароль",
- "defaultMessage": "Password",
- "description": "Placeholder/prompt for entering password",
- "missing": false,
- "obsolete": false
- },
- "password_unchanged_prompt": {
- "translation": "Не изменен",
- "defaultMessage": "Unchanged",
- "description": "Message in editor while password is unchanged",
- "missing": false,
- "obsolete": false
- },
- "peers_messaging_disabled": {
- "translation": "Чат заблокирован у корреспондента.",
- "defaultMessage": "Peer's messaging is disabled.",
- "description": "Shown when the p2p peer's messaging is disabled",
- "missing": false,
- "obsolete": false
- },
- "permission_admin": {
- "translation": "Подтверждать ({val})",
- "defaultMessage": "Approve ({val})",
- "description": "Name of A permission",
- "missing": false,
- "obsolete": false
- },
- "permission_delete": {
- "translation": "Удалять ({val})",
- "defaultMessage": "Delete ({val})",
- "description": "Name of D permission",
- "missing": false,
- "obsolete": false
- },
- "permission_join": {
- "translation": "Подписываться ({val})",
- "defaultMessage": "Join ({val})",
- "description": "Name of J permission",
- "missing": false,
- "obsolete": false
- },
- "permission_owner": {
- "translation": "Владелец ({val})",
- "defaultMessage": "Owner ({val})",
- "description": "Name of O permission",
- "missing": false,
- "obsolete": false
- },
- "permission_pres": {
- "translation": "Уведомлять ({val})",
- "defaultMessage": "Get notified ({val})",
- "description": "Name of P permission",
- "missing": false,
- "obsolete": false
- },
- "permission_read": {
- "translation": "Читать ({val})",
- "defaultMessage": "Read ({val})",
- "description": "Name of R permission",
- "missing": false,
- "obsolete": false
- },
- "permission_share": {
- "translation": "Приглашать ({val})",
- "defaultMessage": "Share ({val})",
- "description": "Name of S permission",
- "missing": false,
- "obsolete": false
- },
- "permission_write": {
- "translation": "Писать ({val})",
- "defaultMessage": "Write ({val})",
- "description": "Name of W permission",
- "missing": false,
- "obsolete": false
- },
- "phone_dative": {
- "translation": "телефону",
- "defaultMessage": "phone",
- "description": "Dative case of 'phone', i.e. 'phone' in 'by phone'",
- "missing": false,
- "obsolete": false
- },
- "private_editing_placeholder": {
- "translation": "Виден только вам",
- "defaultMessage": "Visible to you only",
- "description": "Placeholder for editing 'private'",
- "missing": false,
- "obsolete": false
- },
- "push_init_failed": {
- "translation": "Ошибка инициализации пуш уведомлений",
- "defaultMessage": "Failed to initialize push notifications",
- "description": "Error message when push notifications have failed to initialize.",
- "missing": false,
- "obsolete": false
- },
- "reconnect_countdown": {
- "translation": "Нет связи. Подключение через {seconds}…",
- "defaultMessage": "Disconnected. Reconnecting in {seconds}…",
- "description": "Message shown when an app update is available.",
- "missing": false,
- "obsolete": false
- },
- "reconnect_now": {
- "translation": "Подключить сейчас.",
- "defaultMessage": "Try now",
- "description": "Prompt for reconnecting now",
- "missing": false,
- "obsolete": false
- },
- "reload_update": {
- "translation": "Обновить",
- "defaultMessage": "Reload",
- "description": "Call to action to reload application when update is available.",
- "missing": false,
- "obsolete": false
- },
- "report_chat_warning": {
- "translation": "Вы действительно хотите сообщить о нарушении и заблокировать этот чат?",
- "defaultMessage": "Are you sure you want to block and report this conversation?",
- "description": "Alert dialog warning when reporting a conversation for abuse",
- "missing": false,
- "obsolete": false
- },
- "requested_permissions": {
- "translation": "Требуются",
- "defaultMessage": "Requested",
- "description": "Title for permissions",
- "missing": false,
- "obsolete": false
- },
- "save_attachment": {
- "translation": "сохранить",
- "defaultMessage": "save",
- "description": "Call to save an attachment",
- "missing": false,
- "obsolete": false
- },
- "search_for_contacts": {
- "translation": "Поиск контактов",
- "defaultMessage": "Use search to find contacts",
- "description": "Text shown in contacts view when user entered no search query.",
- "missing": false,
- "obsolete": false
- },
- "search_no_results": {
- "translation": "Ничего не найдено",
- "defaultMessage": "Search returned no results",
- "description": "Text shown in contacts view when query returned no results.",
- "missing": false,
- "obsolete": false
- },
- "search_placeholder": {
- "translation": "Список, напр. email:alice@example.com, tel:+17025550003...",
- "defaultMessage": "List like alice@example.com, +17025550003...",
- "description": "Placeholder in contacts search field",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_notifications": {
- "translation": "Уведомления",
- "defaultMessage": "Notifications",
- "description": "Sidepanel title for AccNotificationsView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_support": {
- "translation": "Поддержка",
- "defaultMessage": "Support",
- "description": "Sidepanel title for AccSupportView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_account_settings": {
- "translation": "Настройки аккаунта",
- "defaultMessage": "Account Settings",
- "description": "Sidepanel title for AccountSettingsView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_archive": {
- "translation": "Архив чатов",
- "defaultMessage": "Archived Chats",
- "description": "Sidepanel title for ContactsView-Archive.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_blocked": {
- "translation": "Заблокированные чаты",
- "defaultMessage": "Blocked Chats",
- "description": "Sidepanel title for ContactsView-Blocked.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_cred": {
- "translation": "Подтвердить",
- "defaultMessage": "Confirm Credentials",
- "description": "Sidepanel title for ValidationView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_login": {
- "translation": "Авторизация",
- "defaultMessage": "Sign In",
- "description": "Sidepanel title for LoginView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_newtpk": {
- "translation": "Новый чат",
- "defaultMessage": "Start New Chat",
- "description": "Sidepanel title for NewTopicView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_register": {
- "translation": "Зарегистрироваться",
- "defaultMessage": "Create Account",
- "description": "Sidepanel title for CreateAccountView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_reset": {
- "translation": "Сменить пароль",
- "defaultMessage": "Reset Password",
- "description": "Sidepanel title for PasswordResetView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_settings": {
- "translation": "Настройки",
- "defaultMessage": "Settings",
- "description": "Sidepanel title for SettingsView.",
- "missing": false,
- "obsolete": false
- },
- "stay_logged_in": {
- "translation": "Запомнить",
- "defaultMessage": "Stay logged in",
- "description": "Label for a checkbox",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_find_user": {
- "translation": "найти",
- "defaultMessage": "find",
- "description": "Tab title Find",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_group_by_id": {
- "translation": "по id",
- "defaultMessage": "by id",
- "description": "Tab title Find topic by ID",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_new_group": {
- "translation": "создать",
- "defaultMessage": "new group",
- "description": "Tab title New Group",
- "missing": false,
- "obsolete": false
- },
- "tags_editor_no_tags": {
- "translation": "Добавьте теги",
- "defaultMessage": "Add some tags",
- "description": "Tag editor prompt when no tags are found.",
- "missing": false,
- "obsolete": false
- },
- "tags_not_found": {
- "translation": "Тегов нет. Добавьте.",
- "defaultMessage": "No tags defined. Add some.",
- "description": "",
- "missing": false,
- "obsolete": false
- },
- "title_all_contacts": {
- "translation": "Все контакты",
- "defaultMessage": "All Contacts",
- "description": "Section title [All Contacts]",
- "missing": false,
- "obsolete": false
- },
- "title_group_members": {
- "translation": "Участники",
- "defaultMessage": "Group Members",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "title_manage_tags": {
- "translation": "Редактировать",
- "defaultMessage": "Manage",
- "description": "Section title for the list of tags",
- "missing": false,
- "obsolete": false
- },
- "title_not_found": {
- "translation": "Не найден",
- "defaultMessage": "Not found",
- "description": "Title shown when topic is not found",
- "missing": false,
- "obsolete": false
- },
- "title_permissions": {
- "translation": "Права доступа",
- "defaultMessage": "Permissions",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "title_tag_manager": {
- "translation": "Теги для поиска",
- "defaultMessage": "Tags (search & discovery)",
- "description": "Section title for TagManager",
- "missing": false,
- "obsolete": false
- },
- "topic_block_warning": {
- "translation": "Вы действительно хотите заблокировать этот чат?",
- "defaultMessage": "Are you sure you want to block this conversation?",
- "description": "Alert warning when blocking a topic.",
- "missing": false,
- "obsolete": false
- },
- "topic_delete_warning": {
- "translation": "Вы действительно хотите удалить этот чат?",
- "defaultMessage": "Are you sure you want to delete this conversation? It cannot be undone.",
- "description": "Alert warning when deleting entire topic",
- "missing": false,
- "obsolete": false
- },
- "topic_name_editing_placeholder": {
- "translation": "Название чата",
- "defaultMessage": "Freeform name of the group",
- "description": "Prompt for entering topic name",
- "missing": false,
- "obsolete": false
- },
- "unnamed_topic": {
- "translation": "Без названия",
- "defaultMessage": "Unnamed",
- "description": "Title shown when the topic has no name",
- "missing": false,
- "obsolete": false
- },
- "update_available": {
- "translation": "Есть новая версия приложения.",
- "defaultMessage": "Update available.",
- "description": "Message shown when an app update is available.",
- "missing": false,
- "obsolete": false
- },
- "upload_finishing": {
- "translation": "завершение...",
- "defaultMessage": "finishing...",
- "description": "Notification that upload is finishing",
- "missing": false,
- "obsolete": false
- },
- "user_not_found": {
- "translation": "Не найден",
- "defaultMessage": "Not found",
- "description": "In place of a user's full name when the user is not found.",
- "missing": false,
- "obsolete": false
- },
- "description_editing_placeholder": {
- "translation": "Описание (не обязательно)",
- "defaultMessage": "Description (optional)",
- "description": "Placeholder for editing topic description",
- "missing": false,
- "obsolete": false
- },
- "label_description": {
- "translation": "Описание",
- "defaultMessage": "Description",
- "description": "Label for description of user or topic",
- "missing": false,
- "obsolete": false
- },
- "button_security": {
- "translation": "Безопасность",
- "defaultMessage": "Security",
- "description": "Navigaton button for security panel.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_crop": {
- "translation": "Обрезать картинку",
- "defaultMessage": "Drag to Adjust",
- "description": "Title for AvatarCropView.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_general": {
- "translation": "Общие настройки",
- "defaultMessage": "General",
- "description": "Title for TopicCommon.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_members": {
- "translation": "Участники",
- "defaultMessage": "Members",
- "description": "Title for managing group members view.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_security": {
- "translation": "Безопасность",
- "defaultMessage": "Security",
- "description": "Title for TopicSecirity and AccSecurity.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_info": {
- "translation": "Подробности",
- "defaultMessage": "Info",
- "description": "Title for InfoView",
- "missing": false,
- "obsolete": false
- },
- "permissions_anonymous": {
- "translation": "Анонимный",
- "defaultMessage": "Anonymous",
- "description": "Title for editing default anonymous permissions",
- "missing": false,
- "obsolete": false
- },
- "permissions_authenticated": {
- "translation": "Авторизованный",
- "defaultMessage": "Authenticated",
- "description": "Title for editing default authenticated permissions",
- "missing": false,
- "obsolete": false
- },
- "topic_delete": {
- "translation": "Удалить чат",
- "defaultMessage": "Delete Conversation",
- "description": "Alert title when deleting the topic.",
- "missing": false,
- "obsolete": false
- },
- "permissions_user": {
- "translation": "Права доступа",
- "defaultMessage": "User's Permissions",
- "description": "Title for editing user's permissions",
- "missing": false,
- "obsolete": false
- },
- "password_reset_email_sent": {
- "translation": "Сообщение было отправлено на указанный email.",
- "defaultMessage": "An email with security code has been sent.",
- "description": "Notification that the email with password reset instructions has been sent",
- "missing": false,
- "obsolete": false
- },
- "label_unarchive_topic": {
- "translation": "Архивирован:",
- "defaultMessage": "Archived:",
- "description": "Label for unarchiving the topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_reply": {
- "translation": "Ответить",
- "defaultMessage": "Reply",
- "description": "Reply to message",
- "missing": false,
- "obsolete": false
- },
- "menu_item_forward": {
- "translation": "Переслать",
- "defaultMessage": "Forward",
- "description": "Forward message",
- "missing": false,
- "obsolete": false
- },
- "forward_to": {
- "translation": "Переслать",
- "defaultMessage": "Forward to",
- "missing": false,
- "obsolete": false
- },
- "forward_to_search_placeholder": {
- "translation": "Поиск контактов",
- "defaultMessage": "Search contacts",
- "description": "Contact search prompt when forwarding a message.",
- "missing": false,
- "obsolete": false
- },
- "label_new_password": {
- "translation": "Новый пароль",
- "defaultMessage": "New password",
- "description": "Label for entering a new password",
- "missing": false,
- "obsolete": false
- },
- "drafty_unknown": {
- "translation": "Не поддерживается",
- "defaultMessage": "Unsupported",
- "description": "Unsupported entity in drafty",
- "missing": false,
- "obsolete": false
- },
- "calls_incoming": {
- "translation": "Входящий звонок",
- "defaultMessage": "Incoming call",
- "description": "Incoming call label",
- "missing": false,
- "obsolete": false
- },
- "calls_outgoing": {
- "translation": "Исходящий звонок",
- "defaultMessage": "Outgoing call",
- "description": "Outgoing call label",
- "missing": false,
- "obsolete": false
- },
- "calls_you_label": {
- "translation": "Вы",
- "defaultMessage": "You",
- "description": "Shown over the local video screen",
- "missing": false,
- "obsolete": false
- },
- "menu_item_video_call": {
- "translation": "Видеозвонок",
- "defaultMessage": "Video call",
- "description": "Start video call",
- "missing": false,
- "obsolete": false
- },
- "already_in_call": {
- "translation": "Вы уже звоните кому-то!",
- "defaultMessage": "You already in an ongoing call!",
- "description": "Error message when the user tried to accept a new call without finishing pervious one",
- "missing": false,
- "obsolete": false
- },
- "call_cancelled": {
- "translation": "отменен",
- "defaultMessage": "cancelled",
- "description": "Label for cancelled outgoing call",
- "missing": false,
- "obsolete": false
- },
- "call_missed": {
- "translation": "пропущен",
- "defaultMessage": "missed",
- "description": "Label for missed incoming call",
- "missing": false,
- "obsolete": false
- },
- "call_declined": {
- "translation": "отклонен",
- "defaultMessage": "declined",
- "description": "Label for declined call",
- "missing": false,
- "obsolete": false
- },
- "call_disconnected": {
- "translation": "разъединен",
- "defaultMessage": "disconnected",
- "description": "Label for disconnected call",
- "missing": false,
- "obsolete": false
- },
- "label_use_secure_connection": {
- "translation": "Безопасное соединение",
- "defaultMessage": "Use secure connection",
- "description": "Label for WS/WSS connection type in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "cred_confirmed_successfully": {
- "translation": "Подтверждено успешно",
- "defaultMessage": "Confirmed successfully",
- "description": "Notification message that the credential was successfully validated.",
- "missing": false,
- "obsolete": false
- },
- "menu_item_edit": {
- "translation": "Изменить",
- "defaultMessage": "Edit",
- "description": "Edit message",
- "missing": false,
- "obsolete": false
- },
- "editing_message": {
- "translation": "Редактирование",
- "defaultMessage": "Editing",
- "description": "Title over message editing preview",
- "missing": false,
- "obsolete": false
- },
- "message_edited_marker": {
- "translation": ", изменено",
- "defaultMessage": ", edited",
- "description": "Marker indicating that the message was edited",
- "missing": false,
- "obsolete": false
- },
- "icon_title_add_image": {
- "translation": "Добавить изображение",
- "defaultMessage": "Add image",
- "description": "Icon tool tip for attaching an image",
- "missing": false,
- "obsolete": false
- },
- "icon_title_attach_file": {
- "translation": "Прикрепить файл",
- "defaultMessage": "Attach file",
- "description": "Icon tool tip for attaching a file",
- "missing": false,
- "obsolete": false
- },
- "icon_title_delete": {
- "translation": "Удалить запись",
- "defaultMessage": "Delete recording",
- "description": "Icon tool tip for deleting recorded audio",
- "missing": false,
- "obsolete": false
- },
- "icon_title_pause": {
- "translation": "Пауза воспроизведения",
- "defaultMessage": "Pause playback",
- "description": "Icon tool tip for pausing audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_play": {
- "translation": "Воспроизвести запись",
- "defaultMessage": "Play recording",
- "description": "Icon tool tip for starting audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_record_voice": {
- "translation": "Записать голосовое сообщение",
- "defaultMessage": "Record voice message",
- "description": "Icon tool tip for recording a voice message",
- "missing": false,
- "obsolete": false
- },
- "icon_title_resume": {
- "translation": "Продолжить",
- "defaultMessage": "Resume playback",
- "description": "Icon tool tip for resuming audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_send": {
- "translation": "Отправить сообщение",
- "defaultMessage": "Send message",
- "description": "Icon tool tip for sending a message",
- "missing": false,
- "obsolete": false
- },
- "drag_file": {
- "translation": "Перетащите файл сюда",
- "defaultMessage": "Drag file here",
- "description": "Prompt on the file drag-n-drop overlay banner",
- "missing": false,
- "obsolete": false
- },
- "drafty_video": {
- "translation": "Видеозапись",
- "defaultMessage": "Video recording",
- "description": "Comment for videos embedded in Drafty",
- "missing": false,
- "obsolete": false
- },
- "call_in_progress": {
- "translation": "в процессе",
- "defaultMessage": "in progress",
- "description": "Label for call in progress",
- "missing": false,
- "obsolete": false
- },
- "menu_item_audio_call": {
- "translation": "Позвонить",
- "defaultMessage": "Call",
- "description": "Start audio call",
- "missing": false,
- "obsolete": false
- },
- "unrecognized_video_format": {
- "translation": "Неизвестный формат видео файла",
- "defaultMessage": "Format of this video is not recognized",
- "description": "Error message when uploaded video is invalid",
- "missing": false,
- "obsolete": false
- },
- "loading_note": {
- "translation": "Загружается...",
- "defaultMessage": "Loading...",
- "description": "Message shown when component is loading",
- "missing": false,
- "obsolete": false
- },
- "password_i_have_code": {
- "translation": "У меня есть код",
- "defaultMessage": "I have code",
- "description": "Call to open field to enter password reset code",
- "missing": false,
- "obsolete": false
- },
- "label_reset_password_tel": {
- "translation": "Отправить SMS для сброса пароля",
- "defaultMessage": "Send SMS to reset password",
- "description": "Label for password reset field by phone",
- "missing": false,
- "obsolete": false
- },
- "mobile_phone_number": {
- "translation": "Номер мобильного телефона",
- "defaultMessage": "Mobile phone number",
- "description": "Prompt for entering a mobile phone number",
- "missing": false,
- "obsolete": false
- },
- "password_reset_sms_sent": {
- "translation": "SMS с защитным кодом отправлен.",
- "defaultMessage": "A text message with security code has been sent.",
- "description": "Notification that the SMS with password reset instructions has been sent",
- "missing": false,
- "obsolete": false
- },
- "mobile_number_required": {
- "translation": "Номер мобильного телефона",
- "defaultMessage": "Mobile phone number required",
- "description": "Error message",
- "missing": false,
- "obsolete": false
- },
- "current_email": {
- "translation": "Текущий e-mail",
- "defaultMessage": "Current email",
- "description": "Label for old email",
- "missing": false,
- "obsolete": false
- },
- "current_phone": {
- "translation": "Текущий номер телефона",
- "defaultMessage": "Current phone number",
- "description": "Label for old phone",
- "missing": false,
- "obsolete": false
- },
- "new_email": {
- "translation": "Новый e-mail",
- "defaultMessage": "New email",
- "description": "Prompt for entering a new email",
- "missing": false,
- "obsolete": false
- },
- "new_phone_number": {
- "translation": "Новый номер телефона",
- "defaultMessage": "New phone number",
- "description": "Prompt for entering a new telephone number",
- "missing": false,
- "obsolete": false
- },
- "change_email": {
- "translation": "Изменить e-mail",
- "defaultMessage": "Change email",
- "description": "Prompt to change current email",
- "missing": false,
- "obsolete": false
- },
- "change_phone": {
- "translation": "Изменить номер телефона",
- "defaultMessage": "Change phone number",
- "description": "Prompt to change current phone number",
- "missing": false,
- "obsolete": false
- },
- "call_busy": {
- "translation": "занято",
- "defaultMessage": "busy",
- "description": "Label for busy call line",
- "missing": false,
- "obsolete": false
- },
- "pin_message": {
- "translation": "Закрепить",
- "defaultMessage": "Pin",
- "description": "Menu item [Pin] for pinning message to the top.",
- "missing": false,
- "obsolete": false
- },
- "unpin_message": {
- "translation": "Открепить",
- "defaultMessage": "Unpin",
- "description": "Menu item [Unpin] for un-pinning the message.",
- "missing": false,
- "obsolete": false
- },
- "failed_to_init_audio": {
- "translation": "Не удалось инициализировать аудиозапись",
- "defaultMessage": "Failed to initialize audio recording",
- "description": "Error message when audio is not available",
- "missing": false,
- "obsolete": false
- },
- "password_reset_success": {
- "translation": "Пароль успешно изменен",
- "defaultMessage": "Password reset successfully",
- "description": "Notification message that the password was successfully reset.",
- "missing": false,
- "obsolete": false
- },
- "scan_qr_code": {
- "translation": "Отсканируйте QR код",
- "defaultMessage": "Scan QR Code",
- "description": "Title for scanning QR code",
- "missing": false,
- "obsolete": false
- },
- "self_topic_comment": {
- "translation": "Заметки, сообщения, ссылки, файлы, сохраненные для потомков",
- "defaultMessage": "Notes, messages, links, files saved for posterity",
- "description": "Comment for self topic for UI",
- "missing": false,
- "obsolete": false
- },
- "self_topic_name": {
- "translation": "Сохраненные сообщения",
- "defaultMessage": "Saved messages",
- "description": "Name of self topic for UI",
- "missing": false,
- "obsolete": false
- },
- "message_not_found": {
- "translation": "сообщение не найдено",
- "defaultMessage": "message not found",
- "description": "Pinned message is not found",
- "missing": false,
- "obsolete": false
- },
- "message_deleted": {
- "translation": "сообщение удалено",
- "defaultMessage": "message deleted",
- "description": "Pinned message is deleted",
- "missing": false,
- "obsolete": false
- },
- "alias_editing_placeholder": {
- "translation": "Псевдоним (необязательно)",
- "defaultMessage": "Alias (optional)",
- "description": "Placeholder for editing user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "label_alias": {
- "translation": "Псевдоним:",
- "defaultMessage": "Alias:",
- "description": "Label for user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "alias_already_taken": {
- "translation": "(уже занято)",
- "defaultMessage": "(already taken)",
- "description": "Error message for alias already taken",
- "missing": false,
- "obsolete": false
- },
- "alias_invalid": {
- "translation": "(некорректно)",
- "defaultMessage": "(invalid)",
- "description": "Error message for invalid alias",
- "missing": false,
- "obsolete": false
- },
- "label_alias_edit": {
- "translation": "Псевдоним",
- "defaultMessage": "Alias",
- "description": "Label for editing user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "select_country": {
- "translation": "Выберите страну",
- "defaultMessage": "Select country",
- "description": "Placeholder for the country selector",
- "missing": false,
- "obsolete": false
- },
- "text_copied": {
- "translation": "Скопировано в буфер обмена",
- "defaultMessage": "Copied to clipboard",
- "description": "Notification that text has been copied to clipboard",
- "missing": false,
- "obsolete": false
- },
- "color_schema_dark": {
- "translation": "Тёмная",
- "defaultMessage": "Dark",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "color_schema_light": {
- "translation": "Светлая",
- "defaultMessage": "Light",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "color_schema_system": {
- "translation": "Системная",
- "defaultMessage": "System default",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "label_color_schema": {
- "translation": "Тема:",
- "defaultMessage": "Theme:",
- "description": "Label for selecting color scheme (dark, light) in Settings",
- "missing": false,
- "obsolete": false
- },
- "label_text_size": {
- "translation": "Размер текста:",
- "defaultMessage": "Text size:",
- "description": "Label adjusting text size",
- "missing": false,
- "obsolete": false
- },
- "incognito_mode_description": {
- "translation": "Скрывать онлайн статус и индикаторы набора текста.",
- "defaultMessage": "Hide your online status and typing indicators.",
- "description": "Description for incognito mode toggle",
- "missing": false,
- "obsolete": false
- },
- "send_command": {
- "translation": "Отправлять на {key}",
- "defaultMessage": "Send on {key}",
- "description": "Config option to send message on hitting CTRL(or Cmd)-Enter",
- "missing": false,
- "obsolete": false
- },
- "send_command_explained": {
- "translation": "Нажмать Enter для новой строки",
- "defaultMessage": "Press Enter for new line",
- "description": "Explanation how to enter newline when [Send on CTRL-Enter] is enabled",
- "missing": false,
- "obsolete": false
- },
- "send_plain": {
- "translation": "Отправлять на Enter",
- "defaultMessage": "Send on Enter",
- "description": "Config option to send message on hitting Enter",
- "missing": false,
- "obsolete": false
- },
- "send_plain_explained": {
- "translation": "Нажимать Shift + Enter для новой строки",
- "defaultMessage": "Press Shift + Enter for new line",
- "description": "Explanation how to enter newline when [Send on Enter] is enabled",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_general": {
- "translation": "Основные",
- "defaultMessage": "General",
- "description": "Sidepanel title for AccGeneralView.",
- "missing": false,
- "obsolete": false
- },
- "label_keyboard": {
- "translation": "Клавиатура:",
- "defaultMessage": "Keyboard:",
- "description": "Label for send on enter settings",
- "missing": false,
- "obsolete": false
- },
- "pin_chat": {
- "translation": "Закрепить",
- "defaultMessage": "Pin",
- "description": "Menu item [Pin] for pinning chat to the top.",
- "missing": false,
- "obsolete": false
- },
- "unpin_chat": {
- "translation": "Открепить",
- "defaultMessage": "Unpin",
- "description": "Menu item [Unpin] for un-pinning the chat.",
- "missing": false,
- "obsolete": false
- },
- "subscriber_count": {
- "translation": "{count, plural, one {{count, number} подписчик} few {{count, number} подписчика} many {{count, number} подписчиков} other {{count, number} подписчиков}}",
- "defaultMessage": "{count, plural, one {{count, number} subscriber} two {{count, number} subscribers} three {{count, number} subscribers} few {{count, number} subscribers} many {{count, number} subscribers} other {{count, number} subscribers}}",
- "description": "Count of channel subscribers",
- "missing": false,
- "obsolete": false
- },
- "label_member_count": {
- "translation": "Участники:",
- "defaultMessage": "Members:",
- "description": "Label for the count of group topic members",
- "missing": false,
- "obsolete": false
- },
- "label_subscriber_count": {
- "translation": "Подписчики:",
- "defaultMessage": "Subscribers:",
- "description": "Label for the count of channel subscribers",
- "missing": false,
- "obsolete": false
- },
- "member_count": {
- "translation": "{count, plural, one {{count, number} участник} few {{count, number} участника} many {{count, number} участников} other {{count, number} участников}}",
- "defaultMessage": "{count, plural, one {{count, number} member} two {{count, number} members} three {{count, number} members} few {{count, number} members} many {{count, number} members} other {{count, number} members}}",
- "description": "Count of group topic members",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_image": {
- "translation": "картинка",
- "defaultMessage": "image",
- "description": "Tab title Image",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_pattern": {
- "translation": "узор",
- "defaultMessage": "pattern",
- "description": "Tab title Pattern",
- "missing": false,
- "obsolete": false
- },
- "button_restore": {
- "translation": "Восстановить по умолчанию",
- "defaultMessage": "Restore default",
- "description": "Reset setting to default",
- "missing": false,
- "obsolete": false
- },
- "wallpapers": {
- "translation": "Обои",
- "defaultMessage": "Wallpapers",
- "description": "Link or title for working with wallpapers.",
- "missing": false,
- "obsolete": false
- },
- "label_blur_wallpaper": {
- "translation": "Размытие:",
- "defaultMessage": "Blur:",
- "description": "Label adjusting blur amount",
- "missing": false,
- "obsolete": false
- },
- "label_scan_id": {
- "translation": "Отсканируйте мой ID:",
- "defaultMessage": "Scan my ID:",
- "description": "Label [Scan my ID:]",
- "missing": false,
- "obsolete": false
- },
- "unknown_name": {
- "translation": "Неизвестный",
- "defaultMessage": "Unknown",
- "description": "Name to show when the name is missing",
- "missing": false,
- "obsolete": false
- },
- "save_action": {
- "translation": "Сохранить",
- "defaultMessage": "Save",
- "description": "Label for Save button",
- "missing": false,
- "obsolete": false
- },
- "cannot_parse_vcard": {
- "translation": "Не удалось обработать файл vCard.",
- "defaultMessage": "Cannot parse vCard file.",
- "description": "Error message when vCard file cannot be parsed",
- "missing": false,
- "obsolete": false
- },
- "chat_now": {
- "translation": "Написать",
- "defaultMessage": "Chat",
- "description": "Label for message button in contact card",
- "missing": false,
- "obsolete": false
- },
- "find_user": {
- "translation": "Найти",
- "defaultMessage": "Find",
- "description": "Label for search button in contact card",
- "missing": false,
- "obsolete": false
- },
- "label_contact_card": {
- "translation": "Карточка контакта",
- "defaultMessage": "Contact Card",
- "description": "Label for contact card preview",
- "missing": false,
- "obsolete": false
- },
- "label_contacts": {
- "translation": "Контакты",
- "defaultMessage": "Contacts",
- "description": "Label for contact information",
- "missing": false,
- "obsolete": false
- },
- "add_members_prompt": {
- "translation": "добавить участников",
- "defaultMessage": "add members",
- "description": "Placeholder in group members input field",
- "missing": false,
- "obsolete": false
- }
-}
diff --git a/src/i18n/th.json b/src/i18n/th.json
deleted file mode 100644
index 7fb1fe58..00000000
--- a/src/i18n/th.json
+++ /dev/null
@@ -1,1966 +0,0 @@
-{
- "action_block_contact": {
- "translation": "บล็อครายชื่อ",
- "defaultMessage": "Block Contact",
- "description": "Flat button [Block Contact]",
- "missing": false,
- "obsolete": false
- },
- "action_cancel": {
- "translation": "ยกเลิก",
- "defaultMessage": "cancel",
- "description": "Call to action [cancel]",
- "missing": false,
- "obsolete": false
- },
- "action_clear_messages": {
- "translation": "ล้างข้อความ",
- "defaultMessage": "Clear Messages",
- "description": "Flat button [Clear Messages] (soft-delete messages)",
- "missing": false,
- "obsolete": false
- },
- "action_delete_messages": {
- "translation": "ล้างข้อความทั้งหมด",
- "defaultMessage": "Clear Messages for All",
- "description": "Flat button [Clear for All] (hard-delete all messages)",
- "missing": false,
- "obsolete": false
- },
- "action_leave_chat": {
- "translation": "ออกจากการสนทนา",
- "defaultMessage": "Leave Conversation",
- "description": "Flat button [Leave Conversation]",
- "missing": false,
- "obsolete": false
- },
- "action_report_chat": {
- "translation": "รายงานการสนทนา",
- "defaultMessage": "Report Conversation",
- "description": "Flat button [Report Group]",
- "missing": false,
- "obsolete": false
- },
- "archived_contacts": {
- "translation": "รายชื่อที่สำเร็จ ({count})",
- "defaultMessage": "Archived contacts ({count})",
- "description": "Label for archived chats",
- "missing": false,
- "obsolete": false
- },
- "badge_danger": {
- "translation": "ไม่น่าไว้วางใจ",
- "defaultMessage": "Untrustworthy",
- "description": "Suspicious or untrustworthy account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_owner": {
- "translation": "เจ้าของ",
- "defaultMessage": "owner",
- "description": "Badge for indicating the owner",
- "missing": false,
- "obsolete": false
- },
- "badge_staff": {
- "translation": "ผู้ดูแล",
- "defaultMessage": "Staff-managed",
- "description": "Explanation of a staff-managed account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_verified": {
- "translation": "ยืนยันแล้ว/ทางการ",
- "defaultMessage": "Verified/official",
- "description": "Explanation of a verified account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_you": {
- "translation": "คุณ",
- "defaultMessage": "you",
- "description": "Badge for indicating the current user",
- "missing": false,
- "obsolete": false
- },
- "block_contact_warning": {
- "translation": "คุณแน่ใจหรือไม่? คุณต้องการบล็อครายชื่อนี้",
- "defaultMessage": "Are you sure you want to block this contact?",
- "description": "Alert dialog warning when blocking a contact.",
- "missing": false,
- "obsolete": false
- },
- "blocked_contacts_link": {
- "translation": "รายชื่อที่ถูกบล็อก ({count})",
- "defaultMessage": "Blocked contacts ({count})",
- "description": "Blocked contacts link",
- "missing": false,
- "obsolete": false
- },
- "button_add_members": {
- "translation": "เพิ่มสมาชิก",
- "defaultMessage": "Add members",
- "description": "Flat button [Add members] (to topic)",
- "missing": false,
- "obsolete": false
- },
- "button_cancel": {
- "translation": "ยกเลิก",
- "defaultMessage": "Cancel",
- "description": "Button [Cancel]",
- "missing": false,
- "obsolete": false
- },
- "button_confirm": {
- "translation": "ยืนยัน",
- "defaultMessage": "Confirm",
- "description": "Button [Confirm]",
- "missing": false,
- "obsolete": false
- },
- "button_create": {
- "translation": "สร้าง",
- "defaultMessage": "Create",
- "description": "Button [Create]",
- "missing": false,
- "obsolete": false
- },
- "button_delete_account": {
- "translation": "ลบบัญชี",
- "defaultMessage": "Delete account",
- "description": "Button [Delete account]",
- "missing": false,
- "obsolete": false
- },
- "button_edit": {
- "translation": "แก้ไข",
- "defaultMessage": "Edit",
- "description": "Call to action [Edit]",
- "missing": false,
- "obsolete": false
- },
- "button_logout": {
- "translation": "ลงชื่อออก",
- "defaultMessage": "Logout",
- "description": "Button [Logout]",
- "missing": false,
- "obsolete": false
- },
- "button_ok": {
- "translation": "ตกลง",
- "defaultMessage": "OK",
- "description": "Button [OK]",
- "missing": false,
- "obsolete": false
- },
- "button_reset": {
- "translation": "รีเซ็ต",
- "defaultMessage": "Reset",
- "description": "Button [Reset]",
- "missing": false,
- "obsolete": false
- },
- "button_send_request": {
- "translation": "ส่งคำขอ",
- "defaultMessage": "Send request",
- "description": "Button [Send request]",
- "missing": false,
- "obsolete": false
- },
- "button_sign_in": {
- "translation": "ลงชื่อเข้าใช้",
- "defaultMessage": "Sign in",
- "description": "Button [Sign In]",
- "missing": false,
- "obsolete": false
- },
- "button_sign_up": {
- "translation": "สร้างบัญชี",
- "defaultMessage": "Sign up",
- "description": "Create account button [Sign Up]",
- "missing": false,
- "obsolete": false
- },
- "button_subscribe": {
- "translation": "ติดตาม",
- "defaultMessage": "Subscribe",
- "description": "Button [Subscribe]",
- "missing": false,
- "obsolete": false
- },
- "button_update": {
- "translation": "อัพเดต",
- "defaultMessage": "Update",
- "description": "Button [Update]",
- "missing": false,
- "obsolete": false
- },
- "cannot_initiate_file_upload": {
- "translation": "ไม่สามารถอัพโหลดไฟล์",
- "defaultMessage": "Cannot initiate file upload.",
- "description": "Generic error messagewhen attachment fails",
- "missing": false,
- "obsolete": false
- },
- "channel": {
- "translation": "ช่อง",
- "defaultMessage": "channel",
- "description": "Subtitle shown for channels in MessagesView instead of last seen",
- "missing": false,
- "obsolete": false
- },
- "channel_prompt": {
- "translation": "นี่คือช่อง",
- "defaultMessage": "This is a channel",
- "description": "Checkbox label when creating a channel",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation": {
- "translation": "คุณถูกเชิญให้เข้าร่วมแชทใหม่ คุณต้องการทำอะไร?",
- "defaultMessage": "You are invited to start a new chat. What would you like to do?",
- "description": "New chat invitation message: [Accept] [Ignore] [Block].",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_accept": {
- "translation": "ยอมรับ",
- "defaultMessage": "Accept",
- "description": "Action [Accept] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_block": {
- "translation": "บล็อค",
- "defaultMessage": "Block",
- "description": "Action [Block] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_ignore": {
- "translation": "ไม่สนใจ",
- "defaultMessage": "Ignore",
- "description": "Action [Ignore] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "clear_messages_warning": {
- "translation": "คุณแน่ใจหรือไม่ที่จะล้างข้อความทั้งหมด? ส่ิงนี้ไม่สามารถเลิกทำได้",
- "defaultMessage": "Are you sure you want to clear all messages? It cannot be undone.",
- "description": "Alert dialog warning when deleting all messages.",
- "missing": false,
- "obsolete": false
- },
- "code_doesnot_match": {
- "translation": "โค้ด ไม่ถูกต้อง",
- "defaultMessage": "Code does not match",
- "description": "Error message when the credential validation code is incorrect.",
- "missing": false,
- "obsolete": false
- },
- "contacts_not_found": {
- "translation": "ไม่พบแชท \n¯∖_(ツ)_/¯",
- "defaultMessage": "You have no chats\\n¯∖_(ツ)_/¯",
- "description": "HTML message shown in ContactList when no contacts are found",
- "missing": false,
- "obsolete": false
- },
- "contacts_not_found_short": {
- "translation": "ไม่พบรายชื่อ ''{query}''",
- "defaultMessage": "No contacts match ''{query}''",
- "description": "Shown in ContactsView when search returned no results",
- "missing": false,
- "obsolete": false
- },
- "credential_email_prompt": {
- "translation": "อีเมลที่ลงทะเบียนของคุณ",
- "defaultMessage": "Your registration email",
- "description": "Placeholder for entering email",
- "missing": false,
- "obsolete": false
- },
- "delete_account": {
- "translation": "ลบบัญชี",
- "defaultMessage": "Delete account",
- "description": "Title for delete account warning",
- "missing": false,
- "obsolete": false
- },
- "delete_account_warning": {
- "translation": "คุณแน่ใจหรือไม่ที่จะลบบัญชีผู้ใช้ของคุณ? สิ่งนี้ไม่สามารถเลิกทำได้.",
- "defaultMessage": "Are you sure you want to delete your account? It cannot be undone.",
- "description": "Warning message when deleting an account",
- "missing": false,
- "obsolete": false
- },
- "delete_messages_warning": {
- "translation": "คุณแน่ใจหรือไม่ที่จะลบข้อความทั้งหมดสำหรับทุกคน? ส่ิงนี้ไม่สามารถเลิกทำได้",
- "defaultMessage": "Are you sure you want to delete all messages for everyone? It cannot be undone.",
- "description": "Alert dialog warning when hard-deleting all messages.",
- "missing": false,
- "obsolete": false
- },
- "download_action": {
- "translation": "ดาวน์โหลด",
- "defaultMessage": "download",
- "description": "Call to action [download]",
- "missing": false,
- "obsolete": false
- },
- "drafty_attachment": {
- "translation": "สิ่งที่แนบมา",
- "defaultMessage": "Attachment",
- "description": "Comment for attachment in Drafty",
- "missing": false,
- "obsolete": false
- },
- "drafty_form": {
- "translation": "ฟอร์ม:",
- "defaultMessage": "Form:",
- "description": "Comment for form in Drafty",
- "missing": false,
- "obsolete": false
- },
- "drafty_image": {
- "translation": "รูปภาพ",
- "defaultMessage": "Picture",
- "description": "Comment for embedded images in Drafty",
- "missing": false,
- "obsolete": false
- },
- "email_dative": {
- "translation": "อีเมล์",
- "defaultMessage": "email",
- "description": "Dative case of 'email', i.e. 'email' in 'by email'",
- "missing": false,
- "obsolete": false
- },
- "email_prompt": {
- "translation": "อีเมล, เช่น jdoe@example.com",
- "defaultMessage": "Email, e.g. jdoe@example.com",
- "description": "Input placeholder for email entry",
- "missing": false,
- "obsolete": false
- },
- "enable_peers_messaging": {
- "translation": "เปิดใช้งาน",
- "defaultMessage": "Enable",
- "description": "Call to action to enable peer's messaging",
- "missing": false,
- "obsolete": false
- },
- "enter_confirmation_code_prompt": {
- "translation": "รหัสยืนยัน",
- "defaultMessage": "Confirmation code",
- "description": "Request to enter confirmation code",
- "missing": false,
- "obsolete": false
- },
- "error_invalid_id": {
- "translation": "ID ไม่ถูกต้อง",
- "defaultMessage": "Invalid ID",
- "description": "Error message",
- "missing": false,
- "obsolete": false
- },
- "file_attachment_too_large": {
- "translation": "ขนาดไฟล์ {size} เกิน {limit} ที่กำหนด.",
- "defaultMessage": "The file size {size} exceeds the {limit} limit.",
- "description": "Error message when attachment is too large",
- "missing": false,
- "obsolete": false
- },
- "forgot_password_link": {
- "translation": "ลืมรหัสผ่าน?",
- "defaultMessage": "Forgot password?",
- "description": "Link to Reset password form",
- "missing": false,
- "obsolete": false
- },
- "full_name_prompt": {
- "translation": "ชื่อเต็ม, เช่น John Doe",
- "defaultMessage": "Full name, e.g. John Doe",
- "description": "Input placeholder for person's full name",
- "missing": false,
- "obsolete": false
- },
- "granted_permissions": {
- "translation": "ยินยอม",
- "defaultMessage": "Granted",
- "description": "Title for permissions",
- "missing": false,
- "obsolete": false
- },
- "group_has_no_members": {
- "translation": "ไม่มีสมาชิก",
- "defaultMessage": "No members",
- "description": "Shown in place of group members",
- "missing": false,
- "obsolete": false
- },
- "group_user_id_prompt": {
- "translation": "กลุ่ม หรือ ID ผู้ใช้",
- "defaultMessage": "Group or User ID",
- "description": "Prompt for entering user or group ID",
- "missing": false,
- "obsolete": false
- },
- "image_caption_prompt": {
- "translation": "คำอธิบายภาพ",
- "defaultMessage": "Image caption",
- "description": "Prompt in SendMessage for attached image",
- "missing": false,
- "obsolete": false
- },
- "invalid_content": {
- "translation": "เนื้อหาไม่ถูกต้อง",
- "defaultMessage": "invalid content",
- "description": "Shown when the message is unreadable",
- "missing": false,
- "obsolete": false
- },
- "invalid_security_token": {
- "translation": "security token ไม่ถูกต้อง",
- "defaultMessage": "Invalid security token",
- "description": "Error message when resetting password.",
- "missing": false,
- "obsolete": false
- },
- "label_client": {
- "translation": "Client:",
- "defaultMessage": "Client:",
- "description": "Label for a client version",
- "missing": false,
- "obsolete": false
- },
- "label_content_type": {
- "translation": "ชนิดของเนื้อหา:",
- "defaultMessage": "Content type:",
- "description": "Label for file content type (mime)",
- "missing": false,
- "obsolete": false
- },
- "label_default_access_mode": {
- "translation": "โหมดการเข้าถึงเริ่มต้น:",
- "defaultMessage": "Default access mode:",
- "description": "Label for default access mode",
- "missing": false,
- "obsolete": false
- },
- "label_file_name": {
- "translation": "ชื่อไฟล์:",
- "defaultMessage": "File name:",
- "description": "Label for a file name",
- "missing": false,
- "obsolete": false
- },
- "label_group_members": {
- "translation": "สมาชิกกลุ่ม:",
- "defaultMessage": "Group members:",
- "description": "Section title or label",
- "missing": false,
- "obsolete": false
- },
- "label_incognito_mode": {
- "translation": "โหมดไม่ระบุตัวตน:",
- "defaultMessage": "Incognito mode:",
- "description": "Label for incognito mode toggle",
- "missing": false,
- "obsolete": false
- },
- "label_message_sound": {
- "translation": "เสียงข้อความ:",
- "defaultMessage": "Message sound:",
- "description": "Label for message sounds toggle",
- "missing": false,
- "obsolete": false
- },
- "label_muting_topic": {
- "translation": "ไม่สนใจ:",
- "defaultMessage": "Muted:",
- "description": "Label for Muting/unmuting the topic",
- "missing": false,
- "obsolete": false
- },
- "label_other_user": {
- "translation": "อื่นๆ",
- "defaultMessage": "Other",
- "description": "Label for the other user when the user is unnamed",
- "missing": false,
- "obsolete": false
- },
- "label_password": {
- "translation": "รหัสผ่าน",
- "defaultMessage": "Password",
- "description": "Label for password editing",
- "missing": false,
- "obsolete": false
- },
- "label_permissions": {
- "translation": "สิทธิ์:",
- "defaultMessage": "Permissions:",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "label_private": {
- "translation": "ความคิดเห็นส่วนตัว",
- "defaultMessage": "Private comment",
- "description": "Label for editing 'private'",
- "missing": false,
- "obsolete": false
- },
- "label_push_notifications": {
- "translation": "การแจ้งเตือน:",
- "defaultMessage": "Notification alerts:",
- "description": "Label for push notifications switch",
- "missing": false,
- "obsolete": false
- },
- "label_push_notifications_disabled": {
- "translation": "การแจ้งเตือน (requires HTTPS):",
- "defaultMessage": "Notification alerts (requires HTTPS):",
- "description": "Label for push notifications switch",
- "missing": false,
- "obsolete": false
- },
- "label_reset_password": {
- "translation": "ส่งรหัสรีเซ็ตอีเมล:",
- "defaultMessage": "Send a password reset email",
- "description": "Label for password reset field",
- "missing": false,
- "obsolete": false
- },
- "label_sdk": {
- "translation": "SDK:",
- "defaultMessage": "SDK:",
- "missing": false,
- "obsolete": false
- },
- "label_server": {
- "translation": "เซิร์ฟเวอร์:",
- "defaultMessage": "Server:",
- "description": "Label for a server version",
- "missing": false,
- "obsolete": false
- },
- "label_server_address": {
- "translation": "ที่อยู่เซิร์ฟเวอร์:",
- "defaultMessage": "Server address:",
- "missing": false,
- "obsolete": false
- },
- "label_server_to_use": {
- "translation": "เลือกเซิร์ฟเวอร์:",
- "defaultMessage": "Server to use:",
- "description": "Label for server selector in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "label_size": {
- "translation": "ขนาด:",
- "defaultMessage": "Size:",
- "description": "Label for file size",
- "missing": false,
- "obsolete": false
- },
- "label_topic_name": {
- "translation": "ชื่อ",
- "defaultMessage": "Name",
- "description": "Label for editing topic name",
- "missing": false,
- "obsolete": false
- },
- "label_user_contacts": {
- "translation": "รายชื่อ",
- "defaultMessage": "Contacts",
- "description": "Label for user contacts",
- "missing": false,
- "obsolete": false
- },
- "label_user_id": {
- "translation": "ID:",
- "defaultMessage": "ID:",
- "description": "Label for user address (ID)",
- "missing": false,
- "obsolete": false
- },
- "label_wire_transport": {
- "translation": "Wire transport:",
- "defaultMessage": "Wire transport:",
- "description": "Label for wire transport selection in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "label_you": {
- "translation": "คุณ:",
- "defaultMessage": "You:",
- "description": "Label for the current user",
- "missing": false,
- "obsolete": false
- },
- "label_your_name": {
- "translation": "ชื่อของคุณ",
- "defaultMessage": "Your name",
- "description": "Label for full name editing",
- "missing": false,
- "obsolete": false
- },
- "label_your_permissions": {
- "translation": "สิทธิของคุณ:",
- "defaultMessage": "Your permissions:",
- "description": "Label for current user permissions",
- "missing": false,
- "obsolete": false
- },
- "last_seen_timestamp": {
- "translation": "ล่าสุดเมื่อ",
- "defaultMessage": "Last seen: {timestamp}",
- "description": "Label for the timestamp of when the user or topic was last online",
- "missing": false,
- "obsolete": false
- },
- "leave_chat_warning": {
- "translation": "คุณแน่ใจหรือไม่ที่จะออกจากการสนทนานี้?",
- "defaultMessage": "Are you sure you want to leave this conversation?",
- "description": "Alert dialog warning when unsubscribing from a chat.",
- "missing": false,
- "obsolete": false
- },
- "link_contact_us": {
- "translation": "ติดต่อเรา",
- "defaultMessage": "Contact Us",
- "description": "Ancor text for contacting us by email",
- "missing": false,
- "obsolete": false
- },
- "link_privacy_policy": {
- "translation": "ข้อกำหนดความเป็นส่วนตัว",
- "defaultMessage": "Privacy Policy",
- "description": "Ancor text for privacy policy link",
- "missing": false,
- "obsolete": false
- },
- "link_terms_of_service": {
- "translation": "ข้อกำหนดการใช้งาน",
- "defaultMessage": "Terms of Service",
- "description": "Ancor text for terms of service link",
- "missing": false,
- "obsolete": false
- },
- "login_prompt": {
- "translation": "ล็อกอิน",
- "defaultMessage": "Login",
- "description": "Placeholer for username/login",
- "missing": false,
- "obsolete": false
- },
- "menu_item_archive_topic": {
- "translation": "สำเร็จ",
- "defaultMessage": "Archive",
- "description": "Move topic from the list of active chats to archive",
- "missing": false,
- "obsolete": false
- },
- "menu_item_block": {
- "translation": "บล็อค",
- "defaultMessage": "Block",
- "description": "Block topic or user",
- "missing": false,
- "obsolete": false
- },
- "menu_item_clear_messages": {
- "translation": "ล้างข้อความ",
- "defaultMessage": "Clear messages",
- "description": "Delete all messages",
- "missing": false,
- "obsolete": false
- },
- "menu_item_clear_messages_for_all": {
- "translation": "ล้างทั้งหมด",
- "defaultMessage": "Clear for All",
- "description": "Delete all message(s) for all members",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete": {
- "translation": "ลบ",
- "defaultMessage": "Delete",
- "description": "Delete selected messages",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete_for_all": {
- "translation": "ลบทั้งหมด",
- "defaultMessage": "Delete for All",
- "description": "Delete selected message(s) for all members",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete_topic": {
- "translation": "ลบ",
- "defaultMessage": "Delete",
- "description": "Delete entire topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_edit_permissions": {
- "translation": "แก้ไขสิทธิ",
- "defaultMessage": "Edit permissions",
- "description": "Menu item [Edit permissions]",
- "missing": false,
- "obsolete": false
- },
- "menu_item_info": {
- "translation": "ข้อมูล",
- "defaultMessage": "Info",
- "description": "Show extended topic information",
- "missing": false,
- "obsolete": false
- },
- "menu_item_member_delete": {
- "translation": "ลบ",
- "defaultMessage": "Remove",
- "description": "Remove user from topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_mute": {
- "translation": "ปิดการแจ้งเตือน",
- "defaultMessage": "Mute",
- "description": "Turn off notifications",
- "missing": false,
- "obsolete": false
- },
- "menu_item_reply": {
- "translation": "ตอบกลับ",
- "defaultMessage": "Reply",
- "description": "Reply to message",
- "missing": false,
- "obsolete": false
- },
- "menu_item_restore_topic": {
- "translation": "เรื่มใหม่",
- "defaultMessage": "Restore",
- "description": "Restore topic from archive",
- "missing": false,
- "obsolete": false
- },
- "menu_item_send_retry": {
- "translation": "ลองอีกครั้ง",
- "defaultMessage": "Retry",
- "description": "Retry sending message",
- "missing": false,
- "obsolete": false
- },
- "menu_item_unblock": {
- "translation": "ปลดบล็อค",
- "defaultMessage": "Unblock",
- "description": "Unblock topic or user",
- "missing": false,
- "obsolete": false
- },
- "menu_item_unmute": {
- "translation": "เปิดการแจ้งเตือน",
- "defaultMessage": "Unmute",
- "description": "Turn notifications on",
- "missing": false,
- "obsolete": false
- },
- "message_sending": {
- "translation": "กำลังส่ง...",
- "defaultMessage": "sending...",
- "description": "Message being sent, in place of time stamp",
- "missing": false,
- "obsolete": false
- },
- "message_sending_failed": {
- "translation": "ล้มเหลว",
- "defaultMessage": "failed",
- "description": "Failed to send message, in place of time stamp",
- "missing": false,
- "obsolete": false
- },
- "messages_not_readable": {
- "translation": "ไม่มีสิทธิเข้าถึงข้อความ",
- "defaultMessage": "no access to messages",
- "description": "Message shown in topic without the read access",
- "missing": false,
- "obsolete": false
- },
- "messaging_disabled_prompt": {
- "translation": "ปิดการส่งข้อความ",
- "defaultMessage": "Messaging disabled",
- "description": "Prompt in SendMessage in read-only topic",
- "missing": false,
- "obsolete": false
- },
- "more_online_members": {
- "translation": "+{overflow} เพิ่มเติม",
- "defaultMessage": "+{overflow} more",
- "description": "Shown in MessagesView title bar when the number of online subscribers exceeds MAX_ONLINE_IN_TOPIC",
- "missing": false,
- "obsolete": false
- },
- "new_message_prompt": {
- "translation": "ข้อความใหม่",
- "defaultMessage": "New message",
- "description": "Prompt in send message field",
- "missing": false,
- "obsolete": false
- },
- "new_password_placeholder": {
- "translation": "ใส่รหัสใหม่",
- "defaultMessage": "Enter new password",
- "description": "Placeholder for entering new password",
- "missing": false,
- "obsolete": false
- },
- "no_connection": {
- "translation": "ไม่มีการเชื่อมต่อ",
- "defaultMessage": "No connection",
- "description": "Warning that the user is offline.",
- "missing": false,
- "obsolete": false
- },
- "no_contacts": {
- "translation": "คุณไม่มีรายชื่อ :-(",
- "defaultMessage": "You have no contacts :-(",
- "description": "Shown in ContactsView when the user has no contacts",
- "missing": false,
- "obsolete": false
- },
- "numeric_confirmation_code_prompt": {
- "translation": "ตัวเลขเท่านั้น",
- "defaultMessage": "Numbers only",
- "description": "Prompt for numeric conformation code",
- "missing": false,
- "obsolete": false
- },
- "online_now": {
- "translation": "ออนไลน์อยู่",
- "defaultMessage": "online now",
- "description": "Indicator that the user or topic is currently online",
- "missing": false,
- "obsolete": false
- },
- "password_prompt": {
- "translation": "รหัสผ่าน",
- "defaultMessage": "Password",
- "description": "Placeholder/prompt for entering password",
- "missing": false,
- "obsolete": false
- },
- "password_unchanged_prompt": {
- "translation": "ไม่เปลี่ยนแปลง",
- "defaultMessage": "Unchanged",
- "description": "Message in editor while password is unchanged",
- "missing": false,
- "obsolete": false
- },
- "peers_messaging_disabled": {
- "translation": "ปิดใช้งาน การส่งข้อความ Peer's",
- "defaultMessage": "Peer's messaging is disabled.",
- "description": "Shown when the p2p peer's messaging is disabled",
- "missing": false,
- "obsolete": false
- },
- "permission_admin": {
- "translation": "ยอมรับ ({val})",
- "defaultMessage": "Approve ({val})",
- "description": "Name of A permission",
- "missing": false,
- "obsolete": false
- },
- "permission_delete": {
- "translation": "ลบ ({val})",
- "defaultMessage": "Delete ({val})",
- "description": "Name of D permission",
- "missing": false,
- "obsolete": false
- },
- "permission_join": {
- "translation": "เข้าร่วม ({val})",
- "defaultMessage": "Join ({val})",
- "description": "Name of J permission",
- "missing": false,
- "obsolete": false
- },
- "permission_owner": {
- "translation": "เจ้าของ ({val})",
- "defaultMessage": "Owner ({val})",
- "description": "Name of O permission",
- "missing": false,
- "obsolete": false
- },
- "permission_pres": {
- "translation": "รับการแจ้งเตือน ({val})",
- "defaultMessage": "Get notified ({val})",
- "description": "Name of P permission",
- "missing": false,
- "obsolete": false
- },
- "permission_read": {
- "translation": "อ่าน ({val})",
- "defaultMessage": "Read ({val})",
- "description": "Name of R permission",
- "missing": false,
- "obsolete": false
- },
- "permission_share": {
- "translation": "แชร์ ({val})",
- "defaultMessage": "Share ({val})",
- "description": "Name of S permission",
- "missing": false,
- "obsolete": false
- },
- "permission_write": {
- "translation": "เขียน ({val})",
- "defaultMessage": "Write ({val})",
- "description": "Name of W permission",
- "missing": false,
- "obsolete": false
- },
- "phone_dative": {
- "translation": "มือถือ",
- "defaultMessage": "phone",
- "description": "Dative case of 'phone', i.e. 'phone' in 'by phone'",
- "missing": false,
- "obsolete": false
- },
- "private_editing_placeholder": {
- "translation": "แค่คุณที่เห็นสิ่งนี้",
- "defaultMessage": "Visible to you only",
- "description": "Placeholder for editing 'private'",
- "missing": false,
- "obsolete": false
- },
- "push_init_failed": {
- "translation": "การแจ้งเตือนล้มเหลว",
- "defaultMessage": "Failed to initialize push notifications",
- "description": "Error message when push notifications have failed to initialize.",
- "missing": false,
- "obsolete": false
- },
- "reconnect_countdown": {
- "translation": "ถูกตัดการเชื่อมต่อ. จะเชื่อมต่อใหม่ใน {seconds}…",
- "defaultMessage": "Disconnected. Reconnecting in {seconds}…",
- "description": "Message shown when an app update is available.",
- "missing": false,
- "obsolete": false
- },
- "reconnect_now": {
- "translation": "ลองเลย",
- "defaultMessage": "Try now",
- "description": "Prompt for reconnecting now",
- "missing": false,
- "obsolete": false
- },
- "reload_update": {
- "translation": "โหลดใหม่",
- "defaultMessage": "Reload",
- "description": "Call to action to reload application when update is available.",
- "missing": false,
- "obsolete": false
- },
- "report_chat_warning": {
- "translation": "คุณแน่ใจหรือไม่ที่จะบล็อคและรายงานการสนทนานี้",
- "defaultMessage": "Are you sure you want to block and report this conversation?",
- "description": "Alert dialog warning when reporting a conversation for abuse",
- "missing": false,
- "obsolete": false
- },
- "requested_permissions": {
- "translation": "ต้องการ",
- "defaultMessage": "Requested",
- "description": "Title for permissions",
- "missing": false,
- "obsolete": false
- },
- "save_attachment": {
- "translation": "บันทึก",
- "defaultMessage": "save",
- "description": "Call to save an attachment",
- "missing": false,
- "obsolete": false
- },
- "search_for_contacts": {
- "translation": "ใช้ค้นหารายชื่อ",
- "defaultMessage": "Use search to find contacts",
- "description": "Text shown in contacts view when user entered no search query.",
- "missing": false,
- "obsolete": false
- },
- "search_no_results": {
- "translation": "ไม่พบผลลัพธ์",
- "defaultMessage": "Search returned no results",
- "description": "Text shown in contacts view when query returned no results.",
- "missing": false,
- "obsolete": false
- },
- "search_placeholder": {
- "translation": "เช่น email:alice@example.com, tel:17025550003...",
- "defaultMessage": "List like alice@example.com, +17025550003...",
- "description": "Placeholder in contacts search field",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_notifications": {
- "translation": "การแจ้งเตือน",
- "defaultMessage": "Notifications",
- "description": "Sidepanel title for AccNotificationsView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_support": {
- "translation": "สนับสนุน",
- "defaultMessage": "Support",
- "description": "Sidepanel title for AccSupportView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_account_settings": {
- "translation": "การตั้งค่าบัญชี",
- "defaultMessage": "Account Settings",
- "description": "Sidepanel title for AccountSettingsView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_archive": {
- "translation": "แชทที่สำเร็จ",
- "defaultMessage": "Archived Chats",
- "description": "Sidepanel title for ContactsView-Archive.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_blocked": {
- "translation": "แชทที่ถูกบล็อค",
- "defaultMessage": "Blocked Chats",
- "description": "Sidepanel title for ContactsView-Blocked.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_cred": {
- "translation": "ยืนยันตัวตน",
- "defaultMessage": "Confirm Credentials",
- "description": "Sidepanel title for ValidationView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_login": {
- "translation": "ลงชื่อเข้าใช้",
- "defaultMessage": "Sign In",
- "description": "Sidepanel title for LoginView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_newtpk": {
- "translation": "เริ่มแชทใหม่",
- "defaultMessage": "Start New Chat",
- "description": "Sidepanel title for NewTopicView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_register": {
- "translation": "สร้างบัญชี",
- "defaultMessage": "Create Account",
- "description": "Sidepanel title for CreateAccountView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_reset": {
- "translation": "รีเซ็ตรหัสผ่าน",
- "defaultMessage": "Reset Password",
- "description": "Sidepanel title for PasswordResetView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_settings": {
- "translation": "ตั้งค่า",
- "defaultMessage": "Settings",
- "description": "Sidepanel title for SettingsView.",
- "missing": false,
- "obsolete": false
- },
- "stay_logged_in": {
- "translation": "คงอยู่ในระบบ",
- "defaultMessage": "Stay logged in",
- "description": "Label for a checkbox",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_find_user": {
- "translation": "หา",
- "defaultMessage": "find",
- "description": "Tab title Find",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_group_by_id": {
- "translation": "ด้วย ID",
- "defaultMessage": "by id",
- "description": "Tab title Find topic by ID",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_new_group": {
- "translation": "กลุ่มใหม่",
- "defaultMessage": "new group",
- "description": "Tab title New Group",
- "missing": false,
- "obsolete": false
- },
- "tags_editor_no_tags": {
- "translation": "เพิ่มแท็ก",
- "defaultMessage": "Add some tags",
- "description": "Tag editor prompt when no tags are found.",
- "missing": false,
- "obsolete": false
- },
- "tags_not_found": {
- "translation": "ไม่มีการกำหนดแท็ก เพิ่มแท็กเลย",
- "defaultMessage": "No tags defined. Add some.",
- "description": "",
- "missing": false,
- "obsolete": false
- },
- "title_all_contacts": {
- "translation": "รายชื่อทั้งหมด",
- "defaultMessage": "All Contacts",
- "description": "Section title [All Contacts]",
- "missing": false,
- "obsolete": false
- },
- "title_group_members": {
- "translation": "สมาชิกกลุ่ม",
- "defaultMessage": "Group Members",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "title_manage_tags": {
- "translation": "จัดการ",
- "defaultMessage": "Manage",
- "description": "Section title for the list of tags",
- "missing": false,
- "obsolete": false
- },
- "title_not_found": {
- "translation": "ไม่พบ",
- "defaultMessage": "Not found",
- "description": "Title shown when topic is not found",
- "missing": false,
- "obsolete": false
- },
- "title_permissions": {
- "translation": "การอนุญาต",
- "defaultMessage": "Permissions",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "title_tag_manager": {
- "translation": "แท็ก (ค้นหา & ค้นพบ)",
- "defaultMessage": "Tags (search & discovery)",
- "description": "Section title for TagManager",
- "missing": false,
- "obsolete": false
- },
- "topic_block_warning": {
- "translation": "คุณแน่ใจหรือไม่ที่ต้องการบล็อกการสนทนานี้?",
- "defaultMessage": "Are you sure you want to block this conversation?",
- "description": "Alert warning when blocking a topic.",
- "missing": false,
- "obsolete": false
- },
- "topic_delete_warning": {
- "translation": "คุณแน่ใจหรือไม่ที่ต้องการลบการสนทนานี้? สิ่งนี้ไม่สามารถเลิกทำได้",
- "defaultMessage": "Are you sure you want to delete this conversation? It cannot be undone.",
- "description": "Alert warning when deleting entire topic",
- "missing": false,
- "obsolete": false
- },
- "topic_name_editing_placeholder": {
- "translation": "ชื่อกลุ่มอิสระ",
- "defaultMessage": "Freeform name of the group",
- "description": "Prompt for entering topic name",
- "missing": false,
- "obsolete": false
- },
- "unnamed_topic": {
- "translation": "ไม่มีชื่อ",
- "defaultMessage": "Unnamed",
- "description": "Title shown when the topic has no name",
- "missing": false,
- "obsolete": false
- },
- "update_available": {
- "translation": "มีการอัพเดตพร้อมใช้งาน",
- "defaultMessage": "Update available.",
- "description": "Message shown when an app update is available.",
- "missing": false,
- "obsolete": false
- },
- "upload_finishing": {
- "translation": "กำลังจบ...",
- "defaultMessage": "finishing...",
- "description": "Notification that upload is finishing",
- "missing": false,
- "obsolete": false
- },
- "user_not_found": {
- "translation": "ไม่พบ",
- "defaultMessage": "Not found",
- "description": "In place of a user's full name when the user is not found.",
- "missing": false,
- "obsolete": false
- },
- "description_editing_placeholder": {
- "translation": "คำอธิบาย (ไม่บังคับ)",
- "defaultMessage": "Description (optional)",
- "description": "Placeholder for editing topic description",
- "missing": false,
- "obsolete": false
- },
- "label_description": {
- "translation": "คำอธิบาย",
- "defaultMessage": "Description",
- "description": "Label for description of user or topic",
- "missing": false,
- "obsolete": false
- },
- "button_security": {
- "translation": "ความปลอดภัย",
- "defaultMessage": "Security",
- "description": "Navigaton button for security panel.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_crop": {
- "translation": "ลากเพื่อปรับ",
- "defaultMessage": "Drag to Adjust",
- "description": "Title for AvatarCropView.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_general": {
- "translation": "ทั่วไป",
- "defaultMessage": "General",
- "description": "Title for TopicCommon.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_members": {
- "translation": "สมาชิก",
- "defaultMessage": "Members",
- "description": "Title for managing group members view.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_security": {
- "translation": "ความปลอดภัย",
- "defaultMessage": "Security",
- "description": "Title for TopicSecirity and AccSecurity.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_info": {
- "translation": "ข้อมูล",
- "defaultMessage": "Info",
- "description": "Title for InfoView",
- "missing": false,
- "obsolete": false
- },
- "permissions_anonymous": {
- "translation": "ไม่ระบุตัวตน",
- "defaultMessage": "Anonymous",
- "description": "Title for editing default anonymous permissions",
- "missing": false,
- "obsolete": false
- },
- "permissions_authenticated": {
- "translation": "รับรองความถูกต้องแล้ว",
- "defaultMessage": "Authenticated",
- "description": "Title for editing default authenticated permissions",
- "missing": false,
- "obsolete": false
- },
- "topic_delete": {
- "translation": "ลบการสนทนา",
- "defaultMessage": "Delete Conversation",
- "description": "Alert title when deleting the topic.",
- "missing": false,
- "obsolete": false
- },
- "permissions_user": {
- "translation": "การอนุญาตของผู้ใช้",
- "defaultMessage": "User's Permissions",
- "description": "Title for editing user's permissions",
- "missing": false,
- "obsolete": false
- },
- "password_reset_email_sent": {
- "translation": "รหัสความปลอดภัยถูกส่งไปยังอีเมลแล้ว",
- "defaultMessage": "An email with security code has been sent.",
- "description": "Notification that the email with password reset instructions has been sent",
- "missing": false,
- "obsolete": false
- },
- "label_unarchive_topic": {
- "translation": "สำเร็จ:",
- "defaultMessage": "Archived:",
- "description": "Label for unarchiving the topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_forward": {
- "translation": "ส่งต่อ",
- "defaultMessage": "Forward",
- "description": "Forward message",
- "missing": false,
- "obsolete": false
- },
- "forward_to": {
- "translation": "ส่งต่อไปยัง",
- "defaultMessage": "Forward to",
- "missing": false,
- "obsolete": false
- },
- "forward_to_search_placeholder": {
- "translation": "ค้นหารายชื่อ",
- "defaultMessage": "Search contacts",
- "description": "Contact search prompt when forwarding a message.",
- "missing": false,
- "obsolete": false
- },
- "label_new_password": {
- "translation": "รหัสผ่านใหม่",
- "defaultMessage": "New password",
- "description": "Label for entering a new password",
- "missing": false,
- "obsolete": false
- },
- "drafty_unknown": {
- "translation": "ไม่รองรับ",
- "defaultMessage": "Unsupported",
- "description": "Unsupported entity in drafty",
- "missing": false,
- "obsolete": false
- },
- "calls_incoming": {
- "translation": "สายโทรเข้า",
- "defaultMessage": "Incoming call",
- "description": "Incoming call label",
- "missing": false,
- "obsolete": false
- },
- "calls_outgoing": {
- "translation": "สายโทรออก",
- "defaultMessage": "Outgoing call",
- "description": "Outgoing call label",
- "missing": false,
- "obsolete": false
- },
- "calls_you_label": {
- "translation": "คุณ",
- "defaultMessage": "You",
- "description": "Shown over the local video screen",
- "missing": false,
- "obsolete": false
- },
- "menu_item_video_call": {
- "translation": "วีดีโอคอล",
- "defaultMessage": "Video call",
- "description": "Start video call",
- "missing": false,
- "obsolete": false
- },
- "already_in_call": {
- "translation": "คุณกำลังอยู่ในการสนทนา!",
- "defaultMessage": "You already in an ongoing call!",
- "description": "Error message when the user tried to accept a new call without finishing pervious one",
- "missing": false,
- "obsolete": false
- },
- "call_cancelled": {
- "translation": "ยกเลิกแล้ว",
- "defaultMessage": "cancelled",
- "description": "Label for cancelled outgoing call",
- "missing": false,
- "obsolete": false
- },
- "call_missed": {
- "translation": "ไม่ได้รับสาย",
- "defaultMessage": "missed",
- "description": "Label for missed incoming call",
- "missing": false,
- "obsolete": false
- },
- "call_declined": {
- "translation": "ไม่รับสาย",
- "defaultMessage": "declined",
- "description": "Label for declined call",
- "missing": false,
- "obsolete": false
- },
- "call_disconnected": {
- "translation": "ตัดการเชื่อมต่อแล้ว",
- "defaultMessage": "disconnected",
- "description": "Label for disconnected call",
- "missing": false,
- "obsolete": false
- },
- "label_use_secure_connection": {
- "translation": "ใช้การเชื่อมต่อที่ปลอดภัย",
- "defaultMessage": "Use secure connection",
- "description": "Label for WS/WSS connection type in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "cred_confirmed_successfully": {
- "translation": "การยืนยันสำเร็จ",
- "defaultMessage": "Confirmed successfully",
- "description": "Notification message that the credential was successfully validated.",
- "missing": false,
- "obsolete": false
- },
- "menu_item_edit": {
- "translation": "แก้ไข",
- "defaultMessage": "Edit",
- "description": "Edit message",
- "missing": false,
- "obsolete": false
- },
- "editing_message": {
- "translation": "กำลังแก้ไข",
- "defaultMessage": "Editing",
- "description": "Title over message editing preview",
- "missing": false,
- "obsolete": false
- },
- "message_edited_marker": {
- "translation": ", ถูกแก้ไข",
- "defaultMessage": ", edited",
- "description": "Marker indicating that the message was edited",
- "missing": false,
- "obsolete": false
- },
- "icon_title_add_image": {
- "translation": "เพิ่มรูปภาพ",
- "defaultMessage": "Add image",
- "description": "Icon tool tip for attaching an image",
- "missing": false,
- "obsolete": false
- },
- "icon_title_attach_file": {
- "translation": "แนบไฟล์",
- "defaultMessage": "Attach file",
- "description": "Icon tool tip for attaching a file",
- "missing": false,
- "obsolete": false
- },
- "icon_title_delete": {
- "translation": "ลบ",
- "defaultMessage": "Delete recording",
- "description": "Icon tool tip for deleting recorded audio",
- "missing": false,
- "obsolete": false
- },
- "icon_title_pause": {
- "translation": "หยุด",
- "defaultMessage": "Pause playback",
- "description": "Icon tool tip for pausing audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_play": {
- "translation": "เล่น",
- "defaultMessage": "Play recording",
- "description": "Icon tool tip for starting audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_record_voice": {
- "translation": "บันทึกข้อความเสียง",
- "defaultMessage": "Record voice message",
- "description": "Icon tool tip for recording a voice message",
- "missing": false,
- "obsolete": false
- },
- "icon_title_resume": {
- "translation": "ต่อ",
- "defaultMessage": "Resume playback",
- "description": "Icon tool tip for resuming audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_send": {
- "translation": "ส่งข้อความ",
- "defaultMessage": "Send message",
- "description": "Icon tool tip for sending a message",
- "missing": false,
- "obsolete": false
- },
- "drag_file": {
- "translation": "ลากไฟล์มาที่นี่",
- "defaultMessage": "Drag file here",
- "description": "Prompt on the file drag-n-drop overlay banner",
- "missing": false,
- "obsolete": false
- },
- "drafty_video": {
- "translation": "กำลังบันทึกวิดีโอ",
- "defaultMessage": "Video recording",
- "description": "Comment for videos embedded in Drafty",
- "missing": false,
- "obsolete": false
- },
- "call_in_progress": {
- "translation": "กำลังดำเนินการ",
- "defaultMessage": "in progress",
- "description": "Label for call in progress",
- "missing": false,
- "obsolete": false
- },
- "menu_item_audio_call": {
- "translation": "โทร",
- "defaultMessage": "Call",
- "description": "Start audio call",
- "missing": false,
- "obsolete": false
- },
- "unrecognized_video_format": {
- "translation": "ไม่รู้จักรูปแบบของวิดีโอนี้",
- "defaultMessage": "Format of this video is not recognized",
- "description": "Error message when uploaded video is invalid",
- "missing": false,
- "obsolete": false
- },
- "loading_note": {
- "translation": "กำลังโหลด...",
- "defaultMessage": "Loading...",
- "description": "Message shown when component is loading",
- "missing": false,
- "obsolete": false
- },
- "password_i_have_code": {
- "translation": "ฉันมีรหัส",
- "defaultMessage": "I have code",
- "description": "Call to open field to enter password reset code",
- "missing": false,
- "obsolete": false
- },
- "label_reset_password_tel": {
- "translation": "ส่ง SMS เพื่อรีเซ็ตรหัสผ่าน:",
- "defaultMessage": "Send SMS to reset password",
- "description": "Label for password reset field by phone",
- "missing": false,
- "obsolete": false
- },
- "mobile_phone_number": {
- "translation": "หมายเลขโทรศัพท์มือถือ",
- "defaultMessage": "Mobile phone number",
- "description": "Prompt for entering a mobile phone number",
- "missing": false,
- "obsolete": false
- },
- "password_reset_sms_sent": {
- "translation": "ส่งข้อความพร้อมรหัสความปลอดภัยแล้ว",
- "defaultMessage": "A text message with security code has been sent.",
- "description": "Notification that the SMS with password reset instructions has been sent",
- "missing": false,
- "obsolete": false
- },
- "mobile_number_required": {
- "translation": "จำเป็นต้องระบุหมายเลขโทรศัพท์มือถือ",
- "defaultMessage": "Mobile phone number required",
- "description": "Error message",
- "missing": false,
- "obsolete": false
- },
- "current_email": {
- "translation": "อีเมลปัจจุบัน",
- "defaultMessage": "Current email",
- "description": "Label for old email",
- "missing": false,
- "obsolete": false
- },
- "current_phone": {
- "translation": "หมายเลขโทรศัพท์ปัจจุบัน",
- "defaultMessage": "Current phone number",
- "description": "Label for old phone",
- "missing": false,
- "obsolete": false
- },
- "new_email": {
- "translation": "อีเมลใหม่",
- "defaultMessage": "New email",
- "description": "Prompt for entering a new email",
- "missing": false,
- "obsolete": false
- },
- "new_phone_number": {
- "translation": "หมายเลขโทรศัพท์ใหม่",
- "defaultMessage": "New phone number",
- "description": "Prompt for entering a new telephone number",
- "missing": false,
- "obsolete": false
- },
- "change_email": {
- "translation": "เปลี่ยนอีเมล",
- "defaultMessage": "Change email",
- "description": "Prompt to change current email",
- "missing": false,
- "obsolete": false
- },
- "change_phone": {
- "translation": "เปลี่ยนหมายเลขโทรศัพท์",
- "defaultMessage": "Change phone number",
- "description": "Prompt to change current phone number",
- "missing": false,
- "obsolete": false
- },
- "call_busy": {
- "translation": "ยุ่งอยู่",
- "defaultMessage": "busy",
- "description": "Label for busy call line",
- "missing": false,
- "obsolete": false
- },
- "failed_to_init_audio": {
- "translation": "ไม่สามารถเริ่มต้นการบันทึกเสียงได้",
- "defaultMessage": "Failed to initialize audio recording",
- "description": "Error message when audio is not available",
- "missing": false,
- "obsolete": false
- },
- "password_reset_success": {
- "translation": "รีเซ็ตรหัสผ่านสำเร็จ",
- "defaultMessage": "Password reset successfully",
- "description": "Notification message that the password was successfully reset.",
- "missing": false,
- "obsolete": false
- },
- "scan_qr_code": {
- "translation": "สแกนรหัส QR",
- "defaultMessage": "Scan QR Code",
- "description": "Title for scanning QR code",
- "missing": false,
- "obsolete": false
- },
- "pin_message": {
- "translation": "พิน",
- "defaultMessage": "Pin",
- "description": "Menu item [Pin] for pinning message to the top.",
- "missing": false,
- "obsolete": false
- },
- "unpin_message": {
- "translation": "ปลดหมุด",
- "defaultMessage": "Unpin",
- "description": "Menu item [Unpin] for un-pinning the message.",
- "missing": false,
- "obsolete": false
- },
- "self_topic_comment": {
- "translation": "บันทึก ข้อความ ลิงก์ ไฟล์ที่บันทึกไว้เพื่ออนาคต",
- "defaultMessage": "Notes, messages, links, files saved for posterity",
- "description": "Comment for self topic for UI",
- "missing": false,
- "obsolete": false
- },
- "self_topic_name": {
- "translation": "ข้อความที่บันทึกไว้",
- "defaultMessage": "Saved messages",
- "description": "Name of self topic for UI",
- "missing": false,
- "obsolete": false
- },
- "message_not_found": {
- "translation": "ไม่พบข้อความ",
- "defaultMessage": "message not found",
- "description": "Pinned message is not found",
- "missing": false,
- "obsolete": false
- },
- "message_deleted": {
- "translation": "ข้อความถูกลบ",
- "defaultMessage": "message deleted",
- "description": "Pinned message is deleted",
- "missing": false,
- "obsolete": false
- },
- "alias_editing_placeholder": {
- "translation": "นามแฝง (ไม่บังคับ)",
- "defaultMessage": "Alias (optional)",
- "description": "Placeholder for editing user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "label_alias": {
- "translation": "นามแฝง:",
- "defaultMessage": "Alias:",
- "description": "Label for user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "alias_already_taken": {
- "translation": "(ใช้ไปแล้ว)",
- "defaultMessage": "(already taken)",
- "description": "Error message for alias already taken",
- "missing": false,
- "obsolete": false
- },
- "alias_invalid": {
- "translation": "(ไม่ถูกต้อง)",
- "defaultMessage": "(invalid)",
- "description": "Error message for invalid alias",
- "missing": false,
- "obsolete": false
- },
- "label_alias_edit": {
- "translation": "นามแฝง",
- "defaultMessage": "Alias",
- "description": "Label for editing user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "select_country": {
- "translation": "เลือกประเทศ",
- "defaultMessage": "Select country",
- "description": "Placeholder for the country selector",
- "missing": false,
- "obsolete": false
- },
- "text_copied": {
- "translation": "คัดลอกไปยังคลิปบอร์ดแล้ว",
- "defaultMessage": "Copied to clipboard",
- "description": "Notification that text has been copied to clipboard",
- "missing": false,
- "obsolete": false
- },
- "color_schema_dark": {
- "translation": "มืด",
- "defaultMessage": "Dark",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "color_schema_light": {
- "translation": "สว่าง",
- "defaultMessage": "Light",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "color_schema_system": {
- "translation": "ค่าเริ่มต้นของระบบ",
- "defaultMessage": "System default",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "label_color_schema": {
- "translation": "ธีม:",
- "defaultMessage": "Theme:",
- "description": "Label for selecting color scheme (dark, light) in Settings",
- "missing": false,
- "obsolete": false
- },
- "label_text_size": {
- "translation": "ขนาดตัวอักษร:",
- "defaultMessage": "Text size:",
- "description": "Label adjusting text size",
- "missing": false,
- "obsolete": false
- },
- "incognito_mode_description": {
- "translation": "ซ่อนสถานะออนไลน์และตัวบ่งชี้การพิมพ์ของคุณ",
- "defaultMessage": "Hide your online status and typing indicators.",
- "description": "Description for incognito mode toggle",
- "missing": false,
- "obsolete": false
- },
- "send_command": {
- "translation": "ส่งด้วย {key}",
- "defaultMessage": "Send on {key}",
- "description": "Config option to send message on hitting CTRL(or Cmd)-Enter",
- "missing": false,
- "obsolete": false
- },
- "send_command_explained": {
- "translation": "กด Enter เพื่อขึ้นบรรทัดใหม่",
- "defaultMessage": "Press Enter for new line",
- "description": "Explanation how to enter newline when [Send on CTRL-Enter] is enabled",
- "missing": false,
- "obsolete": false
- },
- "send_plain": {
- "translation": "ส่งด้วย Enter",
- "defaultMessage": "Send on Enter",
- "description": "Config option to send message on hitting Enter",
- "missing": false,
- "obsolete": false
- },
- "send_plain_explained": {
- "translation": "กด Shift + Enter เพื่อขึ้นบรรทัดใหม่",
- "defaultMessage": "Press Shift + Enter for new line",
- "description": "Explanation how to enter newline when [Send on Enter] is enabled",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_general": {
- "translation": "ทั่วไป",
- "defaultMessage": "General",
- "description": "Sidepanel title for AccGeneralView.",
- "missing": false,
- "obsolete": false
- },
- "label_keyboard": {
- "translation": "แป้นพิมพ์:",
- "defaultMessage": "Keyboard:",
- "description": "Label for send on enter settings",
- "missing": false,
- "obsolete": false
- },
- "pin_chat": {
- "translation": "ปักหมุด",
- "defaultMessage": "Pin",
- "description": "Menu item [Pin] for pinning chat to the top.",
- "missing": false,
- "obsolete": false
- },
- "unpin_chat": {
- "translation": "เลิกปักหมุด",
- "defaultMessage": "Unpin",
- "description": "Menu item [Unpin] for un-pinning the chat.",
- "missing": false,
- "obsolete": false
- },
- "subscriber_count": {
- "translation": "{count, plural, other {{count, number} ผู้ติดตาม}}",
- "defaultMessage": "{count, plural, one {{count, number} subscriber} two {{count, number} subscribers} three {{count, number} subscribers} few {{count, number} subscribers} many {{count, number} subscribers} other {{count, number} subscribers}}",
- "description": "Count of channel subscribers",
- "missing": false,
- "obsolete": false
- },
- "label_member_count": {
- "translation": "สมาชิก:",
- "defaultMessage": "Members:",
- "description": "Label for the count of group topic members",
- "missing": false,
- "obsolete": false
- },
- "label_subscriber_count": {
- "translation": "ผู้ติดตาม:",
- "defaultMessage": "Subscribers:",
- "description": "Label for the count of channel subscribers",
- "missing": false,
- "obsolete": false
- },
- "member_count": {
- "translation": "{count, plural, other {{count, number} สมาชิก}}",
- "defaultMessage": "{count, plural, one {{count, number} member} two {{count, number} members} three {{count, number} members} few {{count, number} members} many {{count, number} members} other {{count, number} members}}",
- "description": "Count of group topic members",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_image": {
- "translation": "รูปภาพ",
- "defaultMessage": "image",
- "description": "Tab title Image",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_pattern": {
- "translation": "ลายลวดลาย",
- "defaultMessage": "pattern",
- "description": "Tab title Pattern",
- "missing": false,
- "obsolete": false
- },
- "button_restore": {
- "translation": "คืนค่าเริ่มต้น",
- "defaultMessage": "Restore default",
- "description": "Reset setting to default",
- "missing": false,
- "obsolete": false
- },
- "wallpapers": {
- "translation": "วอลเปเปอร์",
- "defaultMessage": "Wallpapers",
- "description": "Link or title for working with wallpapers.",
- "missing": false,
- "obsolete": false
- },
- "label_blur_wallpaper": {
- "translation": "เบลอ:",
- "defaultMessage": "Blur:",
- "description": "Label adjusting blur amount",
- "missing": false,
- "obsolete": false
- },
- "label_scan_id": {
- "translation": "สแกน ID ของฉัน:",
- "defaultMessage": "Scan my ID:",
- "description": "Label [Scan my ID:]",
- "missing": false,
- "obsolete": false
- },
- "unknown_name": {
- "translation": "ไม่ทราบ",
- "defaultMessage": "Unknown",
- "description": "Name to show when the name is missing",
- "missing": false,
- "obsolete": false
- },
- "save_action": {
- "translation": "บันทึก",
- "defaultMessage": "Save",
- "description": "Label for Save button",
- "missing": false,
- "obsolete": false
- },
- "cannot_parse_vcard": {
- "translation": "ไม่สามารถอ่านไฟล์ vCard ได้",
- "defaultMessage": "Cannot parse vCard file.",
- "description": "Error message when vCard file cannot be parsed",
- "missing": false,
- "obsolete": false
- },
- "chat_now": {
- "translation": "แชท",
- "defaultMessage": "Chat",
- "description": "Label for message button in contact card",
- "missing": false,
- "obsolete": false
- },
- "find_user": {
- "translation": "ค้นหา",
- "defaultMessage": "Find",
- "description": "Label for search button in contact card",
- "missing": false,
- "obsolete": false
- },
- "label_contact_card": {
- "translation": "นามบัตร",
- "defaultMessage": "Contact Card",
- "description": "Label for contact card preview",
- "missing": false,
- "obsolete": false
- },
- "label_contacts": {
- "translation": "ผู้ติดต่อ",
- "defaultMessage": "Contacts",
- "description": "Label for contact information",
- "missing": false,
- "obsolete": false
- },
- "add_members_prompt": {
- "translation": "เพิ่มสมาชิก",
- "defaultMessage": "add members",
- "description": "Placeholder in group members input field",
- "missing": false,
- "obsolete": false
- }
-}
diff --git a/src/i18n/uk.json b/src/i18n/uk.json
deleted file mode 100644
index 6d76d4fd..00000000
--- a/src/i18n/uk.json
+++ /dev/null
@@ -1,1966 +0,0 @@
-{
- "action_block_contact": {
- "translation": "Заблокувати контакт",
- "defaultMessage": "Block Contact",
- "description": "Flat button [Block Contact]",
- "missing": false,
- "obsolete": false
- },
- "action_cancel": {
- "translation": "Скасувати",
- "defaultMessage": "cancel",
- "description": "Call to action [cancel]",
- "missing": false,
- "obsolete": false
- },
- "action_clear_messages": {
- "translation": "Очистити повідомлення",
- "defaultMessage": "Clear Messages",
- "description": "Flat button [Clear Messages] (soft-delete messages)",
- "missing": false,
- "obsolete": false
- },
- "action_delete_messages": {
- "translation": "Очистити повідомлення для всіх",
- "defaultMessage": "Clear Messages for All",
- "description": "Flat button [Clear for All] (hard-delete all messages)",
- "missing": false,
- "obsolete": false
- },
- "action_leave_chat": {
- "translation": "Покинути чат",
- "defaultMessage": "Leave Conversation",
- "description": "Flat button [Leave Conversation]",
- "missing": false,
- "obsolete": false
- },
- "action_report_chat": {
- "translation": "Сповістити про порушення",
- "defaultMessage": "Report Conversation",
- "description": "Flat button [Report Group]",
- "missing": false,
- "obsolete": false
- },
- "archived_contacts": {
- "translation": "Чати в архіві ({count})",
- "defaultMessage": "Archived contacts ({count})",
- "description": "Label for archived chats",
- "missing": false,
- "obsolete": false
- },
- "badge_danger": {
- "translation": "Підозрільний",
- "defaultMessage": "Untrustworthy",
- "description": "Suspicious or untrustworthy account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_owner": {
- "translation": "власник",
- "defaultMessage": "owner",
- "description": "Badge for indicating the owner",
- "missing": false,
- "obsolete": false
- },
- "badge_staff": {
- "translation": "Адміністрація",
- "defaultMessage": "Staff-managed",
- "description": "Explanation of a staff-managed account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_verified": {
- "translation": "Верифікований",
- "defaultMessage": "Verified/official",
- "description": "Explanation of a verified account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_you": {
- "translation": "ви",
- "defaultMessage": "you",
- "description": "Badge for indicating the current user",
- "missing": false,
- "obsolete": false
- },
- "block_contact_warning": {
- "translation": "Ви дійсно бажаєте заблокувати цей контакт?",
- "defaultMessage": "Are you sure you want to block this contact?",
- "description": "Alert dialog warning when blocking a contact.",
- "missing": false,
- "obsolete": false
- },
- "blocked_contacts_link": {
- "translation": "Заблоковані контакти ({count})",
- "defaultMessage": "Blocked contacts ({count})",
- "description": "Blocked contacts link",
- "missing": false,
- "obsolete": false
- },
- "button_add_members": {
- "translation": "Додати учасників",
- "defaultMessage": "Add members",
- "description": "Flat button [Add members] (to topic)",
- "missing": false,
- "obsolete": false
- },
- "button_cancel": {
- "translation": "Скасувати",
- "defaultMessage": "Cancel",
- "description": "Button [Cancel]",
- "missing": false,
- "obsolete": false
- },
- "button_confirm": {
- "translation": "Підтвердити",
- "defaultMessage": "Confirm",
- "description": "Button [Confirm]",
- "missing": false,
- "obsolete": false
- },
- "button_create": {
- "translation": "Створити",
- "defaultMessage": "Create",
- "description": "Button [Create]",
- "missing": false,
- "obsolete": false
- },
- "button_delete_account": {
- "translation": "Видалити акаунт",
- "defaultMessage": "Delete account",
- "description": "Button [Delete account]",
- "missing": false,
- "obsolete": false
- },
- "button_edit": {
- "translation": "Змінити",
- "defaultMessage": "Edit",
- "description": "Call to action [Edit]",
- "missing": false,
- "obsolete": false
- },
- "button_logout": {
- "translation": "Вийти",
- "defaultMessage": "Logout",
- "description": "Button [Logout]",
- "missing": false,
- "obsolete": false
- },
- "button_ok": {
- "translation": "OK",
- "defaultMessage": "OK",
- "description": "Button [OK]",
- "missing": false,
- "obsolete": false
- },
- "button_reset": {
- "translation": "Змінити",
- "defaultMessage": "Reset",
- "description": "Button [Reset]",
- "missing": false,
- "obsolete": false
- },
- "button_send_request": {
- "translation": "Надіслати",
- "defaultMessage": "Send request",
- "description": "Button [Send request]",
- "missing": false,
- "obsolete": false
- },
- "button_sign_in": {
- "translation": "Увійти",
- "defaultMessage": "Sign in",
- "description": "Button [Sign In]",
- "missing": false,
- "obsolete": false
- },
- "button_sign_up": {
- "translation": "Створити акаунт",
- "defaultMessage": "Sign up",
- "description": "Create account button [Sign Up]",
- "missing": false,
- "obsolete": false
- },
- "button_subscribe": {
- "translation": "Підписатися",
- "defaultMessage": "Subscribe",
- "description": "Button [Subscribe]",
- "missing": false,
- "obsolete": false
- },
- "button_update": {
- "translation": "Оновити",
- "defaultMessage": "Update",
- "description": "Button [Update]",
- "missing": false,
- "obsolete": false
- },
- "cannot_initiate_file_upload": {
- "translation": "Помилка завантаження файлу.",
- "defaultMessage": "Cannot initiate file upload.",
- "description": "Generic error messagewhen attachment fails",
- "missing": false,
- "obsolete": false
- },
- "channel": {
- "translation": "канал",
- "defaultMessage": "channel",
- "description": "Subtitle shown for channels in MessagesView instead of last seen",
- "missing": false,
- "obsolete": false
- },
- "channel_prompt": {
- "translation": "Створити канал",
- "defaultMessage": "This is a channel",
- "description": "Checkbox label when creating a channel",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation": {
- "translation": "Вас запросили розпочати новий чат. Як ви хочете вчинити?",
- "defaultMessage": "You are invited to start a new chat. What would you like to do?",
- "description": "New chat invitation message: [Accept] [Ignore] [Block].",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_accept": {
- "translation": "Прийняти",
- "defaultMessage": "Accept",
- "description": "Action [Accept] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_block": {
- "translation": "Заблокувати",
- "defaultMessage": "Block",
- "description": "Action [Block] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_ignore": {
- "translation": "Ігнорувати",
- "defaultMessage": "Ignore",
- "description": "Action [Ignore] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "clear_messages_warning": {
- "translation": "Ви дійсно бажаєте видалити всі повідомлення в чаті? Їх неможливо буде відновити.",
- "defaultMessage": "Are you sure you want to clear all messages? It cannot be undone.",
- "description": "Alert dialog warning when deleting all messages.",
- "missing": false,
- "obsolete": false
- },
- "code_doesnot_match": {
- "translation": "Код не збігається",
- "defaultMessage": "Code does not match",
- "description": "Error message when the credential validation code is incorrect.",
- "missing": false,
- "obsolete": false
- },
- "contacts_not_found": {
- "translation": "Чатів немає\n¯∖_(ツ)_/¯",
- "defaultMessage": "You have no chats\\n¯∖_(ツ)_/¯",
- "description": "HTML message shown in ContactList when no contacts are found",
- "missing": false,
- "obsolete": false
- },
- "contacts_not_found_short": {
- "translation": "Немає контактів для запиту ''{query}''",
- "defaultMessage": "No contacts match ''{query}''",
- "description": "Shown in ContactsView when search returned no results",
- "missing": false,
- "obsolete": false
- },
- "credential_email_prompt": {
- "translation": "Реєстраційний емейл",
- "defaultMessage": "Your registration email",
- "description": "Placeholder for entering email",
- "missing": false,
- "obsolete": false
- },
- "delete_account": {
- "translation": "Видалити акаунт",
- "defaultMessage": "Delete account",
- "description": "Title for delete account warning",
- "missing": false,
- "obsolete": false
- },
- "delete_account_warning": {
- "translation": "Ви впевнені, що хочете видалити свій акаунт? Його неможливо буде відновити.",
- "defaultMessage": "Are you sure you want to delete your account? It cannot be undone.",
- "description": "Warning message when deleting an account",
- "missing": false,
- "obsolete": false
- },
- "delete_messages_warning": {
- "translation": "Ви впевнені, що бажаєте видалити всі повідомлення для всіх? Їх неможливо буде відновити.",
- "defaultMessage": "Are you sure you want to delete all messages for everyone? It cannot be undone.",
- "description": "Alert dialog warning when hard-deleting all messages.",
- "missing": false,
- "obsolete": false
- },
- "download_action": {
- "translation": "завантажити",
- "defaultMessage": "download",
- "description": "Call to action [download]",
- "missing": false,
- "obsolete": false
- },
- "drafty_attachment": {
- "translation": "Аттачмент",
- "defaultMessage": "Attachment",
- "description": "Comment for attachment in Drafty",
- "missing": false,
- "obsolete": false
- },
- "drafty_form": {
- "translation": "Форма:",
- "defaultMessage": "Form:",
- "description": "Comment for form in Drafty",
- "missing": false,
- "obsolete": false
- },
- "drafty_image": {
- "translation": "Картинка",
- "defaultMessage": "Picture",
- "description": "Comment for embedded images in Drafty",
- "missing": false,
- "obsolete": false
- },
- "email_dative": {
- "translation": "емейлу",
- "defaultMessage": "email",
- "description": "Dative case of 'email', i.e. 'email' in 'by email'",
- "missing": false,
- "obsolete": false
- },
- "email_prompt": {
- "translation": "Емейл, напр. ivan@example.com",
- "defaultMessage": "Email, e.g. jdoe@example.com",
- "description": "Input placeholder for email entry",
- "missing": false,
- "obsolete": false
- },
- "enable_peers_messaging": {
- "translation": "Розблокувати.",
- "defaultMessage": "Enable",
- "description": "Call to action to enable peer's messaging",
- "missing": false,
- "obsolete": false
- },
- "enter_confirmation_code_prompt": {
- "translation": "Код підтвердження, отриманий по {method}:",
- "defaultMessage": "Confirmation code",
- "description": "Request to enter confirmation code",
- "missing": false,
- "obsolete": false
- },
- "error_invalid_id": {
- "translation": "Невірний ID",
- "defaultMessage": "Invalid ID",
- "description": "Error message",
- "missing": false,
- "obsolete": false
- },
- "file_attachment_too_large": {
- "translation": "Розмір файла {size} перевищує {limit} ліміт.",
- "defaultMessage": "The file size {size} exceeds the {limit} limit.",
- "description": "Error message when attachment is too large",
- "missing": false,
- "obsolete": false
- },
- "forgot_password_link": {
- "translation": "Нагадати пароль",
- "defaultMessage": "Forgot password?",
- "description": "Link to Reset password form",
- "missing": false,
- "obsolete": false
- },
- "full_name_prompt": {
- "translation": "Повне ім'я, напр. Дмитро Соколов",
- "defaultMessage": "Full name, e.g. John Doe",
- "description": "Input placeholder for person's full name",
- "missing": false,
- "obsolete": false
- },
- "granted_permissions": {
- "translation": "Отримано",
- "defaultMessage": "Granted",
- "description": "Title for permissions",
- "missing": false,
- "obsolete": false
- },
- "group_has_no_members": {
- "translation": "Нема учасників",
- "defaultMessage": "No members",
- "description": "Shown in place of group members",
- "missing": false,
- "obsolete": false
- },
- "group_user_id_prompt": {
- "translation": "ID чату або користувача",
- "defaultMessage": "Group or User ID",
- "description": "Prompt for entering user or group ID",
- "missing": false,
- "obsolete": false
- },
- "image_caption_prompt": {
- "translation": "Підпис до фото",
- "defaultMessage": "Image caption",
- "description": "Prompt in SendMessage for attached image",
- "missing": false,
- "obsolete": false
- },
- "invalid_content": {
- "translation": "повідомлення не читається",
- "defaultMessage": "invalid content",
- "description": "Shown when the message is unreadable",
- "missing": false,
- "obsolete": false
- },
- "invalid_security_token": {
- "translation": "Токен некоректний",
- "defaultMessage": "Invalid security token",
- "description": "Error message when resetting password.",
- "missing": false,
- "obsolete": false
- },
- "label_client": {
- "translation": "Клієнт:",
- "defaultMessage": "Client:",
- "description": "Label for a client version",
- "missing": false,
- "obsolete": false
- },
- "label_content_type": {
- "translation": "Тип:",
- "defaultMessage": "Content type:",
- "description": "Label for file content type (mime)",
- "missing": false,
- "obsolete": false
- },
- "label_default_access_mode": {
- "translation": "Доступу по замовчуванню:",
- "defaultMessage": "Default access mode:",
- "description": "Label for default access mode",
- "missing": false,
- "obsolete": false
- },
- "label_file_name": {
- "translation": "Ім'я файлу:",
- "defaultMessage": "File name:",
- "description": "Label for a file name",
- "missing": false,
- "obsolete": false
- },
- "label_group_members": {
- "translation": "Учасники чату:",
- "defaultMessage": "Group members:",
- "description": "Section title or label",
- "missing": false,
- "obsolete": false
- },
- "label_incognito_mode": {
- "translation": "Режим інкогніто:",
- "defaultMessage": "Incognito mode:",
- "description": "Label for incognito mode toggle",
- "missing": false,
- "obsolete": false
- },
- "label_message_sound": {
- "translation": "Звук нового повідомлення:",
- "defaultMessage": "Message sound:",
- "description": "Label for message sounds toggle",
- "missing": false,
- "obsolete": false
- },
- "label_muting_topic": {
- "translation": "Без сповіщень",
- "defaultMessage": "Muted:",
- "description": "Label for Muting/unmuting the topic",
- "missing": false,
- "obsolete": false
- },
- "label_other_user": {
- "translation": "Інший",
- "defaultMessage": "Other",
- "description": "Label for the other user when the user is unnamed",
- "missing": false,
- "obsolete": false
- },
- "label_password": {
- "translation": "Пароль",
- "defaultMessage": "Password",
- "description": "Label for password editing",
- "missing": false,
- "obsolete": false
- },
- "label_permissions": {
- "translation": "Права доступу:",
- "defaultMessage": "Permissions:",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "label_private": {
- "translation": "Коментарій",
- "defaultMessage": "Private comment",
- "description": "Label for editing 'private'",
- "missing": false,
- "obsolete": false
- },
- "label_push_notifications": {
- "translation": "Сповіщення:",
- "defaultMessage": "Notification alerts:",
- "description": "Label for push notifications switch",
- "missing": false,
- "obsolete": false
- },
- "label_push_notifications_disabled": {
- "translation": "Сповіщення (потребують HTTPS):",
- "defaultMessage": "Notification alerts (requires HTTPS):",
- "description": "Label for push notifications switch",
- "missing": false,
- "obsolete": false
- },
- "label_reset_password": {
- "translation": "Надіслати емейл для зміни пароля:",
- "defaultMessage": "Send a password reset email",
- "description": "Label for password reset field",
- "missing": false,
- "obsolete": false
- },
- "label_sdk": {
- "translation": "SDK:",
- "defaultMessage": "SDK:",
- "missing": false,
- "obsolete": false
- },
- "label_server": {
- "translation": "Сервер:",
- "defaultMessage": "Server:",
- "description": "Label for a server version",
- "missing": false,
- "obsolete": false
- },
- "label_server_address": {
- "translation": "Адреса сервера:",
- "defaultMessage": "Server address:",
- "missing": false,
- "obsolete": false
- },
- "label_server_to_use": {
- "translation": "Використання сервера:",
- "defaultMessage": "Server to use:",
- "description": "Label for server selector in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "label_size": {
- "translation": "Розмір:",
- "defaultMessage": "Size:",
- "description": "Label for file size",
- "missing": false,
- "obsolete": false
- },
- "label_topic_name": {
- "translation": "Назва",
- "defaultMessage": "Name",
- "description": "Label for editing topic name",
- "missing": false,
- "obsolete": false
- },
- "label_user_contacts": {
- "translation": "Конакти",
- "defaultMessage": "Contacts",
- "description": "Label for user contacts",
- "missing": false,
- "obsolete": false
- },
- "label_user_id": {
- "translation": "ID:",
- "defaultMessage": "ID:",
- "description": "Label for user address (ID)",
- "missing": false,
- "obsolete": false
- },
- "label_wire_transport": {
- "translation": "З'єднання:",
- "defaultMessage": "Wire transport:",
- "description": "Label for wire transport selection in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "label_you": {
- "translation": "Ви:",
- "defaultMessage": "You:",
- "description": "Label for the current user",
- "missing": false,
- "obsolete": false
- },
- "label_your_name": {
- "translation": "Ваше ім'я",
- "defaultMessage": "Your name",
- "description": "Label for full name editing",
- "missing": false,
- "obsolete": false
- },
- "label_your_permissions": {
- "translation": "Ваші права доступу:",
- "defaultMessage": "Your permissions:",
- "description": "Label for current user permissions",
- "missing": false,
- "obsolete": false
- },
- "last_seen_timestamp": {
- "translation": "Був активний",
- "defaultMessage": "Last seen: {timestamp}",
- "description": "Label for the timestamp of when the user or topic was last online",
- "missing": false,
- "obsolete": false
- },
- "leave_chat_warning": {
- "translation": "Ви дійсно бажаєте покинути цей чат?",
- "defaultMessage": "Are you sure you want to leave this conversation?",
- "description": "Alert dialog warning when unsubscribing from a chat.",
- "missing": false,
- "obsolete": false
- },
- "link_contact_us": {
- "translation": "Зв'язатися з нами",
- "defaultMessage": "Contact Us",
- "description": "Ancor text for contacting us by email",
- "missing": false,
- "obsolete": false
- },
- "link_privacy_policy": {
- "translation": "Політика конфіденційності",
- "defaultMessage": "Privacy Policy",
- "description": "Ancor text for privacy policy link",
- "missing": false,
- "obsolete": false
- },
- "link_terms_of_service": {
- "translation": "Умови сервісу",
- "defaultMessage": "Terms of Service",
- "description": "Ancor text for terms of service link",
- "missing": false,
- "obsolete": false
- },
- "login_prompt": {
- "translation": "Логін",
- "defaultMessage": "Login",
- "description": "Placeholer for username/login",
- "missing": false,
- "obsolete": false
- },
- "menu_item_archive_topic": {
- "translation": "В архів",
- "defaultMessage": "Archive",
- "description": "Move topic from the list of active chats to archive",
- "missing": false,
- "obsolete": false
- },
- "menu_item_block": {
- "translation": "Заблокувати",
- "defaultMessage": "Block",
- "description": "Block topic or user",
- "missing": false,
- "obsolete": false
- },
- "menu_item_clear_messages": {
- "translation": "Очистити усі повідомлення",
- "defaultMessage": "Clear messages",
- "description": "Delete all messages",
- "missing": false,
- "obsolete": false
- },
- "menu_item_clear_messages_for_all": {
- "translation": "Очистити для всіх",
- "defaultMessage": "Clear for All",
- "description": "Delete all message(s) for all members",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete": {
- "translation": "Видалити повідомлення",
- "defaultMessage": "Delete",
- "description": "Delete selected messages",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete_for_all": {
- "translation": "Видалити для всіх",
- "defaultMessage": "Delete for All",
- "description": "Delete selected message(s) for all members",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete_topic": {
- "translation": "Видалити чат",
- "defaultMessage": "Delete",
- "description": "Delete entire topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_edit_permissions": {
- "translation": "Права доступу",
- "defaultMessage": "Edit permissions",
- "description": "Menu item [Edit permissions]",
- "missing": false,
- "obsolete": false
- },
- "menu_item_info": {
- "translation": "Інформація",
- "defaultMessage": "Info",
- "description": "Show extended topic information",
- "missing": false,
- "obsolete": false
- },
- "menu_item_member_delete": {
- "translation": "Відписати",
- "defaultMessage": "Remove",
- "description": "Remove user from topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_mute": {
- "translation": "Не повідомляти",
- "defaultMessage": "Mute",
- "description": "Turn off notifications",
- "missing": false,
- "obsolete": false
- },
- "menu_item_restore_topic": {
- "translation": "Розархівувати",
- "defaultMessage": "Restore",
- "description": "Restore topic from archive",
- "missing": false,
- "obsolete": false
- },
- "menu_item_send_retry": {
- "translation": "Надіслати заново",
- "defaultMessage": "Retry",
- "description": "Retry sending message",
- "missing": false,
- "obsolete": false
- },
- "menu_item_unblock": {
- "translation": "Разблокувати",
- "defaultMessage": "Unblock",
- "description": "Unblock topic or user",
- "missing": false,
- "obsolete": false
- },
- "menu_item_unmute": {
- "translation": "Повідомляти",
- "defaultMessage": "Unmute",
- "description": "Turn notifications on",
- "missing": false,
- "obsolete": false
- },
- "message_sending": {
- "translation": "відправлення...",
- "defaultMessage": "sending...",
- "description": "Message being sent, in place of time stamp",
- "missing": false,
- "obsolete": false
- },
- "message_sending_failed": {
- "translation": "помилка",
- "defaultMessage": "failed",
- "description": "Failed to send message, in place of time stamp",
- "missing": false,
- "obsolete": false
- },
- "messages_not_readable": {
- "translation": "немає доступу до повідомлень",
- "defaultMessage": "no access to messages",
- "description": "Message shown in topic without the read access",
- "missing": false,
- "obsolete": false
- },
- "messaging_disabled_prompt": {
- "translation": "Відправка недоступна",
- "defaultMessage": "Messaging disabled",
- "description": "Prompt in SendMessage in read-only topic",
- "missing": false,
- "obsolete": false
- },
- "more_online_members": {
- "translation": "+ще {overflow}",
- "defaultMessage": "+{overflow} more",
- "description": "Shown in MessagesView title bar when the number of online subscribers exceeds MAX_ONLINE_IN_TOPIC",
- "missing": false,
- "obsolete": false
- },
- "new_message_prompt": {
- "translation": "Нове повідомлення",
- "defaultMessage": "New message",
- "description": "Prompt in send message field",
- "missing": false,
- "obsolete": false
- },
- "new_password_placeholder": {
- "translation": "Введіть новий пароль",
- "defaultMessage": "Enter new password",
- "description": "Placeholder for entering new password",
- "missing": false,
- "obsolete": false
- },
- "no_connection": {
- "translation": "Немає зв'язку",
- "defaultMessage": "No connection",
- "description": "Warning that the user is offline.",
- "missing": false,
- "obsolete": false
- },
- "no_contacts": {
- "translation": "Ви не маєте контактів :-(",
- "defaultMessage": "You have no contacts :-(",
- "description": "Shown in ContactsView when the user has no contacts",
- "missing": false,
- "obsolete": false
- },
- "numeric_confirmation_code_prompt": {
- "translation": "Тільки цифри",
- "defaultMessage": "Numbers only",
- "description": "Prompt for numeric conformation code",
- "missing": false,
- "obsolete": false
- },
- "online_now": {
- "translation": "онлайн",
- "defaultMessage": "online now",
- "description": "Indicator that the user or topic is currently online",
- "missing": false,
- "obsolete": false
- },
- "password_prompt": {
- "translation": "Пароль",
- "defaultMessage": "Password",
- "description": "Placeholder/prompt for entering password",
- "missing": false,
- "obsolete": false
- },
- "password_unchanged_prompt": {
- "translation": "Не змінено",
- "defaultMessage": "Unchanged",
- "description": "Message in editor while password is unchanged",
- "missing": false,
- "obsolete": false
- },
- "peers_messaging_disabled": {
- "translation": "Чат заблоковано у кореспондента.",
- "defaultMessage": "Peer's messaging is disabled.",
- "description": "Shown when the p2p peer's messaging is disabled",
- "missing": false,
- "obsolete": false
- },
- "permission_admin": {
- "translation": "Підтверджувати ({val})",
- "defaultMessage": "Approve ({val})",
- "description": "Name of A permission",
- "missing": false,
- "obsolete": false
- },
- "permission_delete": {
- "translation": "Видаляти ({val})",
- "defaultMessage": "Delete ({val})",
- "description": "Name of D permission",
- "missing": false,
- "obsolete": false
- },
- "permission_join": {
- "translation": "Підписуватись ({val})",
- "defaultMessage": "Join ({val})",
- "description": "Name of J permission",
- "missing": false,
- "obsolete": false
- },
- "permission_owner": {
- "translation": "Власник ({val})",
- "defaultMessage": "Owner ({val})",
- "description": "Name of O permission",
- "missing": false,
- "obsolete": false
- },
- "permission_pres": {
- "translation": "Повідомляти ({val})",
- "defaultMessage": "Get notified ({val})",
- "description": "Name of P permission",
- "missing": false,
- "obsolete": false
- },
- "permission_read": {
- "translation": "Читати ({val})",
- "defaultMessage": "Read ({val})",
- "description": "Name of R permission",
- "missing": false,
- "obsolete": false
- },
- "permission_share": {
- "translation": "Запрошувати ({val})",
- "defaultMessage": "Share ({val})",
- "description": "Name of S permission",
- "missing": false,
- "obsolete": false
- },
- "permission_write": {
- "translation": "Писати ({val})",
- "defaultMessage": "Write ({val})",
- "description": "Name of W permission",
- "missing": false,
- "obsolete": false
- },
- "phone_dative": {
- "translation": "телефону",
- "defaultMessage": "phone",
- "description": "Dative case of 'phone', i.e. 'phone' in 'by phone'",
- "missing": false,
- "obsolete": false
- },
- "private_editing_placeholder": {
- "translation": "Видно тільки вам",
- "defaultMessage": "Visible to you only",
- "description": "Placeholder for editing 'private'",
- "missing": false,
- "obsolete": false
- },
- "push_init_failed": {
- "translation": "Помилка ініціалізації пуш сповіщень",
- "defaultMessage": "Failed to initialize push notifications",
- "description": "Error message when push notifications have failed to initialize.",
- "missing": false,
- "obsolete": false
- },
- "reconnect_countdown": {
- "translation": "Немає зв'язку. Підключення через {seconds}…",
- "defaultMessage": "Disconnected. Reconnecting in {seconds}…",
- "description": "Message shown when an app update is available.",
- "missing": false,
- "obsolete": false
- },
- "reconnect_now": {
- "translation": "Підключити зараз.",
- "defaultMessage": "Try now",
- "description": "Prompt for reconnecting now",
- "missing": false,
- "obsolete": false
- },
- "reload_update": {
- "translation": "Оновити",
- "defaultMessage": "Reload",
- "description": "Call to action to reload application when update is available.",
- "missing": false,
- "obsolete": false
- },
- "report_chat_warning": {
- "translation": "Ви дійсно бажаєте повідомити про порушення і заблокувати цей?",
- "defaultMessage": "Are you sure you want to block and report this conversation?",
- "description": "Alert dialog warning when reporting a conversation for abuse",
- "missing": false,
- "obsolete": false
- },
- "requested_permissions": {
- "translation": "Потрібні",
- "defaultMessage": "Requested",
- "description": "Title for permissions",
- "missing": false,
- "obsolete": false
- },
- "save_attachment": {
- "translation": "зберегти",
- "defaultMessage": "save",
- "description": "Call to save an attachment",
- "missing": false,
- "obsolete": false
- },
- "search_for_contacts": {
- "translation": "Пошук контактів",
- "defaultMessage": "Use search to find contacts",
- "description": "Text shown in contacts view when user entered no search query.",
- "missing": false,
- "obsolete": false
- },
- "search_no_results": {
- "translation": "Нічого не знайдено",
- "defaultMessage": "Search returned no results",
- "description": "Text shown in contacts view when query returned no results.",
- "missing": false,
- "obsolete": false
- },
- "search_placeholder": {
- "translation": "Список, напр. email:alice@example.com, tel:+17025550003...",
- "defaultMessage": "List like alice@example.com, +17025550003...",
- "description": "Placeholder in contacts search field",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_notifications": {
- "translation": "Сповіщення",
- "defaultMessage": "Notifications",
- "description": "Sidepanel title for AccNotificationsView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_support": {
- "translation": "Підтримка",
- "defaultMessage": "Support",
- "description": "Sidepanel title for AccSupportView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_account_settings": {
- "translation": "Налаштування акаунту",
- "defaultMessage": "Account Settings",
- "description": "Sidepanel title for AccountSettingsView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_archive": {
- "translation": "Архів чатів",
- "defaultMessage": "Archived Chats",
- "description": "Sidepanel title for ContactsView-Archive.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_blocked": {
- "translation": "Заблоковані чати",
- "defaultMessage": "Blocked Chats",
- "description": "Sidepanel title for ContactsView-Blocked.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_cred": {
- "translation": "Підтвердити",
- "defaultMessage": "Confirm Credentials",
- "description": "Sidepanel title for ValidationView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_login": {
- "translation": "Авторизація",
- "defaultMessage": "Sign In",
- "description": "Sidepanel title for LoginView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_newtpk": {
- "translation": "Новий чат",
- "defaultMessage": "Start New Chat",
- "description": "Sidepanel title for NewTopicView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_register": {
- "translation": "Зареєструватися",
- "defaultMessage": "Create Account",
- "description": "Sidepanel title for CreateAccountView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_reset": {
- "translation": "Змінити пароль",
- "defaultMessage": "Reset Password",
- "description": "Sidepanel title for PasswordResetView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_settings": {
- "translation": "Налаштування",
- "defaultMessage": "Settings",
- "description": "Sidepanel title for SettingsView.",
- "missing": false,
- "obsolete": false
- },
- "stay_logged_in": {
- "translation": "Запам'ятати",
- "defaultMessage": "Stay logged in",
- "description": "Label for a checkbox",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_find_user": {
- "translation": "знайти",
- "defaultMessage": "find",
- "description": "Tab title Find",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_group_by_id": {
- "translation": "по id",
- "defaultMessage": "by id",
- "description": "Tab title Find topic by ID",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_new_group": {
- "translation": "створити",
- "defaultMessage": "new group",
- "description": "Tab title New Group",
- "missing": false,
- "obsolete": false
- },
- "tags_editor_no_tags": {
- "translation": "Додайте теги",
- "defaultMessage": "Add some tags",
- "description": "Tag editor prompt when no tags are found.",
- "missing": false,
- "obsolete": false
- },
- "tags_not_found": {
- "translation": "Теґів немає. Додайте.",
- "defaultMessage": "No tags defined. Add some.",
- "description": "",
- "missing": false,
- "obsolete": false
- },
- "title_all_contacts": {
- "translation": "Всі контакти",
- "defaultMessage": "All Contacts",
- "description": "Section title [All Contacts]",
- "missing": false,
- "obsolete": false
- },
- "title_group_members": {
- "translation": "Учасники",
- "defaultMessage": "Group Members",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "title_manage_tags": {
- "translation": "Редагувати",
- "defaultMessage": "Manage",
- "description": "Section title for the list of tags",
- "missing": false,
- "obsolete": false
- },
- "title_not_found": {
- "translation": "Не знайдено",
- "defaultMessage": "Not found",
- "description": "Title shown when topic is not found",
- "missing": false,
- "obsolete": false
- },
- "title_permissions": {
- "translation": "Права доступу",
- "defaultMessage": "Permissions",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "title_tag_manager": {
- "translation": "Теги для пошуку",
- "defaultMessage": "Tags (search & discovery)",
- "description": "Section title for TagManager",
- "missing": false,
- "obsolete": false
- },
- "topic_block_warning": {
- "translation": "Ви дійсно бажаєте заблокувати цей чат?",
- "defaultMessage": "Are you sure you want to block this conversation?",
- "description": "Alert warning when blocking a topic.",
- "missing": false,
- "obsolete": false
- },
- "topic_delete_warning": {
- "translation": "Ви дійсно бажаєте видалити цей чат?",
- "defaultMessage": "Are you sure you want to delete this conversation? It cannot be undone.",
- "description": "Alert warning when deleting entire topic",
- "missing": false,
- "obsolete": false
- },
- "topic_name_editing_placeholder": {
- "translation": "Назва чата",
- "defaultMessage": "Freeform name of the group",
- "description": "Prompt for entering topic name",
- "missing": false,
- "obsolete": false
- },
- "unnamed_topic": {
- "translation": "Без назви",
- "defaultMessage": "Unnamed",
- "description": "Title shown when the topic has no name",
- "missing": false,
- "obsolete": false
- },
- "update_available": {
- "translation": "Є нова версія програми.",
- "defaultMessage": "Update available.",
- "description": "Message shown when an app update is available.",
- "missing": false,
- "obsolete": false
- },
- "upload_finishing": {
- "translation": "завершення...",
- "defaultMessage": "finishing...",
- "description": "Notification that upload is finishing",
- "missing": false,
- "obsolete": false
- },
- "user_not_found": {
- "translation": "Не найдено",
- "defaultMessage": "Not found",
- "description": "In place of a user's full name when the user is not found.",
- "missing": false,
- "obsolete": false
- },
- "description_editing_placeholder": {
- "translation": "Опис (не обов'язково)",
- "defaultMessage": "Description (optional)",
- "description": "Placeholder for editing topic description",
- "missing": false,
- "obsolete": false
- },
- "label_description": {
- "translation": "Опис",
- "defaultMessage": "Description",
- "description": "Label for description of user or topic",
- "missing": false,
- "obsolete": false
- },
- "button_security": {
- "translation": "Безпека",
- "defaultMessage": "Security",
- "description": "Navigaton button for security panel.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_crop": {
- "translation": "Обрізати картинку",
- "defaultMessage": "Drag to Adjust",
- "description": "Title for AvatarCropView.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_general": {
- "translation": "Загальні налаштування",
- "defaultMessage": "General",
- "description": "Title for TopicCommon.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_members": {
- "translation": "Учасники",
- "defaultMessage": "Members",
- "description": "Title for managing group members view.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_security": {
- "translation": "Безпека",
- "defaultMessage": "Security",
- "description": "Title for TopicSecirity and AccSecurity.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_info": {
- "translation": "Деталі",
- "defaultMessage": "Info",
- "description": "Title for InfoView",
- "missing": false,
- "obsolete": false
- },
- "permissions_anonymous": {
- "translation": "Анонімний",
- "defaultMessage": "Anonymous",
- "description": "Title for editing default anonymous permissions",
- "missing": false,
- "obsolete": false
- },
- "permissions_authenticated": {
- "translation": "Авторизований",
- "defaultMessage": "Authenticated",
- "description": "Title for editing default authenticated permissions",
- "missing": false,
- "obsolete": false
- },
- "topic_delete": {
- "translation": "Видалити чат",
- "defaultMessage": "Delete Conversation",
- "description": "Alert title when deleting the topic.",
- "missing": false,
- "obsolete": false
- },
- "permissions_user": {
- "translation": "Права доступу",
- "defaultMessage": "User's Permissions",
- "description": "Title for editing user's permissions",
- "missing": false,
- "obsolete": false
- },
- "password_reset_email_sent": {
- "translation": "Повідомлення було надіслано на вказаний email.",
- "defaultMessage": "An email with security code has been sent.",
- "description": "Notification that the email with password reset instructions has been sent",
- "missing": false,
- "obsolete": false
- },
- "label_unarchive_topic": {
- "translation": "Архівований:",
- "defaultMessage": "Archived:",
- "description": "Label for unarchiving the topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_reply": {
- "translation": "Відповісти",
- "defaultMessage": "Reply",
- "description": "Reply to message",
- "missing": false,
- "obsolete": false
- },
- "menu_item_forward": {
- "translation": "Переслати",
- "defaultMessage": "Forward",
- "description": "Forward message",
- "missing": false,
- "obsolete": false
- },
- "forward_to": {
- "translation": "Переслати",
- "defaultMessage": "Forward to",
- "missing": false,
- "obsolete": false
- },
- "forward_to_search_placeholder": {
- "translation": "Пошук контактів",
- "defaultMessage": "Search contacts",
- "description": "Contact search prompt when forwarding a message.",
- "missing": false,
- "obsolete": false
- },
- "label_new_password": {
- "translation": "Новий пароль",
- "defaultMessage": "New password",
- "description": "Label for entering a new password",
- "missing": false,
- "obsolete": false
- },
- "drafty_unknown": {
- "translation": "Не підтримується",
- "defaultMessage": "Unsupported",
- "description": "Unsupported entity in drafty",
- "missing": false,
- "obsolete": false
- },
- "calls_incoming": {
- "translation": "Вхідний дзвінок",
- "defaultMessage": "Incoming call",
- "description": "Incoming call label",
- "missing": false,
- "obsolete": false
- },
- "calls_outgoing": {
- "translation": "Вихідний дзвінок",
- "defaultMessage": "Outgoing call",
- "description": "Outgoing call label",
- "missing": false,
- "obsolete": false
- },
- "calls_you_label": {
- "translation": "Ви",
- "defaultMessage": "You",
- "description": "Shown over the local video screen",
- "missing": false,
- "obsolete": false
- },
- "menu_item_video_call": {
- "translation": "Відеодзвінок",
- "defaultMessage": "Video call",
- "description": "Start video call",
- "missing": false,
- "obsolete": false
- },
- "already_in_call": {
- "translation": "Ви вже дзвоните комусь!",
- "defaultMessage": "You already in an ongoing call!",
- "description": "Error message when the user tried to accept a new call without finishing pervious one",
- "missing": false,
- "obsolete": false
- },
- "call_cancelled": {
- "translation": "скасовано",
- "defaultMessage": "cancelled",
- "description": "Label for cancelled outgoing call",
- "missing": false,
- "obsolete": false
- },
- "call_missed": {
- "translation": "пропущений",
- "defaultMessage": "missed",
- "description": "Label for missed incoming call",
- "missing": false,
- "obsolete": false
- },
- "call_declined": {
- "translation": "відхилений",
- "defaultMessage": "declined",
- "description": "Label for declined call",
- "missing": false,
- "obsolete": false
- },
- "call_disconnected": {
- "translation": "роз'єднаний",
- "defaultMessage": "disconnected",
- "description": "Label for disconnected call",
- "missing": false,
- "obsolete": false
- },
- "label_use_secure_connection": {
- "translation": "Безпечне з'єднання",
- "defaultMessage": "Use secure connection",
- "description": "Label for WS/WSS connection type in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "cred_confirmed_successfully": {
- "translation": "Підтверджено успішно",
- "defaultMessage": "Confirmed successfully",
- "description": "Notification message that the credential was successfully validated.",
- "missing": false,
- "obsolete": false
- },
- "menu_item_edit": {
- "translation": "Змінити",
- "defaultMessage": "Edit",
- "description": "Edit message",
- "missing": false,
- "obsolete": false
- },
- "editing_message": {
- "translation": "Редагування",
- "defaultMessage": "Editing",
- "description": "Title over message editing preview",
- "missing": false,
- "obsolete": false
- },
- "message_edited_marker": {
- "translation": ", змінено",
- "defaultMessage": ", edited",
- "description": "Marker indicating that the message was edited",
- "missing": false,
- "obsolete": false
- },
- "icon_title_add_image": {
- "translation": "Додати зображення",
- "defaultMessage": "Add image",
- "description": "Icon tool tip for attaching an image",
- "missing": false,
- "obsolete": false
- },
- "icon_title_attach_file": {
- "translation": "Прикріпити файл",
- "defaultMessage": "Attach file",
- "description": "Icon tool tip for attaching a file",
- "missing": false,
- "obsolete": false
- },
- "icon_title_delete": {
- "translation": "Видалити запис",
- "defaultMessage": "Delete recording",
- "description": "Icon tool tip for deleting recorded audio",
- "missing": false,
- "obsolete": false
- },
- "icon_title_pause": {
- "translation": "Пауза програвання",
- "defaultMessage": "Pause playback",
- "description": "Icon tool tip for pausing audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_play": {
- "translation": "Програти запис",
- "defaultMessage": "Play recording",
- "description": "Icon tool tip for starting audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_record_voice": {
- "translation": "Записати голосове повідомлення",
- "defaultMessage": "Record voice message",
- "description": "Icon tool tip for recording a voice message",
- "missing": false,
- "obsolete": false
- },
- "icon_title_resume": {
- "translation": "Продовжити",
- "defaultMessage": "Resume playback",
- "description": "Icon tool tip for resuming audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_send": {
- "translation": "Надіслати повідомлення",
- "defaultMessage": "Send message",
- "description": "Icon tool tip for sending a message",
- "missing": false,
- "obsolete": false
- },
- "drag_file": {
- "translation": "Перетягніть файл сюди",
- "defaultMessage": "Drag file here",
- "description": "Prompt on the file drag-n-drop overlay banner",
- "missing": false,
- "obsolete": false
- },
- "drafty_video": {
- "translation": "Відеозапис",
- "defaultMessage": "Video recording",
- "description": "Comment for videos embedded in Drafty",
- "missing": false,
- "obsolete": false
- },
- "call_in_progress": {
- "translation": "в процесі",
- "defaultMessage": "in progress",
- "description": "Label for call in progress",
- "missing": false,
- "obsolete": false
- },
- "menu_item_audio_call": {
- "translation": "Подзвонити",
- "defaultMessage": "Call",
- "description": "Start audio call",
- "missing": false,
- "obsolete": false
- },
- "unrecognized_video_format": {
- "translation": "Невідомий формат відео файлу",
- "defaultMessage": "Format of this video is not recognized",
- "description": "Error message when uploaded video is invalid",
- "missing": false,
- "obsolete": false
- },
- "loading_note": {
- "translation": "Завантажується...",
- "defaultMessage": "Loading...",
- "description": "Message shown when component is loading",
- "missing": false,
- "obsolete": false
- },
- "password_i_have_code": {
- "translation": "У мене є код",
- "defaultMessage": "I have code",
- "description": "Call to open field to enter password reset code",
- "missing": false,
- "obsolete": false
- },
- "label_reset_password_tel": {
- "translation": "Надіслати СМС для скидання пароля",
- "defaultMessage": "Send SMS to reset password",
- "description": "Label for password reset field by phone",
- "missing": false,
- "obsolete": false
- },
- "mobile_phone_number": {
- "translation": "Номер мобільного телефону",
- "defaultMessage": "Mobile phone number",
- "description": "Prompt for entering a mobile phone number",
- "missing": false,
- "obsolete": false
- },
- "password_reset_sms_sent": {
- "translation": "СМС із захисним кодом відправлено.",
- "defaultMessage": "A text message with security code has been sent.",
- "description": "Notification that the SMS with password reset instructions has been sent",
- "missing": false,
- "obsolete": false
- },
- "mobile_number_required": {
- "translation": "Номер мобільного телефону",
- "defaultMessage": "Mobile phone number required",
- "description": "Error message",
- "missing": false,
- "obsolete": false
- },
- "current_email": {
- "translation": "Поточний e-mail",
- "defaultMessage": "Current email",
- "description": "Label for old email",
- "missing": false,
- "obsolete": false
- },
- "current_phone": {
- "translation": "Поточний номер телефону",
- "defaultMessage": "Current phone number",
- "description": "Label for old phone",
- "missing": false,
- "obsolete": false
- },
- "new_email": {
- "translation": "Новий e-mail",
- "defaultMessage": "New email",
- "description": "Prompt for entering a new email",
- "missing": false,
- "obsolete": false
- },
- "new_phone_number": {
- "translation": "Новий номер телефону",
- "defaultMessage": "New phone number",
- "description": "Prompt for entering a new telephone number",
- "missing": false,
- "obsolete": false
- },
- "change_email": {
- "translation": "Змінити e-mail",
- "defaultMessage": "Change email",
- "description": "Prompt to change current email",
- "missing": false,
- "obsolete": false
- },
- "change_phone": {
- "translation": "Змінити номер телефону",
- "defaultMessage": "Change phone number",
- "description": "Prompt to change current phone number",
- "missing": false,
- "obsolete": false
- },
- "call_busy": {
- "translation": "зайнято",
- "defaultMessage": "busy",
- "description": "Label for busy call line",
- "missing": false,
- "obsolete": false
- },
- "failed_to_init_audio": {
- "translation": "Не вдалося ініціалізувати аудіозапис",
- "defaultMessage": "Failed to initialize audio recording",
- "description": "Error message when audio is not available",
- "missing": false,
- "obsolete": false
- },
- "password_reset_success": {
- "translation": "Пароль змінено успішно",
- "defaultMessage": "Password reset successfully",
- "description": "Notification message that the password was successfully reset.",
- "missing": false,
- "obsolete": false
- },
- "scan_qr_code": {
- "translation": "Відскануйте QR-код",
- "defaultMessage": "Scan QR Code",
- "description": "Title for scanning QR code",
- "missing": false,
- "obsolete": false
- },
- "pin_message": {
- "translation": "закріпити",
- "defaultMessage": "Pin",
- "description": "Menu item [Pin] for pinning message to the top.",
- "missing": false,
- "obsolete": false
- },
- "unpin_message": {
- "translation": "відкріпити",
- "defaultMessage": "Unpin",
- "description": "Menu item [Unpin] for un-pinning the message.",
- "missing": false,
- "obsolete": false
- },
- "self_topic_comment": {
- "translation": "Нотатки, повідомлення, посилання, файли збережені для нащадків",
- "defaultMessage": "Notes, messages, links, files saved for posterity",
- "description": "Comment for self topic for UI",
- "missing": false,
- "obsolete": false
- },
- "self_topic_name": {
- "translation": "Збережені повідомлення",
- "defaultMessage": "Saved messages",
- "description": "Name of self topic for UI",
- "missing": false,
- "obsolete": false
- },
- "message_not_found": {
- "translation": "повідомлення не знайдено",
- "defaultMessage": "message not found",
- "description": "Pinned message is not found",
- "missing": false,
- "obsolete": false
- },
- "message_deleted": {
- "translation": "повідомлення видалено",
- "defaultMessage": "message deleted",
- "description": "Pinned message is deleted",
- "missing": false,
- "obsolete": false
- },
- "alias_editing_placeholder": {
- "translation": "Псевдонім (необов'язково)",
- "defaultMessage": "Alias (optional)",
- "description": "Placeholder for editing user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "label_alias": {
- "translation": "Псевдонім:",
- "defaultMessage": "Alias:",
- "description": "Label for user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "alias_already_taken": {
- "translation": "(вже зайнято)",
- "defaultMessage": "(already taken)",
- "description": "Error message for alias already taken",
- "missing": false,
- "obsolete": false
- },
- "alias_invalid": {
- "translation": "(недійсний)",
- "defaultMessage": "(invalid)",
- "description": "Error message for invalid alias",
- "missing": false,
- "obsolete": false
- },
- "label_alias_edit": {
- "translation": "Псевдонім",
- "defaultMessage": "Alias",
- "description": "Label for editing user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "select_country": {
- "translation": "Виберіть країну",
- "defaultMessage": "Select country",
- "description": "Placeholder for the country selector",
- "missing": false,
- "obsolete": false
- },
- "text_copied": {
- "translation": "Скопійовано в буфер обміну",
- "defaultMessage": "Copied to clipboard",
- "description": "Notification that text has been copied to clipboard",
- "missing": false,
- "obsolete": false
- },
- "color_schema_dark": {
- "translation": "Темна",
- "defaultMessage": "Dark",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "color_schema_light": {
- "translation": "Світла",
- "defaultMessage": "Light",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "color_schema_system": {
- "translation": "Системна",
- "defaultMessage": "System default",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "label_color_schema": {
- "translation": "Тема:",
- "defaultMessage": "Theme:",
- "description": "Label for selecting color scheme (dark, light) in Settings",
- "missing": false,
- "obsolete": false
- },
- "label_text_size": {
- "translation": "Розмір тексту:",
- "defaultMessage": "Text size:",
- "description": "Label adjusting text size",
- "missing": false,
- "obsolete": false
- },
- "incognito_mode_description": {
- "translation": "Приховати ваш статус онлайн та індикатори набору тексту.",
- "defaultMessage": "Hide your online status and typing indicators.",
- "description": "Description for incognito mode toggle",
- "missing": false,
- "obsolete": false
- },
- "send_command": {
- "translation": "Надіслати на {key}",
- "defaultMessage": "Send on {key}",
- "description": "Config option to send message on hitting CTRL(or Cmd)-Enter",
- "missing": false,
- "obsolete": false
- },
- "send_command_explained": {
- "translation": "Натисніть Enter для нового рядка",
- "defaultMessage": "Press Enter for new line",
- "description": "Explanation how to enter newline when [Send on CTRL-Enter] is enabled",
- "missing": false,
- "obsolete": false
- },
- "send_plain": {
- "translation": "Надіслати на Enter",
- "defaultMessage": "Send on Enter",
- "description": "Config option to send message on hitting Enter",
- "missing": false,
- "obsolete": false
- },
- "send_plain_explained": {
- "translation": "Натисніть Shift + Enter для нового рядка",
- "defaultMessage": "Press Shift + Enter for new line",
- "description": "Explanation how to enter newline when [Send on Enter] is enabled",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_general": {
- "translation": "Загальні",
- "defaultMessage": "General",
- "description": "Sidepanel title for AccGeneralView.",
- "missing": false,
- "obsolete": false
- },
- "label_keyboard": {
- "translation": "Клавіатура:",
- "defaultMessage": "Keyboard:",
- "description": "Label for send on enter settings",
- "missing": false,
- "obsolete": false
- },
- "pin_chat": {
- "translation": "Закріпити",
- "defaultMessage": "Pin",
- "description": "Menu item [Pin] for pinning chat to the top.",
- "missing": false,
- "obsolete": false
- },
- "unpin_chat": {
- "translation": "Відкріпити",
- "defaultMessage": "Unpin",
- "description": "Menu item [Unpin] for un-pinning the chat.",
- "missing": false,
- "obsolete": false
- },
- "subscriber_count": {
- "translation": "{count, plural, one {{count, number} підписник} few {{count, number} підписники} many {{count, number} підписників} other {{count, number} підписників}}",
- "defaultMessage": "{count, plural, one {{count, number} subscriber} two {{count, number} subscribers} three {{count, number} subscribers} few {{count, number} subscribers} many {{count, number} subscribers} other {{count, number} subscribers}}",
- "description": "Count of channel subscribers",
- "missing": false,
- "obsolete": false
- },
- "label_member_count": {
- "translation": "Учасники:",
- "defaultMessage": "Members:",
- "description": "Label for the count of group topic members",
- "missing": false,
- "obsolete": false
- },
- "label_subscriber_count": {
- "translation": "Підписники:",
- "defaultMessage": "Subscribers:",
- "description": "Label for the count of channel subscribers",
- "missing": false,
- "obsolete": false
- },
- "member_count": {
- "translation": "{count, plural, one {{count, number} учасник} few {{count, number} учасники} many {{count, number} учасників} other {{count, number} учасників}}",
- "defaultMessage": "{count, plural, one {{count, number} member} two {{count, number} members} three {{count, number} members} few {{count, number} members} many {{count, number} members} other {{count, number} members}}",
- "description": "Count of group topic members",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_image": {
- "translation": "зображення",
- "defaultMessage": "image",
- "description": "Tab title Image",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_pattern": {
- "translation": "візерунок",
- "defaultMessage": "pattern",
- "description": "Tab title Pattern",
- "missing": false,
- "obsolete": false
- },
- "button_restore": {
- "translation": "Відновити типові",
- "defaultMessage": "Restore default",
- "description": "Reset setting to default",
- "missing": false,
- "obsolete": false
- },
- "wallpapers": {
- "translation": "Шпалери",
- "defaultMessage": "Wallpapers",
- "description": "Link or title for working with wallpapers.",
- "missing": false,
- "obsolete": false
- },
- "label_blur_wallpaper": {
- "translation": "Розмиття:",
- "defaultMessage": "Blur:",
- "description": "Label adjusting blur amount",
- "missing": false,
- "obsolete": false
- },
- "label_scan_id": {
- "translation": "Сканувати мій ID:",
- "defaultMessage": "Scan my ID:",
- "description": "Label [Scan my ID:]",
- "missing": false,
- "obsolete": false
- },
- "unknown_name": {
- "translation": "Невідомий",
- "defaultMessage": "Unknown",
- "description": "Name to show when the name is missing",
- "missing": false,
- "obsolete": false
- },
- "save_action": {
- "translation": "Зберегти",
- "defaultMessage": "Save",
- "description": "Label for Save button",
- "missing": false,
- "obsolete": false
- },
- "cannot_parse_vcard": {
- "translation": "Не вдалося обробити файл vCard.",
- "defaultMessage": "Cannot parse vCard file.",
- "description": "Error message when vCard file cannot be parsed",
- "missing": false,
- "obsolete": false
- },
- "chat_now": {
- "translation": "Написати",
- "defaultMessage": "Chat",
- "description": "Label for message button in contact card",
- "missing": false,
- "obsolete": false
- },
- "find_user": {
- "translation": "Знайти",
- "defaultMessage": "Find",
- "description": "Label for search button in contact card",
- "missing": false,
- "obsolete": false
- },
- "label_contact_card": {
- "translation": "Картка контакту",
- "defaultMessage": "Contact Card",
- "description": "Label for contact card preview",
- "missing": false,
- "obsolete": false
- },
- "label_contacts": {
- "translation": "Контакти",
- "defaultMessage": "Contacts",
- "description": "Label for contact information",
- "missing": false,
- "obsolete": false
- },
- "add_members_prompt": {
- "translation": "додати учасників",
- "defaultMessage": "add members",
- "description": "Placeholder in group members input field",
- "missing": false,
- "obsolete": false
- }
-}
diff --git a/src/i18n/vi.json b/src/i18n/vi.json
deleted file mode 100644
index c3c19a1a..00000000
--- a/src/i18n/vi.json
+++ /dev/null
@@ -1,1966 +0,0 @@
-{
- "action_block_contact": {
- "translation": "Chặn liên hệ",
- "defaultMessage": "Block Contact",
- "description": "Flat button [Block Contact]",
- "missing": false,
- "obsolete": false
- },
- "action_cancel": {
- "translation": "hủy",
- "defaultMessage": "cancel",
- "description": "Call to action [cancel]",
- "missing": false,
- "obsolete": false
- },
- "action_clear_messages": {
- "translation": "Xóa tin nhắn",
- "defaultMessage": "Clear Messages",
- "description": "Flat button [Clear Messages] (soft-delete messages)",
- "missing": false,
- "obsolete": false
- },
- "action_delete_messages": {
- "translation": "Xóa tin nhắn cho tất cả",
- "defaultMessage": "Clear Messages for All",
- "description": "Flat button [Clear for All] (hard-delete all messages)",
- "missing": false,
- "obsolete": false
- },
- "action_leave_chat": {
- "translation": "Rời cuộc trò chuyện",
- "defaultMessage": "Leave Conversation",
- "description": "Flat button [Leave Conversation]",
- "missing": false,
- "obsolete": false
- },
- "action_report_chat": {
- "translation": "Báo cáo cuộc trò chuyện",
- "defaultMessage": "Report Conversation",
- "description": "Flat button [Report Group]",
- "missing": false,
- "obsolete": false
- },
- "archived_contacts": {
- "translation": "Liên hệ đã lưu trữ ({count})",
- "defaultMessage": "Archived contacts ({count})",
- "description": "Label for archived chats",
- "missing": false,
- "obsolete": false
- },
- "badge_danger": {
- "translation": "Không đáng tin",
- "defaultMessage": "Untrustworthy",
- "description": "Suspicious or untrustworthy account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_owner": {
- "translation": "chủ sở hữu",
- "defaultMessage": "owner",
- "description": "Badge for indicating the owner",
- "missing": false,
- "obsolete": false
- },
- "badge_staff": {
- "translation": "Được quản lý bởi nhân viên",
- "defaultMessage": "Staff-managed",
- "description": "Explanation of a staff-managed account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_verified": {
- "translation": "Đã xác minh/chính thức",
- "defaultMessage": "Verified/official",
- "description": "Explanation of a verified account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_you": {
- "translation": "bạn",
- "defaultMessage": "you",
- "description": "Badge for indicating the current user",
- "missing": false,
- "obsolete": false
- },
- "block_contact_warning": {
- "translation": "Bạn có chắc muốn chặn liên hệ này?",
- "defaultMessage": "Are you sure you want to block this contact?",
- "description": "Alert dialog warning when blocking a contact.",
- "missing": false,
- "obsolete": false
- },
- "blocked_contacts_link": {
- "translation": "Liên hệ bị chặn ({count})",
- "defaultMessage": "Blocked contacts ({count})",
- "description": "Blocked contacts link",
- "missing": false,
- "obsolete": false
- },
- "button_add_members": {
- "translation": "Thêm thành viên",
- "defaultMessage": "Add members",
- "description": "Flat button [Add members] (to topic)",
- "missing": false,
- "obsolete": false
- },
- "button_cancel": {
- "translation": "Hủy",
- "defaultMessage": "Cancel",
- "description": "Button [Cancel]",
- "missing": false,
- "obsolete": false
- },
- "button_confirm": {
- "translation": "Xác nhận",
- "defaultMessage": "Confirm",
- "description": "Button [Confirm]",
- "missing": false,
- "obsolete": false
- },
- "button_create": {
- "translation": "Tạo",
- "defaultMessage": "Create",
- "description": "Button [Create]",
- "missing": false,
- "obsolete": false
- },
- "button_delete_account": {
- "translation": "Xóa tài khoản",
- "defaultMessage": "Delete account",
- "description": "Button [Delete account]",
- "missing": false,
- "obsolete": false
- },
- "button_edit": {
- "translation": "Sửa",
- "defaultMessage": "Edit",
- "description": "Call to action [Edit]",
- "missing": false,
- "obsolete": false
- },
- "button_logout": {
- "translation": "Đăng xuất",
- "defaultMessage": "Logout",
- "description": "Button [Logout]",
- "missing": false,
- "obsolete": false
- },
- "button_ok": {
- "translation": "OK",
- "defaultMessage": "OK",
- "description": "Button [OK]",
- "missing": false,
- "obsolete": false
- },
- "button_reset": {
- "translation": "Đặt lại",
- "defaultMessage": "Reset",
- "description": "Button [Reset]",
- "missing": false,
- "obsolete": false
- },
- "button_send_request": {
- "translation": "Gửi yêu cầu",
- "defaultMessage": "Send request",
- "description": "Button [Send request]",
- "missing": false,
- "obsolete": false
- },
- "button_sign_in": {
- "translation": "Đăng nhập",
- "defaultMessage": "Sign in",
- "description": "Button [Sign In]",
- "missing": false,
- "obsolete": false
- },
- "button_sign_up": {
- "translation": "Đăng ký",
- "defaultMessage": "Sign up",
- "description": "Create account button [Sign Up]",
- "missing": false,
- "obsolete": false
- },
- "button_subscribe": {
- "translation": "Theo dõi",
- "defaultMessage": "Subscribe",
- "description": "Button [Subscribe]",
- "missing": false,
- "obsolete": false
- },
- "button_update": {
- "translation": "Cập nhật",
- "defaultMessage": "Update",
- "description": "Button [Update]",
- "missing": false,
- "obsolete": false
- },
- "cannot_initiate_file_upload": {
- "translation": "Không thể bắt đầu tải tệp lên.",
- "defaultMessage": "Cannot initiate file upload.",
- "description": "Generic error messagewhen attachment fails",
- "missing": false,
- "obsolete": false
- },
- "channel": {
- "translation": "kênh",
- "defaultMessage": "channel",
- "description": "Subtitle shown for channels in MessagesView instead of last seen",
- "missing": false,
- "obsolete": false
- },
- "channel_prompt": {
- "translation": "Đây là một kênh",
- "defaultMessage": "This is a channel",
- "description": "Checkbox label when creating a channel",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation": {
- "translation": "Bạn được mời bắt đầu cuộc trò chuyện mới. Bạn muốn làm gì?",
- "defaultMessage": "You are invited to start a new chat. What would you like to do?",
- "description": "New chat invitation message: [Accept] [Ignore] [Block].",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_accept": {
- "translation": "Chấp nhận",
- "defaultMessage": "Accept",
- "description": "Action [Accept] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_block": {
- "translation": "Chặn",
- "defaultMessage": "Block",
- "description": "Action [Block] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_ignore": {
- "translation": "Bỏ qua",
- "defaultMessage": "Ignore",
- "description": "Action [Ignore] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "clear_messages_warning": {
- "translation": "Bạn có chắc muốn xóa tất cả tin nhắn? Hành động này không thể hoàn tác.",
- "defaultMessage": "Are you sure you want to clear all messages? It cannot be undone.",
- "description": "Alert dialog warning when deleting all messages.",
- "missing": false,
- "obsolete": false
- },
- "code_doesnot_match": {
- "translation": "Mã không khớp",
- "defaultMessage": "Code does not match",
- "description": "Error message when the credential validation code is incorrect.",
- "missing": false,
- "obsolete": false
- },
- "contacts_not_found": {
- "translation": "Bạn chưa có cuộc trò chuyện nào\n¯∖_(ツ)_/¯",
- "defaultMessage": "You have no chats\\n¯∖_(ツ)_/¯",
- "description": "HTML message shown in ContactList when no contacts are found",
- "missing": false,
- "obsolete": false
- },
- "contacts_not_found_short": {
- "translation": "Không có liên hệ nào khớp với ''{query}''",
- "defaultMessage": "No contacts match ''{query}''",
- "description": "Shown in ContactsView when search returned no results",
- "missing": false,
- "obsolete": false
- },
- "credential_email_prompt": {
- "translation": "Email đăng ký của bạn",
- "defaultMessage": "Your registration email",
- "description": "Placeholder for entering email",
- "missing": false,
- "obsolete": false
- },
- "delete_account": {
- "translation": "Xóa tài khoản",
- "defaultMessage": "Delete account",
- "description": "Title for delete account warning",
- "missing": false,
- "obsolete": false
- },
- "delete_account_warning": {
- "translation": "Bạn có chắc muốn xóa tài khoản? Hành động này không thể hoàn tác.",
- "defaultMessage": "Are you sure you want to delete your account? It cannot be undone.",
- "description": "Warning message when deleting an account",
- "missing": false,
- "obsolete": false
- },
- "delete_messages_warning": {
- "translation": "Bạn có chắc muốn xóa tất cả tin nhắn cho mọi người? Hành động này không thể hoàn tác.",
- "defaultMessage": "Are you sure you want to delete all messages for everyone? It cannot be undone.",
- "description": "Alert dialog warning when hard-deleting all messages.",
- "missing": false,
- "obsolete": false
- },
- "download_action": {
- "translation": "tải xuống",
- "defaultMessage": "download",
- "description": "Call to action [download]",
- "missing": false,
- "obsolete": false
- },
- "drafty_attachment": {
- "translation": "Tệp đính kèm",
- "defaultMessage": "Attachment",
- "description": "Comment for attachment in Drafty",
- "missing": false,
- "obsolete": false
- },
- "drafty_form": {
- "translation": "Biểu mẫu:",
- "defaultMessage": "Form:",
- "description": "Comment for form in Drafty",
- "missing": false,
- "obsolete": false
- },
- "drafty_image": {
- "translation": "Hình ảnh",
- "defaultMessage": "Picture",
- "description": "Comment for embedded images in Drafty",
- "missing": false,
- "obsolete": false
- },
- "email_dative": {
- "translation": "email",
- "defaultMessage": "email",
- "description": "Dative case of 'email', i.e. 'email' in 'by email'",
- "missing": false,
- "obsolete": false
- },
- "email_prompt": {
- "translation": "Email, ví dụ jdoe@example.com",
- "defaultMessage": "Email, e.g. jdoe@example.com",
- "description": "Input placeholder for email entry",
- "missing": false,
- "obsolete": false
- },
- "enable_peers_messaging": {
- "translation": "Bật",
- "defaultMessage": "Enable",
- "description": "Call to action to enable peer's messaging",
- "missing": false,
- "obsolete": false
- },
- "enter_confirmation_code_prompt": {
- "translation": "Mã xác nhận",
- "defaultMessage": "Confirmation code",
- "description": "Request to enter confirmation code",
- "missing": false,
- "obsolete": false
- },
- "error_invalid_id": {
- "translation": "ID không hợp lệ",
- "defaultMessage": "Invalid ID",
- "description": "Error message",
- "missing": false,
- "obsolete": false
- },
- "file_attachment_too_large": {
- "translation": "Kích thước tệp {size} vượt quá giới hạn {limit}.",
- "defaultMessage": "The file size {size} exceeds the {limit} limit.",
- "description": "Error message when attachment is too large",
- "missing": false,
- "obsolete": false
- },
- "forgot_password_link": {
- "translation": "Quên mật khẩu?",
- "defaultMessage": "Forgot password?",
- "description": "Link to Reset password form",
- "missing": false,
- "obsolete": false
- },
- "full_name_prompt": {
- "translation": "Họ và tên, ví dụ John Doe",
- "defaultMessage": "Full name, e.g. John Doe",
- "description": "Input placeholder for person's full name",
- "missing": false,
- "obsolete": false
- },
- "granted_permissions": {
- "translation": "Đã cấp",
- "defaultMessage": "Granted",
- "description": "Title for permissions",
- "missing": false,
- "obsolete": false
- },
- "group_has_no_members": {
- "translation": "Không có thành viên",
- "defaultMessage": "No members",
- "description": "Shown in place of group members",
- "missing": false,
- "obsolete": false
- },
- "group_user_id_prompt": {
- "translation": "ID nhóm hoặc người dùng",
- "defaultMessage": "Group or User ID",
- "description": "Prompt for entering user or group ID",
- "missing": false,
- "obsolete": false
- },
- "image_caption_prompt": {
- "translation": "Chú thích hình ảnh",
- "defaultMessage": "Image caption",
- "description": "Prompt in SendMessage for attached image",
- "missing": false,
- "obsolete": false
- },
- "invalid_content": {
- "translation": "nội dung không hợp lệ",
- "defaultMessage": "invalid content",
- "description": "Shown when the message is unreadable",
- "missing": false,
- "obsolete": false
- },
- "invalid_security_token": {
- "translation": "Mã bảo mật không hợp lệ",
- "defaultMessage": "Invalid security token",
- "description": "Error message when resetting password.",
- "missing": false,
- "obsolete": false
- },
- "label_client": {
- "translation": "Client:",
- "defaultMessage": "Client:",
- "description": "Label for a client version",
- "missing": false,
- "obsolete": false
- },
- "label_content_type": {
- "translation": "Kiểu nội dung:",
- "defaultMessage": "Content type:",
- "description": "Label for file content type (mime)",
- "missing": false,
- "obsolete": false
- },
- "label_default_access_mode": {
- "translation": "Chế độ truy cập mặc định:",
- "defaultMessage": "Default access mode:",
- "description": "Label for default access mode",
- "missing": false,
- "obsolete": false
- },
- "label_file_name": {
- "translation": "Tên tệp:",
- "defaultMessage": "File name:",
- "description": "Label for a file name",
- "missing": false,
- "obsolete": false
- },
- "label_group_members": {
- "translation": "Thành viên nhóm:",
- "defaultMessage": "Group members:",
- "description": "Section title or label",
- "missing": false,
- "obsolete": false
- },
- "label_incognito_mode": {
- "translation": "Chế độ ẩn danh:",
- "defaultMessage": "Incognito mode:",
- "description": "Label for incognito mode toggle",
- "missing": false,
- "obsolete": false
- },
- "label_message_sound": {
- "translation": "Âm thanh tin nhắn:",
- "defaultMessage": "Message sound:",
- "description": "Label for message sounds toggle",
- "missing": false,
- "obsolete": false
- },
- "label_muting_topic": {
- "translation": "Đã tắt:",
- "defaultMessage": "Muted:",
- "description": "Label for Muting/unmuting the topic",
- "missing": false,
- "obsolete": false
- },
- "label_other_user": {
- "translation": "Khác",
- "defaultMessage": "Other",
- "description": "Label for the other user when the user is unnamed",
- "missing": false,
- "obsolete": false
- },
- "label_password": {
- "translation": "Mật khẩu",
- "defaultMessage": "Password",
- "description": "Label for password editing",
- "missing": false,
- "obsolete": false
- },
- "label_permissions": {
- "translation": "Quyền:",
- "defaultMessage": "Permissions:",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "label_private": {
- "translation": "Chỉ bạn nhìn thấy",
- "defaultMessage": "Private comment",
- "description": "Label for editing 'private'",
- "missing": false,
- "obsolete": false
- },
- "label_push_notifications": {
- "translation": "Cảnh báo thông báo:",
- "defaultMessage": "Notification alerts:",
- "description": "Label for push notifications switch",
- "missing": false,
- "obsolete": false
- },
- "label_push_notifications_disabled": {
- "translation": "Cảnh báo thông báo (yêu cầu HTTPS):",
- "defaultMessage": "Notification alerts (requires HTTPS):",
- "description": "Label for push notifications switch",
- "missing": false,
- "obsolete": false
- },
- "label_reset_password": {
- "translation": "Gửi email đặt lại mật khẩu:",
- "defaultMessage": "Send a password reset email",
- "description": "Label for password reset field",
- "missing": false,
- "obsolete": false
- },
- "label_sdk": {
- "translation": "SDK:",
- "defaultMessage": "SDK:",
- "missing": false,
- "obsolete": false
- },
- "label_server": {
- "translation": "Máy chủ:",
- "defaultMessage": "Server:",
- "description": "Label for a server version",
- "missing": false,
- "obsolete": false
- },
- "label_server_address": {
- "translation": "Địa chỉ máy chủ:",
- "defaultMessage": "Server address:",
- "missing": false,
- "obsolete": false
- },
- "label_server_to_use": {
- "translation": "Máy chủ sử dụng:",
- "defaultMessage": "Server to use:",
- "description": "Label for server selector in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "label_size": {
- "translation": "Kích thước:",
- "defaultMessage": "Size:",
- "description": "Label for file size",
- "missing": false,
- "obsolete": false
- },
- "label_topic_name": {
- "translation": "Tên",
- "defaultMessage": "Name",
- "description": "Label for editing topic name",
- "missing": false,
- "obsolete": false
- },
- "label_user_contacts": {
- "translation": "Danh bạ",
- "defaultMessage": "Contacts",
- "description": "Label for user contacts",
- "missing": false,
- "obsolete": false
- },
- "label_user_id": {
- "translation": "ID:",
- "defaultMessage": "ID:",
- "description": "Label for user address (ID)",
- "missing": false,
- "obsolete": false
- },
- "label_wire_transport": {
- "translation": "Giao thức truyền tải:",
- "defaultMessage": "Wire transport:",
- "description": "Label for wire transport selection in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "label_you": {
- "translation": "Bạn:",
- "defaultMessage": "You:",
- "description": "Label for the current user",
- "missing": false,
- "obsolete": false
- },
- "label_your_name": {
- "translation": "Tên của bạn",
- "defaultMessage": "Your name",
- "description": "Label for full name editing",
- "missing": false,
- "obsolete": false
- },
- "label_your_permissions": {
- "translation": "Quyền của bạn:",
- "defaultMessage": "Your permissions:",
- "description": "Label for current user permissions",
- "missing": false,
- "obsolete": false
- },
- "last_seen_timestamp": {
- "translation": "Lần cuối hoạt động: {timestamp}",
- "defaultMessage": "Last seen: {timestamp}",
- "description": "Label for the timestamp of when the user or topic was last online",
- "missing": false,
- "obsolete": false
- },
- "leave_chat_warning": {
- "translation": "Bạn có chắc muốn rời cuộc trò chuyện này?",
- "defaultMessage": "Are you sure you want to leave this conversation?",
- "description": "Alert dialog warning when unsubscribing from a chat.",
- "missing": false,
- "obsolete": false
- },
- "link_contact_us": {
- "translation": "Liên hệ chúng tôi",
- "defaultMessage": "Contact Us",
- "description": "Ancor text for contacting us by email",
- "missing": false,
- "obsolete": false
- },
- "link_privacy_policy": {
- "translation": "Chính sách quyền riêng tư",
- "defaultMessage": "Privacy Policy",
- "description": "Ancor text for privacy policy link",
- "missing": false,
- "obsolete": false
- },
- "link_terms_of_service": {
- "translation": "Điều khoản dịch vụ",
- "defaultMessage": "Terms of Service",
- "description": "Ancor text for terms of service link",
- "missing": false,
- "obsolete": false
- },
- "login_prompt": {
- "translation": "Đăng nhập",
- "defaultMessage": "Login",
- "description": "Placeholer for username/login",
- "missing": false,
- "obsolete": false
- },
- "menu_item_archive_topic": {
- "translation": "Lưu trữ",
- "defaultMessage": "Archive",
- "description": "Move topic from the list of active chats to archive",
- "missing": false,
- "obsolete": false
- },
- "menu_item_block": {
- "translation": "Chặn",
- "defaultMessage": "Block",
- "description": "Block topic or user",
- "missing": false,
- "obsolete": false
- },
- "menu_item_clear_messages": {
- "translation": "Xóa tin nhắn",
- "defaultMessage": "Clear messages",
- "description": "Delete all messages",
- "missing": false,
- "obsolete": false
- },
- "menu_item_clear_messages_for_all": {
- "translation": "Xóa cho tất cả",
- "defaultMessage": "Clear for All",
- "description": "Delete all message(s) for all members",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete": {
- "translation": "Xóa",
- "defaultMessage": "Delete",
- "description": "Delete selected messages",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete_for_all": {
- "translation": "Xóa cho tất cả",
- "defaultMessage": "Delete for All",
- "description": "Delete selected message(s) for all members",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete_topic": {
- "translation": "Xóa",
- "defaultMessage": "Delete",
- "description": "Delete entire topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_edit_permissions": {
- "translation": "Sửa quyền",
- "defaultMessage": "Edit permissions",
- "description": "Menu item [Edit permissions]",
- "missing": false,
- "obsolete": false
- },
- "menu_item_info": {
- "translation": "Thông tin",
- "defaultMessage": "Info",
- "description": "Show extended topic information",
- "missing": false,
- "obsolete": false
- },
- "menu_item_member_delete": {
- "translation": "Xóa",
- "defaultMessage": "Remove",
- "description": "Remove user from topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_mute": {
- "translation": "Tắt thông báo",
- "defaultMessage": "Mute",
- "description": "Turn off notifications",
- "missing": false,
- "obsolete": false
- },
- "menu_item_reply": {
- "translation": "Trả lời",
- "defaultMessage": "Reply",
- "description": "Reply to message",
- "missing": false,
- "obsolete": false
- },
- "menu_item_restore_topic": {
- "translation": "Khôi phục",
- "defaultMessage": "Restore",
- "description": "Restore topic from archive",
- "missing": false,
- "obsolete": false
- },
- "menu_item_send_retry": {
- "translation": "Thử lại",
- "defaultMessage": "Retry",
- "description": "Retry sending message",
- "missing": false,
- "obsolete": false
- },
- "menu_item_unblock": {
- "translation": "Bỏ chặn",
- "defaultMessage": "Unblock",
- "description": "Unblock topic or user",
- "missing": false,
- "obsolete": false
- },
- "menu_item_unmute": {
- "translation": "Bật thông báo",
- "defaultMessage": "Unmute",
- "description": "Turn notifications on",
- "missing": false,
- "obsolete": false
- },
- "message_sending": {
- "translation": "đang gửi...",
- "defaultMessage": "sending...",
- "description": "Message being sent, in place of time stamp",
- "missing": false,
- "obsolete": false
- },
- "message_sending_failed": {
- "translation": "thất bại",
- "defaultMessage": "failed",
- "description": "Failed to send message, in place of time stamp",
- "missing": false,
- "obsolete": false
- },
- "messages_not_readable": {
- "translation": "không có quyền xem tin nhắn",
- "defaultMessage": "no access to messages",
- "description": "Message shown in topic without the read access",
- "missing": false,
- "obsolete": false
- },
- "messaging_disabled_prompt": {
- "translation": "Tin nhắn bị tắt",
- "defaultMessage": "Messaging disabled",
- "description": "Prompt in SendMessage in read-only topic",
- "missing": false,
- "obsolete": false
- },
- "more_online_members": {
- "translation": "+{overflow} nữa",
- "defaultMessage": "+{overflow} more",
- "description": "Shown in MessagesView title bar when the number of online subscribers exceeds MAX_ONLINE_IN_TOPIC",
- "missing": false,
- "obsolete": false
- },
- "new_message_prompt": {
- "translation": "Tin nhắn mới",
- "defaultMessage": "New message",
- "description": "Prompt in send message field",
- "missing": false,
- "obsolete": false
- },
- "new_password_placeholder": {
- "translation": "Nhập mật khẩu mới",
- "defaultMessage": "Enter new password",
- "description": "Placeholder for entering new password",
- "missing": false,
- "obsolete": false
- },
- "no_connection": {
- "translation": "Không có kết nối",
- "defaultMessage": "No connection",
- "description": "Warning that the user is offline.",
- "missing": false,
- "obsolete": false
- },
- "no_contacts": {
- "translation": "Bạn chưa có liên hệ nào :-(",
- "defaultMessage": "You have no contacts :-(",
- "description": "Shown in ContactsView when the user has no contacts",
- "missing": false,
- "obsolete": false
- },
- "numeric_confirmation_code_prompt": {
- "translation": "Chỉ số",
- "defaultMessage": "Numbers only",
- "description": "Prompt for numeric conformation code",
- "missing": false,
- "obsolete": false
- },
- "online_now": {
- "translation": "đang online",
- "defaultMessage": "online now",
- "description": "Indicator that the user or topic is currently online",
- "missing": false,
- "obsolete": false
- },
- "password_prompt": {
- "translation": "Mật khẩu",
- "defaultMessage": "Password",
- "description": "Placeholder/prompt for entering password",
- "missing": false,
- "obsolete": false
- },
- "password_unchanged_prompt": {
- "translation": "Không thay đổi",
- "defaultMessage": "Unchanged",
- "description": "Message in editor while password is unchanged",
- "missing": false,
- "obsolete": false
- },
- "peers_messaging_disabled": {
- "translation": "Tin nhắn của người kia đã bị tắt.",
- "defaultMessage": "Peer's messaging is disabled.",
- "description": "Shown when the p2p peer's messaging is disabled",
- "missing": false,
- "obsolete": false
- },
- "permission_admin": {
- "translation": "Phê duyệt ({val})",
- "defaultMessage": "Approve ({val})",
- "description": "Name of A permission",
- "missing": false,
- "obsolete": false
- },
- "permission_delete": {
- "translation": "Xóa ({val})",
- "defaultMessage": "Delete ({val})",
- "description": "Name of D permission",
- "missing": false,
- "obsolete": false
- },
- "permission_join": {
- "translation": "Tham gia ({val})",
- "defaultMessage": "Join ({val})",
- "description": "Name of J permission",
- "missing": false,
- "obsolete": false
- },
- "permission_owner": {
- "translation": "Chủ sở hữu ({val})",
- "defaultMessage": "Owner ({val})",
- "description": "Name of O permission",
- "missing": false,
- "obsolete": false
- },
- "permission_pres": {
- "translation": "Nhận thông báo ({val})",
- "defaultMessage": "Get notified ({val})",
- "description": "Name of P permission",
- "missing": false,
- "obsolete": false
- },
- "permission_read": {
- "translation": "Đọc ({val})",
- "defaultMessage": "Read ({val})",
- "description": "Name of R permission",
- "missing": false,
- "obsolete": false
- },
- "permission_share": {
- "translation": "Chia sẻ ({val})",
- "defaultMessage": "Share ({val})",
- "description": "Name of S permission",
- "missing": false,
- "obsolete": false
- },
- "permission_write": {
- "translation": "Ghi ({val})",
- "defaultMessage": "Write ({val})",
- "description": "Name of W permission",
- "missing": false,
- "obsolete": false
- },
- "phone_dative": {
- "translation": "điện thoại",
- "defaultMessage": "phone",
- "description": "Dative case of 'phone', i.e. 'phone' in 'by phone'",
- "missing": false,
- "obsolete": false
- },
- "private_editing_placeholder": {
- "translation": "Chỉ mình bạn nhìn thấy",
- "defaultMessage": "Visible to you only",
- "description": "Placeholder for editing 'private'",
- "missing": false,
- "obsolete": false
- },
- "push_init_failed": {
- "translation": "Khởi tạo thông báo đẩy thất bại",
- "defaultMessage": "Failed to initialize push notifications",
- "description": "Error message when push notifications have failed to initialize.",
- "missing": false,
- "obsolete": false
- },
- "reconnect_countdown": {
- "translation": "Mất kết nối. Kết nối lại sau {seconds}…",
- "defaultMessage": "Disconnected. Reconnecting in {seconds}…",
- "description": "Message shown when an app update is available.",
- "missing": false,
- "obsolete": false
- },
- "reconnect_now": {
- "translation": "Thử ngay",
- "defaultMessage": "Try now",
- "description": "Prompt for reconnecting now",
- "missing": false,
- "obsolete": false
- },
- "reload_update": {
- "translation": "Tải lại",
- "defaultMessage": "Reload",
- "description": "Call to action to reload application when update is available.",
- "missing": false,
- "obsolete": false
- },
- "report_chat_warning": {
- "translation": "Bạn có chắc muốn chặn và báo cáo cuộc trò chuyện này?",
- "defaultMessage": "Are you sure you want to block and report this conversation?",
- "description": "Alert dialog warning when reporting a conversation for abuse",
- "missing": false,
- "obsolete": false
- },
- "requested_permissions": {
- "translation": "Đã yêu cầu",
- "defaultMessage": "Requested",
- "description": "Title for permissions",
- "missing": false,
- "obsolete": false
- },
- "save_attachment": {
- "translation": "lưu",
- "defaultMessage": "save",
- "description": "Call to save an attachment",
- "missing": false,
- "obsolete": false
- },
- "search_for_contacts": {
- "translation": "Dùng tìm kiếm để tìm liên hệ",
- "defaultMessage": "Use search to find contacts",
- "description": "Text shown in contacts view when user entered no search query.",
- "missing": false,
- "obsolete": false
- },
- "search_no_results": {
- "translation": "Không có kết quả nào",
- "defaultMessage": "Search returned no results",
- "description": "Text shown in contacts view when query returned no results.",
- "missing": false,
- "obsolete": false
- },
- "search_placeholder": {
- "translation": "Liệt kê như email:alice@example.com, tel:17025550003...",
- "defaultMessage": "List like alice@example.com, +17025550003...",
- "description": "Placeholder in contacts search field",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_notifications": {
- "translation": "Thông báo",
- "defaultMessage": "Notifications",
- "description": "Sidepanel title for AccNotificationsView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_support": {
- "translation": "Hỗ trợ",
- "defaultMessage": "Support",
- "description": "Sidepanel title for AccSupportView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_account_settings": {
- "translation": "Cài đặt tài khoản",
- "defaultMessage": "Account Settings",
- "description": "Sidepanel title for AccountSettingsView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_archive": {
- "translation": "Cuộc trò chuyện đã lưu trữ",
- "defaultMessage": "Archived Chats",
- "description": "Sidepanel title for ContactsView-Archive.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_blocked": {
- "translation": "Cuộc trò chuyện bị chặn",
- "defaultMessage": "Blocked Chats",
- "description": "Sidepanel title for ContactsView-Blocked.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_cred": {
- "translation": "Xác nhận thông tin",
- "defaultMessage": "Confirm Credentials",
- "description": "Sidepanel title for ValidationView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_login": {
- "translation": "Đăng nhập",
- "defaultMessage": "Sign In",
- "description": "Sidepanel title for LoginView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_newtpk": {
- "translation": "Bắt đầu cuộc trò chuyện mới",
- "defaultMessage": "Start New Chat",
- "description": "Sidepanel title for NewTopicView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_register": {
- "translation": "Tạo tài khoản",
- "defaultMessage": "Create Account",
- "description": "Sidepanel title for CreateAccountView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_reset": {
- "translation": "Đặt lại mật khẩu",
- "defaultMessage": "Reset Password",
- "description": "Sidepanel title for PasswordResetView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_settings": {
- "translation": "Cài đặt",
- "defaultMessage": "Settings",
- "description": "Sidepanel title for SettingsView.",
- "missing": false,
- "obsolete": false
- },
- "stay_logged_in": {
- "translation": "Giữ tôi đăng nhập",
- "defaultMessage": "Stay logged in",
- "description": "Label for a checkbox",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_find_user": {
- "translation": "tìm",
- "defaultMessage": "find",
- "description": "Tab title Find",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_group_by_id": {
- "translation": "theo id",
- "defaultMessage": "by id",
- "description": "Tab title Find topic by ID",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_new_group": {
- "translation": "nhóm mới",
- "defaultMessage": "new group",
- "description": "Tab title New Group",
- "missing": false,
- "obsolete": false
- },
- "tags_editor_no_tags": {
- "translation": "Thêm vài thẻ",
- "defaultMessage": "Add some tags",
- "description": "Tag editor prompt when no tags are found.",
- "missing": false,
- "obsolete": false
- },
- "tags_not_found": {
- "translation": "Chưa có thẻ nào. Hãy thêm một vài thẻ.",
- "defaultMessage": "No tags defined. Add some.",
- "description": "",
- "missing": false,
- "obsolete": false
- },
- "title_all_contacts": {
- "translation": "Tất cả liên hệ",
- "defaultMessage": "All Contacts",
- "description": "Section title [All Contacts]",
- "missing": false,
- "obsolete": false
- },
- "title_group_members": {
- "translation": "Thành viên nhóm",
- "defaultMessage": "Group Members",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "title_manage_tags": {
- "translation": "Quản lý",
- "defaultMessage": "Manage",
- "description": "Section title for the list of tags",
- "missing": false,
- "obsolete": false
- },
- "title_not_found": {
- "translation": "Không tìm thấy",
- "defaultMessage": "Not found",
- "description": "Title shown when topic is not found",
- "missing": false,
- "obsolete": false
- },
- "title_permissions": {
- "translation": "Quyền",
- "defaultMessage": "Permissions",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "title_tag_manager": {
- "translation": "Thẻ (tìm kiếm & khám phá)",
- "defaultMessage": "Tags (search & discovery)",
- "description": "Section title for TagManager",
- "missing": false,
- "obsolete": false
- },
- "topic_block_warning": {
- "translation": "Bạn có chắc muốn chặn cuộc trò chuyện này?",
- "defaultMessage": "Are you sure you want to block this conversation?",
- "description": "Alert warning when blocking a topic.",
- "missing": false,
- "obsolete": false
- },
- "topic_delete_warning": {
- "translation": "Bạn có chắc muốn xóa cuộc trò chuyện này? Hành động này không thể hoàn tác.",
- "defaultMessage": "Are you sure you want to delete this conversation? It cannot be undone.",
- "description": "Alert warning when deleting entire topic",
- "missing": false,
- "obsolete": false
- },
- "topic_name_editing_placeholder": {
- "translation": "Tên nhóm tự do",
- "defaultMessage": "Freeform name of the group",
- "description": "Prompt for entering topic name",
- "missing": false,
- "obsolete": false
- },
- "unnamed_topic": {
- "translation": "Chưa đặt tên",
- "defaultMessage": "Unnamed",
- "description": "Title shown when the topic has no name",
- "missing": false,
- "obsolete": false
- },
- "update_available": {
- "translation": "Có bản cập nhật.",
- "defaultMessage": "Update available.",
- "description": "Message shown when an app update is available.",
- "missing": false,
- "obsolete": false
- },
- "upload_finishing": {
- "translation": "đang hoàn tất...",
- "defaultMessage": "finishing...",
- "description": "Notification that upload is finishing",
- "missing": false,
- "obsolete": false
- },
- "user_not_found": {
- "translation": "Không tìm thấy",
- "defaultMessage": "Not found",
- "description": "In place of a user's full name when the user is not found.",
- "missing": false,
- "obsolete": false
- },
- "description_editing_placeholder": {
- "translation": "Mô tả tùy chọn",
- "defaultMessage": "Description (optional)",
- "description": "Placeholder for editing topic description",
- "missing": false,
- "obsolete": false
- },
- "label_description": {
- "translation": "Mô tả",
- "defaultMessage": "Description",
- "description": "Label for description of user or topic",
- "missing": false,
- "obsolete": false
- },
- "button_security": {
- "translation": "Bảo mật",
- "defaultMessage": "Security",
- "description": "Navigaton button for security panel.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_crop": {
- "translation": "Kéo để điều chỉnh",
- "defaultMessage": "Drag to Adjust",
- "description": "Title for AvatarCropView.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_general": {
- "translation": "Chung",
- "defaultMessage": "General",
- "description": "Title for TopicCommon.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_members": {
- "translation": "Thành viên",
- "defaultMessage": "Members",
- "description": "Title for managing group members view.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_security": {
- "translation": "Bảo mật",
- "defaultMessage": "Security",
- "description": "Title for TopicSecirity and AccSecurity.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_info": {
- "translation": "Thông tin",
- "defaultMessage": "Info",
- "description": "Title for InfoView",
- "missing": false,
- "obsolete": false
- },
- "permissions_anonymous": {
- "translation": "Ẩn danh",
- "defaultMessage": "Anonymous",
- "description": "Title for editing default anonymous permissions",
- "missing": false,
- "obsolete": false
- },
- "permissions_authenticated": {
- "translation": "Đã xác thực",
- "defaultMessage": "Authenticated",
- "description": "Title for editing default authenticated permissions",
- "missing": false,
- "obsolete": false
- },
- "topic_delete": {
- "translation": "Xóa cuộc trò chuyện",
- "defaultMessage": "Delete Conversation",
- "description": "Alert title when deleting the topic.",
- "missing": false,
- "obsolete": false
- },
- "permissions_user": {
- "translation": "Quyền của người dùng",
- "defaultMessage": "User's Permissions",
- "description": "Title for editing user's permissions",
- "missing": false,
- "obsolete": false
- },
- "password_reset_email_sent": {
- "translation": "Email chứa mã bảo mật đã được gửi.",
- "defaultMessage": "An email with security code has been sent.",
- "description": "Notification that the email with password reset instructions has been sent",
- "missing": false,
- "obsolete": false
- },
- "label_unarchive_topic": {
- "translation": "Đã lưu trữ:",
- "defaultMessage": "Archived:",
- "description": "Label for unarchiving the topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_forward": {
- "translation": "Chuyển tiếp",
- "defaultMessage": "Forward",
- "description": "Forward message",
- "missing": false,
- "obsolete": false
- },
- "forward_to": {
- "translation": "Chuyển tiếp đến",
- "defaultMessage": "Forward to",
- "missing": false,
- "obsolete": false
- },
- "forward_to_search_placeholder": {
- "translation": "Tìm kiếm liên hệ",
- "defaultMessage": "Search contacts",
- "description": "Contact search prompt when forwarding a message.",
- "missing": false,
- "obsolete": false
- },
- "label_new_password": {
- "translation": "Mật khẩu mới",
- "defaultMessage": "New password",
- "description": "Label for entering a new password",
- "missing": false,
- "obsolete": false
- },
- "drafty_unknown": {
- "translation": "Không hỗ trợ",
- "defaultMessage": "Unsupported",
- "description": "Unsupported entity in drafty",
- "missing": false,
- "obsolete": false
- },
- "calls_incoming": {
- "translation": "Cuộc gọi đến",
- "defaultMessage": "Incoming call",
- "description": "Incoming call label",
- "missing": false,
- "obsolete": false
- },
- "calls_outgoing": {
- "translation": "Cuộc gọi đi",
- "defaultMessage": "Outgoing call",
- "description": "Outgoing call label",
- "missing": false,
- "obsolete": false
- },
- "calls_you_label": {
- "translation": "Bạn",
- "defaultMessage": "You",
- "description": "Shown over the local video screen",
- "missing": false,
- "obsolete": false
- },
- "menu_item_video_call": {
- "translation": "Gọi video",
- "defaultMessage": "Video call",
- "description": "Start video call",
- "missing": false,
- "obsolete": false
- },
- "already_in_call": {
- "translation": "Bạn đang có một cuộc gọi đang diễn ra!",
- "defaultMessage": "You already in an ongoing call!",
- "description": "Error message when the user tried to accept a new call without finishing pervious one",
- "missing": false,
- "obsolete": false
- },
- "call_cancelled": {
- "translation": "đã hủy",
- "defaultMessage": "cancelled",
- "description": "Label for cancelled outgoing call",
- "missing": false,
- "obsolete": false
- },
- "call_missed": {
- "translation": "bỏ lỡ",
- "defaultMessage": "missed",
- "description": "Label for missed incoming call",
- "missing": false,
- "obsolete": false
- },
- "call_declined": {
- "translation": "từ chối",
- "defaultMessage": "declined",
- "description": "Label for declined call",
- "missing": false,
- "obsolete": false
- },
- "call_disconnected": {
- "translation": "mất kết nối",
- "defaultMessage": "disconnected",
- "description": "Label for disconnected call",
- "missing": false,
- "obsolete": false
- },
- "label_use_secure_connection": {
- "translation": "Sử dụng kết nối bảo mật",
- "defaultMessage": "Use secure connection",
- "description": "Label for WS/WSS connection type in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "cred_confirmed_successfully": {
- "translation": "Xác nhận thành công",
- "defaultMessage": "Confirmed successfully",
- "description": "Notification message that the credential was successfully validated.",
- "missing": false,
- "obsolete": false
- },
- "menu_item_edit": {
- "translation": "Sửa",
- "defaultMessage": "Edit",
- "description": "Edit message",
- "missing": false,
- "obsolete": false
- },
- "editing_message": {
- "translation": "Đang sửa",
- "defaultMessage": "Editing",
- "description": "Title over message editing preview",
- "missing": false,
- "obsolete": false
- },
- "message_edited_marker": {
- "translation": ", đã sửa",
- "defaultMessage": ", edited",
- "description": "Marker indicating that the message was edited",
- "missing": false,
- "obsolete": false
- },
- "icon_title_add_image": {
- "translation": "Thêm hình ảnh",
- "defaultMessage": "Add image",
- "description": "Icon tool tip for attaching an image",
- "missing": false,
- "obsolete": false
- },
- "icon_title_attach_file": {
- "translation": "Đính kèm tệp",
- "defaultMessage": "Attach file",
- "description": "Icon tool tip for attaching a file",
- "missing": false,
- "obsolete": false
- },
- "icon_title_delete": {
- "translation": "Xóa",
- "defaultMessage": "Delete recording",
- "description": "Icon tool tip for deleting recorded audio",
- "missing": false,
- "obsolete": false
- },
- "icon_title_pause": {
- "translation": "Tạm dừng",
- "defaultMessage": "Pause playback",
- "description": "Icon tool tip for pausing audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_play": {
- "translation": "Phát",
- "defaultMessage": "Play recording",
- "description": "Icon tool tip for starting audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_record_voice": {
- "translation": "Ghi âm tin nhắn",
- "defaultMessage": "Record voice message",
- "description": "Icon tool tip for recording a voice message",
- "missing": false,
- "obsolete": false
- },
- "icon_title_resume": {
- "translation": "Tiếp tục",
- "defaultMessage": "Resume playback",
- "description": "Icon tool tip for resuming audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_send": {
- "translation": "Gửi tin nhắn",
- "defaultMessage": "Send message",
- "description": "Icon tool tip for sending a message",
- "missing": false,
- "obsolete": false
- },
- "drag_file": {
- "translation": "Kéo tệp vào đây",
- "defaultMessage": "Drag file here",
- "description": "Prompt on the file drag-n-drop overlay banner",
- "missing": false,
- "obsolete": false
- },
- "drafty_video": {
- "translation": "Video",
- "defaultMessage": "Video recording",
- "description": "Comment for videos embedded in Drafty",
- "missing": false,
- "obsolete": false
- },
- "call_in_progress": {
- "translation": "đang diễn ra",
- "defaultMessage": "in progress",
- "description": "Label for call in progress",
- "missing": false,
- "obsolete": false
- },
- "menu_item_audio_call": {
- "translation": "Gọi",
- "defaultMessage": "Call",
- "description": "Start audio call",
- "missing": false,
- "obsolete": false
- },
- "unrecognized_video_format": {
- "translation": "Không nhận dạng được định dạng video này",
- "defaultMessage": "Format of this video is not recognized",
- "description": "Error message when uploaded video is invalid",
- "missing": false,
- "obsolete": false
- },
- "loading_note": {
- "translation": "Đang tải...",
- "defaultMessage": "Loading...",
- "description": "Message shown when component is loading",
- "missing": false,
- "obsolete": false
- },
- "password_i_have_code": {
- "translation": "Tôi có mã",
- "defaultMessage": "I have code",
- "description": "Call to open field to enter password reset code",
- "missing": false,
- "obsolete": false
- },
- "label_reset_password_tel": {
- "translation": "Gửi SMS đặt lại mật khẩu:",
- "defaultMessage": "Send SMS to reset password",
- "description": "Label for password reset field by phone",
- "missing": false,
- "obsolete": false
- },
- "mobile_phone_number": {
- "translation": "Số điện thoại di động",
- "defaultMessage": "Mobile phone number",
- "description": "Prompt for entering a mobile phone number",
- "missing": false,
- "obsolete": false
- },
- "password_reset_sms_sent": {
- "translation": "Tin nhắn chứa mã bảo mật đã được gửi.",
- "defaultMessage": "A text message with security code has been sent.",
- "description": "Notification that the SMS with password reset instructions has been sent",
- "missing": false,
- "obsolete": false
- },
- "mobile_number_required": {
- "translation": "Cần số điện thoại di động",
- "defaultMessage": "Mobile phone number required",
- "description": "Error message",
- "missing": false,
- "obsolete": false
- },
- "current_email": {
- "translation": "Email hiện tại",
- "defaultMessage": "Current email",
- "description": "Label for old email",
- "missing": false,
- "obsolete": false
- },
- "current_phone": {
- "translation": "Số điện thoại hiện tại",
- "defaultMessage": "Current phone number",
- "description": "Label for old phone",
- "missing": false,
- "obsolete": false
- },
- "new_email": {
- "translation": "Email mới",
- "defaultMessage": "New email",
- "description": "Prompt for entering a new email",
- "missing": false,
- "obsolete": false
- },
- "new_phone_number": {
- "translation": "Số điện thoại mới",
- "defaultMessage": "New phone number",
- "description": "Prompt for entering a new telephone number",
- "missing": false,
- "obsolete": false
- },
- "change_email": {
- "translation": "Đổi email",
- "defaultMessage": "Change email",
- "description": "Prompt to change current email",
- "missing": false,
- "obsolete": false
- },
- "change_phone": {
- "translation": "Đổi số điện thoại",
- "defaultMessage": "Change phone number",
- "description": "Prompt to change current phone number",
- "missing": false,
- "obsolete": false
- },
- "call_busy": {
- "translation": "bận",
- "defaultMessage": "busy",
- "description": "Label for busy call line",
- "missing": false,
- "obsolete": false
- },
- "failed_to_init_audio": {
- "translation": "Không thể khởi tạo ghi âm",
- "defaultMessage": "Failed to initialize audio recording",
- "description": "Error message when audio is not available",
- "missing": false,
- "obsolete": false
- },
- "password_reset_success": {
- "translation": "Đặt lại mật khẩu thành công",
- "defaultMessage": "Password reset successfully",
- "description": "Notification message that the password was successfully reset.",
- "missing": false,
- "obsolete": false
- },
- "scan_qr_code": {
- "translation": "Quét mã QR",
- "defaultMessage": "Scan QR Code",
- "description": "Title for scanning QR code",
- "missing": false,
- "obsolete": false
- },
- "add_members_prompt": {
- "translation": "thêm thành viên",
- "defaultMessage": "add members",
- "description": "Placeholder in group members input field",
- "missing": false,
- "obsolete": false
- },
- "alias_already_taken": {
- "translation": "(đã được sử dụng)",
- "defaultMessage": "(already taken)",
- "description": "Error message for alias already taken",
- "missing": false,
- "obsolete": false
- },
- "alias_editing_placeholder": {
- "translation": "Bí danh (tùy chọn)",
- "defaultMessage": "Alias (optional)",
- "description": "Placeholder for editing user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "alias_invalid": {
- "translation": "(không hợp lệ)",
- "defaultMessage": "(invalid)",
- "description": "Error message for invalid alias",
- "missing": false,
- "obsolete": false
- },
- "button_restore": {
- "translation": "Khôi phục mặc định",
- "defaultMessage": "Restore default",
- "description": "Reset setting to default",
- "missing": false,
- "obsolete": false
- },
- "cannot_parse_vcard": {
- "translation": "Không thể đọc tệp vCard.",
- "defaultMessage": "Cannot parse vCard file.",
- "description": "Error message when vCard file cannot be parsed",
- "missing": false,
- "obsolete": false
- },
- "chat_now": {
- "translation": "Nhắn tin",
- "defaultMessage": "Chat",
- "description": "Label for message button in contact card",
- "missing": false,
- "obsolete": false
- },
- "color_schema_dark": {
- "translation": "Tối",
- "defaultMessage": "Dark",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "color_schema_light": {
- "translation": "Sáng",
- "defaultMessage": "Light",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "color_schema_system": {
- "translation": "Mặc định hệ thống",
- "defaultMessage": "System default",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "find_user": {
- "translation": "Tìm kiếm",
- "defaultMessage": "Find",
- "description": "Label for search button in contact card",
- "missing": false,
- "obsolete": false
- },
- "incognito_mode_description": {
- "translation": "Ẩn trạng thái trực tuyến và chỉ báo đang nhập.",
- "defaultMessage": "Hide your online status and typing indicators.",
- "description": "Description for incognito mode toggle",
- "missing": false,
- "obsolete": false
- },
- "label_alias": {
- "translation": "Bí danh:",
- "defaultMessage": "Alias:",
- "description": "Label for user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "label_alias_edit": {
- "translation": "Bí danh",
- "defaultMessage": "Alias",
- "description": "Label for editing user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "label_blur_wallpaper": {
- "translation": "Làm mờ:",
- "defaultMessage": "Blur:",
- "description": "Label adjusting blur amount",
- "missing": false,
- "obsolete": false
- },
- "label_color_schema": {
- "translation": "Giao diện:",
- "defaultMessage": "Theme:",
- "description": "Label for selecting color scheme (dark, light) in Settings",
- "missing": false,
- "obsolete": false
- },
- "label_contact_card": {
- "translation": "Danh thiếp",
- "defaultMessage": "Contact Card",
- "description": "Label for contact card preview",
- "missing": false,
- "obsolete": false
- },
- "label_contacts": {
- "translation": "Danh bạ",
- "defaultMessage": "Contacts",
- "description": "Label for contact information",
- "missing": false,
- "obsolete": false
- },
- "label_keyboard": {
- "translation": "Bàn phím:",
- "defaultMessage": "Keyboard:",
- "description": "Label for send on enter settings",
- "missing": false,
- "obsolete": false
- },
- "label_member_count": {
- "translation": "Thành viên:",
- "defaultMessage": "Members:",
- "description": "Label for the count of group topic members",
- "missing": false,
- "obsolete": false
- },
- "label_scan_id": {
- "translation": "Quét mã của tôi:",
- "defaultMessage": "Scan my ID:",
- "description": "Label [Scan my ID:]",
- "missing": false,
- "obsolete": false
- },
- "label_subscriber_count": {
- "translation": "Người theo dõi:",
- "defaultMessage": "Subscribers:",
- "description": "Label for the count of channel subscribers",
- "missing": false,
- "obsolete": false
- },
- "label_text_size": {
- "translation": "Cỡ chữ:",
- "defaultMessage": "Text size:",
- "description": "Label adjusting text size",
- "missing": false,
- "obsolete": false
- },
- "member_count": {
- "translation": "{count, plural, one {{count, number} thành viên} other {{count, number} thành viên}}",
- "defaultMessage": "{count, plural, one {{count, number} member} two {{count, number} members} three {{count, number} members} few {{count, number} members} many {{count, number} members} other {{count, number} members}}",
- "description": "Count of group topic members",
- "missing": false,
- "obsolete": false
- },
- "message_deleted": {
- "translation": "tin nhắn đã xóa",
- "defaultMessage": "message deleted",
- "description": "Pinned message is deleted",
- "missing": false,
- "obsolete": false
- },
- "message_not_found": {
- "translation": "không tìm thấy tin nhắn",
- "defaultMessage": "message not found",
- "description": "Pinned message is not found",
- "missing": false,
- "obsolete": false
- },
- "pin_chat": {
- "translation": "Ghim",
- "defaultMessage": "Pin",
- "description": "Menu item [Pin] for pinning chat to the top.",
- "missing": false,
- "obsolete": false
- },
- "pin_message": {
- "translation": "Ghim",
- "defaultMessage": "Pin",
- "description": "Menu item [Pin] for pinning message to the top.",
- "missing": false,
- "obsolete": false
- },
- "save_action": {
- "translation": "Lưu",
- "defaultMessage": "Save",
- "description": "Label for Save button",
- "missing": false,
- "obsolete": false
- },
- "select_country": {
- "translation": "Chọn quốc gia",
- "defaultMessage": "Select country",
- "description": "Placeholder for the country selector",
- "missing": false,
- "obsolete": false
- },
- "self_topic_comment": {
- "translation": "Ghi chú, tin nhắn, liên kết, tệp lưu lại",
- "defaultMessage": "Notes, messages, links, files saved for posterity",
- "description": "Comment for self topic for UI",
- "missing": false,
- "obsolete": false
- },
- "self_topic_name": {
- "translation": "Tin nhắn đã lưu",
- "defaultMessage": "Saved messages",
- "description": "Name of self topic for UI",
- "missing": false,
- "obsolete": false
- },
- "send_command": {
- "translation": "Gửi bằng {key}",
- "defaultMessage": "Send on {key}",
- "description": "Config option to send message on hitting CTRL(or Cmd)-Enter",
- "missing": false,
- "obsolete": false
- },
- "send_command_explained": {
- "translation": "Nhấn Enter để xuống dòng",
- "defaultMessage": "Press Enter for new line",
- "description": "Explanation how to enter newline when [Send on CTRL-Enter] is enabled",
- "missing": false,
- "obsolete": false
- },
- "send_plain": {
- "translation": "Gửi bằng Enter",
- "defaultMessage": "Send on Enter",
- "description": "Config option to send message on hitting Enter",
- "missing": false,
- "obsolete": false
- },
- "send_plain_explained": {
- "translation": "Nhấn Shift + Enter để xuống dòng",
- "defaultMessage": "Press Shift + Enter for new line",
- "description": "Explanation how to enter newline when [Send on Enter] is enabled",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_general": {
- "translation": "Chung",
- "defaultMessage": "General",
- "description": "Sidepanel title for AccGeneralView.",
- "missing": false,
- "obsolete": false
- },
- "subscriber_count": {
- "translation": "{count, plural, one {{count, number} người theo dõi} other {{count, number} người theo dõi}}",
- "defaultMessage": "{count, plural, one {{count, number} subscriber} two {{count, number} subscribers} three {{count, number} subscribers} few {{count, number} subscribers} many {{count, number} subscribers} other {{count, number} subscribers}}",
- "description": "Count of channel subscribers",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_image": {
- "translation": "hình ảnh",
- "defaultMessage": "image",
- "description": "Tab title Image",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_pattern": {
- "translation": "hoa văn",
- "defaultMessage": "pattern",
- "description": "Tab title Pattern",
- "missing": false,
- "obsolete": false
- },
- "text_copied": {
- "translation": "Đã sao chép vào clipboard",
- "defaultMessage": "Copied to clipboard",
- "description": "Notification that text has been copied to clipboard",
- "missing": false,
- "obsolete": false
- },
- "unknown_name": {
- "translation": "Không rõ",
- "defaultMessage": "Unknown",
- "description": "Name to show when the name is missing",
- "missing": false,
- "obsolete": false
- },
- "unpin_chat": {
- "translation": "Bỏ ghim",
- "defaultMessage": "Unpin",
- "description": "Menu item [Unpin] for un-pinning the chat.",
- "missing": false,
- "obsolete": false
- },
- "unpin_message": {
- "translation": "Bỏ ghim",
- "defaultMessage": "Unpin",
- "description": "Menu item [Unpin] for un-pinning the message.",
- "missing": false,
- "obsolete": false
- },
- "wallpapers": {
- "translation": "Hình nền",
- "defaultMessage": "Wallpapers",
- "description": "Link or title for working with wallpapers.",
- "missing": false,
- "obsolete": false
- }
-}
diff --git a/src/i18n/zh-TW.json b/src/i18n/zh-TW.json
deleted file mode 100644
index 93d72721..00000000
--- a/src/i18n/zh-TW.json
+++ /dev/null
@@ -1,1966 +0,0 @@
-{
- "action_block_contact": {
- "translation": "封鎖聯絡人",
- "defaultMessage": "Block Contact",
- "description": "Flat button [Block Contact]",
- "missing": false,
- "obsolete": false
- },
- "action_cancel": {
- "translation": "取消",
- "defaultMessage": "cancel",
- "description": "Call to action [cancel]",
- "missing": false,
- "obsolete": false
- },
- "action_clear_messages": {
- "translation": "清除訊息",
- "defaultMessage": "Clear Messages",
- "description": "Flat button [Clear Messages] (soft-delete messages)",
- "missing": false,
- "obsolete": false
- },
- "action_delete_messages": {
- "translation": "為所有人清除訊息",
- "defaultMessage": "Clear Messages for All",
- "description": "Flat button [Clear for All] (hard-delete all messages)",
- "missing": false,
- "obsolete": false
- },
- "action_leave_chat": {
- "translation": "離開對話",
- "defaultMessage": "Leave Conversation",
- "description": "Flat button [Leave Conversation]",
- "missing": false,
- "obsolete": false
- },
- "action_report_chat": {
- "translation": "檢舉對話",
- "defaultMessage": "Report Conversation",
- "description": "Flat button [Report Group]",
- "missing": false,
- "obsolete": false
- },
- "archived_contacts": {
- "translation": "已封存的聯絡人 ({count})",
- "defaultMessage": "Archived contacts ({count})",
- "description": "Label for archived chats",
- "missing": false,
- "obsolete": false
- },
- "badge_danger": {
- "translation": "不可信",
- "defaultMessage": "Untrustworthy",
- "description": "Suspicious or untrustworthy account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_owner": {
- "translation": "擁有者",
- "defaultMessage": "owner",
- "description": "Badge for indicating the owner",
- "missing": false,
- "obsolete": false
- },
- "badge_staff": {
- "translation": "員工管理",
- "defaultMessage": "Staff-managed",
- "description": "Explanation of a staff-managed account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_verified": {
- "translation": "已驗證/官方",
- "defaultMessage": "Verified/official",
- "description": "Explanation of a verified account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_you": {
- "translation": "您",
- "defaultMessage": "you",
- "description": "Badge for indicating the current user",
- "missing": false,
- "obsolete": false
- },
- "block_contact_warning": {
- "translation": "您確定要封鎖此聯絡人嗎?",
- "defaultMessage": "Are you sure you want to block this contact?",
- "description": "Alert dialog warning when blocking a contact.",
- "missing": false,
- "obsolete": false
- },
- "blocked_contacts_link": {
- "translation": "已封鎖的聯絡人 ({count})",
- "defaultMessage": "Blocked contacts ({count})",
- "description": "Blocked contacts link",
- "missing": false,
- "obsolete": false
- },
- "button_add_members": {
- "translation": "新增成員",
- "defaultMessage": "Add members",
- "description": "Flat button [Add members] (to topic)",
- "missing": false,
- "obsolete": false
- },
- "button_cancel": {
- "translation": "取消",
- "defaultMessage": "Cancel",
- "description": "Button [Cancel]",
- "missing": false,
- "obsolete": false
- },
- "button_confirm": {
- "translation": "確認",
- "defaultMessage": "Confirm",
- "description": "Button [Confirm]",
- "missing": false,
- "obsolete": false
- },
- "button_create": {
- "translation": "建立",
- "defaultMessage": "Create",
- "description": "Button [Create]",
- "missing": false,
- "obsolete": false
- },
- "button_delete_account": {
- "translation": "刪除帳號",
- "defaultMessage": "Delete account",
- "description": "Button [Delete account]",
- "missing": false,
- "obsolete": false
- },
- "button_edit": {
- "translation": "編輯",
- "defaultMessage": "Edit",
- "description": "Call to action [Edit]",
- "missing": false,
- "obsolete": false
- },
- "button_logout": {
- "translation": "登出",
- "defaultMessage": "Logout",
- "description": "Button [Logout]",
- "missing": false,
- "obsolete": false
- },
- "button_ok": {
- "translation": "OK",
- "defaultMessage": "OK",
- "description": "Button [OK]",
- "missing": false,
- "obsolete": false
- },
- "button_reset": {
- "translation": "重設",
- "defaultMessage": "Reset",
- "description": "Button [Reset]",
- "missing": false,
- "obsolete": false
- },
- "button_send_request": {
- "translation": "傳送要求",
- "defaultMessage": "Send request",
- "description": "Button [Send request]",
- "missing": false,
- "obsolete": false
- },
- "button_sign_in": {
- "translation": "登入",
- "defaultMessage": "Sign in",
- "description": "Button [Sign In]",
- "missing": false,
- "obsolete": false
- },
- "button_sign_up": {
- "translation": "註冊",
- "defaultMessage": "Sign up",
- "description": "Create account button [Sign Up]",
- "missing": false,
- "obsolete": false
- },
- "button_subscribe": {
- "translation": "訂閱",
- "defaultMessage": "Subscribe",
- "description": "Button [Subscribe]",
- "missing": false,
- "obsolete": false
- },
- "button_update": {
- "translation": "更新",
- "defaultMessage": "Update",
- "description": "Button [Update]",
- "missing": false,
- "obsolete": false
- },
- "cannot_initiate_file_upload": {
- "translation": "無法為檔案上傳初始化。",
- "defaultMessage": "Cannot initiate file upload.",
- "description": "Generic error messagewhen attachment fails",
- "missing": false,
- "obsolete": false
- },
- "channel": {
- "translation": "頻道",
- "defaultMessage": "channel",
- "description": "Subtitle shown for channels in MessagesView instead of last seen",
- "missing": false,
- "obsolete": false
- },
- "channel_prompt": {
- "translation": "這是頻道",
- "defaultMessage": "This is a channel",
- "description": "Checkbox label when creating a channel",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation": {
- "translation": "您已被邀請開始新對話。您接下來要做什麼呢?",
- "defaultMessage": "You are invited to start a new chat. What would you like to do?",
- "description": "New chat invitation message: [Accept] [Ignore] [Block].",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_accept": {
- "translation": "接受",
- "defaultMessage": "Accept",
- "description": "Action [Accept] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_block": {
- "translation": "封鎖",
- "defaultMessage": "Block",
- "description": "Action [Block] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_ignore": {
- "translation": "忽略",
- "defaultMessage": "Ignore",
- "description": "Action [Ignore] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "clear_messages_warning": {
- "translation": "您確定要清除所有訊息嗎?此操作無法復原。",
- "defaultMessage": "Are you sure you want to clear all messages? It cannot be undone.",
- "description": "Alert dialog warning when deleting all messages.",
- "missing": false,
- "obsolete": false
- },
- "code_doesnot_match": {
- "translation": "代碼不相符",
- "defaultMessage": "Code does not match",
- "description": "Error message when the credential validation code is incorrect.",
- "missing": false,
- "obsolete": false
- },
- "contacts_not_found": {
- "translation": "您沒有任何對話\n¯∖_(ツ)_/¯",
- "defaultMessage": "You have no chats\\n¯∖_(ツ)_/¯",
- "description": "HTML message shown in ContactList when no contacts are found",
- "missing": false,
- "obsolete": false
- },
- "contacts_not_found_short": {
- "translation": "沒有符合 ''{query}'' 的聯絡人",
- "defaultMessage": "No contacts match ''{query}''",
- "description": "Shown in ContactsView when search returned no results",
- "missing": false,
- "obsolete": false
- },
- "credential_email_prompt": {
- "translation": "您的註冊電子郵件地址",
- "defaultMessage": "Your registration email",
- "description": "Placeholder for entering email",
- "missing": false,
- "obsolete": false
- },
- "delete_account": {
- "translation": "刪除帳號",
- "defaultMessage": "Delete account",
- "description": "Title for delete account warning",
- "missing": false,
- "obsolete": false
- },
- "delete_account_warning": {
- "translation": "您確定要刪除您的帳戶嗎?此操作無法撤消。",
- "defaultMessage": "Are you sure you want to delete your account? It cannot be undone.",
- "description": "Warning message when deleting an account",
- "missing": false,
- "obsolete": false
- },
- "delete_messages_warning": {
- "translation": "您確定要為所有人刪除所有訊息嗎?此操作無法復原。",
- "defaultMessage": "Are you sure you want to delete all messages for everyone? It cannot be undone.",
- "description": "Alert dialog warning when hard-deleting all messages.",
- "missing": false,
- "obsolete": false
- },
- "download_action": {
- "translation": "下載",
- "defaultMessage": "download",
- "description": "Call to action [download]",
- "missing": false,
- "obsolete": false
- },
- "drafty_attachment": {
- "translation": "附加檔案",
- "defaultMessage": "Attachment",
- "description": "Comment for attachment in Drafty",
- "missing": false,
- "obsolete": false
- },
- "drafty_form": {
- "translation": "表單:",
- "defaultMessage": "Form:",
- "description": "Comment for form in Drafty",
- "missing": false,
- "obsolete": false
- },
- "drafty_image": {
- "translation": "圖片",
- "defaultMessage": "Picture",
- "description": "Comment for embedded images in Drafty",
- "missing": false,
- "obsolete": false
- },
- "email_dative": {
- "translation": "電子郵件地址",
- "defaultMessage": "email",
- "description": "Dative case of 'email', i.e. 'email' in 'by email'",
- "missing": false,
- "obsolete": false
- },
- "email_prompt": {
- "translation": "電子郵件地址,例如:jdoe@example.com",
- "defaultMessage": "Email, e.g. jdoe@example.com",
- "description": "Input placeholder for email entry",
- "missing": false,
- "obsolete": false
- },
- "enable_peers_messaging": {
- "translation": "啟用",
- "defaultMessage": "Enable",
- "description": "Call to action to enable peer's messaging",
- "missing": false,
- "obsolete": false
- },
- "enter_confirmation_code_prompt": {
- "translation": "輸入透過 {method} 傳送給您的確認碼:",
- "defaultMessage": "Confirmation code",
- "description": "Request to enter confirmation code",
- "missing": false,
- "obsolete": false
- },
- "error_invalid_id": {
- "translation": "ID 無效",
- "defaultMessage": "Invalid ID",
- "description": "Error message",
- "missing": false,
- "obsolete": false
- },
- "file_attachment_too_large": {
- "translation": "檔案大小 {size} 超出 {limit} 限制。",
- "defaultMessage": "The file size {size} exceeds the {limit} limit.",
- "description": "Error message when attachment is too large",
- "missing": false,
- "obsolete": false
- },
- "forgot_password_link": {
- "translation": "忘記密碼?",
- "defaultMessage": "Forgot password?",
- "description": "Link to Reset password form",
- "missing": false,
- "obsolete": false
- },
- "full_name_prompt": {
- "translation": "全名,例如:王小明",
- "defaultMessage": "Full name, e.g. John Doe",
- "description": "Input placeholder for person's full name",
- "missing": false,
- "obsolete": false
- },
- "granted_permissions": {
- "translation": "已授權",
- "defaultMessage": "Granted",
- "description": "Title for permissions",
- "missing": false,
- "obsolete": false
- },
- "group_has_no_members": {
- "translation": "無成員",
- "defaultMessage": "No members",
- "description": "Shown in place of group members",
- "missing": false,
- "obsolete": false
- },
- "group_user_id_prompt": {
- "translation": "群組或使用者 ID",
- "defaultMessage": "Group or User ID",
- "description": "Prompt for entering user or group ID",
- "missing": false,
- "obsolete": false
- },
- "image_caption_prompt": {
- "translation": "圖片說明",
- "defaultMessage": "Image caption",
- "description": "Prompt in SendMessage for attached image",
- "missing": false,
- "obsolete": false
- },
- "invalid_content": {
- "translation": "內容無效",
- "defaultMessage": "invalid content",
- "description": "Shown when the message is unreadable",
- "missing": false,
- "obsolete": false
- },
- "invalid_security_token": {
- "translation": "安全權杖無效",
- "defaultMessage": "Invalid security token",
- "description": "Error message when resetting password.",
- "missing": false,
- "obsolete": false
- },
- "label_client": {
- "translation": "客戶端:",
- "defaultMessage": "Client:",
- "description": "Label for a client version",
- "missing": false,
- "obsolete": false
- },
- "label_content_type": {
- "translation": "類型:",
- "defaultMessage": "Content type:",
- "description": "Label for file content type (mime)",
- "missing": false,
- "obsolete": false
- },
- "label_default_access_mode": {
- "translation": "預設存取模式:",
- "defaultMessage": "Default access mode:",
- "description": "Label for default access mode",
- "missing": false,
- "obsolete": false
- },
- "label_file_name": {
- "translation": "檔名:",
- "defaultMessage": "File name:",
- "description": "Label for a file name",
- "missing": false,
- "obsolete": false
- },
- "label_group_members": {
- "translation": "群組成員:",
- "defaultMessage": "Group members:",
- "description": "Section title or label",
- "missing": false,
- "obsolete": false
- },
- "label_incognito_mode": {
- "translation": "無痕模式:",
- "defaultMessage": "Incognito mode:",
- "description": "Label for incognito mode toggle",
- "missing": false,
- "obsolete": false
- },
- "label_message_sound": {
- "translation": "訊息提示聲:",
- "defaultMessage": "Message sound:",
- "description": "Label for message sounds toggle",
- "missing": false,
- "obsolete": false
- },
- "label_muting_topic": {
- "translation": "靜音:",
- "defaultMessage": "Muted:",
- "description": "Label for Muting/unmuting the topic",
- "missing": false,
- "obsolete": false
- },
- "label_other_user": {
- "translation": "其他",
- "defaultMessage": "Other",
- "description": "Label for the other user when the user is unnamed",
- "missing": false,
- "obsolete": false
- },
- "label_password": {
- "translation": "密碼",
- "defaultMessage": "Password",
- "description": "Label for password editing",
- "missing": false,
- "obsolete": false
- },
- "label_permissions": {
- "translation": "權限:",
- "defaultMessage": "Permissions:",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "label_private": {
- "translation": "私人留言",
- "defaultMessage": "Private comment",
- "description": "Label for editing 'private'",
- "missing": false,
- "obsolete": false
- },
- "label_push_notifications": {
- "translation": "通知:",
- "defaultMessage": "Notification alerts:",
- "description": "Label for push notifications switch",
- "missing": false,
- "obsolete": false
- },
- "label_push_notifications_disabled": {
- "translation": "通知 (需要 HTTPS):",
- "defaultMessage": "Notification alerts (requires HTTPS):",
- "description": "Label for push notifications switch",
- "missing": false,
- "obsolete": false
- },
- "label_reset_password": {
- "translation": "傳送重設密碼郵件:",
- "defaultMessage": "Send a password reset email",
- "description": "Label for password reset field",
- "missing": false,
- "obsolete": false
- },
- "label_sdk": {
- "translation": "SDK:",
- "defaultMessage": "SDK:",
- "missing": false,
- "obsolete": false
- },
- "label_server": {
- "translation": "伺服器:",
- "defaultMessage": "Server:",
- "description": "Label for a server version",
- "missing": false,
- "obsolete": false
- },
- "label_server_address": {
- "translation": "伺服器位址:",
- "defaultMessage": "Server address:",
- "missing": false,
- "obsolete": false
- },
- "label_server_to_use": {
- "translation": "使用的伺服器:",
- "defaultMessage": "Server to use:",
- "description": "Label for server selector in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "label_size": {
- "translation": "大小:",
- "defaultMessage": "Size:",
- "description": "Label for file size",
- "missing": false,
- "obsolete": false
- },
- "label_topic_name": {
- "translation": "名稱",
- "defaultMessage": "Name",
- "description": "Label for editing topic name",
- "missing": false,
- "obsolete": false
- },
- "label_user_contacts": {
- "translation": "聯絡人",
- "defaultMessage": "Contacts",
- "description": "Label for user contacts",
- "missing": false,
- "obsolete": false
- },
- "label_user_id": {
- "translation": "ID:",
- "defaultMessage": "ID:",
- "description": "Label for user address (ID)",
- "missing": false,
- "obsolete": false
- },
- "label_wire_transport": {
- "translation": "線路傳輸:",
- "defaultMessage": "Wire transport:",
- "description": "Label for wire transport selection in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "label_you": {
- "translation": "您:",
- "defaultMessage": "You:",
- "description": "Label for the current user",
- "missing": false,
- "obsolete": false
- },
- "label_your_name": {
- "translation": "您的名字",
- "defaultMessage": "Your name",
- "description": "Label for full name editing",
- "missing": false,
- "obsolete": false
- },
- "label_your_permissions": {
- "translation": "您的權限:",
- "defaultMessage": "Your permissions:",
- "description": "Label for current user permissions",
- "missing": false,
- "obsolete": false
- },
- "last_seen_timestamp": {
- "translation": "最後上線",
- "defaultMessage": "Last seen: {timestamp}",
- "description": "Label for the timestamp of when the user or topic was last online",
- "missing": false,
- "obsolete": false
- },
- "leave_chat_warning": {
- "translation": "您確定要離開此對話嗎?",
- "defaultMessage": "Are you sure you want to leave this conversation?",
- "description": "Alert dialog warning when unsubscribing from a chat.",
- "missing": false,
- "obsolete": false
- },
- "link_contact_us": {
- "translation": "聯絡我們",
- "defaultMessage": "Contact Us",
- "description": "Ancor text for contacting us by email",
- "missing": false,
- "obsolete": false
- },
- "link_privacy_policy": {
- "translation": "隱私權政策",
- "defaultMessage": "Privacy Policy",
- "description": "Ancor text for privacy policy link",
- "missing": false,
- "obsolete": false
- },
- "link_terms_of_service": {
- "translation": "服務條款",
- "defaultMessage": "Terms of Service",
- "description": "Ancor text for terms of service link",
- "missing": false,
- "obsolete": false
- },
- "login_prompt": {
- "translation": "登入",
- "defaultMessage": "Login",
- "description": "Placeholer for username/login",
- "missing": false,
- "obsolete": false
- },
- "menu_item_archive_topic": {
- "translation": "封存",
- "defaultMessage": "Archive",
- "description": "Move topic from the list of active chats to archive",
- "missing": false,
- "obsolete": false
- },
- "menu_item_block": {
- "translation": "封鎖",
- "defaultMessage": "Block",
- "description": "Block topic or user",
- "missing": false,
- "obsolete": false
- },
- "menu_item_clear_messages": {
- "translation": "清除訊息",
- "defaultMessage": "Clear messages",
- "description": "Delete all messages",
- "missing": false,
- "obsolete": false
- },
- "menu_item_clear_messages_for_all": {
- "translation": "為所有人清除訊息",
- "defaultMessage": "Clear for All",
- "description": "Delete all message(s) for all members",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete": {
- "translation": "刪除",
- "defaultMessage": "Delete",
- "description": "Delete selected messages",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete_for_all": {
- "translation": "為所有人刪除",
- "defaultMessage": "Delete for All",
- "description": "Delete selected message(s) for all members",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete_topic": {
- "translation": "刪除",
- "defaultMessage": "Delete",
- "description": "Delete entire topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_edit_permissions": {
- "translation": "編輯權限",
- "defaultMessage": "Edit permissions",
- "description": "Menu item [Edit permissions]",
- "missing": false,
- "obsolete": false
- },
- "menu_item_info": {
- "translation": "Info",
- "defaultMessage": "Info",
- "description": "Show extended topic information",
- "missing": false,
- "obsolete": false
- },
- "menu_item_member_delete": {
- "translation": "移除",
- "defaultMessage": "Remove",
- "description": "Remove user from topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_mute": {
- "translation": "靜音",
- "defaultMessage": "Mute",
- "description": "Turn off notifications",
- "missing": false,
- "obsolete": false
- },
- "menu_item_restore_topic": {
- "translation": "恢復",
- "defaultMessage": "Restore",
- "description": "Restore topic from archive",
- "missing": false,
- "obsolete": false
- },
- "menu_item_send_retry": {
- "translation": "重試",
- "defaultMessage": "Retry",
- "description": "Retry sending message",
- "missing": false,
- "obsolete": false
- },
- "menu_item_unblock": {
- "translation": "解除封鎖",
- "defaultMessage": "Unblock",
- "description": "Unblock topic or user",
- "missing": false,
- "obsolete": false
- },
- "menu_item_unmute": {
- "translation": "解除靜音",
- "defaultMessage": "Unmute",
- "description": "Turn notifications on",
- "missing": false,
- "obsolete": false
- },
- "message_sending": {
- "translation": "正在傳送...",
- "defaultMessage": "sending...",
- "description": "Message being sent, in place of time stamp",
- "missing": false,
- "obsolete": false
- },
- "message_sending_failed": {
- "translation": "失敗",
- "defaultMessage": "failed",
- "description": "Failed to send message, in place of time stamp",
- "missing": false,
- "obsolete": false
- },
- "messages_not_readable": {
- "translation": "沒有存取訊息的權限",
- "defaultMessage": "no access to messages",
- "description": "Message shown in topic without the read access",
- "missing": false,
- "obsolete": false
- },
- "messaging_disabled_prompt": {
- "translation": "停用訊息",
- "defaultMessage": "Messaging disabled",
- "description": "Prompt in SendMessage in read-only topic",
- "missing": false,
- "obsolete": false
- },
- "more_online_members": {
- "translation": "+{overflow}",
- "defaultMessage": "+{overflow} more",
- "description": "Shown in MessagesView title bar when the number of online subscribers exceeds MAX_ONLINE_IN_TOPIC",
- "missing": false,
- "obsolete": false
- },
- "new_message_prompt": {
- "translation": "新訊息",
- "defaultMessage": "New message",
- "description": "Prompt in send message field",
- "missing": false,
- "obsolete": false
- },
- "new_password_placeholder": {
- "translation": "輸入新的密碼",
- "defaultMessage": "Enter new password",
- "description": "Placeholder for entering new password",
- "missing": false,
- "obsolete": false
- },
- "no_connection": {
- "translation": "無連線",
- "defaultMessage": "No connection",
- "description": "Warning that the user is offline.",
- "missing": false,
- "obsolete": false
- },
- "no_contacts": {
- "translation": "您沒有任何聯絡人 :-(",
- "defaultMessage": "You have no contacts :-(",
- "description": "Shown in ContactsView when the user has no contacts",
- "missing": false,
- "obsolete": false
- },
- "numeric_confirmation_code_prompt": {
- "translation": "僅數字",
- "defaultMessage": "Numbers only",
- "description": "Prompt for numeric conformation code",
- "missing": false,
- "obsolete": false
- },
- "online_now": {
- "translation": "上線中",
- "defaultMessage": "online now",
- "description": "Indicator that the user or topic is currently online",
- "missing": false,
- "obsolete": false
- },
- "password_prompt": {
- "translation": "密碼",
- "defaultMessage": "Password",
- "description": "Placeholder/prompt for entering password",
- "missing": false,
- "obsolete": false
- },
- "password_unchanged_prompt": {
- "translation": "未修改",
- "defaultMessage": "Unchanged",
- "description": "Message in editor while password is unchanged",
- "missing": false,
- "obsolete": false
- },
- "peers_messaging_disabled": {
- "translation": "Peer 已停用訊息。",
- "defaultMessage": "Peer's messaging is disabled.",
- "description": "Shown when the p2p peer's messaging is disabled",
- "missing": false,
- "obsolete": false
- },
- "permission_admin": {
- "translation": "核可 ({val})",
- "defaultMessage": "Approve ({val})",
- "description": "Name of A permission",
- "missing": false,
- "obsolete": false
- },
- "permission_delete": {
- "translation": "刪除 ({val})",
- "defaultMessage": "Delete ({val})",
- "description": "Name of D permission",
- "missing": false,
- "obsolete": false
- },
- "permission_join": {
- "translation": "加入 ({val})",
- "defaultMessage": "Join ({val})",
- "description": "Name of J permission",
- "missing": false,
- "obsolete": false
- },
- "permission_owner": {
- "translation": "擁有者 ({val})",
- "defaultMessage": "Owner ({val})",
- "description": "Name of O permission",
- "missing": false,
- "obsolete": false
- },
- "permission_pres": {
- "translation": "收到通知 ({val})",
- "defaultMessage": "Get notified ({val})",
- "description": "Name of P permission",
- "missing": false,
- "obsolete": false
- },
- "permission_read": {
- "translation": "讀取 ({val})",
- "defaultMessage": "Read ({val})",
- "description": "Name of R permission",
- "missing": false,
- "obsolete": false
- },
- "permission_share": {
- "translation": "分享 ({val})",
- "defaultMessage": "Share ({val})",
- "description": "Name of S permission",
- "missing": false,
- "obsolete": false
- },
- "permission_write": {
- "translation": "撰寫 ({val})",
- "defaultMessage": "Write ({val})",
- "description": "Name of W permission",
- "missing": false,
- "obsolete": false
- },
- "phone_dative": {
- "translation": "phone",
- "defaultMessage": "phone",
- "description": "Dative case of 'phone', i.e. 'phone' in 'by phone'",
- "missing": false,
- "obsolete": false
- },
- "private_editing_placeholder": {
- "translation": "僅您可見",
- "defaultMessage": "Visible to you only",
- "description": "Placeholder for editing 'private'",
- "missing": false,
- "obsolete": false
- },
- "push_init_failed": {
- "translation": "初始化通知失敗。",
- "defaultMessage": "Failed to initialize push notifications",
- "description": "Error message when push notifications have failed to initialize.",
- "missing": false,
- "obsolete": false
- },
- "reconnect_countdown": {
- "translation": "已斷線。將在 {seconds} 秒後重新連線…",
- "defaultMessage": "Disconnected. Reconnecting in {seconds}…",
- "description": "Message shown when an app update is available.",
- "missing": false,
- "obsolete": false
- },
- "reconnect_now": {
- "translation": "現在重試",
- "defaultMessage": "Try now",
- "description": "Prompt for reconnecting now",
- "missing": false,
- "obsolete": false
- },
- "reload_update": {
- "translation": "重新載入",
- "defaultMessage": "Reload",
- "description": "Call to action to reload application when update is available.",
- "missing": false,
- "obsolete": false
- },
- "report_chat_warning": {
- "translation": "您確定要封鎖並檢舉此對話嗎?",
- "defaultMessage": "Are you sure you want to block and report this conversation?",
- "description": "Alert dialog warning when reporting a conversation for abuse",
- "missing": false,
- "obsolete": false
- },
- "requested_permissions": {
- "translation": "重新請求",
- "defaultMessage": "Requested",
- "description": "Title for permissions",
- "missing": false,
- "obsolete": false
- },
- "save_attachment": {
- "translation": "儲存",
- "defaultMessage": "save",
- "description": "Call to save an attachment",
- "missing": false,
- "obsolete": false
- },
- "search_for_contacts": {
- "translation": "使用搜尋來尋找聯絡人",
- "defaultMessage": "Use search to find contacts",
- "description": "Text shown in contacts view when user entered no search query.",
- "missing": false,
- "obsolete": false
- },
- "search_no_results": {
- "translation": "搜尋無結果",
- "defaultMessage": "Search returned no results",
- "description": "Text shown in contacts view when query returned no results.",
- "missing": false,
- "obsolete": false
- },
- "search_placeholder": {
- "translation": "例如 email:alice@example.com, tel:17025550003... 的清單",
- "defaultMessage": "List like alice@example.com, +17025550003...",
- "description": "Placeholder in contacts search field",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_notifications": {
- "translation": "通知",
- "defaultMessage": "Notifications",
- "description": "Sidepanel title for AccNotificationsView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_support": {
- "translation": "支援",
- "defaultMessage": "Support",
- "description": "Sidepanel title for AccSupportView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_account_settings": {
- "translation": "帳號設定",
- "defaultMessage": "Account Settings",
- "description": "Sidepanel title for AccountSettingsView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_archive": {
- "translation": "已封存的對話",
- "defaultMessage": "Archived Chats",
- "description": "Sidepanel title for ContactsView-Archive.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_blocked": {
- "translation": "已封鎖的對話",
- "defaultMessage": "Blocked Chats",
- "description": "Sidepanel title for ContactsView-Blocked.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_cred": {
- "translation": "確認認證",
- "defaultMessage": "Confirm Credentials",
- "description": "Sidepanel title for ValidationView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_login": {
- "translation": "登入",
- "defaultMessage": "Sign In",
- "description": "Sidepanel title for LoginView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_newtpk": {
- "translation": "開始新對話",
- "defaultMessage": "Start New Chat",
- "description": "Sidepanel title for NewTopicView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_register": {
- "translation": "建立帳號",
- "defaultMessage": "Create Account",
- "description": "Sidepanel title for CreateAccountView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_reset": {
- "translation": "重設密碼",
- "defaultMessage": "Reset Password",
- "description": "Sidepanel title for PasswordResetView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_settings": {
- "translation": "設定",
- "defaultMessage": "Settings",
- "description": "Sidepanel title for SettingsView.",
- "missing": false,
- "obsolete": false
- },
- "stay_logged_in": {
- "translation": "保持登入",
- "defaultMessage": "Stay logged in",
- "description": "Label for a checkbox",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_find_user": {
- "translation": "尋找",
- "defaultMessage": "find",
- "description": "Tab title Find",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_group_by_id": {
- "translation": "以 id",
- "defaultMessage": "by id",
- "description": "Tab title Find topic by ID",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_new_group": {
- "translation": "新群組",
- "defaultMessage": "new group",
- "description": "Tab title New Group",
- "missing": false,
- "obsolete": false
- },
- "tags_editor_no_tags": {
- "translation": "新增一些標籤",
- "defaultMessage": "Add some tags",
- "description": "Tag editor prompt when no tags are found.",
- "missing": false,
- "obsolete": false
- },
- "tags_not_found": {
- "translation": "未定義任何標籤。請新增一些標籤。",
- "defaultMessage": "No tags defined. Add some.",
- "description": "",
- "missing": false,
- "obsolete": false
- },
- "title_all_contacts": {
- "translation": "所有聯絡人",
- "defaultMessage": "All Contacts",
- "description": "Section title [All Contacts]",
- "missing": false,
- "obsolete": false
- },
- "title_group_members": {
- "translation": "群組成員",
- "defaultMessage": "Group Members",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "title_manage_tags": {
- "translation": "管理",
- "defaultMessage": "Manage",
- "description": "Section title for the list of tags",
- "missing": false,
- "obsolete": false
- },
- "title_not_found": {
- "translation": "找不到",
- "defaultMessage": "Not found",
- "description": "Title shown when topic is not found",
- "missing": false,
- "obsolete": false
- },
- "title_permissions": {
- "translation": "權限",
- "defaultMessage": "Permissions",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "title_tag_manager": {
- "translation": "標籤 (使用者探索)",
- "defaultMessage": "Tags (search & discovery)",
- "description": "Section title for TagManager",
- "missing": false,
- "obsolete": false
- },
- "topic_block_warning": {
- "translation": "您確定要封鎖此對話嗎?",
- "defaultMessage": "Are you sure you want to block this conversation?",
- "description": "Alert warning when blocking a topic.",
- "missing": false,
- "obsolete": false
- },
- "topic_delete_warning": {
- "translation": "您確定要刪除此對話嗎?",
- "defaultMessage": "Are you sure you want to delete this conversation? It cannot be undone.",
- "description": "Alert warning when deleting entire topic",
- "missing": false,
- "obsolete": false
- },
- "topic_name_editing_placeholder": {
- "translation": "群組的任意名稱",
- "defaultMessage": "Freeform name of the group",
- "description": "Prompt for entering topic name",
- "missing": false,
- "obsolete": false
- },
- "unnamed_topic": {
- "translation": "未命名",
- "defaultMessage": "Unnamed",
- "description": "Title shown when the topic has no name",
- "missing": false,
- "obsolete": false
- },
- "update_available": {
- "translation": "有可用的更新。",
- "defaultMessage": "Update available.",
- "description": "Message shown when an app update is available.",
- "missing": false,
- "obsolete": false
- },
- "upload_finishing": {
- "translation": "正在完成...",
- "defaultMessage": "finishing...",
- "description": "Notification that upload is finishing",
- "missing": false,
- "obsolete": false
- },
- "user_not_found": {
- "translation": "找不到",
- "defaultMessage": "Not found",
- "description": "In place of a user's full name when the user is not found.",
- "missing": false,
- "obsolete": false
- },
- "description_editing_placeholder": {
- "translation": "說明(可選)",
- "defaultMessage": "Description (optional)",
- "description": "Placeholder for editing topic description",
- "missing": false,
- "obsolete": false
- },
- "label_description": {
- "translation": "說明",
- "defaultMessage": "Description",
- "description": "Label for description of user or topic",
- "missing": false,
- "obsolete": false
- },
- "button_security": {
- "translation": "安全性",
- "defaultMessage": "Security",
- "description": "Navigaton button for security panel.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_crop": {
- "translation": "拖動調整",
- "defaultMessage": "Drag to Adjust",
- "description": "Title for AvatarCropView.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_general": {
- "translation": "一般",
- "defaultMessage": "General",
- "description": "Title for TopicCommon.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_members": {
- "translation": "成員",
- "defaultMessage": "Members",
- "description": "Title for managing group members view.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_security": {
- "translation": "安全性",
- "defaultMessage": "Security",
- "description": "Title for TopicSecirity and AccSecurity.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_info": {
- "translation": "資訊",
- "defaultMessage": "Info",
- "description": "Title for InfoView",
- "missing": false,
- "obsolete": false
- },
- "permissions_anonymous": {
- "translation": "匿名",
- "defaultMessage": "Anonymous",
- "description": "Title for editing default anonymous permissions",
- "missing": false,
- "obsolete": false
- },
- "permissions_authenticated": {
- "translation": "已認證",
- "defaultMessage": "Authenticated",
- "description": "Title for editing default authenticated permissions",
- "missing": false,
- "obsolete": false
- },
- "topic_delete": {
- "translation": "刪除聊天",
- "defaultMessage": "Delete Conversation",
- "description": "Alert title when deleting the topic.",
- "missing": false,
- "obsolete": false
- },
- "permissions_user": {
- "translation": "用戶權限",
- "defaultMessage": "User's Permissions",
- "description": "Title for editing user's permissions",
- "missing": false,
- "obsolete": false
- },
- "password_reset_email_sent": {
- "translation": "已發送一封帶有安全代碼的電子郵件。",
- "defaultMessage": "An email with security code has been sent.",
- "description": "Notification that the email with password reset instructions has been sent",
- "missing": false,
- "obsolete": false
- },
- "label_unarchive_topic": {
- "translation": "存檔聊天:",
- "defaultMessage": "Archived:",
- "description": "Label for unarchiving the topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_reply": {
- "translation": "回答",
- "defaultMessage": "Reply",
- "description": "Reply to message",
- "missing": false,
- "obsolete": false
- },
- "menu_item_forward": {
- "translation": "重定向",
- "defaultMessage": "Forward",
- "description": "Forward message",
- "missing": false,
- "obsolete": false
- },
- "forward_to": {
- "translation": "重定向消息",
- "defaultMessage": "Forward to",
- "missing": false,
- "obsolete": false
- },
- "forward_to_search_placeholder": {
- "translation": "搜索聯繫人",
- "defaultMessage": "Search contacts",
- "description": "Contact search prompt when forwarding a message.",
- "missing": false,
- "obsolete": false
- },
- "label_new_password": {
- "translation": "新密碼",
- "defaultMessage": "New password",
- "description": "Label for entering a new password",
- "missing": false,
- "obsolete": false
- },
- "drafty_unknown": {
- "translation": "不支持",
- "defaultMessage": "Unsupported",
- "description": "Unsupported entity in drafty",
- "missing": false,
- "obsolete": false
- },
- "calls_incoming": {
- "translation": "來電",
- "defaultMessage": "Incoming call",
- "description": "Incoming call label",
- "missing": false,
- "obsolete": false
- },
- "calls_outgoing": {
- "translation": "撥出電話",
- "defaultMessage": "Outgoing call",
- "description": "Outgoing call label",
- "missing": false,
- "obsolete": false
- },
- "calls_you_label": {
- "translation": "你",
- "defaultMessage": "You",
- "description": "Shown over the local video screen",
- "missing": false,
- "obsolete": false
- },
- "menu_item_video_call": {
- "translation": "視頻電話",
- "defaultMessage": "Video call",
- "description": "Start video call",
- "missing": false,
- "obsolete": false
- },
- "already_in_call": {
- "translation": "你已經在說話了!",
- "defaultMessage": "You already in an ongoing call!",
- "description": "Error message when the user tried to accept a new call without finishing pervious one",
- "missing": false,
- "obsolete": false
- },
- "call_cancelled": {
- "translation": "取消",
- "defaultMessage": "cancelled",
- "description": "Label for cancelled outgoing call",
- "missing": false,
- "obsolete": false
- },
- "call_missed": {
- "translation": "未接",
- "defaultMessage": "missed",
- "description": "Label for missed incoming call",
- "missing": false,
- "obsolete": false
- },
- "call_declined": {
- "translation": "被拒",
- "defaultMessage": "declined",
- "description": "Label for declined call",
- "missing": false,
- "obsolete": false
- },
- "call_disconnected": {
- "translation": "斷開連接",
- "defaultMessage": "disconnected",
- "description": "Label for disconnected call",
- "missing": false,
- "obsolete": false
- },
- "label_use_secure_connection": {
- "translation": "使用安全連接",
- "defaultMessage": "Use secure connection",
- "description": "Label for WS/WSS connection type in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "cred_confirmed_successfully": {
- "translation": "確認成功",
- "defaultMessage": "Confirmed successfully",
- "description": "Notification message that the credential was successfully validated.",
- "missing": false,
- "obsolete": false
- },
- "menu_item_edit": {
- "translation": "編輯",
- "defaultMessage": "Edit",
- "description": "Edit message",
- "missing": false,
- "obsolete": false
- },
- "editing_message": {
- "translation": "編輯",
- "defaultMessage": "Editing",
- "description": "Title over message editing preview",
- "missing": false,
- "obsolete": false
- },
- "message_edited_marker": {
- "translation": ",編輯",
- "defaultMessage": ", edited",
- "description": "Marker indicating that the message was edited",
- "missing": false,
- "obsolete": false
- },
- "icon_title_add_image": {
- "translation": "添加圖片",
- "defaultMessage": "Add image",
- "description": "Icon tool tip for attaching an image",
- "missing": false,
- "obsolete": false
- },
- "icon_title_attach_file": {
- "translation": "附加文件",
- "defaultMessage": "Attach file",
- "description": "Icon tool tip for attaching a file",
- "missing": false,
- "obsolete": false
- },
- "icon_title_delete": {
- "translation": "刪除錄音",
- "defaultMessage": "Delete recording",
- "description": "Icon tool tip for deleting recorded audio",
- "missing": false,
- "obsolete": false
- },
- "icon_title_pause": {
- "translation": "暫停播放",
- "defaultMessage": "Pause playback",
- "description": "Icon tool tip for pausing audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_play": {
- "translation": "播放錄音",
- "defaultMessage": "Play recording",
- "description": "Icon tool tip for starting audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_record_voice": {
- "translation": "錄製語音消息",
- "defaultMessage": "Record voice message",
- "description": "Icon tool tip for recording a voice message",
- "missing": false,
- "obsolete": false
- },
- "icon_title_resume": {
- "translation": "恢復播放",
- "defaultMessage": "Resume playback",
- "description": "Icon tool tip for resuming audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_send": {
- "translation": "發信息",
- "defaultMessage": "Send message",
- "description": "Icon tool tip for sending a message",
- "missing": false,
- "obsolete": false
- },
- "drag_file": {
- "translation": "將文件拖到這裡",
- "defaultMessage": "Drag file here",
- "description": "Prompt on the file drag-n-drop overlay banner",
- "missing": false,
- "obsolete": false
- },
- "drafty_video": {
- "translation": "視頻錄製",
- "defaultMessage": "Video recording",
- "description": "Comment for videos embedded in Drafty",
- "missing": false,
- "obsolete": false
- },
- "call_in_progress": {
- "translation": "通話中",
- "defaultMessage": "in progress",
- "description": "Label for call in progress",
- "missing": false,
- "obsolete": false
- },
- "menu_item_audio_call": {
- "translation": "語音電話",
- "defaultMessage": "Call",
- "description": "Start audio call",
- "missing": false,
- "obsolete": false
- },
- "unrecognized_video_format": {
- "translation": "無法識別此視頻的格式",
- "defaultMessage": "Format of this video is not recognized",
- "description": "Error message when uploaded video is invalid",
- "missing": false,
- "obsolete": false
- },
- "loading_note": {
- "translation": "加載中...",
- "defaultMessage": "Loading...",
- "description": "Message shown when component is loading",
- "missing": false,
- "obsolete": false
- },
- "password_i_have_code": {
- "translation": "我有代碼",
- "defaultMessage": "I have code",
- "description": "Call to open field to enter password reset code",
- "missing": false,
- "obsolete": false
- },
- "label_reset_password_tel": {
- "translation": "發送短信重置密碼",
- "defaultMessage": "Send SMS to reset password",
- "description": "Label for password reset field by phone",
- "missing": false,
- "obsolete": false
- },
- "mobile_phone_number": {
- "translation": "手機號碼",
- "defaultMessage": "Mobile phone number",
- "description": "Prompt for entering a mobile phone number",
- "missing": false,
- "obsolete": false
- },
- "password_reset_sms_sent": {
- "translation": "已發送帶有安全代碼的短信。",
- "defaultMessage": "A text message with security code has been sent.",
- "description": "Notification that the SMS with password reset instructions has been sent",
- "missing": false,
- "obsolete": false
- },
- "mobile_number_required": {
- "translation": "需要手機號",
- "defaultMessage": "Mobile phone number required",
- "description": "Error message",
- "missing": false,
- "obsolete": false
- },
- "current_email": {
- "translation": "當前郵箱",
- "defaultMessage": "Current email",
- "description": "Label for old email",
- "missing": false,
- "obsolete": false
- },
- "current_phone": {
- "translation": "當前電話號碼",
- "defaultMessage": "Current phone number",
- "description": "Label for old phone",
- "missing": false,
- "obsolete": false
- },
- "new_email": {
- "translation": "新電子郵件地址",
- "defaultMessage": "New email",
- "description": "Prompt for entering a new email",
- "missing": false,
- "obsolete": false
- },
- "new_phone_number": {
- "translation": "新電話號碼",
- "defaultMessage": "New phone number",
- "description": "Prompt for entering a new telephone number",
- "missing": false,
- "obsolete": false
- },
- "change_email": {
- "translation": "更改郵箱",
- "defaultMessage": "Change email",
- "description": "Prompt to change current email",
- "missing": false,
- "obsolete": false
- },
- "change_phone": {
- "translation": "更改電話號碼",
- "defaultMessage": "Change phone number",
- "description": "Prompt to change current phone number",
- "missing": false,
- "obsolete": false
- },
- "call_busy": {
- "translation": "忙碌的",
- "defaultMessage": "busy",
- "description": "Label for busy call line",
- "missing": false,
- "obsolete": false
- },
- "pin_message": {
- "translation": "置頂消息",
- "defaultMessage": "Pin",
- "description": "Menu item [Pin] for pinning message to the top.",
- "missing": false,
- "obsolete": false
- },
- "unpin_message": {
- "translation": "取消置頂消息",
- "defaultMessage": "Unpin",
- "description": "Menu item [Unpin] for un-pinning the message.",
- "missing": false,
- "obsolete": false
- },
- "failed_to_init_audio": {
- "translation": "初始化錄音失敗",
- "defaultMessage": "Failed to initialize audio recording",
- "description": "Error message when audio is not available",
- "missing": false,
- "obsolete": false
- },
- "password_reset_success": {
- "translation": "密碼重置成功",
- "defaultMessage": "Password reset successfully",
- "description": "Notification message that the password was successfully reset.",
- "missing": false,
- "obsolete": false
- },
- "scan_qr_code": {
- "translation": "掃描二維碼",
- "defaultMessage": "Scan QR Code",
- "description": "Title for scanning QR code",
- "missing": false,
- "obsolete": false
- },
- "self_topic_comment": {
- "translation": "為後代所儲存的註解、訊息、連結、檔案",
- "defaultMessage": "Notes, messages, links, files saved for posterity",
- "description": "Comment for self topic for UI",
- "missing": false,
- "obsolete": false
- },
- "self_topic_name": {
- "translation": "已儲存的訊息",
- "defaultMessage": "Saved messages",
- "description": "Name of self topic for UI",
- "missing": false,
- "obsolete": false
- },
- "message_not_found": {
- "translation": "未找到訊息",
- "defaultMessage": "message not found",
- "description": "Pinned message is not found",
- "missing": false,
- "obsolete": false
- },
- "message_deleted": {
- "translation": "訊息已刪除",
- "defaultMessage": "message deleted",
- "description": "Pinned message is deleted",
- "missing": false,
- "obsolete": false
- },
- "alias_editing_placeholder": {
- "translation": "別名(可選)",
- "defaultMessage": "Alias (optional)",
- "description": "Placeholder for editing user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "label_alias": {
- "translation": "別名:",
- "defaultMessage": "Alias:",
- "description": "Label for user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "alias_already_taken": {
- "translation": "(已採取)",
- "defaultMessage": "(already taken)",
- "description": "Error message for alias already taken",
- "missing": false,
- "obsolete": false
- },
- "alias_invalid": {
- "translation": "(無效)",
- "defaultMessage": "(invalid)",
- "description": "Error message for invalid alias",
- "missing": false,
- "obsolete": false
- },
- "label_alias_edit": {
- "translation": "別名",
- "defaultMessage": "Alias",
- "description": "Label for editing user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "select_country": {
- "translation": "選擇國家",
- "defaultMessage": "Select country",
- "description": "Placeholder for the country selector",
- "missing": false,
- "obsolete": false
- },
- "text_copied": {
- "translation": "已複製到剪貼簿",
- "defaultMessage": "Copied to clipboard",
- "description": "Notification that text has been copied to clipboard",
- "missing": false,
- "obsolete": false
- },
- "color_schema_dark": {
- "translation": "深色",
- "defaultMessage": "Dark",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "color_schema_light": {
- "translation": "淺色",
- "defaultMessage": "Light",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "color_schema_system": {
- "translation": "系統預設",
- "defaultMessage": "System default",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "label_color_schema": {
- "translation": "主題:",
- "defaultMessage": "Theme:",
- "description": "Label for selecting color scheme (dark, light) in Settings",
- "missing": false,
- "obsolete": false
- },
- "label_text_size": {
- "translation": "文字大小:",
- "defaultMessage": "Text size:",
- "description": "Label adjusting text size",
- "missing": false,
- "obsolete": false
- },
- "incognito_mode_description": {
- "translation": "隱藏您的線上狀態和正在輸入指示器。",
- "defaultMessage": "Hide your online status and typing indicators.",
- "description": "Description for incognito mode toggle",
- "missing": false,
- "obsolete": false
- },
- "send_command": {
- "translation": "按 {key} 傳送",
- "defaultMessage": "Send on {key}",
- "description": "Config option to send message on hitting CTRL(or Cmd)-Enter",
- "missing": false,
- "obsolete": false
- },
- "send_command_explained": {
- "translation": "按 Enter 換行",
- "defaultMessage": "Press Enter for new line",
- "description": "Explanation how to enter newline when [Send on CTRL-Enter] is enabled",
- "missing": false,
- "obsolete": false
- },
- "send_plain": {
- "translation": "按 Enter 傳送",
- "defaultMessage": "Send on Enter",
- "description": "Config option to send message on hitting Enter",
- "missing": false,
- "obsolete": false
- },
- "send_plain_explained": {
- "translation": "按 Shift + Enter 換行",
- "defaultMessage": "Press Shift + Enter for new line",
- "description": "Explanation how to enter newline when [Send on Enter] is enabled",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_general": {
- "translation": "一般",
- "defaultMessage": "General",
- "description": "Sidepanel title for AccGeneralView.",
- "missing": false,
- "obsolete": false
- },
- "label_keyboard": {
- "translation": "鍵盤:",
- "defaultMessage": "Keyboard:",
- "description": "Label for send on enter settings",
- "missing": false,
- "obsolete": false
- },
- "pin_chat": {
- "translation": "置頂",
- "defaultMessage": "Pin",
- "description": "Menu item [Pin] for pinning chat to the top.",
- "missing": false,
- "obsolete": false
- },
- "unpin_chat": {
- "translation": "取消置頂",
- "defaultMessage": "Unpin",
- "description": "Menu item [Unpin] for un-pinning the chat.",
- "missing": false,
- "obsolete": false
- },
- "subscriber_count": {
- "translation": "{count, plural, other {{count, number} 位訂閱者}}",
- "defaultMessage": "{count, plural, one {{count, number} subscriber} two {{count, number} subscribers} three {{count, number} subscribers} few {{count, number} subscribers} many {{count, number} subscribers} other {{count, number} subscribers}}",
- "description": "Count of channel subscribers",
- "missing": false,
- "obsolete": false
- },
- "label_member_count": {
- "translation": "成員:",
- "defaultMessage": "Members:",
- "description": "Label for the count of group topic members",
- "missing": false,
- "obsolete": false
- },
- "label_subscriber_count": {
- "translation": "訂閱者:",
- "defaultMessage": "Subscribers:",
- "description": "Label for the count of channel subscribers",
- "missing": false,
- "obsolete": false
- },
- "member_count": {
- "translation": "{count, plural, other {{count, number} 位成員}}",
- "defaultMessage": "{count, plural, one {{count, number} member} two {{count, number} members} three {{count, number} members} few {{count, number} members} many {{count, number} members} other {{count, number} members}}",
- "description": "Count of group topic members",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_image": {
- "translation": "圖片",
- "defaultMessage": "image",
- "description": "Tab title Image",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_pattern": {
- "translation": "圖案",
- "defaultMessage": "pattern",
- "description": "Tab title Pattern",
- "missing": false,
- "obsolete": false
- },
- "button_restore": {
- "translation": "恢復預設",
- "defaultMessage": "Restore default",
- "description": "Reset setting to default",
- "missing": false,
- "obsolete": false
- },
- "wallpapers": {
- "translation": "桌布",
- "defaultMessage": "Wallpapers",
- "description": "Link or title for working with wallpapers.",
- "missing": false,
- "obsolete": false
- },
- "label_blur_wallpaper": {
- "translation": "模糊:",
- "defaultMessage": "Blur:",
- "description": "Label adjusting blur amount",
- "missing": false,
- "obsolete": false
- },
- "label_scan_id": {
- "translation": "掃描我的 ID:",
- "defaultMessage": "Scan my ID:",
- "description": "Label [Scan my ID:]",
- "missing": false,
- "obsolete": false
- },
- "unknown_name": {
- "translation": "未知",
- "defaultMessage": "Unknown",
- "description": "Name to show when the name is missing",
- "missing": false,
- "obsolete": false
- },
- "save_action": {
- "translation": "儲存",
- "defaultMessage": "Save",
- "description": "Label for Save button",
- "missing": false,
- "obsolete": false
- },
- "cannot_parse_vcard": {
- "translation": "無法解析 vCard 檔案。",
- "defaultMessage": "Cannot parse vCard file.",
- "description": "Error message when vCard file cannot be parsed",
- "missing": false,
- "obsolete": false
- },
- "chat_now": {
- "translation": "聊天",
- "defaultMessage": "Chat",
- "description": "Label for message button in contact card",
- "missing": false,
- "obsolete": false
- },
- "find_user": {
- "translation": "尋找",
- "defaultMessage": "Find",
- "description": "Label for search button in contact card",
- "missing": false,
- "obsolete": false
- },
- "label_contact_card": {
- "translation": "聯絡人卡片",
- "defaultMessage": "Contact Card",
- "description": "Label for contact card preview",
- "missing": false,
- "obsolete": false
- },
- "label_contacts": {
- "translation": "聯絡人",
- "defaultMessage": "Contacts",
- "description": "Label for contact information",
- "missing": false,
- "obsolete": false
- },
- "add_members_prompt": {
- "translation": "新增成員",
- "defaultMessage": "add members",
- "description": "Placeholder in group members input field",
- "missing": false,
- "obsolete": false
- }
-}
diff --git a/src/i18n/zh.json b/src/i18n/zh.json
deleted file mode 100644
index d68960cb..00000000
--- a/src/i18n/zh.json
+++ /dev/null
@@ -1,1966 +0,0 @@
-{
- "action_block_contact": {
- "translation": "屏蔽联系人",
- "defaultMessage": "Block Contact",
- "description": "Flat button [Block Contact]",
- "missing": false,
- "obsolete": false
- },
- "action_cancel": {
- "translation": "取消",
- "defaultMessage": "cancel",
- "description": "Call to action [cancel]",
- "missing": false,
- "obsolete": false
- },
- "action_clear_messages": {
- "translation": "删除讯息",
- "defaultMessage": "Clear Messages",
- "description": "Flat button [Clear Messages] (soft-delete messages)",
- "missing": false,
- "obsolete": false
- },
- "action_delete_messages": {
- "translation": "删除所有帖子",
- "defaultMessage": "Clear Messages for All",
- "description": "Flat button [Clear for All] (hard-delete all messages)",
- "missing": false,
- "obsolete": false
- },
- "action_leave_chat": {
- "translation": "离开",
- "defaultMessage": "Leave Conversation",
- "description": "Flat button [Leave Conversation]",
- "missing": false,
- "obsolete": false
- },
- "action_report_chat": {
- "translation": "检举垃圾邮件",
- "defaultMessage": "Report Conversation",
- "description": "Flat button [Report Group]",
- "missing": false,
- "obsolete": false
- },
- "archived_contacts": {
- "translation": "已归档联系人 ({count})",
- "defaultMessage": "Archived contacts ({count})",
- "description": "Label for archived chats",
- "missing": false,
- "obsolete": false
- },
- "badge_danger": {
- "translation": "可疑的",
- "defaultMessage": "Untrustworthy",
- "description": "Suspicious or untrustworthy account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_owner": {
- "translation": "所有者",
- "defaultMessage": "owner",
- "description": "Badge for indicating the owner",
- "missing": false,
- "obsolete": false
- },
- "badge_staff": {
- "translation": "在员工管理下",
- "defaultMessage": "Staff-managed",
- "description": "Explanation of a staff-managed account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_verified": {
- "translation": "值得信赖",
- "defaultMessage": "Verified/official",
- "description": "Explanation of a verified account or topic badge",
- "missing": false,
- "obsolete": false
- },
- "badge_you": {
- "translation": "你",
- "defaultMessage": "you",
- "description": "Badge for indicating the current user",
- "missing": false,
- "obsolete": false
- },
- "block_contact_warning": {
- "translation": "您确定要阻止此联系人吗?",
- "defaultMessage": "Are you sure you want to block this contact?",
- "description": "Alert dialog warning when blocking a contact.",
- "missing": false,
- "obsolete": false
- },
- "blocked_contacts_link": {
- "translation": "封锁的联络人 ({count})",
- "defaultMessage": "Blocked contacts ({count})",
- "description": "Blocked contacts link",
- "missing": false,
- "obsolete": false
- },
- "button_add_members": {
- "translation": "添加成员",
- "defaultMessage": "Add members",
- "description": "Flat button [Add members] (to topic)",
- "missing": false,
- "obsolete": false
- },
- "button_cancel": {
- "translation": "取消",
- "defaultMessage": "Cancel",
- "description": "Button [Cancel]",
- "missing": false,
- "obsolete": false
- },
- "button_confirm": {
- "translation": "确认",
- "defaultMessage": "Confirm",
- "description": "Button [Confirm]",
- "missing": false,
- "obsolete": false
- },
- "button_create": {
- "translation": "创建",
- "defaultMessage": "Create",
- "description": "Button [Create]",
- "missing": false,
- "obsolete": false
- },
- "button_delete_account": {
- "translation": "删除帐户",
- "defaultMessage": "Delete account",
- "description": "Button [Delete account]",
- "missing": false,
- "obsolete": false
- },
- "button_edit": {
- "translation": "编辑",
- "defaultMessage": "Edit",
- "description": "Call to action [Edit]",
- "missing": false,
- "obsolete": false
- },
- "button_logout": {
- "translation": "登出",
- "defaultMessage": "Logout",
- "description": "Button [Logout]",
- "missing": false,
- "obsolete": false
- },
- "button_ok": {
- "translation": "好",
- "defaultMessage": "OK",
- "description": "Button [OK]",
- "missing": false,
- "obsolete": false
- },
- "button_reset": {
- "translation": "重置",
- "defaultMessage": "Reset",
- "description": "Button [Reset]",
- "missing": false,
- "obsolete": false
- },
- "button_send_request": {
- "translation": "发送请求",
- "defaultMessage": "Send request",
- "description": "Button [Send request]",
- "missing": false,
- "obsolete": false
- },
- "button_sign_in": {
- "translation": "登录",
- "defaultMessage": "Sign in",
- "description": "Button [Sign In]",
- "missing": false,
- "obsolete": false
- },
- "button_sign_up": {
- "translation": "注册",
- "defaultMessage": "Sign up",
- "description": "Create account button [Sign Up]",
- "missing": false,
- "obsolete": false
- },
- "button_subscribe": {
- "translation": "订阅",
- "defaultMessage": "Subscribe",
- "description": "Button [Subscribe]",
- "missing": false,
- "obsolete": false
- },
- "button_update": {
- "translation": "更新",
- "defaultMessage": "Update",
- "description": "Button [Update]",
- "missing": false,
- "obsolete": false
- },
- "cannot_initiate_file_upload": {
- "translation": "无法初始化文件上传。",
- "defaultMessage": "Cannot initiate file upload.",
- "description": "Generic error messagewhen attachment fails",
- "missing": false,
- "obsolete": false
- },
- "channel": {
- "translation": "频道",
- "defaultMessage": "channel",
- "description": "Subtitle shown for channels in MessagesView instead of last seen",
- "missing": false,
- "obsolete": false
- },
- "channel_prompt": {
- "translation": "这是一个频道",
- "defaultMessage": "This is a channel",
- "description": "Checkbox label when creating a channel",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation": {
- "translation": "你受邀开始新会话。你想怎么做?",
- "defaultMessage": "You are invited to start a new chat. What would you like to do?",
- "description": "New chat invitation message: [Accept] [Ignore] [Block].",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_accept": {
- "translation": "接受",
- "defaultMessage": "Accept",
- "description": "Action [Accept] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_block": {
- "translation": "屏蔽",
- "defaultMessage": "Block",
- "description": "Action [Block] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "chat_invitation_ignore": {
- "translation": "忽略",
- "defaultMessage": "Ignore",
- "description": "Action [Ignore] for chat invitation.",
- "missing": false,
- "obsolete": false
- },
- "clear_messages_warning": {
- "translation": "您确定要清除所有消息吗?无法撤消。",
- "defaultMessage": "Are you sure you want to clear all messages? It cannot be undone.",
- "description": "Alert dialog warning when deleting all messages.",
- "missing": false,
- "obsolete": false
- },
- "code_doesnot_match": {
- "translation": "代码不匹配",
- "defaultMessage": "Code does not match",
- "description": "Error message when the credential validation code is incorrect.",
- "missing": false,
- "obsolete": false
- },
- "contacts_not_found": {
- "translation": "你尚无会话\n¯∖_(ツ)_/¯",
- "defaultMessage": "You have no chats\\n¯∖_(ツ)_/¯",
- "description": "HTML message shown in ContactList when no contacts are found",
- "missing": false,
- "obsolete": false
- },
- "contacts_not_found_short": {
- "translation": "无联系人匹配''{query}''",
- "defaultMessage": "No contacts match ''{query}''",
- "description": "Shown in ContactsView when search returned no results",
- "missing": false,
- "obsolete": false
- },
- "credential_email_prompt": {
- "translation": "你的注册邮箱",
- "defaultMessage": "Your registration email",
- "description": "Placeholder for entering email",
- "missing": false,
- "obsolete": false
- },
- "delete_account": {
- "translation": "删除帐户",
- "defaultMessage": "Delete account",
- "description": "Title for delete account warning",
- "missing": false,
- "obsolete": false
- },
- "delete_account_warning": {
- "translation": "您确定要删除您的帐户吗?无法撤消。",
- "defaultMessage": "Are you sure you want to delete your account? It cannot be undone.",
- "description": "Warning message when deleting an account",
- "missing": false,
- "obsolete": false
- },
- "delete_messages_warning": {
- "translation": "您确定要删除所有消息吗?无法撤消。",
- "defaultMessage": "Are you sure you want to delete all messages for everyone? It cannot be undone.",
- "description": "Alert dialog warning when hard-deleting all messages.",
- "missing": false,
- "obsolete": false
- },
- "download_action": {
- "translation": "下载",
- "defaultMessage": "download",
- "description": "Call to action [download]",
- "missing": false,
- "obsolete": false
- },
- "drafty_attachment": {
- "translation": "附件",
- "defaultMessage": "Attachment",
- "description": "Comment for attachment in Drafty",
- "missing": false,
- "obsolete": false
- },
- "drafty_form": {
- "translation": "形式:",
- "defaultMessage": "Form:",
- "description": "Comment for form in Drafty",
- "missing": false,
- "obsolete": false
- },
- "drafty_image": {
- "translation": "图像",
- "defaultMessage": "Picture",
- "description": "Comment for embedded images in Drafty",
- "missing": false,
- "obsolete": false
- },
- "email_dative": {
- "translation": "电子邮件",
- "defaultMessage": "email",
- "description": "Dative case of 'email', i.e. 'email' in 'by email'",
- "missing": false,
- "obsolete": false
- },
- "email_prompt": {
- "translation": "电子邮件,例如 zhang@example.com",
- "defaultMessage": "Email, e.g. jdoe@example.com",
- "description": "Input placeholder for email entry",
- "missing": false,
- "obsolete": false
- },
- "enable_peers_messaging": {
- "translation": "启用",
- "defaultMessage": "Enable",
- "description": "Call to action to enable peer's messaging",
- "missing": false,
- "obsolete": false
- },
- "enter_confirmation_code_prompt": {
- "translation": "输入通过{method}发送的验证码:",
- "defaultMessage": "Confirmation code",
- "description": "Request to enter confirmation code",
- "missing": false,
- "obsolete": false
- },
- "error_invalid_id": {
- "translation": "无效 ID",
- "defaultMessage": "Invalid ID",
- "description": "Error message",
- "missing": false,
- "obsolete": false
- },
- "file_attachment_too_large": {
- "translation": "文件大小 {size} 超过 {limit} 限制。",
- "defaultMessage": "The file size {size} exceeds the {limit} limit.",
- "description": "Error message when attachment is too large",
- "missing": false,
- "obsolete": false
- },
- "forgot_password_link": {
- "translation": "忘记密码?",
- "defaultMessage": "Forgot password?",
- "description": "Link to Reset password form",
- "missing": false,
- "obsolete": false
- },
- "full_name_prompt": {
- "translation": "全名,例如张伟",
- "defaultMessage": "Full name, e.g. John Doe",
- "description": "Input placeholder for person's full name",
- "missing": false,
- "obsolete": false
- },
- "granted_permissions": {
- "translation": "已授予",
- "defaultMessage": "Granted",
- "description": "Title for permissions",
- "missing": false,
- "obsolete": false
- },
- "group_has_no_members": {
- "translation": "无成员",
- "defaultMessage": "No members",
- "description": "Shown in place of group members",
- "missing": false,
- "obsolete": false
- },
- "group_user_id_prompt": {
- "translation": "群组或用户 ID",
- "defaultMessage": "Group or User ID",
- "description": "Prompt for entering user or group ID",
- "missing": false,
- "obsolete": false
- },
- "image_caption_prompt": {
- "translation": "图片标题",
- "defaultMessage": "Image caption",
- "description": "Prompt in SendMessage for attached image",
- "missing": false,
- "obsolete": false
- },
- "invalid_content": {
- "translation": "无效内容",
- "defaultMessage": "invalid content",
- "description": "Shown when the message is unreadable",
- "missing": false,
- "obsolete": false
- },
- "invalid_security_token": {
- "translation": "无效的安全令牌",
- "defaultMessage": "Invalid security token",
- "description": "Error message when resetting password.",
- "missing": false,
- "obsolete": false
- },
- "label_client": {
- "translation": "客户端:",
- "defaultMessage": "Client:",
- "description": "Label for a client version",
- "missing": false,
- "obsolete": false
- },
- "label_content_type": {
- "translation": "内容类型:",
- "defaultMessage": "Content type:",
- "description": "Label for file content type (mime)",
- "missing": false,
- "obsolete": false
- },
- "label_default_access_mode": {
- "translation": "默认访问模式:",
- "defaultMessage": "Default access mode:",
- "description": "Label for default access mode",
- "missing": false,
- "obsolete": false
- },
- "label_file_name": {
- "translation": "文件名:",
- "defaultMessage": "File name:",
- "description": "Label for a file name",
- "missing": false,
- "obsolete": false
- },
- "label_group_members": {
- "translation": "群组成员:",
- "defaultMessage": "Group members:",
- "description": "Section title or label",
- "missing": false,
- "obsolete": false
- },
- "label_incognito_mode": {
- "translation": "无痕模式:",
- "defaultMessage": "Incognito mode:",
- "description": "Label for incognito mode toggle",
- "missing": false,
- "obsolete": false
- },
- "label_message_sound": {
- "translation": "消息提示音:",
- "defaultMessage": "Message sound:",
- "description": "Label for message sounds toggle",
- "missing": false,
- "obsolete": false
- },
- "label_muting_topic": {
- "translation": "已静音:",
- "defaultMessage": "Muted:",
- "description": "Label for Muting/unmuting the topic",
- "missing": false,
- "obsolete": false
- },
- "label_other_user": {
- "translation": "其他",
- "defaultMessage": "Other",
- "description": "Label for the other user when the user is unnamed",
- "missing": false,
- "obsolete": false
- },
- "label_password": {
- "translation": "密码",
- "defaultMessage": "Password",
- "description": "Label for password editing",
- "missing": false,
- "obsolete": false
- },
- "label_permissions": {
- "translation": "权限:",
- "defaultMessage": "Permissions:",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "label_private": {
- "translation": "私人评论",
- "defaultMessage": "Private comment",
- "description": "Label for editing 'private'",
- "missing": false,
- "obsolete": false
- },
- "label_push_notifications": {
- "translation": "通知提醒:",
- "defaultMessage": "Notification alerts:",
- "description": "Label for push notifications switch",
- "missing": false,
- "obsolete": false
- },
- "label_push_notifications_disabled": {
- "translation": "通知提醒(需要 HTTPS):",
- "defaultMessage": "Notification alerts (requires HTTPS):",
- "description": "Label for push notifications switch",
- "missing": false,
- "obsolete": false
- },
- "label_reset_password": {
- "translation": "发送密码重置邮件:",
- "defaultMessage": "Send a password reset email",
- "description": "Label for password reset field",
- "missing": false,
- "obsolete": false
- },
- "label_sdk": {
- "translation": "开发包:",
- "defaultMessage": "SDK:",
- "missing": false,
- "obsolete": false
- },
- "label_server": {
- "translation": "服务器:",
- "defaultMessage": "Server:",
- "description": "Label for a server version",
- "missing": false,
- "obsolete": false
- },
- "label_server_address": {
- "translation": "服务器地址:",
- "defaultMessage": "Server address:",
- "missing": false,
- "obsolete": false
- },
- "label_server_to_use": {
- "translation": "使用的服务器:",
- "defaultMessage": "Server to use:",
- "description": "Label for server selector in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "label_size": {
- "translation": "大小:",
- "defaultMessage": "Size:",
- "description": "Label for file size",
- "missing": false,
- "obsolete": false
- },
- "label_topic_name": {
- "translation": "名称",
- "defaultMessage": "Name",
- "description": "Label for editing topic name",
- "missing": false,
- "obsolete": false
- },
- "label_user_contacts": {
- "translation": "往来",
- "defaultMessage": "Contacts",
- "description": "Label for user contacts",
- "missing": false,
- "obsolete": false
- },
- "label_user_id": {
- "translation": "地址:",
- "defaultMessage": "ID:",
- "description": "Label for user address (ID)",
- "missing": false,
- "obsolete": false
- },
- "label_wire_transport": {
- "translation": "线路传输:",
- "defaultMessage": "Wire transport:",
- "description": "Label for wire transport selection in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "label_you": {
- "translation": "你:",
- "defaultMessage": "You:",
- "description": "Label for the current user",
- "missing": false,
- "obsolete": false
- },
- "label_your_name": {
- "translation": "你的姓名",
- "defaultMessage": "Your name",
- "description": "Label for full name editing",
- "missing": false,
- "obsolete": false
- },
- "label_your_permissions": {
- "translation": "你的权限:",
- "defaultMessage": "Your permissions:",
- "description": "Label for current user permissions",
- "missing": false,
- "obsolete": false
- },
- "last_seen_timestamp": {
- "translation": "最后在线:{timestamp}",
- "defaultMessage": "Last seen: {timestamp}",
- "description": "Label for the timestamp of when the user or topic was last online",
- "missing": false,
- "obsolete": false
- },
- "leave_chat_warning": {
- "translation": "您确定要退出此对话吗?",
- "defaultMessage": "Are you sure you want to leave this conversation?",
- "description": "Alert dialog warning when unsubscribing from a chat.",
- "missing": false,
- "obsolete": false
- },
- "link_contact_us": {
- "translation": "联系我们",
- "defaultMessage": "Contact Us",
- "description": "Ancor text for contacting us by email",
- "missing": false,
- "obsolete": false
- },
- "link_privacy_policy": {
- "translation": "隐私政策",
- "defaultMessage": "Privacy Policy",
- "description": "Ancor text for privacy policy link",
- "missing": false,
- "obsolete": false
- },
- "link_terms_of_service": {
- "translation": "条款和条件",
- "defaultMessage": "Terms of Service",
- "description": "Ancor text for terms of service link",
- "missing": false,
- "obsolete": false
- },
- "login_prompt": {
- "translation": "登录",
- "defaultMessage": "Login",
- "description": "Placeholer for username/login",
- "missing": false,
- "obsolete": false
- },
- "menu_item_archive_topic": {
- "translation": "归档",
- "defaultMessage": "Archive",
- "description": "Move topic from the list of active chats to archive",
- "missing": false,
- "obsolete": false
- },
- "menu_item_block": {
- "translation": "屏蔽",
- "defaultMessage": "Block",
- "description": "Block topic or user",
- "missing": false,
- "obsolete": false
- },
- "menu_item_clear_messages": {
- "translation": "清空消息",
- "defaultMessage": "Clear messages",
- "description": "Delete all messages",
- "missing": false,
- "obsolete": false
- },
- "menu_item_clear_messages_for_all": {
- "translation": "全部清除",
- "defaultMessage": "Clear for All",
- "description": "Delete all message(s) for all members",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete": {
- "translation": "删除",
- "defaultMessage": "Delete",
- "description": "Delete selected messages",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete_for_all": {
- "translation": "全部删除",
- "defaultMessage": "Delete for All",
- "description": "Delete selected message(s) for all members",
- "missing": false,
- "obsolete": false
- },
- "menu_item_delete_topic": {
- "translation": "删除",
- "defaultMessage": "Delete",
- "description": "Delete entire topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_edit_permissions": {
- "translation": "编辑权限",
- "defaultMessage": "Edit permissions",
- "description": "Menu item [Edit permissions]",
- "missing": false,
- "obsolete": false
- },
- "menu_item_info": {
- "translation": "信息",
- "defaultMessage": "Info",
- "description": "Show extended topic information",
- "missing": false,
- "obsolete": false
- },
- "menu_item_member_delete": {
- "translation": "移除",
- "defaultMessage": "Remove",
- "description": "Remove user from topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_mute": {
- "translation": "静音",
- "defaultMessage": "Mute",
- "description": "Turn off notifications",
- "missing": false,
- "obsolete": false
- },
- "menu_item_restore_topic": {
- "translation": "从存档中恢复",
- "defaultMessage": "Restore",
- "description": "Restore topic from archive",
- "missing": false,
- "obsolete": false
- },
- "menu_item_send_retry": {
- "translation": "重试",
- "defaultMessage": "Retry",
- "description": "Retry sending message",
- "missing": false,
- "obsolete": false
- },
- "menu_item_unblock": {
- "translation": "取消屏蔽",
- "defaultMessage": "Unblock",
- "description": "Unblock topic or user",
- "missing": false,
- "obsolete": false
- },
- "menu_item_unmute": {
- "translation": "取消静音",
- "defaultMessage": "Unmute",
- "description": "Turn notifications on",
- "missing": false,
- "obsolete": false
- },
- "message_sending": {
- "translation": "正在发送...",
- "defaultMessage": "sending...",
- "description": "Message being sent, in place of time stamp",
- "missing": false,
- "obsolete": false
- },
- "message_sending_failed": {
- "translation": "发送失败",
- "defaultMessage": "failed",
- "description": "Failed to send message, in place of time stamp",
- "missing": false,
- "obsolete": false
- },
- "messages_not_readable": {
- "translation": "无消息访问权限",
- "defaultMessage": "no access to messages",
- "description": "Message shown in topic without the read access",
- "missing": false,
- "obsolete": false
- },
- "messaging_disabled_prompt": {
- "translation": "消息已禁用",
- "defaultMessage": "Messaging disabled",
- "description": "Prompt in SendMessage in read-only topic",
- "missing": false,
- "obsolete": false
- },
- "more_online_members": {
- "translation": "还有{overflow}个",
- "defaultMessage": "+{overflow} more",
- "description": "Shown in MessagesView title bar when the number of online subscribers exceeds MAX_ONLINE_IN_TOPIC",
- "missing": false,
- "obsolete": false
- },
- "new_message_prompt": {
- "translation": "新消息",
- "defaultMessage": "New message",
- "description": "Prompt in send message field",
- "missing": false,
- "obsolete": false
- },
- "new_password_placeholder": {
- "translation": "输入新密码",
- "defaultMessage": "Enter new password",
- "description": "Placeholder for entering new password",
- "missing": false,
- "obsolete": false
- },
- "no_connection": {
- "translation": "无连接",
- "defaultMessage": "No connection",
- "description": "Warning that the user is offline.",
- "missing": false,
- "obsolete": false
- },
- "no_contacts": {
- "translation": "你尚无联系人 (._.)",
- "defaultMessage": "You have no contacts :-(",
- "description": "Shown in ContactsView when the user has no contacts",
- "missing": false,
- "obsolete": false
- },
- "numeric_confirmation_code_prompt": {
- "translation": "仅数字",
- "defaultMessage": "Numbers only",
- "description": "Prompt for numeric conformation code",
- "missing": false,
- "obsolete": false
- },
- "online_now": {
- "translation": "在线",
- "defaultMessage": "online now",
- "description": "Indicator that the user or topic is currently online",
- "missing": false,
- "obsolete": false
- },
- "password_prompt": {
- "translation": "密码",
- "defaultMessage": "Password",
- "description": "Placeholder/prompt for entering password",
- "missing": false,
- "obsolete": false
- },
- "password_unchanged_prompt": {
- "translation": "未改变",
- "defaultMessage": "Unchanged",
- "description": "Message in editor while password is unchanged",
- "missing": false,
- "obsolete": false
- },
- "peers_messaging_disabled": {
- "translation": "对方已关闭消息。",
- "defaultMessage": "Peer's messaging is disabled.",
- "description": "Shown when the p2p peer's messaging is disabled",
- "missing": false,
- "obsolete": false
- },
- "permission_admin": {
- "translation": "批准 ({val})",
- "defaultMessage": "Approve ({val})",
- "description": "Name of A permission",
- "missing": false,
- "obsolete": false
- },
- "permission_delete": {
- "translation": "删除 ({val})",
- "defaultMessage": "Delete ({val})",
- "description": "Name of D permission",
- "missing": false,
- "obsolete": false
- },
- "permission_join": {
- "translation": "加入 ({val})",
- "defaultMessage": "Join ({val})",
- "description": "Name of J permission",
- "missing": false,
- "obsolete": false
- },
- "permission_owner": {
- "translation": "所有者 ({val})",
- "defaultMessage": "Owner ({val})",
- "description": "Name of O permission",
- "missing": false,
- "obsolete": false
- },
- "permission_pres": {
- "translation": "获取通知 ({val})",
- "defaultMessage": "Get notified ({val})",
- "description": "Name of P permission",
- "missing": false,
- "obsolete": false
- },
- "permission_read": {
- "translation": "读取 ({val})",
- "defaultMessage": "Read ({val})",
- "description": "Name of R permission",
- "missing": false,
- "obsolete": false
- },
- "permission_share": {
- "translation": "分享 ({val})",
- "defaultMessage": "Share ({val})",
- "description": "Name of S permission",
- "missing": false,
- "obsolete": false
- },
- "permission_write": {
- "translation": "写入 ({val})",
- "defaultMessage": "Write ({val})",
- "description": "Name of W permission",
- "missing": false,
- "obsolete": false
- },
- "phone_dative": {
- "translation": "电话",
- "defaultMessage": "phone",
- "description": "Dative case of 'phone', i.e. 'phone' in 'by phone'",
- "missing": false,
- "obsolete": false
- },
- "private_editing_placeholder": {
- "translation": "仅自己可见",
- "defaultMessage": "Visible to you only",
- "description": "Placeholder for editing 'private'",
- "missing": false,
- "obsolete": false
- },
- "push_init_failed": {
- "translation": "初始化推送通知失败",
- "defaultMessage": "Failed to initialize push notifications",
- "description": "Error message when push notifications have failed to initialize.",
- "missing": false,
- "obsolete": false
- },
- "reconnect_countdown": {
- "translation": "连接已断开。{seconds} 秒后重新连接…",
- "defaultMessage": "Disconnected. Reconnecting in {seconds}…",
- "description": "Message shown when an app update is available.",
- "missing": false,
- "obsolete": false
- },
- "reconnect_now": {
- "translation": "立即尝试",
- "defaultMessage": "Try now",
- "description": "Prompt for reconnecting now",
- "missing": false,
- "obsolete": false
- },
- "reload_update": {
- "translation": "重新载入",
- "defaultMessage": "Reload",
- "description": "Call to action to reload application when update is available.",
- "missing": false,
- "obsolete": false
- },
- "report_chat_warning": {
- "translation": "您确定要停止并报告此对话吗?",
- "defaultMessage": "Are you sure you want to block and report this conversation?",
- "description": "Alert dialog warning when reporting a conversation for abuse",
- "missing": false,
- "obsolete": false
- },
- "requested_permissions": {
- "translation": "已请求",
- "defaultMessage": "Requested",
- "description": "Title for permissions",
- "missing": false,
- "obsolete": false
- },
- "save_attachment": {
- "translation": "保存",
- "defaultMessage": "save",
- "description": "Call to save an attachment",
- "missing": false,
- "obsolete": false
- },
- "search_for_contacts": {
- "translation": "使用搜索寻找联系人",
- "defaultMessage": "Use search to find contacts",
- "description": "Text shown in contacts view when user entered no search query.",
- "missing": false,
- "obsolete": false
- },
- "search_no_results": {
- "translation": "搜索无结果",
- "defaultMessage": "Search returned no results",
- "description": "Text shown in contacts view when query returned no results.",
- "missing": false,
- "obsolete": false
- },
- "search_placeholder": {
- "translation": "列表如 email:alice@example.com, tel:+17025550003...",
- "defaultMessage": "List like alice@example.com, +17025550003...",
- "description": "Placeholder in contacts search field",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_notifications": {
- "translation": "通知",
- "defaultMessage": "Notifications",
- "description": "Sidepanel title for AccNotificationsView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_support": {
- "translation": "支持",
- "defaultMessage": "Support",
- "description": "Sidepanel title for AccSupportView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_account_settings": {
- "translation": "帐号设定",
- "defaultMessage": "Account Settings",
- "description": "Sidepanel title for AccountSettingsView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_archive": {
- "translation": "已存档会话",
- "defaultMessage": "Archived Chats",
- "description": "Sidepanel title for ContactsView-Archive.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_blocked": {
- "translation": "被阻止的聊天",
- "defaultMessage": "Blocked Chats",
- "description": "Sidepanel title for ContactsView-Blocked.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_cred": {
- "translation": "确认凭据",
- "defaultMessage": "Confirm Credentials",
- "description": "Sidepanel title for ValidationView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_login": {
- "translation": "登录",
- "defaultMessage": "Sign In",
- "description": "Sidepanel title for LoginView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_newtpk": {
- "translation": "开始新会话",
- "defaultMessage": "Start New Chat",
- "description": "Sidepanel title for NewTopicView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_register": {
- "translation": "创建账户",
- "defaultMessage": "Create Account",
- "description": "Sidepanel title for CreateAccountView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_reset": {
- "translation": "重置密码",
- "defaultMessage": "Reset Password",
- "description": "Sidepanel title for PasswordResetView.",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_settings": {
- "translation": "设置",
- "defaultMessage": "Settings",
- "description": "Sidepanel title for SettingsView.",
- "missing": false,
- "obsolete": false
- },
- "stay_logged_in": {
- "translation": "保持登录",
- "defaultMessage": "Stay logged in",
- "description": "Label for a checkbox",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_find_user": {
- "translation": "搜索",
- "defaultMessage": "find",
- "description": "Tab title Find",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_group_by_id": {
- "translation": "通过 id",
- "defaultMessage": "by id",
- "description": "Tab title Find topic by ID",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_new_group": {
- "translation": "新群组",
- "defaultMessage": "new group",
- "description": "Tab title New Group",
- "missing": false,
- "obsolete": false
- },
- "tags_editor_no_tags": {
- "translation": "添加一些标签",
- "defaultMessage": "Add some tags",
- "description": "Tag editor prompt when no tags are found.",
- "missing": false,
- "obsolete": false
- },
- "tags_not_found": {
- "translation": "尚未定义标签。添加一些。",
- "defaultMessage": "No tags defined. Add some.",
- "description": "",
- "missing": false,
- "obsolete": false
- },
- "title_all_contacts": {
- "translation": "全部联系人",
- "defaultMessage": "All Contacts",
- "description": "Section title [All Contacts]",
- "missing": false,
- "obsolete": false
- },
- "title_group_members": {
- "translation": "群组成员",
- "defaultMessage": "Group Members",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "title_manage_tags": {
- "translation": "管理标签",
- "defaultMessage": "Manage",
- "description": "Section title for the list of tags",
- "missing": false,
- "obsolete": false
- },
- "title_not_found": {
- "translation": "无法找到",
- "defaultMessage": "Not found",
- "description": "Title shown when topic is not found",
- "missing": false,
- "obsolete": false
- },
- "title_permissions": {
- "translation": "权限",
- "defaultMessage": "Permissions",
- "description": "Section title",
- "missing": false,
- "obsolete": false
- },
- "title_tag_manager": {
- "translation": "标签(用户发现)",
- "defaultMessage": "Tags (search & discovery)",
- "description": "Section title for TagManager",
- "missing": false,
- "obsolete": false
- },
- "topic_block_warning": {
- "translation": "您确定要阻止此对话吗?",
- "defaultMessage": "Are you sure you want to block this conversation?",
- "description": "Alert warning when blocking a topic.",
- "missing": false,
- "obsolete": false
- },
- "topic_delete_warning": {
- "translation": "您确定要删除此对话吗?",
- "defaultMessage": "Are you sure you want to delete this conversation? It cannot be undone.",
- "description": "Alert warning when deleting entire topic",
- "missing": false,
- "obsolete": false
- },
- "topic_name_editing_placeholder": {
- "translation": "群组自由格式名称",
- "defaultMessage": "Freeform name of the group",
- "description": "Prompt for entering topic name",
- "missing": false,
- "obsolete": false
- },
- "unnamed_topic": {
- "translation": "未命名",
- "defaultMessage": "Unnamed",
- "description": "Title shown when the topic has no name",
- "missing": false,
- "obsolete": false
- },
- "update_available": {
- "translation": "更新可用。",
- "defaultMessage": "Update available.",
- "description": "Message shown when an app update is available.",
- "missing": false,
- "obsolete": false
- },
- "upload_finishing": {
- "translation": "正在结束...",
- "defaultMessage": "finishing...",
- "description": "Notification that upload is finishing",
- "missing": false,
- "obsolete": false
- },
- "user_not_found": {
- "translation": "未找到",
- "defaultMessage": "Not found",
- "description": "In place of a user's full name when the user is not found.",
- "missing": false,
- "obsolete": false
- },
- "description_editing_placeholder": {
- "translation": "说明(可选)",
- "defaultMessage": "Description (optional)",
- "description": "Placeholder for editing topic description",
- "missing": false,
- "obsolete": false
- },
- "label_description": {
- "translation": "说明",
- "defaultMessage": "Description",
- "description": "Label for description of user or topic",
- "missing": false,
- "obsolete": false
- },
- "button_security": {
- "translation": "安全",
- "defaultMessage": "Security",
- "description": "Navigaton button for security panel.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_crop": {
- "translation": "拖动调整",
- "defaultMessage": "Drag to Adjust",
- "description": "Title for AvatarCropView.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_general": {
- "translation": "常用设定",
- "defaultMessage": "General",
- "description": "Title for TopicCommon.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_members": {
- "translation": "成员",
- "defaultMessage": "Members",
- "description": "Title for managing group members view.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_security": {
- "translation": "安全",
- "defaultMessage": "Security",
- "description": "Title for TopicSecirity and AccSecurity.",
- "missing": false,
- "obsolete": false
- },
- "panel_title_info": {
- "translation": "信息",
- "defaultMessage": "Info",
- "description": "Title for InfoView",
- "missing": false,
- "obsolete": false
- },
- "permissions_anonymous": {
- "translation": "匿名",
- "defaultMessage": "Anonymous",
- "description": "Title for editing default anonymous permissions",
- "missing": false,
- "obsolete": false
- },
- "permissions_authenticated": {
- "translation": "已认证",
- "defaultMessage": "Authenticated",
- "description": "Title for editing default authenticated permissions",
- "missing": false,
- "obsolete": false
- },
- "topic_delete": {
- "translation": "删除聊天",
- "defaultMessage": "Delete Conversation",
- "description": "Alert title when deleting the topic.",
- "missing": false,
- "obsolete": false
- },
- "permissions_user": {
- "translation": "用户权限",
- "defaultMessage": "User's Permissions",
- "description": "Title for editing user's permissions",
- "missing": false,
- "obsolete": false
- },
- "password_reset_email_sent": {
- "translation": "已发送一封带有安全代码的电子邮件。",
- "defaultMessage": "An email with security code has been sent.",
- "description": "Notification that the email with password reset instructions has been sent",
- "missing": false,
- "obsolete": false
- },
- "label_unarchive_topic": {
- "translation": "存档:",
- "defaultMessage": "Archived:",
- "description": "Label for unarchiving the topic",
- "missing": false,
- "obsolete": false
- },
- "menu_item_reply": {
- "translation": "回复",
- "defaultMessage": "Reply",
- "description": "Reply to message",
- "missing": false,
- "obsolete": false
- },
- "menu_item_forward": {
- "translation": "转发",
- "defaultMessage": "Forward",
- "description": "Forward message",
- "missing": false,
- "obsolete": false
- },
- "forward_to": {
- "translation": "转发给",
- "defaultMessage": "Forward to",
- "missing": false,
- "obsolete": false
- },
- "forward_to_search_placeholder": {
- "translation": "搜索联系人",
- "defaultMessage": "Search contacts",
- "description": "Contact search prompt when forwarding a message.",
- "missing": false,
- "obsolete": false
- },
- "label_new_password": {
- "translation": "新密码",
- "defaultMessage": "New password",
- "description": "Label for entering a new password",
- "missing": false,
- "obsolete": false
- },
- "drafty_unknown": {
- "translation": "不支持",
- "defaultMessage": "Unsupported",
- "description": "Unsupported entity in drafty",
- "missing": false,
- "obsolete": false
- },
- "calls_incoming": {
- "translation": "来电",
- "defaultMessage": "Incoming call",
- "description": "Incoming call label",
- "missing": false,
- "obsolete": false
- },
- "calls_outgoing": {
- "translation": "拨出电话",
- "defaultMessage": "Outgoing call",
- "description": "Outgoing call label",
- "missing": false,
- "obsolete": false
- },
- "calls_you_label": {
- "translation": "你",
- "defaultMessage": "You",
- "description": "Shown over the local video screen",
- "missing": false,
- "obsolete": false
- },
- "menu_item_video_call": {
- "translation": "视频电话",
- "defaultMessage": "Video call",
- "description": "Start video call",
- "missing": false,
- "obsolete": false
- },
- "already_in_call": {
- "translation": "你已经在说话了!",
- "defaultMessage": "You already in an ongoing call!",
- "description": "Error message when the user tried to accept a new call without finishing pervious one",
- "missing": false,
- "obsolete": false
- },
- "call_cancelled": {
- "translation": "取消",
- "defaultMessage": "cancelled",
- "description": "Label for cancelled outgoing call",
- "missing": false,
- "obsolete": false
- },
- "call_missed": {
- "translation": "未接",
- "defaultMessage": "missed",
- "description": "Label for missed incoming call",
- "missing": false,
- "obsolete": false
- },
- "call_declined": {
- "translation": "被拒",
- "defaultMessage": "declined",
- "description": "Label for declined call",
- "missing": false,
- "obsolete": false
- },
- "call_disconnected": {
- "translation": "断开连接",
- "defaultMessage": "disconnected",
- "description": "Label for disconnected call",
- "missing": false,
- "obsolete": false
- },
- "label_use_secure_connection": {
- "translation": "使用安全连接",
- "defaultMessage": "Use secure connection",
- "description": "Label for WS/WSS connection type in SettingsView",
- "missing": false,
- "obsolete": false
- },
- "cred_confirmed_successfully": {
- "translation": "确认成功",
- "defaultMessage": "Confirmed successfully",
- "description": "Notification message that the credential was successfully validated.",
- "missing": false,
- "obsolete": false
- },
- "menu_item_edit": {
- "translation": "编辑",
- "defaultMessage": "Edit",
- "description": "Edit message",
- "missing": false,
- "obsolete": false
- },
- "editing_message": {
- "translation": "编辑",
- "defaultMessage": "Editing",
- "description": "Title over message editing preview",
- "missing": false,
- "obsolete": false
- },
- "message_edited_marker": {
- "translation": ",编辑",
- "defaultMessage": ", edited",
- "description": "Marker indicating that the message was edited",
- "missing": false,
- "obsolete": false
- },
- "icon_title_add_image": {
- "translation": "添加图片",
- "defaultMessage": "Add image",
- "description": "Icon tool tip for attaching an image",
- "missing": false,
- "obsolete": false
- },
- "icon_title_attach_file": {
- "translation": "附加文件",
- "defaultMessage": "Attach file",
- "description": "Icon tool tip for attaching a file",
- "missing": false,
- "obsolete": false
- },
- "icon_title_delete": {
- "translation": "删除录音",
- "defaultMessage": "Delete recording",
- "description": "Icon tool tip for deleting recorded audio",
- "missing": false,
- "obsolete": false
- },
- "icon_title_pause": {
- "translation": "暂停播放",
- "defaultMessage": "Pause playback",
- "description": "Icon tool tip for pausing audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_play": {
- "translation": "播放录音",
- "defaultMessage": "Play recording",
- "description": "Icon tool tip for starting audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_record_voice": {
- "translation": "录制语音信息",
- "defaultMessage": "Record voice message",
- "description": "Icon tool tip for recording a voice message",
- "missing": false,
- "obsolete": false
- },
- "icon_title_resume": {
- "translation": "恢复播放",
- "defaultMessage": "Resume playback",
- "description": "Icon tool tip for resuming audio playback",
- "missing": false,
- "obsolete": false
- },
- "icon_title_send": {
- "translation": "发送消息",
- "defaultMessage": "Send message",
- "description": "Icon tool tip for sending a message",
- "missing": false,
- "obsolete": false
- },
- "drag_file": {
- "translation": "将文件拖到这里",
- "defaultMessage": "Drag file here",
- "description": "Prompt on the file drag-n-drop overlay banner",
- "missing": false,
- "obsolete": false
- },
- "drafty_video": {
- "translation": "视频录制",
- "defaultMessage": "Video recording",
- "description": "Comment for videos embedded in Drafty",
- "missing": false,
- "obsolete": false
- },
- "call_in_progress": {
- "translation": "通话中",
- "defaultMessage": "in progress",
- "description": "Label for call in progress",
- "missing": false,
- "obsolete": false
- },
- "menu_item_audio_call": {
- "translation": "语音电话",
- "defaultMessage": "Call",
- "description": "Start audio call",
- "missing": false,
- "obsolete": false
- },
- "unrecognized_video_format": {
- "translation": "无法识别此视频的格式",
- "defaultMessage": "Format of this video is not recognized",
- "description": "Error message when uploaded video is invalid",
- "missing": false,
- "obsolete": false
- },
- "loading_note": {
- "translation": "加载中...",
- "defaultMessage": "Loading...",
- "description": "Message shown when component is loading",
- "missing": false,
- "obsolete": false
- },
- "password_i_have_code": {
- "translation": "我有代码",
- "defaultMessage": "I have code",
- "description": "Call to open field to enter password reset code",
- "missing": false,
- "obsolete": false
- },
- "label_reset_password_tel": {
- "translation": "发送短信重置密码",
- "defaultMessage": "Send SMS to reset password",
- "description": "Label for password reset field by phone",
- "missing": false,
- "obsolete": false
- },
- "mobile_phone_number": {
- "translation": "手机号",
- "defaultMessage": "Mobile phone number",
- "description": "Prompt for entering a mobile phone number",
- "missing": false,
- "obsolete": false
- },
- "password_reset_sms_sent": {
- "translation": "已发送带有安全代码的短信。",
- "defaultMessage": "A text message with security code has been sent.",
- "description": "Notification that the SMS with password reset instructions has been sent",
- "missing": false,
- "obsolete": false
- },
- "mobile_number_required": {
- "translation": "需要手机号",
- "defaultMessage": "Mobile phone number required",
- "description": "Error message",
- "missing": false,
- "obsolete": false
- },
- "current_email": {
- "translation": "当前邮箱",
- "defaultMessage": "Current email",
- "description": "Label for old email",
- "missing": false,
- "obsolete": false
- },
- "current_phone": {
- "translation": "当前电话号码",
- "defaultMessage": "Current phone number",
- "description": "Label for old phone",
- "missing": false,
- "obsolete": false
- },
- "new_email": {
- "translation": "新电子邮件地址",
- "defaultMessage": "New email",
- "description": "Prompt for entering a new email",
- "missing": false,
- "obsolete": false
- },
- "new_phone_number": {
- "translation": "新电话号码",
- "defaultMessage": "New phone number",
- "description": "Prompt for entering a new telephone number",
- "missing": false,
- "obsolete": false
- },
- "change_email": {
- "translation": "更改邮箱",
- "defaultMessage": "Change email",
- "description": "Prompt to change current email",
- "missing": false,
- "obsolete": false
- },
- "change_phone": {
- "translation": "更改电话号码",
- "defaultMessage": "Change phone number",
- "description": "Prompt to change current phone number",
- "missing": false,
- "obsolete": false
- },
- "call_busy": {
- "translation": "忙碌的",
- "defaultMessage": "busy",
- "description": "Label for busy call line",
- "missing": false,
- "obsolete": false
- },
- "pin_message": {
- "translation": "置顶消息",
- "defaultMessage": "Pin",
- "description": "Menu item [Pin] for pinning message to the top.",
- "missing": false,
- "obsolete": false
- },
- "unpin_message": {
- "translation": "取消置顶消息",
- "defaultMessage": "Unpin",
- "description": "Menu item [Unpin] for un-pinning the message.",
- "missing": false,
- "obsolete": false
- },
- "failed_to_init_audio": {
- "translation": "初始化录音失败",
- "defaultMessage": "Failed to initialize audio recording",
- "description": "Error message when audio is not available",
- "missing": false,
- "obsolete": false
- },
- "password_reset_success": {
- "translation": "密码重置成功",
- "defaultMessage": "Password reset successfully",
- "description": "Notification message that the password was successfully reset.",
- "missing": false,
- "obsolete": false
- },
- "scan_qr_code": {
- "translation": "扫描二维码",
- "defaultMessage": "Scan QR Code",
- "description": "Title for scanning QR code",
- "missing": false,
- "obsolete": false
- },
- "self_topic_comment": {
- "translation": "为后人保存的注释、消息、链接、文件",
- "defaultMessage": "Notes, messages, links, files saved for posterity",
- "description": "Comment for self topic for UI",
- "missing": false,
- "obsolete": false
- },
- "self_topic_name": {
- "translation": "已保存的消息",
- "defaultMessage": "Saved messages",
- "description": "Name of self topic for UI",
- "missing": false,
- "obsolete": false
- },
- "message_not_found": {
- "translation": "未找到消息",
- "defaultMessage": "message not found",
- "description": "Pinned message is not found",
- "missing": false,
- "obsolete": false
- },
- "message_deleted": {
- "translation": "消息已删除",
- "defaultMessage": "message deleted",
- "description": "Pinned message is deleted",
- "missing": false,
- "obsolete": false
- },
- "alias_editing_placeholder": {
- "translation": "别名(可选)",
- "defaultMessage": "Alias (optional)",
- "description": "Placeholder for editing user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "label_alias": {
- "translation": "别名:",
- "defaultMessage": "Alias:",
- "description": "Label for user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "alias_already_taken": {
- "translation": "(已占用)",
- "defaultMessage": "(already taken)",
- "description": "Error message for alias already taken",
- "missing": false,
- "obsolete": false
- },
- "alias_invalid": {
- "translation": "(无效)",
- "defaultMessage": "(invalid)",
- "description": "Error message for invalid alias",
- "missing": false,
- "obsolete": false
- },
- "label_alias_edit": {
- "translation": "别名",
- "defaultMessage": "Alias",
- "description": "Label for editing user or topic alias",
- "missing": false,
- "obsolete": false
- },
- "select_country": {
- "translation": "选择国家",
- "defaultMessage": "Select country",
- "description": "Placeholder for the country selector",
- "missing": false,
- "obsolete": false
- },
- "text_copied": {
- "translation": "已复制到剪贴板",
- "defaultMessage": "Copied to clipboard",
- "description": "Notification that text has been copied to clipboard",
- "missing": false,
- "obsolete": false
- },
- "color_schema_dark": {
- "translation": "深色",
- "defaultMessage": "Dark",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "color_schema_light": {
- "translation": "浅色",
- "defaultMessage": "Light",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "color_schema_system": {
- "translation": "系统默认",
- "defaultMessage": "System default",
- "description": "Name of the color schema",
- "missing": false,
- "obsolete": false
- },
- "label_color_schema": {
- "translation": "主题:",
- "defaultMessage": "Theme:",
- "description": "Label for selecting color scheme (dark, light) in Settings",
- "missing": false,
- "obsolete": false
- },
- "label_text_size": {
- "translation": "文字大小:",
- "defaultMessage": "Text size:",
- "description": "Label adjusting text size",
- "missing": false,
- "obsolete": false
- },
- "incognito_mode_description": {
- "translation": "隐藏您的在线状态和正在输入指示器。",
- "defaultMessage": "Hide your online status and typing indicators.",
- "description": "Description for incognito mode toggle",
- "missing": false,
- "obsolete": false
- },
- "send_command": {
- "translation": "按 {key} 发送",
- "defaultMessage": "Send on {key}",
- "description": "Config option to send message on hitting CTRL(or Cmd)-Enter",
- "missing": false,
- "obsolete": false
- },
- "send_command_explained": {
- "translation": "按 Enter 换行",
- "defaultMessage": "Press Enter for new line",
- "description": "Explanation how to enter newline when [Send on CTRL-Enter] is enabled",
- "missing": false,
- "obsolete": false
- },
- "send_plain": {
- "translation": "按 Enter 发送",
- "defaultMessage": "Send on Enter",
- "description": "Config option to send message on hitting Enter",
- "missing": false,
- "obsolete": false
- },
- "send_plain_explained": {
- "translation": "按 Shift + Enter 换行",
- "defaultMessage": "Press Shift + Enter for new line",
- "description": "Explanation how to enter newline when [Send on Enter] is enabled",
- "missing": false,
- "obsolete": false
- },
- "sidepanel_title_acc_general": {
- "translation": "常规",
- "defaultMessage": "General",
- "description": "Sidepanel title for AccGeneralView.",
- "missing": false,
- "obsolete": false
- },
- "label_keyboard": {
- "translation": "键盘:",
- "defaultMessage": "Keyboard:",
- "description": "Label for send on enter settings",
- "missing": false,
- "obsolete": false
- },
- "pin_chat": {
- "translation": "置顶",
- "defaultMessage": "Pin",
- "description": "Menu item [Pin] for pinning chat to the top.",
- "missing": false,
- "obsolete": false
- },
- "unpin_chat": {
- "translation": "取消置顶",
- "defaultMessage": "Unpin",
- "description": "Menu item [Unpin] for un-pinning the chat.",
- "missing": false,
- "obsolete": false
- },
- "subscriber_count": {
- "translation": "{count, plural, other {{count, number} 位订阅者}}",
- "defaultMessage": "{count, plural, one {{count, number} subscriber} two {{count, number} subscribers} three {{count, number} subscribers} few {{count, number} subscribers} many {{count, number} subscribers} other {{count, number} subscribers}}",
- "description": "Count of channel subscribers",
- "missing": false,
- "obsolete": false
- },
- "label_member_count": {
- "translation": "成员:",
- "defaultMessage": "Members:",
- "description": "Label for the count of group topic members",
- "missing": false,
- "obsolete": false
- },
- "label_subscriber_count": {
- "translation": "订阅者:",
- "defaultMessage": "Subscribers:",
- "description": "Label for the count of channel subscribers",
- "missing": false,
- "obsolete": false
- },
- "member_count": {
- "translation": "{count, plural, other {{count, number} 位成员}}",
- "defaultMessage": "{count, plural, one {{count, number} member} two {{count, number} members} three {{count, number} members} few {{count, number} members} many {{count, number} members} other {{count, number} members}}",
- "description": "Count of group topic members",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_image": {
- "translation": "图片",
- "defaultMessage": "image",
- "description": "Tab title Image",
- "missing": false,
- "obsolete": false
- },
- "tabtitle_pattern": {
- "translation": "图案",
- "defaultMessage": "pattern",
- "description": "Tab title Pattern",
- "missing": false,
- "obsolete": false
- },
- "button_restore": {
- "translation": "恢复默认",
- "defaultMessage": "Restore default",
- "description": "Reset setting to default",
- "missing": false,
- "obsolete": false
- },
- "wallpapers": {
- "translation": "壁纸",
- "defaultMessage": "Wallpapers",
- "description": "Link or title for working with wallpapers.",
- "missing": false,
- "obsolete": false
- },
- "label_blur_wallpaper": {
- "translation": "模糊:",
- "defaultMessage": "Blur:",
- "description": "Label adjusting blur amount",
- "missing": false,
- "obsolete": false
- },
- "label_scan_id": {
- "translation": "扫描我的 ID:",
- "defaultMessage": "Scan my ID:",
- "description": "Label [Scan my ID:]",
- "missing": false,
- "obsolete": false
- },
- "unknown_name": {
- "translation": "未知",
- "defaultMessage": "Unknown",
- "description": "Name to show when the name is missing",
- "missing": false,
- "obsolete": false
- },
- "save_action": {
- "translation": "保存",
- "defaultMessage": "Save",
- "description": "Label for Save button",
- "missing": false,
- "obsolete": false
- },
- "cannot_parse_vcard": {
- "translation": "无法解析 vCard 文件。",
- "defaultMessage": "Cannot parse vCard file.",
- "description": "Error message when vCard file cannot be parsed",
- "missing": false,
- "obsolete": false
- },
- "chat_now": {
- "translation": "聊天",
- "defaultMessage": "Chat",
- "description": "Label for message button in contact card",
- "missing": false,
- "obsolete": false
- },
- "find_user": {
- "translation": "查找",
- "defaultMessage": "Find",
- "description": "Label for search button in contact card",
- "missing": false,
- "obsolete": false
- },
- "label_contact_card": {
- "translation": "联系人卡片",
- "defaultMessage": "Contact Card",
- "description": "Label for contact card preview",
- "missing": false,
- "obsolete": false
- },
- "label_contacts": {
- "translation": "联系人",
- "defaultMessage": "Contacts",
- "description": "Label for contact information",
- "missing": false,
- "obsolete": false
- },
- "add_members_prompt": {
- "translation": "添加成员",
- "defaultMessage": "add members",
- "description": "Placeholder in group members input field",
- "missing": false,
- "obsolete": false
- }
-}
diff --git a/src/index.js b/src/index.js
deleted file mode 100644
index ce4cd1ec..00000000
--- a/src/index.js
+++ /dev/null
@@ -1,78 +0,0 @@
-// Put all packages together.
-// Used to generate umd/index.prod.js
-
-import React from 'react';
-import { createRoot } from 'react-dom/client';
-
-import { IntlProvider } from 'react-intl';
-
-import TinodeWeb from './views/tinode-web.jsx';
-import Navigation from './lib/navigation.js';
-
-// Insert google analytics script and tag if configured.
-if (typeof FIREBASE_INIT != 'undefined' && FIREBASE_INIT && FIREBASE_INIT.measurementId) {
- const head = document.getElementsByTagName('head')[0];
- let script = document.createElement('script');
- script.src = 'https://www.googletagmanager.com/gtag/js?id=' + FIREBASE_INIT.measurementId;
- script.async = true;
- head.prepend(script);
- window.dataLayer = window.dataLayer || [];
- function gtag(){dataLayer.push(arguments);}
- gtag('js', new Date());
- gtag('config', FIREBASE_INIT.measurementId);
-}
-
-// Allow loading translation strings for just one language.
-const messageLoader = {
- 'ar': _ => import('./i18n.min/ar.json'),
- 'de': _ => import('./i18n.min/de.json'),
- 'en': _ => import('./i18n.min/en.json'),
- 'es': _ => import('./i18n.min/es.json'),
- 'fr': _ => import('./i18n.min/fr.json'),
- 'it': _ => import('./i18n.min/it.json'),
- 'ko': _ => import('./i18n.min/ko.json'),
- 'ro': _ => import('./i18n.min/ro.json'),
- 'ru': _ => import('./i18n.min/ru.json'),
- 'th': _ => import('./i18n.min/th.json'),
- 'uk': _ => import('./i18n.min/uk.json'),
- 'vi': _ => import('./i18n.min/vi.json'),
- 'zh': _ => import('./i18n.min/zh.json'),
- 'zh-TW': _ => import('./i18n.min/zh-TW.json')
-};
-
-// List of languages that are written right-to-left.
-const rtl = ['ar', 'fa', 'he', 'ur'];
-
-// Detect human language to use in the UI:
-// Check parameters from URL hash #?hl=ru, then browser, then use 'en' as a fallback.
-const { params } = Navigation.parseUrl(Navigation.currentUrl());
-const language = (params && params.hl) ||
- (navigator.languages && navigator.languages[0]) ||
- navigator.language ||
- navigator.userLanguage ||
- 'en';
-
-// Make sure names like 'en_GB' and 'en-GB' consistently use '-'.
-const normalized = language.replace('_', '-');
-// Get the base language 'en' from a more specific 'en-US' as a partial fallback.
-const baseLanguage = normalized.split('-')[0].toLowerCase();
-
-// Try the full locale first, then the locale without the region code, fallback to 'en'.
-const htmlLang = messageLoader[normalized] ? language : messageLoader[baseLanguage] ? baseLanguage : 'en';
-
-const html = document.getElementsByTagName('html')[0];
-// Set lang attribute of the HTML element:
-html.setAttribute('lang', htmlLang);
-if (rtl.includes(baseLanguage)) {
- // Set right-to-left dir attribute of the HTML element:
- html.setAttribute('dir', 'rtl');
-}
-// Render the app.
-const root = createRoot(document.getElementById('mountPoint'));
-messageLoader[htmlLang]().then(messages =>
- root.render(
- React.createElement(IntlProvider,
- { locale: language, messages: messages, textComponent: React.Fragment },
- React.createElement(TinodeWeb, null)
- )
-));
diff --git a/src/lib/blob-helpers.js b/src/lib/blob-helpers.js
deleted file mode 100644
index d305911d..00000000
--- a/src/lib/blob-helpers.js
+++ /dev/null
@@ -1,326 +0,0 @@
-// File and image helper functions.
-import { TheCard } from 'tinode-sdk';
-
-// Supported image MIME types and corresponding file extensions.
-export const SUPPORTED_IMAGE_FORMATS = ['image/jpeg', 'image/gif', 'image/png', 'image/svg', 'image/svg+xml'];
-export const MIME_EXTENSIONS = ['jpg', 'gif', 'png', 'svg', 'svg'];
-
-// Get an URL from a theCard photo: either create a data: URL or return reference URL.
-export function makeImageUrl(photo) {
- if (photo && typeof photo == 'object') {
- if (photo.ref) {
- return photo.ref
- }
- if (photo.data && photo.type) {
- const mime = photo.type.startsWith('image/') ? photo.type : ('image/' + photo.type);
- return 'data:' + mime + ';base64,' + photo.data;
- }
- }
- return null;
-}
-
-// Calculate linear dimensions for scaling image down to fit under a certain size.
-// Returns an object which contains destination sizes, source sizes, and offsets
-// into source (when making square images).
-export function fitImageSize(width, height, maxWidth, maxHeight, forceSquare) {
- // Sanitize input
- width = width | 0;
- height = height | 0;
- maxWidth = maxWidth | 0;
- maxHeight = maxHeight | 0;
-
- if (width <= 0 || height <= 0 || maxWidth <= 0 || maxHeight <= 0) {
- return null;
- }
-
- if (forceSquare) {
- maxWidth = maxHeight = Math.min(maxWidth, maxHeight);
- }
-
- const scale = Math.min(
- Math.min(width, maxWidth) / width,
- Math.min(height, maxHeight) / height
- );
-
- const size = {
- dstWidth: (width * scale) | 0,
- dstHeight: (height * scale) | 0,
- };
-
- if (forceSquare) {
- // Also calculate parameters for making the image square.
- size.dstWidth = size.dstHeight = Math.min(size.dstWidth, size.dstHeight);
- size.srcWidth = size.srcHeight = Math.min(width, height);
- size.xoffset = ((width - size.srcWidth) / 2) | 0;
- size.yoffset = ((height - size.srcWidth) / 2) | 0;
- } else {
- size.xoffset = size.yoffset = 0;
- size.srcWidth = width;
- size.srcHeight = height;
- }
- return size;
-}
-
-// Ensure file's extension matches mime content type
-export function fileNameForMime(fname, mime) {
- const idx = SUPPORTED_IMAGE_FORMATS.indexOf(mime);
- if (idx < 0 || !fname) {
- // Unknown mime or empty name.
- return fname;
- }
- const ext = MIME_EXTENSIONS[idx];
-
- const at = fname.lastIndexOf('.');
- if (at >= 0) {
- fname = fname.substring(0, at);
- }
- return fname + '.' + ext;
-}
-
-// Scale uploaded image to fit under certain dimensions and byte size, optionally constraining to a square.
-// Returns a promise which is resolven on success or rejected on failure.
-export function imageScaled(fileOrBlob, maxWidth, maxHeight, maxSize, forceSquare) {
- return new Promise((resolve, reject) => {
- const img = new Image();
- img.crossOrigin = 'Anonymous';
- img.onerror = function(err) {
- reject(new Error("Image format unrecognized"));
- }
- img.onload = async function() {
- // Once the image is loaded, the URL is no longer needed.
- URL.revokeObjectURL(img.src);
-
- // Calculate the desired image dimensions.
- const dim = fitImageSize(img.width, img.height, maxWidth, maxHeight, forceSquare);
- if (!dim) {
- reject(new Error("Invalid image"));
- return;
- }
- let canvas = document.createElement('canvas');
- canvas.width = dim.dstWidth;
- canvas.height = dim.dstHeight;
- let ctx = canvas.getContext('2d');
- ctx.imageSmoothingEnabled = true;
- ctx.drawImage(img, dim.xoffset, dim.yoffset, dim.srcWidth, dim.srcHeight,
- 0, 0, dim.dstWidth, dim.dstHeight);
-
- const mime = SUPPORTED_IMAGE_FORMATS.includes(fileOrBlob.type) ? fileOrBlob.type : 'image/jpeg';
- // Generate blob to check size of the image.
- let blob = await new Promise(resolve => canvas.toBlob(resolve, mime));
- if (!blob) {
- reject(new Error("Unsupported image format"));
- return;
- }
- // Ensure the image is not too large. Shrink the image keeping the aspect ratio.
- // Do nothing if maxsize is <= 0.
- while (maxSize > 0 && blob.length > maxSize) {
- dim.dstWidth = (dim.dstWidth * 0.70710678118) | 0;
- dim.dstHeight = (dim.dstHeight * 0.70710678118) | 0;
- canvas.width = dim.dstWidth;
- canvas.height = dim.dstHeight;
- ctx = canvas.getContext('2d');
- ctx.clearRect(0, 0, canvas.width, canvas.height);
- ctx.drawImage(img, dim.xoffset, dim.yoffset, dim.srcWidth, dim.srcHeight,
- 0, 0, dim.dstWidth, dim.dstHeight);
- blob = await new Promise(resolve => canvas.toBlob(resolve, mime));
- }
-
- canvas = null;
- resolve({mime: mime, blob: blob, width: dim.dstWidth, height: dim.dstHeight, name: fileNameForMime(fileOrBlob.name, mime)});
- };
- img.src = URL.createObjectURL(fileOrBlob);
- });
-}
-
-// Scale and crop image according to specified dimensions.
-// The coordinates are in unscaled image pixels, i.e. cut the rectangle first then scale it.
-// returns a promise.
-export function imageCrop(mime, objURL, left, top, width, height, scale) {
- return new Promise((resolve, reject) => {
- const img = new Image();
- img.crossOrigin = 'Anonymous';
- img.onerror = _ => {
- reject(new Error("Image format unrecognized"));
- };
- img.onload = _ => {
- // Once the image is loaded, the URL is no longer needed.
- URL.revokeObjectURL(img.src);
-
- let canvas = document.createElement('canvas');
- canvas.width = width * scale;
- canvas.height = height * scale;
- let ctx = canvas.getContext('2d');
- ctx.imageSmoothingEnabled = true;
- ctx.drawImage(img, left, top, width, height, 0, 0, canvas.width, canvas.height);
-
- mime = SUPPORTED_IMAGE_FORMATS.includes(mime) ? mime : 'image/jpeg';
- // Generate blob to check size of the image.
- canvas.toBlob((blob) => {
- // Allow GC.
- canvas = null;
- if (blob) {
- resolve({mime: mime, blob: blob, width: width, height: height});
- } else {
- reject(new Error("Unsupported image format"));
- }
- }, mime);
- };
- img.src = objURL;
- });
-}
-
-// Convert file to base64 string.
-export function fileToBase64(file) {
- return new Promise((resolve, reject) => {
- const reader = new FileReader();
- reader.onerror = _ => {
- reject(reader.error);
- };
- reader.onload = _ => {
- resolve({mime: file.type, bits: reader.result.split(',')[1], name: file.name});
- };
- reader.readAsDataURL(file);
- });
-}
-
-// Convert Blob to base64 string. Returns a promise resolved with the base64 string and mime.
-export function blobToBase64(blob) {
- return new Promise((resolve, reject) => {
- const reader = new FileReader();
- reader.onerror = _ => {
- reject(reader.error);
- };
- reader.onload = _ => {
- resolve({mime: blob.type, bits: reader.result.split(',')[1]});
- };
- reader.readAsDataURL(blob);
- });
-}
-
-// File pasted from the clipboard. It's either an inline image or a file attachment.
-export function filePasted(event, onImageSuccess, onAttachmentSuccess, onError) {
- const items = (event.clipboardData || event.originalEvent.clipboardData || {}).items;
- if (!items || !items.length) {
- return false;
- }
-
- for (let i in items) {
- const item = items[i];
- if (item.kind === 'file') {
- const file = item.getAsFile();
- if (!file) {
- console.error("Failed to get file object from pasted file item", item.kind, item.type);
- onError("Failed to get file object from pasted file item");
- continue;
- }
- if (file.type && file.type.split('/')[0] == 'image') {
- onImageSuccess(file);
- } else {
- onAttachmentSuccess(file);
- }
- // Indicate that the pasted data contains a file.
- return true;
- }
- }
- // No file found.
- return false;
-}
-
-// Get mime type from data URL header.
-export function getMimeType(header) {
- const mime = /^data:(image\/[-+a-z0-9.]+);base64/.exec(header);
- return (mime && mime.length > 1) ? mime[1] : null;
-}
-
-// Given length of a binary object in bytes, calculate the length after
-// base64 encoding.
-export function base64EncodedLen(n) {
- return Math.floor((n + 2) / 3) * 4;
-}
-
-// Given length of a base64-encoded object, calculate decoded size of the
-// pbject in bytes.
-export function base64DecodedLen(n) {
- return Math.floor(n / 4) * 3;
-}
-
-// Re-encode string to standard base64 encoding with padding.
-// The string may be base64-URL encoded without the padding.
-export function base64ReEncode(str) {
- if (str) {
- str = str.replace(/-/g, '+').replace(/_/g, '/');
- try {
- str = btoa(atob(str));
- } catch(err) {
- console.error("Failed to base64 re-encode string.", err);
- str = null;
- }
- }
- return str;
-}
-
-// Convert a base64 encoded string with the provided mime type into a Blob.
-export function base64ToBlob(str, mime) {
- if (!str) {
- return null;
- }
-
- try {
- // Make blob.
- const bin = atob(str);
- const length = bin.length;
- const buf = new ArrayBuffer(length);
- const arr = new Uint8Array(buf);
- for (let i = 0; i < length; i++) {
- arr[i] = bin.charCodeAt(i);
- }
-
- return new Blob([buf], { type: mime });
- } catch(err) {
- console.error("Failed to convert base64 to blob: ", err);
- }
-
- return null;
-}
-
-export function intArrayToBase64(arr) {
- if (!Array.isArray(arr)) {
- return null;
- }
- try {
- let bin = '';
- new Uint8Array(arr).forEach(b => bin += String.fromCharCode(b));
- return window.btoa(bin);
- } catch (err) {}
- return null;
-}
-
-export function base64ToIntArray(b64) {
- const arr = [];
- try {
- const bin = window.atob(b64);
- [...bin].forEach(c => {
- arr.push(c.charCodeAt(0));
- });
- return arr;
- } catch (err) {}
- return null;
-}
-
-export function importVCard(fileOrBlob) {
- return new Promise((resolve, reject) => {
- const reader = new FileReader();
- reader.onerror = _ => {
- reject(reader.error);
- };
- reader.onload = _ => {
- const card = TheCard.importVCard(reader.result);
- if (!card) {
- reject(new Error("Failed to parse vCard"));
- return;
- }
- resolve(card);
- };
- reader.readAsText(fileOrBlob);
- });
-}
diff --git a/src/lib/formatters.js b/src/lib/formatters.js
deleted file mode 100644
index 79ccac5a..00000000
--- a/src/lib/formatters.js
+++ /dev/null
@@ -1,580 +0,0 @@
-import React from 'react';
-import { defineMessages } from 'react-intl';
-
-import { Drafty, TheCard } from 'tinode-sdk';
-
-import AudioPlayer from '../widgets/audio-player.jsx'
-import CallMessage from '../widgets/call-message.jsx'
-import CallStatus from '../widgets/call-status.jsx';
-import InlineVideo from '../widgets/inline-video.jsx';
-import LazyImage from '../widgets/lazy-image.jsx';
-import TheCardMini from '../widgets/the-card-mini.jsx';
-import UploadingImage from '../widgets/uploading-image.jsx'
-
-import { BROKEN_IMAGE_SIZE, CLICKABLE_URL_SCHEMES, IMAGE_THUMBNAIL_DIM, NO_DIMENSIONS_VIDEO,
- REM_SIZE, VIDEO_THUMBNAIL_WIDTH } from '../config.js';
-import { base64ToBlob, blobToBase64, fitImageSize, imageScaled } from './blob-helpers.js';
-import { normalizeFileReference } from './im-api.js';
-import { idToColorClass, secondsToTime, shortenFileName } from './strformat.js';
-import { cancelablePromise, sanitizeUrl, sanitizeUrlForMime } from './utils.js';
-
-const messages = defineMessages({
- drafty_form: {
- id: 'drafty_form',
- defaultMessage: 'Form: ',
- description: 'Comment for form in Drafty'
- },
- drafty_attachment: {
- id: 'drafty_attachment',
- defaultMessage: 'Attachment',
- description: 'Comment for attachment in Drafty'
- },
- drafty_image: {
- id: 'drafty_image',
- defaultMessage: 'Picture',
- description: 'Comment for embedded images in Drafty'
- },
- drafty_video: {
- id: 'drafty_video',
- defaultMessage: 'Video recording',
- description: 'Comment for videos embedded in Drafty'
- },
- drafty_unknown: {
- id: 'drafty_unknown',
- defaultMessage: 'Unsupported',
- description: 'Unsupported entity in drafty'
- }
-});
-
-// The main Drafty formatter: converts Drafty elements into React classes. 'this' is set by the caller.
-// 'this' must contain:
-// viewportWidth:
-// authorizeURL:
-// onHandleClick:
-export function fullFormatter(style, data, values, key, stack) {
- if (stack.includes('QQ')) {
- return quoteFormatter.call(this, style, data, values, key);
- }
-
- if (!style) {
- // Unformatted.
- return values;
- }
-
- let el = Drafty.tagName(style);
- let attr = Drafty.attrValue(style, data) || {};
- attr.key = key;
- switch (style) {
- case 'AU':
- // Show audio player.
- if (attr.src) {
- attr.src = this.authorizeURL(normalizeFileReference(sanitizeUrlForMime(attr.src, 'audio')));
- attr.duration = data.duration > 0 ? (data.duration | 0) : undefined;
- attr.preview = data.preview;
- attr.loading = 'lazy';
- // 4.9 语音未读红点:chat-message.jsx 的 onVoicePlayed 记录"这条语音真的被点开听过",
- // 跟消息本身有没有被标记已读是两件事。favorites/history 等别处复用同一个 formatter
- // context 没有这个字段时,attr.onPlay 就是 undefined,AudioPlayer 那边本来就把它当
- // 可选 prop 处理,不会因此出问题。
- attr.onPlay = this.onVoicePlayed;
- }
- el = AudioPlayer;
- // Audio element cannot have content.
- values = null;
- break;
- case 'BR':
- values = null;
- break;
- case 'EX':
- // Ignore.
- break;
- case 'HL':
- // Highlighted text. Assign class name.
- attr.className = 'highlight';
- break;
- case 'HD':
- el = null;
- values = null;
- break;
- case 'IM':
- // Additional processing for images
- el = handleImageData.call(this, el, data, attr);
- // Image element cannot have content.
- values = null;
- break;
- case 'BN':
- // Button
- attr.onClick = (e) => this.onHandleClick(e, 'form_button');
- let inner = React.Children.map(values, (child) => {
- return typeof child == 'string' ? child : undefined;
- });
- if (!inner || inner.length == 0) {
- inner = [attr.name]
- }
- // Get text which will be sent back when the button is clicked.
- attr['data-title'] = inner.join('');
- break;
- case 'LN':
- if (attr) {
- // Ensure only safe URL schemes are clickable.
- attr.href = typeof attr.href == 'string' ? sanitizeUrl(attr.href, CLICKABLE_URL_SCHEMES) : '';
- }
- break;
- case 'MN':
- // Mention
- attr.className = 'mention'
- if (data) {
- attr.className += ' ' + idToColorClass(data.val, false, true);
- }
- break;
- case 'FM':
- // Form
- attr.className = 'bot-form';
- break;
- case 'RW':
- // Form element formatting is dependent on element content.
- break;
- case 'QQ':
- // Quote/citation.
- attr.className = 'reply-quote'
- attr.onClick = (e) => this.onHandleClick(e, 'quote');
- break;
- case 'TC':
- // TheCard
- el = TheCardMini;
- attr.content = data;
- attr.authorizeURL = this.authorizeURL;
- attr.onChatClick = (e) => this.onHandleClick(e, 'contact_chat');
- attr.onFindClick = (e) => this.onHandleClick(e, 'contact_find');
- values = null;
- break;
- case 'VC':
- el = CallMessage;
- // Video call messages do not have content.
- values = null;
- if (data) {
- attr.callState = data.state;
- attr.incoming = data.incoming;
- attr.duration = data.duration;
- }
- break;
- case 'VD':
- // Additional processing for videos.
- el = handleVideoData.call(this, el, data, attr);
- // Video element cannot have content.
- values = null;
- break;
- default:
- if (!el) {
- // Unknown element.
- el = React.Fragment;
- attr = {key: key};
- // Generate comment for unknown element.
- let body = values;
- if (!Array.isArray(values) || !values.join('').trim()) {
- body = [React.createElement('span',
- {key: 'x1', className: 'gray'},
- this.formatMessage(messages.drafty_unknown)
- )];
- }
- values = [React.createElement('i',
- {key: 'x0', className: 'material-icons gray'},
- 'extension'
- ), ' '].concat(body);
- }
- break;
- }
- if (!el) {
- return values;
- }
- return React.createElement(el, attr, values);
-}
-
-// Additional processing of image data.
-function handleImageData(el, data, attr) {
- if (!data) {
- attr.src = 'img/broken_image.png';
- attr.style = {
- width: IMAGE_THUMBNAIL_DIM + 'px',
- height: IMAGE_THUMBNAIL_DIM + 'px',
- };
- return el;
- }
-
- attr.className = 'inline-image';
- const dim = fitImageSize(data.width, data.height,
- this.viewportWidth > 0 ? Math.min(this.viewportWidth - REM_SIZE * 6.5, REM_SIZE * 34.5) :
- REM_SIZE * 34.5, REM_SIZE * 24, false) ||
- {dstWidth: BROKEN_IMAGE_SIZE, dstHeight: BROKEN_IMAGE_SIZE};
- attr.style = {
- width: dim.dstWidth + 'px',
- height: dim.dstHeight + 'px',
- // Looks like a Chrome bug: broken image does not respect 'width' and 'height'.
- minWidth: dim.dstWidth + 'px',
- minHeight: dim.dstHeight + 'px'
- };
- if (!Drafty.isProcessing(data)) {
- attr.src = this.authorizeURL(normalizeFileReference(sanitizeUrlForMime(attr.src, 'image')));
- attr.alt = data.name;
- if (attr.src) {
- if (Math.max(data.width || 0, data.height || 0) > IMAGE_THUMBNAIL_DIM) {
- // Allow previews for large enough images.
- attr.onClick = (e) => this.onHandleClick(e, 'image');
- attr.className += ' image-clickable';
- }
- attr.loading = 'lazy';
- } else {
- attr.src = null;
- }
- } else {
- // Use custom element instead of or .
- el = UploadingImage;
- }
-
- return el;
-}
-
-// Additional processing of attached video data.
-function handleVideoData(el, data, attr) {
- if (!data) {
- attr.src = 'img/broken_video.png';
- attr.style = {
- width: IMAGE_THUMBNAIL_DIM + 'px',
- height: IMAGE_THUMBNAIL_DIM + 'px',
- };
- return el;
- }
- attr.className = 'inline-image';
- const dim = fitImageSize(data.width, data.height,
- this.viewportWidth > 0 ? Math.min(this.viewportWidth - REM_SIZE * 6.5, REM_SIZE * 34.5) :
- REM_SIZE * 34.5, REM_SIZE * 24, false) ||
- {dstWidth: NO_DIMENSIONS_VIDEO, dstHeight: NO_DIMENSIONS_VIDEO};
- attr.style = {
- width: dim.dstWidth + 'px',
- height: dim.dstHeight + 'px',
- // Looks like a Chrome bug: broken image does not respect 'width' and 'height'.
- minWidth: dim.dstWidth + 'px',
- minHeight: dim.dstHeight + 'px'
- };
- if (!Drafty.isProcessing(data)) {
- attr.src = this.authorizeURL(normalizeFileReference(sanitizeUrlForMime(attr.src, 'image')));
- attr.alt = data.name;
- if (data.ref || data.val) {
- attr.onClick = (e) => this.onHandleClick(e, 'video');
- attr.loading = 'lazy';
- }
- el = InlineVideo;
- } else {
- // Use custom element instead of or .
- el = UploadingImage;
- }
-
- return el;
-}
-
-// Converts Drafty object into a one-line preview. 'this' is set by the caller.
-// 'this' must contain:
-// formatMessage: this.props.intl.formatMessage
-// messages: formatjs messages defined with defineMessages.
-export function previewFormatter(style, data, values, key) {
- if (!style) {
- // Unformatted.
- return values;
- }
-
- let el = Drafty.tagName(style);
- const attr = { key: key };
- switch (style) {
- case 'AU':
- // Voicemail as '[mic] 0:00'.
- el = React.Fragment;
- values = [React.createElement('i', {key: 'au', className: 'material-icons'}, 'mic'),
- ' ', secondsToTime(data.duration/1000)];
- break;
- case 'BR':
- // Replace new line with a space.
- el = React.Fragment;
- values = [' '];
- break;
- case 'HL':
- // Make highlight less prominent in preview.
- attr.className = 'highlight preview';
- break;
- case 'LN':
- case 'MN':
- // Disable links in previews.
- el = 'span';
- break;
- case 'IM':
- // Replace image with '[icon] Image'.
- el = React.Fragment;
- values = [React.createElement('i', {key: 'im', className: 'material-icons'}, 'photo'),
- ' ', this.formatMessage(messages.drafty_image)];
- break;
- case 'BN':
- el = 'span';
- attr.className = 'flat-button faux';
- break;
- case 'FM':
- el = React.Fragment;
- values = [React.createElement('i', {key: 'fm', className: 'material-icons'}, 'dashboard'),
- this.formatMessage(messages.drafty_form)].concat(' ', values || []);
- break;
- case 'RW':
- el = React.Fragment;
- break;
- case 'EX':
- if (data) {
- if (Drafty.isFormResponseType(data.mime)) {
- // Ignore JSON attachments: they are form response payloads.
- return null;
- }
- // Clear payload.
- delete data.val;
- delete data.ref;
- }
- el = React.Fragment;
- values = [React.createElement('i', {key: 'ex', className: 'material-icons'}, 'attachment'),
- ' ', this.formatMessage(messages.drafty_attachment)];
- break;
- case 'VC':
- el = CallStatus;
- if (data) {
- attr.callState = data.state;
- attr.incoming = data.incoming;
- attr.duration = data.duration;
- }
- values = null;
- break;
- case 'QQ':
- case 'HD':
- el = null;
- values = null;
- break;
- case 'TC':
- // TheCard
- el = React.Fragment;
- values = [React.createElement('i', {key: 'tc', className: 'material-icons'}, 'contact_mail'), ' ',
- TheCard.getFn(data) || this.formatMessage(messages.drafty_unknown)];
- break;
- case 'VD':
- // Replace image with '[icon] Video'.
- el = React.Fragment;
- values = [React.createElement('i', {key: 'im', className: 'material-icons'}, 'play_circle_outline'),
- ' ', this.formatMessage(messages.drafty_video)];
- break;
- default:
- if (!el) {
- // Unknown element.
- el = React.Fragment;
- values = [React.createElement('i', {key: 'x0', className: 'material-icons gray'}, 'extension'),
- ' ', this.formatMessage(messages.drafty_unknown)];
- }
- break;
- }
- if (!el) {
- return values;
- }
- return React.createElement(el, attr, values);
-};
-
-// Converts Drafty object into a quoted reply; 'this' is set by the caller.
-function inlineImageAttr(attr, data) {
- attr.style = {
- width: IMAGE_THUMBNAIL_DIM + 'px',
- height: IMAGE_THUMBNAIL_DIM + 'px',
- maxWidth: IMAGE_THUMBNAIL_DIM + 'px',
- maxHeight: IMAGE_THUMBNAIL_DIM + 'px',
- }
- attr.className = 'inline-image';
- attr.alt = this.formatMessage(messages.drafty_image);
- if (!data) {
- attr.src = 'img/broken_image.png';
- } else {
- attr.src = attr.src || 'img/broken_image.png';
- }
- attr.title = attr.alt;
- return attr;
-}
-
-// Converts Drafty object into a quoted reply; 'this' is set by the caller.
-function inlineVideoAttr(attr, data) {
- const dim = fitImageSize(data.width, data.height, VIDEO_THUMBNAIL_WIDTH, IMAGE_THUMBNAIL_DIM);
- attr.style = {
- width: dim.width + 'px',
- height: dim.height + 'px',
- maxWidth: VIDEO_THUMBNAIL_WIDTH + 'px',
- maxHeight: IMAGE_THUMBNAIL_DIM + 'px',
- }
- attr.className = 'inline-image';
- attr.alt = this.formatMessage(messages.drafty_video);
- attr.title = attr.alt;
- if (!data) {
- attr.src = 'img/broken_video.png';
- } else {
- attr.src = attr.src || 'img/broken_video.png';
- }
- return attr;
-}
-
-// Displays a portion of Drafty within 'QQ' quotes. 'this' is set by the caller.
-// 'this' must contain:
-// formatMessage: this.props.intl.formatMessage
-// messages: formatjs messages defined with defineMessages.
-// authorizeURL: this.props.tinode.authorizeURL
-// onHandleClick: this.handleDraftyClick
-function quoteFormatter(style, data, values, key) {
- if (['BR', 'EX', 'IM', 'MN', 'VD'].includes(style)) {
- let el = Drafty.tagName(style);
- let attr = Drafty.attrValue(style, data) || {};
- attr.key = key;
- switch(style) {
- case 'BR':
- values = null;
- break;
- case 'IM':
- attr = inlineImageAttr.call(this, attr, data);
- values = [React.createElement('img', attr, null), ' ', attr.alt];
- el = React.Fragment;
- // Fragment attributes.
- attr = {key: key};
- break;
- case 'VD':
- attr = inlineVideoAttr.call(this, attr, data);
- values = [React.createElement('img', attr, null), ' ', attr.alt];
- el = React.Fragment;
- // Fragment attributes.
- attr = {key: key};
- break;
- case 'MN':
- el = 'span';
- attr.className = 'mention'
- if (data) {
- attr.className += ' ' + idToColorClass(data.val, false, true);
- }
- break;
- case 'EX':
- let fname;
- if (data) {
- if (Drafty.isFormResponseType(data.mime)) {
- // Ignore JSON attachments: they are form response payloads.
- return null;
- }
- fname = data.name;
- // Clear payload.
- delete data.val;
- delete data.ref;
- }
- el = React.Fragment;
- values = [React.createElement('i', {key: 'ex', className: 'material-icons'}, 'attachment'),
- shortenFileName(fname, 16) || this.formatMessage(messages.drafty_attachment)];
- break;
- }
- return React.createElement(el, attr, values);
- }
- return previewFormatter.call(this, style, data, values, key);
-}
-
-// Create image thumbnail suitable for inclusion in a quote.
-function quoteImageOrVideo(data, isVideo) {
- let promise;
- let bits, ref, mime;
- if (isVideo) {
- bits = data.preview;
- mime = data.premime || 'image/jpeg';
- ref = data.preref;
- } else {
- bits = data.val;
- mime = data.mime;
- ref = data.ref;
- }
- // Get the blob from the image data.
- if (bits) {
- const blob = base64ToBlob(bits, mime);
- if (!blob) {
- throw new Error("Invalid image");
- }
- promise = Promise.resolve(blob);
- } else if (ref) {
- promise = fetch(this.authorizeURL(normalizeFileReference(sanitizeUrlForMime(ref, 'image'))))
- .then(evt => {
- if (evt.ok) {
- return evt.blob();
- } else {
- throw new Error(`Image fetch unsuccessful: ${evt.status} ${evt.statusText}`);
- }
- });
- } else {
- throw new Error("Missing image data");
- }
-
- // Scale the blob.
- return promise
- .then(blob => {
- // If it's an image, cut the square from the center of the image and shrink it.
- // If it's a video, allow it to be rectantular.
- return imageScaled(blob, isVideo ? VIDEO_THUMBNAIL_WIDTH : IMAGE_THUMBNAIL_DIM, IMAGE_THUMBNAIL_DIM, -1, !isVideo)
- }).then(scaled => {
- if (isVideo) {
- data.premime = scaled.mime;
- } else {
- data.mime = scaled.mime;
- }
- data.size = scaled.blob.size;
- data.width = scaled.width;
- data.height = scaled.height;
- delete data.ref;
- delete data.preref;
- // Keeping the original file name, if provided: ex.data.name;
-
- data.src = URL.createObjectURL(scaled.blob);
- return blobToBase64(scaled.blob);
- }).then(b64 => {
- if (isVideo) {
- data.preview = b64.bits;
- } else {
- data.val = b64.bits;
- }
- return data;
- }).catch(err => {
- delete data.val;
- delete data.preview;
- delete data.src;
- data.width = IMAGE_THUMBNAIL_DIM;
- data.height = IMAGE_THUMBNAIL_DIM;
- // Rethrow.
- throw err;
- });
-}
-
-// Create a preview of a reply.
-export function replyFormatter(style, data, values, key, stack) {
- if (style == 'IM' || style == 'VD') {
- const isImage = style == 'IM';
- const attr = isImage ? inlineImageAttr.call(this, {key: key}, data) :
- inlineVideoAttr.call(this, {key: key}, data);
-
- let loadedPromise;
- try {
- loadedPromise = cancelablePromise(quoteImageOrVideo.call(this, data, style == 'VD'));
- } catch (error) {
- console.warn("Failed to quote image:", error.message);
- loadedPromise = cancelablePromise(error);
- }
- attr.whenDone = loadedPromise;
- values = [React.createElement(LazyImage, attr, null), ' ', attr.alt];
- return React.createElement(React.Fragment, {key: key}, values);
- } else if (style == 'QQ') {
- if (stack.includes('QQ')) {
- // Quote inside quote when forwarding a message.
- return React.createElement('span', {key: key},
- [React.createElement('i', {key: 'qq', className: 'material-icons'}, 'format_quote'), ' ']);
- }
-
- const attr = Drafty.attrValue('QQ', data) || {};
- attr.key = key;
- attr.className = 'reply-quote'
- return React.createElement(Drafty.tagName('QQ'), attr, values);
- }
- return quoteFormatter.call(this, style, data, values, key);
-}
diff --git a/src/lib/host-name.js b/src/lib/host-name.js
deleted file mode 100644
index c95fd21b..00000000
--- a/src/lib/host-name.js
+++ /dev/null
@@ -1,37 +0,0 @@
-import { KNOWN_HOSTS, DEFAULT_HOST } from '../config.js';
-
-// Detect server address from the URL
-export function detectServerAddress() {
- let host = DEFAULT_HOST;
- if (typeof window.location == 'object') {
- if (window.location.protocol == 'file:') {
- // No real page origin to derive a host/port from at all (e.g. a file:// build opened
- // directly) -- this is the one case KNOWN_HOSTS.local's hardcoded port is actually needed.
- host = KNOWN_HOSTS.local;
- } else if (window.location.hostname) {
- // Was `hostname == 'localhost'` on its own branch returning the hardcoded KNOWN_HOSTS.local
- // ('localhost:6060') unconditionally -- any local/self-hosted deployment or dev proxy not on
- // port 6060 (this project's own dev-proxy runs on 8093) got a wrong host and every "no
- // cached tenant/host info yet" connection attempt (e.g. right after clearing storage) failed
- // outright before a later reconnect could recover with the right address. Same-origin
- // same-port assumption already used for every non-localhost host applies here too.
- host = window.location.hostname + (window.location.port ? ':' + window.location.port : '');
- }
- }
- return host;
-}
-
-// Detect if the page is served over HTTPS.
-export function isSecureConnection() {
- if (typeof window.location == 'object') {
- return window.location.protocol == 'https:';
- }
- return false;
-}
-
-export function isLocalHost() {
- if (typeof window.location == 'object') {
- return window.location.hostname == 'localhost';
- }
- return false;
-}
diff --git a/src/lib/im-api.js b/src/lib/im-api.js
deleted file mode 100644
index 2ebd99a6..00000000
--- a/src/lib/im-api.js
+++ /dev/null
@@ -1,867 +0,0 @@
-// Thin REST client for the product-specific `/v1/im/*` API that sits alongside the Tinode WS protocol.
-// Endpoint shapes verified directly against the deployed backend (im_team/im_server, server/im_http.go).
-import { API_KEY } from '../config.js';
-import { PACKAGE_VERSION } from '../version.js';
-import LocalStorageUtil from './local-storage.js';
-
-const DEFAULT_API_BASE = '/v1/im';
-const DEFAULT_FILE_BASE = '/v0/file';
-
-export class ImApiError extends Error {
- constructor(message, reason, status, details) {
- super(message);
- this.reason = reason;
- this.status = status;
- this.details = details;
- }
-}
-
-// docs/IM-前后端对接文档.md §12.4's full reason list. Every existing call site already does
-// `catch(err => onToast(err.message || '...'))`, so translating known reasons at the one place the
-// error is constructed fixes all of them at once, rather than needing a matching change at every
-// individual catch block scattered across the app.
-const REASON_MESSAGES = {
- // Real per-account risk is low (this app always sends a hardcoded key, so hitting these means a
- // deployment-wide config problem, not something an individual user did) -- but exactly this "the
- // whole site is silently misconfigured" scenario has genuinely happened once already (a stale API
- // key got redeployed to production), so it's worth a real message instead of raw English site-wide.
- API_KEY_REQUIRED: '服务暂不可用,请刷新页面重试',
- METHOD_NOT_ALLOWED: '请求方式有误,请刷新页面重试',
- TENANT_UNAVAILABLE: '企业码不存在或已停用',
- TENANT_MISMATCH: '登录状态异常,请重新进入企业码',
- AUTH_FAILED: '账号或密码错误',
- TOKEN_EXPIRED: '登录已过期,请重新登录',
- ACCOUNT_DISABLED: '账号已被禁用',
- RATE_LIMITED: '操作过于频繁,请稍后再试',
- VALIDATION_FAILED: '提交的内容不符合要求',
- IDEMPOTENCY_CONFLICT: '内容已发生变化,请刷新后重试',
- RESOURCE_NOT_FOUND: '内容不存在或已被删除',
- // 后端 server/im_http.go 里 30+ 处 403 用的都是 imReasonPermissionDenied="PERMISSION_DENIED",
- // 从未发送过字面量 "FORBIDDEN"——原来的 FORBIDDEN 条目是死代码,全站 403 一直透传英文原文。
- PERMISSION_DENIED: '没有权限执行此操作',
- NOT_FOUND: '内容不存在或已被删除',
- VERSION_CONFLICT: '内容已被修改,请刷新后重试',
- MALFORMED_REQUEST: '请求内容有误,请稍后重试',
- AUTH_REQUIRED: '请先登录',
- ACTION_TOKEN_INVALID: '操作凭证已失效,请重新操作',
- SCHEMA_UNSUPPORTED: '当前版本过旧,请刷新页面后重试',
- IDEMPOTENCY_IN_PROGRESS: '上一次操作正在处理中,请稍后再试',
- INTERNAL_ERROR: '服务器繁忙,请稍后重试',
- UNSUPPORTED_MEDIA_TYPE: '不支持的文件类型',
- CHALLENGE_EXPIRED: '验证码已过期,请重新获取',
- SMS_DELIVERY_FAILED: '短信发送失败,请稍后重试',
- PASSWORD_POLICY_VIOLATION: '密码不符合安全要求',
- VERIFICATION_RATE_LIMITED: details => details && details.retry_after_seconds
- ? `操作过于频繁,请 ${details.retry_after_seconds} 秒后重试`
- : '操作过于频繁,请稍后再试',
- ALREADY_GROUP_MEMBER: '对方已经是群成员',
- DESCRIPTION_REQUIRED: '请填写简介内容',
- DESCRIPTION_TOO_LONG: '简介内容过长',
- GROUP_ADMIN_LIMIT_EXCEEDED: '群管理员数量已达上限',
- INVALID_CREDIT_CODE: '统一社会信用代码格式不正确',
- INVALID_EMAIL: '邮箱格式不正确',
- MAILBOX_UNAVAILABLE: '总经理信箱暂不可用',
- TOO_MANY_FILES: '文件数量超出限制',
- FRIEND_RELATION_REQUIRED: '需要先添加对方为好友',
- MESSAGE_NOT_ALLOWED: '当前状态无法发送消息',
- GROUP_MUTED: '你已被禁言,无法发言',
- GROUP_MEMBER_LIMIT_EXCEEDED: '群成员数量已达上限',
- TENANT_USER_QUOTA_EXCEEDED: '企业账号数已达上限',
- TENANT_GROUP_QUOTA_EXCEEDED: '企业群组数已达上限',
- FILE_TOO_LARGE: '文件大小超出限制',
- FILE_FORBIDDEN: '没有权限访问此文件',
- FILE_UNAVAILABLE: '文件暂不可用',
- STORAGE_QUOTA_EXCEEDED: '存储空间已达上限',
- REALNAME_REQUIRED: '需要先完成实名认证',
- YOUTH_MODE_RESTRICTED: '青少年模式下无法使用此功能',
- RISK_CONFIRMATION_REQUIRED: '需要先完成安全确认',
- RISK_ACTION_BLOCKED: '该操作已被安全策略拦截',
- // CODE_INVALID is shared by two unrelated verify endpoints -- independent-password PIN check
- // (青少年模式关闭/开启走的这条接口,对接文档 809 行) *and* the plain SMS verification-code
- // verify endpoints (/auth/verification-codes/{id}/verify, /security/challenges/{id}/verify --
- // confirmed live: entering a wrong SMS code on the "忘记独立密码?" identity-check screen also
- // gets back reason=CODE_INVALID). This table only keys off the reason string with no per-call-site
- // override, so a message written for just one of the two ("独立密码不正确") showed up verbatim on
- // the *other* endpoint's screen, where there's no "独立密码" concept in view at all -- confusing,
- // wrong-sounding copy for a real, correctly-rejected wrong code. Kept generic enough to be accurate
- // for both rather than wrong in either.
- CODE_INVALID: '验证码或独立密码不正确',
- TOO_MANY_ATTEMPTS: details => details && details.retry_after_seconds
- ? `连续输错次数过多,请 ${details.retry_after_seconds} 秒后重试`
- : '连续输错次数过多,请稍后重试'
-};
-
-function requestId() {
- return (typeof crypto != 'undefined' && crypto.randomUUID) ? crypto.randomUUID() :
- 'web_' + Date.now().toString(36) + '_' + Math.random().toString(36).slice(2);
-}
-
-// Tenant context persists in localStorage (not sessionStorage): it must survive browser restarts
-// the same way the auth-token does, so a returning user's auto-login can still supply `hi.tenant`.
-export function getTenantContext() {
- return LocalStorageUtil.getObject('im-tenant');
-}
-
-export function currentTenantCode() {
- const tenant = getTenantContext();
- return (tenant && tenant.code) || 'default';
-}
-
-function apiBase() {
- const tenant = getTenantContext();
- return ((tenant && tenant.endpoints && tenant.endpoints.api) || DEFAULT_API_BASE).replace(/\/$/, '');
-}
-
-function fileBase() {
- const tenant = getTenantContext();
- return ((tenant && tenant.endpoints && tenant.endpoints.file) || DEFAULT_FILE_BASE).replace(/\/$/, '');
-}
-
-// Historical clients persisted absolute API-host file references in Drafty
-// messages. Resolve those references through the tenant's configured file
-// endpoint so the web client keeps the authenticated request on its same-origin
-// proxy and does not forward custom auth headers into an OSS redirect preflight.
-export function normalizeFileReferenceAgainst(ref, apiEndpoint, fileEndpoint, pageOrigin) {
- if (!ref || typeof ref !== 'string') {
- return ref;
- }
- try {
- const source = new URL(ref, pageOrigin);
- const api = new URL(apiEndpoint, pageOrigin);
- if (source.origin !== api.origin || !source.pathname.startsWith('/v0/file/')) {
- return ref;
- }
- const target = new URL(fileEndpoint, pageOrigin);
- target.pathname = target.pathname.replace(/\/$/, '') + source.pathname.slice('/v0/file'.length);
- target.search = source.search;
- target.hash = source.hash;
- return target.origin === pageOrigin ? target.pathname + target.search + target.hash : target.toString();
- } catch (e) {
- return ref;
- }
-}
-
-export function normalizeFileReference(ref) {
- return normalizeFileReferenceAgainst(ref, apiBase(), fileBase(), window.location.origin);
-}
-
-// tinode-web.jsx always sets this on login/reconnect (sessionStorage, tab-lifetime), independent of
-// whether the user opted into "keep logged in" (which only controls the separate localStorage token
-// used for surviving a browser restart). REST calls need a token for the current session regardless.
-function authToken() {
- return sessionStorage.getItem('im-session-token') || '';
-}
-
-function normalizeResponse(payload) {
- return (payload && payload.code === 0 && payload.data !== undefined) ? payload.data : payload;
-}
-
-// Core request helper. `auth: false` skips the Authorization header (pre-login calls).
-export async function imRequest(path, {method = 'GET', body, headers, auth = true, tenantCodeOverride} = {}) {
- const finalHeaders = {
- 'Content-Type': 'application/json',
- 'X-MeetingIM-APIKey': API_KEY,
- 'X-IM-Schema': '1',
- 'X-Tenant-Code': tenantCodeOverride || currentTenantCode(),
- 'X-Request-ID': requestId(),
- ...headers
- };
- if (auth) {
- const token = authToken();
- if (token) {
- finalHeaders.Authorization = 'Bearer ' + token;
- }
- }
- const response = await fetch(apiBase() + path, {
- method,
- headers: finalHeaders,
- body: body !== undefined ? JSON.stringify(body) : undefined
- });
- const text = await response.text();
- let payload = {};
- if (text) {
- try {
- payload = JSON.parse(text);
- } catch (e) {
- throw new ImApiError(response.ok ? '接口返回非 JSON 数据' : (response.statusText || '接口请求失败'),
- 'NON_JSON_RESPONSE', response.status, text.slice(0, 160));
- }
- }
- if (!response.ok || (payload && payload.code && payload.code !== 0)) {
- // A known reason always wins over the raw backend message -- gives every caller (they all just do
- // `err.message || '...'`) the doc-mandated, consistently-worded text instead of whatever string
- // that particular backend handler happened to write, and instead of duplicating this mapping at
- // every individual call site. An unrecognized reason still falls back to the backend's own message
- // (not silently replaced with a generic string) since this backend's messages have consistently
- // been reasonable, specific, business-appropriate text in real testing, not raw internals -- only
- // reasons this app doesn't otherwise know how to react to fall through to it.
- const known = translateReason(payload.reason, payload.details);
- throw new ImApiError(known || payload.message || payload.reason || response.statusText || '请求失败',
- payload.reason, response.status, payload.details);
- }
- return normalizeResponse(payload);
-}
-
-// Several batch endpoints (favorites/batch, favorites/forward, messages/forward, ...) always return
-// HTTP 200 and bury the real per-item outcome in a nested `items[]`/`failed[]` array instead of
-// throwing -- imRequest's translation above never runs for those, since it only fires on a non-2xx/
-// non-zero-code response. Exported so call sites reading one of those per-item `reason` strings
-// directly (e.g. send-message.jsx's sendFavorite) can reuse the exact same table instead of either
-// duplicating it or -- as was actually happening -- showing the raw untranslated reason string.
-export function translateReason(reason, details) {
- const entry = reason && REASON_MESSAGES[reason];
- return (typeof entry === 'function' ? entry(details) : entry) || null;
-}
-
-const idem = () => ({'Idempotency-Key': requestId()});
-
-// Most list endpoints here cap out at 20-50 items per page (some as low as 30, e.g. group
-// members against a 128-member cap) and round7 testing found several call sites only ever
-// fetched the first page -- for things like "am I this group's owner/admin" or the admin
-// candidate list, a real owner/admin sitting past page 1 went undetected entirely rather than
-// just showing an incomplete list. Empty/repeated next_cursor means done; this app's list
-// responses haven't been observed to send a separate has_more that disagrees with that.
-export async function fetchAllPages(fetchPage, startCursor = '') {
- let cursor = startCursor;
- let items = [];
- for (;;) {
- const page = await fetchPage(cursor);
- items = items.concat((page && page.items) || []);
- const nextCursor = (page && page.next_cursor) || '';
- if (!nextCursor || nextCursor === cursor) {
- break;
- }
- cursor = nextCursor;
- }
- return items;
-}
-
-export const imGet = (path, opts) => imRequest(path, opts);
-export const imPost = (path, body, opts = {}) => imRequest(path, {method: 'POST', body, headers: idem(), ...opts});
-export const imPatch = (path, body, opts = {}) => imRequest(path, {method: 'PATCH', body, headers: idem(), ...opts});
-export const imPut = (path, body, opts = {}) => imRequest(path, {method: 'PUT', body, headers: idem(), ...opts});
-export const imDelete = (path, opts = {}) => imRequest(path, {method: 'DELETE', headers: idem(), ...opts});
-
-function qs(params) {
- const usp = new URLSearchParams();
- Object.entries(params || {}).forEach(([k, v]) => {
- if (v !== undefined && v !== null && v !== '') {
- usp.set(k, v);
- }
- });
- const s = usp.toString();
- return s ? '?' + s : '';
-}
-
-// -- Public uploads (multipart; used before login and for standalone attachments not tied to a chat message). --
-export async function uploadPublicFile(file, purpose, onProgress) {
- const form = new FormData();
- form.append('purpose', purpose);
- form.append('file', file);
- // Use XHR instead of fetch to get upload progress events.
- return new Promise((resolve, reject) => {
- const xhr = new XMLHttpRequest();
- xhr.open('POST', apiBase() + '/public/uploads');
- xhr.setRequestHeader('X-MeetingIM-APIKey', API_KEY);
- xhr.setRequestHeader('X-IM-Schema', '1');
- xhr.setRequestHeader('X-Tenant-Code', currentTenantCode());
- xhr.setRequestHeader('X-Request-ID', requestId());
- if (onProgress) {
- xhr.upload.onprogress = e => onProgress(e.lengthComputable ? e.loaded / e.total : 0);
- }
- xhr.onload = () => {
- let payload = {};
- try { payload = JSON.parse(xhr.responseText); } catch (e) { /* handled below */ }
- if (xhr.status >= 200 && xhr.status < 300 && payload.code === 0) {
- resolve(normalizeResponse(payload));
- } else {
- reject(new ImApiError(payload.message || payload.reason || '上传失败', payload.reason, xhr.status, payload.details));
- }
- };
- xhr.onerror = () => reject(new ImApiError('网络错误,上传失败', 'NETWORK_ERROR', 0));
- xhr.send(form);
- });
-}
-
-// Native large-file upload (Ch.6 group avatars & group files). uploadPublicFile() above can't be reused --
-// its `purpose` is server-whitelisted to exactly 'enterprise_license'/'tenant_entry_feedback'. The vendored
-// SDK's own tinode.getLargeFileHelper().upload() talks to the same underlying native endpoint but gets two
-// headers wrong for this backend (verified live against the real server, not just read from source):
-// it sends 'X-Tinode-APIKey' (server only accepts 'X-MeetingIM-APIKey', same rename Phase 0 already applied
-// everywhere else in this file) and 'X-Tinode-Auth' (server's getHttpAuth() doesn't recognize that header
-// name at all, only 'X-MeetingIM-Auth' or plain 'Authorization'). The auth *value* must be the native Tinode
-// session token (tinode.getAuthToken().token, scheme 'Token') -- the REST layer's own sessionStorage Bearer
-// token is a separate namespace and fails this endpoint's base64 secret decode with a 400. Response is
-// Tinode's native {ctrl:{code,text,params:{url,expires}}} envelope, not the {code:0,data} REST envelope --
-// there's no separate file_id field; the id is the last path segment of `url`, kept verbatim (including its
-// extension suffix) since getFileUrl() already round-trips that exact format unmodified.
-// This upload talks to Tinode's native {ctrl} protocol (see the comment above), not the REST layer,
-// so REASON_MESSAGES above never applies to it -- confirmed against server/datamodel.go's Err*
-// constructors for the literal ctrl.text values a failure here can actually carry.
-const UPLOAD_CTRL_TEXT_CN = {
- 'too large': '文件大小超出限制',
- 'malformed': '请求内容有误,请稍后重试',
- 'permission denied': '没有权限执行此操作',
- 'gone': '内容不存在或已被删除',
- 'not found': '内容不存在或已被删除'
-};
-export function uploadLargeFile(tinode, file, onProgress) {
- const authToken = tinode && tinode.getAuthToken && tinode.getAuthToken();
- if (!authToken) {
- return Promise.reject(new ImApiError('尚未登录,无法上传文件', 'AUTH_REQUIRED', 401));
- }
- const form = new FormData();
- form.append('file', file);
- form.set('id', tinode.getNextUniqueId());
- return new Promise((resolve, reject) => {
- const xhr = new XMLHttpRequest();
- xhr.open('POST', fileBase() + '/u/');
- xhr.setRequestHeader('X-MeetingIM-APIKey', API_KEY);
- xhr.setRequestHeader('Authorization', 'Token ' + authToken.token);
- if (onProgress) {
- xhr.upload.onprogress = e => onProgress(e.lengthComputable ? e.loaded / e.total : 0);
- }
- xhr.onload = () => {
- let payload = {};
- try { payload = JSON.parse(xhr.responseText); } catch (e) { /* handled below */ }
- const ctrl = payload.ctrl || {};
- if (xhr.status >= 200 && xhr.status < 300 && ctrl.params && ctrl.params.url) {
- const url = ctrl.params.url;
- resolve({fileId: url.slice(url.lastIndexOf('/') + 1), url, expires: ctrl.params.expires});
- } else {
- const known = ctrl.text && UPLOAD_CTRL_TEXT_CN[ctrl.text.trim().toLowerCase()];
- reject(new ImApiError(known || ctrl.text || '上传失败', 'UPLOAD_FAILED', xhr.status));
- }
- };
- xhr.onerror = () => reject(new ImApiError('网络错误,上传失败', 'NETWORK_ERROR', 0));
- xhr.send(form);
- });
-}
-
-// Fixed-headers drop-in replacement for tinode.getLargeFileHelper(), for sending chat attachments
-// (messages-view.jsx). The vendored SDK's LargeFileHelper hardcodes the same two wrong headers
-// uploadLargeFile() above works around ('X-Tinode-APIKey' instead of 'X-MeetingIM-APIKey', and
-// 'X-Tinode-Auth' instead of 'Authorization') -- confirmed live: every image/video/file/voice message
-// too large to send inband was silently failing to upload. Can't just call uploadLargeFile() at each
-// call site instead, because messages-view.jsx also depends on LargeFileHelper's cancel-in-flight-send
-// support (handleCancelUpload) and its urlPromise contract (Drafty.attachFile/insertImage/insertVideo/
-// insertAudio expect the resolved value to be a bare URL string, or for video a [url, prevUrl] pair --
-// not the {fileId,url,expires} object uploadLargeFile() returns). This mirrors just that subset of the
-// real class's interface (upload() returning a bare-URL promise, onProgress, and cancel() aborting any
-// still-in-flight XHRs via the same `this.xhr` array pattern the real LargeFileHelper.cancel() uses).
-export function createChatUploader(tinode) {
- const xhrs = [];
- return {
- onProgress: null,
- upload(file) {
- const self = this;
- const authToken = tinode && tinode.getAuthToken && tinode.getAuthToken();
- if (!authToken) {
- return Promise.reject(new ImApiError('尚未登录,无法上传文件', 'AUTH_REQUIRED', 401));
- }
- return new Promise((resolve, reject) => {
- const form = new FormData();
- form.append('file', file);
- form.set('id', tinode.getNextUniqueId());
- const xhr = new XMLHttpRequest();
- xhrs.push(xhr);
- xhr.open('POST', fileBase() + '/u/');
- xhr.setRequestHeader('X-MeetingIM-APIKey', API_KEY);
- xhr.setRequestHeader('Authorization', 'Token ' + authToken.token);
- xhr.upload.onprogress = e => {
- if (e.lengthComputable && self.onProgress) {
- self.onProgress(e.loaded / e.total);
- }
- };
- xhr.onload = () => {
- let payload = {};
- try { payload = JSON.parse(xhr.responseText); } catch (e) { /* handled below */ }
- const ctrl = payload.ctrl || {};
- if (xhr.status >= 200 && xhr.status < 300 && ctrl.params && ctrl.params.url) {
- resolve(ctrl.params.url);
- } else {
- // Same native-{ctrl}-protocol translation as uploadLargeFile above -- REASON_MESSAGES
- // never applies here, and a too-large photo/video/file picked from the attachment panel
- // is a realistic, easy way for a real user to hit this.
- const known = ctrl.text && UPLOAD_CTRL_TEXT_CN[ctrl.text.trim().toLowerCase()];
- reject(new ImApiError(known || ctrl.text || '上传失败', 'UPLOAD_FAILED', xhr.status));
- }
- };
- xhr.onerror = () => reject(new ImApiError('网络错误,上传失败', 'NETWORK_ERROR', 0));
- xhr.send(form);
- });
- },
- cancel() {
- xhrs.forEach(req => { if (req.readyState < 4) { req.abort(); } });
- }
- };
-}
-
-// Fixed-headers drop-in replacement for tinode.getLargeFileHelper(), for downloading a chat/favorite
-// out-of-band attachment (content-library-view.jsx's DownloadPage). Same wrong-header bug as
-// createChatUploader() above -- the vendored SDK's LargeFileHelper.download() also hardcodes
-// 'X-Tinode-APIKey'/'X-Tinode-Auth', so every real (non-blob:/data:) download was silently failing.
-// Mirrors the real class's download()/cancel() interface exactly (including the onProgress/onError
-// callback-style signature DownloadPage already calls it with) so the call site needs no other change.
-export function createFileDownloader(tinode) {
- const xhrs = [];
- return {
- download(relativeUrl, filename, mimetype, onProgress, onError) {
- const authToken = tinode && tinode.getAuthToken && tinode.getAuthToken();
- if (!authToken) {
- const err = new ImApiError('尚未登录,无法下载文件', 'AUTH_REQUIRED', 401);
- if (onError) { onError(err); }
- return Promise.reject(err);
- }
- return new Promise((resolve, reject) => {
- const xhr = new XMLHttpRequest();
- xhrs.push(xhr);
- xhr.open('GET', relativeUrl, true);
- xhr.setRequestHeader('X-MeetingIM-APIKey', API_KEY);
- xhr.setRequestHeader('Authorization', 'Token ' + authToken.token);
- xhr.responseType = 'blob';
- xhr.onprogress = e => { if (onProgress) { onProgress(e.loaded); } };
- xhr.onload = () => {
- if (xhr.status === 200) {
- const link = document.createElement('a');
- link.href = window.URL.createObjectURL(new Blob([xhr.response], {type: mimetype}));
- link.style.display = 'none';
- link.setAttribute('download', filename);
- document.body.appendChild(link);
- link.click();
- document.body.removeChild(link);
- window.URL.revokeObjectURL(link.href);
- resolve();
- } else {
- const reader = new FileReader();
- reader.onload = function() {
- let err;
- try {
- const pkt = JSON.parse(this.result);
- const text = pkt.ctrl && pkt.ctrl.text;
- const known = text && UPLOAD_CTRL_TEXT_CN[text.trim().toLowerCase()];
- err = new ImApiError(known || text || '下载失败', 'DOWNLOAD_FAILED', xhr.status);
- } catch (e) {
- err = new ImApiError('下载失败', 'DOWNLOAD_FAILED', xhr.status);
- }
- if (onError) { onError(err); }
- reject(err);
- };
- reader.readAsText(xhr.response);
- }
- };
- xhr.onerror = () => {
- const err = new ImApiError('网络错误,下载失败', 'NETWORK_ERROR', 0);
- if (onError) { onError(err); }
- reject(err);
- };
- xhr.send();
- });
- },
- cancel() {
- xhrs.forEach(req => { if (req.readyState < 4) { req.abort(); } });
- }
- };
-}
-
-// -- 1.1 Tenant resolution & entry (pre-login, no Authorization header). --
-export function resolveTenant(tenantCode) {
- return imRequest('/tenants/resolve', {
- method: 'POST', auth: false, tenantCodeOverride: tenantCode,
- body: {tenant_code: tenantCode, platform: 'web', app_version: PACKAGE_VERSION || '0.25.3'}
- });
-}
-
-// 企业注册申请 / 入口反馈 / 意见反馈三个接口后端都定义了 client_context 且会入库
-// (imTenantApplicationRequest / imTenantEntryFeedbackRequest / imFeedbackRequest,
-// server/im_http.go:255/263/367,落库点 :790/:840/:1478),需求文档和对接文档也都要求带上
-// 设备与版本信息。前端一直没发。在这里统一注入,省得三处各写一遍、也不会再漏。
-function clientContext() {
- return {
- platform: 'web',
- app_version: PACKAGE_VERSION || '',
- user_agent: (typeof navigator !== 'undefined' && navigator.userAgent) || '',
- language: (typeof navigator !== 'undefined' && navigator.language) || '',
- // 网络类型不是所有浏览器都有(Safari/Firefox 没有 navigator.connection),拿不到就不发这一项
- network: (typeof navigator !== 'undefined' && navigator.connection && navigator.connection.effectiveType) || undefined
- };
-}
-
-export function submitTenantApplication(body) {
- return imRequest('/tenant-applications', {method: 'POST', auth: false, headers: idem(),
- body: {client_context: clientContext(), ...body}});
-}
-
-export function submitTenantEntryFeedback(body) {
- return imRequest('/tenant-entry-feedback', {method: 'POST', auth: false, headers: idem(),
- body: {client_context: clientContext(), ...body}});
-}
-
-// -- 1.4/1.5/1.6 Phone verification codes (register / reset_password / security scenes). --
-export function requestVerificationCode(scene, phone) {
- return imRequest('/auth/verification-codes', {method: 'POST', auth: false, headers: idem(), body: {scene, phone}});
-}
-
-export function verifyRegisterCode(challengeId, code) {
- return imRequest('/auth/verification-codes/' + encodeURIComponent(challengeId) + '/verify',
- {method: 'POST', auth: false, body: {code}});
-}
-
-export function verifyPasswordResetCode(challengeId, code) {
- return imRequest('/auth/password-reset/challenges/' + encodeURIComponent(challengeId) + '/verify',
- {method: 'POST', auth: false, body: {code}});
-}
-
-export function confirmPasswordReset(resetToken, newPassword) {
- return imRequest('/auth/password-reset/confirm',
- {method: 'POST', auth: false, headers: idem(), body: {reset_token: resetToken, new_password: newPassword}});
-}
-
-// -- Bootstrap / home. --
-export function getBootstrap() {
- return imRequest('/bootstrap');
-}
-
-export function getMe() {
- return imRequest('/me');
-}
-
-// -- Realname (1.8/1.9). --
-export function getRealnameStatus() {
- return imRequest('/security/realname');
-}
-
-export function submitRealname(body) {
- return imPost('/security/realname/submissions', body);
-}
-
-// -- Security challenge (1.7) & independent password (1.11). --
-export function createSecurityChallenge(scene) {
- return imPost('/security/challenges', {scene, channel: 'sms'});
-}
-
-export function verifySecurityChallenge(challengeId, code) {
- return imPost('/security/challenges/' + encodeURIComponent(challengeId) + '/verify', {code});
-}
-
-export function setIndependentPassword(password, purpose, actionToken) {
- return imPost('/settings/independent-password', {password, purpose, action_token: actionToken});
-}
-
-// `action` defaults server-side to 'close_youth_mode' if omitted (server/im_http.go
-// imIndependentPasswordVerifyRequest) -- pass 'set_independent_password' to mint a token usable
-// with enableYouthMode instead, letting an already-set password unlock re-enabling without
-// forcing another SMS round-trip through the identity-check flow.
-export function verifyIndependentPassword(password, purpose, action) {
- return imPost('/settings/independent-password/verify', {password, purpose, action});
-}
-
-// -- Friends / contacts (Ch.5). --
-export const getFriends = (query = '', cursor = '', initial = '') => imGet('/friends' + qs({query, cursor, initial}));
-export const getFriendRequests = (direction = 'inbound', status = 'all', cursor = '') =>
- imGet('/friend-requests' + qs({direction, status, cursor}));
-export const createFriendRequest = (targetUserId, message = '', source = 'search', sourceTopic = '') =>
- imPost('/friend-requests', {target_user_id: targetUserId, message, source, source_topic: sourceTopic});
-export const acceptFriendRequest = (requestId_, version) => imPost('/friend-requests/' + encodeURIComponent(requestId_) + '/accept', version ? {version} : {});
-export const rejectFriendRequest = (requestId_, version) => imPost('/friend-requests/' + encodeURIComponent(requestId_) + '/reject', version ? {version} : {});
-export const deleteFriend = userId => imDelete('/friends/' + encodeURIComponent(userId));
-export const updateFriendRemark = (userId, remark) => imPatch('/friends/' + encodeURIComponent(userId), {remark});
-export const searchUsers = (query, type = 'account', cursor = '') => imGet('/users/search' + qs({q: query, type, cursor}));
-export const getUserProfile = identifier => imGet('/users/' + encodeURIComponent(identifier) + '/profile');
-export const createMyQRCode = (expiresInSeconds = 86400) => imPost('/me/qrcodes', {purpose: 'add_friend', expires_in_seconds: expiresInSeconds});
-export const createUserQRCode = (identifier, expiresInSeconds = 86400) =>
- imPost('/users/' + encodeURIComponent(identifier) + '/qrcodes', {purpose: 'add_friend', expires_in_seconds: expiresInSeconds});
-export const resolveQRCode = (token, purpose = 'add_friend') => imPost('/qrcodes/resolve', {token, purpose});
-export const getBlocks = (cursor = '') => imGet('/blocks' + qs({cursor}));
-export const blockUser = userId => imPut('/blocks/' + encodeURIComponent(userId), {});
-export const unblockUser = userId => imDelete('/blocks/' + encodeURIComponent(userId));
-export const batchUnblockUsers = userIds => imPost('/blocks/batch-delete', {user_ids: userIds});
-
-// -- Conversations (Ch.2/3). --
-export const searchConversations = (query = '', cursor = '') => imGet('/search/conversations' + qs({q: query, cursor}));
-export const getSystemNotifications = (cursor = '', limit = 20) => imGet('/system-notifications' + qs({cursor, limit}));
-export const markSystemNotificationRead = id => imPost('/system-notifications/' + encodeURIComponent(id) + '/read', {read_at: new Date().toISOString()});
-export const getConversationContext = topic => imGet('/conversations/' + encodeURIComponent(topic) + '/context');
-export const createP2PConversation = peerUserId => imPost('/conversations/p2p', {peer_user_id: peerUserId});
-export const getConversationSettings = topic => imGet('/conversations/' + encodeURIComponent(topic) + '/settings');
-export const updateConversationSettings = (topic, body) => imPatch('/conversations/' + encodeURIComponent(topic) + '/settings', body);
-
-// -- Messages (Ch.4/7). --
-// version 只能默认传 1:服务端做的是乐观锁校验(tenant_adapter.go:1963 要求 msg.Version 与传入值
-// 完全相等,否则 ErrDuplicate),但**没有任何消息读取接口下发过 version 字段**——messagePayload /
-// messageSearchPayload / mediaMessagePayload / favoriteSnapshot 全都没有,冲突时的错误里也不回传
-// 真实值。所以前端拿不到正确的版本号,被编辑过(version>1)的消息撤回必然失败。这是后端缺口,
-// 已记入《IM-后端问题清单.md》,不在前端绕。
-export const recallMessage = (topic, seq, version = 1, reason = 'mistake') =>
- imPost('/topics/' + encodeURIComponent(topic) + '/messages/' + encodeURIComponent(seq) + '/recall', {version, reason});
-export const reviseMessage = (topic, seq, content, version = 1) =>
- imPost('/topics/' + encodeURIComponent(topic) + '/messages/' + encodeURIComponent(seq) + '/revisions', {content, client_mid: requestId(), version});
-export const getMessageReceipts = (topic, seq, cursor = '') =>
- imGet('/topics/' + encodeURIComponent(topic) + '/messages/' + encodeURIComponent(seq) + '/receipts' + qs({cursor}));
-// type='all' searches every message type (text/image/video/voice/file/link) -- the backend's own
-// default when the param is omitted/empty is "no filter", so defaulting to 'text' here would have
-// silently restricted every unqualified call to text-only messages.
-export const searchTopicMessages = (topic, {query = '', type = 'all', cursor = ''} = {}) =>
- imGet('/topics/' + encodeURIComponent(topic) + '/messages/search' + qs({q: query, type, cursor}));
-export const getMessageContext = (topic, seq, before = 20, after = 20) =>
- imGet('/topics/' + encodeURIComponent(topic) + '/messages/' + encodeURIComponent(seq) + '/context' + qs({before, after}));
-export const getTopicMedia = (topic, type = 'image', cursor = '') =>
- imGet('/topics/' + encodeURIComponent(topic) + '/media' + qs({type, cursor}));
-export const getForwardTargets = (query = '', cursor = '') => imGet('/forward-targets' + qs({query, cursor}));
-export const forwardMessages = (mode, sources, targetTopics, comment = '') =>
- imPost('/messages/forward', {mode, sources, target_topics: targetTopics, comment});
-export const getForwardBundle = (bundleId, cursor = '') => imGet('/forward-bundles/' + encodeURIComponent(bundleId) + qs({cursor}));
-export const favoriteMessages = messages => imPost('/favorites/batch', {messages: messages.map(m => ({...m, seq: String(m.seq)}))});
-export const getFavorites = ({type = 'all', query = '', cursor = '', limit} = {}) => imGet('/favorites' + qs({type, q: query, cursor, limit}));
-export const getFavorite = favoriteId => imGet('/favorites/' + encodeURIComponent(favoriteId));
-export const deleteFavorite = favoriteId => imDelete('/favorites/' + encodeURIComponent(favoriteId));
-export const batchDeleteFavorites = favoriteIds => imPost('/favorites/batch-delete', {favorite_ids: favoriteIds});
-export const forwardFavorites = (favoriteIds, targetTopics, comment = '') =>
- imPost('/favorites/forward', {favorite_ids: favoriteIds, target_topics: targetTopics, comment});
-export const checkURL = (url, source = 'favorite') => imPost('/security/urls/check', {url, source});
-export const confirmRiskChallenge = (riskChallengeId, decision = 'continue') =>
- imPost('/security/risk-challenges/' + encodeURIComponent(riskChallengeId) + '/confirm', {decision});
-export const getMentionableGroupMembers = (topic, query = '', cursor = '') =>
- imGet('/groups/' + encodeURIComponent(topic) + '/members' + qs({mentionable: true, query, cursor}));
-// 4.1 跨话题草稿:GET 对不存在的草稿返回 {content:'',entities:[],version:0} 而不是 404,PUT 用
-// version 做乐观锁(对接文档描述的样子)。真实验证过用原生话题名(P2P 是对方 user_id,不是
-// REST 层内部的 p2pXXX id)可以正常读写——这个接口内部会先查 Subs.Get 校验会话存在/有权限,
-// 跟 favorites 那条从不做话题归一化、直接 NOT_FOUND 的坏路径不是同一个问题。
-export const getTopicDraft = topic => imGet('/topics/' + encodeURIComponent(topic) + '/draft');
-export const updateTopicDraft = (topic, content, entities, version) =>
- imPut('/topics/' + encodeURIComponent(topic) + '/draft', {content, entities, version});
-
-export async function fetchFileState(fileId, range = '') {
- const headers = {
- 'X-MeetingIM-APIKey': API_KEY, 'X-IM-Schema': '1',
- 'X-Tenant-Code': currentTenantCode(), 'X-Request-ID': requestId()
- };
- const token = authToken();
- // GET /v0/file/s/{id} is the native Tinode file-serving endpoint, not the /v1/im REST API --
- // it's auth-gated by the vendored SDK's auth-handler registry, which is keyed by the literal
- // scheme word in the header and only recognizes 'token' (used by uploadLargeFile above), never
- // 'bearer'. Sending 'Bearer' here (as imRequest correctly does for actual /v1/im/* calls) gets a
- // flat 400 "malformed" from every single call -- confirmed live against the real backend: same
- // token value, only the scheme word differs. authToken() itself already holds the right value
- // (tinode-web.jsx's handleLoginSuccessful stores tinode.getAuthToken().token verbatim into
- // sessionStorage['im-session-token']), so no plumbing change is needed beyond the scheme word.
- if (token) {
- headers.Authorization = 'Token ' + token;
- }
- if (range) {
- headers.Range = range;
- }
- const response = await fetch(fileBase() + '/s/' + encodeURIComponent(fileId), {headers});
- return {
- ok: response.ok, status: response.status,
- contentType: response.headers.get('content-type') || '',
- contentLength: response.headers.get('content-length') || '',
- acceptRanges: response.headers.get('accept-ranges') || '',
- etag: response.headers.get('etag') || ''
- };
-}
-
-export function publicFileBase() {
- return fileBase();
-}
-
-// Just the URL string -- fileBase() (the tenant's real, possibly cross-origin file domain, e.g.
-// "https://api.118im.com/v0/file") + the server-relative path a Drafty ref/upload response carries.
-// No fetch. Split out from getFileUrl() below for DownloadPage (content-library-view.jsx): that
-// caller needs a correctly-qualified URL to *display and hand to the download button*, not to
-// eagerly pull the whole file into a blob the moment the page opens -- getFileUrl()'s eager fetch,
-// used unmodified there, silently downloaded every out-of-band attachment in full before the user
-// ever pressed "download", defeating the ready/downloading/done UI entirely.
-export function resolveFileUrl(fileId) {
- return fileId ? fileBase() + '/s/' + encodeURIComponent(fileId) : null;
-}
-
-// File serving (GET /v0/file/s/{id}) is auth-gated -- the upload endpoints never return a directly
-// servable URL (publicUploadPayload only has file_id/purpose/mime_type/size/expires_at), so a plain
-// can't be pointed at it (browsers don't attach custom headers to img/video/audio requests).
-// This fetches the bytes with the right headers once per file_id and hands back a blob: object URL,
-// cached so avatars/attachments repeated across a list (friends, search results, ...) aren't refetched.
-const fileUrlCache = new Map();
-export function getFileUrl(fileId) {
- if (!fileId) {
- return Promise.resolve(null);
- }
- if (fileUrlCache.has(fileId)) {
- return fileUrlCache.get(fileId);
- }
- const headers = {
- 'X-MeetingIM-APIKey': API_KEY, 'X-IM-Schema': '1',
- 'X-Tenant-Code': currentTenantCode(), 'X-Request-ID': requestId()
- };
- const token = authToken();
- // Same native-endpoint scheme requirement as fetchFileState() above -- 'Token', not 'Bearer'.
- if (token) {
- headers.Authorization = 'Token ' + token;
- }
- const promise = fetch(resolveFileUrl(fileId), {headers}).then(res => {
- if (!res.ok) {
- throw new ImApiError('文件加载失败', 'FILE_FETCH_FAILED', res.status);
- }
- return res.blob();
- }).then(blob => URL.createObjectURL(blob)).catch(err => {
- fileUrlCache.delete(fileId);
- throw err;
- });
- fileUrlCache.set(fileId, promise);
- return promise;
-}
-
-// -- Groups (Ch.6). --
-export const getGroups = ({category = 'joined', query = '', cursor = ''} = {}) => imGet('/groups' + qs({category, query, cursor}));
-export const createGroup = ({name, description = '', memberUserIds = [], avatarFileId = ''}) =>
- imPost('/groups', {name, description, member_user_ids: memberUserIds, avatar_file_id: avatarFileId});
-export const getGroup = topic => imGet('/groups/' + encodeURIComponent(topic));
-export const updateGroup = (topic, body) => imPatch('/groups/' + encodeURIComponent(topic), body);
-export const getGroupSettings = topic => imGet('/groups/' + encodeURIComponent(topic) + '/settings');
-export const updateGroupMemberSettings = (topic, {pinned, mute_notifications}) =>
- imPatch('/groups/' + encodeURIComponent(topic) + '/member-settings', {pinned, mute_notifications});
-export const updateMyGroupProfile = (topic, nickname, version) =>
- imPut('/groups/' + encodeURIComponent(topic) + '/members/me/profile', {nickname, version});
-// GET .../members/me/profile is the only endpoint that actually returns the saved nickname --
-// confirmed live that GET .../members (getGroupMembers below) never includes group_nickname on any
-// item, so anything reading the nickname from that list (as this file's own callers used to) will
-// always see it as unset even right after a successful save.
-export const getMyGroupProfile = topic => imGet('/groups/' + encodeURIComponent(topic) + '/members/me/profile');
-export const leaveGroup = topic => imPost('/groups/' + encodeURIComponent(topic) + '/leave', {});
-export const getGroupMembers = (topic, {query = '', cursor = '', role = '', eligibleRole = '', mentionable, muteEligible} = {}) =>
- imGet('/groups/' + encodeURIComponent(topic) + '/members' +
- qs({query, cursor, role, eligible_role: eligibleRole, mentionable: mentionable ? 'true' : '', mute_eligible: muteEligible ? 'true' : ''}));
-export const createGroupQRCode = (topic, expiresInSeconds = 86400) =>
- imPost('/groups/' + encodeURIComponent(topic) + '/qrcodes', {expires_in_seconds: expiresInSeconds});
-export const inviteGroupMembers = (topic, userIds, message = '') =>
- imPost('/groups/' + encodeURIComponent(topic) + '/invitations', {user_ids: userIds, message});
-export const acceptGroupInvitation = invitationId => imPost('/group-invitations/' + encodeURIComponent(invitationId) + '/accept', {});
-export const createGroupJoinRequest = (topic, {message = '', source = 'qr', qrToken = ''} = {}) =>
- imPost('/groups/' + encodeURIComponent(topic) + '/join-requests', {message, source, qr_token: qrToken});
-export const getGroupJoinRequests = (topic, status = 'pending', cursor = '') =>
- imGet('/groups/' + encodeURIComponent(topic) + '/join-requests' + qs({status, cursor}));
-export const acceptGroupJoinRequest = (topic, requestId_, version) =>
- imPost('/groups/' + encodeURIComponent(topic) + '/join-requests/' + encodeURIComponent(requestId_) + '/accept', version ? {version} : {});
-// Server struct (imGroupJoinRequestActionRequest) only has `Version` -- no reason field exists, and the
-// business layer hardcodes an empty reject-reason even internally, so there's nothing to send here. Every
-// JSON body goes through decodeIMJSONBody's DisallowUnknownFields(), so including an extra `reason` key
-// (as an earlier draft of this function did) makes every single call 400 regardless of its value.
-export const rejectGroupJoinRequest = (topic, requestId_, version) =>
- imPost('/groups/' + encodeURIComponent(topic) + '/join-requests/' + encodeURIComponent(requestId_) + '/reject', version ? {version} : {});
-export const getGroupSummary = topic => imGet('/groups/' + encodeURIComponent(topic) + '/summary');
-export const getLatestGroupAnnouncement = topic => imGet('/groups/' + encodeURIComponent(topic) + '/announcements/latest');
-export const updateCurrentGroupAnnouncement = (topic, content, version) =>
- imPut('/groups/' + encodeURIComponent(topic) + '/announcements/current', {content, version});
-export const getGroupAnnouncement = (topic, announcementId) =>
- imGet('/groups/' + encodeURIComponent(topic) + '/announcements/' + encodeURIComponent(announcementId));
-export const readGroupAnnouncement = (topic, announcementId) =>
- imPost('/groups/' + encodeURIComponent(topic) + '/announcements/' + encodeURIComponent(announcementId) + '/read', {});
-export const getGroupGovernance = topic => imGet('/groups/' + encodeURIComponent(topic) + '/governance');
-export const updateGroupGovernance = (topic, body) => imPatch('/groups/' + encodeURIComponent(topic) + '/governance', body);
-export const getGroupRoleLimits = topic => imGet('/groups/' + encodeURIComponent(topic) + '/role-limits');
-export const addGroupAdmins = (topic, userIds, version) => imPost('/groups/' + encodeURIComponent(topic) + '/admins/batch', {user_ids: userIds, version});
-// Must merge into idem()'s headers, not replace them (imDelete/imPost's `{headers: idem(), ...opts}`
-// lets a same-key `opts.headers` clobber the whole object) -- the same Idempotency-Key-goes-missing
-// bug this codebase already found and fixed once in disableYouthMode.
-export const deleteGroupAdmin = (topic, userId, version) =>
- imDelete('/groups/' + encodeURIComponent(topic) + '/admins/' + encodeURIComponent(userId), version ? {headers: {...idem(), 'If-Match': String(version)}} : {});
-export const muteGroupMembers = (topic, members, reason = '') => imPost('/groups/' + encodeURIComponent(topic) + '/mutes', {members, reason});
-export const getGroupMutes = (topic, cursor = '') => imGet('/groups/' + encodeURIComponent(topic) + '/mutes' + qs({cursor}));
-export const unmuteGroupMember = (topic, userId) => imDelete('/groups/' + encodeURIComponent(topic) + '/mutes/' + encodeURIComponent(userId));
-export const transferGroupOwner = (topic, targetUserId, version, actionToken = '') =>
- imPost('/groups/' + encodeURIComponent(topic) + '/owner-transfer', {target_user_id: targetUserId, version},
- actionToken ? {headers: {...idem(), 'X-Action-Token': actionToken}} : {});
-// Real backend reads the search text from `q`, not `query` (confirmed against ListFiles's real
-// query-param handling, same bug class as getOrganizationMembers) -- the search box in Group Files
-// was silently a no-op.
-export const getGroupFiles = (topic, {query = '', type = '', cursor = ''} = {}) =>
- imGet('/groups/' + encodeURIComponent(topic) + '/files' + qs({q: query, type, cursor}));
-// All fields besides file_id are `omitempty` server-side, so omitting them (as an earlier draft of this
-// function did) never errors -- it just registers a file with a blank name and 0 size in the files list.
-export const addGroupFile = (topic, {fileId, fileName = '', mimeType = '', fileType = '', fileSize = 0, sourceTopic = '', sourceSeq} = {}) =>
- imPost('/groups/' + encodeURIComponent(topic) + '/files', {
- file_id: fileId, file_name: fileName, mime_type: mimeType, file_type: fileType, file_size: fileSize,
- source_topic: sourceTopic, source_seq: sourceSeq
- });
-export const deleteGroupFile = (topic, groupFileId) => imDelete('/groups/' + encodeURIComponent(topic) + '/files/' + encodeURIComponent(groupFileId));
-export const getGroupMemberProfile = (topic, userId) => imGet('/groups/' + encodeURIComponent(topic) + '/members/' + encodeURIComponent(userId) + '/profile');
-
-// -- Organization / workbench (Ch.9). --
-export const getWorkbench = () => imGet('/workbench');
-export const getOrganizationDepartments = (parentId = 'root', cursor = '') => imGet('/organization/departments' + qs({parent_id: parentId, cursor}));
-export const getOrganizationDepartmentMembers = (departmentId, cursor = '') =>
- imGet('/organization/departments/' + encodeURIComponent(departmentId) + '/members' + qs({cursor}));
-// Real backend only reads `q` (server/im_http.go), never `query` -- confirmed against
-// server/im_http_test.go's own request fixtures. This function used to send `query`, silently making
-// every caller's search box a no-op (server ignores the unrecognized param and returns the unfiltered
-// list every time).
-export const getOrganizationMembers = ({query = '', departmentId = '', cursor = '', limit = ''} = {}) =>
- imGet('/organization/members' + qs({q: query, department_id: departmentId, cursor, limit}));
-export const searchOrganization = (query = '', types = 'department,user', cursor = '') =>
- imGet('/organization/search' + qs({q: query, types, cursor}));
-export const getMeetings = (cursor = '', limit = 20) => imGet('/meetings' + qs({limit, cursor}));
-export const createMeeting = body => imPost('/meetings', body);
-export const getMeeting = meetingId => imGet('/meetings/' + encodeURIComponent(meetingId));
-export const updateMeeting = (meetingId, body) => imPatch('/meetings/' + encodeURIComponent(meetingId), body);
-export const cancelMeeting = (meetingId, version, reason = '时间冲突') => imPost('/meetings/' + encodeURIComponent(meetingId) + '/cancel', {version, reason});
-export const sendMeetingReminders = meetingId => imPost('/meetings/' + encodeURIComponent(meetingId) + '/reminders', {});
-export const respondMeeting = (meetingId, response, version) => imPut('/meetings/' + encodeURIComponent(meetingId) + '/my-response', {response, version});
-// Note: the server only reads scope/from/to/status/cursor -- 'date' and 'department_id' are
-// silently ignored (confirmed against server/im_http.go), so a single-day filter must be
-// expressed as from==to, and there is no way to scope this list to one department.
-export const getDailyReports = ({scope = 'mine', from = '', to = '', status = '', cursor = ''} = {}) =>
- imGet('/reports/daily' + qs({scope, from, to, status, cursor}));
-export const createDailyReport = reportDate => imPost('/reports/daily', {report_date: reportDate});
-export const getDailyReport = reportId => imGet('/reports/daily/' + encodeURIComponent(reportId));
-export const updateDailyDraft = (reportId, content, version, attachments = []) =>
- imPut('/reports/daily/' + encodeURIComponent(reportId) + '/draft', {content, attachments, version});
-export const submitDailyReport = (reportId, version) => imPost('/reports/daily/' + encodeURIComponent(reportId) + '/submit', {version});
-// TenantDailyReportStats does honor department_id (reportMemberScopeWhere adds `m.department_id=?`
-// when it's non-empty) -- confirmed against current im_be HEAD, this wasn't always true and an earlier
-// comment here claimed the opposite.
-export const getDailyStatistics = (date, departmentId = '') => imGet('/reports/daily/statistics' + qs({date, department_id: departmentId}));
-export const sendDailyReminders = (date, userIds = []) => imPost('/reports/daily/reminders', {date, user_ids: userIds});
-export const getMonthlyReports = ({scope = 'mine', fromMonth = '', toMonth = '', status = '', cursor = ''} = {}) =>
- imGet('/reports/monthly' + qs({scope, from_month: fromMonth, to_month: toMonth, status, cursor}));
-export const createMonthlyReport = month => imPost('/reports/monthly', {month});
-export const getMonthlyReport = reportId => imGet('/reports/monthly/' + encodeURIComponent(reportId));
-export const updateMonthlyDraft = (reportId, workSummary, nextPlan, version, attachments = []) =>
- imPut('/reports/monthly/' + encodeURIComponent(reportId) + '/draft', {work_summary: workSummary, next_plan: nextPlan, attachments, version});
-export const submitMonthlyReport = (reportId, version) => imPost('/reports/monthly/' + encodeURIComponent(reportId) + '/submit', {version});
-// server/im_http.go's serveIMMonthlyReportReopen decodes only {version} (DisallowUnknownFields
-// rejects anything else) -- an earlier draft of this function sent an extra `reason` field that
-// would 400 MALFORMED_REQUEST on every real call. Also: the store layer's UPDATE has
-// `WHERE ... AND author_id=?` keyed off the *caller's* uid, so this only ever lets a report's own
-// author reopen their own already-submitted report for further editing -- there is no
-// manager-reopens-someone-else's-report capability on the backend.
-export const reopenMonthlyReport = (reportId, version) =>
- imPost('/reports/monthly/' + encodeURIComponent(reportId) + '/reopen', {version});
-// Same department_id support as getDailyStatistics (TenantMonthlyReportStats shares reportMemberScopeWhere).
-export const getMonthlyStatistics = (month, departmentId = '') => imGet('/reports/monthly/statistics' + qs({month, department_id: departmentId}));
-export const getExecutiveMailboxConfig = () => imGet('/executive-mailbox/config');
-export const submitExecutiveMailboxMessage = body => imPost('/executive-mailbox/submissions', body);
-
-// -- Personal settings (Ch.10). --
-export const getMeProfile = () => imGet('/me/profile');
-export const updateMeProfile = body => imPatch('/me/profile', body);
-export const getMePrivacy = () => imGet('/me/privacy');
-export const updateMePrivacy = body => imPatch('/me/privacy', body);
-export const getYouthMode = () => imGet('/settings/youth-mode');
-export const enableYouthMode = (policyVersion, actionToken) => imPost('/settings/youth-mode/enable', {policy_version: policyVersion, action_token: actionToken});
-// imPost spreads `opts` last, so passing `headers` directly would silently replace (not merge
-// with) the Idempotency-Key header from idem() -- confirmed live: the server actually enforces
-// it ("Idempotency-Key is required", 400), unlike the earlier research pass's assumption that a
-// missing key here was harmless. Merge explicitly instead of letting the spread clobber it.
-export const disableYouthMode = actionToken => imPost('/settings/youth-mode/disable', {}, {headers: {...idem(), 'X-Action-Token': actionToken}});
-export const getLatestAppRelease = ({platform = 'android', currentVersion = '1.0.0', channel = 'stable'} = {}) =>
- imGet('/app/releases/latest' + qs({platform, current_version: currentVersion, channel}));
-export const updateDevicePushToken = (deviceId, body) => imPut('/devices/' + encodeURIComponent(deviceId) + '/push-token', body);
-
-// -- Reports & feedback (Ch.11). --
-export const getAbuseReasons = (targetType = 'user') => imGet('/reports/abuse/reasons' + qs({target_type: targetType}));
-export const submitAbuseReport = body => imPost('/reports/abuse', body);
-export const getFeedbackConfig = () => imGet('/feedback/config');
-export const submitFeedback = body => imPost('/feedback', {client_context: clientContext(), ...body});
-
-// -- Contact us (admin 字典 im_contact_us 透传,含邮箱/电话条目;后台修改后经缓存失效近实时生效). --
-export const getContactUs = () => imGet('/contact-us');
diff --git a/src/lib/im-api.test.js b/src/lib/im-api.test.js
deleted file mode 100644
index 44595ecf..00000000
--- a/src/lib/im-api.test.js
+++ /dev/null
@@ -1,38 +0,0 @@
-import { normalizeFileReferenceAgainst } from './im-api';
-
-const PAGE_ORIGIN = 'https://web.118im.com';
-const API_ENDPOINT = 'https://api.118im.com/v1/im';
-const FILE_ENDPOINT = 'https://web.118im.com/v0/file';
-
-test('normalizes historical API-host file references to the same-origin file endpoint', () => {
- expect(normalizeFileReferenceAgainst(
- 'https://api.118im.com/v0/file/s/example.jpg?size=large#preview',
- API_ENDPOINT,
- FILE_ENDPOINT,
- PAGE_ORIGIN
- )).toBe('/v0/file/s/example.jpg?size=large#preview');
-});
-
-test('leaves current and unrelated file references unchanged', () => {
- expect(normalizeFileReferenceAgainst(
- '/v0/file/s/current.jpg',
- API_ENDPOINT,
- FILE_ENDPOINT,
- PAGE_ORIGIN
- )).toBe('/v0/file/s/current.jpg');
- expect(normalizeFileReferenceAgainst(
- 'https://cdn.example.com/image.jpg',
- API_ENDPOINT,
- FILE_ENDPOINT,
- PAGE_ORIGIN
- )).toBe('https://cdn.example.com/image.jpg');
-});
-
-test('uses an external configured file endpoint when same-origin proxy is unavailable', () => {
- expect(normalizeFileReferenceAgainst(
- 'https://api.118im.com/v0/file/s/example.jpg',
- API_ENDPOINT,
- 'https://files.example.com/v0/file',
- PAGE_ORIGIN
- )).toBe('https://files.example.com/v0/file/s/example.jpg');
-});
diff --git a/src/lib/local-storage.js b/src/lib/local-storage.js
deleted file mode 100644
index 9a253d11..00000000
--- a/src/lib/local-storage.js
+++ /dev/null
@@ -1,26 +0,0 @@
-// Helper functions for storing values in localStorage.
-// By default localStorage can store only strings, not objects or other types.
-
-export default class LocalStorageUtil {
- // Replace old object with the new one.
- static setObject(key, value) {
- localStorage.setItem(key, JSON.stringify(value));
- }
-
- // Get stored object.
- static getObject(key) {
- const value = localStorage.getItem(key);
- return value && JSON.parse(value);
- }
-
- // Partially or wholly update stored object.
- static updateObject(key, value) {
- const oldVal = this.getObject(key);
- this.setObject(key, Object.assign(oldVal || {}, value));
- }
-
- // Just a wrapper.
- static removeItem(key) {
- localStorage.removeItem(key);
- }
-}
diff --git a/src/lib/navigation.js b/src/lib/navigation.js
deleted file mode 100644
index cb4a82df..00000000
--- a/src/lib/navigation.js
+++ /dev/null
@@ -1,172 +0,0 @@
-// Utility class for client-side routing via the History API.
-
-// Every `navigateTo` call pushes a real history entry -- `history.back()` would land on whatever
-// the user actually saw before, for free.
-// The product's "back" buttons never used that: each page hardcodes a single destination view name
-// (e.g. Profile's `back={friend ? 'friend-profile' : 'chat-standard'}`), which is only ever right for
-// the one entry point its author had in mind. Reach the same page from a second place (search result
-// vs. QR scan vs. a banner inside an open chat) and "back" throws you at that guessed destination
-// instead of retracing your actual steps. `goBack()` below is the fix: count real in-app navigations
-// this page load and prefer `history.back()` whenever there's one to return to, only falling back to
-// the old hardcoded guess when there truly isn't (fresh load, deep link, or a page refresh -- this
-// counter is in-memory and intentionally does not survive those).
-let navigationCount = 0;
-
-// Placeholder path segment for "no sidepanel view selected, just a topic" (path[0] === '', the last
-// entry in tinode-web.jsx's own SIDE_PANEL_VIEWS whitelist). A real empty path segment can't be
-// represented safely as the FIRST segment of a real URL path: joining ['', 'grpXXX'] would produce
-// "/grpXXX" before the leading '/' this class adds is even accounted for, so the final string comes
-// out "//grpXXX" -- and a path starting with '//' is resolved by pushState/replaceState as a
-// protocol-relative reference (same scheme, host becomes "grpXXX"), not a same-origin path. Swapping
-// in this token keeps composeUrl/parseUrl round-tripping path[0] === '' exactly like before, entirely
-// inside this file -- no caller needs to know the placeholder exists.
-const EMPTY_VIEW_PLACEHOLDER = '_';
-
-export default class Navigation {
- // Parse a route string (path + optional '?args') into path segments and params.
- static parseUrl(url) {
- // Split 'path/?args' into 'path/' and 'args', path -> parts[0], args->path[1].
- const parts = url.split('?', 2);
- const params = {};
- let path = [];
- if (parts[0]) {
- path = parts[0].replace(/^\//, '').split('/');
- if (path.length > 1 && path[0] === EMPTY_VIEW_PLACEHOLDER) {
- path[0] = '';
- }
- }
- if (parts[1]) {
- parts[1].split('&').forEach((arg) => {
- // Can't use split() because the value may contain '='.
- const eq = arg.indexOf('=');
- if (eq > 0) {
- params[arg.slice(0, eq)] = decodeURIComponent(arg.slice(eq + 1));
- }
- });
- }
- return {path: path, params: params};
- }
-
- // The current route string, in the same shape `navigateTo`/`navigateReplace` consume and
- // `parseUrl` expects -- callers that used to read `window.location.hash` directly read this
- // instead (this class never wrapped that read itself, so every call site has to switch explicitly).
- static currentUrl() {
- return window.location.pathname + window.location.search;
- }
-
- static navigateTo(url) {
- navigationCount++;
- window.history.pushState(null, '', url);
- // pushState never fires popstate on its own (only real back/forward/go() does) -- dispatch a
- // synthetic one so tinode-web.jsx's listener re-derives state from the new URL, same trick
- // navigateReplace below already uses for replaceState's identical silence.
- window.dispatchEvent(new PopStateEvent('popstate'));
- }
-
- // For switching between sub-tabs of the *same* page (e.g. content-library-view.jsx's favorites/
- // history type tabs), not for navigating to a different page. `navigateTo` always pushes a new
- // history entry, so clicking through N tabs pushes N entries -- the system back button (and this
- // class's own goBack()) then has to step back through every tab the user ever visited before it
- // reaches whatever page they were actually on before this one. `replaceState` updates the URL
- // (still correct for a refresh or a copied link) without adding a history entry, so it doesn't
- // inflate navigationCount either -- a tab switch was never a real in-app navigation to begin with.
- // replaceState doesn't fire `popstate` on its own, so dispatch one manually for the existing
- // `window.addEventListener('popstate', ...)` listener (tinode-web.jsx's handleRouteChange) to pick
- // up; that handler re-reads the current URL itself rather than the event, so a synthetic event with
- // no detail is indistinguishable from a real one to it.
- static navigateReplace(url) {
- window.history.replaceState(null, '', url);
- window.dispatchEvent(new PopStateEvent('popstate'));
- }
-
- // Real "back": retraces the route the user was actually on before, via native browser history,
- // rather than a page's own guess at where it must have been reached from. Returns whether it
- // actually did that -- every call site already has its own hardcoded-destination `navigate(...)`
- // fallback (a page's `back`/`backUid`/`backTopic` props) for the one case this can't cover: a
- // fresh load, deep link, or refresh, where there's no in-app history yet to go back to (this
- // counter is in-memory by design and does not survive those) -- callers keep using that fallback
- // unchanged, just gated behind `if (!Navigation.goBack()) { ...existing fallback... }`.
- static goBack() {
- if (navigationCount > 0) {
- navigationCount--;
- window.history.back();
- return true;
- }
- return false;
- }
-
- static composeUrl(path, params) {
- let segments = path;
- // !segments[0] (not === '') deliberately also catches a sparse hole at index 0 -- parseUrl('')
- // returns path: [] (empty, not ['']; inherited as-is from the original hash-based parseUrlHash,
- // which had the identical `if (parts[0])` guard), and setUrlTopic('', topic) then does
- // `parsed.path[1] = topic` on that empty array, leaving index 0 an actual hole rather than ''.
- // Array#join treats a hole the same as an empty string either way, so this was always silently
- // producing the same "no view, just a topic" shape -- harmless under the old scheme since a
- // leading '#//' is still just inert fragment text, but a real path starting '//' is parsed by
- // pushState/URL as protocol-relative (steals the topic id as the new host) and throws.
- if (segments.length > 1 && !segments[0]) {
- segments = [EMPTY_VIEW_PLACEHOLDER, ...segments.slice(1)];
- }
- let url = '/' + segments.join('/');
- const args = [];
- for (const key in params) {
- if (params.hasOwnProperty(key)) {
- if (params[key] !== undefined) {
- args.push(key + '=' + encodeURIComponent(params[key]));
- }
- }
- }
- if (args.length > 0) {
- url += '?' + args.join('&');
- }
- return url;
- }
-
- static addUrlParam(url, key, value) {
- const parsed = Navigation.parseUrl(url);
- parsed.params[key] = value;
- return Navigation.composeUrl(parsed.path, parsed.params);
- }
-
- static removeUrlParam(url, key) {
- const parsed = Navigation.parseUrl(url);
- delete parsed.params[key];
- return Navigation.composeUrl(parsed.path, parsed.params);
- }
-
- static removeUrlParams(url, keys) {
- if (!Array.isArray(keys)) {
- keys = [keys];
- }
- const parsed = Navigation.parseUrl(url);
- for (const key of keys) {
- delete parsed.params[key];
- }
- return Navigation.composeUrl(parsed.path, parsed.params);
- }
-
- static setUrlSidePanel(url, sidepanel) {
- const parsed = Navigation.parseUrl(url);
- parsed.path[0] = sidepanel;
- return Navigation.composeUrl(parsed.path, parsed.params);
- }
-
- static setUrlInfoPanel(url, infopanel) {
- const parsed = Navigation.parseUrl(url);
- if (infopanel) {
- parsed.params.info = infopanel;
- } else {
- delete parsed.params.info;
- }
- return Navigation.composeUrl(parsed.path, parsed.params);
- }
-
- static setUrlTopic(url, topic) {
- const parsed = Navigation.parseUrl(url);
- parsed.path[1] = topic;
- // Close InfoView on topic change.
- delete parsed.params.info;
- return Navigation.composeUrl(parsed.path, parsed.params);
- }
-}
diff --git a/src/lib/strformat.js b/src/lib/strformat.js
deleted file mode 100644
index 24b0f881..00000000
--- a/src/lib/strformat.js
+++ /dev/null
@@ -1,125 +0,0 @@
-// Short representation of time in the past.
-export function shortDateFormat(then, locale) {
- locale = locale || window.navigator.userLanguage || window.navigator.language;
- const now = new Date();
- if (then.getFullYear() == now.getFullYear()) {
- // Same year.
- if (then.getMonth() == now.getMonth() && then.getDate() == now.getDate()) {
- // Same month and day, show time only.
- return then.toLocaleTimeString(locale, {hour12: false, hour: '2-digit', minute: '2-digit'});
- }
- // Different month and/or day, show month day, time.
- return then.toLocaleDateString(locale,
- {hour12: false, month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit'});
- }
- // Different year: just show the date.
- return then.toLocaleDateString(locale, {year: 'numeric', month: 'short', day: 'numeric'});
-}
-
-// Representation a date relative to now.
-export function relativeDateFormat(then, locale) {
- locale = locale || window.navigator.userLanguage || window.navigator.language;
- const now = new Date();
- const thenDays = Math.floor((then.getTime() - then.getTimezoneOffset() * 60_000) / 86_400_000);
- const nowDays = Math.floor((now.getTime() - now.getTimezoneOffset() * 60_000) / 86_400_000);
- const diff = thenDays - nowDays;
- if (Math.abs(diff) < 2) {
- // Today or yesterday.
- return new Intl.RelativeTimeFormat(locale, { numeric: 'auto' }).format(diff, 'day')
- }
- // More than two days: just show the date.
- return new Intl.DateTimeFormat(locale).format(then);
-}
-
-// Convert seconds to [hours:]minutes:seconds, i.e. 156 sec -> 2:36, 3756 sec -> 1:02:36.
-// If fixedMins is true, then minutes are represented by at least two digits.
-export function secondsToTime(seconds, fixedMin) {
- if (typeof seconds != 'number') {
- return '';
- }
-
- let min = (Math.floor(seconds / 60) | 0) % 60;
- let hours = Math.floor(seconds / 3600) | 0;
-
- if (fixedMin || hours > 0) {
- min = min < 10 ? `0${min}` : min;
- }
- let sec = (seconds | 0) % 60;
- sec = sec < 10 ? `0${sec}` : sec;
- if (hours == 0) {
- return `${min}:${sec}`;
- }
- return `${hours}:${min}:${sec}`;
-}
-
-// Convert a number of bytes to human-readable format.
-export function bytesToHumanSize(bytes) {
- if (!bytes || bytes == 0) {
- return '0 Bytes';
- }
-
- const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
- const bucket = Math.min(Math.floor(Math.log2(bytes) / 10) | 0, sizes.length-1);
- const count = bytes / Math.pow(1024, bucket);
- const round = bucket > 0 ? (count < 3 ? 2 : (count < 30 ? 1 : 0)) : 0;
- return count.toFixed(round) + ' ' + sizes[bucket];
-}
-
-// Shorten a file name to be under maxLength by clipping out the middle.
-export function shortenFileName(filename, maxLength) {
- if (typeof filename != 'string') {
- return filename;
- }
- return filename.length > maxLength ?
- filename.slice(0, maxLength/2 - 1) + '…' + filename.slice(1 - maxLength/2) : filename;
-}
-
-// Get 32 bit integer hash value for a string. Ideally it should produce the same value
-// as Java's String#hash().
-function stringToColorHash(value) {
- let hash = 0;
- value = '' + value;
- for (let i = 0; i < value.length; i++) {
- hash = ((hash << 5) - hash) + value.charCodeAt(i);
- hash = hash & hash; // Convert to 32bit integer
- }
- return hash;
-}
-
-// User avatar letter color id.
-export function letterTileColorId(userId) {
- return Math.abs(stringToColorHash(userId)) % 16;
-}
-
-// Converts user or topic ID to a CSS color class. Ideally it should produce the same color value as Java version.
-// params:
-// - {string} id: user or topic ID
-// - {boolean} light: light or dark version of the color.
-// - {boolean} fg: foreground (text) or background color.
-export function idToColorClass(id, light, fg) {
- return (light ? 'lt-' : 'dk-') + (fg ? 'fg-' : 'bg-') + letterTileColorId(id);
-}
-
-// Takes ISO Alpha-2 country code, like 'US' and converts it to a flag emoji.
-export function flagEmoji(countryCode) {
- const codePoints = countryCode
- .toUpperCase()
- .split('')
- .map(char => 0x1F1A5 + char.charCodeAt());
- return String.fromCodePoint(...codePoints);
-}
-
-// Truncate a string to a specific length and add ellipsis if needed.
-// Support both LTR and RTL languages.
-export function truncateString(str, maxLength) {
- if (typeof str !== 'string') {
- return str;
- }
- if (str.length <= maxLength) {
- return str;
- }
-
- // Detect RTL characters in the string and truncate accordingly.
- const isRTL = /[\u0590-\u05FF\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]/.test(str);
- return isRTL ? '…' + str.slice(0, maxLength - 1) : str.slice(0, maxLength - 1) + '…';
-}
diff --git a/src/lib/strformat.test.js b/src/lib/strformat.test.js
deleted file mode 100644
index 6d4022de..00000000
--- a/src/lib/strformat.test.js
+++ /dev/null
@@ -1,189 +0,0 @@
-import {
- shortDateFormat,
- relativeDateFormat,
- secondsToTime,
- bytesToHumanSize,
- shortenFileName,
- idToColorClass,
- flagEmoji,
- truncateString
-} from './strformat.js';
-
-describe('shortDateFormat', () => {
- jest
- .useFakeTimers()
- .setSystemTime(new Date('2025-07-25T12:30:00Z'));
-
- test('should format for same day', () => {
- const then = new Date('2025-07-25T10:15:00');
- // Note: output depends on the test runner's locale and timezone.
- // Assuming a locale where the format is HH:mm.
- expect(shortDateFormat(then, 'en-US')).toMatch(/10:15/);
- });
-
- test('should format for same year, different day', () => {
- const then = new Date('2025-06-20T10:15:00');
- expect(shortDateFormat(then, 'en-US')).toMatch(/Jun 20, 10:15/);
- });
-
- test('should format for different year', () => {
- const then = new Date('2024-07-25T10:15:00');
- expect(shortDateFormat(then, 'en-US')).toBe('Jul 25, 2024');
- });
-});
-
-describe('relativeDateFormat', () => {
- // Mock current date to a fixed point for consistent testing.
- jest
- .useFakeTimers()
- .setSystemTime(new Date('2025-07-25T12:30:00Z'));
-
- test('should format for today', () => {
- const then = new Date('2025-07-25T10:00:00Z');
- expect(relativeDateFormat(then, 'en-US')).toBe('today');
- });
-
- test('should format for yesterday', () => {
- const then = new Date('2025-07-24T10:00:00Z');
- expect(relativeDateFormat(then, 'en-US')).toBe('yesterday');
- });
-
- test('should format for a past date', () => {
- const then = new Date('2025-07-20T10:00:00Z');
- expect(relativeDateFormat(then, 'en-US')).toBe('7/20/2025');
- });
-
- test('should format for tomorrow', () => {
- const then = new Date('2025-07-26T10:00:00Z');
- expect(relativeDateFormat(then, 'en-US')).toBe('tomorrow');
- });
-});
-
-describe('secondsToTime', () => {
- test('should format seconds only', () => {
- expect(secondsToTime(45)).toBe('0:45');
- });
-
- test('should format minutes and seconds', () => {
- expect(secondsToTime(156)).toBe('2:36');
- });
-
- test('should format hours, minutes, and seconds', () => {
- expect(secondsToTime(3756)).toBe('1:02:36');
- });
-
- test('should format with fixed minutes', () => {
- expect(secondsToTime(3726, true)).toBe('1:02:06');
- expect(secondsToTime(126, true)).toBe('02:06');
- });
-
- test('should handle non-number input', () => {
- expect(secondsToTime('abc')).toBe('');
- });
-});
-
-describe('bytesToHumanSize', () => {
- test('should format 0 bytes', () => {
- expect(bytesToHumanSize(0)).toBe('0 Bytes');
- });
-
- test('should format bytes', () => {
- expect(bytesToHumanSize(500)).toBe('500 Bytes');
- });
-
- test('should format kilobytes', () => {
- expect(bytesToHumanSize(1500)).toBe('1.46 KB');
- });
-
- test('should format megabytes', () => {
- expect(bytesToHumanSize(1500000)).toBe('1.43 MB');
- });
-
- test('should format gigabytes', () => {
- expect(bytesToHumanSize(1500000000)).toBe('1.40 GB');
- });
-});
-
-describe('shortenFileName', () => {
- test('should not shorten if not needed', () => {
- expect(shortenFileName('shortname.txt', 20)).toBe('shortname.txt');
- });
-
- test('should shorten a long file name', () => {
- const longName = 'this_is_a_very_long_file_name_that_needs_shortening.txt';
- expect(shortenFileName(longName, 20)).toBe('this_is_a…ening.txt');
- expect(shortenFileName(longName, 20).length).toBeLessThanOrEqual(20);
- });
-
- test('should handle non-string input', () => {
- expect(shortenFileName(null, 20)).toBeNull();
- expect(shortenFileName(undefined, 20)).toBeUndefined();
- });
-});
-
-describe('idToColorClass', () => {
- test('should generate light background color class', () => {
- expect(idToColorClass('usr123', true, false)).toMatch(/lt-bg-\d+/);
- });
-
- test('should generate dark background color class', () => {
- expect(idToColorClass('usr123', false, false)).toMatch(/dk-bg-\d+/);
- });
-
- test('should generate light foreground color class', () => {
- expect(idToColorClass('usr123', true, true)).toMatch(/lt-fg-\d+/);
- });
-
- test('should generate dark foreground color class', () => {
- expect(idToColorClass('usr123', false, true)).toMatch(/dk-fg-\d+/);
- });
-
- test('should be deterministic', () => {
- expect(idToColorClass('usr123', true, false)).toBe('lt-bg-2');
- expect(idToColorClass('grpABC', false, true)).toBe('dk-fg-3');
- });
-});
-
-describe('flagEmoji', () => {
- test('should convert country code to flag', () => {
- expect(flagEmoji('US')).toBe('🇺🇸');
- });
-
- test('should handle lowercase country code', () => {
- expect(flagEmoji('jp')).toBe('🇯🇵');
- });
-});
-
-describe('truncateString', () => {
- test('should not truncate if not needed', () => {
- expect(truncateString('short', 10)).toBe('short');
- });
-
- test('should truncate and add ellipsis for long strings', () => {
- expect(truncateString('this is a long string', 10)).toBe('this is a…');
- });
-
- test('should handle RTL strings (Arabic)', () => {
- expect(truncateString('هذا مثال لنص عربي طويل يحتاج.', 10)).toBe('…هذا مثال ');
- });
-
- test('should handle RTL strings (Hebrew)', () => {
- expect(truncateString('זה דוגמה לטקסט עברי ארוך', 10)).toBe('…זה דוגמה ');
- });
-
- test('should handle RTL strings (Persian/Farsi)', () => {
- expect(truncateString('این یک نمونه متن فارسی طولانی است', 10)).toBe('…این یک نم');
- });
-
- test('should handle RTL strings (Urdu)', () => {
- expect(truncateString('یہ اردو زبان کا ایک طویل جملہ ہے', 10)).toBe('…یہ اردو ز');
- });
-
- test('should handle RTL strings (Arabic with mixed punctuation)', () => {
- expect(truncateString('النص العربي الطويل! متحف الفن', 10)).toBe('…النص العر');
- });
-
- test('should handle RTL strings (Hebrew with English mixed)', () => {
- expect(truncateString('טקסט עברי עם English מעורב', 10)).toBe('…טקסט עברי');
- });
-});
diff --git a/src/lib/use-cached-data.js b/src/lib/use-cached-data.js
deleted file mode 100644
index 3c61f5fa..00000000
--- a/src/lib/use-cached-data.js
+++ /dev/null
@@ -1,84 +0,0 @@
-import React from 'react';
-
-// Many product-prototype views fetch their data with `useState(null)` + a `useEffect` that calls an
-// im-api.js getter. Every one of those unmounts when the user switches to a different sidepanel/tab
-// and remounts from scratch on the way back -- React state doesn't survive that, so the screen goes
-// blank (or shows a "加载中" placeholder) again while the same request that already succeeded once
-// this session re-runs. Reported directly: switching between pages feels like it reloads every time.
-//
-// This hook keeps a module-level cache (survives unmount/remount, cleared only by a real page reload)
-// keyed by whatever string the caller passes in, so a remounted view can paint its last-known-good
-// data immediately while a fresh request updates it in the background -- the request still always
-// happens, this only removes the empty flash while waiting for it.
-const cache = new Map();
-
-// `fetcher` is called on every mount/dep-change (this is not a TTL cache -- there's no staleness
-// window, it always revalidates), but the *first* render after mount can use the previous result
-// for this key instead of null. `key` should already encode whatever varies the result (e.g. include
-// a user id or a "mine"/"team" flag) -- this module doesn't know how to derive that itself.
-export function useCachedData(key, fetcher, deps) {
- const [data, setData] = React.useState(() => (key ? cache.get(key) : undefined));
- const [loading, setLoading] = React.useState(() => !!key && !cache.has(key));
-
- React.useEffect(() => {
- if (!key) return;
- let live = true;
- let retried = false;
- // Don't flip back to a loading state just because deps changed while we already have a cached
- // value for the new key -- only the truly-never-fetched case should show a loading affordance.
- setLoading(!cache.has(key));
- const attempt = () => fetcher().then(result => {
- if (!live) return;
- cache.set(key, result);
- setData(result);
- setLoading(false);
- }).catch(() => {
- if (!live) return;
- // A transient failure on the very *first* fetch for this key -- nothing cached yet to fall
- // back on -- used to leave `data` stuck at undefined for the rest of the session, since
- // nothing else ever re-triggers this effect unless `key`/`deps` change. One retry after a
- // short delay covers the common case (a network blip during initial page load) without
- // retry-storming a persistently failing endpoint. A key that already has *some* cached value
- // keeps the original behavior (show the stale value, don't retry) -- that case is already
- // showing the user something, so it's lower urgency than the stuck-at-nothing case.
- if (!retried && !cache.has(key)) {
- retried = true;
- window.setTimeout(() => { if (live) attempt(); }, 3000);
- return;
- }
- // Keep whatever stale/cached value was already showing rather than clearing it on a transient
- // failure -- a blank screen on error is worse than a slightly-out-of-date one.
- setLoading(false);
- });
- attempt();
- return () => { live = false; };
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [key, ...(deps || [])]);
-
- return [data, loading];
-}
-
-// For the (rarer) case a view wants to seed its own useState from the cache directly -- e.g. when it
-// needs to merge the cached value with other local state instead of holding it as the sole source of
-// truth. Does not itself revalidate; pair it with writeCachedData once a fresh fetch resolves.
-export function readCachedData(key) {
- return key ? cache.get(key) : undefined;
-}
-
-export function writeCachedData(key, value) {
- if (key) cache.set(key, value);
-}
-
-// 搜索框防抖:返回一个滞后于 `value` 的值,只有停止输入 `delay` 毫秒后才更新。
-// 用途是把"每敲一个字符发一次请求"降成"停下来才发一次"。收藏搜索、会话内检索、群聊搜索
-// 原来都是把 query 直接放进 effect 依赖里,输入 6 个字的关键词就打 6 次接口,
-// 而且返回顺序没有保证——后发的短前缀请求可能后到,把正确结果覆盖掉。
-export function useDebouncedValue(value, delay = 300) {
- const [debounced, setDebounced] = React.useState(value);
- React.useEffect(() => {
- if (value === debounced) return undefined;
- const timer = window.setTimeout(() => setDebounced(value), delay);
- return () => window.clearTimeout(timer);
- }, [value, delay, debounced]);
- return debounced;
-}
diff --git a/src/lib/utils.js b/src/lib/utils.js
deleted file mode 100644
index c7aa3424..00000000
--- a/src/lib/utils.js
+++ /dev/null
@@ -1,295 +0,0 @@
-// Odds and ends
-
-import { Tinode } from 'tinode-sdk';
-import { WALLPAPER_DEFAULTS, BASE_APP_NAME } from '../config.js';
-
-// The backend's REST group layer writes a group's display name into its native Tinode `public`
-// blob under a plain `name` key instead of the protocol-standard `fn` the SDK (and every other
-// Tinode client) expects -- confirmed live against a freshly created group: `topic.public` comes
-// back as `{name:'...', version:1}`, no `fn` at all. P2P/self topics are unaffected (their `public`
-// really does carry `fn`). Until the backend writes `fn` correctly, every place that shows a
-// topic's name has to fall back to `public.name` specifically for groups, or every group in the
-// app displays as "未命名"/"未命名会话" (unnamed) -- this happened in the chat header, the
-// conversation list, and the contact-list widget (used by member/forward pickers) before this.
-export function topicDisplayName(topicName, publicData, fallback) {
- const fn = publicData && publicData.fn;
- if (fn) return fn;
- if (publicData && Tinode.isGroupTopicName(topicName) && publicData.name) return publicData.name;
- // 自聊天(slf)没有 public,原来会一路落到 fallback —— 消息首页因此把它显示成"未命名会话"。
- // 它的显示名靠 Tinode.isSelfTopicName 特判得出,不在 public 里。这里集中处理,所有调用点都受益;
- // 已经自己判过的地方(batch-forward-dialog 等)在调用前就短路了,不受影响。
- if (Tinode.isSelfTopicName(topicName)) return '已保存的消息';
- return fallback;
-}
-
-// Make shortcut icon appear with a green dot + show unread count in title.
-export function updateFavicon(count) {
- const oldIcon = document.getElementById('shortcut-icon');
- const head = document.head || document.getElementsByTagName('head')[0];
- const newIcon = document.createElement('link');
- newIcon.type = 'image/png';
- newIcon.id = 'shortcut-icon';
- newIcon.rel = 'shortcut icon';
- newIcon.href = 'img/logo32x32' + (count > 0 ? 'a' : '') + '.png';
- if (oldIcon) {
- head.removeChild(oldIcon);
- }
- head.appendChild(newIcon);
-
- document.title = (count > 0 ? '('+count+') ' : '') + BASE_APP_NAME;
-}
-
-// Create theCard which represents user's or topic's "public" info.
-export function theCard(fn, imageUrl, imageMimeType, note) {
- let card = null;
- fn = fn && fn.trim();
- note = note && note.trim();
-
- if (fn) {
- card = {
- fn: fn
- };
- }
-
- if (typeof note == 'string') {
- card = card || {};
- card.note = note ? note : Tinode.DEL_CHAR;
- }
-
- if (imageUrl) {
- card = card || {};
- let mimeType = imageMimeType;
- // Is this a data URL "data:[][;base64],"?
- const matches = /^data:(image\/[-a-z0-9+.]+)?(;base64)?,/i.exec(imageUrl);
- if (matches) {
- mimeType = matches[1];
- card.photo = {
- data: imageUrl.substring(imageUrl.indexOf(',') + 1),
- ref: Tinode.DEL_CHAR
- };
- } else {
- card.photo = {
- data: Tinode.DEL_CHAR,
- ref: imageUrl
- };
- }
- card.photo.type = (mimeType || 'image/jpeg').substring('image/'.length);
- }
-
- return card;
-}
-
-// Deep-shallow compare two arrays: shallow compare each element.
-export function arrayEqual(a, b) {
- if (a === b) {
- return true;
- }
-
- if (!Array.isArray(a) || !Array.isArray(b)) {
- return false;
- }
-
- // Compare lengths first.
- if (a.length != b.length) {
- return false;
- }
- // Order of elements is ignored.
- a.sort();
- b.sort();
- for (let i = 0, l = a.length; i < l; i++) {
- if (a[i] !== b[i]) {
- return false;
- }
- }
- return true;
-}
-
-// Checks (loosely) if the given string is a phone. If so, returns the phone number in a format
-// as close to E.164 as possible.
-export function asPhone(val) {
- val = val.trim();
- if (/^(?:\+?(\d{1,3}))?[- (.]*(\d{3})[- ).]*(\d{3})[- .]*(\d{2})[- .]*(\d{2})?$/.test(val)) {
- return val.replaceAll(/[- ().]*/g, '');
- }
- return null;
-}
-
-// Checks (loosely) if the given string is an email. If so returns the email.
-export function asEmail(val) {
- val = val.trim();
- if (/^[a-z0-9_.+-]+@[a-z0-9-]+(\.[a-z0-9-]+)+$/i.test(val)) {
- return val;
- }
- return null;
-}
-
-// Checks if URL is a relative url, i.e. has no 'scheme://', including the case of missing scheme '//'.
-// The scheme is expected to be RFC-compliant, e.g. [a-z][a-z0-9+.-]*
-// example.html - ok
-// https:example.com - not ok.
-// http:/example.com - not ok.
-// //example.com or \\example.com - not ok.
-// ' ↲ https://example.com' - not ok. (↲ means carriage return)
-export function isUrlRelative(url) {
- // Replacing backslashes with forward slashes to mimic JS URL parser then testing for
- // 'scheme:' and '//' with optional space at the start.
- return url && !/^\s*([a-z][a-z0-9+.-]*:|\/\/)/im.test(url.replace(/\\/g, '/'));
-}
-
-// Ensure URL does not present an XSS risk. Optional allowedSchemes may contain an array of
-// strings with permitted URL schemes, such as ['ftp', 'ftps']; otherwise accept http and https only.
-export function sanitizeUrl(url, allowedSchemes) {
- if (typeof url != 'string') {
- return url;
- }
-
- // Strip control characters and whitespace. They are not valid URL characters anyway.
- url = url.replace(/[^\x21-\x7E]/gmi, '').trim();
-
- // Replace backslashes with forward slashes. They will be replaced in the URL parser anyway.
- url = url.replace(/\\/g, '/');
-
- // Relative URLs are safe.
- // Relative URL does not start with ':', abcd123: or '//'.
- if (!/^([a-z][a-z0-9+.-]*:|\/\/)/i.test(url)) {
- return url;
- }
-
- // Blob URLs are safe.
- if (/^blob:http/.test(url)) {
- return url;
- }
-
- // Absolute URL. Accept only safe schemes, or no scheme.
- const schemes = Array.isArray(allowedSchemes) ? allowedSchemes.join('|') : 'http|https';
- const re = new RegExp('^((' + schemes + '):|//)', 'i');
- if (!re.test(url)) {
- return null;
- }
-
- return url;
-}
-
-// Ensure URL is suitable as a source like field: the URL must be a relative URL or
-// have http:, https:, blob: or data: scheme.
-// In case of data: scheme, the URL must must be of the right MIME type such as 'data:{mimeMajor}/XXXX;base64,'.
-export function sanitizeUrlForMime(url, mimeMajor) {
- if (!url) {
- return null;
- }
-
- const sanitizedUrl = sanitizeUrl(url);
- if (sanitizedUrl) {
- return sanitizedUrl;
- }
-
- // Is this a data: URL of the appropriate mime type?
- const re = new RegExp(`data:${mimeMajor}\/[-+.a-z0-9]+;base64,`, 'i');
- if (re.test(url.trim())) {
- return url;
- }
-
- return null;
-}
-
-// Append query parameter 'asatt=1' to the URL.
-// It will cause Tinode server to add 'Content-Disposition: attachment' header when serving it.
-// The URL is a string. The URL here is always absolute.
-export function urlAsAttachment(url) {
- // TODO: check if URL is local or remote, i.e. compare to window.location.origin.
- /*
- if (typeof window.location == 'object') {
- if (!url.startsWith(window.location.origin)) {
- return url;
- }
- }
- */
- // Check if the URL is a data or blob. Do not alter such URLs.
- if (url.startsWith('data:') || url.startsWith('blob:')) {
- return url;
- }
-
- // Split URL into host+path, query, fragment.
- let query = '', fragment = '';
- const idxF = url.indexOf('#');
- if (idxF > 0) {
- fragment = url.substring(idxF+1);
- url = url.substring(0, idxF);
- }
- const idxQ = url.indexOf('?');
- if (idxQ > 0) {
- query = url.substring(idxQ+1);
- url = url.substring(0, idxQ);
- }
- // Add parameter and reassemble.
- const params = new URLSearchParams(query);
- params.append('asatt', '1');
- return `${url}?${params.toString()}` + (fragment ? `#${fragment}` : '');
-}
-
-// Given message's received status, return name and color of a delivery indicator icon.
-export function deliveryMarker(received) {
- switch (received) {
- case Tinode.MESSAGE_STATUS_SENDING:
- return { name: 'access_time' }; // watch face
- case Tinode.MESSAGE_STATUS_FAILED:
- case Tinode.MESSAGE_STATUS_FATAL:
- return { name: 'warning', color: 'red' }; // red icon /!\
- case Tinode.MESSAGE_STATUS_SENT:
- return { name: 'done' }; // checkmark
- case Tinode.MESSAGE_STATUS_RECEIVED:
- return { name: 'done_all' }; // double checkmark
- case Tinode.MESSAGE_STATUS_READ:
- return { name: 'done_all', color: 'blue' }; // blue double checkmark
- }
- return null;
-}
-
-// Wraps a promise to make it cancelable.
-// The parameter can be either a promise or an error. If it's an error, the wrapped promise is
-// created in a rejected state.
-export function cancelablePromise(promise) {
- let hasCanceled = false;
-
- const wrappedPromise = promise instanceof Error ?
- Promise.reject(promise) :
- new Promise((resolve, reject) => {
- promise.then(
- result => hasCanceled ? reject({isCanceled: true}) : resolve(result),
- error => hasCanceled ? reject({isCanceled: true}) : reject(error)
- );
- });
-
- return {
- promise: wrappedPromise,
- cancel() {
- hasCanceled = true;
- },
- };
-};
-
-export function defaultWallpaper() {
- const wallpaperIndex = require('../../img/bkg/index.json');
- const wp = wallpaperIndex[WALLPAPER_DEFAULTS.type][WALLPAPER_DEFAULTS.index];
- return {name: `../${WALLPAPER_DEFAULTS.path}${wp.name}`, size: wp.size};
-}
-
-export function wallpaperNameFromUrl(wallpaperUrl) {
- // Get filename from URL.
- const parts = wallpaperUrl.split('/');
- return parts[parts.length - 1];
-}
-
-export function wallpaperTypeFromUrl(wallpaperUrl) {
- // Get filename from URL.
- const fname = wallpaperNameFromUrl(wallpaperUrl);
- const wallpaperIndex = require('../../img/bkg/index.json');
- for (const type in wallpaperIndex) {
- for (let i = 0; i < wallpaperIndex[type].length; i++) {
- if (wallpaperIndex[type][i].name == fname) {
- return type;
- }
- }
- }
- return null;
-}
diff --git a/src/lib/utils.test.js b/src/lib/utils.test.js
deleted file mode 100644
index 9af3f2c1..00000000
--- a/src/lib/utils.test.js
+++ /dev/null
@@ -1,127 +0,0 @@
-import {
- arrayEqual,
- asEmail,
- asPhone,
- isUrlRelative,
- sanitizeUrl,
- sanitizeUrlForMime,
- urlAsAttachment
-} from './utils';
-
-test('asPhone', () => {
- expect(asPhone('123-456-7890')).toBe('1234567890');
- expect(asPhone('(123) 456-7890')).toBe('1234567890');
- expect(asPhone('+1 (123) 456-7890')).toBe('+11234567890');
- expect(asPhone('123.456.7890')).toBe('1234567890');
- expect(asPhone('123 456 7890')).toBe('1234567890');
- expect(asPhone('123-456-78901')).toBe('12345678901');
- expect(asPhone('')).toBeNull();
- expect(asPhone('abc-def-ghij')).toBeNull();
- expect(asPhone('123-456-789')).toBeNull();
- expect(asPhone('123-4i6-78901')).toBeNull();
-});
-
-test('arrayEqual', () => {
- expect(arrayEqual([1, 2, 3], [3, 2, 1])).toBe(true);
- expect(arrayEqual([1, 2, 3], [4, 5, 6])).toBe(false);
- expect(arrayEqual([1, 2, 3], [1, 2, 3, 4])).toBe(false);
- expect(arrayEqual([1, 2, 3], [1, 2])).toBe(false);
- expect(arrayEqual([], [])).toBe(true);
- expect(arrayEqual([1], [1])).toBe(true);
- expect(arrayEqual([1], [2])).toBe(false);
- expect(arrayEqual(null, [1, 2, 3])).toBe(false);
- expect(arrayEqual([1, 2, 3], null)).toBe(false);
- expect(arrayEqual(null, null)).toBe(true);
- expect(arrayEqual(undefined, undefined)).toBe(true);
- expect(arrayEqual([1, 2, 3], undefined)).toBe(false);
- expect(arrayEqual([1, 2, 3], [1, 2, '3'])).toBe(false);
-});
-
-test('asEmail', () => {
- expect(asEmail('test@example.com')).toBe('test@example.com');
- expect(asEmail('test@mail.example.com')).toBe('test@mail.example.com');
- expect(asEmail('test@e-mail.example.com')).toBe('test@e-mail.example.com');
- expect(asEmail(' test@example.com ')).toBe('test@example.com');
- expect(asEmail('test.email+alex@leetcode.com')).toBe('test.email+alex@leetcode.com');
- expect(asEmail('test.email@sub.example.com')).toBe('test.email@sub.example.com');
- expect(asEmail('test.email@sub.example.co.uk')).toBe('test.email@sub.example.co.uk');
- expect(asEmail('invalid-email')).toBeNull();
- expect(asEmail('invalid-email@')).toBeNull();
- expect(asEmail('invalid-email@example')).toBeNull();
- expect(asEmail('invalid-email@example.')).toBeNull();
- expect(asEmail('invalid-email@example..com')).toBeNull();
- expect(asEmail('invalid-email@.example.com')).toBeNull();
- expect(asEmail('invalid-email@example.com.')).toBeNull();
- expect(asEmail('invalid-email@@example.com.')).toBeNull();
- expect(asEmail('invalid/email@example.com.')).toBeNull();
-});
-
-test('isUrlRelative', () => {
- expect(isUrlRelative('example.html')).toBe(true);
- expect(isUrlRelative('https:example.com')).toBe(false);
- expect(isUrlRelative('http:/example.com')).toBe(false);
- expect(isUrlRelative(' \n https://example.com')).toBe(false);
- expect(isUrlRelative('//example.com')).toBe(false);
- expect(isUrlRelative('\\example.com')).toBe(true);
- expect(isUrlRelative('example.com')).toBe(true);
- expect(isUrlRelative('/example')).toBe(true);
- expect(isUrlRelative('ftp://example.com')).toBe(false);
- expect(isUrlRelative('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA')).toBe(false);
-});
-
-test('sanitizeUrl', () => {
- // Test with valid URLs
- expect(sanitizeUrl('http://example.com')).toBe('http://example.com');
- expect(sanitizeUrl('https://example.com')).toBe('https://example.com');
- expect(sanitizeUrl('blob:http://example.com')).toBe('blob:http://example.com');
- expect(sanitizeUrl('example.html')).toBe('example.html');
-
- // Test with invalid URLs
- expect(sanitizeUrl('ftp://example.com')).toBeNull();
- expect(sanitizeUrl('javascript:alert(1)')).toBeNull();
- expect(sanitizeUrl('data:text/plain;base64,SGVsbG8sIFdvcmxkIQ==')).toBeNull();
-
- // Test with allowed schemes
- expect(sanitizeUrl('ftp://example.com', ['ftp'])).toBe('ftp://example.com');
- expect(sanitizeUrl('ftps://example.com', ['ftp', 'ftps'])).toBe('ftps://example.com');
-
- // Test with non-string input
- expect(sanitizeUrl(null)).toBe(null);
- expect(sanitizeUrl(123)).toBe(123);
-
- // Test with control characters and whitespace
- expect(sanitizeUrl(' \n https://example.com')).toBe('https://example.com');
- expect(sanitizeUrl('http:\\\\example.com')).toBe('http://example.com');
-});
-
-test('sanitizeUrlForMime', () => {
- expect(sanitizeUrlForMime('data:text/plain;base64,SGVsbG8sIFdvcmxkIQ==', 'image')).toBeNull();
-
- // Test with empty and null URLs
- expect(sanitizeUrlForMime('', 'image')).toBeNull();
- expect(sanitizeUrlForMime(null, 'image')).toBeNull();
-
- // Test with valid data URL but wrong mime type
- expect(sanitizeUrlForMime('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA', 'text')).toBeNull();
-});
-
-test('urlAsAttachment', () => {
- // Test with a simple URL
- expect(urlAsAttachment('http://example.com')).toBe('http://example.com?asatt=1');
-
- // Test with a URL that already has a query parameter
- expect(urlAsAttachment('http://example.com?param=value')).toBe('http://example.com?param=value&asatt=1');
-
- // Test with a URL that has a fragment
- expect(urlAsAttachment('http://example.com#fragment')).toBe('http://example.com?asatt=1#fragment');
-
- // Test with a URL that has both query parameters and a fragment
- expect(urlAsAttachment('http://example.com?param=value#fragment')).toBe('http://example.com?param=value&asatt=1#fragment');
-
- // Test with a data URL
- expect(urlAsAttachment('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA')).toBe('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA');
-
- // Test with a blob URL
- expect(urlAsAttachment('blob:http://example.com/550e8400-e29b-41d4-a716-446655440000')).toBe('blob:http://example.com/550e8400-e29b-41d4-a716-446655440000');
-});
-
diff --git a/src/version.js b/src/version.js
deleted file mode 100644
index 103eb663..00000000
--- a/src/version.js
+++ /dev/null
@@ -1,3 +0,0 @@
-// This is a generated file. Don't edit.
-
-export const PACKAGE_VERSION = "0.25.3";
diff --git a/src/views/acc-general-view.jsx b/src/views/acc-general-view.jsx
deleted file mode 100644
index 5a6d9f0f..00000000
--- a/src/views/acc-general-view.jsx
+++ /dev/null
@@ -1,156 +0,0 @@
-// Edit account parameters.
-import React from 'react';
-import { FormattedMessage } from 'react-intl';
-
-export default class AccGeneralView extends React.PureComponent {
- constructor(props) {
- super(props);
-
- this.state = {
- colorSchema: props.colorSchema || 'auto',
- textSize: (props.textSize * 10) || '100',
- sendOnEnter: props.sendOnEnter || 'plain',
- };
-
- this.isMac = ((navigator.userAgentData && navigator.userAgentData.platform)
- || navigator.platform).toLowerCase().startsWith('mac');
- this.handleColorSchemaSelected = this.handleColorSchemaSelected.bind(this);
- this.handleTextSizeChanged = this.handleTextSizeChanged.bind(this);
- this.handleSendOnEnterSelected = this.handleSendOnEnterSelected.bind(this);
- }
-
- handleColorSchemaSelected(e) {
- this.setState({colorSchema: e.currentTarget.value});
- this.props.onChangeColorSchema(e.currentTarget.value);
- }
-
- handleTextSizeChanged(e) {
- // In %%.
- this.setState({textSize: e.currentTarget.value});
- // Convert %% to pt, 100% = 10pt.
- this.props.onTextSizeChanged((e.currentTarget.value / 10) | 0);
- }
-
- handleSendOnEnterSelected(e) {
- this.setState({sendOnEnter: e.currentTarget.value});
- this.props.onSendOnEnterChanged(e.currentTarget.value);
- }
-
- render() {
- return (
-
- );
- }
-};
diff --git a/src/views/acc-notifications-view.jsx b/src/views/acc-notifications-view.jsx
deleted file mode 100644
index c00e1f56..00000000
--- a/src/views/acc-notifications-view.jsx
+++ /dev/null
@@ -1,51 +0,0 @@
-// Edit account parameters.
-import React from 'react';
-import { FormattedMessage } from 'react-intl';
-
-import CheckBox from '../widgets/checkbox.jsx';
-
-export default class AccNotificationsView extends React.PureComponent {
- constructor(props) {
- super(props);
-
- this.handleCheckboxClick = this.handleCheckboxClick.bind(this);
- }
-
- handleCheckboxClick(what, checked) {
- if (what == 'sound') {
- this.props.onToggleMessageSounds(checked);
- } else if (what == 'alert') {
- this.props.onTogglePushNotifications(checked);
- }
- }
-
- render() {
- return (
-
-
-
-
-
-
-
-
-
- {this.props.desktopAlertsEnabled ?
-
- :
-
- }
-
-
-
-
- );
- }
-};
diff --git a/src/views/acc-security-view.jsx b/src/views/acc-security-view.jsx
deleted file mode 100644
index c4989b5f..00000000
--- a/src/views/acc-security-view.jsx
+++ /dev/null
@@ -1,209 +0,0 @@
-// Edit account parameters.
-import React from 'react';
-import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
-
-import CheckBox from '../widgets/checkbox.jsx';
-import InPlaceEdit from '../widgets/in-place-edit.jsx';
-import PermissionsEditor from '../widgets/permissions-editor.jsx';
-
-const messages = defineMessages({
- delete_account: {
- id: 'delete_account',
- defaultMessage: 'Delete account',
- description: 'Title for delete account warning'
- },
- delete_account_warning: {
- id: 'delete_account_warning',
- defaultMessage: 'Are you sure you want to delete your account? It cannot be undone.',
- description: 'Warning message when deleting an account'
- }
-});
-
-class AccSecurityView extends React.Component {
- constructor(props) {
- super(props);
-
- const me = this.props.tinode.getMeTopic();
- let blockedCount = 0;
- me.contacts((c) => {
- if (c.acs && !c.acs.isJoiner()) {
- blockedCount ++;
- }
- });
- const defacs = me.getDefaultAccess();
- this.state = {
- auth: defacs ? defacs.auth : null,
- anon: defacs ? defacs.anon : null,
- showPermissionEditorFor: undefined,
- blockedCount: blockedCount,
- };
-
- this.handlePasswordUpdate = this.handlePasswordUpdate.bind(this);
- this.handleLaunchPermissionsEditor = this.handleLaunchPermissionsEditor.bind(this);
- this.handleHidePermissionsEditor = this.handleHidePermissionsEditor.bind(this);
- this.handlePermissionsChanged = this.handlePermissionsChanged.bind(this);
- this.handleDeleteAccount = this.handleDeleteAccount.bind(this);
- this.handleLogoutRequest = this.handleLogoutRequest.bind(this);
- this.handleCheckboxClick = this.handleCheckboxClick.bind(this);
- }
-
- handlePasswordUpdate(pwd) {
- this.setState({password: pwd});
- this.props.onUpdatePassword(pwd);
- }
-
- handleLaunchPermissionsEditor(which) {
- this.setState({
- showPermissionEditorFor: which,
- editedPermissions: this.state[which]
- });
- }
-
- handleHidePermissionsEditor() {
- this.setState({showPermissionEditorFor: undefined});
- }
-
- handlePermissionsChanged(perm) {
- let defacs = {};
- defacs[this.state.showPermissionEditorFor] = perm;
- this.props.onUpdateAccountDesc('me', undefined, undefined, defacs);
-
- let newState = {showPermissionEditorFor: undefined};
- newState[this.state.showPermissionEditorFor] = perm;
- this.setState(newState);
- }
-
- handleCheckboxClick(what, checked) {
- this.props.onToggleIncognitoMode(checked);
- }
-
- handleDeleteAccount(e) {
- e.preventDefault();
- const {formatMessage} = this.props.intl;
- this.props.onShowAlert(
- formatMessage(messages.delete_account), // title
- formatMessage(messages.delete_account_warning), // content
- (_ => this.props.onDeleteAccount()), // onConfirm
- null, // use default text "OK"
- true, // Show Reject button
- null // use default text "Cancel"
- );
- }
-
- // "退出登录"这个按钮原来是 onClick 里直接 `this.props.onLogout()`,没有任何二次确认——同一个文件里
- // 紧挨着的"删除帐户"按钮反而是走 onShowAlert 弹确认框的(见上面 handleDeleteAccount)。本页
- // ('security' 路由)并不是废弃页面:account-settings-view.jsx、info-view.jsx 的"安全"入口,以及
- // 第2章系统消息列表里"前往安全设置"跳转按钮(message-center-view.jsx)都会真的落到这个页面,
- // 跟 personal-settings-view.jsx 的"设置"页(那边的退出登录已经在别的改造里换成了自己的
- // ConfirmSheet/LogoutConfirm)是两条并存的独立路径——用户从系统消息点"前往安全设置"进来,单击一下
- // 图标就会被立即退出登录,且没有任何撤销机会。改成跟"删除帐户"一样走 onShowAlert,文案与
- // personal-settings-view.jsx 的 LogoutConfirm 保持一致,不新造一套措辞。
- handleLogoutRequest(e) {
- e.preventDefault();
- this.props.onShowAlert(
- '退出登录',
- '确定要退出当前登录吗?',
- (_ => this.props.onLogout()),
- null, // use default text "OK"
- true, // Show Reject button
- null // use default text "Cancel"
- );
- }
-
- render() {
- return (
- <>{this.state.showPermissionEditorFor ?
-
- :
-
-
-
-
-
-
- {
- (password_unchanged) =>
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- this.props.onNavigate('realname')}>
- verified_user 实名认证 chevron_right
-
- this.props.onNavigate('identity-check')}>
- phonelink_lock 身份验证 chevron_right
-
- this.props.onNavigate('independent-password')}>
- password 设置独立密码 chevron_right
-
-
-
-
-
-
-
-
-
-
-
-
-
Auth: {this.state.auth}
-
Anon: {this.state.anon}
-
-
- {this.state.blockedCount > 0 ?
- <>
-
-
- >
- : null }
-
- }>
- );
- }
-};
-
-export default injectIntl(AccSecurityView);
diff --git a/src/views/acc-support-view.jsx b/src/views/acc-support-view.jsx
deleted file mode 100644
index 546875b6..00000000
--- a/src/views/acc-support-view.jsx
+++ /dev/null
@@ -1,86 +0,0 @@
-// Edit account parameters.
-import React from 'react';
-import { FormattedMessage } from 'react-intl';
-
-import { APP_NAME, LINK_CONTACT_US, LINK_PRIVACY_POLICY, LINK_TERMS_OF_SERVICE } from '../config.js';
-import { getContactUs } from '../lib/im-api.js';
-
-// Admin-managed "contact us" entries (dictionary type im_contact_us): emails get mailto:, phone
-// numbers get tel:, anything else renders as plain (non-navigating) text.
-const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
-const PHONE_PATTERN = /^\+?[0-9][0-9\- ]{4,}$/;
-
-// Renders as a real link when a destination is configured, or as inert (non-navigating) text
-// otherwise -- same treatment auth-onboarding-view.jsx already gives its own placeholder
-// 用户协议/隐私政策 links when there's no real page to send the user to.
-function FlatLink({href, icon, children}) {
- return href ?
- {icon} {children} :
- {icon} {children} ;
-}
-
-export default class AccSupportView extends React.PureComponent {
- constructor(props) {
- super(props);
- this.state = {
- contactItems: []
- };
- }
-
- componentDidMount() {
- // Entries come from the im-server /contact-us endpoint (admin dictionary im_contact_us).
- // On failure the static config fallback below stays in place; no toast for a support page.
- getContactUs().then(data => {
- const items = (data && Array.isArray(data.items)) ?
- data.items.filter(it => it && it.label && it.value) : [];
- this.setState({contactItems: items});
- }).catch(() => {});
- }
-
- render() {
- const {contactItems} = this.state;
- return (
-
-
- {contactItems.length > 0 ?
- contactItems.map((item, idx) => {
- const value = item.value;
- const href = EMAIL_PATTERN.test(value) ? 'mailto:' + value :
- (PHONE_PATTERN.test(value) ? 'tel:' + value.replace(/[^\d+]/g, '') : null);
- return
- {item.label}
- ;
- })
- :
-
-
-
- }
-
-
-
-
-
-
-
-
-
-
-
- {APP_NAME}
-
-
-
- {this.props.serverVersion}
-
-
-
- {this.props.serverAddress}
-
-
-
- );
- }
-};
diff --git a/src/views/account-settings-view.jsx b/src/views/account-settings-view.jsx
deleted file mode 100644
index ac80f30a..00000000
--- a/src/views/account-settings-view.jsx
+++ /dev/null
@@ -1,172 +0,0 @@
-// Edit account parameters.
-import React from 'react';
-import { FormattedMessage } from 'react-intl';
-import { parsePhoneNumber } from 'libphonenumber-js/mobile';
-
-import AvatarUpload from '../widgets/avatar-upload.jsx';
-import BadgeList from '../widgets/badge-list.jsx';
-import CredentialEdit from '../widgets/credential-edit.jsx';
-
-import { makeImageUrl } from '../lib/blob-helpers.js';
-import { truncateString } from '../lib/strformat.js';
-import { MAX_TITLE_LENGTH, MAX_TOPIC_DESCRIPTION_LENGTH } from '../config.js';
-
-function _clip(str, length) {
- return truncateString(str, length);
-}
-
-export default class AccountSettingsView extends React.Component {
- constructor(props) {
- super(props);
-
- const me = this.props.tinode.getMeTopic();
- this.state = {
- fullName: _clip(me.public ? me.public.fn : undefined, MAX_TITLE_LENGTH),
- description: _clip(me.public ? me.public.note : undefined, MAX_TOPIC_DESCRIPTION_LENGTH),
- avatar: makeImageUrl(me.public ? me.public.photo : null),
- credentials: me.getCredentials() || [],
- credEdit: undefined,
- alias: ((me.tags() || []).find(tag => tag.startsWith('alias:')) || '').substring(6),
- };
- }
-
- componentDidMount() {
- const me = this.props.tinode.getMeTopic();
- me.onCredsUpdated = _ => this.setState({credentials: me.getCredentials()});
- }
-
- componentWillUnmount() {
- const me = this.props.tinode.getMeTopic();
- me.onCredsUpdated = null;
- }
-
- render() {
- if (this.state.credEdit) {
- return (
- this.setState({credEdit: undefined})}
- onError={this.props.onError} />
- );
- }
-
- // Count how many times each method is validated.
- const validated = {};
- this.state.credentials.forEach(cred => {
- if (cred.done) {
- validated[cred.meth] = (validated[cred.meth] || 0) + 1;
- }
- });
-
- const credentials = [];
- this.state.credentials.forEach((cred, idx) => {
- if (!['email', 'tel'].includes(cred.meth)) {
- // Skip unknown methods.
- return ;
- }
-
- let val = cred.val
- if (cred.meth == 'tel') {
- const number = parsePhoneNumber(cred.val);
- val = number ? number.formatInternational() : cred.val;
- }
-
- credentials.push(
- );
- });
- if (credentials.length > 0) {
- // Add title as the first element.
- credentials.unshift(
-
-
- );
- }
-
- return (
-
- );
- }
-};
diff --git a/src/views/auth-onboarding-view.jsx b/src/views/auth-onboarding-view.jsx
deleted file mode 100644
index 35948f18..00000000
--- a/src/views/auth-onboarding-view.jsx
+++ /dev/null
@@ -1,865 +0,0 @@
-import React from 'react';
-
-import Navigation from '../lib/navigation.js';
-import { BASE_APP_NAME } from '../config.js';
-import {
- resolveTenant, submitTenantApplication, submitTenantEntryFeedback, uploadPublicFile,
- requestVerificationCode, verifyRegisterCode, verifyPasswordResetCode, confirmPasswordReset,
- createSecurityChallenge, verifySecurityChallenge, getRealnameStatus, submitRealname, setIndependentPassword,
- getYouthMode, enableYouthMode
-} from '../lib/im-api.js';
-
-const USERNAME_RE = /^[a-z0-9]{1,10}$/;
-const PHONE_RE = /^\d{11}$/;
-const INVITE_RE = /^[A-Za-z]{5}$/;
-const CREDIT_CODE_RE = /^[0-9A-Z]{18}$/;
-const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
-
-// params is optional and additive only -- every existing call site that passes just `view` keeps
-// producing the exact same bare hash as the old `Navigation.navigateTo(view)` did.
-const navigate = (view, params) => Navigation.navigateTo(Navigation.composeUrl([view], params || {}));
-
-function Icon({children, className = ''}) {
- return {children} ;
-}
-
-function AppMark({tenantName}) {
- return
-
{BASE_APP_NAME[0]}
-
{tenantName || BASE_APP_NAME}
-
;
-}
-
-function Header({title, back = 'tenant'}) {
- return
- { if (!Navigation.goBack()) navigate(back); }} aria-label="返回">
- arrow_back_ios_new
-
- {title}
-
- ;
-}
-
-function Field({icon, onIconClick, action, error, className = '', ...props}) {
- return
-
- {icon ? (onIconClick ?
- {icon} :
- {icon} ) : null}
-
- {action}
-
- {error ?
{error}
: null}
-
;
-}
-
-function PasswordField(props) {
- const [visible, setVisible] = React.useState(false);
- return setVisible(!visible)}
- aria-label={visible ? '隐藏密码' : '显示密码'}>{visible ? 'visibility' : 'visibility_off'} } />;
-}
-
-function Agreement({checked, onChange, prefix = `同意${BASE_APP_NAME}`}) {
- return
- onChange(e.target.checked)} />
- {prefix} e.preventDefault()}>《用户协议》 和 e.preventDefault()}>《隐私政策》
- ;
-}
-
-function Toast({message, onDone}) {
- React.useEffect(() => {
- if (!message) return undefined;
- const timer = window.setTimeout(onDone, 2800);
- return () => window.clearTimeout(timer);
- }, [message, onDone]);
- return message ? check_circle {message}
: null;
-}
-
-// Shared alert for errors coming back from a real API/WS call (as opposed to local form validation,
-// which each form already reports inline per-field).
-function FormAlert({text}) {
- return text ? error_outline {text}
: null;
-}
-
-function TenantEntry({onTenantResolved}) {
- const [code, setCode] = React.useState(sessionStorage.getItem('im-tenant-code') || '');
- const [error, setError] = React.useState('');
- const [busy, setBusy] = React.useState(false);
-
- const submit = async e => {
- e.preventDefault();
- const value = code.trim();
- if (!value || value.length > 64) {
- setError(value ? '企业码不能超过 64 个字符' : '请输入企业码');
- return;
- }
- setBusy(true);
- setError('');
- try {
- const data = await resolveTenant(value);
- // Kept for the mock UI's own display of the tenant name while on this screen; the durable
- // context (read by every subsequent /v1/im/* call and by the {hi} tenant field) is persisted
- // by onTenantResolved.
- sessionStorage.setItem('im-tenant-code', data.tenant.code || value);
- sessionStorage.setItem('im-tenant-name', data.tenant.name || data.tenant.code || value);
- onTenantResolved(data, value);
- navigate('login');
- } catch (err) {
- // Per the product spec, tenant-resolve failures are shown as one generic message
- // regardless of reason (not found vs. disabled vs. rate limited).
- setError('企业码不存在或已停用,请核实后重试');
- } finally {
- setBusy(false);
- }
- };
-
- return
-
navigate('enterprise-register')}>企业注册
-
-
navigate('enterprise-feedback')}>遇到问题?
-
;
-}
-
-function Login({tenantName, login, loginDisabled, persist, errorText, errorLevel, onLoginRequest, onPersistenceChange, onError}) {
- const [account, setAccount] = React.useState(login || '');
- const [password, setPassword] = React.useState('');
- const [agreed, setAgreed] = React.useState(false);
-
- // The prototype (docs/images/登录页面.png) has no "stay logged in" checkbox -- a consumer IM app
- // just keeps you signed in. Opt in silently instead of exposing the old debug-era toggle.
- React.useEffect(() => {
- if (!persist) {
- onPersistenceChange(true);
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
-
- // Real login failures (wrong password, disabled account, ...) clear the password and keep the account,
- // matching the product spec.
- React.useEffect(() => {
- if (errorLevel === 'err' && errorText) {
- setPassword('');
- }
- }, [errorText, errorLevel]);
-
- const submit = e => {
- e.preventDefault();
- if (!account.trim() || !password || !agreed || loginDisabled) return;
- onLoginRequest(account.trim(), password);
- };
-
- return
-
{ onError && onError(); navigate('tenant'); }}>arrow_back_ios_new 切换企业
-
-
-
;
-}
-
-function EnterpriseRegister() {
- const [creditCode, setCreditCode] = React.useState('');
- const [email, setEmail] = React.useState('');
- const [file, setFile] = React.useState(null);
- const [errors, setErrors] = React.useState({});
- const [submitting, setSubmitting] = React.useState(false);
- const [submitted, setSubmitted] = React.useState(false);
- const [reviewDays, setReviewDays] = React.useState(1);
- const [toast, setToast] = React.useState('');
-
- React.useEffect(() => () => file && URL.revokeObjectURL(file.url), [file]);
-
- const chooseFile = e => {
- const selected = e.target.files[0];
- if (!selected) return;
- if (selected.size > 10 * 1024 * 1024 || !['image/jpeg', 'image/png', 'image/webp', 'application/pdf'].includes(selected.type)) {
- setErrors({...errors, file: '支持 JPG、PNG、WebP、PDF,文件不超过 10MB'});
- return;
- }
- if (file) URL.revokeObjectURL(file.url);
- setFile({name: selected.name, type: selected.type, url: URL.createObjectURL(selected), blob: selected});
- setErrors({...errors, file: ''});
- };
-
- const submit = async e => {
- e.preventDefault();
- const next = {};
- if (!CREDIT_CODE_RE.test(creditCode)) next.creditCode = '请输入 18 位统一社会信用代码';
- if (email && !EMAIL_RE.test(email)) next.email = '请输入有效的企业邮箱';
- setErrors(next);
- if (Object.keys(next).length) return;
-
- setSubmitting(true);
- try {
- let licenseFileId = '';
- if (file) {
- const uploaded = await uploadPublicFile(file.blob, 'enterprise_license');
- licenseFileId = uploaded.file_id;
- }
- const data = await submitTenantApplication({
- credit_code: creditCode,
- enterprise_email: email,
- license_file_id: licenseFileId,
- tenant_code_hint: sessionStorage.getItem('im-tenant-code') || ''
- });
- // Prototype (docs/images/企业注册-填写注册信息.png) states this in business days, not hours.
- setReviewDays(Math.max(1, Math.round((data.estimated_review_hours || 24) / 24)));
- setSubmitted(true);
- setToast('提交成功,我们会尽快联系你');
- } catch (err) {
- setErrors({...next, form: err.message || '提交失败,请稍后重试'});
- } finally {
- setSubmitting(false);
- }
- };
-
- return
-
-
- {/* .active marks a step (and the connector after it) as reached, not "this is the current
- step" -- steps stay lit once passed. There's no separate server-tracked "确定信息" state
- (submitting the form *is* confirming it), so step 2 only ever gets skipped straight from
- 1 to 3; without submitted also keeping step 1 lit, submitting made it look like step 1 had
- been undone instead of completed. */}
-
- 1 填写企业信息
- 2 确定信息
- 3 提交审核
-
-
-
-
setToast('')} />
- ;
-}
-
-// Per docs/images/企业注册-问题反馈.png: titled "意见反馈", 300-char textarea with the instructions
-// as its placeholder (not a separate label), "图片(N/9)" counter-style label, and no contact field.
-function EnterpriseFeedback() {
- const [description, setDescription] = React.useState('');
- const [files, setFiles] = React.useState([]);
- const [error, setError] = React.useState('');
- const [submitting, setSubmitting] = React.useState(false);
- const [toast, setToast] = React.useState('');
-
- const addFiles = e => {
- const selected = Array.from(e.target.files || []).slice(0, 9 - files.length);
- const valid = selected.filter(file => file.size <= 10 * 1024 * 1024 && ['image/jpeg', 'image/png', 'image/webp'].includes(file.type));
- setFiles(files.concat(valid.map(file => ({name: file.name, url: URL.createObjectURL(file), blob: file}))));
- };
- const submit = async e => {
- e.preventDefault();
- const length = description.trim().length;
- if (length < 10 || length > 300) {
- setError('问题描述需为 10 至 300 个字符');
- return;
- }
- setSubmitting(true);
- try {
- const screenshotFileIds = [];
- for (const item of files) {
- const uploaded = await uploadPublicFile(item.blob, 'tenant_entry_feedback');
- screenshotFileIds.push(uploaded.file_id);
- }
- await submitTenantEntryFeedback({
- description: description.trim(),
- tenant_code_hint: sessionStorage.getItem('im-tenant-code') || '',
- screenshot_file_ids: screenshotFileIds
- });
- setToast('已收到反馈,我们会尽快处理');
- files.forEach(item => URL.revokeObjectURL(item.url));
- setDescription(''); setFiles([]); setError('');
- } catch (err) {
- setError(err.message || '提交失败,请稍后重试');
- } finally {
- setSubmitting(false);
- }
- };
-
- return
-
-
-
-
-
setToast('')} />
- ;
-}
-
-// Requests a phone verification code (scene: 'register' | 'reset_password' | 'security') and reports the
-// resulting challenge_id upward -- the caller needs it to verify the code the user types in.
-function VerificationButton({phone, scene = 'register', onChallenge, onError}) {
- const [seconds, setSeconds] = React.useState(0);
- const [busy, setBusy] = React.useState(false);
- React.useEffect(() => {
- if (!seconds) return undefined;
- const timer = window.setTimeout(() => setSeconds(s => s - 1), 1000);
- return () => window.clearTimeout(timer);
- }, [seconds]);
-
- const click = async () => {
- if (busy || seconds > 0 || !PHONE_RE.test(phone)) return;
- setBusy(true);
- try {
- const data = await requestVerificationCode(scene, phone);
- onChallenge(data.challenge_id);
- setSeconds(data.resend_after_seconds || 60);
- } catch (err) {
- onError && onError(err.message || '获取验证码失败');
- if (err.details && err.details.retry_after_seconds) {
- setSeconds(err.details.retry_after_seconds);
- }
- if (err.details && err.details.challenge_id) {
- // Rate-limited but a still-valid challenge already exists for this phone/scene -- reuse it.
- onChallenge(err.details.challenge_id);
- }
- } finally {
- setBusy(false);
- }
- };
-
- return 0 || !PHONE_RE.test(phone)} onClick={click}>
- {seconds > 0 ? `${seconds}s` : busy ? '发送中…' : '获取验证码'}
- ;
-}
-
-// Per the actual prototype screenshots (docs/images/注册页面.png), all fields are shown together --
-// no phone-vs-username mode toggle. "Phone takes precedence" (per the spec text) is validation-only:
-// if a phone is filled it must be verified; the invite code is only required for a username-only
-// signup (matching the mock's original submit-time validation, now with real backend calls behind it).
-function Register({errorText, errorLevel, persist, onImRegisterRequest, onPersistenceChange}) {
- const [form, setForm] = React.useState({username: '', phone: '', code: '', password: '', invite: ''});
- const [challengeId, setChallengeId] = React.useState('');
- const [agreed, setAgreed] = React.useState(false);
- const [errors, setErrors] = React.useState({});
- const [submitting, setSubmitting] = React.useState(false);
- const change = key => e => setForm({...form, [key]: e.target.value});
- const usingPhone = !!form.phone;
-
- // Same silent "stay logged in" opt-in Login has (see its own copy of this effect for why there's
- // no visible checkbox) -- missing here meant every account created by registering (as opposed to
- // logging in) never had keep-logged-in/auth-token set, so handleLoginSuccessful() never persisted
- // the session: the very next refresh silently lost myUserId with no redirect to the login page
- // and no error, just a permanently empty messages-home shell. Every fresh registration hit this on
- // its first reload.
- React.useEffect(() => {
- if (!persist) {
- onPersistenceChange(true);
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
-
- // onImRegisterRequest (tinode-web.jsx's handleImRegisterRequest -> a WS connect+createAccountBasic
- // chain) isn't awaited and doesn't return a promise -- on the username/invite-code path (no
- // verifyRegisterCode await ahead of it) `submit` returns and its `finally` clears `submitting`
- // within milliseconds, long before that WS round-trip finishes, leaving the button re-clickable
- // and able to fire a second concurrent registration. The parent's only failure signal is these
- // props changing, so hold `submitting` until either that happens or the phone-path's own catch
- // already handled it (success unmounts this component regardless).
- React.useEffect(() => {
- if (errorLevel === 'err' && errorText) setSubmitting(false);
- }, [errorText, errorLevel]);
-
- const submit = async e => {
- e.preventDefault();
- const next = {};
- if (!form.username && !form.phone) next.username = '用户名和手机号至少填写一项';
- if (form.username && !USERNAME_RE.test(form.username)) next.username = '用户名仅支持 1-10 位小写字母或数字';
- if (usingPhone) {
- if (!PHONE_RE.test(form.phone)) next.phone = '请输入 11 位手机号';
- if (!challengeId) next.code = '请先获取验证码';
- else if (!/^\d{6}$/.test(form.code)) next.code = '请输入 6 位验证码';
- } else if (!INVITE_RE.test(form.invite)) {
- next.invite = '用户名注册需填写 5 位字母邀请码';
- }
- if (form.password.length < 8) next.password = '密码至少 8 位';
- setErrors(next);
- if (Object.keys(next).length) return;
-
- setSubmitting(true);
- try {
- if (usingPhone) {
- const verified = await verifyRegisterCode(challengeId, form.code);
- // The username field is optional even on the phone path -- filling in both used to silently
- // drop the typed username (always sent '' and always logged in as the phone), because the
- // server only auto-assigns a random handle when username is actually empty
- // (server/user.go resolveRegistrationAccount: username!="" && phone!="" => account=username,
- // secretAccount=username; only username=="" falls back to the phone-derived random handle).
- // Per the protocol spec, the phone number itself must never become the display name -- leave
- // it blank when no username was typed either (server assigns a random handle; the user can
- // set a nickname afterwards).
- const secretAccount = form.username || ('+86' + form.phone);
- onImRegisterRequest(secretAccount, form.password, form.username || '',
- // 邀请码不能在手机号路径上丢掉:后端 user.go:175-181 对邀请码的校验和 RecordUse 都不区分
- // 注册路径(`if inviteCode != ""` 就会校验并记录),而且 user.go:169 的
- // inviteRequired 在短信通道关闭时对手机号注册同样为真。UI 上明明摆着邀请码输入框,
- // 原来却硬传空串,用户填了也被静默丢弃,邀请来源统计全丢。
- {username: form.username || '', phone: form.phone, invite_code: form.invite || ''},
- {meth: 'tel', val: form.phone, resp: verified.verification_token, params: {challenge_id: challengeId}});
- } else {
- onImRegisterRequest(form.username, form.password, form.username,
- {username: form.username, phone: '', invite_code: form.invite}, null);
- }
- // On success the parent (tinode-web.jsx handleImRegisterRequest -> handleLoginSuccessful)
- // navigates away (unmounting this component, so `submitting` no longer matters); on failure
- // it surfaces errorText/errorLevel, which the effect above clears `submitting` on. No
- // `finally` here on purpose -- onImRegisterRequest isn't awaited (it's not a promise), so a
- // `finally` would clear `submitting` within milliseconds on the username/invite-code path,
- // long before that request's own WS round-trip resolves, defeating the double-submit guard.
- } catch (err) {
- setErrors({...next, code: err.message || '验证码错误或已过期'});
- setSubmitting(false);
- }
- };
-
- return
-
-
-
- setForm({...form, username: e.target.value.replace(/[^a-z0-9]/g, '').slice(0, 10)})}
- placeholder="请输入用户名" error={errors.username} autoComplete="username" />
- setForm({...form, phone: e.target.value.replace(/\D/g, '').slice(0, 11)})}
- placeholder="请输入手机号" error={errors.phone} inputMode="numeric" />
- setForm({...form, code: e.target.value.replace(/\D/g, '').slice(0, 6)})}
- placeholder="请输入短信验证码" inputMode="numeric" error={errors.code}
- action={ setErrors(e => ({...e, code: msg}))} />} />
-
- {/* 邀请码大小写敏感(server/store/... 的唯一索引是 ascii_bin 排序),不能像信用代码那样
- .toUpperCase() 统一大小写;autoCapitalize="off" 只是防止移动端输入法把首字母偷偷转大写,
- 没有它,一个真实有效、首字母是小写的邀请码会被静默改错且用户毫无察觉。 */}
- setForm({...form, invite: e.target.value.replace(/[^A-Za-z]/g, '').slice(0, 5)})}
- placeholder="请输入邀请码(非必填)" error={errors.invite} autoCapitalize="off" autoCorrect="off" />
- {/* 需求文档 1.4 第5条硬性要求:"用户名方式不发送短信,必须填写有效邀请码;注册前提示
- '未绑定手机号无法自助找回密码,忘记密码后只能联系企业管理员重置'"——原来这句话在整个
- 注册表单里完全没有出现过(不是视觉遗漏,是从未写过对应的 JSX),用户名注册的人完全不知道
- 自己放弃了手机号找回密码的能力。只在用户名路径(未填手机号)显示,不打扰手机号注册路径
- (那条路径本来就能自助找回密码,不需要这条提醒),参考图(注册页面.png)本身是两个字段
- 都还没交互的初始态,不代表这条路径相关提示不应该出现。 */}
- {!usingPhone ? 未绑定手机号无法自助找回密码,忘记密码后只能联系企业管理员重置
: null}
-
- {submitting ? '注册中…' : '注册'}
-
- 已有{BASE_APP_NAME}账号? navigate('login')}>登录
-
-
-
;
-}
-
-function PasswordReset() {
- const [form, setForm] = React.useState({phone: '', code: '', password: '', confirm: ''});
- const [challengeId, setChallengeId] = React.useState('');
- const [error, setError] = React.useState('');
- const [submitting, setSubmitting] = React.useState(false);
- const [toast, setToast] = React.useState('');
- const change = key => e => setForm({...form, [key]: e.target.value});
-
- const submit = async e => {
- e.preventDefault();
- if (!PHONE_RE.test(form.phone)) return setError('请输入 11 位手机号');
- if (!challengeId || !/^\d{6}$/.test(form.code)) return setError('请先获取并输入 6 位验证码');
- // Matches Register's rule (>= 8, no upper bound -- the backend's own checkPasswordPolicy only
- // enforces a minimum length). This page used to also reject anything over 20 characters, a limit
- // invented here with no backend counterpart: a password set during registration could exceed
- // it, then resetting it back to that same value through this flow would be rejected -- a
- // self-inflicted lockout for password reset's own sake.
- if (form.password.length < 8) return setError('密码至少 8 位');
- if (form.password !== form.confirm) return setError('两次输入的密码不一致');
-
- setSubmitting(true);
- setError('');
- try {
- const verified = await verifyPasswordResetCode(challengeId, form.code);
- await confirmPasswordReset(verified.reset_token, form.password);
- setToast('密码修改成功,请重新登录');
- window.setTimeout(() => navigate('login'), 1200);
- } catch (err) {
- setError(err.message || '验证码错误或已过期');
- setSubmitting(false);
- }
- };
- return ;
-}
-
-// Precedes 1.11 (setting the independent password): resolves the account's own verified phone
-// server-side (POST /v1/im/security/challenges never takes a phone number from the client) and, on
-// successful verify, stashes the resulting action_token for the next screen to spend.
-const IDENTITY_CHECK_SCENE = 'set_independent_password';
-
-function IdentityCheck({authenticated, hashParams}) {
- // Carries through whichever youth-mode flow (enable/disable) sent the user here via "忘记独立
- // 密码?", if any -- IndependentPassword needs it to know whether to auto-continue into actually
- // enabling youth mode, since the server hands back an enable-flow token unconditionally on every
- // successful password (re)set regardless of why the caller got here. Absent (undefined) for the
- // unrelated generic "身份验证" entry in acc-security-view.jsx, which should never touch youth mode.
- const mode = hashParams && hashParams.ym;
- const [phoneMask, setPhoneMask] = React.useState('');
- const [challengeId, setChallengeId] = React.useState('');
- const [code, setCode] = React.useState('');
- const [seconds, setSeconds] = React.useState(0);
- const [busy, setBusy] = React.useState(false);
- const [error, setError] = React.useState('');
-
- React.useEffect(() => {
- if (!seconds) return undefined;
- const timer = window.setTimeout(() => setSeconds(s => s - 1), 1000);
- return () => window.clearTimeout(timer);
- }, [seconds]);
-
- const requestCode = React.useCallback(async () => {
- setBusy(true);
- setError('');
- try {
- const data = await createSecurityChallenge(IDENTITY_CHECK_SCENE);
- setChallengeId(data.challenge_id);
- setPhoneMask(data.phone_mask);
- setSeconds(data.resend_after_seconds || 60);
- } catch (err) {
- setError(err.reason === 'REALNAME_REQUIRED' ? '需要先完成实名认证并绑定手机号' : (err.message || '获取验证码失败'));
- // 限流(429)时服务端会连同还没过期的 challenge_id 和剩余冷却秒数一起返回
- // (im_http.go:6361-6367)。原来这里只把错误文案显示出来就完了,challengeId 一直是空串,
- // 于是 submit() 开头的 `if (!challengeId) return` 会静默吞掉点击——按钮看着能点,
- // 输完 6 位验证码点"确定"毫无反应,也没有任何提示。同文件的 VerificationButton
- // 早就处理对了,这里是漏抄。
- if (err.details && err.details.retry_after_seconds) {
- setSeconds(err.details.retry_after_seconds);
- }
- if (err.details && err.details.challenge_id) {
- setChallengeId(err.details.challenge_id);
- }
- if (err.details && err.details.phone_mask) {
- setPhoneMask(err.details.phone_mask);
- }
- } finally {
- setBusy(false);
- }
- }, []);
-
- React.useEffect(() => { requestCode(); }, [requestCode]);
-
- const submit = async e => {
- e.preventDefault();
- if (!challengeId || code.length !== 6 || busy) return;
- setBusy(true);
- try {
- const data = await verifySecurityChallenge(challengeId, code);
- sessionStorage.setItem('im-action-token:' + IDENTITY_CHECK_SCENE, JSON.stringify(data));
- navigate('independent-password', mode ? {ym: mode} : undefined);
- } catch (err) {
- setError(err.message || '验证码错误');
- } finally {
- setBusy(false);
- }
- };
-
- return ;
-}
-
-function Realname({authenticated = false}) {
- // Real status (unverified/pending/verified/rejected) drives the view -- the old mock's `verified`
- // prop toggle is gone, both 'realname' and 'realname-verified' routes render this and let the
- // GET /v1/im/security/realname response decide what to show.
- const [status, setStatus] = React.useState('loading');
- const [maskedName, setMaskedName] = React.useState('');
- const [maskedIdNumber, setMaskedIdNumber] = React.useState('');
- const [name, setName] = React.useState('');
- const [idNumber, setIdNumber] = React.useState('');
- const [error, setError] = React.useState('');
- const [busy, setBusy] = React.useState(false);
-
- React.useEffect(() => {
- getRealnameStatus().then(data => {
- setStatus(data.status);
- setMaskedName(data.masked_name || '');
- setMaskedIdNumber(data.masked_id_number || '');
- }).catch(err => { setError(err.message || '加载实名状态失败'); setStatus('load_error'); });
- }, []);
-
- const editable = status === 'unverified' || status === 'rejected';
-
- const submit = async e => {
- e.preventDefault();
- if (name.trim().length < 2 || !/^\d{17}[\dXx]$/.test(idNumber)) {
- setError('请输入有效姓名和 18 位身份证号码'); return;
- }
- setBusy(true);
- try {
- // Backend's validIMRealName only accepts the literal 'cn_id' -- 'id_card' was rejected outright
- // with a 400 every time, so real-name submission never actually worked.
- const data = await submitRealname({name: name.trim(), id_type: 'cn_id', id_number: idNumber.toUpperCase()});
- setStatus(data.status || 'pending');
- setError('');
- // The submissions POST only ever returns {status, submission_id} -- never masked_name/
- // masked_id_number -- but GET /security/realname has carried both fields since this feature's
- // original commit (confirmed against server source), we just weren't calling it again right
- // after a successful submit. Best-effort: the submission itself already succeeded above, so a
- // failure here shouldn't take that away from the user -- it just means the pre-existing
- // fallback below (this session's own typed draft) keeps covering the display like before.
- getRealnameStatus().then(fresh => {
- if (fresh.masked_name) setMaskedName(fresh.masked_name);
- if (fresh.masked_id_number) setMaskedIdNumber(fresh.masked_id_number);
- }).catch(() => {});
- } catch (err) {
- setError(err.message || '提交失败,请稍后重试');
- } finally {
- setBusy(false);
- }
- };
-
- if (status === 'loading' || status === 'load_error') {
- return
-
{status === 'load_error' ? (error || '加载失败,请重试') : '加载中…'}
-
;
- }
-
- return
-
-
实名认证 更加真实,更受欢迎
verified_user
-
- {/* docs/images/实名认证-已实名.png 与 docs/images/实名认证.png 的标题文案完全一致,都是
- "请您使用有效身份证信息认证",已实名状态并没有换一句"认证信息"——原来这里按 status
- 分叉出两句不同文案,是从最初的 mock 阶段就带着、从未跟真实原型图核对过的臆造文案,
- 一旦账号真的走到 verified 状态就会显示一句参考图里根本不存在的标题。 */}
- 请您使用有效身份证信息认证
-
- {/* submitRealname's own success response never carries masked_name/masked_id_number (only
- {status, submission_id}) -- submit() above now follows up with a GET /security/realname
- to fetch the server's authoritative masked values right after a successful submission, so
- this local-draft fallback (`name`/`idNumber`, still sitting in state from what was just
- typed) only matters for the brief window before that GET resolves. It still degrades to
- genuinely blank on a fresh page load with no draft in this session and no masked value
- from a prior GET either (e.g. a submission made in an earlier session) -- that residual
- case isn't fixable from here since it depends on the follow-up GET actually having run. */}
- 姓名 setName(e.target.value)} readOnly={!editable} placeholder="请输入真实姓名" />
- 证件号码 setIdNumber(e.target.value)} readOnly={!editable} placeholder="请输入证件号码" />
- {error ? {error}
: null}
-
- {status === 'verified' ? '已实名' : status === 'pending' ? '审核中' : busy ? '提交中…' : '提交'}
-
-
- info 我们将获取您的姓名、证件号用于认证,信息会被严格保护
-
-
;
-}
-
-function RealnameReminder() {
- return
-
消息 search 搜索
{['系统消息', '文件助手', '项目协作群', '陈经理'].map((item, i) =>
{item[0]} {item} {i ? '最近一条消息内容' : '安全通知与账号动态'}
)}
-
-
实名认证提醒 根据国家法律法规要求,您需先进行实名认证才能使用该项 APP
navigate('realname')}>去实名认证
-
-
;
-}
-
-function IndependentPassword({authenticated, hashParams}) {
- // Only the genuine "haven't set a password yet, setting one now to enable youth mode for the
- // first time" flow should auto-continue into enableYouthMode below -- see IdentityCheck's
- // comment on the same param for why this can't just be inferred from enable_action_token being
- // present.
- const mode = hashParams && hashParams.ym;
- const [pin, setPin] = React.useState('');
- const [error, setError] = React.useState('');
- const [toast, setToast] = React.useState('');
- const [busy, setBusy] = React.useState(false);
- // Mirrors server/im_http.go validIMIndependentPassword: 4 digits, not all-same, not a run.
- const invalid = ['0000','1111','2222','3333','4444','5555','6666','7777','8888','9999','0123','1234','2345','3456','4567','5678','6789','9876','8765','7654','6543','5432','4321','3210'];
- const submit = async e => {
- e.preventDefault();
- if (invalid.includes(pin)) { setError('请勿使用连续或重复数字'); return; }
- const stored = sessionStorage.getItem('im-action-token:' + IDENTITY_CHECK_SCENE);
- const actionToken = stored && JSON.parse(stored).action_token;
- if (!actionToken) { setError('身份验证已过期,请返回重新验证'); return; }
- setBusy(true);
- try {
- const result = await setIndependentPassword(pin, 'youth_mode', actionToken);
- sessionStorage.removeItem('im-action-token:' + IDENTITY_CHECK_SCENE);
- setPin('');
- // 设密码只是"首次开启青少年模式"这条路的中间一步,不是终点。服务端在这一步就把下一步
- // 要用的凭证一起发回来了(enable_action_token + next_action:"enable_youth_mode",
- // 见 server/im_http.go:6178-6189)。原来这里把它整个丢掉、只弹一句"设置成功"就停在
- // PIN 页——用户走完短信验证+设密码,青少年模式其实还是关的,得自己摸回设置页再输一遍。
- // 现在拿着这个 token 把最后一步做完。
- // enable_action_token comes back unconditionally on every successful (re)set, regardless of
- // why the caller ended up here (server doesn't know or care) -- only the genuine "first time
- // enabling" flow should spend it. Otherwise this silently flipped youth mode on for someone
- // who was mid-way through disabling it, or who came from the unrelated generic "身份验证"
- // entry with no youth-mode intent at all.
- const enableToken = result && result.enable_action_token;
- if (enableToken && mode === 'enable') {
- try {
- const current = await getYouthMode();
- await enableYouthMode(current.policy_version, enableToken);
- setError(''); setToast('青少年模式已开启');
- window.setTimeout(() => navigate('youth-mode-on'), 600);
- return;
- } catch (enableErr) {
- // 密码确实设成功了,只是自动开启这一步没成,如实说明并把人送回青少年模式页手动开
- setError('独立密码已设置,但自动开启未成功:' + (enableErr.message || '请返回重试'));
- window.setTimeout(() => navigate('youth-mode-off'), 1200);
- return;
- }
- }
- if (mode === 'disable') {
- // enableToken (if any) can't be spent here -- ConsumeActionToken requires an exact
- // action='close_youth_mode' match, and this one is always tagged 'enable_youth_mode'. The
- // user does know the fresh PIN now though, so send them straight back to actually finish
- // disabling instead of leaving them stuck on this screen with nothing left to do.
- setError(''); setToast('独立密码已重置,请使用新密码关闭青少年模式');
- window.setTimeout(() => navigate('youth-mode-verify', {ym: 'disable'}), 800);
- return;
- }
- setError(''); setToast('独立密码设置成功');
- } catch (err) {
- setError(err.message || '设置失败,请返回重新验证身份');
- } finally {
- setBusy(false);
- }
- };
- return
-
-
- 设置独立密码
-
-
{[0,1,2,3].map(i => {pin[i] ? '●' : ''} )}
-
setPin(e.target.value.replace(/\D/g, '').slice(0, 4))} inputMode="numeric" autoComplete="off" aria-label="四位独立密码" autoFocus />
-
- {error ? {error}
: null}
- {busy ? '提交中…' : '确定'}
-
-
setToast('')} />
- ;
-}
-
-export default function AuthOnboardingView({
- view, hashParams, onTenantResolved, authenticated = false,
- login, loginDisabled, persist, errorText, errorLevel,
- onLoginRequest, onPersistenceChange, onImRegisterRequest, onError
-}) {
- const tenantName = sessionStorage.getItem('im-tenant-name') || '';
-
- // errorText/errorLevel is one flat, top-level (tinode-web.jsx) piece of state shared by every
- // view in the whole app -- it's never tied to which sidepanel is showing. Login is the only view
- // here that already clears it on its own (its "切换企业" link calls onError() before navigating),
- // because it was written knowing it can carry a leftover error INTO the tenant screen. Every other
- // transition between these auth views was still missing that: submit a bad invite code on
- // Register (errorText='邀请码错误,请重新输入', view stays 'register', shown correctly), then just
- // click "已有账号?登录" -- Login mounts and, since it renders the exact same errorText/errorLevel
- // props, shows that same registration error as if it were a login failure, with zero user action
- // on this new page. Same the other way: a stale "账号或密码错误" from Login is still sitting there
- // to greet a fresh Register visit. Fixing every call site that navigates between these views
- // (TenantEntry/Login/Register/... several of which don't even receive onError as a prop today)
- // would be a lot of scattered one-off edits for the same bug; clearing it centrally whenever the
- // resolved `view` itself changes covers all of them (button clicks and browser back/forward alike)
- // from the one place they all already funnel through.
- React.useEffect(() => {
- if (onError) onError();
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [view]);
-
- switch (view) {
- case 'tenant': return {})} />;
- case 'login': return ;
- case 'enterprise-register': return ;
- case 'enterprise-feedback': return ;
- case 'register': return ;
- case 'reset': return ;
- case 'identity-check': return ;
- case 'realname': return ;
- case 'realname-verified': return ;
- case 'realname-reminder': return ;
- case 'independent-password': return ;
- default: return {})} />;
- }
-}
diff --git a/src/views/call-experience-view.jsx b/src/views/call-experience-view.jsx
deleted file mode 100644
index 61f7c723..00000000
--- a/src/views/call-experience-view.jsx
+++ /dev/null
@@ -1,137 +0,0 @@
-import React from 'react';
-
-import Navigation from '../lib/navigation.js';
-
-const navigate = view => Navigation.navigateTo(view);
-
-export const CALL_EXPERIENCE_VIEWS = [
- 'call-history', 'call-incoming', 'audio-call-ringing', 'audio-call-active',
- 'audio-call-muted', 'audio-call-speaker', 'video-call-ringing',
- 'video-call-camera-off', 'video-call-active', 'video-call-peer-camera-off',
- 'call-minimized'
-];
-
-const PEER = {name: '安妮', avatar: 'img/bkg/p09.jpg'};
-
-const CALL_RECORDS = [
- {id: 1, type: 'video', direction: 'outgoing', state: 'cancelled', label: '已取消', time: '今天 10:24'},
- {id: 2, type: 'audio', direction: 'incoming', state: 'declined', label: '已拒绝', time: '昨天 21:03'},
- {id: 3, type: 'video', direction: 'incoming', state: 'missed', label: '未接听', time: '昨天 18:42'},
- {id: 4, type: 'audio', direction: 'outgoing', state: 'busy', label: '对方忙线', time: '星期一 15:10'},
- {id: 5, type: 'audio', direction: 'outgoing', state: 'finished', label: '通话时长 12:36', time: '8月20日'}
-];
-
-function Icon({children}) { return {children} ; }
-
-function Toast({message, onDone}) {
- React.useEffect(() => {
- if (!message) return undefined;
- const timer = window.setTimeout(onDone, 1800);
- return () => window.clearTimeout(timer);
- }, [message, onDone]);
- return message ? {message}
: null;
-}
-
-function CallAvatar({small = false}) {
- return ;
-}
-
-function CallHeader({onMinimize, light = false}) {
- return
- keyboard_arrow_down
- 端到端加密通话
- more_vert
- ;
-}
-
-function Control({icon, label, active, danger, onClick}) {
- return {icon} {label} ;
-}
-
-function CallHistory() {
- return
-
navigate('chat-standard')} aria-label="返回会话">arrow_back_ios_new 安妮 navigate('friend-profile')} aria-label="个人资料">person_outline
-
最近通话
{CALL_RECORDS.map(item => navigate(item.type === 'audio' ? 'audio-call-ringing' : 'video-call-ringing')}>{item.type === 'audio' ? 'call' : 'videocam'} {item.type === 'audio' ? '语音通话' : '视频通话'} {item.label} {item.time} {item.direction === 'incoming' ? 'call_received' : 'call_made'} )}
-
navigate('audio-call-ringing')}>call 语音通话 navigate('video-call-ringing')}>videocam 视频通话
-
;
-}
-
-function IncomingCall({onToast}) {
- return
-
-
navigate('call-minimized')} />
- {PEER.name} 邀请你进行视频通话
- {onToast('已拒绝视频通话'); window.setTimeout(() => navigate('call-history'), 600);}} /> navigate('video-call-active')} />
- ;
-}
-
-function formatDuration(seconds) {
- const mins = Math.floor(seconds / 60).toString().padStart(2, '0');
- return `${mins}:${(seconds % 60).toString().padStart(2, '0')}`;
-}
-
-function AudioCall({view, onToast}) {
- const ringing = view === 'audio-call-ringing';
- const [seconds, setSeconds] = React.useState(ringing ? 0 : 68);
- const [mic, setMic] = React.useState(view !== 'audio-call-muted');
- const [speaker, setSpeaker] = React.useState(view === 'audio-call-speaker');
- React.useEffect(() => {
- if (ringing) return undefined;
- const timer = window.setInterval(() => setSeconds(value => value + 1), 1000);
- return () => window.clearInterval(timer);
- }, [ringing]);
- const hangup = () => {onToast(ringing ? '已取消呼叫' : `通话结束 ${formatDuration(seconds)}`); window.setTimeout(() => navigate('call-history'), 650);};
- return
-
-
navigate('call-minimized')} />
- {PEER.name} {ringing ? '正在等待对方接听…' : formatDuration(seconds)}
{!ringing ? 网络良好 : }
- {ringing ? : {setMic(!mic); onToast(mic ? '麦克风已关闭' : '麦克风已开启');}} /> {setSpeaker(!speaker); onToast(speaker ? '已切换到听筒' : '扬声器已开启');}} /> }
- ;
-}
-
-function VideoBackdrop({cameraOff, peerOff}) {
- if (peerOff) return 对方摄像头已关闭 语音通话仍在继续
;
- if (cameraOff) return 摄像头已关闭
;
- return ;
-}
-
-function VideoCall({view, onToast}) {
- const ringing = view === 'video-call-ringing' || view === 'video-call-camera-off';
- const [camera, setCamera] = React.useState(view !== 'video-call-camera-off');
- const [mic, setMic] = React.useState(true);
- const [seconds, setSeconds] = React.useState(ringing ? 0 : 128);
- const peerOff = view === 'video-call-peer-camera-off';
- React.useEffect(() => {
- if (ringing) return undefined;
- const timer = window.setInterval(() => setSeconds(value => value + 1), 1000);
- return () => window.clearInterval(timer);
- }, [ringing]);
- const hangup = () => {onToast(ringing ? '已取消视频呼叫' : `视频通话结束 ${formatDuration(seconds)}`); window.setTimeout(() => navigate('call-history'), 650);};
- return
-
-
-
navigate('call-minimized')} />
- {PEER.name} {ringing ? '正在等待对方接听…' : formatDuration(seconds)}
- {!ringing ? {!camera ? videocam_off : null} : null}
- onToast('已切换摄像头')} /> {setCamera(!camera); onToast(camera ? '摄像头已关闭' : '摄像头已开启');}} /> {!ringing ? setMic(!mic)} /> : null}
- ;
-}
-
-function MinimizedCall({onToast}) {
- const [position, setPosition] = React.useState(() => ({x: Math.max(16, window.innerWidth - 134), y: 168}));
- const drag = React.useRef(null);
- const onPointerDown = event => {drag.current = {x: event.clientX - position.x, y: event.clientY - position.y}; event.currentTarget.setPointerCapture(event.pointerId);};
- const onPointerMove = event => {if (!drag.current) return; setPosition({x: Math.max(8, Math.min(window.innerWidth - 132, event.clientX - drag.current.x)), y: Math.max(70, Math.min(window.innerHeight - 190, event.clientY - drag.current.y))});};
- return navigate('messages-home')}>arrow_back_ios_new 安妮 在线
more_vert 今天 10:24
下午的评审材料我已经发到群里了。
{drag.current = null;}} onClick={() => navigate('video-call-active')} aria-label="返回全屏通话"> 02:18 {onToast('通话已结束'); window.setTimeout(() => navigate('call-history'), 600);}} aria-label="挂断通话">call_end ;
-}
-
-export default function CallExperienceView({view}) {
- const [toast, setToast] = React.useState('');
- let content;
- if (view === 'call-history') content = ;
- else if (view === 'call-incoming') content = ;
- else if (view.startsWith('audio-call')) content = ;
- else if (view === 'call-minimized') content = ;
- else content = ;
- return <>{content} setToast('')} />>;
-}
diff --git a/src/views/contact-management-view.jsx b/src/views/contact-management-view.jsx
deleted file mode 100644
index 512a4895..00000000
--- a/src/views/contact-management-view.jsx
+++ /dev/null
@@ -1,705 +0,0 @@
-import React from 'react';
-import QRCode from 'qrcodejs';
-import { pinyin } from 'pinyin-pro';
-
-import Navigation from '../lib/navigation.js';
-import {
- getFriends, getFriendRequests, createFriendRequest, acceptFriendRequest, rejectFriendRequest,
- deleteFriend, updateFriendRemark, searchUsers, getUserProfile, createMyQRCode, getBlocks,
- blockUser, unblockUser, getFileUrl, getBootstrap, getConversationContext, getConversationSettings,
- updateConversationSettings, createUserQRCode, fetchAllPages
-} from '../lib/im-api.js';
-import { readCachedData, writeCachedData, useCachedData } from '../lib/use-cached-data.js';
-
-// Carries an optional `uid` (and anything else already in the hash) across chapter-5 navigation --
-// e.g. address-book -> friend-profile -> friend-settings all need to know which person this is.
-const navigate = (view, uid) => {
- let hash = Navigation.setUrlSidePanel(Navigation.currentUrl(), view);
- hash = uid ? Navigation.addUrlParam(hash, 'uid', uid) : Navigation.removeUrlParam(hash, 'uid');
- Navigation.navigateTo(hash);
-};
-
-export const CONTACT_MANAGEMENT_VIEWS = [
- 'address-book', 'friend-requests', 'add-friend', 'my-friend-qr', 'friend-qr', 'friend-search-result',
- 'friend-verification', 'friend-profile', 'group-member-profile', 'friend-settings',
- 'friend-settings-blocked', 'friend-delete-confirm', 'block-list', 'block-confirm',
- 'clear-chat-confirm'
-];
-
-// Server's own `initial` (bootstrap.go displayInitial) is just the first Unicode code point of the
-// display name -- for Chinese names that's a hanzi, not a Latin letter, so it can't drive the A/B/C
-// section headers docs/images/通讯录.png actually shows. Group client-side by first-pinyin-letter.
-function alphaInitial(name) {
- const ch = (name || '').trim()[0] || '#';
- if (/[A-Za-z]/.test(ch)) {
- return ch.toUpperCase();
- }
- if (/[一-龥]/.test(ch)) {
- const letter = pinyin(ch, {pattern: 'first', toneType: 'none'});
- return /[a-z]/i.test(letter) ? letter.toUpperCase() : '#';
- }
- return '#';
-}
-
-function Icon({children}) { return {children} ; }
-
-function Header({title, back = 'messages-home', backUid, right, onRight}) {
- return { if (!Navigation.goBack()) navigate(back, backUid); }} aria-label="返回">arrow_back_ios_new {title} {right ? {right} : } ;
-}
-
-// `person.avatar_file_id` (real API field) is loaded lazily via a cached authenticated fetch --
-// file serving is auth-gated, there's no public URL a plain could point at directly.
-function Avatar({person, icon}) {
- const fileId = person && person.avatar_file_id;
- const [url, setUrl] = React.useState(null);
- React.useEffect(() => {
- if (!fileId) { setUrl(null); return undefined; }
- let live = true;
- getFileUrl(fileId).then(u => { if (live) setUrl(u); }).catch(() => {});
- return () => { live = false; };
- }, [fileId]);
- return {url ? : {icon || 'person'} } ;
-}
-
-function Toast({message, onDone}) {
- React.useEffect(() => {
- if (!message) return undefined;
- const timer = window.setTimeout(onDone, 2000);
- return () => window.clearTimeout(timer);
- }, [message, onDone]);
- return message ? {message}
: null;
-}
-
-function Search({value, onChange, placeholder, onSubmit}) {
- return search onChange(event.target.value)}
- onKeyDown={event => { if (event.key === 'Enter' && onSubmit) onSubmit(); }} placeholder={placeholder} /> ;
-}
-
-// `payload` is the real `qr_payload` string from POST /me/qrcodes (already shaped like
-// `im://qr?token=...&purpose=add_friend` -- same scheme the demo literal used, no format change).
-function QrCard({payload, profile, loading}) {
- const ref = React.useRef(null);
- React.useEffect(() => {
- if (!ref.current || !payload) return;
- ref.current.innerHTML = '';
- new QRCode(ref.current, {text: payload, width: 190, height: 190, correctLevel: QRCode.CorrectLevel.M});
- }, [payload]);
- return
- {(profile && profile.display_name) || ''}
- {profile && profile.public_account ? '账号 ' + profile.public_account : ''}
-
- {loading ? 正在生成二维码…
: 扫一扫上面的二维码,加我为好友
}
- 二维码 24 小时内有效
- ;
-}
-
-function AddressBook({myUserId, onTopicSelected}) {
- const [query, setQuery] = React.useState('');
- // Seeded from cache so re-opening the address book (very much a back-and-forth flow: check
- // contacts, open a chat, come back) doesn't drop straight back to "加载中…" for a friend list that
- // hasn't actually changed.
- // Keyed by myUserId (same convention as the 'bootstrap:'+myUserId cache just below) -- a bare
- // 'friends' key meant logging out and into a *different* account in the same tab would briefly
- // paint the previous account's cached friend list on mount, before the fresh getFriends() call
- // overwrote it moments later.
- const friendsCacheKey = myUserId ? 'friends:' + myUserId : null;
- const [friends, setFriends] = React.useState(() => (friendsCacheKey && readCachedData(friendsCacheKey)) || []);
- const [pendingCount, setPendingCount] = React.useState(0);
- const [loading, setLoading] = React.useState(() => !(friendsCacheKey && readCachedData(friendsCacheKey)));
- const [loadError, setLoadError] = React.useState(false);
- // Same bootstrap the message center already fetches (and caches under the same key) -- only need
- // it here for the file assistant's real topic, so a plain read of the shared cache is enough.
- const [bootstrap] = useCachedData(myUserId ? 'bootstrap:' + myUserId : null, getBootstrap, [myUserId]);
- const fileAssistantTopic = bootstrap && bootstrap.entries && bootstrap.entries.file_assistant
- && bootstrap.entries.file_assistant.topic;
- React.useEffect(() => {
- // getFriends() defaults to a 50-item page -- a plain single call silently truncated address
- // books past that size, with no "load more" affordance to recover the rest.
- fetchAllPages(cursor => getFriends('', cursor)).then(list => {
- if (friendsCacheKey) { writeCachedData(friendsCacheKey, list); }
- setFriends(list);
- }).catch(() => setLoadError(true)).finally(() => setLoading(false));
- fetchAllPages(cursor => getFriendRequests('inbound', 'pending', cursor))
- .then(list => setPendingCount(list.length)).catch(() => {});
- }, []);
- const visible = friends.filter(item => `${item.display_name}${item.remark || ''}`.toLowerCase().includes(query.toLowerCase()));
- const groups = visible.reduce((result, item) => {
- const key = alphaInitial(item.remark || item.display_name);
- return {...result, [key]: (result[key] || []).concat(item)};
- }, {});
- const sortedKeys = Object.keys(groups).sort();
- const entries = [
- {icon: 'person_add_alt', label: '新朋友', route: 'friend-requests', badge: pendingCount || null, tone: 'blue'},
- {icon: 'group', label: '群组', route: 'group-list', tone: 'green'},
- {icon: 'folder_special', label: '文件助手', route: 'chat-standard', tone: 'orange', fileAssistant: true},
- {icon: 'notifications', label: '系统消息', route: 'system-messages', tone: 'red'}
- ];
- const scrollTo = key => {
- const el = document.getElementById('people-group-' + key);
- if (el) el.scrollIntoView({behavior: 'smooth', block: 'start'});
- };
- // The file assistant is a real topic (from bootstrap), not a route -- same bug/fix pattern as the
- // "常联系的人" shortcuts in message-center-view.jsx. Falls back to the old demo route only if
- // bootstrap hasn't resolved a real topic yet, so the entry never becomes a dead click.
- const openEntry = item => {
- if (item.fileAssistant && fileAssistantTopic && onTopicSelected) onTopicSelected(fileAssistantTopic);
- else navigate(item.route);
- };
- return ;
-}
-
-function requestStatusLabel(item) {
- if (item.status === 'pending') return item.direction === 'outbound' ? '已发送' : null;
- if (item.status === 'accepted') return '已添加';
- if (item.status === 'rejected') return '已拒绝';
- if (item.status === 'expired') return '已过期';
- return null;
-}
-
-function FriendRequests({onToast, onTopicSelected, onCreateTopic}) {
- const [items, setItems] = React.useState([]);
- const [loading, setLoading] = React.useState(true);
- const [loadError, setLoadError] = React.useState(false);
- React.useEffect(() => {
- fetchAllPages(cursor => getFriendRequests('all', 'all', cursor))
- .then(setItems).catch(() => setLoadError(true)).finally(() => setLoading(false));
- }, []);
- const handle = (item, accept) => {
- const nextStatus = accept ? 'accepted' : 'rejected';
- setItems(values => values.map(v => v.request_id === item.request_id ? {...v, status: nextStatus} : v));
- const call = accept ? acceptFriendRequest(item.request_id) : rejectFriendRequest(item.request_id);
- call.then(() => onToast(accept ? '已通过好友申请' : '已拒绝好友申请')).catch(err => {
- setItems(values => values.map(v => v.request_id === item.request_id ? {...v, status: item.status} : v));
- onToast(err.message || '操作失败');
- });
- };
- // 参考图 个人对话框(对方的状态)/(我的状态).png:申请的验证留言应该在真实聊天窗口里以消息
- // 气泡展示,横幅提示("以上是打招呼的消息"/"好友申请正在等待处理")已经在 messages-view.jsx
- // 里做对了,但这个列表原来每一行完全没有 onClick,点了毫无反应——点进去看这条留言/横幅的入口
- // 一直不存在。跟 Profile 的 openChat 用同一个"优先走 onCreateTopic 保证冷启动也能订阅"的写法。
- const openChat = item => (onCreateTopic || onTopicSelected) &&
- (onCreateTopic || onTopicSelected)(item.direction === 'inbound' ? item.applicant_id : item.target_user_id);
- return
- {loading ? 加载中…
: items.length === 0 ? {loadError ? '加载失败,请重试' : '暂无新朋友申请'}
: null}
- {items.map(item => openChat(item)}>
-
- {(item.peer && item.peer.display_name) || item.applicant_id} {item.message || '请求添加您为好友'}
{new Date(item.created_at).toLocaleString()}
- {item.status === 'pending' && item.direction === 'inbound' ?
- { e.stopPropagation(); handle(item, false); }}>拒绝 { e.stopPropagation(); handle(item, true); }}>通过验证 :
- {requestStatusLabel(item)} }
- )}
- ;
-}
-
-// 中国大陆手机号:11 位、1 开头。与本文件注册/验证流程用的判断保持一致。
-const CN_MOBILE = /^1[3-9]\d{9}$/;
-
-function classifySearchInput(text) {
- if (text.startsWith('+')) {
- // 已经是国际格式,原样按手机号查
- return {query: text.replace(/[\s-]/g, ''), type: 'phone'};
- }
- const digits = text.replace(/[\s-]/g, '');
- if (CN_MOBILE.test(digits)) {
- return {query: '+86' + digits, type: 'phone'};
- }
- return {query: text, type: 'exact_account'};
-}
-
-// 与 personal-settings-view.jsx 的 GENDER_OPTIONS 一一对应,两处写的是同一套后端枚举
-const GENDER_LABELS = {male: '男', female: '女', other: '其他'};
-
-// `GET /users/{id}/profile` (getUserProfile) 403s for anyone who isn't already a friend, by design
-// (server's default `profile_visibility` is "friends"-only, server/im_http.go's imPrivacyAllowsProfile).
-// But the two places that hand you a *stranger* to look at -- an account search hit and a scanned
-// QR code -- already returned the exact same fields (display_name/avatar_file_id/gender/region/
-// public_account/signature, both go through userSearchItemPayload/copyOptionalProfileFields) *before*
-// that gate, unconditionally. Profile used to throw that object away and re-fetch via the gated
-// endpoint, which 403s and leaves the page stuck on "加载中…" forever with no way forward -- this
-// stashes what we already have so Profile can render immediately, and only uses getUserProfile as a
-// best-effort background refresh (for when the two really are/become friends, or the peer's privacy
-// setting is "everyone").
-function seedProfile(item) {
- if (!item || !item.user_id) return;
- writeCachedData('profile-seed:' + item.user_id, {
- ...item,
- capabilities: {can_add_friend: !!item.can_add}
- });
-}
-
-function AddFriend({result = false, onToast, onOpenScanner}) {
- const [query, setQuery] = React.useState('');
- const [searching, setSearching] = React.useState(false);
- const [searched, setSearched] = React.useState(result);
- const [results, setResults] = React.useState([]);
- const runSearch = q => {
- const text = q.trim();
- if (!text) { setSearched(false); setResults([]); return; }
- setSearching(true); setSearched(true);
- // 按输入形态选搜索类型。exact_account 只比对 profile.public_account / u.username / u.id
- // (tenant_adapter.go:9170-9172),**不匹配 u.phone** —— 参考图 5.5 里输的恰恰是手机号
- // 18826827724,一直写死 exact_account 的话这个场景必然搜不到。手机号要走 type=phone。
- // 另外 phone 分支服务端不做 E.164 归一化(只有模糊的 account 分支才调 normalizeUserPhone),
- // 而库里存的是 +86 开头的 E.164,所以裸 11 位号码由前端补上区号——注册流程也是这么拼的。
- const {query, type} = classifySearchInput(text);
- searchUsers(query, type).then(data => setResults(data.items || [])).catch(() => setResults([])).finally(() => setSearching(false));
- };
- // 原来复制到剪贴板这条分支不给任何反馈,点了界面上毫无动静(同页的 QrPage.share 是有 toast 的);
- // 生成二维码失败也被 .catch(() => {}) 静默吞掉,用户同样什么都看不到。
- const shareCard = () => {
- createMyQRCode().then(data => {
- const text = `加我为好友:${data.qr_payload}`;
- if (navigator.share) {
- return navigator.share({title: '添加好友', text}).catch(() => {});
- }
- if (navigator.clipboard) {
- return navigator.clipboard.writeText(text).then(() => onToast('邀请链接已复制'))
- .catch(() => onToast('复制失败,请手动分享二维码'));
- }
- onToast('当前浏览器不支持分享,请使用二维码');
- return undefined;
- }).catch(err => onToast(err.message || '生成邀请链接失败'));
- };
- // Real search results carry a relationship for blocked pairs too (blocked_by_me/blocked_by_peer),
- // which previously fell through to the same generic "添加" as an untouched stranger -- misleading
- // since tapping through leads to a profile page whose own cannotAddReason (see the Profile
- // component below) already refuses to let the request go anywhere. Matches that component's
- // wording, just condensed to fit this one-line list item.
- const actionLabel = item => item.relationship === 'friend' ? '已是好友' :
- item.relationship === 'pending_outbound' ? '已申请' : item.relationship === 'pending_inbound' ? '待通过' :
- item.relationship === 'blocked_by_me' ? '已拉黑' : item.relationship === 'blocked_by_peer' ? '无法添加' : '添加';
- return
-
{ setQuery(v); if (!v.trim()) { setSearched(false); setResults([]); } }} placeholder="查找账号" onSubmit={() => runSearch(query)} />
-
- {searched ? 查找人
- {searching ? 搜索中…
: results.length === 0 ? 未找到相关用户
:
- results.map(item => { seedProfile(item); item.relationship === 'friend' ? navigate('friend-profile', item.user_id) : navigate('group-member-profile', item.user_id); }}>
- {item.display_name}
- {actionLabel(item)}
- )}
- :
- navigate('my-friend-qr')}>qr_code 我的二维码 让朋友扫一扫添加我 chevron_right
- onOpenScanner ? onOpenScanner() : onToast('暂不支持扫码,请使用账号搜索添加好友')}>qr_code_scanner 扫一扫 扫描好友或群二维码 chevron_right
- share 分享给好友 分享链接给好友,一起来玩吧 chevron_right
- }
-
- ;
-}
-
-function QrPage({onToast}) {
- const [payload, setPayload] = React.useState(null);
- const [profile, setProfile] = React.useState(null);
- React.useEffect(() => {
- createMyQRCode().then(data => { setPayload(data.qr_payload); setProfile(data.display_profile); }).catch(() => onToast('二维码生成失败'));
- }, []);
- const share = () => {
- if (navigator.share && payload) {
- navigator.share({title: '添加好友', text: payload}).then(() => onToast('二维码名片已分享')).catch(() => {});
- } else if (navigator.clipboard && payload) {
- navigator.clipboard.writeText(payload);
- onToast('链接已复制');
- }
- };
- const save = () => {
- const canvas = document.querySelector('.people-qrcode canvas');
- if (!canvas) { onToast('二维码尚未生成'); return; }
- const a = document.createElement('a');
- a.href = canvas.toDataURL('image/png');
- a.download = '我的二维码.png';
- a.click();
- onToast('二维码已保存到相册');
- };
- return
- share 分享好友 download 保存到相册
- ;
-}
-
-function FriendQr({uid, onToast}) {
- const [payload, setPayload] = React.useState(null);
- const [profile, setProfile] = React.useState(null);
- React.useEffect(() => {
- if (!uid) return;
- createUserQRCode(uid).then(data => { setPayload(data.qr_payload); setProfile(data.display_profile); }).catch(() => onToast('二维码生成失败'));
- }, [uid]);
- const save = () => {
- const canvas = document.querySelector('.people-qrcode canvas');
- if (!canvas) { onToast('二维码尚未生成'); return; }
- const a = document.createElement('a');
- a.href = canvas.toDataURL('image/png');
- a.download = '好友二维码.png';
- a.click();
- onToast('二维码已保存到相册');
- };
- return ;
-}
-
-function Verification({uid, onToast}) {
- // Same seed-then-best-effort-refresh as Profile -- reached right after it (via its "添加好友"
- // button), so the cache this reads is the one Profile itself just kept warm.
- const [profile, setProfile] = React.useState(() => (uid ? readCachedData('profile-seed:' + uid) : null));
- const [message, setMessage] = React.useState('');
- const [sending, setSending] = React.useState(false);
- const [sent, setSent] = React.useState(false);
- React.useEffect(() => {
- if (!uid) return;
- getUserProfile(uid).then(data => { writeCachedData('profile-seed:' + uid, data); setProfile(data); }).catch(() => {});
- // getUserProfile 403s for anyone who isn't currently a friend (server privacy default) -- same
- // gap Profile already works around below. Without this, a `profile-seed` left over from *earlier
- // in the same session* (e.g. this pair really were friends before one side unfriended them, or a
- // request was sent and then rejected) stayed on screen forever: alreadyFriend/alreadyPending read
- // straight off `profile.relationship`, and the 403 catch above has nothing to correct it with.
- // Confirmed live: unfriend someone, then re-open this exact page for them (still reachable via
- // their still-cached, now-stale seed) -- it kept claiming "你们已经是好友了" and hid the compose
- // box entirely, with no way to send a new request short of a full page reload to drop the cache.
- // getConversationContext isn't gated on friendship and reports the true current relationship.
- getConversationContext(uid).then(ctx => {
- if (ctx && ctx.relationship) {
- setProfile(current => current ? {...current, relationship: ctx.relationship} : current);
- }
- }).catch(() => {});
- }, [uid]);
- const alreadyPending = profile && (profile.relationship === 'pending_outbound' || profile.relationship === 'pending_inbound');
- const alreadyFriend = profile && profile.relationship === 'friend';
- const send = () => {
- setSending(true);
- createFriendRequest(uid, message.trim(), 'search').then(() => {
- onToast('好友申请已发送'); setSent(true);
- // Patch the seed Profile will re-read on remount -- its own background getUserProfile refresh
- // can't do this for us, since a stranger-turned-pending peer still fails that gated endpoint
- // the exact same way a stranger does (server only exempts an established "friend").
- // Must also flip capabilities.can_add_friend to false here, not just relationship: Profile's
- // canAdd render check (contact-management-view.jsx ~line 444) reads capabilities.can_add_friend,
- // not relationship, directly. Leaving the stale can_add_friend:true (copied verbatim from the
- // pre-request search-result seed) meant Profile kept showing an active "添加好友" button after
- // a request was already sent -- confirmed live: send request, navigate back to this profile
- // (the verification screen's own auto-redirect 800ms later, or its back button, both land here),
- // and the button was still clickable, capable of firing a second, backend-undeduped request
- // (see 新增44 in the backend issue list) purely because of this front-end staleness.
- const priorSeed = readCachedData('profile-seed:' + uid) || {};
- writeCachedData('profile-seed:' + uid, {...priorSeed, relationship: 'pending_outbound',
- capabilities: {...priorSeed.capabilities, can_add_friend: false}});
- window.setTimeout(() => navigate('group-member-profile', uid), 800);
- }).catch(err => onToast(err.message || '发送失败')).finally(() => setSending(false));
- };
- return
- {(profile && profile.display_name) || ''}
- {alreadyFriend ? 你们已经是好友了
:
- alreadyPending || sent ? 好友申请正在等待验证
:
- 验证留言 setMessage(event.target.value)} />{message.length}/50 }
- {alreadyFriend || alreadyPending || sent ? null :
- navigate('group-member-profile', uid)}>取消
- {sending ? '发送中…' : '发送'}
-
}
- ;
-}
-
-function Profile({uid, friend, onTopicSelected, onCreateTopic, onToast}) {
- const [profile, setProfile] = React.useState(() => (uid ? readCachedData('profile-seed:' + uid) : null));
- // A failed fetch with no seed to fall back on previously left this stuck on "加载中…" forever --
- // tracked separately from `profile` itself (which legitimately stays null while genuinely still
- // loading) so the render below can tell the two apart, same fix as DownloadPage's `loadError`.
- const [loadError, setLoadError] = React.useState(false);
- React.useEffect(() => {
- if (!uid) return;
- getUserProfile(uid).then(data => {
- writeCachedData('profile-seed:' + uid, data);
- setProfile(data);
- }).catch(() => {
- // Expected for anyone who isn't a friend yet (server-side privacy default) -- if a search/QR
- // seed already got us a usable preview, keep showing that instead of erroring the page out.
- // Only a total stranger with no seed data at all (e.g. a stale/direct deep link) has nothing to
- // show, and that's the one case worth telling the user about.
- setProfile(current => {
- if (!current) { onToast('资料加载失败'); setLoadError(true); }
- return current;
- });
- });
- // getUserProfile 403s outright for someone you've blocked (server-side privacy check) --
- // whatever relationship was cached from before the block (typically "friend") then just stays
- // stuck, which used to make this page keep showing "发消息/音视频通话" for someone actually
- // blocked, entirely missing the blocked_by_me/blocked_by_peer branches cannotAddReason already
- // handles below. getConversationContext resolves for a P2P conversation regardless of block
- // status and correctly reports the real relationship, so it's used here purely to refresh that
- // one field on top of whatever getUserProfile/the seed cache already provided for the rest.
- //
- // 同时必须把 ctx.capabilities 一起合并进去,不能只更新 relationship:canAdd 这个渲染判断读的是
- // profile.capabilities.can_add_friend,不是 relationship 本身。实测复现:从陌生人搜索结果进来的
- // seed 一直带着 capabilities.can_add_friend=true(搜索时 can_add 确实是 true),发送好友申请后
- // relationship 变成 pending_outbound,可这里原来只覆盖 relationship 字段,capabilities 原样保留
- // 着申请前的 can_add_friend:true——canAdd 分支判断不看 relationship,于是"添加好友"按钮在申请
- // 已发出后继续显示、可以重复点击再次发起申请(配合后端不去重的缺口——新增44——实际造成重复申请)。
- // ctx.capabilities 本身就是服务端针对当前 relationship 算出来的权威值(此时 can_add_friend 已经
- // 是 false),直接一起合并即可,不需要在前端另外推导一套"这些 relationship 不能加好友"的规则。
- getConversationContext(uid).then(ctx => {
- if (ctx && ctx.relationship) {
- setProfile(current => current ? {...current, relationship: ctx.relationship,
- capabilities: ctx.capabilities || current.capabilities} : current);
- }
- }).catch(() => {});
- }, [uid]);
- // group-management-view.jsx 的 navigateToProfile 在跳转之后异步用 getGroupMemberProfile 把
- // capabilities/relationship 补进种子缓存(那两个字段 getUserProfile 对非好友会 403 拿不到,
- // 群场景权限更宽松能拿到)。这个组件已经挂载了,不会自己重新读缓存,靠这个事件通知它有更新。
- React.useEffect(() => {
- if (!uid) return undefined;
- const onSeedUpdated = e => {
- if (e.detail && e.detail.uid === uid) {
- const updated = readCachedData('profile-seed:' + uid);
- if (updated) setProfile(updated);
- }
- };
- window.addEventListener('profile-seed-updated', onSeedUpdated);
- return () => window.removeEventListener('profile-seed-updated', onSeedUpdated);
- }, [uid]);
- if (!profile) return {loadError ? '资料加载失败,请重试' : '加载中…'}
;
- const canAdd = profile.capabilities && profile.capabilities.can_add_friend;
- const isFriend = profile.relationship === 'friend';
- // canAdd===false covers several real, distinct relationships (found live: searching a friend who
- // had blocked me showed this exact page claiming a request was "pending verification" when nothing
- // had been sent and nothing ever could be -- they'd blocked me). Only pending_outbound actually
- // matches that wording; the others need their own accurate text instead of reusing it as a catch-all.
- const cannotAddReason = profile.relationship === 'blocked_by_peer' ? '对方已将你拉黑,无法添加为好友' :
- profile.relationship === 'blocked_by_me' ? '你已拉黑对方,需先取消拉黑才能添加为好友' :
- profile.relationship === 'pending_inbound' ? '对方已向你发送好友申请,请前往"新朋友"处理' :
- '好友申请正在等待验证';
- // Real native Tinode p2p topic name (from my side) is just the peer's own user_id ("usrXXXX") --
- // NOT `profile.topic` (a separate "p2pXXXX" identifier the custom REST layer uses internally,
- // which Tinode.topicType() doesn't recognize, so selecting it left the message pane blank).
- //
- // Must go through onCreateTopic (tinode-web.jsx's handleStartTopicRequest), not the plain
- // onTopicSelected -- confirmed live: a freshly-accepted friend with zero prior message history has
- // no locally cached p2p topic yet, and onTopicSelected (handleTopicSelected) only *selects* a topic,
- // it never sends the native {sub} request that actually creates/joins it. messages-view.jsx's own
- // isWriter stays stuck at its default `false` (no ACS ever arrives because nothing subscribed),
- // permanently showing "消息已禁用" with no compose box and no way to send the first message --
- // 100% reproducible for every real "just became friends, say hi" case, not an edge case.
- // handleStartTopicRequest already guards the case this topic *does* have history (isTopicCached),
- // delegating straight to handleTopicSelected then, so this is safe to call unconditionally.
- const openChat = () => (onCreateTopic || onTopicSelected)(uid);
- return ;
-}
-
-function Toggle({checked, onChange, disabled}) { return onChange(!checked)}> ; }
-
-function Settings({uid, initialBlocked, pinBlocked, onToast}) {
- // Same seed-from-what-we-already-know as Profile/Verification -- getUserProfile can 403 even for a
- // real, current friend (see seedProfile's own comment), which used to leave this page's header
- // blank instead of just stuck like Profile, but no less wrong.
- const [profile, setProfile] = React.useState(() => (uid ? readCachedData('profile-seed:' + uid) : null));
- const [blocked, setBlocked] = React.useState(initialBlocked);
- // 置顶/免打扰原来是纯本地 useState,onChange 只是切一下布尔值,从不调用任何接口——
- // GET/PATCH /conversations/{topic}/settings 早就封装好了(getConversationSettings/
- // updateConversationSettings),只是零调用点。实测接口返回真实的 {pinned, mute_notifications,
- // version},PATCH 需要带 version 做乐观锁。打开设置页永远显示"关",点了也只是那次会话内
- // 生效、离开页面就丢,和会话列表真正读取的置顶/免打扰状态是两套毫不相关的东西。
- const [settingsVersion, setSettingsVersion] = React.useState(null);
- const [pinned, setPinned] = React.useState(false);
- const [muted, setMuted] = React.useState(false);
- const [editingRemark, setEditingRemark] = React.useState(false);
- const [remark, setRemark] = React.useState('');
- const [remarkDraft, setRemarkDraft] = React.useState('');
- React.useEffect(() => {
- if (!uid) return;
- getConversationSettings(uid).then(s => {
- setPinned(!!s.pinned); setMuted(!!s.mute_notifications); setSettingsVersion(s.version);
- }).catch(() => {});
- // pinBlocked:确认弹窗把这个页面当背景渲染,并用 initialBlocked 摆出"操作之后"的样子。
- // 这时不能让这次拉取回来的真实关系(还是 friend)把它改回去——原来会导致弹窗一弹出来,
- // 背后的拉黑开关自己跳回"未拉黑",同时"删除好友"红条又冒出来,跟参考图不符也跟弹窗
- // 正在确认的这件事自相矛盾。
- getUserProfile(uid).then(p => { writeCachedData('profile-seed:' + uid, p); setProfile(p); if (!pinBlocked) setBlocked(p.relationship === 'blocked_by_me'); }).catch(() => {
- // Same 403-on-blocked-contact gap as Profile above -- this used to leave `blocked` stuck at
- // whatever initialBlocked was (usually false, e.g. arriving fresh from the address book)
- // whenever the block didn't just happen in this same page session, showing an off "拉黑"
- // toggle and the "删除好友" button for someone actually already blocked.
- if (!pinBlocked) {
- getConversationContext(uid).then(ctx => {
- if (ctx && ctx.relationship) { setBlocked(ctx.relationship === 'blocked_by_me'); }
- }).catch(() => {});
- }
- });
- // GET /users/{id}/profile never exposes the real remark (backend's userProfilePayload always
- // calls userSummaryPayload(user, "") -- confirmed against im_be/server/im_http.go:7844 --
- // folding an empty remark into display_name instead of returning a separate field), only the
- // friends list endpoint's friendListItemPayload does. Look this friend up there instead.
- fetchAllPages(cursor => getFriends('', cursor)).then(list => {
- const entry = list.find(item => item.user_id === uid);
- if (entry) setRemark(entry.remark || '');
- }).catch(() => {});
- }, [uid]);
- const saveRemark = () => {
- const value = remarkDraft.trim();
- updateFriendRemark(uid, value).then(() => {
- setRemark(value);
- setEditingRemark(false); onToast('备注已更新');
- }).catch(err => onToast(err.message || '备注更新失败'));
- };
- return
- {(profile && profile.display_name) || ''}
- Navigation.navigateTo(
- Navigation.addUrlParam(Navigation.setUrlSidePanel(Navigation.currentUrl(), 'history-search'), 'topic', uid))}>
- 查看聊天记录 chevron_right
-
- 消息传输方式 已启动加密传输
- 置顶聊天 {
- // settingsVersion starts null until getConversationSettings' GET resolves (below); a toggle
- // clicked inside that window sent version:null over the wire, which the server 400s on as
- // MALFORMED_REQUEST -- disabling the switch during that (usually brief, but real on a slow
- // connection) gap avoids that avoidable round trip and its "设置失败" toast entirely.
- const prev = pinned; setPinned(value);
- updateConversationSettings(uid, {pinned: value, version: settingsVersion}).then(s => setSettingsVersion(s.version))
- .catch(err => { setPinned(prev); onToast(err.message || '设置失败'); });
- }} />
- 消息免打扰 {
- const prev = muted; setMuted(value);
- updateConversationSettings(uid, {mute_notifications: value, version: settingsVersion}).then(s => setSettingsVersion(s.version))
- .catch(err => { setMuted(prev); onToast(err.message || '设置失败'); });
- }} />
- navigate('clear-chat-confirm', uid)}>清空聊天记录 chevron_right
- {editingRemark ?
- // Backend column is VARCHAR(64) and actually enforces it (65 chars -> 400) -- confirmed
- // live, this used to cap input at less than a third of what the server accepts.
- setRemarkDraft(e.target.value)} autoFocus />保存
:
- { setRemarkDraft(remark); setEditingRemark(true); }}>备注 {remark ? {remark} : null}chevron_right }
- {blocked ? '取消拉黑' : '拉黑'} {
- if (value) { navigate('block-confirm', uid); return; }
- unblockUser(uid).then(() => { setBlocked(false); onToast('已移出黑名单'); }).catch(err => onToast(err.message || '操作失败'));
- }} />
- {
- // The local navigate(view, uid) helper only carries a single `uid` param -- the report
- // flow (security-support-view.jsx's ReportReasons/ReportDetails) needs target_type/
- // target_id/target_name, so build the hash directly here rather than through it.
- let hash = Navigation.setUrlSidePanel(Navigation.currentUrl(), 'abuse-report-reasons');
- hash = Navigation.addUrlParam(hash, 'target_type', 'user');
- hash = Navigation.addUrlParam(hash, 'target_id', uid);
- hash = Navigation.addUrlParam(hash, 'target_name', (profile && profile.display_name) || uid);
- Navigation.navigateTo(hash);
- }}>投诉与举报 chevron_right
-
- {blocked ? null : navigate('friend-delete-confirm', uid)}>删除好友 }
- ;
-}
-
-function BlockList({onToast}) {
- const [people, setPeople] = React.useState([]);
- const [loading, setLoading] = React.useState(true);
- const [loadError, setLoadError] = React.useState(false);
- React.useEffect(() => {
- fetchAllPages(cursor => getBlocks(cursor)).then(setPeople).catch(() => setLoadError(true)).finally(() => setLoading(false));
- }, []);
- const remove = userId => {
- unblockUser(userId).then(() => { setPeople(items => items.filter(item => item.user_id !== userId)); onToast('已移出黑名单'); })
- .catch(err => onToast(err.message || '操作失败'));
- };
- // The only entry point into this view is personal-settings-view.jsx's "设置" list
- // ('android-settings'), not friend-settings -- this page has no friend uid in scope at all.
- return
- {loading ? 加载中…
: people.length ?
- people.map(item => {(item.user && item.user.display_name) || item.user_id} 已阻止消息和通话 remove(item.user_id)}>移出 ) :
- person_off {loadError ? '黑名单加载失败' : '黑名单为空'}
}
- ;
-}
-
-function ConfirmSheet({type, uid, tinode, onToast}) {
- const [busy, setBusy] = React.useState(false);
- const config = {
- delete: {title: '删除好友', text: '删除后将解除好友关系,对方将无法继续向你发送消息。聊天记录仍会保留。', action: '确认删除', route: 'address-book', toast: '好友已删除', keepUid: false},
- block: {title: '黑名单', text: '拉黑后将不会收到对方的消息,可在"设置"—"黑名单"查看人员名单', action: '确定', route: 'friend-settings-blocked', toast: '已加入黑名单', keepUid: true},
- clear: {title: '清除聊天记录', text: '清空后不可找回,确定要清空当前会话的历史记录吗?', action: '确定', route: 'friend-settings', toast: '聊天记录已清除', keepUid: true}
- }[type];
- const confirm = () => {
- setBusy(true);
- let call;
- if (type === 'delete') call = deleteFriend(uid);
- else if (type === 'block') call = blockUser(uid);
- else {
- // No dedicated "clear my view of history" REST endpoint exists yet -- this is the same native
- // Tinode soft-delete-all-for-me the real message-actions menu already uses (context-menu.jsx).
- // Native p2p topic name (from my side) is just the peer's own user_id -- NOT the relationship
- // context's `topic` field, which is a separate "p2pXXXX" id the REST layer uses internally and
- // Tinode.topicType() doesn't recognize (see Profile.openChat for the full story).
- const topic = tinode ? tinode.getTopic(uid) : null;
- call = topic ? topic.delMessagesAll(false) : Promise.resolve();
- }
- call.then(() => { onToast(config.toast); navigate(config.route, config.keepUid ? uid : undefined); })
- .catch(err => onToast(err.message || '操作失败')).finally(() => setBusy(false));
- };
- return
-
{config.title} {config.text}
- {config.action}
- navigate('friend-settings', uid)}>取消
-
-
;
-}
-
-export default function ContactManagementView({view, hashParams, myUserId, tinode, onTopicSelected, onCreateTopic, onOpenScanner}) {
- const [toast, setToast] = React.useState('');
- const uid = hashParams && hashParams.uid;
- let content;
- if (view === 'address-book') content = ;
- else if (view === 'friend-requests') content = ;
- else if (view === 'add-friend' || view === 'friend-search-result') content = ;
- else if (view === 'my-friend-qr') content = ;
- else if (view === 'friend-qr') content = ;
- else if (view === 'friend-verification') content = ;
- else if (view === 'friend-profile' || view === 'group-member-profile') content = ;
- else if (view === 'friend-settings' || view === 'friend-settings-blocked') content = ;
- else if (view === 'block-list') content = ;
- else if (view === 'friend-delete-confirm') content = ;
- else if (view === 'block-confirm') content = ;
- else content = ;
- return <>{content} setToast('')} />>;
-}
diff --git a/src/views/contacts-view.jsx b/src/views/contacts-view.jsx
deleted file mode 100644
index 8db5716f..00000000
--- a/src/views/contacts-view.jsx
+++ /dev/null
@@ -1,111 +0,0 @@
-/* ContactsView holds all contacts-related stuff */
-import React from 'react';
-import { FormattedMessage, defineMessages } from 'react-intl';
-
-import ContactList from '../widgets/contact-list.jsx';
-
-import { updateFavicon } from '../lib/utils.js';
-
-const messages = defineMessages({
- archived_contacts_title: {
- id: "archived_contacts",
- defaultMessage: "Archived contacts ({count})",
- description: "Label for archived chats"
- }
-});
-
-export default class ContactsView extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleAction = this.handleAction.bind(this);
-
- this.state = ContactsView.deriveStateFromProps(props);
- }
-
- static deriveStateFromProps(props) {
- const contacts = [];
- let unreadThreads = 0;
- let archivedCount = 0;
-
- const me = props.tinode.getMeTopic();
- props.chatList.forEach(c => {
- const blocked = c.acs && !c.acs.isJoiner();
- c.pinned = me.pinnedTopicRank(c.topic);
-
- // Show only blocked contacts only when props.blocked == true.
- if (blocked && props.blocked) {
- contacts.push(c);
- }
- if (blocked || props.blocked) {
- return;
- }
-
- if (c.private && c.private.arch) {
- if (props.archive) {
- contacts.push(c);
- } else {
- archivedCount ++;
- }
- } else if (!props.archive) {
- contacts.push(c);
- unreadThreads += c.unread > 0 ? 1 : 0;
- }
- });
-
- contacts.sort((a, b) => {
- const pin = b.pinned - a.pinned;
- return pin != 0 ? pin : (b.touched || 0) - (a.touched || 0);
- });
-
- if (archivedCount > 0) {
- contacts.push({
- action: 'archive',
- title: messages.archived_contacts_title,
- values: {count: archivedCount}
- });
- }
-
- return {
- contactList: contacts,
- unreadThreads: unreadThreads
- };
- }
-
- componentDidUpdate(prevProps, prevState) {
- if (prevProps.chatList != this.props.chatList ||
- prevProps.archive != this.props.archive ||
- prevProps.blocked != this.props.blocked) {
- const newState = ContactsView.deriveStateFromProps(this.props);
- this.setState(newState);
- if (newState.unreadThreads != prevState.unreadThreads) {
- updateFavicon(newState.unreadThreads);
- }
- }
- }
-
- handleAction(action_ignored) {
- this.props.onShowArchive();
- }
-
- render() {
- return (
- {
- (no_contacts) =>
- }
- );
- }
-};
diff --git a/src/views/content-library-view.jsx b/src/views/content-library-view.jsx
deleted file mode 100644
index 45d71e99..00000000
--- a/src/views/content-library-view.jsx
+++ /dev/null
@@ -1,782 +0,0 @@
-import React from 'react';
-
-import { Drafty, Tinode } from 'tinode-sdk';
-
-import AudioPlayer from '../widgets/audio-player.jsx';
-
-import Navigation from '../lib/navigation.js';
-import { bytesToHumanSize, secondsToTime, shortDateFormat } from '../lib/strformat.js';
-import {
- getFavorites, getFavorite, batchDeleteFavorites, forwardFavorites, createFileDownloader,
- searchTopicMessages, getMessageContext, getTopicMedia, checkURL, getFileUrl, resolveFileUrl, getGroup, getUserProfile,
- getConversationContext
-} from '../lib/im-api.js';
-import { readCachedData, writeCachedData, useDebouncedValue } from '../lib/use-cached-data.js';
-import { topicDisplayName } from '../lib/utils.js';
-
-// favoritePayload's source.topic is the raw conversation topic id -- for a group that's grpXXX, but
-// for a P2P source it's the *composite* p2p topic id (e.g. "p2pU_as9N_wgZphV5XTJmzy3A"), not the bare
-// "usrXXX" uid the comment here used to assume. Passing that composite id straight to
-// /users/{id}/profile 404s every time (confirmed live: MALFORMED_REQUEST/"user not found"), so every
-// P2P-sourced favorite silently fell back to displaying the raw p2pXXX topic string instead of the
-// friend's name -- the .catch(()=>{}) below swallowed the failure with no visible error. There is no
-// way to derive the peer's uid from that composite string on the client: im_be's ParseP2P (server/
-// im_http.go's resolveConversationContextTopic) decodes it, but that decoding never runs client-side,
-// and the already-subscribed chatList doesn't help either since Tinode keys a P2P contact by the
-// peer's bare uid there, not by this composite id, so no local list lookup can bridge the two.
-// GET /conversations/{topic}/context (im-api.js's getConversationContext, already used elsewhere for
-// relationship state) exists precisely to resolve a p2p topic server-side and conveniently returns
-// the resolved peer's own `public` data in the same response -- reuse that instead of guessing at a
-// uid shape that doesn't apply here. Cached since many favorites usually share a handful of source
-// conversations.
-const sourceNameCache = new Map();
-function resolveSourceName(topic, onDone) {
- if (!topic) return;
- if (sourceNameCache.has(topic)) { onDone(sourceNameCache.get(topic)); return; }
- if (Tinode.isSelfTopicName(topic)) { sourceNameCache.set(topic, '已保存的消息'); onDone('已保存的消息'); return; }
- const call = topic.startsWith('grp') ? getGroup(topic).then(g => g.name) :
- getConversationContext(topic).then(ctx => ctx.peer && topicDisplayName(ctx.peer_user_id, ctx.peer.public, ctx.peer_user_id));
- call.then(name => { if (name) { sourceNameCache.set(topic, name); onDone(name); } }).catch(() => {});
-}
-
-// Generic multi-param navigate -- content-library-view.jsx's original helper was a bare
-// `Navigation.navigateTo(view)`, replacing the whole hash and dropping any existing params on
-// every internal tab/link click; nothing in this file could carry a topic/favoriteId through its own
-// navigation even if a caller threaded one in from outside.
-const navigate = (view, params) => {
- let hash = Navigation.setUrlSidePanel(Navigation.currentUrl(), view);
- Object.entries(params || {}).forEach(([key, value]) => {
- hash = value ? Navigation.addUrlParam(hash, key, value) : Navigation.removeUrlParam(hash, key);
- });
- Navigation.navigateTo(hash);
-};
-
-// Same as navigate() above but for switching this page's own type tabs (Favorites' 全部/图片/.../链接,
-// History's 聊天记录/图片/视频/文件/链接) -- these change which content shows on the *same* page, not
-// which page the user is on, so they shouldn't push a browser-history entry the way a real navigate()
-// does. Previously every tab click called navigate(), so N tab switches pushed N history entries and
-// the system back button just cycled through past tabs instead of leaving the page.
-const navigateReplace = (view) => Navigation.navigateReplace(Navigation.setUrlSidePanel(Navigation.currentUrl(), view));
-
-export const CONTENT_LIBRARY_VIEWS = [
- 'favorites', 'favorites-mixed', 'favorites-files', 'favorites-text', 'favorites-voice',
- 'favorites-links', 'favorites-image', 'favorites-video', 'favorites-select', 'favorite-share', 'file-download',
- 'file-downloading', 'file-downloaded', 'history-search', 'history-files',
- 'history-links', 'history-images', 'history-videos'
-];
-
-function Icon({children}) { return {children} ; }
-function Header({title, back = 'messages-home', backParams, right, onRight}) {
- return
- { if (!Navigation.goBack()) navigate(back, backParams); }} aria-label="返回">arrow_back_ios_new
- {title}
- {right ? {right} : }
- ;
-}
-function Avatar({item}) { return {item && item.image ? : {(item && item.icon) || 'person'} } ; }
-function Toast({message, onDone}) { React.useEffect(() => { if (!message) return undefined; const timer = window.setTimeout(onDone, 2000); return () => window.clearTimeout(timer); }, [message, onDone]); return message ? {message}
: null; }
-function Search({value, onChange, placeholder = '搜索'}) { return search onChange(event.target.value)} placeholder={placeholder} /> ; }
-
-// -- Drafty extraction helpers: a favorite's snapshot.content (and a history item's flattened content
-// fields) are literally the original message's own content, not a Ch.7-specific shape -- reuse the same
-// Drafty API chat-message.jsx/context-menu.jsx already use for live messages instead of re-deriving it.
-function draftyContentOf(content) {
- return Drafty.isValid(content) ? content : null;
-}
-// 必须用 Drafty.entities 而不是 Drafty.attachments:后者在 SDK 里硬过滤 `ent.tp == 'EX'`
-// (tinode.dev.js:2538),只认「文件附件」这一种实体。而本应用发图片/视频/语音走的分别是
-// insertImage(IM)/insertVideo(VD)/appendAudio(AU),在 attachments 下一个都取不到——收藏的图片视频
-// 缩略图、语音收藏的播放源、聊天记录的图片视频宫格因此全部拿不到资源。entities 的回调第三参给出
-// tp,可以按类型分派,chat-message.jsx:139 用的就是这个。
-const MEDIA_ENTITY_TYPES = ['IM', 'VD', 'AU', 'EX'];
-function firstAttachment(content) {
- const drafty = draftyContentOf(content);
- if (!drafty) return null;
- let found = null;
- Drafty.entities(drafty, (data, _index, tp) => {
- if (!found && data && MEDIA_ENTITY_TYPES.includes(tp)) {
- found = data;
- return true; // 命中第一个就停,entities 的回调返回 true 表示中断遍历
- }
- return false;
- });
- return found;
-}
-// Drafty.getDownloadUrl returns either an already-usable blob: URL (small inband attachment, .val) or
-// the raw .ref relative path (out-of-band, needs an authenticated fetch) -- getFileUrl() takes a bare
-// file_id and builds that path itself, so pull just the id back out of .ref rather than double it up.
-function resolveAttachmentUrl(att) {
- if (!att) return Promise.resolve(null);
- const direct = Drafty.getDownloadUrl(att);
- if (!direct) return Promise.resolve(null);
- if (direct.startsWith('blob:') || direct.startsWith('data:')) return Promise.resolve(direct);
- return getFileUrl(direct.slice(direct.lastIndexOf('/') + 1)).catch(() => null);
-}
-// A video entity's .val/.ref (what resolveAttachmentUrl above resolves) is the video's own bytes --
-// meant for a real element, not decodable by a plain . The poster frame lives in the
-// separate .preview/.premime fields the backend already sends alongside it, same convention
-// lib/formatters.js's quoteImageOrVideo()/inlineVideoAttr() rely on for the chat-bubble quote case.
-function videoPosterUrl(att) {
- if (!att || !att.preview) return null;
- return 'data:' + (att.premime || 'image/jpeg') + ';base64,' + att.preview;
-}
-// searchTopicMessages/getTopicMedia results spread the original Drafty content's own fields directly
-// onto each item (mediaMessagePayload/messageSearchPayload flatten `content`, they don't nest it) --
-// reconstruct the Drafty shape from whatever ent/fmt/txt survived so the same attachment-extraction
-// path works here as everywhere else, rather than guessing at flat field names that may not exist.
-function attachmentFromFlat(item) {
- if (item && (item.ent || item.fmt || item.txt !== undefined)) {
- return firstAttachment({txt: item.txt, fmt: item.fmt, ent: item.ent});
- }
- return null;
-}
-function plainTextOf(content, fallbackText) {
- if (typeof content === 'string') return content;
- const drafty = draftyContentOf(content);
- if (drafty) return Drafty.toPlainText(drafty);
- return fallbackText || '';
-}
-// file_type -> {icon, color} matching Ch.6's group-file convention (a colored square + letter/icon).
-const FILE_TYPE_META = {image: ['image', '#3ba55c'], video: ['videocam', '#3b82c4'], audio: ['audiotrack', '#8b5cf6'], document: ['description', '#e05d44'], file: ['insert_drive_file', '#8a8f98']};
-const DOCUMENT_EXT = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt'];
-function extFromName(name) {
- const m = /\.([a-zA-Z0-9]+)$/.exec(name || '');
- return m ? m[1].toUpperCase() : '';
-}
-// Ch.6's getGroupFiles() returns a server-classified file_type field; favorites/history items have
-// no equivalent (they're derived from a Drafty attachment, not a registered file record), so it's
-// approximated here from mime/extension instead of left unclassified.
-function fileTypeFromMime(mime, name) {
- const prefix = (mime || '').split('/')[0];
- if (prefix === 'image' || prefix === 'video' || prefix === 'audio') return prefix;
- const ext = extFromName(name).toLowerCase();
- if (mime === 'application/pdf' || DOCUMENT_EXT.includes(ext)) return 'document';
- return 'file';
-}
-
-function FileCard({name, size, mime, sender, removed, onOpen}) {
- const ext = extFromName(name);
- const [, color] = FILE_TYPE_META[fileTypeFromMime(mime, name)];
- return
- {ext || (mime || '').split('/')[0].toUpperCase() || '?'}
- {name || '未命名文件'} {typeof size === 'number' ? bytesToHumanSize(size) : (size || '未知大小')}{sender ? ` · ${sender}` : ''}
- {removed ? 文件已失效 : chevron_right }
- ;
-}
-
-// Real playback via the same widgets/audio-player.jsx used for live voice messages, rather than the
-// mock's plain state-toggle with no actual element -- resolves its own blob URL like Avatar
-// does, so callers don't need their own per-item URL-map bookkeeping just for voice favorites.
-// `removed` (source message deleted/recalled, im_http.go's messageAvailability) skips resolving a
-// blob URL for a file that's gone -- without this, a removed voice favorite just sat on
-// `hourglass_empty` forever (resolveAttachmentUrl's fetch fails silently), indistinguishable from
-// "still loading". Same FileCard-style treatment (an label, no playback) as the file case.
-function VoiceCard({att, head, removed}) {
- const [src, setSrc] = React.useState(null);
- React.useEffect(() => { if (!removed) resolveAttachmentUrl(att).then(setSrc); }, [att, removed]);
- const duration = (att && att.duration) || (head && head.duration) || 0;
- if (removed) {
- return block 语音已失效
;
- }
- return
- {src ?
:
hourglass_empty }
-
;
-}
-
-// No OG-metadata extraction exists anywhere in the backend (favoriteSnapshot/mediaMessagePayload just
-// copy the raw message content -- no title/thumbnail/summary field is ever populated), matching what
-// every one of the reference screenshots for both 我的收藏-链接 and 查找聊天记录-链接 actually show: a
-// plain URL string, not a card. Rendering a rich card here would be inventing data that doesn't exist.
-function LinkText({url, onOpen, removed}) {
- if (removed) {
- return link_off 链接已失效
;
- }
- return onOpen && onOpen(url)}>link {url} ;
-}
-
-// 需求7.4「支持长文本折叠、展开和复制」外加"搜索结果应对匹配关键词进行高亮显示"——原实现整段
-// 需求在这里都没接:文本收藏是一段不限高度的纯 ,长文本不折叠也没有展开/收起,没有复制按钮,
-// 收藏搜索的 query 也从没传到这一层,History 那边已经在用的 Highlight 组件在 Favorites 里从未
-// 被引用过(实测:在"我的收藏-文本"搜索一个真实命中的关键词,结果列表 0 个 )。
-// overflowing 用 scrollHeight/clientHeight 判断 line-clamp 是否真的裁到了内容(标准判定手法),
-// 避免给短文本也显示一个没用的"展开"按钮;复制走跟 context-menu.jsx 的 copyMessageText 完全一样
-// 的 navigator.clipboard.writeText,保持与聊天消息长按菜单里"复制"一致的行为。
-function FavoriteText({text, query, onToast}) {
- const [expanded, setExpanded] = React.useState(false);
- const [overflowing, setOverflowing] = React.useState(false);
- const ref = React.useRef(null);
- React.useEffect(() => {
- const el = ref.current;
- if (el) setOverflowing(el.scrollHeight - el.clientHeight > 1);
- }, [text]);
- const copy = () => {
- navigator.clipboard.writeText(text || '').then(() => onToast('已复制')).catch(() => onToast('复制失败'));
- };
- return
-
-
- {overflowing ? setExpanded(v => !v)}>{expanded ? '收起' : '展开'} : null}
- content_copy 复制
-
-
;
-}
-
-function MediaThumb({url, poster, video, duration, onOpen, removed}) {
- if (removed) {
- return {video ? 'videocam_off' : 'broken_image'} {video ? '视频已失效' : '图片已失效'}
;
- }
- // video's own bytes (url) can't be decoded by -- only the poster frame can. Images have no
- // separate poster; their own url doubles as the thumbnail.
- const imgSrc = video ? poster : url;
- return
- {imgSrc ? : {video ? 'videocam' : 'image'} }
- {/* duration follows the AU/VD entity storage convention of milliseconds (see sendVideoAttachment/
- insertVideo), same conversion widgets/audio-player.jsx already does for its own duration prop. */}
- {video ? <>play_circle {duration > 0 ? {secondsToTime(duration / 1000)} : null}> : null}
- ;
-}
-
-function FavoriteItem({item, onOpen, selecting, selected, onSelect, query, onToast}) {
- const att = firstAttachment(item.snapshot && item.snapshot.content);
- const source = item.source_display || (item.source && item.source.topic) || '';
- return
- {selecting ? {selected ? 'check' : ''} : null}
-
- {/* 需求 7.3「失效文件需展示明确状态而非直接报错」原来只有 file 类型传了 removed(FileCard),
- text/voice/link/image/video 全部没处理——即便后端把"问题13"(availability 恒为 available)
- 修好,这五类在 UI 上也不会有任何变化,因为组件本身就不认识这个状态,只会静默显示一个和
- "还在加载中"没法区分的占位(text 类型更糟:源消息撤回后 snapshot 里的旧文本会原样一直
- 显示下去,没有"加载中"和"已失效"的区别,看起来像是这条消息完全没被撤回)。现在五个类型
- 统一都传。后端 messageAvailability(im_http.go)在源消息被删/被撤回时给 'removed'。 */}
- {item.type === 'text' ? (item.availability === 'removed' ?
-
block 文本已失效
:
-
) : null}
- {item.type === 'file' ?
: null}
- {item.type === 'voice' ?
: null}
- {item.type === 'link' ?
: null}
- {item.type === 'image' || item.type === 'video' ?
: null}
-
来自 {source} {shortDateFormat(new Date(item.collected_at))}
-
- ;
-}
-
-const TYPE_ROUTE = {all: 'favorites-mixed', file: 'favorites-files', text: 'favorites-text', voice: 'favorites-voice', link: 'favorites-links', image: 'favorites-image', video: 'favorites-video'};
-const TYPE_LABELS = [['all', '全部'], ['image', '图片'], ['video', '视频'], ['file', '文件'], ['voice', '语音'], ['text', '文本'], ['link', '链接']];
-
-function Favorites({view, hashParams, tinode, onToast}) {
- const initialType = view === 'favorites-files' ? 'file' : view === 'favorites-text' ? 'text' : view === 'favorites-voice' ? 'voice' : view === 'favorites-links' ? 'link' : view === 'favorites-image' ? 'image' : view === 'favorites-video' ? 'video' : 'all';
- const selecting = view === 'favorites-select';
- // 'favorites-select' 是所有类型 tab 共用的同一个路由,本身不带任何类型信息——原来不管进入
- // 多选前停在哪个 tab,一律硬编码回退成 'file'。实测复现:在"文本"tab 点"多选",列表和头部
- // 的"已选择 N 项"计数都静默换成了"文件"tab 的数据(tab 高亮也跟着跳到"文件"),用户在视觉上
- // 完全看不出发生了什么,勾选/删除会作用在自己压根没看到、以为还是文本收藏的文件项上——测试
- // 过程中真的因为这个把一条本不该删的文件收藏删掉了。现在"多选"按钮把当前 type 作为 hash 参数
- // 带过去,这里读回来,保留进入多选前用户实际所在的 tab;只有直接深链到 'favorites-select'
- // 且没带 type 参数时才落回原来的 'file' 默认值。
- const [type, setType] = React.useState(selecting ? ((hashParams && hashParams.type) || 'file') : initialType);
- // 输入值与实际发请求用的值分开:原来 query 直接进 load 的依赖数组,输入 6 个字的关键词就打
- // 6 次接口,而且返回顺序没有保证——后发的短前缀请求可能后到、把正确结果覆盖掉。
- const [queryInput, setQueryInput] = React.useState('');
- const query = useDebouncedValue(queryInput, 300);
- const [selected, setSelected] = React.useState([]);
- // Seeded per-type -- one Favorites instance backs every favorites* tab/route (confirmed multiple
- // entry points: personal-settings' "我的收藏" row, direct-message-view.jsx's attachment-panel
- // "favorite" action), so an unparameterized key would flash a stale, wrong-type list under a
- // freshly-switched tab. `query` is deliberately excluded -- it always resets to '' on a tab switch,
- // so caching a search result under the same key would show it under a plain (non-search) revisit.
- const [items, setItems] = React.useState(() => readCachedData('favorites:' + type) || null);
- const [cursor, setCursor] = React.useState('');
- const [hasMore, setHasMore] = React.useState(false);
- // A failed initial load (no cached list to fall back on) previously left this stuck on "加载中…"
- // forever -- the catch handler only fired a toast, which fades in 2s, and `items` never left null
- // so the loading branch below never had anything else to show. Same fix already applied to this
- // file's own DownloadPage/GroupFiles(group-management-view.jsx)/Profile(contact-management-view.jsx)
- // for the identical reason; Favorites/History were missed. Concretely hit live: favoriting a large
- // (out-of-band) file attachment makes the *server* 500 on GET /favorites?type=all from then on
- // (a real backend bug, tracked separately -- not something the frontend can prevent), and every
- // subsequent visit to "全部" silently hung on the loading spinner with only a 2s toast, no way to
- // tell "still loading" from "never going to load", no retry affordance.
- const [loadError, setLoadError] = React.useState(false);
- const [fileUrls, setFileUrls] = React.useState({});
- const [sourceNames, setSourceNames] = React.useState({});
- // An in-flight request isn't guaranteed to resolve before a newer one its own type/query change
- // triggered -- without this, a slow full-list request landing after a fast, narrower search
- // response overwrites the correct filtered results with the stale unfiltered ones (confirmed
- // live: search box showing a query, list showing all items). Each load() call claims the counter
- // for itself; a response only applies if nothing newer has been requested since.
- const requestIdRef = React.useRef(0);
- const load = React.useCallback((append) => {
- const requestId = ++requestIdRef.current;
- getFavorites({type, query, cursor: append ? cursor : ''}).then(data => {
- if (requestIdRef.current !== requestId) return;
- setLoadError(false);
- setItems(prev => {
- const list = append ? (prev || []).concat(data.items || []) : (data.items || []);
- if (!query) writeCachedData('favorites:' + type, list);
- return list;
- });
- setCursor(data.next_cursor || '');
- setHasMore(!!data.next_cursor);
- }).catch(err => {
- if (requestIdRef.current !== requestId) return;
- onToast(err.message || '加载失败');
- if (!append) setLoadError(true);
- });
- }, [type, query, cursor]);
- React.useEffect(() => {
- setLoadError(false);
- setItems((!query && readCachedData('favorites:' + type)) || null);
- load(false);
- }, [type, query]);
- // Image/video favorites need an authenticated blob URL, same as any other file_id-backed thumbnail.
- React.useEffect(() => {
- (items || []).forEach(item => {
- if ((item.type !== 'image' && item.type !== 'video') || fileUrls[item.favorite_id]) return;
- const att = firstAttachment(item.snapshot && item.snapshot.content);
- resolveAttachmentUrl(att).then(url => { if (url) setFileUrls(v => ({...v, [item.favorite_id]: url})); });
- });
- }, [items]);
- React.useEffect(() => {
- (items || []).forEach(item => {
- const topic = item.source && item.source.topic;
- if (!topic || sourceNames[topic]) return;
- resolveSourceName(topic, name => name && setSourceNames(v => ({...v, [topic]: name})));
- });
- }, [items]);
- const toggle = id => setSelected(values => values.includes(id) ? values.filter(v => v !== id) : values.concat(id));
- // 原来是裸 window.confirm()——跟这个工程已经修过的另外两处同一类问题(46a98782"退出登录"、
- // 4773ee5f"解散群组")一样:跟本项目其它地方的自定义确认弹层视觉不一致,且部分内嵌WebView/
- // kiosk环境会直接禁用同步对话框(confirm()恒返回false,删除被静默拦下、用户毫无感知)。
- // 改成两段式 state:点"删除"先进"确认中"态渲染弹层,弹层里的"确定"才真正调用删除接口。
- const [confirmingDelete, setConfirmingDelete] = React.useState(false);
- const confirmDelete = () => {
- setConfirmingDelete(false);
- batchDeleteFavorites(selected).then(result => {
- const okIds = new Set(((result && result.items) || []).filter(r => r.ok).map(r => r.favorite_id));
- setItems(values => {
- const next = values.filter(item => !okIds.has(item.favorite_id));
- if (!query) writeCachedData('favorites:' + type, next);
- return next;
- });
- setSelected(values => values.filter(id => !okIds.has(id)));
- onToast(okIds.size === selected.length ? '已删除所选收藏' : `已删除 ${okIds.size}/${selected.length} 项,部分删除失败`);
- }).catch(err => onToast(err.message || '删除失败'));
- };
- const openItem = item => {
- if (item.type === 'file') { navigate('file-download', {favoriteId: item.favorite_id, fromType: type}); return; }
- if (item.type === 'link') {
- const url = (item.snapshot && item.snapshot.content && item.snapshot.content.url) || plainTextOf(item.snapshot && item.snapshot.content, item.snapshot && item.snapshot.text);
- checkURL(url, 'favorite').then(res => {
- if (res.decision === 'allow') window.open(url, '_blank', 'noopener,noreferrer');
- else onToast(res.decision === 'block' ? '该链接存在风险,已被拦截' : '该链接可能存在风险,请谨慎访问');
- }).catch(() => onToast('链接安全校验失败'));
- return;
- }
- if (item.type === 'image' || item.type === 'video') {
- // item._thumbUrl is the real resolveAttachmentUrl() result (see the effect above) -- this
- // backend has no separate scaled-down thumbnail concept, so the same URL used to render the
- // grid cell is already the full attachment, safe to open directly.
- if (item._thumbUrl) window.open(item._thumbUrl, '_blank', 'noopener,noreferrer');
- else onToast('正在加载,请稍后重试');
- return;
- }
- onToast('暂不支持预览此类收藏');
- };
- const list = items || [];
- return
- {/* "返回"/"取消"都要回到多选前那个类型 tab,而不是硬编码的"文件"——TYPE_ROUTE 查不到时
- (比如 type 还是初始的 'all') 才退回 'favorites-files',跟原来的兜底保持一致。 */}
-
;
-}
-
-// Reuses the exact same real-contact-list source as Ch.4's batch-forward-dialog.jsx (the app's own
-// already-subscribed chatList, filtered locally) rather than the pre-built but never-actually-wired
-// getForwardTargets() -- that endpoint's field shape was never verified against a real caller anywhere
-// in this codebase, while this pattern is proven live since Chapter 4.
-function FavoriteShare({hashParams, myUserId, chatList, onToast}) {
- const ids = (hashParams && hashParams.ids ? hashParams.ids.split(',') : []).filter(Boolean);
- const [query, setQuery] = React.useState('');
- const [selected, setSelected] = React.useState([]);
- const [confirming, setConfirming] = React.useState(false);
- const [comment, setComment] = React.useState('');
- const [sending, setSending] = React.useState(false);
- const [preview, setPreview] = React.useState(null);
- React.useEffect(() => { if (ids[0]) getFavorite(ids[0]).then(setPreview).catch(() => {}); }, []);
- const toggle = id => setSelected(values => values.includes(id) ? values.filter(v => v !== id) : values.concat(id).slice(0, 9));
- const send = () => {
- if (sending) return;
- setSending(true);
- forwardFavorites(ids, selected, comment.trim()).then(result => {
- // `items` is one row per (favorite x target) pair once a target passes the permission check,
- // or one bare row per target when it fails outright -- either way its length isn't `selected.length`
- // (target count) whenever more than one favorite is being shared at once, so a plain
- // `selected.length - failedItems` mixed the two units and could go negative. Group back to the
- // "会话" granularity the toast actually claims to report: a target only counts as fully sent once
- // every one of its own items succeeded.
- const items = (result && result.items) || [];
- const successTargets = selected.filter(target => {
- const forTarget = items.filter(r => r.target_topic === target);
- return forTarget.length > 0 && forTarget.every(r => r.status === 'sent');
- }).length;
- onToast(successTargets === selected.length ? `已分享给 ${selected.length} 个会话` : `${successTargets}/${selected.length} 个会话分享成功`);
- // 发完之后回到分享前那个类型 tab,而不是硬编码的"文件"——preview 是这批收藏里第一条的详情,
- // 拿它的 type 就够找回对应 tab;preview 还没加载完时退回原来的 'favorites-files' 兜底。
- window.setTimeout(() => navigate(TYPE_ROUTE[preview && preview.type] || 'favorites-files'), 700);
- }).catch(err => onToast(err.message || '分享失败')).finally(() => setSending(false));
- };
- const previewAtt = preview && firstAttachment(preview.snapshot && preview.snapshot.content);
- const contacts = (chatList || []).filter(c => c.acs && c.acs.isJoiner() && c.acs.isWriter() &&
- (!query || topicDisplayName(c.name, c.public, c.name || '').toLowerCase().includes(query.toLowerCase())));
- const nameOf = uid => { const t = contacts.find(c => c.name === uid); return (t && topicDisplayName(t.name, t.public, uid)) || uid; };
- return
- {/* 同上一条注释——"favorites-select" 不带类型信息,点这里的返回箭头原来会静默把多选列表
- 换成硬编码的"文件"tab。preview.type 是这批收藏第一条的真实类型,带回去能让返回箭头落回
- 分享前实际所在的 tab。 */}
-
- {!confirming ? <>
-
-
最近聊天
-
{contacts.map(item => toggle(item.name)}>
- {topicDisplayName(item.name, item.public, item.name)}
- {selected.includes(item.name) ? 'check' : ''}
- )}
-
已选择 {selected.length}/9 setConfirming(true)}>确定
- > :
- 发送给
- {selected.map(uid =>
{nameOf(uid)} )}
- {preview ? {previewAtt ? {}} /> :
- {plainTextOf(preview.snapshot && preview.snapshot.content, preview.snapshot && preview.snapshot.text)}
}
- 收藏原件会继续保留,并在目标会话中生成一条新消息。
: null}
- setComment(event.target.value)} placeholder="给朋友留言" />
- 发送
- }
-
;
-}
-
-function DownloadPage({hashParams, tinode, onToast}) {
- const [meta, setMeta] = React.useState(null);
- // A failed fetch previously left this stuck on "加载中…" forever -- the toast fired once and faded,
- // but `meta` never left null so the loading branch below never had anything else to show. Tracked
- // separately from `meta` itself since a real "still loading" state also has `meta === null`.
- const [loadError, setLoadError] = React.useState(false);
- const [state, setState] = React.useState('ready');
- const [progress, setProgress] = React.useState(0);
- const downloaderRef = React.useRef(null);
- const {favoriteId, topic, seq, fromType} = hashParams || {};
- // Two distinct entry points share this route: Favorites (favoriteId set, Ch.7.1) and History's
- // file tab (topic+seq set, Ch.7.12) -- "back" must return to whichever one actually opened this
- // page, not unconditionally to favorites-files (which History never came from and has no topic
- // param for anyway). Within Favorites itself, `openItem` now also threads through the tab the user
- // was actually on (`fromType`) -- opening a file from "全部" or "文件" both land here, and should
- // each return to where they came from, not always to the "文件" tab regardless of origin.
- const backView = favoriteId ? (TYPE_ROUTE[fromType] || 'favorites-files') : 'history-files';
- const backParams = favoriteId ? undefined : {topic};
- React.useEffect(() => {
- const apply = content => {
- const att = firstAttachment(content);
- if (!att) { onToast('文件信息加载失败'); setLoadError(true); return; }
- // Drafty.getDownloadUrl's raw return value is only actually usable as-is for a small inband
- // attachment (an already-local blob: URL built from inline base64 data). For an out-of-band
- // one -- everything above the ~3MB inline threshold, i.e. essentially every real photo/video/
- // document -- it's the bare server-relative .ref path (e.g. "/v0/file/s/xxx"), which the
- // browser would resolve against *this page's own* origin rather than the tenant's real
- // (often cross-origin) file domain. resolveAttachmentUrl() elsewhere in this file already
- // gets this right by routing ref-backed attachments through getFileUrl() -- but that eagerly
- // fetches the whole file into a blob immediately, which is wrong here: this page hasn't had
- // the user press "立即下载" yet, and doing that on mount would silently pull the entire file in
- // the background and make the ready/downloading/done states below meaningless.
- // resolveFileUrl() is the same domain-qualification with no fetch, exactly what's needed to
- // just populate `meta.url` for later use by the real download button.
- const direct = Drafty.getDownloadUrl(att);
- const url = (direct && (direct.startsWith('blob:') || direct.startsWith('data:'))) ?
- direct : resolveFileUrl(direct && direct.slice(direct.lastIndexOf('/') + 1));
- setMeta({name: att.name, mime: att.mime, size: Drafty.getEntitySize(att), url});
- };
- if (favoriteId) {
- getFavorite(favoriteId).then(fav => apply(fav.snapshot && fav.snapshot.content)).catch(err => { onToast(err.message || '加载失败'); setLoadError(true); });
- } else if (topic && seq) {
- getMessageContext(topic, seq, 0, 0).then(data => {
- const item = (data.items || []).find(m => String(m.seq) === String(seq)) || data.items[0];
- apply(item && item.content);
- }).catch(err => { onToast(err.message || '加载失败'); setLoadError(true); });
- }
- }, [hashParams]);
- const start = () => {
- if (!meta || !tinode) return;
- // A small inband attachment's "url" (from Drafty.getDownloadUrl) is already a ready-to-use
- // blob: URL built from its inline base64 data -- there's nothing to fetch from the server, so
- // getLargeFileHelper().download() would be the wrong tool (it requires a *relative* HTTP path and
- // returns bare `undefined`, not a rejected promise, for anything else -- confirmed by triggering
- // this exact case: chaining .then() off that return value threw "Cannot read properties of
- // undefined"). Save the already-local blob directly instead.
- if (meta.url && (meta.url.startsWith('blob:') || meta.url.startsWith('data:'))) {
- const link = document.createElement('a');
- link.href = meta.url; link.download = meta.name || 'file';
- document.body.appendChild(link); link.click(); document.body.removeChild(link);
- setState('done');
- return;
- }
- // tinode.getLargeFileHelper().download() sends the same two wrong headers as its upload() sibling
- // (X-Tinode-APIKey/X-Tinode-Auth, neither recognized by this backend) -- every real out-of-band
- // download was silently failing. createFileDownloader() is a fixed-headers drop-in with the exact
- // same download()/cancel() interface.
- const downloader = createFileDownloader(tinode);
- if (!downloader) { onToast('当前环境不支持下载'); return; }
- downloaderRef.current = downloader;
- setState('loading'); setProgress(0);
- // download() resolves once the browser's own save dialog/native download has been triggered --
- // there's no server-side download-completion record to poll (confirmed absent on the backend), so
- // "downloaded" here just means the transfer finished, tracked only for this page's own lifetime.
- const result = downloader.download(meta.url, meta.name, meta.mime,
- loaded => setProgress(meta.size ? Math.min(100, Math.round(loaded / meta.size * 100)) : 0),
- err => { setState('ready'); onToast((err && err.message) || '下载失败'); });
- if (result && result.then) {
- result.then(() => setState('done')).catch(() => {}); // onError above already surfaced the failure.
- }
- };
- const cancel = () => { if (downloaderRef.current) downloaderRef.current.cancel(); setState('ready'); setProgress(0); };
- if (!meta) return
- {loadError ? '文件不存在或已被删除' : '加载中…'}
- ;
- return
-
-
- {extFromName(meta.name) || '?'}
- {meta.name}
- 该文件不支持在线预览
- {state === 'loading' ?
- 下载中... {progress}%
-
- {bytesToHumanSize(meta.size * progress / 100)} / {bytesToHumanSize(meta.size)}
- : null}
- {state === 'ready' ? download 立即下载({bytesToHumanSize(meta.size)}) :
- state === 'loading' ? close 取消
:
- onToast('已保存,可在浏览器下载列表中查看')}>open_in_new 用其他应用打开
}
-
-
;
-}
-
-// 不用服务端返回的 matches 偏移,改为在展示文本上本地匹配关键词。服务端那份偏移有三层问题,
-// 全都对不上前端要高亮的这段文字:
-// 1. 结构是 [{start,end}] 对象数组(im_http.go:9151),按 [start,len] 数组解构会直接抛
-// TypeError(对象不可迭代),整个搜索结果渲染崩掉;
-// 2. start/end 是 Go 的字节下标(strings.Index + len(query)),中文一字三字节,跟 JS 的
-// UTF-16 下标永远错位;
-// 3. 偏移基准是完整原文,但 snippet 在超过 120 字时会被开窗截断(im_http.go:9128-9139),
-// 两者基准不一致——而且那段开窗自己也混用了字节下标和 rune 切片。
-// 本地匹配没有这些歧义:所见即所高亮。
-function Highlight({text, query}) {
- const needle = (query || '').trim();
- if (!needle || !text) return text;
- const parts = [];
- const haystack = text.toLowerCase();
- const lowered = needle.toLowerCase();
- let last = 0;
- for (let at = haystack.indexOf(lowered); at !== -1; at = haystack.indexOf(lowered, at + lowered.length)) {
- if (at > last) parts.push(text.slice(last, at));
- parts.push({text.slice(at, at + needle.length)} );
- last = at + needle.length;
- }
- if (!parts.length) return text;
- if (last < text.length) parts.push(text.slice(last));
- return parts;
-}
-
-function HistoryNav({active}) {
- const tabs = [['history-search', '聊天记录'], ['history-images', '图片'], ['history-videos', '视频'], ['history-files', '文件'], ['history-links', '链接']];
- return {tabs.map(item => navigateReplace(item[0])}>{item[1]} )} ;
-}
-
-const HISTORY_TITLES = {'history-search': '查找聊天记录', 'history-files': '文件', 'history-links': '链接', 'history-images': '图片', 'history-videos': '视频'};
-const HISTORY_TYPE = {'history-search': 'all', 'history-files': 'file', 'history-links': 'link', 'history-images': 'image', 'history-videos': 'video'};
-// Same bracket-label convention as message-center-view.jsx's draftyPreview() -- messageText() on the
-// backend has no attachment-aware summary, so a non-text search hit's own "snippet" field is often just
-// the raw serialized Drafty content; a fixed label per type reads far better than that.
-const TYPE_LABEL = {image: '[图片]', video: '[视频]', voice: '[语音]', file: '[文件]', link: '[链接]'};
-
-function History({view, hashParams, myUserId, onToast}) {
- const topic = hashParams && hashParams.topic;
- // Keyed by both topic and view/tab -- reached from one chat's own settings, and a user plausibly
- // leaves to tweak a setting and comes back to keep browsing/searching the same conversation's
- // records (or checks a different chat's history later the same session), so dropping either half
- // of the key would bleed one chat's or one tab's data into another's.
- const cacheKey = topic ? `history:${topic}:${view}` : null;
- // 输入值与实际发请求用的值分开:原来 query 直接进 load 的依赖数组,输入 6 个字的关键词就打
- // 6 次接口,而且返回顺序没有保证——后发的短前缀请求可能后到、把正确结果覆盖掉。
- const [queryInput, setQueryInput] = React.useState('');
- const query = useDebouncedValue(queryInput, 300);
- const [items, setItems] = React.useState(() => (cacheKey && readCachedData(cacheKey)) || null);
- const [cursor, setCursor] = React.useState('');
- const [hasMore, setHasMore] = React.useState(false);
- // Same "stuck on 加载中… forever" gap as Favorites' loadError above, same fix -- a failed initial
- // load with nothing cached to fall back on left this on the loading branch permanently, with only
- // a 2s toast (already faded by the time anyone looks back) as the only sign anything went wrong.
- const [loadError, setLoadError] = React.useState(false);
- const [mediaUrls, setMediaUrls] = React.useState({});
- const [senderNames, setSenderNames] = React.useState({});
- // Same stale-response guard as Favorites' load() above and for the same reason -- a slower
- // request for a since-abandoned topic/tab/query can still resolve after a newer one and
- // overwrite its correct results.
- const requestIdRef = React.useRef(0);
- const load = React.useCallback((append) => {
- if (!topic) return;
- const requestId = ++requestIdRef.current;
- const type = HISTORY_TYPE[view];
- const call = view === 'history-search' ? searchTopicMessages(topic, {query, type, cursor: append ? cursor : ''}) :
- getTopicMedia(topic, type, append ? cursor : '');
- call.then(data => {
- if (requestIdRef.current !== requestId) return;
- setLoadError(false);
- setItems(prev => {
- const list = append ? (prev || []).concat(data.items || []) : (data.items || []);
- if (!query && cacheKey) writeCachedData(cacheKey, list);
- return list;
- });
- setCursor(data.next_cursor || ''); setHasMore(!!data.next_cursor);
- }).catch(err => {
- if (requestIdRef.current !== requestId) return;
- onToast(err.message || '加载失败');
- if (!append) setLoadError(true);
- });
- }, [topic, view, query, cursor]);
- React.useEffect(() => {
- setLoadError(false);
- setItems((!query && cacheKey && readCachedData(cacheKey)) || null);
- load(false);
- }, [topic, view, query]);
- React.useEffect(() => {
- if (view !== 'history-images' && view !== 'history-videos') return;
- (items || []).forEach(item => {
- if (mediaUrls[item.seq]) return;
- resolveAttachmentUrl(attachmentFromFlat(item)).then(url => { if (url) setMediaUrls(v => ({...v, [item.seq]: url})); });
- });
- }, [items, view]);
- // item.from (like snapshot.from on a favorite) is a bare uid with no "usr" prefix -- every other uid
- // in this app (topic names, hash params, etc.) is fully-prefixed, so it has to be added back before
- // this can be looked up like any other user id.
- React.useEffect(() => {
- (items || []).forEach(item => {
- const from = item.from;
- if (!from || senderNames[from] || from === myUserId || ('usr' + from) === myUserId) return;
- getUserProfile(from.startsWith('usr') ? from : 'usr' + from).then(p => {
- setSenderNames(v => ({...v, [from]: p.display_name}));
- }).catch(() => {});
- });
- }, [items]);
- // Mirrors tinode-web.jsx's own handleTopicSelected exactly (Navigation.setUrlTopic('', topicName)
- // -- an empty base hash, not the current one, per its "Clear newTopicParams after use" comment) so
- // this lands the same way a normal chat-list click does, then layers the jump target on as a param;
- // navigate()'s own helper only ever touches path[0]+params, never path[1] (the actual topic slot).
- const jump = seq => Navigation.navigateTo(Navigation.addUrlParam(Navigation.setUrlTopic('', topic), 'jumpseq', seq));
- const nameOf = from => {
- if (!from) return '';
- if (from === myUserId || ('usr' + from) === myUserId) return '我';
- return senderNames[from] || from;
- };
- const openLink = url => {
- checkURL(url, 'message').then(res => {
- if (res.decision === 'allow') window.open(url, '_blank', 'noopener,noreferrer');
- else onToast(res.decision === 'block' ? '该链接存在风险,已被拦截' : '该链接可能存在风险,请谨慎访问');
- }).catch(() => onToast('链接安全校验失败'));
- };
- const list = items || [];
- return
- {/* Reached from either a friend's chat settings (usrXXX topic, reads hashParams.uid) or a
- group's (grpXXX topic, reads hashParams.topic) -- the topic's own prefix says which settings
- page to return to and which param name it expects; passing the wrong one is the same
- blank-page failure as History's other entry points had before this was a switch. */}
-
- {view === 'history-search' ?
: null}
-
-
- {items === null ? {loadError ? '加载失败,请重试' : '加载中…'}
: !list.length ?
- {view === 'history-search' ? 'search_off' : 'inbox'} 暂无内容
:
- view === 'history-search' ?
- {/* 同一处"问题13"修复暴露的缺口,History 自己的一份,只影响 type==='text':非 text 类型
- 始终走 TYPE_LABEL[item.type] 的固定方括号标签("[图片]"等),从不读 item.snippet,
- 所以 removed 与否对它们没有可观察差异,不需要改。text 类型是唯一真正显示 item.snippet
- 的分支——一个没有搜索关键词的浏览(query 为空,后端不加内容过滤,撤回/删除的消息也会
- 原样出现在结果里)如果命中一条已撤回的文本消息,item.snippet 现在会是后端
- messageText() 对 nil content 兜底出来的字面量字符串 "null"(json.Marshal(nil)),而不
- 是空字符串——不判断 availability 的话,用户会看到一行显示"null"的消息,比"看起来没被
- 撤回"更令人困惑。 */}
- {list.map(item => jump(item.seq)}>
- {nameOf(item.from)} {item.type === 'text' ?
- (item.availability === 'removed' ? '文本已失效' : ) :
- (TYPE_LABEL[item.type] || item.snippet || '')}
- {shortDateFormat(new Date(item.ts))} chevron_right
- )}
- :
- view === 'history-files' ? {list.map(item => {
- const att = attachmentFromFlat(item);
- return
- {nameOf(item.from)} {shortDateFormat(new Date(item.ts))}
- navigate('file-download', {topic, seq: item.seq})} />
- ;
- })} :
- view === 'history-links' ? {list.map(item =>
- {/* 需求7.14"点击时执行外链安全策略,并允许用户定位回原始消息"是两个独立动作——点链接本身
- 走 openLink(安全校验+开外链),定位回原消息复用其它tab都在用的 jump(seq),挂在头部
- 这一行上(sender+时间),跟 history-files 的头部保持视觉一致,只是这里多了可点击行为。 */}
- jump(item.seq)}>
- {nameOf(item.from)} {shortDateFormat(new Date(item.ts))}
-
-
- )} :
- {list.map(item => {
- // mediaMessagePayload (getTopicMedia) is flat and has no top-level `duration`/poster fields --
- // the real values live inside the reconstructed Drafty entity, same as history-files' FileCard
- // already reconstructs `att` for name/size/mime just above.
- const videoAtt = view === 'history-videos' ? attachmentFromFlat(item) : null;
- return jump(item.seq)} removed={item.availability === 'removed'} />;
- })} }
- {hasMore ? load(true)}>加载更多 : null}
-
-
;
-}
-
-export default function ContentLibraryView({view, hashParams, tinode, myUserId, onTopicSelected, chatList}) {
- const [toast, setToast] = React.useState(''); let content;
- if (view.startsWith('favorites')) content = ;
- else if (view === 'favorite-share') content = ;
- else if (view.startsWith('file-download')) content = ;
- else content = ;
- return <>{content} setToast('')} />>;
-}
diff --git a/src/views/direct-message-view.jsx b/src/views/direct-message-view.jsx
deleted file mode 100644
index 8874b4c7..00000000
--- a/src/views/direct-message-view.jsx
+++ /dev/null
@@ -1,278 +0,0 @@
-import React from 'react';
-
-import Navigation from '../lib/navigation.js';
-
-const navigate = view => Navigation.navigateTo(view);
-
-const VIEW_CONFIG = {
- 'chat-inbound-request': {relationship: 'pending_inbound', variant: 'inbound-request'},
- 'chat-inbound-message': {relationship: 'pending_inbound', variant: 'inbound-message'},
- 'chat-outbound-request': {relationship: 'pending_outbound', variant: 'outbound-request'},
- 'chat-status': {relationship: 'friend', variant: 'status'},
- 'chat-friend': {relationship: 'friend', variant: 'friend'},
- 'chat-media': {relationship: 'friend', variant: 'media'},
- 'chat-blocked': {relationship: 'blocked_by_peer', variant: 'blocked'},
- 'chat-deleted': {relationship: 'none', variant: 'deleted'},
- 'chat-standard': {relationship: 'friend', variant: 'standard'},
- 'chat-attachments': {relationship: 'friend', variant: 'attachments'},
- 'chat-mixed': {relationship: 'friend', variant: 'mixed'},
- 'chat-contact-card': {relationship: 'friend', variant: 'contact-card'}
-};
-
-const ATTACHMENTS = [
- {id: 'photo', icon: 'image', label: '照片'},
- {id: 'camera', icon: 'photo_camera', label: '拍摄'},
- {id: 'call', icon: 'call', label: '视频语音'},
- {id: 'card', icon: 'contact_page', label: '名片'},
- {id: 'file', icon: 'folder', label: '文件'},
- {id: 'favorite', icon: 'bookmark', label: '收藏'},
- {id: 'video', icon: 'videocam', label: '视频通话'}
-];
-
-function Icon({children}) {
- return {children} ;
-}
-
-function Toast({message, onDone}) {
- React.useEffect(() => {
- if (!message) return undefined;
- const timer = window.setTimeout(onDone, 2200);
- return () => window.clearTimeout(timer);
- }, [message, onDone]);
- return message ? {message}
: null;
-}
-
-function ChatAvatar({mine = false, peer = false}) {
- return ;
-}
-
-function Status({value, failedReason}) {
- if (!value && !failedReason) return null;
- return
- {failedReason ? error : null}{failedReason || value}
-
;
-}
-
-function FileCard() {
- return
-
365天艾宾浩斯记忆曲线复习计划.pdf 115KB
-
P
-
;
-}
-
-function ContactCard({onOpen}) {
- return
- 栗子来了
- 个人名片 · 账号 QL1024
- ;
-}
-
-function VoiceMessage({duration = 6, playing = false}) {
- return {playing ? 'graphic_eq' : 'volume_up'} {duration}″
;
-}
-
-function ImageMessage({progress}) {
- return
-
- {progress < 100 ?
: null}
-
;
-}
-
-function Bubble({mine = false, children, status, failedReason, type = 'text', onOpenCard}) {
- return
- {!mine ?
: null}
-
- {type === 'file' ?
: type === 'card' ?
:
-
{children}
}
-
-
- {mine ?
: null}
-
;
-}
-
-function RelationNotice({children, action, onAction}) {
- return {children} {action ? {action} : null}
;
-}
-
-function ChatTimeline({variant, uploadProgress, sendStatus, onRetry, onOpenCard, onFriendAction}) {
- if (variant === 'inbound-request' || variant === 'inbound-message') {
- return <>
- 7月30日 11:40
- {variant === 'inbound-request' ? '请求添加你为好友' : '我是祈万事安好'}
- 以上是打招呼的消息
- onFriendAction('declined')}>拒绝 onFriendAction('accepted')}>通过申请
- >;
- }
-
- if (variant === 'outbound-request') {
- return <>
- 7月30日 11:40
- 请求添加你为好友
- 好友申请已发送,等待对方处理
- >;
- }
-
- if (variant === 'status') {
- return <>
- 7月30日 11:40
- 我是祈万事安好
- 你已添加安妮,现在可以开始聊天。
- >;
- }
-
- if (variant === 'friend') {
- return <>
- 8月27日 11:11
- 你们已成为好友,现在可以开始聊天
- 我们已成为好友,现在我们可以开始聊天
- 你还好吗?
- >;
- }
-
- if (variant === 'media') {
- return <>
- 8月27日 11:11
- 接雨彤创建群聊
- 你还好吗?
-
-
-
- >;
- }
-
- if (variant === 'blocked') {
- return <>
- 7月30日 11:40
- 请求添加你为好友
- 你已添加了安妮,现在可以开始聊天。
- 7月30日 13:00
- 这边在网上看见你的留言,你想了解什么课程了
- 有课程辅导的老师?对新手友好吗?
- 这个不用担心特别好上手,学起来特别简单
- 拉黑我这么速度,真的牛
- >;
- }
-
- if (variant === 'deleted') {
- return <>
- 7月30日 13:00
- 你好我是XXX推荐过来的,这边想了解课程是吗
- 这边目前找到更好的课程,不考虑你们家,谢谢
- 这是我们导师名片,你可以加一下,对比一下价格
- onFriendAction('resent')}>你还不是他(她)朋友。
- 这么突然把我删了,真心累了
- onFriendAction('resent')}>你还不是他(她)朋友。
- >;
- }
-
- const base = <>
- 7月30日 11:40
- 请求添加你为好友
- 你已添加了安妮,现在可以开始聊天。
- 7月30日 13:00
- 你好,我是王珊珊,很高兴认识你,你怎么称呼
- 你好,我是安妮,想跟你咨询一下设计课程,方便?
- >;
-
- if (variant === 'standard') {
- return <>{base}非常方便,这是我们的课程目录,你可以看看了解一下 >;
- }
- if (variant === 'mixed') {
- return <>{base} >;
- }
- if (variant === 'contact-card') {
- return <>{base}这是我们导师名片,你可以加一下,有问题随时联系他 好的,非常感谢,那我这边就直接联系他吧 没问题的,有疑问随时沟通 >;
- }
- return base;
-}
-
-function AttachmentPanel({onChoose}) {
- return
- {ATTACHMENTS.map(item => onChoose(item)}>{item.icon} {item.label} )}
-
;
-}
-
-function Composer({relationship, attachmentOpen, setAttachmentOpen, onSend}) {
- const [draft, setDraft] = React.useState(sessionStorage.getItem('im-chat-draft') || '');
- const canSend = relationship === 'friend';
- const updateDraft = value => {
- setDraft(value);
- sessionStorage.setItem('im-chat-draft', value);
- };
- const submit = e => {
- e.preventDefault();
- if (!canSend || !draft.trim()) return;
- onSend(draft.trim());
- updateDraft('');
- };
- return
- navigate('voice-ready')}>record_voice_over
- updateDraft(e.target.value)} placeholder={canSend ? '输入消息' : relationship === 'pending_inbound' ? '请先处理好友申请' : relationship === 'pending_outbound' ? '等待对方通过好友申请' : '当前无法发送消息'} disabled={!canSend} />
- navigate('emoji-panel')}>sentiment_satisfied_alt
- {draft.trim() ? 发送 : setAttachmentOpen(!attachmentOpen)}>{attachmentOpen ? 'keyboard' : 'add_circle_outline'} }
- ;
-}
-
-export default function DirectMessageView({view}) {
- const config = VIEW_CONFIG[view] || VIEW_CONFIG['chat-standard'];
- const [relationship, setRelationship] = React.useState(config.relationship);
- const [variant, setVariant] = React.useState(config.variant);
- const [attachmentOpen, setAttachmentOpen] = React.useState(config.variant === 'attachments');
- const [uploadProgress, setUploadProgress] = React.useState(config.variant === 'media' ? 76 : 100);
- const [sendStatus, setSendStatus] = React.useState('发送中');
- const [extraMessages, setExtraMessages] = React.useState([]);
- const [toast, setToast] = React.useState('');
-
- React.useEffect(() => {
- if (variant !== 'status') return undefined;
- const stages = ['已发送', '已送达', '已读'];
- const timers = stages.map((status, index) => window.setTimeout(() => setSendStatus(status), 600 * (index + 1)));
- return () => timers.forEach(timer => window.clearTimeout(timer));
- }, [variant]);
-
- React.useEffect(() => {
- if (variant !== 'media' || uploadProgress >= 100) return undefined;
- const timer = window.setTimeout(() => setUploadProgress(value => Math.min(value + 4, 100)), 350);
- return () => window.clearTimeout(timer);
- }, [variant, uploadProgress]);
-
- const friendAction = action => {
- if (action === 'declined') {
- setRelationship('none');
- setToast('已拒绝好友申请');
- } else {
- setRelationship(action === 'accepted' ? 'friend' : 'pending_outbound');
- if (action === 'accepted') setVariant('friend');
- setToast(action === 'accepted' ? '好友申请已通过' : '好友验证已发送');
- }
- };
-
- return
-
- navigate('messages-home')} aria-label="返回消息">arrow_back_ios_new
-
接语彤 在线
- navigate('friend-settings')} aria-label="会话设置">more_vert
-
-
- setToast('正在重试发送')} onOpenCard={() => navigate('friend-profile')} onFriendAction={friendAction} />
- {extraMessages.map(item => {item.text} )}
-
- {relationship === 'friend' ? null :
info_outline {relationship === 'pending_inbound' ? '通过好友申请后即可发送消息' : relationship === 'pending_outbound' ? '好友申请正在等待处理' : relationship === 'none' ? '对方已不是你的好友,发送消息前需重新验证' : '当前关系状态下无法发送消息'}
}
-
{
- const id = Date.now();
- setExtraMessages(items => items.concat({id, text, status: '发送中'}));
- window.setTimeout(() => setExtraMessages(items => items.map(item => item.id === id ? {...item, status: '已发送'} : item)), 600);
- }} />
- {attachmentOpen && relationship === 'friend' ? {
- setToast(`已选择${item.label},上传接口将在联调阶段接入`);
- if (item.id === 'card') navigate('chat-contact-card');
- if (item.id === 'photo' || item.id === 'camera') navigate('media-uploading');
- if (item.id === 'call' || item.id === 'video') navigate('call-history');
- if (item.id === 'file') navigate('upload-file');
- if (item.id === 'favorite') navigate('favorites');
- }} /> : null}
- setToast('')} />
- ;
-}
diff --git a/src/views/group-management-view.jsx b/src/views/group-management-view.jsx
deleted file mode 100644
index f61cc48b..00000000
--- a/src/views/group-management-view.jsx
+++ /dev/null
@@ -1,1288 +0,0 @@
-import React from 'react';
-import QRCode from 'qrcodejs';
-import { pinyin } from 'pinyin-pro';
-
-import Navigation from '../lib/navigation.js';
-import {
- getGroups, createGroup, getGroup, updateGroup, getGroupSettings, updateGroupMemberSettings,
- updateMyGroupProfile, getMyGroupProfile, leaveGroup, getGroupMembers, createGroupQRCode, getGroupJoinRequests,
- acceptGroupJoinRequest, rejectGroupJoinRequest, getLatestGroupAnnouncement,
- updateCurrentGroupAnnouncement, readGroupAnnouncement, getGroupGovernance, updateGroupGovernance, addGroupAdmins,
- deleteGroupAdmin, muteGroupMembers, getGroupMutes, unmuteGroupMember, transferGroupOwner,
- getGroupFiles, addGroupFile, deleteGroupFile, uploadLargeFile, getFileUrl, getUserProfile,
- getGroupMemberProfile, fetchAllPages, getFriends, inviteGroupMembers
-} from '../lib/im-api.js';
-import { readCachedData, writeCachedData, useDebouncedValue } from '../lib/use-cached-data.js';
-
-// Carries the group `topic` (and anything else already in the hash) across navigation, the same way
-// contact-management-view.jsx threads `uid` -- these pages all need to know *which* group.
-const navigate = (view, topic) => {
- let hash = Navigation.setUrlSidePanel(Navigation.currentUrl(), view);
- hash = topic ? Navigation.addUrlParam(hash, 'topic', topic) : Navigation.removeUrlParam(hash, 'topic');
- Navigation.navigateTo(hash);
-};
-
-// 'group-member-profile' is contact-management-view.jsx's Profile component (Ch.5) reused as-is for
-// viewing a group member who isn't necessarily a friend -- that route keys off `uid`, not `topic`, so it
-// can't go through this file's own `navigate` helper above without clobbering the wrong hash param.
-// 除了跳转,还必须先把已知资料写进 'profile-seed:' 缓存 —— 目标页(第5章的 Profile)会先读这个种子,
-// 因为 GET /users/{id}/profile 对非好友按隐私默认直接 403(imPrivacyAllowsProfile)。群成员里
-// 大部分不是好友,不 seed 的话点进去就是"资料加载失败,请重试"。第5章自己的入口
-// (contact-management-view.jsx 的 seedProfile)一直是这么做的,这里漏了。
-const navigateToProfile = (topic, userId, member) => {
- // 先用群成员列表里已有的字段摆一个立即可用的种子(避免导航瞬间空白),
- // 但这份种子缺 capabilities/relationship——GET /groups/{topic}/members 从不返回这两个字段
- // (已实测确认),Profile 组件正是靠它们判断"要不要显示添加好友按钮"和"该显示哪句失败文案"。
- // 缺了它们,非好友的群成员主页会落到 cannotAddReason 的默认分支——不管真实关系是什么,
- // 一律显示"好友申请正在等待验证",添加好友按钮永远不出现。
- if (member && userId) {
- writeCachedData('profile-seed:' + userId, {
- user_id: userId,
- display_name: member.group_nickname || member.display_name || userId,
- avatar_file_id: member.avatar_file_id,
- ...(member.signature ? {signature: member.signature} : {}),
- ...(member.region ? {region: member.region} : {}),
- ...(member.gender ? {gender: member.gender} : {})
- });
- }
- Navigation.navigateTo(
- Navigation.addUrlParam(Navigation.setUrlSidePanel(Navigation.currentUrl(), 'group-member-profile'), 'uid', userId));
- // 群场景下真正该用的是 GET /groups/{topic}/members/{user_id}/profile——群上下文里权限判断更
- // 宽松,实测对方哪怕不是好友也能拿到 {capabilities, relationship}(Profile 组件目标页读的
- // GET /users/{id}/profile 对非好友是 403,只能回退用种子)。异步补种:请求回来后如果用户还
- // 停在这个人的资料页(没有连点跳到别处),用真实数据覆盖种子并强制该页面重新读取。
- if (topic && userId) {
- getGroupMemberProfile(topic, userId).then(real => {
- const current = readCachedData('profile-seed:' + userId) || {};
- writeCachedData('profile-seed:' + userId, {...current, ...real});
- window.dispatchEvent(new CustomEvent('profile-seed-updated', {detail: {uid: userId}}));
- }).catch(() => {});
- }
-};
-
-export const GROUP_MANAGEMENT_VIEWS = [
- 'group-list', 'group-empty', 'group-invite', 'group-events', 'group-join-requests',
- 'group-new-request', 'group-announcement', 'group-summary', 'group-announcement-detail',
- 'group-settings', 'group-profile', 'group-avatar', 'group-nickname', 'group-description',
- 'group-my-nickname', 'group-members', 'group-role-message', 'group-qr', 'group-governance', 'group-admins',
- 'group-admin-add', 'group-admin-success', 'group-admin-partial', 'group-admin-removed',
- 'group-mute-add', 'group-muted', 'group-owner-select', 'group-owner-confirm', 'group-files',
- 'group-file-upload', 'group-invite-friends'
-];
-
-// Same first-pinyin-letter grouping as contact-management-view.jsx's alphaInitial -- duplicated rather
-// than shared, since that file's copy is unexported and this one is the only other place that needs it.
-function alphaInitial(name) {
- const ch = (name || '').trim()[0] || '#';
- if (/[A-Za-z]/.test(ch)) {
- return ch.toUpperCase();
- }
- if (/[一-龥]/.test(ch)) {
- const letter = pinyin(ch, {pattern: 'first', toneType: 'none'});
- return /[a-z]/i.test(letter) ? letter.toUpperCase() : '#';
- }
- return '#';
-}
-
-function groupBy(list, keyFn) {
- const groups = {};
- list.forEach(item => {
- const key = keyFn(item);
- (groups[key] || (groups[key] = [])).push(item);
- });
- return Object.keys(groups).sort().map(key => [key, groups[key]]);
-}
-
-// GET /v1/im/groups 的 name/member_count 目前不可信:handler 用的是 store.Users.GetSubs,而 store 自己的
-// 注释就写着它 "Does not load Public/Trusted or Private"(store.go:834-835),可 groupListItemPayload
-// 偏偏去读 sub.GetPublic() 取名字、sub.GetSubCnt() 取人数——于是每个群都返回自己的 topic id 当名字、
-// 0 当人数(应该用 GetTopics,它会把 Public 拷到 subscription 上)。已记入后端问题清单。
-// 在后端修好之前,用本地 Tinode 会话缓存兜底——消息首页的群名一直是从这里取的,是准的。
-function groupDisplayName(tinode, group) {
- if (group.name && group.name !== group.topic) return group.name;
- const t = tinode && tinode.getTopic ? tinode.getTopic(group.topic) : null;
- const pub = t && t.public;
- // 群的 public 用的是 `name`(实测本地缓存就是 {name:"扫码测试群", version:1}),后端自己的
- // groupName() 也是按 name/display_name/fn 这个顺序找的,保持一致。
- const local = pub && (pub.name || pub.display_name || pub.fn);
- return local || group.name || group.topic;
-}
-
-function groupMemberCount(tinode, group) {
- if (group.member_count) return group.member_count;
- const t = tinode && tinode.getTopic ? tinode.getTopic(group.topic) : null;
- return (t && t.subcnt) || 0;
-}
-
-function shortDate(iso) {
- if (!iso) return '';
- const d = new Date(iso);
- return `${d.getMonth() + 1}-${String(d.getDate()).padStart(2, '0')}`;
-}
-
-// -- Demo-only mock data below this line: still used by the untouched GroupChat/ChatHeader/SystemEvent
-// -- trio (see comment above GroupChat). Those views are a standalone design reference showing message-
-// -- timeline states -- the real group chat timeline is messages-view.jsx, not this file (same treatment
-// -- Ch.3 gave direct-message-view.jsx). Nothing else in this file reads these constants any more.
-const MEMBERS = [
- {id: 'annie', name: '安妮', role: '管理员', avatar: 'img/bkg/p09.jpg'},
- {id: 'lizi', name: '栗子来了', role: '成员', avatar: 'img/bkg/p15.jpg'},
- {id: 'wang', name: '王珊珊', role: '成员', avatar: 'img/bkg/p14.jpg'},
- {id: 'sun', name: '孙光洪', role: '成员', avatar: 'img/bkg/p03.jpg'},
- {id: 'lin', name: '林间风', role: '成员', avatar: 'img/bkg/p18.jpg'}
-];
-const GROUPS_DEMO = {
- owned: [{id: 'product', name: '产品研发群', count: 18, avatar: 'img/bkg/p22.jpg'}]
-};
-
-function Icon({children}) { return {children} ; }
-// onBack:少数页面的返回目标不是"另一个侧栏视图",而是要关掉侧栏回到某个会话(需要写 path[1],
-// navigate 辅助函数只会写 query 参数),这类情况传一个自己的回调进来。
-function Header({title, back = 'group-list', backTopic, onBack, right, onRight}) {
- return
- { if (!Navigation.goBack()) { if (onBack) onBack(); else navigate(back, backTopic); } }} aria-label="返回">arrow_back_ios_new
- {title}
- {right ? {right} : }
- ;
-}
-function Avatar({item, icon}) {
- const fileId = item && item.avatar_file_id;
- const [url, setUrl] = React.useState(null);
- React.useEffect(() => {
- if (!fileId) { setUrl(null); return undefined; }
- let live = true;
- getFileUrl(fileId).then(u => { if (live) setUrl(u); }).catch(() => {});
- return () => { live = false; };
- }, [fileId]);
- return {url ? : {icon || 'group'} } ;
-}
-function Toast({message, onDone}) {
- React.useEffect(() => { if (!message) return undefined; const timer = window.setTimeout(onDone, 2000); return () => window.clearTimeout(timer); }, [message, onDone]);
- return message ? {message}
: null;
-}
-// Centered overlay confirmation, docs/images/添加管理员-设置成功.png -- distinct from the bottom Toast
-// above (used for lighter "已同意进群申请"-style feedback that doesn't need a heavier interrupt).
-function CenterToast({message, onDone}) {
- React.useEffect(() => { if (!message) return undefined; const timer = window.setTimeout(onDone, 1400); return () => window.clearTimeout(timer); }, [message, onDone]);
- return message ? check_circle {message}
: null;
-}
-function Search({value, onChange, placeholder = '搜索'}) {
- return search onChange(event.target.value)} placeholder={placeholder} /> ;
-}
-function Toggle({checked, onChange, disabled}) {
- return !disabled && onChange(!checked)}> ;
-}
-
-function GroupList({tinode, onToast}) {
- // Only the unfiltered (query === '') result is worth caching -- caching a search result under the
- // same key would show stale search results instead of the real list on the next plain visit.
- const [lists, setLists] = React.useState(() => readCachedData('groups') || {owned: null, managed: null, joined: null});
- const [loadErrors, setLoadErrors] = React.useState({owned: false, managed: false, joined: false});
- const [category, setCategory] = React.useState('owned');
- // 这里比别处更值得防抖:reload 对 owned/managed/joined 三个分类各发一次请求,
- // 每敲一个字符就是 3 个 getGroups。
- const [queryInput, setQueryInput] = React.useState('');
- const query = useDebouncedValue(queryInput, 300);
- const [creating, setCreating] = React.useState(false);
- const [name, setName] = React.useState('');
- const [submitting, setSubmitting] = React.useState(false);
- const reload = React.useCallback(() => {
- ['owned', 'managed', 'joined'].forEach(cat => {
- getGroups({category: cat, query}).then(data => {
- const items = data.items || [];
- setLists(v => {
- const next = {...v, [cat]: items};
- if (!query) writeCachedData('groups', next);
- return next;
- });
- }).catch(() => { onToast('加载失败'); setLoadErrors(v => ({...v, [cat]: true})); });
- });
- }, [query]);
- React.useEffect(() => { reload(); }, [reload]);
- const current = lists[category];
- const submit = () => {
- if (!name.trim() || submitting) return;
- setSubmitting(true);
- createGroup({name: name.trim()}).then(() => {
- setCreating(false); setName(''); onToast('群聊创建成功'); reload();
- }).catch(err => onToast(err.message || '创建失败')).finally(() => setSubmitting(false));
- };
- return ;
-}
-
-// GroupChat/ChatHeader/SystemEvent: standalone design-reference demo (docs/images/内容沟通对话*.png), same
-// treatment as Ch.3's direct-message-view.jsx -- the real group timeline is messages-view.jsx, which now
-// has its own real relationship/announcement/join-request/role-badge wiring. Left unchanged, not rewired.
-function ChatHeader() { return navigate('group-list')}>arrow_back_ios_new
产品研发群 18人
navigate('group-settings')}>more_vert ; }
-function SystemEvent({children}) { return {children}
; }
-function GroupChat({mode, onToast}) {
- const [joined, setJoined] = React.useState(false);
- return {['group-new-request', 'group-summary'].includes(mode) ?
navigate('group-join-requests')}>person_add_alt 新的进群申请 2 条申请等待处理 chevron_right : null}{['group-announcement', 'group-summary'].includes(mode) ?
navigate('group-announcement-detail')}>campaign 群公告 下午三点进行版本评审,请准时参加 chevron_right : null}
今天 10:24
{mode === 'group-invite' ? 一起参与新版产品的设计与研发讨论
{setJoined(true); onToast('已加入产品研发群');}}>{joined ? '已加入' : '立即加入'} : <>安妮邀请栗子来了加入群聊 管理员通过了王珊珊的进群申请 >}record_voice_over sentiment_satisfied_alt add_circle_outline
;
-}
-
-function JoinRequests({topic, fromChat, onToast}) {
- // A persistent in-chat banner (messages-view.jsx's handleShowAnnouncement/handleShowJoinRequests)
- // is a second entry point besides group-settings, and this whole feature remounts on every
- // navigation step (see GroupManagementView's `key={view}` note) -- so an admin actively working
- // through requests re-triggers this blank "加载中" flash on essentially every accept/reject.
- // `fromChat`/`backToChat` mirror Announcement's handling of the exact same two-entry-point
- // ambiguity above -- without it, returning from a click on the in-chat banner always landed on
- // "group-settings" (closing the open conversation) instead of back to the chat itself.
- const backToChat = React.useCallback(() => {
- Navigation.navigateTo(Navigation.setUrlTopic(Navigation.setUrlSidePanel('', ''), topic));
- }, [topic]);
- const [items, setItems] = React.useState(() => (topic && readCachedData('group-join-requests:' + topic)) || null);
- const [profiles, setProfiles] = React.useState({});
- const [loadError, setLoadError] = React.useState(false);
- // Tracked in a ref (not state) so `load` doesn't need `profiles` in its dependency array --
- // depending on `profiles` here would make `load` a new function every time a profile fetch
- // resolves, which would re-trigger the `useEffect(() => load(), [load])` below and re-fetch
- // the whole join-request list in a cascade, not just skip already-known profiles.
- const fetchedIds = React.useRef(new Set());
- const load = React.useCallback(() => {
- if (!topic) return;
- getGroupJoinRequests(topic, 'all').then(data => {
- const list = data.items || [];
- writeCachedData('group-join-requests:' + topic, list);
- setItems(list);
- const ids = new Set();
- list.forEach(r => { ids.add(r.user_id); if (r.inviter_id) ids.add(r.inviter_id); });
- ids.forEach(id => {
- if (fetchedIds.current.has(id)) return;
- fetchedIds.current.add(id);
- getUserProfile(id).then(p => setProfiles(v => ({...v, [id]: p}))).catch(() => { fetchedIds.current.delete(id); });
- });
- }).catch(() => { onToast('加载失败'); setLoadError(true); });
- }, [topic]);
- React.useEffect(() => { setItems((topic && readCachedData('group-join-requests:' + topic)) || null); load(); }, [topic, load]);
- const handle = (req, accept) => {
- const call = accept ? acceptGroupJoinRequest(topic, req.request_id, req.version) : rejectGroupJoinRequest(topic, req.request_id, req.version);
- call.then(() => { onToast(accept ? '已同意进群申请' : '已拒绝进群申请'); load(); })
- .catch(err => onToast(err.message || '操作失败'));
- };
- const nameOf = id => (profiles[id] && profiles[id].display_name) || id;
- const describe = req => req.source === 'qr' ? `${nameOf(req.user_id)}通过二维码分享进群` :
- req.inviter_id ? `${nameOf(req.inviter_id)}邀请${nameOf(req.user_id)}进群` : `${nameOf(req.user_id)}申请加入群聊`;
- // Exact resolved/rejected label wording isn't confirmed against the live status enum yet -- falls back
- // to the raw status string for anything unrecognized so a mismatch is visible instead of silently wrong.
- const statusLabel = {accepted: '已确定', approved: '已确定', rejected: '已拒绝', expired: '已过期'};
- return
-
-
- {items === null ? {loadError ? '申请列表加载失败' : '加载中…'}
:
- items.length ? items.map(item =>
-
-
- {describe(item)} {shortDate(item.created_at)}
- {item.status === 'pending' ?
-
- handle(item, false)}>拒绝
- handle(item, true)}>确定
- :
- {statusLabel[item.status] || item.status} }
- ) :
- 暂无进群申请
}
-
-
;
-}
-
-function Announcement({topic, myRole, fromChat, onToast}) {
- // Shares the 'group-announcement:'+topic cache key with GroupSettings' preview snippet below --
- // whichever screen loads first warms the cache for the other, and both are re-entered often (the
- // in-chat banner is a persistent second entry point on top of group-settings).
- const cachedAnnouncement = topic && readCachedData('group-announcement:' + topic);
- const [data, setData] = React.useState(() => cachedAnnouncement || null);
- const [draft, setDraft] = React.useState(() => (cachedAnnouncement && cachedAnnouncement.content) || '');
- const [editing, setEditing] = React.useState(false);
- const [saving, setSaving] = React.useState(false);
- const [loadError, setLoadError] = React.useState(false);
- const canEdit = myRole === 'owner' || myRole === 'admin';
- // Coming from the announcement banner atop the chat itself (messages-view.jsx's
- // handleShowAnnouncement) vs. from group-settings' "群公告" row land on the identical
- // path[0]=group-announcement-detail URL -- `fromChat` is the only thing telling them apart, so
- // "back" can return to the chat (close the sidepanel) instead of always forcing group-settings.
- // 从聊天页横幅进来时(messages-view.jsx:1152 用空基准拼路径,所以 path[1] 是空的),返回应该
- // 关掉侧栏回到那个会话。原来写的是 navigate('', topic)——navigate 只把 topic 写成 query 参数,
- // path[1] 依旧为空,tinode-web 的 handleRouteChange 就把 topicSelected 清成 null,会话跟着一起关了。
- // 这里显式把 topic 写回 path[1]。
- const backToChat = React.useCallback(() => {
- Navigation.navigateTo(Navigation.setUrlTopic(Navigation.setUrlSidePanel('', ''), topic));
- }, [topic]);
- React.useEffect(() => {
- if (!topic) return;
- const cached = readCachedData('group-announcement:' + topic);
- if (cached) { setData(cached); setDraft(cached.content || ''); }
- getLatestGroupAnnouncement(topic).then(d => {
- writeCachedData('group-announcement:' + topic, d);
- setData(d); setDraft(d.content || '');
- }).catch(() => { onToast('加载失败'); setLoadError(true); });
- }, [topic]);
- const publish = () => {
- if (!draft.trim() || saving) return;
- setSaving(true);
- updateCurrentGroupAnnouncement(topic, draft.trim(), data ? data.version : 0).then(d => {
- writeCachedData('group-announcement:' + topic, d);
- setData(d); setEditing(false); onToast('群公告已发布');
- }).catch(err => onToast(err.message || '发布失败')).finally(() => setSaving(false));
- };
- // 6.9 要求"记录已读状态",但这个接口(POST .../read)之前完全没有调用点——每次打开公告详情页
- // (无论从群设置点进来还是从聊天顶部横幅点进来)都视为"已读",用ref去重避免同一条公告在同一次
- // 页面停留内重复上报。静默失败:已读打点不是这个页面的主要功能,不应该在失败时打断用户。
- const readReportedFor = React.useRef(null);
- React.useEffect(() => {
- const id = data && data.announcement_id;
- if (!id || !topic || readReportedFor.current === id) return;
- readReportedFor.current = id;
- readGroupAnnouncement(topic, id).catch(() => {});
- }, [topic, data]);
- if (!data) return {loadError ? '群公告加载失败' : '加载中…'}
;
- return
-
-
- {canEdit ? setDraft(e.target.value)} onFocus={() => setEditing(true)}
- placeholder="请输入群公告内容" /> :
- <>
- campaign
- {data.content || '暂无群公告'}
- >}
- {!canEdit ? 仅群主及群管理员可编辑 : null}
-
-
;
-}
-
-function GroupSettings({topic, myUserId, tinode, onToast}) {
- // This is the landing point for "back" from GroupProfile/GroupFiles/Governance/MyNicknameEditor/
- // Announcement, and (per GroupManagementView's `key={view}` note) the whole subtree remounts on
- // every one of those round trips -- essentially every group-settings visit after the first refetches
- // from scratch, making this the single highest-traffic page in this file to seed from cache.
- const [group, setGroup] = React.useState(() => (topic && readCachedData('group-settings:' + topic)) || null);
- const [loadError, setLoadError] = React.useState(false);
- // The member strip and AdminList/`myRole` all call the identical unfiltered getGroupMembers(topic) --
- // sharing one 'group-members:'+topic cache entry (storing the raw list, sliced to 9 only for local
- // display here) lets any one of the three warm it for the others.
- const [members, setMembers] = React.useState(() => {
- const cached = topic && readCachedData('group-members:' + topic);
- return cached ? cached.slice(0, 9) : [];
- });
- const [announcement, setAnnouncement] = React.useState(() => {
- const cached = topic && readCachedData('group-announcement:' + topic);
- return cached && cached.content ? cached : null;
- });
- React.useEffect(() => {
- if (!topic) return;
- setGroup(readCachedData('group-settings:' + topic) || null);
- setMembers((readCachedData('group-members:' + topic) || []).slice(0, 9));
- const cachedAnn = readCachedData('group-announcement:' + topic);
- setAnnouncement(cachedAnn && cachedAnn.content ? cachedAnn : null);
- getGroupSettings(topic).then(g => { writeCachedData('group-settings:' + topic, g); setGroup(g); }).catch(() => { onToast('加载失败'); setLoadError(true); });
- getGroupMembers(topic).then(data => {
- const items = data.items || [];
- writeCachedData('group-members:' + topic, items);
- setMembers(items.slice(0, 9));
- }).catch(() => {});
- getLatestGroupAnnouncement(topic).then(d => {
- writeCachedData('group-announcement:' + topic, d);
- setAnnouncement(d && d.content ? d : null);
- }).catch(() => {});
- }, [topic]);
- const setPref = patch => {
- updateGroupMemberSettings(topic, {pinned: group.pinned, mute_notifications: group.mute_notifications, ...patch})
- .then(() => setGroup(g => {
- const next = {...g, ...patch};
- writeCachedData('group-settings:' + topic, next);
- return next;
- })).catch(err => onToast(err.message || '设置失败'));
- };
- const leave = () => {
- if (group && group.my_role === 'owner') { onToast('群主需先转让群主或解散群聊'); return; }
- leaveGroup(topic).then(() => { onToast('已退出群聊'); navigate('group-list'); }).catch(err => onToast(err.message || '操作失败'));
- };
- if (!group) return {loadError ? '群组信息加载失败' : '加载中…'}
;
- const canManage = group.permissions && group.permissions.can_manage_settings;
- const links = [
- ['group-profile', '群资料', group.name],
- ['group-announcement-detail', '群公告', announcement ? announcement.content : '暂无群公告'],
- ['group-files', '群文件', ''],
- ['group-my-nickname', '我在本群的昵称', '']
- ];
- if (canManage) links.splice(3, 0, ['group-governance', '群组管理', '']);
- return
-
navigate('group-governance', topic) : undefined} />
-
- navigate('group-profile', topic)}>
-
- {group.name} {group.description || '群主很懒,还没有群简介'}
- chevron_right
-
- {/* 参考图 docs/images/群聊设置.png:"群成员"标题行右侧是可点的"查看{N}群成员 >",现在真的有
- 落地页了(GroupMembers,复用 MemberPicker 同款字母分组/搜索,只读不可多选)。参考图里最后
- 一格的"删除"(移除成员)没有做——那是一个独立的、需要产品先确认交互细节的成员管理功能,
- 不属于这条"看不到全部成员"缺口本身。 */}
-
- navigate('group-members', topic)}>
- 群成员 共 {group.member_count} 位成员 chevron_right
-
-
- {members.slice(0, 9).map(item =>
navigateToProfile(topic, item.user_id, item)}>
- {item.group_nickname || item.display_name}
- )}
- {/* docs/images/内容沟通对话3-开启进群验证与拉人状态.png 里"你邀请XX、XX加入群聊"证明这条
- "从好友里选人直接邀请"的路径是产品设计的一部分,POST /groups/{topic}/invitations
- (inviteGroupMembers) 也早就是个真实可用的接口——原来这个格子只弹一句"请通过转发/分享
- 群二维码邀请成员",把这条路堵死了(二维码分享走的是另一条"申请入群待审批"的路,跟"邀请"
- 语义不同,不能互相替代)。 */}
-
navigate('group-invite-friends', topic)}>
- add 添加
-
-
-
- {links.map(item => navigate(item[0], topic)}>
- {item[1]} {item[2]} chevron_right
- )}
-
- 置顶聊天 setPref({pinned: v})} />
- 消息免打扰 setPref({mute_notifications: v})} />
- navigate('history-search', topic)}>查看聊天记录 chevron_right
- {
- const t = tinode ? tinode.getTopic(topic) : null;
- (t ? t.delMessagesAll(false) : Promise.resolve()).then(() => onToast('聊天记录已清除')).catch(err => onToast(err.message || '操作失败'));
- }}>清空聊天记录 chevron_right
- {/* target_type='group' 这条链路早就是通的(tinode-web.jsx 的 handleReportTopic 就在用),
- 这里原来一直是个"暂未开放"的死桩。跟 contact-management-view.jsx 的好友举报入口一样,
- 举报页要的是 target_type/target_id/target_name 三个参数,本文件的 navigate 辅助函数
- 只会写 topic,所以直接拼路径。 */}
- {
- let hash = Navigation.setUrlSidePanel('', 'abuse-report-reasons');
- hash = Navigation.addUrlParam(hash, 'target_type', 'group');
- hash = Navigation.addUrlParam(hash, 'target_id', topic);
- hash = Navigation.addUrlParam(hash, 'target_name', (group && group.name) || topic);
- Navigation.navigateTo(hash);
- }}>投诉/举报 chevron_right
-
- 退出群组
-
- ;
-}
-
-function GroupProfile({topic, myUserId, onToast}) {
- // Shares 'group:'+topic with the nickname/description editors' own getGroup(topic) calls -- opening
- // this once warms the cache for either of those too.
- const [group, setGroup] = React.useState(() => (topic && readCachedData('group:' + topic)) || null);
- const [loadError, setLoadError] = React.useState(false);
- // "群昵称" here means "my nickname within this group" (see MyNicknameEditor below), not the group's
- // own name -- confirmed live this preview row was showing group.name instead, since it's easy to
- // conflate the two. GET .../members/me/profile is the only endpoint that actually returns this --
- // confirmed live that the member list endpoint (getGroupMembers) never includes group_nickname on
- // any item, so a version of this fix that read it from there (like MyNicknameEditor below does)
- // would always show "未设置" even right after a successful save.
- const [myNickname, setMyNickname] = React.useState('');
- React.useEffect(() => {
- if (!topic) return;
- setGroup(readCachedData('group:' + topic) || null);
- getGroup(topic).then(g => { writeCachedData('group:' + topic, g); setGroup(g); }).catch(() => { onToast('加载失败'); setLoadError(true); });
- }, [topic]);
- React.useEffect(() => {
- if (!topic) return;
- getMyGroupProfile(topic).then(data => {
- setMyNickname(data.nickname || '');
- }).catch(() => {});
- }, [topic]);
- if (!group) return {loadError ? '群组信息加载失败' : '加载中…'}
;
- const canEdit = group.permissions && group.permissions.can_edit_profile;
- const nicknameLabel = myNickname || '未设置';
- return
-
-
- canEdit && navigate('group-avatar', topic)}>
- 群头像 chevron_right
-
- navigate('group-my-nickname', topic)}>
- 群昵称 {nicknameLabel} chevron_right
-
- {/* Swapping to a bare when !canEdit used to drop every .group-profile row rule (all of
- them are scoped to > button/> label) -- same fix as the avatar row just above: keep
- , gate it with disabled like that row already does. */}
- canEdit && navigate('group-nickname', topic)}>
- 群名称 {group.name} chevron_right
-
- canEdit && navigate('group-description', topic)}>
- 群简介 {group.description || '群主很懒,还没有群简介'} chevron_right
-
- navigate('group-qr', topic)}>群二维码 qr_code chevron_right
- navigate('group-members', topic)}>群成员 共{group.member_count}位成员 chevron_right
-
-
;
-}
-
-function AvatarSheet({topic, myUserId, tinode, onToast}) {
- const inputRef = React.useRef(null);
- const [uploading, setUploading] = React.useState(false);
- const pick = () => inputRef.current && inputRef.current.click();
- const onFile = e => {
- const file = e.target.files && e.target.files[0];
- e.target.value = '';
- if (!file || !tinode) return;
- setUploading(true);
- // Fetch the current version right before the write, same optimistic-concurrency protection the
- // name/description editors already have (GroupProfile below re-fetches independently and its
- // possibly-stale copy isn't reused here) -- avatar updates were the one group-profile field with
- // none at all.
- uploadLargeFile(tinode, file)
- .then(({fileId}) => getGroup(topic).then(group => updateGroup(topic, {avatar_file_id: fileId, version: group.version})))
- .then(() => { onToast('群头像已更新'); navigate('group-profile', topic); })
- .catch(err => onToast(err.message || '上传失败')).finally(() => setUploading(false));
- };
- return
-
-
-
- 设置群头像
- photo_library {uploading ? '正在上传…' : '从相册选择'}
- navigate('group-profile', topic)}>取消
-
-
;
-}
-
-function GroupQr({topic, onToast}) {
- const ref = React.useRef(null);
- const [data, setData] = React.useState(null);
- React.useEffect(() => {
- if (!topic) return;
- createGroupQRCode(topic).then(setData).catch(() => onToast('二维码生成失败'));
- }, [topic]);
- React.useEffect(() => {
- if (!ref.current || !data) return;
- ref.current.innerHTML = '';
- new QRCode(ref.current, {text: data.qr_payload, width: 190, height: 190, correctLevel: QRCode.CorrectLevel.M});
- }, [data]);
- const group = data && data.group_snapshot;
- const share = () => {
- if (navigator.share && data) {
- navigator.share({title: group && group.name, text: '扫码加入群聊', url: data.qr_payload}).catch(() => {});
- } else if (data) {
- navigator.clipboard.writeText(data.qr_payload).then(() => onToast('链接已复制')).catch(() => onToast('分享失败'));
- }
- };
- const save = () => {
- const canvas = ref.current && ref.current.querySelector('canvas');
- if (!canvas) { onToast('保存失败'); return; }
- const a = document.createElement('a');
- a.href = canvas.toDataURL('image/png');
- a.download = (group ? group.name : 'group') + '-qrcode.png';
- a.click();
- onToast('群二维码已保存');
- };
- return
-
-
-
- {group ? group.name : ''} {group ? group.member_count : 0} 位成员
-
- 使用APP扫码二维码,加入群聊
-
-
- share 分享好友
- download 保存至相册
-
-
-
;
-}
-
-// The five backend-persisted governance fields (server/im_http.go imGroupGovernancePatchRequest) --
-// docs/images/群组管理.png showed a 4th toggle "群成员修改昵称" that doesn't correspond to any real
-// governance field (its own description text also doesn't match member_upload_files' actual meaning,
-// which a *different*, buggier draft screenshot happened to label correctly). Shipping a toggle with
-// nothing to persist would be worse than a label mismatch, so this uses the mock's original 5-key
-// structure -- which lines up exactly with the 5 real backend fields -- with corrected descriptions.
-const GOVERNANCE_FIELDS = [
- ['join_approval', '进群验证', '开启后,他人申请加入群聊需群主或管理员同意'],
- ['mute_all', '全体禁言', '开启后,仅群主和管理员可以发言'],
- ['member_upload_files', '群成员文件上传', '关闭后,普通成员将无法上传群文件'],
- ['member_invite', '群成员邀请', '关闭后,仅群主和管理员可以邀请新成员进群'],
- ['member_private_chat', '群成员私信权限', '关闭后,不允许发送名片消息,且普通成员在群聊界面、群组信息内点击头像无反应。']
-];
-
-function Governance({topic, myRole, tinode, onToast}) {
- // Router page for AdminList/Muted/OwnerSelect/the avatar editor -- remounts (per GroupManagementView's
- // `key={view}` note) and reflashes its 5 toggles every time the user finishes one of those sub-tasks
- // and returns, so this is one of the highest-traffic pages in the file.
- const [gov, setGov] = React.useState(() => (topic && readCachedData('group-governance:' + topic)) || null);
- const [dissolving, setDissolving] = React.useState(false);
- const [confirmingDissolve, setConfirmingDissolve] = React.useState(false);
- const [loadError, setLoadError] = React.useState(false);
- React.useEffect(() => {
- if (!topic) return;
- setGov(readCachedData('group-governance:' + topic) || null);
- getGroupGovernance(topic).then(d => { writeCachedData('group-governance:' + topic, d); setGov(d); }).catch(() => { onToast('加载失败'); setLoadError(true); });
- }, [topic]);
- const setField = (key, value) => {
- // GET .../governance does return a real `version` (confirmed live) -- omitting it here isn't
- // just "no version sent", the server-side UPDATE for this endpoint skips its optimistic-lock
- // WHERE clause entirely whenever version<=0, so two admins toggling different switches at
- // nearly the same time would have the second write silently clobber the first with no conflict
- // signal. Sending the version this page already has on hand costs nothing and closes that gap.
- updateGroupGovernance(topic, {[key]: value, version: gov ? gov.version : 0}).then(data => { writeCachedData('group-governance:' + topic, data); setGov(data); })
- .catch(err => onToast(err.message || '设置失败'));
- };
- // 原来是裸 window.confirm()——本工程已经在"退出登录"那次修复里明确认定这类原生弹窗和
- // 其余确认弹层不一致(personal-settings-view.jsx 的 LogoutConfirm/ConfirmSheet 那条注释:
- // "a native confirm() is also environment-dependent: some embedded WebViews/kiosk contexts
- // disable synchronous dialogs outright...silently preventing [the action] with no error
- // shown")。解散群组同样不可逆、后果同样重大,却是本文件里唯一还在用 window.confirm() 的
- // 危险操作——本文件自己在 OwnerSelect 里已经有现成的同款弹层视觉语言(.group-sheet-backdrop
- // /.group-confirm-sheet),改成两段式状态复用它,不再依赖浏览器原生对话框。
- const requestDissolve = () => {
- // Backend already refuses to actually destroy the group for a non-owner (the same native
- // {del hard:true} call it silently downgrades into "remove me from this group" instead) --
- // but that response is indistinguishable from a real success, so without this gate a member
- // who somehow lands on this page got told "群聊已解散" when they'd really just left it.
- if (myRole !== 'owner') return;
- setConfirmingDissolve(true);
- };
- const dissolve = () => {
- const t = tinode ? tinode.getTopic(topic) : null;
- if (!t) { setConfirmingDissolve(false); return; }
- setDissolving(true);
- t.delTopic(true).then(() => { onToast('群聊已解散'); navigate('group-list'); })
- .catch(err => { onToast(err.message || '解散失败'); setConfirmingDissolve(false); }).finally(() => setDissolving(false));
- };
- if (!gov) return {loadError ? '群组管理加载失败' : '加载中…'}
;
- return
-
-
-
- navigate('group-avatar', topic)}>设置群头像 chevron_right
-
-
- navigate('group-join-requests', topic)}>邀请进群申请 chevron_right
- navigate('group-muted', topic)}>禁言的群成员 chevron_right
- navigate('group-admins', topic)}>设置管理员 chevron_right
- navigate('group-owner-select', topic)}>群主管理权转让 chevron_right
-
- {GOVERNANCE_FIELDS.map(([key, label, desc]) =>
- {label} {desc}
- setField(key, v)} />
-
)}
- 解散群组
-
- {confirmingDissolve ?
- 解散群组
- 解散后群聊将无法恢复,确定要解散该群聊吗?
- {dissolving ? '处理中…' : '确定'}
- setConfirmingDissolve(false)}>取消
- : null}
-
;
-}
-
-function deriveOwnerAndAdmins(items) {
- return {owner: items.find(m => m.role === 'owner') || null, admins: items.filter(m => m.role === 'admin')};
-}
-
-function AdminList({topic, onToast}) {
- // Shares the same 'group-members:'+topic cache entry as GroupSettings' member strip and
- // GroupManagementView's `myRole` lookup -- all three call the identical unfiltered getGroupMembers.
- const cachedMembers = topic && readCachedData('group-members:' + topic);
- const seed = cachedMembers ? deriveOwnerAndAdmins(cachedMembers) : {owner: null, admins: null};
- const [owner, setOwner] = React.useState(() => seed.owner);
- const [admins, setAdmins] = React.useState(() => seed.admins);
- const [toast, setToast] = React.useState('');
- const [loadError, setLoadError] = React.useState(false);
- const load = React.useCallback(() => {
- if (!topic) return;
- const cached = readCachedData('group-members:' + topic);
- if (cached) { const d = deriveOwnerAndAdmins(cached); setOwner(d.owner); setAdmins(d.admins); }
- // Fetch the unfiltered member list rather than {role:'admin'} + a separate per-user profile lookup
- // for the owner -- GET /users/{id}/profile 400s ("malformed request", field "user_id") when {id} is
- // the caller's own uid, which broke this whenever the viewer is themselves the group's owner.
- // Members cap out at 30/page against a 128-member group limit -- an admin sitting past page 1
- // used to vanish from this list entirely rather than the list just being incomplete.
- fetchAllPages(cursor => getGroupMembers(topic, {cursor})).then(items => {
- writeCachedData('group-members:' + topic, items);
- const d = deriveOwnerAndAdmins(items);
- setOwner(d.owner);
- setAdmins(d.admins);
- }).catch(() => { onToast('加载失败'); setLoadError(true); });
- }, [topic]);
- React.useEffect(() => { load(); }, [load]);
- const remove = userId => {
- // deleteGroupAdmin succeeding doesn't mean a follow-up load() will show it -- getGroupMembers
- // is backed by a server-side cache that this exact query shape can keep serving stale for a
- // while (confirmed live: an unfiltered re-query right after removal still showed the removed
- // admin, while a query with different params the session hadn't used yet returned correctly).
- // Patch the already-known-correct result into local state and the shared cache directly
- // instead of trusting a re-fetch to reflect what was just confirmed to have happened.
- deleteGroupAdmin(topic, userId).then(() => {
- setToast('管理员已移除');
- setAdmins(prev => prev ? prev.filter(a => a.user_id !== userId) : prev);
- const cached = readCachedData('group-members:' + topic);
- if (cached) writeCachedData('group-members:' + topic, cached.map(m => m.user_id === userId ? {...m, role: 'member'} : m));
- }).catch(err => onToast(err.message || '操作失败'));
- };
- return ;
-}
-
-// 参考图 群聊设置.png 里"群成员"标题行是可点的("查看{N}群成员 >"),但 GroupSettings 一直没有
-// 对应的落地页——群成员超过预览网格的9人就完全看不到其余成员。复用 MemberPicker 已经在用的
-// 字母分组+搜索逻辑,去掉多选/提交,只读展示每个人的角色。
-function GroupMembers({topic, onToast}) {
- const [members, setMembers] = React.useState(() => (topic && readCachedData('group-members:' + topic)) || null);
- const [query, setQuery] = React.useState('');
- const [loadError, setLoadError] = React.useState(false);
- React.useEffect(() => {
- if (!topic) return;
- fetchAllPages(cursor => getGroupMembers(topic, {cursor})).then(items => {
- writeCachedData('group-members:' + topic, items);
- setMembers(items);
- }).catch(() => { onToast('加载失败'); setLoadError(true); });
- }, [topic]);
- const visible = (members || []).filter(m => (m.group_nickname || m.display_name || '').toLowerCase().includes(query.toLowerCase()));
- const groups = groupBy(visible, m => alphaInitial(m.group_nickname || m.display_name));
- const roleLabel = role => role === 'owner' ? '群主' : role === 'admin' ? '管理员' : '';
- return
-
-
-
- {members === null ? {loadError ? '成员列表加载失败' : '加载中…'}
: groups.map(([letter, list]) =>
- {letter}
- {list.map(item => navigateToProfile(topic, item.user_id, item)}>
- {item.group_nickname || item.display_name} {roleLabel(item.role) ? {roleLabel(item.role)} : null}
- )}
- )}
-
-
;
-}
-
-// Shared alphabetically-grouped multi-select picker for admin-add / mute-add (same visual component,
-// three purposes, per docs/images/添加管理员.png vs .../添加管理员-设置成功(1).png which turned out to
-// actually be the mute-add variant of the identical layout).
-function MemberPicker({type, topic, onToast}) {
- const mute = type === 'mute-add';
- const back = mute ? 'group-muted' : 'group-admins';
- const [members, setMembers] = React.useState(null);
- const [selected, setSelected] = React.useState([]);
- const [query, setQuery] = React.useState('');
- const [toast, setToast] = React.useState('');
- const [busy, setBusy] = React.useState(false);
- const [loadError, setLoadError] = React.useState(false);
- React.useEffect(() => {
- if (!topic) return;
- fetchAllPages(cursor => getGroupMembers(topic, {cursor, ...(mute ? {muteEligible: true} : {eligibleRole: 'admin'})}))
- .then(items => setMembers(items.filter(m => m.eligible !== false)))
- .catch(() => { onToast('加载失败'); setLoadError(true); });
- }, [topic, mute]);
- const visible = (members || []).filter(m => (m.group_nickname || m.display_name || '').toLowerCase().includes(query.toLowerCase()));
- const groups = groupBy(visible, m => alphaInitial(m.group_nickname || m.display_name));
- const toggle = id => setSelected(v => v.includes(id) ? v.filter(x => x != id) : v.concat(id));
- const done = () => {
- if (!selected.length || busy) return;
- setBusy(true);
- const call = mute ? muteGroupMembers(topic, selected.map(user_id => ({user_id}))) : addGroupAdmins(topic, selected);
- call.then(result => {
- // 两个批量接口的响应结构不一样,不能用同一套判定:
- // - 添加管理员 POST .../admins/batch 返回 succeeded[],每项带 ok/reason(im_http.go:5377-5392)
- // - 添加禁言 POST .../mutes 返回 {topic, items},只有成功的才进 items,
- // 非法 uid 会被静默 continue(im_http.go:5486-5488),根本没有 succeeded 字段
- // 原来两条路都读 succeeded,禁言那条恒为 undefined → failed 恒为空 → 不管实际成没成都弹"设置成功"。
- let failedCount;
- if (mute) {
- const done = (result && Array.isArray(result.items)) ? result.items.length : selected.length;
- failedCount = Math.max(selected.length - done, 0);
- } else {
- failedCount = (result && Array.isArray(result.succeeded)) ? result.succeeded.filter(s => !s.ok).length : 0;
- }
- setToast(failedCount ? `成功设置 ${selected.length - failedCount} 人,${failedCount} 人设置失败` : '设置成功');
- // 导航会让整棵子树重挂载、把 toast 一起销毁。原来 900ms 就跳走,而 CenterToast 的生命
- // 周期是 1400ms —— 参考图里那个「设置成功」实际上只闪一下就被截断,而且从不在目标列表页出现。
- window.setTimeout(() => navigate(back, topic), 1500);
- }).catch(err => onToast(err.message || '设置失败')).finally(() => setBusy(false));
- };
- return
-
-
-
- {members === null ? {loadError ? '成员列表加载失败' : '加载中…'}
: groups.map(([letter, list]) =>
- {letter}
- {list.map(item => toggle(item.user_id)}>
- {item.group_nickname || item.display_name}
-
- {selected.includes(item.user_id) ? check : null}
-
- )}
- )}
-
-
setToast('')} />
- ;
-}
-
-// docs/images/内容沟通对话3-开启进群验证与拉人状态.png:"你邀请时间旅行者、李惠惠加入群聊" 这条系统
-//事件证明产品设计里确实有一条"从我的好友里挑人直接邀请进群"的路径,且 `POST /groups/{topic}/invitations`
-// (im-api.js 的 inviteGroupMembers) 早就封装好、服务端也确认真实可用——但全仓库检索不到任何调用点,
-// GroupSettings 成员格的"添加"格子原来只弹一句"请通过转发/分享群二维码邀请成员",把这条本该走的路
-// 堵死成了唯一路径(二维码分享走的是另一条"申请入群待审批"的路,语义并不一样,不能算是同一功能的替代)。
-// 复用 MemberPicker 同款字母分组多选布局,数据源换成好友列表并按当前群成员名单过滤掉已在群里的人。
-function InviteFriends({topic, onToast}) {
- const [friends, setFriends] = React.useState(null);
- const [memberIds, setMemberIds] = React.useState(null);
- const [selected, setSelected] = React.useState([]);
- const [query, setQuery] = React.useState('');
- const [toast, setToast] = React.useState('');
- const [busy, setBusy] = React.useState(false);
- const [loadError, setLoadError] = React.useState(false);
- React.useEffect(() => {
- if (!topic) return;
- fetchAllPages(cursor => getFriends('', cursor)).then(setFriends).catch(() => { onToast('加载失败'); setLoadError(true); });
- fetchAllPages(cursor => getGroupMembers(topic, {cursor})).then(items => setMemberIds(new Set(items.map(m => m.user_id))))
- // 成员表拉取失败不应该拦掉整个邀请流程——退化成"不过滤",最坏情况后端自己会在
- // 响应里把已在群里的人标成 already_member(治理开关那份文档已经实测过这个字段)。
- .catch(() => setMemberIds(new Set()));
- }, [topic]);
- const eligible = (friends || []).filter(f => !memberIds || !memberIds.has(f.user_id));
- const visible = eligible.filter(m => (m.remark || m.display_name || '').toLowerCase().includes(query.toLowerCase()));
- const groups = groupBy(visible, m => alphaInitial(m.remark || m.display_name));
- const toggle = id => setSelected(v => v.includes(id) ? v.filter(x => x != id) : v.concat(id));
- const done = () => {
- if (!selected.length || busy) return;
- setBusy(true);
- inviteGroupMembers(topic, selected).then(result => {
- const items = (result && result.items) || [];
- // 已实测过的两种真实 status:新邀请是 "pending"(对方会在跟我的会话里收到"立即加入"卡片),
- // 治理开关关闭邀请权限那份文档里实测过 "already_member"(对方已经在群里,直接被服务端忽略)。
- // 未识别的 status 一律计入"失败",宁可少报成功也不要谎报。
- const already = items.filter(it => it.status === 'already_member').length;
- const sent = items.filter(it => it.status === 'pending').length;
- const failed = Math.max(selected.length - sent - already, 0);
- const parts = [];
- if (sent) parts.push(`已发送 ${sent} 条邀请`);
- if (already) parts.push(`${already} 人已在群内`);
- if (failed) parts.push(`${failed} 人邀请失败`);
- setToast(parts.join(',') || '邀请已发送');
- window.setTimeout(() => navigate('group-settings', topic), 1500);
- }).catch(err => onToast(err.message || '邀请失败')).finally(() => setBusy(false));
- };
- return
-
-
-
- {friends === null || memberIds === null ? {loadError ? '好友列表加载失败' : '加载中…'}
:
- groups.length === 0 ? {friends.length ? '好友都已在群里了' : '暂无可邀请的好友'}
:
- groups.map(([letter, list]) =>
- {letter}
- {list.map(item => toggle(item.user_id)}>
- {item.remark || item.display_name}
-
- {selected.includes(item.user_id) ? check : null}
-
- )}
- )}
-
-
setToast('')} />
- ;
-}
-
-function Muted({topic, onToast}) {
- const [items, setItems] = React.useState(() => (topic && readCachedData('group-muted:' + topic)) || null);
- // groupGovernancePayload's mute list only ever returns a flat user_id (no display_name field --
- // confirmed against the real backend struct), so without this it rendered raw ids like "usr8x2kf9"
- // instead of names. The full member roster already carries group_nickname/display_name for
- // everyone still in the group, and fetching it here is one extra request for the whole list
- // (not per-row), unlike JoinRequests' per-item getUserProfile lookup.
- const [names, setNames] = React.useState({});
- const [avatars, setAvatars] = React.useState({});
- const [loadError, setLoadError] = React.useState(false);
- const load = React.useCallback(() => {
- if (!topic) return;
- fetchAllPages(cursor => getGroupMutes(topic, cursor)).then(list => {
- writeCachedData('group-muted:' + topic, list);
- setItems(list);
- }).catch(() => { onToast('加载失败'); setLoadError(true); });
- fetchAllPages(cursor => getGroupMembers(topic, {cursor})).then(items => {
- const map = {};
- const avatarMap = {};
- items.forEach(m => {
- map[m.user_id] = m.group_nickname || m.display_name || m.user_id;
- if (m.avatar_file_id) avatarMap[m.user_id] = {avatar_file_id: m.avatar_file_id};
- });
- setNames(map);
- setAvatars(avatarMap);
- }).catch(() => {});
- }, [topic]);
- React.useEffect(() => { setItems((topic && readCachedData('group-muted:' + topic)) || null); load(); }, [topic, load]);
- const unmute = userId => {
- unmuteGroupMember(topic, userId).then(() => { onToast('已解除禁言'); load(); })
- .catch(err => onToast(err.message || '操作失败'));
- };
- return ;
-}
-
-function OwnerSelect({topic, onToast}) {
- const [members, setMembers] = React.useState(null);
- const [query, setQuery] = React.useState('');
- const [target, setTarget] = React.useState(null);
- // 后端 serveIMGroupOwnerTransfer 只认 {target_user_id, version},没有任何二次验证/action_token
- // 机制(跟青少年模式关闭那种真的会被服务端拒绝的场景不一样)。群主转让同样不可逆、后果同样
- // 重大,照抄本项目已有的处理这类"后端没有二次验证但操作本身很危险"场景的方式——见
- // personal-settings-view.jsx 的 DeleteAccountConfirm:加一步前端自己的密码输入门槛,不追加到
- // 请求体、不指望服务端校验它,只是让"手滑点确定"变成需要多一步真实操作才会发生的事。
- const [passwordStep, setPasswordStep] = React.useState(false);
- const [password, setPassword] = React.useState('');
- const [busy, setBusy] = React.useState(false);
- const [loadError, setLoadError] = React.useState(false);
- React.useEffect(() => {
- if (!topic) return;
- fetchAllPages(cursor => getGroupMembers(topic, {cursor, eligibleRole: 'owner'}))
- .then(items => setMembers(items.filter(m => m.eligible !== false)))
- .catch(() => { onToast('加载失败'); setLoadError(true); });
- }, [topic]);
- const visible = (members || []).filter(m => (m.group_nickname || m.display_name || '').toLowerCase().includes(query.toLowerCase()));
- const admins = visible.filter(m => m.role === 'admin');
- const others = visible.filter(m => m.role !== 'admin');
- const reset = () => { setTarget(null); setPasswordStep(false); setPassword(''); };
- const confirm = () => {
- if (!password) { onToast('请输入密码以确认'); return; }
- if (busy || !target) return;
- setBusy(true);
- // transferGroupOwner's `version` param was never supplied here, so the optimistic-lock WHERE
- // clause on the backend's UPDATE was silently skipped (version<=0) -- this is the single most
- // consequential owner-transfer call in the app having zero protection against a concurrent
- // transfer. Fetch the live group version right before the irreversible call rather than trusting
- // any value this page might have cached earlier in its lifetime.
- getGroup(topic).then(group => transferGroupOwner(topic, target.user_id, group.version))
- .then(() => { onToast('群主已转让'); navigate('group-governance', topic); })
- .catch(err => onToast(err.message || '转让失败')).finally(() => setBusy(false));
- };
- const row = item => setTarget(item)}>
- {item.group_nickname || item.display_name}
- ;
- return
-
-
-
- {members === null ? {loadError ? '成员列表加载失败' : '加载中…'}
: <>
- {admins.length ? <>管理员
{admins.map(row)}> : null}
- {others.length ? <>群成员
{others.map(row)}> : null}
- >}
-
- {target && !passwordStep ?
- 选择新群主
- {target.group_nickname || target.display_name}成为新群主,确定后你将立即失去群主身份
- setPasswordStep(true)}>确定
- 取消
- : null}
- {target && passwordStep ?
: null}
-
;
-}
-
-const FILE_TYPE_META = {
- image: ['image', '#3ba55c'], video: ['videocam', '#3b82c4'], audio: ['audiotrack', '#8b5cf6'],
- document: ['description', '#e05d44'], file: ['insert_drive_file', '#8a8f98']
-};
-const DOCUMENT_EXT = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt'];
-// POST /groups/{topic}/files 的 file_type 字段是客户端在登记时自己声明的(addGroupFile 的
-// im-api.js 签名早就留了这个参数),不是服务端拿 mime_type 反推的——实测:即使 mime_type 正确
-// 存成 "image/png",只要请求体没带 file_type,服务端就把它落成字面量 "file"(不是留空)。这里
-// 的 onFile 上传流程原来完全没传这个字段,导致真实是图片/视频/语音的群文件在列表里全部显示成
-// 灰色通用文件图标,跟 FILE_TYPE_META 已经准备好的 image/video/audio/document 四种彩色图标从未
-// 对应起来过。跟 content-library-view.jsx 的 fileTypeFromMime 用的是同一套判断(mime 前缀优先,
-// 帶附件扩展名兜底文档类型),保持两处"什么算图片/视频/文档"的口径一致。
-function fileTypeFromMime(mime, name) {
- const prefix = (mime || '').split('/')[0];
- if (prefix === 'image' || prefix === 'video' || prefix === 'audio') return prefix;
- const ext = (/\.([a-zA-Z0-9]+)$/.exec(name || '') || [])[1];
- if (mime === 'application/pdf' || DOCUMENT_EXT.includes((ext || '').toLowerCase())) return 'document';
- return 'file';
-}
-
-function GroupFiles({topic, tinode, onToast}) {
- const [queryInput, setQueryInput] = React.useState('');
- const query = useDebouncedValue(queryInput, 300);
- const [files, setFiles] = React.useState(() => (topic && readCachedData('group-files:' + topic)) || null);
- const [menuOpen, setMenuOpen] = React.useState(false);
- const [uploading, setUploading] = React.useState(null);
- const [loadError, setLoadError] = React.useState(false);
- // groupFilePayload 只给 uploader_id、不给昵称(im_http.go:6532-6559),原来直接把裸 user_id
- // 显示在"来自"位置。跟 Muted 页一样,拉一次成员表交叉引用出可读的名字。
- const [uploaderNames, setUploaderNames] = React.useState({});
- const inputRefs = {file: React.useRef(null), image: React.useRef(null), video: React.useRef(null)};
- // 搜索框文案承诺"搜索文件名或者上传者",但服务端 ListFiles 的 `q` 只匹配文件名(上传者昵称
- // 这类展示用的名字根本不在服务端索引里,是前端自己拿 getGroupMembers 交叉查出来的)——如果把
- // query 传给服务端,按上传者名字搜索永远是静默的空结果。改成服务端只拿全量列表(本来文件数量
- // 就不大,也没有任何"加载更多"分页UI),文件名/上传者两个维度都在本地用已有的 uploaderNames
- // 表一起过滤,让搜索框真正兑现自己的文案。
- const load = React.useCallback(() => {
- if (!topic) return;
- getGroupFiles(topic, {}).then(data => {
- const items = data.items || [];
- writeCachedData('group-files:' + topic, items);
- setFiles(items);
- }).catch(() => { onToast('加载失败'); setLoadError(true); });
- }, [topic]);
- React.useEffect(() => { load(); }, [load]);
- React.useEffect(() => {
- if (!topic) return;
- getGroupMembers(topic).then(data => {
- const map = {};
- (data.items || []).forEach(m => { map[m.user_id] = m.group_nickname || m.display_name || m.user_id; });
- setUploaderNames(map);
- }).catch(() => {});
- }, [topic]);
- const visibleFiles = React.useMemo(() => {
- const all = files || [];
- const q = query.trim().toLowerCase();
- if (!q) return all;
- return all.filter(f => (f.file_name || '').toLowerCase().includes(q) ||
- (uploaderNames[f.uploader_id] || '').toLowerCase().includes(q));
- }, [files, query, uploaderNames]);
- const usedBytes = (files || []).reduce((sum, f) => sum + (f.file_size || 0), 0);
- const pick = kind => { setMenuOpen(false); inputRefs[kind].current && inputRefs[kind].current.click(); };
- const onFile = e => {
- const file = e.target.files && e.target.files[0];
- e.target.value = '';
- if (!file || !tinode) return;
- setUploading({name: file.name, progress: 0});
- uploadLargeFile(tinode, file, frac => setUploading(u => u ? {...u, progress: Math.round(frac * 100)} : u))
- .then(({fileId}) => addGroupFile(topic, {fileId, fileName: file.name, mimeType: file.type, fileSize: file.size,
- fileType: fileTypeFromMime(file.type, file.name)}))
- .then(() => { setUploading(null); load(); })
- .catch(err => { setUploading(null); onToast(err.message || '上传失败'); });
- };
- const remove = fileId => {
- deleteGroupFile(topic, fileId).then(() => load()).catch(err => onToast(err.message || '删除失败'));
- };
- const gb = n => (n / (1024 ** 3)).toFixed(2);
- return ;
-}
-
-// 名字叫 bytesToGB 却只输出到 MB —— 1.4GB 的文件会显示成 "1433.6 MB"。补上 GB 档。
-function bytesToGB(bytes) {
- if (!bytes) return '0 KB';
- if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
- if (bytes < 1024 * 1024 * 1024) return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
- return (bytes / (1024 * 1024 * 1024)).toFixed(2) + ' GB';
-}
-
-// 标题"群昵称"是参考图 设置昵称(1).png 本身的用词(不是笔误)——虽然编辑的其实是 group.name
-// 这个所有人都看得到的真实群名,跟 MyNicknameEditor("我在本群的昵称",个人在群内的显示名,
-// 完全不同的字段)概念上容易混,但这是设计稿明确画出来的文案,不是代码猜的,不要改。
-function GroupNameEditor({topic, onToast}) {
- const [group, setGroup] = React.useState(null);
- const [value, setValue] = React.useState('');
- React.useEffect(() => { if (topic) getGroup(topic).then(g => { setGroup(g); setValue(g.name); }).catch(() => onToast('加载失败')); }, [topic]);
- const save = () => {
- if (!value.trim()) return;
- updateGroup(topic, {name: value.trim(), version: group ? group.version : 0}).then(() => {
- onToast('保存成功'); window.setTimeout(() => navigate('group-profile', topic), 600);
- }).catch(err => onToast(err.message || '保存失败'));
- };
- return
-
- setValue(e.target.value)} />{value.length}/11
-
;
-}
-
-function GroupDescriptionEditor({topic, onToast}) {
- const [group, setGroup] = React.useState(null);
- const [value, setValue] = React.useState('');
- React.useEffect(() => { if (topic) getGroup(topic).then(g => { setGroup(g); setValue(g.description || ''); }).catch(() => onToast('加载失败')); }, [topic]);
- const publish = () => {
- updateGroup(topic, {description: value.trim(), version: group ? group.version : 0}).then(() => {
- onToast('保存成功'); window.setTimeout(() => navigate('group-profile', topic), 600);
- }).catch(err => onToast(err.message || '保存失败'));
- };
- return
-
-
- {/* 上限跟后端硬限对齐(im_http.go:4667 超过 200 rune 直接 400 field=description)。
- 参考图标的是 0/225,但那个数字后端不接受——按 225 放行会让用户写满再被服务端打回。 */}
- setValue(e.target.value)} />
- {value.length}/200
-
-
;
-}
-
-// "我在本群的昵称" has no dedicated reference screenshot (see plan notes) -- placed in GroupSettings,
-// governed by the real member_invite... no, by the group_nickname field on groupMemberPayload, gated by
-// the "群成员修改昵称" concept the images described (server-side enforcement, not re-checked client-side).
-function MyNicknameEditor({topic, onToast}) {
- const [value, setValue] = React.useState('');
- // getGroupMembers' list items never include group_nickname (confirmed live) -- only
- // GET .../members/me/profile actually returns it, same fix as GroupProfile's preview row above.
- // 同一个 GET 实测确认真的带 version 字段(不是空字符串/undefined),但这里原来保存时
- // 一直硬编码 version:0——跟本文件其余"编辑单个字段+乐观锁"的写法(GroupNameEditor/
- // GroupDescriptionEditor 用 getGroup 拿到的真实 version,Governance/OwnerSelect 的
- // 注释更是明确写了"version<=0 时服务端会整个跳过乐观锁 WHERE 子句,两次并发写入会
- // 静默覆盖")不一致。实测验证过这里同样如此:明明真实 version 是 1,PUT 时传 0 依然
- // 200 成功且服务端自己把 version 推到了 2——多标签页/多端几乎同时改自己在本群的昵称
- // 时,后写入的会静默覆盖先写入的,没有任何冲突信号。跟其余几处一样,把已经拿到手的
- // 真实 version 存下来,保存时原样带上。
- const [version, setVersion] = React.useState(0);
- React.useEffect(() => {
- if (!topic) return;
- getMyGroupProfile(topic).then(data => { setValue(data.nickname || ''); setVersion(data.version || 0); }).catch(() => {});
- }, [topic]);
- const save = () => {
- updateMyGroupProfile(topic, value.trim(), version).then(() => {
- onToast('保存成功'); window.setTimeout(() => navigate('group-settings', topic), 600);
- }).catch(err => onToast(err.message || '保存失败'));
- };
- return
-
- setValue(e.target.value)} />群昵称仅在当前群内显示。
-
;
-}
-
-export default function GroupManagementView({view, hashParams, myUserId, tinode, onTopicSelected}) {
- const [toast, setToast] = React.useState('');
- const topic = hashParams && hashParams.topic;
- // This container remounts on every navigation step inside the feature (see `key={view}` in
- // sidepanel-view.jsx), and Announcement's edit/publish UI is gated on this role -- uncached, that
- // gate visibly flickers off and back on every time the announcement page (or any sibling page) is
- // opened. Also warms the same shared 'group-members:'+topic entry GroupSettings/AdminList use.
- const roleCacheKey = topic && myUserId ? `group-my-role:${topic}:${myUserId}` : null;
- const [myRole, setMyRole] = React.useState(() => (roleCacheKey && readCachedData(roleCacheKey)) || null);
- React.useEffect(() => {
- if (!topic || !myUserId) { setMyRole(null); return; }
- const key = `group-my-role:${topic}:${myUserId}`;
- setMyRole(readCachedData(key) || null);
- // Used to look this up by paginating the full member list and finding myUserId in it (30/page
- // against a 128-member cap) -- a real owner/admin sitting past page 1 read back as `null`,
- // silently hiding every governance affordance gated on this value. getGroup's own `my_role`
- // (already relied on elsewhere, e.g. the disband-group guard above) isn't paginated at all --
- // it's always exactly "me", so this no longer needs the member list at all.
- getGroup(topic).then(group => {
- const role = group ? group.my_role : null;
- writeCachedData(key, role);
- setMyRole(role);
- }).catch(() => {});
- }, [topic, myUserId]);
- let content;
- if (view === 'group-list' || view === 'group-empty') content = ;
- else if (['group-invite', 'group-events', 'group-new-request', 'group-announcement', 'group-summary', 'group-role-message'].includes(view)) content = ;
- else if (view === 'group-join-requests') content = ;
- else if (view === 'group-announcement-detail') content = ;
- else if (view === 'group-settings') content = ;
- else if (view === 'group-profile') content = ;
- else if (view === 'group-avatar') content = ;
- else if (view === 'group-nickname') content = ;
- else if (view === 'group-description') content = ;
- else if (view === 'group-my-nickname') content = ;
- else if (view === 'group-members') content = ;
- else if (view === 'group-qr') content = ;
- else if (view === 'group-governance') content = ;
- else if (['group-admins', 'group-admin-success', 'group-admin-partial', 'group-admin-removed'].includes(view)) content = ;
- else if (view === 'group-admin-add') content = ;
- else if (view === 'group-mute-add') content = ;
- else if (view === 'group-invite-friends') content = ;
- else if (view === 'group-muted') content = ;
- else if (['group-owner-select', 'group-owner-confirm'].includes(view)) content = ;
- else content = ;
- return <>{content} setToast('')} />>;
-}
diff --git a/src/views/info-view.jsx b/src/views/info-view.jsx
deleted file mode 100644
index 3d377245..00000000
--- a/src/views/info-view.jsx
+++ /dev/null
@@ -1,776 +0,0 @@
-// InfoView: panel with topic/user info.
-import React from 'react';
-import { FormattedMessage, FormattedNumber, defineMessages, injectIntl } from 'react-intl';
-
-import { AccessMode, Tinode, TheCard } from 'tinode-sdk';
-
-import AvatarUpload from '../widgets/avatar-upload.jsx';
-import BadgeList from '../widgets/badge-list.jsx';
-import CheckBox from '../widgets/checkbox.jsx';
-import ContactList from '../widgets/contact-list.jsx';
-import ErrorPanel from '../widgets/error-panel.jsx';
-import GroupManager from '../widgets/group-manager.jsx';
-import Navigation from '../lib/navigation.js';
-import MenuCancel from '../widgets/menu-cancel.jsx';
-import PermissionsEditor from '../widgets/permissions-editor.jsx';
-import ShowQRCode from '../widgets/show-qrcode.jsx';
-import TopicCommonView from './topic-common-view.jsx';
-import TopicSecurity from '../widgets/topic-security.jsx';
-
-import { MAX_TITLE_LENGTH, MAX_TOPIC_DESCRIPTION_LENGTH,
- NO_ACCESS_MODE, TOAST_DURATION } from '../config.js';
-
-import { makeImageUrl } from '../lib/blob-helpers.js';
-import { truncateString } from '../lib/strformat.js';
-
-const messages = defineMessages({
- info: {
- id: 'panel_title_info',
- description: 'Title for InfoView',
- defaultMessage: 'Info'
- },
- general: {
- id: 'panel_title_general',
- description: 'Title for TopicCommon.',
- defaultMessage: 'General'
- },
- security: {
- id: 'panel_title_security',
- description: 'Title for TopicSecirity and AccSecurity.',
- defaultMessage: 'Security'
- },
- members: {
- id: 'panel_title_members',
- description: 'Title for managing group members view.',
- defaultMessage: 'Members'
- },
- crop: {
- id: 'panel_title_crop',
- description: 'Title for AvatarCropView.',
- defaultMessage: 'Drag to Adjust'
- },
- perm_want: {
- id: 'requested_permissions',
- defaultMessage: 'Requested',
- description: 'Title for permissions'
- },
- perm_given: {
- id: 'granted_permissions',
- defaultMessage: 'Granted',
- description: 'Title for permissions'
- },
- perm_auth: {
- id: 'permissions_authenticated',
- defaultMessage: 'Authenticated',
- description: 'Title for editing default authenticated permissions'
- },
- perm_anon: {
- id: 'permissions_anonymous',
- defaultMessage: 'Anonymous',
- description: 'Title for editing default anonymous permissions'
- },
- perm_user: {
- id: 'permissions_user',
- defaultMessage: 'User\'s Permissions',
- description: 'Title for editing user\'s permissions'
- },
- edit_permissions: {
- id: 'menu_item_edit_permissions',
- defaultMessage: 'Edit permissions',
- description: 'Menu item [Edit permissions]'
- },
- qrcode: {
- id: 'scan_qr_code',
- defaultMessage: 'Scan QR Code',
- description: 'Title for scanning QR code'
- },
- text_copied: {
- id: 'text_copied',
- defaultMessage: 'Copied to clipboard',
- description: 'Notification that text has been copied to clipboard'
- },
- self_topic_name: {
- id: 'self_topic_name',
- defaultMessage: 'Saved messages',
- description: 'Name of self topic for UI',
- },
- self_topic_comment: {
- id: 'self_topic_comment',
- defaultMessage: 'Notes, messages, links, files saved for posterity',
- description: 'Comment for self topic for UI',
- },
-});
-
-class InfoView extends React.Component {
- constructor(props) {
- super(props);
-
- this.state = {
- topic: null,
- owner: false,
- admin: false,
- sharer: false,
- deleter: false,
- muted: false,
- address: null,
- groupTopic: undefined,
- isSelf: false,
- channel: undefined,
- fullName: undefined,
- description: undefined,
- avatar: null,
- private: null,
- selectedContact: null,
- access: null,
- modeGiven: null,
- modeWant: null,
- modeGiven2: null, // P2P topic, the other user mode given
- modeWant2: null, // P2P topic, the other user mode want
- auth: null,
- anon: null,
- contactList: [],
- trustedBadges: [],
- previousMetaDesc: undefined,
- previousSubsUpdated: undefined,
- };
-
- this.propsUpdated = this.propsUpdated.bind(this);
- this.resetSubs = this.resetSubs.bind(this);
- this.resetDesc = this.resetDesc.bind(this);
- this.resetTags = this.resetTags.bind(this);
- this.onMetaDesc = this.onMetaDesc.bind(this);
- this.onSubsUpdated = this.onSubsUpdated.bind(this);
- this.handleImageChanged = this.handleImageChanged.bind(this);
- this.handleMuted = this.handleMuted.bind(this);
- this.handleUnarchive = this.handleUnarchive.bind(this);
- this.handlePermissionsChanged = this.handlePermissionsChanged.bind(this);
- this.handleLaunchPermissionsEditor = this.handleLaunchPermissionsEditor.bind(this);
- this.handleShowQRCode = this.handleShowQRCode.bind(this);
- this.handleCopyToClipboard = this.handleCopyToClipboard.bind(this);
- this.handleShare = this.handleShare.bind(this);
- this.handleShowAddMembers = this.handleShowAddMembers.bind(this);
- this.handleMemberUpdateRequest = this.handleMemberUpdateRequest.bind(this);
- this.handleMemberSelected = this.handleMemberSelected.bind(this);
- this.handleContextMenu = this.handleContextMenu.bind(this);
- this.handleBackNavigate = this.handleBackNavigate.bind(this);
- }
-
- propsUpdated(props) {
- const topic = this.props.tinode.getTopic(props.topic);
- if (!topic) {
- return;
- }
-
- if (this.onMetaDesc != topic.onMetaDesc) {
- this.previousMetaDesc = topic.onMetaDesc;
- topic.onMetaDesc = this.onMetaDesc;
-
- this.previousSubsUpdated = topic.onSubsUpdated;
- topic.onSubsUpdated = this.onSubsUpdated;
- }
-
- if (this.state.topic != props.topic) {
- this.setState({topic: props.topic});
- this.resetDesc(topic, props);
- this.resetSubs(topic, props);
- this.resetTags(topic);
- }
- }
-
- // Must provide this for lazy loaded components.
- componentDidMount() {
- this.propsUpdated(this.props);
- }
-
- componentDidUpdate(props) {
- this.propsUpdated(props);
- }
-
- componentWillUnmount() {
- const topic = this.props.tinode.getTopic(this.props.topic);
- if (!topic) {
- return;
- }
- this.setState({topic: null});
- topic.onMetaDesc = this.previousMetaDesc;
- topic.onSubsUpdated = this.previousSubsUpdated;
- }
-
- resetSubs(topic, props) {
- const newState = {contactList: []};
- if (topic.getType() == 'p2p') {
- // Fetch the other party in the p2p conversation.
- // Topic may not be ready yet, so check if user is found.
- const user2 = topic.subscriber(props.topic);
- if (user2) {
- newState.modeGiven2 = user2.acs.getGiven();
- newState.modeWant2 = user2.acs.getWant();
- } else {
- newState.modeGiven2 = NO_ACCESS_MODE;
- newState.modeWant2 = NO_ACCESS_MODE;
- }
- } else {
- topic.subscribers((sub) => {
- newState.contactList.push(sub);
- }, this);
- }
-
- this.setState(newState);
- }
-
- resetDesc(topic, props) {
- const defacs = topic.getDefaultAccess() || {};
- const acs = topic.getAccessMode();
-
- const badges = [];
- if (topic.trusted) {
- for (const [key, val] of Object.entries(topic.trusted)) {
- if (val) {
- badges.push(key);
- }
- }
- }
-
- const isSelf = topic.isSelfType();
-
- this.setState({
- owner: acs && acs.isOwner(),
- admin: acs && acs.isAdmin(),
- sharer: acs && acs.isSharer(),
- deleter: acs && acs.isDeleter(),
- muted: acs && acs.isMuted(),
-
- fullName: isSelf ?
- props.intl.formatMessage(messages.self_topic_name) :
- truncateString(topic.public && topic.public.fn, MAX_TITLE_LENGTH),
- description: isSelf ?
- props.intl.formatMessage(messages.self_topic_comment) :
- truncateString(topic.public && topic.public.note, MAX_TOPIC_DESCRIPTION_LENGTH),
- avatar: makeImageUrl(topic.public ? topic.public.photo : null),
- trustedBadges: badges,
- private: truncateString(topic.private && topic.private.comment, MAX_TITLE_LENGTH),
- archived: topic.isArchived(),
- address: topic.name,
- groupTopic: topic.isGroupType(),
- isSelf: isSelf,
- channel: topic.isChannelType() || topic.chan,
- access: acs ? acs.getMode() : undefined,
- modeGiven: acs ? acs.getGiven() : undefined,
- modeWant: acs ? acs.getWant() : undefined,
- auth: defacs.auth,
- anon: defacs.anon
- });
- }
-
- resetTags(topic) {
- if (topic.getType() != 'grp') {
- return;
- }
-
- const acs = topic.getAccessMode();
- if (acs && acs.isOwner()) {
- // Requesting tags: owner is editing the topic.
- topic.getMeta(topic.startMetaQuery().withTags().build());
- }
- }
-
- onMetaDesc(desc) {
- const topic = this.props.tinode.getTopic(this.props.topic);
- if (!topic) {
- return;
- }
- this.resetDesc(topic, this.props);
-
- if (this.previousMetaDesc && this.previousMetaDesc != this.onMetaDesc) {
- this.previousMetaDesc(desc);
- }
- }
-
- onSubsUpdated(subs) {
- const topic = this.props.tinode.getTopic(this.props.topic);
- if (!topic) {
- return;
- }
- this.resetSubs(topic, this.props);
-
- if (this.previousSubsUpdated && this.previousSubsUpdated != this.onSubsUpdated) {
- this.previousSubsUpdated(subs);
- }
- }
-
- handleImageChanged(mime, img) {
- this.setState({avatar: img});
- this.props.onTopicDescUpdate(this.props.topic, new TheCard(null, img || Tinode.DEL_CHAR), null);
- }
-
- handleMuted(ignored, checked) {
- this.setState({muted: checked});
- this.props.onChangePermissions(this.props.topic, checked ? '-P' : '+P');
- }
-
- handleUnarchive(ignored, ignored2) {
- this.props.onTopicUnArchive(this.props.topic);
- }
-
- handlePermissionsChanged(which, perm) {
- switch (which) {
- case 'auth':
- this.props.onTopicDescUpdateRequest(this.props.topic, null, null, {auth: perm});
- break;
- case 'anon':
- this.props.onTopicDescUpdateRequest(this.props.topic, null, null, {anon: perm});
- break;
- case 'mode':
- case 'want':
- this.props.onChangePermissions(this.props.topic, perm);
- break;
- case 'given':
- this.props.onChangePermissions(this.props.topic, perm, this.props.topic);
- break;
- case 'user':
- this.props.onChangePermissions(this.props.topic, perm, this.state.userPermissionsEdited);
- break;
- }
-
- this.handleBackNavigate();
- }
-
- handleLaunchPermissionsEditor(which, uid) {
- const {formatMessage} = this.props.intl;
- let toEdit, toCompare, toSkip, titleEdit, titleCompare, userTitle, userAvatar;
- switch (which) {
- case 'mode':
- toEdit = this.state.access;
- break;
- case 'want':
- toEdit = this.state.modeWant;
- toCompare = this.state.modeGiven;
- if (this.state.owner) {
- // Don't allow owner to unset 'O' permission. All other permissions are OK.
- toSkip = 'O';
- } else {
- // Allow accepting any of 'ASDO' permissions but don't allow asking for them.
- toSkip = AccessMode.encode(AccessMode.diff('ASDO', this.state.modeGiven));
- if (this.state.channel) {
- // Channels are read-only.
- toSkip += 'W';
- }
- }
- titleEdit = formatMessage(messages.perm_want);
- titleCompare = formatMessage(messages.perm_given);
- break;
- case 'given':
- toEdit = this.state.modeGiven2;
- toCompare = this.state.modeWant2;
- toSkip = this.state.groupTopic ? (this.state.owner ? '' : 'O') : 'ASDO';
- titleEdit = formatMessage(messages.perm_given);
- titleCompare = formatMessage(messages.perm_want);
- break;
- case 'auth':
- toEdit = this.state.auth;
- toSkip = 'O';
- break;
- case 'anon':
- toEdit = this.state.anon;
- toSkip = 'O';
- break;
- case 'user': {
- const topic = this.props.tinode.getTopic(this.props.topic);
- if (!topic) {
- return;
- }
- const user = topic.subscriber(uid);
- if (!user || !user.acs) {
- return;
- }
- toEdit = user.acs.getGiven();
- toCompare = user.acs.getWant();
- toSkip = this.state.owner ? '' : 'O';
- titleEdit = formatMessage(messages.perm_given);
- titleCompare = formatMessage(messages.perm_want);
- if (user.public) {
- userTitle = user.public.fn;
- userAvatar = user.public.photo;
- }
- break;
- }
- default:
- console.error("Unknown permission editing mode '" + which + "'");
- return;
- }
- this.setState({
- userPermissionsEdited: uid,
- userPermissionsTitle: userTitle,
- userPermissionsAvatar: userAvatar,
- editedPermissions: toEdit,
- immutablePermissions: toCompare,
- editedPermissionsTitle: titleEdit,
- immutablePermissionsTitle: titleCompare,
- editedPermissionsSkipped: toSkip,
- });
- this.props.onNavigate(`perm/${which}`);
- }
-
- handleShowQRCode(e) {
- e.preventDefault();
- this.props.onNavigate('qrcode');
- }
-
- handleCopyToClipboard(e, text) {
- e.preventDefault();
- navigator.clipboard.writeText(text).then(_ => {
- this.setState({toast: this.props.intl.formatMessage(messages.text_copied)});
- setTimeout(_ => { this.setState({toast: ''}); }, TOAST_DURATION);
- });
- }
-
- handleShare(e) {
- e.preventDefault();
- const card = new TheCard(this.state.fullName, this.state.avatar, null, this.state.description);
- TheCard.setTinodeID(card, Tinode.URI_TOPIC_ID_PREFIX + this.props.topic);
- this.props.onShareTheCard(card);
- }
-
- handleShowAddMembers(e) {
- e.preventDefault();
- this.props.onInitFind();
- this.props.onNavigate('members');
- }
-
- handleMemberUpdateRequest(members, added, removed) {
- this.props.onMemberUpdateRequest(this.props.topic, added, removed);
- this.props.onNavigate('info');
- }
-
- handleMemberSelected(uid) {
- this.setState({selectedContact: uid});
- }
-
- // Every drill-in click inside InfoView (open members/permissions/qrcode/security/...) goes through
- // props.onNavigate -> tinode-web.jsx's infoNavigator -> a real navigateTo() push, so this "go back
- // one level" (or "close InfoView entirely" at the root) should be the matching pop -- goBack() first,
- // same fix/pattern as tinode-web.jsx's handleHideMessagesView and content-library-view.jsx's Header.
- // Before this fix every level of InfoView's own drill-down (info -> members -> a member's profile ->
- // permissions -> ...) pushed going in AND pushed again coming back out via this function, so the
- // system back button had to be pressed once per level ever visited, and each press could land back
- // inside a sub-panel the user had already explicitly backed out of.
- handleBackNavigate() {
- if (Navigation.goBack()) {
- return;
- }
- const args = (this.props.panel || 'info').split('/');
- if (args[0] == 'info') {
- this.props.onNavigate(null);
- } else if (args[0] == 'perm') {
- if (args[1] == 'user') {
- this.props.onNavigate('info');
- } else {
- this.props.onNavigate('security');
- }
- } else {
- this.props.onNavigate('info');
- }
- }
-
- handleContextMenu(params) {
- const {formatMessage} = this.props.intl;
- const topic = this.props.tinode.getTopic(this.props.topic);
- if (!topic) {
- return;
- }
- const user = topic.subscriber(params.topicName);
- if (!user || !user.acs) {
- return;
- }
-
- const isMe = this.props.tinode.isMe(params.topicName);
- const menuItems = [{
- title: formatMessage(messages.edit_permissions),
- handler: _ => this.handleLaunchPermissionsEditor(isMe ? 'want' : 'user', params.topicName)
- }];
- if (!isMe) {
- menuItems.push('member_delete');
- }
- menuItems.push(user.acs.isMuted() ? 'member_unmute' : 'member_mute');
- if (!isMe) {
- menuItems.push(user.acs.isJoiner() ? 'member_block' : 'member_unblock');
- }
- this.props.showContextMenu({
- topicName: this.props.topic,
- x: params.x,
- y: params.y,
- user: params.topicName}, menuItems);
- }
-
- render() {
- const args = (this.props.panel || 'info').split('/');
- const view = args[0];
- args.shift();
-
- const {formatMessage} = this.props.intl;
- const panelTitle = formatMessage((view == 'perm' ? messages['perm_' + args[0]] : messages[view])
- || messages['info']);
-
- const topic = this.props.tinode.getTopic(this.state.topic);
- const alias = topic && topic.alias();
-
- return (
-
-
{this.state.toast}
-
- {this.props.displayMobile ?
-
: null}
- {view == 'members' ?
-
- :
- view == 'perm' && args.length > 0 ?
-
this.handlePermissionsChanged(args[0], mode)}
- onCancel={this.handleBackNavigate} />
- :
- view == 'general' ?
-
- :
- view == 'security' ?
-
- :
- view == 'qrcode' ?
-
- :
-
-
- {!this.state.isSelf ?
-
{e.preventDefault(); this.props.onNavigate('general');}}>
- edit
-
-
- : null
- }
-
-
-
-
-
-
-
-
{this.state.fullName}
- {this.state.channel ? podcasts : null}
-
- {this.state.private ?
-
-
-
-
-
{this.state.private}
-
- : null
- }
- {!this.state.isSelf ?
- <>
-
-
-
- {this.state.address}
-
-
-
- {alias ?
-
- : null}
- >
- : null
- }
- {this.state.trustedBadges.length > 0 ?
-
-
-
- : null
- }
- {this.state.groupTopic && topic.subcnt > 0 ?
-
- {this.state.channel ?
-
- :
-
- }
-
- : null
- }
- {this.state.description ?
-
-
-
-
-
{this.state.description}
-
: null}
-
- {!this.state.isSelf ?
- <>
-
-
-
-
-
-
>
- : null
- }
- {this.state.archived ?
-
-
-
-
-
-
- :
- null
- }
- {!this.state.isSelf ?
- <>
-
>
- : null
- }
- {this.state.groupTopic && this.state.sharer ?
- <>
-
-
-
-
-
-
-
-
{
- (no_members) =>
- }
- >
- :
- null
- }
-
- }
-
- );
- }
-};
-
-export default injectIntl(InfoView);
diff --git a/src/views/logo-view.jsx b/src/views/logo-view.jsx
deleted file mode 100644
index d077b004..00000000
--- a/src/views/logo-view.jsx
+++ /dev/null
@@ -1,20 +0,0 @@
-/* Static placeholder shown in the message pane when no conversation is selected. */
-import React from 'react';
-
-import { BASE_APP_NAME } from '../config.js';
-
-function Icon({children}) { return {children} ; }
-
-export default class LogoView extends React.PureComponent {
- render() {
- return (
-
-
-
forum
-
{BASE_APP_NAME}
-
选择一个会话开始聊天
-
-
- );
- }
-};
diff --git a/src/views/message-center-view.jsx b/src/views/message-center-view.jsx
deleted file mode 100644
index bb58435e..00000000
--- a/src/views/message-center-view.jsx
+++ /dev/null
@@ -1,548 +0,0 @@
-import React from 'react';
-import { Tinode } from 'tinode-sdk';
-
-import Navigation from '../lib/navigation.js';
-import { BASE_APP_NAME } from '../config.js';
-import { getBootstrap, getRealnameStatus, getSystemNotifications, markSystemNotificationRead } from '../lib/im-api.js';
-import { makeImageUrl } from '../lib/blob-helpers.js';
-import { useCachedData } from '../lib/use-cached-data.js';
-import { topicDisplayName } from '../lib/utils.js';
-
-// Must go through setUrlSidePanel (not a bare navigateTo(view), which replaces the whole hash) so
-// switching panels from "messages-home" doesn't silently drop path[1] -- the currently open
-// conversation topic, which coexists with this panel in the split view once a chat is selected.
-// `topic` 可选:系统消息里的群事件通知需要带着群 topic 跳进对应群聊设置页,写法与
-// group-management-view.jsx 的同名辅助函数保持一致(不带 topic 时显式清掉,避免把上一个页面
-// 残留的 topic 参数带进不相关的页面)。
-const navigate = (view, topic) => {
- let hash = Navigation.setUrlSidePanel(Navigation.currentUrl(), view);
- hash = topic ? Navigation.addUrlParam(hash, 'topic', topic) : Navigation.removeUrlParam(hash, 'topic');
- Navigation.navigateTo(hash);
-};
-
-// BottomNavigation's 消息<->工作台 switch is the app's root-level tab bar, not a navigation to a
-// "different page" in the back-stack sense -- same bug/fix as content-library-view.jsx's
-// navigateReplace (see its comment): navigate() here pushed a history entry on every tab click, so
-// bouncing between 消息 and 工作台 a few times (a completely normal usage pattern for the app's only
-// two bottom tabs) made the back button just cycle through past tab switches instead of leaving the
-// app, exactly like a standard bottom-tab-bar app's back button should not have to unwind every past
-// tab switch to back out.
-const navigateReplace = (view) => Navigation.navigateReplace(Navigation.setUrlSidePanel(Navigation.currentUrl(), view));
-
-// Was a hardcoded FREQUENT_CONTACTS mock (5 fake names, always clicking through to the same
-// direct-message-view.jsx demo regardless of which avatar was tapped -- confirmed independently by
-// a user screenshot showing every contact opening the identical "接语彤" script). Real "frequently
-// contacted" data doesn't exist as its own bootstrap field, so this derives it from the same
-// `chatList` the conversation list below already uses: P2P topics only (a "people you talk to" rail
-// isn't the place for groups), most-recently-touched first.
-function frequentContacts(chatList) {
- if (!chatList || chatList.length === 0) return [];
- return chatList
- .filter(item => item.topic && Tinode.isP2PTopicName(item.topic))
- .slice()
- .sort((a, b) => new Date(b.touched || 0).getTime() - new Date(a.touched || 0).getTime())
- .slice(0, 8)
- .map(item => ({
- id: item.topic,
- name: (item.public && item.public.fn) || '未命名',
- avatar: makeImageUrl(item.public && item.public.photo),
- online: !!item.online
- }));
-}
-
-// Matches docs/images/消息页面—长按弹出.png exactly (9 items, in this order -- no "编辑" despite
-// bootstrap.message_policy.supported_actions listing it; the mock predates that field and the
-// prototype just doesn't surface an edit action here). 'recall' is gated against supported_actions
-// (server/im_http.go serveIMBootstrap); favorite/forward/translate/select are pure client affordances.
-const MESSAGE_ACTIONS = [
- {id: 'quote', icon: 'format_quote', label: '引用'},
- {id: 'copy', icon: 'content_copy', label: '复制'},
- {id: 'favorite', icon: 'inventory_2', label: '收藏'},
- {id: 'translate', icon: 'translate', label: '翻译'},
- {id: 'forward', icon: 'reply', label: '转发'},
- {id: 'recall', icon: 'undo', label: '撤回', gated: true},
- {id: 'delete', icon: 'delete_outline', label: '删除', gated: true},
- {id: 'select', icon: 'select_all', label: '多选'},
- // Not in bootstrap.message_policy.supported_actions at all (that list only covers 'recall' for the
- // sender's own message) -- left ungated like the other pure client affordances above.
- {id: 'group_recall', icon: 'restart_alt', label: '群撤回'}
-];
-
-function Icon({children}) {
- return {children} ;
-}
-
-function Toast({message, onDone}) {
- React.useEffect(() => {
- if (!message) return undefined;
- const timer = window.setTimeout(onDone, 2200);
- return () => window.clearTimeout(timer);
- }, [message, onDone]);
- return message ? {message}
: null;
-}
-
-function Avatar({item, small = false}) {
- // The tone modifier is namespaced ('tone-orange', not 'orange') because base.css's original
- // Tinode styles already define bare `.orange`/`.green`/`.blue` as generic `color: ... !important`
- // text-color utility classes -- an unprefixed tone class collides with those and, since
- // `!important` beats any selector this component could use to fight back, silently overrides the
- // intended white icon-on-tint background to whatever that generic utility's text color is (this is
- // exactly why the default/no-tone case rendered an invisible blue-on-blue icon: 'blue' is one of
- // the collision names).
- const className = 'messages-avatar ' + (small ? 'small ' : '') + 'tone-' + (item.tone || 'blue');
- return
- {item.avatar ?
:
{item.icon || 'person'} }
- {item.online !== undefined ?
: null}
-
;
-}
-
-// A chat list row needs a one-line summary even when the latest message isn't plain text. This is a
-// plain-Chinese equivalent of lib/formatters.js's previewFormatter (which needs react-intl wired in,
-// inconsistent with this file's all-hardcoded-Chinese mock strings) -- just enough to label the
-// message by Drafty entity type for a conversation list preview.
-function draftyPreview(content) {
- if (typeof content === 'string' || content == null) return content || '';
- if (Array.isArray(content.ent) && content.ent.length) {
- const label = {IM: '[图片]', VD: '[视频]', AU: '[语音]', EX: '[文件]', TC: '[名片]', VC: '[通话]'}[content.ent[0].tp];
- if (label) return label;
- }
- // 服务端的 system_notice/system_action(好友通过、群成员加入、群邀请卡片等,见 chat-message.jsx
- // 对 x-im-type 的处理)以及合并转发消息都不是 Drafty,也没有 `txt` 字段,真正的可读文案在
- // `text`(system_*)或 `comment`/`title`(forward_bundle)——原来这里只认 `txt`,这三类消息在
- // 会话列表的预览行一律回退成没有信息量的"[消息]",即使内容本身早就是一句完整人话
- // (比如"邀请你加入群聊。"),聊天页里点开看到的和列表预览完全对不上。
- if (typeof content.text === 'string' && content.text) return content.text;
- if (typeof content.comment === 'string' && content.comment) return content.comment;
- if (typeof content.title === 'string' && content.title) return '[聊天记录] ' + content.title;
- return content.txt || '[消息]';
-}
-
-function formatConversationTime(ts) {
- if (!ts) return '';
- const date = new Date(ts);
- const now = new Date();
- const startOfDay = d => new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
- const days = Math.round((startOfDay(now) - startOfDay(date)) / 86400000);
- const hhmm = () => date.toLocaleTimeString('zh-CN', {hour: '2-digit', minute: '2-digit'});
- if (days === 0) return hhmm();
- // 参考图 docs/images/消息.png 里昨天的会话显示的是"昨天21:03",带具体时间,不是只有"昨天"
- if (days === 1) return '昨天' + hhmm();
- if (days < 7) return ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][date.getDay()];
- return date.toLocaleDateString('zh-CN', {month: 'numeric', day: 'numeric'});
-}
-
-// 参考图 系统消息.png 的时间格式是"6月28日 10:04"——不带年份、中文月日、不带秒。原来直接用
-// toLocaleString('zh-CN') 默认格式,会输出"2026/8/20 18:04:00"(带年份、斜杠、秒),跟设计不符。
-function formatNotificationTime(ts) {
- const d = new Date(ts);
- const hh = String(d.getHours()).padStart(2, '0');
- const mm = String(d.getMinutes()).padStart(2, '0');
- return `${d.getMonth() + 1}月${d.getDate()}日 ${hh}:${mm}`;
-}
-
-// Entries (system messages / file assistant) come from bootstrap.entries and are always shown --
-// unlike the old mock, they don't disappear once the user has real conversations (that was a bug).
-function entryConversations(bootstrap) {
- if (!bootstrap || !bootstrap.entries) return [];
- const list = [];
- const sys = bootstrap.entries.system_messages;
- if (sys && sys.enabled) {
- // Matches docs/images/消息.png ("最新公告"). bootstrap doesn't carry a preview of the latest
- // notification, so this is a static label like the rest of that screenshot's demo content.
- list.push({id: 'system', title: '系统消息', summary: '最新公告', icon: 'notifications', tone: 'orange',
- system: true, unread: sys.unread_count || 0});
- }
- const files = bootstrap.entries.file_assistant;
- if (files && files.enabled) {
- list.push({id: 'files', title: '文件助手', summary: '你上传1份文件', icon: 'folder', tone: 'green',
- fileAssistant: true, topic: files.topic, unread: files.unread_count || 0});
- }
- return list;
-}
-
-function normalizeConversations(chatList, myUserId) {
- if (!chatList || chatList.length === 0) return [];
- return chatList.map((item, index) => {
- const latest = typeof item.latestMessage === 'function' ? item.latestMessage() : null;
- let summary = item.private && item.private.comment ? item.private.comment : '暂无消息';
- if (latest) summary = draftyPreview(latest.content);
- if (latest && latest.from === myUserId) summary = '我:' + summary;
- const isGroup = typeof item.topic === 'string' && item.topic.startsWith('grp');
- return {
- id: item.topic || item.user || String(index),
- title: topicDisplayName(item.topic, item.public, '未命名会话'),
- summary,
- time: formatConversationTime(item.touched),
- unread: Math.max(Number(item.unread) || 0, 0),
- // 真实头像:跟同文件 frequentContacts() 一样从 public.photo 取,原来漏了这一项,
- // 导致会话列表每一行都只能落到下面的兜底图标,永远显示不出头像。
- avatar: makeImageUrl(item.public && item.public.photo),
- // 没有真实头像时的兜底图标要区分群聊和单聊,否则群聊也画成单人像,列表里分不出哪条是群
- icon: isGroup ? 'group' : 'person',
- // 在线状态只对单聊有意义:群不存在"在线",传 undefined 让 Avatar 不渲染那个状态点
- online: isGroup ? undefined : !!item.online,
- muted: !!(item.acs && item.acs.isMuted && item.acs.isMuted()),
- // 原来读的是 item.pinned——这个键在原生订阅对象上根本不存在,永远 undefined,置顶开关点了
- // 会话列表从来没有过任何视觉变化。真实存放位置是 subscription 原生 private.pinned 字段,
- // 但只对**群聊**成立:group-management-view.jsx 的置顶走 updateGroupMemberSettings ->
- // serveIMGroupMemberSettings,服务端确实写的是 Subs.Private,随原生 {meta} 同步下来(真实
- // 验证:开关点亮后要等下一次重连/刷新才会在这里体现——同一会话内没有实时 {pres} 通知去让
- // 已连接的这份 subscription 缓存失效,这是已知的、这次没有顺手解决的次要延迟,不是没生效)。
- // contact-management-view.jsx 的单聊置顶走的是完全不同的 updateConversationSettings ->
- // serveIMConversationSettings,服务端存进独立的 im_conversation_settings 表,从不写入原生
- // Subs.Private——单聊置顶因此不管等多久重连都不会在这里出现,这是真实的后端架构缺口(没有
- // 批量查询会话设置的接口,前端也不可能为渲染一屏会话列表挨个按 topic 发请求),已记录到
- // 后端问题清单,不是这一行能修的。
- pinned: !!(item.private && item.private.pinned)
- };
- });
-}
-
-function BottomNavigation() {
- return
- navigateReplace('workbench')}>dashboard 工作台
- person 消息
- ;
-}
-
-function QuickMenu({onClose, onAction, permissions}) {
- const actions = [
- {id: 'friends', icon: 'group', label: '朋友'},
- {id: 'profile', icon: 'person_outline', label: '我的'},
- {id: 'create-group', icon: 'add_comment', label: '创建群组', permission: 'create_group'},
- {id: 'add-friend', icon: 'person_add_alt', label: '添加好友', permission: 'user_search'},
- {id: 'scan', icon: 'qr_code_scanner', label: '扫一扫', permission: 'scan_qr'}
- ].filter(item => !item.permission || !permissions || permissions[item.permission] !== false);
- return
-
e.stopPropagation()} role="menu">
- {actions.map(item => onAction(item)}>
- {item.icon} {item.label}
- )}
-
-
;
-}
-
-// 1.10 实名认证提醒:docs/images/实名认证(安卓).png 显示的是不可跳过的强制态(单按钮"去实名
-// 认证",没有关闭/稍后处理)——但那张图对应的是 bootstrap.security.realname.required===true 这个
-// 分支,这个环境(以及目前后端唯一会返回的默认配置)实际是 required:false / allow_skip:true。
-// 照抄参考图做成完全关不掉的弹层,等于把"实名认证目前是可选的"这个真实配置,实现成挡住所有人的
-// 强制墙——所以按这两个字段分叉:允许跳过时加一个参考图没画但后端明确允许的关闭按钮;且遵守
-// remind_after,跳过后在这个时长内不重复弹(不是每次进消息首页都弹一次)。
-function parseRemindAfter(text) {
- const m = /^(\d+)\s*([hdm])$/i.exec(String(text || '').trim());
- if (!m) return 24 * 60 * 60 * 1000;
- const n = Number(m[1]);
- return m[2].toLowerCase() === 'd' ? n * 86400000 : m[2].toLowerCase() === 'h' ? n * 3600000 : n * 60000;
-}
-
-function RealnameReminderModal({allowSkip, onDismiss}) {
- return
-
e.stopPropagation()}>
- {allowSkip ?
close : null}
-
实名认证提醒
-
根据国家法律法规要求,您需先进行实名认证才能使用该项 APP
-
navigate('realname')}>去实名认证
-
-
;
-}
-
-function MessageHome({view, connected, chatList, myUserId, onTopicSelected, onOpenScanner}) {
- const [query, setQuery] = React.useState('');
- const [menuOpen, setMenuOpen] = React.useState(view === 'messages-menu');
- const [toast, setToast] = React.useState('');
- // Was `useState(null)` + its own effect -- refetched from scratch (blank entries/badges for a
- // beat) every time this component remounted, e.g. after a trip to another sidepanel and back.
- const [bootstrap] = useCachedData(myUserId ? 'bootstrap:' + myUserId : null, getBootstrap, [myUserId]);
- // sidepanel-view.jsx 会在 sidePanelSelected 已经是 'messages-home'(例如收藏夹里的旧链接、浏览器
- // 从上次关闭前恢复的标签页)但 myUserId 还没就绪时,照样把这个视图渲染出来(它自己的
- // `fullPage = !myUserId || view !== 'messages-home'` 分支就是为这种情况准备的)——如果这时候
- // token 续登录失败或者还没跑完,原来这里只看 `!!myUserId && connected===false`,myUserId 本身是
- // null 时这个条件恒为 false,于是不会进入"加载中"分支,会话列表和 bootstrap 又因为
- // useCachedData 的 key 是 null 而永远不发请求,最终呈现的是一句容易误导人的"暂无会话/未找到
- // 相关会话"——用户会以为账号是空的,而不是"还没登录成功"。myUserId 本身为空也按"未连接"处理,
- // 至少诚实地展示"加载中"而不是一个看起来正常但其实是半成品的空列表。
- const offline = view === 'messages-offline' || !myUserId || connected === false;
-
- // "置顶" only ever changed a background tint (see the `pinned` comment inside
- // normalizeConversations) -- every other IM this product's users have used floats a pinned chat to
- // the top of the list and keeps it there, so a tint-only "pin" reads as broken even where the
- // underlying flag is genuinely correct (group chats). Array.prototype.sort is stable (guaranteed
- // since ES2019), so this only reorders pinned-vs-not and otherwise preserves chatList's own
- // ordering. Applied after normalizing, not to the concatenated list, so the two fixed system
- // entries (system_messages/file_assistant) -- which have no `pinned` concept at all -- stay
- // pinned to the very top the way they always have, ahead of even a pinned real conversation.
- const realConversations = normalizeConversations(chatList, myUserId)
- .sort((a, b) => (b.pinned ? 1 : 0) - (a.pinned ? 1 : 0));
- const conversations = entryConversations(bootstrap).concat(realConversations);
- const filtered = conversations.filter(item => `${item.title}${item.summary}`.toLowerCase().includes(query.trim().toLowerCase()));
- const frequent = frequentContacts(chatList);
-
- // bootstrap.security.realname carries required/allow_skip/remind_after, but -- confirmed live --
- // its handler (serveIMBootstrap) builds that block from a 4-field literal that never includes
- // submission_id, unlike the dedicated GET /security/realname endpoint (realNamePayload). The
- // dismissKey fix below depends on submission_id to tell two separate rejections apart; sourcing
- // `status` from bootstrap alone made that fix a no-op in practice (it always fell back to `status`,
- // reproducing the exact bug it was meant to fix). Fetching this separately, the same
- // useCachedData-with-retry way bootstrap itself is fetched, gets the real submission_id.
- const realnameMeta = bootstrap && bootstrap.security && bootstrap.security.realname;
- const [realnameStatus] = useCachedData(myUserId ? 'realname-status:' + myUserId : null, getRealnameStatus, [myUserId]);
- const [reminderDismissed, setReminderDismissed] = React.useState(false);
- // Keyed by submission_id (a new one is minted per resubmission, server/im_http.go's
- // realNamePayload/serveIMRealNameSubmissions), not just status -- two separate rejections both
- // report status:"rejected", and without something that actually changes between them, dismissing
- // the first would keep suppressing the reminder for a brand new rejection submitted and rejected
- // again within the same snooze window. Falls back to status for the states that never had a
- // submission (e.g. never-submitted "unverified", where there's only ever one such state anyway).
- const dismissKey = (myUserId && realnameStatus) ?
- `realname-reminder-dismissed:${myUserId}:${realnameStatus.submission_id || realnameStatus.status}` : null;
- let lastDismissedAt = 0;
- try { lastDismissedAt = dismissKey ? Number(localStorage.getItem(dismissKey) || 0) : 0; } catch (_) { /* ignore */ }
- const withinSnooze = lastDismissedAt > 0 && (Date.now() - lastDismissedAt) < parseRemindAfter(realnameMeta && realnameMeta.remind_after);
- const showRealnameReminder = !!realnameStatus && !reminderDismissed && !withinSnooze &&
- (realnameStatus.status === 'unverified' || realnameStatus.status === 'rejected');
- const dismissRealnameReminder = () => {
- if (dismissKey) { try { localStorage.setItem(dismissKey, String(Date.now())); } catch (_) { /* ignore */ } }
- setReminderDismissed(true);
- };
-
- const selectConversation = item => {
- if (item.system) {
- navigate('system-messages');
- } else if (item.fileAssistant && item.topic && onTopicSelected) {
- onTopicSelected(item.topic);
- } else if (chatList && chatList.length && onTopicSelected) {
- onTopicSelected(item.id);
- } else {
- navigate('chat-standard');
- }
- };
-
- const runQuickAction = item => {
- setMenuOpen(false);
- if (item.id === 'friends') return navigate('address-book');
- if (item.id === 'profile') return navigate('personal-center');
- if (item.id === 'create-group') return navigate('group-list');
- if (item.id === 'add-friend') return navigate('add-friend');
- if (item.id === 'scan' && onOpenScanner) return onOpenScanner();
- setToast(item.id === 'scan' ? '扫码功能暂不可用' : `${item.label}页面将在对应章节中提供`);
- };
-
- return
-
- {offline ? 加载中
: 消息 }
- setMenuOpen(true)} aria-label="更多功能">menu
-
-
- search setQuery(e.target.value)} placeholder="搜索" />
- {frequent.length > 0 ?
- 常联系的人
-
- {frequent.map(item =>
onTopicSelected && onTopicSelected(item.id)}>
- {item.name}
- )}
-
- : null}
-
- {filtered.map(item => selectConversation(item)}>
-
- {item.title} {item.summary}
- {item.time}
- {item.unread ? {item.unread > 99 ? '99+' : item.unread} : item.muted ? notifications_off : null}
-
- )}
- {/* "未找到相关会话"(搜索图标)只在用户真的输入过搜索词却匹配不到时才准确——`conversations`
- 本身为空(例如尚未完成登录/数据尚未拉取到、或租户把系统消息与文件助手都关闭且用户还没有
- 任何会话)跟"搜索没匹配到"是两种不同情况,原来两者共用同一句"未找到相关会话"+放大镜
- 图标,会把"这个账号目前就是没有任何会话"说成"你的搜索没找到东西",用户没搜索过却看到
- 搜索无结果的图标和文案。 */}
- {filtered.length === 0 ? (query.trim() ?
- :
- ) : null}
-
-
-
- {menuOpen ?
setMenuOpen(false)} onAction={runQuickAction} permissions={bootstrap && bootstrap.permissions} /> : null}
- setToast('')} />
- {showRealnameReminder ? : null}
- ;
-}
-
-function MessageActions() {
- const [visible, setVisible] = React.useState(true);
- const [toast, setToast] = React.useState('');
- const [selected, setSelected] = React.useState('');
- const [supportedActions, setSupportedActions] = React.useState(null);
-
- React.useEffect(() => {
- getBootstrap().then(data => setSupportedActions(data.message_policy && data.message_policy.supported_actions)).catch(() => {});
- }, []);
-
- // This screen is a static illustration of the long-press menu (not attached to a real message), so
- // there's no real (topic, seq) to act on -- the real wiring belongs in messages-view.jsx (chapter 4)
- // where an actual message exists. Here we only let the server's declared capabilities decide which
- // gated actions (recall/edit/delete) are worth showing at all.
- const actions = MESSAGE_ACTIONS.filter(item => !item.gated || !supportedActions || supportedActions.includes(item.id));
-
- const runAction = item => {
- setSelected(item.id);
- if (item.id === 'copy' && navigator.clipboard) {
- navigator.clipboard.writeText('明天上午十点开项目会,请准时参加。').catch(() => {});
- }
- setToast(`${item.label}操作已选择`);
- setVisible(false);
- };
-
- return
-
navigate('messages-home')} aria-label="返回">arrow_back_ios_new 项目协作群
-
- 今天 10:24
- {e.preventDefault(); setVisible(true);}}>明天上午十点开项目会,请准时参加。
- {e.preventDefault(); setVisible(true);}}>收到,我会提前准备材料。
- {selected ? 已选择:{MESSAGE_ACTIONS.find(item => item.id === selected).label}
: null}
-
-
mic_none 输入消息 sentiment_satisfied_alt add_circle_outline
- {visible ?
setVisible(false)}>
-
e.stopPropagation()} role="menu">
- {actions.map(item => runAction(item)}>
- {item.label} {item.icon}
- )}
-
-
: null}
-
setToast('')} />
- ;
-}
-
-// 系统通知的 summary 在多处后端代码里被直接填成机器码而不是人话:群事件统一写
-// summary=event.EventType(server/im_group_events.go:42-44,所有群事件共用 title="群聊动态"),
-// 好友申请处理结果写 summary=status(server/im_http.go:5956 与 db/mysql/tenant_adapter.go:8643,8700,
-// 值就是 "accepted"/"rejected")。已记入后端问题清单,在后端给出可读文案之前前端按已知码兜底翻译。
-const NOTIFICATION_TEXT = {
- 'group.created': '你创建了一个新群聊',
- 'group.join_request.created': '有人申请加入你管理的群聊',
- // 实测 asn494180 账号的真实系统消息列表里出现过原样展示的 "group.announcement.updated"——
- // db/mysql/tenant_groups.go 里 insertGroupEventTx 一共写了 9 种 group.* 事件码
- // (announcement.updated/created/join_request.created/member.joined/member.left/
- // member.updated/mute.updated/owner.transferred/role.updated/settings.updated),这张表原来
- // 漏收了公告更新这一种,是 9 种里唯一没有查表命中、直接把机器码露给用户看的。
- 'group.announcement.updated': '群公告有更新',
- 'group.member.joined': '有成员加入了群聊',
- 'group.member.left': '有成员退出了群聊',
- 'group.member.updated': '群成员资料有更新',
- 'group.mute.updated': '群禁言设置有变更',
- 'group.owner.transferred': '群主已转让',
- 'group.role.updated': '群成员角色有变更',
- 'group.settings.updated': '群设置有更新',
- // 上面那 9 种都是 insertGroupEventTx 的静态字符串调用,唯独审批入群申请这一条不是:
- // TenantGroupJoinRequestResolve(tenant_groups.go:642)拼的是
- // "group.join_request."+status,status 是 "accepted" 或 "rejected"(server/store/types/groups.go
- // 的 GroupJoinRequestStatusAccepted/Rejected),实际落进 summary 的是
- // "group.join_request.accepted"/"group.join_request.rejected" 这两个完整机器码,跟本已收录的
- // "group.join_request.created"(申请提交时那条)是三个不同的 key。当初核对 9 种静态事件码时
- // 这条动态拼接的漏了,管理员在群管理页批准/拒绝入群申请后,申请人这边系统消息列表同样会原样
- // 展示机器码——跟 group.announcement.updated 是完全同一类问题,只是触发路径不同(未在真实
- // 环境里等到一条这样的通知,是读 im_be 源码逐个比对 insertGroupEventTx 调用点发现的)。
- 'group.join_request.accepted': '你的入群申请已通过',
- 'group.join_request.rejected': '你的入群申请被拒绝',
- accepted: '对方已通过你的好友申请',
- rejected: '对方拒绝了你的好友申请',
- // 新样本:security.risk.continue/cancel 事件的 summary 直接是 decision 字面量
- // "continue"/"cancel"(风险确认审计记录,见 im-api.js 的 confirmRiskChallenge),同样是机器码——
- // 两个 decision 枚举值只有一个"continue"曾被收录,"cancel"(第11章发消息前风险提示点"取消发送"
- // 产生)此前会直接把英文单词"cancel"展示在系统消息列表里。
- continue: '你确认继续了一次安全风险提示',
- cancel: '你取消了一次风险操作确认'
-};
-
-function systemNotificationText(item) {
- const summary = (item.summary || '').trim();
- // 只在查表命中时才替换。后端的 summary 有些确实是机器码(好友申请通过时是 "accepted"),
- // 但也有些就是真实数据——日报提醒的 summary 是纯日期字符串
- // (db/mysql/tenant_adapter.go:4917 传 date.Format("2006-01-02")),会议通知的 summary 是会议标题。
- // 一旦按"看起来像机器码就吞掉"来判断,这些真实内容会被换成一句无意义的兜底文案。
- const mapped = NOTIFICATION_TEXT[summary] || NOTIFICATION_TEXT[item.event_type];
- if (mapped) return mapped;
- return summary || '您有一条新的系统通知。';
-}
-
-function SystemMessages({authenticated}) {
- const [items, setItems] = React.useState([]);
- const [cursor, setCursor] = React.useState('');
- const [loadingMore, setLoadingMore] = React.useState(false);
- const [loading, setLoading] = React.useState(true);
- const [error, setError] = React.useState('');
- const [toast, setToast] = React.useState('');
-
- React.useEffect(() => {
- getSystemNotifications().then(data => { setItems(data.items || []); setCursor(data.next_cursor || ''); })
- .catch(err => setError(err.message || '加载失败'))
- .finally(() => setLoading(false));
- }, []);
-
- // GET /system-notifications does return next_cursor whenever there's more (real accounts with
- // 50 unread saw it truncate silently at 20, with no way to reach or ever mark the rest read).
- const loadMore = () => {
- if (loadingMore || !cursor) return;
- setLoadingMore(true);
- getSystemNotifications(cursor).then(data => {
- setItems(list => list.concat(data.items || []));
- setCursor(data.next_cursor || '');
- }).catch(() => {}).finally(() => setLoadingMore(false));
- };
-
- const markRead = item => {
- if (item.read_at) return;
- setItems(list => list.map(i => i.id === item.id ? {...i, read_at: new Date().toISOString()} : i));
- markSystemNotificationRead(item.id).catch(() => {
- // Roll the optimistic update back on failure -- otherwise the "new" badge silently vanishes
- // even though the server never actually recorded this notification as read.
- setItems(list => list.map(i => i.id === item.id ? {...i, read_at: null} : i));
- });
- };
-
- return
-
{ if (!Navigation.goBack()) navigate('messages-home'); }} aria-label="返回">arrow_back_ios_new 系统消息
-
- {loading ? hourglass_empty 加载中…
: null}
- {error ? error_outline {error}
: null}
- {!loading && !error && items.length === 0 ? notifications_none 暂无系统消息
: null}
- {items.map(item => markRead(item)}>
- {formatNotificationTime(item.created_at)}
-
-
{item.title} {!item.read_at ? 新 : null}
-
{systemNotificationText(item)}
- {item.action && item.action.device ?
设备: {item.action.device} {item.action.ip ?
IP地址: {item.action.ip} : null} : null}
- {/* 入口按通知自身的 action 决定,不再对所有通知一律显示"前往安全设置"。注意 action.topic
- 不能直接当成群:好友申请类通知的 action 里带的是 p2p 会话 topic
- (db/mysql/tenant_adapter.go:8643),只有 grp 前缀才是真的群。都不匹配时不显示按钮,
- 避免把人导到跟这条通知无关的页面。
- 真实好友申请通知的 action 判断改用 event_type 而不是 action.type:实测
- GET /system-notifications 拿到的 friend.request.created 通知,action 里只有
- {user_id},从来没有 type 字段(当初写 action.type==='friend_request' 是凭对接文档
- 猜的,不是照真实响应写的)——导致"查看新朋友"按钮对这条最常见的通知永远不出现。 */}
- {item.action && typeof item.action.topic === 'string' && item.action.topic.startsWith('grp') ?
-
{ e.stopPropagation(); navigate('group-settings', item.action.topic); }}>查看群聊 chevron_right :
- item.event_type === 'friend.request.created' ?
-
{ e.stopPropagation(); navigate('friend-requests'); }}>查看新朋友 chevron_right :
- item.action && (item.action.device || item.action.ip) ?
-
{ e.stopPropagation(); navigate(authenticated ? 'security' : 'identity-check'); }}>前往安全设置 chevron_right :
- null}
-
-
- )}
- {cursor ? {loadingMore ? '加载中…' : '加载更多'} : null}
-
-
setToast('')} />
- ;
-}
-
-export default function MessageCenterView(props) {
- switch (props.view) {
- case 'system-messages': return ;
- case 'message-actions': return ;
- case 'messages-offline':
- case 'messages-menu':
- case 'messages-home':
- default: return ;
- }
-}
diff --git a/src/views/message-composer-view.jsx b/src/views/message-composer-view.jsx
deleted file mode 100644
index 21960314..00000000
--- a/src/views/message-composer-view.jsx
+++ /dev/null
@@ -1,232 +0,0 @@
-import React from 'react';
-
-import Navigation from '../lib/navigation.js';
-
-const navigate = view => Navigation.navigateTo(view);
-
-export const MESSAGE_COMPOSER_VIEWS = [
- 'compose-keyboard', 'mentions-select', 'mentions-complete', 'mentions-message',
- 'emoji-panel', 'emoji-message', 'voice-ready', 'voice-hold', 'voice-recording',
- 'media-uploading', 'media-gallery', 'media-retry', 'upload-file', 'file-message',
- 'message-multiselect', 'favorite-success', 'forward-select', 'forward-multiple',
- 'forward-individual', 'forward-bundle'
-];
-
-const MEMBERS = [
- {id: 'all', name: '所有人', note: '提醒群内所有成员', avatar: 'campaign'},
- {id: 'annie', name: '安妮', note: '产品设计组', image: 'img/bkg/p09.jpg'},
- {id: 'lizi', name: '栗子来了', note: '前端开发组', image: 'img/bkg/p15.jpg'},
- {id: 'wang', name: '王珊珊', note: '市场运营组', image: 'img/bkg/p14.jpg'},
- {id: 'chen', name: '陈晨', note: '项目管理组', image: 'img/bkg/p05.jpg'}
-];
-
-const TARGETS = [
- {id: 'file', name: '文件助手', note: '仅自己可见', icon: 'folder_special'},
- {id: 'team', name: '产品研发群', note: '18人', image: 'img/bkg/p22.jpg'},
- {id: 'annie', name: '安妮', note: '在线', image: 'img/bkg/p09.jpg'},
- {id: 'lizi', name: '栗子来了', note: '刚刚活跃', image: 'img/bkg/p15.jpg'},
- {id: 'wang', name: '王珊珊', note: '市场运营组', image: 'img/bkg/p14.jpg'}
-];
-
-const EMOJIS = ['😀', '😄', '😂', '🥰', '😎', '🤔', '👍', '👏', '🎉', '❤️', '🔥', '💡', '✅', '🙏', '💪', '🌟', '📌', '🚀'];
-
-function Icon({children}) {
- return {children} ;
-}
-
-function Avatar({item}) {
- return
- {item.image ? : {item.icon || item.avatar || 'person'} }
- ;
-}
-
-function Header({title = '产品研发群', subtitle = '18人', onMore}) {
- return ;
-}
-
-function Toast({message, onDone}) {
- React.useEffect(() => {
- if (!message) return undefined;
- const timer = window.setTimeout(onDone, 2000);
- return () => window.clearTimeout(timer);
- }, [message, onDone]);
- return message ? {message}
: null;
-}
-
-function VoiceBubble({mine = false, duration = 8, unread = false}) {
- return
- {!mine ?
: null}
-
- graphic_eq {duration}″ {unread ? : null}
-
- {mine ?
: null}
-
;
-}
-
-function TextBubble({mine = false, children, selected, onSelect}) {
- return
- {selected !== undefined ?
{selected ? 'check' : ''} : null}
-
- {!mine ?
: null}
-
{children}
- {mine ?
: null}
-
-
;
-}
-
-function ImageBubble({progress, failed = false, selected, onSelect}) {
- return
- {selected !== undefined ?
{selected ? 'check' : ''} : null}
-
-
- {progress < 100 || failed ?
{failed ? 'refresh' : 'cloud_upload'} {failed ? '上传失败,点击重试' : `${progress}%`} {!failed ?
: null}
: null}
-
-
-
-
;
-}
-
-function VideoBubble() {
- return play_circle 00:24 ;
-}
-
-function FileBubble({progress = 100, name = '产品需求说明书.pdf', selected, onSelect}) {
- return
- {selected !== undefined ?
{selected ? 'check' : ''} : null}
-
PDF {name} 2.4 MB · {progress < 100 ? `上传中 ${progress}%` : '已发送'} {progress < 100 ?
: null}
{progress < 100 ? 'close' : 'download'}
-
;
-}
-
-function BaseTimeline({mode, uploadProgress, selected, toggleSelected}) {
- const selectable = mode === 'message-multiselect' || mode === 'favorite-success';
- return
- 今天 10:24
- 上午的评审结论已经整理好了,大家有空看一下。
- toggleSelected('text')}>好的,我补充一下交互细节。
- {mode === 'mentions-message' ? @安妮 @栗子来了 下午三点一起确认新版流程。 : null}
- {['media-uploading', 'media-gallery', 'media-retry'].includes(mode) ? <>
-
-
- {mode === 'media-gallery' ? <> > : null}
- > : null}
- {['file-message', 'message-multiselect', 'favorite-success'].includes(mode) ? toggleSelected('file')} /> : null}
- {selectable ? toggleSelected('image')} /> : null}
- ;
-}
-
-function EmojiPanel({onEmoji, onDelete}) {
- return
- schedule sentiment_satisfied_alt favorite_border
- {EMOJIS.map(emoji => onEmoji(emoji)}>{emoji} )}
- backspace
- ;
-}
-
-function Composer({mode, draft, setDraft, onSend, setMode}) {
- const voice = ['voice-ready', 'voice-hold', 'voice-recording'].includes(mode);
- const emoji = ['emoji-panel', 'emoji-message'].includes(mode);
- const keyDown = event => {
- if (event.key === '@' && mode !== 'mentions-message') window.setTimeout(() => navigate('mentions-select'), 0);
- };
- return <>
- {event.preventDefault(); onSend();}}>
- setMode(voice ? 'compose-keyboard' : 'voice-ready')} aria-label={voice ? '切换键盘' : '切换语音'}>{voice ? 'keyboard' : 'record_voice_over'}
- {voice ? setMode('voice-hold')} onPointerUp={() => setMode('media-uploading')}>按住说话 : setDraft(event.target.value)} onKeyDown={keyDown} placeholder="输入消息" />}
- setMode(emoji ? 'compose-keyboard' : 'emoji-panel')} aria-label="表情">sentiment_satisfied_alt
- {draft.trim() && !voice ? 发送 : navigate('upload-file')} aria-label="更多">add_circle_outline }
-
- {emoji ? setDraft(draft + value)} onDelete={() => setDraft(draft.slice(0, -1))} /> : null}
- >;
-}
-
-function VoiceOverlay({cancel = false}) {
- return
-
{cancel ? 'delete_outline' : 'mic'}
-
{[2, 5, 8, 12, 9, 6, 11, 4].map((height, index) => )}
-
{cancel ? '松开手指,取消发送' : '手指上滑,取消发送'} 00:08
-
;
-}
-
-function MemberPicker({complete}) {
- const [query, setQuery] = React.useState('');
- const [selected, setSelected] = React.useState(complete ? ['annie', 'lizi'] : []);
- const visible = MEMBERS.filter(item => item.name.includes(query) || item.note.includes(query));
- const toggle = id => setSelected(values => values.includes(id) ? values.filter(value => value !== id) : values.concat(id));
- return
-
navigate('compose-keyboard')}>arrow_back_ios_new 选择提醒人 {sessionStorage.setItem('im-mentions', JSON.stringify(selected)); navigate('mentions-message');}}>完成{selected.length ? `(${selected.length})` : ''}
-
search setQuery(event.target.value)} placeholder="搜索群成员" />
-
群成员
-
{visible.map(item =>
toggle(item.id)}>{item.name} {item.note} {selected.includes(item.id) ? check : null} )}
-
;
-}
-
-function UploadFilePage({onToast}) {
- const [files, setFiles] = React.useState([{name: '产品需求说明书.pdf', size: '2.4 MB', progress: 64}]);
- React.useEffect(() => {
- if (files.every(file => file.progress >= 100)) return undefined;
- const timer = window.setTimeout(() => setFiles(items => items.map(file => ({...file, progress: Math.min(100, file.progress + 6)}))), 300);
- return () => window.clearTimeout(timer);
- }, [files]);
- return ;
-}
-
-function ForwardPage({mode, onToast}) {
- const multi = mode !== 'forward-select';
- const confirm = mode === 'forward-individual' || mode === 'forward-bundle';
- const [selected, setSelected] = React.useState(() => {
- if (!confirm) return [];
- try {
- return JSON.parse(sessionStorage.getItem('im-forward-targets')) || ['team', 'annie'];
- } catch (_) {
- return ['team', 'annie'];
- }
- });
- const [query, setQuery] = React.useState('');
- const [comment, setComment] = React.useState('');
- const toggle = id => setSelected(values => values.includes(id) ? values.filter(value => value !== id) : multi ? values.concat(id).slice(0, 9) : [id]);
- const visible = TARGETS.filter(item => item.name.includes(query) || item.note.includes(query));
- return
-
navigate('message-multiselect')}>close {confirm ? (mode === 'forward-bundle' ? '合并转发' : '逐条转发') : '选择转发对象'} {!confirm ? navigate(multi ? 'forward-select' : 'forward-multiple')}>{multi ? '单选' : '多选'} : }
- {!confirm ? <>
search setQuery(event.target.value)} placeholder="搜索会话或联系人" />最近会话
{visible.map(item =>
{toggle(item.id); if (!multi) {sessionStorage.setItem('im-forward-targets', JSON.stringify([item.id])); window.setTimeout(() => navigate('forward-individual'), 0);}}}>{item.name} {item.note} {selected.includes(item.id) ? check : null} )}
{multi ?
已选择 {selected.length}/9 个会话 {sessionStorage.setItem('im-forward-targets', JSON.stringify(selected)); navigate('forward-individual');}}>下一步 : null}> :
发送给 {TARGETS.filter(item => selected.includes(item.id)).map(item =>
{item.name} )}
{mode === 'forward-bundle' ? '产品研发群的聊天记录' : '将逐条发送 3 条消息'} 栗子来了:上午的评审结论已经整理好了
我:好的,我补充一下交互细节。
我:[图片]
setComment(event.target.value)} placeholder="给朋友留言" /> navigate('forward-individual')}>逐条转发 navigate('forward-bundle')}>合并转发
{onToast(`已发送给 ${selected.length} 个会话`); window.setTimeout(() => navigate('chat-standard'), 900);}}>发送 }
-
;
-}
-
-export default function MessageComposerView({view}) {
- const [mode, setMode] = React.useState(view);
- const [draft, setDraftState] = React.useState(() => sessionStorage.getItem('im-message-composer-draft') || (view === 'emoji-message' ? '收到啦 ' : ''));
- const [uploadProgress, setUploadProgress] = React.useState(view === 'media-uploading' ? 22 : 100);
- const [selected, setSelected] = React.useState(view === 'favorite-success' ? ['text', 'file', 'image'] : ['text', 'image']);
- const [toast, setToast] = React.useState(view === 'favorite-success' ? '已收藏 3 条消息' : '');
- const setDraft = value => {setDraftState(value); sessionStorage.setItem('im-message-composer-draft', value);};
-
- React.useEffect(() => {
- if (mode !== 'media-uploading' || uploadProgress >= 100) return undefined;
- const timer = window.setTimeout(() => setUploadProgress(value => Math.min(100, value + 5)), 350);
- return () => window.clearTimeout(timer);
- }, [mode, uploadProgress]);
-
- if (view === 'mentions-select' || view === 'mentions-complete') return ;
- if (view === 'upload-file') return <> setToast('')} />>;
- if (['forward-select', 'forward-multiple', 'forward-individual', 'forward-bundle'].includes(view)) return <> setToast('')} />>;
-
- const toggleSelected = id => setSelected(values => values.includes(id) ? values.filter(value => value !== id) : values.concat(id));
- const recording = mode === 'voice-hold' || mode === 'voice-recording';
- const multiselect = mode === 'message-multiselect' || mode === 'favorite-success';
-
- return
-
setToast('会话设置')} />
- {multiselect ? navigate('chat-standard')}>close 已选择 {selected.length} 条 setSelected(selected.length ? [] : ['text', 'file', 'image'])}>{selected.length ? '取消全选' : '全选'}
: null}
-
- {multiselect ? navigate('forward-select')}>forward 转发 {setMode('favorite-success'); setToast(`已收藏 ${selected.length} 条消息`);}}>bookmark_border 收藏 {setSelected([]); setToast('已删除所选消息');}}>delete_outline 删除 : {if (!draft.trim()) return; setToast('消息已发送'); setDraft('');}} />}
- {recording ? : null}
- setToast('')} />
- ;
-}
diff --git a/src/views/messages-view.jsx b/src/views/messages-view.jsx
deleted file mode 100644
index 995dd12c..00000000
--- a/src/views/messages-view.jsx
+++ /dev/null
@@ -1,2819 +0,0 @@
-// Panel with a chat.
-
-import React, { Suspense } from 'react';
-import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
-
-import { Drafty, Tinode, TheCard } from 'tinode-sdk';
-
-// webpack.config.js maps the 'tinode-sdk' import to the global `tinode` UMD bundle, which only
-// re-exports {AccessMode, Drafty, TheCard, Tinode} (see umd/tinode.dev.js's own top-level export
-// list) -- LOCAL_SEQID was never one of them, despite tinode-sdk's ES module genuinely exporting
-// it (confirmed directly against umd/tinode.dev.js, which still defines it internally as
-// `const LOCAL_SEQID = 0xFFFFFFF;` and uses it in several of its own topic methods -- it's just
-// never attached to the UMD bundle's exported global object). Importing it by name here silently
-// resolved to `undefined` instead of a build error (an externals mapping can't tell "not on this
-// object" from "doesn't exist"), and every `seq < LOCAL_SEQID` below was therefore
-// `seq < undefined`, which JS always evaluates to false -- confirmed live: "撤回"
-// (menu_item_recall)never appeared in the long-press
-// menu for ANY message, own or admin, however fresh, because the seq-range check that's supposed
-// to just filter out not-yet-confirmed local placeholder messages was unconditionally rejecting
-// every real, server-confirmed message too. Hardcoding the same value the SDK itself uses (rather
-// than trying to change what webpack's externals map exposes, which every other page importing
-// from 'tinode-sdk' already depends on staying exactly as-is) is the narrowest fix.
-const LOCAL_SEQID = 0xFFFFFFF;
-
-const CallPanel = React.lazy(_ => import('../widgets/call-panel.jsx'));
-import ChatMessage from '../widgets/chat-message.jsx';
-import ContactBadges from '../widgets/contact-badges.jsx';
-const DocPreview = React.lazy(_ => import('../widgets/doc-preview.jsx'));
-import ErrorPanel from '../widgets/error-panel.jsx';
-import GroupSubs from '../widgets/group-subs.jsx';
-const ImagePreview = React.lazy(_ => import('../widgets/image-preview.jsx'));
-import Invitation from '../widgets/invitation.jsx';
-import LetterTile from '../widgets/letter-tile.jsx';
-import LoadSpinner from '../widgets/load-spinner.jsx';
-import LogoView from './logo-view.jsx';
-import MetaMessage from '../widgets/meta-message.jsx';
-import PinnedMessages from '../widgets/pinned-messages.jsx';
-import { RiskConfirmDialog } from './security-support-view.jsx';
-import SendMessage from '../widgets/send-message.jsx';
-const TheCardPreview = React.lazy(_ => import('../widgets/the-card-preview.jsx'));
-const VideoPreview = React.lazy(_ => import('../widgets/video-preview.jsx'));
-
-import { DEFAULT_P2P_ACCESS_MODE, EDIT_PREVIEW_LENGTH, IMAGE_PREVIEW_DIM, IMMEDIATE_P2P_SUBSCRIPTION,
- DRAFTY_FR_MIME_TYPE_LEGACY, KEYPRESS_DELAY, MESSAGES_PAGE, MAX_EXTERN_ATTACHMENT_SIZE, MAX_IMAGE_DIM,
- MAX_INBAND_ATTACHMENT_SIZE, READ_DELAY, QUOTED_REPLY_LENGTH, VIDEO_PREVIEW_DIM } from '../config.js';
-import { CALL_STATE_OUTGOING_INITATED, CALL_STATE_IN_PROGRESS } from '../constants.js';
-import { blobToBase64, fileToBase64, imageScaled, importVCard, makeImageUrl } from '../lib/blob-helpers.js';
-import { topicDisplayName } from '../lib/utils.js';
-import { acceptGroupInvitation, checkURL, confirmRiskChallenge, createChatUploader, favoriteMessages, getBootstrap,
- getConversationContext, getFavorites, getFriendRequests, getGroupJoinRequests, getGroupMembers, getLatestGroupAnnouncement,
- getTopicDraft, recallMessage, updateTopicDraft } from '../lib/im-api.js';
-import Navigation from '../lib/navigation.js';
-import { bytesToHumanSize, relativeDateFormat, shortDateFormat } from '../lib/strformat.js';
-
-// Run timer with this frequency (ms) for checking notification queue.
-const NOTIFICATION_EXEC_INTERVAL = 300;
-// Scroll distance before [go to latest message] button is shown.
-const SHOW_GO_TO_LAST_DIST = 100;
-// Sctoll distance from the top when fetching the page of earlier messages is triggered.
-const FETCH_PAGE_TRIGGER = 40;
-
-const messages = defineMessages({
- // 已存在于 src/i18n/*.json(zh: "消息已删除"),这里只是注册引用,不是新增待翻译串
- message_deleted: {
- id: 'message_deleted',
- defaultMessage: 'message deleted',
- description: 'Placeholder shown in the timeline where a message was recalled or deleted',
- },
- online_now: {
- id: 'online_now',
- defaultMessage: 'online now',
- description: 'Indicator that the user or topic is currently online',
- },
- last_seen: {
- id: 'last_seen_timestamp',
- defaultMessage: 'Last seen: {timestamp}',
- description: 'Label for the timestamp of when the user or topic was last online'
- },
- not_found: {
- id: 'title_not_found',
- defaultMessage: 'Not found',
- description: 'Title shown when topic is not found'
- },
- channel: {
- id: 'channel',
- defaultMessage: 'channel',
- description: 'Subtitle shown for channels in MessagesView instead of last seen'
- },
- file_attachment_too_large: {
- id: 'file_attachment_too_large',
- defaultMessage: 'The file size {size} exceeds the {limit} limit.',
- description: 'Error message when attachment is too large'
- },
- invalid_content: {
- id: 'invalid_content',
- defaultMessage: 'invalid content',
- description: 'Shown when the message is unreadable'
- },
- editing_message: {
- id: 'editing_message',
- defaultMessage: 'Editing',
- description: 'Title over message editing preview'
- },
- drag_file: {
- id: 'drag_file',
- defaultMessage: 'Drag file here',
- description: 'Prompt on the file drag-n-drop overlay banner'
- },
- self_topic_name: {
- id: 'self_topic_name',
- defaultMessage: 'Saved messages',
- description: 'Name of self topic for UI'
- },
- subscriber_count: {
- id: 'subscriber_count',
- defaultMessage: '{count, plural, ' +
- 'one {{count, number} subscriber} ' +
- 'two {{count, number} subscribers} ' +
- 'three {{count, number} subscribers} ' +
- 'few {{count, number} subscribers} ' +
- 'many {{count, number} subscribers} ' +
- 'other {{count, number} subscribers}}',
- description: 'Count of channel subscribers'
- },
- member_count: {
- id: 'member_count',
- defaultMessage: '{count, plural, ' +
- 'one {{count, number} member} ' +
- 'two {{count, number} members} ' +
- 'three {{count, number} members} ' +
- 'few {{count, number} members} ' +
- 'many {{count, number} members} ' +
- 'other {{count, number} members}}',
- description: 'Count of group topic members'
- },
- cannot_parse_vcard: {
- id: 'cannot_parse_vcard',
- defaultMessage: 'Cannot parse vCard file.',
- description: 'Error message when vCard file cannot be parsed'
- }
-});
-
-// Checks if the access permissions are granted but not yet accepted.
-function isUnconfirmed(acs) {
- if (acs) {
- const ex = acs.getExcessive() || '';
- return acs.isJoiner('given') && (ex.includes('R') || ex.includes('W'));
- }
- return false;
-}
-
-function isPeerRestricted(acs) {
- if (acs) {
- const ms = acs.getMissing() || '';
- return acs.isJoiner('want') && (ms.includes('R') || ms.includes('W'));
- }
- return false;
-}
-
-// Synthetic "所有人" entry in the @提醒 picker (mentionSearch below) -- not a real user_id, just a
-// marker mentionSelected/sendMessage recognize to set head.mention_all instead of resolving a
-// Drafty MN entity to it. Never sent to the server as an actual id.
-const MENTION_ALL_UID = '__mention_all__';
-
-function shouldPresentCallPanel(callState) {
- // Show call panel if either:
- // - call is outgoing (and the client is waiting for the other side to pick up) or,
- // - call is already in progress.
- return callState == CALL_STATE_OUTGOING_INITATED || callState == CALL_STATE_IN_PROGRESS;
-}
-
-class MessagesView extends React.Component {
- constructor(props) {
- super(props);
-
- this.state = MessagesView.getDerivedStateFromProps(props, {});
- // Not topic-specific, fetched once in componentDidMount -- unlike the rest of state this
- // is intentionally not touched by getDerivedStateFromProps on topic change.
- this.state.messagePolicy = null;
- this.state.toast = null;
- this.state.riskDialog = null;
- this.mentionedUsers = [];
- this.mentionAllPending = false;
-
- this.componentSetup = this.componentSetup.bind(this);
- this.leave = this.leave.bind(this);
- this.sendMessage = this.sendMessage.bind(this);
- this.handleRiskConfirm = this.handleRiskConfirm.bind(this);
- this.handleRiskCancel = this.handleRiskCancel.bind(this);
- this.retrySend = this.retrySend.bind(this);
- this.sendImageAttachment = this.sendImageAttachment.bind(this);
- this.sendVideoAttachment = this.sendVideoAttachment.bind(this);
- this.sendFileAttachment = this.sendFileAttachment.bind(this);
- this.sendAudioAttachment = this.sendAudioAttachment.bind(this);
- this.sendTheCardAttachment = this.sendTheCardAttachment.bind(this);
- this.sendKeyPress = this.sendKeyPress.bind(this);
- this.subscribe = this.subscribe.bind(this);
- this.handleScrollReference = this.handleScrollReference.bind(this);
- this.mountDnDEvents = this.mountDnDEvents.bind(this);
- this.handleScrollEvent = this.handleScrollEvent.bind(this);
- this.handleDescChange = this.handleDescChange.bind(this);
- this.handleSubsUpdated = this.handleSubsUpdated.bind(this);
- this.handleMessageUpdate = this.handleMessageUpdate.bind(this);
- this.handleAuxUpdate = this.handleAuxUpdate.bind(this);
- this.handleAllMessagesReceived = this.handleAllMessagesReceived.bind(this);
- this.handleInfoReceipt = this.handleInfoReceipt.bind(this);
- this.handleExpandMedia = this.handleExpandMedia.bind(this);
- this.handleClosePreview = this.handleClosePreview.bind(this);
- this.handleCallPanelToggle = this.handleCallPanelToggle.bind(this);
- this.handleFormResponse = this.handleFormResponse.bind(this);
- this.handleContextClick = this.handleContextClick.bind(this);
- this.handleShowMessageContextMenu = this.handleShowMessageContextMenu.bind(this);
- this.handleNewChatAcceptance = this.handleNewChatAcceptance.bind(this);
- this.handleEnablePeer = this.handleEnablePeer.bind(this);
- this.loadRelationshipContext = this.loadRelationshipContext.bind(this);
- this.loadGroupContext = this.loadGroupContext.bind(this);
- this.loadDraft = this.loadDraft.bind(this);
- this.handleComposerChange = this.handleComposerChange.bind(this);
- this.clearDraft = this.clearDraft.bind(this);
- this.mentionSearch = this.mentionSearch.bind(this);
- this.mentionSelected = this.mentionSelected.bind(this);
- this.handleShowAnnouncement = this.handleShowAnnouncement.bind(this);
- this.handleShowJoinRequests = this.handleShowJoinRequests.bind(this);
- this.handleSendFriendVerification = this.handleSendFriendVerification.bind(this);
- this.handleAcceptGroupInvitation = this.handleAcceptGroupInvitation.bind(this);
- this.handleBatchDelete = this.handleBatchDelete.bind(this);
- this.handleBatchFavorite = this.handleBatchFavorite.bind(this);
- this.handleAttachFile = this.handleAttachFile.bind(this);
- this.handleAttachImageOrVideo = this.handleAttachImageOrVideo.bind(this);
- this.handleCancelUpload = this.handleCancelUpload.bind(this);
- this.postReadNotification = this.postReadNotification.bind(this);
- this.clearNotificationQueue = this.clearNotificationQueue.bind(this);
- this.goToLatestMessage = this.goToLatestMessage.bind(this);
- this.handleFileDrop = this.handleFileDrop.bind(this);
-
- this.handlePickReply = this.handlePickReply.bind(this);
- this.handleEditMessage = this.handleEditMessage.bind(this);
- this.handleCancelReply = this.handleCancelReply.bind(this);
- this.handleQuoteClick = this.handleQuoteClick.bind(this);
- this.jumpToMessage = this.jumpToMessage.bind(this);
- this.handleUnpinMessage = this.handleUnpinMessage.bind(this);
- this.handleCallHangup = this.handleCallHangup.bind(this);
-
- this.isDragEnabled = this.isDragEnabled.bind(this);
- this.handleDragStart = this.handleDragStart.bind(this);
- this.handleDragIn = this.handleDragIn.bind(this);
- this.handleDragOut = this.handleDragOut.bind(this);
- this.handleDrag = this.handleDrag.bind(this);
- this.handleDrop = this.handleDrop.bind(this);
-
- this.chatMessageRefs = [];
- this.getOrCreateMessageRef = this.getOrCreateMessageRef.bind(this);
- this.getVisibleMessageRange = this.getVisibleMessageRange.bind(this);
-
- // Keeps track of the drag event.
- // Need a counter b/c the browser's 'drag' events may fire multiple times
- // when the user takes the mouse pointer over the container:
- // for the component itself and for all nested/child elements.
- this.dragCounter = 0;
- this.dndRef = null;
-
- this.readNotificationQueue = [];
- this.readNotificationTimer = null;
-
- this.keyPressTimer = null;
- }
-
- getOrCreateMessageRef(seqId) {
- if (this.chatMessageRefs[seqId]) {
- return this.chatMessageRefs[seqId];
- }
- const ref = React.createRef();
- this.chatMessageRefs[seqId] = ref;
- return ref;
- }
-
- getVisibleMessageRange(holderRect) {
- let min = Number.MAX_SAFE_INTEGER, max = -1;
- let visibilityStatus = false;
- this.chatMessageRefs.every((ref, seq) => {
- if (ref.current) {
- const { top, bottom, height } = ref.current.getBoundingClientRect();
- const visible = top <= holderRect.top ? holderRect.top - top <= height : bottom - holderRect.bottom <= height;
- if (visible) {
- visibilityStatus = true;
- min = Math.min(min, seq);
- max = Math.max(max, seq);
- } else if (visibilityStatus) {
- // The remaining elements are no longer visible, no need to iterate them.
- return false;
- }
- }
- return true;
- });
-
- return max >= min ? {min: min, max: max} : {min: 0, max: 0};
- }
-
- componentDidMount() {
- if (this.messagesScroller) {
- this.messagesScroller.addEventListener('scroll', this.handleScrollEvent);
- }
-
- // Drag and drop events
- this.mountDnDEvents(this.dndRef);
-
- this.componentSetup({}, {});
-
- // Ch.7: jump to a message found via chat-history search/media grid (content-library-view.jsx's
- // History) -- the componentDidUpdate check a few lines below only fires on a *change* to
- // jumpToSeq while this component stays mounted, but tinode-web.jsx never renders MessagesView
- // and a Ch.7 side panel at the same time (`!chapterSevenActive` gates it) -- every jump from
- // History's search results/image grid/video grid therefore unmounts the Ch.7 view and mounts a
- // *brand new* MessagesView instance with jumpToSeq already set on its very first render, which
- // componentDidUpdate never sees (it doesn't run for the initial mount). Confirmed live: the
- // seq stayed in the URL indefinitely (jumpToMessage's own `removeUrlParam` call never fired)
- // and the conversation opened at the bottom instead of scrolling to the found message --
- // 100% reproducible for every Ch.7 "定位到消息来源" entry point, not an intermittent timing issue.
- if (this.props.jumpToSeq) {
- const seq = parseInt(this.props.jumpToSeq, 10);
- const topicObj = this.props.tinode && this.props.topic ? this.props.tinode.getTopic(this.props.topic) : null;
- const attemptJump = () => this.jumpToMessage(seq);
- // A first-ever mount also means `componentSetup()` just above only *started* subscribing
- // this topic (subscribe() -> topic.subscribe() is async, fire-and-forget from here) --
- // jumpToMessage's own topic.getMeta() call requires an already-established subscription,
- // and firing immediately races ahead of it: confirmed live, this raced getMeta() call came
- // back as a real `{"code":403,"text":"permission denied"}` control message (not a local
- // guard, an actual rejected server round trip) while the real subscribe's own request was
- // still in flight one id apart. Poll the already-exposed isSubscribed() flag rather than
- // guessing a fixed delay or reworking subscribe()'s fire-and-forget call sites to expose
- // their promise; capped at 5s so a genuinely stuck subscribe still falls through to
- // jumpToMessage's own existing error handling instead of waiting forever.
- if (topicObj && !topicObj.isSubscribed()) {
- let tries = 0;
- const waitForSubscription = () => {
- if (topicObj.isSubscribed() || ++tries > 25) {
- attemptJump();
- } else {
- window.setTimeout(waitForSubscription, 200);
- }
- };
- waitForSubscription();
- } else {
- attemptJump();
- }
- }
-
- getBootstrap().then(data => {
- this.setState({messagePolicy: data.message_policy || null});
- }).catch(_ => {});
- }
-
- componentWillUnmount() {
- // componentSetup below has an identical guard for switching from one topic to another during an
- // active call -- but componentDidUpdate/componentSetup never runs on an actual unmount (leaving
- // the messaging section entirely for another chapter, or on mobile switching to the conversation
- // list while displayMobile). Without this, CallPanel's own componentWillUnmount only tears down
- // local media: it never notifies the peer or resets tinode-web.jsx's top-level callState/
- // callTopic, which then stay stuck at CALL_STATE_IN_PROGRESS. Navigating back to the call's topic
- // later remounts a "zombie" CallPanel -- its start() sees that stale CALL_STATE_IN_PROGRESS and
- // takes the "we just accepted" branch, which never calls getUserMedia(): no local media, every
- // control disabled, only hang-up works.
- if (this.state.rtcPanel) {
- this.handleCallHangup(this.state.topic, this.props.callSeq);
- }
- if (this.messagesScroller) {
- this.messagesScroller.removeEventListener('scroll', this.handleScrollEvent);
- }
- window.clearTimeout(this.toastTimer);
-
- // Flush all notifications.
- this.clearNotificationQueue();
-
- // Drag and drop events
- if (this.dndRef) {
- this.dndRef.removeEventListener('dragstart', this.handleDragStart);
- this.dndRef.removeEventListener('dragenter', this.handleDragIn);
- this.dndRef.removeEventListener('dragleave', this.handleDragOut);
- this.dndRef.removeEventListener('dragover', this.handleDrag);
- this.dndRef.removeEventListener('drop', this.handleDrop);
- }
- }
-
- componentDidUpdate(prevProps, prevState) {
- // Scroll last message into view on component update e.g. on message received
- // or vertical shrinking.
- if (this.messagesScroller &&
- (prevState.topic != this.state.topic || prevState.maxSeqId != this.state.maxSeqId ||
- prevState.minSeqId != this.state.minSeqId)) {
- // New message.
- if (this.state.scrollPosition < SHOW_GO_TO_LAST_DIST) {
- this.messagesScroller.scrollTop = this.messagesScroller.scrollHeight -
- this.state.scrollPosition - this.messagesScroller.offsetHeight;
- }
- }
-
- if (!this.props.applicationVisible) {
- // If application is not visible, flush all unsent 'read' notifications.
- this.clearNotificationQueue();
- } else {
- // Otherwise assume there are unread messages.
- this.postReadNotification(0);
- }
-
- // Ch.7: jump to a message found via chat-history search (content-library-view.jsx's History),
- // passed in as a ?jumpseq= hash param rather than a normal prop since the navigation that gets us
- // here also has to change the *topic*, which content-library-view.jsx has no direct way to do.
- if (this.props.jumpToSeq && this.props.jumpToSeq != prevProps.jumpToSeq) {
- this.jumpToMessage(parseInt(this.props.jumpToSeq, 10));
- }
-
- this.componentSetup(prevProps, prevState);
- }
-
- componentSetup(prevProps, prevState) {
- const topic = this.props.tinode ? this.props.tinode.getTopic(this.state.topic) : undefined;
- if (this.state.topic != prevState.topic) {
- if (prevState.topic && !Tinode.isNewGroupTopicName(prevState.topic)) {
- this.leave(prevState.topic);
- if (prevState.rtcPanel) {
- this.handleCallHangup(prevState.topic, prevProps.callSeq);
- }
- }
-
- if (topic) {
- topic.onData = this.handleMessageUpdate;
- topic.onAllMessagesReceived = this.handleAllMessagesReceived;
- topic.onInfo = this.handleInfoReceipt;
- topic.onMetaDesc = this.handleDescChange;
- topic.onSubsUpdated = this.handleSubsUpdated;
- topic.onPres = this.handleSubsUpdated;
- topic.onAuxUpdated = this.handleAuxUpdate;
- }
-
- this.mentionedUsers = [];
- this.mentionAllPending = false;
- this.loadRelationshipContext(topic);
- this.loadGroupContext(topic);
- this.loadDraft(topic);
- }
-
- if (topic) {
- if ((this.state.topic != prevState.topic) || (this.props.myUserId && !prevProps.myUserId)) {
- // Don't immediately subscribe to a new p2p topic, wait for the first message.
- const newTopic = (this.props.newTopicParams && this.props.newTopicParams._topicName == this.props.topic);
- if (topic.isP2PType() && newTopic && !IMMEDIATE_P2P_SUBSCRIPTION) {
- topic.getMeta(topic.startMetaQuery().withDesc().build());
- } else if (this.props.myUserId) {
- this.subscribe(topic);
- }
- } else if (topic.isSubscribed() && this.state.isReader && !prevState.isReader) {
- // If reader status has changed and data became available.
- topic.getMeta(topic.startMetaQuery().withLaterData().build());
- } else if (!topic.isSubscribed() && this.props.ready && !prevProps.ready) {
- // Connection restored (e.g. wake from sleep): resubscribe to the currently open topic.
- this.subscribe(topic);
- }
- }
- }
-
- static getDerivedStateFromProps(nextProps, prevState) {
- let nextState = {};
- if (!nextProps.topic) {
- // Default state: no topic.
- nextState = {
- minSeqId: -1,
- maxSeqId: -1,
- latestClearId: -1,
- onlineSubs: [],
- topic: null,
- title: '',
- avatar: null,
- isVerified: false,
- isStaff: false,
- isDangerous: false,
- deleted: false,
- docPreview: null,
- imagePreview: null,
- imagePostview: null,
- videoPreview: null,
- videoPostview: null,
- rtcPanel: null,
- minimizedCallPanel: false,
- typingIndicator: false,
- scrollPosition: 0,
- fetchingMessages: false,
- peerMessagingDisabled: false,
- channel: false,
- reply: null,
- contentToEdit: null,
- showGoToLastButton: false,
- dragging: false,
- pins: [],
- pinsLoaded: false,
- selectedPin: 0,
- subsVersion: 0,
- relationshipContext: null,
- pendingRequestMessage: null,
- groupRoles: null,
- myGroupRole: null,
- groupAnnouncement: null,
- pendingJoinRequests: 0,
- draftText: null,
- draftVersion: 0
- };
- } else if (nextProps.topic != prevState.topic) {
- const topic = nextProps.tinode.getTopic(nextProps.topic);
-
- nextState = {
- topic: nextProps.topic,
- deleted: topic._deleted,
- docPreview: null,
- imagePreview: null,
- imagePostview: null,
- videoPreview: null,
- videoPostview: null,
- rtcPanel: null,
- minimizedCallPanel: false,
- typingIndicator: false,
- scrollPosition: 0,
- fetchingMessages: false,
- showGoToLastButton: false,
- contentToEdit: null,
- dragging: false,
- selectedPin: 0,
- relationshipContext: null,
- pendingRequestMessage: null,
- groupRoles: null,
- myGroupRole: null,
- groupAnnouncement: null,
- pendingJoinRequests: 0,
- draftText: null,
- draftVersion: 0,
- // MessagesView isn't remounted on topic switch (no `key={topic}` at the call site in
- // tinode-web.jsx), so a pending risk-check dialog from the conversation just left would
- // otherwise survive the switch -- confirming it would then send the old topic's message
- // into whichever topic is now open, since doSend()'s `this.props.sendMessage` resolves the
- // topic to send to at call time, not at the time the dialog was raised.
- riskDialog: null
- };
-
- if (nextProps.forwardMessage) {
- if (nextProps.forwardMessage.content) {
- const content = nextProps.forwardMessage.content;
- const size = content?.length || (content?.size || 0);
- nextState.docPreview = {
- object: content,
- type: nextProps.forwardMessage.type,
- size: size
- };
- } else {
- // We are forwarding a message. Show preview.
- nextState.reply = {
- content: nextProps.forwardMessage.preview,
- seq: null
- };
- }
- } else {
- nextState.reply = null;
- }
-
- if (topic) {
- // Topic exists.
- const subs = [];
-
- if (nextProps.connected) {
- topic.subscribers((sub) => {
- if (sub.online && sub.user != nextProps.myUserId) {
- subs.push(sub);
- }
- });
- }
-
- Object.assign(nextState, {
- onlineSubs: subs
- });
-
- if (topic.isSelfType()) {
- Object.assign(nextState, {
- title: nextProps.intl.formatMessage(messages.self_topic_name),
- avatar: true
- });
- } else if (topic.public) {
- Object.assign(nextState, {
- title: topicDisplayName(topic.name, topic.public, ''),
- avatar: makeImageUrl(topic.public.photo)
- });
- } else {
- Object.assign(nextState, {
- title: '',
- avatar: null
- });
- }
-
- const peer = topic.p2pPeerDesc();
- if (peer) {
- Object.assign(nextState, {
- peerMessagingDisabled: isPeerRestricted(peer.acs)
- });
- } else if (prevState.peerMessagingDisabled) {
- Object.assign(nextState, {
- peerMessagingDisabled: false
- });
- }
- Object.assign(nextState, {
- minSeqId: topic.minMsgSeq(),
- maxSeqId: topic.maxMsgSeq(),
- latestClearId: topic.maxClearId(),
- channel: topic.isChannelType(),
- pins: (topic.aux('pins') || []).slice(),
- pinsLoaded: false
- });
-
- if (nextProps.callTopic == topic.name && shouldPresentCallPanel(nextProps.callState)) {
- nextState.rtcPanel = nextProps.callTopic;
- }
- } else {
- // Invalid topic.
- Object.assign(nextState, {
- minSeqId: -1,
- maxSeqId: -1,
- latestClearId: -1,
- onlineSubs: [],
- title: '',
- avatar: null,
- peerMessagingDisabled: false,
- channel: false,
- pins: [],
- pinsLoaded: false
- });
- }
- } else {
- // We are still in same topic. Show the call panel if necessary.
- if (nextProps.callTopic == prevState.topic && !prevState.rtcPanel &&
- shouldPresentCallPanel(nextProps.callState)) {
- nextState.rtcPanel = nextProps.callTopic;
- }
- }
-
- if (nextProps.acs) {
- if (nextProps.acs.isWriter() != prevState.isWriter) {
- nextState.isWriter = !prevState.isWriter;
- }
- if (nextProps.acs.isReader() != prevState.isReader) {
- nextState.isReader = !prevState.isReader;
- }
- if (nextProps.acs.isAdmin() != prevState.isAdmin) {
- nextState.isAdmin = !prevState.isAdmin;
- }
- if (!nextProps.acs.isReader('given') != prevState.readingBlocked) {
- nextState.readingBlocked = !prevState.readingBlocked;
- }
- if (nextProps.acs.isSharer() != prevState.isSharer) {
- nextState.isSharer = !prevState.isSharer;
- }
- } else {
- if (prevState.isWriter) {
- nextState.isWriter = false;
- }
- if (prevState.isReader) {
- nextState.isReader = false;
- }
- if (prevState.isAdmin) {
- nextState.isAdmin = false;
- }
- if (!prevState.readingBlocked) {
- nextState.readingBlocked = true;
- }
- if (prevState.isSharer) {
- nextState.isSharer = false;
- }
- }
-
- if (isUnconfirmed(nextProps.acs) == !prevState.unconformed) {
- nextState.unconfirmed = !prevState.unconformed;
- }
-
- // Clear subscribers online when there is no connection.
- if (!nextProps.connected && prevState.onlineSubs && prevState.onlineSubs.length > 0) {
- nextState.onlineSubs = [];
- }
-
- return nextState;
- }
-
- subscribe(topic) {
- if (!this.props.ready) {
- return;
- }
-
- // Is this a new topic?
- const newTopic = (this.props.newTopicParams && this.props.newTopicParams._topicName == this.props.topic);
- // Don't request the tags. They are useless unless the user
- // is the owner and is editing the topic.
- let getQuery = topic.startMetaQuery().withLaterDesc().withLaterSub().withAux();
- if (this.state.isReader || newTopic) {
- // Reading is either permitted or we don't know because it's a new topic. Ask for messages.
- //
- // withData(undefined, undefined, MESSAGES_PAGE) instead of the seemingly-more-natural
- // withLaterData(MESSAGES_PAGE): the latter asks the server for "whatever is newer than
- // topic._maxSeq", which is only correct when _maxSeq reflects a real, contiguous, locally-cached
- // history. But tinode-web.jsx's prefetchLatestMessagePreviews() (conversation list "last
- // message" preview) may have already called topic.subscribe(startMetaQuery().withLaterData(1))
- // for this exact topic the moment the chat list rendered -- often *before* this view ever mounts,
- // e.g. the very first time a brand-new conversation is opened in a fresh session -- which sets
- // _maxSeq to that one prefetched message's seq without caching anything before it. From that
- // point on withLaterData(MESSAGES_PAGE) asks for "later than the one message I already have" and
- // gets back nothing: confirmed live, a brand-new 4-message P2P conversation opened this way
- // rendered only the single latest message, silently missing the rest of the history.
- // withData(undefined, undefined, limit) has no such since-cursor -- it always asks for "the
- // latest `limit` messages" regardless of what the client thinks it already has, which is
- // correct both in this poisoned-prefetch case and in the plain fresh-topic case (there
- // topic._maxSeq is 0, so withLaterData(limit) already degrades to the exact same
- // since:undefined query -- this is a strict fix, not a behavior change for the common path).
- getQuery = getQuery.withData(undefined, undefined, MESSAGES_PAGE);
- if (this.state.isReader) {
- getQuery = getQuery.withLaterDel();
- }
- // And show "loading" spinner.
- this.setState({ fetchingMessages: true });
- }
-
- const setQuery = newTopic ? this.props.newTopicParams : undefined;
- const doSubscribe = () => topic.subscribe(getQuery.build(), setQuery)
- .then(ctrl => {
- if (ctrl.code == 303) {
- // Redirect to another topic requested.
- Navigation.navigateTo(Navigation.setUrlTopic('', ctrl.params.topic));
- return;
- }
- if (this.state.topic != ctrl.topic) {
- this.setState({topic: ctrl.topic});
- }
- if (this.state.deleted) {
- this.setState({deleted: false});
- }
- this.props.onNewTopicCreated(this.props.topic, ctrl.topic);
- // If there are unsent messages (except hard-failed and video call messages),
- // try sending them now. Hard-failed and unsent video call messages will be dropped.
- let discard = [];
- topic.queuedMessages(pub => {
- if (pub._sending) {
- return;
- }
- if (pub._fatal || (pub.head && pub.head.webrtc)) {
- // Filter out unsent failed & video call messages.
- discard.push(pub.seq);
- return;
- }
- if (topic.isSubscribed()) {
- this.retrySend(pub);
- }
- });
- if (discard.length > 0) {
- topic.delMessagesList(discard, true);
- }
- })
- .catch(err => {
- console.error("Failed subscription to", this.state.topic, err);
- this.props.onError(err.message, 'err');
- const blankState = MessagesView.getDerivedStateFromProps({}, {});
- blankState.title = this.props.intl.formatMessage(messages.not_found);
- this.setState(blankState);
- });
-
- if (topic.isSubscribed()) {
- // Same prefetch race as the comment on the data query above: the topic may already be
- // "attached" server-side from prefetchLatestMessagePreviews' single-message subscribe, before
- // this view ever asked for anything. The SDK's own Topic.subscribe() unconditionally
- // short-circuits to `Promise.resolve(this)` once `_attached` is true (tinode.dev.js)
- // *without even looking at the getParams passed in* -- calling topic.subscribe(getQuery...)
- // directly in that state would silently no-op and this view would never receive the real
- // history/pinned-messages/peer read-receipts it just asked for. leave(false) clears _attached
- // (both client-side via _resetSub() and server-side, since it's a real round trip) without
- // discarding already-cached messages (that only happens for leave(true), i.e. unsubscribe) --
- // once it resolves, the follow-up doSubscribe() is guaranteed to be a genuine, un-shortcut
- // subscribe that gets the server to actually answer getQuery instead of silently dropping it.
- topic.leave(false).catch(_ => { /* best-effort; doSubscribe() below still needs to run */ }).then(doSubscribe);
- return;
- }
-
- doSubscribe();
- }
-
- leave(oldTopicName) {
- if (!oldTopicName || !this.props.tinode.isTopicCached(oldTopicName)) {
- return;
- }
-
- const oldTopic = this.props.tinode.getTopic(oldTopicName);
- if (oldTopic && oldTopic.isSubscribed()) {
- oldTopic.leave(false)
- .catch(_ => { /* do nothing here */ })
- .finally(_ => {
- // We don't care if the request succeeded or failed.
- // The topic is dead regardless.
- this.setState({fetchingMessages: false});
- oldTopic.onData = undefined;
- oldTopic.onAllMessagesReceived = undefined;
- oldTopic.onInfo = undefined;
- oldTopic.onMetaDesc = undefined;
- oldTopic.onSubsUpdated = undefined;
- oldTopic.onPres = undefined;
- oldTopic.onAuxUpdated = undefined;
- });
- }
- }
-
- // Don't use React.createRef as the ref.current is not available in componentDidMount in this component.
- handleScrollReference(node) {
- if (node) {
- node.addEventListener('scroll', this.handleScrollEvent);
- this.messagesScroller = node;
- this.messagesScroller.scrollTop = this.messagesScroller.scrollHeight -
- this.state.scrollPosition - this.messagesScroller.offsetHeight;
- }
- }
-
- // Get older messages and show/hide [go to latest message] button.
- handleScrollEvent(event) {
- const pos = event.target.scrollHeight - event.target.scrollTop - event.target.offsetHeight;
- this.setState({
- scrollPosition: pos,
- // Show [go to latest message] if far enough from bottom and scrolling down.
- showGoToLastButton: (pos > SHOW_GO_TO_LAST_DIST) && (pos < this.state.scrollPosition),
- });
-
- if (this.state.fetchingMessages || this.processingScrollEvent) {
- return;
- }
-
- if (event.target.scrollTop <= FETCH_PAGE_TRIGGER) {
- const topic = this.props.tinode.getTopic(this.state.topic);
- if (topic && topic.isSubscribed()) {
- this.processingScrollEvent = true;
- const {min, max} = this.getVisibleMessageRange(event.target.getBoundingClientRect());
- const gaps = topic.msgHasMoreMessages(min, max, false);
- if (gaps.length > 0) {
- this.setState({fetchingMessages: true}, _ => {
- topic.getMessagesPage(MESSAGES_PAGE, gaps, min, max)
- .catch(err => this.props.onError(err.message, 'err'))
- .finally(_ => this.setState({fetchingMessages: false}));
- });
- }
- }
- }
- this.processingScrollEvent = false;
- }
-
- /* Mount drag and drop events */
- mountDnDEvents(dnd) {
- if (dnd) {
- dnd.addEventListener('dragstart', this.handleDragStart);
- dnd.addEventListener('dragenter', this.handleDragIn);
- dnd.addEventListener('dragleave', this.handleDragOut);
- dnd.addEventListener('dragover', this.handleDrag);
- dnd.addEventListener('drop', this.handleDrop);
- this.dndRef = dnd;
- }
- }
-
- goToLatestMessage() {
- this.setState({scrollPosition: 0});
- if (this.messagesScroller) {
- this.messagesScroller.scrollTop = this.messagesScroller.scrollHeight - this.messagesScroller.offsetHeight;
- }
- }
-
- handleDescChange(desc) {
- if (Tinode.isSelfTopicName(this.props.topic)) {
- this.setState({
- title: this.props.intl.formatMessage(messages.self_topic_name),
- avatar: true
- });
- } else if (desc.public) {
- this.setState({
- title: topicDisplayName(this.props.topic, desc.public, ''),
- avatar: makeImageUrl(desc.public.photo)
- });
- } else {
- this.setState({
- title: '',
- avatar: null
- });
- }
-
- if (desc.acs) {
- this.setState({
- isWriter: desc.acs.isWriter(),
- isReader: desc.acs.isReader(),
- isAdmin: desc.acs.isAdmin(),
- readingBlocked: !desc.acs.isReader('given'),
- unconfirmed: isUnconfirmed(desc.acs),
- });
- }
- }
-
- postReadNotification(seq) {
- // Ignore notifications if the app is invisible or topic is not yet available.
- if (!this.props.applicationVisible || !this.state.topic) {
- return;
- }
-
- // Set up the timer if it's not running already.
- if (!this.readNotificationTimer) {
- this.readNotificationTimer = setInterval(_ => {
- if (this.readNotificationQueue.length == 0) {
- // Shut down the timer if the queue is empty.
- clearInterval(this.readNotificationTimer);
- this.readNotificationTimer = null;
- return;
- }
-
- let seq = -1;
- while (this.readNotificationQueue.length > 0) {
- const n = this.readNotificationQueue[0];
- if (n.topicName != this.state.topic) {
- // Topic has changed. Drop the notification.
- this.readNotificationQueue.shift();
- continue;
- }
-
- const now = new Date();
- if (n.sendAt <= now) {
- // Remove expired notification from queue.
- this.readNotificationQueue.shift();
- if (n.seq == 0 || Tinode.isServerAssignedSeq(n.seq)) {
- seq = Math.max(seq, n.seq);
- }
- } else {
- break;
- }
- }
-
- // Send only one notification for the whole batch of messages.
- if (seq >= 0) {
- const topic = this.props.tinode.getTopic(this.state.topic);
- if (topic) {
- try {
- topic.noteRead(seq);
- } catch (err) {
- console.error("Failed to send read notification", err);
- }
- }
- }
- }, NOTIFICATION_EXEC_INTERVAL);
- }
-
- const now = new Date();
- this.readNotificationQueue.push({
- topicName: this.state.topic,
- seq: seq,
- sendAt: now.setMilliseconds(now.getMilliseconds() + READ_DELAY)
- });
- }
-
- // Clear notification queue and timer.
- clearNotificationQueue() {
- this.readNotificationQueue = [];
- if (this.readNotificationTimer) {
- clearInterval(this.readNotificationTimer);
- this.readNotificationTimer = null;
- }
- }
-
- handleSubsUpdated() {
- if (this.state.topic) {
- const subs = [];
- const topic = this.props.tinode.getTopic(this.state.topic);
- topic.subscribers(sub => {
- if (sub.online && sub.user != this.props.myUserId) {
- subs.push(sub);
- }
- });
- const newState = {onlineSubs: subs, subsVersion: this.state.subsVersion + 1};
- const peer = topic.p2pPeerDesc();
- if (peer) {
- Object.assign(newState, {
- peerMessagingDisabled: isPeerRestricted(peer.acs)
- });
- } else if (this.state.peerMessagingDisabled) {
- Object.assign(newState, {
- peerMessagingDisabled: false
- });
- }
- this.setState(newState);
- }
- }
-
- // The 'msg' could be false-ish if some message ranges were deleted.
- handleMessageUpdate(msg) {
- if (!this.state.topic) {
- return;
- }
- const topic = this.props.tinode.getTopic(this.state.topic);
- if (!msg) {
- // msg could be null if one or more messages were deleted.
- // Updating state to force redraw.
- this.setState({latestClearId: topic.maxClearId()});
- return;
- }
-
- clearTimeout(this.keyPressTimer)
- this.setState({maxSeqId: topic.maxMsgSeq(), minSeqId: topic.minMsgSeq(), typingIndicator: false}, _ => {
- // Scroll to the bottom if the message is added to the end of the message
- // list if already at the bottom, otherwise show [go to latest] button.
- // Implemented as a callback to be sure the scroll height has been updated.
- if (topic.isNewMessage(msg.seq)) {
- if (this.state.scrollPosition > SHOW_GO_TO_LAST_DIST) {
- this.setState({showGoToLastButton: true});
- } else {
- this.goToLatestMessage();
- }
- } else {
- if (this.messagesScroller) {
- this.messagesScroller.scrollTop = this.messagesScroller.scrollHeight - this.state.scrollPosition -
- this.messagesScroller.offsetHeight;
- }
- }
- });
-
- // Aknowledge messages except own messages. They are
- // automatically assumed to be read and recived.
- const status = topic.msgStatus(msg, true);
- if (status >= Tinode.MESSAGE_STATUS_SENT && msg.from != this.props.myUserId) {
- this.postReadNotification(msg.seq);
- }
- }
-
- handleAllMessagesReceived(count) {
- this.setState({fetchingMessages: false});
- if (!count) {
- // All messages received. Nothing left to receive.
- const topic = this.props.tinode.getTopic(this.state.topic);
- if (topic) {
- // Send read notification for the absolute last message.
- this.postReadNotification(topic.seq);
- }
- return;
- }
-
- // Load pinned message too (if any);
- if (!this.state.pinsLoaded) {
- // This needs to be guarded to avoid an infinite loop.
- // The loop may happen if some pinned messages are deleted.
- const topic = this.props.tinode.getTopic(this.state.topic);
- this.setState({pinsLoaded: true}, _ => {
- topic.getPinnedMessages();
- });
- } else {
- this.setState({pinsLoaded: false});
- }
- }
-
- handleAuxUpdate(aux) {
- const pins = (aux['pins'] || []).slice();
- let selectedPin = this.state.selectedPin;
- if (pins.length > this.state.pins.length) {
- // New pins added, show the latest.
- selectedPin = 0;
- } else if (selectedPin >= pins.length) {
- selectedPin = Math.max(0, pins.length - 1);
- }
- this.setState({pins: pins, selectedPin: selectedPin});
- }
-
- handleInfoReceipt(info) {
- switch (info.what) {
- case 'kp': {
- clearTimeout(this.keyPressTimer);
- this.keyPressTimer = setTimeout(_ => this.setState({typingIndicator: false}), KEYPRESS_DELAY + 1000);
- if (!this.state.typingIndicator) {
- this.setState({typingIndicator: true});
- }
- break;
- }
- case 'read':
- case 'recv':
- // Redraw due to changed recv/read status.
- this.forceUpdate();
- break;
- default:
- console.info("Other change in topic: ", info.what);
- }
- }
-
- handleExpandMedia(content) {
- if (!content) {
- return;
- }
-
- if (content.video) {
- this.setState({videoPostview: content});
- } else {
- this.setState({imagePostview: content});
- }
- }
-
- handleClosePreview() {
- if (this.state.imagePreview && this.state.imagePreview.url) {
- URL.revokeObjectURL(this.state.imagePreview.url);
- }
- if (this.state.videoPreview && this.state.videoPreview.url) {
- URL.revokeObjectURL(this.state.videoPreview.url);
- }
- this.setState({
- imagePostview: null,
- imagePreview: null,
- docPreview: null,
- videoPreview: null,
- videoPostview: null,
- });
- this.props.onCancelForwardMessage();
- }
-
- handleFormResponse(action, text, data) {
- if (action == 'pub') {
- this.sendMessage(Drafty.attachJSON(Drafty.parse(text), data));
- } else if (action == 'url') {
- const url = new URL(data.ref);
- const params = url.searchParams;
- for (let key in data.resp) {
- if (data.resp.hasOwnProperty(key)) {
- params.set(key, data.resp[key]);
- }
- }
- ['name', 'seq'].forEach(key => {
- if (data[key]) {
- params.set(key, data[key]);
- }
- });
- params.set('uid', this.props.myUserId);
- params.set('topic', this.state.topic);
- url.search = params;
- window.open(url, '_blank');
- } else {
- console.info("Unknown action in form", action);
- }
- }
-
- handleContextClick(e) {
- e.preventDefault();
- e.stopPropagation();
- this.props.showContextMenu({ topicName: this.state.topic, y: e.pageY, x: e.pageX });
- }
-
- handleShowMessageContextMenu(params, messageSpecificMenuItems) {
- if (params.userFrom == 'chan') {
- params.userFrom = this.state.topic;
- params.userName = this.state.title;
- }
- params.topicName = this.state.topic;
- let menuItems = messageSpecificMenuItems || [];
- // Picking "回复" on some other message while already editing one (this.state.reply.editing)
- // silently abandons the edit: handlePickReply overwrites this.state.reply wholesale but never
- // touches contentToEdit, so the composer keeps showing the message being edited while the
- // header/send-icon quietly switch to reply mode -- sending then posts that text as a brand new
- // reply to the other message, and the original edit is lost with no confirmation. Simplest fix
- // is the same one already used for menu_item_edit's window check: just don't offer the option
- // that would cause it, rather than trying to reconcile the two states after the fact.
- if (this.state.reply && this.state.reply.editing) {
- menuItems = menuItems.filter(item => item !== 'menu_item_reply');
- }
- const topic = this.props.tinode.getTopic(params.topicName);
- if (topic) {
- // Edit (Ch.4): chat-message.jsx already gated 'menu_item_edit' on writer + non-attachment
- // content, but has no access to the server-configured edit window (that's session-level state
- // living here, not a per-message prop) -- unlike recall just below, this was never actually
- // filtered by age, so an edit attempt on a message well past the window would only fail at
- // send time instead of never showing the option. Same window-check shape as recall's, just
- // removing the item after the fact instead of adding it, since base eligibility already ran.
- const editWindow = this.state.messagePolicy ? this.state.messagePolicy.edit_window_seconds : 0;
- if (menuItems.includes('menu_item_edit') && !(editWindow > 0 && params.timestamp &&
- params.timestamp.getTime() > (new Date().getTime() - editWindow * 1000))) {
- menuItems = menuItems.filter(item => item !== 'menu_item_edit');
- }
- menuItems.push('menu_item_copy', 'menu_item_favorite');
- // Recall (Ch.4): own message, real seq, and still inside the server-configured window.
- // Distinct from delete -- see context-menu.jsx recallOwnMessage for why both exist.
- // A group admin/owner can also recall *other* members' messages within the same window --
- // server/im_http.go's serveIMMessageRecall calls authorizeIMMessageMutation with
- // allowAdmin=true, so the backend has always accepted this; the reference screenshot
- // (消息页面—长按弹出.png) lists "群撤回" as its own, always-shown menu item alongside "撤回" --
- // this was previously never offered at all since the only condition checked was authorship.
- //
- // `params.seq > 0` alone is not enough to mean "a real, server-persisted message": Tinode
- // SDK gives every message that hasn't been confirmed by the server yet (still sending, or
- // failed outright) a local placeholder seq of LOCAL_SEQID (0xFFFFFFF = 268435455) -- a large
- // *positive* number, so it passed this check. Round 11 live testing reproduced this: sending
- // a message into a since-blocked P2P topic left a bubble stuck in "发送失败" with seq
- // 268435455, and this menu still offered "撤回" for it -- clicking it fired
- // POST /topics/.../messages/268435455/recall, which the server correctly rejects (no such
- // message), surfacing a confusing "请求内容有误,请稍后重试" toast for an action that should
- // never have been offered. `seq < LOCAL_SEQID` excludes exactly that placeholder range,
- // matching the same bound the SDK itself uses internally to distinguish a confirmed seq from
- // a queued one.
- const recallWindow = this.state.messagePolicy ? this.state.messagePolicy.recall_window_seconds : 0;
- const canRecallAsAdmin = topic.isGroupType() && topic.getAccessMode() && topic.getAccessMode().isAdmin();
- if ((params.userFrom == this.props.myUserId || canRecallAsAdmin) && params.seq > 0 && params.seq < LOCAL_SEQID &&
- recallWindow > 0 && params.timestamp && params.timestamp.getTime() > (new Date().getTime() - recallWindow * 1000)) {
- menuItems.push('menu_item_recall');
- }
- // Self -> (1) always hard-delete with [Delete] title.
- // P2P -> (1) hard-delete if allowed and not too old, (2) always soft-delete.
- // Group -> (1) hard-delete if allowed and not too old or owner, (2) always soft-delete.
- if (topic.isSelfType()) {
- // Hard-delete with plain [Delete] title.
- menuItems.push('message_delete_generic');
- } else {
- if (!topic.isChannelType()) {
- // P2P or normal group: always allow to soft-delete.
- menuItems.push('message_delete');
- }
-
- const acs = topic.getAccessMode();
- if (acs && acs.isDeleter()) {
- // Owner can always hard-delete regardless of age.
- let canDelete = acs.isOwner();
- if (!canDelete) {
- // Not owner: check message age if configured on the server.
- const maxDelAge = this.props.tinode.getServerParam(Tinode.MSG_DELETE_AGE, 0) | 0;
- // Make sure the message is not too old. This check is here just to make the UI
- // more user-friendly. The server will enforce the limit regardless of this.
- canDelete = maxDelAge == 0 || (maxDelAge > 0 && params.timestamp ?
- params.timestamp.getTime() > (new Date().getTime() - maxDelAge * 1000) : false);
- }
- if (canDelete) {
- // [Delete for All] title.
- menuItems.push('message_delete_hard');
- }
- }
- }
- }
- menuItems.push('menu_item_select');
- this.props.showContextMenu(params, menuItems);
- }
-
- handleNewChatAcceptance(action) {
- this.props.onNewChat(this.state.topic, action);
- }
-
- handleEnablePeer(e) {
- e.preventDefault();
- this.props.onChangePermissions(this.state.topic, DEFAULT_P2P_ACCESS_MODE, this.state.topic);
- }
-
- // Fetch the real friend/block/pending relationship state for a 1:1 topic so the UI can explain
- // (instead of silently disabling) why sending is restricted. Best-effort: a failed fetch just
- // leaves relationshipContext null and the existing isWriter-based disable stays in effect.
- loadRelationshipContext(topic) {
- if (!topic || !topic.isP2PType()) {
- return;
- }
- const topicName = topic.name;
- getConversationContext(topicName).then(data => {
- if (this.state.topic != topicName) {
- return;
- }
- this.setState({relationshipContext: data});
- this.loadPendingRequestMessage(topicName, data);
- }).catch(_ => {});
- }
-
- // 个人对话框(对方的状态).png / (我的状态).png:pending_inbound/pending_outbound 关系下,聊天
- // 页顶部第一条应该是发起好友申请时留的验证留言原文,以真实消息气泡的样式渲染(对方发的居左、
- // 自己发的居右),下面再跟现有的 #relationship-status-note 横幅。这条留言从来不是一条真实的
- // Tinode 消息(没有走 topic.publish(),只存在 im_friend_requests 表的 message 字段里),所以
- // 不会自然出现在 messageNodes 里——relationshipContextPayload 已经带了 request_id,用它去
- // GET /friend-requests 换真实留言文本;找不到(比如留言本身是空的)就回退成参考图里的默认文案。
- loadPendingRequestMessage(topicName, ctx) {
- const relationship = ctx && ctx.relationship;
- if (relationship != 'pending_inbound' && relationship != 'pending_outbound') {
- this.setState({pendingRequestMessage: null});
- return;
- }
- if (!ctx.request_id) {
- this.setState({pendingRequestMessage: {text: '请求添加你为好友', response: relationship == 'pending_inbound', ts: null}});
- return;
- }
- const direction = relationship == 'pending_inbound' ? 'inbound' : 'outbound';
- getFriendRequests(direction, 'all').then(data => {
- if (this.state.topic != topicName) {
- return;
- }
- const match = (data.items || []).find(item => item.request_id == ctx.request_id);
- this.setState({pendingRequestMessage: {
- text: (match && match.message) || '请求添加你为好友',
- response: relationship == 'pending_inbound',
- ts: match ? new Date(match.created_at) : null
- }});
- }).catch(_ => {
- if (this.state.topic == topicName) {
- this.setState({pendingRequestMessage: {text: '请求添加你为好友', response: relationship == 'pending_inbound', ts: null}});
- }
- });
- }
-
- // Ch.6: role badges (docs/images/群标识.png) and the announcement/join-request banners at the top of a
- // group chat (docs/images/群公告状态.png, 新邀请进群申请.png) all need data no P2P topic has -- fetch it
- // once per group-topic-load, same best-effort pattern as loadRelationshipContext above. Every `topic`
- // field these Ch.6 endpoints return is a real grp-prefixed native Tinode name (verified against the Go
- // source), so there's no Ch.5-style translation needed anywhere here.
- loadGroupContext(topic) {
- if (!topic || !topic.isGroupType() || topic.isChannelType()) {
- return;
- }
- const topicName = topic.name;
- getGroupMembers(topicName).then(data => {
- if (this.state.topic != topicName) {
- return;
- }
- const roles = {};
- (data.items || []).forEach(m => { roles[m.user_id] = m.role; });
- this.setState({groupRoles: roles, myGroupRole: roles[this.props.myUserId] || null});
- // Pending join requests can only be resolved by an admin/owner -- only they can act on the banner,
- // and only they pass requireIMGroupMember's IsAdmin() check on the list endpoint itself.
- if (roles[this.props.myUserId] == 'owner' || roles[this.props.myUserId] == 'admin') {
- getGroupJoinRequests(topicName, 'pending').then(reqData => {
- if (this.state.topic == topicName) {
- this.setState({pendingJoinRequests: (reqData.items || []).length});
- }
- }).catch(_ => {});
- }
- }).catch(_ => {});
- getLatestGroupAnnouncement(topicName).then(data => {
- if (this.state.topic == topicName) {
- this.setState({groupAnnouncement: (data && data.content) ? data : null});
- }
- }).catch(_ => {});
- }
-
- // 4.2-4.3 @提醒:只对群话题开放(私聊提@没有意义,也没有可选的"其他成员")。返回给
- // send-message.jsx 的选择器渲染,真正的 uid 解析在 mentionSelected 里记下来,供发送时
- // applyMentionResolution 使用。
- mentionSearch(query) {
- const topic = this.props.tinode ? this.props.tinode.getTopic(this.state.topic) : null;
- if (!topic || !topic.isGroupType()) {
- return Promise.resolve([]);
- }
- return getGroupMembers(this.state.topic, {mentionable: true, query}).then(data => {
- const items = data.items || [];
- // "所有人" (参考图 @人员—选择提醒人.png 里排在真实成员最前面的合成选项) -- server/topic.go's
- // validateAndNormalizeMentions rejects head.mention_all from anyone who isn't admin
- // (ErrPermissionDenied), so this is only offered to accounts GET .../members already told us
- // can actually use it -- an ineligible account would otherwise see the option, pick it, and
- // have the server silently reject the send.
- const canMentionAll = !!(data.permissions && data.permissions.can_mention_all);
- if (canMentionAll && '所有人'.includes((query || '').trim())) {
- return [{user_id: MENTION_ALL_UID, display_name: '所有人'}, ...items];
- }
- return items;
- });
- }
-
- mentionSelected(uid, name) {
- if (uid === MENTION_ALL_UID) {
- // Not a real Drafty mention to resolve -- sendMessage below reads this flag directly to set
- // head.mention_all, independent of the this.mentionedUsers/applyMentionResolution machinery
- // single-person @提醒 uses. The "@所有人" text itself still gets auto-detected by Drafty.parse
- // into an ordinary (uid-less) MN entity and renders highlighted like any other mention --
- // that's just decorative here since the real "notify everyone" effect comes from the head flag.
- this.mentionAllPending = true;
- return;
- }
- this.mentionedUsers = (this.mentionedUsers || []).filter(m => m.name !== name);
- this.mentionedUsers.push({uid, name});
- }
-
- // 4.1 跨话题草稿:话题一切换就去问服务端这个会话有没有存过草稿。原生话题名(P2P 用对方
- // user_id)直接可用,接口内部自己会做订阅存在性校验,不需要额外归一化。
- loadDraft(topic) {
- if (!topic) {
- return;
- }
- const topicName = topic.name;
- getTopicDraft(topicName).then(data => {
- if (this.state.topic != topicName) {
- return;
- }
- this.setState({draftText: data.content || '', draftVersion: data.version || 0});
- }).catch(_ => {});
- }
-
- // 输入框每次变化都会调这个(不在这里节流——防抖延迟统一放这一处,跟日报/月报草稿已有的
- // 600ms 防抖是同一个数值,方便以后统一调整)。编辑已有消息、转发预填内容这两种情况下
- // SendMessage 的文本框也会触发 onChange,但那些不是"新写的草稿",不应该覆盖真正的草稿。
- //
- // 定时器按话题分开存(this.draftSaveTimers 是个 map),不能像原来那样用单个共享句柄——
- // 那样话题A打字触发的600ms保存,会在300ms后切到话题B打字时被 clearTimeout 直接取消掉,
- // A刚打的字就再也不会发给服务器了。
- //
- // version 在这里(调用的当下,this.state.topic 必然还是 topicName,因为 onMessageChange
- // 只有当前激活话题的 SendMessage 实例会触发)同步捕获进闭包,不能等定时器触发时才去读
- // this.state.draftVersion——那时用户很可能已经切到别的话题,届时重新读到的要么是无关话题
- // 的版本号,要么在判断不匹配时退化成硬编码的 0。后端 PUT .../draft 对 version=0 是"无条件
- // 跳过乐观锁校验直接覆盖"而不是"仅当草稿真的不存在时才创建"(见
- // docs/IM-后端问题清单-2026-09-04.md),这个 0 一旦被真的发出去,可能无声覆盖掉这段时间里
- // 别的标签页/设备写入的更新草稿。
- handleComposerChange(text) {
- if (this.state.reply && this.state.reply.editing) {
- return;
- }
- if (this.props.forwardMessage) {
- return;
- }
- const topicName = this.state.topic;
- if (!topicName) {
- return;
- }
- const version = this.state.draftVersion || 0;
- this.draftSaveTimers = this.draftSaveTimers || {};
- if (this.draftSaveTimers[topicName]) {
- window.clearTimeout(this.draftSaveTimers[topicName]);
- }
- this.draftSaveTimers[topicName] = window.setTimeout(() => {
- delete this.draftSaveTimers[topicName];
- updateTopicDraft(topicName, text, [], version).then(d => {
- if (this.state.topic == topicName) {
- this.setState({draftVersion: d.version});
- }
- }).catch(_ => {});
- }, 600);
- }
-
- // 消息真的发出去之后,把服务端存的草稿清空——不然重新打开这个会话会看到已经发送过的旧文字。
- // 单纯等下一次防抖自然把空字符串存上去也能达到同样效果,但那样在"发送后立刻切走"这个真实
- // 会发生的场景里会有一个几百毫秒的窗口,草稿还是旧内容,不如发送成功后立刻清。
- // knownVersion 由调用方(sendMessage)在发送发起的那一刻同步捕获传入,原因同上——避免在
- // 发送成功回调触发时(用户很可能已经切换话题)重新派生出不可靠的版本号。
- clearDraft(topicName, knownVersion) {
- if (this.draftSaveTimers && this.draftSaveTimers[topicName]) {
- window.clearTimeout(this.draftSaveTimers[topicName]);
- delete this.draftSaveTimers[topicName];
- }
- const version = knownVersion != null ? knownVersion :
- (this.state.topic == topicName) ? (this.state.draftVersion || 0) : 0;
- updateTopicDraft(topicName, '', [], version).then(d => {
- if (this.state.topic == topicName) {
- this.setState({draftText: '', draftVersion: d.version});
- }
- }).catch(_ => {});
- }
-
- // Note: setUrlSidePanel only overwrites path[0] -- called with Navigation.currentUrl() while a topic is
- // open (path[1] is the open topic), it leaves that segment in place, producing e.g.
- // "/group-announcement-detail/grpXXX?topic=grpXXX". handleRouteChange then re-parses path[1] as a topic
- // selector on every route change, fighting with the sidepanel that's trying to open and leaving the
- // whole view blank. Build the target URL from an empty path instead of the current one.
- handleShowAnnouncement(e) {
- e.preventDefault();
- // `from=chat` lets Announcement (group-management-view.jsx) tell this entry point apart from
- // the one via group-settings -- both land on the identical path[0]/topic hash otherwise, so
- // without it the "back" button can't know whether to return to group-settings or to this chat.
- let hash = Navigation.addUrlParam(Navigation.setUrlSidePanel('', 'group-announcement-detail'), 'topic', this.state.topic);
- hash = Navigation.addUrlParam(hash, 'from', 'chat');
- Navigation.navigateTo(hash);
- }
-
- handleShowJoinRequests(e) {
- e.preventDefault();
- // `from=chat` mirrors handleShowAnnouncement above -- lets JoinRequests (group-management-view.jsx)
- // return to this chat instead of always landing on group-governance.
- let hash = Navigation.addUrlParam(Navigation.setUrlSidePanel('', 'group-join-requests'), 'topic', this.state.topic);
- hash = Navigation.addUrlParam(hash, 'from', 'chat');
- Navigation.navigateTo(hash);
- }
-
- handleSendFriendVerification(e) {
- e.preventDefault();
- // Same stray-path[1] hazard as handleShowAnnouncement above (build from an empty base, not the
- // current hash, since a topic is open) -- this one additionally never set `uid`, which the
- // friend-verification page needs (contact-management-view.jsx's Verification reads hashParams.uid)
- // to know who the verification is for; for a P2P topic that's simply the topic name itself.
- Navigation.navigateTo(Navigation.addUrlParam(
- Navigation.setUrlSidePanel('', 'friend-verification'), 'uid', this.state.topic));
- }
-
- // 6.3 加入群聊.png:被邀请人在跟邀请人的会话里收到一张"邀请你加入群聊……立即加入"的卡片。
- // 后端 serveIMGroupInvitations 确实往这个 P2P 会话发了一条 x-im-type=system_action、
- // template=group_invitation_created 的消息,content.actions 里带着真实的
- // {type:"group_invitation_accept", label:"立即加入", target_id:}——但
- // chat-message.jsx 原来对所有 system_action/system_notice 消息一律只取 content.text 渲染成
- // 纯文本,这个 action 数据被整体丢弃,卡片上不会出现任何按钮,被邀请人无法接受邀请。
- // acceptGroupInvitation() 这个封装早就在 im-api.js 里,只是从未有调用点。
- handleAcceptGroupInvitation(invitationId) {
- return acceptGroupInvitation(invitationId).then(result => {
- if (result && result.status == 'joined') {
- this.showToast('已加入群聊');
- } else if (result && result.status == 'pending_approval') {
- this.showToast('申请已提交,等待管理员通过');
- } else {
- this.showToast('已处理该邀请');
- }
- return result;
- }).catch(err => {
- this.showToast(err.message || '加入群聊失败,请重试');
- throw err;
- });
- }
-
- showToast(text) {
- this.setState({toast: text});
- window.clearTimeout(this.toastTimer);
- // Matches div.toast.show's own fadein(0.5s) + hold(2.5s) + fadeout(0.5s) animation length,
- // so the class is removed right as the CSS fadeout finishes instead of cutting it short.
- this.toastTimer = window.setTimeout(() => this.setState({toast: null}), 3500);
- }
-
- handleBatchDelete() {
- const topic = this.props.tinode.getTopic(this.state.topic);
- if (!topic || !this.props.selectedSeqs.length) {
- return;
- }
- // A selected message may still be queued/uploading (no server-confirmed seq yet, same case
- // context-menu.jsx's single-message deleteMessages guards with cancelSend before falling
- // back to delMessagesList). delMessagesList's normalizeRanges silently drops local-only
- // seqs instead of cancelling them, so left unfiltered a still-uploading attachment would
- // survive "delete" and get sent normally once its upload finishes.
- const remaining = this.props.selectedSeqs.filter(seq => !topic.cancelSend(seq));
- if (!remaining.length) {
- this.props.onExitSelectMode();
- return;
- }
- topic.delMessagesList(remaining, true).then(_ => {
- this.props.onExitSelectMode();
- }).catch(err => {
- this.props.onError(err.message, 'err');
- });
- }
-
- // Per docs/images/多选-选中对话内容-收藏成功.png the selection stays visible behind the toast --
- // unlike delete, favoriting doesn't remove the messages, so there's no reason to exit select mode.
- handleBatchFavorite() {
- if (!this.props.selectedSeqs.length) {
- return;
- }
- const topicName = this.state.topic;
- const seqs = this.props.selectedSeqs.slice();
- favoriteMessages(seqs.map(seq => ({topic: topicName, seq}))).then(() => {
- // `result.failed` can't be trusted here: the batch-create endpoint reuses one request-level
- // Idempotency-Key across every message in the batch (docs/IM-后端问题清单-2026-09-04.md,
- // 新增1), so every item after the first silently no-ops via ON DUPLICATE KEY UPDATE and gets
- // folded into `existing_count` -- indistinguishable, from this response alone, from a message
- // that really was already favorited. Re-reading the actual favorites list and checking which
- // of the messages we just asked for really landed sidesteps that ambiguity entirely.
- return getFavorites({type: 'all', limit: 100}).then(data => {
- const items = (data && data.items) || [];
- const landed = new Set(items
- .filter(it => it.source && it.source.topic == topicName)
- .map(it => String(it.source.seq)));
- const missing = seqs.filter(seq => !landed.has(String(seq)));
- if (missing.length === seqs.length) {
- this.props.onError('收藏失败', 'err');
- } else if (missing.length) {
- this.showToast(`已收藏 ${seqs.length - missing.length}/${seqs.length} 条`);
- } else {
- this.showToast('收藏成功');
- }
- });
- }).catch(err => {
- this.props.onError(err.message, 'err');
- });
- }
-
- sendKeyPress(audio) {
- const topic = this.props.tinode.getTopic(this.state.topic);
- if (!topic.isSubscribed()) {
- return;
- }
- if (audio) {
- // Doc's default for the "user is recording voice" indicator is OFF unless product opts in
- // (a non-persisted {note what:"kpa"} broadcast to every other group member) -- this was firing
- // unconditionally on every recording, contradicting that documented default. No product signal
- // to turn it on yet, so this is a deliberate no-op rather than a removed feature: re-enable by
- // restoring `topic.noteRecording(true);` here if/when that's confirmed wanted.
- return;
- }
- topic.noteKeyPress();
- }
-
- // sendMessage sends the message with an optional subscription to topic first.
- // typeHint (Ch.7): the backend's imMessageType() classifier -- which drives every type-filtered list
- // in this chapter (favorites tabs, chat-history search/media tabs) -- only recognizes a flat
- // content.file_id/content.url key or an explicit head['x-im-type'] hint; it never walks a real
- // Drafty entity array. Since every attachment this app actually sends uses the standard ent[]
- // format (Drafty.attachFile/insertImage/insertVideo), none of them carry a flat file_id, so without
- // this hint every image/video/file/voice message silently classifies as plain "text" server-side.
- // Mutates a parsed Drafty document in place, upgrading any auto-detected "@name" MN entity whose
- // raw matched text equals a name the user actually picked from the mention dropdown into a real,
- // uid-bearing mention. Entities that don't match anything tracked (a literal "@word" the user just
- // typed, or an edited/mismatched name) are left with their raw text value -- Tinode's own renderer
- // already falls back to displaying that gracefully, this only upgrades the ones it safely can.
- applyMentionResolution(parsed) {
- if (!parsed || !Array.isArray(parsed.ent) || !this.mentionedUsers || !this.mentionedUsers.length) {
- return;
- }
- parsed.ent.forEach(e => {
- if (e.tp === 'MN' && e.data && typeof e.data.val === 'string') {
- const match = this.mentionedUsers.find(m => m.name === e.data.val);
- if (match) {
- e.data.val = match.uid;
- }
- }
- });
- }
-
- sendMessage(msg, uploadCompletionPromise, uploader, typeHint) {
- // Captured before the reply/edit branches below possibly rewrite `msg` into a Drafty object
- // (Drafty.append(...) for a reply-with-quote) -- the URL risk check further down keys off
- // `typeof msg == 'string'`, so without this a plain-text reply containing a link would silently
- // skip the check entirely (a fresh, non-reply message with the same text does get checked).
- const originalText = typeof msg === 'string' ? msg : null;
- let head;
- if (!msg && this.props.forwardMessage) {
- // We are forwarding a message.
- msg = this.props.forwardMessage.msg;
- head = this.props.forwardMessage.head;
- } else if (this.state.reply) {
- if (this.state.reply.editing) {
- if (msg == this.state.contentToEdit) {
- // Message unchanged.
- this.handleCancelReply();
- return;
- }
- // Editing an existing message.
- head = {replace: ':' + this.state.reply.seq};
- } else if (this.state.reply.content) {
- // Replying to a message in this topic.
- // Turn it into Drafty so we can make a quoted Drafty object later.
- head = {reply: '' + this.state.reply.seq};
- if (typeof msg == 'string') {
- msg = Drafty.parse(msg);
- this.applyMentionResolution(msg);
- }
- // Both tinode.dev.js helpers below mutate their first argument in place and return that
- // same reference (Drafty.sanitizeEntities: `return content` after patching content.ent[i]
- // itself; Drafty.append: `first.txt += ...`, `first.fmt.push(...)`, then `return first`) --
- // neither ever clones. Passing `this.state.reply.content` straight through used to hand this
- // live React-state object to both, so every call here permanently welded whatever `msg` this
- // particular attempt carried onto the quoted content the *reply itself* points to. Invisible
- // on an ordinary send since handleCancelReply() clears state.reply right after, discarding the
- // now-mutated copy along with it -- but the URL risk dialog above defers doSend (and this
- // append) until *after* the check resolves, and cancelling it deliberately leaves state.reply
- // alone so the reply survives (see doSend's own comment). Confirmed live: reply to a message,
- // type a flagged link, send, click "取消发送", send the exact same text again, confirm through
- // the dialog this time -- the composed message quoted the reply once but then repeated the
- // typed text twice, because the first (cancelled) attempt had already appended it into
- // state.reply.content, and the second attempt appended onto *that*. A deep clone here keeps
- // both helpers' in-place mutations confined to a throwaway copy every time, however many times
- // sendMessage runs for the same still-open reply.
- const replyContentCopy = JSON.parse(JSON.stringify(this.state.reply.content));
- msg = Drafty.append(Drafty.sanitizeEntities(replyContentCopy), msg);
- }
- } else if (typeof msg == 'string' && this.mentionedUsers && this.mentionedUsers.length) {
- // 4.2-4.3 @提醒:Drafty.parse 本来就会把纯文本里的"@word"自动识别成 MN 实体,但
- // data.val 存的是匹配到的原始文字(tinode.dev.js 的 ENTITY_TYPES 表,pack: val =>
- // ({val: val.slice(1)})),不是 uid——那是纯文本解析器,不可能凭空知道 uid。真正的 uid
- // 只在用户从选择器里点选时才知道,记在 this.mentionedUsers 里;这里按显示名回填,把
- // 自动识别出的占位符换成真的 uid。没有任何选过的提醒时完全不碰 msg,维持字符串原样,
- // 交给 SDK 自己在更底层 parse(跟这次改动之前完全一样的行为,不引入任何差异)。
- const parsed = Drafty.parse(msg);
- this.applyMentionResolution(parsed);
- msg = parsed;
- }
- // Was only cleared on topic switch -- picking someone from the @提醒 picker once left them
- // resolvable for every later message in the same topic too, so literal text "@张三" typed by
- // hand, pasted, or quoted from someone else in a LATER, unrelated message would silently turn
- // into a real mention of whoever was last picked under that name. A retry of a failed send
- // (retrySend) reuses the already-resolved Drafty content stored on the failed pub, not this
- // array, so clearing it here doesn't affect retries.
- this.mentionedUsers = [];
- if (this.mentionAllPending) {
- // server/topic.go's validateAndNormalizeMentions reads head.mention_all directly -- it's a
- // separate signal from the Drafty MN entity the "@所有人" text itself auto-parses into (that
- // entity has no real uid and is purely decorative here, same as any other unresolved mention).
- // Reset immediately after reading, same lifecycle as this.mentionedUsers just above -- a
- // "所有人" pick should only apply to the one message being sent right now, not linger into
- // later ones in the same topic.
- head = {...(head || {}), mention_all: true};
- this.mentionAllPending = false;
- }
- if (typeHint) {
- head = {...(head || {}), 'x-im-type': typeHint};
- }
-
- const topicForDraft = this.state.topic;
- // Captured now, not re-read inside the .then() below -- by the time send actually resolves
- // the user may have switched topics, and clearDraft falling back to a hardcoded 0 runs into
- // the same backend version=0-skips-the-lock issue explained in handleComposerChange above.
- const draftVersionAtSend = this.state.draftVersion || 0;
- const doSend = () => {
- // Was called eagerly above (right after building `msg`/`head` from `this.state.reply`/
- // `this.props.forwardMessage`), synchronously and unconditionally -- including on a message
- // that turns out to need the URL risk dialog below, which only resolves *after* this function
- // already returned. That meant clicking send on a reply/edit containing a link made the
- // "回复"/编辑 preview bar vanish immediately, before the user had even seen the risk prompt;
- // clicking "取消发送" then left the reply/edit permanently abandoned -- the quoted message was
- // gone from the composer with no way back except manually replying again, even though the user
- // only meant to cancel sending the flagged text, not the reply itself. Moving the call here
- // means it only fires at the point a send is actually going to happen: immediately for the
- // common non-URL case (this function runs synchronously right away, same timing as before),
- // or deferred until "继续发送" for a flagged one -- and never at all if the dialog is cancelled,
- // so the reply/edit state and its preview bar correctly survive a cancelled risk prompt.
- this.handleCancelReply();
- const result = this.props.sendMessage(msg, uploadCompletionPromise, uploader, head);
- // Only a plain-text compose (not a reply/edit/forward, all of which pass a Drafty object or
- // reuse contentToEdit rather than the persisted draft) has a server-side draft worth clearing.
- if (originalText != null && result && typeof result.then == 'function' && topicForDraft) {
- result.then(() => this.clearDraft(topicForDraft, draftVersionAtSend));
- }
- return result;
- };
-
- // Pre-send URL risk check (POST /security/urls/check) -- the only Ch.11 "anti-fraud" mechanism
- // with a real backend behind it. Only plain-text composed messages are checked (not forwards,
- // uploads, or already-Drafty content) since that's the one path a URL could plausibly be typed in.
- // Returns a promise resolving to `false` when the message was NOT sent (blocked/cancelled) so
- // send-message.jsx can leave the composer text intact instead of optimistically clearing it --
- // without this, a blocked message's text would just vanish with no failed-message trace to retry.
- // Was missing the `g` flag -- .match() without it only ever returns the FIRST url in the message,
- // so a second url (e.g. the phishing link in "看这个 https://safe.example.com 但是别信这个
- // https://phish.example.com/login?token=abc") never got checked at all as long as the first one
- // came back `allow`. Check every distinct url in the message and act on the riskiest verdict.
- const urlMatches = originalText && originalText.match(/https?:\/\/\S+/g);
- if (urlMatches) {
- const RISK_RANK = {block: 2, warn: 1, allow: 0};
- const urls = Array.from(new Set(urlMatches));
- return Promise.all(urls.map(url => checkURL(url, 'message_compose'))).then(results => {
- // The user may have already switched to a different conversation while checkURL was
- // still in flight. getDerivedStateFromProps only protects against an ALREADY-shown
- // dialog surviving a later topic switch (it nulls riskDialog when nextProps.topic !=
- // prevState.topic) -- it can't protect against this .then() running AFTER that switch,
- // which would otherwise setState({riskDialog:{...}}) fresh right here, popping the risk
- // dialog up while a completely different conversation is now open. doSend() still calls
- // this.props.sendMessage using whatever topic is current AT CONFIRM TIME (see doSend's
- // own comment above), not the one this message was actually typed in, so confirming that
- // dialog would silently deliver this message into the wrong conversation. Confirmed live:
- // typed a flagged URL in topic A, switched to topic B before checkURL resolved, and the
- // resulting dialog rendered while viewing topic B -- confirming it delivered topic A's
- // message into topic B instead. If the topic has moved on, treat this message the same
- // as if it were abandoned -- no dialog, no send -- the same "lost, but not hung forever"
- // outcome already accepted just below for two flagged messages sent in quick succession.
- if (this.state.topic !== topicForDraft) {
- return false;
- }
- const result = results.reduce((worst, r) => (RISK_RANK[r.decision] || 0) > (RISK_RANK[worst.decision] || 0) ? r : worst);
- if (result.decision === 'allow') {
- return doSend();
- }
- return new Promise(resolve => {
- // riskDialog is a single slot -- if an earlier risk check is still awaiting the user's
- // decision (two messages sent in quick succession, both flagged), overwriting it here
- // would leave that first promise permanently unresolved and its message silently dropped
- // with send-message.jsx never told to stop holding it as unsent. Resolving it `false` first
- // means it's treated the same as the user cancelling it -- lost, but not hung forever.
- if (this.state.riskDialog) this.state.riskDialog.resolve(false);
- this.setState({riskDialog: {blocked: result.decision === 'block', challengeId: result.risk_challenge_id, send: doSend, resolve}});
- });
- }).catch(_ => doSend()); // Don't let a risk-check failure block sending altogether.
- }
-
- return doSend();
- }
-
- handleRiskConfirm() {
- const dialog = this.state.riskDialog;
- if (!dialog) return;
- this.setState({riskDialog: null});
- const finish = () => dialog.resolve(dialog.send());
- if (dialog.challengeId) {
- confirmRiskChallenge(dialog.challengeId, 'continue').then(finish).catch(finish);
- } else {
- finish();
- }
- }
-
- handleRiskCancel() {
- const dialog = this.state.riskDialog;
- if (!dialog) return;
- this.setState({riskDialog: null});
- if (dialog.challengeId && !dialog.blocked) {
- confirmRiskChallenge(dialog.challengeId, 'cancel').catch(_ => {});
- }
- dialog.resolve(false);
- }
-
- // Retry sending a message.
- retrySend(pub) {
- this.props.sendMessage(pub.content, undefined, undefined, pub.head)
- .then(_ => {
- // All good. Remove the original message draft from the cache.
- const topic = this.props.tinode.getTopic(this.state.topic);
- topic.delMessagesList([pub.seq], true);
- });
- }
-
- // sendFileAttachment sends the file as Drafty message:
- // - if file is too large, upload it and send a s link.
- // - if file is small enough, just send it in-band.
- sendFileAttachment(file) {
- // Server-provided limit reduced for base64 encoding and overhead.
- const maxInbandAttachmentSize = (this.props.tinode.getServerParam('maxMessageSize',
- MAX_INBAND_ATTACHMENT_SIZE) * 0.75 - 1024) | 0;
-
- if (TheCard.isFileSupported(file.type, file.name) || file.type == TheCard.contentType) {
- // This is a vCard file, try to convert and send as TheCard.
- if (this.sendTheCardAttachment(file, maxInbandAttachmentSize)) {
- // Successfully sent as TheCard.
- return;
- }
- // Sending as TheCard failed, fall through to sending as generic file attachment.
- }
-
- // If the attachment is a JSON file, then use 'application/octet-stream' instead of 'application/json'.
- // This is a temporary workaround for the collision with the 'application/json' MIME type of form responses.
- // Remove this code in 2026 or so.
- const jsonMimeConverter = (fileType) => fileType === DRAFTY_FR_MIME_TYPE_LEGACY ? 'application/octet-stream' : fileType;
-
- if (file.size > maxInbandAttachmentSize) {
- // Too large to send inband - uploading out of band and sending as a link.
- const uploader = createChatUploader(this.props.tinode);
- if (!uploader) {
- this.props.onError(this.props.intl.formatMessage(messages.cannot_initiate_upload));
- return;
- }
- const uploadCompletionPromise = uploader.upload(file);
- const msg = Drafty.attachFile(null, {
- mime: jsonMimeConverter(file.type),
- filename: file.name,
- size: file.size,
- urlPromise: uploadCompletionPromise
- });
- // Pass data and the uploader to the TinodeWeb.
- this.sendMessage(msg, uploadCompletionPromise, uploader, 'file');
- } else {
- // Small enough to send inband.
- fileToBase64(file)
- .then(b64 => this.sendMessage(Drafty.attachFile(null, {
- mime: jsonMimeConverter(b64.mime),
- data: b64.bits,
- filename: b64.name,
- size: file.size
- }), undefined, undefined, 'file'))
- .catch(err => this.props.onError(err.message, 'err'));
- }
- }
-
- // Convert attached vCard to TheCard and send.
- sendTheCardAttachment(file, maxInbandAttachmentSize) {
- if (file.size > maxInbandAttachmentSize) {
- // Cannot convert to TheCard, send as a generic file attachment instead.
- return false;
- }
-
- if (file.type == TheCard.contentType) {
- // Already TheCard, no need to convert.
- this.sendMessage(Drafty.appendTheCard(null, file.object));
- return true;
- }
-
- importVCard(file)
- .then(card => {
- this.sendMessage(Drafty.appendTheCard(null, card));
- })
- .catch(err => {
- this.props.onError(this.props.intl.formatMessage(messages.cannot_parse_vcard, {error: err.message}), 'err');
- });
- return true;
- }
-
- // handleAttachFile method is called when [Attach file] button is clicked: launch attachment preview.
- handleAttachFile(file) {
- const maxExternAttachmentSize = this.props.tinode.getServerParam('maxFileUploadSize', MAX_EXTERN_ATTACHMENT_SIZE);
-
- if (file.size > maxExternAttachmentSize) {
- // Too large.
- this.props.onError(this.props.intl.formatMessage(messages.file_attachment_too_large,
- {size: bytesToHumanSize(file.size), limit: bytesToHumanSize(maxExternAttachmentSize)}), 'err');
- } else {
- this.setState({
- docPreview: {
- file: file,
- name: file.name,
- size: file.size,
- type: file.type
- }
- });
- }
- }
-
- handleCallHangup(topic, seq) {
- this.props.onVideoCallClosed();
- this.setState({
- rtcPanel: null
- });
- this.props.onCallHangup(topic, seq);
- }
-
- handleCallPanelToggle() {
- this.setState({minimizedCallPanel: !this.state.minimizedCallPanel});
- }
-
- // sendImageAttachment sends the image bits as Drafty message.
- sendImageAttachment(caption, blob) {
- const mime = this.state.imagePreview.mime;
- const width = this.state.imagePreview.width;
- const height = this.state.imagePreview.height;
- const fname = this.state.imagePreview.filename;
-
- // Server-provided limit reduced for base64 encoding and overhead.
- const maxInbandAttachmentSize = (this.props.tinode.getServerParam('maxMessageSize',
- MAX_INBAND_ATTACHMENT_SIZE) * 0.75 - 1024) | 0;
-
- if (blob.size > maxInbandAttachmentSize) {
- // Too large to send inband - uploading out of band and sending as a link.
- const uploader = createChatUploader(this.props.tinode);
- if (!uploader) {
- this.props.onError(this.props.intl.formatMessage(messages.cannot_initiate_upload));
- return;
- }
- const uploadCompletionPromise = uploader.upload(blob);
-
- // Make small preview to show while uploading.
- imageScaled(blob, IMAGE_PREVIEW_DIM, IMAGE_PREVIEW_DIM, -1, false)
- // Convert tiny image into base64 for serialization and previewing.
- .then(scaled => blobToBase64(scaled.blob))
- .then(b64 => {
- let msg = Drafty.insertImage(null, 0, {
- mime: mime,
- _tempPreview: b64.bits, // This preview will not be serialized.
- bits: b64.bits, // Image thumbnail.
- width: width,
- height: height,
- filename: fname,
- size: blob.size,
- urlPromise: uploadCompletionPromise
- });
- if (caption) {
- msg = Drafty.appendLineBreak(msg);
- msg = Drafty.append(msg, Drafty.parse(caption));
- }
- // Pass data and the uploader to the TinodeWeb.
- this.sendMessage(msg, uploadCompletionPromise, uploader, 'image');
- })
- .catch(err => this.props.onError(err, 'err'));
- return;
- }
-
- // Send the image inband if it's not too big. The image has been scaled already
- // in image preview.
- blobToBase64(blob)
- .then(b64 => {
- let msg = Drafty.insertImage(null, 0, {
- mime: b64.mime,
- bits: b64.bits,
- width: width,
- height: height,
- filename: fname,
- size: blob.size
- });
- if (caption) {
- msg = Drafty.appendLineBreak(msg);
- msg = Drafty.append(msg, Drafty.parse(caption));
- }
- this.sendMessage(msg, undefined, undefined, 'image');
- });
- }
-
- // sendVideoAttachment sends the video bits as Drafty message.
- sendVideoAttachment(caption, videoBlob, previewBlob, params) {
- const width = params.width;
- const height = params.height;
-
- // Server-provided limit reduced for base64 encoding and overhead.
- const maxInbandAttachmentSize = (this.props.tinode.getServerParam('maxMessageSize',
- MAX_INBAND_ATTACHMENT_SIZE) * 0.75 - 1024) | 0;
-
- const uploads = [];
- let uploader;
- if ((videoBlob.size + previewBlob.size) > maxInbandAttachmentSize) {
- // One or both are too large to send inband. Uploading out of band and sending as a link.
- uploader = createChatUploader(this.props.tinode);
- if (!uploader) {
- this.props.onError(this.props.intl.formatMessage(messages.cannot_initiate_upload));
- return;
- }
-
- uploads[0] = videoBlob.size > maxInbandAttachmentSize * 0.675 ? uploader.upload(videoBlob) : null;
- uploads[1] = previewBlob.size > maxInbandAttachmentSize * 0.275 ? uploader.upload(previewBlob) : null;
- }
-
- if (uploads.length == 0) {
- // Both video and preview are small enough to send inband.
- Promise.all([blobToBase64(videoBlob), blobToBase64(previewBlob)])
- .then(b64s => {
- const [v64, i64] = b64s;
- let msg = Drafty.insertVideo(null, 0, {
- mime: v64.mime,
- bits: v64.bits,
- preview: i64.bits,
- premime: i64.mime,
- width: width,
- height: height,
- duration: params.duration,
- filename: params.name,
- size: videoBlob.size
- });
- if (caption) {
- msg = Drafty.appendLineBreak(msg);
- msg = Drafty.append(msg, Drafty.parse(caption));
- }
- this.sendMessage(msg, undefined, undefined, 'video');
- });
- return;
- }
-
- const uploadCompletionPromise = Promise.all(uploads);
-
- const b64conv = [];
- // Small video converted to base64.
- b64conv[0] = uploads[0] ? null : blobToBase64(videoBlob);
- // Full-size preview fits inline.
- b64conv[1] = uploads[1] ? null : imageScaled(previewBlob, MAX_IMAGE_DIM, MAX_IMAGE_DIM, -1, false)
- .then(scaled => blobToBase64(scaled.blob));
- // Small preview to show while uploading.
- b64conv[2] = imageScaled(previewBlob, VIDEO_PREVIEW_DIM, VIDEO_PREVIEW_DIM, -1, false)
- .then(scaled => blobToBase64(scaled.blob));
- // Convert tiny image into base64 for serialization and previewing.
- Promise.all(b64conv)
- .then(b64s => {
- const [video, img, preview] = b64s;
- let msg = Drafty.insertVideo(null, 0, {
- mime: params.mime,
- bits: video ? video.bits : null,
- _tempPreview: preview.bits,
- preview: img ? img.bits : preview.bits,
- premime: img ? img.mime : preview.mime,
- width: width,
- height: height,
- duration: params.duration,
- filename: params.name,
- size: videoBlob.size,
- urlPromise: uploadCompletionPromise
- });
-
- if (caption) {
- msg = Drafty.appendLineBreak(msg);
- msg = Drafty.append(msg, Drafty.parse(caption));
- }
- // Pass data and the uploader to the TinodeWeb.
- this.sendMessage(msg, uploadCompletionPromise, uploader, 'video');
- })
- .catch(err => this.props.onError(err.message, 'err'));
- }
-
- // handleAttachImageOrVideo method is called when [Attach image or video] button is clicked: launch image or video preview.
- handleAttachImageOrVideo(file) {
- const maxExternAttachmentSize = this.props.tinode.getServerParam('maxFileUploadSize', MAX_EXTERN_ATTACHMENT_SIZE);
-
- if (file.type.startsWith('video/')) {
- this.setState({videoPreview: {
- url: URL.createObjectURL(file),
- blob: file,
- filename: file.name,
- size: file.size,
- mime: file.type
- }});
- return;
- }
-
- // Get image dimensions and size, optionally scale it down.
- imageScaled(file, MAX_IMAGE_DIM, MAX_IMAGE_DIM, maxExternAttachmentSize, false)
- .then(scaled => {
- this.setState({imagePreview: {
- url: URL.createObjectURL(scaled.blob),
- blob: scaled.blob,
- filename: scaled.name,
- width: scaled.width,
- height: scaled.height,
- size: scaled.blob.size,
- mime: scaled.mime
- }});
- }).catch(err => {
- this.props.onError(err.message, 'err');
- });
- }
-
- // handleFileDrop is called when the user drags & drops a file upon the message view.
- handleFileDrop(files) {
- if (!files || files.length == 0) {
- return;
- }
- const file = files[0];
- if (file.type && file.type.startsWith('image/')) {
- this.handleAttachImageOrVideo(file);
- } else {
- this.handleAttachFile(file);
- }
- }
-
- // sendAudioAttachment sends audio bits inband as Drafty message (no preview).
- sendAudioAttachment(url, preview, duration) {
- fetch(url)
- .then(result => result.blob())
- .then(blob => {
- // Server-provided limit reduced for base64 encoding and overhead.
- const maxInbandAttachmentSize = this.props.tinode.getServerParam('maxMessageSize', MAX_INBAND_ATTACHMENT_SIZE) * 0.75 - 1024;
- if (blob.size > maxInbandAttachmentSize) {
- // Too large to send inband - uploading out of band and sending as a link.
- const uploader = createChatUploader(this.props.tinode);
- if (!uploader) {
- this.props.onError(this.props.intl.formatMessage(messages.cannot_initiate_upload));
- return;
- }
- const uploadCompletionPromise = uploader.upload(blob);
- const msg = Drafty.appendAudio(null, {
- mime: blob.type,
- size: blob.size,
- duration: duration,
- preview: preview,
- urlPromise: uploadCompletionPromise
- });
- // Pass data and the uploader to the TinodeWeb.
- this.sendMessage(msg, uploadCompletionPromise, uploader, 'voice');
- } else {
- // Small enough to send inband.
- blobToBase64(blob)
- .then(b64 => {
- this.sendMessage(Drafty.appendAudio(null, {
- mime: b64.mime,
- bits: b64.bits,
- size: blob.size,
- duration: duration,
- preview: preview,
- }), undefined, undefined, 'voice')
- })
- }
- })
- .catch(err => {this.props.onError(err.message, 'err')});;
- }
-
- handleCancelUpload(seq, uploader) {
- const topic = this.props.tinode.getTopic(this.state.topic);
- const found = topic.findMessage(seq);
- if (found) {
- found._cancelled = true;
- }
- uploader.cancel();
- }
-
- // seq: seq ID of the source message
- // context: message content.
- // senderId: UID of the sender of the source message.
- // senderName: full name of the sender of the original message.
- handlePickReply(seq, content, senderId, senderName) {
- if (!seq || !content) {
- this.setState({reply: null});
- return;
- }
-
- content = typeof content == 'string' ? Drafty.init(content) : content;
- if (Drafty.isValid(content)) {
- content = Drafty.replyContent(content, QUOTED_REPLY_LENGTH);
- } else {
- // /!\ invalid content.
- content = Drafty.append(Drafty.init('\u26A0 '),
- Drafty.wrapInto(this.props.intl.formatMessage(messages.invalid_content), 'EM'));
- }
-
- this.setState({
- reply: {
- content: Drafty.quote(senderName, senderId, content),
- seq: seq
- }
- });
- this.props.onCancelForwardMessage();
- }
-
- // seq: seq ID of the message to edit.
- // context: message content.
- handleEditMessage(seq, content) {
- if (!seq || !content) {
- this.setState({reply: null});
- return;
- }
-
- content = typeof content == 'string' ? Drafty.init(content) : content;
- const editable = Drafty.toMarkdown(content);
- if (Drafty.isValid(content)) {
- content = Drafty.replyContent(content, EDIT_PREVIEW_LENGTH);
- } else {
- // /!\ invalid content.
- content = Drafty.append(Drafty.init('\u26A0 '),
- Drafty.wrapInto(this.props.intl.formatMessage(messages.invalid_content), 'EM'));
- }
-
- this.setState({
- reply: {
- content: Drafty.quote(this.props.intl.formatMessage(messages.editing_message), null, content),
- seq: seq,
- editing: true
- },
- contentToEdit: editable
- });
- this.props.onCancelForwardMessage();
- }
-
-
- handleCancelReply() {
- this.setState({reply: null, contentToEdit: null});
- this.props.onCancelForwardMessage();
- }
-
- handleQuoteClick(replyToSeq) {
- const ref = this.getOrCreateMessageRef(replyToSeq);
- if (ref && ref.current) {
- ref.current.scrollIntoView({block: "center", behavior: "smooth"});
- ref.current.classList.add('flash');
- setTimeout(_ => {ref.current.classList.remove('flash')} , 1000);
- } else {
- // Most commonly hit when the target message was deleted/recalled after being found by
- // Ch.7 chat-history search but before this click -- never gets a ref
- // (see jumpToMessage below), so without this the click did nothing visible at all.
- console.error("Unresolved message ref", replyToSeq);
- if (this.props.onError) {
- this.props.onError('该消息已被删除或撤回', 'err');
- }
- }
- }
-
- // Ch.7 chat-history search result -> jump to the message in the real conversation. handleQuoteClick
- // only scrolls to a message already present in this topic's locally-cached message list (used
- // elsewhere for quoted-reply/pinned-message jumps, where that's always true); a search result can
- // point at a message far outside whatever window happens to be loaded, so fetch a window around it
- // first via the native SDK (not the REST search API -- handleQuoteClick reads refs off the SDK's own
- // local topic cache, so the data has to land there, which only getMeta does).
- jumpToMessage(seq) {
- const topic = this.props.tinode && this.props.topic ? this.props.tinode.getTopic(this.props.topic) : null;
- if (!topic || !seq || isNaN(seq)) {
- return;
- }
- Navigation.navigateTo(Navigation.removeUrlParam(Navigation.currentUrl(), 'jumpseq'));
- const jump = () => window.setTimeout(() => this.handleQuoteClick(seq), 150);
- if (topic.findMessage(seq)) {
- jump();
- } else {
- topic.getMeta(topic.startMetaQuery().withData(Math.max(1, seq - 20), seq + 21, 41).build())
- .then(jump).catch(err => this.props.onError(err.message, 'err'));
- }
- }
-
- handleUnpinMessage(seq) {
- const topic = this.props.tinode.getTopic(this.state.topic);
- topic.pinMessage(seq, false);
- }
-
- isDragEnabled() {
- // Same editing-state guard as the attach-panel props in render() (see the `editingMessage`
- // local there) -- drag&drop is a second, independent path into handleFileDrop below, which
- // calls handleAttachImageOrVideo/handleAttachFile directly rather than through those props, so
- // gating only the props left this route as a full bypass of that guard: dropping a file while
- // editing a message (without touching the "+" button at all) still replaced the edited
- // message's content via sendMessage's head.replace, with no confirmation.
- const editingMessage = !!(this.state.reply && this.state.reply.editing);
- return this.state.isWriter && !this.state.unconfirmed && !this.props.forwardMessage &&
- !this.state.peerMessagingDisabled && !editingMessage;
- }
-
- handleDragStart(e) {
- e.preventDefault();
- e.stopPropagation();
- e.dataTransfer.clearData();
- }
-
- handleDragIn(e) {
- e.preventDefault();
- e.stopPropagation();
- this.dragCounter++;
- if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {
- this.setState({dragging: true});
- }
- }
-
- handleDragOut(e) {
- e.preventDefault();
- e.stopPropagation();
- this.dragCounter--;
- if (this.dragCounter <= 0) {
- this.setState({dragging: false});
- }
- }
-
- handleDrag(e) {
- e.preventDefault();
- e.stopPropagation();
- }
-
- handleDrop(e) {
- e.preventDefault();
- e.stopPropagation();
- this.setState({dragging: false});
- if (this.isDragEnabled() && e.dataTransfer.files && e.dataTransfer.files.length > 0) {
- this.handleFileDrop(e.dataTransfer.files);
- this.dragCounter = 0;
- }
- }
-
- render() {
- const {formatMessage} = this.props.intl;
-
- // P2P call is an overlay, not a separate component because it cannot be
- // re-mounted between full screen/minimized.
- const overlay = this.state.rtcPanel &&
- }>
-
- ;
-
- const riskDialog = this.state.riskDialog &&
- ;
-
- let component;
- if (!this.state.topic) {
- component = ;
-
- } else {
- let component2;
- if (this.state.imagePreview) {
- // Preview image before sending.
- component2 = (
- }>
-
-
- );
- } else if (this.state.videoPreview) {
- // Preview video.
- component2 = }>
-
- ;
-
- } else if (this.state.imagePostview) {
- // Expand received image.
- component2 = }>
-
- ;
-
- } else if (this.state.videoPostview) {
- // Play received video.
- component2 = }>
-
- ;
-
- } else if (this.state.docPreview) {
- // Preview attachment before sending.
- if (TheCard.isFileSupported(this.state.docPreview.type, this.state.docPreview.name) ||
- this.state.docPreview.type == TheCard.contentType) {
- component2 = }>
-
- ;
- } else {
- component2 = }>
-
- ;
- }
- } else {
- const topic = this.props.tinode.getTopic(this.state.topic);
- const isChannel = topic.isChannelType() || topic.chan;
- const groupTopic = topic.isGroupType() && !isChannel;
- const icon_badges = [];
- if (topic.trusted) {
- if (topic.trusted.verified) {
- icon_badges.push({icon: 'verified', color: 'badge-inv'});
- }
- if (topic.trusted.staff) {
- icon_badges.push({icon: 'staff', color: 'badge-inv'});
- }
- if (topic.trusted.danger) {
- icon_badges.push({icon: 'dangerous', color: 'badge-inv'});
- }
- }
-
- const pinnedMessages = [];
- this.state.pins.forEach(seq => pinnedMessages.push(topic.latestMsgVersion(seq) || topic.findMessage(seq)));
-
- const messageNodes = [];
- let previousFrom = null;
- let prevDate = null;
- // Set upfront (not just inside the loop below) so an empty (zero-message) conversation still
- // gets the base .chat-box class instead of leaving