feat: sync langpack and auth updates

Sync telesrv commits 49f9bab and 04d9563 into the public mirror. Exclude private docs and runtime key material per sync rules.
This commit is contained in:
A 2026-07-18 00:07:41 +08:00
parent 6af61f26ba
commit 1292540350
244 changed files with 1552172 additions and 27668 deletions

View file

@ -5,14 +5,20 @@
// 用法: // 用法:
// //
// langpackfetch languages [pack] 列出某 pack(默认 android)的可用语言 // langpackfetch languages [pack] 列出某 pack(默认 android)的可用语言
// langpackfetch all <out_dir> [pack...] 拉取所有官方语言及 manifest
// langpackfetch <out_dir> <langCode> [pack...] 拉取语言包(默认 packs = android ios macos) // langpackfetch <out_dir> <langCode> [pack...] 拉取语言包(默认 packs = android ios macos)
package main package main
import ( import (
"context" "context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"sort"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -26,13 +32,46 @@ const (
tdesktopAPIHash = "344583e45741c457fe1862106095a5eb" tdesktopAPIHash = "344583e45741c457fe1862106095a5eb"
) )
var (
officialPacks = []string{"android", "android_x", "ios", "macos", "tdesktop", "weba", "webk"}
packNameRE = regexp.MustCompile(`^[a-z0-9][a-z0-9_-]{0,31}$`)
langCodeRE = regexp.MustCompile(`^[a-z0-9]{1,16}(?:[-_][a-z0-9]{1,16})*$`)
)
type manifest struct {
Schema int `json:"schema"`
Packs []packManifest `json:"packs"`
}
type packManifest struct {
Name string `json:"name"`
Languages []languageManifest `json:"languages"`
}
type languageManifest struct {
LangCode string `json:"lang_code"`
Name string `json:"name"`
NativeName string `json:"native_name"`
BaseLangCode string `json:"base_lang_code,omitempty"`
PluralCode string `json:"plural_code"`
Official bool `json:"official"`
RTL bool `json:"rtl,omitempty"`
Beta bool `json:"beta,omitempty"`
StringsCount int `json:"strings_count"`
TranslatedCount int `json:"translated_count"`
TranslationsURL string `json:"translations_url,omitempty"`
Version int `json:"version"`
File string `json:"file"`
SHA256 string `json:"sha256"`
}
func main() { func main() {
if len(os.Args) < 2 { if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "usage:\n langpackfetch languages [pack]\n langpackfetch <out_dir> <langCode> [pack...]") fmt.Fprintln(os.Stderr, "usage:\n langpackfetch languages [pack]\n langpackfetch all <out_dir> [pack...]\n langpackfetch <out_dir> <langCode> [pack...]")
os.Exit(2) os.Exit(2)
} }
ctx, cancel := context.WithTimeout(context.Background(), 6*time.Minute) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Minute)
defer cancel() defer cancel()
client := telegram.NewClient(tdesktopAPIID, tdesktopAPIHash, telegram.Options{}) client := telegram.NewClient(tdesktopAPIID, tdesktopAPIHash, telegram.Options{})
@ -55,6 +94,16 @@ func main() {
} }
return nil return nil
} }
if os.Args[1] == "all" {
if len(os.Args) < 3 {
return fmt.Errorf("all needs <out_dir>")
}
packs := os.Args[3:]
if len(packs) == 0 {
packs = officialPacks
}
return fetchAll(ctx, api, os.Args[2], packs)
}
outRoot := os.Args[1] outRoot := os.Args[1]
langCode := "en" langCode := "en"
@ -74,7 +123,7 @@ func main() {
fmt.Fprintf(os.Stderr, "skip %s/%s: %v\n", pack, langCode, err) fmt.Fprintf(os.Stderr, "skip %s/%s: %v\n", pack, langCode, err)
continue continue
} }
if err := writePack(outRoot, pack, diff); err != nil { if _, err := writePack(outRoot, pack, diff); err != nil {
return err return err
} }
} }
@ -85,10 +134,88 @@ func main() {
} }
} }
func writePack(root, pack string, diff *tg.LangPackDifference) error { func fetchAll(ctx context.Context, api *tg.Client, root string, packs []string) error {
seen := make(map[string]struct{}, len(packs))
result := manifest{Schema: 1}
for _, pack := range packs {
pack = strings.ToLower(strings.TrimSpace(pack))
if !packNameRE.MatchString(pack) {
return fmt.Errorf("invalid lang pack %q", pack)
}
if _, ok := seen[pack]; ok {
continue
}
seen[pack] = struct{}{}
languages, err := api.LangpackGetLanguages(ctx, pack)
if err != nil {
return fmt.Errorf("getLanguages %q: %w", pack, err)
}
sort.Slice(languages, func(i, j int) bool { return languages[i].LangCode < languages[j].LangCode })
pm := packManifest{Name: pack}
for _, language := range languages {
if !language.Official {
continue
}
code := strings.ToLower(strings.TrimSpace(language.LangCode))
if !langCodeRE.MatchString(code) {
return fmt.Errorf("invalid language code %q returned for %q", language.LangCode, pack)
}
diff, err := api.LangpackGetLangPack(ctx, &tg.LangpackGetLangPackRequest{
LangPack: pack,
LangCode: code,
})
if err != nil {
return fmt.Errorf("getLangPack %s/%s: %w", pack, code, err)
}
written, err := writePack(root, pack, diff)
if err != nil {
return err
}
rel, err := filepath.Rel(root, written.Path)
if err != nil {
return err
}
pm.Languages = append(pm.Languages, languageManifest{
LangCode: code,
Name: language.Name,
NativeName: language.NativeName,
BaseLangCode: language.BaseLangCode,
PluralCode: language.PluralCode,
Official: language.Official,
RTL: language.Rtl,
Beta: language.Beta,
StringsCount: language.StringsCount,
TranslatedCount: language.TranslatedCount,
TranslationsURL: language.TranslationsURL,
Version: diff.Version,
File: filepath.ToSlash(rel),
SHA256: written.SHA256,
})
}
fmt.Printf("pack %s complete: %d official languages\n", pack, len(pm.Languages))
result.Packs = append(result.Packs, pm)
}
encoded, err := json.MarshalIndent(result, "", " ")
if err != nil {
return err
}
return writeFileAtomic(filepath.Join(root, "official-language-packs.json"), append(encoded, '\n'))
}
type writtenPack struct {
Path string
SHA256 string
}
func writePack(root, pack string, diff *tg.LangPackDifference) (writtenPack, error) {
pack = strings.ToLower(strings.TrimSpace(pack))
if !packNameRE.MatchString(pack) {
return writtenPack{}, fmt.Errorf("invalid lang pack %q", pack)
}
dir := filepath.Join(root, pack) dir := filepath.Join(root, pack)
if err := os.MkdirAll(dir, 0o755); err != nil { if err := os.MkdirAll(dir, 0o755); err != nil {
return err return writtenPack{}, err
} }
var b strings.Builder var b strings.Builder
count := 0 count := 0
@ -116,13 +243,51 @@ func writePack(root, pack string, diff *tg.LangPackDifference) error {
} }
langCode := diff.LangCode langCode := diff.LangCode
if langCode == "" { if langCode == "" {
langCode = "unknown" return writtenPack{}, fmt.Errorf("empty language code returned for %q", pack)
}
langCode = strings.ToLower(langCode)
if !langCodeRE.MatchString(langCode) {
return writtenPack{}, fmt.Errorf("invalid language code %q returned for %q", diff.LangCode, pack)
} }
name := fmt.Sprintf("%s_%s_v%d.strings", pack, langCode, diff.Version) name := fmt.Sprintf("%s_%s_v%d.strings", pack, langCode, diff.Version)
path := filepath.Join(dir, name) path := filepath.Join(dir, name)
if err := os.WriteFile(path, []byte(b.String()), 0o644); err != nil { data := []byte(b.String())
if err := writeFileAtomic(path, data); err != nil {
return writtenPack{}, err
}
sum := sha256.Sum256(data)
fmt.Printf("wrote %s (%d strings, version %d)\n", path, count, diff.Version)
return writtenPack{Path: path, SHA256: hex.EncodeToString(sum[:])}, nil
}
func writeFileAtomic(path string, data []byte) error {
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return err return err
} }
fmt.Printf("wrote %s (%d strings, version %d)\n", path, count, diff.Version) tmp, err := os.CreateTemp(filepath.Dir(path), ".langpack-*.tmp")
if err != nil {
return err
}
tmpName := tmp.Name()
defer os.Remove(tmpName)
if err := tmp.Chmod(0o644); err != nil {
_ = tmp.Close()
return err
}
if _, err := tmp.Write(data); err != nil {
_ = tmp.Close()
return err
}
if err := tmp.Close(); err != nil {
return err
}
if err := os.Rename(tmpName, path); err != nil {
if removeErr := os.Remove(path); removeErr != nil && !os.IsNotExist(removeErr) {
return fmt.Errorf("replace %q: %w (remove existing: %v)", path, err, removeErr)
}
if err := os.Rename(tmpName, path); err != nil {
return err
}
}
return nil return nil
} }

View file

@ -0,0 +1,78 @@
package main
import (
"crypto/sha256"
"encoding/hex"
"os"
"path/filepath"
"strings"
"testing"
"github.com/iamxvbaba/td/tg"
)
func TestWritePack(t *testing.T) {
root := t.TempDir()
diff := &tg.LangPackDifference{
LangCode: "pt-br",
Version: 42,
Strings: []tg.LangPackStringClass{
&tg.LangPackString{Key: "plain", Value: "value"},
&tg.LangPackStringPluralized{Key: "items", OneValue: "one", OtherValue: "many"},
&tg.LangPackStringDeleted{Key: "removed"},
},
}
written, err := writePack(root, "tdesktop", diff)
if err != nil {
t.Fatal(err)
}
wantPath := filepath.Join(root, "tdesktop", "tdesktop_pt-br_v42.strings")
if written.Path != wantPath {
t.Fatalf("path = %q, want %q", written.Path, wantPath)
}
data, err := os.ReadFile(wantPath)
if err != nil {
t.Fatal(err)
}
text := string(data)
for _, want := range []string{`"plain" = "value";`, `"items#one" = "one";`, `"items#other" = "many";`} {
if !strings.Contains(text, want) {
t.Errorf("output does not contain %q: %s", want, text)
}
}
if strings.Contains(text, "removed") {
t.Errorf("deleted key was emitted: %s", text)
}
sum := sha256.Sum256(data)
if written.SHA256 != hex.EncodeToString(sum[:]) {
t.Fatalf("sha256 = %q, want %x", written.SHA256, sum)
}
}
func TestWritePackRejectsPathComponents(t *testing.T) {
_, err := writePack(t.TempDir(), "../android", &tg.LangPackDifference{LangCode: "en", Version: 1})
if err == nil {
t.Fatal("expected invalid pack error")
}
_, err = writePack(t.TempDir(), "android", &tg.LangPackDifference{LangCode: "../en", Version: 1})
if err == nil {
t.Fatal("expected invalid language code error")
}
}
func TestWriteFileAtomicReplacesExisting(t *testing.T) {
path := filepath.Join(t.TempDir(), "manifest.json")
if err := writeFileAtomic(path, []byte("first")); err != nil {
t.Fatal(err)
}
if err := writeFileAtomic(path, []byte("second")); err != nil {
t.Fatal(err)
}
data, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
if string(data) != "second" {
t.Fatalf("content = %q, want second", data)
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -2040,7 +2040,7 @@
"BusinessBotNoAddedText" = "You havent added a bot to manage your account. Leave anyway?"; "BusinessBotNoAddedText" = "You havent added a bot to manage your account. Leave anyway?";
"BusinessBotNoAddedTitle" = "No Bot Added"; "BusinessBotNoAddedTitle" = "No Bot Added";
"BusinessBotNotFound" = "Chatbot not found"; "BusinessBotNotFound" = "Chatbot not found";
"BusinessBotNotSupportedMessage" = "This bot doesnt support **Telegram Business** yet."; "BusinessBotNotSupportedMessage" = "This bot doesnt support **Secretary Mode** yet.";
"BusinessBotNotSupportedTitle" = "Oops"; "BusinessBotNotSupportedTitle" = "Oops";
"BusinessBotPermissions" = "Bot permissions"; "BusinessBotPermissions" = "Bot permissions";
"BusinessBotPermissionsGiftsSection" = "Manage Gifts and Stars"; "BusinessBotPermissionsGiftsSection" = "Manage Gifts and Stars";
@ -11646,10 +11646,3 @@
"messages#one" = "%1$d message"; "messages#one" = "%1$d message";
"messages#other" = "%1$d messages"; "messages#other" = "%1$d messages";
"telegram_passport" = "Telegram Passport"; "telegram_passport" = "Telegram Passport";
"FilterByUser" = "Filter by user";
"Gift" = "Gift";
"GiftDiscount" = "-%1$d%";
"Giveaway" = "Giveaway";
"Password" = "Password";
"Previous" = "Previous";
"Volume" = "Volume";

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -743,7 +743,7 @@
"ApproveNewMembersDescriptionFrozen" = "您无法对需要按月付费的链接启用管理员批准。"; "ApproveNewMembersDescriptionFrozen" = "您无法对需要按月付费的链接启用管理员批准。";
"ApproveNewMembersDisable" = "禁用"; "ApproveNewMembersDisable" = "禁用";
"ApproveNewMembersDisableForLinks#other" = "同时禁用此群组中 %1$d 个现有邀请链接的 **批准新成员** 功能?"; "ApproveNewMembersDisableForLinks#other" = "同时禁用此群组中 %1$d 个现有邀请链接的 **批准新成员** 功能?";
"ApproveNewMembersDisableForLinksChannel#other" = "同时禁用此频道中 %1$d 个现有邀请链接的 **批准新成员** 功能?"; "ApproveNewMembersDisableForLinksChannel#other" = "同时禁用此频道中 %1$d 个现有邀请链接的**批准新订阅**功能?";
"ApproveNewMembersDisabledMessageChannel" = "新订阅者加入此频道前,将不再需要获得 %1$s 的批准。"; "ApproveNewMembersDisabledMessageChannel" = "新订阅者加入此频道前,将不再需要获得 %1$s 的批准。";
"ApproveNewMembersDisabledMessageGroup" = "新成员在群内发消息无需再经 **%1$s** 批准。"; "ApproveNewMembersDisabledMessageGroup" = "新成员在群内发消息无需再经 **%1$s** 批准。";
"ApproveNewMembersEnable" = "启用"; "ApproveNewMembersEnable" = "启用";
@ -873,7 +873,18 @@
"AreYouSureUnsaveSingleMessage" = "您确定要从“我的收藏”中删除此消息吗?"; "AreYouSureUnsaveSingleMessage" = "您确定要从“我的收藏”中删除此消息吗?";
"AreYouSureWebSessions" = "您确定要断开所有使用 Telegram 登录的网站吗?"; "AreYouSureWebSessions" = "您确定要断开所有使用 Telegram 登录的网站吗?";
"ArticleByAuthor" = "作者:%1$s"; "ArticleByAuthor" = "作者:%1$s";
"ArticleCode" = "代码";
"ArticleCommandAudio" = "音频";
"ArticleCommandDivider" = "分隔线";
"ArticleCommandImage" = "图片";
"ArticleCommandMap" = "地图";
"ArticleCommandToggle" = "切换";
"ArticleCommandVideo" = "Video";
"ArticleDateByAuthor" = "%2$s 作于 %1$s"; "ArticleDateByAuthor" = "%2$s 作于 %1$s";
"ArticleHintDetailsTitle" = "标题";
"ArticleHintTitle" = "标题";
"ArticleNone" = "无";
"ArticleText" = "文字";
"AskAQuestion" = "向我们提问"; "AskAQuestion" = "向我们提问";
"AskAQuestionInfo" = "请注意Telegram 的支持服务来自志愿者。我们会尽快回复,但仍可能需要等待一段时间。\n\n请前往<![CDATA[<a href=\"https://telegram.org/faq#general-questions\">Telegram 常见问题</a>]]>看看:那里有绝大多数问题的答案和重要的<![CDATA[<a href=\"https://telegram.org/faq#troubleshooting\">故障排除</a>]]>提示。"; "AskAQuestionInfo" = "请注意Telegram 的支持服务来自志愿者。我们会尽快回复,但仍可能需要等待一段时间。\n\n请前往<![CDATA[<a href=\"https://telegram.org/faq#general-questions\">Telegram 常见问题</a>]]>看看:那里有绝大多数问题的答案和重要的<![CDATA[<a href=\"https://telegram.org/faq#troubleshooting\">故障排除</a>]]>提示。";
"AskButton" = "向志愿者提问"; "AskButton" = "向志愿者提问";
@ -1869,7 +1880,7 @@
"BusinessAwayScheduleOutsideHours" = "不在营业时间"; "BusinessAwayScheduleOutsideHours" = "不在营业时间";
"BusinessAwaySend" = "发送离开消息"; "BusinessAwaySend" = "发送离开消息";
"BusinessAwayUnsavedChanges" = "您已更改企业离开留言设置,是否应用更改?"; "BusinessAwayUnsavedChanges" = "您已更改企业离开留言设置,是否应用更改?";
"BusinessBotChats" = "机器人可访问的聊天"; "BusinessBotChats" = "聊天机器人可访问";
"BusinessBotChats2" = "机器人可以访问的聊天"; "BusinessBotChats2" = "机器人可以访问的聊天";
"BusinessBotChatsInfo" = "选择机器人将有权访问的聊天或整个聊天类型。"; "BusinessBotChatsInfo" = "选择机器人将有权访问的聊天或整个聊天类型。";
"BusinessBotChatsInfo2" = "选择机器人不会访问的聊天。"; "BusinessBotChatsInfo2" = "选择机器人不会访问的聊天。";
@ -2707,6 +2718,21 @@
"CommentsNoNumber#other" = "评论"; "CommentsNoNumber#other" = "评论";
"CommentsTitle" = "评论"; "CommentsTitle" = "评论";
"CommonGroups#other" = "%1$d 个共同群组"; "CommonGroups#other" = "%1$d 个共同群组";
"CommunityAddChatTitle" = "添加聊天";
"CommunityAdministrators" = "管理员";
"CommunityMenuRemoveBotFromCommunityConfirm" = "您确定要将从社群中移除此机器人吗?";
"CommunityMenuRemoveChannelFromCommunityConfirm" = "您确定要将从社群中移除此群组吗?";
"CommunityMenuRemoveGroupFromCommunityConfirm" = "您确定要将从社群中移除此群组吗?";
"CommunityMenuSettings" = "设置";
"CommunityPendingRequestDecline" = "拒绝";
"CommunityServiceMessageChannelRemoved" = "**%1$s** 已从社群中移除此频道";
"CommunityServiceMessageChannelRemovedUnknown" = "此频道已从社群中移除";
"CommunityServiceMessageChannelYouRemoved" = "**You** 已从社群中移除此频道";
"CommunityServiceMessageGroupRemoved" = "**%1$s** 已从社群中移除此群组";
"CommunityWhoCanAddChatsAllMembers" = " 所有成员";
"CommunityWhoCanAddChatsOnlyAdmins" = "仅限管理员";
"CommunityYouAreAdminContinue" = "继续";
"CommunityYouAreAdminNoThanks" = "不用了,谢谢";
"CompatibilityChat" = "%1$s 正在使用旧版 Telegram因此阅后即焚图片会以兼容模式显示。\n\n当 %2$s 更新 Telegram 后,若阅后即焚计时为 1 分钟或以内,对方可“长按以观看”;对方截屏时,您也会收到通知。"; "CompatibilityChat" = "%1$s 正在使用旧版 Telegram因此阅后即焚图片会以兼容模式显示。\n\n当 %2$s 更新 Telegram 后,若阅后即焚计时为 1 分钟或以内,对方可“长按以观看”;对方截屏时,您也会收到通知。";
"ConferenceCallIncoming" = "呼入群组通话"; "ConferenceCallIncoming" = "呼入群组通话";
"ConferenceCallMissed" = "未接群组通话"; "ConferenceCallMissed" = "未接群组通话";
@ -2946,7 +2972,7 @@
"DeleteAccountIfAwayFor3" = "保留"; "DeleteAccountIfAwayFor3" = "保留";
"DeleteAccountTitle" = "自动删除帐号"; "DeleteAccountTitle" = "自动删除帐号";
"DeleteAdditionalActions" = "更多选项"; "DeleteAdditionalActions" = "更多选项";
"DeleteAlertReaction" = "您确定要删除此用户的回应吗?"; "DeleteAlertReaction" = "您确定要删除来自此用户的回应吗?";
"DeleteAlertReactionAll" = "删除所有该用户的回应"; "DeleteAlertReactionAll" = "删除所有该用户的回应";
"DeleteAll" = "删除所有"; "DeleteAll" = "删除所有";
"DeleteAllCalls" = "删除所有通话"; "DeleteAllCalls" = "删除所有通话";
@ -4563,7 +4589,7 @@
"GiftCraftSymbolChance#other" = "制作出的礼物将有 **%1$d%%** 的概率带有 **%2$s** 符号。"; "GiftCraftSymbolChance#other" = "制作出的礼物将有 **%1$d%%** 的概率带有 **%2$s** 符号。";
"GiftCraftText1" = "最多可添加 **4** 份礼物来制作全新的"; "GiftCraftText1" = "最多可添加 **4** 份礼物来制作全新的";
"GiftCraftText2" = "**%1$s #%2$s**.\n\n如果制作失败这些礼物 \n将会 **丟失**。"; "GiftCraftText2" = "**%1$s #%2$s**.\n\n如果制作失败这些礼物 \n将会 **丟失**。";
"GiftCraftTextEmpty1" = "最多可添加 **4** 份礼物来制作全新的"; "GiftCraftTextEmpty1" = "至少添加 **4** 份礼物来制作一个新的";
"GiftCraftTextEmpty2" = "**%s** 件礼物。\n\n如果制作失败这些礼物 \n将会 **丟失**。"; "GiftCraftTextEmpty2" = "**%s** 件礼物。\n\n如果制作失败这些礼物 \n将会 **丟失**。";
"GiftCraftTitle" = "制作礼物"; "GiftCraftTitle" = "制作礼物";
"GiftCraftUnavailableText" = "这些礼物目前无法制作。"; "GiftCraftUnavailableText" = "这些礼物目前无法制作。";
@ -6968,6 +6994,9 @@
"PmReadTodayAt" = "%s 已读"; "PmReadTodayAt" = "%s 已读";
"PmReadUnknown" = "未知已读时间"; "PmReadUnknown" = "未知已读时间";
"PmReadYesterdayAt" = "昨天 %s 已读"; "PmReadYesterdayAt" = "昨天 %s 已读";
"PmSentDateTimeAt" = "已发送于 %1$s %2$s";
"PmSentTodayAt" = "已发送于 %s";
"PmSentYesterdayAt" = "已发送于昨天 %s";
"Points#other" = "%1$d"; "Points#other" = "%1$d";
"Poll" = "投票"; "Poll" = "投票";
"PollAddAnOption" = "添加选项"; "PollAddAnOption" = "添加选项";
@ -7173,6 +7202,7 @@
"PremiumPreviewReactions2" = "无限表情回应"; "PremiumPreviewReactions2" = "无限表情回应";
"PremiumPreviewReactions2Description" = "用数千个表情作出回应——可使用多个表情回应单条消息。"; "PremiumPreviewReactions2Description" = "用数千个表情作出回应——可使用多个表情回应单条消息。";
"PremiumPreviewReactionsDescription" = "使用更多动画表情回应消息,仅高级版订阅者可用。"; "PremiumPreviewReactionsDescription" = "使用更多动画表情回应消息,仅高级版订阅者可用。";
"PremiumPreviewRichEditorDescription" = "在消息中添加标题、表格、内嵌媒体和 AI 内容。";
"PremiumPreviewSharingDisable" = "禁用分享"; "PremiumPreviewSharingDisable" = "禁用分享";
"PremiumPreviewSharingDisableDescription" = "禁用私聊中的截屏、收藏和转发消息功能。"; "PremiumPreviewSharingDisableDescription" = "禁用私聊中的截屏、收藏和转发消息功能。";
"PremiumPreviewStickers" = "高级版贴纸"; "PremiumPreviewStickers" = "高级版贴纸";
@ -7907,7 +7937,7 @@
"ResellGiftInfoMinTON" = "最低价格为 %s Gram"; "ResellGiftInfoMinTON" = "最低价格为 %s Gram";
"ResellGiftInfoTON" = "您将收到 **%s** Gram。"; "ResellGiftInfoTON" = "您将收到 **%s** Gram。";
"ResellGiftPriceHintOnlyTON" = "如果买家使用 Gram 付款,则不存在买家要求退款的风险,\n这是与星星付款的不同之处。"; "ResellGiftPriceHintOnlyTON" = "如果买家使用 Gram 付款,则不存在买家要求退款的风险,\n这是与星星付款的不同之处。";
"ResellGiftPriceOnlyTON" = "只接受以 Gram 付款"; "ResellGiftPriceOnlyTON" = "仅接受 Gram";
"ResellGiftPriceTitle" = "以星星作为单位输入价格"; "ResellGiftPriceTitle" = "以星星作为单位输入价格";
"ResellGiftPriceTitleTON" = "输入 Gram 价格"; "ResellGiftPriceTitleTON" = "输入 Gram 价格";
"ResellGiftPriceTooMuch" = "您最高可以报价 %s"; "ResellGiftPriceTooMuch" = "您最高可以报价 %s";
@ -10805,7 +10835,7 @@
"WebBrowserExceptionsLimitMessage" = "\n网站例外数量已达上限。请先移除部分现有例外再添加新的。"; "WebBrowserExceptionsLimitMessage" = "\n网站例外数量已达上限。请先移除部分现有例外再添加新的。";
"WebBrowserExceptionsLimitTitle" = "已达上限"; "WebBrowserExceptionsLimitTitle" = "已达上限";
"WebBrowserShowCloseButton" = "显示关闭按钮"; "WebBrowserShowCloseButton" = "显示关闭按钮";
"WebBrowserShowCloseButtonInfo" = "浏览网页时添加关闭按钮"; "WebBrowserShowCloseButtonInfo" = "在 Telegram 内打开外部浏览器。";
"WebDownloadAlertInfo" = "开始下载 **%s**?"; "WebDownloadAlertInfo" = "开始下载 **%s**?";
"WebDownloadAlertInfoWithSize" = "开始下载 **%1$s** (%2$s)?"; "WebDownloadAlertInfoWithSize" = "开始下载 **%1$s** (%2$s)?";
"WebDownloadAlertTitle" = "下载文件"; "WebDownloadAlertTitle" = "下载文件";

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -3231,7 +3231,7 @@
"ChatbotSetup.BotNotFoundStatus" = "Chatbot not found"; "ChatbotSetup.BotNotFoundStatus" = "Chatbot not found";
"ChatbotSetup.BotSearchPlaceholder" = "Bot Username or Link"; "ChatbotSetup.BotSearchPlaceholder" = "Bot Username or Link";
"ChatbotSetup.BotSectionFooter" = "Choose a bot to manage your chats automatically."; "ChatbotSetup.BotSectionFooter" = "Choose a bot to manage your chats automatically.";
"ChatbotSetup.ErrorBotNotBusinessCapable" = "This bot doesn't support Telegram Business yet."; "ChatbotSetup.ErrorBotNotBusinessCapable" = "This bot doesn't support Secretary Mode yet.";
"ChatbotSetup.Gift.Warning.CombinedText" = "The bot **%@** will be able to **manage your gifts and stars**, including giving them away to other users."; "ChatbotSetup.Gift.Warning.CombinedText" = "The bot **%@** will be able to **manage your gifts and stars**, including giving them away to other users.";
"ChatbotSetup.Gift.Warning.GiftsText" = "The bot **%@** will be able to **manage your gifts**, including giving them away to other users."; "ChatbotSetup.Gift.Warning.GiftsText" = "The bot **%@** will be able to **manage your gifts**, including giving them away to other users.";
"ChatbotSetup.Gift.Warning.Proceed" = "Proceed"; "ChatbotSetup.Gift.Warning.Proceed" = "Proceed";

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Some files were not shown because too many files have changed in this diff Show more