Compare commits

..

117 Commits

Author SHA1 Message Date
e2e60639d9 Remove unused language mapping from transifex.yml 2023-01-22 21:53:30 -05:00
83909c8fc9 Change use of DM to be consistent and add missing localization comments 2023-01-22 21:50:28 -05:00
William Casarin
3e4914462b EventDetailBar disappeared. Let's fix that before (8) 2023-01-22 10:37:33 -08:00
William Casarin
7a11433a98 v1.0.0-8 changelog 2023-01-22 10:28:51 -08:00
William Casarin
03e1c1903f v1.0.0-8 2023-01-22 10:27:35 -08:00
William Casarin
acdee6a326 Show Website on profiles
Changelog-Added: Show website on profiles
2023-01-22 10:25:12 -08:00
William Casarin
f5e03f145c Fix build 2023-01-22 10:10:54 -08:00
Joel Klabo
2a9ddd10c8 Choose Participants on a Thread Reply
Closes: #345
Changelog-Added: Add the ability to choose participants when replying
2023-01-22 10:10:13 -08:00
5e9580377d Fix localized string for privacy access description for photos
Closes: #359
2023-01-22 10:00:09 -08:00
William Casarin
9d2ff2fe65 Fix commas and emojis getting included in hashtags
Changelog-Fixed: Fix commands and emojis getting included in hashtags
2023-01-22 09:57:00 -08:00
Joel Klabo
13ea42a2e2 Don't Parse URL with Only Whitespace 2023-01-22 09:49:41 -08:00
William Casarin
d9e22ce7bf Add translations for de_AT, de_DE, tr_TR, fr_FR
Changelog-Added: Translations for de_AT, de_DE, tr_TR, fr_FR
2023-01-22 09:43:51 -08:00
William Casarin
2335a65b78 Merge remote-tracking branch 'github/translations_damus-localizations-en-us-xcloc-localized-contents-en-us-xliff--master_fr_FR' 2023-01-22 09:42:57 -08:00
William Casarin
566cd141ce Merge remote-tracking branch 'github/translations_damus-localizations-en-us-xcloc-localized-contents-en-us-xliff--master_tr_TR' 2023-01-22 09:42:06 -08:00
William Casarin
55f7f8c072 Merge remote-tracking branch 'github/translations_damus-localizations-en-us-xcloc-localized-contents-en-us-xliff--master_de_DE' 2023-01-22 09:40:46 -08:00
William Casarin
4b4addd215 Merge remote-tracking branch 'github/translations_damus-localizations-en-us-xcloc-localized-contents-en-us-xliff--master_de_AT' 2023-01-22 09:39:51 -08:00
Joel Klabo
255a0c55ba Update CI Specify Xcode Version
Closes: #360
2023-01-22 09:26:29 -08:00
Thomas Rademaker
ced6e2488f Fix duplicate post buttons when swiping tabs
Move the postButtonContainer view to avoid having 2 buttons on screen when swiping tabs

Closes: #366
Changelog-Fixed: Fix duplicate post buttons when swiping tabs
2023-01-22 09:25:16 -08:00
Thomas Rademaker
77c2abc524 fix broken test 2023-01-22 09:25:16 -08:00
Joel Klabo
e4ad15ced1 Center PFP in Zoom View
Closes: #357
Changlog-Fixed: Center profile picture in zoom view
2023-01-22 09:23:11 -08:00
Joel Klabo
904a6e960a Cleanup X Padding and Size 2023-01-22 09:23:11 -08:00
da8a82954a Import localization for fr-FR 2023-01-21 20:59:57 -05:00
383f45fe96 Import localization for de-DE 2023-01-21 20:58:21 -05:00
9dc0f3baf6 Import localization for de-AT 2023-01-21 20:57:34 -05:00
abc857582f Import localization for tr-TR 2023-01-21 20:54:33 -05:00
transifex-integration[bot]
4816b57dcd Apply translations in de_AT
translation completed for the source file '/damus Localizations/en-US.xcloc/Localized Contents/en-US.xliff'
on the 'de_AT' language.
2023-01-21 23:34:52 +00:00
transifex-integration[bot]
06b1953b49 Apply translations in de_DE
translation completed for the source file '/damus Localizations/en-US.xcloc/Localized Contents/en-US.xliff'
on the 'de_DE' language.
2023-01-21 23:33:33 +00:00
William Casarin
d658d1d987 DM Message Requests
Put strangers in a different tab

Changelog-Added: Add DM Message Requests
2023-01-21 11:13:44 -08:00
William Casarin
e14cd99c85 Add first_eref_mention helper to refactor embedded builder 2023-01-21 10:29:40 -08:00
William Casarin
0258ef792f Show embedded note references
This reverts commit 3f3b78f9bc.

Changelog-Fixed: Show embedded note references
2023-01-21 09:57:29 -08:00
transifex-integration[bot]
52524e00a2 Apply translations in tr_TR
translation completed for the source file '/damus Localizations/en-US.xcloc/Localized Contents/en-US.xliff'
on the 'tr_TR' language.
2023-01-21 09:10:29 +00:00
transifex-integration[bot]
342883fbb0 Apply translations in fr_FR
translation completed for the source file '/damus Localizations/en-US.xcloc/Localized Contents/en-US.xliff'
on the 'fr_FR' language.
2023-01-20 22:00:04 +00:00
William Casarin
1e6505abe3 v1.0.0-7 changelog 2023-01-20 09:35:51 -08:00
OlegAba
98c24147e8 Drastically improve image viewer
Changelog-Added: Drastically improved image viewer
Closes: #349
2023-01-20 09:33:22 -08:00
Joel Klabo
d1e7de5dcb Add Preview with 999 Everything 2023-01-20 09:23:42 -08:00
Joel Klabo
11e0a87f06 Fix ... when too many likes/reposts
Closes: #351
Changelog-Fixed: Fix ... when too many likes/reposts
2023-01-20 09:23:14 -08:00
Swift
1154cec719 Avoid showing reboost alert for pubkey user
Closes: #337
Changelog-Fixed: Don't show report alert if logged in as a pubkey
2023-01-19 10:10:07 -08:00
Joel Klabo
1ecfb0487e Swipe to Dismiss Images
Closes: #343
2023-01-18 13:36:57 -08:00
Swift
8ffa8446b6 Image Pinch Zooming
Changelog-Added: Added pinch to zoom on images
2023-01-18 10:10:22 -08:00
Ben Weeks
fb1f99e728 Fixed Jack's issue with homepage gap at the top.
Closes: #328
Changelog-Fixed: Fix padding issue at top of home timeline
2023-01-18 09:59:26 -08:00
Zach Hendel
031408dec3 Makes both name and @username clickable to go to profile
Changelog-Changed: Makes both name and username clickable in sidebar to go to profile
Closes: #329
2023-01-18 09:54:18 -08:00
Zach Hendel
16b6d029fa Icons for hardclick menu event view
changes copy user id icon to person
changes copy note id icon to note.text
changes copy note json icon to magnifying glass

Closes: #330
2023-01-18 09:48:58 -08:00
Brandon Holm
3e093e8572 Added Blixt Wallet TestFlight link
Closes: #331
2023-01-18 09:48:49 -08:00
John Bethancourt
fa11af4b1d Prevent absurdly large sidebar on Mac or iPad
Closes: #338
Changelog-Fixed: Fix absurdly large sidebar on Mac/iPad
2023-01-18 09:39:35 -08:00
William Casarin
91159d70ca Merge remote-tracking branch 'github/translations_damus-localizations-en-us-xcloc-localized-contents-en-us-xliff--master_es_419'
Changelog-Added: Add Latin American Spanish translations
2023-01-18 09:30:53 -08:00
OlegAba
a57d654f32 Fix tab views moving after selecting from search result
Closes: #339
Changelog-Fixed: Fix tab views moving after selecting from search result
2023-01-18 09:29:30 -08:00
OlegAba
0313480685 Change navigation title to bold
Closes: #341
2023-01-18 09:29:04 -08:00
OlegAba
e07b31e0a1 Consistent follow/unfollow button width
Changelog-Fixed: Make follow/unfollow button a consistent width
2023-01-18 09:28:29 -08:00
538a0ae5ea Import es-419 localization files 2023-01-16 20:44:41 -05:00
0f82db2440 Delete unused exported es-419 localization 2023-01-16 20:44:11 -05:00
transifex-integration[bot]
92ae2c7754 Apply translations in es_419
translation completed for the source file '/damus Localizations/en-US.xcloc/Localized Contents/en-US.xliff'
on the 'es_419' language.
2023-01-16 16:59:06 +00:00
William Casarin
00c819140b Don't include garbage in notifications
Check to make sure we have our pubkey on events coming into
notifications. Sometimes relays are buggy.

Changelog-Fixed: Don't add events to notifications from buggy relays
2023-01-15 12:53:37 -08:00
OlegAba
cbc3c46c9d Fix image crash and support SVG profile pictures
Closes: #310
Changelog-Fixed: Fixed some crashes with large images
Changelog-Added: Added SVG profile picture support
2023-01-14 19:30:54 -08:00
radixrat
4b5c34b4e2 Click pfp in side menu should open profile as well
Closes: #323
Changelog-Changed: Clicking pfp in sidebar opens profile as well
2023-01-14 18:49:36 -08:00
ericholguin
9eb39f7e0a Don't blur images if your friend boosted it
Closes: #322
Changelog-Changed: Don't blur images if your friend boosted it
2023-01-14 18:47:45 -08:00
173b22b772 localization: fix string bugs
Closes: #321
2023-01-14 18:44:11 -08:00
William Casarin
18c7cba53c Minor refactor 2023-01-14 17:24:03 -08:00
William Casarin
9a40fd595d Add some DM sorting tests
They didn't help me fix the problem, but maybe they are still useful
somehow
2023-01-14 17:23:35 -08:00
William Casarin
a71c35a6b0 Fix DM sorting bug
Changelog-Fixed: Fix DM sorting on incoming messages
2023-01-14 17:21:44 -08:00
William Casarin
d69d3cc74e create_dm: allow created_at argument
This is mainly used by tests
2023-01-14 16:28:18 -08:00
William Casarin
6e220ac4c1 dms: extract incoming DM handling into pure functions
So that it will be easier to test
2023-01-14 09:48:31 -08:00
William Casarin
bcb40a6ec7 Fix text getting truncated next to link previews
Changelog-Fixed: Fix text getting truncated next to link previews
2023-01-14 09:30:22 -08:00
Swift
2f1063b49f Resolve issue on Roboash not appearing in sidebar pfp
Also works fine with users with profile pic url set

Closes: #315
2023-01-14 09:00:59 -08:00
Swift
73110952e5 Avoid showing wrong alert message for pubkey login user. 2023-01-14 08:58:27 -08:00
d01e7c0595 Fix localization bugs and export localizations
Changelog-Fixes: Fix some localization bugs
Closes: #320
2023-01-14 08:54:43 -08:00
William Casarin
4f9cef541b v1.0.0-6 changelog 2023-01-13 11:30:20 -08:00
William Casarin
2dfbc4b57e v1.0.0-6 2023-01-13 11:29:06 -08:00
William Casarin
b9750dab77 Rename Boost to Repost 2023-01-13 10:18:03 -08:00
William Casarin
d59331bc3c Only show EventDetailBar if we have tips/likes/reposts 2023-01-13 10:17:12 -08:00
Jason Jōb
abd5856f21 Fix bug where all banner images showed as the current users
Fixes: #313
2023-01-13 09:20:51 -08:00
Joel Klabo
42a475bd72 Purple Shaka icon
Changelog-Changed: Make Shaka button purple when liked
2023-01-12 11:50:57 -08:00
William Casarin
65c1325935 Add some eventbar previews 2023-01-12 11:48:06 -08:00
Joel Klabo
db64a73f87 Shaka button improvements
Changelog-Changed: Move counts to right side like Birdsite
Changelog-Changed: Use custom icon for shaka button
Changelog-Fixed: Fixed shaka moving when you press it
2023-01-12 11:40:54 -08:00
Jason Jōb
9d44ed0bfe Profile Banner Images
Changelog-Added: Profile banner images
Closes: #302
2023-01-12 10:51:32 -08:00
William Casarin
33383265c8 Fix horrible reactions view bug 2023-01-12 09:40:28 -08:00
Joel Klabo
76d1ee34d8 Add CI Testing
Closes: #304
2023-01-12 09:32:18 -08:00
punterwantsawhalepass
3b0a84bd43 Update README.md
Closes: #305
2023-01-12 09:32:18 -08:00
William Casarin
5a1daebeca Revert "feat(ci): Publish PR notifications to Nostr"
This reverts commit aee29f145a.
2023-01-12 09:32:18 -08:00
08666ff90d Fix spacing on transifex.yml
Closes: #307
2023-01-12 09:32:18 -08:00
William Casarin
b2b790a969 Reactions View
Changelog-Added: Added Reactions View
2023-01-11 16:13:09 -08:00
William Casarin
907f0d236f Rename boost to repost and removed nip05 domain from them
Changelog-Changed: Renamed boost to repost
Changelog-Changed: Removed nip05 domain from boosts/reposts
2023-01-11 14:49:14 -08:00
William Casarin
4b8f536a9b Refactor NIP05 badge into its own view
Also only make it clickable in profile view
2023-01-11 14:48:35 -08:00
Joel Klabo
c76f92c6ed Move Counts to Right Side Like Twitter 2023-01-10 19:36:25 -08:00
Joel Klabo
a165f4281c Use Overlay for Label to Prevent Views Moving 2023-01-10 18:51:22 -08:00
Joel Klabo
5dbf8029da Update Shaka Assets and Use for EventActionBar 2023-01-10 18:51:20 -08:00
Joel Klabo
b1885700ca Use A Disabled Version of Shaka to Prevent Size Change 2023-01-10 18:50:15 -08:00
Joel Klabo
9e5209b48d Hide the NIP-05 Domain in Follow List
Changelog-Chaged: Hide the NIP-05 Domain in Follow List
Closes: #267
2023-01-10 11:09:44 -08:00
William Casarin
6bcea13d5a minor cleanup from merge 2023-01-10 10:20:42 -08:00
William Casarin
9944a7bf2a sidebar: rename App settings to Settings
Also localize
2023-01-10 10:20:42 -08:00
Darrell
424fb55343 remove unused packages
Shimmer package was added to package dependencies in #56 but Shimmer.swift in Components was used instead.
2023-01-10 10:11:54 -08:00
Paul
901931443f Update README.md
The backticks around the code  markdown example need escaped with two backticks and a space.
2023-01-10 10:11:32 -08:00
Ben Weeks
40d11ad680 Add a Sidebar
Changlog-Added: Sidebar
Closes: #273
2023-01-10 10:08:20 -08:00
Jonathan Milligan
4d8088d0d0 feat: Add left handed option for post button
Added a left handed toggle in the app configuration section to turn on
left handed mode which moves the post button to the left side. Nearly
done. May just need to fix an initialization bug.

Closes: #282
Changelog-Added: Left hand option for post button
2023-01-10 09:46:23 -08:00
bndw
aee29f145a feat(ci): Publish PR notifications to Nostr
Sends pull request notifications to Nostr using Github Workflows and
noscl.

Closes: #297
2023-01-10 09:24:19 -08:00
Jonathan Milligan
c9463804a8 style: DMs only take up 80% of screen width
DMs now take up only 80% of the available screen width so it looks much
more like iMessages.

Closes: #286
Changelog-Changed: Make DMs only take up 80% of screen width
2023-01-10 09:24:19 -08:00
Ben Weeks
dccaf35410 Uses damus image on the homepage
Closes: #291
Changelog-Added: Damus icon at the top
2023-01-10 09:24:18 -08:00
Joel Klabo
45e64dc42c Hide Recommended Relays Section if Empty
Closes: #293
Changelog-Changed: Hide Recommended Relays Section if Empty
2023-01-10 09:23:44 -08:00
OlegAba
5a32a384c4 Add fallback image url to profile pic view
Closes: #294
Changlog-Changed: Add fallback image url to profile pic view
2023-01-10 09:22:57 -08:00
Fredrik Olofsson
35ae69740a Get event after Add button is pressed to make sure it is up to date.
Closes: #296
Changelog-Fixed: Fixed issue with relays not keeping in sync when adding
2023-01-10 09:22:55 -08:00
Joel Klabo
43cc3b6b6b Make NIP-05 Domain Tappable
Closes: #275
Changelog-Added: Make purple badges on profile page tappable
2023-01-10 09:22:20 -08:00
Ben Weeks
966f330317 Added hidden dummy code for future options. 2023-01-08 23:32:35 +00:00
Ben Weeks
cb54ac0494 Updated sidebar to close when tab items at the bottom are clicked too. 2023-01-08 01:00:35 +00:00
Ben Weeks
fac1911524 Undid change to navigation appearing above the tabView as unfortunately has consequence hadn't spotted of the tab view dropping from profile page and settings page. 2023-01-08 00:38:02 +00:00
Ben Weeks
38211a74f5 Moved the sidebar to above the tab buttons. 2023-01-08 00:26:35 +00:00
Ben Weeks
9b236f9583 Changed opacity to Damus on-brand grey. Removed unnecessary code. 2023-01-08 00:20:43 +00:00
Ben Weeks
4378397c4c Fixed animation. Made menu a touch smaller. 2023-01-07 23:59:35 +00:00
Ben Weeks
392281ca40 Removed unnecessary code. 2023-01-07 23:33:57 +00:00
Ben Weeks
3bf39dc04b Fixed overlay issue (now has faded background) 2023-01-07 23:29:22 +00:00
Ben Weeks
8161d55d05 Updated colors and swapped icons. 2023-01-07 22:31:45 +00:00
Ben Weeks
30151e14fa Merge branch 'damus-io:master' into sidebar 2023-01-07 22:15:11 +00:00
Ben Weeks
c6ca0a058e Updated text color to white and swapped icons round 2023-01-07 22:13:44 +00:00
Ben Weeks
d9b60c0052 Merge branch 'damus-io:master' into sidebar 2023-01-06 23:06:44 +00:00
Ben Weeks
fba29d4454 Worked on amending the colors. 2023-01-06 22:41:48 +00:00
Ben Weeks
5733f782d9 Latest updates to the sidebar. 2023-01-06 20:11:19 +00:00
Ben Weeks
aba758b143 Fixes damus-io/damus#246 ChangeLog-Changed: Resolved issue with image URLs in uppercase (@npub1jutptdc2m8kgjmudtws095qk2tcale0eemvp4j2xnjnl4nh6669slrf04x) 2023-01-06 00:39:57 +00:00
Ben Weeks
b7c7b0b3bf Merge branch 'damus-io:master' into beautify-image-zoom 2023-01-06 00:25:15 +00:00
Ben Weeks
2d10d4592b Fixes damus-io/damus#254 Changes background to Color("DamusDarkGrey") ChangeLog-Changed: Allowed pinch and zoom on profile picture (scoder1747) ChangeLog-Changed: Allowed pinch and zoom to post pictures (@npub1jutptdc2m8kgjmudtws095qk2tcale0eemvp4j2xnjnl4nh6669slrf04x) 2023-01-06 00:16:48 +00:00
103 changed files with 11911 additions and 746 deletions

31
.github/workflows/run-tests.yaml vendored Normal file
View File

@@ -0,0 +1,31 @@
name: Run Test Suite
run-name: Testing ${{ github.ref }} by @${{ github.actor }}
on:
push:
branches:
- "master"
pull_request:
branches:
- "*"
jobs:
run_tests:
runs-on: macos-12
strategy:
matrix:
include:
- xcode: "14.2"
ios: "16.2"
name: Test iOS (${{ matrix.ios }})
steps:
- name: Checkout
uses: actions/checkout@v1
- name: Select Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: ${{ matrix.xcode }}
- name: Run Tests
run: xcodebuild test -scheme damus -project damus.xcodeproj -destination 'platform=iOS Simulator,name=iPhone 14,OS=${{ matrix.ios }}' | xcpretty && exit ${PIPESTATUS[0]}

View File

@@ -1,3 +1,89 @@
## [1.0.0-8] - 2023-01-22
### Added
- Show website on profiles (William Casarin)
- Add the ability to choose participants when replying (Joel Klabo)
- Translations for de_AT, de_DE, tr_TR, fr_FR (William Casarin)
- Add DM Message Requests (William Casarin)
### Fixed
- Fix commands and emojis getting included in hashtags (William Casarin)
- Fix duplicate post buttons when swiping tabs (Thomas Rademaker)
- Show embedded note references (William Casarin)
[1.0.0-8]: https://github.com/damus-io/damus/releases/tag/v1.0.0-8
## [1.0.0-7] - 2023-01-20
### Added
- Drastically improved image viewer (OlegAba)
- Added pinch to zoom on images (Swift)
- Add Latin American Spanish translations (William Casarin)
- Added SVG profile picture support (OlegAba)
### Changed
- Makes both name and username clickable in sidebar to go to profile (Zach Hendel)
- Clicking pfp in sidebar opens profile as well (radixrat)
- Don't blur images if your friend boosted it (ericholguin)
### Fixed
- Fix ... when too many likes/reposts (Joel Klabo)
- Don't show report alert if logged in as a pubkey (Swift)
- Fix padding issue at top of home timeline (Ben Weeks)
- Fix absurdly large sidebar on Mac/iPad (John Bethancourt)
- Fix tab views moving after selecting from search result (OlegAba)
- Make follow/unfollow button a consistent width (OlegAba)
- Don't add events to notifications from buggy relays (William Casarin)
- Fixed some crashes with large images (OlegAba)
- Fix DM sorting on incoming messages (William Casarin)
- Fix text getting truncated next to link previews (William Casarin)
[1.0.0-7]: https://github.com/damus-io/damus/releases/tag/v1.0.0-7
## [1.0.0-6] - 2023-01-13
### Added
- Profile banner images (Jason Jōb)
- Added Reactions View (William Casarin)
- Left hand option for post button (Jonathan Milligan)
- Damus icon at the top (Ben Weeks)
- Make purple badges on profile page tappable (Joel Klabo)
### Changed
- Make Shaka button purple when liked (Joel Klabo)
- Move counts to right side like Birdsite (Joel Klabo)
- Use custom icon for shaka button (Joel Klabo)
- Renamed boost to repost (William Casarin)
- Removed nip05 domain from boosts/reposts (William Casarin)
- Make DMs only take up 80% of screen width (Jonathan Milligan)
- Hide Recommended Relays Section if Empty (Joel Klabo)
### Fixed
- Fixed shaka moving when you press it (Joel Klabo)
- Fixed issue with relays not keeping in sync when adding (Fredrik Olofsson)
[1.0.0-6]: https://github.com/damus-io/damus/releases/tag/v1.0.0-6
## [1.0.0-5] - 2023-01-06
### Added
@@ -352,4 +438,3 @@
[0.1.2]: https://github.com/damus-io/damus/releases/tag/v0.1.2

View File

@@ -25,7 +25,7 @@ damus implements the following [Nostr Implementation Possibilities][nips]
## Getting Started on Damus
### Damus iOS
1) Get the Damus app on Testflight: https://testflight.apple.com/join/CLwjLxWl
1) Get the Damus app on TestFlight: https://testflight.apple.com/join/CLwjLxWl
#### ⚙️ Settings (gear icon, top right)
- Relays: You can add more relays to send your notes to by tapping the "+".
@@ -57,7 +57,7 @@ damus implements the following [Nostr Implementation Possibilities][nips]
- Italics: 1 asterisk `*italic*`
- Bold: 2 asterisk `**bold**`
- Strikethrough: 1 tildes `~strikethrough~`
- Code: 1 back-tick ``code``
- Code: 1 back-tick `` `code` ``
#### 💬 Encrypted DMs (chat app, bottom navigation)
- Tap the chat icon and you'll notice there's nothing to see at first. Go to a user profile and tap the 💬 chat icon next to the follow button to begin a DM

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -16,8 +16,8 @@
<note>Bundle name</note>
</trans-unit>
<trans-unit id="NSPhotoLibraryAddUsageDescription" xml:space="preserve">
<source>"Granting Damus access to your photo library allows you to save photos.</source>
<target>"Granting Damus access to your photo library allows you to save photos.</target>
<source>Granting Damus access to your photos allows you to save images.</source>
<target>Granting Damus access to your photos allows you to save images.</target>
<note>Privacy - Photo Library Additions Usage Description</note>
</trans-unit>
</body>
@@ -35,12 +35,14 @@
<trans-unit id="%@" xml:space="preserve">
<source>%@</source>
<target>%@</target>
<note>Number of people following a user.</note>
<note>Amount of time that has passed since reply quote event occurred.
Abbreviated version of a nostr public key.</note>
</trans-unit>
<trans-unit id="%@ %@" xml:space="preserve">
<source>%@ %@</source>
<target>%@ %@</target>
<note>Sentence composed of 2 variables to describe how many profiles a user is following. In source English, the first variable is the number of profiles being followed, and the second variable is 'Following'.</note>
<note>Sentence composed of 2 variables to describe how many reposts. In source English, the first variable is the number of reposts, and the second variable is 'Repost' or 'Reposts'.
Sentence composed of 2 variables to describe how many profiles a user is following. In source English, the first variable is the number of profiles being followed, and the second variable is 'Following'.</note>
</trans-unit>
<trans-unit id="%@. Creating an account doesn't require a phone number, email or name. Get started right away with zero friction." xml:space="preserve">
<source>%@. Creating an account doesn't require a phone number, email or name. Get started right away with zero friction.</source>
@@ -60,18 +62,14 @@
<trans-unit id="%lld" xml:space="preserve">
<source>%lld</source>
<target>%lld</target>
<note>Number of profiles a user is following.</note>
<note>Number of reposts.
Number of profiles a user is following.</note>
</trans-unit>
<trans-unit id="%lld/%lld" xml:space="preserve">
<source>%lld/%lld</source>
<target>%lld/%lld</target>
<note>Fraction of how many of the user's relay servers that are operational.</note>
</trans-unit>
<trans-unit id="&amp;nbsp;" xml:space="preserve">
<source>&amp;nbsp;</source>
<target>&amp;nbsp;</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="'%@' at '%@' will be used for verification" xml:space="preserve">
<source>'%@' at '%@' will be used for verification</source>
<target>'%@' at '%@' will be used for verification</target>
@@ -87,16 +85,6 @@
<target>(Profile.displayName(profile: profile, pubkey: whos))'s Followers</target>
<note>Navigation bar title for view that shows who is following a user.</note>
</trans-unit>
<trans-unit id="(formattedSats) sat" xml:space="preserve">
<source>(formattedSats) sat</source>
<target>(formattedSats) sat</target>
<note>Amount of 1 sat.</note>
</trans-unit>
<trans-unit id="(formattedSats) sats" xml:space="preserve">
<source>(formattedSats) sats</source>
<target>(formattedSats) sats</target>
<note>Amount of sats.</note>
</trans-unit>
<trans-unit id="(who) following" xml:space="preserve">
<source>(who) following</source>
<target>(who) following</target>
@@ -143,15 +131,25 @@
<target>Add Relay</target>
<note>Label for section for adding a relay server.</note>
</trans-unit>
<trans-unit id="Add all" xml:space="preserve">
<source>Add all</source>
<target>Add all</target>
<note>Button label to re-add all original participants as profiles to reply to in a note</note>
</trans-unit>
<trans-unit id="Any" xml:space="preserve">
<source>Any</source>
<target>Any</target>
<note>Any amount of sats</note>
</trans-unit>
<trans-unit id="Are you sure you want to boost this post?" xml:space="preserve">
<source>Are you sure you want to boost this post?</source>
<target>Are you sure you want to boost this post?</target>
<note>Alert message to ask if user wants to boost a post.</note>
<trans-unit id="Are you sure you want to repost this?" xml:space="preserve">
<source>Are you sure you want to repost this?</source>
<target>Are you sure you want to repost this?</target>
<note>Alert message to ask if user wants to repost a post.</note>
</trans-unit>
<trans-unit id="Banner Image" xml:space="preserve">
<source>Banner Image</source>
<target>Banner Image</target>
<note>Label for Banner Image section of user profile form.</note>
</trans-unit>
<trans-unit id="Before we get started, you'll need to save your account info, otherwise you won't be able to login in the future if you ever uninstall Damus." xml:space="preserve">
<source>Before we get started, you'll need to save your account info, otherwise you won't be able to login in the future if you ever uninstall Damus.</source>
@@ -178,17 +176,6 @@
<target>Blue Wallet</target>
<note>Dropdown option label for Lightning wallet, Blue Wallet.</note>
</trans-unit>
<trans-unit id="Boost" xml:space="preserve">
<source>Boost</source>
<target>Boost</target>
<note>Button to confirm boosting a post.
Title of alert for confirming to boost a post.</note>
</trans-unit>
<trans-unit id="Boosted" xml:space="preserve">
<source>Boosted</source>
<target>Boosted</target>
<note>Text indicating that the post was boosted (i.e. re-shared).</note>
</trans-unit>
<trans-unit id="Breez" xml:space="preserve">
<source>Breez</source>
<target>Breez</target>
@@ -203,6 +190,7 @@
<source>Cancel</source>
<target>Cancel</target>
<note>Button to cancel out of posting a note.
Button to cancel out of reposting a post.
Button to cancel out of view adding user inputted relay.
Cancel out of logging out the user.</note>
</trans-unit>
@@ -297,10 +285,16 @@
<target>Creator(s) of Bitcoin. Absolute legend.</target>
<note>Example description about Bitcoin creator(s), Satoshi Nakamoto.</note>
</trans-unit>
<trans-unit id="DM" xml:space="preserve">
<source>DM</source>
<target>DM</target>
<note>Navigation title for DM view, which is the English abbreviation for Direct Message.</note>
<trans-unit id="DM Type" xml:space="preserve">
<source>DM Type</source>
<target>DM Type</target>
<note>DM selector for seeing either DMs or message requests, which are messages that have not been responded to yet. DM is the English abbreviation for Direct Message.</note>
</trans-unit>
<trans-unit id="DMs" xml:space="preserve">
<source>DMs</source>
<target>DMs</target>
<note>Navigation title for DMs view, where DM is the English abbreviation for Direct Message.
Navigation title for view of DMs, where DM is an English abbreviation for Direct Message.</note>
</trans-unit>
<trans-unit id="Damus" xml:space="preserve">
<source>Damus</source>
@@ -342,21 +336,16 @@
<target>Edit</target>
<note>Button to edit user's profile.</note>
</trans-unit>
<trans-unit id="Edit Profile" xml:space="preserve">
<source>Edit Profile</source>
<target>Edit Profile</target>
<note>Title of navigation view for Edit Profile.</note>
<trans-unit id="Edit participants" xml:space="preserve">
<source>Edit participants</source>
<target>Edit participants</target>
<note>Text indicating that the view is used for editing which participants are replied to in a note.</note>
</trans-unit>
<trans-unit id="Encrypted" xml:space="preserve">
<source>Encrypted</source>
<target>Encrypted</target>
<note>Heading indicating that this application keeps private messaging end-to-end encrypted.</note>
</trans-unit>
<trans-unit id="Encrypted DMs" xml:space="preserve">
<source>Encrypted DMs</source>
<target>Encrypted DMs</target>
<note>Navigation title for view of encrypted DMs, where DM is an English abbreviation for Direct Message.</note>
</trans-unit>
<trans-unit id="Enter your account key to login:" xml:space="preserve">
<source>Enter your account key to login:</source>
<target>Enter your account key to login:</target>
@@ -428,6 +417,11 @@ Part of a larger sentence to describe how many profiles a user is following.</no
<target>LNLink</target>
<note>Dropdown option label for Lightning wallet, LNLink.</note>
</trans-unit>
<trans-unit id="Left Handed" xml:space="preserve">
<source>Left Handed</source>
<target>Left Handed</target>
<note>Moves the post button to the left side of the screen</note>
</trans-unit>
<trans-unit id="Let's go!" xml:space="preserve">
<source>Let's go!</source>
<target>Let's go!</target>
@@ -451,7 +445,8 @@ Part of a larger sentence to describe how many profiles a user is following.</no
<trans-unit id="Login" xml:space="preserve">
<source>Login</source>
<target>Login</target>
<note>Button to log into account.</note>
<note>Button to log into account.
Button to log into an account.</note>
</trans-unit>
<trans-unit id="Logout" xml:space="preserve">
<source>Logout</source>
@@ -523,13 +518,13 @@ Part of a larger sentence to describe how many profiles a user is following.</no
<trans-unit id="Private Key" xml:space="preserve">
<source>Private Key</source>
<target>Private Key</target>
<note>Label to indicate that the text below is the user's private key used by only the user themself as a secret to login to access their account.</note>
</trans-unit>
<trans-unit id="PrivateKey" xml:space="preserve">
<source>PrivateKey</source>
<target>PrivateKey</target>
<note>Title of the secure field that holds the user's private key.</note>
</trans-unit>
<trans-unit id="Profile" xml:space="preserve">
<source>Profile</source>
<target>Profile</target>
<note>Sidebar menu label for Profile view.</note>
</trans-unit>
<trans-unit id="Profile Picture" xml:space="preserve">
<source>Profile Picture</source>
<target>Profile Picture</target>
@@ -555,6 +550,11 @@ Part of a larger sentence to describe how many profiles a user is following.</no
<target>Public key</target>
<note>Label indicating that the text is a user's public account key.</note>
</trans-unit>
<trans-unit id="Reactions" xml:space="preserve">
<source>Reactions</source>
<target>Reactions</target>
<note>Navigation bar title for Reactions view.</note>
</trans-unit>
<trans-unit id="Recommended Relays" xml:space="preserve">
<source>Recommended Relays</source>
<target>Recommended Relays</target>
@@ -568,8 +568,12 @@ Part of a larger sentence to describe how many profiles a user is following.</no
<trans-unit id="Relays" xml:space="preserve">
<source>Relays</source>
<target>Relays</target>
<note>Header text for relay server list for configuration.
Part of a larger sentence to describe how many relay servers a user is connected.</note>
<note>Sidebar menu label for Relay servers view</note>
</trans-unit>
<trans-unit id="Remove all" xml:space="preserve">
<source>Remove all</source>
<target>Remove all</target>
<note>Button label to remove all participants from a note reply.</note>
</trans-unit>
<trans-unit id="Reply to self" xml:space="preserve">
<source>Reply to self</source>
@@ -586,11 +590,32 @@ Part of a larger sentence to describe how many relay servers a user is connected
<target>Replying to:</target>
<note>Indicating that the user is replying to the following listed people.</note>
</trans-unit>
<trans-unit id="Repost" xml:space="preserve">
<source>Repost</source>
<target>Repost</target>
<note>Button to confirm reposting a post.
Title of alert for confirming to repost a post.</note>
</trans-unit>
<trans-unit id="Reposted" xml:space="preserve">
<source>Reposted</source>
<target>Reposted</target>
<note>Text indicating that the post was reposted (i.e. re-shared).</note>
</trans-unit>
<trans-unit id="Requests" xml:space="preserve">
<source>Requests</source>
<target>Requests</target>
<note>Picker option for DM selector for seeing only message requests (DMs that someone else sent the user which has not been responded to yet). DM is the English abbreviation for Direct Message.</note>
</trans-unit>
<trans-unit id="Reset" xml:space="preserve">
<source>Reset</source>
<target>Reset</target>
<note>Section title for resetting the user</note>
</trans-unit>
<trans-unit id="Retry" xml:space="preserve">
<source>Retry</source>
<target>Retry</target>
<note>Button to retry completing account creation after an error occurred.</note>
</trans-unit>
<trans-unit id="River" xml:space="preserve">
<source>River</source>
<target>River</target>
@@ -644,7 +669,8 @@ Part of a larger sentence to describe how many relay servers a user is connected
<trans-unit id="Settings" xml:space="preserve">
<source>Settings</source>
<target>Settings</target>
<note>Navigation title for Settings view.</note>
<note>Navigation title for Settings view.
Sidebar menu label for accessing the app settings</note>
</trans-unit>
<trans-unit id="Share" xml:space="preserve">
<source>Share</source>
@@ -661,6 +687,11 @@ Part of a larger sentence to describe how many relay servers a user is connected
<target>Show wallet selector</target>
<note>Toggle to show or hide selection of wallet.</note>
</trans-unit>
<trans-unit id="Sign out" xml:space="preserve">
<source>Sign out</source>
<target>Sign out</target>
<note>Sidebar menu label to sign out of the account.</note>
</trans-unit>
<trans-unit id="Strike" xml:space="preserve">
<source>Strike</source>
<target>Strike</target>
@@ -723,6 +754,11 @@ Part of a larger sentence to describe how many relay servers a user is connected
<note>Label for Username section of user profile form.
Label to prompt username entry.</note>
</trans-unit>
<trans-unit id="Wallet" xml:space="preserve">
<source>Wallet</source>
<target>Wallet</target>
<note>Sidebar menu label for Wallet view.</note>
</trans-unit>
<trans-unit id="Wallet Of Satoshi" xml:space="preserve">
<source>Wallet Of Satoshi</source>
<target>Wallet Of Satoshi</target>
@@ -768,6 +804,11 @@ Part of a larger sentence to describe how many relay servers a user is connected
<target>collapsed_event_view_other_notes</target>
<note>Text to indicate that the thread was collapsed and that there are other notes to view if tapped. (Key in .stringsdict)</note>
</trans-unit>
<trans-unit id="followers_count" translate="no" xml:space="preserve">
<source>followers_count</source>
<target>followers_count</target>
<note>Part of a larger sentence to describe how many people are following a user. (Key in .stringsdict)</note>
</trans-unit>
<trans-unit id="https://example.com/pic.jpg" xml:space="preserve">
<source>https://example.com/pic.jpg</source>
<target>https://example.com/pic.jpg</target>
@@ -803,6 +844,16 @@ Part of a larger sentence to describe how many relay servers a user is connected
<target>optional</target>
<note>Label indicating that a form input is optional.</note>
</trans-unit>
<trans-unit id="reactions_count" translate="no" xml:space="preserve">
<source>reactions_count</source>
<target>reactions_count</target>
<note>Part of a larger sentence to describe how many reactions there are on a post. (Key in .stringsdict)</note>
</trans-unit>
<trans-unit id="relays_count" translate="no" xml:space="preserve">
<source>relays_count</source>
<target>relays_count</target>
<note>Part of a larger sentence to describe how many relay servers a user is connected. (Key in .stringsdict)</note>
</trans-unit>
<trans-unit id="replying_to_one_and_others" translate="no" xml:space="preserve">
<source>replying_to_one_and_others</source>
<target>replying_to_one_and_others</target>
@@ -813,11 +864,31 @@ Part of a larger sentence to describe how many relay servers a user is connected
<target>replying_to_two_and_others</target>
<note>Label to indicate that the user is replying to 2 users and others. (Key in .stringsdict)</note>
</trans-unit>
<trans-unit id="reposts_count" translate="no" xml:space="preserve">
<source>reposts_count</source>
<target>reposts_count</target>
<note>Part of a larger sentence to describe how many reposts there are. (Key in .stringsdict)</note>
</trans-unit>
<trans-unit id="satoshi" xml:space="preserve">
<source>satoshi</source>
<target>satoshi</target>
<note>Example username of Bitcoin creator(s), Satoshi Nakamoto.</note>
</trans-unit>
<trans-unit id="sats_count" translate="no" xml:space="preserve">
<source>sats_count</source>
<target>sats_count</target>
<note>Amount of sats. (Key in .stringsdict)</note>
</trans-unit>
<trans-unit id="tips_count" translate="no" xml:space="preserve">
<source>tips_count</source>
<target>tips_count</target>
<note>Part of a larger sentence to describe how many tip payments there are on a post. (Key in .stringsdict)</note>
</trans-unit>
<trans-unit id="u{00A0}" xml:space="preserve">
<source>u{00A0}</source>
<target>u{00A0}</target>
<note>Non-breaking space character to fill in blank space next to event action button icons.</note>
</trans-unit>
<trans-unit id="wss://some.relay.com" xml:space="preserve">
<source>wss://some.relay.com</source>
<target>wss://some.relay.com</target>
@@ -828,11 +899,6 @@ Part of a larger sentence to describe how many relay servers a user is connected
<target>you</target>
<note>You, in this context, is the person who controls their own social network. You is used in the context of a larger sentence that welcomes the reader to the social network that they control themself.</note>
</trans-unit>
<trans-unit id="🤙" xml:space="preserve">
<source>🤙</source>
<target>🤙</target>
<note>Button with emoji to like an event.</note>
</trans-unit>
</body>
</file>
<file original="damus/en-US.lproj/Localizable.stringsdict" source-language="en-US" target-language="en-US" datatype="plaintext">
@@ -841,8 +907,8 @@ Part of a larger sentence to describe how many relay servers a user is connected
</header>
<body>
<trans-unit id="/collapsed_event_view_other_notes:dict/NOTES:dict/one:dict/:string" xml:space="preserve">
<source>1 other note</source>
<target>1 other note</target>
<source>%d other note</source>
<target>%d other note</target>
<note>Text to indicate that the thread was collapsed and that there are other notes to view if tapped.</note>
</trans-unit>
<trans-unit id="/collapsed_event_view_other_notes:dict/NOTES:dict/other:dict/:string" xml:space="preserve">
@@ -850,24 +916,64 @@ Part of a larger sentence to describe how many relay servers a user is connected
<target>%d other notes</target>
<note>Text to indicate that the thread was collapsed and that there are other notes to view if tapped.</note>
</trans-unit>
<trans-unit id="/collapsed_event_view_other_notes:dict/NOTES:dict/zero:dict/:string" xml:space="preserve">
<source>0 other notes</source>
<target>0 other notes</target>
<note>Text to indicate that the thread was collapsed and that there are other notes to view if tapped.</note>
</trans-unit>
<trans-unit id="/collapsed_event_view_other_notes:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
<source>··· %#@NOTES@ ···</source>
<target>··· %#@NOTES@ ···</target>
<note>Text to indicate that the thread was collapsed and that there are other notes to view if tapped.</note>
</trans-unit>
<trans-unit id="/followers_count:dict/FOLLOWERS:dict/one:dict/:string" xml:space="preserve">
<source>Follower</source>
<target>Follower</target>
<note>Part of a larger sentence to describe how many people are following a user.</note>
</trans-unit>
<trans-unit id="/followers_count:dict/FOLLOWERS:dict/other:dict/:string" xml:space="preserve">
<source>Followers</source>
<target>Followers</target>
<note>Part of a larger sentence to describe how many people are following a user.</note>
</trans-unit>
<trans-unit id="/followers_count:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
<source>%#@FOLLOWERS@</source>
<target>%#@FOLLOWERS@</target>
<note>Part of a larger sentence to describe how many people are following a user.</note>
</trans-unit>
<trans-unit id="/reactions_count:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
<source>%#@REACTIONS@</source>
<target>%#@REACTIONS@</target>
<note>Part of a larger sentence to describe how many reactions there are on a post.</note>
</trans-unit>
<trans-unit id="/reactions_count:dict/REACTIONS:dict/one:dict/:string" xml:space="preserve">
<source>Reaction</source>
<target>Reaction</target>
<note>Part of a larger sentence to describe how many reactions there are on a post.</note>
</trans-unit>
<trans-unit id="/reactions_count:dict/REACTIONS:dict/other:dict/:string" xml:space="preserve">
<source>Reactions</source>
<target>Reactions</target>
<note>Part of a larger sentence to describe how many reactions there are on a post.</note>
</trans-unit>
<trans-unit id="/relays_count:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
<source>%#@RELAYS@</source>
<target>%#@RELAYS@</target>
<note>Part of a larger sentence to describe how many relay servers a user is connected.</note>
</trans-unit>
<trans-unit id="/relays_count:dict/RELAYS:dict/one:dict/:string" xml:space="preserve">
<source>Relay</source>
<target>Relay</target>
<note>Part of a larger sentence to describe how many relay servers a user is connected.</note>
</trans-unit>
<trans-unit id="/relays_count:dict/RELAYS:dict/other:dict/:string" xml:space="preserve">
<source>Relays</source>
<target>Relays</target>
<note>Part of a larger sentence to describe how many relay servers a user is connected.</note>
</trans-unit>
<trans-unit id="/replying_to_one_and_others:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
<source>Replying to %@%#@OTHERS@</source>
<target>Replying to %@%#@OTHERS@</target>
<note>Label to indicate that the user is replying to 1 user and others.</note>
</trans-unit>
<trans-unit id="/replying_to_one_and_others:dict/OTHERS:dict/one:dict/:string" xml:space="preserve">
<source> &amp; 1 other</source>
<target> &amp; 1 other</target>
<source> &amp; %d other</source>
<target> &amp; %d other</target>
<note>Label to indicate that the user is replying to 1 user and others.</note>
</trans-unit>
<trans-unit id="/replying_to_one_and_others:dict/OTHERS:dict/other:dict/:string" xml:space="preserve">
@@ -886,8 +992,8 @@ Part of a larger sentence to describe how many relay servers a user is connected
<note>Label to indicate that the user is replying to 2 users and others.</note>
</trans-unit>
<trans-unit id="/replying_to_two_and_others:dict/OTHERS:dict/one:dict/:string" xml:space="preserve">
<source> &amp; 1 other</source>
<target> &amp; 1 other</target>
<source> &amp; %d other</source>
<target> &amp; %d other</target>
<note>Label to indicate that the user is replying to 2 users and others.</note>
</trans-unit>
<trans-unit id="/replying_to_two_and_others:dict/OTHERS:dict/other:dict/:string" xml:space="preserve">
@@ -900,6 +1006,51 @@ Part of a larger sentence to describe how many relay servers a user is connected
<target/>
<note>Label to indicate that the user is replying to 2 users and others.</note>
</trans-unit>
<trans-unit id="/reposts_count:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
<source>%#@REPOSTS@</source>
<target>%#@REPOSTS@</target>
<note>Part of a larger sentence to describe how many reposts there are.</note>
</trans-unit>
<trans-unit id="/reposts_count:dict/REPOSTS:dict/one:dict/:string" xml:space="preserve">
<source>Repost</source>
<target>Repost</target>
<note>Part of a larger sentence to describe how many reposts there are.</note>
</trans-unit>
<trans-unit id="/reposts_count:dict/REPOSTS:dict/other:dict/:string" xml:space="preserve">
<source>Reposts</source>
<target>Reposts</target>
<note>Part of a larger sentence to describe how many reposts there are.</note>
</trans-unit>
<trans-unit id="/sats_count:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
<source>%1$#@SATS@</source>
<target>%1$#@SATS@</target>
<note>Amount of sats.</note>
</trans-unit>
<trans-unit id="/sats_count:dict/SATS:dict/one:dict/:string" xml:space="preserve">
<source>%2$@ sat</source>
<target>%2$@ sat</target>
<note>Amount of sats.</note>
</trans-unit>
<trans-unit id="/sats_count:dict/SATS:dict/other:dict/:string" xml:space="preserve">
<source>%2$@ sats</source>
<target>%2$@ sats</target>
<note>Amount of sats.</note>
</trans-unit>
<trans-unit id="/tips_count:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
<source>%#@TIPS@</source>
<target>%#@TIPS@</target>
<note>Part of a larger sentence to describe how many tip payments there are on a post.</note>
</trans-unit>
<trans-unit id="/tips_count:dict/TIPS:dict/one:dict/:string" xml:space="preserve">
<source>Tip</source>
<target>Tip</target>
<note>Part of a larger sentence to describe how many tip payments there are on a post.</note>
</trans-unit>
<trans-unit id="/tips_count:dict/TIPS:dict/other:dict/:string" xml:space="preserve">
<source>Tips</source>
<target>Tips</target>
<note>Part of a larger sentence to describe how many tip payments there are on a post.</note>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -3,4 +3,4 @@
/* Bundle name */
"CFBundleName" = "damus";
/* Privacy - Photo Library Additions Usage Description */
"NSPhotoLibraryAddUsageDescription" = "\"Granting Damus access to your photo library allows you to save photos.";
"NSPhotoLibraryAddUsageDescription" = "Granting Damus access to your photos allows you to save images.";

View File

@@ -2,6 +2,70 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>collapsed_event_view_other_notes</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>··· %#@NOTES@ ···</string>
<key>NOTES</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%d other note</string>
<key>other</key>
<string>%d other notes</string>
</dict>
</dict>
<key>followers_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@FOLLOWERS@</string>
<key>FOLLOWERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Follower</string>
<key>other</key>
<string>Followers</string>
</dict>
</dict>
<key>reactions_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@REACTIONS@</string>
<key>REACTIONS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Reaction</string>
<key>other</key>
<string>Reactions</string>
</dict>
</dict>
<key>relays_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@RELAYS@</string>
<key>RELAYS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Relay</string>
<key>other</key>
<string>Relays</string>
</dict>
</dict>
<key>replying_to_one_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
@@ -15,7 +79,7 @@
<key>zero</key>
<string></string>
<key>one</key>
<string> &amp; 1 other</string>
<string> &amp; %d other</string>
<key>other</key>
<string> &amp; %d others</string>
</dict>
@@ -33,27 +97,57 @@
<key>zero</key>
<string></string>
<key>one</key>
<string> &amp; 1 other</string>
<string> &amp; %d other</string>
<key>other</key>
<string> &amp; %d others</string>
</dict>
</dict>
<key>collapsed_event_view_other_notes</key>
<key>reposts_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>··· %#@NOTES@ ···</string>
<key>NOTES</key>
<string>%#@REPOSTS@</string>
<key>REPOSTS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>zero</key>
<string>0 other notes</string>
<key>one</key>
<string>1 other note</string>
<string>Repost</string>
<key>other</key>
<string>%d other notes</string>
<string>Reposts</string>
</dict>
</dict>
<key>sats_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%1$#@SATS@</string>
<key>SATS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>@</string>
<key>one</key>
<string>%2$@ sat</string>
<key>other</key>
<string>%2$@ sats</string>
</dict>
</dict>
<key>tips_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@TIPS@</string>
<key>TIPS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Tip</string>
<key>other</key>
<string>Tips</string>
</dict>
</dict>
</dict>

View File

@@ -1,60 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>replying_to_one_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Replying to %@%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>zero</key>
<string></string>
<key>one</key>
<string> &amp; 1 other</string>
<key>other</key>
<string> &amp; %d others</string>
</dict>
</dict>
<key>replying_to_two_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Replying to %@, %@%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>zero</key>
<string></string>
<key>one</key>
<string> &amp; 1 other</string>
<key>other</key>
<string> &amp; %d others</string>
</dict>
</dict>
<key>collapsed_event_view_other_notes</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>··· %#@NOTES@ ···</string>
<key>NOTES</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>zero</key>
<string>0 other notes</string>
<key>one</key>
<string>1 other note</string>
<key>other</key>
<string>%d other notes</string>
</dict>
</dict>
</dict>
</plist>

View File

@@ -1,12 +0,0 @@
{
"developmentRegion" : "en-US",
"project" : "damus.xcodeproj",
"targetLocale" : "es-419",
"toolInfo" : {
"toolBuildNumber" : "14C18",
"toolID" : "com.apple.dt.xcode",
"toolName" : "Xcode",
"toolVersion" : "14.2"
},
"version" : "1.0"
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -22,6 +22,10 @@ static inline int is_whitespace(char c) {
return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r';
}
static inline int is_boundary(char c) {
return !isalnum(c);
}
static void make_cursor(struct cursor *c, const u8 *content, size_t len)
{
c->start = content;
@@ -29,18 +33,35 @@ static void make_cursor(struct cursor *c, const u8 *content, size_t len)
c->p = content;
}
static int consume_until_whitespace(struct cursor *cur, int or_end) {
static int consume_until_boundary(struct cursor *cur) {
char c;
while (cur->p < cur->end) {
c = *cur->p;
if (is_whitespace(c))
if (is_boundary(c))
return 1;
cur->p++;
}
return 1;
}
static int consume_until_whitespace(struct cursor *cur, int or_end) {
char c;
bool consumedAtLeastOne = false;
while (cur->p < cur->end) {
c = *cur->p;
if (is_whitespace(c) && consumedAtLeastOne)
return 1;
cur->p++;
consumedAtLeastOne = true;
}
return or_end;
}
@@ -145,7 +166,7 @@ static int parse_hashtag(struct cursor *cur, struct block *block) {
return 0;
}
consume_until_whitespace(cur, 1);
consume_until_boundary(cur);
block->type = BLOCK_HASHTAG;
block->block.str.start = (const char*)(start + 1);

View File

@@ -12,6 +12,8 @@
3169CAED294FCCFC00EE4006 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAEC294FCCFC00EE4006 /* Constants.swift */; };
31D2E847295218AF006D67F8 /* Shimmer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D2E846295218AF006D67F8 /* Shimmer.swift */; };
3A4325A82961E11400BFCD9D /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 3A4325AA2961E11400BFCD9D /* Localizable.stringsdict */; };
3ACB685C297633BC00C46468 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3ACB685A297633BC00C46468 /* InfoPlist.strings */; };
3ACB685F297633BC00C46468 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3ACB685D297633BC00C46468 /* Localizable.strings */; };
3ACBCB78295FE5C70037388A /* TimeAgoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ACBCB77295FE5C70037388A /* TimeAgoTests.swift */; };
4C06670128FC7C5900038D2A /* RelayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C06670028FC7C5900038D2A /* RelayView.swift */; };
4C06670428FC7EC500038D2A /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 4C06670328FC7EC500038D2A /* Kingfisher */; };
@@ -116,6 +118,14 @@
4CB55EF3295E5D59007FD187 /* RecommendedRelayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB55EF2295E5D59007FD187 /* RecommendedRelayView.swift */; };
4CB55EF5295E679D007FD187 /* UserRelaysView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB55EF4295E679D007FD187 /* UserRelaysView.swift */; };
4CB8838629656C8B00DC99E7 /* NIP05.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB8838529656C8B00DC99E7 /* NIP05.swift */; };
4CB88389296AF99A00DC99E7 /* EventDetailBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB88388296AF99A00DC99E7 /* EventDetailBar.swift */; };
4CB8838B296F6E1E00DC99E7 /* NIP05Badge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB8838A296F6E1E00DC99E7 /* NIP05Badge.swift */; };
4CB8838D296F710400DC99E7 /* Reposted.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB8838C296F710400DC99E7 /* Reposted.swift */; };
4CB8838F296F781C00DC99E7 /* ReactionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB8838E296F781C00DC99E7 /* ReactionsView.swift */; };
4CB88393296F798300DC99E7 /* ReactionsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB88392296F798300DC99E7 /* ReactionsModel.swift */; };
4CB88396296F7F8B00DC99E7 /* ReactionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB88395296F7F8B00DC99E7 /* ReactionView.swift */; };
4CB8839A297322D200DC99E7 /* DMTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB88399297322D200DC99E7 /* DMTests.swift */; };
4CBCA930297DB57F00EC6B2F /* WebsiteLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBCA92F297DB57F00EC6B2F /* WebsiteLink.swift */; };
4CD7641B28A1641400B6928F /* EndBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CD7641A28A1641400B6928F /* EndBlock.swift */; };
4CE4F8CD281352B30009DFBB /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F8CC281352B30009DFBB /* Notifications.swift */; };
4CE4F9DE2852768D00C00DD9 /* ConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F9DD2852768D00C00DD9 /* ConfigView.swift */; };
@@ -138,13 +148,21 @@
4CEE2AF9280B2EAC00AB5EEF /* PowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2AF8280B2EAC00AB5EEF /* PowView.swift */; };
4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2B01280B39E800AB5EEF /* EventActionBar.swift */; };
4FE60CDD295E1C5E00105A1F /* Wallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE60CDC295E1C5E00105A1F /* Wallet.swift */; };
6439E014296790CF0020672B /* ProfileZoomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6439E013296790CF0020672B /* ProfileZoomView.swift */; };
647D9A8D2968520300A295DE /* SideMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647D9A8C2968520300A295DE /* SideMenuView.swift */; };
64FBD06F296255C400D9D3B2 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64FBD06E296255C400D9D3B2 /* Theme.swift */; };
6C7DE41F2955169800E66263 /* Vault in Frameworks */ = {isa = PBXBuildFile; productRef = 6C7DE41E2955169800E66263 /* Vault */; };
7C45AE6D297352F90031D7BC /* SVGKit in Frameworks */ = {isa = PBXBuildFile; productRef = 7C45AE6C297352F90031D7BC /* SVGKit */; };
7C45AE6F297352F90031D7BC /* SVGKitSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 7C45AE6E297352F90031D7BC /* SVGKitSwift */; };
7C45AE71297353390031D7BC /* KFImageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C45AE70297353390031D7BC /* KFImageModel.swift */; };
9609F058296E220800069BF3 /* BannerImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9609F057296E220800069BF3 /* BannerImageView.swift */; };
BA693074295D649800ADDB87 /* UserSettingsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA693073295D649800ADDB87 /* UserSettingsStore.swift */; };
BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */; };
DD597CBD2963D85A00C64D32 /* MarkdownTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD597CBC2963D85A00C64D32 /* MarkdownTests.swift */; };
E990020F2955F837003BBC5A /* EditMetadataView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E990020E2955F837003BBC5A /* EditMetadataView.swift */; };
E9E4ED0B295867B900DD7078 /* ThreadV2View.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E4ED0A295867B900DD7078 /* ThreadV2View.swift */; };
F7F0BA25297892BD009531F3 /* SwipeToDismiss.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7F0BA24297892BD009531F3 /* SwipeToDismiss.swift */; };
F7F0BA272978E54D009531F3 /* ParicipantsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7F0BA262978E54D009531F3 /* ParicipantsView.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -170,8 +188,22 @@
3169CAEC294FCCFC00EE4006 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Constants.swift; path = damus/Util/Constants.swift; sourceTree = SOURCE_ROOT; };
31D2E846295218AF006D67F8 /* Shimmer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shimmer.swift; sourceTree = "<group>"; };
3A2B8B0A296A8982009CC16D /* en-US */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "en-US"; path = "en-US.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
3A4F3320297CCFEE004B5F72 /* fr-FR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "fr-FR"; path = "fr-FR.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
3A4F3321297CCFEE004B5F72 /* fr-FR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "fr-FR"; path = "fr-FR.lproj/Localizable.strings"; sourceTree = "<group>"; };
3A4F3322297CCFEE004B5F72 /* fr-FR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "fr-FR"; path = "fr-FR.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
3A5C4575296A879E0032D398 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "es-419"; path = "es-419.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
3A5EA10F297CCF6C00569477 /* de-AT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "de-AT"; path = "de-AT.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
3A5EA110297CCF6C00569477 /* de-AT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "de-AT"; path = "de-AT.lproj/Localizable.strings"; sourceTree = "<group>"; };
3A5EA111297CCF6C00569477 /* de-AT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "de-AT"; path = "de-AT.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
3ACB685B297633BC00C46468 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
3ACB685E297633BC00C46468 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/Localizable.strings"; sourceTree = "<group>"; };
3ACBCB77295FE5C70037388A /* TimeAgoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeAgoTests.swift; sourceTree = "<group>"; };
3AEABD20297CCFA8003F2975 /* de-DE */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "de-DE"; path = "de-DE.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
3AEABD21297CCFA8003F2975 /* de-DE */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "de-DE"; path = "de-DE.lproj/Localizable.strings"; sourceTree = "<group>"; };
3AEABD22297CCFA8003F2975 /* de-DE */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "de-DE"; path = "de-DE.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
3AEB8003297CCEA800713A25 /* tr-TR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "tr-TR"; path = "tr-TR.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
3AEB8004297CCEA800713A25 /* tr-TR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "tr-TR"; path = "tr-TR.lproj/Localizable.strings"; sourceTree = "<group>"; };
3AEB8005297CCEA900713A25 /* tr-TR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "tr-TR"; path = "tr-TR.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
4C06670028FC7C5900038D2A /* RelayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayView.swift; sourceTree = "<group>"; };
4C06670528FCB08600038D2A /* ImageCarousel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCarousel.swift; sourceTree = "<group>"; };
4C06670828FDE64700038D2A /* damus-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "damus-Bridging-Header.h"; sourceTree = "<group>"; };
@@ -307,6 +339,14 @@
4CB55EF2295E5D59007FD187 /* RecommendedRelayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecommendedRelayView.swift; sourceTree = "<group>"; };
4CB55EF4295E679D007FD187 /* UserRelaysView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserRelaysView.swift; sourceTree = "<group>"; };
4CB8838529656C8B00DC99E7 /* NIP05.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP05.swift; sourceTree = "<group>"; };
4CB88388296AF99A00DC99E7 /* EventDetailBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventDetailBar.swift; sourceTree = "<group>"; };
4CB8838A296F6E1E00DC99E7 /* NIP05Badge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP05Badge.swift; sourceTree = "<group>"; };
4CB8838C296F710400DC99E7 /* Reposted.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reposted.swift; sourceTree = "<group>"; };
4CB8838E296F781C00DC99E7 /* ReactionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionsView.swift; sourceTree = "<group>"; };
4CB88392296F798300DC99E7 /* ReactionsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionsModel.swift; sourceTree = "<group>"; };
4CB88395296F7F8B00DC99E7 /* ReactionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionView.swift; sourceTree = "<group>"; };
4CB88399297322D200DC99E7 /* DMTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DMTests.swift; sourceTree = "<group>"; };
4CBCA92F297DB57F00EC6B2F /* WebsiteLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebsiteLink.swift; sourceTree = "<group>"; };
4CD7641A28A1641400B6928F /* EndBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndBlock.swift; sourceTree = "<group>"; };
4CE4F8CC281352B30009DFBB /* Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifications.swift; sourceTree = "<group>"; };
4CE4F9DD2852768D00C00DD9 /* ConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigView.swift; sourceTree = "<group>"; };
@@ -332,12 +372,18 @@
4CEE2AF8280B2EAC00AB5EEF /* PowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PowView.swift; sourceTree = "<group>"; };
4CEE2B01280B39E800AB5EEF /* EventActionBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventActionBar.swift; sourceTree = "<group>"; };
4FE60CDC295E1C5E00105A1F /* Wallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Wallet.swift; sourceTree = "<group>"; };
6439E013296790CF0020672B /* ProfileZoomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileZoomView.swift; sourceTree = "<group>"; };
647D9A8C2968520300A295DE /* SideMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideMenuView.swift; sourceTree = "<group>"; };
64FBD06E296255C400D9D3B2 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; };
7C45AE70297353390031D7BC /* KFImageModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KFImageModel.swift; sourceTree = "<group>"; };
9609F057296E220800069BF3 /* BannerImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BannerImageView.swift; sourceTree = "<group>"; };
BA693073295D649800ADDB87 /* UserSettingsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettingsStore.swift; sourceTree = "<group>"; };
BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectWalletView.swift; sourceTree = "<group>"; };
DD597CBC2963D85A00C64D32 /* MarkdownTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkdownTests.swift; sourceTree = "<group>"; };
E990020E2955F837003BBC5A /* EditMetadataView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditMetadataView.swift; sourceTree = "<group>"; };
E9E4ED0A295867B900DD7078 /* ThreadV2View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadV2View.swift; sourceTree = "<group>"; };
F7F0BA24297892BD009531F3 /* SwipeToDismiss.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeToDismiss.swift; sourceTree = "<group>"; };
F7F0BA262978E54D009531F3 /* ParicipantsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParicipantsView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -345,6 +391,8 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
7C45AE6F297352F90031D7BC /* SVGKitSwift in Frameworks */,
7C45AE6D297352F90031D7BC /* SVGKit in Frameworks */,
4C06670428FC7EC500038D2A /* Kingfisher in Frameworks */,
6C7DE41F2955169800E66263 /* Vault in Frameworks */,
4CE6DF1227F7A2B300C66700 /* Starscream in Frameworks */,
@@ -463,6 +511,8 @@
4C99737A28C92A9200E53835 /* ChatroomMetadata.swift */,
BA693073295D649800ADDB87 /* UserSettingsStore.swift */,
4FE60CDC295E1C5E00105A1F /* Wallet.swift */,
4CB88392296F798300DC99E7 /* ReactionsModel.swift */,
7C45AE70297353390031D7BC /* KFImageModel.swift */,
);
path = Models;
sourceTree = "<group>";
@@ -470,6 +520,8 @@
4C75EFA227FA576C0006080F /* Views */ = {
isa = PBXGroup;
children = (
4CB88394296F7F8100DC99E7 /* Reactions */,
4CB88387296AF97C00DC99E7 /* ActionBar */,
4CE4F9E228528C5200C00DD9 /* AddRelayView.swift */,
4C363A8728236948006E126D /* BlocksView.swift */,
4C285C8128385570008A31F1 /* CarouselView.swift */,
@@ -482,13 +534,11 @@
4C216F33286F5ACD00040376 /* DMView.swift */,
E990020E2955F837003BBC5A /* EditMetadataView.swift */,
3169CAE4294E699400EE4006 /* Empty Views */,
4CEE2B01280B39E800AB5EEF /* EventActionBar.swift */,
4CEE2AF0280B216B00AB5EEF /* EventDetailView.swift */,
4C75EFB82804A2740006080F /* EventView.swift */,
4CEE2AF0280B216B00AB5EEF /* EventDetailView.swift */,
4C3AC79E2833115300E1F516 /* FollowButtonView.swift */,
4C3AC79C2833036D00E1F516 /* FollowingView.swift */,
4C90BD17283A9EE5008EE7EF /* LoginView.swift */,
4C3AC7A42836987600E1F516 /* MainTabView.swift */,
4C363A8928236B57006E126D /* MentionView.swift */,
4C363A8D28236FE4006E126D /* NoteContentView.swift */,
4C75EFAC28049CFB0006080F /* PostButton.swift */,
@@ -498,11 +548,13 @@
4C285C892838B985008A31F1 /* ProfilePictureSelector.swift */,
4CEE2AF2280B25C500AB5EEF /* ProfilePicView.swift */,
4C8682862814DE470026224F /* ProfileView.swift */,
4C3AC7A42836987600E1F516 /* MainTabView.swift */,
4C363A8B28236B92006E126D /* PubkeyView.swift */,
4CB55EF2295E5D59007FD187 /* RecommendedRelayView.swift */,
4C06670028FC7C5900038D2A /* RelayView.swift */,
4C0A3F94280F6C78000448DE /* ReplyQuoteView.swift */,
4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */,
F7F0BA262978E54D009531F3 /* ParicipantsView.swift */,
4C285C8D28399BFD008A31F1 /* SaveKeysView.swift */,
4C3AC7A628369BA200E1F516 /* SearchHomeView.swift */,
4C5C7E69284EDE2E00A22DF5 /* SearchResultsView.swift */,
@@ -513,6 +565,10 @@
4C0A3F96280F8E02000448DE /* ThreadView.swift */,
4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */,
4CB55EF4295E679D007FD187 /* UserRelaysView.swift */,
647D9A8C2968520300A295DE /* SideMenuView.swift */,
9609F057296E220800069BF3 /* BannerImageView.swift */,
4CB8838E296F781C00DC99E7 /* ReactionsView.swift */,
6439E013296790CF0020672B /* ProfileZoomView.swift */,
);
path = Views;
sourceTree = "<group>";
@@ -558,6 +614,23 @@
path = Util;
sourceTree = "<group>";
};
4CB88387296AF97C00DC99E7 /* ActionBar */ = {
isa = PBXGroup;
children = (
4CEE2B01280B39E800AB5EEF /* EventActionBar.swift */,
4CB88388296AF99A00DC99E7 /* EventDetailBar.swift */,
);
path = ActionBar;
sourceTree = "<group>";
};
4CB88394296F7F8100DC99E7 /* Reactions */ = {
isa = PBXGroup;
children = (
4CB88395296F7F8B00DC99E7 /* ReactionView.swift */,
);
path = Reactions;
sourceTree = "<group>";
};
4CE4F9DF285287A000C00DD9 /* Components */ = {
isa = PBXGroup;
children = (
@@ -567,6 +640,9 @@
4C06670528FCB08600038D2A /* ImageCarousel.swift */,
4C3EA67C28FFBBA200C48A62 /* InvoicesView.swift */,
4C3EA67E28FFC01D00C48A62 /* InvoiceView.swift */,
4CB8838A296F6E1E00DC99E7 /* NIP05Badge.swift */,
4CB8838C296F710400DC99E7 /* Reposted.swift */,
4CBCA92F297DB57F00EC6B2F /* WebsiteLink.swift */,
);
path = Components;
sourceTree = "<group>";
@@ -596,12 +672,15 @@
4CE6DEE527F7A08100C66700 /* damus */ = {
isa = PBXGroup;
children = (
F7F0BA23297892AE009531F3 /* Modifiers */,
4C4A3A5A288A1B2200453788 /* damus.entitlements */,
4CE4F9DF285287A000C00DD9 /* Components */,
4C7FF7D628233637009601DB /* Util */,
4C0A3F8D280F63FF000448DE /* Models */,
4C75EFAB28049CC80006080F /* Nostr */,
4C75EFA72804823E0006080F /* Info.plist */,
3ACB685D297633BC00C46468 /* Localizable.strings */,
3ACB685A297633BC00C46468 /* InfoPlist.strings */,
4C75EFA227FA576C0006080F /* Views */,
4CE6DEE627F7A08100C66700 /* damusApp.swift */,
4CE6DEE827F7A08100C66700 /* ContentView.swift */,
@@ -630,6 +709,7 @@
4CE6DEF727F7A08200C66700 /* damusTests.swift */,
4C3EA67A28FF7B3900C48A62 /* InvoiceTests.swift */,
3ACBCB77295FE5C70037388A /* TimeAgoTests.swift */,
4CB88399297322D200DC99E7 /* DMTests.swift */,
);
path = damusTests;
sourceTree = "<group>";
@@ -651,6 +731,14 @@
name = Frameworks;
sourceTree = "<group>";
};
F7F0BA23297892AE009531F3 /* Modifiers */ = {
isa = PBXGroup;
children = (
F7F0BA24297892BD009531F3 /* SwipeToDismiss.swift */,
);
path = Modifiers;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -672,6 +760,8 @@
4C649880286E0EE300EAE2B3 /* secp256k1 */,
4C06670328FC7EC500038D2A /* Kingfisher */,
6C7DE41E2955169800E66263 /* Vault */,
7C45AE6C297352F90031D7BC /* SVGKit */,
7C45AE6E297352F90031D7BC /* SVGKitSwift */,
);
productName = damus;
productReference = 4CE6DEE327F7A08100C66700 /* damus.app */;
@@ -745,14 +835,18 @@
Base,
"es-419",
"en-US",
"de-AT",
"de-DE",
"tr-TR",
"fr-FR",
);
mainGroup = 4CE6DEDA27F7A08100C66700;
packageReferences = (
4CE6DF1027F7A2B300C66700 /* XCRemoteSwiftPackageReference "Starscream" */,
4C64987F286E0EE300EAE2B3 /* XCRemoteSwiftPackageReference "secp256k1" */,
4C06670228FC7EC500038D2A /* XCRemoteSwiftPackageReference "Kingfisher" */,
3169CAE9294FCABA00EE4006 /* XCRemoteSwiftPackageReference "Shimmer" */,
6C7DE41D2955169800E66263 /* XCRemoteSwiftPackageReference "Vault" */,
7C45AE6B297352F90031D7BC /* XCRemoteSwiftPackageReference "SVGKit" */,
);
productRefGroup = 4CE6DEE427F7A08100C66700 /* Products */;
projectDirPath = "";
@@ -770,7 +864,9 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
3ACB685F297633BC00C46468 /* Localizable.strings in Resources */,
4CE6DEEE27F7A08200C66700 /* Preview Assets.xcassets in Resources */,
3ACB685C297633BC00C46468 /* InfoPlist.strings in Resources */,
4CE6DEEB27F7A08200C66700 /* Assets.xcassets in Resources */,
3A4325A82961E11400BFCD9D /* Localizable.stringsdict in Resources */,
);
@@ -810,6 +906,7 @@
4C363A8C28236B92006E126D /* PubkeyView.swift in Sources */,
4C5C7E68284ED36500A22DF5 /* SearchHomeModel.swift in Sources */,
4C75EFB728049D990006080F /* RelayPool.swift in Sources */,
4CB8838D296F710400DC99E7 /* Reposted.swift in Sources */,
4C3EA67728FF7A9800C48A62 /* talstr.c in Sources */,
4CE6DEE927F7A08100C66700 /* ContentView.swift in Sources */,
4CEE2AF5280B29E600AB5EEF /* TimeAgo.swift in Sources */,
@@ -824,12 +921,16 @@
4C363A9028247A1D006E126D /* NostrLink.swift in Sources */,
4C0A3F8C280F5FCA000448DE /* ChatroomView.swift in Sources */,
4C477C9E282C3A4800033AA3 /* TipCounter.swift in Sources */,
647D9A8D2968520300A295DE /* SideMenuView.swift in Sources */,
F7F0BA272978E54D009531F3 /* ParicipantsView.swift in Sources */,
4C0A3F91280F6528000448DE /* ChatView.swift in Sources */,
4C216F362870A9A700040376 /* InputDismissKeyboard.swift in Sources */,
4C216F382871EDE300040376 /* DirectMessageModel.swift in Sources */,
4C75EFA627FF87A20006080F /* Nostr.swift in Sources */,
4CE4F9DE2852768D00C00DD9 /* ConfigView.swift in Sources */,
4C285C8E28399BFE008A31F1 /* SaveKeysView.swift in Sources */,
F7F0BA25297892BD009531F3 /* SwipeToDismiss.swift in Sources */,
4CB8838F296F781C00DC99E7 /* ReactionsView.swift in Sources */,
4C649844285A952100EAE2B3 /* LocalUserConfig.swift in Sources */,
4C75EFB328049D640006080F /* NostrEvent.swift in Sources */,
4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */,
@@ -838,6 +939,7 @@
4C363A9A28283854006E126D /* Reply.swift in Sources */,
BA693074295D649800ADDB87 /* UserSettingsStore.swift in Sources */,
4C90BD18283A9EE5008EE7EF /* LoginView.swift in Sources */,
4CB8838B296F6E1E00DC99E7 /* NIP05Badge.swift in Sources */,
4C3EA66828FF5F9900C48A62 /* hex.c in Sources */,
E9E4ED0B295867B900DD7078 /* ThreadV2View.swift in Sources */,
4C3BEFDC281DCE6100B3DE84 /* Liked.swift in Sources */,
@@ -848,6 +950,7 @@
4C3EA67F28FFC01D00C48A62 /* InvoiceView.swift in Sources */,
4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */,
4C3BEFE0281DE1ED00B3DE84 /* DamusState.swift in Sources */,
7C45AE71297353390031D7BC /* KFImageModel.swift in Sources */,
4C0A3F8F280F640A000448DE /* ThreadModel.swift in Sources */,
4C3AC79F2833115300E1F516 /* FollowButtonView.swift in Sources */,
4C3BEFD22819DB9B00B3DE84 /* ProfileModel.swift in Sources */,
@@ -864,6 +967,7 @@
4C285C8C28398BC7008A31F1 /* Keys.swift in Sources */,
4CACA9DC280C38C000D9BBE8 /* Profiles.swift in Sources */,
4C633352283D419F00B1C9C3 /* SignalModel.swift in Sources */,
9609F058296E220800069BF3 /* BannerImageView.swift in Sources */,
4C363A94282704FA006E126D /* Post.swift in Sources */,
4C216F32286E388800040376 /* DMChatView.swift in Sources */,
4C3EA67928FF7ABF00C48A62 /* list.c in Sources */,
@@ -875,12 +979,16 @@
4C363A9C282838B9006E126D /* EventRef.swift in Sources */,
4CD7641B28A1641400B6928F /* EndBlock.swift in Sources */,
4C3EA66528FF5F6800C48A62 /* mem.c in Sources */,
4CBCA930297DB57F00EC6B2F /* WebsiteLink.swift in Sources */,
4C3EA64128FF553900C48A62 /* hash_u5.c in Sources */,
4C3EA64F28FF59F200C48A62 /* tal.c in Sources */,
4CB88393296F798300DC99E7 /* ReactionsModel.swift in Sources */,
4CB88396296F7F8B00DC99E7 /* ReactionView.swift in Sources */,
4C8682872814DE470026224F /* ProfileView.swift in Sources */,
4C5F9114283D694D0052CD1C /* FollowTarget.swift in Sources */,
4CB8838629656C8B00DC99E7 /* NIP05.swift in Sources */,
4C5C7E6A284EDE2E00A22DF5 /* SearchResultsView.swift in Sources */,
6439E014296790CF0020672B /* ProfileZoomView.swift in Sources */,
4CE6DF1627F8DEBF00C66700 /* RelayConnection.swift in Sources */,
4C3BEFD6281D995700B3DE84 /* ActionBarModel.swift in Sources */,
4C363AA428296DEE006E126D /* SearchModel.swift in Sources */,
@@ -899,6 +1007,7 @@
4C363A922825FCF2006E126D /* ProfileUpdate.swift in Sources */,
4C0A3F95280F6C78000448DE /* ReplyQuoteView.swift in Sources */,
4C3BEFDA281DCA1400B3DE84 /* LikeCounter.swift in Sources */,
4CB88389296AF99A00DC99E7 /* EventDetailBar.swift in Sources */,
4C3AC79B28306D7B00E1F516 /* Contacts.swift in Sources */,
4C3EA63D28FF52D600C48A62 /* bolt11.c in Sources */,
4CB55EF3295E5D59007FD187 /* RecommendedRelayView.swift in Sources */,
@@ -930,6 +1039,7 @@
DD597CBD2963D85A00C64D32 /* MarkdownTests.swift in Sources */,
4C3EA67B28FF7B3900C48A62 /* InvoiceTests.swift in Sources */,
4C363A9E2828A822006E126D /* ReplyTests.swift in Sources */,
4CB8839A297322D200DC99E7 /* DMTests.swift in Sources */,
4C363AA02828A8DD006E126D /* LikeTests.swift in Sources */,
4C90BD1C283AC38E008EE7EF /* Bech32Tests.swift in Sources */,
4CE6DEF827F7A08200C66700 /* damusTests.swift in Sources */,
@@ -966,10 +1076,38 @@
children = (
3A5C4575296A879E0032D398 /* es-419 */,
3A2B8B0A296A8982009CC16D /* en-US */,
3A5EA111297CCF6C00569477 /* de-AT */,
3AEABD22297CCFA8003F2975 /* de-DE */,
3AEB8005297CCEA900713A25 /* tr-TR */,
3A4F3322297CCFEE004B5F72 /* fr-FR */,
);
name = Localizable.stringsdict;
sourceTree = "<group>";
};
3ACB685A297633BC00C46468 /* InfoPlist.strings */ = {
isa = PBXVariantGroup;
children = (
3ACB685B297633BC00C46468 /* es-419 */,
3A5EA10F297CCF6C00569477 /* de-AT */,
3AEABD20297CCFA8003F2975 /* de-DE */,
3AEB8003297CCEA800713A25 /* tr-TR */,
3A4F3320297CCFEE004B5F72 /* fr-FR */,
);
name = InfoPlist.strings;
sourceTree = "<group>";
};
3ACB685D297633BC00C46468 /* Localizable.strings */ = {
isa = PBXVariantGroup;
children = (
3ACB685E297633BC00C46468 /* es-419 */,
3A5EA110297CCF6C00569477 /* de-AT */,
3AEABD21297CCFA8003F2975 /* de-DE */,
3AEB8004297CCEA800713A25 /* tr-TR */,
3A4F3321297CCFEE004B5F72 /* fr-FR */,
);
name = Localizable.strings;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
@@ -1101,7 +1239,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 5;
CURRENT_PROJECT_VERSION = 8;
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
DEVELOPMENT_TEAM = XK7H4JAB3D;
ENABLE_PREVIEWS = YES;
@@ -1109,6 +1247,7 @@
INFOPLIST_FILE = damus/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Damus;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Granting Damus access to your photos allows you to save images.";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
@@ -1141,7 +1280,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 5;
CURRENT_PROJECT_VERSION = 8;
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
DEVELOPMENT_TEAM = XK7H4JAB3D;
ENABLE_PREVIEWS = YES;
@@ -1149,6 +1288,7 @@
INFOPLIST_FILE = damus/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Damus;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Granting Damus access to your photos allows you to save images.";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
@@ -1290,14 +1430,6 @@
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
3169CAE9294FCABA00EE4006 /* XCRemoteSwiftPackageReference "Shimmer" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/joshuajhomann/Shimmer";
requirement = {
branch = master;
kind = branch;
};
};
4C06670228FC7EC500038D2A /* XCRemoteSwiftPackageReference "Kingfisher" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/onevcat/Kingfisher";
@@ -1330,6 +1462,14 @@
minimumVersion = 1.0.0;
};
};
7C45AE6B297352F90031D7BC /* XCRemoteSwiftPackageReference "SVGKit" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/SVGKit/SVGKit";
requirement = {
kind = revision;
revision = e1f13e27b1e4c0ffe20e7d8d3984bf49c2a584d0;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
@@ -1353,6 +1493,16 @@
package = 6C7DE41D2955169800E66263 /* XCRemoteSwiftPackageReference "Vault" */;
productName = Vault;
};
7C45AE6C297352F90031D7BC /* SVGKit */ = {
isa = XCSwiftPackageProductDependency;
package = 7C45AE6B297352F90031D7BC /* XCRemoteSwiftPackageReference "SVGKit" */;
productName = SVGKit;
};
7C45AE6E297352F90031D7BC /* SVGKitSwift */ = {
isa = XCSwiftPackageProductDependency;
package = 7C45AE6B297352F90031D7BC /* XCRemoteSwiftPackageReference "SVGKit" */;
productName = SVGKitSwift;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 4CE6DEDB27F7A08100C66700 /* Project object */;

View File

@@ -17,15 +17,6 @@
"revision" : "40b4b38b3b1c83f7088c76189a742870e0ca06a9"
}
},
{
"identity" : "shimmer",
"kind" : "remoteSourceControl",
"location" : "https://github.com/joshuajhomann/Shimmer",
"state" : {
"branch" : "master",
"revision" : "2fde687b3f1d9c5409c53da095d3686361e41343"
}
},
{
"identity" : "starscream",
"kind" : "remoteSourceControl",
@@ -35,6 +26,14 @@
"version" : "4.0.4"
}
},
{
"identity" : "svgkit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/SVGKit/SVGKit",
"state" : {
"revision" : "e1f13e27b1e4c0ffe20e7d8d3984bf49c2a584d0"
}
},
{
"identity" : "vault",
"kind" : "remoteSourceControl",

View File

@@ -1,17 +1,17 @@
{
"images" : [
{
"filename" : "nostr-hello-outline-black.png",
"filename" : "damus-home@1x.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "nostr-hello-outline-black@2x.png",
"filename" : "damus-home@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "nostr-hello-outline-black@3x.png",
"filename" : "damus-home@3x.png",
"idiom" : "universal",
"scale" : "3x"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "shaka-full.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}

View File

@@ -0,0 +1,88 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.000000 -0.073975 cm
1.000000 1.000000 1.000000 scn
1.295334 8.661732 m
3.613694 8.367855 l
4.475733 8.733568 5.268113 9.771931 5.474915 10.327032 c
6.083156 11.959681 5.507567 14.604573 5.474915 15.061715 c
5.448792 15.427428 6.008246 15.693006 6.291239 15.780080 c
7.571236 15.858447 8.508359 14.876789 8.642253 13.984165 c
8.740212 13.331103 8.576948 11.752880 8.381030 10.849482 c
8.979668 10.936556 10.980525 10.901726 11.868687 10.849482 c
12.756847 10.797236 13.474895 10.196423 14.193260 9.412750 c
14.767952 8.237244 13.953805 7.725680 13.474895 7.616838 c
13.834077 7.257654 l
14.781013 5.918882 13.649043 5.178749 13.115711 5.004600 c
13.474895 4.743376 l
14.487136 3.763786 13.246323 2.751544 13.017752 2.882155 c
11.058574 3.176033 l
15.499378 1.673996 l
16.054478 0.400530 15.074889 0.073999 14.781013 0.073999 c
8.576947 1.673996 l
6.291239 1.673996 5.311650 1.869914 4.299407 2.163791 c
4.157911 2.131138 3.659409 1.987464 2.797370 1.673996 c
1.935332 1.360527 1.219143 2.087601 0.968804 2.490320 c
-0.285071 4.083785 -0.467927 7.257655 1.295334 8.661732 c
h
f
n
Q
endstream
endobj
3 0 obj
1149
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 15.666626 15.710510 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000001239 00000 n
0000001262 00000 n
0000001435 00000 n
0000001509 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
1568
%%EOF

View File

@@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "shaka-line.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}

View File

@@ -0,0 +1,323 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.474731 -0.563965 cm
1.000000 1.000000 1.000000 scn
3.613694 9.332577 m
3.553993 8.861599 l
3.637261 8.851044 3.721838 8.862753 3.799107 8.895533 c
3.613694 9.332577 l
h
1.295334 9.626453 m
1.355035 10.097433 l
1.227973 10.113539 1.099794 10.077623 0.999601 9.997839 c
1.295334 9.626453 l
h
0.968804 3.455042 m
1.372000 3.705677 l
1.362764 3.720535 1.352713 3.734872 1.341894 3.748621 c
0.968804 3.455042 l
h
4.299407 3.128512 m
4.431771 3.584435 l
4.353942 3.607030 4.271623 3.609325 4.192656 3.591103 c
4.299407 3.128512 l
h
8.576947 2.638718 m
8.695503 3.098424 l
8.656776 3.108411 8.616942 3.113465 8.576947 3.113465 c
8.576947 2.638718 l
h
14.781013 1.038721 m
14.662457 0.579016 l
14.701184 0.569027 14.741018 0.563974 14.781013 0.563974 c
14.781013 1.038721 l
h
15.499378 2.638718 m
15.934578 2.828420 l
15.881091 2.951125 15.778289 3.045548 15.651489 3.088437 c
15.499378 2.638718 l
h
11.058574 4.140755 m
11.128998 4.610250 l
10.885809 4.646729 10.655017 4.491467 10.597156 4.252461 c
10.539293 4.013455 10.673516 3.769826 10.906463 3.691035 c
11.058574 4.140755 l
h
13.017752 3.846877 m
13.253292 4.259073 l
13.202273 4.288227 13.146286 4.307655 13.088176 4.316372 c
13.017752 3.846877 l
h
13.474895 5.708097 m
13.805044 6.049252 l
13.789093 6.064689 13.772079 6.078987 13.754128 6.092043 c
13.474895 5.708097 l
h
13.115711 5.969321 m
12.968349 6.420619 l
12.798800 6.365256 12.674588 6.219535 12.646772 6.043359 c
12.618958 5.867183 12.692234 5.690281 12.836478 5.585376 c
13.115711 5.969321 l
h
13.834077 8.222376 m
14.221668 8.496526 l
14.206144 8.518474 14.188784 8.539063 14.169774 8.558073 c
13.834077 8.222376 l
h
13.474895 8.581559 m
13.369680 9.044500 l
13.201114 9.006190 13.066693 8.879284 13.018762 8.713197 c
12.970830 8.547110 13.016963 8.368095 13.139197 8.245862 c
13.474895 8.581559 l
h
14.193260 10.377472 m
14.619765 10.585986 l
14.599768 10.626891 14.573989 10.664707 14.543221 10.698271 c
14.193260 10.377472 l
h
8.381030 11.814203 m
7.917068 11.914822 l
7.884080 11.762714 7.927746 11.604099 8.033934 11.490305 c
8.140121 11.376513 8.295343 11.321997 8.449365 11.344399 c
8.381030 11.814203 l
h
8.642253 14.948887 m
9.111748 15.019311 l
8.642253 14.948887 l
h
6.291239 16.744801 m
6.262227 17.218662 l
6.224693 17.216364 6.187564 17.209614 6.151623 17.198555 c
6.291239 16.744801 l
h
5.474915 16.026436 m
5.948456 16.060261 l
5.474915 16.026436 l
h
5.474915 11.291754 m
5.030037 11.457493 l
5.474915 11.291754 l
h
3.673396 9.803555 m
1.355035 10.097433 l
1.235632 9.155476 l
3.553993 8.861599 l
3.673396 9.803555 l
h
0.999601 9.997839 m
-0.029049 9.178730 -0.454726 7.875908 -0.474048 6.619066 c
-0.493367 5.362488 -0.110331 4.058727 0.595713 3.161463 c
1.341894 3.748621 l
0.794064 4.444821 0.458734 5.524729 0.475334 6.604470 c
0.491930 7.683949 0.856455 8.670100 1.591066 9.255068 c
0.999601 9.997839 l
h
0.565608 3.204407 m
0.721970 2.952868 1.013515 2.611341 1.407507 2.372385 c
1.811404 2.127421 2.357187 1.973489 2.959612 2.192553 c
2.635129 3.084882 l
2.375515 2.990478 2.132184 3.043347 1.899893 3.184233 c
1.657696 3.331126 1.465977 3.554496 1.372000 3.705677 c
0.565608 3.204407 l
h
2.959612 2.192553 m
3.816493 2.504146 4.293336 2.639887 4.406158 2.665923 c
4.192656 3.591103 l
4.022485 3.551832 3.502325 3.400227 2.635129 3.084882 c
2.959612 2.192553 l
h
4.167043 2.672591 m
5.229115 2.364247 6.254152 2.163970 8.576947 2.163970 c
8.576947 3.113465 l
6.328326 3.113465 5.394184 3.305025 4.431771 3.584435 c
4.167043 2.672591 l
h
8.458392 2.179011 m
14.662457 0.579016 l
14.899569 1.498427 l
8.695503 3.098424 l
8.458392 2.179011 l
h
14.781013 0.563974 m
15.036198 0.563974 15.495326 0.684875 15.814721 1.047266 c
16.180891 1.462728 16.264221 2.072176 15.934578 2.828420 c
15.064179 2.449016 l
15.289635 1.931793 15.160722 1.741243 15.102402 1.675073 c
15.055794 1.622190 14.990156 1.579316 14.916806 1.549556 c
14.881134 1.535082 14.847747 1.525430 14.820526 1.519657 c
14.791491 1.513498 14.777695 1.513469 14.781013 1.513469 c
14.781013 0.563974 l
h
15.651489 3.088437 m
11.210685 4.590474 l
10.906463 3.691035 l
15.347267 2.188998 l
15.651489 3.088437 l
h
10.988150 3.671260 m
12.947328 3.377382 l
13.088176 4.316372 l
11.128998 4.610250 l
10.988150 3.671260 l
h
12.782211 3.434681 m
12.991495 3.315090 13.204453 3.370091 13.288217 3.396689 c
13.400116 3.432221 13.506123 3.490767 13.598186 3.554502 c
13.783985 3.683133 13.977411 3.877748 14.120350 4.119644 c
14.264680 4.363894 14.369576 4.678114 14.335162 5.031647 c
14.300108 5.391746 14.125634 5.739002 13.805044 6.049252 c
13.144745 5.366943 l
13.330275 5.187398 13.380290 5.040778 13.390134 4.939653 c
13.400617 4.831963 13.370820 4.717613 13.302905 4.602680 c
13.233600 4.485394 13.137231 4.390213 13.057724 4.335170 c
13.017135 4.307070 12.996612 4.300308 13.000857 4.301657 c
13.003194 4.302399 13.024761 4.309311 13.061064 4.310122 c
13.095938 4.310902 13.170414 4.306433 13.253292 4.259073 c
12.782211 3.434681 l
h
13.754128 6.092043 m
13.394944 6.353267 l
12.836478 5.585376 l
13.195662 5.324152 l
13.754128 6.092043 l
h
13.263074 5.518023 m
13.593105 5.625790 14.123367 5.907292 14.433812 6.409482 c
14.595931 6.671733 14.696482 6.993351 14.669847 7.364054 c
14.643518 7.730516 14.495621 8.109214 14.221668 8.496526 c
13.446486 7.948226 l
13.646002 7.666152 13.711709 7.450294 13.722795 7.296009 c
13.733575 7.145966 13.695351 7.020646 13.626177 6.908748 c
13.474038 6.662641 13.171650 6.487002 12.968349 6.420619 c
13.263074 5.518023 l
h
14.169774 8.558073 m
13.810592 8.917255 l
13.139197 8.245862 l
13.498380 7.886679 l
14.169774 8.558073 l
h
13.580109 8.118617 m
13.896242 8.190466 14.344993 8.395787 14.624650 8.816864 c
14.929440 9.275781 14.963785 9.882310 14.619765 10.585986 c
13.766754 10.168959 l
13.997427 9.697128 13.912044 9.460121 13.833706 9.342171 c
13.730235 9.186377 13.532457 9.081495 13.369680 9.044500 c
13.580109 8.118617 l
h
14.543221 10.698271 m
13.820906 11.486253 12.989320 12.223852 11.896564 12.288132 c
11.840808 11.340275 l
12.524374 11.300065 13.128883 10.836036 13.843298 10.056674 c
14.543221 10.698271 l
h
11.896564 12.288132 m
11.441970 12.314873 10.711069 12.336796 10.019300 12.341186 c
9.341933 12.345484 8.654247 12.333687 8.312695 12.284006 c
8.449365 11.344399 l
8.706450 11.381794 9.318512 11.396118 10.013274 11.391710 c
10.693633 11.387392 11.407242 11.365778 11.840808 11.340275 c
11.896564 12.288132 l
h
8.844993 11.713585 m
8.948084 12.188952 9.040332 12.829445 9.094679 13.432834 c
9.147870 14.023395 9.169946 14.631327 9.111748 15.019311 c
8.172758 14.878462 l
8.212520 14.613384 8.201942 14.105675 8.149012 13.518009 c
8.097237 12.943172 8.009893 12.342852 7.917068 11.914822 c
8.844993 11.713585 l
h
9.111748 15.019311 m
8.944062 16.137217 7.805658 17.313158 6.262227 17.218662 c
6.320251 16.270941 l
7.336813 16.333179 8.072657 15.545805 8.172758 14.878462 c
9.111748 15.019311 l
h
6.151623 17.198555 m
5.976391 17.144638 5.715709 17.036982 5.490986 16.876261 c
5.292936 16.734617 4.969444 16.439627 5.001374 15.992612 c
5.948456 16.060261 l
5.951383 16.019283 5.934667 15.999795 5.943361 16.012491 c
5.954769 16.029152 5.984430 16.061831 6.043331 16.103956 c
6.162553 16.189222 6.323094 16.257891 6.430855 16.291048 c
6.151623 17.198555 l
h
5.001374 15.992612 m
5.011176 15.855374 5.059216 15.566318 5.104405 15.255149 c
5.152757 14.922197 5.207128 14.509316 5.241940 14.062993 c
5.312967 13.152368 5.295928 12.171200 5.030037 11.457493 c
5.919792 11.126015 l
6.262142 12.044956 6.261431 13.202559 6.188560 14.136827 c
6.151423 14.612950 6.093790 15.049047 6.044043 15.391605 c
5.991133 15.755945 5.954979 15.968927 5.948456 16.060261 c
5.001374 15.992612 l
h
5.030037 11.457493 m
4.953650 11.252455 4.742510 10.903708 4.434547 10.555828 c
4.127778 10.209298 3.769400 9.914337 3.428282 9.769621 c
3.799107 8.895533 l
4.320028 9.116529 4.788858 9.523607 5.145489 9.926461 c
5.500926 10.327968 5.789377 10.775953 5.919792 11.126015 c
5.030037 11.457493 l
h
f
n
Q
endstream
endobj
3 0 obj
7995
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 16.615845 16.660034 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000008085 00000 n
0000008108 00000 n
0000008281 00000 n
0000008355 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
8414
%%EOF

Binary file not shown.

Before

Width:  |  Height:  |  Size: 381 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 723 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -12,14 +12,14 @@ import Kingfisher
struct ShareSheet: UIViewControllerRepresentable {
typealias Callback = (_ activityType: UIActivity.ActivityType?, _ completed: Bool, _ returnedItems: [Any]?, _ error: Error?) -> Void
let activityItems: [URL]
let activityItems: [URL?]
let callback: Callback? = nil
let applicationActivities: [UIActivity]? = nil
let excludedActivityTypes: [UIActivity.ActivityType]? = nil
func makeUIViewController(context: Context) -> UIActivityViewController {
let controller = UIActivityViewController(
activityItems: activityItems,
activityItems: activityItems as [Any],
applicationActivities: applicationActivities)
controller.excludedActivityTypes = excludedActivityTypes
controller.completionWithItemsHandler = callback
@@ -32,7 +32,7 @@ struct ShareSheet: UIViewControllerRepresentable {
}
struct ImageContextMenuModifier: ViewModifier {
let url: URL
let url: URL?
let image: UIImage?
@Binding var showShareSheet: Bool
@@ -64,8 +64,21 @@ struct ImageContextMenuModifier: ViewModifier {
}
}
struct ImageViewer: View {
let urls: [URL]
private struct ImageContainerView: View {
@ObservedObject var imageModel: KFImageModel
@State private var image: UIImage?
@State private var showShareSheet = false
init(url: URL?) {
self.imageModel = KFImageModel(
url: url,
fallbackUrl: nil,
maxByteSize: 2000000, // 2 MB
downsampleSize: CGSize(width: 400, height: 400)
)
}
private struct ImageHandler: ImageModifier {
@Binding var handler: UIImage?
@@ -75,45 +88,178 @@ struct ImageViewer: View {
return image
}
}
@State private var image: UIImage?
@State private var showShareSheet = false
func onShared(completed: Bool) -> Void {
if (completed) {
showShareSheet = false
var body: some View {
KFAnimatedImage(imageModel.url)
.callbackQueue(.dispatch(.global(qos: .background)))
.processingQueue(.dispatch(.global(qos: .background)))
.cacheOriginalImage()
.configure { view in
view.framePreloadCount = 1
}
.scaleFactor(UIScreen.main.scale)
.loadDiskFileSynchronously()
.fade(duration: 0.1)
.imageModifier(ImageHandler(handler: $image))
.onFailure { _ in
imageModel.downloadFailed()
}
.id(imageModel.refreshID)
.clipped()
.modifier(ImageContextMenuModifier(url: imageModel.url, image: image, showShareSheet: $showShareSheet))
.sheet(isPresented: $showShareSheet) {
ShareSheet(activityItems: [imageModel.url])
}
// TODO: Update ImageCarousel with serializer and processor
// .serialize(by: imageModel.serializer)
// .setProcessor(imageModel.processor)
}
}
struct ZoomableScrollView<Content: View>: UIViewRepresentable {
private var content: Content
init(@ViewBuilder content: () -> Content) {
self.content = content()
}
func makeUIView(context: Context) -> UIScrollView {
let scrollView = UIScrollView()
scrollView.delegate = context.coordinator
scrollView.maximumZoomScale = 20
scrollView.minimumZoomScale = 1
scrollView.bouncesZoom = true
scrollView.showsVerticalScrollIndicator = false
scrollView.showsHorizontalScrollIndicator = false
let hostedView = context.coordinator.hostingController.view!
hostedView.translatesAutoresizingMaskIntoConstraints = true
hostedView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
hostedView.frame = scrollView.bounds
hostedView.backgroundColor = .clear
scrollView.addSubview(hostedView)
return scrollView
}
func makeCoordinator() -> Coordinator {
return Coordinator(hostingController: UIHostingController(rootView: self.content))
}
func updateUIView(_ uiView: UIScrollView, context: Context) {
context.coordinator.hostingController.rootView = self.content
assert(context.coordinator.hostingController.view.superview == uiView)
}
class Coordinator: NSObject, UIScrollViewDelegate {
var hostingController: UIHostingController<Content>
init(hostingController: UIHostingController<Content>) {
self.hostingController = hostingController
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return hostingController.view
}
}
}
struct ImageView: View {
let urls: [URL?]
@Environment(\.presentationMode) var presentationMode
@State private var selectedIndex = 0
@State var showMenu = true
var navBarView: some View {
VStack {
HStack {
Text(urls[selectedIndex]?.lastPathComponent ?? "")
.bold()
Spacer()
Button(action: {
presentationMode.wrappedValue.dismiss()
}, label: {
Image(systemName: "xmark")
})
}
.padding()
Divider()
.ignoresSafeArea()
}
.background(.regularMaterial)
}
var tabViewIndicator: some View {
HStack(spacing: 10) {
ForEach(urls.indices, id: \.self) { index in
Capsule()
.fill(index == selectedIndex ? Color(UIColor.label) : Color.secondary)
.frame(width: 7, height: 7)
}
}
.padding()
.background(.regularMaterial)
.clipShape(Capsule())
}
var body: some View {
TabView {
ForEach(urls, id: \.absoluteString) { url in
VStack{
Text(url.lastPathComponent)
KFAnimatedImage(url)
.configure { view in
view.framePreloadCount = 3
}
.cacheOriginalImage()
.imageModifier(ImageHandler(handler: $image))
.loadDiskFileSynchronously()
.scaleFactor(UIScreen.main.scale)
.fade(duration: 0.1)
.aspectRatio(contentMode: .fit)
.tabItem {
Text(url.absoluteString)
}
.id(url.absoluteString)
.modifier(ImageContextMenuModifier(url: url, image: image, showShareSheet: $showShareSheet))
.sheet(isPresented: $showShareSheet) {
ShareSheet(activityItems: [url])
}
ZStack {
Color(.systemBackground)
.ignoresSafeArea()
TabView(selection: $selectedIndex) {
ForEach(urls.indices, id: \.self) { index in
ZoomableScrollView {
ImageContainerView(url: urls[index])
.aspectRatio(contentMode: .fit)
}
.ignoresSafeArea()
.tag(index)
.modifier(SwipeToDismissModifier(minDistance: 50, onDismiss: {
presentationMode.wrappedValue.dismiss()
}))
}
}
.ignoresSafeArea()
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
.onChange(of: selectedIndex, perform: { _ in
showMenu = true
})
.onTapGesture {
showMenu.toggle()
}
.overlay(
VStack {
if showMenu {
navBarView
Spacer()
if (urls.count > 1) {
tabViewIndicator
}
}
}
.animation(.easeInOut, value: showMenu)
.padding(
.bottom,
UIApplication
.shared
.connectedScenes
.flatMap { ($0 as? UIWindowScene)?.windows ?? [] }
.first { $0.isKeyWindow }?.safeAreaInsets.bottom
)
)
}
.tabViewStyle(PageTabViewStyle())
}
}
@@ -130,13 +276,15 @@ struct ImageCarousel: View {
.foregroundColor(Color.clear)
.overlay {
KFAnimatedImage(url)
.configure { view in
view.framePreloadCount = 3
}
.callbackQueue(.dispatch(.global(qos: .background)))
.processingQueue(.dispatch(.global(qos: .background)))
.cacheOriginalImage()
.loadDiskFileSynchronously()
.scaleFactor(UIScreen.main.scale)
.fade(duration: 0.1)
.configure { view in
view.framePreloadCount = 3
}
.aspectRatio(contentMode: .fit)
.tabItem {
Text(url.absoluteString)
@@ -151,8 +299,8 @@ struct ImageCarousel: View {
}
}
.cornerRadius(10)
.sheet(isPresented: $open_sheet) {
ImageViewer(urls: urls)
.fullScreenCover(isPresented: $open_sheet) {
ImageView(urls: urls)
}
.frame(height: 200)
.onTapGesture {
@@ -164,6 +312,6 @@ struct ImageCarousel: View {
struct ImageCarousel_Previews: PreviewProvider {
static var previews: some View {
ImageCarousel(urls: [URL(string: "https://jb55.com/red-me.jpg")!])
ImageCarousel(urls: [URL(string: "https://jb55.com/red-me.jpg")!,URL(string: "https://jb55.com/red-me.jpg")!])
}
}

View File

@@ -0,0 +1,65 @@
//
// NIP05Badge.swift
// damus
//
// Created by William Casarin on 2023-01-11.
//
import SwiftUI
struct NIP05Badge: View {
let nip05: NIP05
let pubkey: String
let contacts: Contacts
let show_domain: Bool
let clickable: Bool
@Environment(\.openURL) var openURL
init (nip05: NIP05, pubkey: String, contacts: Contacts, show_domain: Bool, clickable: Bool) {
self.nip05 = nip05
self.pubkey = pubkey
self.contacts = contacts
self.show_domain = show_domain
self.clickable = clickable
}
var nip05_color: Color {
return get_nip05_color(pubkey: pubkey, contacts: contacts)
}
var body: some View {
HStack(spacing: 2) {
Image(systemName: "checkmark.seal.fill")
.font(.footnote)
.foregroundColor(nip05_color)
if show_domain {
if clickable {
Text(nip05.host)
.foregroundColor(nip05_color)
.onTapGesture {
if let nip5url = nip05.siteUrl {
openURL(nip5url)
}
}
} else {
Text(nip05.host)
.foregroundColor(nip05_color)
}
}
}
}
}
func get_nip05_color(pubkey: String, contacts: Contacts) -> Color {
return contacts.is_friend_or_self(pubkey) ? .accentColor : .gray
}
struct NIP05Badge_Previews: PreviewProvider {
static var previews: some View {
let test_state = test_damus_state()
NIP05Badge(nip05: NIP05(username: "jb55", host: "jb55.com"), pubkey: test_state.pubkey, contacts: test_state.contacts, show_domain: true, clickable: false)
}
}

View File

@@ -0,0 +1,34 @@
//
// Reposted.swift
// damus
//
// Created by William Casarin on 2023-01-11.
//
import SwiftUI
struct Reposted: View {
let damus: DamusState
let pubkey: String
let profile: Profile?
var body: some View {
HStack(alignment: .center) {
Image(systemName: "arrow.2.squarepath")
.font(.footnote)
.foregroundColor(Color.gray)
ProfileName(pubkey: pubkey, profile: profile, damus: damus, show_friend_confirmed: true, show_nip5_domain: false)
.foregroundColor(Color.gray)
Text("Reposted", comment: "Text indicating that the post was reposted (i.e. re-shared).")
.font(.footnote)
.foregroundColor(Color.gray)
}
}
}
struct Reposted_Previews: PreviewProvider {
static var previews: some View {
let test_state = test_damus_state()
Reposted(damus: test_state, pubkey: test_state.pubkey, profile: make_test_profile())
}
}

View File

@@ -0,0 +1,38 @@
//
// WebsiteLink.swift
// damus
//
// Created by William Casarin on 2023-01-22.
//
import SwiftUI
struct WebsiteLink: View {
let url: URL
@Environment(\.openURL) var openURL
var body: some View {
HStack {
Image(systemName: "link")
.foregroundColor(.gray)
.font(.footnote)
Button(action: {
openURL(url)
}, label: {
Text(link_text)
.font(.footnote)
})
}
}
var link_text: String {
url.host ?? url.absoluteString
}
}
struct WebsiteLink_Previews: PreviewProvider {
static var previews: some View {
WebsiteLink(url: URL(string: "https://jb55.com")!)
}
}

View File

@@ -80,6 +80,7 @@ struct ContentView: View {
@State var thread_open: Bool = false
@State var search_open: Bool = false
@State var filter_state : FilterState = .posts_and_replies
@State private var isSideBarOpened = false
@StateObject var home: HomeModel = HomeModel()
@StateObject var user_settings = UserSettingsStore()
@@ -92,17 +93,25 @@ struct ContentView: View {
var PostingTimelineView: some View {
VStack {
TabView(selection: $filter_state) {
contentTimelineView(filter: FilterState.posts.filter)
.tag(FilterState.posts)
.id(FilterState.posts)
contentTimelineView(filter: FilterState.posts_and_replies.filter)
.tag(FilterState.posts_and_replies)
.id(FilterState.posts_and_replies)
ZStack {
TabView(selection: $filter_state) {
contentTimelineView(filter: FilterState.posts.filter)
.tag(FilterState.posts)
.id(FilterState.posts)
contentTimelineView(filter: FilterState.posts_and_replies.filter)
.tag(FilterState.posts_and_replies)
.id(FilterState.posts_and_replies)
}
.tabViewStyle(.page(indexDisplayMode: .never))
if privkey != nil {
PostButtonContainer(userSettings: user_settings) {
self.active_sheet = .post
}
}
}
.tabViewStyle(.page(indexDisplayMode: .never))
}
.safeAreaInset(edge: .top) {
.safeAreaInset(edge: .top, spacing: 0) {
VStack(spacing: 0) {
FiltersView
//.frame(maxWidth: 275)
@@ -112,7 +121,6 @@ struct ContentView: View {
}
.background(colorScheme == .dark ? Color.black : Color.white)
}
.ignoresSafeArea(.keyboard)
}
func contentTimelineView(filter: (@escaping (NostrEvent) -> Bool)) -> some View {
@@ -120,11 +128,6 @@ struct ContentView: View {
if let damus = self.damus_state {
TimelineView(events: $home.events, loading: $home.loading, damus: damus, show_friend_icon: false, filter: filter)
}
if privkey != nil {
PostButtonContainer {
self.active_sheet = .post
}
}
}
}
@@ -169,6 +172,29 @@ struct ContentView: View {
}
}
.navigationBarTitle(selected_timeline == .home ? NSLocalizedString("Home", comment: "Navigation bar title for Home view where posts and replies appear from those who the user is following.") : NSLocalizedString("Global", comment: "Navigation bar title for Global view where posts from all connected relay servers appear."), displayMode: .inline)
.toolbar {
ToolbarItem(placement: .principal) {
switch selected_timeline {
case .home:
Image("damus-home")
.resizable()
.frame(width:30,height:30)
.shadow(color: Color("DamusPurple"), radius: 2)
case .dms:
Text("DMs", comment: "Toolbar label for DMs view, where DM is the English abbreviation for Direct Message.")
.bold()
case .notifications:
Text("Notifications", comment: "Toolbar label for Notifications view.")
.bold()
case .search:
Text("Global", comment: "Toolbar label for Global view where posts from all connected relay servers appear.")
.bold()
case .none:
Text("", comment: "Toolbar label for unknown views. This label would be displayed only if a new timeline view is added but a toolbar label was not explicitly assigned to it yet.")
}
}
}
.ignoresSafeArea(.keyboard)
}
var MaybeSearchView: some View {
@@ -202,51 +228,49 @@ struct ContentView: View {
}
}
}
var body: some View {
VStack(alignment: .leading, spacing: 0) {
if let damus = self.damus_state {
NavigationView {
MainContent(damus: damus)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
let profile_model = ProfileModel(pubkey: damus_state!.pubkey, damus: damus_state!)
let followers_model = FollowersModel(damus_state: damus_state!, target: damus_state!.pubkey)
let prof_dest = ProfileView(damus_state: damus_state!, profile: profile_model, followers: followers_model)
ZStack {
VStack {
MainContent(damus: damus)
.toolbar() {
ToolbarItem(placement: .navigationBarLeading) {
Button {
isSideBarOpened.toggle()
} label: {
ProfilePicView(pubkey: damus_state!.pubkey, size: 32, highlight: .none, profiles: damus_state!.profiles)
}
}
ToolbarItem(placement: .navigationBarTrailing) {
HStack(alignment: .center) {
if home.signal.signal != home.signal.max_signal {
Text("\(home.signal.signal)/\(home.signal.max_signal)", comment: "Fraction of how many of the user's relay servers that are operational.")
.font(.callout)
.foregroundColor(.gray)
}
NavigationLink(destination: prof_dest) {
/// Verify that the user has a profile picture, if not display a generic SF Symbol
/// (Resolves an in-app error where ``Robohash`` pictures are not generated so the button dissapears
if let picture = damus_state?.profiles.lookup(id: pubkey)?.picture {
ProfilePicView(pubkey: damus_state!.pubkey, size: 32, highlight: .none, profiles: damus_state!.profiles, picture: picture)
} else {
Image(systemName: "person.fill")
}
}
}
.buttonStyle(PlainButtonStyle())
}
ToolbarItem(placement: .navigationBarTrailing) {
HStack(alignment: .center) {
if home.signal.signal != home.signal.max_signal {
Text("\(home.signal.signal)/\(home.signal.max_signal)", comment: "Fraction of how many of the user's relay servers that are operational.")
.font(.callout)
.foregroundColor(.gray)
}
NavigationLink(destination: ConfigView(state: damus_state!).environmentObject(user_settings)) {
Label("", systemImage: "gear")
}
.buttonStyle(PlainButtonStyle())
}
}
}
Color.clear
.overlay(
SideMenuView(damus_state: damus, isSidebarVisible: $isSideBarOpened)
)
}
.navigationBarHidden(isSideBarOpened ? true: false) // Would prefer a different way of doing this.
}
.navigationViewStyle(.stack)
TabBar(new_events: $home.new_events, selected: $selected_timeline, isSidebarVisible: $isSideBarOpened, action: switch_timeline)
.padding([.bottom], 8)
}
TabBar(new_events: $home.new_events, selected: $selected_timeline, action: switch_timeline)
.padding([.bottom], 8)
}
.onAppear() {
self.connect()
@@ -287,7 +311,6 @@ struct ContentView: View {
guard let privkey = self.privkey else {
return
}
let ev = notif.object as! NostrEvent
let boost = make_boost_event(pubkey: pubkey, privkey: privkey, boosted: ev)
self.damus_state?.pool.send(.event(boost))
@@ -320,10 +343,10 @@ struct ContentView: View {
let pk = target.pubkey
if let ev = unfollow_user(pool: damus.pool,
our_contacts: damus.contacts.event,
pubkey: damus.pubkey,
privkey: privkey,
unfollow: pk) {
our_contacts: damus.contacts.event,
pubkey: damus.pubkey,
privkey: privkey,
unfollow: pk) {
notify(.unfollowed, pk)
damus.contacts.event = ev
@@ -342,10 +365,10 @@ struct ContentView: View {
}
if let ev = follow_user(pool: damus.pool,
our_contacts: damus.contacts.event,
pubkey: damus.pubkey,
privkey: privkey,
follow: ReferencedId(ref_id: fnotify.pubkey, relay_id: nil, key: "p")) {
our_contacts: damus.contacts.event,
pubkey: damus.pubkey,
privkey: privkey,
follow: ReferencedId(ref_id: fnotify.pubkey, relay_id: nil, key: "p")) {
notify(.followed, fnotify.pubkey)
damus_state?.contacts.event = ev
@@ -435,7 +458,6 @@ struct ContentView_Previews: PreviewProvider {
}
}
func get_since_time(last_event: NostrEvent?) -> Int64? {
if let last_event = last_event {
return last_event.created_at - 60 * 10

View File

@@ -15,8 +15,6 @@
</array>
</dict>
</array>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>&quot;Granting Damus access to your photo library allows you to save photos.</string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>river</string>

View File

@@ -16,6 +16,10 @@ class ActionBarModel: ObservableObject {
@Published var boosts: Int
@Published var tips: Int64
static func empty() -> ActionBarModel {
return ActionBarModel(likes: 0, boosts: 0, tips: 0, our_like: nil, our_boost: nil, our_tip: nil)
}
init(likes: Int, boosts: Int, tips: Int64, our_like: NostrEvent?, our_boost: NostrEvent?, our_tip: NostrEvent?) {
self.likes = likes
self.boosts = boosts
@@ -25,6 +29,10 @@ class ActionBarModel: ObservableObject {
self.our_tip = our_tip
}
var is_empty: Bool {
return likes == 0 && boosts == 0 && tips == 0
}
var tipped: Bool {
return our_tip != nil
}

View File

@@ -22,8 +22,13 @@ struct DamusState {
var pubkey: String {
return keypair.pubkey
}
var is_privkey_user: Bool {
keypair.privkey != nil
}
static var empty: DamusState {
return DamusState.init(pool: RelayPool(), keypair: Keypair(pubkey: "", privkey: ""), likes: EventCounter(our_pubkey: ""), boosts: EventCounter(our_pubkey: ""), contacts: Contacts(our_pubkey: ""), tips: TipCounter(our_pubkey: ""), profiles: Profiles(), dms: DirectMessagesModel(), previews: PreviewCache())
return DamusState.init(pool: RelayPool(), keypair: Keypair(pubkey: "", privkey: ""), likes: EventCounter(our_pubkey: ""), boosts: EventCounter(our_pubkey: ""), contacts: Contacts(our_pubkey: ""), tips: TipCounter(our_pubkey: ""), profiles: Profiles(), dms: DirectMessagesModel(our_pubkey: ""), previews: PreviewCache())
}
}

View File

@@ -8,13 +8,34 @@
import Foundation
class DirectMessageModel: ObservableObject {
@Published var events: [NostrEvent]
init(events: [NostrEvent]) {
self.events = events
@Published var events: [NostrEvent] {
didSet {
is_request = determine_is_request()
}
}
init() {
var is_request: Bool
var our_pubkey: String
func determine_is_request() -> Bool {
for event in events {
if event.pubkey == our_pubkey {
return false
}
}
return true
}
init(events: [NostrEvent], our_pubkey: String) {
self.events = events
self.is_request = false
self.our_pubkey = our_pubkey
}
init(our_pubkey: String) {
self.events = []
self.is_request = false
self.our_pubkey = our_pubkey
}
}

View File

@@ -10,13 +10,26 @@ import Foundation
class DirectMessagesModel: ObservableObject {
@Published var dms: [(String, DirectMessageModel)] = []
@Published var loading: Bool = false
let our_pubkey: String
init(our_pubkey: String) {
self.our_pubkey = our_pubkey
}
var message_requests: [(String, DirectMessageModel)] {
return dms.filter { dm in dm.1.is_request }
}
var friend_dms: [(String, DirectMessageModel)] {
return dms.filter { dm in !dm.1.is_request }
}
func lookup_or_create(_ pubkey: String) -> DirectMessageModel {
if let dm = lookup(pubkey) {
return dm
}
let new = DirectMessageModel()
let new = DirectMessageModel(our_pubkey: our_pubkey)
dms.append((pubkey, new))
return new
}

View File

@@ -18,11 +18,11 @@ class FollowersModel: ObservableObject {
let sub_id: String = UUID().description
let profiles_id: String = UUID().description
var count_display: String {
var count: Int? {
guard let contacts = self.contacts else {
return "?"
return nil
}
return "\(contacts.count)";
return contacts.count
}
init(damus_state: DamusState, target: String) {
@@ -73,31 +73,30 @@ class FollowersModel: ObservableObject {
}
func handle_event(relay_id: String, ev: NostrConnectionEvent) {
switch ev {
case .ws_event:
break
case .nostr_event(let nev):
switch nev {
case .event(let sub_id, let ev):
guard sub_id == self.sub_id || sub_id == self.profiles_id else {
return
}
if ev.known_kind == .contacts {
handle_contact_event(ev)
} else if ev.known_kind == .metadata {
process_metadata_event(profiles: damus_state.profiles, ev: ev)
}
case .notice(let msg):
print("followingmodel notice: \(msg)")
case .eose(let sub_id):
if sub_id == self.sub_id {
load_profiles(relay_id: relay_id)
} else if sub_id == self.profiles_id {
damus_state.pool.unsubscribe(sub_id: profiles_id, to: [relay_id])
}
guard case .nostr_event(let nev) = ev else {
return
}
switch nev {
case .event(let sub_id, let ev):
guard sub_id == self.sub_id || sub_id == self.profiles_id else {
return
}
if ev.known_kind == .contacts {
handle_contact_event(ev)
} else if ev.known_kind == .metadata {
process_metadata_event(profiles: damus_state.profiles, ev: ev)
}
case .notice(let msg):
print("followingmodel notice: \(msg)")
case .eose(let sub_id):
if sub_id == self.sub_id {
load_profiles(relay_id: relay_id)
} else if sub_id == self.profiles_id {
damus_state.pool.unsubscribe(sub_id: profiles_id, to: [relay_id])
}
}
}

View File

@@ -48,17 +48,19 @@ class HomeModel: ObservableObject {
@Published var new_events: NewEventsBits = NewEventsBits()
@Published var notifications: [NostrEvent] = []
@Published var dms: DirectMessagesModel = DirectMessagesModel()
@Published var dms: DirectMessagesModel
@Published var events: [NostrEvent] = []
@Published var loading: Bool = false
@Published var signal: SignalModel = SignalModel()
init() {
self.damus_state = DamusState.empty
self.dms = DirectMessagesModel(our_pubkey: damus_state.pubkey)
}
init(damus_state: DamusState) {
self.damus_state = damus_state
self.dms = DirectMessagesModel(our_pubkey: damus_state.pubkey)
}
var pool: RelayPool {
@@ -347,24 +349,23 @@ class HomeModel: ObservableObject {
return m[kind]
}
func handle_last_event(ev: NostrEvent, timeline: Timeline, shouldNotify: Bool = true) {
let last_ev = get_last_event(timeline)
if last_ev == nil || last_ev!.created_at < ev.created_at {
save_last_event(ev, timeline: timeline)
if shouldNotify {
new_events = NewEventsBits(prev: new_events, setting: timeline)
}
}
}
func handle_notification(ev: NostrEvent) {
guard event_has_our_pubkey(ev, our_pubkey: self.damus_state.pubkey) else {
return
}
if !insert_uniq_sorted_event(events: &notifications, new_ev: ev, cmp: { $0.created_at > $1.created_at }) {
return
}
handle_last_event(ev: ev, timeline: .notifications)
}
func handle_last_event(ev: NostrEvent, timeline: Timeline, shouldNotify: Bool = true) {
if let new_bits = handle_last_events(new_events: self.new_events, ev: ev, timeline: timeline, shouldNotify: shouldNotify) {
new_events = new_bits
}
}
func insert_home_event(_ ev: NostrEvent) -> Bool {
let ok = insert_uniq_sorted_event(events: &self.events, new_ev: ev, cmp: { $0.created_at > $1.created_at })
@@ -391,49 +392,8 @@ class HomeModel: ObservableObject {
}
func handle_dm(_ ev: NostrEvent) {
var inserted = false
var found = false
let ours = ev.pubkey == self.damus_state.pubkey
var i = 0
var the_pk = ev.pubkey
if ours {
if let ref_pk = ev.referenced_pubkeys.first {
the_pk = ref_pk.ref_id
} else {
// self dm!?
print("TODO: handle self dm?")
}
}
for (pk, _) in dms.dms {
if pk == the_pk {
found = true
inserted = insert_uniq_sorted_event(events: &(dms.dms[i].1.events), new_ev: ev) {
$0.created_at < $1.created_at
}
break
}
i += 1
}
if !found {
inserted = true
let model = DirectMessageModel(events: [ev])
dms.dms.append((the_pk, model))
}
if inserted {
handle_last_event(ev: ev, timeline: .dms, shouldNotify: !ours)
dms.dms = dms.dms.sorted { a, b in
if a.1.events.count > 0 && b.1.events.count > 0 {
return a.1.events.last!.created_at > b.1.events.last!.created_at
}
return false
}
if let notifs = handle_incoming_dm(prev_events: self.new_events, dms: self.dms, our_pubkey: self.damus_state.pubkey, ev: ev) {
self.new_events = notifs
}
}
}
@@ -578,6 +538,13 @@ func process_metadata_event(profiles: Profiles, ev: NostrEvent) {
}
}
let banner = tprof.profile.banner ?? ""
if let _ = URL(string: banner) {
DispatchQueue.main.async {
notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile))
}
}
notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile))
}
@@ -649,4 +616,75 @@ func load_our_relays(contacts: Contacts, our_pubkey: String, pool: RelayPool, m_
}
}
func handle_incoming_dm(prev_events: NewEventsBits, dms: DirectMessagesModel, our_pubkey: String, ev: NostrEvent) -> NewEventsBits? {
var inserted = false
var found = false
let ours = ev.pubkey == our_pubkey
var i = 0
var the_pk = ev.pubkey
if ours {
if let ref_pk = ev.referenced_pubkeys.first {
the_pk = ref_pk.ref_id
} else {
// self dm!?
print("TODO: handle self dm?")
}
}
for (pk, _) in dms.dms {
if pk == the_pk {
found = true
inserted = insert_uniq_sorted_event(events: &(dms.dms[i].1.events), new_ev: ev) {
$0.created_at < $1.created_at
}
break
}
i += 1
}
if !found {
inserted = true
let model = DirectMessageModel(events: [ev], our_pubkey: our_pubkey)
dms.dms.append((the_pk, model))
}
var new_events: NewEventsBits? = nil
if inserted {
new_events = handle_last_events(new_events: prev_events, ev: ev, timeline: .dms, shouldNotify: !ours)
dms.dms = dms.dms.filter({ $0.1.events.count > 0 }).sorted { a, b in
return a.1.events.last!.created_at > b.1.events.last!.created_at
}
}
return new_events
}
/// A helper to determine if we need to notify the user of new events
func handle_last_events(new_events: NewEventsBits, ev: NostrEvent, timeline: Timeline, shouldNotify: Bool = true) -> NewEventsBits? {
let last_ev = get_last_event(timeline)
if last_ev == nil || last_ev!.created_at < ev.created_at {
save_last_event(ev, timeline: timeline)
if shouldNotify {
return NewEventsBits(prev: new_events, setting: timeline)
}
}
return nil
}
/// Sometimes we get garbage in our notifications. Ensure we have our pubkey on this event
func event_has_our_pubkey(_ ev: NostrEvent, our_pubkey: String) -> Bool {
for tag in ev.tags {
if tag.count >= 2 && tag[0] == "p" && tag[1] == our_pubkey {
return true
}
}
return false
}

View File

@@ -0,0 +1,108 @@
//
// KFImageModel.swift
// damus
//
// Created by Oleg Abalonski on 1/11/23.
//
import Foundation
import Kingfisher
import SVGKit
class KFImageModel: ObservableObject {
let url: URL?
let fallbackUrl: URL?
let processor: ImageProcessor
let serializer: CacheSerializer
@Published var refreshID = ""
init(url: URL?, fallbackUrl: URL?, maxByteSize: Int, downsampleSize: CGSize) {
self.url = url
self.fallbackUrl = fallbackUrl
self.processor = CustomImageProcessor(maxSize: maxByteSize, downsampleSize: downsampleSize)
self.serializer = CustomCacheSerializer(maxSize: maxByteSize, downsampleSize: downsampleSize)
}
func refresh() -> Void {
DispatchQueue.main.async {
self.refreshID = UUID().uuidString
}
}
func cache(_ image: UIImage, forKey key: String) -> Void {
KingfisherManager.shared.cache.store(image, forKey: key, processorIdentifier: processor.identifier) { _ in
self.refresh()
}
}
func downloadFailed() -> Void {
guard let url = url, let fallbackUrl = fallbackUrl else { return }
DispatchQueue.global(qos: .background).async {
KingfisherManager.shared.downloader.downloadImage(with: fallbackUrl) { result in
var fallbackImage: UIImage {
switch result {
case .success(let imageLoadingResult):
return imageLoadingResult.image
case .failure(let error):
print(error)
return UIImage()
}
}
self.cache(fallbackImage, forKey: url.absoluteString)
}
}
}
}
struct CustomImageProcessor: ImageProcessor {
let maxSize: Int
let downsampleSize: CGSize
let identifier = "com.damus.customimageprocessor"
func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? {
switch item {
case .image(_):
// This case will never run
return DefaultImageProcessor.default.process(item: item, options: options)
case .data(let data):
// Handle large image size
if data.count > maxSize {
return KingfisherWrapper.downsampledImage(data: data, to: downsampleSize, scale: options.scaleFactor)
}
// Handle SVG image
if let svgImage = SVGKImage(data: data), let image = svgImage.uiImage {
return image.kf.scaled(to: options.scaleFactor)
}
return DefaultImageProcessor.default.process(item: item, options: options)
}
}
}
struct CustomCacheSerializer: CacheSerializer {
let maxSize: Int
let downsampleSize: CGSize
func data(with image: Kingfisher.KFCrossPlatformImage, original: Data?) -> Data? {
return DefaultCacheSerializer.default.data(with: image, original: original)
}
func image(with data: Data, options: Kingfisher.KingfisherParsedOptionsInfo) -> Kingfisher.KFCrossPlatformImage? {
if data.count > maxSize {
return KingfisherWrapper.downsampledImage(data: data, to: downsampleSize, scale: options.scaleFactor)
}
return DefaultCacheSerializer.default.image(with: data, options: options)
}
}

View File

@@ -198,11 +198,7 @@ enum Amount: Equatable {
let sats = NSNumber(value: (Double(amt) / 1000.0))
let formattedSats = numberFormatter.string(from: sats) ?? sats.stringValue
if formattedSats == numberFormatter.string(from: 1) {
return NSLocalizedString("\(formattedSats) sat", comment: "Amount of 1 sat.")
}
return NSLocalizedString("\(formattedSats) sats", comment: "Amount of sats.")
return String(format: NSLocalizedString("sats_count", comment: "Amount of sats."), sats.decimalValue as NSDecimalNumber, formattedSats)
}
}
}

View File

@@ -0,0 +1,78 @@
//
// LikesModel.swift
// damus
//
// Created by William Casarin on 2023-01-11.
//
import Foundation
class ReactionsModel: ObservableObject {
let state: DamusState
let target: String
let sub_id: String
let profiles_id: String
@Published var reactions: [NostrEvent]
init (state: DamusState, target: String) {
self.state = state
self.target = target
self.sub_id = UUID().description
self.profiles_id = UUID().description
self.reactions = []
}
func get_filter() -> NostrFilter {
var filter = NostrFilter.filter_kinds([7])
filter.referenced_ids = [target]
filter.limit = 500
return filter
}
func subscribe() {
let filter = get_filter()
let filters = [filter]
self.state.pool.subscribe(sub_id: sub_id, filters: filters, handler: handle_nostr_event)
}
func unsubscribe() {
self.state.pool.unsubscribe(sub_id: sub_id)
}
func handle_event(relay_id: String, ev: NostrEvent) {
guard ev.kind == 7 else {
return
}
guard let reacted_to = last_etag(tags: ev.tags) else {
return
}
guard reacted_to == self.target else {
return
}
if insert_uniq_sorted_event(events: &self.reactions, new_ev: ev, cmp: { a, b in a.created_at < b.created_at } ) {
objectWillChange.send()
}
}
func handle_nostr_event(relay_id: String, ev: NostrConnectionEvent) {
guard case .nostr_event(let nev) = ev else {
return
}
switch nev {
case .event(_, let ev):
handle_event(relay_id: relay_id, ev: ev)
case .notice(_):
break
case .eose(_):
load_profiles(profiles_subid: profiles_id, relay_id: relay_id, events: reactions, damus_state: state)
break
}
}
}

View File

@@ -20,13 +20,22 @@ class UserSettingsStore: ObservableObject {
}
}
@Published var left_handed: Bool {
didSet {
UserDefaults.standard.set(left_handed, forKey: "left_handed")
}
}
init() {
if let defaultWalletName = UserDefaults.standard.string(forKey: "default_wallet"),
let default_wallet = Wallet(rawValue: defaultWalletName) {
let default_wallet = Wallet(rawValue: defaultWalletName)
{
self.default_wallet = default_wallet
} else {
self.default_wallet = .system_default_wallet
default_wallet = .system_default_wallet
}
self.show_wallet_selector = UserDefaults.standard.object(forKey: "show_wallet_selector") as? Bool ?? true
show_wallet_selector = UserDefaults.standard.object(forKey: "show_wallet_selector") as? Bool ?? true
left_handed = UserDefaults.standard.object(forKey: "left_handed") as? Bool ?? false
}
}

View File

@@ -75,7 +75,7 @@ enum Wallet: String, CaseIterable, Identifiable {
appStoreLink: "https://apps.apple.com/sv/app/bitcoin-beach-wallet/id1531383905", image: "bbw")
case .blixtwallet:
return .init(index: 11, tag: "blixtwallet", displayName: NSLocalizedString("Blixt Wallet", comment: "Dropdown option label for Lightning wallet, Blixt Wallet"), link: "blixtwallet:lightning:",
appStoreLink: nil, image: "blixt-wallet")
appStoreLink: "https://testflight.apple.com/join/EXvGhRzS", image: "blixt-wallet")
case .river:
return .init(index: 12, tag: "river", displayName: NSLocalizedString("River", comment: "Dropdown option label for Lightning wallet, River"), link: "river://",
appStoreLink: "https://apps.apple.com/us/app/river-buy-mine-bitcoin/id1536176542", image: "river")

View File

@@ -0,0 +1,35 @@
//
// SwipeToDismiss.swift
// damus
//
// Created by Joel Klabo on 1/18/23.
//
import SwiftUI
struct SwipeToDismissModifier: ViewModifier {
let minDistance: CGFloat?
var onDismiss: () -> Void
@State private var offset: CGSize = .zero
func body(content: Content) -> some View {
content
.offset(y: offset.height)
.animation(.interactiveSpring(), value: offset)
.simultaneousGesture(
DragGesture(minimumDistance: minDistance ?? 10)
.onChanged { gesture in
if gesture.translation.width < 50 {
offset = gesture.translation
}
}
.onEnded { _ in
if abs(offset.height) > 100 {
onDismiss()
} else {
offset = .zero
}
}
)
}
}

View File

@@ -10,12 +10,13 @@ import Foundation
struct Profile: Codable {
var value: [String: String]
init (name: String?, display_name: String?, about: String?, picture: String?, website: String?, lud06: String?, lud16: String?, nip05: String?) {
init (name: String?, display_name: String?, about: String?, picture: String?, banner: String?, website: String?, lud06: String?, lud16: String?, nip05: String?) {
self.value = [:]
self.name = name
self.display_name = display_name
self.about = about
self.picture = picture
self.banner = banner
self.website = website
self.lud06 = lud06
self.lud16 = lud16
@@ -42,11 +43,20 @@ struct Profile: Codable {
set(s) { value["picture"] = s }
}
var banner: String? {
get { return value["banner"]; }
set(s) { value["banner"] = s }
}
var website: String? {
get { return value["website"]; }
set(s) { value["website"] = s }
}
var website_url: URL? {
return self.website.flatMap { URL(string: $0) }
}
var lud06: String? {
get { return value["lud06"]; }
set(s) { value["lud06"] = s }
@@ -94,26 +104,9 @@ struct Profile: Codable {
}
}
/*
struct Profile: Decodable {
let name: String?
let display_name: String?
let about: String?
let picture: String?
let website: String?
let nip05: String?
let lud06: String?
let lud16: String?
var lightning_uri: URL? {
return make_ln_url(self.lud06) ?? make_ln_url(self.lud16)
}
static func displayName(profile: Profile?, pubkey: String) -> String {
return profile?.name ?? abbrev_pubkey(pubkey)
}
func make_test_profile() -> Profile {
return Profile(name: "jb55", display_name: "Will", about: "Its a me", picture: "https://cdn.jb55.com/img/red-me.jpg", banner: "https://pbs.twimg.com/profile_banners/9918032/1531711830/600x200", website: "jb55.com", lud06: "jb55@jb55.com", lud16: nil, nip05: "jb55@jb55.com")
}
*/
func make_ln_url(_ str: String?) -> URL? {
return str.flatMap { URL(string: "lightning:" + $0) }

View File

@@ -772,6 +772,15 @@ func validate_event(ev: NostrEvent) -> ValidationResult {
return ok ? .ok : .bad_sig
}
func last_etag(tags: [[String]]) -> String? {
var e: String? = nil
for tag in tags {
if tag.count >= 2 && tag[0] == "e" {
e = tag[1]
}
}
return e
}
func inner_event_or_self(ev: NostrEvent) -> NostrEvent {
guard let inner_ev = ev.inner_event else {
@@ -780,3 +789,46 @@ func inner_event_or_self(ev: NostrEvent) -> NostrEvent {
return inner_ev
}
func first_eref_mention(ev: NostrEvent, privkey: String?) -> Mention? {
let blocks = ev.blocks(privkey).filter { block in
guard case .mention(let mention) = block else {
return false
}
guard case .event = mention.type else {
return false
}
if mention.ref.key != "e" {
return false
}
return true
}
/// MARK: - Preview
if let firstBlock = blocks.first, case .mention(let mention) = firstBlock, mention.ref.key == "e" {
return mention
}
return nil
}
extension [ReferencedId] {
var pRefs: [ReferencedId] {
get {
self.filter { ref in
ref.key == "p"
}
}
}
var eRefs: [ReferencedId] {
get {
self.filter { ref in
ref.key == "e"
}
}
}
}

View File

@@ -15,10 +15,11 @@ struct NostrMetadata: Codable {
let website: String?
let nip05: String?
let picture: String?
let banner: String?
let lud06: String?
let lud16: String?
}
func create_account_to_metadata(_ model: CreateAccountModel) -> NostrMetadata {
return NostrMetadata(display_name: model.real_name, name: model.nick_name, about: model.about, website: nil, nip05: nil, picture: nil, lud06: nil, lud16: nil)
return NostrMetadata(display_name: model.real_name, name: model.nick_name, about: model.about, website: nil, nip05: nil, picture: nil, banner: nil, lud06: nil, lud16: nil)
}

View File

@@ -15,6 +15,10 @@ struct NIP05 {
URL(string: "https://\(host)/.well-known/nostr.json?name=\(username)")
}
var siteUrl: URL? {
URL(string: "https://\(host)")
}
static func parse(_ nip05: String) -> NIP05? {
let parts = nip05.split(separator: "@")
guard parts.count == 2 else {

View File

@@ -29,41 +29,28 @@ struct EventActionBar: View {
var body: some View {
HStack {
/*
EventActionButton(img: "square.and.arrow.up") {
print("share")
}
Spacer()
*/
if damus_state.keypair.privkey != nil {
EventActionButton(img: "bubble.left", col: nil) {
notify(.reply, event)
}
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
}
HStack(alignment: .bottom) {
Text("\(bar.boosts > 0 ? "\(bar.boosts)" : "")")
.font(.footnote.weight(.medium))
.foregroundColor(bar.boosted ? Color.green : Color.gray)
Spacer()
ZStack {
EventActionButton(img: "arrow.2.squarepath", col: bar.boosted ? Color.green : nil) {
if bar.boosted {
notify(.delete, bar.our_boost)
} else {
} else if damus_state.is_privkey_user {
self.confirm_boost = true
}
}
}
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
HStack(alignment: .bottom) {
Text("\(bar.likes > 0 ? "\(bar.likes)" : "")")
Text("\(bar.boosts > 0 ? "\(bar.boosts)" : "")")
.offset(x: 18)
.font(.footnote.weight(.medium))
.foregroundColor(bar.liked ? Color.orange : Color.gray)
.foregroundColor(bar.boosted ? Color.green : Color.gray)
}
Spacer()
ZStack {
LikeButton(liked: bar.liked) {
if bar.liked {
notify(.delete, bar.our_like)
@@ -71,29 +58,16 @@ struct EventActionBar: View {
send_like()
}
}
Text("\(bar.likes > 0 ? "\(bar.likes)" : "")")
.offset(x: 22)
.font(.footnote.weight(.medium))
.foregroundColor(bar.liked ? Color.accentColor : Color.gray)
}
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
Spacer()
EventActionButton(img: "square.and.arrow.up", col: Color.gray) {
show_share_sheet = true
}
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
/*
HStack(alignment: .bottom) {
Text("\(bar.tips > 0 ? "\(bar.tips)" : "")")
.font(.footnote)
.foregroundColor(bar.tipped ? Color.orange : Color.gray)
EventActionButton(img: bar.tipped ? "bitcoinsign.circle.fill" : "bitcoinsign.circle", col: bar.tipped ? Color.orange : nil) {
if bar.tipped {
//notify(.delete, bar.our_tip)
} else {
//notify(.boost, event)
}
}
}
*/
}
.sheet(isPresented: $show_share_sheet) {
if let note_id = bech32_note_id(event.id) {
@@ -102,15 +76,15 @@ struct EventActionBar: View {
}
}
}
.alert(NSLocalizedString("Boost", comment: "Title of alert for confirming to boost a post."), isPresented: $confirm_boost) {
Button("Cancel") {
.alert(NSLocalizedString("Repost", comment: "Title of alert for confirming to repost a post."), isPresented: $confirm_boost) {
Button(NSLocalizedString("Cancel", comment: "Button to cancel out of reposting a post.")) {
confirm_boost = false
}
Button(NSLocalizedString("Boost", comment: "Button to confirm boosting a post.")) {
Button(NSLocalizedString("Repost", comment: "Button to confirm reposting a post.")) {
send_boost()
}
} message: {
Text("Are you sure you want to boost this post?", comment: "Alert message to ask if user wants to boost a post.")
Text("Are you sure you want to repost this?", comment: "Alert message to ask if user wants to repost a post.")
}
.onReceive(handle_notify(.liked)) { n in
let liked = n.object as! Counted
@@ -154,7 +128,7 @@ struct EventActionBar: View {
func EventActionButton(img: String, col: Color?, action: @escaping () -> ()) -> some View {
Button(action: action) {
Label("&nbsp;", systemImage: img)
Label(NSLocalizedString("\u{00A0}", comment: "Non-breaking space character to fill in blank space next to event action button icons."), systemImage: img)
.font(.footnote.weight(.medium))
.foregroundColor(col == nil ? Color.gray : col!)
}
@@ -168,13 +142,8 @@ struct LikeButton: View {
var body: some View {
Button(action: action) {
if liked {
Text("🤙", comment: "Button with emoji to like an event.")
} else {
Image("shaka")
.renderingMode(.template)
.foregroundColor(.gray)
}
Image(liked ? "shaka-full" : "shaka-line")
.foregroundColor(liked ? .accentColor : .gray)
}
}
}
@@ -184,8 +153,22 @@ struct EventActionBar_Previews: PreviewProvider {
static var previews: some View {
let pk = "pubkey"
let ds = test_damus_state()
let bar = ActionBarModel(likes: 0, boosts: 0, tips: 0, our_like: nil, our_boost: nil, our_tip: nil)
let ev = NostrEvent(content: "hi", pubkey: pk)
EventActionBar(damus_state: ds, event: ev, bar: bar)
let bar = ActionBarModel(likes: 0, boosts: 0, tips: 0, our_like: nil, our_boost: nil, our_tip: nil)
let likedbar = ActionBarModel(likes: 10, boosts: 10, tips: 0, our_like: nil, our_boost: nil, our_tip: nil)
let likedbar_ours = ActionBarModel(likes: 100, boosts: 100, tips: 0, our_like: NostrEvent(id: "", content: "", pubkey: ""), our_boost: nil, our_tip: nil)
let maxed_bar = ActionBarModel(likes: 999, boosts: 999, tips: 0, our_like: NostrEvent(id: "", content: "", pubkey: ""), our_boost: NostrEvent(id: "", content: "", pubkey: ""), our_tip: nil)
VStack(spacing: 50) {
EventActionBar(damus_state: ds, event: ev, bar: bar)
EventActionBar(damus_state: ds, event: ev, bar: likedbar)
EventActionBar(damus_state: ds, event: ev, bar: likedbar_ours)
EventActionBar(damus_state: ds, event: ev, bar: maxed_bar)
}
.padding(20)
}
}

View File

@@ -0,0 +1,39 @@
//
// EventDetailBar.swift
// damus
//
// Created by William Casarin on 2023-01-08.
//
import SwiftUI
struct EventDetailBar: View {
let state: DamusState
let target: String
@StateObject var bar: ActionBarModel
var body: some View {
HStack {
if bar.boosts > 0 {
Text("\(Text("\(bar.boosts)", comment: "Number of reposts.").font(.body.bold())) \(Text(String(format: NSLocalizedString("reposts_count", comment: "Part of a larger sentence to describe how many reposts there are."), bar.boosts)).foregroundColor(.gray))", comment: "Sentence composed of 2 variables to describe how many reposts. In source English, the first variable is the number of reposts, and the second variable is 'Repost' or 'Reposts'.")
}
if bar.likes > 0 {
NavigationLink(destination: ReactionsView(damus_state: state, model: ReactionsModel(state: state, target: target))) {
Text("\(Text("\(bar.likes)", comment: "Number of reactions on a post.").font(.body.bold())) \(Text(String(format: NSLocalizedString("reactions_count", comment: "Part of a larger sentence to describe how many reactions there are on a post."), bar.likes)).foregroundColor(.gray))", comment: "Sentence composed of 2 variables to describe how many reactions there are on a post. In source English, the first variable is the number of reactions, and the second variable is 'Reaction' or 'Reactions'.")
}
.buttonStyle(PlainButtonStyle())
}
if bar.tips > 0 {
Text("\(Text("\(bar.tips)", comment: "Number of tip payments on a post.").font(.body.bold())) \(Text(String(format: NSLocalizedString("tips_count", comment: "Part of a larger sentence to describe how many tip payments there are on a post."), bar.boosts)).foregroundColor(.gray))", comment: "Sentence composed of 2 variables to describe how many tip payments there are on a post. In source English, the first variable is the number of tip payments, and the second variable is 'Tip' or 'Tips'.")
}
}
}
}
struct EventDetailBar_Previews: PreviewProvider {
static var previews: some View {
EventDetailBar(state: test_damus_state(), target: "", bar: ActionBarModel.empty())
}
}

View File

@@ -0,0 +1,99 @@
//
// BannerImageView.swift
// damus
//
// Created by Jason Jōb on 2023-01-10.
//
import SwiftUI
import Kingfisher
struct InnerBannerImageView: View {
let defaultImage = UIImage(named: "profile-banner") ?? UIImage()
@ObservedObject var imageModel: KFImageModel
init(url: URL?) {
self.imageModel = KFImageModel(
url: url,
fallbackUrl: nil,
maxByteSize: 5000000,
downsampleSize: CGSize(width: 750, height: 250)
)
}
var body: some View {
ZStack {
Color(uiColor: .systemBackground)
if (imageModel.url != nil) {
KFAnimatedImage(imageModel.url)
.callbackQueue(.dispatch(.global(qos: .background)))
.processingQueue(.dispatch(.global(qos: .background)))
.serialize(by: imageModel.serializer)
.setProcessor(imageModel.processor)
.configure { view in
view.framePreloadCount = 1
}
.placeholder { _ in
Color(uiColor: .secondarySystemBackground)
}
.scaleFactor(UIScreen.main.scale)
.loadDiskFileSynchronously()
.fade(duration: 0.1)
.onFailureImage(defaultImage)
.id(imageModel.refreshID)
} else {
Image(uiImage: defaultImage).resizable()
}
}
}
}
struct BannerImageView: View {
let pubkey: String
let profiles: Profiles
@State var banner: String?
init (pubkey: String, profiles: Profiles, banner: String? = nil) {
self.pubkey = pubkey
self.profiles = profiles
self._banner = State(initialValue: banner)
}
var body: some View {
InnerBannerImageView(url: get_banner_url(banner: banner, pubkey: pubkey, profiles: profiles))
.onReceive(handle_notify(.profile_updated)) { notif in
let updated = notif.object as! ProfileUpdate
guard updated.pubkey == self.pubkey else {
return
}
if let bannerImage = updated.profile.banner {
self.banner = bannerImage
}
}
}
}
func get_banner_url(banner: String?, pubkey: String, profiles: Profiles) -> URL? {
let bannerUrlString = banner ?? profiles.lookup(id: pubkey)?.banner ?? ""
if let url = URL(string: bannerUrlString) {
return url
}
return nil
}
struct BannerImageView_Previews: PreviewProvider {
static let pubkey = "ca48854ac6555fed8e439ebb4fa2d928410e0eef13fa41164ec45aaaa132d846"
static var previews: some View {
BannerImageView(
pubkey: pubkey,
profiles: make_preview_profiles(pubkey))
}
}

View File

@@ -70,9 +70,11 @@ struct ConfigView: View {
}
}
Section(NSLocalizedString("Recommended Relays", comment: "Section title for recommend relay servers that could be added as part of configuration")) {
List(recommended, id: \.url) { r in
RecommendedRelayView(damus: state, relay: r.url.absoluteString)
if recommended.count > 0 {
Section(NSLocalizedString("Recommended Relays", comment: "Section title for recommend relay servers that could be added as part of configuration")) {
List(recommended, id: \.url) { r in
RecommendedRelayView(damus: state, relay: r.url.absoluteString)
}
}
}
@@ -89,7 +91,7 @@ struct ConfigView: View {
Section(NSLocalizedString("Secret Account Login Key", comment: "Section title for user's secret account login key.")) {
HStack {
if show_privkey == false {
SecureField(NSLocalizedString("PrivateKey", comment: "Title of the secure field that holds the user's private key."), text: $privkey)
SecureField(NSLocalizedString("Private Key", comment: "Title of the secure field that holds the user's private key."), text: $privkey)
.disabled(true)
} else {
Text(sec)
@@ -113,7 +115,12 @@ struct ConfigView: View {
}
}
}
Section(NSLocalizedString("Left Handed", comment: "Moves the post button to the left side of the screen")) {
Toggle(NSLocalizedString("Left Handed", comment: "Moves the post button to the left side of the screen"), isOn: $user_settings.left_handed)
.toggleStyle(.switch)
}
Section(NSLocalizedString("Clear Cache", comment: "Section title for clearing cached data.")) {
Button(NSLocalizedString("Clear", comment: "Button for clearing cached data.")) {
KingfisherManager.shared.cache.clearMemoryCache()
@@ -121,7 +128,7 @@ struct ConfigView: View {
KingfisherManager.shared.cache.cleanExpiredDiskCache()
}
}
Section(NSLocalizedString("Reset", comment: "Section title for resetting the user")) {
Button(NSLocalizedString("Logout", comment: "Button to logout the user.")) {
confirm_logout = true
@@ -135,7 +142,7 @@ struct ConfigView: View {
Button(NSLocalizedString("Cancel", comment: "Cancel out of logging out the user.")) {
confirm_logout = false
}
Button(NSLocalizedString("Logout", comment: "Button for logging out the user.")) {
Button(NSLocalizedString("Logout", comment: "Button for logging out the user.")) {
notify(.logout, ())
}
} message: {

View File

@@ -149,7 +149,7 @@ struct DMChatView: View {
.opacity(((dms.events.count == 0) ? 1.0 : 0.0))
.foregroundColor(.gray)
}
.navigationTitle(NSLocalizedString("DM", comment: "Navigation title for DM view, which is the English abbreviation for Direct Message."))
.navigationTitle(NSLocalizedString("DMs", comment: "Navigation title for DMs view, where DM is the English abbreviation for Direct Message."))
.toolbar { Header }
}
}
@@ -158,7 +158,7 @@ struct DMChatView_Previews: PreviewProvider {
static var previews: some View {
let ev = NostrEvent(content: "hi", pubkey: "pubkey", kind: 1, tags: [])
let model = DirectMessageModel(events: [ev])
let model = DirectMessageModel(events: [ev], our_pubkey: "pubkey")
DMChatView(damus_state: test_damus_state(), pubkey: "pubkey")
.environmentObject(model)
@@ -166,7 +166,7 @@ struct DMChatView_Previews: PreviewProvider {
}
func create_dm(_ message: String, to_pk: String, tags: [[String]], keypair: Keypair) -> NostrEvent?
func create_dm(_ message: String, to_pk: String, tags: [[String]], keypair: Keypair, created_at: Int64? = nil) -> NostrEvent?
{
guard let privkey = keypair.privkey else {
return nil
@@ -181,7 +181,9 @@ func create_dm(_ message: String, to_pk: String, tags: [[String]], keypair: Keyp
return nil
}
let enc_content = encode_dm_base64(content: enc_message.bytes, iv: iv)
let ev = NostrEvent(content: enc_content, pubkey: keypair.pubkey, kind: 4, tags: tags)
let created = created_at ?? Int64(Date().timeIntervalSince1970)
let ev = NostrEvent(content: enc_content, pubkey: keypair.pubkey, kind: 4, tags: tags, createdAt: created)
ev.calculate_id()
ev.sign(privkey: privkey)
return ev

View File

@@ -10,25 +10,28 @@ import SwiftUI
struct DMView: View {
let event: NostrEvent
let damus_state: DamusState
var is_ours: Bool {
event.pubkey == damus_state.pubkey
}
var body: some View {
HStack {
if is_ours {
Spacer()
Spacer(minLength: UIScreen.main.bounds.width * 0.2)
}
let should_show_img = should_show_images(contacts: damus_state.contacts, ev: event, our_pubkey: damus_state.pubkey)
NoteContentView(privkey: damus_state.keypair.privkey, event: event, profiles: damus_state.profiles, previews: damus_state.previews, show_images: should_show_img, artifacts: .just_content(event.get_content(damus_state.keypair.privkey)), size: .normal)
.foregroundColor(is_ours ? Color.white : Color.primary)
.padding(10)
.background(is_ours ? Color.accentColor : Color.secondary.opacity(0.15))
.cornerRadius(8.0)
.tint(is_ours ? Color.white : Color.accentColor)
if !is_ours {
Spacer(minLength: UIScreen.main.bounds.width * 0.2)
}
}
}
}

View File

@@ -7,15 +7,26 @@
import SwiftUI
enum DMType: Hashable {
case rando
case friend
}
struct DirectMessagesView: View {
let damus_state: DamusState
@State var dm_type: DMType = .friend
@State var open_dm: Bool = false
@State var pubkey: String = ""
@State var active_model: DirectMessageModel = DirectMessageModel()
@EnvironmentObject var model: DirectMessagesModel
@State var active_model: DirectMessageModel
var MainContent: some View {
init(damus_state: DamusState) {
self.damus_state = damus_state
self._active_model = State(initialValue: DirectMessageModel(our_pubkey: damus_state.pubkey))
}
func MainContent(requests: Bool) -> some View {
ScrollView {
let chat = DMChatView(damus_state: damus_state, pubkey: pubkey)
.environmentObject(active_model)
@@ -26,13 +37,12 @@ struct DirectMessagesView: View {
if model.dms.isEmpty, !model.loading {
EmptyTimelineView()
} else {
ForEach(model.dms, id: \.0) { tup in
let dms = requests ? model.message_requests : model.friend_dms
ForEach(dms, id: \.0) { tup in
MaybeEvent(tup)
}
}
}
.padding(.horizontal)
.padding(.top)
}
}
@@ -52,8 +62,29 @@ struct DirectMessagesView: View {
}
var body: some View {
MainContent
.navigationTitle(NSLocalizedString("Encrypted DMs", comment: "Navigation title for view of encrypted DMs, where DM is an English abbreviation for Direct Message."))
VStack {
Picker(NSLocalizedString("DM Type", comment: "DM selector for seeing either DMs or message requests, which are messages that have not been responded to yet. DM is the English abbreviation for Direct Message."), selection: $dm_type) {
Text("DMs", comment: "Picker option for DM selector for seeing only DMs that have been responded to. DM is the English abbreviation for Direct Message.")
.tag(DMType.friend)
Text("Requests", comment: "Picker option for DM selector for seeing only message requests (DMs that someone else sent the user which has not been responded to yet). DM is the English abbreviation for Direct Message.")
.tag(DMType.rando)
}
.pickerStyle(.segmented)
TabView(selection: $dm_type) {
MainContent(requests: false)
.tag(DMType.friend)
MainContent(requests: true)
.tag(DMType.rando)
}
.tabViewStyle(.page(indexDisplayMode: .never))
}
.padding(.horizontal)
.padding(.top)
.navigationTitle(NSLocalizedString("DMs", comment: "Navigation title for view of DMs, where DM is an English abbreviation for Direct Message."))
}
}
@@ -63,8 +94,9 @@ struct DirectMessagesView_Previews: PreviewProvider {
pubkey: "pubkey",
kind: 4,
tags: [])
let model = DirectMessageModel(events: [ev])
DirectMessagesView(damus_state: test_damus_state())
let ds = test_damus_state()
let model = DirectMessageModel(events: [ev], our_pubkey: ds.pubkey)
DirectMessagesView(damus_state: ds)
.environmentObject(model)
}
}

View File

@@ -8,6 +8,7 @@
import SwiftUI
let PPM_SIZE: CGFloat = 80.0
let BANNER_HEIGHT: CGFloat = 150.0;
func isHttpsUrl(_ string: String) -> Bool {
let urlRegEx = "^https://.*$"
@@ -56,12 +57,14 @@ struct EditMetadataView: View {
@State var display_name: String
@State var about: String
@State var picture: String
@State var banner: String
@State var nip05: String
@State var name: String
@State var ln: String
@State var website: String
@Environment(\.dismiss) var dismiss
@Environment(\.colorScheme) var colorScheme
init (damus_state: DamusState) {
self.damus_state = damus_state
@@ -72,10 +75,15 @@ struct EditMetadataView: View {
_about = State(initialValue: data?.about ?? "")
_website = State(initialValue: data?.website ?? "")
_picture = State(initialValue: data?.picture ?? "")
_banner = State(initialValue: data?.banner ?? "")
_nip05 = State(initialValue: data?.nip05 ?? "")
_ln = State(initialValue: data?.lud16 ?? data?.lud06 ?? "")
}
func imageBorderColor() -> Color {
colorScheme == .light ? Color("DamusWhite") : Color("DamusBlack")
}
func save() {
let metadata = NostrMetadata(
display_name: display_name,
@@ -84,6 +92,7 @@ struct EditMetadataView: View {
website: website,
nip05: nip05.isEmpty ? nil : nip05,
picture: picture.isEmpty ? nil : picture,
banner: banner.isEmpty ? nil : banner,
lud06: ln.contains("@") ? nil : ln,
lud16: ln.contains("@") ? ln : nil
);
@@ -99,13 +108,32 @@ struct EditMetadataView: View {
return NIP05.parse(nip05)
}
var TopSection: some View {
ZStack(alignment: .top) {
GeometryReader { geo in
BannerImageView(pubkey: damus_state.pubkey, profiles: damus_state.profiles)
.aspectRatio(contentMode: .fill)
.frame(width: geo.size.width, height: BANNER_HEIGHT)
.clipped()
}.frame(height: BANNER_HEIGHT)
VStack(alignment: .leading) {
let pfp_size: CGFloat = 90.0
HStack(alignment: .center) {
ProfilePicView(pubkey: damus_state.pubkey, size: pfp_size, highlight: .custom(imageBorderColor(), 4.0), profiles: damus_state.profiles)
.offset(y: -(pfp_size/2.0)) // Increase if set a frame
Spacer()
}.padding(.bottom,-(pfp_size/2.0))
}
.padding(.horizontal,18)
.padding(.top,BANNER_HEIGHT)
}
}
var body: some View {
VStack(alignment: .leading) {
HStack {
Spacer()
InnerProfilePicView(url: URL(string: picture), pubkey: damus_state.pubkey, size: PPM_SIZE, highlight: .none)
Spacer()
}
TopSection
Form {
Section(NSLocalizedString("Your Name", comment: "Label for Your Name section of user profile form.")) {
TextField("Satoshi Nakamoto", text: $display_name)
@@ -126,6 +154,12 @@ struct EditMetadataView: View {
.textInputAutocapitalization(.never)
}
Section (NSLocalizedString("Banner Image", comment: "Label for Banner Image section of user profile form.")) {
TextField(NSLocalizedString("https://example.com/pic.jpg", comment: "Placeholder example text for profile picture URL."), text: $banner)
.autocorrectionDisabled(true)
.textInputAutocapitalization(.never)
}
Section(NSLocalizedString("Website", comment: "Label for Website section of user profile form.")) {
TextField(NSLocalizedString("https://jb55.com", comment: "Placeholder example text for website URL for user profile."), text: $website)
.autocorrectionDisabled(true)
@@ -172,7 +206,7 @@ struct EditMetadataView: View {
}
}
}
.navigationTitle(NSLocalizedString("Edit Profile", comment: "Title of navigation view for Edit Profile."))
.ignoresSafeArea()
}
}

View File

@@ -106,7 +106,7 @@ struct BuilderEventView: View {
if let event = event {
let ev = event.inner_event ?? event
NavigationLink(destination: BuildThreadV2View(damus: damus, event_id: ev.id)) {
EventView(damus: damus, event: event, show_friend_icon: true, size: .small)
EventView(damus: damus, event: event, show_friend_icon: true, size: .small, embedded: true)
}.buttonStyle(.plain)
} else {
ProgressView().padding()
@@ -129,10 +129,11 @@ struct EventView: View {
let pubkey: String
let show_friend_icon: Bool
let size: EventViewKind
let embedded: Bool
@EnvironmentObject var action_bar: ActionBarModel
init(event: NostrEvent, highlight: Highlight, has_action_bar: Bool, damus: DamusState, show_friend_icon: Bool, size: EventViewKind = .normal) {
init(event: NostrEvent, highlight: Highlight, has_action_bar: Bool, damus: DamusState, show_friend_icon: Bool, size: EventViewKind = .normal, embedded: Bool = false) {
self.event = event
self.highlight = highlight
self.has_action_bar = has_action_bar
@@ -140,9 +141,10 @@ struct EventView: View {
self.pubkey = event.pubkey
self.show_friend_icon = show_friend_icon
self.size = size
self.embedded = embedded
}
init(damus: DamusState, event: NostrEvent, show_friend_icon: Bool, size: EventViewKind = .normal) {
init(damus: DamusState, event: NostrEvent, show_friend_icon: Bool, size: EventViewKind = .normal, embedded: Bool = false) {
self.event = event
self.highlight = .none
self.has_action_bar = false
@@ -150,6 +152,7 @@ struct EventView: View {
self.pubkey = event.pubkey
self.show_friend_icon = show_friend_icon
self.size = size
self.embedded = embedded
}
init(damus: DamusState, event: NostrEvent, pubkey: String, show_friend_icon: Bool, size: EventViewKind = .normal, embedded: Bool = false) {
@@ -160,6 +163,7 @@ struct EventView: View {
self.pubkey = pubkey
self.show_friend_icon = show_friend_icon
self.size = size
self.embedded = embedded
}
var body: some View {
@@ -172,20 +176,10 @@ struct EventView: View {
let booster_profile = ProfileView(damus_state: damus, profile: prof_model, followers: follow_model)
NavigationLink(destination: booster_profile) {
HStack {
Image(systemName: "arrow.2.squarepath")
.font(.footnote.weight(.bold))
.foregroundColor(Color.gray)
ProfileName(pubkey: event.pubkey, profile: prof, damus: damus, show_friend_confirmed: true)
.font(.footnote.weight(.bold))
.foregroundColor(Color.gray)
Text("Boosted", comment: "Text indicating that the post was boosted (i.e. re-shared).")
.font(.footnote.weight(.bold))
.foregroundColor(Color.gray)
}
Reposted(damus: damus, pubkey: event.pubkey, profile: prof)
}
.buttonStyle(PlainButtonStyle())
TextEvent(inner_ev, pubkey: inner_ev.pubkey)
TextEvent(inner_ev, pubkey: inner_ev.pubkey, booster_pubkey: event.pubkey)
.padding([.top], 1)
}
} else {
@@ -195,7 +189,7 @@ struct EventView: View {
}
}
func TextEvent(_ event: NostrEvent, pubkey: String) -> some View {
func TextEvent(_ event: NostrEvent, pubkey: String, booster_pubkey: String? = nil) -> some View {
let content = event.get_content(damus.keypair.privkey)
return HStack(alignment: .top) {
@@ -206,8 +200,10 @@ struct EventView: View {
let pmodel = ProfileModel(pubkey: pubkey, damus: damus)
let pv = ProfileView(damus_state: damus, profile: pmodel, followers: FollowersModel(damus_state: damus, target: pubkey))
NavigationLink(destination: pv) {
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: highlight, profiles: damus.profiles)
if !embedded {
NavigationLink(destination: pv) {
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: highlight, profiles: damus.profiles)
}
}
Spacer()
@@ -242,30 +238,44 @@ struct EventView: View {
.frame(maxWidth: .infinity, alignment: .leading)
}
let should_show_img = should_show_images(contacts: damus.contacts, ev: event, our_pubkey: damus.pubkey)
let should_show_img = should_show_images(contacts: damus.contacts, ev: event, our_pubkey: damus.pubkey, booster_pubkey: booster_pubkey)
NoteContentView(privkey: damus.keypair.privkey, event: event, profiles: damus.profiles, previews: damus.previews, show_images: should_show_img, artifacts: .just_content(content), size: self.size)
.frame(maxWidth: .infinity, alignment: .leading)
.allowsHitTesting(!embedded)
if has_action_bar {
if size == .selected {
Text("\(format_date(event.created_at))")
.padding(.top, 10)
.font(.footnote)
.foregroundColor(.gray)
Divider()
.padding([.bottom], 4)
} else {
Rectangle().frame(height: 2).opacity(0)
if !embedded {
if let mention = first_eref_mention(ev: event, privkey: damus.keypair.privkey) {
BuilderEventView(damus: damus, event_id: mention.ref.id)
}
let bar = make_actionbar_model(ev: event, damus: damus)
EventActionBar(damus_state: damus, event: event, bar: bar)
}
if has_action_bar {
if size == .selected {
Text("\(format_date(event.created_at))")
.padding(.top, 10)
.font(.footnote)
.foregroundColor(.gray)
Divider()
.padding([.bottom], 4)
} else {
Rectangle().frame(height: 2).opacity(0)
}
let bar = make_actionbar_model(ev: event, damus: damus)
if size == .selected && !bar.is_empty {
EventDetailBar(state: damus, target: event.id, bar: bar)
Divider()
}
EventActionBar(damus_state: damus, event: event, bar: bar)
.padding([.top], 4)
}
Divider()
.padding([.top], 4)
Divider()
.padding([.top], 4)
}
}
.padding([.leading], 2)
}
@@ -279,13 +289,16 @@ struct EventView: View {
}
// blame the porn bots for this code
func should_show_images(contacts: Contacts, ev: NostrEvent, our_pubkey: String) -> Bool {
func should_show_images(contacts: Contacts, ev: NostrEvent, our_pubkey: String, booster_pubkey: String? = nil) -> Bool {
if ev.pubkey == our_pubkey {
return true
}
if contacts.is_in_friendosphere(ev.pubkey) {
return true
}
if let boost_key = booster_pubkey, contacts.is_in_friendosphere(boost_key) {
return true
}
return false
}
@@ -324,19 +337,19 @@ extension View {
Button {
UIPasteboard.general.string = bech32_pubkey(pubkey) ?? pubkey
} label: {
Label(NSLocalizedString("Copy User ID", comment: "Context menu option for copying the ID of the user who created the note."), systemImage: "tag")
Label(NSLocalizedString("Copy User ID", comment: "Context menu option for copying the ID of the user who created the note."), systemImage: "person")
}
Button {
UIPasteboard.general.string = bech32_note_id(event.id) ?? event.id
} label: {
Label(NSLocalizedString("Copy Note ID", comment: "Context menu option for copying the ID of the note."), systemImage: "tag")
Label(NSLocalizedString("Copy Note ID", comment: "Context menu option for copying the ID of the note."), systemImage: "note.text")
}
Button {
UIPasteboard.general.string = event_to_json(ev: event)
} label: {
Label(NSLocalizedString("Copy Note JSON", comment: "Context menu option for copying the JSON text from the note."), systemImage: "note")
Label(NSLocalizedString("Copy Note JSON", comment: "Context menu option for copying the JSON text from the note."), systemImage: "j.square.on.square")
}
Button {

View File

@@ -19,8 +19,7 @@ struct FollowButtonView: View {
follow_state = perform_follow_btn_action(follow_state, target: target)
} label: {
Text(follow_btn_txt(follow_state))
.frame(height: 30)
.padding(.horizontal, 25)
.frame(width: 105, height: 30)
//.padding(.vertical, 10)
.font(.caption.weight(.bold))
.foregroundColor(follow_state == .unfollows ? filledTextColor() : borderColor())

View File

@@ -24,7 +24,7 @@ struct FollowUserView: View {
VStack(alignment: .leading) {
let profile = damus_state.profiles.lookup(id: target.pubkey)
ProfileName(pubkey: target.pubkey, profile: profile, damus: damus_state, show_friend_confirmed: false)
ProfileName(pubkey: target.pubkey, profile: profile, damus: damus_state, show_friend_confirmed: false, show_nip5_domain: false)
if let about = profile?.about {
Text(FollowUserView.markdown.process(about))
.lineLimit(3)

View File

@@ -0,0 +1,20 @@
//
// ImageView.swift
// damus
//
// Created by user232838 on 1/5/23.
//
import SwiftUI
struct ImageView: View {
var body: some View {
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
}
}
struct ImageView_Previews: PreviewProvider {
static var previews: some View {
ImageView()
}
}

View File

@@ -0,0 +1,33 @@
//
// MagnificationGestureView.swift
// damus
//
// Created by user232838 on 1/5/23.
//
import SwiftUI
struct MagnificationGestureView: View {
@GestureState var magnifyBy = 1.0
var magnification: some Gesture {
MagnificationGesture()
.updating($magnifyBy) { currentState, gestureState, transaction in
gestureState = currentState
}
}
var body: some View {
Circle()
.frame(width: 100, height: 100)
.scaleEffect(magnifyBy)
.gesture(magnification)
}
}
struct MagnificationGestureView_Previews: PreviewProvider {
static var previews: some View {
MagnificationGestureView()
}
}

View File

@@ -31,9 +31,9 @@ func timeline_bit(_ timeline: Timeline) -> Int {
struct TabButton: View {
let timeline: Timeline
let img: String
@Binding var selected: Timeline?
@Binding var new_events: NewEventsBits
@Binding var isSidebarVisible: Bool
let action: (Timeline) -> ()
@@ -56,6 +56,7 @@ struct TabButton: View {
Button(action: {
action(timeline)
new_events = NewEventsBits(prev: new_events, unsetting: timeline)
isSidebarVisible = false
}) {
Label("", systemImage: selected == timeline ? "\(img).fill" : img)
.contentShape(Rectangle())
@@ -69,6 +70,7 @@ struct TabButton: View {
struct TabBar: View {
@Binding var new_events: NewEventsBits
@Binding var selected: Timeline?
@Binding var isSidebarVisible: Bool
let action: (Timeline) -> ()
@@ -76,10 +78,10 @@ struct TabBar: View {
VStack {
Divider()
HStack {
TabButton(timeline: .home, img: "house", selected: $selected, new_events: $new_events, action: action).keyboardShortcut("1")
TabButton(timeline: .dms, img: "bubble.left.and.bubble.right", selected: $selected, new_events: $new_events, action: action).keyboardShortcut("2")
TabButton(timeline: .search, img: "magnifyingglass.circle", selected: $selected, new_events: $new_events, action: action).keyboardShortcut("3")
TabButton(timeline: .notifications, img: "bell", selected: $selected, new_events: $new_events, action: action).keyboardShortcut("4")
TabButton(timeline: .home, img: "house", selected: $selected, new_events: $new_events, isSidebarVisible: $isSidebarVisible, action: action).keyboardShortcut("1")
TabButton(timeline: .dms, img: "bubble.left.and.bubble.right", selected: $selected, new_events: $new_events, isSidebarVisible: $isSidebarVisible, action: action).keyboardShortcut("2")
TabButton(timeline: .search, img: "magnifyingglass.circle", selected: $selected, new_events: $new_events, isSidebarVisible: $isSidebarVisible, action: action).keyboardShortcut("3")
TabButton(timeline: .notifications, img: "bell", selected: $selected, new_events: $new_events, isSidebarVisible: $isSidebarVisible, action: action).keyboardShortcut("4")
}
}
}

View File

@@ -53,7 +53,7 @@ func render_note_content(ev: NostrEvent, profiles: Profiles, privkey: String?) -
}
func is_image_url(_ url: URL) -> Bool {
let str = url.lastPathComponent
let str = url.lastPathComponent.lowercased()
return str.hasSuffix("png") || str.hasSuffix("jpg") || str.hasSuffix("jpeg") || str.hasSuffix("gif")
}
@@ -74,6 +74,7 @@ struct NoteContentView: View {
return VStack(alignment: .leading) {
Text(Markdown.parse(content: artifacts.content))
.font(eventviewsize_to_font(size))
.fixedSize(horizontal: false, vertical: true)
if show_images && artifacts.images.count > 0 {
ImageCarousel(urls: artifacts.images)
@@ -90,8 +91,8 @@ struct NoteContentView: View {
InvoicesView(invoices: artifacts.invoices)
}
if show_images, self.preview != nil {
self.preview
if let preview = self.preview, show_images {
preview
} else {
ForEach(artifacts.links, id:\.self) { link in
if let url = link {

View File

@@ -0,0 +1,79 @@
//
// ParicipantsView.swift
// damus
//
// Created by Joel Klabo on 1/18/23.
//
import SwiftUI
struct ParticipantsView: View {
let damus_state: DamusState
@Binding var references: [ReferencedId]
@Binding var originalReferences: [ReferencedId]
var body: some View {
VStack {
Text("Edit participants", comment: "Text indicating that the view is used for editing which participants are replied to in a note.")
HStack {
Spacer()
Button {
// Remove all "p" refs, keep "e" refs
references = originalReferences.eRefs
} label: {
Text("Remove all", comment: "Button label to remove all participants from a note reply.")
}
.buttonStyle(.borderedProminent)
Spacer()
Button {
references = originalReferences
} label: {
Text("Add all", comment: "Button label to re-add all original participants as profiles to reply to in a note")
}
.buttonStyle(.borderedProminent)
Spacer()
}
VStack {
ForEach(originalReferences.pRefs) { participant in
let pubkey = participant.id
HStack {
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles)
VStack(alignment: .leading) {
let profile = damus_state.profiles.lookup(id: pubkey)
ProfileName(pubkey: pubkey, profile: profile, damus: damus_state, show_friend_confirmed: false, show_nip5_domain: false)
if let about = profile?.about {
Text(FollowUserView.markdown.process(about))
.lineLimit(3)
.font(.footnote)
}
}
Spacer()
Image(systemName: "checkmark.circle.fill")
.font(.system(size: 30))
.foregroundColor(references.contains(participant) ? .purple : .gray)
}
.onTapGesture {
if references.contains(participant) {
references = references.filter {
$0 != participant
}
} else {
if references.contains(participant) {
// Don't add it twice
} else {
references.append(participant)
}
}
}
}
}
Spacer()
}
.padding()
}
}

View File

@@ -34,14 +34,20 @@ func PostButton(action: @escaping () -> ()) -> some View {
.keyboardShortcut("n", modifiers: [.command, .shift])
}
func PostButtonContainer(action: @escaping () -> ()) -> some View {
func PostButtonContainer(userSettings: UserSettingsStore, action: @escaping () -> Void) -> some View {
let is_left_handed = userSettings.left_handed.self
return VStack {
Spacer()
HStack {
Spacer()
PostButton(action: action)
if is_left_handed != true {
Spacer()
PostButton(action: action)
} else {
PostButton(action: action)
Spacer()
}
}
}
}

View File

@@ -30,24 +30,27 @@ struct ProfileName: View {
let prefix: String
let show_friend_confirmed: Bool
let show_nip5_domain: Bool
@State var display_name: String?
@State var nip05: NIP05?
init(pubkey: String, profile: Profile?, damus: DamusState, show_friend_confirmed: Bool) {
init(pubkey: String, profile: Profile?, damus: DamusState, show_friend_confirmed: Bool, show_nip5_domain: Bool = true) {
self.pubkey = pubkey
self.profile = profile
self.prefix = ""
self.show_friend_confirmed = show_friend_confirmed
self.show_nip5_domain = show_nip5_domain
self.damus_state = damus
}
init(pubkey: String, profile: Profile?, prefix: String, damus: DamusState, show_friend_confirmed: Bool) {
init(pubkey: String, profile: Profile?, prefix: String, damus: DamusState, show_friend_confirmed: Bool, show_nip5_domain: Bool = true) {
self.pubkey = pubkey
self.profile = profile
self.prefix = prefix
self.damus_state = damus
self.show_friend_confirmed = show_friend_confirmed
self.show_nip5_domain = show_nip5_domain
}
var friend_icon: String? {
@@ -68,10 +71,7 @@ struct ProfileName: View {
.font(.body)
.fontWeight(prefix == "@" ? .none : .bold)
if let nip05 = current_nip05 {
Image(systemName: "checkmark.seal.fill")
.foregroundColor(nip05_color)
Text(nip05.host)
.foregroundColor(nip05_color)
NIP05Badge(nip05: nip05, pubkey: pubkey, contacts: damus_state.contacts, show_domain: show_nip5_domain, clickable: true)
}
if let friend = friend_icon, current_nip05 == nil {
Image(systemName: friend)
@@ -137,7 +137,7 @@ struct EventProfileName: View {
.padding([.trailing], 2)
Text("@" + String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey)))
.foregroundColor(.gray)
.foregroundColor(Color("DamusMediumGrey"))
.font(eventviewsize_to_font(size))
} else {
Text(String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey)))
@@ -145,9 +145,8 @@ struct EventProfileName: View {
.fontWeight(.bold)
}
if let _ = current_nip05 {
Image(systemName: "checkmark.seal.fill")
.foregroundColor(get_nip05_color(pubkey: pubkey, contacts: damus_state.contacts))
if let nip05 = current_nip05 {
NIP05Badge(nip05: nip05, pubkey: pubkey, contacts: damus_state.contacts, show_domain: false, clickable: false)
}
if let frend = friend_icon, current_nip05 == nil {
@@ -167,6 +166,3 @@ struct EventProfileName: View {
}
}
func get_nip05_color(pubkey: String, contacts: Contacts) -> Color {
return contacts.is_friend_or_self(pubkey) ? .accentColor : .gray
}

View File

@@ -33,10 +33,23 @@ func pfp_line_width(_ h: Highlight) -> CGFloat {
}
struct InnerProfilePicView: View {
let url: URL?
let pubkey: String
let size: CGFloat
let highlight: Highlight
@ObservedObject var imageModel: KFImageModel
init(url: URL?, fallbackUrl: URL?, pubkey: String, size: CGFloat, highlight: Highlight) {
self.pubkey = pubkey
self.size = size
self.highlight = highlight
self.imageModel = KFImageModel(
url: url,
fallbackUrl: fallbackUrl,
maxByteSize: 1000000,
downsampleSize: CGSize(width: 200, height: 200)
)
}
var PlaceholderColor: Color {
return id_to_color(pubkey)
@@ -53,11 +66,13 @@ struct InnerProfilePicView: View {
var body: some View {
ZStack {
Color(uiColor: .systemBackground)
KFAnimatedImage(url)
KFAnimatedImage(imageModel.url)
.callbackQueue(.dispatch(.global(qos: .background)))
.processingQueue(.dispatch(.global(qos: .background)))
.appendProcessor(LargeImageProcessor())
.serialize(by: imageModel.serializer)
.setProcessor(imageModel.processor)
.cacheOriginalImage()
.configure { view in
view.framePreloadCount = 1
}
@@ -67,6 +82,10 @@ struct InnerProfilePicView: View {
.scaleFactor(UIScreen.main.scale)
.loadDiskFileSynchronously()
.fade(duration: 0.1)
.onFailure { _ in
imageModel.downloadFailed()
}
.id(imageModel.refreshID)
}
.frame(width: size, height: size)
.clipShape(Circle())
@@ -91,7 +110,7 @@ struct ProfilePicView: View {
}
var body: some View {
InnerProfilePicView(url: get_profile_url(picture: picture, pubkey: pubkey, profiles: profiles), pubkey: pubkey, size: size, highlight: highlight)
InnerProfilePicView(url: get_profile_url(picture: picture, pubkey: pubkey, profiles: profiles), fallbackUrl: URL(string: robohash(pubkey)), pubkey: pubkey, size: size, highlight: highlight)
.onReceive(handle_notify(.profile_updated)) { notif in
let updated = notif.object as! ProfileUpdate
@@ -106,33 +125,6 @@ struct ProfilePicView: View {
}
}
struct LargeImageProcessor: ImageProcessor {
let identifier: String = "com.damus.largeimageprocessor"
let maxSize: Int = 1000000
let downsampleSize = CGSize(width: 200, height: 200)
func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? {
switch item {
case .image(let image):
guard let data = image.kf.data(format: .unknown) else {
return nil
}
if data.count > maxSize {
return KingfisherWrapper.downsampledImage(data: data, to: downsampleSize, scale: options.scaleFactor)
}
return image
case .data(let data):
if data.count > maxSize {
return KingfisherWrapper.downsampledImage(data: data, to: downsampleSize, scale: options.scaleFactor)
}
return KFCrossPlatformImage(data: data)
}
}
}
func get_profile_url(picture: String?, pubkey: String, profiles: Profiles) -> URL {
let pic = picture ?? profiles.lookup(id: pubkey)?.picture ?? robohash(pubkey)
if let url = URL(string: pic) {
@@ -144,7 +136,7 @@ func get_profile_url(picture: String?, pubkey: String, profiles: Profiles) -> UR
func make_preview_profiles(_ pubkey: String) -> Profiles {
let profiles = Profiles()
let picture = "http://cdn.jb55.com/img/red-me.jpg"
let profile = Profile(name: "jb55", display_name: "William Casarin", about: "It's me", picture: picture, website: "https://jb55.com", lud06: nil, lud16: nil, nip05: "jb55.com")
let profile = Profile(name: "jb55", display_name: "William Casarin", about: "It's me", picture: picture, banner: "", website: "https://jb55.com", lud06: nil, lud16: nil, nip05: "jb55.com")
let ts_profile = TimestampedProfile(profile: profile, timestamp: 0)
profiles.add(id: pubkey, profile: ts_profile)
return profiles

View File

@@ -118,7 +118,8 @@ struct ProfileView: View {
@Environment(\.dismiss) var dismiss
@Environment(\.colorScheme) var colorScheme
@Environment(\.openURL) var openURL
// We just want to have a white "< Home" text here, however,
// setting the initialiser is causing issues, and it's late.
// Ref: https://blog.techchee.com/navigation-bar-title-style-color-and-custom-back-button-in-swiftui/
@@ -194,16 +195,15 @@ struct ProfileView: View {
var TopSection: some View {
ZStack(alignment: .top) {
GeometryReader { geo in
Image("profile-banner")
.resizable()
BannerImageView(pubkey: profile.pubkey, profiles: damus_state.profiles)
.aspectRatio(contentMode: .fill)
.frame(width: geo.size.width, height: 150)
.frame(width: geo.size.width, height: BANNER_HEIGHT)
.clipped()
ShareButton
.offset(x: geo.size.width - 80.0, y: 50.0 )
}
VStack(alignment: .leading) {
}.frame(height: BANNER_HEIGHT)
VStack(alignment: .leading, spacing: 8.0) {
let data = damus_state.profiles.lookup(id: profile.pubkey)
let pfp_size: CGFloat = 90.0
@@ -212,9 +212,8 @@ struct ProfileView: View {
.onTapGesture {
is_zoomed.toggle()
}
.sheet(isPresented: $is_zoomed) {
ProfilePicView(pubkey: profile.pubkey, size: zoom_size, highlight: .none, profiles: damus_state.profiles)
}
.fullScreenCover(isPresented: $is_zoomed) {
ProfileZoomView(pubkey: profile.pubkey, profiles: damus_state.profiles) }
.offset(y: -(pfp_size/2.0)) // Increase if set a frame
Spacer()
@@ -252,6 +251,10 @@ struct ProfileView: View {
Text(ProfileView.markdown.process(data?.about ?? ""))
.font(.subheadline)
if let url = data?.website_url {
WebsiteLink(url: url)
}
Divider()
HStack {
@@ -283,7 +286,7 @@ struct ProfileView: View {
if let relays = profile.relays {
NavigationLink(destination: UserRelaysView(state: damus_state, pubkey: profile.pubkey, relays: Array(relays.keys).sorted())) {
Text("\(Text("\(relays.keys.count)", comment: "Number of relay servers a user is connected.").font(.subheadline.weight(.medium))) \(Text("Relays", comment: "Part of a larger sentence to describe how many relay servers a user is connected.").font(.subheadline).foregroundColor(.gray))", comment: "Sentence composed of 2 variables to describe how many relay servers a user is connected. In source English, the first variable is the number of relay servers, and the second variable is 'Relays'.")
Text("\(Text("\(relays.keys.count)", comment: "Number of relay servers a user is connected.").font(.subheadline.weight(.medium))) \(Text(String(format: NSLocalizedString("relays_count", comment: "Part of a larger sentence to describe how many relay servers a user is connected."), relays.keys.count)).font(.subheadline).foregroundColor(.gray))", comment: "Sentence composed of 2 variables to describe how many relay servers a user is connected. In source English, the first variable is the number of relay servers, and the second variable is 'Relay' or 'Relays'.")
}
.buttonStyle(PlainButtonStyle())
}
@@ -297,13 +300,14 @@ struct ProfileView: View {
var FollowersCount: some View {
HStack {
if followers.count_display == "?" {
if followers.count == nil {
Image(systemName: "square.and.arrow.down")
Text("Followers", comment: "Label describing followers of a user.")
.font(.subheadline)
.foregroundColor(.gray)
} else {
Text("\(Text("\(followers.count_display)", comment: "Number of people following a user.").font(.subheadline.weight(.medium))) \(Text("Followers", comment: "Part of a larger sentence to describe how many people are following a user.").font(.subheadline).foregroundColor(.gray))", comment: "Sentence composed of 2 variables to describe how many people are following a user. In source English, the first variable is the number of followers, and the second variable is 'Followers'.")
let followerCount = followers.count!
Text("\(Text("\(followerCount)", comment: "Number of people following a user.").font(.subheadline.weight(.medium))) \(Text(String(format: NSLocalizedString("followers_count", comment: "Part of a larger sentence to describe how many people are following a user."), followerCount)).font(.subheadline).foregroundColor(.gray))", comment: "Sentence composed of 2 variables to describe how many people are following a user. In source English, the first variable is the number of followers, and the second variable is 'Follower' or 'Followers'.")
}
}
}
@@ -356,9 +360,9 @@ struct ProfileView_Previews: PreviewProvider {
func test_damus_state() -> DamusState {
let pubkey = "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"
let damus = DamusState(pool: RelayPool(), keypair: Keypair(pubkey: pubkey, privkey: "privkey"), likes: EventCounter(our_pubkey: pubkey), boosts: EventCounter(our_pubkey: pubkey), contacts: Contacts(our_pubkey: pubkey), tips: TipCounter(our_pubkey: pubkey), profiles: Profiles(), dms: DirectMessagesModel(), previews: PreviewCache())
let damus = DamusState(pool: RelayPool(), keypair: Keypair(pubkey: pubkey, privkey: "privkey"), likes: EventCounter(our_pubkey: pubkey), boosts: EventCounter(our_pubkey: pubkey), contacts: Contacts(our_pubkey: pubkey), tips: TipCounter(our_pubkey: pubkey), profiles: Profiles(), dms: DirectMessagesModel(our_pubkey: pubkey), previews: PreviewCache())
let prof = Profile(name: "damus", display_name: "damus", about: "iOS app!", picture: "https://damus.io/img/logo.png", website: "https://damus.io", lud06: nil, lud16: "jb55@sendsats.lol", nip05: "damus.io")
let prof = Profile(name: "damus", display_name: "damus", about: "iOS app!", picture: "https://damus.io/img/logo.png", banner: "", website: "https://damus.io", lud06: nil, lud16: "jb55@sendsats.lol", nip05: "damus.io")
let tsprof = TimestampedProfile(profile: prof, timestamp: 0)
damus.profiles.add(id: pubkey, profile: tsprof)
return damus

View File

@@ -0,0 +1,97 @@
//
// ProfileZoomView.swift
// damus
//
// Created by scoder1747 on 12/27/22.
//
import SwiftUI
struct ProfileZoomView: View {
@Environment(\.presentationMode) var presentationMode
let pubkey: String
let profiles: Profiles
@GestureState private var scaleState: CGFloat = 1
@GestureState private var offsetState = CGSize.zero
@State private var offset = CGSize.zero
@State private var scale: CGFloat = 1
func resetStatus(){
self.offset = CGSize.zero
self.scale = 1
}
var zoomGesture: some Gesture {
MagnificationGesture()
.updating($scaleState) { currentState, gestureState, _ in
gestureState = currentState
}
.onEnded { value in
scale *= value
}
}
var dragGesture: some Gesture {
DragGesture()
.updating($offsetState) { currentState, gestureState, _ in
gestureState = currentState.translation
}.onEnded { value in
offset.height += value.translation.height
offset.width += value.translation.width
}
}
var doubleTapGesture : some Gesture {
TapGesture(count: 2).onEnded { value in
resetStatus()
}
}
var body: some View {
ZStack(alignment: .topLeading) {
Color("DamusDarkGrey") // Or Color("DamusBlack")
.edgesIgnoringSafeArea(.all)
Button {
presentationMode.wrappedValue.dismiss()
} label: {
Image(systemName: "xmark")
.foregroundColor(.white)
.font(.subheadline)
.padding(.leading, 20)
}
.zIndex(1)
VStack(alignment: .center) {
Spacer()
ProfilePicView(pubkey: pubkey, size: 200.0, highlight: .none, profiles: profiles)
.padding(100)
.scaledToFit()
.scaleEffect(self.scale * scaleState)
.offset(x: offset.width + offsetState.width, y: offset.height + offsetState.height)
.gesture(SimultaneousGesture(zoomGesture, dragGesture))
.gesture(doubleTapGesture)
.modifier(SwipeToDismissModifier(minDistance: nil, onDismiss: {
presentationMode.wrappedValue.dismiss()
}))
Spacer()
}
}
}
}
struct ProfileZoomView_Previews: PreviewProvider {
static let pubkey = "ca48854ac6555fed8e439ebb4fa2d928410e0eef13fa41164ec45aaaa132d846"
static var previews: some View {
ProfileZoomView(
pubkey: pubkey,
profiles: make_preview_profiles(pubkey))
}
}

View File

@@ -14,7 +14,7 @@ struct PubkeyView: View {
var body: some View {
let color: Color = id_to_color(pubkey)
ZStack {
Text("\(abbrev_pubkey(pubkey))")
Text("\(abbrev_pubkey(pubkey))", comment: "Abbreviated version of a nostr public key.")
.foregroundColor(color)
}
}

View File

@@ -0,0 +1,36 @@
//
// ReactionView.swift
// damus
//
// Created by William Casarin on 2023-01-11.
//
import SwiftUI
struct ReactionView: View {
let damus_state: DamusState
let reaction: NostrEvent
var content: String {
if reaction.content == "" || reaction.content == "+" {
return "❤️"
}
return reaction.content
}
var body: some View {
HStack {
Text(content)
.font(Font.headline)
.frame(width: 50, height: 50)
FollowUserView(target: .pubkey(reaction.pubkey), damus_state: damus_state)
}
}
}
struct ReactionView_Previews: PreviewProvider {
static var previews: some View {
ReactionView(damus_state: test_damus_state(), reaction: NostrEvent(id: "", content: "🤙🏼", pubkey: ""))
}
}

View File

@@ -0,0 +1,38 @@
//
// ReactionsView.swift
// damus
//
// Created by William Casarin on 2023-01-11.
//
import SwiftUI
struct ReactionsView: View {
let damus_state: DamusState
@StateObject var model: ReactionsModel
var body: some View {
ScrollView {
LazyVStack {
ForEach(model.reactions, id: \.id) { ev in
ReactionView(damus_state: damus_state, reaction: ev)
}
}
.padding()
}
.navigationBarTitle(NSLocalizedString("Reactions", comment: "Navigation bar title for Reactions view."))
.onAppear {
model.subscribe()
}
.onDisappear {
model.unsubscribe()
}
}
}
struct ReactionsView_Previews: PreviewProvider {
static var previews: some View {
let state = test_damus_state()
ReactionsView(damus_state: state, model: ReactionsModel(state: state, target: "pubkey"))
}
}

View File

@@ -28,14 +28,17 @@ struct RecommendedRelayView: View {
HStack {
Text(relay)
Spacer()
if let ev = damus.contacts.event, add_button {
if add_button {
if let privkey = damus.keypair.privkey {
Button(NSLocalizedString("Add", comment: "Button to add recommended relay server.")) {
guard let ev = add_relay(ev: ev, privkey: privkey, current_relays: damus.pool.descriptors, relay: relay, info: .rw) else {
guard let ev_before_add = damus.contacts.event else {
return
}
process_contact_event(pool: damus.pool, contacts: damus.contacts, pubkey: damus.pubkey, ev: ev)
damus.pool.send(.event(ev))
guard let ev_after_add = add_relay(ev: ev_before_add, privkey: privkey, current_relays: damus.pool.descriptors, relay: relay, info: .rw) else {
return
}
process_contact_event(pool: damus.pool, contacts: damus.contacts, pubkey: damus.pubkey, ev: ev_after_add)
damus.pool.send(.event(ev_after_add))
}
}
}

View File

@@ -28,7 +28,7 @@ struct ReplyQuoteView: View {
ProfilePicView(pubkey: event.pubkey, size: 16, highlight: .reply, profiles: profiles)
Text(Profile.displayName(profile: profiles.lookup(id: event.pubkey), pubkey: event.pubkey))
.foregroundColor(.accentColor)
Text("\(format_relative_time(event.created_at))")
Text("\(format_relative_time(event.created_at))", comment: "Amount of time that has passed since reply quote event occurred.")
.foregroundColor(.gray)
}

View File

@@ -18,11 +18,16 @@ struct ReplyView: View {
let replying_to: NostrEvent
let damus: DamusState
@State var originalReferences: [ReferencedId] = []
@State var references: [ReferencedId] = []
@State var participantsShown: Bool = false
var body: some View {
VStack {
Text("Replying to:", comment: "Indicating that the user is replying to the following listed people.")
HStack(alignment: .top) {
let names = all_referenced_pubkeys(replying_to)
let names = references.pRefs
.map { pubkey in
let pk = pubkey.ref_id
let prof = damus.profiles.lookup(id: pk)
@@ -33,13 +38,22 @@ struct ReplyView: View {
.foregroundColor(.gray)
.font(.footnote)
}
ScrollView {
EventView(event: replying_to, highlight: .none, has_action_bar: false, damus: damus, show_friend_icon: true)
.onTapGesture {
participantsShown.toggle()
}
PostView(replying_to: replying_to, references: gather_reply_ids(our_pubkey: damus.pubkey, from: replying_to))
.sheet(isPresented: $participantsShown) {
ParticipantsView(damus_state: damus, references: $references, originalReferences: $originalReferences)
}
ScrollView {
EventView(event: replying_to, highlight: .none, has_action_bar: false, damus: damus, show_friend_icon: true)
}
PostView(replying_to: replying_to, references: references)
}
.onAppear {
references = gather_reply_ids(our_pubkey: damus.pubkey, from: replying_to)
originalReferences = references
}
.padding()
}
@@ -47,6 +61,6 @@ struct ReplyView: View {
struct ReplyView_Previews: PreviewProvider {
static var previews: some View {
ReplyView(replying_to: NostrEvent(content: "hi", pubkey: "pubkey"), damus: test_damus_state())
ReplyView(replying_to: NostrEvent(content: "hi", pubkey: "pubkey"), damus: test_damus_state(), references: [])
}
}

View File

@@ -63,7 +63,7 @@ struct SaveKeysView: View {
} else if let err = error {
Text("Error: \(err)", comment: "Error message indicating why saving keys failed.")
.foregroundColor(.red)
DamusWhiteButton("Retry") {
DamusWhiteButton(NSLocalizedString("Retry", comment: "Button to retry completing account creation after an error occurred.")) {
complete_account_creation(account)
}
} else {

View File

@@ -42,6 +42,7 @@ struct SearchView_Previews: PreviewProvider {
let test_state = test_damus_state()
let filter = NostrFilter.filter_hashtag(["bitcoin"])
let pool = test_state.pool
let model = SearchModel(pool: pool, search: filter)
SearchView(appstate: test_state, search: model)

View File

@@ -64,7 +64,7 @@ struct SetupView: View {
self.state = .create_account
}
Button("Login") {
Button(NSLocalizedString("Login", comment: "Button to log into an account.")) {
self.state = .login
}
.padding([.top, .bottom], 20)

View File

@@ -0,0 +1,181 @@
//
// SideMenuView.swift
// damus
//
// Created by Ben Weeks on 1/6/23.
// Ref: https://blog.logrocket.com/create-custom-collapsible-sidebar-swiftui/
import SwiftUI
struct SideMenuView: View {
let damus_state: DamusState
@Binding var isSidebarVisible: Bool
@State var confirm_logout: Bool = false
@StateObject var user_settings = UserSettingsStore()
@Environment(\.colorScheme) var colorScheme
var sideBarWidth = min(UIScreen.main.bounds.size.width * 0.65, 400.0)
func fillColor() -> Color {
colorScheme == .light ? Color("DamusWhite") : Color("DamusBlack")
}
func textColor() -> Color {
colorScheme == .light ? Color("DamusBlack") : Color("DamusWhite")
}
var body: some View {
ZStack {
GeometryReader { _ in
EmptyView()
}
.background(Color("DamusDarkGrey").opacity(0.6))
.opacity(isSidebarVisible ? 1 : 0)
.animation(.easeInOut.delay(0.2), value: isSidebarVisible)
.onTapGesture {
isSidebarVisible.toggle()
}
content
}
.edgesIgnoringSafeArea(.all)
}
var content: some View {
HStack(alignment: .top) {
ZStack(alignment: .top) {
fillColor()
VStack(alignment: .leading, spacing: 20) {
let profile = damus_state.profiles.lookup(id: damus_state.pubkey)
let followers = FollowersModel(damus_state: damus_state, target: damus_state.pubkey)
let profile_model = ProfileModel(pubkey: damus_state.pubkey, damus: damus_state)
NavigationLink(destination: ProfileView(damus_state: damus_state, profile: profile_model, followers: followers)) {
if let picture = damus_state.profiles.lookup(id: damus_state.pubkey)?.picture {
ProfilePicView(pubkey: damus_state.pubkey, size: 60, highlight: .none, profiles: damus_state.profiles, picture: picture)
} else {
Image(systemName: "person.fill")
}
VStack(alignment: .leading) {
if let display_name = profile?.display_name {
Text(display_name)
.foregroundColor(textColor())
.font(.title)
}
if let name = profile?.name {
Text("@" + name)
.foregroundColor(Color("DamusMediumGrey"))
.font(.body)
}
}
}
Divider()
.padding(.trailing,40)
/*
HStack(alignment: .bottom) {
Text("69,420")
.foregroundColor(.accentColor)
.font(.largeTitle)
Text("SATS")
.font(.caption)
.padding(.bottom,6)
}
Divider()
.padding(.trailing,40)
*/
// THERE IS A LIMIT OF 10 NAVIGATIONLINKS!!! (Consider some in other views)
NavigationLink(destination: ProfileView(damus_state: damus_state, profile: profile_model, followers: followers)) {
Label(NSLocalizedString("Profile", comment: "Sidebar menu label for Profile view."), systemImage: "person")
.font(.title2)
.foregroundColor(textColor())
}
.simultaneousGesture(TapGesture().onEnded {
isSidebarVisible = false
})
/*
NavigationLink(destination: EmptyView()) {
Label(NSLocalizedString("Relays", comment: "Sidebar menu label for Relay servers view"), systemImage: "xserve")
.font(.title2)
.foregroundColor(textColor())
}
.simultaneousGesture(TapGesture().onEnded {
isSidebarVisible.toggle()
})
*/
/*
NavigationLink(destination: EmptyView()) {
Label(NSLocalizedString("Wallet", comment: "Sidebar menu label for Wallet view."), systemImage: "bolt")
.font(.title2)
.foregroundColor(textColor())
}
.simultaneousGesture(TapGesture().onEnded {
isSidebarVisible.toggle()
})
*/
NavigationLink(destination: ConfigView(state: damus_state).environmentObject(user_settings)) {
Label(NSLocalizedString("Settings", comment: "Sidebar menu label for accessing the app settings"), systemImage: "gear")
.font(.title2)
.foregroundColor(textColor())
}
.simultaneousGesture(TapGesture().onEnded {
isSidebarVisible = false
})
Spacer()
Button(action: {
//ConfigView(state: damus_state)
if damus_state.keypair.privkey == nil {
notify(.logout, ())
} else {
confirm_logout = true
}
}, label: {
Label(NSLocalizedString("Sign out", comment: "Sidebar menu label to sign out of the account."), systemImage: "pip.exit")
.font(.title3)
.foregroundColor(textColor())
})
}
.padding(.top, 60)
.padding(.bottom, 40)
.padding(.leading, 40)
}
.frame(width: sideBarWidth)
.offset(x: isSidebarVisible ? 0 : -sideBarWidth)
.animation(.default, value: isSidebarVisible)
.onTapGesture {
isSidebarVisible.toggle()
}
.alert("Logout", isPresented: $confirm_logout) {
Button(NSLocalizedString("Cancel", comment: "Cancel out of logging out the user.")) {
confirm_logout = false
}
Button(NSLocalizedString("Logout", comment: "Button for logging out the user.")) {
notify(.logout, ())
}
} message: {
Text("Make sure your nsec account key is saved before you logout or you will lose access to this account", comment: "Reminder message in alert to get customer to verify that their private security account key is saved saved before logging out.")
}
Spacer()
}
}
}
struct Previews_SideMenuView_Previews: PreviewProvider {
static var previews: some View {
let ds = test_damus_state()
SideMenuView(damus_state: ds, isSidebarVisible: .constant(true))
}
}

View File

@@ -52,6 +52,7 @@ struct InnerTimelineView: View {
}
}
.padding(.horizontal)
.padding(.top,10)
}
}

View File

@@ -14,10 +14,7 @@ struct damusApp: App {
WindowGroup {
MainView()
}
}
}
struct MainView: View {

View File

@@ -0,0 +1,9 @@
/* Bundle display name */
"CFBundleDisplayName" = "Damus";
/* Bundle name */
"CFBundleName" = "damus";
/* Privacy - Photo Library Additions Usage Description */
"NSPhotoLibraryAddUsageDescription" = "“Damus Zugriff auf deine Fotomediathek zu gewähren, erlaubt dir Fotos zu sichern.";

View File

@@ -0,0 +1,493 @@
/* Blank space to separate profile picture from profile editor form. */
" " = "61b6edf1108e6f396680a33b02486a70_tr";
/* Description of how the nip05 identifier would be used for verification. */
"'%@' at '%@' will be used for verification" = "'%@' bei '%@' wird zur Verifizierung benutzt werden.";
/* Description of why the nip05 identifier is invalid. */
"'%@' is an invalid nip05 identifier. It should look like an email." = "'%@' ist eine ungültige nip05 Kennzeichnung. Diese sollte wie eine Emailadresse aussehen. ";
/* Navigation bar title for view that shows who is following a user. */
"(Profile.displayName(profile: profile, pubkey: whos))'s Followers" = "(Profile.displayName(profile: profile, pubkey: whos)) Follower";
/* Navigation bar title for view that shows who a user is following. */
"(who) following" = "(who) folgt";
/* Prefix character to username. */
"@" = "@";
/* Amount of time that has passed since reply quote event occurred.
Abbreviated version of a nostr public key. */
"%@" = "%@";
/* Sentence composed of 2 variables to describe how many reposts. In source English, the first variable is the number of reposts, and the second variable is 'Repost' or 'Reposts'.
Sentence composed of 2 variables to describe how many profiles a user is following. In source English, the first variable is the number of profiles being followed, and the second variable is 'Following'. */
"%@ %@" = "%@ %@";
/* Explanation of what is done to keep personally identifiable information private. There is a heading that precedes this explanation which is a variable to this string. */
"%@. Creating an account doesn't require a phone number, email or name. Get started right away with zero friction." = "%@. Ein Konto zu erstellen benötigt keine Telefonnummer, Emailadresse oder Name. Fang jetzt sofort ganz reibungslos an.";
/* Explanation of what is done to keep private data encrypted. There is a heading that precedes this explanation which is a variable to this string. */
"%@. End-to-End encrypted private messaging. Keep Big Tech out of your DMs" = "%@. End-zu-End verschlüsselter privater Nachrichtenaustausch. Halte Tech-Riesen aus deinen PNs heraus";
/* Explanation of what can be done by users to earn money. There is a heading that precedes this explanation which is a variable to this string. */
"%@. Tip your friend's posts and stack sats with Bitcoin⚡, the native currency of the internet." = "%@. Belohne Beiträge deiner Freunde und sammle Sats mit Bitcoin⚡, der eigenen Währung des Internets.";
/* Number of reposts.
Number of profiles a user is following. */
"%lld" = "%lld";
/* Fraction of how many of the user's relay servers that are operational. */
"%lld/%lld" = "%lld/%lld";
/* Placeholder for event mention. */
"< e >" = "< e >";
/* Label to prompt for about text entry for user to describe about themself. */
"About" = "Über";
/* Label for About Me section of user profile form. */
"About Me" = "Über mich";
/* Placeholder text for About Me description. */
"Absolute Boss" = "Absoluter Macher";
/* Label to indicate the public ID of the account. */
"Account ID" = "Konto ID";
/* Button to add recommended relay server.
Button to confirm adding user inputted relay. */
"Add" = "Hinzufügen";
/* Label for section for adding a relay server. */
"Add Relay" = "Relay hinzufügen";
/* Any amount of sats */
"Any" = "beliebig";
/* Alert message to ask if user wants to repost a post. */
"Are you sure you want to repost this?" = "Bist du sicher dass Du den Beitrag auf deinem Profil teilen möchtest?";
/* Label for Banner Image section of user profile form. */
"Banner Image" = "Bannerbild";
/* Reminder to user that they should save their account information. */
"Before we get started, you'll need to save your account info, otherwise you won't be able to login in the future if you ever uninstall Damus." = "Bevor wir anfangen, Du wirst deine Kontodaten sichern müssen, sonst wirst du dich in der Zukunft nicht mehr anmelden können wenn du Damus jemals deinstallierst.";
/* Dropdown option label for Lightning wallet, Bitcoin Beach. */
"Bitcoin Beach" = "Bitcoin Beach";
/* Label for Bitcoin Lightning Tips section of user profile form. */
"Bitcoin Lightning Tips" = "Bitcoin Lightning Zahlungen";
/* Dropdown option label for Lightning wallet, Blixt Wallet */
"Blixt Wallet" = "Blixt Wallet";
/* Dropdown option label for Lightning wallet, Blue Wallet. */
"Blue Wallet" = "Blue Wallet";
/* Dropdown option label for Lightning wallet, Breez. */
"Breez" = "Breez";
/* Context menu option for broadcasting the user's note to all of the user's connected relay servers. */
"Broadcast" = "Senden";
/* Button to cancel out of posting a note.
Button to cancel out of reposting a post.
Button to cancel out of view adding user inputted relay.
Cancel out of logging out the user. */
"Cancel" = "Abbrechen";
/* Dropdown option label for Lightning wallet, Cash App. */
"Cash App" = "Cash App";
/* Navigation bar title for Chatroom view. */
"Chat" = "Unterhaltungen";
/* Button for clearing cached data. */
"Clear" = "Löschen";
/* Section title for clearing cached data. */
"Clear Cache" = "Zwischenspeicher löschen";
/* Label indicating that a user's key was copied. */
"Copied" = "Kopiert";
/* Button to copy a relay server address. */
"Copy" = "Kopieren";
/* Context menu option for copying the ID of the account that created the note. */
"Copy Account ID" = "Konto ID kopieren";
/* Context menu option to copy an image into clipboard.
Context menu option to copy an image to clipboard. */
"Copy Image" = "Bild kopieren";
/* Context menu option to copy the URL of an image into clipboard. */
"Copy Image URL" = "Bild URL kopieren";
/* Title of section for copying a Lightning invoice identifier. */
"Copy invoice" = "Zahlungsdaten kopieren";
/* Context menu option for copying a user's Lightning URL. */
"Copy LNURL" = "LNURL kopieren";
/* Context menu option for copying the ID of the note. */
"Copy Note ID" = "Notiz ID kopieren";
/* Context menu option for copying the JSON text from the note. */
"Copy Note JSON" = "Notiz JSON kopieren";
/* Context menu option for copying the text from an note. */
"Copy Text" = "Text kopieren";
/* Context menu option for copying the ID of the user who created the note. */
"Copy User ID" = "Benutzer ID kopieren";
/* Button to create account. */
"Create" = "Erstellen";
/* Button to create an account. */
"Create Account" = "Konto erstellen";
/* Example description about Bitcoin creator(s), Satoshi Nakamoto. */
"Creator(s) of Bitcoin. Absolute legend." = "Erfinder von Bitcoin. Absolute Legende(n).";
/* Name of the app, shown on the first screen when user is not logged in. */
"Damus" = "Damus";
/* Button to pay a Lightning invoice with the user's default Lightning wallet. */
"Default Wallet" = "Voreingestelltes Wallet";
/* Button to delete a relay server that the user connects to. */
"Delete" = "Löschen";
/* Button to dismiss a text field alert. */
"Dismiss" = "Schließen";
/* Label to prompt display name entry. */
"Display Name" = "Profilname";
/* Navigation title for DM view, which is the English abbreviation for Direct Message. */
"DM" = "PN";
/* Button to dismiss wallet selection view for paying Lightning invoice. */
"Done" = "Fertig";
/* Heading indicating that this application allows users to earn money. */
"Earn Money" = "Verdiene Geld";
/* Button to edit user's profile. */
"Edit" = "Bearbeiten";
/* Heading indicating that this application keeps private messaging end-to-end encrypted. */
"Encrypted" = "Verschlüsselt";
/* Navigation title for view of encrypted DMs, where DM is an English abbreviation for Direct Message. */
"Encrypted DMs" = "Verschlüsselte PNs";
/* Prompt for user to enter an account key to login. */
"Enter your account key to login:" = "Gib deinen Kontoschlüssel ein um dich anzumelden:";
/* Error message indicating why saving keys failed. */
"Error: %@" = "Fehler: %@";
/* Filter state for seeing either only posts, or posts & replies. */
"Filter State" = "Filter Einstellung";
/* Button to follow a user. */
"Follow" = "Folgen";
/* Label describing followers of a user. */
"Followers" = "Follower";
/* Text to indicate that the button next to it is in a state that indicates that it is in the process of following a profile.
Part of a larger sentence to describe how many profiles a user is following. */
"Following" = "Folgt";
/* Label to indicate that the user is in the process of following another user. */
"Following..." = "Folge…";
/* Text to indicate that button next to it is in a state that will follow a profile when tapped. */
"Follows" = "Folgt";
/* Navigation bar title for Global view where posts from all connected relay servers appear. */
"Global" = "Weltweit";
/* Navigation link to go to post referenced by hex code. */
"Goto post %@" = "Gehe zum Beitrag %@";
/* Navigation link to go to profile. */
"Goto profile %@" = "Gehe zum Profil %@";
/* Navigation bar title for Home view where posts and replies appear from those who the user is following. */
"Home" = "Heim";
/* Placeholder example text for profile picture URL. */
"https://example.com/pic.jpg" = "https://beispiel.at/bild.jpg";
/* Placeholder example text for website URL for user profile. */
"https://jb55.com" = "https://jb55.com";
/* Error message indicating that an invalid account key was entered for login. */
"Invalid key" = "Ungültiger Schlüssel";
/* Placeholder example text for identifier used for NIP-05 verification. */
"jb55@jb55.com" = "jb55@jb55.com";
/* Moves the post button to the left side of the screen */
"Left Handed" = "Linkshändig";
/* Button to complete account creation and start using the app. */
"Let's go!" = "Lass uns loslegen!";
/* Placeholder text for entry of Lightning Address or LNURL. */
"Lightning Address or LNURL" = "Lightning-Adresse oder LNURL";
/* Indicates that the view is for paying a Lightning invoice. */
"Lightning Invoice" = "Lightning-Rechnung";
/* Dropdown option label for Lightning wallet, LNLink. */
"LNLink" = "LNLink";
/* Dropdown option label for system default for Lightning wallet. */
"Local default" = "System-Standard";
/* Button to log into account.
Button to log into an account. */
"Login" = "Einloggen";
/* Alert for logging out the user.
Button for logging out the user.
Button to logout the user. */
"Logout" = "Ausloggen";
/* Reminder message in alert to get customer to verify that their private security account key is saved saved before logging out. */
"Make sure your nsec account key is saved before you logout or you will lose access to this account" = "Stelle sicher, dass du deinen nsec-Schlüssel gespeichert hast, sonst wirst du den Zugang zu deinem Konto verlieren.";
/* Dropdown option label for Lightning wallet, Muun. */
"Muun" = "Muun";
/* Label for NIP-05 Verification section of user profile form. */
"NIP-05 Verification" = "NIP-05-Verifizierung";
/* No search results. */
"none" = "keine";
/* Indicates that there are no notes in the timeline to view. */
"Nothing to see here. Check back later!" = "Hier gibt es nichts zu sehen. Komm später wieder vorbei!";
/* Navigation title for notifications. */
"Notifications" = "Benachrichtigungen";
/* String indicating that a given timestamp just occurred */
"now" = "jetzt";
/* Prompt for user to enter in an account key to login. This text shows the characters the key could start with if it was a private key. */
"nsec1..." = "nsec1...";
/* Label indicating that a form input is optional. */
"optional" = "optional";
/* Button to pay a Lightning invoice. */
"Pay" = "Bezahlen";
/* Navigation bar title for view to pay Lightning invoice. */
"Pay the Lightning invoice" = "Bezahle die Lightning-Rechnung";
/* Dropdown option label for Lightning wallet, Phoenix. */
"Phoenix" = "Phoenix";
/* Button to post a note. */
"Post" = "Veröffentlichen";
/* Label for filter for seeing only posts (instead of posts and replies). */
"Posts" = "Beiträge";
/* Label for filter for seeing posts and replies (instead of only posts). */
"Posts & Replies" = "Beiträge & Antworten";
/* Heading indicating that this application keeps personally identifiable information private. A sentence describing what is done to keep data private comes after this heading. */
"Private" = "Privat";
/* Label to indicate that the text below is the user's private key used by only the user themself as a secret to login to access their account. */
"Private Key" = "Privater Schlüssel";
/* Title of the secure field that holds the user's private key. */
"PrivateKey" = "Privater Schlüssel";
/* Sidebar menu label for Profile view. */
"Profile" = "Profil";
/* Label for Profile Picture section of user profile form. */
"Profile Picture" = "Profilbild";
/* Section title for the user's public account ID. */
"Public Account ID" = "Öffentliche Konto ID";
/* Label indicating that the text is a user's public account key. */
"Public key" = "Öffentlicher Schlüssel";
/* Label indicating that the text is a user's public account key. */
"Public Key" = "Öffentlicher Schlüssel";
/* Prompt to ask user if the key they entered is a public key. */
"Public Key?" = "Öffentlicher Schlüssel?";
/* Navigation bar title for Reactions view. */
"Reactions" = "Reaktionen";
/* Section title for recommend relay servers that could be added as part of configuration */
"Recommended Relays" = "Empfohlene Relays";
/* Text field for relay server. Used for testing purposes. */
"Relay" = "Relay";
/* Sidebar menu label for Relay servers view */
"Relays" = "Relays";
/* Label to indicate that the user is replying to themself. */
"Reply to self" = "Antwort an sich selbst";
/* Label to indicate that the user is replying to 2 users. */
"Replying to %@ & %@" = "Antwort an %1$@ & %2$@";
/* Indicating that the user is replying to the following listed people. */
"Replying to:" = "Antwort an:";
/* Button to confirm reposting a post.
Title of alert for confirming to repost a post. */
"Repost" = "Teilen";
/* Text indicating that the post was reposted (i.e. re-shared). */
"Reposted" = "Geteilt";
/* Section title for resetting the user */
"Reset" = "Zurücksetzen";
/* Button to retry completing account creation after an error occurred. */
"Retry" = "Erneut versuchen";
/* Dropdown option label for Lightning wallet, River */
"River" = "River";
/* Example username of Bitcoin creator(s), Satoshi Nakamoto. */
"satoshi" = "satoshi";
/* Name of Bitcoin creator(s). */
"Satoshi Nakamoto" = "Satoshi Nakamoto";
/* Button for saving profile. */
"Save" = "Speichern";
/* Context menu option to save an image. */
"Save Image" = "Bild sichern";
/* Navigation link to search hashtag. */
"Search hashtag: #%@" = "Hashtag suchen: #%@";
/* Placeholder text to prompt entry of search query. */
"Search..." = "Suchen...";
/* Section title for user's secret account login key. */
"Secret Account Login Key" = "Geheimer Konto Anmeldeschlüssel";
/* Title of section for selecting a Lightning wallet to pay a Lightning invoice. */
"Select a Lightning wallet" = "Wähle ein Lightning Wallet";
/* Prompt selection of user's default wallet */
"Select default wallet" = "Wähle das voreingestellte Wallet";
/* Text prompt for user to send a message to the other user. */
"Send a message to start the conversation..." = "Sende eine Nachricht um eine Unterhaltung zu beginnen...";
/* Navigation title for Settings view.
Sidebar menu label for accessing the app settings */
"Settings" = "Einstellungen";
/* Button to share an image. */
"Share" = "Teilen";
/* Toggle to show or hide user's secret account login key. */
"Show" = "Anzeigen";
/* Toggle to show or hide selection of wallet. */
"Show wallet selector" = "Wallet-Auswahl zeigen";
/* Sidebar menu label to sign out of the account. */
"Sign out" = "Abmelden";
/* Dropdown option label for Lightning wallet, Strike. */
"Strike" = "Strike";
/* Warning that the inputted account key is a public key and the result of what happens because of it. */
"This is a public key, you will not be able to make posts or interact in any way. This is used for viewing accounts from their perspective." = "Dies ist ein öffentlicher Schlüssel, mit dem Sie keine Beiträge verfassen oder in irgendeiner Weise interagieren können. Er wird verwendet, um Konten aus deren Perspektive zu betrachten.";
/* Warning that the inputted account key for login is an old-style and asking user to verify if it is a public key. */
"This is an old-style nostr key. We're not sure if it's a pubkey or private key. Please toggle the button below if this a public key." = "Dies ist ein Nostr-Schlüssel im alten Format. Es ist nicht eindeutig, ob es ein öffentlicher oder privater Schlüssel ist. Bitte aktiviere die Schaltfläche unten, wenn es ein öffentlicher Schlüssel ist.";
/* Label to describe that a public key is the user's account ID and what they can do with it. */
"This is your account ID, you can give this to your friends so that they can follow you. Click to copy." = "Dies ist deine Konto-ID, die du an deine Freunde weitergeben kannst, damit sie dir folgen können. Zum Kopieren anklicken.";
/* Label to describe that a private key is the user's secret account key and what they should do with it. */
"This is your secret account key. You need this to access your account. Don't share this with anyone! Save it in a password manager and keep it safe!" = "Dies ist dein geheimer, privater Schlüssel. Du benötigst ihn, um auf dein Konto zuzugreifen. Gib den privaten Schlüssel an niemanden weiter! Speichere ihn in einem Passwort-Manager und bewahre ihn sicher auf!";
/* Navigation bar title for note thread.
Navigation bar title for threaded event detail view. */
"Thread" = "Thema";
/* Text box prompt to ask user to type their post. */
"Type your post here..." = "Schreibe deinen Beitrag hier...";
/* Non-breaking space character to fill in blank space next to event action button icons. */
"u{00A0}" = "u{00A0}";
/* Button to unfollow a user. */
"Unfollow" = "Entfolgen";
/* Text to indicate that the button next to it is in a state that indicates that it is in the process of unfollowing a profile. */
"Unfollowing" = "Entfolgen...";
/* Label to indicate that the user is in the process of unfollowing another user. */
"Unfollowing..." = "Entfolgen...";
/* Text to indicate that the button next to it is in a state that will unfollow a profile when tapped. */
"Unfollows" = "Entfolgen";
/* Label for Username section of user profile form.
Label to prompt username entry. */
"Username" = "Benutzername";
/* Sidebar menu label for Wallet view. */
"Wallet" = "Wallet";
/* Dropdown option label for Lightning wallet, Wallet Of Satoshi. */
"Wallet Of Satoshi" = "Wallet Of Satoshi";
/* Section title for selection of wallet. */
"Wallet Selector" = "Wallet-Auswahl";
/* Label for Website section of user profile form. */
"Website" = "Website";
/* Welcoming message to the reader. The variable is 'you', the reader. */
"Welcome to the social network %@ control." = "Willkommen bei dem soziaen Netzwerk, das %@ kontrollierst.";
/* Text to welcome user. */
"Welcome, %@!" = "Willkommen, %@!";
/* Placeholder example for relay server address. */
"wss://some.relay.com" = "wss://irgendein.relay.at";
/* You, in this context, is the person who controls their own social network. You is used in the context of a larger sentence that welcomes the reader to the social network that they control themself. */
"you" = "Du";
/* Label for Your Name section of user profile form. */
"Your Name" = "Dein Name";
/* Dropdown option label for Lightning wallet, Zebedee. */
"Zebedee" = "Zebedee";
/* Dropdown option label for Lightning wallet, Zeus LN. */
"Zeus LN" = "Zeus LN";

View File

@@ -0,0 +1,154 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>collapsed_event_view_other_notes</key>
<dict>
<key>NOTES</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>1%d andere Notiz</string>
<key>other</key>
<string>1%d andere Notizen</string>
</dict>
<key>NSStringLocalizedFormatKey</key>
<string>··· %#@NOTES@ ···</string>
</dict>
<key>followers_count</key>
<dict>
<key>FOLLOWERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Follower</string>
<key>other</key>
<string>Follower</string>
</dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@FOLLOWERS@</string>
</dict>
<key>reactions_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@REACTIONS@</string>
<key>REACTIONS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Reaktion</string>
<key>other</key>
<string>Reaktionen</string>
</dict>
</dict>
<key>relays_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@RELAYS@</string>
<key>RELAYS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Relay</string>
<key>other</key>
<string>Relays</string>
</dict>
</dict>
<key>replying_to_one_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Antwort an %@%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>&amp; 1%d andere</string>
<key>other</key>
<string>&amp; %d andere</string>
<key>zero</key>
<string></string>
</dict>
</dict>
<key>replying_to_two_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Antwort an %@, %@%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>&amp; %d andere</string>
<key>other</key>
<string>&amp; %d andere</string>
<key>zero</key>
<string></string>
</dict>
</dict>
<key>reposts_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@REPOSTS@</string>
<key>REPOSTS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Mal geteilt</string>
<key>other</key>
<string>Mal geteilt</string>
</dict>
</dict>
<key>sats_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%1$#@SATS@</string>
<key>SATS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>@</string>
<key>one</key>
<string>%2$@ sat</string>
<key>other</key>
<string>%2$@ sats</string>
</dict>
</dict>
<key>tips_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@TIPS@</string>
<key>TIPS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Trinkgeld</string>
<key>other</key>
<string>Trinkgelder</string>
</dict>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,9 @@
/* Bundle display name */
"CFBundleDisplayName" = "Damus";
/* Bundle name */
"CFBundleName" = "damus";
/* Privacy - Photo Library Additions Usage Description */
"NSPhotoLibraryAddUsageDescription" = "Damus Zugriff auf deine Fotomediathek zu gewähren erlaubt dir Fotos zu sichern.";

View File

@@ -0,0 +1,493 @@
/* Blank space to separate profile picture from profile editor form. */
" " = "61b6edf1108e6f396680a33b02486a70_tr";
/* Description of how the nip05 identifier would be used for verification. */
"'%@' at '%@' will be used for verification" = "'%@' bei '%@' wird zur Verifizierung benutzt werden.";
/* Description of why the nip05 identifier is invalid. */
"'%@' is an invalid nip05 identifier. It should look like an email." = "'%@' ist eine ungültige nip05 Kennzeichnung. Diese sollte wie eine Emailadresse aussehen. ";
/* Navigation bar title for view that shows who is following a user. */
"(Profile.displayName(profile: profile, pubkey: whos))'s Followers" = "(Profile.displayName(profile: profile, pubkey: whos)) Follower";
/* Navigation bar title for view that shows who a user is following. */
"(who) following" = "(who) folgt";
/* Prefix character to username. */
"@" = "@";
/* Amount of time that has passed since reply quote event occurred.
Abbreviated version of a nostr public key. */
"%@" = "%@";
/* Sentence composed of 2 variables to describe how many reposts. In source English, the first variable is the number of reposts, and the second variable is 'Repost' or 'Reposts'.
Sentence composed of 2 variables to describe how many profiles a user is following. In source English, the first variable is the number of profiles being followed, and the second variable is 'Following'. */
"%@ %@" = "%@ %@";
/* Explanation of what is done to keep personally identifiable information private. There is a heading that precedes this explanation which is a variable to this string. */
"%@. Creating an account doesn't require a phone number, email or name. Get started right away with zero friction." = "Für das Erstellen eines Accounts ist keine Telefonnumer, E-Mail-Adresse und kein Name notwendig. Lege direkt los!";
/* Explanation of what is done to keep private data encrypted. There is a heading that precedes this explanation which is a variable to this string. */
"%@. End-to-End encrypted private messaging. Keep Big Tech out of your DMs" = "%@. Ende-zu-Ende verschlüsselte private Nachrichten. Halte „Big Tech“ aus deinen Direktnachrichten heraus.";
/* Explanation of what can be done by users to earn money. There is a heading that precedes this explanation which is a variable to this string. */
"%@. Tip your friend's posts and stack sats with Bitcoin⚡, the native currency of the internet." = "%@. Belohne Beiträge deiner Freunde und sammle Sats mit Bitcoin⚡, der eigenen Währung des Internets.";
/* Number of reposts.
Number of profiles a user is following. */
"%lld" = "%lld";
/* Fraction of how many of the user's relay servers that are operational. */
"%lld/%lld" = "%lld/%lld";
/* Placeholder for event mention. */
"< e >" = "< e >";
/* Label to prompt for about text entry for user to describe about themself. */
"About" = "Über";
/* Label for About Me section of user profile form. */
"About Me" = "Über mich";
/* Placeholder text for About Me description. */
"Absolute Boss" = "Absoluter Macher";
/* Label to indicate the public ID of the account. */
"Account ID" = "Konto-ID";
/* Button to add recommended relay server.
Button to confirm adding user inputted relay. */
"Add" = "Hinzufügen";
/* Label for section for adding a relay server. */
"Add Relay" = "Relay hinzufügen";
/* Any amount of sats */
"Any" = "beliebig";
/* Alert message to ask if user wants to repost a post. */
"Are you sure you want to repost this?" = "Bist du sicher dass Du den Beitrag auf deinem Profil teilen möchtest?";
/* Label for Banner Image section of user profile form. */
"Banner Image" = "Banner Bild";
/* Reminder to user that they should save their account information. */
"Before we get started, you'll need to save your account info, otherwise you won't be able to login in the future if you ever uninstall Damus." = "Bevor wir anfangen, musst du deine Kontodaten sichern, sonst kannst du dich in Zukunft nicht mehr anmelden, wenn du Damus jemals deinstallierst.";
/* Dropdown option label for Lightning wallet, Bitcoin Beach. */
"Bitcoin Beach" = "Bitcoin Beach";
/* Label for Bitcoin Lightning Tips section of user profile form. */
"Bitcoin Lightning Tips" = "Bitcoin Lightning Zahlungen";
/* Dropdown option label for Lightning wallet, Blixt Wallet */
"Blixt Wallet" = "Blixt Wallet";
/* Dropdown option label for Lightning wallet, Blue Wallet. */
"Blue Wallet" = "Blue Wallet";
/* Dropdown option label for Lightning wallet, Breez. */
"Breez" = "Breez";
/* Context menu option for broadcasting the user's note to all of the user's connected relay servers. */
"Broadcast" = "Senden";
/* Button to cancel out of posting a note.
Button to cancel out of reposting a post.
Button to cancel out of view adding user inputted relay.
Cancel out of logging out the user. */
"Cancel" = "Abbrechen";
/* Dropdown option label for Lightning wallet, Cash App. */
"Cash App" = "Cash App";
/* Navigation bar title for Chatroom view. */
"Chat" = "Unterhaltung";
/* Button for clearing cached data. */
"Clear" = "Löschen";
/* Section title for clearing cached data. */
"Clear Cache" = "Zwischenspeicher löschen";
/* Label indicating that a user's key was copied. */
"Copied" = "Kopiert";
/* Button to copy a relay server address. */
"Copy" = "Kopieren";
/* Context menu option for copying the ID of the account that created the note. */
"Copy Account ID" = "Konto-ID kopieren";
/* Context menu option to copy an image into clipboard.
Context menu option to copy an image to clipboard. */
"Copy Image" = "Bild kopieren";
/* Context menu option to copy the URL of an image into clipboard. */
"Copy Image URL" = "Bild-URL kopieren";
/* Title of section for copying a Lightning invoice identifier. */
"Copy invoice" = "Rechnung kopieren";
/* Context menu option for copying a user's Lightning URL. */
"Copy LNURL" = "LNURL kopieren";
/* Context menu option for copying the ID of the note. */
"Copy Note ID" = "Notiz-ID kopieren";
/* Context menu option for copying the JSON text from the note. */
"Copy Note JSON" = "Notiz-JSON kopieren";
/* Context menu option for copying the text from an note. */
"Copy Text" = "Text kopieren";
/* Context menu option for copying the ID of the user who created the note. */
"Copy User ID" = "Benutzer-ID kopieren";
/* Button to create account. */
"Create" = "Erstellen";
/* Button to create an account. */
"Create Account" = "Konto erstellen";
/* Example description about Bitcoin creator(s), Satoshi Nakamoto. */
"Creator(s) of Bitcoin. Absolute legend." = "Erfinder von Bitcoin. Absolute Legende(n).";
/* Name of the app, shown on the first screen when user is not logged in. */
"Damus" = "Damus";
/* Button to pay a Lightning invoice with the user's default Lightning wallet. */
"Default Wallet" = "Voreingestellte Wallet";
/* Button to delete a relay server that the user connects to. */
"Delete" = "Löschen";
/* Button to dismiss a text field alert. */
"Dismiss" = "Schließen";
/* Label to prompt display name entry. */
"Display Name" = "Profilname";
/* Navigation title for DM view, which is the English abbreviation for Direct Message. */
"DM" = "PN";
/* Button to dismiss wallet selection view for paying Lightning invoice. */
"Done" = "Fertig";
/* Heading indicating that this application allows users to earn money. */
"Earn Money" = "Verdiene Geld";
/* Button to edit user's profile. */
"Edit" = "Bearbeiten";
/* Heading indicating that this application keeps private messaging end-to-end encrypted. */
"Encrypted" = "Verschlüsselt";
/* Navigation title for view of encrypted DMs, where DM is an English abbreviation for Direct Message. */
"Encrypted DMs" = "Verschlüsselte PNs";
/* Prompt for user to enter an account key to login. */
"Enter your account key to login:" = "Gib deinen Kontoschlüssel ein um dich anzumelden:";
/* Error message indicating why saving keys failed. */
"Error: %@" = "Fehler: %@";
/* Filter state for seeing either only posts, or posts & replies. */
"Filter State" = "Filter Einstellung";
/* Button to follow a user. */
"Follow" = "Folgen";
/* Label describing followers of a user. */
"Followers" = "Follower";
/* Text to indicate that the button next to it is in a state that indicates that it is in the process of following a profile.
Part of a larger sentence to describe how many profiles a user is following. */
"Following" = "Folgt";
/* Label to indicate that the user is in the process of following another user. */
"Following..." = "Folge…";
/* Text to indicate that button next to it is in a state that will follow a profile when tapped. */
"Follows" = "Folgt";
/* Navigation bar title for Global view where posts from all connected relay servers appear. */
"Global" = "Weltweit";
/* Navigation link to go to post referenced by hex code. */
"Goto post %@" = "Gehe zum Beitrag %@";
/* Navigation link to go to profile. */
"Goto profile %@" = "Gehe zum Profil %@";
/* Navigation bar title for Home view where posts and replies appear from those who the user is following. */
"Home" = "Heim";
/* Placeholder example text for profile picture URL. */
"https://example.com/pic.jpg" = "https://beispiel.de/bild.jpg";
/* Placeholder example text for website URL for user profile. */
"https://jb55.com" = "https://jb55.com";
/* Error message indicating that an invalid account key was entered for login. */
"Invalid key" = "Ungültiger Schlüssel";
/* Placeholder example text for identifier used for NIP-05 verification. */
"jb55@jb55.com" = "jb55@jb55.com";
/* Moves the post button to the left side of the screen */
"Left Handed" = "Linkshändig";
/* Button to complete account creation and start using the app. */
"Let's go!" = "Los gehts!";
/* Placeholder text for entry of Lightning Address or LNURL. */
"Lightning Address or LNURL" = "Lightning-Adresse oder LNURL";
/* Indicates that the view is for paying a Lightning invoice. */
"Lightning Invoice" = "Lightning-Rechnung";
/* Dropdown option label for Lightning wallet, LNLink. */
"LNLink" = "LNLink";
/* Dropdown option label for system default for Lightning wallet. */
"Local default" = "System-Standard";
/* Button to log into account.
Button to log into an account. */
"Login" = "Anmelden";
/* Alert for logging out the user.
Button for logging out the user.
Button to logout the user. */
"Logout" = "Abmelden";
/* Reminder message in alert to get customer to verify that their private security account key is saved saved before logging out. */
"Make sure your nsec account key is saved before you logout or you will lose access to this account" = "Stelle sicher dass dein nsec Kontoschlüssel gesichert ist bevor du dich abmeldest oder du wirst den Zugang zu diesem Konto verlieren";
/* Dropdown option label for Lightning wallet, Muun. */
"Muun" = "Muun";
/* Label for NIP-05 Verification section of user profile form. */
"NIP-05 Verification" = "NIP-05-Verifizierung";
/* No search results. */
"none" = "keine";
/* Indicates that there are no notes in the timeline to view. */
"Nothing to see here. Check back later!" = "Hier gibts nichts zu sehen. Schau später wieder vorbei!";
/* Navigation title for notifications. */
"Notifications" = "Benachrichtigungen";
/* String indicating that a given timestamp just occurred */
"now" = "jetzt";
/* Prompt for user to enter in an account key to login. This text shows the characters the key could start with if it was a private key. */
"nsec1..." = "nsec1...";
/* Label indicating that a form input is optional. */
"optional" = "optional";
/* Button to pay a Lightning invoice. */
"Pay" = "Bezahlen";
/* Navigation bar title for view to pay Lightning invoice. */
"Pay the Lightning invoice" = "Bezahle die Lightning-Rechnung";
/* Dropdown option label for Lightning wallet, Phoenix. */
"Phoenix" = "Phoenix";
/* Button to post a note. */
"Post" = "Teilen";
/* Label for filter for seeing only posts (instead of posts and replies). */
"Posts" = "Beiträge";
/* Label for filter for seeing posts and replies (instead of only posts). */
"Posts & Replies" = "Beiträge & Antworten";
/* Heading indicating that this application keeps personally identifiable information private. A sentence describing what is done to keep data private comes after this heading. */
"Private" = "Privatsphäre";
/* Label to indicate that the text below is the user's private key used by only the user themself as a secret to login to access their account. */
"Private Key" = "Privater Schlüssel";
/* Title of the secure field that holds the user's private key. */
"PrivateKey" = "Privater Schlüssel";
/* Sidebar menu label for Profile view. */
"Profile" = "Profil";
/* Label for Profile Picture section of user profile form. */
"Profile Picture" = "Profilbild";
/* Section title for the user's public account ID. */
"Public Account ID" = "Öffentliche Konto ID";
/* Label indicating that the text is a user's public account key. */
"Public key" = "Öffentlicher Schlüssel";
/* Label indicating that the text is a user's public account key. */
"Public Key" = "Öffentlicher Schlüssel";
/* Prompt to ask user if the key they entered is a public key. */
"Public Key?" = "Öffentlicher Schlüssel?";
/* Navigation bar title for Reactions view. */
"Reactions" = "Reaktionen";
/* Section title for recommend relay servers that could be added as part of configuration */
"Recommended Relays" = "Empfohlene Relays";
/* Text field for relay server. Used for testing purposes. */
"Relay" = "Relay";
/* Sidebar menu label for Relay servers view */
"Relays" = "Relays";
/* Label to indicate that the user is replying to themself. */
"Reply to self" = "Antwort an dich selbst";
/* Label to indicate that the user is replying to 2 users. */
"Replying to %@ & %@" = "%1$@ & %2$@ antworten";
/* Indicating that the user is replying to the following listed people. */
"Replying to:" = "Du antwortest:";
/* Button to confirm reposting a post.
Title of alert for confirming to repost a post. */
"Repost" = "Teilen";
/* Text indicating that the post was reposted (i.e. re-shared). */
"Reposted" = "Geteilt";
/* Section title for resetting the user */
"Reset" = "Zurücksetzen";
/* Button to retry completing account creation after an error occurred. */
"Retry" = "Wiederholung";
/* Dropdown option label for Lightning wallet, River */
"River" = "River";
/* Example username of Bitcoin creator(s), Satoshi Nakamoto. */
"satoshi" = "satoshi";
/* Name of Bitcoin creator(s). */
"Satoshi Nakamoto" = "Satoshi Nakamoto";
/* Button for saving profile. */
"Save" = "Speichern";
/* Context menu option to save an image. */
"Save Image" = "Bild speichern";
/* Navigation link to search hashtag. */
"Search hashtag: #%@" = "Hashtag suchen: #%@";
/* Placeholder text to prompt entry of search query. */
"Search..." = "Suchen...";
/* Section title for user's secret account login key. */
"Secret Account Login Key" = "Geheimer Konto-Anmeldeschlüssel";
/* Title of section for selecting a Lightning wallet to pay a Lightning invoice. */
"Select a Lightning wallet" = "Wähle eine Lightning-Wallet";
/* Prompt selection of user's default wallet */
"Select default wallet" = "Wähle ein voreingestelltes Wallet";
/* Text prompt for user to send a message to the other user. */
"Send a message to start the conversation..." = "Sende eine Nachricht um die Unterhaltung zu beginnen...";
/* Navigation title for Settings view.
Sidebar menu label for accessing the app settings */
"Settings" = "Einstellungen";
/* Button to share an image. */
"Share" = "Teilen";
/* Toggle to show or hide user's secret account login key. */
"Show" = "Anzeigen";
/* Toggle to show or hide selection of wallet. */
"Show wallet selector" = "Wallet-Auswahl anzeigen";
/* Sidebar menu label to sign out of the account. */
"Sign out" = "Abmelden";
/* Dropdown option label for Lightning wallet, Strike. */
"Strike" = "Strike";
/* Warning that the inputted account key is a public key and the result of what happens because of it. */
"This is a public key, you will not be able to make posts or interact in any way. This is used for viewing accounts from their perspective." = "Dies ist ein öffentlicher Schlüssel, Du wirst keine Beiträge teilen oder oder auf irgendeine Weise interagieren können. Dies wird genutzt um Kontos aus deren Perspektive zu sehen.";
/* Warning that the inputted account key for login is an old-style and asking user to verify if it is a public key. */
"This is an old-style nostr key. We're not sure if it's a pubkey or private key. Please toggle the button below if this a public key." = "Dies ist ein veralteter nostr-Schlüssel. Wir sind und unsicher ob es ein öffentlicher Schlüssel oder ein privater Schlüssel ist. Bitte betätige die untenstehende Schaltfläche wenn es ein öffentlicher Schlüssel ist.";
/* Label to describe that a public key is the user's account ID and what they can do with it. */
"This is your account ID, you can give this to your friends so that they can follow you. Click to copy." = "Dies ist deine Konto-ID, die du an deine Freunde weitergeben kannst, damit sie dir folgen können. Zum Kopieren anklicken.";
/* Label to describe that a private key is the user's secret account key and what they should do with it. */
"This is your secret account key. You need this to access your account. Don't share this with anyone! Save it in a password manager and keep it safe!" = "Dies ist dein geheimer, privater Schlüssel. Du benötigst ihn, um auf dein Konto zuzugreifen. Gib den privaten Schlüssel an niemanden weiter! Speichere ihn in einem Passwort-Manager und bewahre ihn sicher auf!";
/* Navigation bar title for note thread.
Navigation bar title for threaded event detail view. */
"Thread" = "Thema";
/* Text box prompt to ask user to type their post. */
"Type your post here..." = "Schreibe deinen Beitrag hier...";
/* Non-breaking space character to fill in blank space next to event action button icons. */
"u{00A0}" = "u{00A0}";
/* Button to unfollow a user. */
"Unfollow" = "Entfolgen";
/* Text to indicate that the button next to it is in a state that indicates that it is in the process of unfollowing a profile. */
"Unfollowing" = "Entfolgen...";
/* Label to indicate that the user is in the process of unfollowing another user. */
"Unfollowing..." = "Entfolgen...";
/* Text to indicate that the button next to it is in a state that will unfollow a profile when tapped. */
"Unfollows" = "Entfolgen";
/* Label for Username section of user profile form.
Label to prompt username entry. */
"Username" = "Benutzername";
/* Sidebar menu label for Wallet view. */
"Wallet" = "Wallet";
/* Dropdown option label for Lightning wallet, Wallet Of Satoshi. */
"Wallet Of Satoshi" = "Wallet Of Satoshi";
/* Section title for selection of wallet. */
"Wallet Selector" = "Wallet-Auswahl";
/* Label for Website section of user profile form. */
"Website" = "Website";
/* Welcoming message to the reader. The variable is 'you', the reader. */
"Welcome to the social network %@ control." = "Willkommen bei dem sozialen Netzwerk, das %@ kontrollierst.";
/* Text to welcome user. */
"Welcome, %@!" = "Willkommen, %@!";
/* Placeholder example for relay server address. */
"wss://some.relay.com" = "wss://irgendein.relay.de";
/* You, in this context, is the person who controls their own social network. You is used in the context of a larger sentence that welcomes the reader to the social network that they control themself. */
"you" = "Du";
/* Label for Your Name section of user profile form. */
"Your Name" = "Dein Name";
/* Dropdown option label for Lightning wallet, Zebedee. */
"Zebedee" = "Zebedee";
/* Dropdown option label for Lightning wallet, Zeus LN. */
"Zeus LN" = "Zeus LN";

View File

@@ -0,0 +1,154 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>collapsed_event_view_other_notes</key>
<dict>
<key>NOTES</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%d andere Notiz</string>
<key>other</key>
<string>%d andere Notizen</string>
</dict>
<key>NSStringLocalizedFormatKey</key>
<string>··· %#@NOTES@ ···</string>
</dict>
<key>followers_count</key>
<dict>
<key>FOLLOWERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Follower</string>
<key>other</key>
<string>Follower</string>
</dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@FOLLOWERS@</string>
</dict>
<key>reactions_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@REACTIONS@</string>
<key>REACTIONS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Reaktion</string>
<key>other</key>
<string>Reaktionen</string>
</dict>
</dict>
<key>relays_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@RELAYS@</string>
<key>RELAYS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Relay</string>
<key>other</key>
<string>Relays</string>
</dict>
</dict>
<key>replying_to_one_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Antwort an %@%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>&amp; %d andere</string>
<key>other</key>
<string>&amp; %d andere</string>
<key>zero</key>
<string></string>
</dict>
</dict>
<key>replying_to_two_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Antwort an %@, %@%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>&amp; %d andere</string>
<key>other</key>
<string>&amp; %d andere</string>
<key>zero</key>
<string></string>
</dict>
</dict>
<key>reposts_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@REPOSTS@</string>
<key>REPOSTS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Mal geteilt</string>
<key>other</key>
<string>Mal geteilt</string>
</dict>
</dict>
<key>sats_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%1$#@SATS@</string>
<key>SATS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>@</string>
<key>one</key>
<string>%2$@ sat</string>
<key>other</key>
<string>%2$@ sats</string>
</dict>
</dict>
<key>tips_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@TIPS@</string>
<key>TIPS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Trinkgeld</string>
<key>other</key>
<string>Trinkgelder</string>
</dict>
</dict>
</dict>
</plist>

View File

@@ -2,6 +2,70 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>collapsed_event_view_other_notes</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>··· %#@NOTES@ ···</string>
<key>NOTES</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%d other note</string>
<key>other</key>
<string>%d other notes</string>
</dict>
</dict>
<key>followers_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@FOLLOWERS@</string>
<key>FOLLOWERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Follower</string>
<key>other</key>
<string>Followers</string>
</dict>
</dict>
<key>reactions_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@REACTIONS@</string>
<key>REACTIONS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Reaction</string>
<key>other</key>
<string>Reactions</string>
</dict>
</dict>
<key>relays_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@RELAYS@</string>
<key>RELAYS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Relay</string>
<key>other</key>
<string>Relays</string>
</dict>
</dict>
<key>replying_to_one_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
@@ -15,7 +79,7 @@
<key>zero</key>
<string></string>
<key>one</key>
<string> &amp; 1 other</string>
<string> &amp; %d other</string>
<key>other</key>
<string> &amp; %d others</string>
</dict>
@@ -33,27 +97,57 @@
<key>zero</key>
<string></string>
<key>one</key>
<string> &amp; 1 other</string>
<string> &amp; %d other</string>
<key>other</key>
<string> &amp; %d others</string>
</dict>
</dict>
<key>collapsed_event_view_other_notes</key>
<key>reposts_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>··· %#@NOTES@ ···</string>
<key>NOTES</key>
<string>%#@REPOSTS@</string>
<key>REPOSTS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>zero</key>
<string>0 other notes</string>
<key>one</key>
<string>1 other note</string>
<string>Repost</string>
<key>other</key>
<string>%d other notes</string>
<string>Reposts</string>
</dict>
</dict>
<key>sats_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%1$#@SATS@</string>
<key>SATS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>@</string>
<key>one</key>
<string>%2$@ sat</string>
<key>other</key>
<string>%2$@ sats</string>
</dict>
</dict>
<key>tips_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@TIPS@</string>
<key>TIPS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Tip</string>
<key>other</key>
<string>Tips</string>
</dict>
</dict>
</dict>

View File

@@ -1,6 +1,9 @@
/* Bundle display name */
"CFBundleDisplayName" = "Damus";
/* Bundle name */
"CFBundleName" = "damus";
/* Privacy - Photo Library Additions Usage Description */
"NSPhotoLibraryAddUsageDescription" = "\"Granting Damus access to your photo library allows you to save photos.";
"NSPhotoLibraryAddUsageDescription" = "Si le concedes acceso a Damus a tu fototeca, podrás guardar fotos.";

View File

@@ -0,0 +1,493 @@
/* Blank space to separate profile picture from profile editor form. */
" " = "61b6edf1108e6f396680a33b02486a70_tr";
/* Description of how the nip05 identifier would be used for verification. */
"'%@' at '%@' will be used for verification" = "'%@' en '%@' se usarán con fines de verificación";
/* Description of why the nip05 identifier is invalid. */
"'%@' is an invalid nip05 identifier. It should look like an email." = "'%@' es un identificador nip05 no válido. Debería de tener la apariencia de un correo electrónico.";
/* Navigation bar title for view that shows who is following a user. */
"(Profile.displayName(profile: profile, pubkey: whos))'s Followers" = "Seguidores de (Profile.displayName(profile: profile, pubkey: whos))";
/* Navigation bar title for view that shows who a user is following. */
"(who) following" = "(who) sigue a";
/* Prefix character to username. */
"@" = "@";
/* Amount of time that has passed since reply quote event occurred.
Abbreviated version of a nostr public key. */
"%@" = "%@";
/* Sentence composed of 2 variables to describe how many reposts. In source English, the first variable is the number of reposts, and the second variable is 'Repost' or 'Reposts'.
Sentence composed of 2 variables to describe how many profiles a user is following. In source English, the first variable is the number of profiles being followed, and the second variable is 'Following'. */
"%@ %@" = "%@ %@";
/* Explanation of what is done to keep personally identifiable information private. There is a heading that precedes this explanation which is a variable to this string. */
"%@. Creating an account doesn't require a phone number, email or name. Get started right away with zero friction." = "%@. No se requiere un número de teléfono, correo electrónico ni nombre para crear una cuenta. Comienza de inmediato sin fricciones.";
/* Explanation of what is done to keep private data encrypted. There is a heading that precedes this explanation which is a variable to this string. */
"%@. End-to-End encrypted private messaging. Keep Big Tech out of your DMs" = "%@. Mensajes privados con cifrado de un extremo a otro. Mantén a los gigantes tecnológicos fuera de tus mensajes directos.";
/* Explanation of what can be done by users to earn money. There is a heading that precedes this explanation which is a variable to this string. */
"%@. Tip your friend's posts and stack sats with Bitcoin⚡, the native currency of the internet." = "%@. Deja propinas en las publicaciones de tus amigos y acumula sats con Bitcoin⚡, la moneda nativa de internet.";
/* Number of reposts.
Number of profiles a user is following. */
"%lld" = "%lld";
/* Fraction of how many of the user's relay servers that are operational. */
"%lld/%lld" = "%lld/%lld";
/* Placeholder for event mention. */
"< e >" = "< e >";
/* Label to prompt for about text entry for user to describe about themself. */
"About" = "Información";
/* Label for About Me section of user profile form. */
"About Me" = "Acerca de mí";
/* Placeholder text for About Me description. */
"Absolute Boss" = "Jefe supremo";
/* Label to indicate the public ID of the account. */
"Account ID" = "Identificador de cuenta";
/* Button to add recommended relay server.
Button to confirm adding user inputted relay. */
"Add" = "Agregar";
/* Label for section for adding a relay server. */
"Add Relay" = "Agregar relé";
/* Any amount of sats */
"Any" = "Cualquiera";
/* Alert message to ask if user wants to repost a post. */
"Are you sure you want to repost this?" = "¿Seguro quieres volver a publicar esto?";
/* Label for Banner Image section of user profile form. */
"Banner Image" = "Imagen del banner";
/* Reminder to user that they should save their account information. */
"Before we get started, you'll need to save your account info, otherwise you won't be able to login in the future if you ever uninstall Damus." = "Antes de empezar, tendrás que guardar la información de tu cuenta. De lo contrario, no podrás iniciar sesión en el futuro si llegas a desinstalar Damus.";
/* Dropdown option label for Lightning wallet, Bitcoin Beach. */
"Bitcoin Beach" = "Bitcoin Beach";
/* Label for Bitcoin Lightning Tips section of user profile form. */
"Bitcoin Lightning Tips" = "Propinas con Bitcoin Lightning";
/* Dropdown option label for Lightning wallet, Blixt Wallet */
"Blixt Wallet" = "Blixt Wallet";
/* Dropdown option label for Lightning wallet, Blue Wallet. */
"Blue Wallet" = "Blue Wallet";
/* Dropdown option label for Lightning wallet, Breez. */
"Breez" = "Breez";
/* Context menu option for broadcasting the user's note to all of the user's connected relay servers. */
"Broadcast" = "Transmitir";
/* Button to cancel out of posting a note.
Button to cancel out of reposting a post.
Button to cancel out of view adding user inputted relay.
Cancel out of logging out the user. */
"Cancel" = "Cancelar";
/* Dropdown option label for Lightning wallet, Cash App. */
"Cash App" = "Cash App";
/* Navigation bar title for Chatroom view. */
"Chat" = "Chat";
/* Button for clearing cached data. */
"Clear" = "Borrar";
/* Section title for clearing cached data. */
"Clear Cache" = "Borrar caché";
/* Label indicating that a user's key was copied. */
"Copied" = "Copiada";
/* Button to copy a relay server address. */
"Copy" = "Copiar";
/* Context menu option for copying the ID of the account that created the note. */
"Copy Account ID" = "Copiar identificador de cuenta";
/* Context menu option to copy an image into clipboard.
Context menu option to copy an image to clipboard. */
"Copy Image" = "Copiar imagen";
/* Context menu option to copy the URL of an image into clipboard. */
"Copy Image URL" = "Copiar URL de imagen";
/* Title of section for copying a Lightning invoice identifier. */
"Copy invoice" = "Copiar factura";
/* Context menu option for copying a user's Lightning URL. */
"Copy LNURL" = "Copiar LNURL";
/* Context menu option for copying the ID of the note. */
"Copy Note ID" = "Copiar identificador de nota";
/* Context menu option for copying the JSON text from the note. */
"Copy Note JSON" = "Copiar JSON de nota";
/* Context menu option for copying the text from an note. */
"Copy Text" = "Copiar texto";
/* Context menu option for copying the ID of the user who created the note. */
"Copy User ID" = "Copiar identificador de usuario";
/* Button to create account. */
"Create" = "Crear";
/* Button to create an account. */
"Create Account" = "Crear cuenta";
/* Example description about Bitcoin creator(s), Satoshi Nakamoto. */
"Creator(s) of Bitcoin. Absolute legend." = "Creador(es) de Bitcoin. Toda una leyenda.";
/* Name of the app, shown on the first screen when user is not logged in. */
"Damus" = "Damus";
/* Button to pay a Lightning invoice with the user's default Lightning wallet. */
"Default Wallet" = "Billetera predeterminada";
/* Button to delete a relay server that the user connects to. */
"Delete" = "Borrar";
/* Button to dismiss a text field alert. */
"Dismiss" = "Descartar";
/* Label to prompt display name entry. */
"Display Name" = "Mostrar nombre";
/* Navigation title for DM view, which is the English abbreviation for Direct Message. */
"DM" = "MD";
/* Button to dismiss wallet selection view for paying Lightning invoice. */
"Done" = "Listo";
/* Heading indicating that this application allows users to earn money. */
"Earn Money" = "Gana dinero";
/* Button to edit user's profile. */
"Edit" = "Editar";
/* Heading indicating that this application keeps private messaging end-to-end encrypted. */
"Encrypted" = "Cifrada";
/* Navigation title for view of encrypted DMs, where DM is an English abbreviation for Direct Message. */
"Encrypted DMs" = "MD cifrados";
/* Prompt for user to enter an account key to login. */
"Enter your account key to login:" = "Ingresa la clave de tu cuenta para iniciar sesión:";
/* Error message indicating why saving keys failed. */
"Error: %@" = "Error: %@";
/* Filter state for seeing either only posts, or posts & replies. */
"Filter State" = "Estado del filtro";
/* Button to follow a user. */
"Follow" = "Seguir";
/* Label describing followers of a user. */
"Followers" = "Seguidores";
/* Text to indicate that the button next to it is in a state that indicates that it is in the process of following a profile.
Part of a larger sentence to describe how many profiles a user is following. */
"Following" = "Siguiendo";
/* Label to indicate that the user is in the process of following another user. */
"Following..." = "Siguiendo...";
/* Text to indicate that button next to it is in a state that will follow a profile when tapped. */
"Follows" = "Sigue";
/* Navigation bar title for Global view where posts from all connected relay servers appear. */
"Global" = "Global";
/* Navigation link to go to post referenced by hex code. */
"Goto post %@" = "Ir a la publicación %@";
/* Navigation link to go to profile. */
"Goto profile %@" = "Ir al perfil %@";
/* Navigation bar title for Home view where posts and replies appear from those who the user is following. */
"Home" = "Inicio";
/* Placeholder example text for profile picture URL. */
"https://example.com/pic.jpg" = "https://ejemplo.com/foto.jpg";
/* Placeholder example text for website URL for user profile. */
"https://jb55.com" = "https://jb55.com";
/* Error message indicating that an invalid account key was entered for login. */
"Invalid key" = "Clave inválida";
/* Placeholder example text for identifier used for NIP-05 verification. */
"jb55@jb55.com" = "jb55@jb55.com";
/* Moves the post button to the left side of the screen */
"Left Handed" = "Zurdo";
/* Button to complete account creation and start using the app. */
"Let's go!" = "¡Vamos!";
/* Placeholder text for entry of Lightning Address or LNURL. */
"Lightning Address or LNURL" = "Dirección de Lightning o LNURL";
/* Indicates that the view is for paying a Lightning invoice. */
"Lightning Invoice" = "Factura de Lightning";
/* Dropdown option label for Lightning wallet, LNLink. */
"LNLink" = "LNLink";
/* Dropdown option label for system default for Lightning wallet. */
"Local default" = "Predeterminada del sistema";
/* Button to log into account.
Button to log into an account. */
"Login" = "Iniciar sesión";
/* Alert for logging out the user.
Button for logging out the user.
Button to logout the user. */
"Logout" = "Cerrar sesión";
/* Reminder message in alert to get customer to verify that their private security account key is saved saved before logging out. */
"Make sure your nsec account key is saved before you logout or you will lose access to this account" = "Asegúrate de que tu clave de cuenta nsec esté guardada antes de cerrar sesión o perderás el acceso a esta cuenta";
/* Dropdown option label for Lightning wallet, Muun. */
"Muun" = "Muun";
/* Label for NIP-05 Verification section of user profile form. */
"NIP-05 Verification" = "Verificación NIP-05";
/* No search results. */
"none" = "ninguno";
/* Indicates that there are no notes in the timeline to view. */
"Nothing to see here. Check back later!" = "Nada para ver aquí. ¡Vuelve a consultar luego!";
/* Navigation title for notifications. */
"Notifications" = "Notificaciones";
/* String indicating that a given timestamp just occurred */
"now" = "ahora";
/* Prompt for user to enter in an account key to login. This text shows the characters the key could start with if it was a private key. */
"nsec1..." = "nsec1...";
/* Label indicating that a form input is optional. */
"optional" = "opcional";
/* Button to pay a Lightning invoice. */
"Pay" = "Pagar";
/* Navigation bar title for view to pay Lightning invoice. */
"Pay the Lightning invoice" = "Paga la factura de Lightning";
/* Dropdown option label for Lightning wallet, Phoenix. */
"Phoenix" = "Phoenix";
/* Button to post a note. */
"Post" = "Publicar";
/* Label for filter for seeing only posts (instead of posts and replies). */
"Posts" = "Publicaciones";
/* Label for filter for seeing posts and replies (instead of only posts). */
"Posts & Replies" = "Publicaciones y respuestas";
/* Heading indicating that this application keeps personally identifiable information private. A sentence describing what is done to keep data private comes after this heading. */
"Private" = "Privada";
/* Label to indicate that the text below is the user's private key used by only the user themself as a secret to login to access their account. */
"Private Key" = "Clave privada";
/* Title of the secure field that holds the user's private key. */
"PrivateKey" = "ClavePrivada";
/* Sidebar menu label for Profile view. */
"Profile" = "Perfil";
/* Label for Profile Picture section of user profile form. */
"Profile Picture" = "Foto del perfil";
/* Section title for the user's public account ID. */
"Public Account ID" = "Identificador de cuenta pública";
/* Label indicating that the text is a user's public account key. */
"Public key" = "Clave pública";
/* Label indicating that the text is a user's public account key. */
"Public Key" = "Clave pública";
/* Prompt to ask user if the key they entered is a public key. */
"Public Key?" = "¿Clave pública?";
/* Navigation bar title for Reactions view. */
"Reactions" = "Reacciones";
/* Section title for recommend relay servers that could be added as part of configuration */
"Recommended Relays" = "Relés recomendados";
/* Text field for relay server. Used for testing purposes. */
"Relay" = "Relé";
/* Sidebar menu label for Relay servers view */
"Relays" = "Relés";
/* Label to indicate that the user is replying to themself. */
"Reply to self" = "Respuesta a sí mismo";
/* Label to indicate that the user is replying to 2 users. */
"Replying to %@ & %@" = "Respondiendo a %1$@ y %2$@";
/* Indicating that the user is replying to the following listed people. */
"Replying to:" = "Respondiendo a:";
/* Button to confirm reposting a post.
Title of alert for confirming to repost a post. */
"Repost" = "Republicar";
/* Text indicating that the post was reposted (i.e. re-shared). */
"Reposted" = "Republicada";
/* Section title for resetting the user */
"Reset" = "Reiniciar";
/* Button to retry completing account creation after an error occurred. */
"Retry" = "Reintentar";
/* Dropdown option label for Lightning wallet, River */
"River" = "River";
/* Example username of Bitcoin creator(s), Satoshi Nakamoto. */
"satoshi" = "satoshi";
/* Name of Bitcoin creator(s). */
"Satoshi Nakamoto" = "Satoshi Nakamoto";
/* Button for saving profile. */
"Save" = "Guardar";
/* Context menu option to save an image. */
"Save Image" = "Guardar imagen";
/* Navigation link to search hashtag. */
"Search hashtag: #%@" = "Buscar hashtag: #%@";
/* Placeholder text to prompt entry of search query. */
"Search..." = "Búsqueda...";
/* Section title for user's secret account login key. */
"Secret Account Login Key" = "Clave de inicio de sesión de cuenta secreta";
/* Title of section for selecting a Lightning wallet to pay a Lightning invoice. */
"Select a Lightning wallet" = "Seleccionar una billetera de Lightning";
/* Prompt selection of user's default wallet */
"Select default wallet" = "Seleccionar billetera predeterminada";
/* Text prompt for user to send a message to the other user. */
"Send a message to start the conversation..." = "Envía un mensaje para empezar la conversación...";
/* Navigation title for Settings view.
Sidebar menu label for accessing the app settings */
"Settings" = "Configuración";
/* Button to share an image. */
"Share" = "Compartir";
/* Toggle to show or hide user's secret account login key. */
"Show" = "Mostrar";
/* Toggle to show or hide selection of wallet. */
"Show wallet selector" = "Mostrar selector de billetera";
/* Sidebar menu label to sign out of the account. */
"Sign out" = "Cerrar sesión";
/* Dropdown option label for Lightning wallet, Strike. */
"Strike" = "Strike";
/* Warning that the inputted account key is a public key and the result of what happens because of it. */
"This is a public key, you will not be able to make posts or interact in any way. This is used for viewing accounts from their perspective." = "Esta es una clave pública, por lo que no podrás hacer publicaciones ni interactuar de ningún modo. Se usa para ver cuentas desde su perspectiva.";
/* Warning that the inputted account key for login is an old-style and asking user to verify if it is a public key. */
"This is an old-style nostr key. We're not sure if it's a pubkey or private key. Please toggle the button below if this a public key." = "Esta es una clave de nostr antigua. No sabemos con seguridad si es una clave pública o privada. Activa el siguiente botón si es una clave pública.";
/* Label to describe that a public key is the user's account ID and what they can do with it. */
"This is your account ID, you can give this to your friends so that they can follow you. Click to copy." = "Este es tu identificador de cuenta, que puedes compartir con tus amigos para que te sigan. Haz clic para copiarlo.";
/* Label to describe that a private key is the user's secret account key and what they should do with it. */
"This is your secret account key. You need this to access your account. Don't share this with anyone! Save it in a password manager and keep it safe!" = "Esta es tu clave de cuenta secreta, que necesitas para acceder a tu cuenta. ¡No la compartas con nadie! Guárdala en un administrador de contraseñas y protégela!";
/* Navigation bar title for note thread.
Navigation bar title for threaded event detail view. */
"Thread" = "Hilo";
/* Text box prompt to ask user to type their post. */
"Type your post here..." = "Ingresa tu publicación aquí...";
/* Non-breaking space character to fill in blank space next to event action button icons. */
"u{00A0}" = "u{00A0}";
/* Button to unfollow a user. */
"Unfollow" = "Dejar de seguir";
/* Text to indicate that the button next to it is in a state that indicates that it is in the process of unfollowing a profile. */
"Unfollowing" = "Dejando de seguir";
/* Label to indicate that the user is in the process of unfollowing another user. */
"Unfollowing..." = "Dejando de seguir...";
/* Text to indicate that the button next to it is in a state that will unfollow a profile when tapped. */
"Unfollows" = "Deja de seguir";
/* Label for Username section of user profile form.
Label to prompt username entry. */
"Username" = "Nombre de usuario";
/* Sidebar menu label for Wallet view. */
"Wallet" = "Billetera";
/* Dropdown option label for Lightning wallet, Wallet Of Satoshi. */
"Wallet Of Satoshi" = "Wallet Of Satoshi";
/* Section title for selection of wallet. */
"Wallet Selector" = "Selección de billetera";
/* Label for Website section of user profile form. */
"Website" = "Sitio web";
/* Welcoming message to the reader. The variable is 'you', the reader. */
"Welcome to the social network %@ control." = "Te damos la bienvenida a la red social que %@ controlas.";
/* Text to welcome user. */
"Welcome, %@!" = "¡Te damos la bienvenida, %@!";
/* Placeholder example for relay server address. */
"wss://some.relay.com" = "wss://algún.relé.com";
/* You, in this context, is the person who controls their own social network. You is used in the context of a larger sentence that welcomes the reader to the social network that they control themself. */
"you" = "tú";
/* Label for Your Name section of user profile form. */
"Your Name" = "Tu nombre";
/* Dropdown option label for Lightning wallet, Zebedee. */
"Zebedee" = "Zebedee";
/* Dropdown option label for Lightning wallet, Zeus LN. */
"Zeus LN" = "Zeus LN";

View File

@@ -2,58 +2,152 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>replying_to_one_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Replying to %@%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>zero</key>
<string></string>
<key>one</key>
<string> &amp; 1 other</string>
<key>other</key>
<string> &amp; %d others</string>
</dict>
</dict>
<key>replying_to_two_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Replying to %@, %@%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>zero</key>
<string></string>
<key>one</key>
<string> &amp; 1 other</string>
<key>other</key>
<string> &amp; %d others</string>
</dict>
</dict>
<key>collapsed_event_view_other_notes</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>··· %#@NOTES@ ···</string>
<key>NOTES</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>zero</key>
<string>0 notes</string>
<key>one</key>
<string>1 note</string>
<string>%d otra nota</string>
<key>other</key>
<string>%d notes</string>
<string>%d otras notas</string>
</dict>
<key>NSStringLocalizedFormatKey</key>
<string>··· %#@NOTES@ ···</string>
</dict>
<key>followers_count</key>
<dict>
<key>FOLLOWERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Seguidor</string>
<key>other</key>
<string>Seguidores</string>
</dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@FOLLOWERS@</string>
</dict>
<key>reactions_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@REACTIONS@</string>
<key>REACTIONS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Reacción</string>
<key>other</key>
<string>Reacciones</string>
</dict>
</dict>
<key>relays_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@RELAYS@</string>
<key>RELAYS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Relé</string>
<key>other</key>
<string>Relés</string>
</dict>
</dict>
<key>replying_to_one_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Respondiendo a %@%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string> y %d otro</string>
<key>other</key>
<string> y %d otros</string>
<key>zero</key>
<string></string>
</dict>
</dict>
<key>replying_to_two_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Respondiendo a %@, %@%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string> y %d otro</string>
<key>other</key>
<string> y %d otros</string>
<key>zero</key>
<string></string>
</dict>
</dict>
<key>reposts_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@REPOSTS@</string>
<key>REPOSTS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Republicación</string>
<key>other</key>
<string>Republicaciones</string>
</dict>
</dict>
<key>sats_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%1$#@SATS@</string>
<key>SATS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>@</string>
<key>one</key>
<string>%2$@ sat</string>
<key>other</key>
<string>%2$@ sats</string>
</dict>
</dict>
<key>tips_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@TIPS@</string>
<key>TIPS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Propina</string>
<key>other</key>
<string>Propinas</string>
</dict>
</dict>
</dict>

View File

@@ -0,0 +1,9 @@
/* Bundle display name */
"CFBundleDisplayName" = "Damus";
/* Bundle name */
"CFBundleName" = "damus";
/* Privacy - Photo Library Additions Usage Description */
"NSPhotoLibraryAddUsageDescription" = "\"Accorder à Damus l'accès à votre galerie photos vous permet d'enregistrer des photos.";

View File

@@ -0,0 +1,493 @@
/* Blank space to separate profile picture from profile editor form. */
" " = "61b6edf1108e6f396680a33b02486a70_tr";
/* Description of how the nip05 identifier would be used for verification. */
"'%@' at '%@' will be used for verification" = "'%@' à '@' sera utilisé pour la vérification";
/* Description of why the nip05 identifier is invalid. */
"'%@' is an invalid nip05 identifier. It should look like an email." = "'@' est un identifiant nip05 invalide. Cela devrait ressembler à une adresse e-mail.";
/* Navigation bar title for view that shows who is following a user. */
"(Profile.displayName(profile: profile, pubkey: whos))'s Followers" = "(Profile.displayName(profile: profile, pubkey: whos))'s Followers";
/* Navigation bar title for view that shows who a user is following. */
"(who) following" = "(who) following";
/* Prefix character to username. */
"@" = "@";
/* Amount of time that has passed since reply quote event occurred.
Abbreviated version of a nostr public key. */
"%@" = "%@";
/* Sentence composed of 2 variables to describe how many reposts. In source English, the first variable is the number of reposts, and the second variable is 'Repost' or 'Reposts'.
Sentence composed of 2 variables to describe how many profiles a user is following. In source English, the first variable is the number of profiles being followed, and the second variable is 'Following'. */
"%@ %@" = "%@ %@";
/* Explanation of what is done to keep personally identifiable information private. There is a heading that precedes this explanation which is a variable to this string. */
"%@. Creating an account doesn't require a phone number, email or name. Get started right away with zero friction." = "%@. La création d'un compte ne nécessite pas de numéro de téléphone, d'e-mail ou de nom. Commencez tout de suite sans aucune friction.";
/* Explanation of what is done to keep private data encrypted. There is a heading that precedes this explanation which is a variable to this string. */
"%@. End-to-End encrypted private messaging. Keep Big Tech out of your DMs" = "%@. Messagerie privée cryptée de bout en bout. Gardez Big Tech hors de vos DMs";
/* Explanation of what can be done by users to earn money. There is a heading that precedes this explanation which is a variable to this string. */
"%@. Tip your friend's posts and stack sats with Bitcoin⚡, the native currency of the internet." = "%@. Donnez un pourboire aux publications de vos amis et empilez les sats avec Bitcoin⚡, la monnaie native d'Internet.";
/* Number of reposts.
Number of profiles a user is following. */
"%lld" = "%lld";
/* Fraction of how many of the user's relay servers that are operational. */
"%lld/%lld" = "%lld/%lld<br data-mce-bogus=\"1\">";
/* Placeholder for event mention. */
"< e >" = "< e >";
/* Label to prompt for about text entry for user to describe about themself. */
"About" = "À propos de";
/* Label for About Me section of user profile form. */
"About Me" = "À Propos de Moi";
/* Placeholder text for About Me description. */
"Absolute Boss" = "Patron Absolu";
/* Label to indicate the public ID of the account. */
"Account ID" = "Identifiant de Compte";
/* Button to add recommended relay server.
Button to confirm adding user inputted relay. */
"Add" = "Ajouter";
/* Label for section for adding a relay server. */
"Add Relay" = "Ajouter un Relais";
/* Any amount of sats */
"Any" = "N'importe Lequel";
/* Alert message to ask if user wants to repost a post. */
"Are you sure you want to repost this?" = "Êtes-vous sûr de vouloir republier ceci ?";
/* Label for Banner Image section of user profile form. */
"Banner Image" = "Image Bannière";
/* Reminder to user that they should save their account information. */
"Before we get started, you'll need to save your account info, otherwise you won't be able to login in the future if you ever uninstall Damus." = "Avant de commencer, vous devrez enregistrer les informations de votre compte, sinon vous ne pourrez plus vous connecter à l'avenir si vous désinstallez Damus.";
/* Dropdown option label for Lightning wallet, Bitcoin Beach. */
"Bitcoin Beach" = "Bitcoin Beach";
/* Label for Bitcoin Lightning Tips section of user profile form. */
"Bitcoin Lightning Tips" = "Pourboires de Bitcoin Lightning";
/* Dropdown option label for Lightning wallet, Blixt Wallet */
"Blixt Wallet" = "Portefeuille Blixt";
/* Dropdown option label for Lightning wallet, Blue Wallet. */
"Blue Wallet" = "Portefeuille Blue";
/* Dropdown option label for Lightning wallet, Breez. */
"Breez" = "Breez";
/* Context menu option for broadcasting the user's note to all of the user's connected relay servers. */
"Broadcast" = "Diffuser";
/* Button to cancel out of posting a note.
Button to cancel out of reposting a post.
Button to cancel out of view adding user inputted relay.
Cancel out of logging out the user. */
"Cancel" = "Annuler";
/* Dropdown option label for Lightning wallet, Cash App. */
"Cash App" = "Cash App";
/* Navigation bar title for Chatroom view. */
"Chat" = "Chatter";
/* Button for clearing cached data. */
"Clear" = "<br>Vider";
/* Section title for clearing cached data. */
"Clear Cache" = "Vider le Cache";
/* Label indicating that a user's key was copied. */
"Copied" = "Copié";
/* Button to copy a relay server address. */
"Copy" = "Copier";
/* Context menu option for copying the ID of the account that created the note. */
"Copy Account ID" = "Copier l'Identifiant de Compte";
/* Context menu option to copy an image into clipboard.
Context menu option to copy an image to clipboard. */
"Copy Image" = "Copier l'Image";
/* Context menu option to copy the URL of an image into clipboard. */
"Copy Image URL" = "Copier l'URL de l'Image";
/* Title of section for copying a Lightning invoice identifier. */
"Copy invoice" = "Copier la Facture";
/* Context menu option for copying a user's Lightning URL. */
"Copy LNURL" = "Copier le LNURL";
/* Context menu option for copying the ID of the note. */
"Copy Note ID" = "Copier l'Identifiant de la Note";
/* Context menu option for copying the JSON text from the note. */
"Copy Note JSON" = "Copier le JSON de la Note";
/* Context menu option for copying the text from an note. */
"Copy Text" = "Copier le Texte";
/* Context menu option for copying the ID of the user who created the note. */
"Copy User ID" = "Copier l'Identifiant de l'Utilisateur";
/* Button to create account. */
"Create" = "Créer";
/* Button to create an account. */
"Create Account" = "Créer un Compte";
/* Example description about Bitcoin creator(s), Satoshi Nakamoto. */
"Creator(s) of Bitcoin. Absolute legend." = "Créateur(s) de Bitcoin. Légende absolue.";
/* Name of the app, shown on the first screen when user is not logged in. */
"Damus" = "Damus";
/* Button to pay a Lightning invoice with the user's default Lightning wallet. */
"Default Wallet" = "Portefeuille par défaut";
/* Button to delete a relay server that the user connects to. */
"Delete" = "<br>Effacer";
/* Button to dismiss a text field alert. */
"Dismiss" = "Rejeter";
/* Label to prompt display name entry. */
"Display Name" = "Afficher Nom";
/* Navigation title for DM view, which is the English abbreviation for Direct Message. */
"DM" = "DM";
/* Button to dismiss wallet selection view for paying Lightning invoice. */
"Done" = "Fini";
/* Heading indicating that this application allows users to earn money. */
"Earn Money" = "Gagnes de l'argent";
/* Button to edit user's profile. */
"Edit" = "Modifier";
/* Heading indicating that this application keeps private messaging end-to-end encrypted. */
"Encrypted" = "Crypté";
/* Navigation title for view of encrypted DMs, where DM is an English abbreviation for Direct Message. */
"Encrypted DMs" = "DMs cryptés";
/* Prompt for user to enter an account key to login. */
"Enter your account key to login:" = "Entrez votre clé de compte pour vous connecter:";
/* Error message indicating why saving keys failed. */
"Error: %@" = "Error: %@";
/* Filter state for seeing either only posts, or posts & replies. */
"Filter State" = "État du filtre";
/* Button to follow a user. */
"Follow" = "S'abonner<br>";
/* Label describing followers of a user. */
"Followers" = "Abonnés";
/* Text to indicate that the button next to it is in a state that indicates that it is in the process of following a profile.
Part of a larger sentence to describe how many profiles a user is following. */
"Following" = "Abonnements";
/* Label to indicate that the user is in the process of following another user. */
"Following..." = "Abonnements...";
/* Text to indicate that button next to it is in a state that will follow a profile when tapped. */
"Follows" = "Follow<br>";
/* Navigation bar title for Global view where posts from all connected relay servers appear. */
"Global" = "Global";
/* Navigation link to go to post referenced by hex code. */
"Goto post %@" = "Goto post %@";
/* Navigation link to go to profile. */
"Goto profile %@" = "Goto profile %@";
/* Navigation bar title for Home view where posts and replies appear from those who the user is following. */
"Home" = "Accueil";
/* Placeholder example text for profile picture URL. */
"https://example.com/pic.jpg" = "https://example.com/pic.jpg";
/* Placeholder example text for website URL for user profile. */
"https://jb55.com" = "https://jb55.com";
/* Error message indicating that an invalid account key was entered for login. */
"Invalid key" = "Clé non valide";
/* Placeholder example text for identifier used for NIP-05 verification. */
"jb55@jb55.com" = "jb55@jb55.com";
/* Moves the post button to the left side of the screen */
"Left Handed" = "Gaucher";
/* Button to complete account creation and start using the app. */
"Let's go!" = "Allons-y!";
/* Placeholder text for entry of Lightning Address or LNURL. */
"Lightning Address or LNURL" = "Adresse Lightning ou LNURL";
/* Indicates that the view is for paying a Lightning invoice. */
"Lightning Invoice" = "Facture Lightning";
/* Dropdown option label for Lightning wallet, LNLink. */
"LNLink" = "Lien LN";
/* Dropdown option label for system default for Lightning wallet. */
"Local default" = "Valeur locale par défaut";
/* Button to log into account.
Button to log into an account. */
"Login" = "Se connecter";
/* Alert for logging out the user.
Button for logging out the user.
Button to logout the user. */
"Logout" = "Se déconnecter";
/* Reminder message in alert to get customer to verify that their private security account key is saved saved before logging out. */
"Make sure your nsec account key is saved before you logout or you will lose access to this account" = "Assurez-vous que votre clé de compte nsec est enregistrée avant de vous déconnecter ou vous perdrez l'accès à ce compte";
/* Dropdown option label for Lightning wallet, Muun. */
"Muun" = "Muun";
/* Label for NIP-05 Verification section of user profile form. */
"NIP-05 Verification" = "Vérification NIP-05";
/* No search results. */
"none" = "none";
/* Indicates that there are no notes in the timeline to view. */
"Nothing to see here. Check back later!" = "Rien à voir ici. Revenez plus tard!";
/* Navigation title for notifications. */
"Notifications" = "Notifications";
/* String indicating that a given timestamp just occurred */
"now" = "now";
/* Prompt for user to enter in an account key to login. This text shows the characters the key could start with if it was a private key. */
"nsec1..." = "nsec1...";
/* Label indicating that a form input is optional. */
"optional" = "optional";
/* Button to pay a Lightning invoice. */
"Pay" = "Payer";
/* Navigation bar title for view to pay Lightning invoice. */
"Pay the Lightning invoice" = "Payer la facture Lightning";
/* Dropdown option label for Lightning wallet, Phoenix. */
"Phoenix" = "Phoenix";
/* Button to post a note. */
"Post" = "Publication";
/* Label for filter for seeing only posts (instead of posts and replies). */
"Posts" = "Publications";
/* Label for filter for seeing posts and replies (instead of only posts). */
"Posts & Replies" = "Publications & Réponses";
/* Heading indicating that this application keeps personally identifiable information private. A sentence describing what is done to keep data private comes after this heading. */
"Private" = "Privé";
/* Label to indicate that the text below is the user's private key used by only the user themself as a secret to login to access their account. */
"Private Key" = "Clé Privée";
/* Title of the secure field that holds the user's private key. */
"PrivateKey" = "PrivateKey";
/* Sidebar menu label for Profile view. */
"Profile" = "Profil";
/* Label for Profile Picture section of user profile form. */
"Profile Picture" = "Image de profil";
/* Section title for the user's public account ID. */
"Public Account ID" = "Identifiant publique de compte";
/* Label indicating that the text is a user's public account key. */
"Public key" = "Clé Publique";
/* Label indicating that the text is a user's public account key. */
"Public Key" = "Clé Publique";
/* Prompt to ask user if the key they entered is a public key. */
"Public Key?" = "Clé Publique?";
/* Navigation bar title for Reactions view. */
"Reactions" = "Réactions";
/* Section title for recommend relay servers that could be added as part of configuration */
"Recommended Relays" = "Relais Recommandés";
/* Text field for relay server. Used for testing purposes. */
"Relay" = "Relais";
/* Sidebar menu label for Relay servers view */
"Relays" = "Relais";
/* Label to indicate that the user is replying to themself. */
"Reply to self" = "Répondre à soi-même";
/* Label to indicate that the user is replying to 2 users. */
"Replying to %@ & %@" = "Répondre à %1$@ & %2$@";
/* Indicating that the user is replying to the following listed people. */
"Replying to:" = "Répondre à:";
/* Button to confirm reposting a post.
Title of alert for confirming to repost a post. */
"Repost" = "Republier";
/* Text indicating that the post was reposted (i.e. re-shared). */
"Reposted" = "A republié";
/* Section title for resetting the user */
"Reset" = "Réinitialiser";
/* Button to retry completing account creation after an error occurred. */
"Retry" = "Retenter";
/* Dropdown option label for Lightning wallet, River */
"River" = "River";
/* Example username of Bitcoin creator(s), Satoshi Nakamoto. */
"satoshi" = "satoshi";
/* Name of Bitcoin creator(s). */
"Satoshi Nakamoto" = "Satoshi Nakamoto";
/* Button for saving profile. */
"Save" = "Enregistrer";
/* Context menu option to save an image. */
"Save Image" = "Enregistrer Image";
/* Navigation link to search hashtag. */
"Search hashtag: #%@" = "Rechercher hashtag: #%@";
/* Placeholder text to prompt entry of search query. */
"Search..." = "Rechercher...";
/* Section title for user's secret account login key. */
"Secret Account Login Key" = "Clé secrète de connexion au compte";
/* Title of section for selecting a Lightning wallet to pay a Lightning invoice. */
"Select a Lightning wallet" = "Sélectionnez un portefeuille Lightning";
/* Prompt selection of user's default wallet */
"Select default wallet" = "Sélectionnez le portefeuille par défaut";
/* Text prompt for user to send a message to the other user. */
"Send a message to start the conversation..." = "Envoyez un message pour démarrer la conversation...";
/* Navigation title for Settings view.
Sidebar menu label for accessing the app settings */
"Settings" = "Paramètres";
/* Button to share an image. */
"Share" = "Partager";
/* Toggle to show or hide user's secret account login key. */
"Show" = "Afficher<br>";
/* Toggle to show or hide selection of wallet. */
"Show wallet selector" = "Afficher le sélecteur de portefeuille";
/* Sidebar menu label to sign out of the account. */
"Sign out" = "Se déconnecter";
/* Dropdown option label for Lightning wallet, Strike. */
"Strike" = "Strike";
/* Warning that the inputted account key is a public key and the result of what happens because of it. */
"This is a public key, you will not be able to make posts or interact in any way. This is used for viewing accounts from their perspective." = "Il s'agit d'une clé publique, vous ne pourrez pas publier de messages ou interagir de quelque manière que ce soit. Ceci est utilisé pour visualiser les comptes de leur point de vue.";
/* Warning that the inputted account key for login is an old-style and asking user to verify if it is a public key. */
"This is an old-style nostr key. We're not sure if it's a pubkey or private key. Please toggle the button below if this a public key." = "Il s'agit d'une clé Nostr à l'ancienne. Nous ne savons pas s'il s'agit d'une clé publique ou d'une clé privée. Veuillez basculer le bouton ci-dessous s'il s'agit d'une clé publique.";
/* Label to describe that a public key is the user's account ID and what they can do with it. */
"This is your account ID, you can give this to your friends so that they can follow you. Click to copy." = "Ceci est votre identifiant de compte, vous pouvez le donner à vos amis afin qu'ils puissent vous suivre. Cliquez pour copier.";
/* Label to describe that a private key is the user's secret account key and what they should do with it. */
"This is your secret account key. You need this to access your account. Don't share this with anyone! Save it in a password manager and keep it safe!" = "Il s'agit de votre clé de compte secrète. Vous en aurez besoin pour accéder à votre compte. Ne le partagez avec personne ! Enregistrez-le dans un gestionnaire de mots de passe et gardez-le en sécurité!";
/* Navigation bar title for note thread.
Navigation bar title for threaded event detail view. */
"Thread" = "Fil de discussion";
/* Text box prompt to ask user to type their post. */
"Type your post here..." = "Tapez votre message ici...";
/* Non-breaking space character to fill in blank space next to event action button icons. */
"u{00A0}" = "u{00A0}";
/* Button to unfollow a user. */
"Unfollow" = "Se désabonner";
/* Text to indicate that the button next to it is in a state that indicates that it is in the process of unfollowing a profile. */
"Unfollowing" = "Ne plus suivre";
/* Label to indicate that the user is in the process of unfollowing another user. */
"Unfollowing..." = "Ne plus suivre...<br>";
/* Text to indicate that the button next to it is in a state that will unfollow a profile when tapped. */
"Unfollows" = "Se désabonne de";
/* Label for Username section of user profile form.
Label to prompt username entry. */
"Username" = "Nom d'utilisateur";
/* Sidebar menu label for Wallet view. */
"Wallet" = "Portefeuille";
/* Dropdown option label for Lightning wallet, Wallet Of Satoshi. */
"Wallet Of Satoshi" = "Portefeuille de Satoshi";
/* Section title for selection of wallet. */
"Wallet Selector" = "Sélecteur de portefeuille";
/* Label for Website section of user profile form. */
"Website" = "Site Internet";
/* Welcoming message to the reader. The variable is 'you', the reader. */
"Welcome to the social network %@ control." = "Bienvenue sur le réseau social %@ contrôle.";
/* Text to welcome user. */
"Welcome, %@!" = "Bienvenue, %@!";
/* Placeholder example for relay server address. */
"wss://some.relay.com" = "wss://some.relay.com";
/* You, in this context, is the person who controls their own social network. You is used in the context of a larger sentence that welcomes the reader to the social network that they control themself. */
"you" = "you";
/* Label for Your Name section of user profile form. */
"Your Name" = "Votre Nom";
/* Dropdown option label for Lightning wallet, Zebedee. */
"Zebedee" = "Zebedee";
/* Dropdown option label for Lightning wallet, Zeus LN. */
"Zeus LN" = "LN de Zeus";

View File

@@ -0,0 +1,154 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>collapsed_event_view_other_notes</key>
<dict>
<key>NOTES</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%d other note</string>
<key>other</key>
<string>%d other notes</string>
</dict>
<key>NSStringLocalizedFormatKey</key>
<string>··· %#@NOTES@ ···</string>
</dict>
<key>followers_count</key>
<dict>
<key>FOLLOWERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Abonné</string>
<key>other</key>
<string>Abonnés</string>
</dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@FOLLOWERS@</string>
</dict>
<key>reactions_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@REACTIONS@</string>
<key>REACTIONS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Réaction</string>
<key>other</key>
<string>Réactions</string>
</dict>
</dict>
<key>relays_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@RELAYS@</string>
<key>RELAYS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Relais</string>
<key>other</key>
<string>Relais</string>
</dict>
</dict>
<key>replying_to_one_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Répondre à %@%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>&amp; %d other</string>
<key>other</key>
<string>&amp; %d others</string>
<key>zero</key>
<string></string>
</dict>
</dict>
<key>replying_to_two_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Répondre à %@, %@%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>&amp; %d other</string>
<key>other</key>
<string>&amp; %d others</string>
<key>zero</key>
<string></string>
</dict>
</dict>
<key>reposts_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@REPOSTS@</string>
<key>REPOSTS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Republication</string>
<key>other</key>
<string>Republications</string>
</dict>
</dict>
<key>sats_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%1$#@SATS@</string>
<key>SATS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>@</string>
<key>one</key>
<string>%2$@ sat</string>
<key>other</key>
<string>%2$@ sats</string>
</dict>
</dict>
<key>tips_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@TIPS@</string>
<key>TIPS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Pourboire</string>
<key>other</key>
<string>Pourboires</string>
</dict>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,9 @@
/* Bundle display name */
"CFBundleDisplayName" = "Damus";
/* Bundle name */
"CFBundleName" = "damus";
/* Privacy - Photo Library Additions Usage Description */
"NSPhotoLibraryAddUsageDescription" = "Damus'a fotoğraf kitaplığınıza erişim izni vermek, fotoğrafları kaydetmenize olanak tanır.";

View File

@@ -0,0 +1,493 @@
/* Blank space to separate profile picture from profile editor form. */
" " = "61b6edf1108e6f396680a33b02486a70_tr";
/* Description of how the nip05 identifier would be used for verification. */
"'%@' at '%@' will be used for verification" = "'%@' adresindeki '%@' doğrulama için kullanılacaktır";
/* Description of why the nip05 identifier is invalid. */
"'%@' is an invalid nip05 identifier. It should look like an email." = "'%@' geçersiz bir nip05 tanımlayıcısı. Tanımlayıcı, e-posta gibi gözükmeli. ";
/* Navigation bar title for view that shows who is following a user. */
"(Profile.displayName(profile: profile, pubkey: whos))'s Followers" = "(Profile.displayName(profile: profile, pubkey: whos))'nin Takipçileri";
/* Navigation bar title for view that shows who a user is following. */
"(who) following" = "(who)'nin Takip Ettikleri";
/* Prefix character to username. */
"@" = "@";
/* Amount of time that has passed since reply quote event occurred.
Abbreviated version of a nostr public key. */
"%@" = "%@";
/* Sentence composed of 2 variables to describe how many reposts. In source English, the first variable is the number of reposts, and the second variable is 'Repost' or 'Reposts'.
Sentence composed of 2 variables to describe how many profiles a user is following. In source English, the first variable is the number of profiles being followed, and the second variable is 'Following'. */
"%@ %@" = "%@ %@";
/* Explanation of what is done to keep personally identifiable information private. There is a heading that precedes this explanation which is a variable to this string. */
"%@. Creating an account doesn't require a phone number, email or name. Get started right away with zero friction." = "%@. Hesap oluşturmak için telefon numarası, e-posta veya isim gerekmez. Vakit kaybetmeden hemen hemen başlayın.";
/* Explanation of what is done to keep private data encrypted. There is a heading that precedes this explanation which is a variable to this string. */
"%@. End-to-End encrypted private messaging. Keep Big Tech out of your DMs" = "%@. Uçtan uca şifreli özel mesajlaşma. Teknoloji baronlarını DM'lerinden uzak tut";
/* Explanation of what can be done by users to earn money. There is a heading that precedes this explanation which is a variable to this string. */
"%@. Tip your friend's posts and stack sats with Bitcoin⚡, the native currency of the internet." = "%@. Arkadaşlarınızın gönderilerine bahşiş verin ve internetin yerel para birimi olan Bitcoin⚡ ile sats biriktirin.";
/* Number of reposts.
Number of profiles a user is following. */
"%lld" = "%lld";
/* Fraction of how many of the user's relay servers that are operational. */
"%lld/%lld" = "%lld/%lld";
/* Placeholder for event mention. */
"< e >" = "< e >";
/* Label to prompt for about text entry for user to describe about themself. */
"About" = "Hakkında";
/* Label for About Me section of user profile form. */
"About Me" = "Hakkında";
/* Placeholder text for About Me description. */
"Absolute Boss" = "Kral Adam";
/* Label to indicate the public ID of the account. */
"Account ID" = "Hesap Kimliği";
/* Button to add recommended relay server.
Button to confirm adding user inputted relay. */
"Add" = "Ekle";
/* Label for section for adding a relay server. */
"Add Relay" = "Röle Ekle";
/* Any amount of sats */
"Any" = "Herhangi";
/* Alert message to ask if user wants to repost a post. */
"Are you sure you want to repost this?" = "Bu gönderiyi yinelemek istediğinden emin misin?";
/* Label for Banner Image section of user profile form. */
"Banner Image" = "Pankart Resmi";
/* Reminder to user that they should save their account information. */
"Before we get started, you'll need to save your account info, otherwise you won't be able to login in the future if you ever uninstall Damus." = "Başlamadan önce, hesap bilgilierini kaydetmen gerekiyor. Aksi takdirde, Damus'u silme durumunda hesabına giriş yapamayacaksın.";
/* Dropdown option label for Lightning wallet, Bitcoin Beach. */
"Bitcoin Beach" = "Bitcoin Beach";
/* Label for Bitcoin Lightning Tips section of user profile form. */
"Bitcoin Lightning Tips" = "Bitcoin Lightning Bahşişleri";
/* Dropdown option label for Lightning wallet, Blixt Wallet */
"Blixt Wallet" = "Blixt Wallet";
/* Dropdown option label for Lightning wallet, Blue Wallet. */
"Blue Wallet" = "Blue Wallet";
/* Dropdown option label for Lightning wallet, Breez. */
"Breez" = "Breez";
/* Context menu option for broadcasting the user's note to all of the user's connected relay servers. */
"Broadcast" = "Yayınla";
/* Button to cancel out of posting a note.
Button to cancel out of reposting a post.
Button to cancel out of view adding user inputted relay.
Cancel out of logging out the user. */
"Cancel" = "İptal Et";
/* Dropdown option label for Lightning wallet, Cash App. */
"Cash App" = "Cash App";
/* Navigation bar title for Chatroom view. */
"Chat" = "Sohbet";
/* Button for clearing cached data. */
"Clear" = "Temizle";
/* Section title for clearing cached data. */
"Clear Cache" = "Önbelleği Temizle";
/* Label indicating that a user's key was copied. */
"Copied" = "Kopyalandı";
/* Button to copy a relay server address. */
"Copy" = "Kopyala";
/* Context menu option for copying the ID of the account that created the note. */
"Copy Account ID" = "Hesap Kimliğini Kopyala";
/* Context menu option to copy an image into clipboard.
Context menu option to copy an image to clipboard. */
"Copy Image" = "Resmi Kopyala";
/* Context menu option to copy the URL of an image into clipboard. */
"Copy Image URL" = "Resim URL'sini Kopyala";
/* Title of section for copying a Lightning invoice identifier. */
"Copy invoice" = "Faturayı Kopyala";
/* Context menu option for copying a user's Lightning URL. */
"Copy LNURL" = "LNURL'i Kopyala";
/* Context menu option for copying the ID of the note. */
"Copy Note ID" = "Gönderi Kimliğini Kopyala ";
/* Context menu option for copying the JSON text from the note. */
"Copy Note JSON" = "Gönderinin JSON Metnini Kopyala";
/* Context menu option for copying the text from an note. */
"Copy Text" = "Metni Kopyala";
/* Context menu option for copying the ID of the user who created the note. */
"Copy User ID" = "Hesap Kimliğini Kopyala";
/* Button to create account. */
"Create" = "Yarat";
/* Button to create an account. */
"Create Account" = "Hesap Yarat";
/* Example description about Bitcoin creator(s), Satoshi Nakamoto. */
"Creator(s) of Bitcoin. Absolute legend." = "Bitcoin'in Yaratıcısı. Kral Adam.";
/* Name of the app, shown on the first screen when user is not logged in. */
"Damus" = "Damus";
/* Button to pay a Lightning invoice with the user's default Lightning wallet. */
"Default Wallet" = "Varsayılan Cüzdan";
/* Button to delete a relay server that the user connects to. */
"Delete" = "Sil";
/* Button to dismiss a text field alert. */
"Dismiss" = "Yok Say";
/* Label to prompt display name entry. */
"Display Name" = "Görünür İsim";
/* Navigation title for DM view, which is the English abbreviation for Direct Message. */
"DM" = "DM";
/* Button to dismiss wallet selection view for paying Lightning invoice. */
"Done" = "Tamam";
/* Heading indicating that this application allows users to earn money. */
"Earn Money" = "Para Kazan";
/* Button to edit user's profile. */
"Edit" = "Düzenle";
/* Heading indicating that this application keeps private messaging end-to-end encrypted. */
"Encrypted" = "Şifreli";
/* Navigation title for view of encrypted DMs, where DM is an English abbreviation for Direct Message. */
"Encrypted DMs" = "Şifreli DM'ler";
/* Prompt for user to enter an account key to login. */
"Enter your account key to login:" = "Giriş yapmak için hesap anahtarını gir:";
/* Error message indicating why saving keys failed. */
"Error: %@" = "Hata: %@";
/* Filter state for seeing either only posts, or posts & replies. */
"Filter State" = "Filtre Durumu";
/* Button to follow a user. */
"Follow" = "Takip Et";
/* Label describing followers of a user. */
"Followers" = "Takipçiler";
/* Text to indicate that the button next to it is in a state that indicates that it is in the process of following a profile.
Part of a larger sentence to describe how many profiles a user is following. */
"Following" = "Takip Ediliyor";
/* Label to indicate that the user is in the process of following another user. */
"Following..." = "Takip Ediliyor...";
/* Text to indicate that button next to it is in a state that will follow a profile when tapped. */
"Follows" = "Takipler";
/* Navigation bar title for Global view where posts from all connected relay servers appear. */
"Global" = "Küresel";
/* Navigation link to go to post referenced by hex code. */
"Goto post %@" = "%@'a git";
/* Navigation link to go to profile. */
"Goto profile %@" = "%@'a git";
/* Navigation bar title for Home view where posts and replies appear from those who the user is following. */
"Home" = "Ev";
/* Placeholder example text for profile picture URL. */
"https://example.com/pic.jpg" = "https://example.com/pic.jpg";
/* Placeholder example text for website URL for user profile. */
"https://jb55.com" = "https://jb55.com";
/* Error message indicating that an invalid account key was entered for login. */
"Invalid key" = "Hatalı anahtar";
/* Placeholder example text for identifier used for NIP-05 verification. */
"jb55@jb55.com" = "jb55@jb55.com";
/* Moves the post button to the left side of the screen */
"Left Handed" = "Solak Modu";
/* Button to complete account creation and start using the app. */
"Let's go!" = "Haydi Başla!";
/* Placeholder text for entry of Lightning Address or LNURL. */
"Lightning Address or LNURL" = "Lightning Adresi ya da LNURL";
/* Indicates that the view is for paying a Lightning invoice. */
"Lightning Invoice" = "Lightning Faturası";
/* Dropdown option label for Lightning wallet, LNLink. */
"LNLink" = "LNLink";
/* Dropdown option label for system default for Lightning wallet. */
"Local default" = "Cihaz varsayılanı";
/* Button to log into account.
Button to log into an account. */
"Login" = "Giriş yap";
/* Alert for logging out the user.
Button for logging out the user.
Button to logout the user. */
"Logout" = "Çıkış yap";
/* Reminder message in alert to get customer to verify that their private security account key is saved saved before logging out. */
"Make sure your nsec account key is saved before you logout or you will lose access to this account" = "Çıkış yapmadan önce, nsec hesap anahtarını kaydetmeyi unutma. Aksi takdirde bu hesaba giremeyeceksin.";
/* Dropdown option label for Lightning wallet, Muun. */
"Muun" = "Muun";
/* Label for NIP-05 Verification section of user profile form. */
"NIP-05 Verification" = "NIP-05 Doğrulaması";
/* No search results. */
"none" = "sonuç yok";
/* Indicates that there are no notes in the timeline to view. */
"Nothing to see here. Check back later!" = "Görülecek bir şey yok. Daha sonra tekrar kontrol et!";
/* Navigation title for notifications. */
"Notifications" = "Bildirimler";
/* String indicating that a given timestamp just occurred */
"now" = "şimdi";
/* Prompt for user to enter in an account key to login. This text shows the characters the key could start with if it was a private key. */
"nsec1..." = "nsec1...";
/* Label indicating that a form input is optional. */
"optional" = "opsiyonel";
/* Button to pay a Lightning invoice. */
"Pay" = "Öde";
/* Navigation bar title for view to pay Lightning invoice. */
"Pay the Lightning invoice" = "Lightning faturasını öde";
/* Dropdown option label for Lightning wallet, Phoenix. */
"Phoenix" = "Phoenix";
/* Button to post a note. */
"Post" = "Gönder";
/* Label for filter for seeing only posts (instead of posts and replies). */
"Posts" = "Gönderiler";
/* Label for filter for seeing posts and replies (instead of only posts). */
"Posts & Replies" = "Gönderiler & Yorumlar";
/* Heading indicating that this application keeps personally identifiable information private. A sentence describing what is done to keep data private comes after this heading. */
"Private" = "Özel";
/* Label to indicate that the text below is the user's private key used by only the user themself as a secret to login to access their account. */
"Private Key" = "Özel Anahtar";
/* Title of the secure field that holds the user's private key. */
"PrivateKey" = "Özel Anahtar";
/* Sidebar menu label for Profile view. */
"Profile" = "Profil";
/* Label for Profile Picture section of user profile form. */
"Profile Picture" = "Profil Fotoğrafı";
/* Section title for the user's public account ID. */
"Public Account ID" = "Herkese Açık Hesap Kimliği";
/* Label indicating that the text is a user's public account key. */
"Public key" = "Umumi anahtar";
/* Label indicating that the text is a user's public account key. */
"Public Key" = "Herkese Açık Anahtar";
/* Prompt to ask user if the key they entered is a public key. */
"Public Key?" = "Herkese Açık Anahtar?";
/* Navigation bar title for Reactions view. */
"Reactions" = "Tepkiler";
/* Section title for recommend relay servers that could be added as part of configuration */
"Recommended Relays" = "Önerilen Röleler";
/* Text field for relay server. Used for testing purposes. */
"Relay" = "Röle";
/* Sidebar menu label for Relay servers view */
"Relays" = "Röleler";
/* Label to indicate that the user is replying to themself. */
"Reply to self" = "Kendine yanıt ver";
/* Label to indicate that the user is replying to 2 users. */
"Replying to %@ & %@" = "%1$@ & %2$@'ya yanıt ver";
/* Indicating that the user is replying to the following listed people. */
"Replying to:" = "Şunlara yanıt ver:";
/* Button to confirm reposting a post.
Title of alert for confirming to repost a post. */
"Repost" = "Yinele";
/* Text indicating that the post was reposted (i.e. re-shared). */
"Reposted" = "Yinelendi";
/* Section title for resetting the user */
"Reset" = "Sıfırla";
/* Button to retry completing account creation after an error occurred. */
"Retry" = "Yeniden Dene";
/* Dropdown option label for Lightning wallet, River */
"River" = "River";
/* Example username of Bitcoin creator(s), Satoshi Nakamoto. */
"satoshi" = "satoshi";
/* Name of Bitcoin creator(s). */
"Satoshi Nakamoto" = "Satoshi Nakamoto";
/* Button for saving profile. */
"Save" = "Kaydet";
/* Context menu option to save an image. */
"Save Image" = "Resmi Kaydet";
/* Navigation link to search hashtag. */
"Search hashtag: #%@" = "Etiketi ara: #%@";
/* Placeholder text to prompt entry of search query. */
"Search..." = "Ara...";
/* Section title for user's secret account login key. */
"Secret Account Login Key" = "Gizli Hesap Giriş Anahtarı";
/* Title of section for selecting a Lightning wallet to pay a Lightning invoice. */
"Select a Lightning wallet" = "Bir Lightning cüzdanı seç";
/* Prompt selection of user's default wallet */
"Select default wallet" = "Varsayılan cüzdanı seç";
/* Text prompt for user to send a message to the other user. */
"Send a message to start the conversation..." = "Sohbeti başlatmak için bir mesaj gönder";
/* Navigation title for Settings view.
Sidebar menu label for accessing the app settings */
"Settings" = "Ayarlar";
/* Button to share an image. */
"Share" = "Paylaş";
/* Toggle to show or hide user's secret account login key. */
"Show" = "Göster";
/* Toggle to show or hide selection of wallet. */
"Show wallet selector" = "Cüzdan seçiciyi göster";
/* Sidebar menu label to sign out of the account. */
"Sign out" = "Çıkış yap";
/* Dropdown option label for Lightning wallet, Strike. */
"Strike" = "Strike";
/* Warning that the inputted account key is a public key and the result of what happens because of it. */
"This is a public key, you will not be able to make posts or interact in any way. This is used for viewing accounts from their perspective." = "Bu herkese açık bir anahtardır, herhangi bir şekilde gönderi yapamaz veya etkileşimde bulunamazsınız. Bu, hesapları kendi bakış açılarından görüntülemek için kullanılır.";
/* Warning that the inputted account key for login is an old-style and asking user to verify if it is a public key. */
"This is an old-style nostr key. We're not sure if it's a pubkey or private key. Please toggle the button below if this a public key." = "Bu eski tip bir nostr anahtarı. Bunun bir pubkey mi yoksa private key mi olduğundan emin değiliz. Bu bir genel anahtarsa lütfen aşağıdaki düğmeyi değiştirin.";
/* Label to describe that a public key is the user's account ID and what they can do with it. */
"This is your account ID, you can give this to your friends so that they can follow you. Click to copy." = "Bu sizin hesap kimliğinizdir, sizi takip edebilmeleri için bunu arkadaşlarınıza verebilirsiniz. Kopyalamak için tıklayın.";
/* Label to describe that a private key is the user's secret account key and what they should do with it. */
"This is your secret account key. You need this to access your account. Don't share this with anyone! Save it in a password manager and keep it safe!" = "Bu sizin gizli hesap anahtarınızdır. Hesabınıza erişmek için buna ihtiyacınız var. Bunu kimseyle paylaşmayın! Bir şifre yöneticisine kaydedin ve güvende tutun!";
/* Navigation bar title for note thread.
Navigation bar title for threaded event detail view. */
"Thread" = "Konu";
/* Text box prompt to ask user to type their post. */
"Type your post here..." = "Gönderinizi buraya yazın...";
/* Non-breaking space character to fill in blank space next to event action button icons. */
"u{00A0}" = "u{00A0}";
/* Button to unfollow a user. */
"Unfollow" = "Takipten Çık";
/* Text to indicate that the button next to it is in a state that indicates that it is in the process of unfollowing a profile. */
"Unfollowing" = "Takipten Çıkılıyor";
/* Label to indicate that the user is in the process of unfollowing another user. */
"Unfollowing..." = "Takipten Çıkılıyor...";
/* Text to indicate that the button next to it is in a state that will unfollow a profile when tapped. */
"Unfollows" = "Takipten Çık";
/* Label for Username section of user profile form.
Label to prompt username entry. */
"Username" = "Kullanıcı Adı";
/* Sidebar menu label for Wallet view. */
"Wallet" = "Cüzdan";
/* Dropdown option label for Lightning wallet, Wallet Of Satoshi. */
"Wallet Of Satoshi" = "Wallet Of Satoshi";
/* Section title for selection of wallet. */
"Wallet Selector" = "Cüzdan Seçici";
/* Label for Website section of user profile form. */
"Website" = "Websitesi";
/* Welcoming message to the reader. The variable is 'you', the reader. */
"Welcome to the social network %@ control." = "Kontrolün %@'da olduğu sosyal ağa hoş geldiniz.";
/* Text to welcome user. */
"Welcome, %@!" = "Hoş geldin, %@";
/* Placeholder example for relay server address. */
"wss://some.relay.com" = "wss://some.relay.com";
/* You, in this context, is the person who controls their own social network. You is used in the context of a larger sentence that welcomes the reader to the social network that they control themself. */
"you" = "sen";
/* Label for Your Name section of user profile form. */
"Your Name" = "İsminiz";
/* Dropdown option label for Lightning wallet, Zebedee. */
"Zebedee" = "Zebedee";
/* Dropdown option label for Lightning wallet, Zeus LN. */
"Zeus LN" = "Zeus LN";

View File

@@ -0,0 +1,154 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>collapsed_event_view_other_notes</key>
<dict>
<key>NOTES</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>1%d diğer not</string>
<key>other</key>
<string>%d diğer notlar</string>
</dict>
<key>NSStringLocalizedFormatKey</key>
<string>··· %#@NOTES@ ···</string>
</dict>
<key>followers_count</key>
<dict>
<key>FOLLOWERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Takipçi</string>
<key>other</key>
<string>Takipçiler</string>
</dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@FOLLOWERS@</string>
</dict>
<key>reactions_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@REACTIONS@</string>
<key>REACTIONS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Tepki</string>
<key>other</key>
<string>Tepkiler</string>
</dict>
</dict>
<key>relays_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@RELAYS@</string>
<key>RELAYS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Röle</string>
<key>other</key>
<string>Röleler</string>
</dict>
</dict>
<key>replying_to_one_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%@%#@OTHERS@'lara yanıt </string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>&amp; %d diğer</string>
<key>other</key>
<string>&amp; %d diğerleri</string>
<key>zero</key>
<string></string>
</dict>
</dict>
<key>replying_to_two_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%@, %@%#@OTHERS@'a yanıt</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>&amp; %d diğer</string>
<key>other</key>
<string>&amp; %d diğerleri</string>
<key>zero</key>
<string></string>
</dict>
</dict>
<key>reposts_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@REPOSTS@</string>
<key>REPOSTS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Yineleme</string>
<key>other</key>
<string>Yinelemeler</string>
</dict>
</dict>
<key>sats_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%1$#@SATS@</string>
<key>SATS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>@</string>
<key>one</key>
<string>%2$@ sat</string>
<key>other</key>
<string>%2$@ sat</string>
</dict>
</dict>
<key>tips_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@TIPS@</string>
<key>TIPS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Bahşiş</string>
<key>other</key>
<string>Bahşişler</string>
</dict>
</dict>
</dict>
</plist>

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