Compare commits

..

141 Commits

Author SHA1 Message Date
binwiederhier
74ea78fbdc Make require-login work 2025-05-24 14:46:18 -04:00
binwiederhier
1561251028 Merge branch 'main' of github.com:binwiederhier/ntfy into feat_optional_require_login 2025-05-24 14:27:40 -04:00
binwiederhier
f4c37ccfb9 Bump VIte 2025-05-24 14:22:02 -04:00
Philipp C. Heckel
7182d3a4e5 Merge pull request #1342 from binwiederhier/dependabot/npm_and_yarn/web/multi-4e779676ec
Bump esbuild, vite and vite-plugin-pwa in /web
2025-05-24 14:07:01 -04:00
binwiederhier
eecd3245f0 Release notes 2025-05-24 09:36:16 -04:00
binwiederhier
4dc3b38c95 Allow adding/changing user with password hash via v1/users API 2025-05-24 09:31:57 -04:00
binwiederhier
9edab24d4c Merge branch 'main' of github.com:binwiederhier/ntfy 2025-05-24 09:10:07 -04:00
Philipp C. Heckel
3b627b27b3 Merge pull request #1340 from Tom-Hubrecht/hashed-pwd
user: Allow changing the hashed password directly
2025-05-24 09:10:02 -04:00
binwiederhier
80462f7ee5 Refine user API change 2025-05-24 08:58:44 -04:00
Philipp C. Heckel
65e377ec63 Merge pull request #1267 from wunter8/api-change-user-password
Api change user password
2025-05-24 08:53:28 -04:00
Hunter Kehoe
0fb60ae72d test change user password and tier in single request 2025-05-22 20:01:50 -06:00
Hunter Kehoe
e36e4856c9 allow changing password or tier with user PUT 2025-05-22 19:57:57 -06:00
Hunter Kehoe
fa48639517 make POST create user and PUT update user 2025-05-22 19:57:02 -06:00
Hunter Kehoe
2b40ad9a12 make staticcheck happy 2025-05-22 19:57:02 -06:00
Hunter Kehoe
ad7ab18fb7 prevent changing admin passwords 2025-05-22 19:57:02 -06:00
Hunter Kehoe
8f9dafce20 change user password via accounts API 2025-05-22 19:57:00 -06:00
binwiederhier
69cf773834 Fix webpush command 2025-05-22 21:56:28 -04:00
binwiederhier
b2b9891a58 Add Tamil language 2025-05-22 21:43:45 -04:00
Andrea Toska
3bf02d3cd9 Translated using Weblate (Albanian)
Currently translated at 15.0% (61 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/sq/
2025-05-23 03:23:15 +02:00
Priit Jõerüüt
8777990d2d Translated using Weblate (Estonian)
Currently translated at 24.9% (101 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/et/
2025-05-23 03:23:15 +02:00
Tyxiel
70f0e7ccc7 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/pt_BR/
2025-05-23 03:23:15 +02:00
Max Badran
adfacf820e Translated using Weblate (Ukrainian)
Currently translated at 99.7% (404 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/uk/
2025-05-23 03:23:15 +02:00
András
35e15cfd9d Translated using Weblate (Hungarian)
Currently translated at 53.5% (217 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/hu/
2025-05-23 03:23:15 +02:00
Eero Häkkinen
4e2a884da5 Translated using Weblate (Finnish)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/fi/
2025-05-23 03:23:15 +02:00
Eero Häkkinen
29cf4f16d1 Translated using Weblate (Finnish)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/fi/
2025-05-23 03:23:15 +02:00
Łukasz Podgórski
609c9fa37d Translated using Weblate (Polish)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/pl/
2025-05-23 03:23:15 +02:00
Korab Arifi
2eb5eb3e29 Translated using Weblate (Albanian)
Currently translated at 10.1% (41 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/sq/
2025-05-23 03:23:15 +02:00
Korab Arifi
a92306b181 Added translation using Weblate (Albanian) 2025-05-23 03:23:15 +02:00
OZZY
047cc22dba Translated using Weblate (Arabic)
Currently translated at 88.3% (358 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/ar/
2025-05-23 03:23:15 +02:00
Priit Jõerüüt
f31d777b69 Translated using Weblate (Estonian)
Currently translated at 10.6% (43 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/et/
2025-05-23 03:23:15 +02:00
தமிழ்நேரம்
ac983cd9bc Translated using Weblate (Tamil)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/ta/
2025-05-23 03:23:15 +02:00
AtomicDude
dd45fd90b7 Translated using Weblate (Romanian)
Currently translated at 56.7% (230 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/ro/
2025-05-23 03:23:15 +02:00
தமிழ்நேரம்
e76e6274a3 Added translation using Weblate (Tamil) 2025-05-23 03:23:15 +02:00
Marius Pop
161ce468fe Translated using Weblate (Romanian)
Currently translated at 46.6% (189 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/ro/
2025-05-23 03:23:15 +02:00
Danial Behzadi
04df6f1390 Translated using Weblate (Persian)
Currently translated at 13.8% (56 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/fa/
2025-05-23 03:23:15 +02:00
Faraz Sadri Alamdari
79852fec59 Translated using Weblate (Persian)
Currently translated at 13.8% (56 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/fa/
2025-05-23 03:23:15 +02:00
Danial Behzadi
92de1b5a88 Translated using Weblate (Persian)
Currently translated at 13.8% (56 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/fa/
2025-05-23 03:23:15 +02:00
Ihor Kalnytskyi
fc93de9a28 Translated using Weblate (Ukrainian)
Currently translated at 99.7% (404 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/uk/
2025-05-23 03:23:15 +02:00
qtm
ae9fa85676 Translated using Weblate (Russian)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/ru/
2025-05-23 03:23:15 +02:00
Christer Solstrand Johannessen
b26666f635 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/nb_NO/
2025-05-23 03:23:15 +02:00
Christer Solstrand Johannessen
70a9301e25 Translated using Weblate (Norwegian Bokmål)
Currently translated at 51.1% (207 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/nb_NO/
2025-05-23 03:23:15 +02:00
Cairo Braga
86c548ae37 Translated using Weblate (Portuguese)
Currently translated at 76.5% (310 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/pt/
2025-05-23 03:23:15 +02:00
Ed
1e1b2be464 Translated using Weblate (Portuguese)
Currently translated at 76.5% (310 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/pt/
2025-05-23 03:23:15 +02:00
Cairo Braga
1b8906f1fd Translated using Weblate (Portuguese (Brazil))
Currently translated at 84.1% (341 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/pt_BR/
2025-05-23 03:23:15 +02:00
Luis Eduardo Brito
b81f7b21a9 Translated using Weblate (Portuguese (Brazil))
Currently translated at 83.9% (340 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/pt_BR/
2025-05-23 03:23:15 +02:00
K0ntact
db2dc09189 Translated using Weblate (Vietnamese)
Currently translated at 7.1% (29 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/vi/
2025-05-23 03:23:14 +02:00
Shoshin Akamine
5f6b7e6f82 Translated using Weblate (Japanese)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/ja/
2025-05-23 03:23:14 +02:00
109247019824
6daf4141c6 Translated using Weblate (Bulgarian)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/bg/
2025-05-23 03:23:14 +02:00
Vito0912
41083cfd07 Translated using Weblate (German)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/de/
2025-05-23 03:23:14 +02:00
Soaibuzzaman
c03f795508 Added translation using Weblate (Bengali) 2025-05-23 03:23:14 +02:00
Ricardo Vieira
58d7cb8ef8 Translated using Weblate (Portuguese)
Currently translated at 76.2% (309 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/pt/
2025-05-23 03:23:14 +02:00
Linerly
8acf0f4350 Translated using Weblate (Indonesian)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/id/
2025-05-23 03:23:14 +02:00
Carl Fritze
236b7b7a16 Translated using Weblate (German)
Currently translated at 99.5% (403 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/de/
2025-05-23 03:23:14 +02:00
Petri Hämäläinen
871883f6e9 Translated using Weblate (Finnish)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/fi/
2025-05-23 03:23:14 +02:00
Malte Saling
a92c8a9ec9 Translated using Weblate (German)
Currently translated at 95.5% (387 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/de/
2025-05-23 03:23:14 +02:00
Vito0912
1c6aa49fca Translated using Weblate (German)
Currently translated at 95.3% (386 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/de/
2025-05-23 03:23:14 +02:00
githubozaurus
49d258706d Translated using Weblate (Romanian)
Currently translated at 31.1% (126 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/ro/
2025-05-23 03:23:14 +02:00
josé m
bbce1200b4 Translated using Weblate (Galician)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/gl/
2025-05-23 03:23:14 +02:00
Stefano Maggi
94d0c5a335 Translated using Weblate (Italian)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/it/
2025-05-23 03:23:14 +02:00
Jakob Malchow
7835fc65c4 Translated using Weblate (Italian)
Currently translated at 87.6% (355 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/it/
2025-05-23 03:23:14 +02:00
Stefano Maggi
dc6b8ece1e Translated using Weblate (Italian)
Currently translated at 87.6% (355 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/it/
2025-05-23 03:23:14 +02:00
josé m
f595dff66f Translated using Weblate (Galician)
Currently translated at 100.0% (405 of 405 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/gl/
2025-05-23 03:23:14 +02:00
binwiederhier
0514ea4ac0 Merge branch 'main' of github.com:binwiederhier/ntfy 2025-05-22 20:59:29 -04:00
binwiederhier
1598087e1f Fix tests 2025-05-22 20:58:28 -04:00
Philipp C. Heckel
3709ea689a Merge pull request #1216 from wunter8/since-latest-param
feat: add since=latest subscribe param
2025-05-22 20:57:59 -04:00
Philipp C. Heckel
f4aba12546 Merge branch 'main' into since-latest-param 2025-05-22 20:57:52 -04:00
Philipp C. Heckel
521fe791b0 Merge pull request #1271 from RoboMagus/feat_1174
Add major and minor version tags to docker release flow
2025-05-22 20:51:00 -04:00
binwiederhier
6d15b9face Fix up APNs PR 2025-05-22 20:48:24 -04:00
Philipp C. Heckel
9fbe7804dd Merge pull request #1345 from tr4nt0r/homeassistant_official
Add official Home Assistant integration and async python library
2025-05-22 20:30:18 -04:00
Philipp C. Heckel
faa4dcbcee Merge pull request #1287 from barart/fix-anonymous-read-restriction
Handle anonymous read restrictions by sending a poll_request event
2025-05-22 20:22:58 -04:00
tr4nt0r
ad3e7960ce Add official Home Assistant integration and async python library 2025-05-23 01:31:16 +02:00
Philipp C. Heckel
3234189cd2 Merge pull request #1343 from ptmorris1/patch-2
Add NtfyPwsh integration and blog
2025-05-22 16:51:53 -04:00
Patrick Morris
e64a0bd8c9 Add NtfyPwsh integration and blog 2025-05-22 13:37:54 -05:00
Philipp C. Heckel
97a59f19e0 Merge pull request #1262 from rake5k/markdown-code-scroll
Make markdown code blocks scrollable
2025-05-21 20:57:54 -04:00
binwiederhier
7067d8aa77 Release notes 2025-05-21 20:55:54 -04:00
Philipp C. Heckel
5999653456 Merge pull request #1138 from nogweii/webpush-key-file
Teach ntfy webpush to write the keys to a file
2025-05-21 20:44:44 -04:00
Philipp C. Heckel
9ce6b03450 Merge branch 'main' into webpush-key-file 2025-05-21 20:39:12 -04:00
Philipp C. Heckel
7e916516e0 Merge pull request #1338 from wunter8/websockets-401
Websocket http error codes
2025-05-21 20:26:32 -04:00
Philipp C. Heckel
09c2b4bdca Merge pull request #1239 from thiswillbeyourgithub/integration_toc
docs: add ToC to integrations.md
2025-05-21 20:21:40 -04:00
Philipp C. Heckel
978ee81df3 Merge pull request #1199 from quantum5/ntfy-run
docs: add quantum5/ntfy-run to integrations and examples
2025-05-21 20:19:47 -04:00
Philipp C. Heckel
86f2ab8a55 Merge branch 'main' into ntfy-run 2025-05-21 20:19:39 -04:00
Philipp C. Heckel
e4aff00455 Merge pull request #1234 from ungeskriptet/fix-typo
docs: config.md: fix typo
2025-05-21 20:17:57 -04:00
Philipp C. Heckel
e88f24bae7 Merge pull request #1241 from thiswillbeyourgithub/integration_csv
docs: add integration: Ntfy_CSV_Reminders
2025-05-21 20:16:45 -04:00
Philipp C. Heckel
b4797ef212 Merge branch 'main' into integration_csv 2025-05-21 20:16:35 -04:00
Philipp C. Heckel
2f8c0e4d5d Merge pull request #1224 from dmitrygudkov/patch-1
Update integrations.md: Added EasyMorph
2025-05-21 20:14:07 -04:00
Philipp C. Heckel
56231f9288 Merge branch 'main' into patch-1 2025-05-21 20:13:56 -04:00
Philipp C. Heckel
ef7c7c7b09 Merge pull request #1209 from jim3692/patch-1
Add Clipboard IO to projects
2025-05-21 20:10:51 -04:00
Philipp C. Heckel
88e4b8f0e6 Merge branch 'main' into patch-1 2025-05-21 20:10:43 -04:00
Philipp C. Heckel
090bdd93ba Merge pull request #1187 from 13x1/patch-1
Fix typo
2025-05-21 20:09:18 -04:00
Philipp C. Heckel
790044e899 Merge pull request #1240 from thiswillbeyourgithub/integration_daily_fact
docs: add integration: Daily Fact Ntfy
2025-05-21 20:07:00 -04:00
dependabot[bot]
7aab7d387f Bump esbuild, vite and vite-plugin-pwa in /web
Bumps [esbuild](https://github.com/evanw/esbuild) to 0.25.4 and updates ancestor dependencies [esbuild](https://github.com/evanw/esbuild), [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) and [vite-plugin-pwa](https://github.com/vite-pwa/vite-plugin-pwa). These dependencies need to be updated together.


Updates `esbuild` from 0.18.20 to 0.25.4
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG-2023.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.18.20...v0.25.4)

Updates `vite` from 4.5.14 to 6.3.5
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.3.5/packages/vite)

Updates `vite-plugin-pwa` from 0.15.2 to 1.0.0
- [Release notes](https://github.com/vite-pwa/vite-plugin-pwa/releases)
- [Commits](https://github.com/vite-pwa/vite-plugin-pwa/compare/v0.15.2...v1.0.0)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-version: 0.25.4
  dependency-type: indirect
- dependency-name: vite
  dependency-version: 6.3.5
  dependency-type: direct:development
- dependency-name: vite-plugin-pwa
  dependency-version: 1.0.0
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-22 00:04:33 +00:00
binwiederhier
a461aafb91 Merge branch 'main' of github.com:binwiederhier/ntfy 2025-05-21 20:02:19 -04:00
binwiederhier
1569c22a65 Fix some broken links in the docs 2025-05-21 20:02:06 -04:00
Philipp C. Heckel
2091ceb4d2 Merge pull request #1248 from pixitha/patch-1
Update quickstart example
2025-05-21 19:50:40 -04:00
Philipp C. Heckel
ec337b5de9 Merge pull request #1296 from sharjeelaziz/add-bashrc-example
Add Terminal Notifications for Long-Running Commands example
2025-05-21 19:33:54 -04:00
Philipp C. Heckel
5a245f889c Merge pull request #1260 from snex/main
add canary in the cage podcast coverage to integrations page
2025-05-21 19:31:27 -04:00
Philipp C. Heckel
ee595067ba Merge pull request #1307 from jlssmt/patch-1
set LABEL org.opencontainers.image.version
2025-05-21 19:24:43 -04:00
Philipp C. Heckel
bd4b5e9e1b Merge pull request #1312 from vkrause/work/vkrause/detect-encrypted-messages-as-unified-push
Consider aes128gcm content encoding as an indicator for UnifiedPush
2025-05-21 19:16:17 -04:00
Philipp C. Heckel
786e588397 Merge pull request #1325 from gitmotion/add-ntfy-me-mcp-integrations-projects
docs: add integration ntfy-me-mcp to integrations.md
2025-05-21 19:15:02 -04:00
Philipp C. Heckel
2dfb53ec53 Merge branch 'main' into add-ntfy-me-mcp-integrations-projects 2025-05-21 19:14:54 -04:00
Philipp C. Heckel
a30c5eb9cf Merge pull request #1310 from biodrone/biodrone/issue1309
docs: correct mountPath for server.yml
2025-05-21 19:13:51 -04:00
Philipp C. Heckel
d96d4b03c7 Merge pull request #1225 from sedlund/docs/fix_typo
docs: publish.md typo
2025-05-21 19:13:23 -04:00
Philipp C. Heckel
3257ce91ef Merge pull request #1264 from brian6932/patch-1
docs: Typo `wep` -> `web`
2025-05-21 19:12:44 -04:00
Philipp C. Heckel
f563b671c8 Merge pull request #1266 from mmatuska/fix/extractIPAddress
server/util.go: fix logic in extractIPAddress()
2025-05-21 19:11:13 -04:00
Philipp C. Heckel
ea1cda5f92 Merge pull request #1299 from cvilsmeier/main
docs: add integration: Monibot
2025-05-21 19:09:49 -04:00
Philipp C. Heckel
36ba27ba09 Merge pull request #1313 from patricksthannon/patch-1
Update integrations.md
2025-05-21 19:09:06 -04:00
Philipp C. Heckel
60eccba2fa Merge pull request #1320 from yassirh/main
docs: Added reference to UptimeObserver integration
2025-05-21 19:07:14 -04:00
Philipp C. Heckel
aec4b97fae Merge pull request #1319 from therobbielee/therobbielee-patch-1
Update integrations.md
2025-05-21 19:06:18 -04:00
Philipp C. Heckel
389ae682a5 Merge pull request #1341 from binwiederhier/security-updates
Security updates
2025-05-21 19:04:26 -04:00
Tom Hubrecht
44b7c2f198 user: Allow changing the hashed password directly
This adds the detection of `NTFY_PASSWORD_HASH` when creating a user or
changing its passsword so that scripts don't have to manipulate the bare
password.
2025-05-21 16:34:14 +02:00
Hunter Kehoe
cdae5493e2 write http errors to websocket connection instead of always 200 2025-05-14 11:39:18 -06:00
Hunter Kehoe
f110472204 fix typo 2025-05-14 11:20:30 -06:00
Thea Tischbein
03aeb707f2 feat: Add optional web app flag which requires a login for every action 2025-05-05 11:39:53 +02:00
gitmotion
3f1342c05b Add ntfy-me-mcp 2025-04-28 19:25:40 -07:00
Yassir Hannoun
8b95b1a213 docs: Added UptimeObserver integration 2025-04-26 21:13:12 +00:00
Robbie Björk
d4dfd3f657 Update integrations.md
Added alertmanager-ntfy-relay to integrations.md
2025-04-26 00:22:54 +02:00
patricksthannon
c1d718ee68 Update integrations.md
Added InvaderInformant integration to integration list. Thanks!
2025-04-09 09:53:17 -07:00
Volker Krause
bd08a120cd Consider aes128gcm content encoding as an indicator for UnifiedPush
Without this a UnifiedPush/Web Push message with encryption would be
turned into an attachment. That in itself isn't pretty but can still
work, but it requires attachments to be enabled in the first place.
2025-04-07 17:19:44 +02:00
Josh J
c9126e7aa9 docs: correct mountPath for server.yml
Fixed #1309
2025-04-07 09:26:52 +01:00
jlssmt
db9b974e47 set LABEL org.opencontainers.image.version 2025-04-06 11:35:42 +02:00
Christoph Vilsmeier
889a6f03f8 docs: add integration: Monibot 2025-03-25 16:56:47 +01:00
Sharjeel Aziz
6af8d03470 Add Terminal Notifications for Long-Running Commands example
Signed-off-by: Sharjeel Aziz <sharjeel.aziz@gmail.com>
2025-03-19 15:35:46 -04:00
barart
6b2cfb1d1d Handle anonymous read restrictions by sending a poll_request event
If a topic does not allow anonymous reads, this change ensures that we send a "poll_request" event instead of relaying the message via Firebase. Additionally, we include generic text in the title and body/message. This way, if the client cannot retrieve the actual message, the user will still receive a notification, prompting them to update the client manually.
2025-03-05 13:04:21 -06:00
RoboMagus
35458230a8 add major and minor version tags to docker release flow 2025-01-30 23:49:22 +01:00
Martin Matuska
bd39cf4b54 server/util.go: fix logic in extractIPAddress() 2025-01-26 00:00:06 +01:00
Brian
f739a3067e docs: Typo wep -> web 2025-01-24 20:28:29 -05:00
Christian Harke
2344eee2c6 Make markdown code blocks scrollable 2025-01-20 22:13:15 +01:00
David Havlicek
5822a2ec41 add canary in the cage podcast coverage to integrations page 2025-01-17 13:26:22 -08:00
Kyle Duren
6345e7f864 Update quickstart example
Just noticed the behind proxy was missing from the example that was supposed to include it.
2025-01-01 22:08:30 -05:00
thiswillbeyourgithub
80bc600ff0 docs: add integration: Ntfy_CSV_Reminders
Signed-off-by: thiswillbeyourgithub
<26625900+thiswillbeyourgithub@users.noreply.github.com>
2024-12-12 17:55:08 +01:00
thiswillbeyourgithub
758828e7aa docs: add integration: Daily Fact Ntfy
Signed-off-by: thiswillbeyourgithub <26625900+thiswillbeyourgithub@users.noreply.github.com>
2024-12-11 12:50:03 +01:00
thiswillbeyourgithub
4c179b7d9d docs: add ToC to integrations.md
Signed-off-by: thiswillbeyourgithub <26625900+thiswillbeyourgithub@users.noreply.github.com>
2024-12-11 12:40:36 +01:00
David Wronek
27398e7d72 docs: config.md: fix typo
Add a missing parenthesis.

Signed-off-by: David Wronek <david@mainlining.org>
2024-12-10 08:42:59 +01:00
Scott Edlund
19f8a35588 docs: publish.md typo 2024-11-23 13:24:24 +07:00
Dmitry Gudkov
8feb0f1a2e Update integrations.md: Added EasyMorph
EasyMorph (https://easymorph.com) is a visual workflow-based data preparation and automation tool. It has 180+ actions, including a dedicated action to send notifications to ntfy as a workflow step.

The proposed link leads to the official help page for the "Send message to ntfy" action.
2024-11-19 21:28:59 -05:00
Hunter Kehoe
9241b0550c feat: add subscribe param 2024-11-04 21:33:35 -07:00
jim3692
90f21ba408 Add Clipboard IO to projects 2024-10-30 14:48:32 +02:00
Quantum
b843c69c16 docs: add quantum5/ntfy-run to integrations and examples 2024-10-16 22:00:48 -04:00
lexi
903ef71b6f Fix typo
"Firebase (FCM" -> "Firebase (FCM)"
2024-09-22 11:58:26 +02:00
Nogweii
20cca8e888 update go.sum 2024-06-25 22:59:17 -07:00
Nogweii
49a548252c teach ntfy webpush to write the keys to a file 2024-06-25 22:58:36 -07:00
67 changed files with 2899 additions and 791 deletions

1
.gitignore vendored
View File

@@ -15,3 +15,4 @@ node_modules/
__pycache__
web/dev-dist/
venv/
cmd/key-file.yaml

View File

@@ -197,3 +197,15 @@ docker_manifests:
- *arm64v8_image
- *armv7_image
- *armv6_image
- name_template: "binwiederhier/ntfy:v{{ .Major }}"
image_templates:
- *amd64_image
- *arm64v8_image
- *armv7_image
- *armv6_image
- name_template: "binwiederhier/ntfy:v{{ .Major }}.{{ .Minor }}"
image_templates:
- *amd64_image
- *arm64v8_image
- *armv7_image
- *armv6_image

View File

@@ -44,6 +44,8 @@ RUN make VERSION=$VERSION COMMIT=$COMMIT cli-linux-server
FROM alpine
ARG VERSION=dev
LABEL org.opencontainers.image.authors="philipp.heckel@gmail.com"
LABEL org.opencontainers.image.url="https://ntfy.sh/"
LABEL org.opencontainers.image.documentation="https://docs.ntfy.sh/"
@@ -52,6 +54,7 @@ LABEL org.opencontainers.image.vendor="Philipp C. Heckel"
LABEL org.opencontainers.image.licenses="Apache-2.0, GPL-2.0"
LABEL org.opencontainers.image.title="ntfy"
LABEL org.opencontainers.image.description="Send push notifications to your phone or desktop using PUT/POST"
LABEL org.opencontainers.image.version="$VERSION"
COPY --from=builder /app/dist/ntfy_linux_server/ntfy /usr/bin/ntfy

View File

@@ -62,6 +62,7 @@ var flagsServe = append(
altsrc.NewBoolFlag(&cli.BoolFlag{Name: "enable-signup", Aliases: []string{"enable_signup"}, EnvVars: []string{"NTFY_ENABLE_SIGNUP"}, Value: false, Usage: "allows users to sign up via the web app, or API"}),
altsrc.NewBoolFlag(&cli.BoolFlag{Name: "enable-login", Aliases: []string{"enable_login"}, EnvVars: []string{"NTFY_ENABLE_LOGIN"}, Value: false, Usage: "allows users to log in via the web app, or API"}),
altsrc.NewBoolFlag(&cli.BoolFlag{Name: "enable-reservations", Aliases: []string{"enable_reservations"}, EnvVars: []string{"NTFY_ENABLE_RESERVATIONS"}, Value: false, Usage: "allows users to reserve topics (if their tier allows it)"}),
altsrc.NewBoolFlag(&cli.BoolFlag{Name: "require-login", Aliases: []string{"require_login"}, EnvVars: []string{"NTFY_REQUIRE_LOGIN"}, Value: false, Usage: "all actions via the web app requires a login"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "upstream-base-url", Aliases: []string{"upstream_base_url"}, EnvVars: []string{"NTFY_UPSTREAM_BASE_URL"}, Value: "", Usage: "forward poll request to an upstream server, this is needed for iOS push notifications for self-hosted servers"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "upstream-access-token", Aliases: []string{"upstream_access_token"}, EnvVars: []string{"NTFY_UPSTREAM_ACCESS_TOKEN"}, Value: "", Usage: "access token to use for the upstream server; needed only if upstream rate limits are exceeded or upstream server requires auth"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-addr", Aliases: []string{"smtp_sender_addr"}, EnvVars: []string{"NTFY_SMTP_SENDER_ADDR"}, Usage: "SMTP server address (host:port) for outgoing emails"}),
@@ -158,6 +159,7 @@ func execServe(c *cli.Context) error {
webRoot := c.String("web-root")
enableSignup := c.Bool("enable-signup")
enableLogin := c.Bool("enable-login")
requireLogin := c.Bool("require-login")
enableReservations := c.Bool("enable-reservations")
upstreamBaseURL := c.String("upstream-base-url")
upstreamAccessToken := c.String("upstream-access-token")
@@ -407,6 +409,7 @@ func execServe(c *cli.Context) error {
conf.BillingContact = billingContact
conf.EnableSignup = enableSignup
conf.EnableLogin = enableLogin
conf.RequireLogin = requireLogin
conf.EnableReservations = enableReservations
conf.EnableMetrics = enableMetrics
conf.MetricsListenHTTP = metricsListenHTTP

View File

@@ -42,7 +42,7 @@ var cmdUser = &cli.Command{
Name: "add",
Aliases: []string{"a"},
Usage: "Adds a new user",
UsageText: "ntfy user add [--role=admin|user] USERNAME\nNTFY_PASSWORD=... ntfy user add [--role=admin|user] USERNAME",
UsageText: "ntfy user add [--role=admin|user] USERNAME\nNTFY_PASSWORD=... ntfy user add [--role=admin|user] USERNAME\nNTFY_PASSWORD_HASH=... ntfy user add [--role=admin|user] USERNAME",
Action: execUserAdd,
Flags: []cli.Flag{
&cli.StringFlag{Name: "role", Aliases: []string{"r"}, Value: string(user.RoleUser), Usage: "user role"},
@@ -55,12 +55,13 @@ granted otherwise by the auth-default-access setting). An admin user has read an
topics.
Examples:
ntfy user add phil # Add regular user phil
ntfy user add --role=admin phil # Add admin user phil
NTFY_PASSWORD=... ntfy user add phil # Add user, using env variable to set password (for scripts)
ntfy user add phil # Add regular user phil
ntfy user add --role=admin phil # Add admin user phil
NTFY_PASSWORD=... ntfy user add phil # Add user, using env variable to set password (for scripts)
NTFY_PASSWORD_HASH=... ntfy user add phil # Add user, using env variable to set password hash (for scripts)
You may set the NTFY_PASSWORD environment variable to pass the password. This is useful if
you are creating users via scripts.
You may set the NTFY_PASSWORD environment variable to pass the password, or NTFY_PASSWORD_HASH to pass
directly the bcrypt hash. This is useful if you are creating users via scripts.
`,
},
{
@@ -79,7 +80,7 @@ Example:
Name: "change-pass",
Aliases: []string{"chp"},
Usage: "Changes a user's password",
UsageText: "ntfy user change-pass USERNAME\nNTFY_PASSWORD=... ntfy user change-pass USERNAME",
UsageText: "ntfy user change-pass USERNAME\nNTFY_PASSWORD=... ntfy user change-pass USERNAME\nNTFY_PASSWORD_HASH=... ntfy user change-pass USERNAME",
Action: execUserChangePass,
Description: `Change the password for the given user.
@@ -89,9 +90,10 @@ it twice.
Example:
ntfy user change-pass phil
NTFY_PASSWORD=.. ntfy user change-pass phil
NTFY_PASSWORD_HASH=.. ntfy user change-pass phil
You may set the NTFY_PASSWORD environment variable to pass the new password. This is
useful if you are updating users via scripts.
You may set the NTFY_PASSWORD environment variable to pass the new password or NTFY_PASSWORD_HASH to pass
directly the bcrypt hash. This is useful if you are updating users via scripts.
`,
},
@@ -174,7 +176,12 @@ variable to pass the new password. This is useful if you are creating/updating u
func execUserAdd(c *cli.Context) error {
username := c.Args().Get(0)
role := user.Role(c.String("role"))
password := os.Getenv("NTFY_PASSWORD")
password, hashed := os.LookupEnv("NTFY_PASSWORD_HASH")
if !hashed {
password = os.Getenv("NTFY_PASSWORD")
}
if username == "" {
return errors.New("username expected, type 'ntfy user add --help' for help")
} else if username == userEveryone || username == user.Everyone {
@@ -200,7 +207,7 @@ func execUserAdd(c *cli.Context) error {
}
password = p
}
if err := manager.AddUser(username, password, role); err != nil {
if err := manager.AddUser(username, password, role, hashed); err != nil {
return err
}
fmt.Fprintf(c.App.ErrWriter, "user %s added with role %s\n", username, role)
@@ -230,7 +237,11 @@ func execUserDel(c *cli.Context) error {
func execUserChangePass(c *cli.Context) error {
username := c.Args().Get(0)
password := os.Getenv("NTFY_PASSWORD")
password, hashed := os.LookupEnv("NTFY_PASSWORD_HASH")
if !hashed {
password = os.Getenv("NTFY_PASSWORD")
}
if username == "" {
return errors.New("username expected, type 'ntfy user change-pass --help' for help")
} else if username == userEveryone || username == user.Everyone {
@@ -249,7 +260,7 @@ func execUserChangePass(c *cli.Context) error {
return err
}
}
if err := manager.ChangePassword(username, password); err != nil {
if err := manager.ChangePassword(username, password, hashed); err != nil {
return err
}
fmt.Fprintf(c.App.ErrWriter, "changed password for user %s\n", username)

View File

@@ -4,9 +4,16 @@ package cmd
import (
"fmt"
"os"
"github.com/SherClockHolmes/webpush-go"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v2/altsrc"
)
var flagsWebPush = append(
[]cli.Flag{},
altsrc.NewStringFlag(&cli.StringFlag{Name: "output-file", Aliases: []string{"f"}, Usage: "write VAPID keys to this file"}),
)
func init() {
@@ -26,6 +33,7 @@ var cmdWebPush = &cli.Command{
Usage: "Generate VAPID keys to enable browser background push notifications",
UsageText: "ntfy webpush keys",
Category: categoryServer,
Flags: flagsWebPush,
},
},
}
@@ -35,7 +43,19 @@ func generateWebPushKeys(c *cli.Context) error {
if err != nil {
return err
}
_, err = fmt.Fprintf(c.App.ErrWriter, `Web Push keys generated. Add the following lines to your config file:
if outputFile := c.String("output-file"); outputFile != "" {
contents := fmt.Sprintf(`---
web-push-public-key: %s
web-push-private-key: %s
`, publicKey, privateKey)
err = os.WriteFile(outputFile, []byte(contents), 0660)
if err != nil {
return err
}
_, err = fmt.Fprintf(c.App.ErrWriter, "Web Push keys written to %s.\n", outputFile)
} else {
_, err = fmt.Fprintf(c.App.ErrWriter, `Web Push keys generated. Add the following lines to your config file:
web-push-public-key: %s
web-push-private-key: %s
@@ -44,5 +64,6 @@ web-push-email-address: <email address>
See https://ntfy.sh/docs/config/#web-push for details.
`, publicKey, privateKey)
}
return err
}

View File

@@ -14,6 +14,13 @@ func TestCLI_WebPush_GenerateKeys(t *testing.T) {
require.Contains(t, stderr.String(), "Web Push keys generated.")
}
func TestCLI_WebPush_WriteKeysToFile(t *testing.T) {
app, _, _, stderr := newTestApp()
require.Nil(t, runWebPushCommand(app, server.NewConfig(), "keys", "--output-file=key-file.yaml"))
require.Contains(t, stderr.String(), "Web Push keys written to key-file.yaml")
require.FileExists(t, "key-file.yaml")
}
func runWebPushCommand(app *cli.App, conf *server.Config, args ...string) error {
webPushArgs := []string{
"ntfy",

View File

@@ -50,6 +50,7 @@ Here are a few working sample configs using a `/etc/ntfy/server.yml` file:
listen-http: ":2586"
cache-file: "/var/cache/ntfy/cache.db"
attachment-cache-dir: "/var/cache/ntfy/attachments"
behind-proxy: true
```
=== "server.yml (ntfy.sh config)"
@@ -865,7 +866,7 @@ it'll show `New message` as a popup.
## Web Push
[Web Push](https://developer.mozilla.org/en-US/docs/Web/API/Push_API) ([RFC8030](https://datatracker.ietf.org/doc/html/rfc8030))
allows ntfy to receive push notifications, even when the ntfy web app (or even the browser, depending on the platform) is closed.
When enabled, the user can enable **background notifications** for their topics in the wep app under Settings. Once enabled by the
When enabled, the user can enable **background notifications** for their topics in the web app under Settings. Once enabled by the
user, ntfy will forward published messages to the push endpoint (browser-provided, e.g. fcm.googleapis.com), which will then
forward it to the browser.
@@ -1379,10 +1380,10 @@ variable before running the `ntfy` command (e.g. `export NTFY_LISTEN_HTTP=:80`).
| `listen-unix-mode` | `NTFY_LISTEN_UNIX_MODE` | *file mode* | *system default* | File mode of the Unix socket, e.g. 0700 or 0777 |
| `key-file` | `NTFY_KEY_FILE` | *filename* | - | HTTPS/TLS private key file, only used if `listen-https` is set. |
| `cert-file` | `NTFY_CERT_FILE` | *filename* | - | HTTPS/TLS certificate file, only used if `listen-https` is set. |
| `firebase-key-file` | `NTFY_FIREBASE_KEY_FILE` | *filename* | - | If set, also publish messages to a Firebase Cloud Messaging (FCM) topic for your app. This is optional and only required to save battery when using the Android app. See [Firebase (FCM](#firebase-fcm). |
| `firebase-key-file` | `NTFY_FIREBASE_KEY_FILE` | *filename* | - | If set, also publish messages to a Firebase Cloud Messaging (FCM) topic for your app. This is optional and only required to save battery when using the Android app. See [Firebase (FCM)](#firebase-fcm). |
| `cache-file` | `NTFY_CACHE_FILE` | *filename* | - | If set, messages are cached in a local SQLite database instead of only in-memory. This allows for service restarts without losing messages in support of the since= parameter. See [message cache](#message-cache). |
| `cache-duration` | `NTFY_CACHE_DURATION` | *duration* | 12h | Duration for which messages will be buffered before they are deleted. This is required to support the `since=...` and `poll=1` parameter. Set this to `0` to disable the cache entirely. |
| `cache-startup-queries` | `NTFY_CACHE_STARTUP_QUERIES` | *string (SQL queries)* | - | SQL queries to run during database startup; this is useful for tuning and [enabling WAL mode](#wal-for-message-cache) |
| `cache-startup-queries` | `NTFY_CACHE_STARTUP_QUERIES` | *string (SQL queries)* | - | SQL queries to run during database startup; this is useful for tuning and [enabling WAL mode](#message-cache) |
| `cache-batch-size` | `NTFY_CACHE_BATCH_SIZE` | *int* | 0 | Max size of messages to batch together when writing to message cache (if zero, writes are synchronous) |
| `cache-batch-timeout` | `NTFY_CACHE_BATCH_TIMEOUT` | *duration* | 0s | Timeout for batched async writes to the message cache (if zero, writes are synchronous) |
| `auth-file` | `NTFY_AUTH_FILE` | *filename* | - | Auth database file used for access control. If set, enables authentication and access control. See [access control](#access-control). |
@@ -1424,6 +1425,7 @@ variable before running the `ntfy` command (e.g. `export NTFY_LISTEN_HTTP=:80`).
| `enable-signup` | `NTFY_ENABLE_SIGNUP` | *boolean* (`true` or `false`) | `false` | Allows users to sign up via the web app, or API |
| `enable-login` | `NTFY_ENABLE_LOGIN` | *boolean* (`true` or `false`) | `false` | Allows users to log in via the web app, or API |
| `enable-reservations` | `NTFY_ENABLE_RESERVATIONS` | *boolean* (`true` or `false`) | `false` | Allows users to reserve topics (if their tier allows it) |
| `require-login` | `NTFY_REQUIRE_LOGIN` | *boolean* (`true` or `false`) | `false` | All actions via the web app require a login |
| `stripe-secret-key` | `NTFY_STRIPE_SECRET_KEY` | *string* | - | Payments: Key used for the Stripe API communication, this enables payments |
| `stripe-webhook-key` | `NTFY_STRIPE_WEBHOOK_KEY` | *string* | - | Payments: Key required to validate the authenticity of incoming webhooks from Stripe |
| `billing-contact` | `NTFY_BILLING_CONTACT` | *email address* or *website* | - | Payments: Email or website displayed in Upgrade dialog as a billing contact |

View File

@@ -384,7 +384,7 @@ strictly based off of my development on this app. There may be other versions of
### Apple setup
!!! info
Along with this step, the [PLIST Deployment](#plist-deployment-and-configuration) step is also required
Along with this step, the [PLIST Deployment](#plist-config) step is also required
for these changes to take effect in the iOS app.
1. [Create a new key in Apple Developer Member Center](https://developer.apple.com/account/resources/authkeys/add)

View File

@@ -31,6 +31,12 @@ GitHub have been hopeless. In case it ever becomes available, I want to know imm
*/6 * * * * if curl -s https://api.github.com/users/ntfy | grep "Not Found"; then curl -d "github.com/ntfy is available" -H "Tags: tada" -H "Prio: high" ntfy.sh/my-alerts; fi
```
You can also use [`ntfy-run`](https://github.com/quantum5/ntfy-run) to send the output of your cronjob in the
notification, so that you know exactly why it failed:
```
0 0 * * * ntfy-run -n https://ntfy.sh/backups --success-priority low --failure-tags warning ~/backup-computer
```
## Low disk space alerts
Here's a simple cronjob that I use to alert me when the disk space on the root disk is running low. It's simple, but
@@ -634,3 +640,56 @@ or by simply providing traccar with a valid username/password combination.
<entry key='sms.http.user'>phil</entry>
<entry key='sms.http.password'>mypass</entry>
```
## Terminal Notifications for Long-Running Commands
This example provides a simple way to send notifications using [ntfy.sh](https://ntfy.sh) when a terminal command completes. It includes success or failure indicators based on the command's exit status.
Store your ntfy.sh bearer token securely if access control is enabled:
```sh
echo "your_bearer_token_here" > ~/.ntfy_token
chmod 600 ~/.ntfy_token
```
Add the following function and alias to your `.bashrc` or `.bash_profile`:
```sh
# Function for alert notifications using ntfy.sh
notify_via_ntfy() {
local exit_status=$? # Capture the exit status before doing anything else
local token=$(< ~/.ntfy_token) # Securely read the token
local status_icon="$([ $exit_status -eq 0 ] && echo magic_wand || echo warning)"
local last_command=$(history | tail -n1 | sed -e 's/^[[:space:]]*[0-9]\{1,\}[[:space:]]*//' -e 's/[;&|][[:space:]]*alert$//')
curl -s -X POST "https://n.example.dev/alerts" \
-H "Authorization: Bearer $token" \
-H "Title: Terminal" \
-H "X-Priority: 3" \
-H "Tags: $status_icon" \
-d "Command: $last_command (Exit: $exit_status)"
echo "Tags: $status_icon"
echo "$last_command (Exit: $exit_status)"
}
# Add an "alert" alias for long running commands using ntfy.sh
alias alert='notify_via_ntfy'
```
Now you can run any long-running command and append `alert` to notify when it completes:
```sh
sleep 10; alert
```
![ntfy notifications on mobile device](static/img/mobile-screenshot-notification.png)
**Notification Sent** with a success 🪄 (`magic_wand`) or failure ⚠️ (`warning`) tag.
To test failure notifications:
```sh
false; alert # Always fails (exit 1)
ls --invalid; alert # Invalid option
cat nonexistent_file; alert # File not found
```

View File

@@ -540,7 +540,7 @@ kubectl apply -k /ntfy
cpu: 150m
memory: 150Mi
volumeMounts:
- mountPath: /etc/ntfy/server.yml
- mountPath: /etc/ntfy
subPath: server.yml
name: config-volume # generated vie configMapGenerator from kustomization file
- mountPath: /var/cache/ntfy

View File

@@ -4,9 +4,21 @@ There are quite a few projects that work with ntfy, integrate ntfy, or have been
I've added a ⭐ to projects or posts that have a significant following, or had a lot of interaction by the community.
## Table of Contents
- [Official integrations](#official-integrations)
- [Integration via HTTP/SMTP/etc.](#integration-via-httpsmtpetc)
- [UnifiedPush integrations](#unifiedpush-integrations)
- [Libraries](#libraries)
- [CLIs + GUIs](#clis-guis)
- [Projects + scripts](#projects-scripts)
- [Blog + forum posts](#blog-forum-posts)
- [Alternative ntfy servers](#alternative-ntfy-servers)
## Official integrations
- [changedetection.io](https://changedetection.io) ⭐ - Website change detection and notification
- [Home Assistant](https://www.home-assistant.io/integrations/ntfy) ⭐ - Home Assistant is an open-source platform for automating and controlling smart home devices.
- [Healthchecks.io](https://healthchecks.io/) ⭐ - Online service for monitoring regularly running tasks such as cron jobs
- [Apprise](https://github.com/caronc/apprise/wiki/Notify_ntfy) ⭐ - Push notifications that work with just about every platform
- [Uptime Kuma](https://uptime.kuma.pet/) ⭐ - A self-hosted monitoring tool
@@ -26,6 +38,8 @@ I've added a ⭐ to projects or posts that have a significant following, or had
- [Cloudron](https://www.cloudron.io/store/sh.ntfy.cloudronapp.html) - Platform that makes it easy to manage web apps on your server
- [Xitoring](https://xitoring.com/docs/notifications/notification-roles/ntfy/) - Server and Uptime monitoring
- [HetrixTools](https://docs.hetrixtools.com/ntfy-sh-notifications/) - Uptime monitoring
- [EasyMorph](https://help.easymorph.com/doku.php?id=transformations:sendntfymessage) - Visual data transformation and automation tool
- [Monibot](https://monibot.io/) - Monibot monitors your websites, servers and applications and notifies you if something goes wrong.
## Integration via HTTP/SMTP/etc.
@@ -36,6 +50,7 @@ I've added a ⭐ to projects or posts that have a significant following, or had
- [Mailrise](https://github.com/YoRyan/mailrise) - An SMTP gateway (integration via [Apprise](https://github.com/caronc/apprise/wiki/Notify_ntfy))
- [Proxmox-Ntfy](https://github.com/qtsone/proxmox-ntfy) - Python script that monitors Proxmox tasks and sends notifications using the Ntfy service.
- [Scrutiny](https://github.com/AnalogJ/scrutiny) - WebUI for smartd S.M.A.R.T monitoring. Scrutiny includes shoutrrr/ntfy integration ([see integration README](https://github.com/AnalogJ/scrutiny?tab=readme-ov-file#notifications))
- [UptimeObserver](https://uptimeobserver.com) - Uptime Monitoring tool for Websites, APIs, SSL Certificates, DNS, Domain Names and Ports. [Integration Guide](https://support.uptimeobserver.com/integrations/ntfy/)
## [UnifiedPush](https://unifiedpush.org/users/apps/) integrations
@@ -63,6 +78,7 @@ I've added a ⭐ to projects or posts that have a significant following, or had
- [gotfy](https://github.com/AnthonyHewins/gotfy) - A Go wrapper for the ntfy API (Go)
- [symfony/ntfy-notifier](https://symfony.com/components/NtfyNotifier) ⭐ - Symfony Notifier integration for ntfy (PHP)
- [ntfy-java](https://github.com/MaheshBabu11/ntfy-java/) - A Java package to interact with a ntfy server (Java)
- [aiontfy](https://github.com/tr4nt0r/aiontfy) - Asynchronous client library for publishing and subscribing to ntfy (Python)
## CLIs + GUIs
@@ -74,6 +90,8 @@ I've added a ⭐ to projects or posts that have a significant following, or had
- [ntfyr](https://github.com/haxwithaxe/ntfyr) - A simple commandline tool to send notifications to ntfy
- [ntfy.py](https://github.com/ioqy/ntfy-client-python) - ntfy.py is a simple nfty.sh client for sending notifications
- [wlzntfy](https://github.com/Walzen-Group/ntfy-toaster) - A minimalistic, receive-only toast notification client for Windows 11
- [Ntfy_CSV_Reminders](https://github.com/thiswillbeyourgithub/Ntfy_CSV_Reminders) - A Python tool that sends random-timing phone notifications for recurring tasks by using daily probability checks based on CSV-defined frequencies.
- [Daily Fact Ntfy](https://github.com/thiswillbeyourgithub/Daily_Fact_Ntfy) - Generate [llm](https://github.com/simonw/llm) generated fact every day about any topic you're interested in.
## Projects + scripts
@@ -82,6 +100,7 @@ I've added a ⭐ to projects or posts that have a significant following, or had
- [Grafana-to-ntfy](https://gitlab.com/Saibe1111/grafana-to-ntfy) - Grafana-to-ntfy alerts channel (Node Js)
- [ntfy-long-zsh-command](https://github.com/robfox92/ntfy-long-zsh-command) - Notifies you once a long-running command completes (zsh)
- [ntfy-shellscripts](https://github.com/nickexyz/ntfy-shellscripts) - A few scripts for the ntfy project (Shell)
- [alertmanager-ntfy-relay](https://github.com/therobbielee/alertmanager-ntfy-relay) - ntfy.sh relay for Alertmanager (Go)
- [QuickStatus](https://github.com/corneliusroot/QuickStatus) - A shell script to alert to any immediate problems upon login (Shell)
- [ntfy.el](https://github.com/shombando/ntfy) - Send notifications from Emacs (Emacs)
- [backup-projects](https://gist.github.com/anthonyaxenov/826ba65abbabd5b00196bc3e6af76002) - Stupidly simple backup script for own projects (Shell)
@@ -146,6 +165,11 @@ I've added a ⭐ to projects or posts that have a significant following, or had
- [ntfy-java](https://github.com/MaheshBabu11/ntfy-java/) - A Java package to interact with a ntfy server (Java)
- [container-update-check](https://github.com/stendler/container-update-check) - Scripts to check and notify if a podman or docker container image can be updated (Podman/Shell)
- [ignition-combustion-template](https://github.com/stendler/ignition-combustion-template) - Templates and scripts to generate a configuration to automatically setup a system on first boot. Including systemd-ntfy-poweronoff (Shell)
- [ntfy-run](https://github.com/quantum5/ntfy-run) - Tool to run a command, capture its output, and send it to ntfy (Rust)
- [Clipboard IO](https://github.com/jim3692/clipboard-io) - End to end encrypted clipboard
- [ntfy-me-mcp](https://github.com/gitmotion/ntfy-me-mcp) - An ntfy MCP server for sending/fetching ntfy notifications to your self-hosted ntfy server from AI Agents (supports secure token auth & more - use with npx or docker!) (Node/Typescript)
- [InvaderInformant](https://github.com/patricksthannon/InvaderInformant) - Script for Mac OS systems that monitors new or dropped connections to your network using ntfy (Shell)
- [NtfyPwsh](https://github.com/ptmorris1/NtfyPwsh) - PowerShell module to help send messages to ntfy (PowerShell)
## Blog + forum posts
@@ -246,6 +270,8 @@ I've added a ⭐ to projects or posts that have a significant following, or had
- [ntfy otro sistema de notificaciones pub-sub simple basado en HTTP](https://ugeek.github.io/blog/post/2021-11-05-ntfy-sh-otro-sistema-de-notificaciones-pub-sub-simple-basado-en-http.html) - ugeek.github.io - 11/2021
- [Show HN: A tool to send push notifications to your phone, written in Go](https://news.ycombinator.com/item?id=29715464) ⭐ - news.ycombinator.com - 12/2021
- [Reddit selfhostable post](https://www.reddit.com/r/selfhosted/comments/qxlsm9/my_open_source_notification_android_app_and/) ⭐ - reddit.com - 11/2021
- [ntfy on The Canary in the Cage Podcast](https://odysee.com/@TheCanaryInTheCage:b/The-Canary-in-the-Cage-Episode-42:1?r=4gitYjTacQqPEjf22874USecDQYJ5y5E&t=3062) - odysee.com - 1/2025
- [NtfyPwsh - A PowerShell Module to Send Ntfy Messages](https://ptmorris1.github.io/posts/NtfyPwsh/) - github.io - 5/2025
## Alternative ntfy servers

View File

@@ -1007,7 +1007,7 @@ Here's an **easier example with a shorter JSON payload**:
=== "Command line (curl)"
```
# To use { and } in the URL without encoding, we need to turn of
# To use { and } in the URL without encoding, we need to turn off
# curl's globbing using --globoff
curl \
@@ -1243,7 +1243,7 @@ all the supported fields:
| `priority` | - | *int (one of: 1, 2, 3, 4, or 5)* | `4` | Message [priority](#message-priority) with 1=min, 3=default and 5=max |
| `actions` | - | *JSON array* | *(see [action buttons](#action-buttons))* | Custom [user action buttons](#action-buttons) for notifications |
| `click` | - | *URL* | `https://example.com` | Website opened when notification is [clicked](#click-action) |
| `attach` | - | *URL* | `https://example.com/file.jpg` | URL of an attachment, see [attach via URL](#attach-file-from-url) |
| `attach` | - | *URL* | `https://example.com/file.jpg` | URL of an attachment, see [attach via URL](#attach-file-from-a-url) |
| `markdown` | - | *bool* | `true` | Set to true if the `message` is Markdown-formatted |
| `icon` | - | *string* | `https://example.com/icon.png` | URL to use as notification [icon](#icons) |
| `filename` | - | *string* | `file.jpg` | File name of the attachment |
@@ -3094,7 +3094,7 @@ may be read/write protected so that only users with the correct credentials can
To publish/subscribe to protected topics, you can:
* Use [username & password](#username-password) via Basic auth, e.g. `Authorization: Basic dGVzdHVzZXI6ZmFrZXBhc3N3b3Jk`
* Use [access tokens](#bearer-auth) via Bearer/Basic auth, e.g. `Authorization: Bearer tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2`
* Use [access tokens](#access-tokens) via Bearer/Basic auth, e.g. `Authorization: Bearer tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2`
* or use either with the [`auth` query parameter](#query-param), e.g. `?auth=QmFzaWMgZEdWemRIVnpaWEk2Wm1GclpYQmhjM04zYjNKaw`
!!! warning

View File

@@ -689,7 +689,7 @@ minute or so, due to competing stats gathering (personal installations will like
**Features:**
* Add `cache-startup-queries` option to allow custom [SQLite performance tuning](config.md#wal-for-message-cache) (no ticket)
* Add `cache-startup-queries` option to allow custom [SQLite performance tuning](config.md#message-cache) (no ticket)
* ntfy CLI can now [wait for a command or PID](subscribe/cli.md#wait-for-pidcommand) before publishing ([#263](https://github.com/binwiederhier/ntfy/issues/263), thanks to the [original ntfy](https://github.com/dschep/ntfy) for the idea)
* Trace: Log entire HTTP request to simplify debugging (no ticket)
* Allow setting user password via `NTFY_PASSWORD` env variable ([#327](https://github.com/binwiederhier/ntfy/pull/327), thanks to [@Kenix3](https://github.com/Kenix3))
@@ -1378,19 +1378,48 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release
**Features:**
* Add username/password auth to email publishing ([#1164](https://github.com/binwiederhier/ntfy/pull/1164), thanks to [@bishtawi](https://github.com/bishtawi))
* Write VAPID keys to file in `ntfy webpush --output-file` ([#1138](https://github.com/binwiederhier/ntfy/pull/1138), thanks to [@nogweii](https://github.com/nogweii))
* Add Docker major/minor version to image tags ([#1271](https://github.com/binwiederhier/ntfy/pull/1271), thanks to [@RoboMagus](https://github.com/RoboMagus))
* Add `latest` subscription param for grabbing just the most recent message ([#1216](https://github.com/binwiederhier/ntfy/pull/1216), thanks to [@wunter8](https://github.com/wunter8))
* Allow using `NTFY_PASSWORD_HASH` in `ntfy user` command instead of raw password ([#1340](https://github.com/binwiederhier/ntfy/pull/1340), thanks to [@wunter8](https://github.com/wunter8) for implementing)
* You can now change passwords via `v1/users` API ([#1267](https://github.com/binwiederhier/ntfy/pull/1267), thanks to [@wunter8](https://github.com/wunter8) for implementing)
**Bug fixes + maintenance:**
* Add `Date` header to outgoing emails to avoid rejection ([#1141](https://github.com/binwiederhier/ntfy/pull/1141), thanks to [pcouy](https://github.com/pcouy))
* Security updates for dependencies and Docker images ([#1341](https://github.com/binwiederhier/ntfy/pull/1341))
* Upgrade to Vite 6 ([#1342](https://github.com/binwiederhier/ntfy/pull/1342), thanks Dependabot)
* Fix iOS delivery issues for read-protected topics ([#1207](https://github.com/binwiederhier/ntfy/pull/1287), thanks a lot to [@barart](https://github.com/barart)!)
* Add `Date` header to outgoing emails to avoid rejection ([#1141](https://github.com/binwiederhier/ntfy/pull/1141), thanks to [@pcouy](https://github.com/pcouy))
* Fix IP address parsing when behind a proxy ([#1266](https://github.com/binwiederhier/ntfy/pull/1266), thanks to [@mmatuska](https://github.com/mmatuska))
* Make sure UnifiedPush messages are not treated as attachments ([#1312](https://github.com/binwiederhier/ntfy/pull/1312), thanks to [@vkrause](https://github.com/vkrause))
* Add OCI image version to Docker image ([#1307](https://github.com/binwiederhier/ntfy/pull/1307), thanks to [@jlssmt](https://github.com/jlssmt))
* WebSocket returning incorrect HTTP error code ([#1338](https://github.com/binwiederhier/ntfy/pull/1338) / [#1337](https://github.com/binwiederhier/ntfy/pull/1337), thanks to [@wunter8](https://github.com/wunter8) for debugging and implementing)
* Make Markdown in the web app scrollable horizontally ([#1262](https://github.com/binwiederhier/ntfy/pull/1262), thanks to [@rake5k](https://github.com/rake5k) for fixing)
**Documentation:**
* Lots of new integrations and projects. Amazing!
* [ntfy-me-mcp](https://github.com/gitmotion/ntfy-me-mcp)
* [UptimeObserver](https://uptimeobserver.com)
* [alertmanager-ntfy-relay](https://github.com/therobbielee/alertmanager-ntfy-relay)
* [Monibot](https://monibot.io/)
* [Daily_Fact_Ntfy](https://github.com/thiswillbeyourgithub/Daily_Fact_Ntfy)
* [EasyMorph](https://help.easymorph.com/doku.php?id=transformations:sendntfymessage)
* [ntfy-run](https://github.com/quantum5/ntfy-run)
* [Clipboard IO](https://github.com/jim3692/clipboard-io)
* [ntfy-me-mcp](https://github.com/gitmotion/ntfy-me-mcp)
* [InvaderInformant](https://github.com/patricksthannon/InvaderInformant)
* Various docs updates ([#1161](https://github.com/binwiederhier/ntfy/pull/1161), thanks to [@OneWeekNotice](https://github.com/OneWeekNotice))
* Typo in config docs ([#1177](https://github.com/binwiederhier/ntfy/pull/1177), thanks to [@hoho4190](https://github.com/hoho4190))
* Typo in CLI docs ([#1172](https://github.com/binwiederhier/ntfy/pull/1172), thanks to [@anirvan](https://github.com/anirvan))
* Correction about MacroDroid ([#1137](https://github.com/binwiederhier/ntfy/pull/1137), thanks to [@ShlomoCode](https://github.com/ShlomoCode))
* Note about fail2ban in Docker ([#1175](https://github.com/binwiederhier/ntfy/pull/1175)), thanks to [@Measurity](https://github.com/Measurity))
* Lots of other tiny docs updates, tanks to everyone who contributed!
**Languages**
* Update new languages from Weblate. Thanks to all the contributors!
* Added Tamil (தமிழ்) as a new language to the web app
### ntfy Android app v1.16.1 (UNRELEASED)

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

View File

@@ -132,7 +132,7 @@ easy to use. Here's what it looks like. You may also want to check out the [full
### Subscribe as raw stream
The `/raw` endpoint will output one line per message, and **will only include the message body**. It's useful for extremely
simple scripts, and doesn't include all the data. Additional fields such as [priority](../publish.md#message-priority),
[tags](../publish.md#tags--emojis--) or [message title](../publish.md#message-title) are not included in this output
[tags](../publish.md#tags-emojis) or [message title](../publish.md#message-title) are not included in this output
format. Keepalive messages are sent as empty lines.
=== "Command line (curl)"
@@ -257,6 +257,14 @@ curl -s "ntfy.sh/mytopic/json?since=1645970742"
curl -s "ntfy.sh/mytopic/json?since=nFS3knfcQ1xe"
```
### Fetch latest message
If you only want the most recent message sent to a topic and do not have a message ID or timestamp to use with
`since=`, you can use `since=latest` to grab the most recent message from the cache for a particular topic.
```
curl -s "ntfy.sh/mytopic/json?poll=1&since=latest"
```
### Fetch scheduled messages
Messages that are [scheduled to be delivered](../publish.md#scheduled-delivery) at a later date are not typically
returned when subscribing via the API, which makes sense, because after all, the messages have technically not been
@@ -305,7 +313,7 @@ Depending on whether the server is configured to support [access control](../con
may be read/write protected so that only users with the correct credentials can subscribe or publish to them.
To publish/subscribe to protected topics, you can:
* Use [basic auth](../publish.md#basic-auth), e.g. `Authorization: Basic dGVzdHVzZXI6ZmFrZXBhc3N3b3Jk`
* Use [basic auth](../publish.md#authentication), e.g. `Authorization: Basic dGVzdHVzZXI6ZmFrZXBhc3N3b3Jk`
* or use the [`auth` query parameter](../publish.md#query-param), e.g. `?auth=QmFzaWMgZEdWemRIVnpaWEk2Wm1GclpYQmhjM04zYjNKaw`
Please refer to the [publishing documentation](../publish.md#authentication) for additional details.

6
go.mod
View File

@@ -83,9 +83,9 @@ require (
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
github.com/zeebo/errs v1.4.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.35.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
go.opentelemetry.io/otel v1.36.0 // indirect
go.opentelemetry.io/otel/metric v1.36.0 // indirect
go.opentelemetry.io/otel/sdk v1.36.0 // indirect

12
go.sum
View File

@@ -157,12 +157,12 @@ github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=
github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/detectors/gcp v1.35.0 h1:bGvFt68+KTiAKFlacHW6AhA56GF2rS0bdD3aJYEnmzA=
go.opentelemetry.io/contrib/detectors/gcp v1.35.0/go.mod h1:qGWP8/+ILwMRIUf9uIVLloR1uo5ZYAslM4O6OqUi1DA=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw=
go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.35.0 h1:PB3Zrjs1sG1GBX51SXyTSoOTqcDglmsk7nT6tkKPb/k=

View File

@@ -23,7 +23,7 @@ If you want to chat, simply join the Discord server (https://discord.gg/cT7ECsZj
the Matrix room (https://matrix.to/#/#ntfy:matrix.org).
ntfy %s (%s), runtime %s, built at %s
Copyright (C) Philipp C. Heckel, licensed under Apache License 2.0 & GPLv2
Copyright (C) Philipp C. Heckel, licensed under Apache License 2.0 & GPLv2
`, version, commit[:7], runtime.Version(), date)
app := cmd.New()

View File

@@ -150,6 +150,7 @@ type Config struct {
BillingContact string
EnableSignup bool // Enable creation of accounts via API and UI
EnableLogin bool
RequireLogin bool
EnableReservations bool // Allow users with role "user" to own/reserve topics
EnableMetrics bool
AccessControlAllowOrigin string // CORS header field to restrict access from web clients
@@ -240,6 +241,7 @@ func NewConfig() *Config {
EnableSignup: false,
EnableLogin: false,
EnableReservations: false,
RequireLogin: false,
AccessControlAllowOrigin: "*",
Version: "",
WebPushPrivateKey: "",

View File

@@ -99,6 +99,13 @@ const (
WHERE topic = ? AND (id > ? OR published = 0)
ORDER BY time, id
`
selectMessagesLatestQuery = `
SELECT mid, time, expires, topic, message, title, priority, tags, click, icon, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, user, content_type, encoding
FROM messages
WHERE topic = ? AND published = 1
ORDER BY time DESC, id DESC
LIMIT 1
`
selectMessagesDueQuery = `
SELECT mid, time, expires, topic, message, title, priority, tags, click, icon, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, user, content_type, encoding
FROM messages
@@ -416,6 +423,8 @@ func (c *messageCache) addMessages(ms []*message) error {
func (c *messageCache) Messages(topic string, since sinceMarker, scheduled bool) ([]*message, error) {
if since.IsNone() {
return make([]*message, 0), nil
} else if since.IsLatest() {
return c.messagesLatest(topic)
} else if since.IsID() {
return c.messagesSinceID(topic, since, scheduled)
}
@@ -462,6 +471,14 @@ func (c *messageCache) messagesSinceID(topic string, since sinceMarker, schedule
return readMessages(rows)
}
func (c *messageCache) messagesLatest(topic string) ([]*message, error) {
rows, err := c.db.Query(selectMessagesLatestQuery, topic)
if err != nil {
return nil, err
}
return readMessages(rows)
}
func (c *messageCache) MessagesDue() ([]*message, error) {
rows, err := c.db.Query(selectMessagesDueQuery, time.Now().Unix())
if err != nil {

View File

@@ -66,6 +66,11 @@ func testCacheMessages(t *testing.T, c *messageCache) {
require.Equal(t, 1, len(messages))
require.Equal(t, "my other message", messages[0].Message)
// mytopic: latest
messages, _ = c.Messages("mytopic", sinceLatestMessage, false)
require.Equal(t, 1, len(messages))
require.Equal(t, "my other message", messages[0].Message)
// example: count
counts, err = c.MessageCounts()
require.Nil(t, err)

View File

@@ -413,7 +413,8 @@ func (s *Server) handleError(w http.ResponseWriter, r *http.Request, v *visitor,
} else {
ev.Info("WebSocket error: %s", err.Error())
}
return // Do not attempt to write to upgraded connection
w.WriteHeader(httpErr.HTTPCode)
return // Do not attempt to write any body to upgraded connection
}
if isNormalError {
ev.Debug("Connection closed with HTTP %d (ntfy error %d)", httpErr.HTTPCode, httpErr.Code)
@@ -445,8 +446,10 @@ func (s *Server) handleInternal(w http.ResponseWriter, r *http.Request, v *visit
return s.ensureWebPushEnabled(s.handleWebManifest)(w, r, v)
} else if r.Method == http.MethodGet && r.URL.Path == apiUsersPath {
return s.ensureAdmin(s.handleUsersGet)(w, r, v)
} else if r.Method == http.MethodPut && r.URL.Path == apiUsersPath {
} else if r.Method == http.MethodPost && r.URL.Path == apiUsersPath {
return s.ensureAdmin(s.handleUsersAdd)(w, r, v)
} else if r.Method == http.MethodPut && r.URL.Path == apiUsersPath {
return s.ensureAdmin(s.handleUsersUpdate)(w, r, v)
} else if r.Method == http.MethodDelete && r.URL.Path == apiUsersPath {
return s.ensureAdmin(s.handleUsersDelete)(w, r, v)
} else if (r.Method == http.MethodPut || r.Method == http.MethodPost) && r.URL.Path == apiUsersAccessPath {
@@ -583,6 +586,7 @@ func (s *Server) handleWebConfig(w http.ResponseWriter, _ *http.Request, _ *visi
EnableCalls: s.config.TwilioAccount != "",
EnableEmails: s.config.SMTPSenderFrom != "",
EnableReservations: s.config.EnableReservations,
RequireLogin: s.config.RequireLogin,
EnableWebPush: s.config.WebPushPublicKey != "",
BillingContact: s.config.BillingContact,
WebPushPublicKey: s.config.WebPushPublicKey,
@@ -1025,7 +1029,8 @@ func (s *Server) parsePublishParams(r *http.Request, m *message) (cache bool, fi
}
template = readBoolParam(r, false, "x-template", "template", "tpl")
unifiedpush = readBoolParam(r, false, "x-unifiedpush", "unifiedpush", "up") // see GET too!
if unifiedpush {
contentEncoding := readParam(r, "content-encoding")
if unifiedpush || contentEncoding == "aes128gcm" {
firebase = false
unifiedpush = true
}
@@ -1556,8 +1561,8 @@ func (s *Server) sendOldMessages(topics []*topic, since sinceMarker, scheduled b
// parseSince returns a timestamp identifying the time span from which cached messages should be received.
//
// Values in the "since=..." parameter can be either a unix timestamp or a duration (e.g. 12h), or
// "all" for all messages.
// Values in the "since=..." parameter can be either a unix timestamp or a duration (e.g. 12h),
// "all" for all messages, or "latest" for the most recent message for a topic
func parseSince(r *http.Request, poll bool) (sinceMarker, error) {
since := readParam(r, "x-since", "since", "si")
@@ -1569,6 +1574,8 @@ func parseSince(r *http.Request, poll bool) (sinceMarker, error) {
return sinceNoMessages, nil
} else if since == "all" {
return sinceAllMessages, nil
} else if since == "latest" {
return sinceLatestMessage, nil
} else if since == "none" {
return sinceNoMessages, nil
}
@@ -1885,14 +1892,14 @@ func (s *Server) transformMatrixJSON(next handleFunc) handleFunc {
}
func (s *Server) authorizeTopicWrite(next handleFunc) handleFunc {
return s.autorizeTopic(next, user.PermissionWrite)
return s.authorizeTopic(next, user.PermissionWrite)
}
func (s *Server) authorizeTopicRead(next handleFunc) handleFunc {
return s.autorizeTopic(next, user.PermissionRead)
return s.authorizeTopic(next, user.PermissionRead)
}
func (s *Server) autorizeTopic(next handleFunc, perm user.Permission) handleFunc {
func (s *Server) authorizeTopic(next handleFunc, perm user.Permission) handleFunc {
return func(w http.ResponseWriter, r *http.Request, v *visitor) error {
if s.userManager == nil {
return next(w, r, v)

View File

@@ -214,10 +214,12 @@
# - enable-signup allows users to sign up via the web app, or API
# - enable-login allows users to log in via the web app, or API
# - enable-reservations allows users to reserve topics (if their tier allows it)
# - require-login all user actions via the web app require a login
#
# enable-signup: false
# enable-login: false
# enable-reservations: false
# require-login: false
# Server URL of a Firebase/APNS-connected ntfy server (likely "https://ntfy.sh").
#

View File

@@ -37,7 +37,7 @@ func (s *Server) handleAccountCreate(w http.ResponseWriter, r *http.Request, v *
return errHTTPConflictUserExists
}
logvr(v, r).Tag(tagAccount).Field("user_name", newAccount.Username).Info("Creating user %s", newAccount.Username)
if err := s.userManager.AddUser(newAccount.Username, newAccount.Password, user.RoleUser); err != nil {
if err := s.userManager.AddUser(newAccount.Username, newAccount.Password, user.RoleUser, false); err != nil {
if errors.Is(err, user.ErrInvalidArgument) {
return errHTTPBadRequestInvalidUsername
}
@@ -207,7 +207,7 @@ func (s *Server) handleAccountPasswordChange(w http.ResponseWriter, r *http.Requ
return errHTTPBadRequestIncorrectPasswordConfirmation
}
logvr(v, r).Tag(tagAccount).Debug("Changing password for user %s", u.Name)
if err := s.userManager.ChangePassword(u.Name, req.NewPassword); err != nil {
if err := s.userManager.ChangePassword(u.Name, req.NewPassword, false); err != nil {
return err
}
return s.writeJSON(w, newSuccessResponse())

View File

@@ -87,9 +87,9 @@ func TestAccount_Signup_AsUser(t *testing.T) {
defer s.closeDatabases()
log.Info("1")
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin, false))
log.Info("2")
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser))
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser, false))
log.Info("3")
rr := request(t, s, "POST", "/v1/account", `{"username":"emma", "password":"emma"}`, map[string]string{
"Authorization": util.BasicAuth("phil", "phil"),
@@ -174,7 +174,7 @@ func TestAccount_ChangeSettings(t *testing.T) {
s := newTestServer(t, newTestConfigWithAuthFile(t))
defer s.closeDatabases()
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
u, _ := s.userManager.User("phil")
token, _ := s.userManager.CreateToken(u.ID, "", time.Unix(0, 0), netip.IPv4Unspecified())
@@ -203,7 +203,7 @@ func TestAccount_Subscription_AddUpdateDelete(t *testing.T) {
s := newTestServer(t, newTestConfigWithAuthFile(t))
defer s.closeDatabases()
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
rr := request(t, s, "POST", "/v1/account/subscription", `{"base_url": "http://abc.com", "topic": "def"}`, map[string]string{
"Authorization": util.BasicAuth("phil", "phil"),
@@ -254,7 +254,7 @@ func TestAccount_ChangePassword(t *testing.T) {
s := newTestServer(t, newTestConfigWithAuthFile(t))
defer s.closeDatabases()
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
rr := request(t, s, "POST", "/v1/account/password", `{"password": "WRONG", "new_password": ""}`, map[string]string{
"Authorization": util.BasicAuth("phil", "phil"),
@@ -296,7 +296,7 @@ func TestAccount_ExtendToken(t *testing.T) {
s := newTestServer(t, newTestConfigWithAuthFile(t))
defer s.closeDatabases()
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
rr := request(t, s, "POST", "/v1/account/token", "", map[string]string{
"Authorization": util.BasicAuth("phil", "phil"),
@@ -332,7 +332,7 @@ func TestAccount_ExtendToken_NoTokenProvided(t *testing.T) {
s := newTestServer(t, newTestConfigWithAuthFile(t))
defer s.closeDatabases()
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
rr := request(t, s, "PATCH", "/v1/account/token", "", map[string]string{
"Authorization": util.BasicAuth("phil", "phil"), // Not Bearer!
@@ -345,7 +345,7 @@ func TestAccount_DeleteToken(t *testing.T) {
s := newTestServer(t, newTestConfigWithAuthFile(t))
defer s.closeDatabases()
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
rr := request(t, s, "POST", "/v1/account/token", "", map[string]string{
"Authorization": util.BasicAuth("phil", "phil"),
@@ -455,14 +455,14 @@ func TestAccount_Reservation_AddAdminSuccess(t *testing.T) {
Code: "pro",
ReservationLimit: 2,
}))
require.Nil(t, s.userManager.AddUser("noadmin1", "pass", user.RoleUser))
require.Nil(t, s.userManager.AddUser("noadmin1", "pass", user.RoleUser, false))
require.Nil(t, s.userManager.ChangeTier("noadmin1", "pro"))
require.Nil(t, s.userManager.AddReservation("noadmin1", "mytopic", user.PermissionDenyAll))
require.Nil(t, s.userManager.AddUser("noadmin2", "pass", user.RoleUser))
require.Nil(t, s.userManager.AddUser("noadmin2", "pass", user.RoleUser, false))
require.Nil(t, s.userManager.ChangeTier("noadmin2", "pro"))
require.Nil(t, s.userManager.AddUser("phil", "adminpass", user.RoleAdmin))
require.Nil(t, s.userManager.AddUser("phil", "adminpass", user.RoleAdmin, false))
// Admin can reserve topic
rr := request(t, s, "POST", "/v1/account/reservation", `{"topic":"sometopic","everyone":"deny-all"}`, map[string]string{
@@ -624,7 +624,7 @@ func TestAccount_Reservation_Delete_Messages_And_Attachments(t *testing.T) {
s := newTestServer(t, conf)
// Create user with tier
require.Nil(t, s.userManager.AddUser("phil", "mypass", user.RoleUser))
require.Nil(t, s.userManager.AddUser("phil", "mypass", user.RoleUser, false))
require.Nil(t, s.userManager.AddTier(&user.Tier{
Code: "pro",
MessageLimit: 20,

View File

@@ -39,11 +39,11 @@ func (s *Server) handleUsersGet(w http.ResponseWriter, r *http.Request, v *visit
}
func (s *Server) handleUsersAdd(w http.ResponseWriter, r *http.Request, v *visitor) error {
req, err := readJSONWithLimit[apiUserAddRequest](r.Body, jsonBodyBytesLimit, false)
req, err := readJSONWithLimit[apiUserAddOrUpdateRequest](r.Body, jsonBodyBytesLimit, false)
if err != nil {
return err
} else if !user.AllowedUsername(req.Username) || req.Password == "" {
return errHTTPBadRequest.Wrap("username invalid, or password missing")
} else if !user.AllowedUsername(req.Username) || (req.Password == "" && req.Hash == "") {
return errHTTPBadRequest.Wrap("username invalid, or password/password_hash missing")
}
u, err := s.userManager.User(req.Username)
if err != nil && !errors.Is(err, user.ErrUserNotFound) {
@@ -60,7 +60,11 @@ func (s *Server) handleUsersAdd(w http.ResponseWriter, r *http.Request, v *visit
return err
}
}
if err := s.userManager.AddUser(req.Username, req.Password, user.RoleUser); err != nil {
password, hashed := req.Password, false
if req.Hash != "" {
password, hashed = req.Hash, true
}
if err := s.userManager.AddUser(req.Username, password, user.RoleUser, hashed); err != nil {
return err
}
if tier != nil {
@@ -71,6 +75,53 @@ func (s *Server) handleUsersAdd(w http.ResponseWriter, r *http.Request, v *visit
return s.writeJSON(w, newSuccessResponse())
}
func (s *Server) handleUsersUpdate(w http.ResponseWriter, r *http.Request, v *visitor) error {
req, err := readJSONWithLimit[apiUserAddOrUpdateRequest](r.Body, jsonBodyBytesLimit, false)
if err != nil {
return err
} else if !user.AllowedUsername(req.Username) {
return errHTTPBadRequest.Wrap("username invalid")
} else if req.Password == "" && req.Hash == "" && req.Tier == "" {
return errHTTPBadRequest.Wrap("need to provide at least one of \"password\", \"password_hash\" or \"tier\"")
}
u, err := s.userManager.User(req.Username)
if err != nil && !errors.Is(err, user.ErrUserNotFound) {
return err
} else if u != nil {
if u.IsAdmin() {
return errHTTPForbidden
}
if req.Hash != "" {
if err := s.userManager.ChangePassword(req.Username, req.Hash, true); err != nil {
return err
}
} else if req.Password != "" {
if err := s.userManager.ChangePassword(req.Username, req.Password, false); err != nil {
return err
}
}
} else {
password, hashed := req.Password, false
if req.Hash != "" {
password, hashed = req.Hash, true
}
if err := s.userManager.AddUser(req.Username, password, user.RoleUser, hashed); err != nil {
return err
}
}
if req.Tier != "" {
if _, err = s.userManager.Tier(req.Tier); errors.Is(err, user.ErrTierNotFound) {
return errHTTPBadRequestTierInvalid
} else if err != nil {
return err
}
if err := s.userManager.ChangeTier(req.Username, req.Tier); err != nil {
return err
}
}
return s.writeJSON(w, newSuccessResponse())
}
func (s *Server) handleUsersDelete(w http.ResponseWriter, r *http.Request, v *visitor) error {
req, err := readJSONWithLimit[apiUserDeleteRequest](r.Body, jsonBodyBytesLimit, false)
if err != nil {

View File

@@ -14,13 +14,13 @@ func TestUser_AddRemove(t *testing.T) {
defer s.closeDatabases()
// Create admin, tier
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin, false))
require.Nil(t, s.userManager.AddTier(&user.Tier{
Code: "tier1",
}))
// Create user via API
rr := request(t, s, "PUT", "/v1/users", `{"username": "ben", "password":"ben"}`, map[string]string{
rr := request(t, s, "POST", "/v1/users", `{"username": "ben", "password":"ben"}`, map[string]string{
"Authorization": util.BasicAuth("phil", "phil"),
})
require.Equal(t, 200, rr.Code)
@@ -49,6 +49,226 @@ func TestUser_AddRemove(t *testing.T) {
"Authorization": util.BasicAuth("phil", "phil"),
})
require.Equal(t, 200, rr.Code)
// Check user was deleted
users, err = s.userManager.Users()
require.Nil(t, err)
require.Equal(t, 3, len(users))
require.Equal(t, "phil", users[0].Name)
require.Equal(t, "emma", users[1].Name)
require.Equal(t, user.Everyone, users[2].Name)
// Reject invalid user change
rr = request(t, s, "PUT", "/v1/users", `{"username": "ben"}`, map[string]string{
"Authorization": util.BasicAuth("phil", "phil"),
})
require.Equal(t, 400, rr.Code)
}
func TestUser_AddWithPasswordHash(t *testing.T) {
s := newTestServer(t, newTestConfigWithAuthFile(t))
defer s.closeDatabases()
// Create admin
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin, false))
// Create user via API
rr := request(t, s, "POST", "/v1/users", `{"username": "ben", "hash":"$2a$04$2aPIIqPXQU16OfkSUZH1XOzpu1gsPRKkrfVdFLgWQ.tqb.vtTCuVe"}`, map[string]string{
"Authorization": util.BasicAuth("phil", "phil"),
})
require.Equal(t, 200, rr.Code)
// Check that user can login with password
rr = request(t, s, "POST", "/v1/account/token", "", map[string]string{
"Authorization": util.BasicAuth("ben", "ben"),
})
require.Equal(t, 200, rr.Code)
// Check users
users, err := s.userManager.Users()
require.Nil(t, err)
require.Equal(t, 3, len(users))
require.Equal(t, "phil", users[0].Name)
require.Equal(t, user.RoleAdmin, users[0].Role)
require.Equal(t, "ben", users[1].Name)
require.Equal(t, user.RoleUser, users[1].Role)
}
func TestUser_ChangeUserPassword(t *testing.T) {
s := newTestServer(t, newTestConfigWithAuthFile(t))
defer s.closeDatabases()
// Create admin
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin, false))
// Create user via API
rr := request(t, s, "POST", "/v1/users", `{"username": "ben", "password": "ben"}`, map[string]string{
"Authorization": util.BasicAuth("phil", "phil"),
})
require.Equal(t, 200, rr.Code)
// Try to login with first password
rr = request(t, s, "POST", "/v1/account/token", "", map[string]string{
"Authorization": util.BasicAuth("ben", "ben"),
})
require.Equal(t, 200, rr.Code)
// Change password via API
rr = request(t, s, "PUT", "/v1/users", `{"username": "ben", "password": "ben-two"}`, map[string]string{
"Authorization": util.BasicAuth("phil", "phil"),
})
require.Equal(t, 200, rr.Code)
// Make sure first password fails
rr = request(t, s, "POST", "/v1/account/token", "", map[string]string{
"Authorization": util.BasicAuth("ben", "ben"),
})
require.Equal(t, 401, rr.Code)
// Try to login with second password
rr = request(t, s, "POST", "/v1/account/token", "", map[string]string{
"Authorization": util.BasicAuth("ben", "ben-two"),
})
require.Equal(t, 200, rr.Code)
}
func TestUser_ChangeUserTier(t *testing.T) {
s := newTestServer(t, newTestConfigWithAuthFile(t))
defer s.closeDatabases()
// Create admin, tier
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin, false))
require.Nil(t, s.userManager.AddTier(&user.Tier{
Code: "tier1",
}))
require.Nil(t, s.userManager.AddTier(&user.Tier{
Code: "tier2",
}))
// Create user with tier via API
rr := request(t, s, "POST", "/v1/users", `{"username": "ben", "password":"ben", "tier": "tier1"}`, map[string]string{
"Authorization": util.BasicAuth("phil", "phil"),
})
require.Equal(t, 200, rr.Code)
// Check users
users, err := s.userManager.Users()
require.Nil(t, err)
require.Equal(t, 3, len(users))
require.Equal(t, "phil", users[0].Name)
require.Equal(t, "ben", users[1].Name)
require.Equal(t, user.RoleUser, users[1].Role)
require.Equal(t, "tier1", users[1].Tier.Code)
// Change user tier via API
rr = request(t, s, "PUT", "/v1/users", `{"username": "ben", "tier": "tier2"}`, map[string]string{
"Authorization": util.BasicAuth("phil", "phil"),
})
require.Equal(t, 200, rr.Code)
// Check users again
users, err = s.userManager.Users()
require.Nil(t, err)
require.Equal(t, "tier2", users[1].Tier.Code)
}
func TestUser_ChangeUserPasswordAndTier(t *testing.T) {
s := newTestServer(t, newTestConfigWithAuthFile(t))
defer s.closeDatabases()
// Create admin, tier
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin, false))
require.Nil(t, s.userManager.AddTier(&user.Tier{
Code: "tier1",
}))
require.Nil(t, s.userManager.AddTier(&user.Tier{
Code: "tier2",
}))
// Create user with tier via API
rr := request(t, s, "POST", "/v1/users", `{"username": "ben", "password":"ben", "tier": "tier1"}`, map[string]string{
"Authorization": util.BasicAuth("phil", "phil"),
})
require.Equal(t, 200, rr.Code)
// Check users
users, err := s.userManager.Users()
require.Nil(t, err)
require.Equal(t, 3, len(users))
require.Equal(t, "phil", users[0].Name)
require.Equal(t, "ben", users[1].Name)
require.Equal(t, user.RoleUser, users[1].Role)
require.Equal(t, "tier1", users[1].Tier.Code)
// Change user password and tier via API
rr = request(t, s, "PUT", "/v1/users", `{"username": "ben", "password":"ben-two", "tier": "tier2"}`, map[string]string{
"Authorization": util.BasicAuth("phil", "phil"),
})
require.Equal(t, 200, rr.Code)
// Make sure first password fails
rr = request(t, s, "POST", "/v1/account/token", "", map[string]string{
"Authorization": util.BasicAuth("ben", "ben"),
})
require.Equal(t, 401, rr.Code)
// Try to login with second password
rr = request(t, s, "POST", "/v1/account/token", "", map[string]string{
"Authorization": util.BasicAuth("ben", "ben-two"),
})
require.Equal(t, 200, rr.Code)
// Check new tier
users, err = s.userManager.Users()
require.Nil(t, err)
require.Equal(t, "tier2", users[1].Tier.Code)
}
func TestUser_ChangeUserPasswordWithHash(t *testing.T) {
s := newTestServer(t, newTestConfigWithAuthFile(t))
defer s.closeDatabases()
// Create admin
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin, false))
// Create user with tier via API
rr := request(t, s, "POST", "/v1/users", `{"username": "ben", "password":"not-ben"}`, map[string]string{
"Authorization": util.BasicAuth("phil", "phil"),
})
require.Equal(t, 200, rr.Code)
// Try to login with first password
rr = request(t, s, "POST", "/v1/account/token", "", map[string]string{
"Authorization": util.BasicAuth("ben", "not-ben"),
})
require.Equal(t, 200, rr.Code)
// Change user password and tier via API
rr = request(t, s, "PUT", "/v1/users", `{"username": "ben", "hash":"$2a$04$2aPIIqPXQU16OfkSUZH1XOzpu1gsPRKkrfVdFLgWQ.tqb.vtTCuVe"}`, map[string]string{
"Authorization": util.BasicAuth("phil", "phil"),
})
require.Equal(t, 200, rr.Code)
// Try to login with second password
rr = request(t, s, "POST", "/v1/account/token", "", map[string]string{
"Authorization": util.BasicAuth("ben", "ben"),
})
require.Equal(t, 200, rr.Code)
}
func TestUser_DontChangeAdminPassword(t *testing.T) {
s := newTestServer(t, newTestConfigWithAuthFile(t))
defer s.closeDatabases()
// Create admin
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin, false))
require.Nil(t, s.userManager.AddUser("admin", "admin", user.RoleAdmin, false))
// Try to change password via API
rr := request(t, s, "PUT", "/v1/users", `{"username": "admin", "password": "admin-new"}`, map[string]string{
"Authorization": util.BasicAuth("phil", "phil"),
})
require.Equal(t, 403, rr.Code)
}
func TestUser_AddRemove_Failures(t *testing.T) {
@@ -56,23 +276,23 @@ func TestUser_AddRemove_Failures(t *testing.T) {
defer s.closeDatabases()
// Create admin
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin))
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin, false))
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser, false))
// Cannot create user with invalid username
rr := request(t, s, "PUT", "/v1/users", `{"username": "not valid", "password":"ben"}`, map[string]string{
rr := request(t, s, "POST", "/v1/users", `{"username": "not valid", "password":"ben"}`, map[string]string{
"Authorization": util.BasicAuth("phil", "phil"),
})
require.Equal(t, 400, rr.Code)
// Cannot create user if user already exists
rr = request(t, s, "PUT", "/v1/users", `{"username": "phil", "password":"phil"}`, map[string]string{
rr = request(t, s, "POST", "/v1/users", `{"username": "phil", "password":"phil"}`, map[string]string{
"Authorization": util.BasicAuth("phil", "phil"),
})
require.Equal(t, 40901, toHTTPError(t, rr.Body.String()).Code)
// Cannot create user with invalid tier
rr = request(t, s, "PUT", "/v1/users", `{"username": "emma", "password":"emma", "tier": "invalid"}`, map[string]string{
rr = request(t, s, "POST", "/v1/users", `{"username": "emma", "password":"emma", "tier": "invalid"}`, map[string]string{
"Authorization": util.BasicAuth("phil", "phil"),
})
require.Equal(t, 40030, toHTTPError(t, rr.Body.String()).Code)
@@ -97,8 +317,8 @@ func TestAccess_AllowReset(t *testing.T) {
defer s.closeDatabases()
// User and admin
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin))
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin, false))
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser, false))
// Subscribing not allowed
rr := request(t, s, "GET", "/gold/json?poll=1", "", map[string]string{
@@ -138,7 +358,7 @@ func TestAccess_AllowReset_NonAdminAttempt(t *testing.T) {
defer s.closeDatabases()
// User
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser))
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser, false))
// Grant access fails, because non-admin
rr := request(t, s, "POST", "/v1/users/access", `{"username": "ben", "topic":"gold", "permission":"ro"}`, map[string]string{
@@ -154,8 +374,8 @@ func TestAccess_AllowReset_KillConnection(t *testing.T) {
defer s.closeDatabases()
// User and admin, grant access to "gol*" topics
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin))
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin, false))
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser, false))
require.Nil(t, s.userManager.AllowAccess("ben", "gol*", user.PermissionRead)) // Wildcard!
start, timeTaken := time.Now(), atomic.Int64{}

View File

@@ -50,7 +50,7 @@ func (c *firebaseClient) Send(v *visitor, m *message) error {
ev.Field("firebase_message", util.MaybeMarshalJSON(fbm)).Trace("Firebase message")
}
err = c.sender.Send(fbm)
if err == errFirebaseQuotaExceeded {
if errors.Is(err, errFirebaseQuotaExceeded) {
logvm(v, m).
Tag(tagFirebase).
Err(err).
@@ -133,7 +133,7 @@ func toFirebaseMessage(m *message, auther user.Auther) (*messaging.Message, erro
"time": fmt.Sprintf("%d", m.Time),
"event": m.Event,
"topic": m.Topic,
"message": m.Message,
"message": newMessageBody,
"poll_id": m.PollID,
}
apnsConfig = createAPNSAlertConfig(m, data)
@@ -173,15 +173,29 @@ func toFirebaseMessage(m *message, auther user.Auther) (*messaging.Message, erro
}
apnsConfig = createAPNSAlertConfig(m, data)
} else {
// If anonymous read for a topic is not allowed, we cannot send the message along
// If "anonymous read" for a topic is not allowed, we cannot send the message along
// via Firebase. Instead, we send a "poll_request" message, asking the client to poll.
//
// The data map needs to contain all the fields for it to function properly. If not all
// fields are set, the iOS app fails to decode the message.
//
// See https://github.com/binwiederhier/ntfy/pull/1345
data = map[string]string{
"id": m.ID,
"time": fmt.Sprintf("%d", m.Time),
"event": pollRequestEvent,
"topic": m.Topic,
"id": m.ID,
"time": fmt.Sprintf("%d", m.Time),
"event": pollRequestEvent,
"topic": m.Topic,
"priority": fmt.Sprintf("%d", m.Priority),
"tags": "",
"click": "",
"icon": "",
"title": "",
"message": newMessageBody,
"content_type": m.ContentType,
"encoding": m.Encoding,
"poll_id": m.ID,
}
// TODO Handle APNS?
apnsConfig = createAPNSAlertConfig(m, data)
}
}
var androidConfig *messaging.AndroidConfig

View File

@@ -223,13 +223,22 @@ func TestToFirebaseMessage_Message_Normal_Not_Allowed(t *testing.T) {
require.Equal(t, &messaging.AndroidConfig{
Priority: "high",
}, fbm.Android)
require.Equal(t, "", fbm.Data["message"])
require.Equal(t, "", fbm.Data["priority"])
require.Equal(t, "New message", fbm.Data["message"])
require.Equal(t, "5", fbm.Data["priority"])
require.Equal(t, map[string]string{
"id": m.ID,
"time": fmt.Sprintf("%d", m.Time),
"event": "poll_request",
"topic": "mytopic",
"id": m.ID,
"time": fmt.Sprintf("%d", m.Time),
"event": "poll_request",
"topic": "mytopic",
"message": "New message",
"title": "",
"tags": "",
"click": "",
"icon": "",
"priority": "5",
"encoding": "",
"content_type": "",
"poll_id": m.ID,
}, fbm.Data)
}

View File

@@ -148,7 +148,7 @@ func TestPayments_SubscriptionCreate_NotAStripeCustomer_Success(t *testing.T) {
Code: "pro",
StripeMonthlyPriceID: "price_123",
}))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
// Create subscription
response := request(t, s, "POST", "/v1/account/billing/subscription", `{"tier": "pro", "interval": "month"}`, map[string]string{
@@ -184,7 +184,7 @@ func TestPayments_SubscriptionCreate_StripeCustomer_Success(t *testing.T) {
Code: "pro",
StripeMonthlyPriceID: "price_123",
}))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
u, err := s.userManager.User("phil")
require.Nil(t, err)
@@ -226,7 +226,7 @@ func TestPayments_AccountDelete_Cancels_Subscription(t *testing.T) {
Code: "pro",
StripeMonthlyPriceID: "price_123",
}))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
u, err := s.userManager.User("phil")
require.Nil(t, err)
@@ -280,7 +280,7 @@ func TestPayments_Checkout_Success_And_Increase_Rate_Limits_Reset_Visitor(t *tes
MessageLimit: 220, // 220 * 5% = 11 requests before rate limiting kicks in
MessageExpiryDuration: time.Hour,
}))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser)) // No tier
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false)) // No tier
u, err := s.userManager.User("phil")
require.Nil(t, err)
@@ -461,7 +461,7 @@ func TestPayments_Webhook_Subscription_Updated_Downgrade_From_PastDue_To_Active(
AttachmentTotalSizeLimit: 1000000,
AttachmentBandwidthLimit: 1000000,
}))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
require.Nil(t, s.userManager.ChangeTier("phil", "pro"))
require.Nil(t, s.userManager.AddReservation("phil", "atopic", user.PermissionDenyAll))
require.Nil(t, s.userManager.AddReservation("phil", "ztopic", user.PermissionDenyAll))
@@ -570,7 +570,7 @@ func TestPayments_Webhook_Subscription_Deleted(t *testing.T) {
StripeMonthlyPriceID: "price_1234",
ReservationLimit: 1,
}))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
require.Nil(t, s.userManager.ChangeTier("phil", "pro"))
require.Nil(t, s.userManager.AddReservation("phil", "atopic", user.PermissionDenyAll))
@@ -658,7 +658,7 @@ func TestPayments_Subscription_Update_Different_Tier(t *testing.T) {
StripeMonthlyPriceID: "price_456",
StripeYearlyPriceID: "price_457",
}))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
require.Nil(t, s.userManager.ChangeTier("phil", "pro"))
require.Nil(t, s.userManager.ChangeBilling("phil", &user.Billing{
StripeCustomerID: "acct_123",
@@ -690,7 +690,7 @@ func TestPayments_Subscription_Delete_At_Period_End(t *testing.T) {
Return(&stripe.Subscription{}, nil)
// Create user
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
require.Nil(t, s.userManager.ChangeBilling("phil", &user.Billing{
StripeCustomerID: "acct_123",
StripeSubscriptionID: "sub_123",
@@ -724,7 +724,7 @@ func TestPayments_CreatePortalSession(t *testing.T) {
}, nil)
// Create user
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
require.Nil(t, s.userManager.ChangeBilling("phil", &user.Billing{
StripeCustomerID: "acct_123",
StripeSubscriptionID: "sub_123",

View File

@@ -411,7 +411,7 @@ func TestServer_PublishAt_FromUser(t *testing.T) {
t.Parallel()
s := newTestServer(t, newTestConfigWithAuthFile(t))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin, false))
response := request(t, s, "PUT", "/mytopic", "a message", map[string]string{
"Authorization": util.BasicAuth("phil", "phil"),
"In": "1h",
@@ -594,6 +594,11 @@ func TestServer_PublishAndPollSince(t *testing.T) {
require.Equal(t, 1, len(messages))
require.Equal(t, "test 2", messages[0].Message)
response = request(t, s, "GET", "/mytopic/json?poll=1&since=latest", "", nil)
messages = toMessages(t, response.Body.String())
require.Equal(t, 1, len(messages))
require.Equal(t, "test 2", messages[0].Message)
response = request(t, s, "GET", "/mytopic/json?poll=1&since=INVALID", "", nil)
require.Equal(t, 40008, toHTTPError(t, response.Body.String()).Code)
}
@@ -781,7 +786,7 @@ func TestServer_Auth_Success_Admin(t *testing.T) {
c := newTestConfigWithAuthFile(t)
s := newTestServer(t, c)
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin, false))
response := request(t, s, "GET", "/mytopic/auth", "", map[string]string{
"Authorization": util.BasicAuth("phil", "phil"),
@@ -795,7 +800,7 @@ func TestServer_Auth_Success_User(t *testing.T) {
c.AuthDefault = user.PermissionDenyAll
s := newTestServer(t, c)
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser))
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser, false))
require.Nil(t, s.userManager.AllowAccess("ben", "mytopic", user.PermissionReadWrite))
response := request(t, s, "GET", "/mytopic/auth", "", map[string]string{
@@ -809,7 +814,7 @@ func TestServer_Auth_Success_User_MultipleTopics(t *testing.T) {
c.AuthDefault = user.PermissionDenyAll
s := newTestServer(t, c)
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser))
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser, false))
require.Nil(t, s.userManager.AllowAccess("ben", "mytopic", user.PermissionReadWrite))
require.Nil(t, s.userManager.AllowAccess("ben", "anothertopic", user.PermissionReadWrite))
@@ -830,7 +835,7 @@ func TestServer_Auth_Fail_InvalidPass(t *testing.T) {
c.AuthDefault = user.PermissionDenyAll
s := newTestServer(t, c)
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin, false))
response := request(t, s, "GET", "/mytopic/auth", "", map[string]string{
"Authorization": util.BasicAuth("phil", "INVALID"),
@@ -843,7 +848,7 @@ func TestServer_Auth_Fail_Unauthorized(t *testing.T) {
c.AuthDefault = user.PermissionDenyAll
s := newTestServer(t, c)
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser))
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser, false))
require.Nil(t, s.userManager.AllowAccess("ben", "sometopic", user.PermissionReadWrite)) // Not mytopic!
response := request(t, s, "GET", "/mytopic/auth", "", map[string]string{
@@ -857,7 +862,7 @@ func TestServer_Auth_Fail_CannotPublish(t *testing.T) {
c.AuthDefault = user.PermissionReadWrite // Open by default
s := newTestServer(t, c)
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin, false))
require.Nil(t, s.userManager.AllowAccess(user.Everyone, "private", user.PermissionDenyAll))
require.Nil(t, s.userManager.AllowAccess(user.Everyone, "announcements", user.PermissionRead))
@@ -906,7 +911,7 @@ func TestServer_Auth_ViaQuery(t *testing.T) {
c.AuthDefault = user.PermissionDenyAll
s := newTestServer(t, c)
require.Nil(t, s.userManager.AddUser("ben", "some pass", user.RoleAdmin))
require.Nil(t, s.userManager.AddUser("ben", "some pass", user.RoleAdmin, false))
u := fmt.Sprintf("/mytopic/json?poll=1&auth=%s", base64.RawURLEncoding.EncodeToString([]byte(util.BasicAuth("ben", "some pass"))))
response := request(t, s, "GET", u, "", nil)
@@ -954,8 +959,8 @@ func TestServer_StatsResetter(t *testing.T) {
MessageLimit: 5,
MessageExpiryDuration: -5 * time.Second, // Second, what a hack!
}))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
require.Nil(t, s.userManager.AddUser("tieruser", "tieruser", user.RoleUser))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
require.Nil(t, s.userManager.AddUser("tieruser", "tieruser", user.RoleUser, false))
require.Nil(t, s.userManager.ChangeTier("tieruser", "test"))
// Send an anonymous message
@@ -1099,7 +1104,7 @@ func TestServer_DailyMessageQuotaFromDatabase(t *testing.T) {
require.Nil(t, s.userManager.AddTier(&user.Tier{
Code: "test",
}))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
require.Nil(t, s.userManager.ChangeTier("phil", "test"))
u, err := s.userManager.User("phil")
@@ -1696,7 +1701,7 @@ func TestServer_PublishWithTierBasedMessageLimitAndExpiry(t *testing.T) {
MessageLimit: 5,
MessageExpiryDuration: -5 * time.Second, // Second, what a hack!
}))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
require.Nil(t, s.userManager.ChangeTier("phil", "test"))
// Publish to reach message limit
@@ -1932,7 +1937,7 @@ func TestServer_PublishAttachmentWithTierBasedExpiry(t *testing.T) {
AttachmentExpiryDuration: sevenDays, // 7 days
AttachmentBandwidthLimit: 100000,
}))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
require.Nil(t, s.userManager.ChangeTier("phil", "test"))
// Publish and make sure we can retrieve it
@@ -1977,7 +1982,7 @@ func TestServer_PublishAttachmentWithTierBasedBandwidthLimit(t *testing.T) {
AttachmentExpiryDuration: time.Hour,
AttachmentBandwidthLimit: 14000, // < 3x5000 bytes -> enough for one upload, one download
}))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
require.Nil(t, s.userManager.ChangeTier("phil", "test"))
// Publish and make sure we can retrieve it
@@ -2015,7 +2020,7 @@ func TestServer_PublishAttachmentWithTierBasedLimits(t *testing.T) {
AttachmentExpiryDuration: 30 * time.Second,
AttachmentBandwidthLimit: 1000000,
}))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
require.Nil(t, s.userManager.ChangeTier("phil", "test"))
// Publish small file as anonymous
@@ -2237,7 +2242,7 @@ func TestServer_AnonymousUser_And_NonTierUser_Are_Same_Visitor(t *testing.T) {
defer s.closeDatabases()
// Create user without tier
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
// Publish a message (anonymous user)
rr := request(t, s, "POST", "/mytopic", "hi", nil)

View File

@@ -63,7 +63,7 @@ func TestServer_Twilio_Call_Add_Verify_Call_Delete_Success(t *testing.T) {
MessageLimit: 10,
CallLimit: 1,
}))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
require.Nil(t, s.userManager.ChangeTier("phil", "pro"))
u, err := s.userManager.User("phil")
require.Nil(t, err)
@@ -140,7 +140,7 @@ func TestServer_Twilio_Call_Success(t *testing.T) {
MessageLimit: 10,
CallLimit: 1,
}))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
require.Nil(t, s.userManager.ChangeTier("phil", "pro"))
u, err := s.userManager.User("phil")
require.Nil(t, err)
@@ -185,7 +185,7 @@ func TestServer_Twilio_Call_Success_With_Yes(t *testing.T) {
MessageLimit: 10,
CallLimit: 1,
}))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
require.Nil(t, s.userManager.ChangeTier("phil", "pro"))
u, err := s.userManager.User("phil")
require.Nil(t, err)
@@ -216,7 +216,7 @@ func TestServer_Twilio_Call_UnverifiedNumber(t *testing.T) {
MessageLimit: 10,
CallLimit: 1,
}))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false))
require.Nil(t, s.userManager.ChangeTier("phil", "pro"))
// Do the thing

View File

@@ -96,7 +96,7 @@ func TestServer_WebPush_TopicSubscribeProtected_Allowed(t *testing.T) {
config.AuthDefault = user.PermissionDenyAll
s := newTestServer(t, config)
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser))
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser, false))
require.Nil(t, s.userManager.AllowAccess("ben", "test-topic", user.PermissionReadWrite))
response := request(t, s, "POST", "/v1/webpush", payloadForTopics(t, []string{"test-topic"}, testWebPushEndpoint), map[string]string{
@@ -126,7 +126,7 @@ func TestServer_WebPush_DeleteAccountUnsubscribe(t *testing.T) {
config := configureAuth(t, newTestConfigWithWebPush(t))
s := newTestServer(t, config)
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser))
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser, false))
require.Nil(t, s.userManager.AllowAccess("ben", "test-topic", user.PermissionReadWrite))
response := request(t, s, "POST", "/v1/webpush", payloadForTopics(t, []string{"test-topic"}, testWebPushEndpoint), map[string]string{

View File

@@ -169,8 +169,12 @@ func (t sinceMarker) IsNone() bool {
return t == sinceNoMessages
}
func (t sinceMarker) IsLatest() bool {
return t == sinceLatestMessage
}
func (t sinceMarker) IsID() bool {
return t.id != ""
return t.id != "" && t.id != "latest"
}
func (t sinceMarker) Time() time.Time {
@@ -182,8 +186,9 @@ func (t sinceMarker) ID() string {
}
var (
sinceAllMessages = sinceMarker{time.Unix(0, 0), ""}
sinceNoMessages = sinceMarker{time.Unix(1, 0), ""}
sinceAllMessages = sinceMarker{time.Unix(0, 0), ""}
sinceNoMessages = sinceMarker{time.Unix(1, 0), ""}
sinceLatestMessage = sinceMarker{time.Unix(0, 0), "latest"}
)
type queryFilter struct {
@@ -248,9 +253,10 @@ type apiStatsResponse struct {
MessagesRate float64 `json:"messages_rate"` // Average number of messages per second
}
type apiUserAddRequest struct {
type apiUserAddOrUpdateRequest struct {
Username string `json:"username"`
Password string `json:"password"`
Hash string `json:"hash"`
Tier string `json:"tier"`
// Do not add 'role' here. We don't want to add admins via the API.
}
@@ -395,6 +401,7 @@ type apiConfigResponse struct {
BaseURL string `json:"base_url"`
AppRoot string `json:"app_root"`
EnableLogin bool `json:"enable_login"`
RequireLogin bool `json:"require_login"`
EnableSignup bool `json:"enable_signup"`
EnablePayments bool `json:"enable_payments"`
EnableCalls bool `json:"enable_calls"`

View File

@@ -82,7 +82,7 @@ func extractIPAddress(r *http.Request, behindProxy bool) netip.Addr {
ip, err = netip.ParseAddr(remoteAddr)
if err != nil {
ip = netip.IPv4Unspecified()
if remoteAddr != "@" || !behindProxy { // RemoteAddr is @ when unix socket is used
if remoteAddr != "@" && !behindProxy { // RemoteAddr is @ when unix socket is used
logr(r).Err(err).Warn("unable to parse IP (%s), new visitor with unspecified IP (0.0.0.0) created", remoteAddr)
}
}

View File

@@ -864,13 +864,19 @@ func (a *Manager) resolvePerms(base, perm Permission) error {
}
// AddUser adds a user with the given username, password and role
func (a *Manager) AddUser(username, password string, role Role) error {
func (a *Manager) AddUser(username, password string, role Role, hashed bool) error {
if !AllowedUsername(username) || !AllowedRole(role) {
return ErrInvalidArgument
}
hash, err := bcrypt.GenerateFromPassword([]byte(password), a.bcryptCost)
if err != nil {
return err
var hash []byte
var err error = nil
if hashed {
hash = []byte(password)
} else {
hash, err = bcrypt.GenerateFromPassword([]byte(password), a.bcryptCost)
if err != nil {
return err
}
}
userID := util.RandomStringPrefix(userIDPrefix, userIDLength)
syncTopic, now := util.RandomStringPrefix(syncTopicPrefix, syncTopicLength), time.Now().Unix()
@@ -1192,10 +1198,17 @@ func (a *Manager) ReservationOwner(topic string) (string, error) {
}
// ChangePassword changes a user's password
func (a *Manager) ChangePassword(username, password string) error {
hash, err := bcrypt.GenerateFromPassword([]byte(password), a.bcryptCost)
if err != nil {
return err
func (a *Manager) ChangePassword(username, password string, hashed bool) error {
var hash []byte
var err error
if hashed {
hash = []byte(password)
} else {
hash, err = bcrypt.GenerateFromPassword([]byte(password), a.bcryptCost)
if err != nil {
return err
}
}
if _, err := a.db.Exec(updateUserPassQuery, hash, username); err != nil {
return err

View File

@@ -18,9 +18,9 @@ const minBcryptTimingMillis = int64(50) // Ideally should be >100ms, but this sh
func TestManager_FullScenario_Default_DenyAll(t *testing.T) {
a := newTestManagerFromFile(t, filepath.Join(t.TempDir(), "user.db"), "", PermissionDenyAll, DefaultUserPasswordBcryptCost, DefaultUserStatsQueueWriterInterval)
require.Nil(t, a.AddUser("phil", "phil", RoleAdmin))
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
require.Nil(t, a.AddUser("john", "john", RoleUser))
require.Nil(t, a.AddUser("phil", "phil", RoleAdmin, false))
require.Nil(t, a.AddUser("ben", "ben", RoleUser, false))
require.Nil(t, a.AddUser("john", "john", RoleUser, false))
require.Nil(t, a.AllowAccess("ben", "mytopic", PermissionReadWrite))
require.Nil(t, a.AllowAccess("ben", "readme", PermissionRead))
require.Nil(t, a.AllowAccess("ben", "writeme", PermissionWrite))
@@ -134,7 +134,7 @@ func TestManager_Access_Order_LengthWriteRead(t *testing.T) {
// and longer ACL rules are prioritized as well.
a := newTestManagerFromFile(t, filepath.Join(t.TempDir(), "user.db"), "", PermissionDenyAll, DefaultUserPasswordBcryptCost, DefaultUserStatsQueueWriterInterval)
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
require.Nil(t, a.AddUser("ben", "ben", RoleUser, false))
require.Nil(t, a.AllowAccess("ben", "test*", PermissionReadWrite))
require.Nil(t, a.AllowAccess("ben", "*", PermissionRead))
@@ -147,20 +147,20 @@ func TestManager_Access_Order_LengthWriteRead(t *testing.T) {
func TestManager_AddUser_Invalid(t *testing.T) {
a := newTestManager(t, PermissionDenyAll)
require.Equal(t, ErrInvalidArgument, a.AddUser(" invalid ", "pass", RoleAdmin))
require.Equal(t, ErrInvalidArgument, a.AddUser("validuser", "pass", "invalid-role"))
require.Equal(t, ErrInvalidArgument, a.AddUser(" invalid ", "pass", RoleAdmin, false))
require.Equal(t, ErrInvalidArgument, a.AddUser("validuser", "pass", "invalid-role", false))
}
func TestManager_AddUser_Timing(t *testing.T) {
a := newTestManagerFromFile(t, filepath.Join(t.TempDir(), "user.db"), "", PermissionDenyAll, DefaultUserPasswordBcryptCost, DefaultUserStatsQueueWriterInterval)
start := time.Now().UnixMilli()
require.Nil(t, a.AddUser("user", "pass", RoleAdmin))
require.Nil(t, a.AddUser("user", "pass", RoleAdmin, false))
require.GreaterOrEqual(t, time.Now().UnixMilli()-start, minBcryptTimingMillis)
}
func TestManager_AddUser_And_Query(t *testing.T) {
a := newTestManagerFromFile(t, filepath.Join(t.TempDir(), "user.db"), "", PermissionDenyAll, DefaultUserPasswordBcryptCost, DefaultUserStatsQueueWriterInterval)
require.Nil(t, a.AddUser("user", "pass", RoleAdmin))
require.Nil(t, a.AddUser("user", "pass", RoleAdmin, false))
require.Nil(t, a.ChangeBilling("user", &Billing{
StripeCustomerID: "acct_123",
StripeSubscriptionID: "sub_123",
@@ -187,7 +187,7 @@ func TestManager_MarkUserRemoved_RemoveDeletedUsers(t *testing.T) {
a := newTestManager(t, PermissionDenyAll)
// Create user, add reservations and token
require.Nil(t, a.AddUser("user", "pass", RoleAdmin))
require.Nil(t, a.AddUser("user", "pass", RoleAdmin, false))
require.Nil(t, a.AddReservation("user", "mytopic", PermissionRead))
u, err := a.User("user")
@@ -237,7 +237,7 @@ func TestManager_CreateToken_Only_Lower(t *testing.T) {
a := newTestManager(t, PermissionDenyAll)
// Create user, add reservations and token
require.Nil(t, a.AddUser("user", "pass", RoleAdmin))
require.Nil(t, a.AddUser("user", "pass", RoleAdmin, false))
u, err := a.User("user")
require.Nil(t, err)
@@ -248,8 +248,8 @@ func TestManager_CreateToken_Only_Lower(t *testing.T) {
func TestManager_UserManagement(t *testing.T) {
a := newTestManager(t, PermissionDenyAll)
require.Nil(t, a.AddUser("phil", "phil", RoleAdmin))
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
require.Nil(t, a.AddUser("phil", "phil", RoleAdmin, false))
require.Nil(t, a.AddUser("ben", "ben", RoleUser, false))
require.Nil(t, a.AllowAccess("ben", "mytopic", PermissionReadWrite))
require.Nil(t, a.AllowAccess("ben", "readme", PermissionRead))
require.Nil(t, a.AllowAccess("ben", "writeme", PermissionWrite))
@@ -339,21 +339,31 @@ func TestManager_UserManagement(t *testing.T) {
func TestManager_ChangePassword(t *testing.T) {
a := newTestManager(t, PermissionDenyAll)
require.Nil(t, a.AddUser("phil", "phil", RoleAdmin))
require.Nil(t, a.AddUser("phil", "phil", RoleAdmin, false))
require.Nil(t, a.AddUser("jane", "$2b$10$OyqU72muEy7VMd1SAU2Iru5IbeSMgrtCGHu/fWLmxL1MwlijQXWbG", RoleUser, true))
_, err := a.Authenticate("phil", "phil")
require.Nil(t, err)
require.Nil(t, a.ChangePassword("phil", "newpass"))
_, err = a.Authenticate("jane", "jane")
require.Nil(t, err)
require.Nil(t, a.ChangePassword("phil", "newpass", false))
_, err = a.Authenticate("phil", "phil")
require.Equal(t, ErrUnauthenticated, err)
_, err = a.Authenticate("phil", "newpass")
require.Nil(t, err)
require.Nil(t, a.ChangePassword("jane", "$2b$10$CNaCW.q1R431urlbQ5Drh.zl48TiiOeJSmZgfcswkZiPbJGQ1ApSS", true))
_, err = a.Authenticate("jane", "jane")
require.Equal(t, ErrUnauthenticated, err)
_, err = a.Authenticate("jane", "newpass")
require.Nil(t, err)
}
func TestManager_ChangeRole(t *testing.T) {
a := newTestManager(t, PermissionDenyAll)
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
require.Nil(t, a.AddUser("ben", "ben", RoleUser, false))
require.Nil(t, a.AllowAccess("ben", "mytopic", PermissionReadWrite))
require.Nil(t, a.AllowAccess("ben", "readme", PermissionRead))
@@ -378,8 +388,8 @@ func TestManager_ChangeRole(t *testing.T) {
func TestManager_Reservations(t *testing.T) {
a := newTestManager(t, PermissionDenyAll)
require.Nil(t, a.AddUser("phil", "phil", RoleUser))
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
require.Nil(t, a.AddUser("phil", "phil", RoleUser, false))
require.Nil(t, a.AddUser("ben", "ben", RoleUser, false))
require.Nil(t, a.AddReservation("ben", "ztopic_", PermissionDenyAll))
require.Nil(t, a.AddReservation("ben", "readme", PermissionRead))
require.Nil(t, a.AllowAccess("ben", "something-else", PermissionRead))
@@ -460,7 +470,7 @@ func TestManager_ChangeRoleFromTierUserToAdmin(t *testing.T) {
AttachmentTotalSizeLimit: 524288000,
AttachmentExpiryDuration: 24 * time.Hour,
}))
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
require.Nil(t, a.AddUser("ben", "ben", RoleUser, false))
require.Nil(t, a.ChangeTier("ben", "pro"))
require.Nil(t, a.AddReservation("ben", "mytopic", PermissionDenyAll))
@@ -507,7 +517,7 @@ func TestManager_ChangeRoleFromTierUserToAdmin(t *testing.T) {
func TestManager_Token_Valid(t *testing.T) {
a := newTestManager(t, PermissionDenyAll)
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
require.Nil(t, a.AddUser("ben", "ben", RoleUser, false))
u, err := a.User("ben")
require.Nil(t, err)
@@ -551,7 +561,7 @@ func TestManager_Token_Valid(t *testing.T) {
func TestManager_Token_Invalid(t *testing.T) {
a := newTestManager(t, PermissionDenyAll)
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
require.Nil(t, a.AddUser("ben", "ben", RoleUser, false))
u, err := a.AuthenticateToken(strings.Repeat("x", 32)) // 32 == token length
require.Nil(t, u)
@@ -570,7 +580,7 @@ func TestManager_Token_NotFound(t *testing.T) {
func TestManager_Token_Expire(t *testing.T) {
a := newTestManager(t, PermissionDenyAll)
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
require.Nil(t, a.AddUser("ben", "ben", RoleUser, false))
u, err := a.User("ben")
require.Nil(t, err)
@@ -618,7 +628,7 @@ func TestManager_Token_Expire(t *testing.T) {
func TestManager_Token_Extend(t *testing.T) {
a := newTestManager(t, PermissionDenyAll)
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
require.Nil(t, a.AddUser("ben", "ben", RoleUser, false))
// Try to extend token for user without token
u, err := a.User("ben")
@@ -647,8 +657,8 @@ func TestManager_Token_MaxCount_AutoDelete(t *testing.T) {
// Tests that tokens are automatically deleted when the maximum number of tokens is reached
a := newTestManager(t, PermissionDenyAll)
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
require.Nil(t, a.AddUser("phil", "phil", RoleUser))
require.Nil(t, a.AddUser("ben", "ben", RoleUser, false))
require.Nil(t, a.AddUser("phil", "phil", RoleUser, false))
ben, err := a.User("ben")
require.Nil(t, err)
@@ -723,7 +733,7 @@ func TestManager_Token_MaxCount_AutoDelete(t *testing.T) {
func TestManager_EnqueueStats_ResetStats(t *testing.T) {
a, err := NewManager(filepath.Join(t.TempDir(), "db"), "", PermissionReadWrite, bcrypt.MinCost, 1500*time.Millisecond)
require.Nil(t, err)
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
require.Nil(t, a.AddUser("ben", "ben", RoleUser, false))
// Baseline: No messages or emails
u, err := a.User("ben")
@@ -765,7 +775,7 @@ func TestManager_EnqueueStats_ResetStats(t *testing.T) {
func TestManager_EnqueueTokenUpdate(t *testing.T) {
a, err := NewManager(filepath.Join(t.TempDir(), "db"), "", PermissionReadWrite, bcrypt.MinCost, 500*time.Millisecond)
require.Nil(t, err)
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
require.Nil(t, a.AddUser("ben", "ben", RoleUser, false))
// Create user and token
u, err := a.User("ben")
@@ -798,7 +808,7 @@ func TestManager_EnqueueTokenUpdate(t *testing.T) {
func TestManager_ChangeSettings(t *testing.T) {
a, err := NewManager(filepath.Join(t.TempDir(), "db"), "", PermissionReadWrite, bcrypt.MinCost, 1500*time.Millisecond)
require.Nil(t, err)
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
require.Nil(t, a.AddUser("ben", "ben", RoleUser, false))
// No settings
u, err := a.User("ben")
@@ -866,7 +876,7 @@ func TestManager_Tier_Create_Update_List_Delete(t *testing.T) {
AttachmentBandwidthLimit: 21474836480,
StripeMonthlyPriceID: "price_2",
}))
require.Nil(t, a.AddUser("phil", "phil", RoleUser))
require.Nil(t, a.AddUser("phil", "phil", RoleUser, false))
require.Nil(t, a.ChangeTier("phil", "pro"))
ti, err := a.Tier("pro")
@@ -981,7 +991,7 @@ func TestManager_Tier_Change_And_Reset(t *testing.T) {
Name: "Pro",
ReservationLimit: 4,
}))
require.Nil(t, a.AddUser("phil", "phil", RoleUser))
require.Nil(t, a.AddUser("phil", "phil", RoleUser, false))
require.Nil(t, a.ChangeTier("phil", "pro"))
// Add 10 reservations (pro tier allows that)
@@ -1007,7 +1017,7 @@ func TestManager_Tier_Change_And_Reset(t *testing.T) {
func TestUser_PhoneNumberAddListRemove(t *testing.T) {
a := newTestManager(t, PermissionDenyAll)
require.Nil(t, a.AddUser("phil", "phil", RoleUser))
require.Nil(t, a.AddUser("phil", "phil", RoleUser, false))
phil, err := a.User("phil")
require.Nil(t, err)
require.Nil(t, a.AddPhoneNumber(phil.ID, "+1234567890"))
@@ -1032,8 +1042,8 @@ func TestUser_PhoneNumberAddListRemove(t *testing.T) {
func TestUser_PhoneNumberAdd_Multiple_Users_Same_Number(t *testing.T) {
a := newTestManager(t, PermissionDenyAll)
require.Nil(t, a.AddUser("phil", "phil", RoleUser))
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
require.Nil(t, a.AddUser("phil", "phil", RoleUser, false))
require.Nil(t, a.AddUser("ben", "ben", RoleUser, false))
phil, err := a.User("phil")
require.Nil(t, err)
ben, err := a.User("ben")

1247
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -44,8 +44,8 @@
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"prettier": "^2.8.8",
"vite": "^4.3.9",
"vite-plugin-pwa": "^0.15.0"
"vite": "^6.3.5",
"vite-plugin-pwa": "^1.0.0"
},
"browserslist": {
"production": [

View File

@@ -9,6 +9,7 @@ var config = {
base_url: window.location.origin, // Change to test against a different server
app_root: "/",
enable_login: true,
require_login: true,
enable_signup: true,
enable_payments: false,
enable_reservations: true,

View File

@@ -359,5 +359,6 @@
"account_basics_phone_numbers_dialog_verify_button_call": "اتصل بي",
"account_basics_phone_numbers_dialog_code_label": "رمز التحقّق",
"account_upgrade_dialog_tier_price_per_month": "شهر",
"prefs_appearance_theme_title": "الحُلّة"
"prefs_appearance_theme_title": "الحُلّة",
"subscribe_dialog_subscribe_use_another_background_info": "لن يتم استلام الاشعارات من الخوادم الخارجية عندما يكون تطبيق الويب مغلقاً"
}

View File

@@ -212,7 +212,7 @@
"nav_upgrade_banner_label": "Надграждане до ntfy Pro",
"signup_form_confirm_password": "Парола отново",
"signup_disabled": "Регистрациите са затворени",
"signup_error_creation_limit_reached": "Достигнатео е ограничението за създаване на профили",
"signup_error_creation_limit_reached": "Достигнато е ограничението за създаване на профили",
"display_name_dialog_title": "Промяна на показваното име",
"action_bar_reservation_edit": "Промяна на резервацията",
"action_bar_sign_up": "Регистриране",

View File

@@ -0,0 +1 @@
{}

View File

@@ -36,7 +36,7 @@
"message_bar_type_message": "Gib hier eine Nachricht ein",
"message_bar_error_publishing": "Fehler beim Senden der Benachrichtigung",
"alert_not_supported_title": "Benachrichtigungen werden nicht unterstützt",
"alert_not_supported_description": "Benachrichtigungen werden von Deinem Browser nicht unterstützt.",
"alert_not_supported_description": "Benachrichtigungen werden von Deinem Browser nicht unterstützt",
"action_bar_settings": "Einstellungen",
"action_bar_clear_notifications": "Alle Benachrichtigungen löschen",
"alert_notification_permission_required_button": "Jetzt erlauben",
@@ -383,5 +383,25 @@
"account_upgrade_dialog_tier_features_calls_one": "{{calls}} Telefonanrufe pro Tag",
"action_bar_mute_notifications": "Benachrichtigungen stummschalten",
"action_bar_unmute_notifications": "Benachrichtigungen laut schalten",
"alert_notification_permission_denied_title": "Benachrichtigungen sind blockiert"
"alert_notification_permission_denied_title": "Benachrichtigungen sind blockiert",
"alert_notification_permission_denied_description": "Bitte reaktiviere diese in deinem Browser",
"notifications_actions_failed_notification": "Aktion nicht erfolgreich",
"alert_notification_ios_install_required_title": "iOS Installation erforderlich",
"alert_notification_ios_install_required_description": "Klicke auf das Teilen-Symbol und “Zum Home-Bildschirm” um auf iOS Benachrichtigungen zu aktivieren",
"subscribe_dialog_subscribe_use_another_background_info": "Benachrichtigungen von anderen Servern werden nicht empfangen, wenn die Web App nicht geöffnet ist",
"publish_dialog_checkbox_markdown": "Als Markdown formatieren",
"prefs_notifications_web_push_title": "Hintergrund-Benachrichtigungen",
"prefs_notifications_web_push_disabled_description": "Benachrichtigungen werden empfangen wenn die Web App läuft (über WebSocket)",
"prefs_notifications_web_push_enabled": "Aktiviert für {{server}}",
"prefs_notifications_web_push_disabled": "Deaktiviert",
"prefs_appearance_theme_title": "Thema",
"prefs_appearance_theme_system": "System (Standard)",
"prefs_appearance_theme_dark": "Nachtmodus",
"prefs_appearance_theme_light": "Tagmodus",
"error_boundary_button_reload_ntfy": "ntfy neu laden",
"web_push_subscription_expiring_title": "Benachrichtigungen werden pausiert",
"web_push_subscription_expiring_body": "Öffne ntfy um weiterhin Benachrichtigungen zu erhalten",
"web_push_unknown_notification_title": "Unbekannte Benachrichtigung vom server empfangen",
"web_push_unknown_notification_body": "Du musst möglicherweise ntfy aktualisieren, indem du die Web App öffnest.",
"prefs_notifications_web_push_enabled_description": "Benachrichtigungen werden empfangen, auch wenn die Web App nicht läuft (über Web Push)"
}

View File

@@ -97,6 +97,8 @@
"notifications_none_for_any_description": "To send notifications to a topic, simply PUT or POST to the topic URL. Here's an example using one of your topics.",
"notifications_no_subscriptions_title": "It looks like you don't have any subscriptions yet.",
"notifications_no_subscriptions_description": "Click the \"{{linktext}}\" link to create or subscribe to a topic. After that, you can send messages via PUT or POST and you'll receive notifications here.",
"notifications_no_subscriptions_login_title": "This page requires a Login.",
"notifications_no_subscriptions_login_description": "Click \"{{linktext}}\" to login into your account.",
"notifications_example": "Example",
"notifications_more_details": "For more information, check out the <websiteLink>website</websiteLink> or <docsLink>documentation</docsLink>.",
"display_name_dialog_title": "Change display name",

View File

@@ -22,5 +22,82 @@
"common_add": "Lisa",
"signup_form_button_submit": "Liitu",
"signup_form_toggle_password_visibility": "Vaheta salasõna nähtavust",
"action_bar_account": "Kasutajakonto"
"action_bar_account": "Kasutajakonto",
"action_bar_sign_in": "Logi sisse",
"nav_button_documentation": "Dokumentatsioon",
"action_bar_profile_title": "Profiil",
"action_bar_profile_settings": "Seadistused",
"action_bar_sign_up": "Liitu",
"message_bar_type_message": "Sisesta oma sõnum siia",
"message_bar_error_publishing": "Viga teavituse avaldamisel",
"message_bar_show_dialog": "Näita avaldamisvaadet",
"message_bar_publish": "Avalda sõnum",
"nav_topics_title": "Tellitud teemad",
"nav_button_all_notifications": "Kõik teavitused",
"nav_button_account": "Kasutajakonto",
"nav_button_settings": "Seadistused",
"nav_button_publish_message": "Avalda teavitus",
"nav_button_subscribe": "Telli teema",
"nav_button_muted": "Teavitused on summutatud",
"nav_button_connecting": "loome ühendust",
"nav_upgrade_banner_label": "Uuenda ntfy Pro teenuseks",
"action_bar_profile_logout": "Logi välja",
"notifications_list_item": "Teavitus",
"account_tokens_table_expires_header": "Aegub",
"notifications_attachment_file_document": "muu dokument",
"notifications_list": "Teavituste loend",
"notifications_delete": "Kustuta",
"notifications_copied_to_clipboard": "Kopeeritud lõikelauale",
"alert_notification_permission_denied_description": "Palun luba nad veebibrauseris uuesti",
"account_tokens_table_last_access_header": "Viimase kasutamise aeg",
"account_tokens_table_token_header": "Tunnusluba",
"account_tokens_table_last_origin_tooltip": "IP-aadressilt {{ip}}, klõpsi täpsema teabe nägemiseks",
"action_bar_reservation_add": "Reserveeri teema",
"action_bar_reservation_edit": "Muuda reserveeringut",
"action_bar_reservation_delete": "Eemalda reserveering",
"action_bar_reservation_limit_reached": "Ülempiir on käes",
"action_bar_send_test_notification": "Saata testteavitus",
"action_bar_clear_notifications": "Kustuta kõik teavitused",
"action_bar_mute_notifications": "Summuta teavitused",
"nav_upgrade_banner_description": "Reserveeri teemasid, rohkem sõnumeid ja e-kirju ning suuremad manused",
"action_bar_unmute_notifications": "Lõpeta teavituste summutamine",
"action_bar_unsubscribe": "Lõpeta tellimus",
"action_bar_toggle_mute": "Lülita teavituste summutamine sisse/välja",
"action_bar_toggle_action_menu": "Ava/sulge tegevuste menüü",
"notifications_mark_read": "Märgi loetuks",
"notifications_tags": "Sildid",
"notifications_priority_x": "{{priority}}. prioriteet",
"notifications_new_indicator": "Uus teavitus",
"notifications_attachment_image": "Pilt manusena",
"notifications_attachment_copy_url_title": "Kopeeri manuse võrguaadress lõikelauale",
"notifications_attachment_copy_url_button": "Kopeeri võrguaadress",
"notifications_attachment_open_title": "Ava {{url}} aadress",
"notifications_attachment_open_button": "Ava manus",
"notifications_attachment_link_expires": "link aegub {{date}}",
"notifications_attachment_link_expired": "allalaadimise link on aegunud",
"notifications_attachment_file_image": "pildifail",
"notifications_attachment_file_video": "videofail",
"notifications_attachment_file_audio": "helifail",
"notifications_attachment_file_app": "Androidi rakenduse fail",
"notifications_click_copy_url_title": "Kopeeri lingi võrguaadress lõikelauale",
"notifications_click_copy_url_button": "Kopeeri link",
"notifications_click_open_button": "Ava link",
"notifications_actions_open_url_title": "Ava {{url}} aadress",
"notifications_actions_not_supported": "Toiming pole veebirakenduses toetatud",
"alert_notification_permission_required_title": "Teavitused pole kasutusel",
"alert_notification_permission_required_description": "Anna oma brauserile õigused näidata töölauateavitusi",
"alert_notification_permission_required_button": "Luba nüüd",
"alert_notification_permission_denied_title": "Teavitused on blokeeritud",
"alert_notification_ios_install_required_title": "Vajalik on iOS-i paigaldamine",
"alert_not_supported_title": "Teavitused pole toetatud",
"alert_not_supported_description": "Teavitused pole sinu veebibrauseris toetatud",
"account_tokens_table_label_header": "Silt",
"account_tokens_table_never_expires": "Ei aegu iialgi",
"account_tokens_table_current_session": "Praegune brauserisessioon",
"account_tokens_table_copied_to_clipboard": "Ligipääsu tunnusluba on kopeeritud",
"account_tokens_table_cannot_delete_or_edit": "Praeguse sessiooni tunnusluba ei saa muuta ega kustutada",
"account_tokens_table_create_token_button": "Loo ligipääsuks vajalik tunnusluba",
"account_tokens_dialog_title_create": "Loo ligipääsuks vajalik tunnusluba",
"account_tokens_dialog_title_edit": "Muuda ligipääsuks vajalikku tunnusluba",
"account_tokens_dialog_title_delete": "Kustuta ligipääsuks vajalik tunnusluba"
}

View File

@@ -32,5 +32,27 @@
"action_bar_reservation_edit": "تغییر رزرو",
"action_bar_reservation_delete": "حذف رزرو",
"action_bar_mute_notifications": "ساکت کردن اعلان ها",
"action_bar_clear_notifications": "پاک کردن تمام اعلان ها"
"action_bar_clear_notifications": "پاک کردن تمام اعلان ها",
"action_bar_toggle_action_menu": "گشودن يا بستن فهرست کنش",
"action_bar_profile_title": "نمايه",
"action_bar_profile_settings": "تنظیمات",
"action_bar_profile_logout": "خروج",
"action_bar_sign_in": "ورود",
"action_bar_sign_up": "ثبت نام",
"message_bar_type_message": "یک پیام بنویسید",
"message_bar_error_publishing": "خطا در انتظار اعلان",
"message_bar_publish": "انتشار پیام",
"nav_button_all_notifications": "همه اعلان‌ها",
"nav_button_account": "حساب کاربری",
"nav_button_settings": "تنظیمات",
"nav_button_documentation": "مستندات",
"nav_button_publish_message": "انتشار اعلان",
"nav_button_muted": "اعلان بی‌صدا شد",
"nav_button_connecting": "در حال اتصال",
"nav_upgrade_banner_label": "ارتقا با ntfy پیشرفته",
"alert_notification_permission_required_title": "اعلان‌ها غیرفعال هستند",
"alert_notification_permission_required_description": "به مرورگر خود اجازه دهید تا اعلان‌های دسکتاپ را نمایش دهد",
"alert_notification_permission_denied_title": "اعلان‌ها مسدود هستند",
"alert_notification_ios_install_required_title": "لازم به نصب نسخه iOS است",
"alert_notification_ios_install_required_description": "برای فعال کردن اعلان‌ها در iOS، روی نماد اشتراک‌گذاری و افزودن به صفحه اصلی کلیک کنید"
}

View File

@@ -170,7 +170,7 @@
"account_basics_tier_description": "Tilisi taso",
"account_basics_phone_numbers_description": "Puheluilmoituksia varten",
"prefs_reservations_dialog_title_add": "Varaa topikki",
"account_basics_tier_free": "Vapaa",
"account_basics_tier_free": "Maksuton",
"account_upgrade_dialog_cancel_warning": "Tämä <strong>peruuttaa tilauksesi</strong> ja alentaa tilisi {{date}}. Tuona päivänä topikit sekä palvelimen välimuistissa olevat viestit <strong>poistetaan</strong>.",
"notifications_click_copy_url_button": "Kopioi linkki",
"account_basics_tier_admin": "Admin",
@@ -266,7 +266,7 @@
"alert_not_supported_title": "Ilmoituksia ei tueta",
"account_tokens_dialog_button_cancel": "Peruuta",
"subscribe_dialog_error_user_anonymous": "Anonyymi",
"account_upgrade_dialog_tier_price_billed_yearly": "{{price}} laskutetaan vuosittain. Tallenna {{save}}.",
"account_upgrade_dialog_tier_price_billed_yearly": "{{price}} laskutetaan vuosittain. Säästä {{save}}.",
"prefs_notifications_min_priority_high_and_higher": "Korkea prioriteetti ja korkeammat",
"account_usage_basis_ip_description": "Tämän tilin käyttötilastot ja rajoitukset perustuvat IP-osoitteeseesi, joten ne voidaan jakaa muiden käyttäjien kanssa. Yllä esitetyt rajat ovat likimääräisiä perustuen olemassa oleviin rajoituksiin.",
"publish_dialog_priority_high": "Korkea prioriteetti",
@@ -400,5 +400,11 @@
"error_boundary_button_reload_ntfy": "Lataa ntfy uudelleen",
"web_push_subscription_expiring_title": "Ilmoitukset keskeytetään",
"web_push_subscription_expiring_body": "Avaa ntfy jatkaaksesi ilmoitusten vastaanottamista",
"web_push_unknown_notification_title": "Tuntematon ilmoitus vastaanotettu palvelimelta"
"web_push_unknown_notification_title": "Tuntematon ilmoitus vastaanotettu palvelimelta",
"alert_notification_ios_install_required_description": "Napauta Jaa-kuvaketta ja Lisää aloitusnäyttöön ottaaksesi ilmoitukset käyttöön iOS:ssä",
"prefs_notifications_web_push_disabled_description": "Ilmoituksia vastaanotetaan, kun verkkosovellus on käynnissä (WebSocket:in kautta)",
"web_push_unknown_notification_body": "Voit joutua päivittämään ntfy:n avaamalla verkkosovelluksen",
"notifications_actions_failed_notification": "Epäonnistunut toiminto",
"subscribe_dialog_subscribe_use_another_background_info": "Ilmoituksia muilta palvelimilta ei vastaanoteta, mikäli verkkosovellus ei ole avoinna",
"prefs_notifications_web_push_enabled_description": "Ilmoituksia vastaanotetaan siitä huolimatta, että verkkosovellus ei ole käynnissä (Web Push:n kautta)"
}

View File

@@ -62,7 +62,7 @@
"notifications_none_for_topic_title": "Aínda non recibiches ningunha notificación para este tema.",
"reserve_dialog_checkbox_label": "Reservar tema e configurar acceso",
"notifications_loading": "Cargando notificacións…",
"publish_dialog_base_url_placeholder": "URL de servizo, ex. https://exemplo.com",
"publish_dialog_base_url_placeholder": "URL do servizo, ex. https://exemplo.com",
"publish_dialog_topic_label": "Nome do tema",
"publish_dialog_topic_placeholder": "Nome do tema, ex. alertas_equipo",
"publish_dialog_topic_reset": "Restablecer tema",
@@ -172,7 +172,7 @@
"account_tokens_table_token_header": "Token",
"prefs_notifications_delete_after_never": "Nunca",
"prefs_users_description": "Engadir/eliminar usuarias dos temas protexidos. Ten en conta que as credenciais gárdanse na almacenaxe local do navegador.",
"subscribe_dialog_subscribe_description": "Os temas poderían non estar proxetidos con contrasinal, así que elixe un nome complicado de adiviñar. Unha vez subscrita, podes PUT/POST notificacións.",
"subscribe_dialog_subscribe_description": "Os temas poden non estar protexidos con contrasinal, asi que escolle un nome que non sexa fácil de pesquisar. Unha vez suscrito, podes notificar con PUT/POST.",
"account_upgrade_dialog_interval_yearly_discount_save_up_to": "aforro ata un {{discount}}%",
"account_tokens_dialog_label": "Etiqueta, ex. notificación de Radarr",
"account_tokens_table_expires_header": "Caducidade",
@@ -315,17 +315,17 @@
"account_basics_password_dialog_current_password_incorrect": "Contrasinal incorrecto",
"account_basics_phone_numbers_dialog_number_label": "Número de teléfono",
"account_basics_password_dialog_button_submit": "Modificar contrasinal",
"account_basics_username_title": "Usuario",
"account_basics_username_title": "Identificador",
"account_basics_phone_numbers_dialog_check_verification_button": "Código de confirmación",
"account_usage_messages_title": "Mesaxes publicados",
"account_basics_phone_numbers_dialog_verify_button_sms": "Enviar SMS",
"account_basics_tier_change_button": "Cambiar",
"account_basics_phone_numbers_dialog_description": "Para usar a característica de chamadas de teléfono, vostede debe engadir e verificar ao menos un número de teléfono. A verificación pode ser realizada vía SMS ou a través de chamada.",
"account_delete_title": "Borrar conta",
"account_delete_title": "Eliminar a conta",
"account_delete_dialog_label": "Contrasinal",
"account_basics_tier_admin_suffix_with_tier": "(con tier {{tier}})",
"subscribe_dialog_login_username_label": "Nome de usuario, ex. phil",
"subscribe_dialog_error_user_not_authorized": "Usuario {{username}} non autorizado",
"subscribe_dialog_login_username_label": "Identificador, ex. xoana",
"subscribe_dialog_error_user_not_authorized": "Identificador {{username}} non autorizado",
"account_basics_title": "Conta",
"account_basics_phone_numbers_no_phone_numbers_yet": "Aínda non hay números de teléfono",
"subscribe_dialog_subscribe_button_generate_topic_name": "Xerar nome",
@@ -333,9 +333,9 @@
"subscribe_dialog_subscribe_button_subscribe": "Subscribirse",
"account_basics_phone_numbers_dialog_title": "Engadir número de teléfono",
"account_basics_username_admin_tooltip": "É vostede Admin",
"account_delete_dialog_description": "Isto borrará permanentemente a túa conta, incluido todos os datos almacenados no servidor. Despois do borrado, o teu nome de usuario non estará dispoñible durante 7 días. Se realmente queres proceder, por favor confirme co seu contrasinal na caixa inferior.",
"account_delete_dialog_description": "Isto borrará permanentemente a conta, incluido todos os datos almacenados no servidor. Despois do borrado, o teu identificador non estará dispoñible durante 7 días. Se realmente queres proceder, por favor confirma co contrasinal na caixa inferior.",
"account_usage_reservations_none": "Non hai temas reservados para esta conta",
"subscribe_dialog_subscribe_topic_placeholder": "Nome do tema, ex. phil_alertas",
"subscribe_dialog_subscribe_topic_placeholder": "Nome do tema, ex. alertas_xoana",
"account_usage_title": "Uso",
"account_basics_tier_upgrade_button": "Mexorar a Pro",
"subscribe_dialog_error_topic_already_reserved": "Tema xa reservado",
@@ -351,11 +351,11 @@
"account_basics_phone_numbers_copied_to_clipboard": "Número de teléfono copiado no portapapeis",
"account_basics_tier_title": "Tipo de conta",
"account_usage_cannot_create_portal_session": "Non foi posible abrir o portal de pagos",
"account_delete_description": "Borrar permanentemente a túa conta",
"account_delete_description": "Eliminar a conta de xeito definitivo",
"account_basics_phone_numbers_dialog_number_placeholder": "ex. +1222333444",
"account_basics_phone_numbers_dialog_code_placeholder": "ex. 123456",
"account_basics_tier_manage_billing_button": "Xestionar pagos",
"account_basics_username_description": "Ei, ese eres ti ❤",
"account_basics_username_description": "Ei, es ti ❤",
"account_basics_password_dialog_confirm_password_label": "Confirmar contrasinal",
"account_basics_tier_interval_yearly": "anual",
"account_delete_dialog_button_submit": "Borrar permanentemente a conta",
@@ -364,7 +364,7 @@
"account_basics_password_dialog_new_password_label": "Novo contrasinal",
"account_usage_of_limit": "de {{limit}}",
"subscribe_dialog_error_user_anonymous": "anónimo",
"account_usage_basis_ip_description": "Estadísticas de uso e límites para esta conta están basados na sua IP, polo que poden estar compartidos con outros usuarios. Os limites mostrados son aproximados, basados nos ratios de limite existentes.",
"account_usage_basis_ip_description": "As estatísticas de uso e límites para esta conta están basados na IP, polo que poden estar compartidas con outras usuarias. Os limites mostrados son aproximados, baseados nos límites das taxas existentes.",
"account_basics_password_dialog_title": "Modificar contrasinal",
"account_usage_limits_reset_daily": "Límite de uso é reiniciado diariamente a medianoite (UTC(",
"account_usage_unlimited": "Sen límites",
@@ -380,7 +380,7 @@
"account_basics_phone_numbers_dialog_verify_button_call": "Chámame",
"account_usage_emails_title": "Emails enviados",
"account_basics_phone_numbers_dialog_channel_sms": "SMS",
"subscribe_dialog_login_description": "Este tema está protexido por contrasinal. Por favor, introduza o usuario e contrasinal para subscribirse.",
"subscribe_dialog_login_description": "Este tema está protexido por contrasinal. Por favor, escribe as credenciais para subscribirte.",
"action_bar_mute_notifications": "Acalar notificacións",
"action_bar_unmute_notifications": "Reactivar notificacións",
"alert_notification_permission_required_title": "Notificacións desactivadas",

View File

@@ -1,7 +1,7 @@
{
"action_bar_send_test_notification": "Teszt értesítés küldése",
"action_bar_clear_notifications": "Összes értesítés törlése",
"alert_not_supported_description": "A böngésző nem támogatja az értesítések fogadását.",
"alert_not_supported_description": "A böngésződ nem támogatja az értesítések fogadását",
"action_bar_settings": "Beállítások",
"action_bar_unsubscribe": "Leiratkozás",
"message_bar_type_message": "Írd ide az üzenetet",
@@ -9,19 +9,19 @@
"nav_button_all_notifications": "Összes értesítés",
"nav_topics_title": "Feliratkozott témák",
"alert_notification_permission_required_title": "Az értesítések le vannak tiltva",
"alert_notification_permission_required_description": "Engedélyezd a böngészőnek, hogy asztali értesítéseket jeleníttessen meg.",
"alert_notification_permission_required_description": "Engedélyezd a böngésződnek, hogy asztali értesítéseket jelenítsen meg",
"nav_button_settings": "Beállítások",
"nav_button_documentation": "Dokumentáció",
"nav_button_publish_message": "Értesítés küldése",
"alert_notification_permission_required_button": "Engedélyezés",
"alert_not_supported_title": "Nem támogatott funkció",
"notifications_copied_to_clipboard": "Másolva a vágólapra",
"alert_not_supported_title": "Az értesítések nincsenek támogatva",
"notifications_copied_to_clipboard": "Vágólapra másolva",
"notifications_tags": "Címkék",
"notifications_attachment_copy_url_title": "Másolja vágólapra a csatolmány URL-ét",
"notifications_attachment_copy_url_button": "URL másolása",
"notifications_attachment_open_title": "Menjen a(z) {{url}} címre",
"notifications_attachment_open_button": "Csatolmány megnyitása",
"notifications_attachment_link_expired": "A letöltési hivatkozás lejárt",
"notifications_attachment_link_expired": "A letöltési link lejárt",
"notifications_attachment_link_expires": "A hivatkozás {{date}}-kor jár le",
"nav_button_subscribe": "Feliratkozás témára",
"notifications_click_copy_url_title": "Másolja vágólapra a hivatkozás URL-ét",
@@ -187,5 +187,33 @@
"prefs_users_edit_button": "Felhasználó szerkesztése",
"prefs_users_delete_button": "Felhasználó törlése",
"error_boundary_unsupported_indexeddb_title": "Privát böngészés nem támogatott",
"subscribe_dialog_subscribe_base_url_label": "Szolgáltató URL"
"subscribe_dialog_subscribe_base_url_label": "Szolgáltató URL",
"signup_form_username": "Felhasználónév",
"signup_form_password": "Jelszó",
"signup_form_button_submit": "Regisztráció",
"login_form_button_submit": "Bejelentkezés",
"login_link_signup": "Regisztráció",
"login_disabled": "Bejelentkezés kikapcsolva",
"action_bar_change_display_name": "Megjelenített név módosítása",
"action_bar_profile_logout": "Kijelentkezés",
"action_bar_sign_in": "Bejelentkezés",
"action_bar_sign_up": "Regisztráció",
"action_bar_profile_title": "Profil",
"nav_button_account": "Fiók",
"common_copy_to_clipboard": "Másolás vágólapra",
"action_bar_reservation_limit_reached": "Limit elérve",
"login_title": "Jelentkezz be a ntfy felhasználódba",
"signup_title": "Hozz létre egy ntfy felhasználói fiókot",
"signup_form_confirm_password": "Jelszó megerősítése",
"signup_already_have_account": "Már van felhasználód? Jelentkezz be!",
"action_bar_account": "Fiók",
"action_bar_profile_settings": "Beállítások",
"signup_error_username_taken": "A felhasználónév {{username}} már foglalt",
"signup_error_creation_limit_reached": "Felhasználói regisztráció limit elérve",
"action_bar_mute_notifications": "Értesítések némítása",
"action_bar_unmute_notifications": "Értesítések némításának feloldása",
"alert_notification_permission_denied_title": "Az értesítések blokkolva vannak",
"alert_notification_permission_denied_description": "Kérjük kapcsold őket vissza a böngésződben",
"alert_notification_ios_install_required_title": "iOS telepítés szükséges",
"alert_not_supported_context_description": "Az értesítések kizárólag HTTPS-en keresztül támogatottak. Ez a <mdnLink>Notifications API</mdnLink> korlátozása."
}

View File

@@ -24,7 +24,7 @@
"nav_button_subscribe": "Berlangganan ke topik",
"alert_notification_permission_required_title": "Notifikasi dinonaktifkan",
"alert_notification_permission_required_description": "Berikan izin ke peramban untuk menampilkan notifikasi desktop.",
"alert_not_supported_description": "Notifikasi tidak didukung dalam peramban Anda.",
"alert_not_supported_description": "Notifikasi tidak didukung dalam peramban Anda",
"notifications_attachment_open_title": "Pergi ke {{url}}",
"notifications_attachment_open_button": "Buka lampiran",
"notifications_attachment_link_expires": "tautan kadaluwarsa {{date}}",

View File

@@ -316,5 +316,92 @@
"action_bar_unmute_notifications": "Riattiva audio notifiche",
"alert_notification_ios_install_required_title": "E' richiesta l'installazione di iOS",
"alert_notification_ios_install_required_description": "Fare clic sull'icona Condividi e Aggiungi alla schermata home per abilitare le notifiche su iOS",
"publish_dialog_checkbox_markdown": "Formatta come markdown"
"publish_dialog_checkbox_markdown": "Formatta come markdown",
"account_upgrade_dialog_interval_yearly": "Annualmente",
"account_tokens_table_token_header": "Token",
"account_tokens_table_label_header": "Etichetta",
"account_tokens_table_cannot_delete_or_edit": "Impossibile modificare o eliminare il token della sessione corrente",
"account_tokens_dialog_label": "Etichetta, ad esempio Notifiche Radarr",
"account_tokens_dialog_title_delete": "Elimina token di accesso",
"account_tokens_dialog_title_edit": "Modifica token di accesso",
"account_tokens_dialog_button_create": "Crea token",
"account_tokens_dialog_button_update": "Aggiorna token",
"account_upgrade_dialog_tier_features_emails_one": "{{emails}} e-mails giornaliere",
"account_upgrade_dialog_tier_features_messages_other": "{{messages}} messaggi giornalieri",
"account_upgrade_dialog_tier_features_attachment_file_size": "{{filesize}} per file",
"account_upgrade_dialog_tier_features_attachment_total_size": "{{totalsize}} spazio di archiviazione totale",
"notifications_actions_failed_notification": "Azione non riuscita",
"account_usage_attachment_storage_description": "{{filesize}} per file, eliminato dopo {{expiry}}",
"account_upgrade_dialog_title": "Cambia livello account",
"account_upgrade_dialog_interval_monthly": "Mensilmente",
"account_upgrade_dialog_cancel_warning": "Questa azione <strong>annullerà il tuo abbonamento</strong> e declasserà il tuo account il {{date}}. In quella data, le prenotazioni degli argomenti e i messaggi memorizzati nella cache del server <strong>verranno eliminati</strong>.",
"account_upgrade_dialog_reservations_warning_other": "Il livello selezionato consente meno argomenti riservati rispetto al livello attuale. Prima di cambiare il livello, <strong>elimina almeno {{count}} prenotazioni</strong>. Puoi rimuovere le prenotazioni nelle <Link>Impostazioni</Link>.",
"account_upgrade_dialog_tier_features_reservations_other": "{{reservations}} argomenti riservati",
"account_upgrade_dialog_tier_features_emails_other": "{{emails}} e-mail giornaliere",
"account_upgrade_dialog_tier_features_calls_one": "{{calls}} telefonate giornaliere",
"account_upgrade_dialog_tier_features_calls_other": "{{calls}} telefonate giornaliere",
"account_upgrade_dialog_tier_features_no_calls": "Nessuna telefonata",
"account_tokens_description": "Utilizza i token di accesso quando pubblichi e ti iscrivi tramite l'API ntfy, così non dovrai inviare le credenziali del tuo account. Consulta la <Link>documentazione</Link> per saperne di più.",
"account_tokens_table_copied_to_clipboard": "Token di accesso copiato",
"account_tokens_table_create_token_button": "Crea token di accesso",
"account_tokens_table_last_origin_tooltip": "Dall'indirizzo IP {{ip}}, clicca per cercare",
"account_tokens_dialog_title_create": "Crea token di accesso",
"account_tokens_dialog_button_cancel": "Annulla",
"web_push_unknown_notification_body": "Potrebbe essere necessario aggiornare ntfy aprendo l'app web",
"account_upgrade_dialog_proration_info": "<strong>Prorata</strong>: quando si esegue l'upgrade tra piani a pagamento, la differenza di prezzo verrà <strong>addebitata immediatamente</strong>. Quando si esegue il downgrade a un livello inferiore, il saldo verrà utilizzato per pagare i periodi di fatturazione futuri.",
"account_tokens_table_last_access_header": "Ultimo accesso",
"account_tokens_table_expires_header": "Scade",
"account_tokens_table_never_expires": "Non scade mai",
"account_tokens_table_current_session": "Sessione corrente del browser",
"account_upgrade_dialog_interval_yearly_discount_save_up_to": "Risparmia fino al {{discount}}%",
"account_upgrade_dialog_interval_yearly_discount_save": "conserva {{discount}}%",
"prefs_users_description_no_sync": "Gli utenti e le password non vengono sincronizzati con il tuo account.",
"prefs_reservations_title": "Argomenti riservati",
"prefs_reservations_table_access_header": "Accesso",
"reservation_delete_dialog_action_delete_title": "Elimina i messaggi e gli allegati memorizzati nella cache",
"reservation_delete_dialog_submit_button": "Elimina prenotazione",
"account_tokens_dialog_expires_label": "Il token di accesso scade tra",
"account_tokens_dialog_expires_unchanged": "Lascia la data di scadenza invariata",
"account_tokens_delete_dialog_submit_button": "Elimina definitivamente il token",
"prefs_reservations_description": "Qui puoi riservare i nomi degli argomenti per uso personale. Riservare un argomento ti dà la proprietà dell'argomento e ti consente di definire i permessi di accesso per altri utenti sull'argomento.",
"prefs_reservations_add_button": "Aggiungi argomento riservato",
"prefs_reservations_edit_button": "Modifica accesso argomento",
"prefs_reservations_delete_button": "Reimposta accesso argomento",
"prefs_reservations_table_everyone_read_only": "Posso pubblicare e iscrivermi, tutti possono iscriversi",
"prefs_reservations_table_not_subscribed": "Non iscritto",
"prefs_reservations_table_everyone_write_only": "Posso pubblicare ed iscrivermi, tutti possono pubblicare",
"prefs_reservations_table_everyone_read_write": "Tutti possono pubblicare e iscriversi",
"prefs_reservations_dialog_title_delete": "Elimina prenotazione argomento",
"prefs_reservations_dialog_description": "Prenotando un argomento ne diventi proprietario e puoi definire le autorizzazioni di accesso per altri utenti.",
"reservation_delete_dialog_action_keep_description": "I messaggi e gli allegati memorizzati nella cache del server diventeranno visibili al pubblico per le persone a conoscenza del nome dell'argomento.",
"reservation_delete_dialog_action_delete_description": "I messaggi e gli allegati memorizzati nella cache verranno eliminati definitivamente. Questa azione non può essere annullata.",
"prefs_reservations_limit_reached": "Hai raggiunto il limite di argomenti riservati.",
"prefs_reservations_table_click_to_subscribe": "Clicca per iscriverti",
"prefs_reservations_dialog_title_add": "Prenota argomento",
"prefs_reservations_dialog_title_edit": "Modifica argomento riservato",
"account_tokens_dialog_expires_x_days": "Il token scade tra {{days}} giorni",
"account_tokens_dialog_expires_never": "Il token non scade mai",
"account_tokens_delete_dialog_title": "Elimina token di accesso",
"account_tokens_delete_dialog_description": "Prima di eliminare un token di accesso, assicurati che nessuna applicazione o script lo stia utilizzando attivamente. <strong>Questa azione non può essere annullata</strong>.",
"prefs_notifications_web_push_title": "Notifiche in background",
"prefs_notifications_web_push_enabled_description": "Le notifiche vengono ricevute anche quando l'app Web non è in esecuzione (tramite Web Push)",
"prefs_notifications_web_push_disabled_description": "Le notifiche vengono ricevute quando l'app Web è in esecuzione (tramite WebSocket)",
"prefs_notifications_web_push_enabled": "Abilitato per {{server}}",
"prefs_notifications_web_push_disabled": "Disabilitato",
"prefs_users_table_cannot_delete_or_edit": "Impossibile eliminare o modificare l'utente registrato",
"prefs_appearance_theme_title": "Tema",
"prefs_appearance_theme_system": "Sistema (predefinito)",
"prefs_appearance_theme_dark": "Modalità scura",
"prefs_appearance_theme_light": "Modalità chiara",
"prefs_reservations_table_topic_header": "Argomento",
"prefs_reservations_dialog_access_label": "Accesso",
"reservation_delete_dialog_description": "La rimozione di una prenotazione comporta la rinuncia alla proprietà dell'argomento e consente ad altri di riservarlo. Puoi mantenere o eliminare i messaggi e gli allegati esistenti.",
"prefs_reservations_table_everyone_deny_all": "Solo io posso pubblicare e iscrivermi",
"prefs_reservations_dialog_topic_label": "Argomento",
"reservation_delete_dialog_action_keep_title": "Mantieni i messaggi e gli allegati memorizzati nella cache",
"web_push_subscription_expiring_title": "Le notifiche verranno sospese",
"web_push_subscription_expiring_body": "Apri ntfy per continuare a ricevere notifiche",
"web_push_unknown_notification_title": "Notifica sconosciuta ricevuta dal server",
"account_tokens_dialog_expires_x_hours": "Il token scade tra {{hours}} ore",
"prefs_reservations_table": "Tabella argomenti riservati"
}

View File

@@ -7,7 +7,7 @@
"action_bar_clear_notifications": "全ての通知を消去",
"action_bar_unsubscribe": "購読解除",
"nav_button_documentation": "ドキュメント",
"alert_not_supported_description": "通知機能はこのブラウザではサポートされていません",
"alert_not_supported_description": "通知機能はこのブラウザではサポートされていません",
"notifications_copied_to_clipboard": "クリップボードにコピーしました",
"notifications_example": "例",
"publish_dialog_title_topic": "{{topic}}に送信",
@@ -28,7 +28,7 @@
"message_bar_type_message": "メッセージを入力してください",
"nav_topics_title": "購読しているトピック",
"nav_button_subscribe": "トピックを購読",
"alert_notification_permission_required_description": "ブラウザのデスクトップ通知を許可してください",
"alert_notification_permission_required_description": "ブラウザのデスクトップ通知を許可してください",
"alert_notification_permission_required_button": "許可する",
"notifications_attachment_link_expires": "リンクは {{date}} に失効します",
"notifications_click_copy_url_button": "リンクをコピー",
@@ -191,7 +191,7 @@
"signup_form_username": "ユーザー名",
"signup_form_password": "パスワード",
"signup_form_confirm_password": "パスワードを確認",
"signup_already_have_account": "アカウントをお持ちならサインイン",
"signup_already_have_account": "アカウントをお持ちならサインイン",
"signup_disabled": "サインアップは無効化されています",
"signup_error_creation_limit_reached": "アカウント作成制限に達しました",
"login_title": "あなたのntfyアカウントにサインイン",
@@ -380,5 +380,28 @@
"account_upgrade_dialog_tier_features_calls_other": "電話 1日 {{calls}} 回",
"publish_dialog_chip_call_no_verified_numbers_tooltip": "認証済み電話番号がありません",
"account_basics_phone_numbers_dialog_channel_sms": "SMS",
"account_basics_phone_numbers_dialog_channel_call": "電話する"
"account_basics_phone_numbers_dialog_channel_call": "電話する",
"error_boundary_button_reload_ntfy": "ntfyをリロード",
"prefs_appearance_theme_light": "ライトモード",
"web_push_subscription_expiring_title": "通知は一時停止されます",
"web_push_subscription_expiring_body": "ntfyを開いて通知の受信を継続させてください",
"alert_notification_ios_install_required_description": "Shareアイコンをクリック・ホーム画面に追加してiOSでの通知を有効化して下さい",
"action_bar_mute_notifications": "通知をミュート",
"action_bar_unmute_notifications": "通知ミュートを解除",
"alert_notification_permission_denied_title": "通知はブロックされています",
"alert_notification_permission_denied_description": "ブラウザで通知を再度有効化してください",
"notifications_actions_failed_notification": "アクション失敗",
"alert_notification_ios_install_required_title": "iOS用インストールが必要です",
"publish_dialog_checkbox_markdown": "Markdownとして表示",
"subscribe_dialog_subscribe_use_another_background_info": "ウェブアプリが開かれていない場合は他のサーバーからの通知は受信されません",
"prefs_notifications_web_push_title": "バックグラウンド通知",
"prefs_notifications_web_push_enabled_description": "ウェブアプリが開かれていなくても通知を受信します (Web Push経由)",
"prefs_notifications_web_push_disabled_description": "ウェブアプリが開かれていなくても通知を受信します (WebSocket経由)",
"prefs_notifications_web_push_enabled": "{{server}}で有効",
"prefs_notifications_web_push_disabled": "無効",
"prefs_appearance_theme_title": "テーマ",
"prefs_appearance_theme_system": "システム (既定)",
"prefs_appearance_theme_dark": "ダークモード",
"web_push_unknown_notification_title": "不明な通知を受信しました",
"web_push_unknown_notification_body": "ウェブアプリを開いてntfyをアップデートする必要があります"
}

View File

@@ -3,7 +3,7 @@
"action_bar_settings": "Innstillinger",
"action_bar_send_test_notification": "Send testmerknad",
"action_bar_clear_notifications": "Tøm alle merknader",
"action_bar_unsubscribe": "Opphev abonnement",
"action_bar_unsubscribe": "Meld av",
"message_bar_type_message": "Skriv en melding her",
"nav_button_all_notifications": "Alle merknader",
"nav_button_settings": "Innstillinger",
@@ -133,8 +133,8 @@
"publish_dialog_chip_delay_label": "Forsink leveringen",
"publish_dialog_details_examples_description": "For eksempler og en detaljert beskrivelse av alle sendefunksjoner, se <docsLink>dokumentasjonen</docsLink>.",
"publish_dialog_base_url_placeholder": "Tjeneste-URL, f.eks. https://example.com",
"alert_notification_permission_required_description": "Gi nettleseren din tillatelse til å vise skrivebordsvarsler.",
"alert_not_supported_description": "Varsler støttes ikke i nettleseren din.",
"alert_notification_permission_required_description": "Gi nettleseren din tillatelse til å vise skrivebordsvarsler",
"alert_not_supported_description": "Varsler støttes ikke i nettleseren din",
"notifications_attachment_file_app": "Android-app-fil",
"notifications_no_subscriptions_description": "Klikk på \"{{linktext}}\"-koblingen for å opprette eller abonnere på et emne. Etter det kan du sende meldinger via PUT eller POST, og du vil motta varsler her.",
"notifications_actions_http_request_title": "Send HTTP {{metode}} til {{url}}",
@@ -195,5 +195,213 @@
"signup_form_username": "Brukernavn",
"signup_form_password": "Passord",
"signup_form_button_submit": "Meld deg på",
"signup_form_confirm_password": "Bekreft passord"
"signup_form_confirm_password": "Bekreft passord",
"signup_disabled": "Registrering er deaktivert",
"common_copy_to_clipboard": "Kopier til utklippstavle",
"signup_form_toggle_password_visibility": "Slå av/på passordvisning",
"signup_already_have_account": "Har du allerede en konto? Logg inn!",
"signup_error_username_taken": "Brukernavnet {{username}} er allerede opptatt",
"signup_error_creation_limit_reached": "Grense for nye kontoer nådd",
"login_title": "Logg inn på ntfy-kontoen din",
"login_form_button_submit": "Logg inn",
"login_link_signup": "Registrer deg",
"login_disabled": "Innlogging deaktivert",
"action_bar_change_display_name": "Endre visningsnavn",
"account_basics_tier_interval_yearly": "årlig",
"account_basics_tier_change_button": "Endre",
"account_usage_reservations_title": "Reserverte emner",
"account_usage_cannot_create_portal_session": "Kunne ikke åpne betalingsportalen",
"account_delete_dialog_label": "Passord",
"account_tokens_table_copied_to_clipboard": "Tilgangstoken kopiert",
"account_tokens_table_last_origin_tooltip": "Fra IP-adresse {{ip}}, klikk for å gjøre oppslag",
"account_tokens_dialog_title_create": "Opprett tilgangstoken",
"account_tokens_delete_dialog_title": "Slett tilgangstoken",
"prefs_users_table_cannot_delete_or_edit": "Kan ikke slette eller redigere innlogget bruker",
"prefs_reservations_table_everyone_deny_all": "Bare jeg kan publisere og abonnere",
"prefs_reservations_dialog_access_label": "Tilgang",
"reservation_delete_dialog_action_keep_title": "Behold mellomlagrede meldinger og vedlegg",
"action_bar_reservation_add": "Reserver emne",
"action_bar_reservation_edit": "Endre reservasjon",
"action_bar_reservation_delete": "Fjern reservasjon",
"action_bar_reservation_limit_reached": "Grense nådd",
"account_basics_phone_numbers_dialog_description": "For å bruke ringevarslingsfunksjonen må du legge til og verifisere minst ett telefonnummer. Verifisering kan gjøres vis SMS eller oppringing.",
"account_basics_tier_interval_monthly": "månedlig",
"account_basics_tier_upgrade_button": "Oppgrader til Pro",
"account_usage_emails_title": "E-poster sendt",
"account_delete_description": "Slett kontoen din permanent",
"account_usage_calls_title": "Telefonsamtaler",
"account_upgrade_dialog_interval_monthly": "Månedlig",
"account_upgrade_dialog_tier_features_reservations_other": "{{reservations}} reserverte emner",
"account_upgrade_dialog_tier_features_messages_other": "{{messages}} daglige meldinger",
"account_upgrade_dialog_tier_features_emails_other": "{{emails}} daglige e-poster",
"account_upgrade_dialog_tier_features_calls_one": "{{calls}} daglige telefonsamtaler",
"account_upgrade_dialog_tier_selected_label": "Valgt",
"account_upgrade_dialog_tier_current_label": "Nåværende",
"account_upgrade_dialog_button_cancel": "Avbryt",
"account_upgrade_dialog_billing_contact_email": "For faktureringsspørsmål, vennligst <Link>kontakt oss</Link> direkte.",
"account_tokens_table_token_header": "Token",
"account_tokens_table_label_header": "Etikett",
"account_tokens_table_cannot_delete_or_edit": "Kan ikke redigere eller slette nåværende økt-token",
"account_tokens_table_create_token_button": "Opprett tilgangstoken",
"account_tokens_dialog_expires_unchanged": "La utløpsdato være uendret",
"account_tokens_dialog_expires_x_hours": "Token utløper om {{hours}} timer",
"account_tokens_delete_dialog_description": "Før du sletter et tilgangstoken, sørg for at ingen applikasjoner eller script bruker det. <strong>Denne handlingen kan ikke angres</strong>.",
"account_tokens_delete_dialog_submit_button": "Slett token permanent",
"prefs_users_description_no_sync": "Brukere og passord synkroniseres ikke til kontoen din.",
"prefs_reservations_dialog_title_delete": "Slett emnereservasjon",
"prefs_reservations_dialog_topic_label": "Emne",
"display_name_dialog_title": "Endre visningsnavn",
"reserve_dialog_checkbox_label": "Rserver emne og sett opp tilgang",
"publish_dialog_chip_call_label": "Telefonsamtale",
"account_basics_tier_free": "Gratis",
"account_basics_tier_basic": "Grunnleggende",
"account_basics_tier_canceled_subscription": "Abonnementet ditt ble avsluttet og blir degradert til en gratiskonto den {{date}}.",
"account_delete_dialog_description": "Dette vil slette kontoen din permanent, inkludert alle data som er lagret på serveren. Etter sletting vil brukernavnet ditt være utilgjengelig i 7 dager. Hvis du virkelig vil fortsette, vennligst bekreft ved å skrive passordet ditt i boksen under.",
"account_upgrade_dialog_proration_info": "<strong>Pro-rate</strong>: Når du oppgraderer mellom betalte kontotyper, vil prisforskjellen <strong>bli fakturert umiddelbart</strong>. Når du nedgraderer til en billigere kontotype, vil det allerede innbetalte beløpet brukes til å betale for fremtidige regningsperioder.",
"account_upgrade_dialog_reservations_warning_other": "Det valgte nivået tillater færre reserverte emner enn ditt nåvære nivå. Før du endrer nivå, <strong>vennligst slett minst {{count}} reservasjoner</strong>. Du kan slette reservasjoner i <Link>Innstillingene</Link>.",
"account_upgrade_dialog_tier_features_messages_one": "{{messages}} daglig melding",
"account_upgrade_dialog_tier_price_billed_monthly": "{{price}} pr. år. Fakturert månedlig.",
"account_upgrade_dialog_button_redirect_signup": "Registrer deg nå",
"account_upgrade_dialog_button_pay_now": "Betal nå og abonner",
"account_upgrade_dialog_button_cancel_subscription": "Avslutt abonnement",
"account_tokens_description": "Bruk tilgangstokener når du publiserer og abonnerer via ntfy-APIet, slik at du ikke trenger å sende innloggingsinformasjon for kontoen din. Se <Link>dokumentasjonen</Link> for å lære mer.",
"account_tokens_table_current_session": "Nåværende nettleserøkt",
"prefs_appearance_theme_system": "System (standard)",
"prefs_notifications_web_push_disabled_description": "Varslinger mottas når web-appen kjører (via WebSocket)",
"prefs_appearance_theme_title": "Tema",
"prefs_appearance_theme_dark": "Mørk modus",
"prefs_appearance_theme_light": "Lys modus",
"prefs_reservations_title": "Reserverte emner",
"prefs_reservations_table_click_to_subscribe": "Klikk for å abonnere",
"prefs_reservations_table_everyone_read_write": "Alle kan publisere og abonnere",
"prefs_reservations_table_not_subscribed": "Ikke abonnent",
"prefs_reservations_table_everyone_write_only": "Jeg kan publisere og abonnere, alle andre kan publisere",
"prefs_reservations_dialog_title_add": "Reserver emne",
"prefs_reservations_dialog_title_edit": "Rediger reservert emne",
"account_upgrade_dialog_tier_features_reservations_one": "{{reservations}} reservert emne",
"reservation_delete_dialog_action_delete_title": "Slett mellomlagrede meldinger og vedlegg",
"nav_upgrade_banner_label": "Oppgrader til ntfy Pro",
"nav_upgrade_banner_description": "Reserver emner, flere meldinger & e-poster, og større vedlegg",
"account_delete_dialog_button_submit": "Slett konto permanent",
"account_basics_username_description": "Hei, det er deg ❤",
"account_basics_username_admin_tooltip": "Du er administrator",
"account_basics_password_title": "Passord",
"account_basics_password_description": "Endre passordet ditt",
"account_usage_title": "Forbruk",
"account_delete_dialog_button_cancel": "Avbryt",
"account_tokens_dialog_title_delete": "Slett tilgangstoken",
"account_tokens_dialog_label": "Etikett, f.eks. Radarr-varslinger",
"prefs_reservations_table": "Tabell over reserverte emner",
"prefs_reservations_edit_button": "Rediger tilgang til emne",
"prefs_reservations_delete_button": "Nullstill tilgang til emne",
"prefs_reservations_table_topic_header": "Emne",
"account_basics_title": "Konto",
"account_basics_phone_numbers_dialog_code_label": "Verifiseringskode",
"alert_notification_permission_denied_title": "Varslinger blokkert",
"alert_notification_permission_denied_description": "Vennligst reaktiver dem i nettleseren din",
"alert_notification_ios_install_required_title": "iOS-installasjon kreves",
"alert_notification_ios_install_required_description": "Klikk på Del-ikonet og Legg til hjemmeskjerm for å aktivere varslinger på iOS",
"action_bar_mute_notifications": "Demp varslinger",
"action_bar_unmute_notifications": "Avdemp varslinger",
"action_bar_profile_title": "Profil",
"action_bar_profile_logout": "Logg ut",
"action_bar_sign_in": "Logg inn",
"action_bar_sign_up": "Registrer deg",
"alert_not_supported_context_description": "Varslinger er kun støttet over HTTPS. Dette er en begrensning i <mdnLink>Varslings-APIet</mdnLink>.",
"notifications_actions_failed_notification": "Handling feilet",
"display_name_dialog_description": "Angi et alternativt navn for et emne som vises i abonneringslisten. Dette hjelper til med å enklere identifisere emner med kompliserte navn.",
"display_name_dialog_placeholder": "Visningnavn",
"publish_dialog_call_label": "Telefonsamtale",
"publish_dialog_call_item": "Ring telefonnummer {{number}}",
"publish_dialog_call_reset": "Fjern telefonsamtale",
"publish_dialog_chip_call_no_verified_numbers_tooltip": "Ingen verfiserte telefonnumre",
"publish_dialog_checkbox_markdown": "Formatter som Markdown",
"subscribe_dialog_subscribe_use_another_background_info": "Varslinger fra andre servere vil ikke bli tatt imot når webappen ikke er åpen",
"subscribe_dialog_subscribe_button_generate_topic_name": "Generer navn",
"subscribe_dialog_error_topic_already_reserved": "Emne allerede reservert",
"account_basics_username_title": "Brukernavn",
"account_basics_password_dialog_title": "Endre passord",
"account_basics_password_dialog_current_password_label": "Nåværende passord",
"account_basics_password_dialog_new_password_label": "Nytt passord",
"account_basics_password_dialog_confirm_password_label": "Bekreft passord",
"account_basics_password_dialog_button_submit": "Endre passord",
"account_basics_password_dialog_current_password_incorrect": "Passordet er feil",
"account_basics_phone_numbers_title": "Telefonnumre",
"account_basics_phone_numbers_description": "For telefonvarsling",
"account_basics_phone_numbers_no_phone_numbers_yet": "Ingen telefonnumre enda",
"account_basics_phone_numbers_copied_to_clipboard": "Telefonnummer kopiert til utklippstavle",
"account_basics_phone_numbers_dialog_title": "Legg til telefonnummer",
"account_basics_phone_numbers_dialog_number_label": "Telefonnummer",
"account_basics_phone_numbers_dialog_number_placeholder": "f.eks. +1222333444",
"account_basics_phone_numbers_dialog_verify_button_sms": "Send SMS",
"account_basics_phone_numbers_dialog_verify_button_call": "Ring meg",
"account_basics_phone_numbers_dialog_code_placeholder": "f.eks. 123456",
"account_basics_phone_numbers_dialog_check_verification_button": "Bekreft kode",
"account_basics_phone_numbers_dialog_channel_sms": "SMS",
"account_basics_phone_numbers_dialog_channel_call": "Ring",
"account_usage_of_limit": "av {{limit}}",
"account_usage_unlimited": "Ubegrenset",
"account_usage_limits_reset_daily": "Forbruksgrenser nullstilles hver dag ved midnatt (UTC)",
"account_basics_tier_title": "Kontotype",
"account_basics_tier_description": "Din kontos styrke",
"account_basics_tier_admin": "Administrator",
"account_basics_tier_admin_suffix_with_tier": "(med {{tier}} nivå)",
"account_basics_tier_admin_suffix_no_tier": "(ingen nivå)",
"account_basics_tier_paid_until": "Abonnement betalt til {{date}}, og vil bli fornyet automatisk",
"account_basics_tier_payment_overdue": "Betalingen din har forfalt. Vennligst oppdater betalingsmetoden din, hvis ikke blir kontoen din snart degradert.",
"account_basics_tier_manage_billing_button": "Behandle betalinger",
"account_usage_messages_title": "Publiserte meldinger",
"account_usage_calls_none": "Ingen telefonsamtaler kan foretas med denne kontoen",
"account_usage_reservations_none": "Ingen reserverte emner for denne kontoen",
"account_usage_attachment_storage_title": "Vedleggslagring",
"account_usage_basis_ip_description": "Forbruksstatistikk og -grenser for denne kontoen er basert på IP-adressen din, så det kan være de er delt med andre brukere. Forbruksgrenser vist over er omtrentlige, basert på eksisterende begrensninger.",
"account_delete_title": "Slett konto",
"account_delete_dialog_billing_warning": "Sletting av kontoen din avslutter også abonnementet og betalingene dine umiddelbart. Du vil ikke ha tilgang til betalingsportalen lenger.",
"account_upgrade_dialog_title": "Endre kontonivå",
"account_upgrade_dialog_interval_yearly": "Årlig",
"account_upgrade_dialog_interval_yearly_discount_save": "spar {{discount}}%",
"account_upgrade_dialog_interval_yearly_discount_save_up_to": "spar inntil {{discount}}%",
"account_upgrade_dialog_cancel_warning": "Dette vil <strong>avslutte abonnementet ditt</strong>, og nedgradere kontoen din den {{date}}. På den datoen vil alle emnereservasjoner såvel som meldinger lagret på serveren <strong>bli slettet</strong>.",
"account_upgrade_dialog_reservations_warning_one": "Det valgte nivået tillater færre reserverte emner enn ditt nåvære nivå. Før du endrer nivå, <strong>vennligst slett minst én reservasjon</strong>. Du kan slette reservasjoner i <Link>Innstillingene</Link>.",
"account_upgrade_dialog_tier_features_no_reservations": "Ingen reserverte emner",
"account_upgrade_dialog_tier_features_emails_one": "{{emails}} daglig e-post",
"account_upgrade_dialog_tier_features_calls_other": "{{calls}} daglige telefonsamtaler",
"account_upgrade_dialog_tier_features_no_calls": "Ingen telefonsamtaler",
"account_upgrade_dialog_tier_features_attachment_file_size": "{{filesize}} pr. fil",
"account_upgrade_dialog_tier_features_attachment_total_size": "{{totalsize}} total lagringsplass",
"account_upgrade_dialog_tier_price_per_month": "måned",
"account_upgrade_dialog_tier_price_billed_yearly": "{{price}} fakturert årlig. Spar {{save}}.",
"account_upgrade_dialog_billing_contact_website": "For faktureringsspørsmål, vennligst se vår <Link>nettside</Link>.",
"account_upgrade_dialog_button_update_subscription": "Oppdater abonnement",
"account_tokens_title": "Tilgangstokener",
"account_tokens_table_last_access_header": "Sist aksessert",
"account_tokens_table_expires_header": "Utløper",
"account_tokens_table_never_expires": "Utløper aldri",
"account_tokens_dialog_title_edit": "Rediger tilgangstoken",
"account_tokens_dialog_button_create": "Opprett token",
"account_tokens_dialog_button_update": "Oppdater token",
"account_tokens_dialog_button_cancel": "Avbryt",
"account_tokens_dialog_expires_label": "Tilgangstoken utløper om",
"account_tokens_dialog_expires_x_days": "Token utløper om {{days}} dager",
"account_tokens_dialog_expires_never": "Token utløper aldri",
"prefs_notifications_web_push_title": "Bakgrunnsvarslinger",
"prefs_notifications_web_push_enabled_description": "Varslinger mottas send om web-appen ikke kjører (via Web Push)",
"prefs_notifications_web_push_enabled": "Aktivert for {{server}}",
"prefs_notifications_web_push_disabled": "Deaktivert",
"prefs_reservations_description": "Du kan reservere emnenavn for personlig bruk her. Reservasjon av et emne gir deg eierskap over emnet og lar deg definere tilgangsrettigheter for andre brukere av dette emnet.",
"prefs_reservations_limit_reached": "Du har nådd grensen for antall reserverte emner du kan ha.",
"prefs_reservations_add_button": "Legg til reservert emne",
"prefs_reservations_table_access_header": "Tilgang",
"prefs_reservations_table_everyone_read_only": "Jeg kan publisere og abonnere, alle andre kan abonnere",
"prefs_reservations_dialog_description": "Reservering av et emne gir deg eierskap over emnet, og lar deg definere tilgangsrettigheter for andre brukere av emnet.",
"reservation_delete_dialog_description": "Ved å fjerne en reservasjon gir du fra deg eierskapet over emnet, og gir dermed andre muligheten til å reservere det. Du kan beholde eller slette eksisterende meldinger og vedlegg.",
"reservation_delete_dialog_action_keep_description": "Meldinger og vedlegg som er mellomlagret på serveren vil bli synlige for alle som kjenner til emnenavnet.",
"reservation_delete_dialog_action_delete_description": "Mellomlagrede meldinger og vedlegg vil bli permanent slettet. Denne handlingen kan ikke angres.",
"reservation_delete_dialog_submit_button": "Slett reservasjon",
"error_boundary_button_reload_ntfy": "Last inn ntfy på nytt",
"web_push_subscription_expiring_title": "Varslinger vil bli satt på pause",
"web_push_subscription_expiring_body": "Åpne ntfy for å fortsette å motta varslinger",
"web_push_unknown_notification_title": "Ukjent varsel mottatt fra server",
"web_push_unknown_notification_body": "Du må muligens oppdatere ntfy ved å åpne web-appen",
"account_usage_attachment_storage_description": "{{filesize}} pr. fil, slettet etter {{expiry}}"
}

View File

@@ -404,5 +404,10 @@
"prefs_reservations_dialog_title_add": "Zarezerwuj temat",
"reservation_delete_dialog_action_keep_title": "Zachowaj wiadomości i załącznik w pamięci cache",
"reservation_delete_dialog_action_keep_description": "Wiadomości i załączniki które są zapisane w pamięci cache będą dostępne publicznie dla każdego znającego nazwę powiązanego z nimi tematu.",
"web_push_unknown_notification_title": "Nieznane powiadomienie otrzymane od serwera"
"web_push_unknown_notification_title": "Nieznane powiadomienie otrzymane od serwera",
"action_bar_unmute_notifications": "Włącz ponownie powiadomienia",
"prefs_appearance_theme_title": "Wygląd",
"prefs_reservations_dialog_description": "Zastrzeżenie tematu daje użytkownikowi prawo własności do tego tematu i umożliwia zdefiniowanie uprawnień dostępu do tego tematu dla innych użytkowników.",
"reservation_delete_dialog_description": "Usunięcie rezerwacji powoduje rezygnację z prawa własności do tematu i umożliwia innym jego zarezerwowanie. Istniejące wiadomości i załączniki można zachować lub usunąć.",
"web_push_unknown_notification_body": "Konieczne może być zaktualizowanie ntfy poprzez otwarcie aplikacji internetowej"
}

View File

@@ -16,7 +16,7 @@
"nav_button_muted": "Notificações desativadas",
"nav_button_connecting": "A ligar",
"alert_notification_permission_required_title": "As notificações estão desativadas",
"alert_notification_permission_required_description": "Conceder permissão ao seu navegador para mostrar notificações.",
"alert_notification_permission_required_description": "Conceder permissão ao seu navegador para mostrar notificações",
"alert_not_supported_title": "Notificações não suportadas",
"notifications_list": "Lista de notificações",
"alert_not_supported_description": "As notificações não são suportadas pelo seu navegador",
@@ -215,14 +215,14 @@
"action_bar_reservation_add": "Reservar tópico",
"action_bar_sign_up": "Registar",
"nav_button_account": "Conta",
"common_copy_to_clipboard": "Copiar",
"nav_upgrade_banner_label": "Atualizar para ntfy Pro",
"alert_not_supported_context_description": "Notificações são suportadas apenas sobre HTTPS. Essa é uma limitação da <mdnLink>API de Notificações</mdnLink>.",
"display_name_dialog_title": "Alterar nome mostrado",
"display_name_dialog_description": "Configura um nome alternativo ao tópico que é mostrado na lista de assinaturas. Isto ajuda a identificar tópicos com nomes complicados mais facilmente.",
"display_name_dialog_placeholder": "Nome exibido",
"common_copy_to_clipboard": "Copiar à área de transferência",
"nav_upgrade_banner_label": "Upgrade para ntfy Pro",
"alert_not_supported_context_description": "As notificações são apenas suportadas através de HTTPS. Isto é uma limitação da <mdnLink>Notifications API</mdnLink>.",
"display_name_dialog_title": "Alterar o nome público",
"display_name_dialog_description": "Configurar um nome alternativo para um tópico que é mostrado na lista de subscrições. Isto ajuda a identificar tópicos com nomes complicados mais facilmente.",
"display_name_dialog_placeholder": "Nome público",
"reserve_dialog_checkbox_label": "Reservar tópico e configurar acesso",
"publish_dialog_call_label": "Chamada telefônica",
"publish_dialog_call_label": "Chamada telefónica",
"publish_dialog_call_placeholder": "Número de telefone para ligar com a mensagem, ex: +12223334444, ou 'Sim'",
"publish_dialog_call_reset": "Remover chamada telefônica",
"publish_dialog_chip_call_label": "Chamada telefônica",
@@ -231,17 +231,17 @@
"alert_notification_ios_install_required_description": "Clique no ícone Compartilhar e Adicionar à Tela Inicial para ativar as notificações no iOS",
"publish_dialog_checkbox_markdown": "Formatar como Markdown",
"publish_dialog_chip_call_no_verified_numbers_tooltip": "Números de telefone não verificados",
"subscribe_dialog_error_topic_already_reserved": "Tópico já está reservado",
"subscribe_dialog_error_topic_already_reserved": "Tópico já reservado",
"action_bar_mute_notifications": "Silenciar notificações",
"alert_notification_permission_denied_title": "Notificações estão bloqueadas",
"alert_notification_permission_denied_description": "Por favor reative-as em seu navegador",
"alert_notification_ios_install_required_title": "Requer instalação em iOS",
"notifications_actions_failed_notification": "Houve uma falha na ação",
"publish_dialog_call_item": "Ligar para o número {{number}}",
"publish_dialog_call_item": "Ligar para o número de telefone {{number}}",
"subscribe_dialog_subscribe_use_another_background_info": "Notificações de outros servidores não serão recebidas enquanto o aplicativo web não estiver aberto",
"account_basics_username_description": "Olá, é você ❤",
"account_basics_password_dialog_new_password_label": "Nova senha",
"account_basics_password_dialog_current_password_incorrect": "Senha incorreta",
"account_basics_username_description": "Olá, és tu ❤",
"account_basics_password_dialog_new_password_label": "Nova palavra-passe",
"account_basics_password_dialog_current_password_incorrect": "Palavra-passe inválida",
"account_basics_phone_numbers_title": "Números de telefone",
"account_basics_phone_numbers_dialog_description": "Para utilizar o recurso de notificação por ligação, você precisa adicionar e verificar pelo menos um número de telefone. A verificação poderá ser feita via SMS ou ligação telefônica.",
"account_basics_phone_numbers_dialog_title": "Adicionar número de telefone",
@@ -258,20 +258,20 @@
"account_usage_reservations_none": "Esta conta não possui tópicos reservados",
"account_usage_attachment_storage_title": "Armazenamento de anexos",
"account_usage_emails_title": "E-mails enviados",
"account_basics_password_description": "Alterar a senha da sua conta",
"account_basics_password_dialog_title": "Alterar a senha",
"account_basics_password_description": "Mudar a palavra-passe da conta",
"account_basics_password_dialog_title": "Mudar a palavra-passe",
"account_basics_phone_numbers_description": "Para notificações por ligação",
"account_basics_tier_paid_until": "Assinatura paga até {{date}}, e será renovada automaticamente",
"account_basics_password_dialog_confirm_password_label": "Confirmar senha",
"account_basics_password_dialog_button_submit": "Alterar senha",
"account_basics_password_dialog_confirm_password_label": "Confirmar palavra-passe",
"account_basics_password_dialog_button_submit": "Mudar palavra-passe",
"account_basics_title": "Conta",
"account_basics_username_admin_tooltip": "Você é Administrador",
"account_basics_password_title": "Senha",
"account_basics_password_dialog_current_password_label": "Senha atual",
"account_basics_username_admin_tooltip": "És Admin",
"account_basics_password_title": "Palavra-passe",
"account_basics_password_dialog_current_password_label": "Palavra-passe atual",
"account_basics_phone_numbers_no_phone_numbers_yet": "Nenhum número de telefone",
"account_basics_phone_numbers_copied_to_clipboard": "Telefones copiados para área de transferência",
"account_basics_phone_numbers_dialog_channel_sms": "SMS",
"account_usage_title": "Uso",
"account_usage_title": "Utilização",
"account_usage_of_limit": "de {{limit}}",
"account_usage_unlimited": "Ilimitado",
"account_usage_limits_reset_daily": "Limites de uso são resetados diariamente à meia noite (UTC)",
@@ -290,5 +290,24 @@
"account_usage_messages_title": "Mensagens publicadas",
"account_usage_calls_title": "Ligações realizadas",
"account_usage_calls_none": "Esta conta não pode realizar ligações",
"account_usage_reservations_title": "Tópicos reservados"
"account_usage_reservations_title": "Tópicos reservados",
"account_basics_username_title": "Usuário",
"account_delete_dialog_description": "Isto irá eliminar definitivamente a sua conta, incluindo dados que estejam armazenados no servidor. Apos ser eliminado, o nome de utilizador ficará indisponível durante 7 dias. Se deseja mesmo proceder, por favor confirme com a sua palavra-passe na caixa abaixo.",
"account_delete_dialog_button_submit": "Eliminar conta definitivamente",
"account_delete_dialog_billing_warning": "Eliminar a sua conta também cancela a sua subscrição de faturação imediatamente. Não terá acesso ao portal de faturação de futuro.",
"account_upgrade_dialog_title": "Alterar o nível da sua conta",
"account_upgrade_dialog_interval_monthly": "Mensalmente",
"account_upgrade_dialog_interval_yearly": "Anualmente",
"account_upgrade_dialog_interval_yearly_discount_save": "poupe {{discount}}%",
"account_upgrade_dialog_interval_yearly_discount_save_up_to": "poupe até {{discount}}%",
"account_delete_dialog_label": "Palavra-passe",
"account_usage_cannot_create_portal_session": "Impossível abrir o portal de faturação",
"account_usage_basis_ip_description": "Estatísticas de utilização e limites para esta conta são baseadas no seu endereço IP, pelo que podem ser partilhados com outros utilizadores. Os limites mostrados acima são aproximados com base nos limites existentes.",
"account_usage_attachment_storage_description": "{{filesize}} por ficheiro, eliminado após {{expiry}}",
"account_delete_title": "Eliminar conta",
"account_delete_description": "Eliminar definitivamente a sua conta",
"account_delete_dialog_button_cancel": "Cancelar",
"account_upgrade_dialog_cancel_warning": "Isto irá <strong>cancelar a sua assinatura</strong>, e fazer downgrade da sua conta em {{date}}. Nessa data, tópicos reservados bem como mensagens guardadas no servidor <strong>serão eliminados</strong>.",
"account_upgrade_dialog_proration_info": "<strong>Proporção</strong>: Quando atualizar entre planos pagos, a diferença de preço será <strong>debitada imediatamente</strong>. Quando efetuar um downgrade para um escalão inferior, o saldo disponível será usado para futuros períodos de faturação.",
"prefs_users_description_no_sync": "Utilizadores e palavras-passe não estão sincronizados com a sua conta."
}

View File

@@ -8,10 +8,10 @@
"nav_button_settings": "Configurações",
"nav_button_subscribe": "Inscrever no tópico",
"alert_notification_permission_required_title": "Notificações estão desativadas",
"alert_notification_permission_required_description": "Conceder ao navegador permissão para mostrar notificações.",
"alert_notification_permission_required_description": "Conceder permissão ao seu navegador para mostrar notificações",
"alert_notification_permission_required_button": "Conceder agora",
"alert_not_supported_title": "Notificações não são suportadas",
"alert_not_supported_description": "Notificações não são suportadas pelo seu navegador.",
"alert_not_supported_description": "Notificações não são suportadas pelo seu navegador",
"notifications_copied_to_clipboard": "Copiado para a área de transferência",
"notifications_tags": "Etiquetas",
"notifications_attachment_copy_url_title": "Copiar URL do anexo para a área de transferência",
@@ -189,15 +189,15 @@
"prefs_users_delete_button": "Excluir usuário",
"error_boundary_unsupported_indexeddb_title": "Navegação anônima não suportada",
"error_boundary_unsupported_indexeddb_description": "O ntfy web app precisa do IndexedDB para funcionar, e seu navegador não suporta IndexedDB no modo de navegação privada.<br/><br/>Embora isso seja lamentável, também não faz muito sentido usar o ntfy web app no modo de navegação privada de qualquer maneira, porque tudo é armazenado no armazenamento do navegador. Você pode ler mais sobre isso <githubLink>nesta edição do GitHub</githubLink>, ou falar conosco em <discordLink>Discord</discordLink> ou <matrixLink>Matrix</matrixLink>.",
"action_bar_reservation_add": "Reserve topic",
"action_bar_reservation_edit": "Change reservation",
"signup_disabled": "Registrar está desativado",
"signup_error_username_taken": "Usuário {{username}} já existe",
"action_bar_reservation_add": "Reservar tópico",
"action_bar_reservation_edit": "Mudar reserva",
"signup_disabled": "O registro está desativado",
"signup_error_username_taken": "O nome de usuário {{username}} já está em uso",
"signup_error_creation_limit_reached": "Limite de criação de contas atingido",
"action_bar_reservation_delete": "Remover reserva",
"action_bar_account": "Conta",
"action_bar_change_display_name": "Change display name",
"common_copy_to_clipboard": "Copiar para área de transferência",
"action_bar_change_display_name": "Mudar nome de exibição",
"common_copy_to_clipboard": "Copiar para a Área de Transferência",
"login_link_signup": "Registrar",
"login_title": "Entrar na sua conta ntfy",
"login_form_button_submit": "Entrar",
@@ -210,13 +210,13 @@
"action_bar_sign_up": "Registrar",
"nav_button_account": "Conta",
"signup_title": "Criar uma conta ntfy",
"signup_form_username": "Usuário",
"signup_form_username": "Nome de usuário",
"signup_form_password": "Senha",
"signup_form_confirm_password": "Confirmar senha",
"signup_form_button_submit": "Registrar",
"signup_form_button_submit": "Criar conta",
"account_basics_phone_numbers_title": "Telefones",
"signup_form_toggle_password_visibility": "Ativar visibilidade de senha",
"signup_already_have_account": "Já possui uma conta? Entrar!",
"signup_form_toggle_password_visibility": "Alterar visibilidade da senha",
"signup_already_have_account": "Já tem uma conta? Entre!",
"nav_upgrade_banner_label": "Atualizar para ntfy Pro",
"account_basics_phone_numbers_dialog_description": "Para usar o recurso de notificação de chamada, é necessários adicionar e verificar pelo menos um número de telefone. A verificação pode ser feita por SMS ou chamada telefônica.",
"account_basics_phone_numbers_description": "Para notificações de chamada telefônica",
@@ -224,7 +224,7 @@
"account_basics_tier_canceled_subscription": "Sua assinatura foi cancelada e será rebaixada para uma conta gratuita em {{date}}.",
"account_basics_password_dialog_current_password_incorrect": "Senha incorreta",
"account_basics_phone_numbers_dialog_number_label": "Número de telefone",
"account_basics_password_dialog_button_submit": "Alterar senha",
"account_basics_password_dialog_button_submit": "Mudar senha",
"reserve_dialog_checkbox_label": "Guardar tópico e configurar acesso",
"account_basics_username_title": "Nome de usuário",
"account_basics_phone_numbers_dialog_check_verification_button": "Confirmar código",
@@ -250,11 +250,11 @@
"account_basics_tier_free": "Grátis",
"account_basics_tier_admin": "Administrador",
"publish_dialog_chip_call_no_verified_numbers_tooltip": "Nenhum número de telefone verificado",
"account_basics_password_description": "Alterar a senha da sua conta",
"account_basics_password_description": "Mudar a senha da sua conta",
"publish_dialog_call_label": "Chamada telefônica",
"account_usage_calls_title": "Chamadas de telefone feitas",
"account_basics_tier_basic": "Básico",
"alert_not_supported_context_description": "Notificações são suportadas apenas através de HTTPS. Esta é uma limitação da <mdnLink>API de Notificações</mdnLink>.",
"alert_not_supported_context_description": "Notificações são suportadas somente por HTTPS. Essa é uma limitação da <mdnLink>Notifications API</mdnLink>.",
"account_basics_phone_numbers_copied_to_clipboard": "Número de telefone copiado para a área de transferência",
"account_basics_tier_title": "Tipo de conta",
"account_basics_phone_numbers_dialog_number_placeholder": "ex. +1222333444",
@@ -268,14 +268,14 @@
"account_basics_password_dialog_new_password_label": "Nova senha",
"display_name_dialog_placeholder": "Nome de exibição",
"account_usage_of_limit": "de {{limit}}",
"account_basics_password_dialog_title": "Alterar senha",
"account_basics_password_dialog_title": "Mudar senha",
"account_usage_limits_reset_daily": "Os limites de uso são redefinidos diariamente à meia-noite (UTC)",
"account_usage_unlimited": "Ilimitado",
"account_basics_password_dialog_current_password_label": "Senha atual",
"account_usage_reservations_title": "Tópicos reservados",
"account_usage_calls_none": "Nenhum telefonema pode ser feito com esta conta",
"display_name_dialog_title": "Alterar o nome de exibição",
"nav_upgrade_banner_description": "Guarde tópicos, mais mensagens & emails e anexos grandes",
"display_name_dialog_title": "Alterar nome de exibição",
"nav_upgrade_banner_description": "Reserve tópicos, mais mensagens e e-mails, e anexos maiores",
"publish_dialog_call_reset": "Remover chamada telefônica",
"account_basics_phone_numbers_dialog_code_label": "Código de verificação",
"account_basics_tier_paid_until": "Assinatura paga até {{date}}, será renovada automaticamente",
@@ -302,15 +302,15 @@
"subscribe_dialog_subscribe_use_another_background_info": "Notificações de outros servidores não serão recebidas quando o web app não estiver aberto",
"account_usage_basis_ip_description": "As estatísticas e limites de uso desta conta são baseados no seu endereço IP, portanto, podem ser compartilhados com outros usuários. Os limites mostrados acima são aproximados com base nos limites de taxa existentes.",
"account_usage_cannot_create_portal_session": "Não foi possível abrir o portal de cobrança",
"account_delete_description": "Deletar conta permanentemente",
"account_delete_description": "Deletar sua conta permanentemente",
"account_delete_dialog_button_cancel": "Cancelar",
"account_delete_dialog_button_submit": "Deletar conta permanentemente",
"account_upgrade_dialog_interval_monthly": "Mensal",
"account_upgrade_dialog_interval_yearly_discount_save": "desconto de {{discount}}%",
"account_upgrade_dialog_interval_yearly_discount_save_up_to": "desconto de até {{discount}}%",
"account_upgrade_dialog_cancel_warning": "Isso <strong>cancelará sua assinatura</strong> e fará downgrade de sua conta em {{date}}. Nessa data, as reservas de tópicos, bem como as mensagens armazenadas em cache no servidor <strong>serão excluídas</strong>.",
"account_upgrade_dialog_reservations_warning_one": "O nível selecionada permite menos tópicos reservados do que a camada atual. Antes de alterar seu nível, <strong>exclua pelo menos uma reserva</strong>. Você pode remover reservas nas <Link>Configurações</Link>",
"account_upgrade_dialog_reservations_warning_other": "O plano selecionado permite menos tópicos reservados do que o seu plano atual. Antes de mudar seu plano, exclua por favor ao menos {{count}} reservas. Você pode remover reservas em Configurações.",
"account_upgrade_dialog_reservations_warning_one": "O nível selecionado permite menos tópicos reservados do que o nível atual. Antes de alterar seu nível, <strong>exclua pelo menos uma reserva</strong>. Você pode remover reservas nas <Link>Configurações</Link>.",
"account_upgrade_dialog_reservations_warning_other": "O nível selecionado permite menos tópicos reservados do que o seu nível atual. Antes de mudar seu nível, <strong>por favor exclua ao menos {{count}} reservas</strong>. Você pode remover reservas nas <Link>Configurações</Link>.",
"account_upgrade_dialog_tier_features_no_reservations": "Sem tópicos reservados",
"account_upgrade_dialog_tier_features_messages_one": "{{messages}} mensagen diária",
"account_upgrade_dialog_tier_features_emails_one": "{{emails}} email diário",
@@ -321,5 +321,88 @@
"account_upgrade_dialog_tier_current_label": "Atual",
"account_upgrade_dialog_tier_price_per_month": "mês",
"account_upgrade_dialog_button_cancel": "Cancelar",
"account_upgrade_dialog_tier_selected_label": "Selecionado"
"account_upgrade_dialog_tier_selected_label": "Selecionado",
"account_upgrade_dialog_tier_features_attachment_file_size": "{{filesize}} por arquivo",
"account_tokens_table_last_access_header": "Último acesso",
"account_upgrade_dialog_button_cancel_subscription": "Cancelar assinatura",
"account_tokens_table_never_expires": "Nunca expira",
"account_upgrade_dialog_tier_price_billed_yearly": "{{price}} cobrado anualmente. Salvar {{save}}.",
"account_upgrade_dialog_tier_features_no_calls": "Nenhuma chamada",
"account_tokens_table_token_header": "Token",
"account_upgrade_dialog_button_update_subscription": "Atualizar assinatura",
"account_tokens_table_current_session": "Sessão atual do navegador",
"account_tokens_table_copied_to_clipboard": "Token de acesso copiado",
"account_tokens_title": "Tokens de Acesso",
"account_upgrade_dialog_button_redirect_signup": "Cadastre-se agora",
"account_upgrade_dialog_tier_features_calls_one": "{{calls}} chamadas diárias",
"account_upgrade_dialog_tier_features_calls_other": "{{calls}} chamadas telefônicas diárias",
"account_upgrade_dialog_tier_features_attachment_total_size": "{{totalsize}} armazenamento total",
"account_upgrade_dialog_tier_price_billed_monthly": "{{price}} por ano. Cobrado mensalmente.",
"account_upgrade_dialog_button_pay_now": "Pague agora para assinar",
"account_tokens_table_expires_header": "Expira",
"prefs_users_description_no_sync": "Usuários e senhas não estão sincronizados com a sua conta.",
"account_tokens_description": "Use tokens de acesso ao publicar e assinar por meio da API ntfy, para que você não precise enviar as credenciais da sua conta. Consulte a <Link>documentação</Link> para saber mais.",
"account_tokens_table_cannot_delete_or_edit": "Não é possível editar ou excluir o token da sessão atual",
"account_tokens_dialog_title_edit": "Editar token de acesso",
"account_tokens_dialog_title_delete": "Excluir token de acesso",
"prefs_reservations_table_everyone_read_write": "Todos podem publicar e se inscrever",
"prefs_reservations_table_everyone_read_only": "Posso publicar e me inscrever, todos podem se inscrever",
"prefs_reservations_limit_reached": "Você atingiu seu limite de tópicos reservados.",
"prefs_reservations_delete_button": "Redefinir o acesso ao tópico",
"prefs_reservations_edit_button": "Editar acesso ao tópico",
"prefs_reservations_table_everyone_write_only": "Eu posso publicar e me inscrever, todos podem publicar",
"prefs_reservations_table_not_subscribed": "Não inscrito",
"prefs_reservations_table_click_to_subscribe": "Clique para se inscrever",
"reservation_delete_dialog_action_keep_title": "Manter mensagens e anexos em cache",
"account_tokens_table_label_header": "Rótulo",
"account_tokens_table_last_origin_tooltip": "Do endereço IP {{ip}}, clique para pesquisar",
"account_tokens_dialog_title_create": "Criar token de acesso",
"account_tokens_delete_dialog_title": "Excluir token de acesso",
"account_tokens_dialog_label": "Rótulo, por exemplo, notificações de Radarr",
"account_tokens_dialog_expires_never": "O token nunca expira",
"prefs_reservations_dialog_title_edit": "Editar tópico reservado",
"prefs_notifications_web_push_enabled_description": "As notificações são recebidas mesmo quando o aplicativo Web não está em execução (via Web Push)",
"prefs_notifications_web_push_disabled_description": "As notificações são recebidas quando o aplicativo Web está em execução (via WebSocket)",
"account_upgrade_dialog_billing_contact_website": "Para perguntas sobre faturamento, consulte nosso <Link>website</Link>.",
"account_tokens_table_create_token_button": "Criar token de acesso",
"account_tokens_dialog_button_cancel": "Cancelar",
"account_tokens_dialog_button_update": "Atualizar token",
"prefs_reservations_table": "Tabela de tópicos reservados",
"prefs_reservations_table_everyone_deny_all": "Somente eu posso publicar e me inscrever",
"account_tokens_delete_dialog_description": "Antes de excluir um token de acesso, certifique-se de que nenhum aplicativo ou script o esteja usando ativamente. <strong>Esta ação não pode ser desfeita</strong>.",
"account_tokens_delete_dialog_submit_button": "Excluir token permanentemente",
"account_tokens_dialog_expires_x_hours": "O token expira em {{hours}} horas",
"account_tokens_dialog_expires_x_days": "O token expira em {{days}} dias",
"prefs_reservations_description": "Você pode reservar nomes de tópicos para uso pessoal aqui. A reserva de um tópico lhe dá propriedade sobre ele e permite que você defina permissões de acesso para outros usuários sobre o tópico.",
"prefs_reservations_dialog_access_label": "Acesso",
"account_upgrade_dialog_billing_contact_email": "Para questões de cobrança, <Link>entre em contato conosco</Link> diretamente.",
"account_tokens_dialog_button_create": "Criar token",
"account_tokens_dialog_expires_label": "O token de acesso expira em",
"account_tokens_dialog_expires_unchanged": "Deixar a data de validade inalterada",
"prefs_notifications_web_push_title": "Notificações em segundo plano",
"prefs_notifications_web_push_enabled": "Ativado para {{server}}",
"prefs_notifications_web_push_disabled": "Desativado",
"prefs_appearance_theme_title": "Tema",
"prefs_users_table_cannot_delete_or_edit": "Não é possível excluir ou editar o usuário conectado",
"prefs_appearance_theme_system": "Sistema (padrão)",
"prefs_appearance_theme_dark": "Modo escuro",
"prefs_appearance_theme_light": "Modo claro",
"prefs_reservations_title": "Tópicos reservados",
"prefs_reservations_add_button": "Adicionar tópico reservado",
"prefs_reservations_table_topic_header": "Tópico",
"prefs_reservations_table_access_header": "Acesso",
"prefs_reservations_dialog_title_add": "Reservar tópico",
"prefs_reservations_dialog_title_delete": "Excluir reserva de tópico",
"prefs_reservations_dialog_description": "A reserva de um tópico lhe dá propriedade sobre ele e permite definir permissões de acesso para outros usuários sobre o tópico.",
"prefs_reservations_dialog_topic_label": "Tópico",
"reservation_delete_dialog_description": "A remoção de uma reserva abre mão da propriedade sobre o tópico e permite que outros o reservem. Você pode manter ou excluir as mensagens e os anexos existentes.",
"reservation_delete_dialog_action_keep_description": "As mensagens e os anexos armazenados em cache no servidor ficarão visíveis publicamente para as pessoas que souberem o nome do tópico.",
"reservation_delete_dialog_action_delete_title": "Excluir mensagens e anexos armazenados em cache",
"reservation_delete_dialog_action_delete_description": "As mensagens e os anexos armazenados em cache serão excluídos permanentemente. Essa ação não pode ser desfeita.",
"reservation_delete_dialog_submit_button": "Excluir reserva",
"error_boundary_button_reload_ntfy": "Recarregar ntfy",
"web_push_subscription_expiring_title": "As notificações serão pausadas",
"web_push_subscription_expiring_body": "Abra o ntfy para continuar recebendo notificações",
"web_push_unknown_notification_title": "Notificação desconhecida recebida do servidor",
"web_push_unknown_notification_body": "Talvez seja necessário atualizar o ntfy abrindo o aplicativo da Web"
}

View File

@@ -27,8 +27,8 @@
"alert_notification_permission_required_title": "Notificările sunt dezactivate",
"alert_notification_permission_required_button": "Permite acum",
"alert_not_supported_title": "Notificările nu sunt acceptate",
"alert_not_supported_description": "Notificările nu sunt acceptate în browser.",
"alert_notification_permission_required_description": "Permite browser-ului să afișeze notificări.",
"alert_not_supported_description": "Notificările nu sunt acceptate în browserul tău",
"alert_notification_permission_required_description": "Permite browser-ului să afișeze notificări",
"notifications_list": "Lista de notificări",
"notifications_list_item": "Notificare",
"notifications_mark_read": "Marchează ca citit",
@@ -102,9 +102,9 @@
"publish_dialog_emoji_picker_show": "Alege un emoji",
"notifications_loading": "Încărcare notificări…",
"publish_dialog_priority_low": "Prioritate joasă",
"signup_form_username": "Nume de utilizator",
"signup_form_button_submit": "Înscrie-te",
"common_copy_to_clipboard": "Copiază în clipboard",
"signup_form_username": "Utilizator",
"signup_form_button_submit": "Înregistrare",
"common_copy_to_clipboard": "Copiază",
"signup_form_toggle_password_visibility": "Schimbă vizibilitatea parolei",
"signup_title": "Crează un cont ntfy",
"signup_already_have_account": "Deja ai un cont? Autentifică-te!",
@@ -123,5 +123,110 @@
"message_bar_show_dialog": "Arată dialogul de publicare",
"signup_error_username_taken": "Numele de utilizator {{username}} este deja folosit",
"login_title": "Autentifică-te în contul ntfy",
"action_bar_reservation_add": "Rezervă topicul"
"action_bar_reservation_add": "Rezervă topicul",
"action_bar_mute_notifications": "Oprește notificările",
"action_bar_unmute_notifications": "Pornește notificările",
"nav_topics_title": "Subiecte abonate",
"publish_dialog_chip_attach_url_label": "Atașează fișier prin URL",
"publish_dialog_call_label": "Apel telefonic",
"publish_dialog_button_cancel_sending": "Anulează trimiterea",
"subscribe_dialog_subscribe_title": "Abonează-te la subiect",
"subscribe_dialog_login_password_label": "Parolă",
"subscribe_dialog_login_button_login": "Autentificare",
"subscribe_dialog_error_user_not_authorized": "Utilizatorul {{username}} nu este autorizat",
"account_basics_title": "Cont",
"account_basics_username_title": "Nume de utilizator",
"account_basics_username_description": "Hei, ești tu ❤",
"subscribe_dialog_error_topic_already_reserved": "Subiectul este deja rezervat",
"publish_dialog_attached_file_title": "Fișier atașat:",
"publish_dialog_attached_file_filename_placeholder": "Nume fișier atașat",
"publish_dialog_attached_file_remove": "Elimină fișierul atașat",
"emoji_picker_search_placeholder": "Caută emoji",
"nav_button_muted": "Notificări dezactivate",
"alert_notification_permission_denied_title": "Notificările sunt blocate",
"alert_notification_ios_install_required_description": "Apasă pe butonul Partajare și Adăugați la ecranul principal pentru a porni notificările pe iOS",
"alert_notification_ios_install_required_title": "Instalare iOS necesară",
"alert_notification_permission_denied_description": "Repornește-le în browserul tău",
"alert_not_supported_context_description": "Notificările sunt acceptate doar prin HTTPS. Aceasta este o limitare a <mdnLink>API-ului de notificări</mdnLink>.",
"notifications_actions_failed_notification": "Acțiune nereușită",
"publish_dialog_email_placeholder": "Adresă către care se va redirecționa notificarea, ex. phil@example.com",
"publish_dialog_email_reset": "Șterge redirecționare email",
"publish_dialog_call_item": "Apelează numărul de telefon {{number}}",
"publish_dialog_attach_label": "URL atașament",
"publish_dialog_attach_placeholder": "Atașează fișier prin URL, ex. https://f-droid.org/F-Droid.apk",
"publish_dialog_attach_reset": "Șterge atașament URL",
"publish_dialog_filename_label": "Nume fișier",
"publish_dialog_filename_placeholder": "Nume fișier atașament",
"publish_dialog_delay_label": "Întârziere",
"publish_dialog_call_reset": "Șterge apel telefonic",
"publish_dialog_delay_placeholder": "Întârzie livrarea, ex. {{unixTimestamp}}, {{relativeTime}}, sau \"{{naturalLanguage}}\" (doar engleză)",
"publish_dialog_delay_reset": "Șterge livrare întârziată",
"publish_dialog_other_features": "Alte funcționalități:",
"publish_dialog_chip_click_label": "Accesează URL-ul",
"publish_dialog_chip_email_label": "Redirecționează către email",
"publish_dialog_chip_call_label": "Apel telefonic",
"publish_dialog_chip_call_no_verified_numbers_tooltip": "Nu există numere de telefon verificate",
"publish_dialog_chip_attach_file_label": "Atașează fișier local",
"publish_dialog_chip_delay_label": "Întârziere livrare",
"publish_dialog_chip_topic_label": "Schimbă subiectul",
"publish_dialog_details_examples_description": "Pentru exemple și o descriere detaliată a tuturor funcțiilor de trimitere, vă rugăm să consultați <docsLink>documentația</docsLink>.",
"publish_dialog_button_cancel": "Anulează",
"publish_dialog_button_send": "Trimite",
"publish_dialog_checkbox_markdown": "Formatează ca Markdown",
"publish_dialog_checkbox_publish_another": "Publică altul",
"publish_dialog_drop_file_here": "Trage fișierul aici",
"emoji_picker_search_clear": "Șterge căutarea",
"subscribe_dialog_subscribe_description": "Subiectele nu pot fi protejate prin parolă, așa că alege un nume care să nu fie ușor de ghicit. Odată abonat, poți utiliza metodele PUT/POST pentru a trimite notificări.",
"subscribe_dialog_subscribe_topic_placeholder": "Nume subiect, de exemplu, phil_alerts",
"subscribe_dialog_subscribe_use_another_label": "Foloseșste alt server",
"subscribe_dialog_subscribe_use_another_background_info": "Notificările de la alte servere nu vor fi primite atunci când aplicația web nu este deschisă",
"subscribe_dialog_subscribe_base_url_label": "URL serviciu",
"subscribe_dialog_subscribe_button_generate_topic_name": "Generează nume",
"subscribe_dialog_subscribe_button_cancel": "Anulează",
"subscribe_dialog_subscribe_button_subscribe": "Abonează-te",
"subscribe_dialog_login_title": "Autentificare necesară",
"subscribe_dialog_login_description": "Acest subiect este protejat prin parolă. Vă rugăm să introduceți numele de utilizator și parola pentru a vă abona.",
"subscribe_dialog_login_username_label": "Nume de utilizator, de exemplu, phil",
"subscribe_dialog_error_user_anonymous": "anonim",
"account_basics_tier_interval_monthly": "lunar",
"account_basics_password_dialog_title": "Schimbă parola",
"account_basics_password_dialog_current_password_label": "Parola actuală",
"account_basics_phone_numbers_copied_to_clipboard": "Numărul de telefon a fost copiat",
"account_basics_username_admin_tooltip": "Sunteți administrator",
"account_basics_tier_paid_until": "Abonamentul este plătit până la {{date}}, și se va reînnoi automat",
"account_basics_tier_payment_overdue": "Plata dvs. este restantă. Actualizați metoda de plată sau contul dvs. va fi retrogradat în curând.",
"account_basics_tier_interval_yearly": "anual",
"account_basics_tier_upgrade_button": "Upgrade la Pro",
"account_basics_phone_numbers_title": "Numere de telefon",
"account_basics_password_description": "Schimbă parola contului",
"account_basics_password_dialog_confirm_password_label": "Confirmă parola",
"account_basics_password_dialog_button_submit": "Schimbă parola",
"account_basics_password_dialog_current_password_incorrect": "Parola este incorectă",
"account_basics_phone_numbers_dialog_description": "Pentru a folosi funcția de notificare prin apel, trebuie să adăugați și să verificați cel puțin un număr de telefon. Verificare poate fi făcută prin SMS sau apel vocal.",
"account_basics_phone_numbers_description": "Pentru notificări prin apel",
"account_basics_phone_numbers_dialog_verify_button_sms": "Trimite SMS",
"account_basics_phone_numbers_no_phone_numbers_yet": "Încă nu există numere de telefon",
"account_basics_phone_numbers_dialog_title": "Adaugă număr de telefon",
"account_basics_phone_numbers_dialog_number_label": "Număr de telefon",
"account_basics_phone_numbers_dialog_number_placeholder": "e.x. +1222333444",
"account_basics_phone_numbers_dialog_verify_button_call": "Sună-mă",
"account_basics_phone_numbers_dialog_code_label": "Cod de verificare",
"account_basics_phone_numbers_dialog_code_placeholder": "e.x. 123456",
"account_basics_phone_numbers_dialog_check_verification_button": "Confirmă codul",
"account_basics_phone_numbers_dialog_channel_sms": "SMS",
"account_basics_phone_numbers_dialog_channel_call": "Apel",
"account_usage_title": "Utilizare",
"account_usage_unlimited": "Nelimitat",
"account_usage_limits_reset_daily": "Limitele de utilizare sunt resetate zilnic la miezul nopții (UTC)",
"account_basics_tier_title": "Tip de cont",
"account_usage_of_limit": "din {{limit}}",
"account_basics_tier_admin": "Administrator",
"account_basics_tier_admin_suffix_with_tier": "(cu nivelul {{tier}})",
"account_basics_tier_admin_suffix_no_tier": "(niciun nivel)",
"account_basics_tier_basic": "De bază",
"account_basics_tier_change_button": "Schimbă",
"account_basics_password_dialog_new_password_label": "Parola nouă",
"account_basics_password_title": "Parolă",
"account_basics_tier_description": "Nivelul de putere al contului",
"account_basics_tier_free": "Gratuit"
}

View File

@@ -67,7 +67,7 @@
"subscribe_dialog_subscribe_title": "Подписаться на тему",
"publish_dialog_button_cancel": "Отмена",
"subscribe_dialog_subscribe_description": "Темы могут быть не защищены паролем, поэтому укажите сложное имя. После подписки Вы сможете отправлять уведомления используя PUT/POST-запросы.",
"prefs_users_description": "Добавляйте/удаляйте пользователей для защищенных тем. Обратите внимание, что имя пользователя и пароль хранятся в локальном хранилище браузера.",
"prefs_users_description": "Вы можете управлять пользователями для защищённых тем. Учтите, что имя учётные данные хранятся в локальном хранилище браузера.",
"error_boundary_description": "Это не должно было случиться. Нам очень жаль. <br/>Если Вы можете уделить минуту своего времени, пожалуйста <githubLink>сообщите об этом на GitHub</githubLink>, или дайте нам знать через <discordLink>Discord</discordLink> или <matrixLink>Matrix</matrixLink>.",
"publish_dialog_email_placeholder": "Адрес для пересылки уведомления. Например, phil@example.com",
"publish_dialog_attach_placeholder": "Прикрепите файл по URL. Например, https://f-droid.org/F-Droid.apk",
@@ -96,36 +96,36 @@
"subscribe_dialog_subscribe_button_subscribe": "Подписаться",
"subscribe_dialog_login_title": "Требуется авторизация",
"subscribe_dialog_login_description": "Эта тема защищена паролем. Пожалуйста, введите имя пользователя и пароль, чтобы подписаться.",
"subscribe_dialog_login_username_label": "Имя пользователя. Например, phil",
"subscribe_dialog_login_username_label": "Имя пользователя. Например, oleg",
"subscribe_dialog_login_password_label": "Пароль",
"common_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_title": "Звук уведомлений",
"prefs_notifications_sound_description_none": "При получении уведомлений не звуки не проигрываются",
"prefs_notifications_sound_no_sound": "Без звука",
"prefs_notifications_min_priority_title": "Минимальный приоритет",
"prefs_notifications_min_priority_description_any": "Показывать все уведомления, независимо от приоритета",
"prefs_notifications_min_priority_description_any": "Показывать все уведомления, независимо от их приоритета",
"prefs_notifications_min_priority_description_x_or_higher": "Показывать уведомления, если приоритет {{number}} ({{name}}) или выше",
"prefs_notifications_min_priority_description_max": "Показывать уведомления, если приоритет равен 5 (максимальный)",
"prefs_notifications_min_priority_any": "Любой приоритет",
"prefs_notifications_min_priority_low_and_higher": "Низкий приоритет и выше",
"prefs_notifications_min_priority_max_only": "Только максимальный приоритет",
"prefs_notifications_delete_after_title": "Удалить уведомления",
"prefs_notifications_delete_after_title": "Удаление уведомлений",
"prefs_notifications_delete_after_never": "Никогда",
"prefs_notifications_delete_after_three_hours": "Через три часа",
"prefs_notifications_sound_description_some": "Уведомления воспроизводят звук {{sound}}",
"prefs_notifications_sound_description_some": "При уведомлениях проигрывается звук {{sound}}",
"prefs_notifications_min_priority_default_and_higher": "Стандартный приоритет и выше",
"prefs_notifications_delete_after_one_day": "Через день",
"prefs_notifications_delete_after_one_week": "Через неделю",
"prefs_notifications_delete_after_one_month": "Через месяц",
"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_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_add_button": "Добавить пользователя",
"prefs_users_table_user_header": "Пользователь",
@@ -133,7 +133,7 @@
"prefs_users_dialog_title_add": "Добавить пользователя",
"prefs_users_dialog_title_edit": "Редактировать пользователя",
"prefs_users_dialog_base_url_label": "URL-адрес сервера. Например, https://ntfy.sh",
"prefs_users_dialog_username_label": "Имя пользователя. Например, phil",
"prefs_users_dialog_username_label": "Имя пользователя. Например, oleg",
"prefs_users_dialog_password_label": "Пароль",
"common_cancel": "Отмена",
"common_add": "Добавить",
@@ -157,11 +157,11 @@
"emoji_picker_search_clear": "Сбросить поиск",
"account_upgrade_dialog_cancel_warning": "Это действие <strong>отменит Вашу подписку</strong> и переведет Вашую учетную запись на бесплатное обслуживание {{date}}. При наступлении этой даты, все резервирования и сообщения в кэше <strong>будут удалены</strong>.",
"account_tokens_table_create_token_button": "Создать токен доступа",
"account_tokens_table_last_origin_tooltip": "с IP-адреса {{ip}}, нажмите для подробностей",
"account_tokens_table_last_origin_tooltip": "С IP-адреса {{ip}}, нажмите для подробностей",
"account_tokens_dialog_title_edit": "Изменить токен доступа",
"account_delete_dialog_button_cancel": "Отмена",
"account_delete_dialog_billing_warning": "Удаление учетной записи также отменяет все платные подписки. У Вас не будет доступа к порталу оплаты.",
"account_delete_dialog_description": "Это действие безвозвратно удалит Вашу учетную запись, включая все Ваши данные хранящиеся на сервере. После удаления, Ваше имя пользователя не будет доступно для регистрации в течении 7 дней. Если Вы действительно хотите продолжить, пожалуйста введите Ваш пароль ниже.",
"account_delete_dialog_description": "Это действие безвозвратно удалит вашу учётную запись, включая все данные, хранящиеся на сервере. После удаления имя пользователя вашей учётной записи не будет доступно для регистрации в течение 7 дней. Если вы точно хотите продолжить, пожалуйста, введите свой пароль ниже.",
"account_delete_dialog_label": "Пароль",
"reservation_delete_dialog_action_keep_description": "Сообщения и вложения которые находятся в кэше сервера станут доступны всем, кто знает имя темы.",
"prefs_reservations_table": "Список зарезервированных тем",
@@ -173,7 +173,7 @@
"prefs_reservations_table_not_subscribed": "Не подписан",
"prefs_reservations_table_everyone_deny_all": "Только я могу публиковать и подписываться",
"prefs_reservations_table_everyone_read_write": "Все могут публиковать и подписываться",
"prefs_reservations_table_click_to_subscribe": "Нажмите чтобы подписаться",
"prefs_reservations_table_click_to_subscribe": "Нажмите, чтобы подписаться",
"prefs_reservations_dialog_title_add": "Зарезервировать тему",
"prefs_reservations_dialog_title_delete": "Удалить резервирование",
"prefs_reservations_dialog_title_edit": "Изменение резервированной темы",
@@ -202,7 +202,7 @@
"account_tokens_dialog_expires_never": "Токен никогда не истекает",
"prefs_notifications_sound_play": "Воспроизводить выбранный звук",
"account_upgrade_dialog_tier_features_reservations_other": "{{reservations}} зарезервированных тем",
"account_upgrade_dialog_tier_features_emails_other": "{{emails}} эл. сообщений в день",
"account_upgrade_dialog_tier_features_emails_other": "{{emails}} эл. писем в день",
"account_basics_tier_free": "Бесплатный",
"account_tokens_dialog_title_create": "Создать токен доступа",
"account_tokens_dialog_title_delete": "Удалить токен доступа",
@@ -215,11 +215,11 @@
"account_upgrade_dialog_tier_current_label": "Текущая",
"account_upgrade_dialog_button_cancel": "Отмена",
"prefs_users_edit_button": "Редактировать пользователя",
"account_basics_tier_upgrade_button": "Подписаться на Pro",
"account_basics_tier_upgrade_button": "Обновить до Pro",
"account_basics_tier_paid_until": "Подписка оплачена до {{date}} и будет продляться автоматически",
"account_basics_tier_change_button": "Изменить",
"account_delete_dialog_button_submit": "Безвозвратно удалить учетную запись",
"account_upgrade_dialog_title": "Изменить уровень учетной записи",
"account_delete_dialog_button_submit": "Безвозвратно удалить эту учётную запись",
"account_upgrade_dialog_title": "Изменить уровень учётной записи",
"account_usage_basis_ip_description": "Статистика и ограничения на использование учитываются по IP-адресу, поэтому они могут совмещаться с другими пользователями. Уровни, указанные выше, примерно соответствуют текущим ограничениям.",
"publish_dialog_topic_reset": "Сбросить тему",
"account_basics_tier_admin_suffix_no_tier": "(без подписки)",
@@ -231,9 +231,9 @@
"signup_form_toggle_password_visibility": "Показать/скрыть пароль",
"signup_disabled": "Регистрация недоступна",
"signup_error_username_taken": "Имя пользователя {{username}} уже занято",
"signup_title": "Создать учетную запись ntfy",
"signup_already_have_account": "Уже есть учетная запись? Войдите!",
"signup_error_creation_limit_reached": "Лимит на создание учетных записей исчерпан",
"signup_title": "Создать учётную запись ntfy",
"signup_already_have_account": "Уже есть учётная запись? Войдите!",
"signup_error_creation_limit_reached": "Исчерпано ограничение создания учётных записей",
"login_form_button_submit": "Вход",
"login_link_signup": "Регистрация",
"login_disabled": "Вход недоступен",
@@ -249,12 +249,12 @@
"message_bar_publish": "Опубликовать сообщение",
"nav_button_muted": "Уведомления заглушены",
"nav_button_connecting": "установка соединения",
"action_bar_account": "Учетная запись",
"login_title": "Вход в Вашу учетную запись ntfy",
"action_bar_account": "Учётная запись",
"login_title": "Войдите в учётную запись ntfy",
"action_bar_reservation_limit_reached": "Лимит исчерпан",
"action_bar_toggle_mute": "Заглушить/разрешить уведомления",
"nav_button_account": "Учетная запись",
"nav_upgrade_banner_label": "Купить подписку ntfy Pro",
"nav_button_account": "Учётная запись",
"nav_upgrade_banner_label": "Подписка ntfy Pro",
"message_bar_show_dialog": "Открыть диалог публикации",
"notifications_list": "Список уведомлений",
"notifications_list_item": "Уведомление",
@@ -279,12 +279,12 @@
"subscribe_dialog_subscribe_base_url_label": "URL-адрес сервера",
"subscribe_dialog_subscribe_button_generate_topic_name": "Сгенерировать случайное имя",
"subscribe_dialog_error_topic_already_reserved": "Тема уже зарезервирована",
"account_basics_title": "Учетная запись",
"account_basics_title": "Учётная запись",
"account_basics_username_title": "Имя пользователя",
"account_basics_username_admin_tooltip": "Вы Администратор",
"account_basics_username_admin_tooltip": "Вы администратор",
"account_basics_password_title": "Пароль",
"account_basics_username_description": "Это Вы! :)",
"account_basics_password_description": "Смена пароля учетной записи",
"account_basics_username_description": "Это вы! :)",
"account_basics_password_description": "Смена пароля учётной записи",
"account_basics_password_dialog_title": "Смена пароля",
"account_basics_password_dialog_current_password_label": "Текущий пароль",
"account_basics_password_dialog_current_password_incorrect": "Введен неверный пароль",
@@ -292,11 +292,11 @@
"account_usage_of_limit": "из {{limit}}",
"account_usage_unlimited": "Неограниченно",
"account_usage_limits_reset_daily": "Ограничения сбрасываются ежедневно в полночь (UTC)",
"account_basics_tier_description": "Уровень Вашей учетной записи",
"account_basics_tier_description": "Уровень вашей учётной записи",
"account_basics_tier_admin": "Администратор",
"account_basics_tier_admin_suffix_with_tier": "(с {{tier}} подпиской)",
"account_basics_tier_payment_overdue": "У Вас задолженность по оплате. Пожалуйста проверьте метод оплаты, иначе Вы скоро потеряете преимущества Вашей подписки.",
"account_basics_tier_canceled_subscription": "Ваша подписка была отменена; учетная запись перейдет на бесплатное обслуживание {{date}}.",
"account_basics_tier_admin_suffix_with_tier": "(с подпиской {{tier}})",
"account_basics_tier_payment_overdue": "У вас имеется задолженность по оплате. Пожалуйста, проверьте метод оплаты, иначе скоро вы утратите преимущества подписки.",
"account_basics_tier_canceled_subscription": "Ваша подписка была отменена. Учётная запись перейдет на бесплатное обслуживание {{date}}.",
"account_basics_tier_manage_billing_button": "Управление оплатой",
"account_usage_messages_title": "Опубликованные сообщения",
"account_usage_emails_title": "Отправленные электронные сообщения",
@@ -305,8 +305,8 @@
"account_usage_attachment_storage_title": "Хранение вложений",
"account_usage_attachment_storage_description": "{{filesize}} за файл, удаляются спустя {{expiry}}",
"account_usage_cannot_create_portal_session": "Невозможно открыть портал оплаты",
"account_delete_title": "Удалить учетную запись",
"account_delete_description": "Безвозвратно удалить Вашу учетную запись",
"account_delete_title": "Удаление учётной записи",
"account_delete_description": "Безвозвратное удаление этой учётной записи",
"account_upgrade_dialog_button_redirect_signup": "Зарегистрироваться",
"account_upgrade_dialog_button_pay_now": "Оплатить и подписаться",
"account_upgrade_dialog_button_cancel_subscription": "Отменить подписку",
@@ -319,8 +319,8 @@
"account_tokens_table_expires_header": "Истекает",
"account_tokens_dialog_label": "Название, например Radarr notifications",
"prefs_reservations_title": "Зарезервированные темы",
"prefs_reservations_description": "Здесь Вы можете резервировать темы для личного пользования. Резервирование дает Вам возможность управлять темой и настраивать правила доступа к ней для пользователей.",
"prefs_reservations_limit_reached": "Вы исчерпали Ваш лимит на количество зарезервированных тем.",
"prefs_reservations_description": "Здесь вы можете резервировать темы для личного пользования. Резервирование дает возможность управления темой и настройки правил доступа к ней для других пользователей.",
"prefs_reservations_limit_reached": "Лимит количества зарезервированных тем исчерпан.",
"prefs_reservations_add_button": "Добавить тему",
"prefs_reservations_edit_button": "Настройка доступа",
"prefs_reservations_delete_button": "Сбросить правила доступа",
@@ -339,7 +339,7 @@
"account_basics_password_dialog_new_password_label": "Новый пароль",
"account_basics_password_dialog_confirm_password_label": "Подтвердите пароль",
"account_basics_password_dialog_button_submit": "Сменить пароль",
"account_basics_tier_title": "Тип учетной записи",
"account_basics_tier_title": "Тип учётной записи",
"error_boundary_unsupported_indexeddb_description": "Веб-приложение ntfy использует IndexedDB, который не поддерживается Вашим браузером в приватном режиме.<br/><br/>Хотя это и не лучший вариант, использовать веб-приложение ntfy в приватном режиме не имеет особого смысла, так как все данные храняться в локальном хранилище браузера. Вы можете узнать больше в <githubLink>этом отчете на GitHub</githubLink> или связавшись с нами через <discordLink>Discord</discordLink> или <matrixLink>Matrix</matrixLink>.",
"account_basics_tier_interval_monthly": "ежемесячно",
"account_basics_tier_interval_yearly": "ежегодно",
@@ -356,11 +356,11 @@
"publish_dialog_call_reset": "Удалить вызов",
"account_basics_phone_numbers_dialog_description": "Для того что бы использовать возможность уведомлений о вызовах, нужно добавить и проверить хотя бы один номер телефона. Проверить можно используя SMS или звонок.",
"account_basics_phone_numbers_dialog_title": "Добавить номер телефона",
"account_basics_phone_numbers_dialog_number_placeholder": "например +1222333444",
"account_basics_phone_numbers_dialog_code_placeholder": "например 123456",
"account_basics_phone_numbers_dialog_number_placeholder": "например, +72223334444",
"account_basics_phone_numbers_dialog_code_placeholder": "например, 123456",
"account_basics_phone_numbers_dialog_verify_button_sms": "Отправить SMS",
"account_usage_calls_title": "Совершённые вызовы",
"account_usage_calls_none": "Невозможно совершать вызовы с этим аккаунтом",
"account_usage_calls_none": "Невозможно совершать вызовы с этой учётной записью",
"publish_dialog_chip_call_no_verified_numbers_tooltip": "Нет проверенных номеров",
"account_basics_phone_numbers_copied_to_clipboard": "Номер телефона скопирован в буфер обмена",
"account_upgrade_dialog_tier_features_no_calls": "Нет вызовов",
@@ -371,8 +371,8 @@
"account_upgrade_dialog_tier_features_reservations_one": "{{reservations}} зарезервированная тема",
"account_basics_phone_numbers_no_phone_numbers_yet": "Телефонных номеров пока нет",
"publish_dialog_chip_call_label": "Звонок",
"account_upgrade_dialog_tier_features_emails_one": "{{emails}} ежедневное письмо",
"account_upgrade_dialog_tier_features_messages_one": "{{messages}} ежедневное сообщения",
"account_upgrade_dialog_tier_features_emails_one": "{{emails}} эл. письмо в день",
"account_upgrade_dialog_tier_features_messages_one": "{{messages}} сообщение в день",
"account_basics_phone_numbers_description": "Для уведомлений о телефонных звонках",
"publish_dialog_call_label": "Звонок",
"account_basics_phone_numbers_dialog_channel_call": "Позвонить",
@@ -383,8 +383,8 @@
"account_basics_phone_numbers_dialog_channel_sms": "SMS",
"action_bar_mute_notifications": "Заглушить уведомления",
"action_bar_unmute_notifications": "Разрешить уведомления",
"alert_notification_permission_denied_title": "Уведомления заблокированы",
"alert_notification_permission_denied_description": "Пожалуйста, разрешите их в своём браузере",
"alert_notification_permission_denied_title": "Уведомления не разрешены",
"alert_notification_permission_denied_description": "Пожалуйста, разрешите отправку уведомлений браузере",
"alert_notification_ios_install_required_title": "iOS требует установку",
"alert_notification_ios_install_required_description": "Нажмите на значок \"Поделиться\" и \"Добавить на главный экран\", чтобы включить уведомления на iOS",
"error_boundary_button_reload_ntfy": "Перезагрузить ntfy",
@@ -401,7 +401,7 @@
"notifications_actions_failed_notification": "Неудачное действие",
"publish_dialog_checkbox_markdown": "Форматировать как Markdown",
"subscribe_dialog_subscribe_use_another_background_info": "Уведомления с других серверов не будут получены, когда веб-приложение не открыто",
"prefs_appearance_theme_system": "Системный (по умолчанию)",
"prefs_appearance_theme_dark": "Ночной режим",
"prefs_appearance_theme_light": "Дневной режим"
"prefs_appearance_theme_system": "Как в системе (по умолчанию)",
"prefs_appearance_theme_dark": "Тёмная",
"prefs_appearance_theme_light": "Светлая"
}

View File

@@ -0,0 +1,63 @@
{
"common_back": "Prapa",
"signup_form_username": "Emri i përdoruesit",
"signup_title": "Krijo një llogari \"ntfy\"",
"signup_form_toggle_password_visibility": "Ndrysho dukshmërinë e fjalëkalimit",
"common_save": "Ruaj",
"signup_form_confirm_password": "Konfirmo Fjalëkalimin",
"common_copy_to_clipboard": "Kopjo",
"signup_form_button_submit": "Regjistrohu",
"signup_already_have_account": "Keni tashmë llogari? Identifikohu!",
"signup_disabled": "Regjistrimi është i çaktivizuar",
"signup_error_username_taken": "Emri i përdoruesit {{username}} është marrë tashmë",
"signup_error_creation_limit_reached": "U arrit kufiri i krijimit të llogarisë",
"login_title": "Hyni në llogarinë tuaj ntfy",
"login_form_button_submit": "Identifikohu",
"login_disabled": "Identifikimi është i çaktivizuar",
"action_bar_show_menu": "Shfaq menunë",
"action_bar_settings": "Parametrat",
"action_bar_account": "Llogaria",
"action_bar_change_display_name": "Ndrysho emrin e shfaqur",
"action_bar_reservation_add": "Rezervo temën",
"action_bar_reservation_edit": "Ndrysho rezervimin",
"action_bar_reservation_delete": "Hiq rezervimin",
"action_bar_reservation_limit_reached": "U arrit kufiri",
"action_bar_send_test_notification": "Dërgo njoftim testues",
"action_bar_clear_notifications": "Pastro të gjitha njoftimet",
"action_bar_mute_notifications": "Heshti njoftimet",
"action_bar_unmute_notifications": "Lejo njoftimet",
"action_bar_unsubscribe": "Ç'abonohu",
"action_bar_toggle_mute": "Hesht/lejo njoftimet",
"action_bar_toggle_action_menu": "Hap/mbyll menynë e veprimit",
"action_bar_profile_title": "Profili",
"action_bar_profile_settings": "Parametrat",
"action_bar_profile_logout": "Dil",
"action_bar_sign_in": "Identifikohu",
"action_bar_sign_up": "Regjistrohu",
"message_bar_type_message": "Shkruaj një mesazh këtu",
"common_cancel": "Anullo",
"signup_form_password": "Fjalëkalimi",
"common_add": "Shto",
"login_link_signup": "Regjistrohu",
"action_bar_logo_alt": "logo e ntfy",
"message_bar_error_publishing": "Gabim duke postuar njoftimin",
"message_bar_show_dialog": "Trego dialogun e publikimit",
"message_bar_publish": "Publiko mesazhin",
"nav_topics_title": "Temat e abonuara",
"nav_button_all_notifications": "Të gjitha njoftimet",
"nav_button_account": "Llogaria",
"nav_button_settings": "Cilësimet",
"nav_button_publish_message": "Publiko njoftimin",
"nav_button_subscribe": "Abunohu tek tema",
"nav_button_connecting": "duke u lidhur",
"nav_upgrade_banner_label": "Përmirëso në ntfy Pro",
"nav_upgrade_banner_description": "Rezervoni tema, më shumë mesazhe dhe email-e, si dhe bashkëngjitje më të mëdha",
"nav_button_muted": "Njoftimet janë të fikura",
"alert_notification_permission_required_title": "Njoftimet janë të çaktivizuar",
"alert_notification_permission_required_description": "Jepni leje Browser-it tuaj për të shfaqur njoftimet në desktop",
"alert_notification_permission_denied_title": "Njoftimet janë të bllokuara",
"alert_notification_ios_install_required_title": "Instalimi i iOS-it detyrohet",
"alert_notification_permission_denied_description": "Ju lutemi riaktivizoni ato në Browser-in tuaj",
"nav_button_documentation": "Dokumentacion",
"alert_notification_permission_required_button": "Lejo tani"
}

View File

@@ -0,0 +1,407 @@
{
"action_bar_account": "கணக்கு",
"action_bar_change_display_name": "காட்சி பெயரை மாற்றவும்",
"action_bar_show_menu": "மெனுவைக் காட்டு",
"action_bar_logo_alt": "ntfy லோகோ",
"action_bar_settings": "அமைப்புகள்",
"action_bar_reservation_add": "இருப்பு தலைப்பு",
"message_bar_publish": "செய்தியை வெளியிடுங்கள்",
"nav_topics_title": "சந்தா தலைப்புகள்",
"nav_button_all_notifications": "அனைத்து அறிவிப்புகளும்",
"nav_button_account": "கணக்கு",
"nav_button_settings": "அமைப்புகள்",
"nav_button_documentation": "ஆவணப்படுத்துதல்",
"nav_button_publish_message": "அறிவிப்பை வெளியிடுங்கள்",
"alert_not_supported_description": "உங்கள் உலாவியில் அறிவிப்புகள் ஆதரிக்கப்படவில்லை",
"alert_not_supported_context_description": "அறிவிப்புகள் HTTP களில் மட்டுமே ஆதரிக்கப்படுகின்றன. இது <mdnlink> அறிவிப்புகள் பநிஇ </mdnlink> இன் வரம்பு.",
"notifications_list": "அறிவிப்புகள் பட்டியல்",
"notifications_delete": "நீக்கு",
"notifications_copied_to_clipboard": "இடைநிலைப்பலகைக்கு நகலெடுக்கப்பட்டது",
"notifications_list_item": "அறிவிப்பு",
"notifications_mark_read": "படித்தபடி குறி",
"notifications_tags": "குறிச்சொற்கள்",
"notifications_priority_x": "முன்னுரிமை {{priority}}",
"notifications_actions_not_supported": "வலை பயன்பாட்டில் நடவடிக்கை ஆதரிக்கப்படவில்லை",
"notifications_none_for_topic_title": "இந்த தலைப்புக்கு நீங்கள் இதுவரை எந்த அறிவிப்புகளையும் பெறவில்லை.",
"notifications_actions_http_request_title": "Http {{method}} {{url}} க்கு அனுப்பவும்",
"notifications_actions_failed_notification": "தோல்வியுற்ற செயல்",
"notifications_none_for_topic_description": "இந்த தலைப்புக்கு அறிவிப்புகளை அனுப்ப, தலைப்பு முகவரி க்கு வைக்கவும் அல்லது இடுகையிடவும்.",
"notifications_loading": "அறிவிப்புகளை ஏற்றுகிறது…",
"publish_dialog_title_topic": "{{topic}} க்கு வெளியிடுங்கள்",
"publish_dialog_title_no_topic": "அறிவிப்பை வெளியிடுங்கள்",
"publish_dialog_progress_uploading": "பதிவேற்றுதல்…",
"publish_dialog_message_published": "அறிவிப்பு வெளியிடப்பட்டது",
"publish_dialog_attachment_limits_file_and_quota_reached": "{{fileSizeLimit}} கோப்பு வரம்பு மற்றும் ஒதுக்கீடு, {{remainingBytes}} மீதமுள்ளது",
"publish_dialog_attachment_limits_file_reached": "{{fileSizeLimit}} கோப்பு வரம்பை மீறுகிறது",
"publish_dialog_attachment_limits_quota_reached": "ஒதுக்கீட்டை மீறுகிறது, {{remainingBytes}} மீதமுள்ளவை",
"publish_dialog_progress_uploading_detail": "பதிவேற்றுவது {{loaded}}/{{{total}} ({{percent}}%)…",
"publish_dialog_priority_min": "மணித்துளி. முன்னுரிமை",
"publish_dialog_emoji_picker_show": "ஈமோசியைத் தேர்ந்தெடுங்கள்",
"publish_dialog_priority_low": "குறைந்த முன்னுரிமை",
"publish_dialog_priority_default": "இயல்புநிலை முன்னுரிமை",
"publish_dialog_priority_high": "அதிக முன்னுரிமை",
"publish_dialog_priority_max": "அதிகபட்சம். முன்னுரிமை",
"publish_dialog_base_url_label": "பணி முகவரி",
"publish_dialog_base_url_placeholder": "பணி முகவரி, எ.கா. https://example.com",
"publish_dialog_topic_label": "தலைப்பு பெயர்",
"publish_dialog_topic_placeholder": "தலைப்பு பெயர், எ.கா. phil_alerts",
"publish_dialog_topic_reset": "தலைப்பை மீட்டமைக்கவும்",
"publish_dialog_title_label": "தலைப்பு",
"publish_dialog_title_placeholder": "அறிவிப்பு தலைப்பு, எ.கா. வட்டு விண்வெளி எச்சரிக்கை",
"publish_dialog_message_label": "செய்தி",
"publish_dialog_message_placeholder": "இங்கே ஒரு செய்தியைத் தட்டச்சு செய்க",
"publish_dialog_tags_label": "குறிச்சொற்கள்",
"publish_dialog_tags_placeholder": "குறிச்சொற்களின் கமாவால் பிரிக்கப்பட்ட பட்டியல், எ.கா. எச்சரிக்கை, SRV1-Backup",
"publish_dialog_priority_label": "முன்னுரிமை",
"publish_dialog_click_label": "முகவரி ஐக் சொடுக்கு செய்க",
"publish_dialog_click_placeholder": "அறிவிப்பைக் சொடுக்கு செய்யும் போது திறக்கப்படும் முகவரி",
"publish_dialog_click_reset": "சொடுக்கு முகவரி ஐ அகற்று",
"publish_dialog_email_label": "மின்னஞ்சல்",
"publish_dialog_email_placeholder": "அறிவிப்பை அனுப்ப முகவரி, எ.கா. phil@example.com",
"publish_dialog_email_reset": "மின்னஞ்சலை முன்னோக்கி அகற்றவும்",
"publish_dialog_call_label": "தொலைபேசி அழைப்பு",
"publish_dialog_call_item": "தொலைபேசி எண்ணை அழைக்கவும் {{number}}",
"publish_dialog_call_reset": "தொலைபேசி அழைப்பை அகற்று",
"publish_dialog_attach_label": "இணைப்பு முகவரி",
"publish_dialog_attach_placeholder": "முகவரி ஆல் கோப்பை இணைக்கவும், எ.கா. https://f-droid.org/f-droid.apk",
"publish_dialog_attach_reset": "இணைப்பு முகவரி ஐ அகற்று",
"publish_dialog_filename_label": "கோப்புப்பெயர்",
"publish_dialog_filename_placeholder": "இணைப்பு கோப்பு பெயர்",
"publish_dialog_delay_label": "சுணக்கம்",
"publish_dialog_delay_placeholder": "நேரந்தவறுகை வழங்கல், எ.கா. {{unixTimestamp}}, {{relativeTime}}, அல்லது \"{{naturalLanguage}}\" (ஆங்கிலம் மட்டும்)",
"publish_dialog_delay_reset": "தாமதமான விநியோகத்தை அகற்று",
"publish_dialog_other_features": "பிற அம்சங்கள்:",
"publish_dialog_chip_click_label": "முகவரி ஐக் சொடுக்கு செய்க",
"publish_dialog_chip_call_label": "தொலைபேசி அழைப்பு",
"publish_dialog_chip_email_label": "மின்னஞ்சலுக்கு அனுப்பவும்",
"publish_dialog_chip_call_no_verified_numbers_tooltip": "சரிபார்க்கப்பட்ட தொலைபேசி எண்கள் இல்லை",
"publish_dialog_chip_attach_url_label": "முகவரி மூலம் கோப்பை இணைக்கவும்",
"publish_dialog_details_examples_description": "எடுத்துக்காட்டுகள் மற்றும் அனைத்து அனுப்பும் அம்சங்களின் விரிவான விளக்கத்திற்கு, தயவுசெய்து <ock இணைப்பு> ஆவணங்கள் </டாக்ச் இணைப்பு> ஐப் பார்க்கவும்.",
"publish_dialog_chip_attach_file_label": "உள்ளக கோப்பை இணைக்கவும்",
"publish_dialog_chip_delay_label": "நேரந்தவறுகை வழங்கல்",
"publish_dialog_chip_topic_label": "தலைப்பை மாற்றவும்",
"subscribe_dialog_subscribe_button_generate_topic_name": "பெயரை உருவாக்குங்கள்",
"subscribe_dialog_subscribe_use_another_background_info": "வலை பயன்பாடு திறக்கப்படாதபோது பிற சேவையகங்களிலிருந்து அறிவிப்புகள் பெறப்படாது",
"subscribe_dialog_subscribe_button_cancel": "ரத்துசெய்",
"subscribe_dialog_subscribe_button_subscribe": "குழுசேர்",
"subscribe_dialog_login_title": "உள்நுழைவு தேவை",
"account_basics_password_dialog_confirm_password_label": "கடவுச்சொல்லை உறுதிப்படுத்தவும்",
"account_basics_password_dialog_current_password_incorrect": "கடவுச்சொல் தவறானது",
"account_basics_password_dialog_button_submit": "கடவுச்சொல்லை மாற்றவும்",
"account_basics_phone_numbers_title": "தொலைபேசி எண்கள்",
"account_basics_phone_numbers_dialog_description": "அழைப்பு அறிவிப்பு அம்சத்தைப் பயன்படுத்த, நீங்கள் குறைந்தது ஒரு தொலைபேசி எண்ணையாவது சேர்த்து சரிபார்க்க வேண்டும். சரிபார்ப்பு எச்எம்எச் அல்லது தொலைபேசி அழைப்பு வழியாக செய்யப்படலாம்.",
"account_basics_phone_numbers_description": "தொலைபேசி அழைப்பு அறிவிப்புகளுக்கு",
"account_basics_phone_numbers_no_phone_numbers_yet": "தொலைபேசி எண்கள் இதுவரை இல்லை",
"account_basics_phone_numbers_copied_to_clipboard": "தொலைபேசி எண் இடைநிலைப்பலகைக்கு நகலெடுக்கப்பட்டது",
"account_basics_phone_numbers_dialog_title": "தொலைபேசி எண்ணைச் சேர்க்கவும்",
"account_basics_phone_numbers_dialog_number_placeholder": "எ.கா. +122333444",
"account_basics_phone_numbers_dialog_verify_button_sms": "எச்எம்எச் அனுப்பு",
"account_basics_phone_numbers_dialog_code_placeholder": "எ.கா. 123456",
"account_basics_phone_numbers_dialog_check_verification_button": "குறியீட்டை உறுதிப்படுத்தவும்",
"account_basics_phone_numbers_dialog_verify_button_call": "என்னை அழைக்கவும்",
"account_basics_phone_numbers_dialog_code_label": "சரிபார்ப்பு குறியீடு",
"account_basics_phone_numbers_dialog_channel_sms": "எச்.எம்.எச்",
"account_basics_phone_numbers_dialog_channel_call": "அழைப்பு",
"account_usage_title": "பயன்பாடு",
"account_usage_unlimited": "வரம்பற்றது",
"account_usage_of_limit": "{{limit}} of",
"account_usage_limits_reset_daily": "பயன்பாட்டு வரம்புகள் நள்ளிரவில் தினமும் மீட்டமைக்கப்படுகின்றன (UTC)",
"account_basics_tier_title": "கணக்கு வகை",
"account_basics_tier_description": "உங்கள் கணக்கின் ஆற்றல் நிலை",
"account_basics_tier_admin": "நிர்வாகி",
"account_basics_tier_admin_suffix_with_tier": "({{tier}} அடுக்கு)",
"account_basics_tier_admin_suffix_no_tier": "(அடுக்கு இல்லை)",
"account_basics_tier_basic": "அடிப்படை",
"account_basics_tier_free": "இலவசம்",
"account_basics_tier_interval_monthly": "மாதாந்திர",
"account_basics_tier_interval_yearly": "ஆண்டுதோறும்",
"account_basics_tier_upgrade_button": "சார்புக்கு மேம்படுத்தவும்",
"account_basics_tier_change_button": "மாற்றம்",
"account_basics_tier_paid_until": "சந்தா {{date}} வரை செலுத்தப்படுகிறது, மேலும் தானாக புதுப்பிக்கப்படும்",
"account_basics_tier_canceled_subscription": "உங்கள் சந்தா ரத்து செய்யப்பட்டது மற்றும் {{date} at இல் இலவச கணக்கிற்கு தரமிறக்கப்படும்.",
"account_basics_tier_manage_billing_button": "பட்டியலிடல் நிர்வகிக்கவும்",
"account_basics_tier_payment_overdue": "உங்கள் கட்டணம் தாமதமானது. தயவுசெய்து உங்கள் கட்டண முறையைப் புதுப்பிக்கவும், அல்லது உங்கள் கணக்கு விரைவில் தரமிறக்கப்படும்.",
"account_usage_messages_title": "வெளியிடப்பட்ட செய்திகள்",
"account_usage_emails_title": "மின்னஞ்சல்கள் அனுப்பப்பட்டன",
"account_usage_calls_title": "தொலைபேசி அழைப்புகள் செய்யப்பட்டன",
"account_usage_calls_none": "இந்த கணக்கில் தொலைபேசி அழைப்புகள் எதுவும் செய்ய முடியாது",
"account_usage_reservations_title": "ஒதுக்கப்பட்ட தலைப்புகள்",
"account_usage_reservations_none": "இந்த கணக்கிற்கு ஒதுக்கப்பட்ட தலைப்புகள் இல்லை",
"account_usage_attachment_storage_title": "இணைப்பு சேமிப்பு",
"account_usage_attachment_storage_description": "கோப்பு {{filesize}} க்குப் பிறகு நீக்கப்பட்ட ஒரு கோப்பிற்கு {{expiry}}}}",
"account_usage_basis_ip_description": "இந்த கணக்கிற்கான பயன்பாட்டு புள்ளிவிவரங்கள் மற்றும் வரம்புகள் உங்கள் ஐபி முகவரியை அடிப்படையாகக் கொண்டவை, எனவே அவை மற்ற பயனர்களுடன் பகிரப்படலாம். மேலே காட்டப்பட்டுள்ள வரம்புகள் தற்போதுள்ள விகித வரம்புகளின் அடிப்படையில் தோராயங்கள்.",
"account_usage_cannot_create_portal_session": "பட்டியலிடல் போர்ட்டலைத் திறக்க முடியவில்லை",
"account_delete_title": "கணக்கை நீக்கு",
"account_delete_description": "உங்கள் கணக்கை நிரந்தரமாக நீக்கவும்",
"account_upgrade_dialog_cancel_warning": "இது <strong> உங்கள் சந்தாவை ரத்துசெய்யும் </strong>, மேலும் உங்கள் கணக்கை {{date} at இல் தரமிறக்குகிறது. அந்த தேதியில், தலைப்பு முன்பதிவு மற்றும் சேவையகத்தில் தற்காலிகமாக சேமிக்கப்பட்ட செய்திகளும் நீக்கப்படும் </strong>.",
"account_upgrade_dialog_proration_info": "<strong> புரோரேசன் </strong>: கட்டணத் திட்டங்களுக்கு இடையில் மேம்படுத்தும்போது, விலை வேறுபாடு <strong> உடனடியாக கட்டணம் வசூலிக்கப்படும் </strong>. குறைந்த அடுக்குக்கு தரமிறக்கும்போது, எதிர்கால பட்டியலிடல் காலங்களுக்கு செலுத்த இருப்பு பயன்படுத்தப்படும்.",
"account_upgrade_dialog_reservations_warning_one": "தேர்ந்தெடுக்கப்பட்ட அடுக்கு உங்கள் தற்போதைய அடுக்கை விட குறைவான ஒதுக்கப்பட்ட தலைப்புகளை அனுமதிக்கிறது. உங்கள் அடுக்கை மாற்றுவதற்கு முன், <strong> தயவுசெய்து குறைந்தது ஒரு முன்பதிவை நீக்கு </strong>. <இணைப்பு> அமைப்புகள் </இணைப்பு> இல் முன்பதிவுகளை அகற்றலாம்.",
"account_upgrade_dialog_reservations_warning_other": "தேர்ந்தெடுக்கப்பட்ட அடுக்கு உங்கள் தற்போதைய அடுக்கை விட குறைவான ஒதுக்கப்பட்ட தலைப்புகளை அனுமதிக்கிறது. உங்கள் அடுக்கை மாற்றுவதற்கு முன், <strong> தயவுசெய்து குறைந்தபட்சம் {{count}} முன்பதிவு </strong> ஐ நீக்கவும். <இணைப்பு> அமைப்புகள் </இணைப்பு> இல் முன்பதிவுகளை அகற்றலாம்.",
"account_upgrade_dialog_tier_features_reservations_other": "{{reservations}} ஒதுக்கப்பட்ட தலைப்புகள்",
"account_upgrade_dialog_tier_features_no_reservations": "ஒதுக்கப்பட்ட தலைப்புகள் இல்லை",
"account_upgrade_dialog_tier_features_messages_one": "{{messages}} நாள்தோறும் செய்தி",
"account_upgrade_dialog_tier_features_messages_other": "{{messages}} நாள்தோறும் செய்திகள்",
"account_upgrade_dialog_tier_features_emails_one": "{{emails}} நாள்தோறும் மின்னஞ்சல்",
"account_upgrade_dialog_tier_features_emails_other": "{{emails}} நாள்தோறும் மின்னஞ்சல்கள்",
"account_upgrade_dialog_tier_features_calls_one": "{{calls}} நாள்தோறும் தொலைபேசி அழைப்புகள்",
"account_upgrade_dialog_tier_features_calls_other": "{{calls}} நாள்தோறும் தொலைபேசி அழைப்புகள்",
"account_upgrade_dialog_tier_features_attachment_total_size": "{{totalsize}} மொத்த சேமிப்பு",
"account_upgrade_dialog_tier_price_per_month": "மாதம்",
"account_upgrade_dialog_tier_price_billed_monthly": "{{price}}}}}}. மாதந்தோறும் பாடு.",
"account_upgrade_dialog_tier_features_no_calls": "தொலைபேசி அழைப்புகள் இல்லை",
"account_upgrade_dialog_tier_features_attachment_file_size": "கோப்பு {filesize}}} ஒரு கோப்பிற்கு",
"account_upgrade_dialog_tier_price_billed_yearly": "{{price}} ஆண்டுதோறும் கட்டணம் செலுத்தப்படுகிறது. {{save}} சேமி.",
"account_upgrade_dialog_tier_selected_label": "தேர்ந்தெடுக்கப்பட்டது",
"account_upgrade_dialog_tier_current_label": "மின்னோட்ட்ம், ஓட்டம்",
"account_upgrade_dialog_billing_contact_email": "பட்டியலிடல் கேள்விகளுக்கு, தயவுசெய்து <இணைப்பு> எங்களை தொடர்பு கொள்ளவும் </இணைப்பு> நேரடியாக.",
"account_upgrade_dialog_button_cancel": "ரத்துசெய்",
"account_upgrade_dialog_billing_contact_website": "பட்டியலிடல் கேள்விகளுக்கு, தயவுசெய்து எங்கள் <இணைப்பு> வலைத்தளம் </இணைப்பு> ஐப் பார்க்கவும்.",
"account_upgrade_dialog_button_redirect_signup": "இப்போது பதிவுபெறுக",
"account_upgrade_dialog_button_pay_now": "இப்போது பணம் செலுத்தி குழுசேரவும்",
"account_upgrade_dialog_button_cancel_subscription": "சந்தாவை ரத்துசெய்",
"account_tokens_title": "டோக்கன்களை அணுகவும்",
"account_tokens_description": "NTFY பநிஇ வழியாக வெளியிடும் மற்றும் சந்தா செலுத்தும் போது அணுகல் டோக்கன்களைப் பயன்படுத்தவும், எனவே உங்கள் கணக்கு நற்சான்றிதழ்களை அனுப்ப வேண்டியதில்லை. மேலும் அறிய <இணைப்பு> ஆவணங்கள் </இணைப்பு> ஐப் பாருங்கள்.",
"account_upgrade_dialog_button_update_subscription": "சந்தாவைப் புதுப்பிக்கவும்",
"account_tokens_table_token_header": "கிள்ளாக்கு",
"account_tokens_table_label_header": "சிட்டை",
"account_tokens_table_last_access_header": "கடைசி அணுகல்",
"account_tokens_table_expires_header": "காலாவதியாகிறது",
"account_tokens_table_never_expires": "ஒருபோதும் காலாவதியாகாது",
"account_tokens_table_cannot_delete_or_edit": "தற்போதைய அமர்வு டோக்கனைத் திருத்தவோ நீக்கவோ முடியாது",
"account_tokens_table_current_session": "தற்போதைய உலாவி அமர்வு",
"account_tokens_table_copied_to_clipboard": "அணுகல் கிள்ளாக்கு நகலெடுக்கப்பட்டது",
"account_tokens_table_create_token_button": "அணுகல் கிள்ளாக்கை உருவாக்கவும்",
"account_tokens_dialog_title_create": "அணுகல் கிள்ளாக்கை உருவாக்கவும்",
"account_tokens_table_last_origin_tooltip": "ஐபி முகவரி {{ip} இருந்து இலிருந்து, தேடலைக் சொடுக்கு செய்க",
"account_tokens_dialog_title_edit": "அணுகல் டோக்கனைத் திருத்தவும்",
"account_tokens_dialog_title_delete": "அணுகல் கிள்ளாக்கை நீக்கு",
"account_tokens_dialog_label": "சிட்டை, எ.கா. ராடார் அறிவிப்புகள்",
"account_tokens_dialog_button_create": "கிள்ளாக்கை உருவாக்கவும்",
"account_tokens_dialog_button_update": "கிள்ளாக்கைப் புதுப்பிக்கவும்",
"account_tokens_dialog_button_cancel": "ரத்துசெய்",
"account_tokens_dialog_expires_label": "அணுகல் கிள்ளாக்கு காலாவதியாகிறது",
"account_tokens_dialog_expires_unchanged": "காலாவதி தேதி மாறாமல் விடுங்கள்",
"account_tokens_dialog_expires_x_hours": "கிள்ளாக்கு {{hours}} மணிநேரங்களில் காலாவதியாகிறது",
"account_tokens_dialog_expires_x_days": "கிள்ளாக்கு {{days}} நாட்களில் காலாவதியாகிறது",
"account_tokens_dialog_expires_never": "கிள்ளாக்கு ஒருபோதும் காலாவதியாகாது",
"account_tokens_delete_dialog_title": "அணுகல் கிள்ளாக்கை நீக்கு",
"account_tokens_delete_dialog_description": "அணுகல் கிள்ளாக்கை நீக்குவதற்கு முன், பயன்பாடுகள் அல்லது ச்கிரிப்ட்கள் எதுவும் தீவிரமாகப் பயன்படுத்தவில்லை என்பதை உறுதிப்படுத்திக் கொள்ளுங்கள். <strong> இந்த செயலை செயல்தவிர்க்க முடியாது </strong>.",
"account_tokens_delete_dialog_submit_button": "கிள்ளாக்கை நிரந்தரமாக நீக்கு",
"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_any": "முன்னுரிமையைப் பொருட்படுத்தாமல் அனைத்து அறிவிப்புகளையும் காட்டுகிறது",
"prefs_notifications_min_priority_description_x_or_higher": "முன்னுரிமை {{number}} ({{name}}) அல்லது அதற்கு மேல் இருந்தால் அறிவிப்புகளைக் காட்டு",
"prefs_notifications_min_priority_description_max": "முன்னுரிமை 5 (அதிகபட்சம்) என்றால் அறிவிப்புகளைக் காட்டு",
"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_title": "அறிவிப்புகளை நீக்கு",
"prefs_notifications_delete_after_never": "ஒருபோதும்",
"prefs_notifications_delete_after_three_hours": "மூன்று மணி நேரம் கழித்து",
"prefs_notifications_delete_after_one_day": "ஒரு நாள் கழித்து",
"prefs_notifications_delete_after_one_week": "ஒரு வாரம் கழித்து",
"prefs_notifications_delete_after_one_month": "ஒரு மாதத்திற்குப் பிறகு",
"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_notifications_web_push_title": "பின்னணி அறிவிப்புகள்",
"prefs_notifications_web_push_enabled_description": "வலை பயன்பாடு இயங்காதபோது கூட அறிவிப்புகள் பெறப்படுகின்றன (வலை புச் வழியாக)",
"prefs_notifications_web_push_disabled_description": "வலை பயன்பாடு இயங்கும்போது அறிவிப்பு பெறப்படுகிறது (வெப்சாக்கெட் வழியாக)",
"prefs_notifications_web_push_enabled": "{{server} க்கு க்கு இயக்கப்பட்டது",
"prefs_notifications_web_push_disabled": "முடக்கப்பட்டது",
"prefs_users_title": "பயனர்களை நிர்வகிக்கவும்",
"prefs_users_description": "உங்கள் பாதுகாக்கப்பட்ட தலைப்புகளுக்கு பயனர்களை இங்கே சேர்க்கவும்/அகற்றவும். பயனர்பெயர் மற்றும் கடவுச்சொல் உலாவியின் உள்ளக சேமிப்பகத்தில் சேமிக்கப்பட்டுள்ளன என்பதை நினைவில் கொள்க.",
"prefs_users_description_no_sync": "பயனர்கள் மற்றும் கடவுச்சொற்கள் உங்கள் கணக்கில் ஒத்திசைக்கப்படவில்லை.",
"prefs_users_table": "பயனர்கள் அட்டவணை",
"prefs_users_edit_button": "பயனரைத் திருத்து",
"prefs_users_delete_button": "பயனரை நீக்கு",
"prefs_users_table_cannot_delete_or_edit": "உள்நுழைந்த பயனரை நீக்கவோ திருத்தவோ முடியாது",
"prefs_users_table_user_header": "பயனர்",
"prefs_users_table_base_url_header": "பணி முகவரி",
"prefs_users_dialog_title_add": "பயனரைச் சேர்க்கவும்",
"prefs_users_dialog_title_edit": "பயனரைத் திருத்து",
"prefs_users_dialog_base_url_label": "பணி முகவரி, எ.கா. https://ntfy.sh",
"prefs_users_dialog_username_label": "பயனர்பெயர், எ.கா. பில்",
"prefs_users_dialog_password_label": "கடவுச்சொல்",
"prefs_appearance_title": "தோற்றம்",
"prefs_appearance_language_title": "மொழி",
"prefs_appearance_theme_title": "கருப்பொருள்",
"prefs_appearance_theme_system": "கணினி (இயல்புநிலை)",
"prefs_appearance_theme_dark": "இருண்ட முறை",
"prefs_appearance_theme_light": "ஒளி பயன்முறை",
"prefs_reservations_title": "ஒதுக்கப்பட்ட தலைப்புகள்",
"prefs_reservations_description": "தனிப்பட்ட பயன்பாட்டிற்காக தலைப்பு பெயர்களை இங்கே முன்பதிவு செய்யலாம். ஒரு தலைப்பை முன்பதிவு செய்வது தலைப்பின் மீது உங்களுக்கு உரிமையை அளிக்கிறது, மேலும் தலைப்பில் பிற பயனர்களுக்கான அணுகல் அனுமதிகளை வரையறுக்க உங்களை அனுமதிக்கிறது.",
"prefs_reservations_limit_reached": "உங்கள் ஒதுக்கப்பட்ட தலைப்புகளின் வரம்பை நீங்கள் அடைந்தீர்கள்.",
"prefs_reservations_add_button": "ஒதுக்கப்பட்ட தலைப்பைச் சேர்க்கவும்",
"prefs_reservations_edit_button": "தலைப்பு அணுகலைத் திருத்தவும்",
"prefs_reservations_delete_button": "தலைப்பு அணுகலை மீட்டமைக்கவும்",
"prefs_reservations_table": "ஒதுக்கப்பட்ட தலைப்புகள் அட்டவணை",
"prefs_reservations_table_topic_header": "தலைப்பு",
"prefs_reservations_table_access_header": "அணுகல்",
"prefs_reservations_table_everyone_deny_all": "நான் மட்டுமே வெளியிட்டு குழுசேர முடியும்",
"prefs_reservations_table_everyone_read_only": "நான் வெளியிட்டு குழுசேரலாம், அனைவரும் குழுசேரலாம்",
"prefs_reservations_table_everyone_write_only": "நான் வெளியிட்டு குழுசேரலாம், எல்லோரும் வெளியிடலாம்",
"prefs_reservations_table_everyone_read_write": "எல்லோரும் வெளியிட்டு குழுசேரலாம்",
"prefs_reservations_table_not_subscribed": "குழுசேரவில்லை",
"prefs_reservations_table_click_to_subscribe": "குழுசேர சொடுக்கு செய்க",
"prefs_reservations_dialog_title_add": "இருப்பு தலைப்பு",
"prefs_reservations_dialog_title_edit": "ஒதுக்கப்பட்ட தலைப்பைத் திருத்து",
"prefs_reservations_dialog_title_delete": "தலைப்பு முன்பதிவை நீக்கு",
"prefs_reservations_dialog_description": "ஒரு தலைப்பை முன்பதிவு செய்வது தலைப்பின் மீது உங்களுக்கு உரிமையை அளிக்கிறது, மேலும் தலைப்பில் பிற பயனர்களுக்கான அணுகல் அனுமதிகளை வரையறுக்க உங்களை அனுமதிக்கிறது.",
"prefs_reservations_dialog_topic_label": "தலைப்பு",
"prefs_reservations_dialog_access_label": "அணுகல்",
"reservation_delete_dialog_description": "முன்பதிவை அகற்றுவது தலைப்பின் மீது உரிமையை அளிக்கிறது, மேலும் மற்றவர்கள் அதை முன்பதிவு செய்ய அனுமதிக்கிறது. ஏற்கனவே உள்ள செய்திகளையும் இணைப்புகளையும் வைத்திருக்கலாம் அல்லது நீக்கலாம்.",
"reservation_delete_dialog_action_keep_title": "தற்காலிக சேமிப்பு செய்திகள் மற்றும் இணைப்புகளை வைத்திருங்கள்",
"reservation_delete_dialog_action_keep_description": "சேவையகத்தில் தற்காலிக சேமிப்பில் உள்ள செய்திகள் மற்றும் இணைப்புகள் தலைப்புப் பெயரைப் பற்றிய அறிவுள்ளவர்களுக்கு பகிரங்கமாகத் தெரியும்.",
"reservation_delete_dialog_action_delete_title": "தற்காலிக சேமிப்பு செய்திகள் மற்றும் இணைப்புகளை நீக்கவும்",
"reservation_delete_dialog_submit_button": "முன்பதிவை நீக்கு",
"reservation_delete_dialog_action_delete_description": "தற்காலிக சேமிக்கப்பட்ட செய்திகள் மற்றும் இணைப்புகள் நிரந்தரமாக நீக்கப்படும். இந்த செயலை செயல்தவிர்க்க முடியாது.",
"priority_min": "மணித்துளி",
"priority_low": "குறைந்த",
"priority_high": "உயர்ந்த",
"priority_max": "அதிகபட்சம்",
"priority_default": "இயல்புநிலை",
"error_boundary_title": "ஓ, NTFY செயலிழந்தது",
"error_boundary_description": "இது வெளிப்படையாக நடக்கக்கூடாது. இதைப் பற்றி மிகவும் வருந்துகிறேன். .",
"error_boundary_button_copy_stack_trace": "அடுக்கு சுவடு நகலெடுக்கவும்",
"error_boundary_button_reload_ntfy": "Ntfy ஐ மீண்டும் ஏற்றவும்",
"error_boundary_stack_trace": "ச்டாக் சுவடு",
"error_boundary_gathering_info": "மேலும் தகவலை சேகரிக்கவும்…",
"error_boundary_unsupported_indexeddb_title": "தனியார் உலாவல் ஆதரிக்கப்படவில்லை",
"common_cancel": "ரத்துசெய்",
"common_save": "சேமி",
"common_add": "கூட்டு",
"common_back": "பின்",
"common_copy_to_clipboard": "இடைநிலைப்பலகைக்கு நகலெடுக்கவும்",
"signup_title": "ஒரு NTFY கணக்கை உருவாக்கவும்",
"signup_form_username": "பயனர்பெயர்",
"signup_form_password": "கடவுச்சொல்",
"signup_form_confirm_password": "கடவுச்சொல்லை உறுதிப்படுத்தவும்",
"signup_form_button_submit": "பதிவு செய்க",
"signup_form_toggle_password_visibility": "கடவுச்சொல் தெரிவுநிலையை மாற்றவும்",
"signup_already_have_account": "ஏற்கனவே ஒரு கணக்கு இருக்கிறதா? உள்நுழைக!",
"signup_disabled": "கையொப்பம் முடக்கப்பட்டுள்ளது",
"signup_error_username_taken": "பயனர்பெயர் {{username}} ஏற்கனவே எடுக்கப்பட்டுள்ளது",
"signup_error_creation_limit_reached": "கணக்கு உருவாக்கும் வரம்பு எட்டப்பட்டது",
"login_title": "உங்கள் NTFY கணக்கில் உள்நுழைக",
"login_form_button_submit": "விடுபதிகை",
"login_link_signup": "பதிவு செய்க",
"login_disabled": "உள்நுழைவு முடக்கப்பட்டுள்ளது",
"action_bar_reservation_edit": "முன்பதிவை மாற்றவும்",
"action_bar_reservation_delete": "முன்பதிவை அகற்று",
"action_bar_reservation_limit_reached": "வரம்பு எட்டப்பட்டது",
"action_bar_send_test_notification": "சோதனை அறிவிப்பை அனுப்பவும்",
"action_bar_clear_notifications": "எல்லா அறிவிப்புகளையும் அழிக்கவும்",
"action_bar_mute_notifications": "முடக்கு அறிவிப்புகள்",
"action_bar_unmute_notifications": "ஊடுருவல் அறிவிப்புகள்",
"action_bar_unsubscribe": "குழுவிலகவும்",
"action_bar_toggle_mute": "முடக்கு/அசைவது அறிவிப்புகள்",
"action_bar_toggle_action_menu": "செயல் மெனுவைத் திறக்க/மூடு",
"action_bar_profile_title": "சுயவிவரம்",
"action_bar_profile_settings": "அமைப்புகள்",
"action_bar_profile_logout": "வெளியேற்றம்",
"action_bar_sign_in": "விடுபதிகை",
"action_bar_sign_up": "பதிவு செய்க",
"message_bar_type_message": "இங்கே ஒரு செய்தியைத் தட்டச்சு செய்க",
"message_bar_error_publishing": "பிழை வெளியீட்டு அறிவிப்பு",
"message_bar_show_dialog": "வெளியீட்டு உரையாடலைக் காட்டு",
"nav_button_subscribe": "தலைப்புக்கு குழுசேரவும்",
"nav_button_muted": "அறிவிப்புகள் முடக்கப்பட்டன",
"nav_button_connecting": "இணைத்தல்",
"nav_upgrade_banner_label": "Ntfy Pro க்கு மேம்படுத்தவும்",
"nav_upgrade_banner_description": "தலைப்புகள், கூடுதல் செய்திகள் மற்றும் மின்னஞ்சல்கள் மற்றும் பெரிய இணைப்புகளை முன்பதிவு செய்யுங்கள்",
"alert_notification_permission_required_title": "அறிவிப்புகள் முடக்கப்பட்டுள்ளன",
"alert_notification_permission_required_description": "டெச்க்டாப் அறிவிப்புகளைக் காண்பிக்க உங்கள் உலாவி இசைவு வழங்கவும்",
"alert_notification_permission_required_button": "இப்போது வழங்கவும்",
"alert_notification_permission_denied_title": "அறிவிப்புகள் தடுக்கப்பட்டுள்ளன",
"alert_notification_permission_denied_description": "தயவுசெய்து அவற்றை உங்கள் உலாவியில் மீண்டும் இயக்கவும்",
"alert_notification_ios_install_required_title": "ஐஇமு நிறுவல் தேவை",
"alert_notification_ios_install_required_description": "ஐஇமு இல் அறிவிப்புகளை இயக்க பகிர்வு ஐகானைக் சொடுக்கு செய்து முகப்புத் திரையில் சேர்க்கவும்",
"alert_not_supported_title": "அறிவிப்புகள் ஆதரிக்கப்படவில்லை",
"notifications_new_indicator": "புதிய அறிவிப்பு",
"notifications_attachment_image": "இணைப்பு படம்",
"notifications_attachment_copy_url_title": "இணைப்பு முகவரி ஐ இடைநிலைப்பலகைக்கு நகலெடுக்கவும்",
"notifications_attachment_copy_url_button": "முகவரி ஐ நகலெடுக்கவும்",
"notifications_attachment_open_title": "{{url}} க்குச் செல்லவும்",
"notifications_attachment_open_button": "திறந்த இணைப்பு",
"notifications_attachment_link_expires": "இணைப்பு காலாவதியாகிறது {{date}}",
"notifications_attachment_link_expired": "இணைப்பு காலாவதியான பதிவிறக்க",
"notifications_attachment_file_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": "இணைப்பை திற",
"notifications_actions_open_url_title": "{{url}} க்குச் செல்லவும்",
"notifications_none_for_any_title": "உங்களுக்கு எந்த அறிவிப்புகளும் கிடைக்கவில்லை.",
"notifications_none_for_any_description": "ஒரு தலைப்புக்கு அறிவிப்புகளை அனுப்ப, தலைப்பு முகவரி க்கு வைக்கவும் அல்லது இடுகையிடவும். உங்கள் தலைப்புகளில் ஒன்றைப் பயன்படுத்தி இங்கே ஒரு எடுத்துக்காட்டு.",
"notifications_no_subscriptions_title": "உங்களிடம் இன்னும் சந்தாக்கள் இல்லை என்று தெரிகிறது.",
"notifications_no_subscriptions_description": "ஒரு தலைப்பை உருவாக்க அல்லது குழுசேர \"{{linktext}}\" இணைப்பைக் சொடுக்கு செய்க. அதன்பிறகு, நீங்கள் புட் அல்லது இடுகை வழியாக செய்திகளை அனுப்பலாம், மேலும் நீங்கள் இங்கே அறிவிப்புகளைப் பெறுவீர்கள்.",
"notifications_example": "எடுத்துக்காட்டு",
"notifications_more_details": "மேலும் தகவலுக்கு, </webititeLink> வலைத்தளம் </websiteLink> அல்லது <ockslink> ஆவணங்கள் </docslink> ஐப் பாருங்கள்.",
"display_name_dialog_title": "காட்சி பெயரை மாற்றவும்",
"display_name_dialog_description": "சந்தா பட்டியலில் காட்டப்படும் தலைப்புக்கு மாற்று பெயரை அமைக்கவும். சிக்கலான பெயர்களைக் கொண்ட தலைப்புகளை மிக எளிதாக அடையாளம் காண இது உதவுகிறது.",
"display_name_dialog_placeholder": "காட்சி பெயர்",
"reserve_dialog_checkbox_label": "தலைப்பை முன்பதிவு செய்து அணுகலை உள்ளமைக்கவும்",
"publish_dialog_button_cancel_sending": "அனுப்புவதை ரத்துசெய்",
"publish_dialog_button_cancel": "ரத்துசெய்",
"publish_dialog_button_send": "அனுப்பு",
"publish_dialog_checkbox_markdown": "மார்க் பேரூர் என வடிவம்",
"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": "தலைப்புக்கு குழுசேரவும்",
"subscribe_dialog_subscribe_description": "தலைப்புகள் கடவுச்சொல் பாதுகாக்கப்பட்டதாக இருக்காது, எனவே யூகிக்க எளிதான பெயரைத் தேர்வுசெய்க. சந்தா செலுத்தியதும், நீங்கள் அறிவிப்புகளை வைக்கலாம்/இடுகையிடலாம்.",
"subscribe_dialog_subscribe_topic_placeholder": "தலைப்பு பெயர், எ.கா. phil_alerts",
"subscribe_dialog_subscribe_use_another_label": "மற்றொரு சேவையகத்தைப் பயன்படுத்தவும்",
"subscribe_dialog_subscribe_base_url_label": "பணி முகவரி",
"subscribe_dialog_login_description": "இந்த தலைப்பு கடவுச்சொல் பாதுகாக்கப்படுகிறது. குழுசேர பயனர்பெயர் மற்றும் கடவுச்சொல்லை உள்ளிடவும்.",
"subscribe_dialog_login_username_label": "பயனர்பெயர், எ.கா. பில்",
"subscribe_dialog_login_password_label": "கடவுச்சொல்",
"subscribe_dialog_login_button_login": "புகுபதிவு",
"subscribe_dialog_error_user_not_authorized": "பயனர் {{username}} அங்கீகரிக்கப்படவில்லை",
"subscribe_dialog_error_topic_already_reserved": "தலைப்பு ஏற்கனவே ஒதுக்கப்பட்டுள்ளது",
"subscribe_dialog_error_user_anonymous": "அநாமதேய",
"account_basics_title": "கணக்கு",
"account_basics_username_title": "பயனர்பெயர்",
"account_basics_username_description": "ஏய், அது நீங்கள் தான்",
"account_basics_username_admin_tooltip": "நீங்கள் நிர்வாகி",
"account_basics_password_title": "கடவுச்சொல்",
"account_basics_password_description": "உங்கள் கணக்கு கடவுச்சொல்லை மாற்றவும்",
"account_basics_password_dialog_title": "கடவுச்சொல்லை மாற்றவும்",
"account_basics_password_dialog_current_password_label": "தற்போதைய கடவுச்சொல்",
"account_basics_password_dialog_new_password_label": "புதிய கடவுச்சொல்",
"account_basics_phone_numbers_dialog_number_label": "தொலைபேசி எண்",
"account_delete_dialog_description": "இது சேவையகத்தில் சேமிக்கப்பட்டுள்ள அனைத்து தரவுகளும் உட்பட உங்கள் கணக்கை நிரந்தரமாக நீக்கும். நீக்கப்பட்ட பிறகு, உங்கள் பயனர்பெயர் 7 நாட்களுக்கு கிடைக்காது. நீங்கள் உண்மையிலேயே தொடர விரும்பினால், கீழே உள்ள பெட்டியில் உங்கள் கடவுச்சொல்லை உறுதிப்படுத்தவும்.",
"account_delete_dialog_label": "கடவுச்சொல்",
"account_delete_dialog_button_cancel": "ரத்துசெய்",
"account_delete_dialog_button_submit": "கணக்கை நிரந்தரமாக நீக்கு",
"account_delete_dialog_billing_warning": "உங்கள் கணக்கை நீக்குவது உடனடியாக உங்கள் பட்டியலிடல் சந்தாவை ரத்து செய்கிறது. உங்களுக்கு இனி பட்டியலிடல் டாச்போர்டுக்கு அணுகல் இருக்காது.",
"account_upgrade_dialog_title": "கணக்கு அடுக்கை மாற்றவும்",
"account_upgrade_dialog_interval_monthly": "மாதாந்திர",
"account_upgrade_dialog_interval_yearly": "ஆண்டுதோறும்",
"account_upgrade_dialog_interval_yearly_discount_save": "{{discount}}% சேமிக்கவும்",
"account_upgrade_dialog_interval_yearly_discount_save_up_to": "{{discount}}% வரை சேமிக்கவும்",
"account_upgrade_dialog_tier_features_reservations_one": "{{reservations}} முன்பதிவு செய்யப்பட்ட தலைப்பு",
"prefs_users_add_button": "பயனரைச் சேர்க்கவும்",
"error_boundary_unsupported_indexeddb_description": "NTFY வலை பயன்பாட்டிற்கு செயல்பட குறியீட்டு தேவை, மற்றும் உங்கள் உலாவி தனிப்பட்ட உலாவல் பயன்முறையில் IndexEDDB ஐ ஆதரிக்காது. எப்படியிருந்தாலும் தனிப்பட்ட உலாவல் பயன்முறையில் பயன்பாடு, ஏனென்றால் அனைத்தும் உலாவி சேமிப்பகத்தில் சேமிக்கப்படுகின்றன. இந்த அறிவிலிமையம் இதழில் </githublink> இல் <githublink> பற்றி நீங்கள் மேலும் படிக்கலாம் அல்லது <scordlink> டிச்கார்ட் </disordlink> அல்லது <agadgaglelink> மேட்ரிக்ச் </மேட்ரிக்ச்லிங்க்> இல் எங்களுடன் பேசலாம்.",
"web_push_subscription_expiring_title": "அறிவிப்புகள் இடைநிறுத்தப்படும்",
"web_push_subscription_expiring_body": "தொடர்ந்து அறிவிப்புகளைப் பெற NTFY ஐத் திறக்கவும்",
"web_push_unknown_notification_title": "சேவையகத்திலிருந்து அறியப்படாத அறிவிப்பு பெறப்பட்டது",
"web_push_unknown_notification_body": "வலை பயன்பாட்டைத் திறப்பதன் மூலம் நீங்கள் NTFY ஐ புதுப்பிக்க வேண்டியிருக்கலாம்"
}

View File

@@ -88,7 +88,7 @@
"action_bar_unsubscribe": "Відписатися",
"message_bar_publish": "Опублікувати повідомлення",
"nav_button_all_notifications": "Усі сповіщення",
"alert_not_supported_description": "Ваш браузер не підтримує сповіщення.",
"alert_not_supported_description": "Ваш браузер не підтримує сповіщення",
"notifications_list": "Список сповіщень",
"notifications_mark_read": "Позначити як прочитане",
"notifications_delete": "Видалити",
@@ -381,5 +381,28 @@
"reservation_delete_dialog_action_delete_title": "Видалення кешованих повідомлень і вкладень",
"reservation_delete_dialog_action_keep_title": "Збереження кешованих повідомлень і вкладень",
"reservation_delete_dialog_action_keep_description": "Повідомлення і вкладення, які кешуються на сервері, стають загальнодоступними для людей, які знають назву теми.",
"reservation_delete_dialog_action_delete_description": "Кешовані повідомлення та вкладення будуть видалені назавжди. Ця дія не може бути скасована."
"reservation_delete_dialog_action_delete_description": "Кешовані повідомлення та вкладення будуть видалені назавжди. Ця дія не може бути скасована.",
"subscribe_dialog_subscribe_use_another_background_info": "Сповіщення з інших серверів не надходитимуть, якщо вебзастосунок не відкрито",
"publish_dialog_checkbox_markdown": "Форматувати як Markdown",
"alert_notification_ios_install_required_description": "Натисніть піктограму \"Поділитися\" та \"Додати на головний екран\", щоб увімкнути сповіщення на iOS",
"prefs_appearance_theme_dark": "Темний режим",
"web_push_unknown_notification_title": "Отримано невідоме сповіщення від сервера",
"action_bar_mute_notifications": "Вимкнути сповіщення",
"action_bar_unmute_notifications": "Увімкнути сповіщення",
"alert_notification_permission_denied_title": "Сповіщення заблоковано",
"alert_notification_permission_denied_description": "Будь ласка, увімкніть їх повторно у своєму браузері",
"notifications_actions_failed_notification": "Невдала дія",
"prefs_notifications_web_push_title": "Фонові сповіщення",
"prefs_notifications_web_push_enabled_description": "Сповіщення надходитимуть навіть якщо вебзастосунок не запущений (за допомоги Web Push)",
"prefs_notifications_web_push_disabled_description": "Сповіщення надходитимуть якщо вебзастосунок запущений (за допомоги WebSocket)",
"prefs_notifications_web_push_enabled": "Увімкнено для {{server}}",
"prefs_notifications_web_push_disabled": "Вимкнено",
"prefs_appearance_theme_title": "Тема",
"prefs_appearance_theme_system": "Система (за замовчуванням)",
"prefs_appearance_theme_light": "Світлий режим",
"error_boundary_button_reload_ntfy": "Перезавантажити ntfy",
"web_push_subscription_expiring_title": "Сповіщення буде призупинено",
"web_push_subscription_expiring_body": "Відкрийте ntfy, щоб продовжити отримувати сповіщення",
"web_push_unknown_notification_body": "Можливо вам потрібно оновити ntfy шляхом відкриття вебзастосунку",
"alert_notification_ios_install_required_title": "потрібно встановити на iOS"
}

View File

@@ -9,13 +9,23 @@
"signup_already_have_account": "Đã có tài khoản? Đăng nhập!",
"signup_disabled": "Đăng kí bị đóng",
"signup_error_username_taken": "Tên {{username}} đã được sử dụng",
"signup_error_creation_limit_reached": "Đã bị giới hạn tạo tài khoản",
"signup_error_creation_limit_reached": "Đã đạt giới hạn tạo tài khoản",
"login_title": "Đăng nhập vào tài khoản ntfy",
"login_link_signup": "Đăng kí",
"login_disabled": "Đăng nhập bị đóng",
"login_disabled": "Đăng nhập bị vô hiệu hóa",
"action_bar_show_menu": "Hiện menu",
"signup_form_password": "Mật khẩu",
"action_bar_settings": "Cài đặt",
"signup_form_confirm_password": "Xác nhận mật khẩu",
"signup_form_button_submit": "Đăng kí"
"signup_form_button_submit": "Đăng kí",
"action_bar_change_display_name": "Đổi tên hiển thị",
"action_bar_send_test_notification": "Gửi thông báo thử",
"action_bar_clear_notifications": "Xóa tất cả thông báo",
"action_bar_logo_alt": "Logo ntfy",
"action_bar_account": "Tài khoản",
"action_bar_reservation_limit_reached": "Đã đạt giới hạn",
"action_bar_unsubscribe": "Hủy đăng kí",
"action_bar_unmute_notifications": "Bật thông báo",
"action_bar_toggle_mute": "Bật/tắt thông báo",
"action_bar_mute_notifications": "Tắt thông báo"
}

View File

@@ -135,7 +135,7 @@ const NavList = (props) => {
{showNotificationContextNotSupportedBox && <NotificationContextNotSupportedAlert />}
{showNotificationIOSInstallRequired && <NotificationIOSInstallRequiredAlert />}
{alertVisible && <Divider />}
{!showSubscriptionsList && (
{!showSubscriptionsList && (session.exists() || !config.require_login) && (
<ListItemButton onClick={() => navigate(routes.app)} selected={location.pathname === config.app_root}>
<ListItemIcon>
<ChatBubble />
@@ -164,30 +164,39 @@ const NavList = (props) => {
<ListItemText primary={t("nav_button_account")} />
</ListItemButton>
)}
<ListItemButton onClick={() => navigate(routes.settings)} selected={location.pathname === routes.settings}>
<ListItemIcon>
<SettingsIcon />
</ListItemIcon>
<ListItemText primary={t("nav_button_settings")} />
</ListItemButton>
{session.exists() ||
(!config.require_login && (
<ListItemButton onClick={() => navigate(routes.settings)} selected={location.pathname === routes.settings}>
<ListItemIcon>
<SettingsIcon />
</ListItemIcon>
<ListItemText primary={t("nav_button_settings")} />
</ListItemButton>
))}
<ListItemButton onClick={() => openUrl("/docs")}>
<ListItemIcon>
<ArticleIcon />
</ListItemIcon>
<ListItemText primary={t("nav_button_documentation")} />
</ListItemButton>
<ListItemButton onClick={() => props.onPublishMessageClick()}>
<ListItemIcon>
<Send />
</ListItemIcon>
<ListItemText primary={t("nav_button_publish_message")} />
</ListItemButton>
<ListItemButton onClick={() => setSubscribeDialogOpen(true)}>
<ListItemIcon>
<AddIcon />
</ListItemIcon>
<ListItemText primary={t("nav_button_subscribe")} />
</ListItemButton>
{session.exists() ||
(!config.require_login && (
<ListItemButton onClick={() => props.onPublishMessageClick()}>
<ListItemIcon>
<Send />
</ListItemIcon>
<ListItemText primary={t("nav_button_publish_message")} />
</ListItemButton>
))}
{session.exists() ||
(!config.require_login && (
<ListItemButton onClick={() => setSubscribeDialogOpen(true)}>
<ListItemIcon>
<AddIcon />
</ListItemIcon>
<ListItemText primary={t("nav_button_subscribe")} />
</ListItemButton>
))}
{showUpgradeBanner && (
// The text background gradient didn't seem to do well with switching between light/dark mode,
// So adding a `key` forces React to replace the entire component when the theme changes

View File

@@ -37,6 +37,7 @@ import priority5 from "../img/priority-5.svg";
import logoOutline from "../img/ntfy-outline.svg";
import AttachmentIcon from "./AttachmentIcon";
import { useAutoSubscribe } from "./hooks";
import session from "../app/Session";
const priorityFiles = {
1: priority1,
@@ -189,6 +190,7 @@ const MarkdownContainer = styled("div")`
}
pre {
overflow-x: scroll;
padding: 0.9rem;
}
@@ -634,12 +636,20 @@ const NoSubscriptions = () => {
<Typography variant="h5" align="center" sx={{ paddingBottom: 1 }}>
<img src={logoOutline} height="64" width="64" alt={t("action_bar_logo_alt")} />
<br />
{t("notifications_no_subscriptions_title")}
{!session.exists() && !config.require_login && t("notifications_no_subscriptions_title")}
{!session.exists() && config.require_login && t("notifications_no_subscriptions_login_title")}
</Typography>
<Paragraph>
{t("notifications_no_subscriptions_description", {
linktext: t("nav_button_subscribe"),
})}
{!session.exists() &&
!config.require_login &&
t("notifications_no_subscriptions_description", {
linktext: t("nav_button_subscribe"),
})}
{!session.exists() &&
config.require_login &&
t("notifications_no_subscriptions_login_description", {
linktext: t("action_bar_sign_in"),
})}
</Paragraph>
<Paragraph>
<ForMoreDetails />

View File

@@ -65,16 +65,22 @@ const maybeUpdateAccountSettings = async (payload) => {
}
};
const Preferences = () => (
<Container maxWidth="md" sx={{ marginTop: 3, marginBottom: 3 }}>
<Stack spacing={3}>
<Notifications />
<Reservations />
<Users />
<Appearance />
</Stack>
</Container>
);
const Preferences = () => {
if (!session.exists() || !config.require_login) {
window.location.href = routes.app;
return <></>;
}
return (
<Container maxWidth="md" sx={{ marginTop: 3, marginBottom: 3 }}>
<Stack spacing={3}>
<Notifications />
<Reservations />
<Users />
<Appearance />
</Stack>
</Container>
);
};
const Notifications = () => {
const { t } = useTranslation();
@@ -592,6 +598,7 @@ const Language = () => {
<MenuItem value="fi">Suomi</MenuItem>
<MenuItem value="sv">Svenska</MenuItem>
<MenuItem value="tr">Türkçe</MenuItem>
<MenuItem value="ta">தமி</MenuItem>
</Select>
</FormControl>
</Pref>