Merge branch 'main' into logging
This commit is contained in:
commit
bd865fd55d
8 changed files with 244 additions and 16 deletions
15
README.md
15
README.md
|
@ -33,6 +33,16 @@ too.
|
||||||
[Install / Self-hosting](https://ntfy.sh/docs/install/) |
|
[Install / Self-hosting](https://ntfy.sh/docs/install/) |
|
||||||
[Building](https://ntfy.sh/docs/develop/)
|
[Building](https://ntfy.sh/docs/develop/)
|
||||||
|
|
||||||
|
## Chat
|
||||||
|
You can directly contact me **[on Discord](https://discord.gg/cT7ECsZj9w)** or [on Matrix](https://matrix.to/#/#ntfy:matrix.org)
|
||||||
|
(bridged from Discord), or via the [GitHub issues](https://github.com/binwiederhier/ntfy/issues), or find more contact information
|
||||||
|
[on my website](https://heckel.io/about).
|
||||||
|
|
||||||
|
## Announcements / beta testers
|
||||||
|
For announcements of new releases and cutting-edge beta versions, please subscribe to the [ntfy.sh/announcements](https://ntfy.sh/announcements)
|
||||||
|
topic. If you'd like to test the iOS app, join [TestFlight](https://testflight.apple.com/join/P1fFnAm9). For Android betas,
|
||||||
|
join Discord/Matrix (I'll eventually make a testing channel in Google Play).
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
I welcome any and all contributions. Just create a PR or an issue. To contribute code, check out
|
I welcome any and all contributions. Just create a PR or an issue. To contribute code, check out
|
||||||
the [build instructions](https://ntfy.sh/docs/develop/) for the server and the Android app.
|
the [build instructions](https://ntfy.sh/docs/develop/) for the server and the Android app.
|
||||||
|
@ -43,11 +53,6 @@ Or, if you'd like to help translate 🇩🇪 🇺🇸 🇧🇬, you can start im
|
||||||
<img src="https://hosted.weblate.org/widgets/ntfy/-/multi-blue.svg" alt="Translation status" />
|
<img src="https://hosted.weblate.org/widgets/ntfy/-/multi-blue.svg" alt="Translation status" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
## Contact me
|
|
||||||
You can directly contact me **[on Discord](https://discord.gg/cT7ECsZj9w)** or [on Matrix](https://matrix.to/#/#ntfy:matrix.org)
|
|
||||||
(bridged from Discord), or via the [GitHub issues](https://github.com/binwiederhier/ntfy/issues), or find more contact information
|
|
||||||
[on my website](https://heckel.io/about).
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
Made with ❤️ by [Philipp C. Heckel](https://heckel.io).
|
Made with ❤️ by [Philipp C. Heckel](https://heckel.io).
|
||||||
The project is dual licensed under the [Apache License 2.0](LICENSE) and the [GPLv2 License](LICENSE.GPLv2).
|
The project is dual licensed under the [Apache License 2.0](LICENSE) and the [GPLv2 License](LICENSE.GPLv2).
|
||||||
|
|
|
@ -643,10 +643,18 @@ In case you're curious, here's an example of the entire flow:
|
||||||
- In the iOS app, you subscribe to `https://ntfy.example.com/mytopic`
|
- In the iOS app, you subscribe to `https://ntfy.example.com/mytopic`
|
||||||
- The app subscribes to the Firebase topic `6de73be8dfb7d69e...` (the SHA256 of the topic URL)
|
- The app subscribes to the Firebase topic `6de73be8dfb7d69e...` (the SHA256 of the topic URL)
|
||||||
- When you publish a message to `https://ntfy.example.com/mytopic`, your ntfy server will publish a
|
- When you publish a message to `https://ntfy.example.com/mytopic`, your ntfy server will publish a
|
||||||
poll request to `https://ntfy.sh/6de73be8dfb7d69e...` (passing the message ID in the `X-Poll-ID` header)
|
poll request to `https://ntfy.sh/6de73be8dfb7d69e...`. The request from your server to the upstream server
|
||||||
- The ntfy.sh server publishes the message to Firebase, which forwards it to APNS, which forwards it to your iOS device
|
contains only the message ID (in the `X-Poll-ID` header), and the SHA256 checksum of the topic URL (as upstream topic).
|
||||||
|
- The ntfy.sh server publishes the poll request message to Firebase, which forwards it to APNS, which forwards it to your iOS device
|
||||||
- Your iOS device receives the poll request, and fetches the actual message from your server, and then displays it
|
- Your iOS device receives the poll request, and fetches the actual message from your server, and then displays it
|
||||||
|
|
||||||
|
Here's an example of what the self-hosted server forwards to the upstream server. The request is equivalent to this curl:
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -X POST -H "X-Poll-ID: s4PdJozxM8na" https://ntfy.sh/6de73be8dfb7d69e32fb2c00c23fe7adbd8b5504406e3068c273aa24cef4055b
|
||||||
|
{"id":"4HsClFEuCIcs","time":1654087955,"event":"poll_request","topic":"6de73be8dfb7d69e32fb2c00c23fe7adbd8b5504406e3068c273aa24cef4055b","message":"New message","poll_id":"s4PdJozxM8na"}
|
||||||
|
```
|
||||||
|
|
||||||
## Rate limiting
|
## Rate limiting
|
||||||
!!! info
|
!!! info
|
||||||
Be aware that if you are running ntfy behind a proxy, you must set the `behind-proxy` flag.
|
Be aware that if you are running ntfy behind a proxy, you must set the `behind-proxy` flag.
|
||||||
|
|
|
@ -176,6 +176,12 @@ cd ntfysh-bin
|
||||||
makepkg -si
|
makepkg -si
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## NixOS / Nix
|
||||||
|
ntfy is packaged in nixpkgs as `ntfy-sh`. It can be installed by adding the package name to the configuration file and calling `nixos-rebuild`. Alternatively, the following command can be used to install ntfy in the current user environment:
|
||||||
|
```
|
||||||
|
nix-env -iA ntfy-sh
|
||||||
|
```
|
||||||
|
|
||||||
## macOS
|
## macOS
|
||||||
The [ntfy CLI](subscribe/cli.md) (`ntfy publish` and `ntfy subscribe` only) is supported on macOS as well.
|
The [ntfy CLI](subscribe/cli.md) (`ntfy publish` and `ntfy subscribe` only) is supported on macOS as well.
|
||||||
To install, please download the tarball, extract it and place it somewhere in your `PATH` (e.g. `/usr/local/bin/ntfy`).
|
To install, please download the tarball, extract it and place it somewhere in your `PATH` (e.g. `/usr/local/bin/ntfy`).
|
||||||
|
|
|
@ -25,6 +25,12 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release
|
||||||
**Documentation**:
|
**Documentation**:
|
||||||
|
|
||||||
* [Examples](examples.md) for [Home Assistant](https://www.home-assistant.io/) ([#282](https://github.com/binwiederhier/ntfy/pull/282), thanks to [@poblabs](https://github.com/poblabs))
|
* [Examples](examples.md) for [Home Assistant](https://www.home-assistant.io/) ([#282](https://github.com/binwiederhier/ntfy/pull/282), thanks to [@poblabs](https://github.com/poblabs))
|
||||||
|
* Install instructions for [NixOS/Nix](https://ntfy.sh/docs/install/#nixos-nix) ([#282](https://github.com/binwiederhier/ntfy/pull/282), thanks to [@arjan-s](https://github.com/arjan-s))
|
||||||
|
* Clarify `poll_request` wording for [iOS push notifications](https://ntfy.sh/docs/config/#ios-instant-notifications) ([#300](https://github.com/binwiederhier/ntfy/issues/300), thanks to [@prabirshrestha](https://github.com/prabirshrestha) for reporting)
|
||||||
|
|
||||||
|
**Additional translations:**
|
||||||
|
|
||||||
|
* Chinese/Simplified (thanks to [@yufei.im](https://hosted.weblate.org/user/yufei.im/))
|
||||||
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"heckel.io/ntfy/auth"
|
"heckel.io/ntfy/auth"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -29,6 +30,7 @@ func (t testAuther) Authorize(_ *auth.User, _ string, _ auth.Permission) error {
|
||||||
type testFirebaseSender struct {
|
type testFirebaseSender struct {
|
||||||
allowed int
|
allowed int
|
||||||
messages []*messaging.Message
|
messages []*messaging.Message
|
||||||
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestFirebaseSender(allowed int) *testFirebaseSender {
|
func newTestFirebaseSender(allowed int) *testFirebaseSender {
|
||||||
|
@ -37,7 +39,10 @@ func newTestFirebaseSender(allowed int) *testFirebaseSender {
|
||||||
messages: make([]*messaging.Message, 0),
|
messages: make([]*messaging.Message, 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *testFirebaseSender) Send(m *messaging.Message) error {
|
func (s *testFirebaseSender) Send(m *messaging.Message) error {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
if len(s.messages)+1 > s.allowed {
|
if len(s.messages)+1 > s.allowed {
|
||||||
return errFirebaseQuotaExceeded
|
return errFirebaseQuotaExceeded
|
||||||
}
|
}
|
||||||
|
@ -45,6 +50,12 @@ func (s *testFirebaseSender) Send(m *messaging.Message) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *testFirebaseSender) Messages() []*messaging.Message {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
return append(make([]*messaging.Message, 0), s.messages...)
|
||||||
|
}
|
||||||
|
|
||||||
func TestToFirebaseMessage_Keepalive(t *testing.T) {
|
func TestToFirebaseMessage_Keepalive(t *testing.T) {
|
||||||
m := newKeepaliveMessage("mytopic")
|
m := newKeepaliveMessage("mytopic")
|
||||||
fbm, err := toFirebaseMessage(m, nil)
|
fbm, err := toFirebaseMessage(m, nil)
|
||||||
|
@ -311,15 +322,15 @@ func TestToFirebaseSender_Abuse(t *testing.T) {
|
||||||
visitor := newVisitor(newTestConfig(t), newMemTestCache(t), "1.2.3.4")
|
visitor := newVisitor(newTestConfig(t), newMemTestCache(t), "1.2.3.4")
|
||||||
|
|
||||||
require.Nil(t, client.Send(visitor, &message{Topic: "mytopic"}))
|
require.Nil(t, client.Send(visitor, &message{Topic: "mytopic"}))
|
||||||
require.Equal(t, 1, len(sender.messages))
|
require.Equal(t, 1, len(sender.Messages()))
|
||||||
|
|
||||||
require.Nil(t, client.Send(visitor, &message{Topic: "mytopic"}))
|
require.Nil(t, client.Send(visitor, &message{Topic: "mytopic"}))
|
||||||
require.Equal(t, 2, len(sender.messages))
|
require.Equal(t, 2, len(sender.Messages()))
|
||||||
|
|
||||||
require.Equal(t, errFirebaseQuotaExceeded, client.Send(visitor, &message{Topic: "mytopic"}))
|
require.Equal(t, errFirebaseQuotaExceeded, client.Send(visitor, &message{Topic: "mytopic"}))
|
||||||
require.Equal(t, 2, len(sender.messages))
|
require.Equal(t, 2, len(sender.Messages()))
|
||||||
|
|
||||||
sender.messages = make([]*messaging.Message, 0) // Reset to test that time limit is working
|
sender.messages = make([]*messaging.Message, 0) // Reset to test that time limit is working
|
||||||
require.Equal(t, errFirebaseQuotaExceeded, client.Send(visitor, &message{Topic: "mytopic"}))
|
require.Equal(t, errFirebaseQuotaExceeded, client.Send(visitor, &message{Topic: "mytopic"}))
|
||||||
require.Equal(t, 0, len(sender.messages))
|
require.Equal(t, 0, len(sender.Messages()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,10 +63,10 @@ func TestServer_PublishWithFirebase(t *testing.T) {
|
||||||
msg1 := toMessage(t, response.Body.String())
|
msg1 := toMessage(t, response.Body.String())
|
||||||
require.NotEmpty(t, msg1.ID)
|
require.NotEmpty(t, msg1.ID)
|
||||||
require.Equal(t, "my first message", msg1.Message)
|
require.Equal(t, "my first message", msg1.Message)
|
||||||
require.Equal(t, 1, len(sender.messages))
|
require.Equal(t, 1, len(sender.Messages()))
|
||||||
require.Equal(t, "my first message", sender.messages[0].Data["message"])
|
require.Equal(t, "my first message", sender.Messages()[0].Data["message"])
|
||||||
require.Equal(t, "my first message", sender.messages[0].APNS.Payload.Aps.Alert.Body)
|
require.Equal(t, "my first message", sender.Messages()[0].APNS.Payload.Aps.Alert.Body)
|
||||||
require.Equal(t, "my first message", sender.messages[0].APNS.Payload.CustomData["message"])
|
require.Equal(t, "my first message", sender.Messages()[0].APNS.Payload.CustomData["message"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServer_SubscribeOpenAndKeepalive(t *testing.T) {
|
func TestServer_SubscribeOpenAndKeepalive(t *testing.T) {
|
||||||
|
|
191
web/public/static/langs/zh_Hans.json
Normal file
191
web/public/static/langs/zh_Hans.json
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
{
|
||||||
|
"action_bar_show_menu": "显示菜单",
|
||||||
|
"action_bar_logo_alt": "ntfy图标",
|
||||||
|
"action_bar_settings": "设置",
|
||||||
|
"action_bar_send_test_notification": "发送测试通知",
|
||||||
|
"action_bar_clear_notifications": "清除所有通知",
|
||||||
|
"action_bar_unsubscribe": "取消订阅",
|
||||||
|
"action_bar_toggle_action_menu": "开启或关闭操作菜单",
|
||||||
|
"message_bar_type_message": "在此处输入消息",
|
||||||
|
"message_bar_show_dialog": "显示发布对话框",
|
||||||
|
"message_bar_publish": "发布消息",
|
||||||
|
"nav_topics_title": "订阅主题",
|
||||||
|
"nav_button_all_notifications": "全部通知",
|
||||||
|
"nav_button_documentation": "文档",
|
||||||
|
"nav_button_publish_message": "发布通知",
|
||||||
|
"nav_button_subscribe": "订阅主题",
|
||||||
|
"nav_button_connecting": "正在连接",
|
||||||
|
"alert_grant_title": "已禁用通知",
|
||||||
|
"alert_grant_description": "授予浏览器显示桌面通知的权限。",
|
||||||
|
"alert_grant_button": "现在授予",
|
||||||
|
"alert_not_supported_title": "不支持通知",
|
||||||
|
"alert_not_supported_description": "您的浏览器不支持通知。",
|
||||||
|
"notifications_list": "通知列表",
|
||||||
|
"notifications_list_item": "通知",
|
||||||
|
"notifications_mark_read": "标记为已读",
|
||||||
|
"notifications_copied_to_clipboard": "复制到剪贴板",
|
||||||
|
"notifications_tags": "标记",
|
||||||
|
"notifications_priority_x": "优先级 {{priority}}",
|
||||||
|
"notifications_new_indicator": "新通知",
|
||||||
|
"notifications_attachment_open_button": "打开附件",
|
||||||
|
"notifications_attachment_link_expires": "链接过期 {{date}}",
|
||||||
|
"notifications_attachment_link_expired": "下载链接已过期",
|
||||||
|
"notifications_attachment_file_image": "图片文件",
|
||||||
|
"notifications_attachment_image": "附件图片",
|
||||||
|
"notifications_attachment_file_video": "视频文件",
|
||||||
|
"notifications_attachment_file_audio": "音频文件",
|
||||||
|
"notifications_attachment_file_app": "安卓应用文件",
|
||||||
|
"notifications_attachment_file_document": "其他文件",
|
||||||
|
"notifications_click_copy_url_title": "复制链接地址到剪贴板",
|
||||||
|
"notifications_click_copy_url_button": "复制链接",
|
||||||
|
"notifications_click_open_button": "打开链接",
|
||||||
|
"action_bar_toggle_mute": "暂停或恢复通知",
|
||||||
|
"nav_button_muted": "已暂停通知",
|
||||||
|
"notifications_actions_not_supported": "网页应用程序不支持操作",
|
||||||
|
"notifications_none_for_topic_title": "您尚未收到有关此主题的任何通知。",
|
||||||
|
"notifications_none_for_any_title": "您尚未收到任何通知。",
|
||||||
|
"notifications_none_for_any_description": "要向此主题发送通知,只需使用 PUT 或 POST 到主题链接即可。以下是使用您的主题的示例。",
|
||||||
|
"notifications_no_subscriptions_title": "看起来你还没有任何订阅。",
|
||||||
|
"notifications_example": "示例",
|
||||||
|
"notifications_more_details": "有关更多信息,请查看<websiteLink>网站</websiteLink>或<docsLink>文档</docsLink>。",
|
||||||
|
"notifications_loading": "正在加载通知……",
|
||||||
|
"publish_dialog_title_topic": "发布到 {{topic}}",
|
||||||
|
"publish_dialog_title_no_topic": "发布通知",
|
||||||
|
"publish_dialog_progress_uploading": "正在上传……",
|
||||||
|
"publish_dialog_progress_uploading_detail": "正在上传 {{loaded}}/{{total}} ({{percent}}%) ……",
|
||||||
|
"publish_dialog_message_published": "已发布通知",
|
||||||
|
"publish_dialog_attachment_limits_file_and_quota_reached": "超过 {{fileSizeLimit}} 文件限制和配额,剩余 {{remainingBytes}}",
|
||||||
|
"publish_dialog_emoji_picker_show": "选择表情符号",
|
||||||
|
"publish_dialog_priority_min": "最低优先级",
|
||||||
|
"publish_dialog_priority_low": "低优先级",
|
||||||
|
"publish_dialog_priority_default": "默认优先级",
|
||||||
|
"publish_dialog_priority_high": "高优先级",
|
||||||
|
"publish_dialog_priority_max": "最高优先级",
|
||||||
|
"publish_dialog_topic_label": "主题名称",
|
||||||
|
"publish_dialog_topic_placeholder": "主题名称,例如 phil_alerts",
|
||||||
|
"publish_dialog_topic_reset": "重置主题",
|
||||||
|
"publish_dialog_title_label": "主题",
|
||||||
|
"publish_dialog_message_label": "消息",
|
||||||
|
"publish_dialog_message_placeholder": "在此输入消息",
|
||||||
|
"publish_dialog_tags_label": "标记",
|
||||||
|
"publish_dialog_priority_label": "优先级",
|
||||||
|
"publish_dialog_base_url_label": "服务链接地址",
|
||||||
|
"publish_dialog_base_url_placeholder": "服务链接地址,例如 https://example.com",
|
||||||
|
"publish_dialog_click_label": "点击链接地址",
|
||||||
|
"publish_dialog_click_placeholder": "点击通知时打开链接地址",
|
||||||
|
"publish_dialog_email_placeholder": "将通知转发到的地址,例如 phil@example.com",
|
||||||
|
"publish_dialog_email_reset": "移除电子邮件转发",
|
||||||
|
"publish_dialog_filename_label": "文件名",
|
||||||
|
"publish_dialog_filename_placeholder": "附件文件名",
|
||||||
|
"publish_dialog_delay_label": "延期",
|
||||||
|
"publish_dialog_other_features": "其它功能:",
|
||||||
|
"publish_dialog_attach_placeholder": "使用链接地址附加文件,例如 https://f-droid.org/F-Droid.apk",
|
||||||
|
"publish_dialog_delay_reset": "删除延迟交付",
|
||||||
|
"publish_dialog_attach_reset": "移除附件链接地址",
|
||||||
|
"publish_dialog_chip_click_label": "点击链接地址",
|
||||||
|
"publish_dialog_chip_email_label": "转发邮件",
|
||||||
|
"publish_dialog_chip_attach_file_label": "本地文件附件",
|
||||||
|
"publish_dialog_chip_topic_label": "变更主题",
|
||||||
|
"publish_dialog_button_cancel_sending": "取消发送",
|
||||||
|
"publish_dialog_checkbox_publish_another": "发布另一个",
|
||||||
|
"publish_dialog_attached_file_title": "附件文件:",
|
||||||
|
"publish_dialog_attached_file_filename_placeholder": "附件文件名",
|
||||||
|
"publish_dialog_attached_file_remove": "删除附件文件",
|
||||||
|
"publish_dialog_drop_file_here": "将文件拖拽至此",
|
||||||
|
"emoji_picker_search_placeholder": "查找表情符号",
|
||||||
|
"emoji_picker_search_clear": "清除搜索",
|
||||||
|
"subscribe_dialog_subscribe_title": "订阅主题",
|
||||||
|
"publish_dialog_chip_delay_label": "延迟交付",
|
||||||
|
"publish_dialog_chip_attach_url_label": "链接附件地址",
|
||||||
|
"subscribe_dialog_subscribe_use_another_label": "使用其他服务器",
|
||||||
|
"subscribe_dialog_subscribe_button_subscribe": "订阅",
|
||||||
|
"subscribe_dialog_login_title": "请登录",
|
||||||
|
"subscribe_dialog_login_description": "本主题受密码保护,请输入用户名和密码进行订阅。",
|
||||||
|
"subscribe_dialog_login_username_label": "用户名,例如 phil",
|
||||||
|
"subscribe_dialog_login_password_label": "密码",
|
||||||
|
"subscribe_dialog_login_button_back": "返回",
|
||||||
|
"subscribe_dialog_login_button_login": "登录",
|
||||||
|
"subscribe_dialog_error_user_not_authorized": "未授权 {{username}} 用户",
|
||||||
|
"subscribe_dialog_error_user_anonymous": "匿名",
|
||||||
|
"prefs_notifications_title": "通知",
|
||||||
|
"prefs_notifications_sound_title": "通知提示音",
|
||||||
|
"prefs_notifications_sound_description_none": "收到通知时不播放任何声音",
|
||||||
|
"prefs_notifications_sound_description_some": "收到通知时播放 {{sound}} 声音",
|
||||||
|
"prefs_notifications_sound_no_sound": "静音",
|
||||||
|
"prefs_notifications_sound_play": "播放选中声音",
|
||||||
|
"prefs_notifications_min_priority_title": "最低优先级",
|
||||||
|
"prefs_notifications_min_priority_description_x_or_higher": "仅显示优先级为{{number}}({{name}})或以上的通知",
|
||||||
|
"prefs_notifications_min_priority_description_max": "仅显示最高优先级的通知",
|
||||||
|
"prefs_notifications_min_priority_any": "任意优先级",
|
||||||
|
"prefs_notifications_min_priority_low_and_higher": "低优先级和更高优先级",
|
||||||
|
"prefs_notifications_min_priority_default_and_higher": "默认优先级或更高优先级",
|
||||||
|
"prefs_notifications_min_priority_high_and_higher": "高优先级或更高优先级",
|
||||||
|
"prefs_notifications_min_priority_max_only": "仅最高优先级",
|
||||||
|
"prefs_notifications_delete_after_never": "从不",
|
||||||
|
"prefs_notifications_delete_after_one_month": "一月后",
|
||||||
|
"prefs_notifications_delete_after_one_week": "一周后",
|
||||||
|
"prefs_notifications_delete_after_never_description": "永不自动删除通知",
|
||||||
|
"prefs_notifications_delete_after_three_hours_description": "三小时后自动删除通知",
|
||||||
|
"prefs_notifications_delete_after_one_day_description": "一天后自动删除通知",
|
||||||
|
"prefs_notifications_delete_after_one_week_description": "一周后自动删除通知",
|
||||||
|
"prefs_notifications_delete_after_one_month_description": "一月后后自动删除通知",
|
||||||
|
"prefs_users_title": "管理用户",
|
||||||
|
"prefs_users_description": "在此处添加/删除受保护主题的用户。请注意,用户名和密码存储在浏览器的本地存储中。",
|
||||||
|
"prefs_users_add_button": "添加用户",
|
||||||
|
"prefs_users_dialog_title_add": "添加用户",
|
||||||
|
"prefs_users_dialog_title_edit": "编辑用户",
|
||||||
|
"prefs_users_dialog_username_label": "用户名,例如 phil",
|
||||||
|
"prefs_users_dialog_password_label": "密码",
|
||||||
|
"prefs_users_dialog_button_cancel": "取消",
|
||||||
|
"prefs_users_dialog_button_save": "保存",
|
||||||
|
"prefs_appearance_title": "外观",
|
||||||
|
"prefs_appearance_language_title": "语言",
|
||||||
|
"priority_min": "最低",
|
||||||
|
"priority_low": "低",
|
||||||
|
"priority_default": "默认",
|
||||||
|
"priority_high": "高",
|
||||||
|
"priority_max": "最高",
|
||||||
|
"error_boundary_title": "天啊,ntfy 崩溃了",
|
||||||
|
"prefs_users_table_base_url_header": "服务链接地址",
|
||||||
|
"prefs_users_dialog_base_url_label": "服务链接地址,例如 https://ntfy.sh",
|
||||||
|
"error_boundary_button_copy_stack_trace": "复制堆栈跟踪",
|
||||||
|
"error_boundary_stack_trace": "堆栈跟踪",
|
||||||
|
"error_boundary_gathering_info": "收集更多信息……",
|
||||||
|
"error_boundary_unsupported_indexeddb_title": "不支持隐私浏览",
|
||||||
|
"error_boundary_unsupported_indexeddb_description": "Ntfy Web应用程序需要IndexedDB才能运行,并且您的浏览器在私隐私浏览模式下不支持IndexedDB。<br/><br/>虽然这很不幸,但在隐私浏览模式下使用ntfy Web应用程序也没有多大意义,因为所有东西都存储在浏览器存储中。您可以在<githubLink>本GitHub问题</githubLink>中阅读有关它的更多信息,或者在<discordLink>Discord</discordLink>或<matrixLink>Matrix</matrixLink>上与我们交谈。",
|
||||||
|
"message_bar_error_publishing": "发布通知时出错",
|
||||||
|
"nav_button_settings": "设置",
|
||||||
|
"notifications_delete": "删除",
|
||||||
|
"notifications_attachment_copy_url_title": "将附件中链接地址复制到剪贴板",
|
||||||
|
"notifications_attachment_copy_url_button": "复制链接地址",
|
||||||
|
"notifications_attachment_open_title": "转到 {{url}}",
|
||||||
|
"notifications_actions_http_request_title": "发送 HTTP {{method}} 到 {{url}}",
|
||||||
|
"notifications_actions_open_url_title": "转到 {{url}}",
|
||||||
|
"notifications_none_for_topic_description": "要向此主题发送通知,只需使用 PUT 或 POST 到主题链接即可。",
|
||||||
|
"subscribe_dialog_subscribe_topic_placeholder": "主题名,例如 phil_alerts",
|
||||||
|
"notifications_no_subscriptions_description": "单击 \"{{linktext}}\" 链接以创建或订阅主题。之后,您可以使用 PUT 或 POST 发送消息,您将在这里收到通知。",
|
||||||
|
"publish_dialog_attachment_limits_file_reached": "超过 {{fileSizeLimit}} 文件限制",
|
||||||
|
"publish_dialog_title_placeholder": "主题标题,例如 磁盘空间告警",
|
||||||
|
"publish_dialog_email_label": "电子邮件",
|
||||||
|
"publish_dialog_button_send": "发送",
|
||||||
|
"publish_dialog_attachment_limits_quota_reached": "超过配额,剩余 {{remainingBytes}}",
|
||||||
|
"publish_dialog_attach_label": "附件链接地址",
|
||||||
|
"publish_dialog_click_reset": "移除点击连接地址",
|
||||||
|
"publish_dialog_button_cancel": "取消",
|
||||||
|
"subscribe_dialog_subscribe_button_cancel": "取消",
|
||||||
|
"subscribe_dialog_subscribe_base_url_label": "服务地址地址",
|
||||||
|
"prefs_notifications_min_priority_description_any": "显示所有通知,无论优先级如何",
|
||||||
|
"prefs_notifications_delete_after_title": "删除通知",
|
||||||
|
"prefs_notifications_delete_after_three_hours": "三小时后",
|
||||||
|
"prefs_users_delete_button": "删除用户",
|
||||||
|
"prefs_users_table_user_header": "用户",
|
||||||
|
"prefs_users_dialog_button_add": "添加",
|
||||||
|
"prefs_notifications_delete_after_one_day": "一天后",
|
||||||
|
"error_boundary_description": "这显然不应该发生。对此非常抱歉。<br/>如果您有时间,请<githubLink>在GitHub</githubLink>上报告,或通过<discordLink>Discord</discordLink>或<matrixLink>Matrix</matrixLink>告诉我们。",
|
||||||
|
"prefs_users_table": "用户表",
|
||||||
|
"prefs_users_edit_button": "编辑用户",
|
||||||
|
"publish_dialog_tags_placeholder": "英文逗号分隔标记列表,例如 warning, srv1-backup",
|
||||||
|
"publish_dialog_details_examples_description": "有关所有发送功能的示例和详细说明,请参阅<docsLink>文档</docsLink>。",
|
||||||
|
"subscribe_dialog_subscribe_description": "主题可能不受密码保护,因此请选择一个不容易猜测的名字。订阅后,您可以使用 PUT/POST 通知。",
|
||||||
|
"publish_dialog_delay_placeholder": "延迟交付,例如{{unixTimestamp}}、{{relativeTime}}或“{{naturalLanguage}}”(仅限英语)"
|
||||||
|
}
|
|
@ -436,7 +436,7 @@ const Appearance = () => {
|
||||||
const Language = () => {
|
const Language = () => {
|
||||||
const { t, i18n } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
const labelId = "prefLanguage";
|
const labelId = "prefLanguage";
|
||||||
const randomFlags = shuffle(["🇬🇧", "🇺🇸", "🇪🇸", "🇫🇷", "🇧🇬", "🇨🇿", "🇩🇪", "🇮🇹", "🇭🇺", "🇧🇷", "🇮🇩", "🇯🇵", "🇷🇺", "🇹🇷"]).slice(0, 3);
|
const randomFlags = shuffle(["🇬🇧", "🇺🇸", "🇪🇸", "🇫🇷", "🇧🇬", "🇨🇿", "🇩🇪", "🇨🇳", "🇮🇹", "🇭🇺", "🇧🇷", "🇮🇩", "🇯🇵", "🇷🇺", "🇹🇷"]).slice(0, 3);
|
||||||
const title = t("prefs_appearance_language_title") + " " + randomFlags.join(" ");
|
const title = t("prefs_appearance_language_title") + " " + randomFlags.join(" ");
|
||||||
const lang = i18n.language ?? "en";
|
const lang = i18n.language ?? "en";
|
||||||
|
|
||||||
|
@ -452,6 +452,7 @@ const Language = () => {
|
||||||
<MenuItem value="id">Bahasa Indonesia</MenuItem>
|
<MenuItem value="id">Bahasa Indonesia</MenuItem>
|
||||||
<MenuItem value="bg">Български</MenuItem>
|
<MenuItem value="bg">Български</MenuItem>
|
||||||
<MenuItem value="cs">Čeština</MenuItem>
|
<MenuItem value="cs">Čeština</MenuItem>
|
||||||
|
<MenuItem value="zh_Hans">中文</MenuItem>
|
||||||
<MenuItem value="de">Deutsch</MenuItem>
|
<MenuItem value="de">Deutsch</MenuItem>
|
||||||
<MenuItem value="es">Español</MenuItem>
|
<MenuItem value="es">Español</MenuItem>
|
||||||
<MenuItem value="fr">Français</MenuItem>
|
<MenuItem value="fr">Français</MenuItem>
|
||||||
|
|
Loading…
Reference in a new issue