diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml new file mode 100644 index 000000000..b849515de --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature.yml @@ -0,0 +1,48 @@ +--- +name: 🛠️ Feature Request +description: Suggest an idea to help us improve +title: "[Feature]: " +labels: + - "feature_request" + +body: + - type: markdown + attributes: + value: | + **Thanks :heart: for taking the time to fill out this feature request report!** + We kindly ask that you search to see if an issue [already exists](https://github.com/mastodon/mastodon-ios/issues?q=is%3Aissue+sort%3Acreated-desc+) for your feature. + + We are also happy to accept contributions from our users. For more details see [here](https://github.com/mastodon/mastodon-ios/blob/develop/Documentation/CONTRIBUTING.md). + + - type: textarea + attributes: + label: Description + description: | + A clear and concise description of the feature you're interested in. + validations: + required: true + + - type: textarea + attributes: + label: Suggested Solution + description: | + Describe the solution you'd like. A clear and concise description of what you want to happen. + validations: + required: true + + - type: textarea + attributes: + label: Alternatives + description: | + Describe alternatives you've considered. + A clear and concise description of any alternative solutions or features you've considered. + validations: + required: false + + - type: textarea + attributes: + label: Additional Context + description: | + Add any other context about the problem here. + validations: + required: false diff --git a/.github/scripts/build-release.sh b/.github/scripts/build-release.sh index e3efc59df..069aa673e 100755 --- a/.github/scripts/build-release.sh +++ b/.github/scripts/build-release.sh @@ -36,6 +36,8 @@ BUILD_NUMBER=$(app-store-connect get-latest-testflight-build-number $ENV_APP_ID BUILD_NUMBER=$((BUILD_NUMBER+1)) CURRENT_PROJECT_VERSION=${BUILD_NUMBER:-0} +echo "GITHUB_TAG_NAME=build-$CURRENT_PROJECT_VERSION" >> $GITHUB_ENV + agvtool new-version -all $CURRENT_PROJECT_VERSION xcrun xcodebuild clean \ diff --git a/.github/workflows/develop-build.yml b/.github/workflows/develop-build.yml index c37f00731..7dd9ebaab 100644 --- a/.github/workflows/develop-build.yml +++ b/.github/workflows/develop-build.yml @@ -4,6 +4,7 @@ on: push: branches: - develop + - release* - ci-test jobs: @@ -11,18 +12,19 @@ jobs: name: Build runs-on: macOS-12 steps: - - name: checkout + - name: Checkout uses: actions/checkout@v2 - - name: setup + - name: Setup env: NotificationEndpointDebug: ${{ secrets.NotificationEndpointDebug }} NotificationEndpointRelease: ${{ secrets.NotificationEndpointRelease }} run: exec ./.github/scripts/setup.sh - - uses: actions/setup-python@v4 + - name: Install codemagic-cli-tools + uses: actions/setup-python@v4 with: - python-version: '3.11' + python-version: '3.10' - run: | pip3 install codemagic-cli-tools - run: | @@ -43,7 +45,7 @@ jobs: api-key-id: ${{ secrets.APPSTORE_KEY_ID }} api-private-key: ${{ secrets.APPSTORE_PRIVATE_KEY }} - - name: build + - name: Build env: ENV_APP_ID: ${{ secrets.APP_ID }} ENV_ISSUER_ID: ${{ secrets.APPSTORE_ISSUER_ID }} @@ -60,6 +62,12 @@ jobs: api-key-id: ${{ secrets.APPSTORE_KEY_ID }} api-private-key: ${{ secrets.APPSTORE_PRIVATE_KEY }} + - name: Tag commit + uses: tvdias/github-tagger@v0.0.1 + with: + repo-token: "${{ secrets.GITHUB_TOKEN }}" + tag: "${{ env.GITHUB_TAG_NAME }}" + - name: Clean up keychain and provisioning profile if: ${{ always() }} run: | diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 827276208..1c40b6556 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -6,6 +6,9 @@ on: - master - develop - feature/* + - feature-* + - issue/* + - issue-* pull_request: branches: - develop diff --git a/.gitignore b/.gitignore index 2d787576b..017cfd6fa 100644 --- a/.gitignore +++ b/.gitignore @@ -125,4 +125,9 @@ Localization/StringsConvertor/output .DS_Store env/**/** -!env/.env \ No newline at end of file +!env/.env + + +## Ruby ### +vendor/ +.bundle/ diff --git a/Documentation/How-it-works.md b/Documentation/How-it-works.md new file mode 100644 index 000000000..6b48159e5 --- /dev/null +++ b/Documentation/How-it-works.md @@ -0,0 +1,96 @@ +# How it works +The app is currently build for iOS and iPadOS. We use the MVVM architecture to construct the whole app. Some design detail may not be the best practice. And any suggestions for improvements are welcome. + +## Data +A typical status timeline fetches results from the database using a predicate that fetch the active account's entities. Then data source dequeues an item then configure the view. Likes many other MVVM applications. The app binds the Core Data entity to view via Combine publisher. Because the RunLoop dispatch drawing on the next loop. So we could return quickly. + +## Layout +A timeline has many posts and each post has many components. For example avatar, name, username, timestamp, content, media, toolbar and e.t.c. The app uses `AutoLayout` with `UIStackView` to place it and control whether it should hide or not. + +## Performance +Although it's easily loading timeline with hundreds of thousands of entities due to the Core Data fault mechanism. Some old devices may have slow performance when I/O bottleneck. There are three potential profile chances for entities: +- preload fulfill +- layout in background +- limit the data fetching + +## SwiftUI +Some view models already migrate to `@Published` annotated output. It's future-proof support for SwiftUI. There are some views already transformed to `SwiftUI` likes `MastodonRegisterView` and `ReportReasonView`. + +# Take it apart +## Targets +The app builds with those targets: + +- Mastodon: the app itself +- NotificationService: E2E push notification service +- ShareActionExtension: iOS share action +- MastodonIntent: Siri shortcuts + +## MastodonSDK +There is a self-hosted Swift Package that contains the common libraries to build this app. + +- CoreDataStack: Core Data model definition and util methods +- MastodonAsset: image and font assets +- MastodonCommon: store App Group ID +- MastodonCore: the logic for the app +- MastodonExtension: system API extension utility +- MastodonLocalization: i18n resources +- MastodonSDK: Mastodon API client +- MastodonUI: App UI components + +#### CoreDataStack +App uses Core Data as the backend to persist all entitles from the server. So the app has the capability to keep the timeline and notifications. Another reason for using a database is it makes the app could respond to entity changes between different sources. For example, a user could skim in the home timeline and then interact with the same post on other pages with favorite or reblog action. Core Data will handle the property modifications and notify the home timeline to update the view. + +To simplify the database operations. There is only one persistent store for all accounts. We use `domain` to identify entity for different servers (a.k.a instance). Do not mix the `domain` with the Mastodon remote server name. For example. The domain is `mastodon.online` whereever the post (e.g. post come from `mstdn.jp`) and friends from for the account sign in `mastodon.online`. Also, do not only rely on `id` because it has conflict potential between different `domain`. The unique predicate is `domain` + `id`. + +The app use "One stack, two context" setup. There is one main managed object context for UI displaying and another background managed context for entities creating and updating. We assert the background context performs in a queue. Also, the app could accept mulitple background context model. Then the flag `-com.apple.CoreData.ConcurrencyDebug 1` will be usful. + +###### How to create a new Entity +First, select the `CoreData.xcdatamodeld` file and in menu `Editor > Add Model Version…` to create a new version. Make sure active the new version in the inspect panel. e.g. `Model Version. Current > "Core Data 5"` + +Then use the `Add Entity` button create new Entity. +1. Give a name in data model inspect panel. +2. Also, set the `Module` to `CoreDataStack`. +3. Set the `Codegen` to `Manual/None`. We use `Sourery` generates the template code. +4. Create the `Entity.swift` file and declear the properties and relationships. + +###### How to add or remove property for Entity +We using the Core Data lightweight migration. Please check the rules detail [here](https://developer.apple.com/documentation/coredata/using_lightweight_migration). And keep in mind that we using two-way relationship. And a relationship could be one-to-one, one-to-many/many-to-one. + +Tip: + +Please check the `Soucery` and use that generates getter and setter for properties and relationships. It's could save you time. To take the benefit from the dynamic property. We can declare a raw value property and then use compute property to construct the struct we want (e.g. `Feed.acct`). Or control the primitive by hand and declare the mirror type for this value (e.g `Status.attachments`). + +###### How to persist the Entity +Please check the `Persistence+Status.swift`. We follow the pattern: migrate the old one if exists. Otherwise, create a new one. (Maybe some improvements could be adopted?) + +#### MastodonAsset +Sourcery powered assets package. + +#### MastodonCommon +Shared code for preference and configuration. + +### MastodonCore +The core logic to drive the app. + +#### MastodonExtension +Utility extension codes for SDK. + +#### MastodonLocalization +Sourcery powered i18n package. + +#### MastodonSDK +Mastodon API wrapper with Combine style API. + +#### MastodonUI +Mastodon app UI components. + +## NotificationService +Mastodon server accepts push notification register and we use the [toot-relay](https://github.com/DagAgren/toot-relay) to pass the server notifications to APNs. The message is E2E encrypted. The app will create an on-device private key for notification and save it into the keychain. + +When the push notification is incoming. iOS will spawn our NotificationService extension to handle the message. At that time the message is decrypted and displayed as a banner or in-app silent notification event when the app is in the foreground. All the notification count and deep-link logic are handled by the main app. + +## ShareActionExtension +The iOS Share Extension allows users to share links or media from other apps. The app uses the same implementation for the main app and the share extension. Then different is less available memoery for extension so maybe some memory bounded task could crash the app. (Plesae file the issue) + +## MastodonIntent +iOS Siri shortcut supports. It allows iOS directly publish posts via Shortcut without app launching. diff --git a/Gemfile.lock b/Gemfile.lock index e0ed91c5b..15d02a8ec 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -100,7 +100,9 @@ GEM PLATFORMS arm64-darwin-21 + arm64-darwin-22 x86_64-darwin-21 + x86_64-darwin-22 DEPENDENCIES arkana diff --git a/Localization/Localizable.stringsdict b/Localization/Localizable.stringsdict index cd97825f4..37ce1f032 100644 --- a/Localization/Localizable.stringsdict +++ b/Localization/Localizable.stringsdict @@ -13,15 +13,15 @@ NSStringFormatValueTypeKey ld zero - no unread notification + no unread notifications one 1 unread notification few %ld unread notifications many - %ld unread notification + %ld unread notifications other - %ld unread notification + %ld unread notifications a11y.plural.count.input_limit_exceeds @@ -71,7 +71,7 @@ a11y.plural.count.characters_left NSStringLocalizedFormatKey - %#@character_count@ left + %#@character_count@ character_count NSStringFormatSpecTypeKey @@ -79,15 +79,15 @@ NSStringFormatValueTypeKey ld zero - no characters + no characters left one - 1 character + 1 character left few - %ld characters + %ld characters left many - %ld characters + %ld characters left other - %ld characters + %ld characters left plural.count.followed_by_and_mutual diff --git a/Localization/README.md b/Localization/README.md index 93bf290bc..b44fa350d 100644 --- a/Localization/README.md +++ b/Localization/README.md @@ -27,7 +27,7 @@ If there are new translations, Crowdin pushes new commits to a branch called `l1 To update or add new translations, the workflow is as follows: 1. Merge the PR with `l10n_develop` into `develop`. It's usually called `New Crowdin Updates` -2. Run `update.localization.sh` on your computer. +2. Run `update_localization.sh` on your computer. 3. Commit the changes and push `develop`. [crowdin-mastodon-ios]: https://crowdin.com/project/mastodon-for-ios diff --git a/Localization/StringsConvertor/Intents/input/an.lproj/Intents.strings b/Localization/StringsConvertor/Intents/input/an.lproj/Intents.strings new file mode 100644 index 000000000..597347680 --- /dev/null +++ b/Localization/StringsConvertor/Intents/input/an.lproj/Intents.strings @@ -0,0 +1,51 @@ +"16wxgf" = "Publicar en Mastodon"; + +"751xkl" = "Conteniu de Texto"; + +"CsR7G2" = "Publicar en Mastodon"; + +"HZSGTr" = "Qué conteniu publicar?"; + +"HdGikU" = "Publicación fallida"; + +"KDNTJ4" = "Motivo d'o Fallo"; + +"RHxKOw" = "Ninviar Publicación con conteniu de texto"; + +"RxSqsb" = "Publicación"; + +"WCIR3D" = "Publicar ${content} en Mastodon"; + +"ZKJSNu" = "Publicación"; + +"ZS1XaK" = "${content}"; + +"ZbSjzC" = "Visibilidat"; + +"Zo4jgJ" = "Visibilidat d'o Post"; + +"apSxMG-dYQ5NN" = "I hai ${count} opcions que coinciden con «Publico»."; + +"apSxMG-ehFLjY" = "I hai ${count} opcions que coinciden con «Solo seguidores»."; + +"ayoYEb-dYQ5NN" = "${content}, Publico"; + +"ayoYEb-ehFLjY" = "${content}, Nomás Seguidores"; + +"dUyuGg" = "Publicar en Mastodon"; + +"dYQ5NN" = "Publico"; + +"ehFLjY" = "Solo Seguidores"; + +"gfePDu" = "Publicación fallida. ${failureReason}"; + +"k7dbKQ" = "Publicación ninviada con exito."; + +"oGiqmY-dYQ5NN" = "Nomás per confirmar, querebas «Publico»?"; + +"oGiqmY-ehFLjY" = "Nomás per confirmar, querebas «Nomás seguidores»?"; + +"rM6dvp" = "URL"; + +"ryJLwG" = "Publicación ninviada con exito. "; diff --git a/Localization/StringsConvertor/Intents/input/an.lproj/Intents.stringsdict b/Localization/StringsConvertor/Intents/input/an.lproj/Intents.stringsdict new file mode 100644 index 000000000..9e972ae51 --- /dev/null +++ b/Localization/StringsConvertor/Intents/input/an.lproj/Intents.stringsdict @@ -0,0 +1,38 @@ + + + + + There are ${count} options matching ‘${content}’. - 2 + + NSStringLocalizedFormatKey + I hai %#@count_option@ coincidencias con «${content}». + count_option + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + %ld + one + 1 opción + other + %ld opcions + + + There are ${count} options matching ‘${visibility}’. + + NSStringLocalizedFormatKey + I hai %#@count_option@ coincidencias con «${visibility}». + count_option + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + %ld + one + 1 opción + other + %ld opcions + + + + diff --git a/Localization/StringsConvertor/Intents/input/ca.lproj/Intents.strings b/Localization/StringsConvertor/Intents/input/ca.lproj/Intents.strings index 6b92eb263..c02ac08cf 100644 --- a/Localization/StringsConvertor/Intents/input/ca.lproj/Intents.strings +++ b/Localization/StringsConvertor/Intents/input/ca.lproj/Intents.strings @@ -22,7 +22,7 @@ "ZbSjzC" = "Visibilitat"; -"Zo4jgJ" = "Visibilitat de la Publicació"; +"Zo4jgJ" = "Visibilitat de la publicació"; "apSxMG-dYQ5NN" = "Hi ha ${count} opcions que coincideixen amb ‘Públic’."; @@ -30,9 +30,9 @@ "ayoYEb-dYQ5NN" = "${content}, Públic"; -"ayoYEb-ehFLjY" = "${content}, Només Seguidors"; +"ayoYEb-ehFLjY" = "${content}, Només seguidors"; -"dUyuGg" = "Publicació"; +"dUyuGg" = "Publica a Mastodon"; "dYQ5NN" = "Públic"; diff --git a/Localization/StringsConvertor/Intents/input/cs.lproj/Intents.stringsdict b/Localization/StringsConvertor/Intents/input/cs.lproj/Intents.stringsdict index deea8db12..29249b2cc 100644 --- a/Localization/StringsConvertor/Intents/input/cs.lproj/Intents.stringsdict +++ b/Localization/StringsConvertor/Intents/input/cs.lproj/Intents.stringsdict @@ -25,7 +25,7 @@ There are ${count} options matching ‘${visibility}’. NSStringLocalizedFormatKey - There are %#@count_option@ matching ‘${visibility}’. + Existuje %#@count_option@ odpovídající „${visibility}“. count_option NSStringFormatSpecTypeKey diff --git a/Localization/StringsConvertor/Intents/input/cy.lproj/Intents.strings b/Localization/StringsConvertor/Intents/input/cy.lproj/Intents.strings index 6877490ba..11007d059 100644 --- a/Localization/StringsConvertor/Intents/input/cy.lproj/Intents.strings +++ b/Localization/StringsConvertor/Intents/input/cy.lproj/Intents.strings @@ -1,51 +1,51 @@ -"16wxgf" = "Post on Mastodon"; +"16wxgf" = "Postio ar Mastodon"; -"751xkl" = "Text Content"; +"751xkl" = "Cynnwys Testun"; -"CsR7G2" = "Post on Mastodon"; +"CsR7G2" = "Postio ar Mastodon"; -"HZSGTr" = "What content to post?"; +"HZSGTr" = "Pa gynnwys i bostio?"; -"HdGikU" = "Posting failed"; +"HdGikU" = "Methwyd postio"; -"KDNTJ4" = "Failure Reason"; +"KDNTJ4" = "Rheswm y Gwall"; -"RHxKOw" = "Send Post with text content"; +"RHxKOw" = "Cyhoeddi Post â chynnwys testun"; "RxSqsb" = "Post"; -"WCIR3D" = "Post ${content} on Mastodon"; +"WCIR3D" = "Postio ${content} ar Mastodon"; "ZKJSNu" = "Post"; "ZS1XaK" = "${content}"; -"ZbSjzC" = "Visibility"; +"ZbSjzC" = "Preifatrwydd"; -"Zo4jgJ" = "Post Visibility"; +"Zo4jgJ" = "Preifatrwydd Post"; -"apSxMG-dYQ5NN" = "There are ${count} options matching ‘Public’."; +"apSxMG-dYQ5NN" = "Ceir ${count} opsiwn ar gyfer ‘Cyhoeddus’."; -"apSxMG-ehFLjY" = "There are ${count} options matching ‘Followers Only’."; +"apSxMG-ehFLjY" = "Ceir ${count} opsiwn ar gyfer ‘Dilynwyr yn Unig’."; -"ayoYEb-dYQ5NN" = "${content}, Public"; +"ayoYEb-dYQ5NN" = "${content}, Cyhoeddus"; -"ayoYEb-ehFLjY" = "${content}, Followers Only"; +"ayoYEb-ehFLjY" = "${content}, Dilynwyr yn Unig"; -"dUyuGg" = "Post on Mastodon"; +"dUyuGg" = "Postio ar Mastodon"; -"dYQ5NN" = "Public"; +"dYQ5NN" = "Cyhoeddus"; -"ehFLjY" = "Followers Only"; +"ehFLjY" = "Dilynwyr yn unig"; -"gfePDu" = "Posting failed. ${failureReason}"; +"gfePDu" = "Methwyd postio. ${failureReason}"; -"k7dbKQ" = "Post was sent successfully."; +"k7dbKQ" = "Cyhoeddwyd y post yn llwyddiannus."; -"oGiqmY-dYQ5NN" = "Just to confirm, you wanted ‘Public’?"; +"oGiqmY-dYQ5NN" = "I gadarnhau, rydych chi am ddewis ‘Cyhoeddus’?"; -"oGiqmY-ehFLjY" = "Just to confirm, you wanted ‘Followers Only’?"; +"oGiqmY-ehFLjY" = "I gadarnhau, rydych chi am ddewis ‘Dilynwyr yn Unig’?"; "rM6dvp" = "URL"; -"ryJLwG" = "Post was sent successfully. "; +"ryJLwG" = "Cyhoeddwyd y post yn llwyddiannus. "; diff --git a/Localization/StringsConvertor/Intents/input/cy.lproj/Intents.stringsdict b/Localization/StringsConvertor/Intents/input/cy.lproj/Intents.stringsdict index f273a551d..1f37f6a45 100644 --- a/Localization/StringsConvertor/Intents/input/cy.lproj/Intents.stringsdict +++ b/Localization/StringsConvertor/Intents/input/cy.lproj/Intents.stringsdict @@ -5,7 +5,7 @@ There are ${count} options matching ‘${content}’. - 2 NSStringLocalizedFormatKey - There are %#@count_option@ matching ‘${content}’. + Ceir %#@count_option@ ar gyfer '${content}'. count_option NSStringFormatSpecTypeKey @@ -13,23 +13,23 @@ NSStringFormatValueTypeKey %ld zero - %ld options + %ld opsiynau one - 1 option + %ld opsiwn two - %ld options + %ld opsiwn few - %ld options + %ld opsiwn many - %ld options + %ld o opsiynau other - %ld options + %ld o opsiynau There are ${count} options matching ‘${visibility}’. NSStringLocalizedFormatKey - There are %#@count_option@ matching ‘${visibility}’. + Ceir %#@count_option@ ar gyfer '${visibility}'. count_option NSStringFormatSpecTypeKey @@ -37,17 +37,17 @@ NSStringFormatValueTypeKey %ld zero - %ld options + %ld opsiynau one - 1 option + %ld opsiwn two - %ld options + %ld opsiwn few - %ld options + %ld opsiwn many - %ld options + %ld o opsiynau other - %ld options + %ld opsiwn diff --git a/Localization/StringsConvertor/Intents/input/de.lproj/Intents.strings b/Localization/StringsConvertor/Intents/input/de.lproj/Intents.strings index fd3fbd40f..5d0056766 100644 --- a/Localization/StringsConvertor/Intents/input/de.lproj/Intents.strings +++ b/Localization/StringsConvertor/Intents/input/de.lproj/Intents.strings @@ -1,12 +1,12 @@ -"16wxgf" = "Auf Mastodon posten"; +"16wxgf" = "Auf Mastodon veröffentlichen"; "751xkl" = "Textinhalt"; -"CsR7G2" = "Auf Mastodon posten"; +"CsR7G2" = "Auf Mastodon veröffentlichen"; -"HZSGTr" = "Welcher Inhalt soll gepostet werden?"; +"HZSGTr" = "Welcher Inhalt soll veröffentlicht werden?"; -"HdGikU" = "Posten fehlgeschlagen"; +"HdGikU" = "Veröffentlichen fehlgeschlagen"; "KDNTJ4" = "Fehlerursache"; diff --git a/Localization/StringsConvertor/Intents/input/he.lproj/Intents.strings b/Localization/StringsConvertor/Intents/input/he.lproj/Intents.strings new file mode 100644 index 000000000..6877490ba --- /dev/null +++ b/Localization/StringsConvertor/Intents/input/he.lproj/Intents.strings @@ -0,0 +1,51 @@ +"16wxgf" = "Post on Mastodon"; + +"751xkl" = "Text Content"; + +"CsR7G2" = "Post on Mastodon"; + +"HZSGTr" = "What content to post?"; + +"HdGikU" = "Posting failed"; + +"KDNTJ4" = "Failure Reason"; + +"RHxKOw" = "Send Post with text content"; + +"RxSqsb" = "Post"; + +"WCIR3D" = "Post ${content} on Mastodon"; + +"ZKJSNu" = "Post"; + +"ZS1XaK" = "${content}"; + +"ZbSjzC" = "Visibility"; + +"Zo4jgJ" = "Post Visibility"; + +"apSxMG-dYQ5NN" = "There are ${count} options matching ‘Public’."; + +"apSxMG-ehFLjY" = "There are ${count} options matching ‘Followers Only’."; + +"ayoYEb-dYQ5NN" = "${content}, Public"; + +"ayoYEb-ehFLjY" = "${content}, Followers Only"; + +"dUyuGg" = "Post on Mastodon"; + +"dYQ5NN" = "Public"; + +"ehFLjY" = "Followers Only"; + +"gfePDu" = "Posting failed. ${failureReason}"; + +"k7dbKQ" = "Post was sent successfully."; + +"oGiqmY-dYQ5NN" = "Just to confirm, you wanted ‘Public’?"; + +"oGiqmY-ehFLjY" = "Just to confirm, you wanted ‘Followers Only’?"; + +"rM6dvp" = "URL"; + +"ryJLwG" = "Post was sent successfully. "; diff --git a/Localization/StringsConvertor/Intents/input/he.lproj/Intents.stringsdict b/Localization/StringsConvertor/Intents/input/he.lproj/Intents.stringsdict new file mode 100644 index 000000000..c88207000 --- /dev/null +++ b/Localization/StringsConvertor/Intents/input/he.lproj/Intents.stringsdict @@ -0,0 +1,46 @@ + + + + + There are ${count} options matching ‘${content}’. - 2 + + NSStringLocalizedFormatKey + There are %#@count_option@ matching ‘${content}’. + count_option + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + %ld + one + 1 option + two + %ld options + many + %ld options + other + %ld options + + + There are ${count} options matching ‘${visibility}’. + + NSStringLocalizedFormatKey + There are %#@count_option@ matching ‘${visibility}’. + count_option + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + %ld + one + 1 option + two + %ld options + many + %ld options + other + %ld options + + + + diff --git a/Localization/StringsConvertor/Intents/input/id.lproj/Intents.strings b/Localization/StringsConvertor/Intents/input/id.lproj/Intents.strings index aa86f7bde..e5376e35e 100644 --- a/Localization/StringsConvertor/Intents/input/id.lproj/Intents.strings +++ b/Localization/StringsConvertor/Intents/input/id.lproj/Intents.strings @@ -4,13 +4,13 @@ "CsR7G2" = "Buat Postingan di Mastodon"; -"HZSGTr" = "What content to post?"; +"HZSGTr" = "Konten apa yang ingin diposting?"; "HdGikU" = "Gagal memposting"; "KDNTJ4" = "Alasan Kegagalan"; -"RHxKOw" = "Send Post with text content"; +"RHxKOw" = "Kirim Postingan dengan konten berupa teks"; "RxSqsb" = "Postingan"; @@ -24,9 +24,9 @@ "Zo4jgJ" = "Visibilitas Postingan"; -"apSxMG-dYQ5NN" = "There are ${count} options matching ‘Public’."; +"apSxMG-dYQ5NN" = "Tidak ada ${count} opsi yang cocok dengan 'Publik'."; -"apSxMG-ehFLjY" = "There are ${count} options matching ‘Followers Only’."; +"apSxMG-ehFLjY" = "Tidak ada ${count} opsi yang cocok dengan 'Untuk Pengikut Saja'."; "ayoYEb-dYQ5NN" = "${content}, Publik"; diff --git a/Localization/StringsConvertor/Intents/input/id.lproj/Intents.stringsdict b/Localization/StringsConvertor/Intents/input/id.lproj/Intents.stringsdict index a14f8b9ff..61779d15c 100644 --- a/Localization/StringsConvertor/Intents/input/id.lproj/Intents.stringsdict +++ b/Localization/StringsConvertor/Intents/input/id.lproj/Intents.stringsdict @@ -5,7 +5,7 @@ There are ${count} options matching ‘${content}’. - 2 NSStringLocalizedFormatKey - There are %#@count_option@ matching ‘${content}’. + Ada %#@count_option@ yang cocok dengan ${content}. count_option NSStringFormatSpecTypeKey @@ -13,13 +13,13 @@ NSStringFormatValueTypeKey %ld other - %ld options + %ld opsi There are ${count} options matching ‘${visibility}’. NSStringLocalizedFormatKey - There are %#@count_option@ matching ‘${visibility}’. + Ada %#@count_option@ yang cocok dengan ${visibility}. count_option NSStringFormatSpecTypeKey @@ -27,7 +27,7 @@ NSStringFormatValueTypeKey %ld other - %ld options + %ld opsi diff --git a/Localization/StringsConvertor/Intents/input/is.lproj/Intents.strings b/Localization/StringsConvertor/Intents/input/is.lproj/Intents.strings new file mode 100644 index 000000000..196c33e70 --- /dev/null +++ b/Localization/StringsConvertor/Intents/input/is.lproj/Intents.strings @@ -0,0 +1,51 @@ +"16wxgf" = "Birta á Mastodon"; + +"751xkl" = "Efni texta"; + +"CsR7G2" = "Birta á Mastodon"; + +"HZSGTr" = "Hvaða efni á að birta?"; + +"HdGikU" = "Birting færslu mistókst"; + +"KDNTJ4" = "Ástæða bilunar"; + +"RHxKOw" = "Senda færslu með textaefni"; + +"RxSqsb" = "Færsla"; + +"WCIR3D" = "Birta ${content} á Mastodon"; + +"ZKJSNu" = "Færsla"; + +"ZS1XaK" = "${content}"; + +"ZbSjzC" = "Sýnileiki"; + +"Zo4jgJ" = "Sýnileiki færslu"; + +"apSxMG-dYQ5NN" = "Það eru ${count} valkostir sem samsvara ‘Opinbert’."; + +"apSxMG-ehFLjY" = "Það eru ${count} valkostir sem samsvara ‘Einungis fylgjendur’."; + +"ayoYEb-dYQ5NN" = "${content}, opinbert"; + +"ayoYEb-ehFLjY" = "${content}, einungis fylgjendur"; + +"dUyuGg" = "Birta á Mastodon"; + +"dYQ5NN" = "Opinbert"; + +"ehFLjY" = "Einungis fylgjendur"; + +"gfePDu" = "Birting færslu mistókst. ${failureReason}"; + +"k7dbKQ" = "Það tókst að senda færsluna."; + +"oGiqmY-dYQ5NN" = "Bara til að staðfesta, þú vildir 'Opinbert'?"; + +"oGiqmY-ehFLjY" = "Bara til að staðfesta, þú vildir ''Einungis fylgjendur'?"; + +"rM6dvp" = "URL-slóð"; + +"ryJLwG" = "Það tókst að senda færsluna. "; diff --git a/Localization/StringsConvertor/Intents/input/is.lproj/Intents.stringsdict b/Localization/StringsConvertor/Intents/input/is.lproj/Intents.stringsdict new file mode 100644 index 000000000..fe12c972a --- /dev/null +++ b/Localization/StringsConvertor/Intents/input/is.lproj/Intents.stringsdict @@ -0,0 +1,38 @@ + + + + + There are ${count} options matching ‘${content}’. - 2 + + NSStringLocalizedFormatKey + Það eru %#@count_option@ sem samsvara ‘${content}’. + count_option + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + %ld + one + 1 valkostur + other + %ld valkostir + + + There are ${count} options matching ‘${visibility}’. + + NSStringLocalizedFormatKey + Það eru %#@count_option@ sem samsvara ‘${visibility}’. + count_option + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + %ld + one + 1 valkostur + other + %ld valkostir + + + + diff --git a/Localization/StringsConvertor/Intents/input/kab.lproj/Intents.stringsdict b/Localization/StringsConvertor/Intents/input/kab.lproj/Intents.stringsdict index a8aeeaaf1..6cead6f34 100644 --- a/Localization/StringsConvertor/Intents/input/kab.lproj/Intents.stringsdict +++ b/Localization/StringsConvertor/Intents/input/kab.lproj/Intents.stringsdict @@ -29,9 +29,9 @@ NSStringFormatValueTypeKey %ld one - 1 uɣewwaṛ + %ld n uɣewwaṛ other - %ld iɣewwaṛen + %ld n iɣewwaṛen diff --git a/Localization/StringsConvertor/Intents/input/lv.lproj/Intents.strings b/Localization/StringsConvertor/Intents/input/lv.lproj/Intents.strings index eddd2026f..fdd529eee 100644 --- a/Localization/StringsConvertor/Intents/input/lv.lproj/Intents.strings +++ b/Localization/StringsConvertor/Intents/input/lv.lproj/Intents.strings @@ -1,51 +1,51 @@ -"16wxgf" = "Post on Mastodon"; +"16wxgf" = "Publicē Mastodon"; -"751xkl" = "Text Content"; +"751xkl" = "Teksta Saturu"; -"CsR7G2" = "Post on Mastodon"; +"CsR7G2" = "Publicē Mastodon"; -"HZSGTr" = "What content to post?"; +"HZSGTr" = "Kādu saturu publicēt?"; -"HdGikU" = "Posting failed"; +"HdGikU" = "Publicēšana neizdevās"; -"KDNTJ4" = "Failure Reason"; +"KDNTJ4" = "Neizdošanās Iemesls"; -"RHxKOw" = "Send Post with text content"; +"RHxKOw" = "Sūtīt Ziņu ar teksta saturu"; "RxSqsb" = "Ziņa"; -"WCIR3D" = "Post ${content} on Mastodon"; +"WCIR3D" = "Publicēt ${content} Mastodon"; -"ZKJSNu" = "Post"; +"ZKJSNu" = "Ziņa"; "ZS1XaK" = "${content}"; -"ZbSjzC" = "Visibility"; +"ZbSjzC" = "Redzamība"; -"Zo4jgJ" = "Post Visibility"; +"Zo4jgJ" = "Ziņu Redzamība"; -"apSxMG-dYQ5NN" = "There are ${count} options matching ‘Public’."; +"apSxMG-dYQ5NN" = "Ir ${count} opcijas, kas atbilst “Publisks”."; -"apSxMG-ehFLjY" = "There are ${count} options matching ‘Followers Only’."; +"apSxMG-ehFLjY" = "Ir ${count} opcijas, kas atbilst “Tikai Sekotājiem”."; -"ayoYEb-dYQ5NN" = "${content}, Public"; +"ayoYEb-dYQ5NN" = "${content}, Publisks"; -"ayoYEb-ehFLjY" = "${content}, Followers Only"; +"ayoYEb-ehFLjY" = "${content}, Tikai Sekotājiem"; -"dUyuGg" = "Post on Mastodon"; +"dUyuGg" = "Publicē Mastodon"; "dYQ5NN" = "Publisks"; "ehFLjY" = "Tikai sekotājiem"; -"gfePDu" = "Posting failed. ${failureReason}"; +"gfePDu" = "Publicēšana neizdevās. ${failureReason}"; -"k7dbKQ" = "Post was sent successfully."; +"k7dbKQ" = "Ziņa tika veiksmīgi nosūtīta."; -"oGiqmY-dYQ5NN" = "Just to confirm, you wanted ‘Public’?"; +"oGiqmY-dYQ5NN" = "Tikai apstiprinājumam, tu vēlējies \"Publisks\"?"; -"oGiqmY-ehFLjY" = "Just to confirm, you wanted ‘Followers Only’?"; +"oGiqmY-ehFLjY" = "Tikai apstiprinājumam, tu vēlējies \"Tikai Sekotājiem\"?"; "rM6dvp" = "URL"; -"ryJLwG" = "Post was sent successfully. "; +"ryJLwG" = "Ziņa tika veiksmīgi nosūtīta. "; diff --git a/Localization/StringsConvertor/Intents/input/lv.lproj/Intents.stringsdict b/Localization/StringsConvertor/Intents/input/lv.lproj/Intents.stringsdict index 8926678aa..2461fec49 100644 --- a/Localization/StringsConvertor/Intents/input/lv.lproj/Intents.stringsdict +++ b/Localization/StringsConvertor/Intents/input/lv.lproj/Intents.stringsdict @@ -5,7 +5,7 @@ There are ${count} options matching ‘${content}’. - 2 NSStringLocalizedFormatKey - There are %#@count_option@ matching ‘${content}’. + Ir %#@count_option@, kas atbilst “${content}”. count_option NSStringFormatSpecTypeKey @@ -13,17 +13,17 @@ NSStringFormatValueTypeKey %ld zero - %ld options + %ld opciju one - 1 option + 1 opcija other - %ld options + %ld opcijas There are ${count} options matching ‘${visibility}’. NSStringLocalizedFormatKey - There are %#@count_option@ matching ‘${visibility}’. + Ir %#@count_option@, kas atbilst “${visibility}”. count_option NSStringFormatSpecTypeKey @@ -31,11 +31,11 @@ NSStringFormatValueTypeKey %ld zero - %ld options + %ld izvēles one - 1 option + 1 izvēle other - %ld options + %ld izvēles diff --git a/Localization/StringsConvertor/Intents/input/my.lproj/Intents.strings b/Localization/StringsConvertor/Intents/input/my.lproj/Intents.strings new file mode 100644 index 000000000..8fd6a698c --- /dev/null +++ b/Localization/StringsConvertor/Intents/input/my.lproj/Intents.strings @@ -0,0 +1,51 @@ +"16wxgf" = "Mastodon တွင် ပို့စ်တင်ရန်"; + +"751xkl" = "အကြောင်းအရာ"; + +"CsR7G2" = "Mastodon တွင် ပို့စ်တင်ရန်"; + +"HZSGTr" = "ဘယ်အကြောင်းအရာကို ပို့စ်တင်မလဲ?"; + +"HdGikU" = "ပို့စ်တင််ခြင်း မအောင်မြင်ပါ"; + +"KDNTJ4" = "မအောင်မြင်ရသည့် အကြောင်းပြချက်"; + +"RHxKOw" = "ပို့စ်ကို အကြောင်းအရာနှင့်တကွ တင်ရန်"; + +"RxSqsb" = "ပို့စ်"; + +"WCIR3D" = "${content} ကို Mastodon တွင် ပိုစ့်တင်ရန်"; + +"ZKJSNu" = "ပို့စ်"; + +"ZS1XaK" = "${content}"; + +"ZbSjzC" = "မြင်နိုင်မှု"; + +"Zo4jgJ" = "ပို့်စ်မြင်နိုင်မှု"; + +"apSxMG-dYQ5NN" = "‘Public’ နှင့် ကိုက်ညီသော ရွေးချယ်စရာ ${count} ခု ရှိသည်။"; + +"apSxMG-ehFLjY" = "‘Followers Only’ နှင့် ကိုက်ညီသော ရွေးချယ်စရာ ${count} ခု ရှိသည်။"; + +"ayoYEb-dYQ5NN" = "${content}, အများမြင်"; + +"ayoYEb-ehFLjY" = "${content}, စောင့်ကြည့်သူများ သီးသန့်"; + +"dUyuGg" = "Mastodon တွင် ပို့စ်တင်ရန်"; + +"dYQ5NN" = "အများမြင်"; + +"ehFLjY" = "စောင့်ကြည့်သူများသီးသန့်"; + +"gfePDu" = "ပို့စ်တင််ခြင်း မအောင်မြင်ပါ၊ ${failureReason}"; + +"k7dbKQ" = "ပို့စ်ကို အောင်မြင်စွာ ပို့ခဲ့သည်"; + +"oGiqmY-dYQ5NN" = "\"အများမြင်\" ကို ရွေးမည်် သေချာပါသလား?"; + +"oGiqmY-ehFLjY" = "\"စောင့်ကြည့်သူများသီးသန့်\" ကို ရွေးမည်် သေချာပါသလား?"; + +"rM6dvp" = "URL"; + +"ryJLwG" = "ပို့စ်ကို အောင်မြင်စွာ ပို့ခဲ့သည်"; diff --git a/Localization/StringsConvertor/Intents/input/my.lproj/Intents.stringsdict b/Localization/StringsConvertor/Intents/input/my.lproj/Intents.stringsdict new file mode 100644 index 000000000..577ffd124 --- /dev/null +++ b/Localization/StringsConvertor/Intents/input/my.lproj/Intents.stringsdict @@ -0,0 +1,34 @@ + + + + + There are ${count} options matching ‘${content}’. - 2 + + NSStringLocalizedFormatKey + ‘${content}’ နှင့် ကိုက်ညီသော ရွေးချယ်စရာ %#@count_option@ ခု ရှိသည်။ + count_option + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + %ld + other + ရွေးချယ်စရာ %ld ခု + + + There are ${count} options matching ‘${visibility}’. + + NSStringLocalizedFormatKey + ‘${visibility}’ နှင့် ကိုက်ညီသော ရွေးချယ်စရာ %#@count_option@ ခု ရှိသည်။ + count_option + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + %ld + other + ရွေးချယ်စရာ %ld ခု + + + + diff --git a/Localization/StringsConvertor/Intents/input/pt-BR.lproj/Intents.strings b/Localization/StringsConvertor/Intents/input/pt-BR.lproj/Intents.strings index 4d4e426c6..3e6806953 100644 --- a/Localization/StringsConvertor/Intents/input/pt-BR.lproj/Intents.strings +++ b/Localization/StringsConvertor/Intents/input/pt-BR.lproj/Intents.strings @@ -4,9 +4,9 @@ "CsR7G2" = "Postar no Mastodon"; -"HZSGTr" = "What content to post?"; +"HZSGTr" = "Qual conteúdo a publicar?"; -"HdGikU" = "Posting failed"; +"HdGikU" = "Falha na publicação"; "KDNTJ4" = "Motivo da falha"; @@ -22,30 +22,30 @@ "ZbSjzC" = "Visibilidade"; -"Zo4jgJ" = "Post Visibility"; +"Zo4jgJ" = "Visibilidade da publicação"; -"apSxMG-dYQ5NN" = "There are ${count} options matching ‘Public’."; +"apSxMG-dYQ5NN" = "Existem ${count} opções correspondentes a ‘Público’."; -"apSxMG-ehFLjY" = "There are ${count} options matching ‘Followers Only’."; +"apSxMG-ehFLjY" = "Existem ${count} opções correspondentes a ‘Apenas para seguidores’."; -"ayoYEb-dYQ5NN" = "${content}, Public"; +"ayoYEb-dYQ5NN" = "${content}, Público"; -"ayoYEb-ehFLjY" = "${content}, Followers Only"; +"ayoYEb-ehFLjY" = "${content}, Apenas para seguidores"; -"dUyuGg" = "Post on Mastodon"; +"dUyuGg" = "Postar no Mastodon"; -"dYQ5NN" = "Public"; +"dYQ5NN" = "Público"; -"ehFLjY" = "Followers Only"; +"ehFLjY" = "Apenas para seguidores"; -"gfePDu" = "Posting failed. ${failureReason}"; +"gfePDu" = "Falha na publicação. ${failureReason}"; -"k7dbKQ" = "Post was sent successfully."; +"k7dbKQ" = "Publicação enviada com sucesso."; -"oGiqmY-dYQ5NN" = "Just to confirm, you wanted ‘Public’?"; +"oGiqmY-dYQ5NN" = "Só para confirmar, você queria ‘Público’?"; -"oGiqmY-ehFLjY" = "Just to confirm, you wanted ‘Followers Only’?"; +"oGiqmY-ehFLjY" = "Só para confirmar, você queria ‘Apenas para seguidores’?"; "rM6dvp" = "URL"; -"ryJLwG" = "Post was sent successfully. "; +"ryJLwG" = "Publicação enviada com sucesso. "; diff --git a/Localization/StringsConvertor/Intents/input/uk.lproj/Intents.strings b/Localization/StringsConvertor/Intents/input/uk.lproj/Intents.strings index 6877490ba..ecbc72785 100644 --- a/Localization/StringsConvertor/Intents/input/uk.lproj/Intents.strings +++ b/Localization/StringsConvertor/Intents/input/uk.lproj/Intents.strings @@ -1,51 +1,51 @@ -"16wxgf" = "Post on Mastodon"; +"16wxgf" = "Поділитись в Mastodon"; -"751xkl" = "Text Content"; +"751xkl" = "Текстовий вміст"; -"CsR7G2" = "Post on Mastodon"; +"CsR7G2" = "Поділитись в Mastodon"; -"HZSGTr" = "What content to post?"; +"HZSGTr" = "Який зміст допису?"; -"HdGikU" = "Posting failed"; +"HdGikU" = "Помилка при надсиланні"; -"KDNTJ4" = "Failure Reason"; +"KDNTJ4" = "Причина помилки"; -"RHxKOw" = "Send Post with text content"; +"RHxKOw" = "Надіслати пост із текстом"; -"RxSqsb" = "Post"; +"RxSqsb" = "Допис"; -"WCIR3D" = "Post ${content} on Mastodon"; +"WCIR3D" = "Опублікувати ${content} на Mastodon"; -"ZKJSNu" = "Post"; +"ZKJSNu" = "Допис"; "ZS1XaK" = "${content}"; -"ZbSjzC" = "Visibility"; +"ZbSjzC" = "Видимість"; -"Zo4jgJ" = "Post Visibility"; +"Zo4jgJ" = "Видимість допису"; -"apSxMG-dYQ5NN" = "There are ${count} options matching ‘Public’."; +"apSxMG-dYQ5NN" = "Знайдено ${count} варіантів, що задовольняють \"Публічні\"."; -"apSxMG-ehFLjY" = "There are ${count} options matching ‘Followers Only’."; +"apSxMG-ehFLjY" = "Знайдено ${count} варіантів, які відповідають \"Тільки для підписників\"."; -"ayoYEb-dYQ5NN" = "${content}, Public"; +"ayoYEb-dYQ5NN" = "${content}, публічний"; -"ayoYEb-ehFLjY" = "${content}, Followers Only"; +"ayoYEb-ehFLjY" = "${content}, тільки підписники"; -"dUyuGg" = "Post on Mastodon"; +"dUyuGg" = "Поділитись в Mastodon"; -"dYQ5NN" = "Public"; +"dYQ5NN" = "Публічно"; -"ehFLjY" = "Followers Only"; +"ehFLjY" = "Тільки для підписників"; -"gfePDu" = "Posting failed. ${failureReason}"; +"gfePDu" = "Помилка. ${failureReason}"; -"k7dbKQ" = "Post was sent successfully."; +"k7dbKQ" = "Допис успішно відправлено."; -"oGiqmY-dYQ5NN" = "Just to confirm, you wanted ‘Public’?"; +"oGiqmY-dYQ5NN" = "Вам дійсно потрібні \"Публічно\"?"; -"oGiqmY-ehFLjY" = "Just to confirm, you wanted ‘Followers Only’?"; +"oGiqmY-ehFLjY" = "Вам дійсно потрібні \"Тільки для підписників\"?"; "rM6dvp" = "URL"; -"ryJLwG" = "Post was sent successfully. "; +"ryJLwG" = "Допис успішно відправлено. "; diff --git a/Localization/StringsConvertor/Intents/input/uk.lproj/Intents.stringsdict b/Localization/StringsConvertor/Intents/input/uk.lproj/Intents.stringsdict index a739f778f..ef8eb5d56 100644 --- a/Localization/StringsConvertor/Intents/input/uk.lproj/Intents.stringsdict +++ b/Localization/StringsConvertor/Intents/input/uk.lproj/Intents.stringsdict @@ -5,7 +5,7 @@ There are ${count} options matching ‘${content}’. - 2 NSStringLocalizedFormatKey - There are %#@count_option@ matching ‘${content}’. + Знайдено %#@count_option@ відповідних '${content}. count_option NSStringFormatSpecTypeKey @@ -13,19 +13,19 @@ NSStringFormatValueTypeKey %ld one - 1 option + параметр few - %ld options + %ld параметри many - %ld options + %ld параметрів other - %ld options + %ld параметрів There are ${count} options matching ‘${visibility}’. NSStringLocalizedFormatKey - There are %#@count_option@ matching ‘${visibility}’. + Знайдено %#@count_option@ відповідних '${visibility}. count_option NSStringFormatSpecTypeKey @@ -33,13 +33,13 @@ NSStringFormatValueTypeKey %ld one - 1 option + параметр few - %ld options + %ld параметри many - %ld options + %ld параметрів other - %ld options + %ld параметрів diff --git a/Localization/StringsConvertor/Sources/StringsConvertor/main.swift b/Localization/StringsConvertor/Sources/StringsConvertor/main.swift index beba6cb3f..665161e2c 100644 --- a/Localization/StringsConvertor/Sources/StringsConvertor/main.swift +++ b/Localization/StringsConvertor/Sources/StringsConvertor/main.swift @@ -53,6 +53,7 @@ private func map(language: String) -> String? { case "ca.lproj": return "ca" // Catalan case "zh-Hans.lproj": return "zh-Hans" // Chinese Simplified case "zh-Hant.lproj": return "zh-Hant" // Chinese Traditional + case "cs.lproj": return "cs" // Czech case "nl.lproj": return "nl" // Dutch case "en.lproj": return "en" case "fi.lproj": return "fi" // Finnish @@ -65,6 +66,7 @@ private func map(language: String) -> String? { case "kmr.lproj": return "ku" // Kurmanji (Kurdish) [intent mapping] case "ru.lproj": return "ru" // Russian case "gd.lproj": return "gd" // Scottish Gaelic + case "sl.lproj": return "sl" // Slovenian case "ckb.lproj": return "ckb" // Sorani (Kurdish) case "es.lproj": return "es" // Spanish case "es_AR.lproj": return "es-AR" // Spanish, Argentina diff --git a/Localization/StringsConvertor/input/Base.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/Base.lproj/Localizable.stringsdict index cd97825f4..37ce1f032 100644 --- a/Localization/StringsConvertor/input/Base.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/Base.lproj/Localizable.stringsdict @@ -13,15 +13,15 @@ NSStringFormatValueTypeKey ld zero - no unread notification + no unread notifications one 1 unread notification few %ld unread notifications many - %ld unread notification + %ld unread notifications other - %ld unread notification + %ld unread notifications a11y.plural.count.input_limit_exceeds @@ -71,7 +71,7 @@ a11y.plural.count.characters_left NSStringLocalizedFormatKey - %#@character_count@ left + %#@character_count@ character_count NSStringFormatSpecTypeKey @@ -79,15 +79,15 @@ NSStringFormatValueTypeKey ld zero - no characters + no characters left one - 1 character + 1 character left few - %ld characters + %ld characters left many - %ld characters + %ld characters left other - %ld characters + %ld characters left plural.count.followed_by_and_mutual diff --git a/Localization/StringsConvertor/input/Base.lproj/app.json b/Localization/StringsConvertor/input/Base.lproj/app.json index 30566d8d6..963b4aed1 100644 --- a/Localization/StringsConvertor/input/Base.lproj/app.json +++ b/Localization/StringsConvertor/input/Base.lproj/app.json @@ -51,6 +51,11 @@ "clean_cache": { "title": "Clean Cache", "message": "Successfully cleaned %s cache." + }, + "translation_failed": { + "title": "Note", + "message": "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported.", + "button": "OK" } }, "controls": { @@ -74,10 +79,11 @@ "take_photo": "Take Photo", "save_photo": "Save Photo", "copy_photo": "Copy Photo", - "sign_in": "Sign In", - "sign_up": "Sign Up", + "sign_in": "Log in", + "sign_up": "Create account", "see_more": "See More", "preview": "Preview", + "copy": "Copy", "share": "Share", "share_user": "Share %s", "share_post": "Share Post", @@ -91,12 +97,16 @@ "block_domain": "Block %s", "unblock_domain": "Unblock %s", "settings": "Settings", - "delete": "Delete" + "delete": "Delete", + "translate_post": { + "title": "Translate from %s", + "unknown_language": "Unknown" + } }, "tabs": { "home": "Home", - "search": "Search", - "notification": "Notification", + "search_and_explore": "Search and Explore", + "notifications": "Notifications", "profile": "Profile" }, "keyboard": { @@ -132,6 +142,8 @@ "sensitive_content": "Sensitive Content", "media_content_warning": "Tap anywhere to reveal", "tap_to_reveal": "Tap to reveal", + "load_embed": "Load Embed", + "link_via_user": "%s via %s", "poll": { "vote": "Vote", "closed": "Closed" @@ -153,6 +165,7 @@ "show_image": "Show image", "show_gif": "Show GIF", "show_video_player": "Show video player", + "share_link_in_post": "Share Link in Post", "tap_then_hold_to_show_menu": "Tap then hold to show menu" }, "tag": { @@ -168,6 +181,18 @@ "private": "Only their followers can see this post.", "private_from_me": "Only my followers can see this post.", "direct": "Only mentioned user can see this post." + }, + "translation": { + "translated_from": "Translated from %s using %s", + "unknown_language": "Unknown", + "unknown_provider": "Unknown", + "show_original": "Shown Original" + }, + "media": { + "accessibility_label": "%s, attachment %d of %d", + "expand_image_hint": "Expands the image. Double-tap and hold to show actions", + "expand_gif_hint": "Expands the GIF. Double-tap and hold to show actions", + "expand_video_hint": "Shows the video player. Double-tap and hold to show actions" } }, "friendship": { @@ -218,10 +243,16 @@ "get_started": "Get Started", "log_in": "Log In" }, + "login": { + "title": "Welcome back", + "subtitle": "Log you in on the server you created your account on.", + "server_search_field": { + "placeholder": "Enter URL or search for your server" + } + }, "server_picker": { "title": "Mastodon is made of users in different servers.", - "subtitle": "Pick a server based on your interests, region, or a general purpose one.", - "subtitle_extend": "Pick a server based on your interests, region, or a general purpose one. Each server is operated by an entirely independent organization or individual.", + "subtitle": "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers.", "button": { "category": { "all": "All", @@ -248,8 +279,7 @@ "category": "CATEGORY" }, "input": { - "placeholder": "Search servers", - "search_servers_or_enter_url": "Search servers or enter URL" + "search_servers_or_enter_url": "Search communities or enter URL" }, "empty_state": { "finding_servers": "Finding available servers...", @@ -355,7 +385,7 @@ "published": "Published!", "Publishing": "Publishing post...", "accessibility": { - "logo_label": "Logo Button", + "logo_label": "Mastodon", "logo_hint": "Tap to scroll to top and tap again to previous location" } } @@ -439,11 +469,15 @@ "follows_you": "Follows You" }, "dashboard": { - "posts": "posts", - "following": "following", - "followers": "followers" + "my_posts": "posts", + "my_following": "following", + "my_followers": "followers", + "other_posts": "posts", + "other_following": "following", + "other_followers": "followers" }, "fields": { + "joined": "Joined", "add_row": "Add Row", "placeholder": { "label": "Label", @@ -717,6 +751,19 @@ }, "bookmark": { "title": "Bookmarks" + + }, + "followed_tags": { + "title": "Followed Tags", + "header": { + "posts": "posts", + "participants": "participants", + "posts_today": "posts today" + }, + "actions": { + "follow": "Follow", + "unfollow": "Unfollow" + } } } -} \ No newline at end of file +} diff --git a/Localization/StringsConvertor/input/an.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/an.lproj/Localizable.stringsdict new file mode 100644 index 000000000..d7187e039 --- /dev/null +++ b/Localization/StringsConvertor/input/an.lproj/Localizable.stringsdict @@ -0,0 +1,465 @@ + + + + + a11y.plural.count.unread.notification + + NSStringLocalizedFormatKey + %#@notification_count_unread_notification@ + notification_count_unread_notification + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 notificación no leyida + other + %ld notificacions no leyidas + + + a11y.plural.count.input_limit_exceeds + + NSStringLocalizedFormatKey + Limite de dentrada superau en %#@character_count@ caracters + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 caracter + other + %ld caracters + + + a11y.plural.count.input_limit_remains + + NSStringLocalizedFormatKey + Limite de dentrada restante: %#@character_count@ caracters + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 caracter + other + %ld caracters + + + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + queda %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 caracter + other + %ld caracters + + + plural.count.followed_by_and_mutual + + NSStringLocalizedFormatKey + %#@names@%#@count_mutual@ + names + + one + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + + + count_mutual + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + Seguiu per %1$@ y unatro mutuo + other + Seguiu per %1$@ y %ld mutuos + + + plural.count.metric_formatted.post + + NSStringLocalizedFormatKey + %@ %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + publicación + other + publicacions + + + plural.count.media + + NSStringLocalizedFormatKey + %#@media_count@ + media_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 media + other + %ld media + + + plural.count.post + + NSStringLocalizedFormatKey + %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 publicación + other + %ld publicacions + + + plural.count.favorite + + NSStringLocalizedFormatKey + %#@favorite_count@ + favorite_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 favorito + other + %ld favoritos + + + plural.count.reblog + + NSStringLocalizedFormatKey + %#@reblog_count@ + reblog_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 reblogueo + other + %ld reblogueos + + + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 respuesta + other + %ld respuestas + + + plural.count.vote + + NSStringLocalizedFormatKey + %#@vote_count@ + vote_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 voto + other + %ld votos + + + plural.count.voter + + NSStringLocalizedFormatKey + %#@voter_count@ + voter_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 votante + other + %ld votantes + + + plural.people_talking + + NSStringLocalizedFormatKey + %#@count_people_talking@ + count_people_talking + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 persona charrando + other + %ld personas son charrando d'esto + + + plural.count.following + + NSStringLocalizedFormatKey + %#@count_following@ + count_following + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 seguindo + other + %ld seguindo + + + plural.count.follower + + NSStringLocalizedFormatKey + %#@count_follower@ + count_follower + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 seguidor + other + %ld seguidores + + + date.year.left + + NSStringLocalizedFormatKey + %#@count_year_left@ + count_year_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 anyo restante + other + %ld anyos restantes + + + date.month.left + + NSStringLocalizedFormatKey + %#@count_month_left@ + count_month_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 mes restante + other + %ld meses restantes + + + date.day.left + + NSStringLocalizedFormatKey + %#@count_day_left@ + count_day_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 día restante + other + %ld días restantes + + + date.hour.left + + NSStringLocalizedFormatKey + %#@count_hour_left@ + count_hour_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 hora restante + other + %ld horas restantes + + + date.minute.left + + NSStringLocalizedFormatKey + %#@count_minute_left@ + count_minute_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 minuto restant + other + %ld minutos restants + + + date.second.left + + NSStringLocalizedFormatKey + %#@count_second_left@ + count_second_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 segundo restante + other + %ld segundos restantes + + + date.year.ago.abbr + + NSStringLocalizedFormatKey + %#@count_year_ago_abbr@ + count_year_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + Fa 1 anyo + other + Fa %ld anyos + + + date.month.ago.abbr + + NSStringLocalizedFormatKey + %#@count_month_ago_abbr@ + count_month_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + Fa 1 mes + other + Fa %ld meses + + + date.day.ago.abbr + + NSStringLocalizedFormatKey + %#@count_day_ago_abbr@ + count_day_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + Fa 1 día + other + Fa %ld días + + + date.hour.ago.abbr + + NSStringLocalizedFormatKey + %#@count_hour_ago_abbr@ + count_hour_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + Fa 1 h + other + Fa %ld h + + + date.minute.ago.abbr + + NSStringLocalizedFormatKey + %#@count_minute_ago_abbr@ + count_minute_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + Fa 1 min + other + Fa %ld min + + + date.second.ago.abbr + + NSStringLocalizedFormatKey + %#@count_second_ago_abbr@ + count_second_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + Fa 1 s + other + Fa %ld s + + + + diff --git a/Localization/StringsConvertor/input/an.lproj/app.json b/Localization/StringsConvertor/input/an.lproj/app.json new file mode 100644 index 000000000..b0e090cf0 --- /dev/null +++ b/Localization/StringsConvertor/input/an.lproj/app.json @@ -0,0 +1,762 @@ +{ + "common": { + "alerts": { + "common": { + "please_try_again": "Per favor, torna a intentar-lo.", + "please_try_again_later": "Per favor, torna a intentar-lo mas enta debant." + }, + "sign_up_failure": { + "title": "Error en rechistrar-se" + }, + "server_error": { + "title": "Error d'o servidor" + }, + "vote_failure": { + "title": "Voto fallido", + "poll_ended": "La enquesta ha rematau" + }, + "discard_post_content": { + "title": "Descartar borrador", + "message": "Confirma pa descartar lo conteniu d'a publicación." + }, + "publish_post_failure": { + "title": "Error de publicación", + "message": "No s'ha puesto publicar la publicación. Per favor, revise la suya connexión a internet.", + "attachments_message": { + "video_attach_with_photo": "No puetz adchuntar un video a una publicación que ya contiene imachens.", + "more_than_one_video": "No puetz adchuntar mas d'un video." + } + }, + "edit_profile_failure": { + "title": "Error en a Edición d'o Perfil", + "message": "No s'ha puesto editar lo perfil. Per favor, intenta-lo de nuevo." + }, + "sign_out": { + "title": "Zarrar Sesión", + "message": "Yes seguro de que quiers zarrar la sesión?", + "confirm": "Zarrar Sesión" + }, + "block_domain": { + "title": "Yes realment seguro, de verdat, que quiers blocar %s a lo completo? En a mayoría d'os casos, uns pocos bloqueyos u silenciaus concretos son suficients y preferibles. No veyerás conteniu d'ixe dominio y totz los tuyos seguidores d'ixe dominio serán eliminaus.", + "block_entire_domain": "Blocar Dominio" + }, + "save_photo_failure": { + "title": "Error en Alzar Foto", + "message": "Per favor, activa lo permiso d'acceso a la biblioteca de fotos pa alzar la foto." + }, + "delete_post": { + "title": "Yes seguro de que quiers eliminar esta publicación?", + "message": "Yes seguro de que quiers borrar esta publicación?" + }, + "clean_cache": { + "title": "Limpiar Caché", + "message": "S'ha limpiau con exito %s de caché." + }, + "translation_failed": { + "title": "Note", + "message": "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported.", + "button": "OK" + } + }, + "controls": { + "actions": { + "back": "Dezaga", + "next": "Siguient", + "previous": "Anterior", + "open": "Ubrir", + "add": "Anyadir", + "remove": "Eliminar", + "edit": "Editar", + "save": "Alzar", + "ok": "Acceptar", + "done": "Feito", + "confirm": "Confirmar", + "continue": "Continar", + "compose": "Redactar", + "cancel": "Cancelar", + "discard": "Descartar", + "try_again": "Intenta-lo de nuevo", + "take_photo": "Prener foto", + "save_photo": "Alzar foto", + "copy_photo": "Copiar foto", + "sign_in": "Iniciar sesión", + "sign_up": "Crear cuenta", + "see_more": "Veyer mas", + "preview": "Vista previa", + "copy": "Copy", + "share": "Compartir", + "share_user": "Compartir %s", + "share_post": "Compartir publicación", + "open_in_safari": "Ubrir en Safari", + "open_in_browser": "Ubrir en o navegador", + "find_people": "Troba chent a la quala seguir", + "manually_search": "Millor fer una busqueda manual", + "skip": "Omitir", + "reply": "Responder", + "report_user": "Reportar a %s", + "block_domain": "Blocar %s", + "unblock_domain": "Desbloquiar %s", + "settings": "Configuración", + "delete": "Borrar", + "translate_post": { + "title": "Translate from %s", + "unknown_language": "Unknown" + } + }, + "tabs": { + "home": "Inicio", + "search_and_explore": "Search and Explore", + "notifications": "Notificacions", + "profile": "Perfil" + }, + "keyboard": { + "common": { + "switch_to_tab": "Cambiar a %s", + "compose_new_post": "Escribir Nueva Publicación", + "show_favorites": "Amostrar Favoritos", + "open_settings": "Ubrir Configuración" + }, + "timeline": { + "previous_status": "Publicación Anterior", + "next_status": "Siguient Publicación", + "open_status": "Ubrir Publicación", + "open_author_profile": "Ubrir Perfil de l'Autor", + "open_reblogger_profile": "Ubrir Perfil d'o Reblogueador", + "reply_status": "Responder Publicación", + "toggle_reblog": "Commutar lo Reblogueo en a Publicación", + "toggle_favorite": "Commutar la Marca de Favorito en a Publicación", + "toggle_content_warning": "Alternar l'Alvertencia de Conteniu", + "preview_image": "Previsualizar Imachen" + }, + "segmented_control": { + "previous_section": "Sección Anterior", + "next_section": "Siguient Sección" + } + }, + "status": { + "user_reblogged": "%s lo reblogueó", + "user_replied_to": "En respuesta a %s", + "show_post": "Amostrar Publicación", + "show_user_profile": "Amostrar perfil de l'usuario", + "content_warning": "Alvertencia de Conteniu", + "sensitive_content": "Conteniu sensible", + "media_content_warning": "Preta en qualsequier puesto pa amostrar", + "tap_to_reveal": "Tocar pa revelar", + "load_embed": "Load Embed", + "link_via_user": "%s via %s", + "poll": { + "vote": "Vota", + "closed": "Zarrau" + }, + "meta_entity": { + "url": "Vinclo: %s", + "hashtag": "Hashtag: %s", + "mention": "Amostrar lo perfil: %s", + "email": "Adreza de correu: %s" + }, + "actions": { + "reply": "Responder", + "reblog": "Rebloguear", + "unreblog": "Desfer reblogueo", + "favorite": "Favorito", + "unfavorite": "No favorito", + "menu": "Menú", + "hide": "Amagar", + "show_image": "Amostrar imachen", + "show_gif": "Amostrar GIF", + "show_video_player": "Amostrar reproductor de video", + "share_link_in_post": "Share Link in Post", + "tap_then_hold_to_show_menu": "Toca, dimpués mantiene pa amostrar lo menú" + }, + "tag": { + "url": "URL", + "mention": "Mención", + "link": "Vinclo", + "hashtag": "Etiqueta", + "email": "E-mail", + "emoji": "Emoji" + }, + "visibility": { + "unlisted": "Totz pueden veyer este post pero no amostrar-lo en una linia de tiempo publica.", + "private": "Nomás los suyos seguidores pueden veyer este mensache.", + "private_from_me": "Nomás los míos seguidores pueden veyer este mensache.", + "direct": "Nomás l'usuario mencionau puede veyer este mensache." + }, + "translation": { + "translated_from": "Translated from %s using %s", + "unknown_language": "Unknown", + "unknown_provider": "Unknown", + "show_original": "Shown Original" + } + }, + "friendship": { + "follow": "Seguir", + "following": "Seguindo", + "request": "Solicitut", + "pending": "Pendient", + "block": "Blocar", + "block_user": "Blocar a %s", + "block_domain": "Blocar a %s", + "unblock": "Desbloquiar", + "unblock_user": "Desbloquiar a %s", + "blocked": "Blocau", + "mute": "Silenciar", + "mute_user": "Silenciar a %s", + "unmute": "Desmutear", + "unmute_user": "Desmutear a %s", + "muted": "Silenciau", + "edit_info": "Editar Info", + "show_reblogs": "Amostrar los retuts", + "hide_reblogs": "Amagar los reblogs" + }, + "timeline": { + "filtered": "Filtrau", + "timestamp": { + "now": "Agora" + }, + "loader": { + "load_missing_posts": "Cargar publicacions faltantes", + "loading_missing_posts": "Cargando publicacions faltantes...", + "show_more_replies": "Amostrar mas respuestas" + }, + "header": { + "no_status_found": "No s'ha trobau garra publicación", + "blocking_warning": "No puetz veyer lo perfil d'este usuario\n dica que lo desbloqueyes.\nLo tuyo perfil se veye asinas pa ell.", + "user_blocking_warning": "No puetz veyer lo perfil de %s\n dica que lo desbloqueyes.\nLo tuyo perfil se veye asinas pa ell.", + "blocked_warning": "No puetz veyer lo perfil d'este usuario\n dica que te desbloqueye.", + "user_blocked_warning": "No puetz veyer lo perfil de %s\n dica que te desbloqueye.", + "suspended_warning": "Este usuario ha estau suspendiu.", + "user_suspended_warning": "La cuenta de %s ha estau suspendida." + } + } + } + }, + "scene": { + "welcome": { + "slogan": "Los retz socials\nde nuevo en as tuyas mans.", + "get_started": "Empecipiar", + "log_in": "Iniciar sesión" + }, + "login": { + "title": "Bienveniu de nuevas", + "subtitle": "Dentrar en o servidor an que creyiés la cuenta.", + "server_search_field": { + "placeholder": "Escribir la URL u buscar lo tuyo servidor" + } + }, + "server_picker": { + "title": "Tría un servidor,\nqualsequier servidor.", + "subtitle": "Tría un servidor basau en a tuya rechión, intereses u uno de proposito cheneral. Podrás seguir connectau con totz en Mastodon, independiement d'o servidor.", + "button": { + "category": { + "all": "Totas", + "all_accessiblity_description": "Categoría: Totas", + "academia": "academicos", + "activism": "activismo", + "food": "minchada", + "furry": "furry", + "games": "chuegos", + "general": "cheneral", + "journalism": "periodismo", + "lgbt": "lgbt", + "regional": "rechional", + "art": "arte", + "music": "mosica", + "tech": "tecnolochía" + }, + "see_less": "Veyer Menos", + "see_more": "Veyer Más" + }, + "label": { + "language": "IDIOMA", + "users": "USUARIOS", + "category": "CATEGORÍA" + }, + "input": { + "search_servers_or_enter_url": "Mirar comunidatz u escribir URL" + }, + "empty_state": { + "finding_servers": "Trobando servidors disponibles...", + "bad_network": "Bella cosa ha iu malament en cargar los datos. Compreba la tuya connexión a Internet.", + "no_results": "Sin resultaus" + } + }, + "register": { + "title": "Deixa que te configuremos en %s", + "lets_get_you_set_up_on_domain": "Deixa que te configuremos en %s", + "input": { + "avatar": { + "delete": "Borrar" + }, + "username": { + "placeholder": "nombre d'usuario", + "duplicate_prompt": "Este nombre d'usuario ya ye en uso." + }, + "display_name": { + "placeholder": "nombre a amostrar" + }, + "email": { + "placeholder": "correu electronico" + }, + "password": { + "placeholder": "clau", + "require": "La tuya clau ha de contener como minimo:", + "character_limit": "8 caracters", + "accessibility": { + "checked": "marcau", + "unchecked": "sin marcar" + }, + "hint": "La tuya clau ameneste tener a lo menos ueito caracters" + }, + "invite": { + "registration_user_invite_request": "Per qué quiers unir-te?" + } + }, + "error": { + "item": { + "username": "Nombre d'usuario", + "email": "Correu electronico", + "password": "Clau", + "agreement": "Acceptación", + "locale": "Idioma", + "reason": "Motivo" + }, + "reason": { + "blocked": "%s contiene un furnidor de correu no permitiu", + "unreachable": "%s pareixe no existir", + "taken": "%s ya ye en uso", + "reserved": "%s ye una parola clau reservada", + "accepted": "%s ha d'estar acceptau", + "blank": "Se requiere %s", + "invalid": "%s no ye valido", + "too_long": "%s ye masiau largo", + "too_short": "%s ye masiau tallo", + "inclusion": "%s no ye una valor admitida" + }, + "special": { + "username_invalid": "Lo nombre d'usuario solo puede contener caracters alfanumericos y guións baixos", + "username_too_long": "Lo nombre d'usuario ye masiau largo (no puede tener mas de 30 caracters)", + "email_invalid": "Esta no ye una adreza de correu electronico valida", + "password_too_short": "La clau ye masiau curta (ha de tener a lo menos 8 caracters)" + } + } + }, + "server_rules": { + "title": "Qualques reglas basicas.", + "subtitle": "Estas reglas son establidas per los administradors de %s.", + "prompt": "Si continas serás sucheto a los termins de servicio y la politica de privacidat de %s.", + "terms_of_service": "termins d'o servicio", + "privacy_policy": "politica de privacidat", + "button": { + "confirm": "Accepto" + } + }, + "confirm_email": { + "title": "Una zaguera coseta.", + "subtitle": "T'acabamos de ninviar un correu a %s, preta en o vinclo pa confirmar la tuya cuenta.", + "tap_the_link_we_emailed_to_you_to_verify_your_account": "Toca lo vinclo que te ninviamos per correu electronico pa verificar la tuya cuenta", + "button": { + "open_email_app": "Ubrir Aplicación de Correu Electronico", + "resend": "Reninviar" + }, + "dont_receive_email": { + "title": "Revisa lo tuyo correu electronico", + "description": "Compreba que la tuya adreza de correu electronico sía correcta y revisa la carpeta de correu no deseyau si no l'has feito ya.", + "resend_email": "Tornar a Ninviar Correu Electronico" + }, + "open_email_app": { + "title": "Revisa la tuya servilla de dentrada.", + "description": "T'acabamos de ninviar un correu electronico. Revisa la tuya carpeta de correu no deseyau si no l'has feito ya.", + "mail": "Correu", + "open_email_client": "Ubrir Client de Correu Electronico" + } + }, + "home_timeline": { + "title": "Inicio", + "navigation_bar_state": { + "offline": "Sin Connexión", + "new_posts": "Veyer nuevas publicacions", + "published": "Publicau!", + "Publishing": "Publicación en curso...", + "accessibility": { + "logo_label": "Botón d'o logo", + "logo_hint": "Toca pa desplazar-te enta alto y toca de nuevo pa la localización anterior" + } + } + }, + "suggestion_account": { + "title": "Troba Chent a la quala Seguir", + "follow_explain": "Quan sigas a belún veyerás las suyas publicacions en a tuya pachina d'inicio." + }, + "compose": { + "title": { + "new_post": "Nueva Publicación", + "new_reply": "Nueva Respuesta" + }, + "media_selection": { + "camera": "Fer Foto", + "photo_library": "Galería de Fotos", + "browse": "Explorar" + }, + "content_input_placeholder": "Escribe u apega lo que tiengas en mente", + "compose_action": "Publicar", + "replying_to_user": "en respuesta a %s", + "attachment": { + "photo": "foto", + "video": "video", + "attachment_broken": "Este %s ye roto y no puede\npuyar-se a Mastodon.", + "description_photo": "Describe la foto pa los usuarios con dificultat visual...", + "description_video": "Describe lo video pa los usuarios con dificultat visual...", + "load_failed": "Fallo de carga", + "upload_failed": "Fallo de carga", + "can_not_recognize_this_media_attachment": "No se puede reconocer este adchunto multimedia", + "attachment_too_large": "Adchunto masiau gran", + "compressing_state": "Comprimindo...", + "server_processing_state": "Lo servidor ye procesando..." + }, + "poll": { + "duration_time": "Duración: %s", + "thirty_minutes": "30 minutos", + "one_hour": "1 Hora", + "six_hours": "6 Horas", + "one_day": "1 Día", + "three_days": "3 Días", + "seven_days": "7 Días", + "option_number": "Opción %ld", + "the_poll_is_invalid": "La enquesta ye invalida", + "the_poll_has_empty_option": "La enquesta tiene opcions vuedas" + }, + "content_warning": { + "placeholder": "Escribe una alvertencia precisa aquí..." + }, + "visibility": { + "public": "Publica", + "unlisted": "Sin listar", + "private": "Solo seguidores", + "direct": "Solo la chent que yo menciono" + }, + "auto_complete": { + "space_to_add": "Espacio pa anyadir" + }, + "accessibility": { + "append_attachment": "Anyadir Adchunto", + "append_poll": "Anyadir Enqüesta", + "remove_poll": "Eliminar Enqüesta", + "custom_emoji_picker": "Selector de Emojis Personalizaus", + "enable_content_warning": "Activar Alvertencia de Conteniu", + "disable_content_warning": "Desactivar Alvertencia de Conteniu", + "post_visibility_menu": "Menú de Visibilidat d'a Publicación", + "post_options": "Opcions d'o tut", + "posting_as": "Publicando como %s" + }, + "keyboard": { + "discard_post": "Descartar Publicación", + "publish_post": "Publicar", + "toggle_poll": "Commutar Enqüesta", + "toggle_content_warning": "Commutar Alvertencia de Conteniu", + "append_attachment_entry": "Anyadir Adchunto - %s", + "select_visibility_entry": "Triar Visibilidat - %s" + } + }, + "profile": { + "header": { + "follows_you": "Te sigue" + }, + "dashboard": { + "my_posts": "posts", + "my_following": "following", + "my_followers": "followers", + "other_posts": "posts", + "other_following": "following", + "other_followers": "followers" + }, + "fields": { + "joined": "Joined", + "add_row": "Anyadir Ringlera", + "placeholder": { + "label": "Nombre pa lo campo", + "content": "Conteniu" + }, + "verified": { + "short": "Verificau en %s", + "long": "La propiedat d'este vinclo ha estau verificada lo %s" + } + }, + "segmented_control": { + "posts": "Publicacions", + "replies": "Respuestas", + "posts_and_replies": "Publicacions y respuestas", + "media": "Multimedia", + "about": "Sobre" + }, + "relationship_action_alert": { + "confirm_mute_user": { + "title": "Silenciar cuenta", + "message": "Confirmar pa silenciar %s" + }, + "confirm_unmute_user": { + "title": "Deixar de Silenciar Cuenta", + "message": "Confirmar pa deixar de silenciar a %s" + }, + "confirm_block_user": { + "title": "Blocar cuenta", + "message": "Confirmar pa blocar a %s" + }, + "confirm_unblock_user": { + "title": "Desbloquiar cuenta", + "message": "Confirmar pa desbloquiar a %s" + }, + "confirm_show_reblogs": { + "title": "Amostrar los reblogs", + "message": "Confimrar pa amostrar los reblogs" + }, + "confirm_hide_reblogs": { + "title": "Amagar los reblogs", + "message": "Comfirmar pa amagar los reblogs" + } + }, + "accessibility": { + "show_avatar_image": "Amostrar imachen d'o avatar", + "edit_avatar_image": "Editar imachen d'o avatar", + "show_banner_image": "Amostrar imachen de banner", + "double_tap_to_open_the_list": "Preta dos vegadas pa ubrir la lista" + } + }, + "follower": { + "title": "seguidor", + "footer": "No s'amuestran los seguidores d'atros servidors." + }, + "following": { + "title": "seguindo", + "footer": "No s'amuestran los seguius d'atros servidors." + }, + "familiarFollowers": { + "title": "Seguidores que conoixes", + "followed_by_names": "Seguiu per %s" + }, + "favorited_by": { + "title": "Feito favorito per" + }, + "reblogged_by": { + "title": "Reblogueado per" + }, + "search": { + "title": "Buscar", + "search_bar": { + "placeholder": "Buscar etiquetas y usuarios", + "cancel": "Cancelar" + }, + "recommend": { + "button_text": "Veyer Totas", + "hash_tag": { + "title": "Tendencias en Mastodon", + "description": "Etiquetas que son recibindo pro atención", + "people_talking": "%s personas son charrando d'esto" + }, + "accounts": { + "title": "Cuentas que talment quieras seguir", + "description": "Puede que faiga goyo seguir estas cuentas", + "follow": "Seguir" + } + }, + "searching": { + "segment": { + "all": "Tot", + "people": "Chent", + "hashtags": "Etiquetas", + "posts": "Publicacions" + }, + "empty_state": { + "no_results": "Sin resultaus" + }, + "recent_search": "Busquedas recients", + "clear": "Borrar" + } + }, + "discovery": { + "tabs": { + "posts": "Publicacions", + "hashtags": "Etiquetas", + "news": "Noticias", + "community": "Comunidat", + "for_you": "Pa Tu" + }, + "intro": "Estas son las publicacions que son ganando tracción en a tuya rincón de Mastodon." + }, + "favorite": { + "title": "Los tuyos Favoritos" + }, + "notification": { + "title": { + "Everything": "Tot", + "Mentions": "Mencions" + }, + "notification_description": { + "followed_you": "te siguió", + "favorited_your_post": "ha marcau como favorita la tuya publicación", + "reblogged_your_post": "reblogueó la tuya publicación", + "mentioned_you": "te mencionó", + "request_to_follow_you": "solicitó seguir-te", + "poll_has_ended": "enqüesta ha rematau" + }, + "keyobard": { + "show_everything": "Amostrar Tot", + "show_mentions": "Amostrar Mencions" + }, + "follow_request": { + "accept": "Acceptar", + "accepted": "Acceptau", + "reject": "refusar", + "rejected": "Refusau" + } + }, + "thread": { + "back_title": "Publicación", + "title": "Publicación de %s" + }, + "settings": { + "title": "Configuración", + "section": { + "appearance": { + "title": "Apariencia", + "automatic": "Automatica", + "light": "Siempre Clara", + "dark": "Siempre Fosca" + }, + "look_and_feel": { + "title": "Apariencia", + "use_system": "Uso d'o sistema", + "really_dark": "Realment Fosco", + "sorta_dark": "Más u Menos Fosco", + "light": "Claro" + }, + "notifications": { + "title": "Notificacions", + "favorites": "Marque como favorita la mía publicación", + "follows": "me siga", + "boosts": "Rebloguee la mía publicación", + "mentions": "me mencione", + "trigger": { + "anyone": "qualsequiera", + "follower": "un seguidor", + "follow": "qualsequiera que yo siga", + "noone": "dengún", + "title": "Recibir notificación quan" + } + }, + "preference": { + "title": "Preferencias", + "true_black_dark_mode": "Modo fosco negro real", + "disable_avatar_animation": "Deshabilitar avatares animaus", + "disable_emoji_animation": "Deshabilitar emojis animaus", + "using_default_browser": "Usar navegador predeterminau pa ubrir los vinclos", + "open_links_in_mastodon": "Ubrir links en Mastodon" + }, + "boring_zone": { + "title": "La Zona Aburrida", + "account_settings": "Configuración de Cuenta", + "terms": "Termins de Servicio", + "privacy": "Politica de Privacidat" + }, + "spicy_zone": { + "title": "La Zona Picante", + "clear": "Borrar Caché de Multimedia", + "signout": "Zarrar Sesión" + } + }, + "footer": { + "mastodon_description": "Mastodon ye software de codigo ubierto. Puetz informar d'errors en GitHub en %s (%s)" + }, + "keyboard": { + "close_settings_window": "Zarrar Finestra de Configuración" + } + }, + "report": { + "title_report": "Reportar", + "title": "Reportar %s", + "step1": "Paso 1 de 2", + "step2": "Paso 2 de 2", + "content1": "I hai belatra publicación que te fería goyo d'anyadir a lo reporte?", + "content2": "I hai bella cosa que los moderadors habrían de saber sobre este reporte?", + "report_sent_title": "Gracias per denunciar, estudiaremos esto.", + "send": "Ninviar Denuncia", + "skip_to_send": "Ninviar sin comentarios", + "text_placeholder": "Escribe u apega comentarios adicionals", + "reported": "DENUNCIAU", + "step_one": { + "step_1_of_4": "Paso 1 de 4", + "whats_wrong_with_this_post": "Qué i hai de malo con esta publicación?", + "whats_wrong_with_this_account": "Qué i hai de malo con esta cuenta?", + "whats_wrong_with_this_username": "Qué i hai de malo con %s?", + "select_the_best_match": "Tría la millor opción", + "i_dont_like_it": "No me fa goyo", + "it_is_not_something_you_want_to_see": "No ye bella cosa que quieras veyer", + "its_spam": "Ye spam", + "malicious_links_fake_engagement_or_repetetive_replies": "Vinclos maliciosos, compromisos falsos u respuestas repetitivas", + "it_violates_server_rules": "Viola las reglas d'o servidor", + "you_are_aware_that_it_breaks_specific_rules": "Yes conscient de que infrinche las normas especificas", + "its_something_else": "Ye bella cosa mas", + "the_issue_does_not_fit_into_other_categories": "Lo problema no encaixa en atras categorías" + }, + "step_two": { + "step_2_of_4": "Paso 2 de 4", + "which_rules_are_being_violated": "Qué normas se son violando?", + "select_all_that_apply": "Tría totz los que correspondan", + "i_just_don’t_like_it": "Nomás no me fa goyo" + }, + "step_three": { + "step_3_of_4": "Paso 3 de 4", + "are_there_any_posts_that_back_up_this_report": "I hai bella publicación que refirme este informe?", + "select_all_that_apply": "Tría totz los que correspondan" + }, + "step_four": { + "step_4_of_4": "Paso 4 de 4", + "is_there_anything_else_we_should_know": "I hai bella cosa mas que habríanos de saber?" + }, + "step_final": { + "dont_want_to_see_this": "No quiers veyer esto?", + "when_you_see_something_you_dont_like_on_mastodon_you_can_remove_the_person_from_your_experience.": "Quan veigas bella cosa que no te fa goyo en Mastodon, puetz sacar a la persona d'a tuya experiencia.", + "unfollow": "Deixar de seguir", + "unfollowed": "Ha deixau de seguir-te", + "unfollow_user": "Deixar de seguir a %s", + "mute_user": "Silenciar a %s", + "you_wont_see_their_posts_or_reblogs_in_your_home_feed_they_wont_know_they_ve_been_muted": "No veyerás las suyas publicacions u reblogueos en a tuya linia temporal. No sabrán que han estau silenciaus.", + "block_user": "Blocar a %s", + "they_will_no_longer_be_able_to_follow_or_see_your_posts_but_they_can_see_if_theyve_been_blocked": "Ya no podrán estar capaces de seguir-te u veyer las tuyas publicacions, pero pueden veyer si han estau blocaus.", + "while_we_review_this_you_can_take_action_against_user": "Mientres revisamos esto, puetz prener medidas contra %s" + } + }, + "preview": { + "keyboard": { + "close_preview": "Zarrar Previsualización", + "show_next": "Amostrar Siguient", + "show_previous": "Amostrar Anterior" + } + }, + "account_list": { + "tab_bar_hint": "Perfil triau actualment: %s. Fe un doble toque y mantiene pretau pa amostrar lo selector de cuentas", + "dismiss_account_switcher": "Descartar lo selector de cuentas", + "add_account": "Anyadir cuenta" + }, + "wizard": { + "new_in_mastodon": "Nuevo en Mastodon", + "multiple_account_switch_intro_description": "Cambie entre quantas cuentas mantenendo presionado lo botón de perfil.", + "accessibility_hint": "Fe doble toque pa descartar este asistent" + }, + "bookmark": { + "title": "Marcapachinas" + }, + "followed_tags": { + "title": "Followed Tags", + "header": { + "posts": "posts", + "participants": "participants", + "posts_today": "posts today" + }, + "actions": { + "follow": "Follow", + "unfollow": "Unfollow" + } + } + } +} diff --git a/Localization/StringsConvertor/input/an.lproj/ios-infoPlist.json b/Localization/StringsConvertor/input/an.lproj/ios-infoPlist.json new file mode 100644 index 000000000..a493d53d9 --- /dev/null +++ b/Localization/StringsConvertor/input/an.lproj/ios-infoPlist.json @@ -0,0 +1,6 @@ +{ + "NSCameraUsageDescription": "S'usa pa quitar fotos pa las publicacions", + "NSPhotoLibraryAddUsageDescription": "S'usa pa alzar fotos en a Galería de Fotos", + "NewPostShortcutItemTitle": "Nueva Publicación", + "SearchShortcutItemTitle": "Buscar" +} diff --git a/Localization/StringsConvertor/input/ar.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/ar.lproj/Localizable.stringsdict index 862d98184..35727c0d6 100644 --- a/Localization/StringsConvertor/input/ar.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/ar.lproj/Localizable.stringsdict @@ -74,6 +74,30 @@ %ld حَرف + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + يتبقى %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + لَا حَرف + one + حَرفٌ واحِد + two + حَرفانِ اِثنان + few + %ld أحرُف + many + %ld حَرفًا + other + %ld حَرف + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/ar.lproj/app.json b/Localization/StringsConvertor/input/ar.lproj/app.json index 4c5ac4c8b..c27cf8b70 100644 --- a/Localization/StringsConvertor/input/ar.lproj/app.json +++ b/Localization/StringsConvertor/input/ar.lproj/app.json @@ -51,6 +51,11 @@ "clean_cache": { "title": "مَحوُ ذاكِرَةِ التَّخزينِ المُؤقَّت", "message": "مُحِيَ ما مَساحَتُهُ %s مِن ذاكِرَةِ التَّخزينِ المُؤقَّت بِنجاح." + }, + "translation_failed": { + "title": "مُلاحظة", + "message": "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported.", + "button": "حسنًا" } }, "controls": { @@ -74,10 +79,11 @@ "take_photo": "اِلتِقاطُ صُورَة", "save_photo": "حفظ الصورة", "copy_photo": "نسخ الصورة", - "sign_in": "تسجيل الدخول", - "sign_up": "إنشاء حِساب", + "sign_in": "تسجيلُ الدخول", + "sign_up": "إنشاءُ حِساب", "see_more": "عرض المزيد", "preview": "مُعاينة", + "copy": "نَسخ", "share": "المُشارك", "share_user": "مُشارَكَةُ %s", "share_post": "مشارك المنشور", @@ -91,12 +97,16 @@ "block_domain": "حظر %s", "unblock_domain": "رفع الحظر عن %s", "settings": "الإعدادات", - "delete": "حذف" + "delete": "حذف", + "translate_post": { + "title": "الترجَمَة مِن %s", + "unknown_language": "غير مَعرُوفة" + } }, "tabs": { "home": "الرَّئِيسَة", - "search": "البَحث", - "notification": "الإشعارات", + "search_and_explore": "البَحث وَالاِستِكشاف", + "notifications": "الإشعارات", "profile": "المِلَفُّ التَّعريفِيّ" }, "keyboard": { @@ -132,6 +142,8 @@ "sensitive_content": "مُحتَوى حَسَّاس", "media_content_warning": "اُنقُر لِلكَشف", "tap_to_reveal": "اُنقُر لِلكَشف", + "load_embed": "تحميل المُضمَن", + "link_via_user": "%s via %s", "poll": { "vote": "صَوِّت", "closed": "انتهى" @@ -153,6 +165,7 @@ "show_image": "أظْهِرِ الصُّورَة", "show_gif": "أظْهِر GIF", "show_video_player": "أظْهِر مُشَغِّلَ المَقاطِعِ المَرئِيَّة", + "share_link_in_post": "مُشارَكَة الرابِط فِي مَنشور", "tap_then_hold_to_show_menu": "اُنقُر مُطَوَّلًا لِإظْهَارِ القائِمَة" }, "tag": { @@ -168,6 +181,12 @@ "private": "فَقَطْ مُتابِعينَهُم مَن يُمكِنُهُم رُؤيَةُ هَذَا المَنشُور.", "private_from_me": "فَقَطْ مُتابِعيني أنَا مَن يُمكِنُهُم رُؤيَةُ هَذَا المَنشُور.", "direct": "المُستخدمِونَ المُشارِ إليهم فَقَطْ مَن يُمكِنُهُم رُؤيَةُ هَذَا المَنشُور." + }, + "translation": { + "translated_from": "الترجَمَة مِن %s بِاستِخدَام %s", + "unknown_language": "غير مَعرُوفة", + "unknown_provider": "غير مَعرُوف", + "show_original": "Shown Original" } }, "friendship": { @@ -218,10 +237,16 @@ "get_started": "ابدأ الآن", "log_in": "تسجيلُ الدخول" }, + "login": { + "title": "مَرحَبًا بِكَ مُجَدَّدًا", + "subtitle": "سَجِّل دُخولَكَ إلى الخادِم الَّذي أنشأتَ حِسابَكَ فيه.", + "server_search_field": { + "placeholder": "أدخِل عُنوانَ URL أو اِبحَث عَنِ الخادِمِ الخاصّ بِك" + } + }, "server_picker": { "title": "اِختر خادِم،\nأيًّا مِنهُم.", - "subtitle": "اختر مجتمعًا بناءً على اهتماماتك، منطقتك أو يمكنك حتى اختيارُ مجتمعٍ ذي غرضٍ عام.", - "subtitle_extend": "اختر مجتمعًا بناءً على اهتماماتك، منطقتك أو يمكنك حتى اختيارُ مجتمعٍ ذي غرضٍ عام. تُشغَّل جميعُ المجتمعِ مِن قِبَلِ مُنظمَةٍ أو فردٍ مُستقلٍ تمامًا.", + "subtitle": "اِختر خادمًا بناءً على منطقتك، اِهتماماتك أو يُمكنك حتى اِختيارُ مجتمعٍ ذِي غرضٍ عام. بِإمكانِكَ الدردشة مع أي شخص على مَاستودُون، بغض النظر عن الخادم الخاصة بك.", "button": { "category": { "all": "الكُل", @@ -248,8 +273,7 @@ "category": "الفئة" }, "input": { - "placeholder": "اِبحَث عن خادِم أو انضم إلى آخر خاص بك...", - "search_servers_or_enter_url": "اِبحَث فِي الخَوادِم أو أدخِل رابِط" + "search_servers_or_enter_url": "اِبحث عَن مُجتَمَعَات أو أدخِل عُنوانَ URL" }, "empty_state": { "finding_servers": "يجري إيجاد خوادم متوفِّرَة...", @@ -383,10 +407,12 @@ "attachment_broken": "هذا ال%s مُعطَّل\nويتعذَّرُ رفعُه إلى ماستودون.", "description_photo": "صِف الصورة للمَكفوفين...", "description_video": "صِف المقطع المرئي للمَكفوفين...", - "load_failed": "Load Failed", - "upload_failed": "Upload Failed", - "can_not_recognize_this_media_attachment": "Can not regonize this media attachment", - "attachment_too_large": "Attachment too large" + "load_failed": "فَشَلَ التَّحميل", + "upload_failed": "فَشَلَ الرَّفع", + "can_not_recognize_this_media_attachment": "يتعذَّرُ التعرُّفُ على وسائِطِ هذا المُرفَق", + "attachment_too_large": "المُرفَق كَبيرٌ جِدًّا", + "compressing_state": "يجري الضغط...", + "server_processing_state": "مُعالجة الخادم جارِيَة..." }, "poll": { "duration_time": "المُدَّة: %s", @@ -396,7 +422,9 @@ "one_day": "يومٌ واحِد", "three_days": "ثلاثةُ أيام", "seven_days": "سبعةُ أيام", - "option_number": "الخيار %ld" + "option_number": "الخيار %ld", + "the_poll_is_invalid": "الاِستِطلاعُ غيرُ صالِح", + "the_poll_has_empty_option": "يوجَدُ خِيارٌ فارِغٌ فِي الاِستِطلاع" }, "content_warning": { "placeholder": "اكتب تَحذيرًا دَقيقًا هُنا..." @@ -417,7 +445,9 @@ "custom_emoji_picker": "منتقي الرموز التعبيرية المُخصَّص", "enable_content_warning": "تفعيل تحذير المُحتَوى", "disable_content_warning": "تعطيل تحذير المُحتَوى", - "post_visibility_menu": "قائمة ظهور المنشور" + "post_visibility_menu": "قائمة ظهور المنشور", + "post_options": "خياراتُ المَنشور", + "posting_as": "نَشر كَـ %s" }, "keyboard": { "discard_post": "تجاهُل المنشور", @@ -433,15 +463,23 @@ "follows_you": "يُتابِعُك" }, "dashboard": { - "posts": "مَنشورات", - "following": "مُتابَع", - "followers": "مُتابِع" + "my_posts": "مَنشورات", + "my_following": "مُتابَعُون", + "my_followers": "مُتابِعُون", + "other_posts": "مَنشورات", + "other_following": "مُتابَعُون", + "other_followers": "مُتابِعُون" }, "fields": { + "joined": "Joined", "add_row": "إضافة صف", "placeholder": { "label": "التسمية", "content": "المُحتَوى" + }, + "verified": { + "short": "تمَّ التَّحقق بِتاريخ %s", + "long": "تمَّ التَّحقق مِن مِلكية هذا الرابِطِ بِتاريخ %s" } }, "segmented_control": { @@ -707,6 +745,18 @@ }, "bookmark": { "title": "العَلاماتُ المَرجعيَّة" + }, + "followed_tags": { + "title": "وُسُومُ المُتابَع", + "header": { + "posts": "مَنشورات", + "participants": "المُشارِكُون", + "posts_today": "مَنشوراتُ اليَوم" + }, + "actions": { + "follow": "مُتابَعَة", + "unfollow": "إلغاءُ المُتابَعَة" + } } } } diff --git a/Localization/StringsConvertor/input/ca.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/ca.lproj/Localizable.stringsdict index cc28edbc6..615fd0c48 100644 --- a/Localization/StringsConvertor/input/ca.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/ca.lproj/Localizable.stringsdict @@ -50,6 +50,22 @@ %ld caràcters + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + resten %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 caràcter + other + %ld caràcters + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey @@ -392,7 +408,7 @@ NSStringFormatValueTypeKey ld one - fa 1 día + fa 1 dia other fa %ld dies @@ -408,7 +424,7 @@ NSStringFormatValueTypeKey ld one - fa 1h + fa 1 h other fa %ld hores @@ -424,9 +440,9 @@ NSStringFormatValueTypeKey ld one - fa 1 minut + fa 1 min other - fa %ld minuts + fa %ld min date.second.ago.abbr @@ -440,9 +456,9 @@ NSStringFormatValueTypeKey ld one - fa 1 segon + fa 1 s other - fa %ld segons + fa %ld s diff --git a/Localization/StringsConvertor/input/ca.lproj/app.json b/Localization/StringsConvertor/input/ca.lproj/app.json index 7164d1d12..3222ec984 100644 --- a/Localization/StringsConvertor/input/ca.lproj/app.json +++ b/Localization/StringsConvertor/input/ca.lproj/app.json @@ -2,55 +2,60 @@ "common": { "alerts": { "common": { - "please_try_again": "Si us plau intenta-ho de nou.", - "please_try_again_later": "Si us plau, prova-ho més tard." + "please_try_again": "Torna-ho a provar.", + "please_try_again_later": "Prova-ho més tard." }, "sign_up_failure": { "title": "Error en el registre" }, "server_error": { - "title": "Error del Servidor" + "title": "Error del servidor" }, "vote_failure": { - "title": "Error del Vot", + "title": "Error en votar", "poll_ended": "L'enquesta ha finalitzat" }, "discard_post_content": { "title": "Descarta l'esborrany", - "message": "Confirma per a descartar el contingut de la publicació composta." + "message": "Confirma per a descartar el contingut de la publicació." }, "publish_post_failure": { - "title": "Error de Publicació", - "message": "No s'ha pogut enviar la publicació.\nComprova la teva connexió a Internet.", + "title": "Error en publicar", + "message": "No s'ha pogut enviar la publicació.\nComprova la connexió a Internet.", "attachments_message": { "video_attach_with_photo": "No es pot adjuntar un vídeo a una publicació que ja contingui imatges.", - "more_than_one_video": "No pots adjuntar més d'un vídeo." + "more_than_one_video": "No es pot adjuntar més d'un vídeo." } }, "edit_profile_failure": { - "title": "Error al Editar el Perfil", - "message": "No es pot editar el perfil. Si us plau torna-ho a provar." + "title": "Error en editar el perfil", + "message": "No es pot editar el perfil. Torna-ho a provar." }, "sign_out": { - "title": "Tancar Sessió", - "message": "Estàs segur que vols tancar la sessió?", - "confirm": "Tancar Sessió" + "title": "Tanca la sessió", + "message": "Segur que vols tancar la sessió?", + "confirm": "Tanca la sessió" }, "block_domain": { - "title": "Estàs segur, realment segur que vols bloquejar totalment %s? En la majoria dels casos bloquejar o silenciar uns pocs objectius és suficient i preferible. No veureu contingut d’aquest domini i se suprimirà qualsevol dels vostres seguidors d’aquest domini.", - "block_entire_domain": "Bloquejar Domini" + "title": "Estàs totalment segur que vols bloquejar per complet %s? En la majoria dels casos bloquejar o silenciar uns pocs objectius és suficient i preferible. No veureu contingut d’aquest domini i se suprimirà qualsevol dels vostres seguidors d’aquest domini.", + "block_entire_domain": "Bloca el domini" }, "save_photo_failure": { - "title": "Error al Desar la Foto", - "message": "Activa el permís d'accés a la biblioteca de fotos per desar-la." + "title": "Error en desar la foto", + "message": "Activa el permís d'accés a la biblioteca de fotos per a desar-la." }, "delete_post": { - "title": "Esborrar Publicació", - "message": "Estàs segur que vols suprimir aquesta publicació?" + "title": "Eliminar la publicació", + "message": "Segur que vols eliminar aquesta publicació?" }, "clean_cache": { "title": "Neteja la memòria cau", "message": "S'ha netejat correctament la memòria cau de %s." + }, + "translation_failed": { + "title": "Nota", + "message": "La traducció ha fallat. Potser l'administrador d'aquest servidor no ha activat les traduccions o està executant una versió vella de Mastodon on les traduccions encara no eren suportades.", + "button": "D'acord" } }, "controls": { @@ -67,20 +72,21 @@ "done": "Fet", "confirm": "Confirma", "continue": "Continua", - "compose": "Composa", + "compose": "Redacta", "cancel": "Cancel·la", "discard": "Descarta", "try_again": "Torna a provar", "take_photo": "Fes una foto", "save_photo": "Desa la foto", "copy_photo": "Copia la foto", - "sign_in": "Iniciar sessió", - "sign_up": "Registre", - "see_more": "Veure més", + "sign_in": "Inicia sessió", + "sign_up": "Crea un compte", + "see_more": "Mostra'n més", "preview": "Vista prèvia", + "copy": "Copia", "share": "Comparteix", - "share_user": "Compartir %s", - "share_post": "Compartir Publicació", + "share_user": "Comparteix %s", + "share_post": "Comparteix la publicació", "open_in_safari": "Obrir a Safari", "open_in_browser": "Obre al navegador", "find_people": "Busca persones a seguir", @@ -91,12 +97,16 @@ "block_domain": "Bloqueja %s", "unblock_domain": "Desbloqueja %s", "settings": "Configuració", - "delete": "Suprimeix" + "delete": "Suprimeix", + "translate_post": { + "title": "Traduït del %s", + "unknown_language": "Desconegut" + } }, "tabs": { "home": "Inici", - "search": "Cerca", - "notification": "Notificació", + "search_and_explore": "Cerca i Explora", + "notifications": "Notificacions", "profile": "Perfil" }, "keyboard": { @@ -110,9 +120,9 @@ "previous_status": "Publicació anterior", "next_status": "Publicació següent", "open_status": "Obre la publicació", - "open_author_profile": "Obre el Perfil de l'Autor", - "open_reblogger_profile": "Obre el Perfil del Impulsor", - "reply_status": "Respon a la Publicació", + "open_author_profile": "Obre el perfil de l'autor", + "open_reblogger_profile": "Obre el perfil de l'impuls", + "reply_status": "Respon a la publicació", "toggle_reblog": "Commuta l'Impuls de la Publicació", "toggle_favorite": "Commuta el Favorit de la Publicació", "toggle_content_warning": "Commuta l'Avís de Contingut", @@ -132,27 +142,30 @@ "sensitive_content": "Contingut sensible", "media_content_warning": "Toca qualsevol lloc per a mostrar", "tap_to_reveal": "Toca per a mostrar", + "load_embed": "Carregar incrustat", + "link_via_user": "%s través de %s", "poll": { "vote": "Vota", "closed": "Finalitzada" }, "meta_entity": { "url": "Enllaç: %s", - "hashtag": "Etiqueta %s", - "mention": "Mostra el Perfil: %s", + "hashtag": "Etiqueta: %s", + "mention": "Mostra el perfil: %s", "email": "Correu electrònic: %s" }, "actions": { "reply": "Respon", - "reblog": "Impuls", - "unreblog": "Desfer l'impuls", + "reblog": "Impulsa", + "unreblog": "Desfés l'impuls", "favorite": "Favorit", - "unfavorite": "Desfer Favorit", + "unfavorite": "Desfés el favorit", "menu": "Menú", "hide": "Amaga", "show_image": "Mostra la imatge", "show_gif": "Mostra el GIF", "show_video_player": "Mostra el reproductor de vídeo", + "share_link_in_post": "Compartir l'Enllaç en el Tut", "tap_then_hold_to_show_menu": "Toca i manté per a veure el menú" }, "tag": { @@ -161,26 +174,32 @@ "link": "Enllaç", "hashtag": "Etiqueta", "email": "Correu electrònic", - "emoji": "Emoji" + "emoji": "Emojis" }, "visibility": { - "unlisted": "Tothom pot veure aquesta publicació però no es mostra en la línia de temps pública.", + "unlisted": "Tothom pot veure aquesta publicació, però no es mostra en la línia de temps pública.", "private": "Només els seus seguidors poden veure aquesta publicació.", "private_from_me": "Només els meus seguidors poden veure aquesta publicació.", "direct": "Només l'usuari mencionat pot veure aquesta publicació." + }, + "translation": { + "translated_from": "Traduït del %s fent servir %s", + "unknown_language": "Desconegut", + "unknown_provider": "Desconegut", + "show_original": "Mostra l'original" } }, "friendship": { "follow": "Segueix", "following": "Seguint", - "request": "Petició", + "request": "Sol·licitud", "pending": "Pendent", - "block": "Bloqueja", - "block_user": "Bloqueja %s", - "block_domain": "Bloqueja %s", - "unblock": "Desbloqueja", - "unblock_user": "Desbloqueja %s", - "blocked": "Bloquejat", + "block": "Bloca", + "block_user": "Bloca %s", + "block_domain": "Bloca %s", + "unblock": "Desbloca", + "unblock_user": "Desbloca %s", + "blocked": "Blocat", "mute": "Silencia", "mute_user": "Silencia %s", "unmute": "Deixa de silenciar", @@ -196,16 +215,16 @@ "now": "Ara" }, "loader": { - "load_missing_posts": "Carrega les publicacions faltants", - "loading_missing_posts": "Carregant les publicacions faltants...", + "load_missing_posts": "Carrega les publicacions restants", + "loading_missing_posts": "Carregant les publicacions restants...", "show_more_replies": "Mostra més respostes" }, "header": { "no_status_found": "No s'ha trobat cap publicació", - "blocking_warning": "No pots veure el perfil d'aquest usuari\n fins que el desbloquegis.\nEl teu perfil els sembla així.", - "user_blocking_warning": "No pots veure el perfil de %s\n fins que el desbloquegis.\nEl teu perfil els sembla així.", - "blocked_warning": "No pots veure el perfil d'aquest usuari\nfins que et desbloquegi.", - "user_blocked_warning": "No pots veure el perfil de %s\n fins que et desbloquegi.", + "blocking_warning": "No pots veure el perfil d'aquest usuari\nfins que el desbloquis.\nEl teu perfil els sembla així.", + "user_blocking_warning": "No pots veure el perfil de %s\nfins que el desbloquis.\nEl teu perfil els sembla així.", + "blocked_warning": "No pots veure el perfil d'aquest usuari\nfins que et desbloqui.", + "user_blocked_warning": "No pots veure el perfil de %s\n fins que et desbloqui.", "suspended_warning": "Aquest usuari ha estat suspès.", "user_suspended_warning": "El compte de %s ha estat suspès." } @@ -218,10 +237,16 @@ "get_started": "Comença", "log_in": "Inicia sessió" }, + "login": { + "title": "Ben tornat", + "subtitle": "T'inicia sessió en el servidor on has creat el teu compte.", + "server_search_field": { + "placeholder": "Insereix la URL o cerca el teu servidor" + } + }, "server_picker": { - "title": "Mastodon està fet d'usuaris en diferents comunitats.", - "subtitle": "Tria una comunitat segons els teus interessos, regió o una de propòsit general.", - "subtitle_extend": "Tria una comunitat segons els teus interessos, regió o una de propòsit general. Cada comunitat és operada per una organització totalment independent o individualment.", + "title": "Mastodon està fet d'usuaris en diferents servidors.", + "subtitle": "Tria un servidor en funció de la teva regió, interessos o un de propòsit general. Seguiràs podent connectar amb tothom a Mastodon, independentment del servidor.", "button": { "category": { "all": "Totes", @@ -248,8 +273,7 @@ "category": "CATEGORIA" }, "input": { - "placeholder": "Cerca servidors", - "search_servers_or_enter_url": "Cerca servidors o introdueix l'enllaç" + "search_servers_or_enter_url": "Cerca comunitats o introdueix l'URL" }, "empty_state": { "finding_servers": "Cercant els servidors disponibles...", @@ -362,11 +386,11 @@ }, "suggestion_account": { "title": "Cerca Persones a Seguir", - "follow_explain": "Quan segueixes algú, veuràs les seves publicacions a Inici." + "follow_explain": "Quan segueixes algú, veuràs els seus tuts a Inici." }, "compose": { "title": { - "new_post": "Nova publicació", + "new_post": "Nou Tut", "new_reply": "Nova Resposta" }, "media_selection": { @@ -381,12 +405,14 @@ "photo": "foto", "video": "vídeo", "attachment_broken": "Aquest %s està trencat i no pot ser\ncarregat a Mastodon.", - "description_photo": "Descriu la foto per als disminuïts visuals...", - "description_video": "Descriu el vídeo per als disminuïts visuals...", + "description_photo": "Descriu la foto per a les persones amb diversitat funcional...", + "description_video": "Descriu el vídeo per a les persones amb diversitat funcional...", "load_failed": "Ha fallat la càrrega", "upload_failed": "Pujada fallida", - "can_not_recognize_this_media_attachment": "No es pot reconèixer l'adjunt multimèdia", - "attachment_too_large": "El fitxer adjunt és massa gran" + "can_not_recognize_this_media_attachment": "No es pot reconèixer aquest adjunt multimèdia", + "attachment_too_large": "El fitxer adjunt és massa gran", + "compressing_state": "Comprimint...", + "server_processing_state": "Servidor processant..." }, "poll": { "duration_time": "Durada: %s", @@ -396,7 +422,9 @@ "one_day": "1 Dia", "three_days": "3 Dies", "seven_days": "7 Dies", - "option_number": "Opció %ld" + "option_number": "Opció %ld", + "the_poll_is_invalid": "L'enquesta no és vàlida", + "the_poll_has_empty_option": "L'enquesta té una opció buida" }, "content_warning": { "placeholder": "Escriu un advertiment precís aquí..." @@ -417,11 +445,13 @@ "custom_emoji_picker": "Selector d'Emoji Personalitzat", "enable_content_warning": "Activa l'Avís de Contingut", "disable_content_warning": "Desactiva l'Avís de Contingut", - "post_visibility_menu": "Menú de Visibilitat de Publicació" + "post_visibility_menu": "Menú de Visibilitat del Tut", + "post_options": "Opcions del Tut", + "posting_as": "Publicant com a %s" }, "keyboard": { - "discard_post": "Descarta la Publicació", - "publish_post": "Envia la Publicació", + "discard_post": "Descarta el Tut", + "publish_post": "Envia el Tut", "toggle_poll": "Commuta l'enquesta", "toggle_content_warning": "Commuta l'Avís de Contingut", "append_attachment_entry": "Afegeix Adjunt - %s", @@ -433,21 +463,29 @@ "follows_you": "Et segueix" }, "dashboard": { - "posts": "publicacions", - "following": "seguint", - "followers": "seguidors" + "my_posts": "tuts", + "my_following": "seguint", + "my_followers": "seguidors", + "other_posts": "tuts", + "other_following": "seguint", + "other_followers": "seguidors" }, "fields": { + "joined": "S'hi va unir", "add_row": "Afegeix fila", "placeholder": { "label": "Etiqueta", "content": "Contingut" + }, + "verified": { + "short": "Verificat a %s", + "long": "La propietat d'aquest enllaç es va verificar el dia %s" } }, "segmented_control": { - "posts": "Publicacions", + "posts": "Tuts", "replies": "Respostes", - "posts_and_replies": "Publicacions i Respostes", + "posts_and_replies": "Tuts i Respostes", "media": "Mèdia", "about": "Quant a" }, @@ -461,12 +499,12 @@ "message": "Confirma deixar de silenciar a %s" }, "confirm_block_user": { - "title": "Bloqueja el Compte", - "message": "Confirma per a bloquejar %s" + "title": "Bloca el Compte", + "message": "Confirma per a blocar %s" }, "confirm_unblock_user": { - "title": "Desbloqueja el Compte", - "message": "Confirma per a desbloquejar %s" + "title": "Desbloca el Compte", + "message": "Confirma per a desblocar %s" }, "confirm_show_reblogs": { "title": "Mostra els Impulsos", @@ -526,7 +564,7 @@ "all": "Tots", "people": "Gent", "hashtags": "Etiquetes", - "posts": "Publicacions" + "posts": "Tuts" }, "empty_state": { "no_results": "No hi ha resultats" @@ -537,13 +575,13 @@ }, "discovery": { "tabs": { - "posts": "Publicacions", + "posts": "Tuts", "hashtags": "Etiquetes", "news": "Notícies", "community": "Comunitat", "for_you": "Per a tu" }, - "intro": "Aquestes son les publicacions que criden l'atenció en el teu racó de Mastodon." + "intro": "Aquests son els tuts que criden l'atenció en el teu racó de Mastodon." }, "favorite": { "title": "Els teus Favorits" @@ -573,8 +611,8 @@ } }, "thread": { - "back_title": "Publicació", - "title": "Publicació de %s" + "back_title": "Tut", + "title": "Tut de %s" }, "settings": { "title": "Configuració", @@ -594,9 +632,9 @@ }, "notifications": { "title": "Notificacions", - "favorites": "Ha afavorit el meu estat", + "favorites": "Ha afavorit el meu tut", "follows": "Em segueix", - "boosts": "Ha impulsat el meu estat", + "boosts": "Ha impulsat el meu tut", "mentions": "M'ha mencionat", "trigger": { "anyone": "algú", @@ -638,7 +676,7 @@ "title": "Informa sobre %s", "step1": "Pas 1 de 2", "step2": "Pas 2 de 2", - "content1": "Hi ha alguna altre publicació que vulguis afegir a l'informe?", + "content1": "Hi ha algun altre tut que vulguis afegir a l'informe?", "content2": "Hi ha alguna cosa que els moderadors hagin de saber sobre aquest informe?", "report_sent_title": "Gràcies per informar, ho investigarem.", "send": "Envia Informe", @@ -647,7 +685,7 @@ "reported": "REPORTAT", "step_one": { "step_1_of_4": "Pas 1 de 4", - "whats_wrong_with_this_post": "Quin és el problema amb aquesta publicació?", + "whats_wrong_with_this_post": "Quin és el problema amb aquest tut?", "whats_wrong_with_this_account": "Quin és el problema amb aquest compte?", "whats_wrong_with_this_username": "Quin és el problema amb %s?", "select_the_best_match": "Selecciona la millor coincidència", @@ -668,7 +706,7 @@ }, "step_three": { "step_3_of_4": "Pas 3 de 4", - "are_there_any_posts_that_back_up_this_report": "Hi ha alguna publicació que recolzi aquest informe?", + "are_there_any_posts_that_back_up_this_report": "Hi ha alguns tuts que recolzin aquest informe?", "select_all_that_apply": "Selecciona tot el que correspongui" }, "step_four": { @@ -677,14 +715,14 @@ }, "step_final": { "dont_want_to_see_this": "No vols veure això?", - "when_you_see_something_you_dont_like_on_mastodon_you_can_remove_the_person_from_your_experience.": "Quan veus alguna cosa que no t'agrada a Mastodon, pots eliminar la persona de la vostra experiència.", + "when_you_see_something_you_dont_like_on_mastodon_you_can_remove_the_person_from_your_experience.": "Quan veus alguna cosa que no t'agrada a Mastodon, pots eliminar la persona de la teva experiència.", "unfollow": "Deixa de seguir", "unfollowed": "S'ha deixat de seguir", "unfollow_user": "Deixa de seguir %s", "mute_user": "Silencia %s", - "you_wont_see_their_posts_or_reblogs_in_your_home_feed_they_wont_know_they_ve_been_muted": "No veuràs les seves publicacions o impulsos a la teva línia de temps personal. No sabran que han estat silenciats.", + "you_wont_see_their_posts_or_reblogs_in_your_home_feed_they_wont_know_they_ve_been_muted": "No veuràs els seus tuts o impulsos a la teva línia de temps personal. No sabran que han estat silenciats.", "block_user": "Bloca %s", - "they_will_no_longer_be_able_to_follow_or_see_your_posts_but_they_can_see_if_theyve_been_blocked": "Ja no podran seguir ni veure les teves publicacions, però poden veure si han estat bloquejats.", + "they_will_no_longer_be_able_to_follow_or_see_your_posts_but_they_can_see_if_theyve_been_blocked": "Ja no podran seguir ni veure els teus tus, però poden veure si han estat blocats.", "while_we_review_this_you_can_take_action_against_user": "Mentre ho revisem, pots prendre mesures contra %s" } }, @@ -707,6 +745,18 @@ }, "bookmark": { "title": "Marcadors" + }, + "followed_tags": { + "title": "Etiquetes seguides", + "header": { + "posts": "tuts", + "participants": "participants", + "posts_today": "tuts d'avui" + }, + "actions": { + "follow": "Segueix", + "unfollow": "Deixa de seguir" + } } } } diff --git a/Localization/StringsConvertor/input/ckb.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/ckb.lproj/Localizable.stringsdict index 001a8a608..8116226ec 100644 --- a/Localization/StringsConvertor/input/ckb.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/ckb.lproj/Localizable.stringsdict @@ -50,6 +50,22 @@ %ld نووسە + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ left + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 character + other + %ld characters + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/ckb.lproj/app.json b/Localization/StringsConvertor/input/ckb.lproj/app.json index 25452f38b..e7222d645 100644 --- a/Localization/StringsConvertor/input/ckb.lproj/app.json +++ b/Localization/StringsConvertor/input/ckb.lproj/app.json @@ -51,6 +51,11 @@ "clean_cache": { "title": "بیرگە پاک بکەوە", "message": "سەرکەوتووانە بیرگەی %s پاک کرایەوە." + }, + "translation_failed": { + "title": "Note", + "message": "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported.", + "button": "OK" } }, "controls": { @@ -74,10 +79,11 @@ "take_photo": "وێنە بگرە", "save_photo": "هەڵی بگرە", "copy_photo": "لەبەری بگرەوە", - "sign_in": "بچۆ ژوورەوە", - "sign_up": "خۆت تۆمار بکە", + "sign_in": "Log in", + "sign_up": "Create account", "see_more": "زیاتر ببینە", "preview": "پێشبینین", + "copy": "Copy", "share": "هاوبەشی بکە", "share_user": "%s هاوبەش بکە", "share_post": "هاوبەشی بکە", @@ -91,12 +97,16 @@ "block_domain": "%s ئاستەنگ بکە", "unblock_domain": "%s ئاستەنگ مەکە", "settings": "رێکخستنەکان", - "delete": "بیسڕەوە" + "delete": "بیسڕەوە", + "translate_post": { + "title": "Translate from %s", + "unknown_language": "Unknown" + } }, "tabs": { "home": "ماڵەوە", - "search": "بگەڕێ", - "notification": "ئاگادارکردنەوەکان", + "search_and_explore": "Search and Explore", + "notifications": "Notifications", "profile": "پرۆفایل" }, "keyboard": { @@ -132,6 +142,8 @@ "sensitive_content": "ناوەڕۆکی هەستیار", "media_content_warning": "دەستی پیا بنێ بۆ نیشاندانی", "tap_to_reveal": "دەستی پیا بنێ بۆ نیشاندانی", + "load_embed": "Load Embed", + "link_via_user": "%s via %s", "poll": { "vote": "دەنگ بدە", "closed": "داخراوە" @@ -153,6 +165,7 @@ "show_image": "وێنەکە نیشان بدە", "show_gif": "گیفەکە نیشان بدە", "show_video_player": "ڤیدیۆکە لێ بدە", + "share_link_in_post": "Share Link in Post", "tap_then_hold_to_show_menu": "دەستی پیا بنێ و بیگرە بۆ نیشاندانی پێڕستەکە" }, "tag": { @@ -168,6 +181,12 @@ "private": "تەنیا شوێنکەوتووەکانی دەتوانن ئەم پۆستە ببینن.", "private_from_me": "تەنیا شوێنکەوتووەکانم دەتوانن ئەم پۆستە ببینن.", "direct": "تەنیا بەکارهێنەرە ئاماژە پێکراوەکە دەتوانێت ئەم پۆستە ببینێت." + }, + "translation": { + "translated_from": "Translated from %s using %s", + "unknown_language": "Unknown", + "unknown_provider": "Unknown", + "show_original": "Shown Original" } }, "friendship": { @@ -218,10 +237,16 @@ "get_started": "دەست پێ بکە", "log_in": "بچۆ ژوورەوە" }, + "login": { + "title": "Welcome back", + "subtitle": "Log you in on the server you created your account on.", + "server_search_field": { + "placeholder": "Enter URL or search for your server" + } + }, "server_picker": { "title": "ماستۆدۆن لە چەندان بەکارهێنەر پێک دێت کە لە ڕاژەکاری جیاواز دان.", - "subtitle": "ڕاژەکارێکێکی گشتی یان دانەیەک لەسەر بنەمای حەزەکانت و هەرێمەکەت هەڵبژێرە.", - "subtitle_extend": "ڕاژەکارێکێکی گشتی یان دانەیەک لەسەر بنەمای حەزەکانت و هەرێمەکەت هەڵبژێرە. هەر ڕاژەکارێک لەلایەن ڕێکخراوێک یان تاکەکەسێک بەڕێوە دەبرێت.", + "subtitle": "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers.", "button": { "category": { "all": "هەموو", @@ -248,8 +273,7 @@ "category": "بەش" }, "input": { - "placeholder": "بگەڕێ", - "search_servers_or_enter_url": "Search servers or enter URL" + "search_servers_or_enter_url": "Search communities or enter URL" }, "empty_state": { "finding_servers": "ڕاژەکار دەدۆزرێتەوە...", @@ -385,8 +409,10 @@ "description_video": "ڤیدیۆکەت بۆ نابیناکان باس بکە...", "load_failed": "Load Failed", "upload_failed": "Upload Failed", - "can_not_recognize_this_media_attachment": "Can not regonize this media attachment", - "attachment_too_large": "Attachment too large" + "can_not_recognize_this_media_attachment": "Can not recognize this media attachment", + "attachment_too_large": "Attachment too large", + "compressing_state": "Compressing...", + "server_processing_state": "Server Processing..." }, "poll": { "duration_time": "کات:‌ %s", @@ -396,7 +422,9 @@ "one_day": "1 ڕۆژ", "three_days": "3 ڕۆژ", "seven_days": "7 ڕۆژ", - "option_number": "بژاردەی %ld" + "option_number": "بژاردەی %ld", + "the_poll_is_invalid": "The poll is invalid", + "the_poll_has_empty_option": "The poll has empty option" }, "content_warning": { "placeholder": "ئاگادارییەکەت لێرە بنووسە..." @@ -417,7 +445,9 @@ "custom_emoji_picker": "هەڵبژێری ئیمۆجی", "enable_content_warning": "ئاگاداریی ناوەڕۆک چالاک بکە", "disable_content_warning": "ئاگاداریی ناوەڕۆک ناچالاک بکە", - "post_visibility_menu": "پێڕستی شێوازی دەرکەوتنی پۆست" + "post_visibility_menu": "پێڕستی شێوازی دەرکەوتنی پۆست", + "post_options": "Post Options", + "posting_as": "Posting as %s" }, "keyboard": { "discard_post": "پۆستەکە هەڵوەشێنەوە", @@ -433,15 +463,23 @@ "follows_you": "Follows You" }, "dashboard": { - "posts": "پۆستەکان", - "following": "شوێنکەوتن", - "followers": "شوێنکەوتوو" + "my_posts": "posts", + "my_following": "following", + "my_followers": "followers", + "other_posts": "posts", + "other_following": "following", + "other_followers": "followers" }, "fields": { + "joined": "Joined", "add_row": "ڕیز زیاد بکە", "placeholder": { "label": "ناونیشان", "content": "ناوەڕۆک" + }, + "verified": { + "short": "Verified on %s", + "long": "Ownership of this link was checked on %s" } }, "segmented_control": { @@ -707,6 +745,18 @@ }, "bookmark": { "title": "Bookmarks" + }, + "followed_tags": { + "title": "Followed Tags", + "header": { + "posts": "posts", + "participants": "participants", + "posts_today": "posts today" + }, + "actions": { + "follow": "Follow", + "unfollow": "Unfollow" + } } } } diff --git a/Localization/StringsConvertor/input/cs.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/cs.lproj/Localizable.stringsdict index 827bd79e6..63e324a1b 100644 --- a/Localization/StringsConvertor/input/cs.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/cs.lproj/Localizable.stringsdict @@ -62,6 +62,26 @@ %ld znaků + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ zbývá + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 znak + few + %ld znaky + many + %ld znaků + other + %ld znaků + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey @@ -288,13 +308,13 @@ NSStringFormatValueTypeKey ld one - 1 following + 1 sledující few - %ld following + %ld sledující many - %ld following + %ld sledujících other - %ld following + %ld sledujících plural.count.follower @@ -328,13 +348,13 @@ NSStringFormatValueTypeKey ld one - 1 year left + Zbývá 1 rok few - %ld years left + Zbývají %ld roky many - %ld years left + Zbývá %ld roků other - %ld years left + Zbývá %ld roků date.month.left @@ -348,7 +368,7 @@ NSStringFormatValueTypeKey ld one - 1 months left + Zbývá 1 měsíc few %ld months left many diff --git a/Localization/StringsConvertor/input/cs.lproj/app.json b/Localization/StringsConvertor/input/cs.lproj/app.json index 4050352f4..30d5eb26c 100644 --- a/Localization/StringsConvertor/input/cs.lproj/app.json +++ b/Localization/StringsConvertor/input/cs.lproj/app.json @@ -51,6 +51,11 @@ "clean_cache": { "title": "Vyčistit mezipaměť", "message": "Úspěšně vyčištěno %s mezipaměti." + }, + "translation_failed": { + "title": "Poznámka", + "message": "Překlad se nezdařil. Správce možná nepovolil překlad na tomto serveru nebo tento server používá starší verzi Mastodonu, kde překlady ještě nejsou podporovány.", + "button": "OK" } }, "controls": { @@ -75,9 +80,10 @@ "save_photo": "Uložit fotku", "copy_photo": "Kopírovat fotografii", "sign_in": "Přihlásit se", - "sign_up": "Zaregistrovat se", + "sign_up": "Vytvořit účet", "see_more": "Zobrazit více", "preview": "Náhled", + "copy": "Kopírovat", "share": "Sdílet", "share_user": "Sdílet %s", "share_post": "Sdílet příspěvek", @@ -91,12 +97,16 @@ "block_domain": "Blokovat %s", "unblock_domain": "Odblokovat %s", "settings": "Nastavení", - "delete": "Smazat" + "delete": "Smazat", + "translate_post": { + "title": "Přeložit z %s", + "unknown_language": "Neznámý" + } }, "tabs": { "home": "Domů", - "search": "Hledat", - "notification": "Oznamování", + "search_and_explore": "Hledat a zkoumat", + "notifications": "Oznámení", "profile": "Profil" }, "keyboard": { @@ -113,8 +123,8 @@ "open_author_profile": "Otevřít profil autora", "open_reblogger_profile": "Otevřít rebloggerův profil", "reply_status": "Odpovědět na příspěvek", - "toggle_reblog": "Toggle Reblog on Post", - "toggle_favorite": "Toggle Favorite on Post", + "toggle_reblog": "Přepnout Reblog na příspěvku", + "toggle_favorite": "Přepnout Oblíbené na příspěvku", "toggle_content_warning": "Přepnout varování obsahu", "preview_image": "Náhled obrázku" }, @@ -132,6 +142,8 @@ "sensitive_content": "Citlivý obsah", "media_content_warning": "Klepnutím kdekoli zobrazíte", "tap_to_reveal": "Klepnutím zobrazit", + "load_embed": "Načíst vložené", + "link_via_user": "%s přes %s", "poll": { "vote": "Hlasovat", "closed": "Uzavřeno" @@ -153,6 +165,7 @@ "show_image": "Zobrazit obrázek", "show_gif": "Zobrazit GIF", "show_video_player": "Zobrazit video přehrávač", + "share_link_in_post": "Sdílet odkaz v příspěvku", "tap_then_hold_to_show_menu": "Klepnutím podržte pro zobrazení nabídky" }, "tag": { @@ -168,6 +181,12 @@ "private": "Pouze jejich sledující mohou vidět tento příspěvek.", "private_from_me": "Pouze moji sledující mohou vidět tento příspěvek.", "direct": "Pouze zmíněný uživatel může vidět tento příspěvek." + }, + "translation": { + "translated_from": "Přeloženo z %s pomocí %s", + "unknown_language": "Neznámý", + "unknown_provider": "Neznámý", + "show_original": "Zobrazit originál" } }, "friendship": { @@ -218,10 +237,16 @@ "get_started": "Začínáme", "log_in": "Přihlásit se" }, + "login": { + "title": "Vítejte zpět", + "subtitle": "Přihlaste se na serveru, na kterém jste si vytvořili účet.", + "server_search_field": { + "placeholder": "Zadejte URL nebo vyhledávejte váš server" + } + }, "server_picker": { "title": "Mastodon tvoří uživatelé z různých serverů.", - "subtitle": "Vyberte server založený na vašich zájmech, regionu nebo obecném účelu.", - "subtitle_extend": "Vyberte server založený na vašich zájmech, regionu nebo obecném účelu. Každý server je provozován zcela nezávislou organizací nebo jednotlivcem.", + "subtitle": "Vyberte server založený ve vašem regionu, podle zájmů nebo podle obecného účelu. Stále můžete chatovat s kýmkoli na Mastodonu bez ohledu na vaše servery.", "button": { "category": { "all": "Vše", @@ -248,8 +273,7 @@ "category": "KATEGORIE" }, "input": { - "placeholder": "Hledat servery", - "search_servers_or_enter_url": "Hledat servery nebo zadat URL" + "search_servers_or_enter_url": "Hledejte komunity nebo zadejte URL" }, "empty_state": { "finding_servers": "Hledání dostupných serverů...", @@ -386,7 +410,9 @@ "load_failed": "Načtení se nezdařilo", "upload_failed": "Nahrání selhalo", "can_not_recognize_this_media_attachment": "Nelze rozpoznat toto medium přílohy", - "attachment_too_large": "Příloha je příliš velká" + "attachment_too_large": "Příloha je příliš velká", + "compressing_state": "Probíhá komprese...", + "server_processing_state": "Zpracování serveru..." }, "poll": { "duration_time": "Doba trvání: %s", @@ -396,7 +422,9 @@ "one_day": "1 den", "three_days": "3 dny", "seven_days": "7 dní", - "option_number": "Možnost %ld" + "option_number": "Možnost %ld", + "the_poll_is_invalid": "Anketa je neplatná", + "the_poll_has_empty_option": "Anketa má prázdnou možnost" }, "content_warning": { "placeholder": "Zde napište přesné varování..." @@ -417,7 +445,9 @@ "custom_emoji_picker": "Vlastní výběr Emoji", "enable_content_warning": "Povolit upozornění na obsah", "disable_content_warning": "Vypnout upozornění na obsah", - "post_visibility_menu": "Menu viditelnosti příspěvku" + "post_visibility_menu": "Menu viditelnosti příspěvku", + "post_options": "Možnosti příspěvku", + "posting_as": "Odesílání jako %s" }, "keyboard": { "discard_post": "Zahodit příspěvek", @@ -433,15 +463,23 @@ "follows_you": "Sleduje vás" }, "dashboard": { - "posts": "příspěvky", - "following": "sledování", - "followers": "sledující" + "my_posts": "příspěvky", + "my_following": "sledování", + "my_followers": "sledující", + "other_posts": "příspěvky", + "other_following": "sledování", + "other_followers": "sledující" }, "fields": { + "joined": "Připojen/a", "add_row": "Přidat řádek", "placeholder": { "label": "Označení", "content": "Obsah" + }, + "verified": { + "short": "Ověřeno na %s", + "long": "Vlastnictví tohoto odkazu bylo zkontrolováno na %s" } }, "segmented_control": { @@ -707,6 +745,18 @@ }, "bookmark": { "title": "Záložky" + }, + "followed_tags": { + "title": "Sledované štítky", + "header": { + "posts": "příspěvky", + "participants": "účastníci", + "posts_today": "příspěvky dnes" + }, + "actions": { + "follow": "Sledovat", + "unfollow": "Přestat sledovat" + } } } } diff --git a/Localization/StringsConvertor/input/cy.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/cy.lproj/Localizable.stringsdict index 038eaffda..33bcd5a8d 100644 --- a/Localization/StringsConvertor/input/cy.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/cy.lproj/Localizable.stringsdict @@ -13,23 +13,23 @@ NSStringFormatValueTypeKey ld zero - %ld unread notification + %ld hysbysiad heb ei ddarllen one - 1 unread notification + %ld hysbysiad heb ei ddarllen two - %ld unread notification + %ld hysbysiad heb eu darllen few - %ld unread notification + %ld hysbysiad heb eu darllen many - %ld unread notification + %ld hysbysiad heb eu darllen other - %ld unread notification + %ld hysbysiad heb eu darllen a11y.plural.count.input_limit_exceeds NSStringLocalizedFormatKey - Input limit exceeds %#@character_count@ + Mae'r terfyn mewnbwn yn fwy na %#@character_count@ character_count NSStringFormatSpecTypeKey @@ -37,23 +37,23 @@ NSStringFormatValueTypeKey ld zero - %ld characters + %ld nod one - 1 character + %ld nod two - %ld characters + %ld nod few - %ld characters + %ld nod many - %ld characters + %ld nod other - %ld nodau + %ld nod a11y.plural.count.input_limit_remains NSStringLocalizedFormatKey - Input limit remains %#@character_count@ + Mae'r terfyn mewnbwn yn %#@character_count@ character_count NSStringFormatSpecTypeKey @@ -61,17 +61,41 @@ NSStringFormatValueTypeKey ld zero - %ld characters + %ld nod one - 1 character + %ld nod two - %ld characters + %ld nod few - %ld characters + %ld nod many - %ld characters + %ld nod other - %ld characters + %ld nod + + + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ ar ôl + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + %ld nod + one + %ld nod + two + %ld nod + few + %ld nod + many + %ld nod + other + %ld nod plural.count.followed_by_and_mutual @@ -104,17 +128,17 @@ NSStringFormatValueTypeKey ld zero - Followed by %1$@, and %ld mutuals + Dilynwyd gan %1$@, a %ld mewn cyffredin one - Followed by %1$@, and another mutual + Dilynwyd gan %1$@, a pherson gyffredin two - Followed by %1$@, and %ld mutuals + Dilynwyd gan %1$@, a %ld mewn cyffredin few - Followed by %1$@, and %ld mutuals + Dilynwyd gan %1$@, a %ld mewn cyffredin many - Followed by %1$@, and %ld mutuals + Dilynwyd gan %1$@, a %ld mewn cyffredin other - Followed by %1$@, and %ld mutuals + Dilynwyd gan %1$@, a %ld mewn cyffredin plural.count.metric_formatted.post @@ -128,17 +152,17 @@ NSStringFormatValueTypeKey ld zero - post + tŵt one - post + tŵt two - postiau + tŵt few - posts + tŵt many - posts + tŵt other - postiau + postiadau plural.count.media @@ -152,17 +176,17 @@ NSStringFormatValueTypeKey ld zero - %ld media + %ld cyfrwng one - 1 media + %ld cyfrwng two - %ld media + %ld gyfrwng few - %ld media + %ld cyfrwng many - %ld media + %ld cyfrwng other - %ld media + %ld cyfrwng plural.count.post @@ -176,17 +200,17 @@ NSStringFormatValueTypeKey ld zero - %ld posts + %ld post one - 1 post + %ld post two - %ld posts + %ld bost few - %ld posts + %ld post many - %ld posts + %ld post other - %ld posts + %ld post plural.count.favorite @@ -200,17 +224,17 @@ NSStringFormatValueTypeKey ld zero - %ld favorites + %ld ffefrynnau one - 1 favorite + %ld ffefryn two - %ld favorites + %ld ffefryn few - %ld favorites + %ld ffefryn many - %ld favorites + %ld ffefryn other - %ld favorites + %ld ffefryn plural.count.reblog @@ -224,17 +248,17 @@ NSStringFormatValueTypeKey ld zero - %ld reblogs + %ld ailflogiau one - 1 reblog + %ld ailflog two - %ld reblogs + %ld ailflog few - %ld reblogs + %ld ailflog many - %ld reblogs + %ld ailflog other - %ld reblogs + %ld o ailflogiau plural.count.reply @@ -248,17 +272,17 @@ NSStringFormatValueTypeKey ld zero - %ld replies + %ld ymatebau one - 1 reply + %ld ymateb two - %ld replies + %ld ymateb few - %ld replies + %ld ymateb many - %ld replies + %ld o ymatebau other - %ld replies + %ld ymateb plural.count.vote @@ -272,17 +296,17 @@ NSStringFormatValueTypeKey ld zero - %ld votes + %ld pleidleisiau one - 1 vote + %ld pleidlais two - %ld votes + %ld bleidlais few - %ld votes + %ld phleidlais many - %ld votes + %ld pleidlais other - %ld votes + %ld pleidlais plural.count.voter @@ -296,17 +320,17 @@ NSStringFormatValueTypeKey ld zero - %ld voters + %ld pleidleiswyr one - 1 voter + %ld pleidleisiwr two - %ld voters + %ld bleidleisiwr few - %ld voters + %ld phleidleisiwr many - %ld voters + %ld pleidleisiwr other - %ld voters + %ld pleidleisiwr plural.people_talking @@ -320,17 +344,17 @@ NSStringFormatValueTypeKey ld zero - %ld people talking + %ld person yn trafod one - 1 people talking + %ld person yn trafod two - %ld people talking + %ld berson yn trafod few - %ld people talking + %ld o bobl yn trafod many - %ld people talking + %ld o bobl yn trafod other - %ld people talking + %ld o bobl yn trafod plural.count.following @@ -344,17 +368,17 @@ NSStringFormatValueTypeKey ld zero - %ld following + %ld yn dilyn one - 1 following + %ld yn dilyn two - %ld following + %ld yn dilyn few - %ld following + %ld yn dilyn many - %ld following + %ld yn dilyn other - %ld following + %ld yn dilyn plural.count.follower @@ -368,17 +392,17 @@ NSStringFormatValueTypeKey ld zero - %ld followers + %ld dilynwyr one - 1 follower + %ld dilynwr two - %ld followers + %ld ddilynwr few - %ld followers + %ld dilynwr many - %ld followers + %ld o ddilynwyr other - %ld followers + %ld dilynwr date.year.left @@ -392,17 +416,17 @@ NSStringFormatValueTypeKey ld zero - %ld years left + %ld blwyddyn ar ôl one - 1 year left + %ld blwyddyn ar ôl two - %ld years left + %ld flwyddyn ar ôl few - %ld years left + %ld blwyddyn ar ôl many - %ld years left + %ld blwyddyn ar ôl other - %ld years left + %ld blwyddyn ar ôl date.month.left @@ -416,17 +440,17 @@ NSStringFormatValueTypeKey ld zero - %ld months left + %ld mis ar ôl one - 1 months left + %ld mis ar ôl two - %ld months left + %ld fis ar ôl few - %ld months left + %ld mis ar ôl many - %ld months left + %ld mis ar ôl other - %ld months left + %ld mis ar ôl date.day.left @@ -440,17 +464,17 @@ NSStringFormatValueTypeKey ld zero - %ld days left + %ld diwrnod ar ôl one - 1 day left + %ld diwrnod ar ôl two - %ld days left + %ld ddiwrnod ar ôl few - %ld days left + %ld diwrnod ar ôl many - %ld days left + %ld diwrnod ar ôl other - %ld days left + %ld diwrnod ar ôl date.hour.left @@ -464,17 +488,17 @@ NSStringFormatValueTypeKey ld zero - %ld hours left + %ld awr ar ôl one - 1 hour left + %ld awr ar ôl two - %ld hours left + %ld awr ar ôl few - %ld hours left + %ld awr ar ôl many - %ld hours left + %ld awr ar ôl other - %ld hours left + %ld awr ar ôl date.minute.left @@ -488,17 +512,17 @@ NSStringFormatValueTypeKey ld zero - %ld minutes left + %ld munud ar ôl one - 1 minute left + %ld munud ar ôl two - %ld minutes left + %ld funud ar ôl few - %ld minutes left + %ld munud ar ôl many - %ld minutes left + %ld munud ar ôl other - %ld minutes left + %ld munud ar ôl date.second.left @@ -512,17 +536,17 @@ NSStringFormatValueTypeKey ld zero - %ld seconds left + %ld eiliad ar ôl one - 1 second left + %ld eiliad ar ôl two - %ld seconds left + %ld eiliad ar ôl few - %ld seconds left + %ld eiliad ar ôl many - %ld seconds left + %ld eiliad ar ôl other - %ld seconds left + %ld eiliad ar ôl date.year.ago.abbr @@ -536,17 +560,17 @@ NSStringFormatValueTypeKey ld zero - %ldy ago + %ld blwyddyn yn ôl one - 1y ago + %ld blwyddyn yn ôl two - %ldy ago + %ld flwyddyn yn ôl few - %ldy ago + %ld blwyddyn yn ôl many - %ldy ago + %ld blwyddyn yn ôl other - %ldy ago + %ld blwyddyn yn ôl date.month.ago.abbr @@ -560,17 +584,17 @@ NSStringFormatValueTypeKey ld zero - %ldM ago + %ld munud yn ôl one - 1M ago + %ld munud yn ôl two - %ldM ago + %ld funud yn ôl few - %ldM ago + %ld munud yn ôl many - %ldM ago + %ld munud yn ôl other - %ldM ago + %ld munud yn ôl date.day.ago.abbr @@ -584,17 +608,17 @@ NSStringFormatValueTypeKey ld zero - %ldd ago + %ldd yn ôl one - 1d ago + %ldd yn ôl two - %ldd ago + %ldd yn ôl few - %ldd ago + %ldd yn ôl many - %ldd ago + %ldd yn ôl other - %ldd ago + %ldd yn ôl date.hour.ago.abbr @@ -608,17 +632,17 @@ NSStringFormatValueTypeKey ld zero - %ldh ago + %ld awr yn ôl one - 1h ago + %ld awr yn ôl two - %ldh ago + %ld awr yn ôl few - %ldh ago + %ld awr yn ôl many - %ldh ago + %ld awr yn ôl other - %ldh ago + %ld awr yn ôl date.minute.ago.abbr @@ -632,17 +656,17 @@ NSStringFormatValueTypeKey ld zero - %ldm ago + %ld munud yn ôl one - 1m ago + %ld munud yn ôl two - %ldm ago + %ld funud yn ôl few - %ldm ago + %ld munud yn ôl many - %ldm ago + %ld munud yn ôl other - %ldm ago + %ld munud yn ôl date.second.ago.abbr @@ -656,17 +680,17 @@ NSStringFormatValueTypeKey ld zero - %lds ago + %ld eiliad yn ôl one - 1s ago + %ld eiliad yn ôl two - %lds ago + %ld eiliad yn ôl few - %lds ago + %ld eiliad yn ôl many - %lds ago + %ld eiliad yn ôl other - %lds ago + %ld eiliad yn ôl diff --git a/Localization/StringsConvertor/input/cy.lproj/app.json b/Localization/StringsConvertor/input/cy.lproj/app.json index f36fe7d16..b276dfa4b 100644 --- a/Localization/StringsConvertor/input/cy.lproj/app.json +++ b/Localization/StringsConvertor/input/cy.lproj/app.json @@ -2,711 +2,761 @@ "common": { "alerts": { "common": { - "please_try_again": "Please try again.", - "please_try_again_later": "Please try again later." + "please_try_again": "Ceisiwch eto.", + "please_try_again_later": "Ceisiwch eto nes ymlaen." }, "sign_up_failure": { - "title": "Sign Up Failure" + "title": "Gwall Ymgofrestru" }, "server_error": { - "title": "Server Error" + "title": "Gwall Gweinydd" }, "vote_failure": { - "title": "Vote Failure", - "poll_ended": "The poll has ended" + "title": "Gwall Pleidleisio", + "poll_ended": "Mae'r pôl wedi dod i ben" }, "discard_post_content": { - "title": "Discard Draft", - "message": "Confirm to discard composed post content." + "title": "Dileu Drafft", + "message": "Cadarnhau i ddileu post drafft." }, "publish_post_failure": { - "title": "Publish Failure", - "message": "Failed to publish the post.\nPlease check your internet connection.", + "title": "Gwall Cyhoeddi", + "message": "Gwall wrth gyhoeddi'r tŵt.\nGwiriwch eich cysylltiad i'r rhyngrhwyd.", "attachments_message": { - "video_attach_with_photo": "Cannot attach a video to a post that already contains images.", - "more_than_one_video": "Cannot attach more than one video." + "video_attach_with_photo": "Ni ellir ychwanegu fideo at bost sy'n cynnwys lluniau yn barod.", + "more_than_one_video": "Ni ellir ychwanegu mwy nag un fideo." } }, "edit_profile_failure": { - "title": "Edit Profile Error", - "message": "Cannot edit profile. Please try again." + "title": "Gwall Golygu Proffil", + "message": "Ni ellir golygu proffil. Ceisiwch eto." }, "sign_out": { - "title": "Sign Out", - "message": "Are you sure you want to sign out?", - "confirm": "Sign Out" + "title": "Allgofnodi", + "message": "Ydych chi wir eisiau allgofnodi?", + "confirm": "Allgofnodi" }, "block_domain": { - "title": "Are you really, really sure you want to block the entire %s? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain and any of your followers from that domain will be removed.", - "block_entire_domain": "Block Domain" + "title": "Ydych chi wir, wir eisiau blocio'r holl %s? Fel arfer, mae blocio neu anwybyddu pobl penodol yn broses mwy effeithiol. Ni fyddwch yn gweld cynnwys o'r parth hwnnw a bydd eich dilynwyr o'r parth hwnnw yn cael eu ddileu.", + "block_entire_domain": "Blocio parth" }, "save_photo_failure": { - "title": "Save Photo Failure", - "message": "Please enable the photo library access permission to save the photo." + "title": "Gwall Cadw Llun", + "message": "Rhowch caniatád mynediad i'r Photo Library i gadw'r llun." }, "delete_post": { - "title": "Delete Post", - "message": "Are you sure you want to delete this post?" + "title": "Dileu Tŵt", + "message": "Ydych chi wir eisiau dileu'r post hwn?" }, "clean_cache": { - "title": "Clean Cache", - "message": "Successfully cleaned %s cache." + "title": "Clirio storfa", + "message": "Cliriwyd storfa %s yn llwyddiannus." + }, + "translation_failed": { + "title": "Nodyn", + "message": "Methwyd cyfieithu. Efallai nad yw'r gweinyddwr wedi galluogi cyfieithiadau ar y gweinydd hwn neu mae'r gweinydd hwn yn rhedeg fersiwn hŷn o Mastodon lle nad yw cyfieithiadau ar gael eto.", + "button": "Iawn" } }, "controls": { "actions": { - "back": "Back", - "next": "Next", - "previous": "Previous", - "open": "Open", - "add": "Add", - "remove": "Remove", - "edit": "Edit", - "save": "Save", - "ok": "OK", - "done": "Done", - "confirm": "Confirm", - "continue": "Continue", - "compose": "Compose", - "cancel": "Cancel", - "discard": "Discard", - "try_again": "Try Again", - "take_photo": "Take Photo", - "save_photo": "Save Photo", - "copy_photo": "Copy Photo", - "sign_in": "Sign In", - "sign_up": "Sign Up", - "see_more": "See More", - "preview": "Preview", - "share": "Share", - "share_user": "Share %s", - "share_post": "Share Post", - "open_in_safari": "Open in Safari", - "open_in_browser": "Open in Browser", - "find_people": "Find people to follow", - "manually_search": "Manually search instead", - "skip": "Skip", - "reply": "Reply", - "report_user": "Report %s", - "block_domain": "Block %s", - "unblock_domain": "Unblock %s", - "settings": "Settings", - "delete": "Delete" + "back": "Nôl", + "next": "Ymlaen", + "previous": "Blaenorol", + "open": "Agor", + "add": "Ychwanegu", + "remove": "Dileu", + "edit": "Golygu", + "save": "Cadw", + "ok": "Iawn", + "done": "Iawn", + "confirm": "Cadarnhau", + "continue": "Parhau", + "compose": "Creu", + "cancel": "Canslo", + "discard": "Diddymu", + "try_again": "Ceisio eto", + "take_photo": "Tynnu Llun", + "save_photo": "Cadw Llun", + "copy_photo": "Copïo Llun", + "sign_in": "Mewngofnodi", + "sign_up": "Creu cyfrif", + "see_more": "Gweld Mwy", + "preview": "Rhagolwg", + "copy": "Copïo", + "share": "Rhannu", + "share_user": "Rhannu %s", + "share_post": "Rhannu Tŵt", + "open_in_safari": "Agor yn Safari", + "open_in_browser": "Agor yn y Porwr", + "find_people": "Dilyn pobl newydd", + "manually_search": "Chwilio yn fanwl", + "skip": "Anwybyddu", + "reply": "Ymateb", + "report_user": "Riportio %s", + "block_domain": "Blocio %s", + "unblock_domain": "Dadflocio %s", + "settings": "Gosodiadau", + "delete": "Dileu", + "translate_post": { + "title": "Cyfieithu o %s", + "unknown_language": "Anhysbys" + } }, "tabs": { - "home": "Home", - "search": "Search", - "notification": "Notification", - "profile": "Profile" + "home": "Hafan", + "search_and_explore": "Chwilio ac Archwilio", + "notifications": "Hysbysiadau", + "profile": "Proffil" }, "keyboard": { "common": { - "switch_to_tab": "Switch to %s", - "compose_new_post": "Compose New Post", - "show_favorites": "Show Favorites", - "open_settings": "Open Settings" + "switch_to_tab": "Newid i %s", + "compose_new_post": "Creu Tŵt Newydd", + "show_favorites": "Dangos Ffefrynnau", + "open_settings": "Agor Gosodiadau" }, "timeline": { - "previous_status": "Previous Post", - "next_status": "Next Post", - "open_status": "Open Post", - "open_author_profile": "Open Author's Profile", - "open_reblogger_profile": "Open Reblogger's Profile", - "reply_status": "Reply to Post", - "toggle_reblog": "Toggle Reblog on Post", - "toggle_favorite": "Toggle Favorite on Post", - "toggle_content_warning": "Toggle Content Warning", - "preview_image": "Preview Image" + "previous_status": "Tŵt Blaenorol", + "next_status": "Tŵt Nesaf", + "open_status": "Agor Tŵt", + "open_author_profile": "Agor Proffil yr Awdur", + "open_reblogger_profile": "Agor Proffil yr Ailflogwr", + "reply_status": "Ymateb i Tŵt", + "toggle_reblog": "Toglo Ailfloggio ar Tŵt", + "toggle_favorite": "Toglo Ffefrynnu ar Tŵt", + "toggle_content_warning": "Toglo Rhybudd Cynnwys", + "preview_image": "Rhagolygu'r Llun" }, "segmented_control": { - "previous_section": "Previous Section", - "next_section": "Next Section" + "previous_section": "Adran Flaenorol", + "next_section": "Adran Nesaf" } }, "status": { - "user_reblogged": "%s reblogged", - "user_replied_to": "Replied to %s", - "show_post": "Show Post", - "show_user_profile": "Show user profile", - "content_warning": "Content Warning", - "sensitive_content": "Sensitive Content", - "media_content_warning": "Tap anywhere to reveal", - "tap_to_reveal": "Tap to reveal", + "user_reblogged": "%s wedi ailflogio", + "user_replied_to": "Wedi ymateb i %s", + "show_post": "Dangos Tŵt", + "show_user_profile": "Dangos proffil y defnyddiwr", + "content_warning": "Rhybudd Cynnwys", + "sensitive_content": "Cynnwys Sensitif", + "media_content_warning": "Tapiwch unrhywle i weld", + "tap_to_reveal": "Tapiwch i weld", + "load_embed": "Llwytho Planiad", + "link_via_user": "%s trwy %s", "poll": { - "vote": "Vote", - "closed": "Closed" + "vote": "Pleidleisio", + "closed": "Wedi cau" }, "meta_entity": { - "url": "Link: %s", - "hashtag": "Hashtag: %s", - "mention": "Show Profile: %s", - "email": "Email address: %s" + "url": "Dolen: %s", + "hashtag": "Hashnod: %s", + "mention": "Dangos Proffil: %s", + "email": "Cyfeiriad e-bost: %s" }, "actions": { - "reply": "Reply", + "reply": "Ymateb", "reblog": "Hybwch", - "unreblog": "Undo reblog", - "favorite": "Favorite", - "unfavorite": "Unfavorite", - "menu": "Menu", - "hide": "Hide", - "show_image": "Show image", - "show_gif": "Show GIF", - "show_video_player": "Show video player", - "tap_then_hold_to_show_menu": "Tap then hold to show menu" + "unreblog": "Dadwneud ailfloggiad", + "favorite": "Ffefrynnu", + "unfavorite": "Dad-ffefrynnu", + "menu": "Dewislen", + "hide": "Cuddio", + "show_image": "Dangos llun", + "show_gif": "Dangos GIF", + "show_video_player": "Dangos chwaraewr fideo", + "share_link_in_post": "Rhannu'r Ddolen yn y Postiad", + "tap_then_hold_to_show_menu": "Tapiwch a gwasgu i gael y ddewislen" }, "tag": { "url": "URL", - "mention": "Mention", - "link": "Link", - "hashtag": "Hashtag", - "email": "Email", + "mention": "Sôn", + "link": "Dolen", + "hashtag": "Hashnod", + "email": "E-bost", "emoji": "Emoji" }, "visibility": { - "unlisted": "Everyone can see this post but not display in the public timeline.", - "private": "Only their followers can see this post.", - "private_from_me": "Only my followers can see this post.", - "direct": "Only mentioned user can see this post." + "unlisted": "Gall pawb weld y post hwn ond nid yn y ffrwd cyhoeddus.", + "private": "Dim ond eu dilynwyr nhw sy'n gallu gweld y post hwn.", + "private_from_me": "Dim ond fy nilynwyr ni sy'n gallu gweld y post hwn.", + "direct": "Dim ond y ddefnyddiwr â soniwyd sy'n gallu gweld y post hwn." + }, + "translation": { + "translated_from": "Cyfieithwyd o %s gan ddefnyddio %s", + "unknown_language": "Anhysbys", + "unknown_provider": "Anhysbys", + "show_original": "Dangos y Gwreiddiol" } }, "friendship": { - "follow": "Follow", - "following": "Following", - "request": "Request", - "pending": "Pending", - "block": "Block", - "block_user": "Block %s", - "block_domain": "Block %s", - "unblock": "Unblock", - "unblock_user": "Unblock %s", - "blocked": "Blocked", - "mute": "Mute", - "mute_user": "Mute %s", - "unmute": "Unmute", - "unmute_user": "Unmute %s", - "muted": "Muted", - "edit_info": "Edit Info", - "show_reblogs": "Show Reblogs", - "hide_reblogs": "Hide Reblogs" + "follow": "Dilyn", + "following": "Yn dilyn", + "request": "Gwneud cais", + "pending": "Yn aros", + "block": "Blocio", + "block_user": "Blocio %s", + "block_domain": "Blocio %s", + "unblock": "Dadflocio", + "unblock_user": "Dadflocio %s", + "blocked": "Wedi blocio", + "mute": "Anwybyddu", + "mute_user": "Anwybyddu %s", + "unmute": "Dad-anwybyddu", + "unmute_user": "Dad-anwybyddu %s", + "muted": "Wedi anwybyddu", + "edit_info": "Golygu", + "show_reblogs": "Dangos Ailfloggiau", + "hide_reblogs": "Cuddio Ailfloggiau" }, "timeline": { - "filtered": "Filtered", + "filtered": "Wedi'i hidlo", "timestamp": { - "now": "Now" + "now": "Nawr" }, "loader": { - "load_missing_posts": "Load missing posts", - "loading_missing_posts": "Loading missing posts...", - "show_more_replies": "Show more replies" + "load_missing_posts": "Llwytho postau coll", + "loading_missing_posts": "Wrthi'n llwytho postau coll...", + "show_more_replies": "Dangos mwy o ymatebion" }, "header": { - "no_status_found": "No Post Found", - "blocking_warning": "You can’t view this user's profile\nuntil you unblock them.\nYour profile looks like this to them.", - "user_blocking_warning": "You can’t view %s’s profile\nuntil you unblock them.\nYour profile looks like this to them.", - "blocked_warning": "You can’t view this user’s profile\nuntil they unblock you.", - "user_blocked_warning": "You can’t view %s’s profile\nuntil they unblock you.", - "suspended_warning": "This user has been suspended.", - "user_suspended_warning": "%s’s account has been suspended." + "no_status_found": "Ni Chanfuwyd Post", + "blocking_warning": "Ni allwch gweld proffil y ddefnyddiwr hwn\nos ydych wedi'u blocio.\nMae'ch proffil chi yn edrych fel hyn iddynt.", + "user_blocking_warning": "Ni allwch gweld proffil %s\nos ydych wedi'u blocio.\nMae'ch proffil chi yn edrych fel hyn iddynt.", + "blocked_warning": "Ni allwch gweld proffil y ddefnyddiwr hwn\ngan eu bod wedi eich blocio chi.", + "user_blocked_warning": "Ni allwch gweld proffil %s\ngan eu bod wedi eich blocio chi.", + "suspended_warning": "Mae'r defnyddiwr hwn wedi derbyn gwaharddiad.", + "user_suspended_warning": "Mae cyfrif %s wedi derbyn gwaharddiad." } } } }, "scene": { "welcome": { - "slogan": "Social networking\nback in your hands.", - "get_started": "Get Started", - "log_in": "Log In" + "slogan": "Rhwydweithio cymdeithasol, \nyn eich dwylo chi.", + "get_started": "Cychwyn Arni", + "log_in": "Mewngofnodi" + }, + "login": { + "title": "Croeso nôl", + "subtitle": "Mewngofnodi ar y gweinydd y rydych chi wedi creu cyfrif arno.", + "server_search_field": { + "placeholder": "Mewnosod URL neu chwilio am eich gweinydd" + } }, "server_picker": { - "title": "Mastodon is made of users in different servers.", - "subtitle": "Pick a server based on your interests, region, or a general purpose one.", - "subtitle_extend": "Pick a server based on your interests, region, or a general purpose one. Each server is operated by an entirely independent organization or individual.", + "title": "Mae Mastodon yn cynnwys defnyddwyr o weinyddion gwahanol.", + "subtitle": "Dewiswch gweinydd yn ôl eich lleoliad, diddordebau, neu defnydd cyffredin. Gallwch siarad gydag unrhywun ar Mastodon, yn annibynnol o weinyddion chi.", "button": { "category": { - "all": "All", - "all_accessiblity_description": "Category: All", - "academia": "academia", - "activism": "activism", - "food": "food", + "all": "Popeth", + "all_accessiblity_description": "Categori: Popeth", + "academia": "academaidd", + "activism": "gweithgarwch", + "food": "bwyd", "furry": "furry", - "games": "games", - "general": "general", - "journalism": "journalism", - "lgbt": "lgbt", - "regional": "regional", - "art": "art", - "music": "music", - "tech": "tech" + "games": "gemau", + "general": "cyffredinol", + "journalism": "newyddiaduraeth", + "lgbt": "LHDT+", + "regional": "rhanbarthol", + "art": "celf", + "music": "cerddoriaeth", + "tech": "technoleg" }, - "see_less": "See Less", - "see_more": "See More" + "see_less": "Gweld Llai", + "see_more": "Gweld Mwy" }, "label": { - "language": "LANGUAGE", - "users": "USERS", - "category": "CATEGORY" + "language": "IAITH", + "users": "DEFNYDDWYR", + "category": "CATEGORI" }, "input": { - "placeholder": "Search servers", - "search_servers_or_enter_url": "Search servers or enter URL" + "search_servers_or_enter_url": "Chwilio cymunedau neu fewnosod URL" }, "empty_state": { - "finding_servers": "Finding available servers...", - "bad_network": "Something went wrong while loading the data. Check your internet connection.", - "no_results": "No results" + "finding_servers": "Wrthi'n chwilio am weinyddion sydd ar gael...", + "bad_network": "Digwyddodd gwall wrth lwytho'r data. Gwiriwch eich cyswllt â'r rhyngrwyd.", + "no_results": "Dim canlyniadau" } }, "register": { - "title": "Let’s get you set up on %s", - "lets_get_you_set_up_on_domain": "Let’s get you set up on %s", + "title": "Rhoi popeth yn ei le ar %s", + "lets_get_you_set_up_on_domain": "Rhoi popeth yn ei le ar %s", "input": { "avatar": { - "delete": "Delete" + "delete": "Dileu" }, "username": { - "placeholder": "username", - "duplicate_prompt": "This username is taken." + "placeholder": "enw defnyddiwr", + "duplicate_prompt": "Defnyddir yr enw hwn yn barod." }, "display_name": { - "placeholder": "display name" + "placeholder": "enw arddangos" }, "email": { - "placeholder": "email" + "placeholder": "e-bost" }, "password": { - "placeholder": "password", - "require": "Your password needs at least:", - "character_limit": "8 characters", + "placeholder": "cyfrinair", + "require": "Mae angen i'ch cyfrinair gael o leiaf:", + "character_limit": "8 nod", "accessibility": { - "checked": "checked", - "unchecked": "unchecked" + "checked": "ticiwyd", + "unchecked": "na thiciwyd" }, - "hint": "Your password needs at least eight characters" + "hint": "Mae angen o leiaf wyth nod yn eich cyfrinair" }, "invite": { - "registration_user_invite_request": "Why do you want to join?" + "registration_user_invite_request": "Pam ydych chi eisiau ymuno?" } }, "error": { "item": { - "username": "Username", - "email": "Email", - "password": "Password", - "agreement": "Agreement", - "locale": "Locale", - "reason": "Reason" + "username": "Enw defnyddiwr", + "email": "E-bost", + "password": "Cyfrinair", + "agreement": "Cytundeb", + "locale": "Lleoliad", + "reason": "Rheswm" }, "reason": { - "blocked": "%s contains a disallowed email provider", - "unreachable": "%s does not seem to exist", - "taken": "%s is already in use", - "reserved": "%s is a reserved keyword", - "accepted": "%s must be accepted", - "blank": "%s is required", - "invalid": "%s is invalid", - "too_long": "%s is too long", - "too_short": "%s is too short", - "inclusion": "%s is not a supported value" + "blocked": "Mae %s yn defnyddio darparwr e-bost nad yw'n cael ei ganiatáu", + "unreachable": "Nid yw %s yn bodoli", + "taken": "Defnyddir %s yn barod", + "reserved": "Mae %s yn air allweddol a chadwyd", + "accepted": "Mae angen cytuno â %s", + "blank": "Mae angen cael %s", + "invalid": "Mae %s yn annilys", + "too_long": "Mae %s yn rhy hir", + "too_short": "Mae %s yn rhy fyr", + "inclusion": "Nid yw %s yn gwerth a chefnogir" }, "special": { - "username_invalid": "Username must only contain alphanumeric characters and underscores", - "username_too_long": "Username is too long (can’t be longer than 30 characters)", - "email_invalid": "This is not a valid email address", - "password_too_short": "Password is too short (must be at least 8 characters)" + "username_invalid": "Dylai enwau ddefnyddiwr gynnwys nodau alffaniwmerig a thanlinellau yn unig", + "username_too_long": "Enw defnyddiwr yn rhy hir (na all fod yn fwy na 30 nodyn)", + "email_invalid": "Nid yw'n cyfeiriad e-bost dilys", + "password_too_short": "Cyfrinair yn rhy fyr (Rhaid fod o leiaf 8 nodyn)" } } }, "server_rules": { - "title": "Some ground rules.", - "subtitle": "These are set and enforced by the %s moderators.", - "prompt": "By continuing, you’re subject to the terms of service and privacy policy for %s.", - "terms_of_service": "terms of service", - "privacy_policy": "privacy policy", + "title": "Rhai rheolau sylfaenol.", + "subtitle": "Mae'r rhain yn cael eu gosod a'u gorfodi gan gymedrolwyr %s.", + "prompt": "Wrth barhau, rydych yn cytuno â'r Telerau Gwasanaeth a Pholisi Preifatrwydd %s.", + "terms_of_service": "telerau gwasanaeth", + "privacy_policy": "polisi preifatrwydd", "button": { - "confirm": "I Agree" + "confirm": "Rwy'n cytuno" } }, "confirm_email": { - "title": "One last thing.", - "subtitle": "Tap the link we emailed to you to verify your account.", - "tap_the_link_we_emailed_to_you_to_verify_your_account": "Tap the link we emailed to you to verify your account", + "title": "Un peth olaf.", + "subtitle": "Tapiwch ar y ddolen yn eich e-bost i ddilysu eich cyfrif.", + "tap_the_link_we_emailed_to_you_to_verify_your_account": "Tapiwch ar y ddolen yn eich e-bost i ddilysu eich cyfrif", "button": { - "open_email_app": "Open Email App", - "resend": "Resend" + "open_email_app": "Agor ap e-byst", + "resend": "Ailanfon" }, "dont_receive_email": { - "title": "Check your email", - "description": "Check if your email address is correct as well as your junk folder if you haven’t.", - "resend_email": "Resend Email" + "title": "Gwiriwch eich e-byst", + "description": "Gwiriwch a yw eich cyfeiriad e-bost yn gywir a hefyd eich ffolder 'junk' os nad ydych wedi ei gwneud.", + "resend_email": "Ailanfon E-bost" }, "open_email_app": { - "title": "Check your inbox.", - "description": "We just sent you an email. Check your junk folder if you haven’t.", + "title": "Gwiriwch eich blwch derbyn.", + "description": "Rydym newydd anfon e-bost atoch chi. Gwiriwch eich ffolder 'junk' os nad ydych wedi ei gwneud.", "mail": "Mail", - "open_email_client": "Open Email Client" + "open_email_client": "Agor Cleient E-byst" } }, "home_timeline": { - "title": "Home", + "title": "Hafan", "navigation_bar_state": { - "offline": "Offline", - "new_posts": "See new posts", - "published": "Published!", - "Publishing": "Publishing post...", + "offline": "All-lein", + "new_posts": "Gweld tŵts newydd", + "published": "Wedi cyhoeddi!", + "Publishing": "Wrthi'n cyhoeddi...", "accessibility": { - "logo_label": "Logo Button", - "logo_hint": "Tap to scroll to top and tap again to previous location" + "logo_label": "Botwm Logo", + "logo_hint": "Tapiwch i sgrolio i'r frig, a thapiwch eto er mwyn mynd i'r lleoliad blaenorol" } } }, "suggestion_account": { - "title": "Find People to Follow", - "follow_explain": "When you follow someone, you’ll see their posts in your home feed." + "title": "Chwilio a Dilyn Pobl", + "follow_explain": "Ar ôl ichi ddilyn rhywun, byddwch yn gweld eu postiadau yn eich ffrwd hafan." }, "compose": { "title": { - "new_post": "New Post", - "new_reply": "New Reply" + "new_post": "Tŵt Newydd", + "new_reply": "Ymateb Newydd" }, "media_selection": { - "camera": "Take Photo", + "camera": "Tynnu Llun", "photo_library": "Photo Library", - "browse": "Browse" + "browse": "Pori" }, - "content_input_placeholder": "Type or paste what’s on your mind", - "compose_action": "Publish", - "replying_to_user": "replying to %s", + "content_input_placeholder": "Teipiwch neu gludo'r hyn sydd ar eich meddwl", + "compose_action": "Cyhoeddi", + "replying_to_user": "yn ymateb i %s", "attachment": { - "photo": "photo", - "video": "video", - "attachment_broken": "This %s is broken and can’t be\nuploaded to Mastodon.", - "description_photo": "Describe the photo for the visually-impaired...", - "description_video": "Describe the video for the visually-impaired...", - "load_failed": "Load Failed", - "upload_failed": "Upload Failed", - "can_not_recognize_this_media_attachment": "Can not regonize this media attachment", - "attachment_too_large": "Attachment too large" + "photo": "llun", + "video": "fideo", + "attachment_broken": "Mae %s wedi torri a ni ellir\nuwchlwytho hwn i Mastodon.", + "description_photo": "Disgrifio i'r rheini â nam ar eu golwg...", + "description_video": "Disgrifio i'r rheini â nam ar eu golwg...", + "load_failed": "Methwyd Llwytho", + "upload_failed": "Methwyd Uwchlwytho", + "can_not_recognize_this_media_attachment": "Ni ellir dilysu'r atodiad cyfrwng", + "attachment_too_large": "Mae'r atodiad yn rhy fawr", + "compressing_state": "Wrthi'n cywasgu...", + "server_processing_state": "Mae'r gweinydd yn prosesu..." }, "poll": { - "duration_time": "Duration: %s", - "thirty_minutes": "30 minutes", - "one_hour": "1 Hour", - "six_hours": "6 Hours", - "one_day": "1 Day", - "three_days": "3 Days", - "seven_days": "7 Days", - "option_number": "Option %ld" + "duration_time": "Hyd: %s", + "thirty_minutes": "30 munud", + "one_hour": "1 awr", + "six_hours": "6 awr", + "one_day": "1 diwrnod", + "three_days": "3 diwrnod", + "seven_days": "7 diwrnod", + "option_number": "Opsiwn %ld", + "the_poll_is_invalid": "Nid yw'r pôl yn ddilys", + "the_poll_has_empty_option": "Mae gan y pôl opsiwn gwag" }, "content_warning": { - "placeholder": "Write an accurate warning here..." + "placeholder": "Ysgrifenwch rybudd manwl yma..." }, "visibility": { - "public": "Public", - "unlisted": "Unlisted", - "private": "Followers only", - "direct": "Only people I mention" + "public": "Cyhoeddus", + "unlisted": "Heb ei rhestru", + "private": "Dilynwyr yn unig", + "direct": "Dim ond pobl rwy'n eu crybwyll" }, "auto_complete": { - "space_to_add": "Space to add" + "space_to_add": "Lle i ychwanegu" }, "accessibility": { - "append_attachment": "Add Attachment", - "append_poll": "Add Poll", - "remove_poll": "Remove Poll", - "custom_emoji_picker": "Custom Emoji Picker", - "enable_content_warning": "Enable Content Warning", - "disable_content_warning": "Disable Content Warning", - "post_visibility_menu": "Post Visibility Menu" + "append_attachment": "Ychwanegu Atodiad", + "append_poll": "Ychwanegu Pôl", + "remove_poll": "Dileu Pôl", + "custom_emoji_picker": "Dewislen Emoji Bersonol", + "enable_content_warning": "Galluogi Rhybudd Cynnwys", + "disable_content_warning": "Analluogi Rhybudd Cynnwys", + "post_visibility_menu": "Dewislen Breifatrwydd Post", + "post_options": "Opsiynau Tŵt", + "posting_as": "Cyhoeddi fel %s" }, "keyboard": { - "discard_post": "Discard Post", - "publish_post": "Publish Post", - "toggle_poll": "Toggle Poll", - "toggle_content_warning": "Toggle Content Warning", - "append_attachment_entry": "Add Attachment - %s", - "select_visibility_entry": "Select Visibility - %s" + "discard_post": "Dileu Tŵt", + "publish_post": "Cyhoeddi Tŵt", + "toggle_poll": "Toglo Pôl", + "toggle_content_warning": "Toglo Rhybudd Cynnwys", + "append_attachment_entry": "Ychwanegu Atodiad - %s", + "select_visibility_entry": "Dewis Preifatrwydd - %s" } }, "profile": { "header": { - "follows_you": "Follows You" + "follows_you": "Yn eich dilyn" }, "dashboard": { - "posts": "postiadau", - "following": "following", - "followers": "followers" + "my_posts": "postiadu", + "my_following": "yn dilyn", + "my_followers": "dilynwyr", + "other_posts": "postiadu", + "other_following": "yn dilyn", + "other_followers": "dilynwyr" }, "fields": { - "add_row": "Add Row", + "joined": "Ymunwyd", + "add_row": "Ychwanegu Rhes", "placeholder": { "label": "Label", - "content": "Content" + "content": "Cynnwys" + }, + "verified": { + "short": "Wedi dilysu ar %s", + "long": "Gwiriwyd perchnogaeth y ddolen yma ar %s" } }, "segmented_control": { "posts": "Postiadau", - "replies": "Replies", + "replies": "Ymatebion", "posts_and_replies": "Postiadau ac Atebion", - "media": "Media", - "about": "About" + "media": "Cyfryngau", + "about": "Ynghylch" }, "relationship_action_alert": { "confirm_mute_user": { - "title": "Mute Account", - "message": "Confirm to mute %s" + "title": "Anwybyddu Cyfrif", + "message": "Parhau i anwybyddu %s" }, "confirm_unmute_user": { - "title": "Unmute Account", - "message": "Confirm to unmute %s" + "title": "Dad-anwybyddu Cyfrif", + "message": "Parhau i ddad-anwybyddu %s" }, "confirm_block_user": { - "title": "Block Account", - "message": "Confirm to block %s" + "title": "Blocio Cyfrif", + "message": "Cadarnhau i flocio %s" }, "confirm_unblock_user": { - "title": "Unblock Account", - "message": "Confirm to unblock %s" + "title": "Dadflocio Cyfrif", + "message": "Parhau i ddadflocio %s" }, "confirm_show_reblogs": { - "title": "Show Reblogs", - "message": "Confirm to show reblogs" + "title": "Dangos Ailfloggiau", + "message": "Cadarnhau i ddangos hybiau" }, "confirm_hide_reblogs": { - "title": "Hide Reblogs", - "message": "Confirm to hide reblogs" + "title": "Cuddio Ailflogiau", + "message": "Cadarnhau i guddio ailflogiau" } }, "accessibility": { - "show_avatar_image": "Show avatar image", - "edit_avatar_image": "Edit avatar image", - "show_banner_image": "Show banner image", - "double_tap_to_open_the_list": "Double tap to open the list" + "show_avatar_image": "Dangos afatar", + "edit_avatar_image": "Golygu afatar", + "show_banner_image": "Dangos baner", + "double_tap_to_open_the_list": "Tapiwch dwywaith i agor y restr" } }, "follower": { - "title": "follower", - "footer": "Followers from other servers are not displayed." + "title": "dilynwr", + "footer": "Ni ddangosir dilynwyr o weinyddion eraill." }, "following": { - "title": "following", - "footer": "Follows from other servers are not displayed." + "title": "yn dilyn", + "footer": "Ni ddangosir dilynion o weinyddion eraill." }, "familiarFollowers": { - "title": "Followers you familiar", - "followed_by_names": "Followed by %s" + "title": "Dilynwyr sy'n gyfarwydd ichi", + "followed_by_names": "Dilynwyd gan %s" }, "favorited_by": { - "title": "Favorited By" + "title": "Ffefrynnwyd gan" }, "reblogged_by": { - "title": "Reblogged By" + "title": "Wedi'i hybu gan" }, "search": { - "title": "Search", + "title": "Chwilio", "search_bar": { - "placeholder": "Search hashtags and users", - "cancel": "Cancel" + "placeholder": "Chwilio hashnodau a defnyddwyr", + "cancel": "Canslo" }, "recommend": { - "button_text": "See All", + "button_text": "Gweld Popeth", "hash_tag": { - "title": "Trending on Mastodon", - "description": "Hashtags that are getting quite a bit of attention", - "people_talking": "%s people are talking" + "title": "Pynciau Llosg ar Mastodon", + "description": "Dyma'r hashnodau sy'n derbyn tipyn o sylw", + "people_talking": "%s person yn trafod" }, "accounts": { - "title": "Accounts you might like", - "description": "You may like to follow these accounts", - "follow": "Follow" + "title": "Cyfrifon ar eich cyfer", + "description": "Cyfrifon sydd efallai o ddiddordeb ichi", + "follow": "Dilyn" } }, "searching": { "segment": { - "all": "All", - "people": "People", - "hashtags": "Hashtags", + "all": "Popeth", + "people": "Pobl", + "hashtags": "Hashnodau", "posts": "Postiadau" }, "empty_state": { - "no_results": "No results" + "no_results": "Dim canlyniadau" }, - "recent_search": "Recent searches", - "clear": "Clear" + "recent_search": "Chwiliadau diweddar", + "clear": "Clirio" } }, "discovery": { "tabs": { "posts": "Postiadau", - "hashtags": "Hashtags", - "news": "News", - "community": "Community", - "for_you": "For You" + "hashtags": "Hashnodau", + "news": "Newyddion", + "community": "Cymuned", + "for_you": "I Ti" }, - "intro": "These are the posts gaining traction in your corner of Mastodon." + "intro": "Dyma'r postiadau sy'n denu tipyn o sylw yn eich cŵr Mastodon." }, "favorite": { - "title": "Your Favorites" + "title": "Eich Ffefrynnau" }, "notification": { "title": { - "Everything": "Everything", - "Mentions": "Mentions" + "Everything": "Popeth", + "Mentions": "Crybwylliadau" }, "notification_description": { - "followed_you": "followed you", - "favorited_your_post": "favorited your post", - "reblogged_your_post": "reblogged your post", - "mentioned_you": "mentioned you", - "request_to_follow_you": "request to follow you", - "poll_has_ended": "poll has ended" + "followed_you": "wedi eich dilyn", + "favorited_your_post": "wedi ffefrynnu eich tŵt", + "reblogged_your_post": "wedi ailfloggio'ch tŵt", + "mentioned_you": "wedi sôn amdanoch", + "request_to_follow_you": "wedi gwneud cais i'ch dilyn", + "poll_has_ended": "pôl wedi dod i ben" }, "keyobard": { - "show_everything": "Show Everything", - "show_mentions": "Show Mentions" + "show_everything": "Dangos Popeth", + "show_mentions": "Dangos Crybwylliadau" }, "follow_request": { - "accept": "Accept", - "accepted": "Accepted", - "reject": "reject", - "rejected": "Rejected" + "accept": "Derbyn", + "accepted": "Wedi Derbyn", + "reject": "gwrthod", + "rejected": "Gwrthodwyd" } }, "thread": { "back_title": "Post", - "title": "Post from %s" + "title": "Tŵt gan %s" }, "settings": { - "title": "Settings", + "title": "Gosodiadau", "section": { "appearance": { - "title": "Appearance", - "automatic": "Automatic", - "light": "Always Light", - "dark": "Always Dark" + "title": "Gwedd", + "automatic": "Awtomatig", + "light": "Golau", + "dark": "Tywyll" }, "look_and_feel": { - "title": "Look and Feel", - "use_system": "Use System", - "really_dark": "Really Dark", - "sorta_dark": "Sorta Dark", - "light": "Light" + "title": "Gwedd", + "use_system": "Dull y System", + "really_dark": "Tywyll Iawn", + "sorta_dark": "Eitha' Tywyll", + "light": "Golau" }, "notifications": { - "title": "Notifications", - "favorites": "Favorites my post", - "follows": "Follows me", - "boosts": "Reblogs my post", - "mentions": "Mentions me", + "title": "Hysbysiadau", + "favorites": "Yn ffefrynnu fy mhost", + "follows": "Yn fy nilyn", + "boosts": "Yn hybu fy mhost", + "mentions": "Yn sôn amdana i", "trigger": { - "anyone": "anyone", - "follower": "a follower", - "follow": "anyone I follow", - "noone": "no one", - "title": "Notify me when" + "anyone": "unrhyw un", + "follower": "dilynwr", + "follow": "unrhyw un o'm dilynion", + "noone": "neb", + "title": "Rhowch wybod i mi pan" } }, "preference": { - "title": "Preferences", - "true_black_dark_mode": "True black dark mode", - "disable_avatar_animation": "Disable animated avatars", - "disable_emoji_animation": "Disable animated emojis", - "using_default_browser": "Use default browser to open links", - "open_links_in_mastodon": "Open links in Mastodon" + "title": "Dewisiadau", + "true_black_dark_mode": "Gwedd tywyll go iawn", + "disable_avatar_animation": "Analluogi rhithffurfiau wedi'u hanimeiddio", + "disable_emoji_animation": "Analluogi emoji wedi'u hanimeiddio", + "using_default_browser": "Defnyddio porwr rhagosodedig i agor dolenni", + "open_links_in_mastodon": "Agor dolenni ym Mastodon" }, "boring_zone": { - "title": "The Boring Zone", - "account_settings": "Account Settings", - "terms": "Terms of Service", - "privacy": "Privacy Policy" + "title": "Yr Ardal Ddiflas", + "account_settings": "Gosodiadau Cyfrif", + "terms": "Telerau Gwasanaeth", + "privacy": "Polisi Preifatrwydd" }, "spicy_zone": { - "title": "The Spicy Zone", - "clear": "Clear Media Cache", - "signout": "Sign Out" + "title": "Yr Ardal Sbeislyd", + "clear": "Clirio Storfa Cyfryngau", + "signout": "Allgofnodi" } }, "footer": { - "mastodon_description": "Mastodon is open source software. You can report issues on GitHub at %s (%s)" + "mastodon_description": "Mae Mastodon yn feddalwedd cod agored. Gallech roi gwybod am wallau ar GitHub: %s (%s)" }, "keyboard": { - "close_settings_window": "Close Settings Window" + "close_settings_window": "Cau Ffenest Gosodiadau" } }, "report": { - "title_report": "Report", - "title": "Report %s", - "step1": "Step 1 of 2", - "step2": "Step 2 of 2", - "content1": "Are there any other posts you’d like to add to the report?", - "content2": "Is there anything the moderators should know about this report?", - "report_sent_title": "Thanks for reporting, we’ll look into this.", - "send": "Send Report", - "skip_to_send": "Send without comment", - "text_placeholder": "Type or paste additional comments", - "reported": "REPORTED", + "title_report": "Riportio", + "title": "Riportio %s", + "step1": "Cam 1 o 2", + "step2": "Cam 2 o 2", + "content1": "Hoffech chi ychwanegu unrhyw postiadau eraill i'r adroddiad?", + "content2": "Oes unrhyw beth y dylai'r cymedrolwyr wybod yn yr adroddiad hwn?", + "report_sent_title": "Diolch am adrodd, byddwn yn ymchwilio i hyn.", + "send": "Anfon adroddiad", + "skip_to_send": "Anfon heb sylw", + "text_placeholder": "Teipiwch neu gludwch sylwadau ychwanegol", + "reported": "WEDI RIPORTIO", "step_one": { - "step_1_of_4": "Step 1 of 4", - "whats_wrong_with_this_post": "What's wrong with this post?", - "whats_wrong_with_this_account": "What's wrong with this account?", - "whats_wrong_with_this_username": "What's wrong with %s?", - "select_the_best_match": "Select the best match", - "i_dont_like_it": "I don’t like it", - "it_is_not_something_you_want_to_see": "It is not something you want to see", - "its_spam": "It’s spam", - "malicious_links_fake_engagement_or_repetetive_replies": "Malicious links, fake engagement, or repetetive replies", - "it_violates_server_rules": "It violates server rules", - "you_are_aware_that_it_breaks_specific_rules": "You are aware that it breaks specific rules", - "its_something_else": "It’s something else", - "the_issue_does_not_fit_into_other_categories": "The issue does not fit into other categories" + "step_1_of_4": "Cam 1 o 4", + "whats_wrong_with_this_post": "Beth sy'n bod gyda'r tŵt hwn?", + "whats_wrong_with_this_account": "Beth sy'n bod gyda'r cyfrif hwn?", + "whats_wrong_with_this_username": "Beth sy'n bod gyda %s?", + "select_the_best_match": "Dewiswch yr opsiwn gorau", + "i_dont_like_it": "Dydw i ddim yn ei hoffi", + "it_is_not_something_you_want_to_see": "Nid yw'n rhywbeth ydych chi eisiau ei weld", + "its_spam": "Mae'n sbam", + "malicious_links_fake_engagement_or_repetetive_replies": "Dolenni maleisus, ymgysylltu ffug, neu ymatebion ailadroddus", + "it_violates_server_rules": "Mae'n torri rheolau'r gweinydd", + "you_are_aware_that_it_breaks_specific_rules": "Rydych yn ymwybodol ei fod yn torri rheolau penodol", + "its_something_else": "Mae'n rhywbeth arall", + "the_issue_does_not_fit_into_other_categories": "Nid yw'r mater yn ffitio i gategorïau eraill" }, "step_two": { - "step_2_of_4": "Step 2 of 4", - "which_rules_are_being_violated": "Which rules are being violated?", - "select_all_that_apply": "Select all that apply", - "i_just_don’t_like_it": "I just don’t like it" + "step_2_of_4": "Cam 2 o 4", + "which_rules_are_being_violated": "Pa reolau sy'n cael eu torri?", + "select_all_that_apply": "Dewiswch bob un sy'n berthnasol", + "i_just_don’t_like_it": "Dydw i ddim yn ei hoffi" }, "step_three": { - "step_3_of_4": "Step 3 of 4", - "are_there_any_posts_that_back_up_this_report": "Are there any posts that back up this report?", - "select_all_that_apply": "Select all that apply" + "step_3_of_4": "Cam 3 o 4", + "are_there_any_posts_that_back_up_this_report": "Oes postiadau eraill sy'n cefnogi'r adroddiad hwn?", + "select_all_that_apply": "Dewiswch bob un sy'n berthnasol" }, "step_four": { - "step_4_of_4": "Step 4 of 4", - "is_there_anything_else_we_should_know": "Is there anything else we should know?" + "step_4_of_4": "Cam 4 o 4", + "is_there_anything_else_we_should_know": "Oes unrhyw beth arall y dylem ei wybod?" }, "step_final": { - "dont_want_to_see_this": "Don’t want to see this?", - "when_you_see_something_you_dont_like_on_mastodon_you_can_remove_the_person_from_your_experience.": "When you see something you don’t like on Mastodon, you can remove the person from your experience.", - "unfollow": "Unfollow", - "unfollowed": "Unfollowed", - "unfollow_user": "Unfollow %s", - "mute_user": "Mute %s", - "you_wont_see_their_posts_or_reblogs_in_your_home_feed_they_wont_know_they_ve_been_muted": "You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted.", - "block_user": "Block %s", - "they_will_no_longer_be_able_to_follow_or_see_your_posts_but_they_can_see_if_theyve_been_blocked": "They will no longer be able to follow or see your posts, but they can see if they’ve been blocked.", - "while_we_review_this_you_can_take_action_against_user": "While we review this, you can take action against %s" + "dont_want_to_see_this": "Ddim eisiau gweld hwn?", + "when_you_see_something_you_dont_like_on_mastodon_you_can_remove_the_person_from_your_experience.": "Pan welwch chi gynnwys nad ydych chi'n ei hoffi ar Mastodon, gallwch dynnu'r person o'ch profiad.", + "unfollow": "Dad-ddilyn", + "unfollowed": "Dad-ddilynwyd", + "unfollow_user": "Dad-ddilyn %s", + "mute_user": "Anwybyddu %s", + "you_wont_see_their_posts_or_reblogs_in_your_home_feed_they_wont_know_they_ve_been_muted": "Ni fyddwch yn gweld eu postiadau neu hybiau yn eich ffrwd hafan. Ni fyddant yn gwybod eich bod wedi eu hanwybyddu.", + "block_user": "Blocio %s", + "they_will_no_longer_be_able_to_follow_or_see_your_posts_but_they_can_see_if_theyve_been_blocked": "Ni fyddant yn gallu eich dilyn neu weld eich postiadau, ond maen nhw'n gallu gweld eich bod wedi'u blocio.", + "while_we_review_this_you_can_take_action_against_user": "Tra byddwn ni'n edrych ar hyn, gallwch gymryd camau yn erbyn %s" } }, "preview": { "keyboard": { - "close_preview": "Close Preview", - "show_next": "Show Next", - "show_previous": "Show Previous" + "close_preview": "Cau Rhagolwg", + "show_next": "Dangos Nesaf", + "show_previous": "Dangos Blaenorol" } }, "account_list": { - "tab_bar_hint": "Current selected profile: %s. Double tap then hold to show account switcher", - "dismiss_account_switcher": "Dismiss Account Switcher", - "add_account": "Add Account" + "tab_bar_hint": "Proffil cyfredol: %s. Tapiwch dwywaith a gwasgu i ddefnyddio'r newidiwr cyfrifon", + "dismiss_account_switcher": "Diddymu Newidiwr Cyfrifon", + "add_account": "Ychwanegu Cyfrif" }, "wizard": { - "new_in_mastodon": "New in Mastodon", - "multiple_account_switch_intro_description": "Switch between multiple accounts by holding the profile button.", - "accessibility_hint": "Double tap to dismiss this wizard" + "new_in_mastodon": "Newydd i Mastodon", + "multiple_account_switch_intro_description": "Newid rhwng cyfrifon gwahanol wrth wasgu'r botwm proffil.", + "accessibility_hint": "Tapiwch dwywaith i ddiddymu'r dewin hwn" }, "bookmark": { - "title": "Bookmarks" + "title": "Tudalnodau" + }, + "followed_tags": { + "title": "Tagiau a Ddilynwyd", + "header": { + "posts": "postiadau", + "participants": "cyfranwyr", + "posts_today": "postiadau heddiw" + }, + "actions": { + "follow": "Dilyn", + "unfollow": "Dad-ddilyn" + } } } } diff --git a/Localization/StringsConvertor/input/cy.lproj/ios-infoPlist.json b/Localization/StringsConvertor/input/cy.lproj/ios-infoPlist.json index ee0c5bfee..e2a58387a 100644 --- a/Localization/StringsConvertor/input/cy.lproj/ios-infoPlist.json +++ b/Localization/StringsConvertor/input/cy.lproj/ios-infoPlist.json @@ -1,6 +1,6 @@ { - "NSCameraUsageDescription": "Used to take photo for post status", - "NSPhotoLibraryAddUsageDescription": "Used to save photo into the Photo Library", + "NSCameraUsageDescription": "Defnyddir hwn i gymryd llun am bost", + "NSPhotoLibraryAddUsageDescription": "Defnyddir hwn i gadw llun yn eich Photo Library", "NewPostShortcutItemTitle": "Post Newydd", "SearchShortcutItemTitle": "Chwilio" } diff --git a/Localization/StringsConvertor/input/da.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/da.lproj/Localizable.stringsdict index bdcae6ac9..788eb95fc 100644 --- a/Localization/StringsConvertor/input/da.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/da.lproj/Localizable.stringsdict @@ -15,7 +15,7 @@ one 1 unread notification other - %ld unread notification + %ld unread notifications a11y.plural.count.input_limit_exceeds @@ -50,6 +50,22 @@ %ld characters + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ left + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 character + other + %ld characters + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/da.lproj/app.json b/Localization/StringsConvertor/input/da.lproj/app.json index a6a971860..4939d3afb 100644 --- a/Localization/StringsConvertor/input/da.lproj/app.json +++ b/Localization/StringsConvertor/input/da.lproj/app.json @@ -51,6 +51,11 @@ "clean_cache": { "title": "Clean Cache", "message": "Successfully cleaned %s cache." + }, + "translation_failed": { + "title": "Note", + "message": "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported.", + "button": "OK" } }, "controls": { @@ -74,10 +79,11 @@ "take_photo": "Take Photo", "save_photo": "Save Photo", "copy_photo": "Copy Photo", - "sign_in": "Sign In", - "sign_up": "Sign Up", + "sign_in": "Log in", + "sign_up": "Create account", "see_more": "See More", "preview": "Preview", + "copy": "Copy", "share": "Share", "share_user": "Share %s", "share_post": "Share Post", @@ -91,12 +97,16 @@ "block_domain": "Block %s", "unblock_domain": "Unblock %s", "settings": "Settings", - "delete": "Delete" + "delete": "Delete", + "translate_post": { + "title": "Translate from %s", + "unknown_language": "Unknown" + } }, "tabs": { "home": "Home", - "search": "Search", - "notification": "Notification", + "search_and_explore": "Search and Explore", + "notifications": "Notifications", "profile": "Profile" }, "keyboard": { @@ -132,6 +142,8 @@ "sensitive_content": "Sensitive Content", "media_content_warning": "Tap anywhere to reveal", "tap_to_reveal": "Tap to reveal", + "load_embed": "Load Embed", + "link_via_user": "%s via %s", "poll": { "vote": "Vote", "closed": "Closed" @@ -153,6 +165,7 @@ "show_image": "Show image", "show_gif": "Show GIF", "show_video_player": "Show video player", + "share_link_in_post": "Share Link in Post", "tap_then_hold_to_show_menu": "Tap then hold to show menu" }, "tag": { @@ -168,6 +181,12 @@ "private": "Only their followers can see this post.", "private_from_me": "Only my followers can see this post.", "direct": "Only mentioned user can see this post." + }, + "translation": { + "translated_from": "Translated from %s using %s", + "unknown_language": "Unknown", + "unknown_provider": "Unknown", + "show_original": "Shown Original" } }, "friendship": { @@ -218,10 +237,16 @@ "get_started": "Get Started", "log_in": "Log In" }, + "login": { + "title": "Welcome back", + "subtitle": "Log you in on the server you created your account on.", + "server_search_field": { + "placeholder": "Enter URL or search for your server" + } + }, "server_picker": { "title": "Mastodon is made of users in different servers.", - "subtitle": "Pick a server based on your interests, region, or a general purpose one.", - "subtitle_extend": "Pick a server based on your interests, region, or a general purpose one. Each server is operated by an entirely independent organization or individual.", + "subtitle": "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers.", "button": { "category": { "all": "All", @@ -248,8 +273,7 @@ "category": "CATEGORY" }, "input": { - "placeholder": "Search servers", - "search_servers_or_enter_url": "Search servers or enter URL" + "search_servers_or_enter_url": "Search communities or enter URL" }, "empty_state": { "finding_servers": "Finding available servers...", @@ -385,8 +409,10 @@ "description_video": "Describe the video for the visually-impaired...", "load_failed": "Load Failed", "upload_failed": "Upload Failed", - "can_not_recognize_this_media_attachment": "Can not regonize this media attachment", - "attachment_too_large": "Attachment too large" + "can_not_recognize_this_media_attachment": "Can not recognize this media attachment", + "attachment_too_large": "Attachment too large", + "compressing_state": "Compressing...", + "server_processing_state": "Server Processing..." }, "poll": { "duration_time": "Duration: %s", @@ -396,7 +422,9 @@ "one_day": "1 Day", "three_days": "3 Days", "seven_days": "7 Days", - "option_number": "Option %ld" + "option_number": "Option %ld", + "the_poll_is_invalid": "The poll is invalid", + "the_poll_has_empty_option": "The poll has empty option" }, "content_warning": { "placeholder": "Write an accurate warning here..." @@ -417,7 +445,9 @@ "custom_emoji_picker": "Custom Emoji Picker", "enable_content_warning": "Enable Content Warning", "disable_content_warning": "Disable Content Warning", - "post_visibility_menu": "Post Visibility Menu" + "post_visibility_menu": "Post Visibility Menu", + "post_options": "Post Options", + "posting_as": "Posting as %s" }, "keyboard": { "discard_post": "Discard Post", @@ -433,15 +463,23 @@ "follows_you": "Follows You" }, "dashboard": { - "posts": "posts", - "following": "following", - "followers": "followers" + "my_posts": "posts", + "my_following": "following", + "my_followers": "followers", + "other_posts": "posts", + "other_following": "following", + "other_followers": "followers" }, "fields": { + "joined": "Joined", "add_row": "Add Row", "placeholder": { "label": "Label", "content": "Content" + }, + "verified": { + "short": "Verified on %s", + "long": "Ownership of this link was checked on %s" } }, "segmented_control": { @@ -707,6 +745,18 @@ }, "bookmark": { "title": "Bookmarks" + }, + "followed_tags": { + "title": "Followed Tags", + "header": { + "posts": "posts", + "participants": "participants", + "posts_today": "posts today" + }, + "actions": { + "follow": "Follow", + "unfollow": "Unfollow" + } } } } diff --git a/Localization/StringsConvertor/input/de.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/de.lproj/Localizable.stringsdict index f60c6b0d7..9d07f80d1 100644 --- a/Localization/StringsConvertor/input/de.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/de.lproj/Localizable.stringsdict @@ -37,7 +37,23 @@ a11y.plural.count.input_limit_remains NSStringLocalizedFormatKey - Noch %#@character_count@ übrig + Noch %#@character_count@ Zeichen übrig + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 Zeichen + other + %ld Zeichen + + + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ übrig character_count NSStringFormatSpecTypeKey diff --git a/Localization/StringsConvertor/input/de.lproj/app.json b/Localization/StringsConvertor/input/de.lproj/app.json index 481f07a4a..8c28db94f 100644 --- a/Localization/StringsConvertor/input/de.lproj/app.json +++ b/Localization/StringsConvertor/input/de.lproj/app.json @@ -45,12 +45,17 @@ "message": "Bitte aktiviere den Zugriff auf die Fotobibliothek, um das Foto zu speichern." }, "delete_post": { - "title": "Bist du dir sicher, dass du diesen Beitrag löschen möchtest?", + "title": "Beiträge löschen", "message": "Bist du dir sicher, dass du diesen Beitrag löschen willst?" }, "clean_cache": { "title": "Zwischenspeicher leeren", "message": "%s erfolgreich aus dem Cache gelöscht." + }, + "translation_failed": { + "title": "Hinweis", + "message": "Übersetzung fehlgeschlagen. Möglicherweise hat der/die Administrator*in die Übersetzungen auf diesem Server nicht aktiviert oder dieser Server läuft mit einer älteren Version von Mastodon, in der Übersetzungen noch nicht unterstützt wurden.", + "button": "OK" } }, "controls": { @@ -66,7 +71,7 @@ "ok": "OK", "done": "Fertig", "confirm": "Bestätigen", - "continue": "Fortfahren", + "continue": "Weiter", "compose": "Neue Nachricht", "cancel": "Abbrechen", "discard": "Verwerfen", @@ -75,15 +80,16 @@ "save_photo": "Foto speichern", "copy_photo": "Foto kopieren", "sign_in": "Anmelden", - "sign_up": "Registrieren", + "sign_up": "Konto erstellen", "see_more": "Mehr anzeigen", "preview": "Vorschau", + "copy": "Kopieren", "share": "Teilen", "share_user": "%s teilen", "share_post": "Beitrag teilen", "open_in_safari": "In Safari öffnen", "open_in_browser": "Im Browser anzeigen", - "find_people": "Finde Personen zum Folgen", + "find_people": "Personen zum Folgen finden", "manually_search": "Stattdessen manuell suchen", "skip": "Überspringen", "reply": "Antworten", @@ -91,12 +97,16 @@ "block_domain": "%s blockieren", "unblock_domain": "Blockierung von %s aufheben", "settings": "Einstellungen", - "delete": "Löschen" + "delete": "Löschen", + "translate_post": { + "title": "Von %s übersetzen", + "unknown_language": "Unbekannt" + } }, "tabs": { "home": "Startseite", - "search": "Suche", - "notification": "Benachrichtigungen", + "search_and_explore": "Suchen und Entdecken", + "notifications": "Mitteilungen", "profile": "Profil" }, "keyboard": { @@ -132,6 +142,8 @@ "sensitive_content": "NSFW-Inhalt", "media_content_warning": "Tippe irgendwo zum Anzeigen", "tap_to_reveal": "Zum Anzeigen tippen", + "load_embed": "Eingebettetes laden", + "link_via_user": "%s via %s", "poll": { "vote": "Abstimmen", "closed": "Beendet" @@ -139,8 +151,8 @@ "meta_entity": { "url": "Link: %s", "hashtag": "Hashtag: %s", - "mention": "Show Profile: %s", - "email": "Email address: %s" + "mention": "Profil anzeigen: %s", + "email": "E-Mail-Adresse: %s" }, "actions": { "reply": "Antworten", @@ -153,6 +165,7 @@ "show_image": "Bild anzeigen", "show_gif": "GIF anzeigen", "show_video_player": "Zeige Video-Player", + "share_link_in_post": "Link im Beitrag teilen", "tap_then_hold_to_show_menu": "Halte gedrückt um das Menü anzuzeigen" }, "tag": { @@ -165,14 +178,20 @@ }, "visibility": { "unlisted": "Jeder kann diesen Post sehen, aber nicht in der öffentlichen Timeline zeigen.", - "private": "Nur Follower des Authors können diesen Beitrag sehen.", - "private_from_me": "Nur meine Follower können diesen Beitrag sehen.", + "private": "Nur die, die dem Autor folgen, können diesen Beitrag sehen.", + "private_from_me": "Nur die, die mir folgen, können diesen Beitrag sehen.", "direct": "Nur erwähnte Benutzer können diesen Beitrag sehen." + }, + "translation": { + "translated_from": "Übersetzt von %s mit %s", + "unknown_language": "Unbekannt", + "unknown_provider": "Unbekannt", + "show_original": "Original anzeigen" } }, "friendship": { "follow": "Folgen", - "following": "Folge Ich", + "following": "Gefolgt", "request": "Anfragen", "pending": "In Warteschlange", "block": "Blockieren", @@ -187,8 +206,8 @@ "unmute_user": "%s nicht mehr stummschalten", "muted": "Stummgeschaltet", "edit_info": "Information bearbeiten", - "show_reblogs": "Reblogs anzeigen", - "hide_reblogs": "Reblogs ausblenden" + "show_reblogs": "Teilen anzeigen", + "hide_reblogs": "Teilen ausblenden" }, "timeline": { "filtered": "Gefiltert", @@ -197,7 +216,7 @@ }, "loader": { "load_missing_posts": "Fehlende Beiträge laden", - "loading_missing_posts": "Lade fehlende Beiträge...", + "loading_missing_posts": "Fehlende Beiträge werden geladen …", "show_more_replies": "Weitere Antworten anzeigen" }, "header": { @@ -218,10 +237,16 @@ "get_started": "Registrieren", "log_in": "Anmelden" }, + "login": { + "title": "Willkommen zurück", + "subtitle": "Melden Sie sich auf dem Server an, auf dem Sie Ihr Konto erstellt haben.", + "server_search_field": { + "placeholder": "URL eingeben oder nach Server suchen" + } + }, "server_picker": { "title": "Wähle einen Server,\nbeliebigen Server.", - "subtitle": "Wähle eine Gemeinschaft, die auf deinen Interessen, Region oder einem allgemeinen Zweck basiert.", - "subtitle_extend": "Wähle eine Gemeinschaft basierend auf deinen Interessen, deiner Region oder einem allgemeinen Zweck. Jede Gemeinschaft wird von einer völlig unabhängigen Organisation oder Einzelperson betrieben.", + "subtitle": "Wähle einen Server basierend auf deinen Interessen oder deiner Region – oder einfach einen allgemeinen. Du kannst trotzdem mit jedem interagieren, egal auf welchem Server.", "button": { "category": { "all": "Alle", @@ -248,11 +273,10 @@ "category": "KATEGORIE" }, "input": { - "placeholder": "Nach Server suchen oder URL eingeben", - "search_servers_or_enter_url": "Nach Server suchen oder URL eingeben" + "search_servers_or_enter_url": "Suche nach einer Community oder gib eine URL ein" }, "empty_state": { - "finding_servers": "Verfügbare Server werden gesucht...", + "finding_servers": "Verfügbare Server werden gesucht …", "bad_network": "Beim Laden der Daten ist etwas schief gelaufen. Überprüfe deine Internetverbindung.", "no_results": "Keine Ergebnisse" } @@ -343,7 +367,7 @@ "open_email_app": { "title": "Überprüfe deinen Posteingang.", "description": "Wir haben dir gerade eine E-Mail geschickt. Überprüfe deinen Spam-Ordner, falls du es noch nicht getan hast.", - "mail": "Mail", + "mail": "E-Mail", "open_email_client": "E-Mail-Client öffnen" } }, @@ -353,7 +377,7 @@ "offline": "Offline", "new_posts": "Neue Beiträge anzeigen", "published": "Veröffentlicht!", - "Publishing": "Beitrag wird veröffentlicht...", + "Publishing": "Beitrag wird veröffentlicht …", "accessibility": { "logo_label": "Logo-Button", "logo_hint": "Zum Scrollen nach oben tippen und zum vorherigen Ort erneut tippen" @@ -362,7 +386,7 @@ }, "suggestion_account": { "title": "Finde Personen zum Folgen", - "follow_explain": "Wenn du jemandem folgst, dann siehst du deren Beiträge in deinem Home-Feed." + "follow_explain": "Sobald du anderen folgst, siehst du deren Beiträge in deinem Home-Feed." }, "compose": { "title": { @@ -381,12 +405,14 @@ "photo": "Foto", "video": "Video", "attachment_broken": "Dieses %s scheint defekt zu sein und\nkann nicht auf Mastodon hochgeladen werden.", - "description_photo": "Für Menschen mit Sehbehinderung beschreiben...", - "description_video": "Für Menschen mit Sehbehinderung beschreiben...", + "description_photo": "Für Menschen mit Sehbehinderung beschreiben …", + "description_video": "Für Menschen mit Sehbehinderung beschreiben …", "load_failed": "Laden fehlgeschlagen", "upload_failed": "Upload fehlgeschlagen", "can_not_recognize_this_media_attachment": "Medienanhang wurde nicht erkannt", - "attachment_too_large": "Anhang zu groß" + "attachment_too_large": "Anhang zu groß", + "compressing_state": "wird komprimiert …", + "server_processing_state": "Serververarbeitung …" }, "poll": { "duration_time": "Dauer: %s", @@ -396,10 +422,12 @@ "one_day": "1 Tag", "three_days": "3 Tage", "seven_days": "7 Tage", - "option_number": "Auswahlmöglichkeit %ld" + "option_number": "Auswahlmöglichkeit %ld", + "the_poll_is_invalid": "Die Umfrage ist ungültig", + "the_poll_has_empty_option": "Die Umfrage hat eine leere Option" }, "content_warning": { - "placeholder": "Schreibe eine Inhaltswarnung hier..." + "placeholder": "Hier eine Inhaltswarnung schreiben …" }, "visibility": { "public": "Öffentlich", @@ -417,7 +445,9 @@ "custom_emoji_picker": "Benutzerdefinierter Emojiwähler", "enable_content_warning": "Inhaltswarnung einschalten", "disable_content_warning": "Inhaltswarnung ausschalten", - "post_visibility_menu": "Sichtbarkeitsmenü" + "post_visibility_menu": "Sichtbarkeitsmenü", + "post_options": "Beitragsoptionen", + "posting_as": "Veröffentlichen als %s" }, "keyboard": { "discard_post": "Beitrag verwerfen", @@ -433,15 +463,23 @@ "follows_you": "Folgt dir" }, "dashboard": { - "posts": "Beiträge", - "following": "Gefolgte", - "followers": "Folgende" + "my_posts": "Beiträge", + "my_following": "folge ich", + "my_followers": "followers", + "other_posts": "Beiträge", + "other_following": "folge ich", + "other_followers": "followers" }, "fields": { + "joined": "Beigetreten", "add_row": "Zeile hinzufügen", "placeholder": { "label": "Bezeichnung", "content": "Inhalt" + }, + "verified": { + "short": "Überprüft am %s", + "long": "Besitz des Links wurde überprüft am %s" } }, "segmented_control": { @@ -469,12 +507,12 @@ "message": "Bestätige %s zu entsperren" }, "confirm_show_reblogs": { - "title": "Reblogs anzeigen", - "message": "Bestätigen um Reblogs anzuzeigen" + "title": "Teilen anzeigen", + "message": "Bestätigen, um Teilen anzuzeigen" }, "confirm_hide_reblogs": { - "title": "Reblogs ausblenden", - "message": "Confirm to hide reblogs" + "title": "Teilen ausblenden", + "message": "Bestätigen, um Teilen auszublenden" } }, "accessibility": { @@ -485,15 +523,15 @@ } }, "follower": { - "title": "Follower", - "footer": "Folger, die nicht auf deinem Server registriert sind, werden nicht angezeigt." + "title": "Folgende", + "footer": "Folgende, die nicht auf deinem Server registriert sind, werden nicht angezeigt." }, "following": { - "title": "Folgende", + "title": "Gefolgte", "footer": "Gefolgte, die nicht auf deinem Server registriert sind, werden nicht angezeigt." }, "familiarFollowers": { - "title": "Follower, die dir bekannt vorkommen", + "title": "Folgende, die du kennst", "followed_by_names": "Gefolgt von %s" }, "favorited_by": { @@ -517,7 +555,7 @@ }, "accounts": { "title": "Konten, die dir gefallen könnten", - "description": "Vielleicht gefallen dir diese Benutzer", + "description": "Vielleicht gefallen dir diese Konten", "follow": "Folgen" } }, @@ -707,6 +745,18 @@ }, "bookmark": { "title": "Lesezeichen" + }, + "followed_tags": { + "title": "Gefolgte Hashtags", + "header": { + "posts": "Beiträge", + "participants": "Teilnehmer*innen", + "posts_today": "Beiträge heute" + }, + "actions": { + "follow": "Folgen", + "unfollow": "Entfolgen" + } } } } diff --git a/Localization/StringsConvertor/input/en-US.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/en-US.lproj/Localizable.stringsdict index bdcae6ac9..788eb95fc 100644 --- a/Localization/StringsConvertor/input/en-US.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/en-US.lproj/Localizable.stringsdict @@ -15,7 +15,7 @@ one 1 unread notification other - %ld unread notification + %ld unread notifications a11y.plural.count.input_limit_exceeds @@ -50,6 +50,22 @@ %ld characters + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ left + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 character + other + %ld characters + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/en-US.lproj/app.json b/Localization/StringsConvertor/input/en-US.lproj/app.json index a6a971860..b94b9df8a 100644 --- a/Localization/StringsConvertor/input/en-US.lproj/app.json +++ b/Localization/StringsConvertor/input/en-US.lproj/app.json @@ -51,6 +51,11 @@ "clean_cache": { "title": "Clean Cache", "message": "Successfully cleaned %s cache." + }, + "translation_failed": { + "title": "Note", + "message": "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported.", + "button": "OK" } }, "controls": { @@ -74,10 +79,11 @@ "take_photo": "Take Photo", "save_photo": "Save Photo", "copy_photo": "Copy Photo", - "sign_in": "Sign In", - "sign_up": "Sign Up", + "sign_in": "Log in", + "sign_up": "Create account", "see_more": "See More", "preview": "Preview", + "copy": "Copy", "share": "Share", "share_user": "Share %s", "share_post": "Share Post", @@ -91,12 +97,16 @@ "block_domain": "Block %s", "unblock_domain": "Unblock %s", "settings": "Settings", - "delete": "Delete" + "delete": "Delete", + "translate_post": { + "title": "Translate from %s", + "unknown_language": "Unknown" + } }, "tabs": { "home": "Home", - "search": "Search", - "notification": "Notification", + "search_and_explore": "Search and Explore", + "notifications": "Notifications", "profile": "Profile" }, "keyboard": { @@ -132,6 +142,8 @@ "sensitive_content": "Sensitive Content", "media_content_warning": "Tap anywhere to reveal", "tap_to_reveal": "Tap to reveal", + "load_embed": "Load Embed", + "link_via_user": "%s via %s", "poll": { "vote": "Vote", "closed": "Closed" @@ -153,6 +165,7 @@ "show_image": "Show image", "show_gif": "Show GIF", "show_video_player": "Show video player", + "share_link_in_post": "Share Link in Post", "tap_then_hold_to_show_menu": "Tap then hold to show menu" }, "tag": { @@ -168,6 +181,12 @@ "private": "Only their followers can see this post.", "private_from_me": "Only my followers can see this post.", "direct": "Only mentioned user can see this post." + }, + "translation": { + "translated_from": "Translated from %s using %s", + "unknown_language": "Unknown", + "unknown_provider": "Unknown", + "show_original": "Shown Original" } }, "friendship": { @@ -218,10 +237,16 @@ "get_started": "Get Started", "log_in": "Log In" }, + "login": { + "title": "Welcome back", + "subtitle": "Log you in on the server you created your account on.", + "server_search_field": { + "placeholder": "Enter URL or search for your server" + } + }, "server_picker": { "title": "Mastodon is made of users in different servers.", - "subtitle": "Pick a server based on your interests, region, or a general purpose one.", - "subtitle_extend": "Pick a server based on your interests, region, or a general purpose one. Each server is operated by an entirely independent organization or individual.", + "subtitle": "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers.", "button": { "category": { "all": "All", @@ -248,8 +273,7 @@ "category": "CATEGORY" }, "input": { - "placeholder": "Search servers", - "search_servers_or_enter_url": "Search servers or enter URL" + "search_servers_or_enter_url": "Search communities or enter URL" }, "empty_state": { "finding_servers": "Finding available servers...", @@ -355,7 +379,7 @@ "published": "Published!", "Publishing": "Publishing post...", "accessibility": { - "logo_label": "Logo Button", + "logo_label": "Mastodon", "logo_hint": "Tap to scroll to top and tap again to previous location" } } @@ -385,8 +409,10 @@ "description_video": "Describe the video for the visually-impaired...", "load_failed": "Load Failed", "upload_failed": "Upload Failed", - "can_not_recognize_this_media_attachment": "Can not regonize this media attachment", - "attachment_too_large": "Attachment too large" + "can_not_recognize_this_media_attachment": "Can not recognize this media attachment", + "attachment_too_large": "Attachment too large", + "compressing_state": "Compressing...", + "server_processing_state": "Server Processing..." }, "poll": { "duration_time": "Duration: %s", @@ -396,7 +422,9 @@ "one_day": "1 Day", "three_days": "3 Days", "seven_days": "7 Days", - "option_number": "Option %ld" + "option_number": "Option %ld", + "the_poll_is_invalid": "The poll is invalid", + "the_poll_has_empty_option": "The poll has empty option" }, "content_warning": { "placeholder": "Write an accurate warning here..." @@ -417,7 +445,9 @@ "custom_emoji_picker": "Custom Emoji Picker", "enable_content_warning": "Enable Content Warning", "disable_content_warning": "Disable Content Warning", - "post_visibility_menu": "Post Visibility Menu" + "post_visibility_menu": "Post Visibility Menu", + "post_options": "Post Options", + "posting_as": "Posting as %s" }, "keyboard": { "discard_post": "Discard Post", @@ -433,15 +463,23 @@ "follows_you": "Follows You" }, "dashboard": { - "posts": "posts", - "following": "following", - "followers": "followers" + "my_posts": "posts", + "my_following": "following", + "my_followers": "followers", + "other_posts": "posts", + "other_following": "following", + "other_followers": "followers" }, "fields": { + "joined": "Joined", "add_row": "Add Row", "placeholder": { "label": "Label", "content": "Content" + }, + "verified": { + "short": "Verified on %s", + "long": "Ownership of this link was checked on %s" } }, "segmented_control": { @@ -707,6 +745,18 @@ }, "bookmark": { "title": "Bookmarks" + }, + "followed_tags": { + "title": "Followed Tags", + "header": { + "posts": "posts", + "participants": "participants", + "posts_today": "posts today" + }, + "actions": { + "follow": "Follow", + "unfollow": "Unfollow" + } } } } diff --git a/Localization/StringsConvertor/input/en.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/en.lproj/Localizable.stringsdict index 297e6675a..788eb95fc 100644 --- a/Localization/StringsConvertor/input/en.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/en.lproj/Localizable.stringsdict @@ -15,7 +15,7 @@ one 1 unread notification other - %ld unread notification + %ld unread notifications a11y.plural.count.input_limit_exceeds @@ -60,14 +60,8 @@ NSStringPluralRuleType NSStringFormatValueTypeKey ld - zero - no characters one 1 character - few - %ld characters - many - %ld characters other %ld characters diff --git a/Localization/StringsConvertor/input/en.lproj/app.json b/Localization/StringsConvertor/input/en.lproj/app.json index 25f06ad83..b94b9df8a 100644 --- a/Localization/StringsConvertor/input/en.lproj/app.json +++ b/Localization/StringsConvertor/input/en.lproj/app.json @@ -51,6 +51,11 @@ "clean_cache": { "title": "Clean Cache", "message": "Successfully cleaned %s cache." + }, + "translation_failed": { + "title": "Note", + "message": "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported.", + "button": "OK" } }, "controls": { @@ -74,10 +79,11 @@ "take_photo": "Take Photo", "save_photo": "Save Photo", "copy_photo": "Copy Photo", - "sign_in": "Sign In", - "sign_up": "Sign Up", + "sign_in": "Log in", + "sign_up": "Create account", "see_more": "See More", "preview": "Preview", + "copy": "Copy", "share": "Share", "share_user": "Share %s", "share_post": "Share Post", @@ -91,12 +97,16 @@ "block_domain": "Block %s", "unblock_domain": "Unblock %s", "settings": "Settings", - "delete": "Delete" + "delete": "Delete", + "translate_post": { + "title": "Translate from %s", + "unknown_language": "Unknown" + } }, "tabs": { "home": "Home", - "search": "Search", - "notification": "Notification", + "search_and_explore": "Search and Explore", + "notifications": "Notifications", "profile": "Profile" }, "keyboard": { @@ -132,6 +142,8 @@ "sensitive_content": "Sensitive Content", "media_content_warning": "Tap anywhere to reveal", "tap_to_reveal": "Tap to reveal", + "load_embed": "Load Embed", + "link_via_user": "%s via %s", "poll": { "vote": "Vote", "closed": "Closed" @@ -153,6 +165,7 @@ "show_image": "Show image", "show_gif": "Show GIF", "show_video_player": "Show video player", + "share_link_in_post": "Share Link in Post", "tap_then_hold_to_show_menu": "Tap then hold to show menu" }, "tag": { @@ -168,6 +181,12 @@ "private": "Only their followers can see this post.", "private_from_me": "Only my followers can see this post.", "direct": "Only mentioned user can see this post." + }, + "translation": { + "translated_from": "Translated from %s using %s", + "unknown_language": "Unknown", + "unknown_provider": "Unknown", + "show_original": "Shown Original" } }, "friendship": { @@ -218,10 +237,16 @@ "get_started": "Get Started", "log_in": "Log In" }, + "login": { + "title": "Welcome back", + "subtitle": "Log you in on the server you created your account on.", + "server_search_field": { + "placeholder": "Enter URL or search for your server" + } + }, "server_picker": { "title": "Mastodon is made of users in different servers.", - "subtitle": "Pick a server based on your interests, region, or a general purpose one.", - "subtitle_extend": "Pick a server based on your interests, region, or a general purpose one. Each server is operated by an entirely independent organization or individual.", + "subtitle": "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers.", "button": { "category": { "all": "All", @@ -248,8 +273,7 @@ "category": "CATEGORY" }, "input": { - "placeholder": "Search servers", - "search_servers_or_enter_url": "Search servers or enter URL" + "search_servers_or_enter_url": "Search communities or enter URL" }, "empty_state": { "finding_servers": "Finding available servers...", @@ -355,7 +379,7 @@ "published": "Published!", "Publishing": "Publishing post...", "accessibility": { - "logo_label": "Logo Button", + "logo_label": "Mastodon", "logo_hint": "Tap to scroll to top and tap again to previous location" } } @@ -385,8 +409,10 @@ "description_video": "Describe the video for the visually-impaired...", "load_failed": "Load Failed", "upload_failed": "Upload Failed", - "can_not_recognize_this_media_attachment": "Can not regonize this media attachment", - "attachment_too_large": "Attachment too large" + "can_not_recognize_this_media_attachment": "Can not recognize this media attachment", + "attachment_too_large": "Attachment too large", + "compressing_state": "Compressing...", + "server_processing_state": "Server Processing..." }, "poll": { "duration_time": "Duration: %s", @@ -396,7 +422,9 @@ "one_day": "1 Day", "three_days": "3 Days", "seven_days": "7 Days", - "option_number": "Option %ld" + "option_number": "Option %ld", + "the_poll_is_invalid": "The poll is invalid", + "the_poll_has_empty_option": "The poll has empty option" }, "content_warning": { "placeholder": "Write an accurate warning here..." @@ -435,15 +463,23 @@ "follows_you": "Follows You" }, "dashboard": { - "posts": "posts", - "following": "following", - "followers": "followers" + "my_posts": "posts", + "my_following": "following", + "my_followers": "followers", + "other_posts": "posts", + "other_following": "following", + "other_followers": "followers" }, "fields": { + "joined": "Joined", "add_row": "Add Row", "placeholder": { "label": "Label", "content": "Content" + }, + "verified": { + "short": "Verified on %s", + "long": "Ownership of this link was checked on %s" } }, "segmented_control": { @@ -709,6 +745,18 @@ }, "bookmark": { "title": "Bookmarks" + }, + "followed_tags": { + "title": "Followed Tags", + "header": { + "posts": "posts", + "participants": "participants", + "posts_today": "posts today" + }, + "actions": { + "follow": "Follow", + "unfollow": "Unfollow" + } } } } diff --git a/Localization/StringsConvertor/input/es-AR.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/es-AR.lproj/Localizable.stringsdict index 2bd66395a..fb939a040 100644 --- a/Localization/StringsConvertor/input/es-AR.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/es-AR.lproj/Localizable.stringsdict @@ -50,6 +50,22 @@ %ld caracteres + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + Quedan %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 caracter + other + %ld caracteres + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/es-AR.lproj/app.json b/Localization/StringsConvertor/input/es-AR.lproj/app.json index 309cf4d34..120460c44 100644 --- a/Localization/StringsConvertor/input/es-AR.lproj/app.json +++ b/Localization/StringsConvertor/input/es-AR.lproj/app.json @@ -51,6 +51,11 @@ "clean_cache": { "title": "Limpiar caché", "message": "Se limpió exitosamente %s de la memoria caché." + }, + "translation_failed": { + "title": "Nota", + "message": "Falló la traducción. Tal vez el administrador no habilitó las traducciones en este servidor, o este servidor está ejecutando una versión antigua de Mastodon en donde las traducciones aún no estaban disponibles.", + "button": "Aceptar" } }, "controls": { @@ -75,9 +80,10 @@ "save_photo": "Guardar foto", "copy_photo": "Copiar foto", "sign_in": "Iniciar sesión", - "sign_up": "Registrarse", + "sign_up": "Crear cuenta", "see_more": "Ver más", "preview": "Previsualización", + "copy": "Copiar", "share": "Compartir", "share_user": "Compartir %s", "share_post": "Compartir mensaje", @@ -91,12 +97,16 @@ "block_domain": "Bloquear a %s", "unblock_domain": "Desbloquear a %s", "settings": "Configuración", - "delete": "Eliminar" + "delete": "Eliminar", + "translate_post": { + "title": "Traducido desde el %s", + "unknown_language": "Desconocido" + } }, "tabs": { "home": "Principal", - "search": "Buscar", - "notification": "Notificación", + "search_and_explore": "Buscar y explorar", + "notifications": "Notificaciones", "profile": "Perfil" }, "keyboard": { @@ -132,15 +142,17 @@ "sensitive_content": "Contenido sensible", "media_content_warning": "Tocá en cualquier parte para mostrar", "tap_to_reveal": "Tocá para mostrar", + "load_embed": "Cargar lo insertado", + "link_via_user": "%s vía %s", "poll": { "vote": "Votar", "closed": "Cerrada" }, "meta_entity": { - "url": "Link: %s", - "hashtag": "Hashtag: %s", - "mention": "Show Profile: %s", - "email": "Email address: %s" + "url": "Enlace: %s", + "hashtag": "Etiqueta: %s", + "mention": "Mostrar perfil: %s", + "email": "Dirección de correo electrónico: %s" }, "actions": { "reply": "Responder", @@ -153,6 +165,7 @@ "show_image": "Mostrar imagen", "show_gif": "Mostrar GIF", "show_video_player": "Mostrar reproductor de video", + "share_link_in_post": "Compartir enlace en mensaje", "tap_then_hold_to_show_menu": "Tocá y mantené presionado para mostrar el menú" }, "tag": { @@ -168,6 +181,12 @@ "private": "Sólo sus seguidores pueden ver este mensaje.", "private_from_me": "Sólo mis seguidores pueden ver este mensaje.", "direct": "Sólo el usuario mencionado puede ver este mensaje." + }, + "translation": { + "translated_from": "Traducido desde el %s vía %s", + "unknown_language": "Desconocido", + "unknown_provider": "Desconocido", + "show_original": "Mostrar original" } }, "friendship": { @@ -218,10 +237,16 @@ "get_started": "Comenzá", "log_in": "Iniciar sesión" }, + "login": { + "title": "Hola de nuevo", + "subtitle": "Iniciá sesión en el servidor en donde creaste tu cuenta.", + "server_search_field": { + "placeholder": "Ingresá la dirección web o buscá tu servidor" + } + }, "server_picker": { "title": "Mastodon está compuesto de cuentas en diferentes servidores.", - "subtitle": "Elegí un servidor basado en tus intereses, región, o de propósitos generales.", - "subtitle_extend": "Elegí un servidor basado en tus intereses, región, o de propósitos generales. Cada servidor es operado por una organización o individuo totalmente independientes.", + "subtitle": "Elegí un servidor basado en tu región, en tus intereses o uno de propósitos generales. Vas a poder interactuar con cualquier cuenta de Mastodon, independientemente del servidor.", "button": { "category": { "all": "Todas", @@ -248,8 +273,7 @@ "category": "CATEGORÍA" }, "input": { - "placeholder": "Buscar servidores", - "search_servers_or_enter_url": "Buscar servidores o introducir dirección web" + "search_servers_or_enter_url": "Buscá comunidades o ingresá la dirección web" }, "empty_state": { "finding_servers": "Buscando servidores disponibles…", @@ -386,7 +410,9 @@ "load_failed": "Falló la descarga", "upload_failed": "Falló la subida", "can_not_recognize_this_media_attachment": "No se pudo reconocer este archivo adjunto", - "attachment_too_large": "Adjunto demasiado grande" + "attachment_too_large": "Adjunto demasiado grande", + "compressing_state": "Comprimiendo…", + "server_processing_state": "Servidor procesando…" }, "poll": { "duration_time": "Duración: %s", @@ -396,7 +422,9 @@ "one_day": "1 día", "three_days": "3 días", "seven_days": "7 días", - "option_number": "Opción %ld" + "option_number": "Opción %ld", + "the_poll_is_invalid": "La encuesta no es válida", + "the_poll_has_empty_option": "La encuesta tiene opción vacía" }, "content_warning": { "placeholder": "Escribí una advertencia precisa acá…" @@ -417,7 +445,9 @@ "custom_emoji_picker": "Selector de emoji personalizado", "enable_content_warning": "Habilitar advertencia de contenido", "disable_content_warning": "Deshabilitar advertencia de contenido", - "post_visibility_menu": "Menú de visibilidad del mensaje" + "post_visibility_menu": "Menú de visibilidad del mensaje", + "post_options": "Opciones de mensaje", + "posting_as": "Enviar como %s" }, "keyboard": { "discard_post": "Descartar mensaje", @@ -433,15 +463,23 @@ "follows_you": "Te sigue" }, "dashboard": { - "posts": "mensajes", - "following": "siguiendo", - "followers": "seguidores" + "my_posts": "mensajes", + "my_following": "siguiendo", + "my_followers": "seguidores", + "other_posts": "mensajes", + "other_following": "siguiendo", + "other_followers": "seguidores" }, "fields": { + "joined": "En este servidor desde", "add_row": "Agregar fila", "placeholder": { "label": "Nombre de campo", "content": "Valor de campo" + }, + "verified": { + "short": "Verificado en %s", + "long": "La propiedad de este enlace fue verificada el %s" } }, "segmented_control": { @@ -706,7 +744,19 @@ "accessibility_hint": "Tocá dos veces para descartar este asistente" }, "bookmark": { - "title": "Bookmarks" + "title": "Marcadores" + }, + "followed_tags": { + "title": "Etiquetas seguidas", + "header": { + "posts": "mensajes", + "participants": "participantes", + "posts_today": "mensajes hoy" + }, + "actions": { + "follow": "Seguir", + "unfollow": "Dejar de seguir" + } } } } diff --git a/Localization/StringsConvertor/input/es.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/es.lproj/Localizable.stringsdict index def3d7bba..0a904fcfd 100644 --- a/Localization/StringsConvertor/input/es.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/es.lproj/Localizable.stringsdict @@ -50,6 +50,22 @@ %ld caracteres + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + Quedan %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 carácter + other + %ld caracteres + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/es.lproj/app.json b/Localization/StringsConvertor/input/es.lproj/app.json index 7eaff340d..6d19e833b 100644 --- a/Localization/StringsConvertor/input/es.lproj/app.json +++ b/Localization/StringsConvertor/input/es.lproj/app.json @@ -51,6 +51,11 @@ "clean_cache": { "title": "Limpiar Caché", "message": "Se han limpiado con éxito %s de caché." + }, + "translation_failed": { + "title": "Nota", + "message": "Error al traducir. Tal vez el administrador no ha habilitado las traducciones en este servidor o este servidor está ejecutando una versión antigua de Mastodon donde las traducciones aún no están soportadas.", + "button": "Aceptar" } }, "controls": { @@ -75,9 +80,10 @@ "save_photo": "Guardar foto", "copy_photo": "Copiar foto", "sign_in": "Iniciar sesión", - "sign_up": "Regístrate", + "sign_up": "Crear cuenta", "see_more": "Ver más", "preview": "Vista previa", + "copy": "Copiar", "share": "Compartir", "share_user": "Compartir %s", "share_post": "Compartir publicación", @@ -91,12 +97,16 @@ "block_domain": "Bloquear %s", "unblock_domain": "Desbloquear %s", "settings": "Configuración", - "delete": "Borrar" + "delete": "Borrar", + "translate_post": { + "title": "Traducir desde %s", + "unknown_language": "Desconocido" + } }, "tabs": { "home": "Inicio", - "search": "Buscar", - "notification": "Notificación", + "search_and_explore": "Buscar y explorar", + "notifications": "Notificaciones", "profile": "Perfil" }, "keyboard": { @@ -132,15 +142,17 @@ "sensitive_content": "Contenido sensible", "media_content_warning": "Pulsa en cualquier sitio para mostrar", "tap_to_reveal": "Tocar para revelar", + "load_embed": "Cargar incrustado", + "link_via_user": "%s vía %s", "poll": { "vote": "Vota", "closed": "Cerrado" }, "meta_entity": { - "url": "Link: %s", - "hashtag": "Hashtag: %s", - "mention": "Show Profile: %s", - "email": "Email address: %s" + "url": "Enlace: %s", + "hashtag": "Etiqueta: %s", + "mention": "Mostrar Perfil: %s", + "email": "Dirección de correo electrónico: %s" }, "actions": { "reply": "Responder", @@ -153,6 +165,7 @@ "show_image": "Mostrar imagen", "show_gif": "Mostrar GIF", "show_video_player": "Mostrar reproductor de vídeo", + "share_link_in_post": "Compartir enlace en publicación", "tap_then_hold_to_show_menu": "Toca, después mantén para mostrar el menú" }, "tag": { @@ -168,6 +181,12 @@ "private": "Sólo sus seguidores pueden ver este mensaje.", "private_from_me": "Sólo mis seguidores pueden ver este mensaje.", "direct": "Sólo el usuario mencionado puede ver este mensaje." + }, + "translation": { + "translated_from": "Traducido desde %s usando %s", + "unknown_language": "Desconocido", + "unknown_provider": "Desconocido", + "show_original": "Mostrar Original" } }, "friendship": { @@ -187,8 +206,8 @@ "unmute_user": "Desmutear a %s", "muted": "Silenciado", "edit_info": "Editar Info", - "show_reblogs": "Show Reblogs", - "hide_reblogs": "Hide Reblogs" + "show_reblogs": "Mostrar reblogs", + "hide_reblogs": "Ocultar reblogs" }, "timeline": { "filtered": "Filtrado", @@ -218,10 +237,16 @@ "get_started": "Empezar", "log_in": "Iniciar sesión" }, + "login": { + "title": "Bienvenido de nuevo", + "subtitle": "Inicie sesión en el servidor en el que creó su cuenta.", + "server_search_field": { + "placeholder": "Introduzca la URL o busque su servidor" + } + }, "server_picker": { "title": "Elige un servidor,\ncualquier servidor.", - "subtitle": "Elige una comunidad relacionada con tus intereses, con tu región o una más genérica.", - "subtitle_extend": "Elige una comunidad relacionada con tus intereses, con tu región o una más genérica. Cada comunidad está operada por una organización o individuo completamente independiente.", + "subtitle": "Escoge un servidor basado en tu región, intereses o un propósito general. Aún puedes chatear con cualquiera en Mastodon, independientemente de tus servidores.", "button": { "category": { "all": "Todas", @@ -248,8 +273,7 @@ "category": "CATEGORÍA" }, "input": { - "placeholder": "Encuentra un servidor o únete al tuyo propio...", - "search_servers_or_enter_url": "Buscar servidores o introducir la URL" + "search_servers_or_enter_url": "Buscar comunidades o introducir URL" }, "empty_state": { "finding_servers": "Encontrando servidores disponibles...", @@ -383,10 +407,12 @@ "attachment_broken": "Este %s está roto y no puede\nsubirse a Mastodon.", "description_photo": "Describe la foto para los usuarios con dificultad visual...", "description_video": "Describe el vídeo para los usuarios con dificultad visual...", - "load_failed": "Load Failed", - "upload_failed": "Upload Failed", - "can_not_recognize_this_media_attachment": "Can not regonize this media attachment", - "attachment_too_large": "Attachment too large" + "load_failed": "Carga fallida", + "upload_failed": "Error al cargar", + "can_not_recognize_this_media_attachment": "No se puede reconocer este archivo adjunto", + "attachment_too_large": "Adjunto demasiado grande", + "compressing_state": "Comprimiendo...", + "server_processing_state": "Procesando en el servidor..." }, "poll": { "duration_time": "Duración: %s", @@ -396,7 +422,9 @@ "one_day": "1 Día", "three_days": "4 Días", "seven_days": "7 Días", - "option_number": "Opción %ld" + "option_number": "Opción %ld", + "the_poll_is_invalid": "La encuesta no es válida", + "the_poll_has_empty_option": "La encuesta tiene una opción vacía" }, "content_warning": { "placeholder": "Escribe una advertencia precisa aquí..." @@ -417,7 +445,9 @@ "custom_emoji_picker": "Selector de Emojis Personalizados", "enable_content_warning": "Activar Advertencia de Contenido", "disable_content_warning": "Desactivar Advertencia de Contenido", - "post_visibility_menu": "Menú de Visibilidad de la Publicación" + "post_visibility_menu": "Menú de Visibilidad de la Publicación", + "post_options": "Opciones de Publicación", + "posting_as": "Publicado como %s" }, "keyboard": { "discard_post": "Descartar Publicación", @@ -433,15 +463,23 @@ "follows_you": "Te sigue" }, "dashboard": { - "posts": "publicaciones", - "following": "siguiendo", - "followers": "seguidores" + "my_posts": "posts", + "my_following": "following", + "my_followers": "followers", + "other_posts": "posts", + "other_following": "following", + "other_followers": "followers" }, "fields": { + "joined": "Se unió", "add_row": "Añadir Fila", "placeholder": { "label": "Nombre para el campo", "content": "Contenido" + }, + "verified": { + "short": "Verificado en %s", + "long": "La propiedad de este enlace fue verificada el %s" } }, "segmented_control": { @@ -469,12 +507,12 @@ "message": "Confirmar para desbloquear a %s" }, "confirm_show_reblogs": { - "title": "Show Reblogs", - "message": "Confirm to show reblogs" + "title": "Mostrar reblogs", + "message": "Confirmar para mostrar reblogs" }, "confirm_hide_reblogs": { - "title": "Hide Reblogs", - "message": "Confirm to hide reblogs" + "title": "Ocultar reblogs", + "message": "Confirmar para ocultar reblogs" } }, "accessibility": { @@ -706,7 +744,19 @@ "accessibility_hint": "Haz doble toque para descartar este asistente" }, "bookmark": { - "title": "Bookmarks" + "title": "Marcadores" + }, + "followed_tags": { + "title": "Etiquetas seguidas", + "header": { + "posts": "publicaciones", + "participants": "participantes", + "posts_today": "publicaciones de hoy" + }, + "actions": { + "follow": "Seguir", + "unfollow": "Dejar de seguir" + } } } } diff --git a/Localization/StringsConvertor/input/eu.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/eu.lproj/Localizable.stringsdict index 0159a7da9..404deebd3 100644 --- a/Localization/StringsConvertor/input/eu.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/eu.lproj/Localizable.stringsdict @@ -50,10 +50,26 @@ %ld karaktere + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ left + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 character + other + %ld karaktere + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey - %#@names@%#@count_mutual@ + %#@names@: "%#@count_mutual@ names one diff --git a/Localization/StringsConvertor/input/eu.lproj/app.json b/Localization/StringsConvertor/input/eu.lproj/app.json index 3f58f522c..e7c5021bb 100644 --- a/Localization/StringsConvertor/input/eu.lproj/app.json +++ b/Localization/StringsConvertor/input/eu.lproj/app.json @@ -51,6 +51,11 @@ "clean_cache": { "title": "Garbitu cache-a", "message": "Behar bezala garbitu da %s cache-a." + }, + "translation_failed": { + "title": "Note", + "message": "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported.", + "button": "OK" } }, "controls": { @@ -75,9 +80,10 @@ "save_photo": "Gorde argazkia", "copy_photo": "Kopiatu argazkia", "sign_in": "Hasi saioa", - "sign_up": "Eman Izena", + "sign_up": "Sortu kontua", "see_more": "Ikusi gehiago", "preview": "Aurrebista", + "copy": "Copy", "share": "Partekatu", "share_user": "Partekatu %s", "share_post": "Partekatu bidalketa", @@ -91,12 +97,16 @@ "block_domain": "Blokeatu %s", "unblock_domain": "Desblokeatu %s", "settings": "Ezarpenak", - "delete": "Ezabatu" + "delete": "Ezabatu", + "translate_post": { + "title": "Translate from %s", + "unknown_language": "Unknown" + } }, "tabs": { "home": "Hasiera", - "search": "Bilatu", - "notification": "Jakinarazpena", + "search_and_explore": "Search and Explore", + "notifications": "Notifications", "profile": "Profila" }, "keyboard": { @@ -129,18 +139,20 @@ "show_post": "Erakutsi bidalketa", "show_user_profile": "Erakutsi erabiltzailearen profila", "content_warning": "Edukiaren abisua", - "sensitive_content": "Sensitive Content", + "sensitive_content": "Eduki hunkigarria", "media_content_warning": "Ukitu edonon bistaratzeko", "tap_to_reveal": "Sakatu erakusteko", + "load_embed": "Load Embed", + "link_via_user": "%s via %s", "poll": { "vote": "Bozkatu", "closed": "Itxita" }, "meta_entity": { - "url": "Link: %s", - "hashtag": "Hashtag: %s", - "mention": "Show Profile: %s", - "email": "Email address: %s" + "url": "Lotura: %s", + "hashtag": "Traolak: %s", + "mention": "Erakutsi Profila: %s", + "email": "E-posta helbidea: %s" }, "actions": { "reply": "Erantzun", @@ -153,6 +165,7 @@ "show_image": "Erakutsi irudia", "show_gif": "Erakutsi GIFa", "show_video_player": "Erakutsi bideo-erreproduzigailua", + "share_link_in_post": "Share Link in Post", "tap_then_hold_to_show_menu": "Sakatu eta eutsi menua erakusteko" }, "tag": { @@ -168,6 +181,12 @@ "private": "Beren jarraitzaileek soilik ikus dezakete bidalketa hau.", "private_from_me": "Nire jarraitzaileek soilik ikus dezakete bidalketa hau.", "direct": "Aipatutako erabiltzaileek soilik ikus dezakete bidalketa hau." + }, + "translation": { + "translated_from": "Translated from %s using %s", + "unknown_language": "Unknown", + "unknown_provider": "Unknown", + "show_original": "Shown Original" } }, "friendship": { @@ -187,8 +206,8 @@ "unmute_user": "Desmututu %s", "muted": "Mutututa", "edit_info": "Editatu informazioa", - "show_reblogs": "Show Reblogs", - "hide_reblogs": "Hide Reblogs" + "show_reblogs": "Ikusi bultzadak", + "hide_reblogs": "Ezkutatu bultzadak" }, "timeline": { "filtered": "Iragazita", @@ -218,10 +237,16 @@ "get_started": "Nola hasi", "log_in": "Hasi saioa" }, + "login": { + "title": "Ongi etorri berriro ere", + "subtitle": "Log you in on the server you created your account on.", + "server_search_field": { + "placeholder": "Enter URL or search for your server" + } + }, "server_picker": { "title": "Aukeratu zerbitzari bat,\nedozein zerbitzari.", - "subtitle": "Aukeratu komunitate bat zure interes edo lurraldearen arabera, edo erabilera orokorreko bat.", - "subtitle_extend": "Aukeratu komunitate bat zure interes edo lurraldearen arabera, edo erabilera orokorreko bat. Komunitate bakoitza erakunde edo norbanako independente batek kudeatzen du.", + "subtitle": "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers.", "button": { "category": { "all": "Guztiak", @@ -248,8 +273,7 @@ "category": "KATEGORIA" }, "input": { - "placeholder": "Bilatu zerbitzari bat edo sortu zurea...", - "search_servers_or_enter_url": "Search servers or enter URL" + "search_servers_or_enter_url": "Search communities or enter URL" }, "empty_state": { "finding_servers": "Erabilgarri dauden zerbitzariak bilatzen...", @@ -259,7 +283,7 @@ }, "register": { "title": "Hitz egin iezaguzu zuri buruz.", - "lets_get_you_set_up_on_domain": "Let’s get you set up on %s", + "lets_get_you_set_up_on_domain": "%s zerbitzariko kontua prestatuko dizugu", "input": { "avatar": { "delete": "Ezabatu" @@ -330,7 +354,7 @@ "confirm_email": { "title": "Eta azkenik...", "subtitle": "Sakatu epostaz bidali dizugun loturan zure kontua egiaztatzeko.", - "tap_the_link_we_emailed_to_you_to_verify_your_account": "Tap the link we emailed to you to verify your account", + "tap_the_link_we_emailed_to_you_to_verify_your_account": "Sakatu epostaz bidali dizugun loturan zure kontua egiaztatzeko", "button": { "open_email_app": "Ireki eposta aplikazioa", "resend": "Berbidali" @@ -355,7 +379,7 @@ "published": "Argitaratua!", "Publishing": "Bidalketa argitaratzen...", "accessibility": { - "logo_label": "Logo Button", + "logo_label": "Logo botoia", "logo_hint": "Tap to scroll to top and tap again to previous location" } } @@ -384,9 +408,11 @@ "description_photo": "Deskribatu argazkia ikusmen arazoak dituztenentzat...", "description_video": "Deskribatu bideoa ikusmen arazoak dituztenentzat...", "load_failed": "Load Failed", - "upload_failed": "Upload Failed", - "can_not_recognize_this_media_attachment": "Can not regonize this media attachment", - "attachment_too_large": "Attachment too large" + "upload_failed": "Kargatzeak huts egin du", + "can_not_recognize_this_media_attachment": "Can not recognize this media attachment", + "attachment_too_large": "Eranskina handiegia da", + "compressing_state": "Konprimatzen...", + "server_processing_state": "Server Processing..." }, "poll": { "duration_time": "Iraupena: %s", @@ -396,7 +422,9 @@ "one_day": "Egun 1", "three_days": "3 egun", "seven_days": "7 egun", - "option_number": "%ld aukera" + "option_number": "%ld aukera", + "the_poll_is_invalid": "Inkesta ez da balekoa", + "the_poll_has_empty_option": "The poll has empty option" }, "content_warning": { "placeholder": "Idatzi abisu zehatz bat hemen..." @@ -417,7 +445,9 @@ "custom_emoji_picker": "Emoji pertsonalizatuen hautatzailea", "enable_content_warning": "Gaitu edukiaren abisua", "disable_content_warning": "Desgaitu edukiaren abisua", - "post_visibility_menu": "Bidalketaren ikusgaitasunaren menua" + "post_visibility_menu": "Bidalketaren ikusgaitasunaren menua", + "post_options": "Bildalketaren aukerak", + "posting_as": "Posting as %s" }, "keyboard": { "discard_post": "Baztertu bidalketa", @@ -430,18 +460,26 @@ }, "profile": { "header": { - "follows_you": "Follows You" + "follows_you": "Jarraitzen zaitu" }, "dashboard": { - "posts": "bidalketa", - "following": "jarraitzen", - "followers": "jarraitzaile" + "my_posts": "posts", + "my_following": "following", + "my_followers": "followers", + "other_posts": "posts", + "other_following": "following", + "other_followers": "followers" }, "fields": { + "joined": "Joined", "add_row": "Gehitu errenkada", "placeholder": { "label": "Etiketa", "content": "Edukia" + }, + "verified": { + "short": "Verified on %s", + "long": "Esteka honen jabetzaren egiaztaketa data: %s" } }, "segmented_control": { @@ -469,12 +507,12 @@ "message": "Berretsi %s desblokeatzea" }, "confirm_show_reblogs": { - "title": "Show Reblogs", - "message": "Confirm to show reblogs" + "title": "Ikusi bultzadak", + "message": "Berretsi birbidalketak ikustea" }, "confirm_hide_reblogs": { - "title": "Hide Reblogs", - "message": "Confirm to hide reblogs" + "title": "Ezkutatu bultzadak", + "message": "Berretsi birbidalketak ezkutatzea" } }, "accessibility": { @@ -485,11 +523,11 @@ } }, "follower": { - "title": "follower", + "title": "jarraitzaile", "footer": "Beste zerbitzarietako jarraitzaileak ez dira bistaratzen." }, "following": { - "title": "following", + "title": "jarraitzen", "footer": "Beste zerbitzarietan jarraitutakoak ez dira bistaratzen." }, "familiarFollowers": { @@ -540,10 +578,10 @@ "posts": "Argitalpenak", "hashtags": "Traolak", "news": "Albisteak", - "community": "Community", + "community": "Komunitatea", "for_you": "Zuretzat" }, - "intro": "These are the posts gaining traction in your corner of Mastodon." + "intro": "Hauek dira zure Mastodon txokoan beraien lekua hartzen ari diren argitalpenak." }, "favorite": { "title": "Zure gogokoak" @@ -566,10 +604,10 @@ "show_mentions": "Erakutsi aipamenak" }, "follow_request": { - "accept": "Accept", - "accepted": "Accepted", - "reject": "reject", - "rejected": "Rejected" + "accept": "Onartu", + "accepted": "Onartuta", + "reject": "ukatu", + "rejected": "Ukatua" } }, "thread": { @@ -646,46 +684,46 @@ "text_placeholder": "Idatzi edo itsatsi iruzkin gehigarriak", "reported": "SALATUA", "step_one": { - "step_1_of_4": "Step 1 of 4", - "whats_wrong_with_this_post": "What's wrong with this post?", - "whats_wrong_with_this_account": "What's wrong with this account?", - "whats_wrong_with_this_username": "What's wrong with %s?", - "select_the_best_match": "Select the best match", - "i_dont_like_it": "I don’t like it", - "it_is_not_something_you_want_to_see": "It is not something you want to see", - "its_spam": "It’s spam", - "malicious_links_fake_engagement_or_repetetive_replies": "Malicious links, fake engagement, or repetetive replies", - "it_violates_server_rules": "It violates server rules", - "you_are_aware_that_it_breaks_specific_rules": "You are aware that it breaks specific rules", - "its_something_else": "It’s something else", - "the_issue_does_not_fit_into_other_categories": "The issue does not fit into other categories" + "step_1_of_4": "1. urratsa 4tik", + "whats_wrong_with_this_post": "Zer du txarra argitalpen honek?", + "whats_wrong_with_this_account": "Zer du txarra kontu honek?", + "whats_wrong_with_this_username": "Zer du txarra %s?", + "select_the_best_match": "Aukeratu egokiena", + "i_dont_like_it": "Ez dut gustukoa", + "it_is_not_something_you_want_to_see": "Ikusi nahi ez dudan zerbait da", + "its_spam": "Spama da", + "malicious_links_fake_engagement_or_repetetive_replies": "Esteka maltzurrak, gezurrezko elkarrekintzak edo erantzun errepikakorrak", + "it_violates_server_rules": "Zerbitzariaren arauak hausten ditu", + "you_are_aware_that_it_breaks_specific_rules": "Arau zehatzak urratzen dituela badakizu", + "its_something_else": "Beste zerbait da", + "the_issue_does_not_fit_into_other_categories": "Arazoa ezin da beste kategorietan sailkatu" }, "step_two": { - "step_2_of_4": "Step 2 of 4", - "which_rules_are_being_violated": "Which rules are being violated?", - "select_all_that_apply": "Select all that apply", + "step_2_of_4": "2. urratsa 4tik", + "which_rules_are_being_violated": "Ze arau hautsi ditu?", + "select_all_that_apply": "Hautatu dagozkion guztiak", "i_just_don’t_like_it": "I just don’t like it" }, "step_three": { - "step_3_of_4": "Step 3 of 4", - "are_there_any_posts_that_back_up_this_report": "Are there any posts that back up this report?", - "select_all_that_apply": "Select all that apply" + "step_3_of_4": "3. urratsa 4tik", + "are_there_any_posts_that_back_up_this_report": "Salaketa hau babesten duen bidalketarik badago?", + "select_all_that_apply": "Hautatu dagozkion guztiak" }, "step_four": { - "step_4_of_4": "Step 4 of 4", - "is_there_anything_else_we_should_know": "Is there anything else we should know?" + "step_4_of_4": "4. urratsa 4tik", + "is_there_anything_else_we_should_know": "Beste zerbait jakin beharko genuke?" }, "step_final": { - "dont_want_to_see_this": "Don’t want to see this?", - "when_you_see_something_you_dont_like_on_mastodon_you_can_remove_the_person_from_your_experience.": "When you see something you don’t like on Mastodon, you can remove the person from your experience.", - "unfollow": "Unfollow", + "dont_want_to_see_this": "Ez duzu hau ikusi nahi?", + "when_you_see_something_you_dont_like_on_mastodon_you_can_remove_the_person_from_your_experience.": "Mastodonen gustuko ez duzun zerbait ikusten duzunean, zure esperientziatik atera dezakezu pertsona hori.", + "unfollow": "Utzi jarraitzeari", "unfollowed": "Unfollowed", - "unfollow_user": "Unfollow %s", - "mute_user": "Mute %s", - "you_wont_see_their_posts_or_reblogs_in_your_home_feed_they_wont_know_they_ve_been_muted": "You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted.", - "block_user": "Block %s", - "they_will_no_longer_be_able_to_follow_or_see_your_posts_but_they_can_see_if_theyve_been_blocked": "They will no longer be able to follow or see your posts, but they can see if they’ve been blocked.", - "while_we_review_this_you_can_take_action_against_user": "While we review this, you can take action against %s" + "unfollow_user": "%s jarraitzeari utzi", + "mute_user": "Mututu %s", + "you_wont_see_their_posts_or_reblogs_in_your_home_feed_they_wont_know_they_ve_been_muted": "Ez dituzu bere bidalketa eta birbidalketak zure hasierako jarioan ikusiko. Ez dute jakingo isilarazi dituztenik.", + "block_user": "Blokeatu %s", + "they_will_no_longer_be_able_to_follow_or_see_your_posts_but_they_can_see_if_theyve_been_blocked": "Ezin izango dituzte zure bidalketak jarraitu edo ikusi, baina blokeatuta dauden ikusi ahal izango dute.", + "while_we_review_this_you_can_take_action_against_user": "Hau berrikusten dugun bitartean, %s erabiltzailearen aurkako neurriak hartu ditzakezu" } }, "preview": { @@ -706,7 +744,19 @@ "accessibility_hint": "Ukitu birritan morroi hau baztertzeko" }, "bookmark": { - "title": "Bookmarks" + "title": "Laster-markak" + }, + "followed_tags": { + "title": "Followed Tags", + "header": { + "posts": "posts", + "participants": "participants", + "posts_today": "posts today" + }, + "actions": { + "follow": "Follow", + "unfollow": "Unfollow" + } } } } diff --git a/Localization/StringsConvertor/input/fi.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/fi.lproj/Localizable.stringsdict index 8048edf2d..ccfee35c9 100644 --- a/Localization/StringsConvertor/input/fi.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/fi.lproj/Localizable.stringsdict @@ -50,6 +50,22 @@ %ld merkkiä + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ left + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 character + other + %ld characters + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/fi.lproj/app.json b/Localization/StringsConvertor/input/fi.lproj/app.json index a42642786..f6beff049 100644 --- a/Localization/StringsConvertor/input/fi.lproj/app.json +++ b/Localization/StringsConvertor/input/fi.lproj/app.json @@ -51,6 +51,11 @@ "clean_cache": { "title": "Puhdista välimuisti", "message": "%s välimuisti tyhjennetty onnistuneesti." + }, + "translation_failed": { + "title": "Note", + "message": "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported.", + "button": "OK" } }, "controls": { @@ -74,10 +79,11 @@ "take_photo": "Ota kuva", "save_photo": "Tallenna kuva", "copy_photo": "Kopioi kuva", - "sign_in": "Kirjaudu sisään", - "sign_up": "Rekisteröidy", + "sign_in": "Log in", + "sign_up": "Create account", "see_more": "Näytä lisää", "preview": "Esikatselu", + "copy": "Copy", "share": "Jaa", "share_user": "Jaa %s", "share_post": "Jaa julkaisu", @@ -91,12 +97,16 @@ "block_domain": "Estä %s", "unblock_domain": "Poista esto %s", "settings": "Asetukset", - "delete": "Poista" + "delete": "Poista", + "translate_post": { + "title": "Translate from %s", + "unknown_language": "Unknown" + } }, "tabs": { "home": "Koti", - "search": "Haku", - "notification": "Ilmoitus", + "search_and_explore": "Search and Explore", + "notifications": "Notifications", "profile": "Profiili" }, "keyboard": { @@ -132,6 +142,8 @@ "sensitive_content": "Sensitive Content", "media_content_warning": "Napauta mistä tahansa paljastaaksesi", "tap_to_reveal": "Tap to reveal", + "load_embed": "Load Embed", + "link_via_user": "%s via %s", "poll": { "vote": "Vote", "closed": "Suljettu" @@ -153,6 +165,7 @@ "show_image": "Show image", "show_gif": "Show GIF", "show_video_player": "Show video player", + "share_link_in_post": "Share Link in Post", "tap_then_hold_to_show_menu": "Tap then hold to show menu" }, "tag": { @@ -168,6 +181,12 @@ "private": "Only their followers can see this post.", "private_from_me": "Only my followers can see this post.", "direct": "Only mentioned user can see this post." + }, + "translation": { + "translated_from": "Käännetty kielestä %s palvelulla %s", + "unknown_language": "Unknown", + "unknown_provider": "Tuntematon", + "show_original": "Shown Original" } }, "friendship": { @@ -218,10 +237,16 @@ "get_started": "Get Started", "log_in": "Log In" }, + "login": { + "title": "Welcome back", + "subtitle": "Log you in on the server you created your account on.", + "server_search_field": { + "placeholder": "Enter URL or search for your server" + } + }, "server_picker": { "title": "Valitse palvelin,\nmikä tahansa palvelin.", - "subtitle": "Pick a server based on your interests, region, or a general purpose one.", - "subtitle_extend": "Pick a server based on your interests, region, or a general purpose one. Each server is operated by an entirely independent organization or individual.", + "subtitle": "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers.", "button": { "category": { "all": "Kaikki", @@ -248,8 +273,7 @@ "category": "KATEGORIA" }, "input": { - "placeholder": "Etsi palvelin tai liity omaan...", - "search_servers_or_enter_url": "Search servers or enter URL" + "search_servers_or_enter_url": "Search communities or enter URL" }, "empty_state": { "finding_servers": "Etsistään saatavilla olevia palvelimia...", @@ -385,8 +409,10 @@ "description_video": "Kuvaile video näkövammaisille...", "load_failed": "Load Failed", "upload_failed": "Upload Failed", - "can_not_recognize_this_media_attachment": "Can not regonize this media attachment", - "attachment_too_large": "Attachment too large" + "can_not_recognize_this_media_attachment": "Can not recognize this media attachment", + "attachment_too_large": "Attachment too large", + "compressing_state": "Compressing...", + "server_processing_state": "Server Processing..." }, "poll": { "duration_time": "Kesto: %s", @@ -396,7 +422,9 @@ "one_day": "1 päivä", "three_days": "3 päivää", "seven_days": "7 päivää", - "option_number": "Vaihtoehto %ld" + "option_number": "Vaihtoehto %ld", + "the_poll_is_invalid": "The poll is invalid", + "the_poll_has_empty_option": "The poll has empty option" }, "content_warning": { "placeholder": "Kirjoita tarkka varoitus tähän..." @@ -417,7 +445,9 @@ "custom_emoji_picker": "Mukautettu emojivalitsin", "enable_content_warning": "Ota sisältövaroitus käyttöön", "disable_content_warning": "Poista sisältövaroitus käytöstä", - "post_visibility_menu": "Julkaisun näkyvyysvalikko" + "post_visibility_menu": "Julkaisun näkyvyysvalikko", + "post_options": "Post Options", + "posting_as": "Posting as %s" }, "keyboard": { "discard_post": "Hylkää julkaisu", @@ -433,15 +463,23 @@ "follows_you": "Follows You" }, "dashboard": { - "posts": "julkaisut", - "following": "seurataan", - "followers": "seuraajat" + "my_posts": "julkaisut", + "my_following": "seurattavat", + "my_followers": "seuraajat", + "other_posts": "julkaisut", + "other_following": "seurattavat", + "other_followers": "seuraajat" }, "fields": { + "joined": "Joined", "add_row": "Lisää rivi", "placeholder": { "label": "Nimi", "content": "Sisältö" + }, + "verified": { + "short": "Verified on %s", + "long": "Ownership of this link was checked on %s" } }, "segmented_control": { @@ -707,6 +745,18 @@ }, "bookmark": { "title": "Bookmarks" + }, + "followed_tags": { + "title": "Followed Tags", + "header": { + "posts": "posts", + "participants": "participants", + "posts_today": "posts today" + }, + "actions": { + "follow": "Follow", + "unfollow": "Unfollow" + } } } } diff --git a/Localization/StringsConvertor/input/fr.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/fr.lproj/Localizable.stringsdict index d9d860a47..4eb068697 100644 --- a/Localization/StringsConvertor/input/fr.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/fr.lproj/Localizable.stringsdict @@ -50,6 +50,22 @@ %ld caractères + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ restants + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 caractère + other + %ld caractères + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/fr.lproj/app.json b/Localization/StringsConvertor/input/fr.lproj/app.json index 25bb6e511..1d998ba77 100644 --- a/Localization/StringsConvertor/input/fr.lproj/app.json +++ b/Localization/StringsConvertor/input/fr.lproj/app.json @@ -51,6 +51,11 @@ "clean_cache": { "title": "Vider le cache", "message": "Cache de %s effacé avec succès." + }, + "translation_failed": { + "title": "Note", + "message": "La traduction a échoué. Peut-être que l'administrateur n'a pas activé les traductions sur ce serveur ou que ce serveur utilise une ancienne version de Mastodon où les traductions ne sont pas encore prises en charge.", + "button": "OK" } }, "controls": { @@ -78,6 +83,7 @@ "sign_up": "Créer un compte", "see_more": "Voir plus", "preview": "Aperçu", + "copy": "Copier", "share": "Partager", "share_user": "Partager %s", "share_post": "Partager la publication", @@ -91,12 +97,16 @@ "block_domain": "Bloquer %s", "unblock_domain": "Débloquer %s", "settings": "Paramètres", - "delete": "Supprimer" + "delete": "Supprimer", + "translate_post": { + "title": "Traduit depuis %s", + "unknown_language": "Inconnu" + } }, "tabs": { "home": "Accueil", - "search": "Rechercher", - "notification": "Notification", + "search_and_explore": "Rechercher et explorer", + "notifications": "Notifications", "profile": "Profil" }, "keyboard": { @@ -132,6 +142,8 @@ "sensitive_content": "Contenu sensible", "media_content_warning": "Tapotez n’importe où pour révéler la publication", "tap_to_reveal": "Appuyer pour afficher", + "load_embed": "Charger l'intégration", + "link_via_user": "%s via %s", "poll": { "vote": "Voter", "closed": "Fermé" @@ -153,6 +165,7 @@ "show_image": "Afficher l’image", "show_gif": "Afficher le GIF", "show_video_player": "Afficher le lecteur vidéo", + "share_link_in_post": "Partager le lien dans le message", "tap_then_hold_to_show_menu": "Appuyez et maintenez pour afficher le menu" }, "tag": { @@ -168,6 +181,12 @@ "private": "Seul·e·s leurs abonné·e·s peuvent voir ce message.", "private_from_me": "Seul·e·s mes abonné·e·s peuvent voir ce message.", "direct": "Seul·e l’utilisateur·rice mentionnée peut voir ce message." + }, + "translation": { + "translated_from": "Traduit de %s en utilisant %s", + "unknown_language": "Inconnu", + "unknown_provider": "Inconnu", + "show_original": "Afficher l’original" } }, "friendship": { @@ -218,10 +237,16 @@ "get_started": "Prise en main", "log_in": "Se connecter" }, + "login": { + "title": "Content de vous revoir", + "subtitle": "Connectez-vous sur le serveur sur lequel vous avez créé votre compte.", + "server_search_field": { + "placeholder": "Entrez l'URL ou recherchez votre serveur" + } + }, "server_picker": { "title": "Choisissez un serveur,\nn'importe quel serveur.", - "subtitle": "Choisissez une communauté en fonction de vos intérêts, de votre région ou de votre objectif général.", - "subtitle_extend": "Choisissez une communauté basée sur vos intérêts, votre région ou un but général. Chaque communauté est gérée par une organisation ou un individu entièrement indépendant.", + "subtitle": "Choisissez un serveur basé sur votre région, vos intérêts ou un généraliste. Vous pouvez toujours discuter avec n'importe qui sur Mastodon, indépendamment de vos serveurs.", "button": { "category": { "all": "Tout", @@ -248,8 +273,7 @@ "category": "CATÉGORIE" }, "input": { - "placeholder": "Trouvez un serveur ou rejoignez le vôtre...", - "search_servers_or_enter_url": "Rechercher des serveurs ou entrer une URL" + "search_servers_or_enter_url": "Rechercher parmi les communautés ou renseigner une URL" }, "empty_state": { "finding_servers": "Recherche des serveurs disponibles...", @@ -383,10 +407,12 @@ "attachment_broken": "Ce %s est brisé et ne peut pas être\ntéléversé sur Mastodon.", "description_photo": "Décrire cette photo pour les personnes malvoyantes...", "description_video": "Décrire cette vidéo pour les personnes malvoyantes...", - "load_failed": "Load Failed", - "upload_failed": "Upload Failed", - "can_not_recognize_this_media_attachment": "Can not regonize this media attachment", - "attachment_too_large": "Attachment too large" + "load_failed": "Échec du chargement", + "upload_failed": "Échec de l’envoi", + "can_not_recognize_this_media_attachment": "Impossible de reconnaître cette pièce jointe", + "attachment_too_large": "La pièce jointe est trop volumineuse", + "compressing_state": "Compression...", + "server_processing_state": "Traitement du serveur..." }, "poll": { "duration_time": "Durée: %s", @@ -396,7 +422,9 @@ "one_day": "1 Jour", "three_days": "3 jour", "seven_days": "7 jour", - "option_number": "Option %ld" + "option_number": "Option %ld", + "the_poll_is_invalid": "Le sondage est invalide", + "the_poll_has_empty_option": "Le sondage n'a pas d'options" }, "content_warning": { "placeholder": "Écrivez un avertissement précis ici..." @@ -417,7 +445,9 @@ "custom_emoji_picker": "Sélecteur d’émojis personnalisés", "enable_content_warning": "Basculer l’avertissement de contenu", "disable_content_warning": "Désactiver l'avertissement de contenu", - "post_visibility_menu": "Menu de Visibilité de la publication" + "post_visibility_menu": "Menu de Visibilité de la publication", + "post_options": "Options de publication", + "posting_as": "Publié en tant que %s" }, "keyboard": { "discard_post": "Rejeter la publication", @@ -433,15 +463,23 @@ "follows_you": "Vous suit" }, "dashboard": { - "posts": "publications", - "following": "abonnements", - "followers": "abonnés" + "my_posts": "messages", + "my_following": "abonnement", + "my_followers": "abonnés", + "other_posts": "publications", + "other_following": "abonnement", + "other_followers": "abonnés" }, "fields": { + "joined": "Ici depuis", "add_row": "Ajouter une rangée", "placeholder": { "label": "Étiquette", "content": "Contenu" + }, + "verified": { + "short": "Vérifié le %s", + "long": "La propriété de ce lien a été vérifiée le %s" } }, "segmented_control": { @@ -707,6 +745,18 @@ }, "bookmark": { "title": "Favoris" + }, + "followed_tags": { + "title": "Tags suivis", + "header": { + "posts": "messages", + "participants": "participants", + "posts_today": "messages aujourd'hui" + }, + "actions": { + "follow": "Suivre", + "unfollow": "Ne plus suivre" + } } } } diff --git a/Localization/StringsConvertor/input/gd.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/gd.lproj/Localizable.stringsdict index d0ccb5f41..9b3e69ea7 100644 --- a/Localization/StringsConvertor/input/gd.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/gd.lproj/Localizable.stringsdict @@ -62,6 +62,26 @@ %ld caractar + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ air fhàgail + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld charactar + two + %ld charactar + few + %ld caractaran + other + %ld caractar + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/gd.lproj/app.json b/Localization/StringsConvertor/input/gd.lproj/app.json index c1d17f813..5f27b8f00 100644 --- a/Localization/StringsConvertor/input/gd.lproj/app.json +++ b/Localization/StringsConvertor/input/gd.lproj/app.json @@ -51,6 +51,11 @@ "clean_cache": { "title": "Falamhaich an tasgadan", "message": "Chaidh %s a thasgadan fhalamhachadh." + }, + "translation_failed": { + "title": "Note", + "message": "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported.", + "button": "OK" } }, "controls": { @@ -75,9 +80,10 @@ "save_photo": "Sàbhail an dealbh", "copy_photo": "Dèan lethbhreac dhen dealbh", "sign_in": "Clàraich a-steach", - "sign_up": "Clàraich leinn", + "sign_up": "Cruthaich cunntas", "see_more": "Seall a bharrachd", "preview": "Ro-sheall", + "copy": "Copy", "share": "Co-roinn", "share_user": "Co-roinn %s", "share_post": "Co-roinn am post", @@ -91,12 +97,16 @@ "block_domain": "Bac %s", "unblock_domain": "Dì-bhac %s", "settings": "Roghainnean", - "delete": "Sguab às" + "delete": "Sguab às", + "translate_post": { + "title": "Translate from %s", + "unknown_language": "Unknown" + } }, "tabs": { "home": "Dachaigh", - "search": "Lorg", - "notification": "Brath", + "search_and_explore": "Search and Explore", + "notifications": "Notifications", "profile": "Pròifil" }, "keyboard": { @@ -132,15 +142,17 @@ "sensitive_content": "Susbaint fhrionasach", "media_content_warning": "Thoir gnogag àite sam bith gus a nochdadh", "tap_to_reveal": "Thoir gnogag gus a nochdadh", + "load_embed": "Load Embed", + "link_via_user": "%s via %s", "poll": { "vote": "Cuir bhòt", "closed": "Dùinte" }, "meta_entity": { - "url": "Link: %s", - "hashtag": "Hashtag: %s", - "mention": "Show Profile: %s", - "email": "Email address: %s" + "url": "Ceangal: %s", + "hashtag": "Taga hais: %s", + "mention": "Seall a’ phròifil: %s", + "email": "Seòladh puist-d: %s" }, "actions": { "reply": "Freagair", @@ -153,6 +165,7 @@ "show_image": "Seall an dealbh", "show_gif": "Seall an GIF", "show_video_player": "Seall cluicheadair video", + "share_link_in_post": "Share Link in Post", "tap_then_hold_to_show_menu": "Thoir gnogag ’s cùm sìos a shealltainn a’ chlàir-thaice" }, "tag": { @@ -168,6 +181,12 @@ "private": "Chan fhaic ach an luchd-leantainn aca am post seo.", "private_from_me": "Chan fhaic ach an luchd-leantainn agam am post seo.", "direct": "Chan fhaic ach an cleachdaiche air an dugadh iomradh am post seo." + }, + "translation": { + "translated_from": "Translated from %s using %s", + "unknown_language": "Unknown", + "unknown_provider": "Unknown", + "show_original": "Shown Original" } }, "friendship": { @@ -187,8 +206,8 @@ "unmute_user": "Dì-mhùch %s", "muted": "’Ga mhùchadh", "edit_info": "Deasaich", - "show_reblogs": "Show Reblogs", - "hide_reblogs": "Hide Reblogs" + "show_reblogs": "Seall na brosnachaidhean", + "hide_reblogs": "Falaich na brosnachaidhean" }, "timeline": { "filtered": "Criathraichte", @@ -218,10 +237,16 @@ "get_started": "Dèan toiseach-tòiseachaidh", "log_in": "Clàraich a-steach" }, + "login": { + "title": "Fàilte air ais", + "subtitle": "Clàraich a-steach air an fhrithealaiche far an do chruthaich thu an cunntas agad.", + "server_search_field": { + "placeholder": "Cuir a-steach URL an fhrithealaiche agad" + } + }, "server_picker": { "title": "Tha cleachdaichean Mhastodon air iomadh frithealaiche eadar-dhealaichte.", - "subtitle": "Tagh frithealaiche stèidhichte air d’ ùidhean, air far a bheil thu no fear coitcheann.", - "subtitle_extend": "Tagh frithealaiche stèidhichte air d’ ùidhean, air far a bheil thu no fear coitcheann. Tha gach frithealaiche fo stiùireadh buidhinn no neach neo-eisimeilich fa leth.", + "subtitle": "Tagh frithealaiche stèidhichte air na sgìre agad, d’ ùidhean, air far a bheil thu no fear coitcheann. ’S urrainn dhut fhathast conaltradh le duine sam bith air Mastodon ge b’ e na frithealaichean agaibh-se.", "button": { "category": { "all": "Na h-uile", @@ -248,8 +273,7 @@ "category": "ROINN-SEÒRSA" }, "input": { - "placeholder": "Lorg frithealaiche", - "search_servers_or_enter_url": "Lorg frithealaiche no cuir a-steach URL" + "search_servers_or_enter_url": "Lorg coimhearsnachd no cuir a-steach URL" }, "empty_state": { "finding_servers": "A’ lorg nam frithealaichean ri am faighinn…", @@ -383,10 +407,12 @@ "attachment_broken": "Seo %s a tha briste is cha ghabh\na luchdadh suas gu Mastodon.", "description_photo": "Mìnich an dealbh dhan fheadhainn air a bheil cion-lèirsinne…", "description_video": "Mìnich a’ video dhan fheadhainn air a bheil cion-lèirsinne…", - "load_failed": "Load Failed", - "upload_failed": "Upload Failed", - "can_not_recognize_this_media_attachment": "Can not regonize this media attachment", - "attachment_too_large": "Attachment too large" + "load_failed": "Dh’fhàillig leis an luchdadh", + "upload_failed": "Dh’fhàillig leis an luchdadh suas", + "can_not_recognize_this_media_attachment": "Cha do dh’aithnich sinn an ceanglachan meadhain seo", + "attachment_too_large": "Tha an ceanglachan ro mhòr", + "compressing_state": "’Ga dhùmhlachadh…", + "server_processing_state": "Tha am frithealaiche ’ga phròiseasadh…" }, "poll": { "duration_time": "Faide: %s", @@ -396,7 +422,9 @@ "one_day": "Latha", "three_days": "3 làithean", "seven_days": "Seachdain", - "option_number": "Roghainn %ld" + "option_number": "Roghainn %ld", + "the_poll_is_invalid": "Tha an cunntas-bheachd mì-dhligheach", + "the_poll_has_empty_option": "Tha roghainn fhalamh aig a’ chunntas-bheachd" }, "content_warning": { "placeholder": "Sgrìobh rabhadh pongail an-seo…" @@ -417,7 +445,9 @@ "custom_emoji_picker": "Roghnaichear nan Emoji gnàthaichte", "enable_content_warning": "Cuir rabhadh susbainte an comas", "disable_content_warning": "Cuir rabhadh susbainte à comas", - "post_visibility_menu": "Clàr-taice faicsinneachd a’ phuist" + "post_visibility_menu": "Clàr-taice faicsinneachd a’ phuist", + "post_options": "Roghainnean postaidh", + "posting_as": "A’ postadh mar %s" }, "keyboard": { "discard_post": "Tilg air falbh am post", @@ -433,15 +463,23 @@ "follows_you": "’Gad leantainn" }, "dashboard": { - "posts": "postaichean", - "following": "a’ leantainn", - "followers": "luchd-leantainn" + "my_posts": "posts", + "my_following": "following", + "my_followers": "followers", + "other_posts": "posts", + "other_following": "following", + "other_followers": "followers" }, "fields": { + "joined": "Joined", "add_row": "Cuir ràgh ris", "placeholder": { "label": "Leubail", "content": "Susbaint" + }, + "verified": { + "short": "Air a dhearbhadh %s", + "long": "Chaidh dearbhadh cò leis a tha an ceangal seo %s" } }, "segmented_control": { @@ -469,12 +507,12 @@ "message": "Dearbh dì-bhacadh %s" }, "confirm_show_reblogs": { - "title": "Show Reblogs", - "message": "Confirm to show reblogs" + "title": "Seall na brosnachaidhean", + "message": "Dearbh sealladh nam brosnachaidhean" }, "confirm_hide_reblogs": { - "title": "Hide Reblogs", - "message": "Confirm to hide reblogs" + "title": "Falaich na brosnachaidhean", + "message": "Dearbh falach nam brosnachaidhean" } }, "accessibility": { @@ -706,7 +744,19 @@ "accessibility_hint": "Thoir gnogag dhùbailte a’ leigeil seachad an draoidh seo" }, "bookmark": { - "title": "Bookmarks" + "title": "Comharran-lìn" + }, + "followed_tags": { + "title": "Followed Tags", + "header": { + "posts": "posts", + "participants": "participants", + "posts_today": "posts today" + }, + "actions": { + "follow": "Follow", + "unfollow": "Unfollow" + } } } } diff --git a/Localization/StringsConvertor/input/gl.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/gl.lproj/Localizable.stringsdict index ff9d87c18..51b146ed4 100644 --- a/Localization/StringsConvertor/input/gl.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/gl.lproj/Localizable.stringsdict @@ -50,6 +50,22 @@ %ld caracteres + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ restantes + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 caracter + other + %ld caracteres + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/gl.lproj/app.json b/Localization/StringsConvertor/input/gl.lproj/app.json index 3c394be95..f8bda509e 100644 --- a/Localization/StringsConvertor/input/gl.lproj/app.json +++ b/Localization/StringsConvertor/input/gl.lproj/app.json @@ -51,6 +51,11 @@ "clean_cache": { "title": "Limpar caché", "message": "Baleirouse %s da caché correctamente." + }, + "translation_failed": { + "title": "Nota", + "message": "Fallou a tradución. É posible que a administración non activase a tradución neste servidor ou que o servidor teña unha versión antiga de Mastodon que non ten soporte para a tradución.", + "button": "OK" } }, "controls": { @@ -75,9 +80,10 @@ "save_photo": "Gardar foto", "copy_photo": "Copiar foto", "sign_in": "Acceder", - "sign_up": "Inscribirse", + "sign_up": "Crear conta", "see_more": "Ver máis", "preview": "Vista previa", + "copy": "Copiar", "share": "Compartir", "share_user": "Compartir %s", "share_post": "Compartir publicación", @@ -91,12 +97,16 @@ "block_domain": "Bloquear a %s", "unblock_domain": "Desbloquear a %s", "settings": "Axustes", - "delete": "Eliminar" + "delete": "Eliminar", + "translate_post": { + "title": "Traducido do %s", + "unknown_language": "Descoñecido" + } }, "tabs": { "home": "Inicio", - "search": "Busca", - "notification": "Notificación", + "search_and_explore": "Buscar e Explorar", + "notifications": "Notificacións", "profile": "Perfil" }, "keyboard": { @@ -132,6 +142,8 @@ "sensitive_content": "Contido sensible", "media_content_warning": "Toca nalgures para mostrar", "tap_to_reveal": "Toca para mostrar", + "load_embed": "Cargar o contido", + "link_via_user": "%s vía %s", "poll": { "vote": "Votar", "closed": "Pechada" @@ -153,6 +165,7 @@ "show_image": "Mostrar a imaxe", "show_gif": "Mostrar GIF", "show_video_player": "Mostrar reprodutor de vídeo", + "share_link_in_post": "Compartir Ligazón na Publicación", "tap_then_hold_to_show_menu": "Toca e mantén preso para menú" }, "tag": { @@ -168,6 +181,12 @@ "private": "Só as seguidoras poden ver a publicación.", "private_from_me": "Só as miñas seguidoras poden ver esta publicación.", "direct": "Só a usuaria mencionada pode ver a publicación." + }, + "translation": { + "translated_from": "Traducido do %s usando %s", + "unknown_language": "Descoñecido", + "unknown_provider": "Descoñecido", + "show_original": "Mostrar o orixinal" } }, "friendship": { @@ -218,10 +237,16 @@ "get_started": "Crear conta", "log_in": "Acceder" }, + "login": { + "title": "Benvido outra vez", + "subtitle": "Conéctate ao servidor no que creaches a conta.", + "server_search_field": { + "placeholder": "Escribe o URL ou busca o teu servidor" + } + }, "server_picker": { "title": "Mastodon fórmano as persoas das diferentes comunidades.", - "subtitle": "Elixe unha comunidade en función dos teus intereses, rexión ou unha de propósito xeral.", - "subtitle_extend": "Elixe unha comunidade en función dos teus intereses, rexión ou unha de propósito xeral. Cada comunidade está xestionada por unha organización totalmente independente ou unha única persoa.", + "subtitle": "Elixe un servidor en función dos teus intereses, rexión o un de propósito xeral. Poderás conversar con calquera en Mastodon, independentemente do servidor que elixas.", "button": { "category": { "all": "Todo", @@ -248,8 +273,7 @@ "category": "CATEGORÍA" }, "input": { - "placeholder": "Buscar comunidades", - "search_servers_or_enter_url": "Busca un servidor ou escribe URL" + "search_servers_or_enter_url": "Busca comunidades ou escribe URL" }, "empty_state": { "finding_servers": "Buscando servidores dispoñibles...", @@ -386,7 +410,9 @@ "load_failed": "Fallou a carga", "upload_failed": "Erro na subida", "can_not_recognize_this_media_attachment": "Non se recoñece o tipo de multimedia", - "attachment_too_large": "Adxunto demasiado grande" + "attachment_too_large": "Adxunto demasiado grande", + "compressing_state": "Comprimindo...", + "server_processing_state": "Procesando no servidor..." }, "poll": { "duration_time": "Duración: %s", @@ -396,7 +422,9 @@ "one_day": "1 Día", "three_days": "3 Días", "seven_days": "7 Días", - "option_number": "Opción %ld" + "option_number": "Opción %ld", + "the_poll_is_invalid": "A enquisa non é válida", + "the_poll_has_empty_option": "A enquisa ten unha opción baleira" }, "content_warning": { "placeholder": "Escribe o teu aviso aquí..." @@ -417,7 +445,9 @@ "custom_emoji_picker": "Selector emoji personalizado", "enable_content_warning": "Marcar con Aviso sobre o contido", "disable_content_warning": "Retirar Aviso sobre o contido", - "post_visibility_menu": "Visibilidade da publicación" + "post_visibility_menu": "Visibilidade da publicación", + "post_options": "Opcións da publicación", + "posting_as": "Publicando como %s" }, "keyboard": { "discard_post": "Descartar publicación", @@ -433,15 +463,23 @@ "follows_you": "Séguete" }, "dashboard": { - "posts": "publicacións", - "following": "seguindo", - "followers": "seguidoras" + "my_posts": "publicacións", + "my_following": "seguindo", + "my_followers": "seguidoras", + "other_posts": "publicacións", + "other_following": "seguindo", + "other_followers": "seguidoras" }, "fields": { + "joined": "Uniuse", "add_row": "Engadir fila", "placeholder": { "label": "Etiqueta", "content": "Contido" + }, + "verified": { + "short": "Verificada en %s", + "long": "A propiedade desta ligazón foi verificada o %s" } }, "segmented_control": { @@ -707,6 +745,18 @@ }, "bookmark": { "title": "Marcadores" + }, + "followed_tags": { + "title": "Cancelos seguidos", + "header": { + "posts": "publicacións", + "participants": "participantes", + "posts_today": "publicacións de hoxe" + }, + "actions": { + "follow": "Seguir", + "unfollow": "Deixar de seguir" + } } } } diff --git a/Localization/StringsConvertor/input/he.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/he.lproj/Localizable.stringsdict new file mode 100644 index 000000000..63ed25f8a --- /dev/null +++ b/Localization/StringsConvertor/input/he.lproj/Localizable.stringsdict @@ -0,0 +1,581 @@ + + + + + a11y.plural.count.unread.notification + + NSStringLocalizedFormatKey + %#@notification_count_unread_notification@ + notification_count_unread_notification + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + התראה אחת שלא נקראה + two + שתי התראות שלא נקראו + many + %ld unread notifications + other + %ld התראות שלא נקראו + + + a11y.plural.count.input_limit_exceeds + + NSStringLocalizedFormatKey + Input limit exceeds %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 character + two + %ld characters + many + %ld characters + other + %ld characters + + + a11y.plural.count.input_limit_remains + + NSStringLocalizedFormatKey + Input limit remains %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 character + two + %ld characters + many + %ld characters + other + %ld characters + + + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ left + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 character + two + %ld characters + many + %ld characters + other + %ld characters + + + plural.count.followed_by_and_mutual + + NSStringLocalizedFormatKey + %#@names@%#@count_mutual@ + names + + one + + two + + many + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + + + count_mutual + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + Followed by %1$@, and another mutual + two + Followed by %1$@, and %ld mutuals + many + Followed by %1$@, and %ld mutuals + other + Followed by %1$@, and %ld mutuals + + + plural.count.metric_formatted.post + + NSStringLocalizedFormatKey + %@ %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + post + two + posts + many + posts + other + posts + + + plural.count.media + + NSStringLocalizedFormatKey + %#@media_count@ + media_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 media + two + %ld media + many + %ld media + other + %ld media + + + plural.count.post + + NSStringLocalizedFormatKey + %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 post + two + %ld posts + many + %ld posts + other + %ld posts + + + plural.count.favorite + + NSStringLocalizedFormatKey + %#@favorite_count@ + favorite_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 favorite + two + %ld favorites + many + %ld favorites + other + %ld favorites + + + plural.count.reblog + + NSStringLocalizedFormatKey + %#@reblog_count@ + reblog_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 reblog + two + %ld reblogs + many + %ld reblogs + other + %ld reblogs + + + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 reply + two + %ld replies + many + %ld replies + other + %ld replies + + + plural.count.vote + + NSStringLocalizedFormatKey + %#@vote_count@ + vote_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 vote + two + %ld votes + many + %ld votes + other + %ld votes + + + plural.count.voter + + NSStringLocalizedFormatKey + %#@voter_count@ + voter_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 voter + two + %ld voters + many + %ld voters + other + %ld voters + + + plural.people_talking + + NSStringLocalizedFormatKey + %#@count_people_talking@ + count_people_talking + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 people talking + two + %ld people talking + many + %ld people talking + other + %ld people talking + + + plural.count.following + + NSStringLocalizedFormatKey + %#@count_following@ + count_following + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 following + two + %ld following + many + %ld following + other + %ld following + + + plural.count.follower + + NSStringLocalizedFormatKey + %#@count_follower@ + count_follower + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 follower + two + %ld followers + many + %ld followers + other + %ld followers + + + date.year.left + + NSStringLocalizedFormatKey + %#@count_year_left@ + count_year_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 year left + two + %ld years left + many + %ld years left + other + %ld years left + + + date.month.left + + NSStringLocalizedFormatKey + %#@count_month_left@ + count_month_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 months left + two + %ld months left + many + %ld months left + other + %ld months left + + + date.day.left + + NSStringLocalizedFormatKey + %#@count_day_left@ + count_day_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 day left + two + %ld days left + many + %ld days left + other + %ld days left + + + date.hour.left + + NSStringLocalizedFormatKey + %#@count_hour_left@ + count_hour_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 hour left + two + %ld hours left + many + %ld hours left + other + %ld hours left + + + date.minute.left + + NSStringLocalizedFormatKey + %#@count_minute_left@ + count_minute_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 minute left + two + %ld minutes left + many + %ld minutes left + other + %ld minutes left + + + date.second.left + + NSStringLocalizedFormatKey + %#@count_second_left@ + count_second_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 second left + two + %ld seconds left + many + %ld seconds left + other + %ld seconds left + + + date.year.ago.abbr + + NSStringLocalizedFormatKey + %#@count_year_ago_abbr@ + count_year_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + לפני שנה + two + לפני שנתיים + many + %ldy ago + other + לפני %ld שנים + + + date.month.ago.abbr + + NSStringLocalizedFormatKey + %#@count_month_ago_abbr@ + count_month_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + לפני חודש + two + לפני חודשיים + many + %ldM ago + other + לפני %ld חודשים + + + date.day.ago.abbr + + NSStringLocalizedFormatKey + %#@count_day_ago_abbr@ + count_day_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + לפני יום + two + לפני יומיים + many + %ldd ago + other + לפני %ld ימים + + + date.hour.ago.abbr + + NSStringLocalizedFormatKey + %#@count_hour_ago_abbr@ + count_hour_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + לפני שעה + two + לפני שעתיים + many + %ldh ago + other + לפני %ld שעות + + + date.minute.ago.abbr + + NSStringLocalizedFormatKey + %#@count_minute_ago_abbr@ + count_minute_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + לפני דקה + two + לפני שתי דקות + many + %ldm ago + other + לפני %ld דקות + + + date.second.ago.abbr + + NSStringLocalizedFormatKey + %#@count_second_ago_abbr@ + count_second_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + לפני שנייה + two + לפני שתי שניות + many + %lds ago + other + לפני %ld שניות + + + + diff --git a/Localization/StringsConvertor/input/he.lproj/app.json b/Localization/StringsConvertor/input/he.lproj/app.json new file mode 100644 index 000000000..6b484e301 --- /dev/null +++ b/Localization/StringsConvertor/input/he.lproj/app.json @@ -0,0 +1,762 @@ +{ + "common": { + "alerts": { + "common": { + "please_try_again": "Please try again.", + "please_try_again_later": "Please try again later." + }, + "sign_up_failure": { + "title": "Sign Up Failure" + }, + "server_error": { + "title": "Server Error" + }, + "vote_failure": { + "title": "Vote Failure", + "poll_ended": "The poll has ended" + }, + "discard_post_content": { + "title": "Discard Draft", + "message": "Confirm to discard composed post content." + }, + "publish_post_failure": { + "title": "Publish Failure", + "message": "Failed to publish the post.\nPlease check your internet connection.", + "attachments_message": { + "video_attach_with_photo": "Cannot attach a video to a post that already contains images.", + "more_than_one_video": "Cannot attach more than one video." + } + }, + "edit_profile_failure": { + "title": "Edit Profile Error", + "message": "Cannot edit profile. Please try again." + }, + "sign_out": { + "title": "Sign Out", + "message": "Are you sure you want to sign out?", + "confirm": "Sign Out" + }, + "block_domain": { + "title": "Are you really, really sure you want to block the entire %s? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain and any of your followers from that domain will be removed.", + "block_entire_domain": "Block Domain" + }, + "save_photo_failure": { + "title": "Save Photo Failure", + "message": "Please enable the photo library access permission to save the photo." + }, + "delete_post": { + "title": "Delete Post", + "message": "Are you sure you want to delete this post?" + }, + "clean_cache": { + "title": "Clean Cache", + "message": "Successfully cleaned %s cache." + }, + "translation_failed": { + "title": "Note", + "message": "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported.", + "button": "OK" + } + }, + "controls": { + "actions": { + "back": "Back", + "next": "הבא", + "previous": "הקודם", + "open": "Open", + "add": "Add", + "remove": "Remove", + "edit": "Edit", + "save": "Save", + "ok": "OK", + "done": "Done", + "confirm": "Confirm", + "continue": "Continue", + "compose": "Compose", + "cancel": "Cancel", + "discard": "Discard", + "try_again": "Try Again", + "take_photo": "Take Photo", + "save_photo": "Save Photo", + "copy_photo": "Copy Photo", + "sign_in": "Log in", + "sign_up": "יצירת חשבון", + "see_more": "See More", + "preview": "Preview", + "copy": "Copy", + "share": "Share", + "share_user": "Share %s", + "share_post": "Share Post", + "open_in_safari": "Open in Safari", + "open_in_browser": "Open in Browser", + "find_people": "Find people to follow", + "manually_search": "Manually search instead", + "skip": "Skip", + "reply": "Reply", + "report_user": "Report %s", + "block_domain": "חסימת %s", + "unblock_domain": "הסרת חסימה מ־%s", + "settings": "הגדרות", + "delete": "Delete", + "translate_post": { + "title": "Translate from %s", + "unknown_language": "Unknown" + } + }, + "tabs": { + "home": "Home", + "search_and_explore": "Search and Explore", + "notifications": "Notifications", + "profile": "פרופיל" + }, + "keyboard": { + "common": { + "switch_to_tab": "Switch to %s", + "compose_new_post": "Compose New Post", + "show_favorites": "Show Favorites", + "open_settings": "Open Settings" + }, + "timeline": { + "previous_status": "Previous Post", + "next_status": "Next Post", + "open_status": "Open Post", + "open_author_profile": "Open Author's Profile", + "open_reblogger_profile": "Open Reblogger's Profile", + "reply_status": "Reply to Post", + "toggle_reblog": "Toggle Reblog on Post", + "toggle_favorite": "Toggle Favorite on Post", + "toggle_content_warning": "Toggle Content Warning", + "preview_image": "Preview Image" + }, + "segmented_control": { + "previous_section": "Previous Section", + "next_section": "Next Section" + } + }, + "status": { + "user_reblogged": "%s reblogged", + "user_replied_to": "Replied to %s", + "show_post": "Show Post", + "show_user_profile": "Show user profile", + "content_warning": "Content Warning", + "sensitive_content": "תוכן רגיש", + "media_content_warning": "Tap anywhere to reveal", + "tap_to_reveal": "Tap to reveal", + "load_embed": "Load Embed", + "link_via_user": "%s via %s", + "poll": { + "vote": "Vote", + "closed": "Closed" + }, + "meta_entity": { + "url": "Link: %s", + "hashtag": "Hashtag: %s", + "mention": "Show Profile: %s", + "email": "Email address: %s" + }, + "actions": { + "reply": "תגובה", + "reblog": "Reblog", + "unreblog": "Undo reblog", + "favorite": "Favorite", + "unfavorite": "Unfavorite", + "menu": "Menu", + "hide": "Hide", + "show_image": "Show image", + "show_gif": "Show GIF", + "show_video_player": "Show video player", + "share_link_in_post": "Share Link in Post", + "tap_then_hold_to_show_menu": "Tap then hold to show menu" + }, + "tag": { + "url": "URL", + "mention": "Mention", + "link": "Link", + "hashtag": "Hashtag", + "email": "Email", + "emoji": "Emoji" + }, + "visibility": { + "unlisted": "Everyone can see this post but not display in the public timeline.", + "private": "Only their followers can see this post.", + "private_from_me": "Only my followers can see this post.", + "direct": "Only mentioned user can see this post." + }, + "translation": { + "translated_from": "Translated from %s using %s", + "unknown_language": "Unknown", + "unknown_provider": "Unknown", + "show_original": "Shown Original" + } + }, + "friendship": { + "follow": "Follow", + "following": "Following", + "request": "Request", + "pending": "Pending", + "block": "Block", + "block_user": "Block %s", + "block_domain": "Block %s", + "unblock": "Unblock", + "unblock_user": "Unblock %s", + "blocked": "Blocked", + "mute": "Mute", + "mute_user": "Mute %s", + "unmute": "Unmute", + "unmute_user": "Unmute %s", + "muted": "Muted", + "edit_info": "Edit Info", + "show_reblogs": "Show Reblogs", + "hide_reblogs": "Hide Reblogs" + }, + "timeline": { + "filtered": "Filtered", + "timestamp": { + "now": "Now" + }, + "loader": { + "load_missing_posts": "Load missing posts", + "loading_missing_posts": "Loading missing posts...", + "show_more_replies": "Show more replies" + }, + "header": { + "no_status_found": "No Post Found", + "blocking_warning": "You can’t view this user's profile\nuntil you unblock them.\nYour profile looks like this to them.", + "user_blocking_warning": "You can’t view %s’s profile\nuntil you unblock them.\nYour profile looks like this to them.", + "blocked_warning": "You can’t view this user’s profile\nuntil they unblock you.", + "user_blocked_warning": "You can’t view %s’s profile\nuntil they unblock you.", + "suspended_warning": "This user has been suspended.", + "user_suspended_warning": "%s’s account has been suspended." + } + } + } + }, + "scene": { + "welcome": { + "slogan": "Social networking\nback in your hands.", + "get_started": "בואו נתחיל", + "log_in": "Log In" + }, + "login": { + "title": "Welcome back", + "subtitle": "Log you in on the server you created your account on.", + "server_search_field": { + "placeholder": "Enter URL or search for your server" + } + }, + "server_picker": { + "title": "Mastodon is made of users in different servers.", + "subtitle": "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers.", + "button": { + "category": { + "all": "All", + "all_accessiblity_description": "Category: All", + "academia": "academia", + "activism": "אקטיביזם", + "food": "אוכל", + "furry": "furry", + "games": "משחקים", + "general": "כללי", + "journalism": "journalism", + "lgbt": "להט\"ב", + "regional": "regional", + "art": "אומנות", + "music": "מוזיקה", + "tech": "tech" + }, + "see_less": "See Less", + "see_more": "See More" + }, + "label": { + "language": "LANGUAGE", + "users": "USERS", + "category": "CATEGORY" + }, + "input": { + "search_servers_or_enter_url": "Search communities or enter URL" + }, + "empty_state": { + "finding_servers": "Finding available servers...", + "bad_network": "Something went wrong while loading the data. Check your internet connection.", + "no_results": "No results" + } + }, + "register": { + "title": "Let’s get you set up on %s", + "lets_get_you_set_up_on_domain": "Let’s get you set up on %s", + "input": { + "avatar": { + "delete": "Delete" + }, + "username": { + "placeholder": "שם משתמש/ת", + "duplicate_prompt": "This username is taken." + }, + "display_name": { + "placeholder": "שם תצוגה" + }, + "email": { + "placeholder": "דוא״ל" + }, + "password": { + "placeholder": "סיסמה", + "require": "Your password needs at least:", + "character_limit": "8 characters", + "accessibility": { + "checked": "checked", + "unchecked": "unchecked" + }, + "hint": "Your password needs at least eight characters" + }, + "invite": { + "registration_user_invite_request": "Why do you want to join?" + } + }, + "error": { + "item": { + "username": "שם משתמש/ת", + "email": "דוא״ל", + "password": "סיסמה", + "agreement": "Agreement", + "locale": "Locale", + "reason": "Reason" + }, + "reason": { + "blocked": "%s contains a disallowed email provider", + "unreachable": "%s does not seem to exist", + "taken": "%s is already in use", + "reserved": "%s is a reserved keyword", + "accepted": "%s must be accepted", + "blank": "%s is required", + "invalid": "%s is invalid", + "too_long": "%s is too long", + "too_short": "%s is too short", + "inclusion": "%s is not a supported value" + }, + "special": { + "username_invalid": "Username must only contain alphanumeric characters and underscores", + "username_too_long": "Username is too long (can’t be longer than 30 characters)", + "email_invalid": "This is not a valid email address", + "password_too_short": "Password is too short (must be at least 8 characters)" + } + } + }, + "server_rules": { + "title": "Some ground rules.", + "subtitle": "These are set and enforced by the %s moderators.", + "prompt": "By continuing, you’re subject to the terms of service and privacy policy for %s.", + "terms_of_service": "terms of service", + "privacy_policy": "privacy policy", + "button": { + "confirm": "I Agree" + } + }, + "confirm_email": { + "title": "One last thing.", + "subtitle": "Tap the link we emailed to you to verify your account.", + "tap_the_link_we_emailed_to_you_to_verify_your_account": "Tap the link we emailed to you to verify your account", + "button": { + "open_email_app": "Open Email App", + "resend": "Resend" + }, + "dont_receive_email": { + "title": "Check your email", + "description": "Check if your email address is correct as well as your junk folder if you haven’t.", + "resend_email": "Resend Email" + }, + "open_email_app": { + "title": "Check your inbox.", + "description": "We just sent you an email. Check your junk folder if you haven’t.", + "mail": "Mail", + "open_email_client": "Open Email Client" + } + }, + "home_timeline": { + "title": "Home", + "navigation_bar_state": { + "offline": "Offline", + "new_posts": "See new posts", + "published": "Published!", + "Publishing": "Publishing post...", + "accessibility": { + "logo_label": "Logo Button", + "logo_hint": "Tap to scroll to top and tap again to previous location" + } + } + }, + "suggestion_account": { + "title": "Find People to Follow", + "follow_explain": "When you follow someone, you’ll see their posts in your home feed." + }, + "compose": { + "title": { + "new_post": "New Post", + "new_reply": "New Reply" + }, + "media_selection": { + "camera": "Take Photo", + "photo_library": "Photo Library", + "browse": "Browse" + }, + "content_input_placeholder": "Type or paste what’s on your mind", + "compose_action": "Publish", + "replying_to_user": "replying to %s", + "attachment": { + "photo": "photo", + "video": "video", + "attachment_broken": "This %s is broken and can’t be\nuploaded to Mastodon.", + "description_photo": "Describe the photo for the visually-impaired...", + "description_video": "Describe the video for the visually-impaired...", + "load_failed": "Load Failed", + "upload_failed": "Upload Failed", + "can_not_recognize_this_media_attachment": "Can not recognize this media attachment", + "attachment_too_large": "Attachment too large", + "compressing_state": "Compressing...", + "server_processing_state": "Server Processing..." + }, + "poll": { + "duration_time": "Duration: %s", + "thirty_minutes": "חצי שעה", + "one_hour": "שעה", + "six_hours": "6 שעות", + "one_day": "יום אחד", + "three_days": "3 ימים", + "seven_days": "7 ימים", + "option_number": "Option %ld", + "the_poll_is_invalid": "The poll is invalid", + "the_poll_has_empty_option": "The poll has empty option" + }, + "content_warning": { + "placeholder": "Write an accurate warning here..." + }, + "visibility": { + "public": "Public", + "unlisted": "Unlisted", + "private": "לעוקבים בלבד", + "direct": "Only people I mention" + }, + "auto_complete": { + "space_to_add": "Space to add" + }, + "accessibility": { + "append_attachment": "Add Attachment", + "append_poll": "Add Poll", + "remove_poll": "Remove Poll", + "custom_emoji_picker": "Custom Emoji Picker", + "enable_content_warning": "Enable Content Warning", + "disable_content_warning": "Disable Content Warning", + "post_visibility_menu": "Post Visibility Menu", + "post_options": "Post Options", + "posting_as": "Posting as %s" + }, + "keyboard": { + "discard_post": "Discard Post", + "publish_post": "Publish Post", + "toggle_poll": "Toggle Poll", + "toggle_content_warning": "Toggle Content Warning", + "append_attachment_entry": "Add Attachment - %s", + "select_visibility_entry": "Select Visibility - %s" + } + }, + "profile": { + "header": { + "follows_you": "Follows You" + }, + "dashboard": { + "my_posts": "posts", + "my_following": "following", + "my_followers": "followers", + "other_posts": "posts", + "other_following": "following", + "other_followers": "followers" + }, + "fields": { + "joined": "Joined", + "add_row": "Add Row", + "placeholder": { + "label": "Label", + "content": "Content" + }, + "verified": { + "short": "Verified on %s", + "long": "Ownership of this link was checked on %s" + } + }, + "segmented_control": { + "posts": "Posts", + "replies": "Replies", + "posts_and_replies": "Posts and Replies", + "media": "Media", + "about": "About" + }, + "relationship_action_alert": { + "confirm_mute_user": { + "title": "Mute Account", + "message": "Confirm to mute %s" + }, + "confirm_unmute_user": { + "title": "Unmute Account", + "message": "Confirm to unmute %s" + }, + "confirm_block_user": { + "title": "Block Account", + "message": "Confirm to block %s" + }, + "confirm_unblock_user": { + "title": "Unblock Account", + "message": "Confirm to unblock %s" + }, + "confirm_show_reblogs": { + "title": "Show Reblogs", + "message": "Confirm to show reblogs" + }, + "confirm_hide_reblogs": { + "title": "Hide Reblogs", + "message": "Confirm to hide reblogs" + } + }, + "accessibility": { + "show_avatar_image": "Show avatar image", + "edit_avatar_image": "Edit avatar image", + "show_banner_image": "Show banner image", + "double_tap_to_open_the_list": "Double tap to open the list" + } + }, + "follower": { + "title": "follower", + "footer": "Followers from other servers are not displayed." + }, + "following": { + "title": "following", + "footer": "Follows from other servers are not displayed." + }, + "familiarFollowers": { + "title": "Followers you familiar", + "followed_by_names": "Followed by %s" + }, + "favorited_by": { + "title": "Favorited By" + }, + "reblogged_by": { + "title": "Reblogged By" + }, + "search": { + "title": "Search", + "search_bar": { + "placeholder": "Search hashtags and users", + "cancel": "Cancel" + }, + "recommend": { + "button_text": "See All", + "hash_tag": { + "title": "Trending on Mastodon", + "description": "Hashtags that are getting quite a bit of attention", + "people_talking": "%s people are talking" + }, + "accounts": { + "title": "Accounts you might like", + "description": "You may like to follow these accounts", + "follow": "Follow" + } + }, + "searching": { + "segment": { + "all": "All", + "people": "People", + "hashtags": "Hashtags", + "posts": "Posts" + }, + "empty_state": { + "no_results": "No results" + }, + "recent_search": "Recent searches", + "clear": "Clear" + } + }, + "discovery": { + "tabs": { + "posts": "Posts", + "hashtags": "Hashtags", + "news": "News", + "community": "Community", + "for_you": "For You" + }, + "intro": "These are the posts gaining traction in your corner of Mastodon." + }, + "favorite": { + "title": "Your Favorites" + }, + "notification": { + "title": { + "Everything": "Everything", + "Mentions": "Mentions" + }, + "notification_description": { + "followed_you": "followed you", + "favorited_your_post": "favorited your post", + "reblogged_your_post": "reblogged your post", + "mentioned_you": "mentioned you", + "request_to_follow_you": "request to follow you", + "poll_has_ended": "poll has ended" + }, + "keyobard": { + "show_everything": "Show Everything", + "show_mentions": "Show Mentions" + }, + "follow_request": { + "accept": "Accept", + "accepted": "Accepted", + "reject": "reject", + "rejected": "Rejected" + } + }, + "thread": { + "back_title": "Post", + "title": "Post from %s" + }, + "settings": { + "title": "Settings", + "section": { + "appearance": { + "title": "Appearance", + "automatic": "Automatic", + "light": "Always Light", + "dark": "Always Dark" + }, + "look_and_feel": { + "title": "Look and Feel", + "use_system": "Use System", + "really_dark": "Really Dark", + "sorta_dark": "Sorta Dark", + "light": "Light" + }, + "notifications": { + "title": "Notifications", + "favorites": "Favorites my post", + "follows": "Follows me", + "boosts": "Reblogs my post", + "mentions": "Mentions me", + "trigger": { + "anyone": "anyone", + "follower": "a follower", + "follow": "anyone I follow", + "noone": "no one", + "title": "Notify me when" + } + }, + "preference": { + "title": "Preferences", + "true_black_dark_mode": "True black dark mode", + "disable_avatar_animation": "Disable animated avatars", + "disable_emoji_animation": "Disable animated emojis", + "using_default_browser": "Use default browser to open links", + "open_links_in_mastodon": "Open links in Mastodon" + }, + "boring_zone": { + "title": "The Boring Zone", + "account_settings": "Account Settings", + "terms": "Terms of Service", + "privacy": "Privacy Policy" + }, + "spicy_zone": { + "title": "The Spicy Zone", + "clear": "Clear Media Cache", + "signout": "Sign Out" + } + }, + "footer": { + "mastodon_description": "Mastodon is open source software. You can report issues on GitHub at %s (%s)" + }, + "keyboard": { + "close_settings_window": "Close Settings Window" + } + }, + "report": { + "title_report": "Report", + "title": "Report %s", + "step1": "Step 1 of 2", + "step2": "Step 2 of 2", + "content1": "Are there any other posts you’d like to add to the report?", + "content2": "Is there anything the moderators should know about this report?", + "report_sent_title": "Thanks for reporting, we’ll look into this.", + "send": "Send Report", + "skip_to_send": "Send without comment", + "text_placeholder": "Type or paste additional comments", + "reported": "REPORTED", + "step_one": { + "step_1_of_4": "Step 1 of 4", + "whats_wrong_with_this_post": "What's wrong with this post?", + "whats_wrong_with_this_account": "What's wrong with this account?", + "whats_wrong_with_this_username": "What's wrong with %s?", + "select_the_best_match": "Select the best match", + "i_dont_like_it": "I don’t like it", + "it_is_not_something_you_want_to_see": "It is not something you want to see", + "its_spam": "It’s spam", + "malicious_links_fake_engagement_or_repetetive_replies": "Malicious links, fake engagement, or repetetive replies", + "it_violates_server_rules": "It violates server rules", + "you_are_aware_that_it_breaks_specific_rules": "You are aware that it breaks specific rules", + "its_something_else": "It’s something else", + "the_issue_does_not_fit_into_other_categories": "The issue does not fit into other categories" + }, + "step_two": { + "step_2_of_4": "Step 2 of 4", + "which_rules_are_being_violated": "Which rules are being violated?", + "select_all_that_apply": "Select all that apply", + "i_just_don’t_like_it": "I just don’t like it" + }, + "step_three": { + "step_3_of_4": "Step 3 of 4", + "are_there_any_posts_that_back_up_this_report": "Are there any posts that back up this report?", + "select_all_that_apply": "Select all that apply" + }, + "step_four": { + "step_4_of_4": "Step 4 of 4", + "is_there_anything_else_we_should_know": "Is there anything else we should know?" + }, + "step_final": { + "dont_want_to_see_this": "Don’t want to see this?", + "when_you_see_something_you_dont_like_on_mastodon_you_can_remove_the_person_from_your_experience.": "When you see something you don’t like on Mastodon, you can remove the person from your experience.", + "unfollow": "Unfollow", + "unfollowed": "Unfollowed", + "unfollow_user": "Unfollow %s", + "mute_user": "Mute %s", + "you_wont_see_their_posts_or_reblogs_in_your_home_feed_they_wont_know_they_ve_been_muted": "You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted.", + "block_user": "Block %s", + "they_will_no_longer_be_able_to_follow_or_see_your_posts_but_they_can_see_if_theyve_been_blocked": "They will no longer be able to follow or see your posts, but they can see if they’ve been blocked.", + "while_we_review_this_you_can_take_action_against_user": "While we review this, you can take action against %s" + } + }, + "preview": { + "keyboard": { + "close_preview": "Close Preview", + "show_next": "Show Next", + "show_previous": "Show Previous" + } + }, + "account_list": { + "tab_bar_hint": "Current selected profile: %s. Double tap then hold to show account switcher", + "dismiss_account_switcher": "Dismiss Account Switcher", + "add_account": "Add Account" + }, + "wizard": { + "new_in_mastodon": "New in Mastodon", + "multiple_account_switch_intro_description": "Switch between multiple accounts by holding the profile button.", + "accessibility_hint": "Double tap to dismiss this wizard" + }, + "bookmark": { + "title": "Bookmarks" + }, + "followed_tags": { + "title": "Followed Tags", + "header": { + "posts": "posts", + "participants": "participants", + "posts_today": "posts today" + }, + "actions": { + "follow": "Follow", + "unfollow": "Unfollow" + } + } + } +} diff --git a/Localization/StringsConvertor/input/he.lproj/ios-infoPlist.json b/Localization/StringsConvertor/input/he.lproj/ios-infoPlist.json new file mode 100644 index 000000000..c6db73de0 --- /dev/null +++ b/Localization/StringsConvertor/input/he.lproj/ios-infoPlist.json @@ -0,0 +1,6 @@ +{ + "NSCameraUsageDescription": "Used to take photo for post status", + "NSPhotoLibraryAddUsageDescription": "Used to save photo into the Photo Library", + "NewPostShortcutItemTitle": "New Post", + "SearchShortcutItemTitle": "Search" +} diff --git a/Localization/StringsConvertor/input/hi.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/hi.lproj/Localizable.stringsdict index bdcae6ac9..788eb95fc 100644 --- a/Localization/StringsConvertor/input/hi.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/hi.lproj/Localizable.stringsdict @@ -15,7 +15,7 @@ one 1 unread notification other - %ld unread notification + %ld unread notifications a11y.plural.count.input_limit_exceeds @@ -50,6 +50,22 @@ %ld characters + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ left + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 character + other + %ld characters + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/hi.lproj/app.json b/Localization/StringsConvertor/input/hi.lproj/app.json index f0fedf75f..e7ea54abb 100644 --- a/Localization/StringsConvertor/input/hi.lproj/app.json +++ b/Localization/StringsConvertor/input/hi.lproj/app.json @@ -51,6 +51,11 @@ "clean_cache": { "title": "Clean Cache", "message": "Successfully cleaned %s cache." + }, + "translation_failed": { + "title": "Note", + "message": "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported.", + "button": "OK" } }, "controls": { @@ -74,10 +79,11 @@ "take_photo": "Take Photo", "save_photo": "Save Photo", "copy_photo": "Copy Photo", - "sign_in": "Sign In", - "sign_up": "Sign Up", + "sign_in": "Log in", + "sign_up": "Create account", "see_more": "See More", "preview": "Preview", + "copy": "Copy", "share": "Share", "share_user": "Share %s", "share_post": "Share Post", @@ -91,12 +97,16 @@ "block_domain": "Block %s", "unblock_domain": "Unblock %s", "settings": "Settings", - "delete": "Delete" + "delete": "Delete", + "translate_post": { + "title": "Translate from %s", + "unknown_language": "Unknown" + } }, "tabs": { "home": "Home", - "search": "Search", - "notification": "Notification", + "search_and_explore": "Search and Explore", + "notifications": "Notifications", "profile": "Profile" }, "keyboard": { @@ -132,6 +142,8 @@ "sensitive_content": "Sensitive Content", "media_content_warning": "Tap anywhere to reveal", "tap_to_reveal": "Tap to reveal", + "load_embed": "Load Embed", + "link_via_user": "%s via %s", "poll": { "vote": "Vote", "closed": "Closed" @@ -153,6 +165,7 @@ "show_image": "Show image", "show_gif": "Show GIF", "show_video_player": "Show video player", + "share_link_in_post": "Share Link in Post", "tap_then_hold_to_show_menu": "Tap then hold to show menu" }, "tag": { @@ -168,6 +181,12 @@ "private": "Only their followers can see this post.", "private_from_me": "Only my followers can see this post.", "direct": "Only mentioned user can see this post." + }, + "translation": { + "translated_from": "Translated from %s using %s", + "unknown_language": "Unknown", + "unknown_provider": "Unknown", + "show_original": "Shown Original" } }, "friendship": { @@ -218,10 +237,16 @@ "get_started": "Get Started", "log_in": "Log In" }, + "login": { + "title": "Welcome back", + "subtitle": "Log you in on the server you created your account on.", + "server_search_field": { + "placeholder": "Enter URL or search for your server" + } + }, "server_picker": { "title": "Mastodon is made of users in different servers.", - "subtitle": "Pick a server based on your interests, region, or a general purpose one.", - "subtitle_extend": "Pick a server based on your interests, region, or a general purpose one. Each server is operated by an entirely independent organization or individual.", + "subtitle": "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers.", "button": { "category": { "all": "All", @@ -248,8 +273,7 @@ "category": "CATEGORY" }, "input": { - "placeholder": "Search servers", - "search_servers_or_enter_url": "Search servers or enter URL" + "search_servers_or_enter_url": "Search communities or enter URL" }, "empty_state": { "finding_servers": "Finding available servers...", @@ -385,8 +409,10 @@ "description_video": "Describe the video for the visually-impaired...", "load_failed": "Load Failed", "upload_failed": "Upload Failed", - "can_not_recognize_this_media_attachment": "Can not regonize this media attachment", - "attachment_too_large": "Attachment too large" + "can_not_recognize_this_media_attachment": "Can not recognize this media attachment", + "attachment_too_large": "Attachment too large", + "compressing_state": "Compressing...", + "server_processing_state": "Server Processing..." }, "poll": { "duration_time": "Duration: %s", @@ -396,7 +422,9 @@ "one_day": "1 Day", "three_days": "3 Days", "seven_days": "7 Days", - "option_number": "Option %ld" + "option_number": "Option %ld", + "the_poll_is_invalid": "The poll is invalid", + "the_poll_has_empty_option": "The poll has empty option" }, "content_warning": { "placeholder": "Write an accurate warning here..." @@ -417,7 +445,9 @@ "custom_emoji_picker": "Custom Emoji Picker", "enable_content_warning": "Enable Content Warning", "disable_content_warning": "Disable Content Warning", - "post_visibility_menu": "Post Visibility Menu" + "post_visibility_menu": "Post Visibility Menu", + "post_options": "Post Options", + "posting_as": "Posting as %s" }, "keyboard": { "discard_post": "Discard Post", @@ -433,15 +463,23 @@ "follows_you": "Follows You" }, "dashboard": { - "posts": "posts", - "following": "following", - "followers": "followers" + "my_posts": "posts", + "my_following": "following", + "my_followers": "followers", + "other_posts": "posts", + "other_following": "following", + "other_followers": "followers" }, "fields": { + "joined": "Joined", "add_row": "Add Row", "placeholder": { "label": "Label", "content": "Content" + }, + "verified": { + "short": "Verified on %s", + "long": "Ownership of this link was checked on %s" } }, "segmented_control": { @@ -707,6 +745,18 @@ }, "bookmark": { "title": "Bookmarks" + }, + "followed_tags": { + "title": "Followed Tags", + "header": { + "posts": "posts", + "participants": "participants", + "posts_today": "posts today" + }, + "actions": { + "follow": "Follow", + "unfollow": "Unfollow" + } } } } diff --git a/Localization/StringsConvertor/input/id.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/id.lproj/Localizable.stringsdict index 2635defb8..3a39c085f 100644 --- a/Localization/StringsConvertor/input/id.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/id.lproj/Localizable.stringsdict @@ -13,13 +13,13 @@ NSStringFormatValueTypeKey ld other - %ld unread notification + %ld notifikasi belum dibaca a11y.plural.count.input_limit_exceeds NSStringLocalizedFormatKey - Input limit exceeds %#@character_count@ + Batas input melebihi %#@character_count@ character_count NSStringFormatSpecTypeKey @@ -33,7 +33,21 @@ a11y.plural.count.input_limit_remains NSStringLocalizedFormatKey - Input limit remains %#@character_count@ + Batas input masih tersisa %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld karakter + + + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + tersisa %#@character_count@ character_count NSStringFormatSpecTypeKey @@ -64,7 +78,7 @@ NSStringFormatValueTypeKey ld other - Followed by %1$@, and %ld mutuals + Diikuti oleh %1$@, dan %ld mutual plural.count.metric_formatted.post @@ -134,7 +148,7 @@ NSStringFormatValueTypeKey ld other - %ld reblogs + Posting ulang %ld plural.count.reply @@ -148,7 +162,7 @@ NSStringFormatValueTypeKey ld other - %ld replies + %ld balasan plural.count.vote @@ -190,7 +204,7 @@ NSStringFormatValueTypeKey ld other - %ld people talking + %ld obrolan plural.count.following @@ -204,7 +218,7 @@ NSStringFormatValueTypeKey ld other - %ld following + %ld mengikuti plural.count.follower diff --git a/Localization/StringsConvertor/input/id.lproj/app.json b/Localization/StringsConvertor/input/id.lproj/app.json index d942a22ad..558dad5d1 100644 --- a/Localization/StringsConvertor/input/id.lproj/app.json +++ b/Localization/StringsConvertor/input/id.lproj/app.json @@ -51,6 +51,11 @@ "clean_cache": { "title": "Bersihkan Cache", "message": "Berhasil menghapus %s cache." + }, + "translation_failed": { + "title": "Note", + "message": "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported.", + "button": "OK" } }, "controls": { @@ -75,9 +80,10 @@ "save_photo": "Simpan Foto", "copy_photo": "Salin Foto", "sign_in": "Masuk", - "sign_up": "Daftar", + "sign_up": "Buat akun", "see_more": "Lihat lebih banyak", "preview": "Pratinjau", + "copy": "Copy", "share": "Bagikan", "share_user": "Bagikan %s", "share_post": "Bagikan Postingan", @@ -91,12 +97,16 @@ "block_domain": "Blokir %s", "unblock_domain": "Berhenti memblokir %s", "settings": "Pengaturan", - "delete": "Hapus" + "delete": "Hapus", + "translate_post": { + "title": "Translate from %s", + "unknown_language": "Unknown" + } }, "tabs": { "home": "Beranda", - "search": "Cari", - "notification": "Notifikasi", + "search_and_explore": "Search and Explore", + "notifications": "Notifikasi", "profile": "Profil" }, "keyboard": { @@ -129,31 +139,34 @@ "show_post": "Tampilkan Postingan", "show_user_profile": "Tampilkan Profil Pengguna", "content_warning": "Peringatan Konten", - "sensitive_content": "Sensitive Content", + "sensitive_content": "Konten Sensitif", "media_content_warning": "Ketuk di mana saja untuk melihat", - "tap_to_reveal": "Tap to reveal", + "tap_to_reveal": "Ketuk untuk mengungkap", + "load_embed": "Load Embed", + "link_via_user": "%s via %s", "poll": { - "vote": "Vote", + "vote": "Pilih", "closed": "Ditutup" }, "meta_entity": { - "url": "Link: %s", - "hashtag": "Hashtag: %s", - "mention": "Show Profile: %s", - "email": "Email address: %s" + "url": "Tautan: %s", + "hashtag": "Tagar: %s", + "mention": "Tampilkan Profile: %s", + "email": "Alamat email: %s" }, "actions": { "reply": "Balas", "reblog": "Reblog", - "unreblog": "Undo reblog", + "unreblog": "Batalkan reblog", "favorite": "Favorit", - "unfavorite": "Unfavorite", + "unfavorite": "Batalkan favorit", "menu": "Menu", - "hide": "Hide", - "show_image": "Show image", - "show_gif": "Show GIF", - "show_video_player": "Show video player", - "tap_then_hold_to_show_menu": "Tap then hold to show menu" + "hide": "Sembunyikan", + "show_image": "Tampilkan gambar", + "show_gif": "Tampilkan GIF", + "show_video_player": "Tampilkan pemutar video", + "share_link_in_post": "Share Link in Post", + "tap_then_hold_to_show_menu": "Ketuk lalu tahan untuk menampilkan menu" }, "tag": { "url": "URL", @@ -164,17 +177,23 @@ "emoji": "Emoji" }, "visibility": { - "unlisted": "Everyone can see this post but not display in the public timeline.", - "private": "Only their followers can see this post.", - "private_from_me": "Only my followers can see this post.", - "direct": "Only mentioned user can see this post." + "unlisted": "Postingan ini dapat dilihat semua orang tetapi tidak ditampilkan di timeline publik.", + "private": "Hanya pengikut mereka yang dapat melihat postingan ini.", + "private_from_me": "Hanya pengikut saya yang dapat melihat postingan ini.", + "direct": "Hanya pengguna yang disebut yang dapat melihat postingan ini." + }, + "translation": { + "translated_from": "Translated from %s using %s", + "unknown_language": "Unknown", + "unknown_provider": "Unknown", + "show_original": "Shown Original" } }, "friendship": { "follow": "Ikuti", "following": "Mengikuti", - "request": "Request", - "pending": "Pending", + "request": "Minta", + "pending": "Tertunda", "block": "Blokir", "block_user": "Blokir %s", "block_domain": "Blokir %s", @@ -187,8 +206,8 @@ "unmute_user": "Berhenti membisukan %s", "muted": "Dibisukan", "edit_info": "Sunting Info", - "show_reblogs": "Show Reblogs", - "hide_reblogs": "Hide Reblogs" + "show_reblogs": "Tampilkan Reblog", + "hide_reblogs": "Sembunyikan Reblog" }, "timeline": { "filtered": "Tersaring", @@ -196,32 +215,38 @@ "now": "Sekarang" }, "loader": { - "load_missing_posts": "Load missing posts", - "loading_missing_posts": "Loading missing posts...", + "load_missing_posts": "Muat postingan yang hilang", + "loading_missing_posts": "Memuat postingan yang hilang...", "show_more_replies": "Tampilkan lebih banyak balasan" }, "header": { - "no_status_found": "No Post Found", - "blocking_warning": "You can’t view this user's profile\nuntil you unblock them.\nYour profile looks like this to them.", - "user_blocking_warning": "You can’t view %s’s profile\nuntil you unblock them.\nYour profile looks like this to them.", - "blocked_warning": "You can’t view this user’s profile\nuntil they unblock you.", - "user_blocked_warning": "You can’t view %s’s profile\nuntil they unblock you.", - "suspended_warning": "This user has been suspended.", - "user_suspended_warning": "%s’s account has been suspended." + "no_status_found": "Tidak Ditemukan Postingan", + "blocking_warning": "Anda tidak dapat melihat profil pengguna ini sampai Anda membuka blokir mereka.\nProfil Anda terlihat seperti ini bagi mereka.", + "user_blocking_warning": "Anda tidak dapat melihat profil %s sampai Anda membuka blokir mereka.\nProfil Anda terlihat seperti ini bagi mereka.", + "blocked_warning": "Anda tidak dapat melihat profil pengguna ini sampai mereka membuka blokir Anda.", + "user_blocked_warning": "Anda tidak dapat melihat profil %s sampai mereka membuka blokir Anda.", + "suspended_warning": "Pengguna ini telah ditangguhkan.", + "user_suspended_warning": "Akun %s telah ditangguhkan." } } } }, "scene": { "welcome": { - "slogan": "Social networking\nback in your hands.", - "get_started": "Get Started", - "log_in": "Log In" + "slogan": "Jejaring sosial dalam genggaman Anda.", + "get_started": "Mulai", + "log_in": "Login" + }, + "login": { + "title": "Selamat datang kembali", + "subtitle": "Masuklah pada server yang Anda buat di mana akun Anda berada.", + "server_search_field": { + "placeholder": "Masukkan URL atau pencarian di server Anda" + } }, "server_picker": { "title": "Pilih sebuah server,\nserver manapun.", - "subtitle": "Pick a server based on your interests, region, or a general purpose one.", - "subtitle_extend": "Pick a server based on your interests, region, or a general purpose one. Each server is operated by an entirely independent organization or individual.", + "subtitle": "Pilih server berdasarkan agamamu, minat, atau subjek umum lainnya. Kamu masih bisa berkomunikasi dengan semua orang di Mastodon, tanpa memperdulikan server Anda.", "button": { "category": { "all": "Semua", @@ -248,8 +273,7 @@ "category": "KATEGORI" }, "input": { - "placeholder": "Search servers", - "search_servers_or_enter_url": "Search servers or enter URL" + "search_servers_or_enter_url": "Cari komunitas atau masukkan URL" }, "empty_state": { "finding_servers": "Mencari server yang tersedia...", @@ -259,7 +283,7 @@ }, "register": { "title": "Beritahu kami tentang diri Anda.", - "lets_get_you_set_up_on_domain": "Let’s get you set up on %s", + "lets_get_you_set_up_on_domain": "Mari kita siapkan Anda di %s", "input": { "avatar": { "delete": "Hapus" @@ -269,18 +293,18 @@ "duplicate_prompt": "Nama pengguna ini sudah diambil." }, "display_name": { - "placeholder": "display name" + "placeholder": "nama yang ditampilkan" }, "email": { "placeholder": "surel" }, "password": { "placeholder": "kata sandi", - "require": "Your password needs at least:", - "character_limit": "8 characters", + "require": "Kata sandi Anda harus memiliki setidaknya:", + "character_limit": "8 karakter", "accessibility": { - "checked": "checked", - "unchecked": "unchecked" + "checked": "dicentang", + "unchecked": "tidak dicentang" }, "hint": "Kata sandi Anda harus memiliki sekurang-kurangnya delapan karakter" }, @@ -294,23 +318,23 @@ "email": "Surel", "password": "Kata sandi", "agreement": "Persetujuan", - "locale": "Locale", + "locale": "Lokal", "reason": "Alasan" }, "reason": { "blocked": "%s mengandung penyedia surel yang dilarang", "unreachable": "%s sepertinya tidak ada", "taken": "%s sudah digunakan", - "reserved": "%s is a reserved keyword", + "reserved": "%s adalah kata kunci yang dipesan", "accepted": "%s harus diterima", "blank": "%s diperlukan", "invalid": "%s tidak valid", "too_long": "%s terlalu panjang", "too_short": "%s terlalu pendek", - "inclusion": "%s is not a supported value" + "inclusion": "%s bukan nilai yang didukung" }, "special": { - "username_invalid": "Username must only contain alphanumeric characters and underscores", + "username_invalid": "Nama pengguna hanya berisi angka karakter dan garis bawah", "username_too_long": "Nama pengguna terlalu panjang (tidak boleh lebih dari 30 karakter)", "email_invalid": "Ini bukan alamat surel yang valid", "password_too_short": "Kata sandi terlalu pendek (harus sekurang-kurangnya 8 karakter)" @@ -320,7 +344,7 @@ "server_rules": { "title": "Beberapa aturan dasar.", "subtitle": "Peraturan ini ditetapkan oleh admin %s.", - "prompt": "By continuing, you’re subject to the terms of service and privacy policy for %s.", + "prompt": "Dengan melanjutkan, Anda tunduk pada ketentuan layanan dan kebijakan privasi untuk %s.", "terms_of_service": "kebijakan layanan", "privacy_policy": "kebijakan privasi", "button": { @@ -330,10 +354,10 @@ "confirm_email": { "title": "Satu hal lagi.", "subtitle": "Kami baru saja mengirim sebuah surel ke %s,\nketuk tautannya untuk mengkonfirmasi akun Anda.", - "tap_the_link_we_emailed_to_you_to_verify_your_account": "Tap the link we emailed to you to verify your account", + "tap_the_link_we_emailed_to_you_to_verify_your_account": "Ketuk tautan yang kami kirimkan kepada Anda via email untuk memverifikasi akun Anda", "button": { "open_email_app": "Buka Aplikasi Surel", - "resend": "Resend" + "resend": "Kirim ulang" }, "dont_receive_email": { "title": "Periksa surel Anda", @@ -343,8 +367,8 @@ "open_email_app": { "title": "Periksa kotak masuk Anda.", "description": "Kami baru saja mengirimkan Anda sebuah surel. Periksa folder junk Anda jika Anda belum memeriksanya.", - "mail": "Mail", - "open_email_client": "Open Email Client" + "mail": "Pesan", + "open_email_client": "Buka Email Klien" } }, "home_timeline": { @@ -355,24 +379,24 @@ "published": "Dipublikasikan!", "Publishing": "Mempublikasikan postingan...", "accessibility": { - "logo_label": "Logo Button", - "logo_hint": "Tap to scroll to top and tap again to previous location" + "logo_label": "Tombol Logo", + "logo_hint": "Ketuk untuk menggulir ke atas dan ketuk lagi ke lokasi sebelumnya" } } }, "suggestion_account": { - "title": "Find People to Follow", + "title": "Temukan Orang untuk Diikuti", "follow_explain": "Ketika Anda mengikuti seseorang, Anda akan melihat postingan mereka di beranda Anda." }, "compose": { "title": { "new_post": "Postingan Baru", - "new_reply": "New Reply" + "new_reply": "Pesan Baru" }, "media_selection": { - "camera": "Take Photo", - "photo_library": "Photo Library", - "browse": "Browse" + "camera": "Ambil Foto", + "photo_library": "Galeri Foto", + "browse": "Telusuri" }, "content_input_placeholder": "Ketik atau tempel apa yang Anda pada pikiran Anda", "compose_action": "Publikasikan", @@ -383,10 +407,12 @@ "attachment_broken": "%s ini rusak dan tidak dapat diunggah ke Mastodon.", "description_photo": "Jelaskan fotonya untuk mereka yang tidak dapat melihat dengan jelas...", "description_video": "Jelaskan videonya untuk mereka yang tidak dapat melihat dengan jelas...", - "load_failed": "Load Failed", - "upload_failed": "Upload Failed", - "can_not_recognize_this_media_attachment": "Can not regonize this media attachment", - "attachment_too_large": "Attachment too large" + "load_failed": "Gagal Memuat", + "upload_failed": "Gagal Mengunggah", + "can_not_recognize_this_media_attachment": "Tidak dapat mengenali lampiran media ini", + "attachment_too_large": "Lampiran terlalu besar", + "compressing_state": "Mengompres...", + "server_processing_state": "Server Memproses..." }, "poll": { "duration_time": "Durasi: %s", @@ -396,32 +422,36 @@ "one_day": "1 Hari", "three_days": "3 Hari", "seven_days": "7 Hari", - "option_number": "Option %ld" + "option_number": "Opsi %ld", + "the_poll_is_invalid": "Japat tidak valid", + "the_poll_has_empty_option": "Japat memiliki opsi kosong" }, "content_warning": { "placeholder": "Tulis peringatan yang akurat di sini..." }, "visibility": { "public": "Publik", - "unlisted": "Unlisted", + "unlisted": "Tidak terdaftar", "private": "Pengikut saja", "direct": "Hanya orang yang saya sebut" }, "auto_complete": { - "space_to_add": "Space to add" + "space_to_add": "Tekan spasi untuk menambahkan" }, "accessibility": { "append_attachment": "Tambahkan Lampiran", "append_poll": "Tambahkan Japat", "remove_poll": "Hapus Japat", - "custom_emoji_picker": "Custom Emoji Picker", + "custom_emoji_picker": "Pemilih Emoji Kustom", "enable_content_warning": "Aktifkan Peringatan Konten", "disable_content_warning": "Nonaktifkan Peringatan Konten", - "post_visibility_menu": "Post Visibility Menu" + "post_visibility_menu": "Menu Visibilitas Postingan", + "post_options": "Opsi Postingan", + "posting_as": "Posting sebagai %s" }, "keyboard": { - "discard_post": "Discard Post", - "publish_post": "Publish Post", + "discard_post": "Hapus Postingan", + "publish_post": "Publikasikan Postingan", "toggle_poll": "Toggle Poll", "toggle_content_warning": "Toggle Content Warning", "append_attachment_entry": "Tambahkan Lampiran - %s", @@ -430,43 +460,51 @@ }, "profile": { "header": { - "follows_you": "Follows You" + "follows_you": "Mengikutimu" }, "dashboard": { - "posts": "postingan", - "following": "mengikuti", - "followers": "pengikut" + "my_posts": "posts", + "my_following": "following", + "my_followers": "followers", + "other_posts": "posts", + "other_following": "following", + "other_followers": "followers" }, "fields": { - "add_row": "Add Row", + "joined": "Bergabung", + "add_row": "Tambah Baris", "placeholder": { "label": "Label", "content": "Isi" + }, + "verified": { + "short": "Verifikasi %s", + "long": "Kepemilikan tautan ini dapat dicek pada %s" } }, "segmented_control": { "posts": "Postingan", "replies": "Balasan", - "posts_and_replies": "Posts and Replies", + "posts_and_replies": "Kirim dan Balas", "media": "Media", - "about": "About" + "about": "Tentang" }, "relationship_action_alert": { "confirm_mute_user": { - "title": "Mute Account", - "message": "Confirm to mute %s" + "title": "Bisukan Akun", + "message": "Konfirmasi untuk bisukan %s" }, "confirm_unmute_user": { "title": "Berhenti Membisukan Akun", - "message": "Confirm to unmute %s" + "message": "Konfirmasi untuk membisukan %s" }, "confirm_block_user": { - "title": "Block Account", - "message": "Confirm to block %s" + "title": "Blokir Akun", + "message": "Konfirmasi memblokir %s" }, "confirm_unblock_user": { - "title": "Unblock Account", - "message": "Confirm to unblock %s" + "title": "Buka Blokir Akun", + "message": "Konfirmasi membuka blokir %s" }, "confirm_show_reblogs": { "title": "Show Reblogs", @@ -478,23 +516,23 @@ } }, "accessibility": { - "show_avatar_image": "Show avatar image", - "edit_avatar_image": "Edit avatar image", + "show_avatar_image": "Tampilkan gambar avatar", + "edit_avatar_image": "Ubah gambar avatar", "show_banner_image": "Show banner image", - "double_tap_to_open_the_list": "Double tap to open the list" + "double_tap_to_open_the_list": "Ketuk ganda untuk membuka daftar" } }, "follower": { - "title": "follower", + "title": "pengikut", "footer": "Followers from other servers are not displayed." }, "following": { - "title": "following", + "title": "mengikuti", "footer": "Follows from other servers are not displayed." }, "familiarFollowers": { "title": "Followers you familiar", - "followed_by_names": "Followed by %s" + "followed_by_names": "Diikuti oleh %s" }, "favorited_by": { "title": "Favorited By" @@ -511,9 +549,9 @@ "recommend": { "button_text": "Lihat Semua", "hash_tag": { - "title": "Trending on Mastodon", + "title": "Sedang Tren di Mastodon", "description": "Hashtags that are getting quite a bit of attention", - "people_talking": "%s people are talking" + "people_talking": "%s orang sedang membicarakan" }, "accounts": { "title": "Akun-akun yang mungkin Anda sukai", @@ -531,22 +569,22 @@ "empty_state": { "no_results": "Tidak ada hasil" }, - "recent_search": "Recent searches", + "recent_search": "Pencarian terbaru", "clear": "Hapus" } }, "discovery": { "tabs": { "posts": "Posts", - "hashtags": "Hashtags", - "news": "News", - "community": "Community", - "for_you": "For You" + "hashtags": "Tagar", + "news": "Berita", + "community": "Komunitas", + "for_you": "Untuk Anda" }, "intro": "These are the posts gaining traction in your corner of Mastodon." }, "favorite": { - "title": "Your Favorites" + "title": "Favorit Anda" }, "notification": { "title": { @@ -554,11 +592,11 @@ "Mentions": "Sebutan" }, "notification_description": { - "followed_you": "followed you", - "favorited_your_post": "favorited your post", + "followed_you": "mengikutimu", + "favorited_your_post": "menyukai postinganmu", "reblogged_your_post": "reblogged your post", - "mentioned_you": "mentioned you", - "request_to_follow_you": "request to follow you", + "mentioned_you": "menyebutmu", + "request_to_follow_you": "meminta mengikutimu", "poll_has_ended": "poll has ended" }, "keyobard": { @@ -566,10 +604,10 @@ "show_mentions": "Tampilkan Sebutan" }, "follow_request": { - "accept": "Accept", - "accepted": "Accepted", - "reject": "reject", - "rejected": "Rejected" + "accept": "Menerima", + "accepted": "Diterima", + "reject": "menolak", + "rejected": "Ditolak" } }, "thread": { @@ -586,11 +624,11 @@ "dark": "Selalu Gelap" }, "look_and_feel": { - "title": "Look and Feel", + "title": "Lihat dan Rasakan", "use_system": "Use System", - "really_dark": "Really Dark", - "sorta_dark": "Sorta Dark", - "light": "Light" + "really_dark": "Sangat Gelap", + "sorta_dark": "Agak Gelap", + "light": "Terang" }, "notifications": { "title": "Notifikasi", @@ -602,17 +640,17 @@ "anyone": "siapapun", "follower": "seorang pengikut", "follow": "siapapun yang saya ikuti", - "noone": "no one", + "noone": "tidak ada", "title": "Beritahu saya ketika" } }, "preference": { "title": "Preferensi", "true_black_dark_mode": "True black dark mode", - "disable_avatar_animation": "Disable animated avatars", - "disable_emoji_animation": "Disable animated emojis", + "disable_avatar_animation": "Nonaktifkan animasi avatar", + "disable_emoji_animation": "Nonaktifkan animasi emoji", "using_default_browser": "Use default browser to open links", - "open_links_in_mastodon": "Open links in Mastodon" + "open_links_in_mastodon": "Buka tautan di Mastodon" }, "boring_zone": { "title": "Zona Membosankan", @@ -634,79 +672,91 @@ } }, "report": { - "title_report": "Report", + "title_report": "Laporkan", "title": "Laporkan %s", "step1": "Langkah 1 dari 2", "step2": "Langkah 2 dari 2", "content1": "Apakah ada postingan lain yang ingin Anda tambahkan ke laporannya?", "content2": "Ada yang moderator harus tahu tentang laporan ini?", - "report_sent_title": "Thanks for reporting, we’ll look into this.", + "report_sent_title": "Terima kasih atas pelaporan Anda, kami akan memeriksa ini lebih lanjut.", "send": "Kirim Laporan", "skip_to_send": "Kirim tanpa komentar", "text_placeholder": "Ketik atau tempel komentar tambahan", - "reported": "REPORTED", + "reported": "DILAPORKAN", "step_one": { - "step_1_of_4": "Step 1 of 4", - "whats_wrong_with_this_post": "What's wrong with this post?", - "whats_wrong_with_this_account": "What's wrong with this account?", - "whats_wrong_with_this_username": "What's wrong with %s?", - "select_the_best_match": "Select the best match", - "i_dont_like_it": "I don’t like it", - "it_is_not_something_you_want_to_see": "It is not something you want to see", - "its_spam": "It’s spam", - "malicious_links_fake_engagement_or_repetetive_replies": "Malicious links, fake engagement, or repetetive replies", - "it_violates_server_rules": "It violates server rules", - "you_are_aware_that_it_breaks_specific_rules": "You are aware that it breaks specific rules", - "its_something_else": "It’s something else", + "step_1_of_4": "Langkah 1 dari 4", + "whats_wrong_with_this_post": "Ada yang salah dengan postingan ini?", + "whats_wrong_with_this_account": "Ada yang salah dengan akun ini?", + "whats_wrong_with_this_username": "Ada yang salah dengan %s?", + "select_the_best_match": "Pilih yang paling cocok", + "i_dont_like_it": "Saya tidak suka", + "it_is_not_something_you_want_to_see": "Ini bukan sesuatu yang Anda ingin lihat", + "its_spam": "Ini sampah", + "malicious_links_fake_engagement_or_repetetive_replies": "Tautan berbahaya, engagement palsu, atau balasan berulang", + "it_violates_server_rules": "Melanggar ketentuan server", + "you_are_aware_that_it_breaks_specific_rules": "Anda yakin bahwa ini melanggar ketentuan khusus", + "its_something_else": "Alasan lainnya", "the_issue_does_not_fit_into_other_categories": "The issue does not fit into other categories" }, "step_two": { - "step_2_of_4": "Step 2 of 4", - "which_rules_are_being_violated": "Which rules are being violated?", - "select_all_that_apply": "Select all that apply", + "step_2_of_4": "Langkah 2 dari 4", + "which_rules_are_being_violated": "Ketentuan manakah yang dilanggar?", + "select_all_that_apply": "Pilih semua yang berlaku", "i_just_don’t_like_it": "I just don’t like it" }, "step_three": { - "step_3_of_4": "Step 3 of 4", + "step_3_of_4": "Langkah 3 dari 4", "are_there_any_posts_that_back_up_this_report": "Are there any posts that back up this report?", - "select_all_that_apply": "Select all that apply" + "select_all_that_apply": "Pilih semua yang berlaku" }, "step_four": { - "step_4_of_4": "Step 4 of 4", - "is_there_anything_else_we_should_know": "Is there anything else we should know?" + "step_4_of_4": "Langkah 4 dari 4", + "is_there_anything_else_we_should_know": "Ada hal lain yang perlu kami ketahui?" }, "step_final": { - "dont_want_to_see_this": "Don’t want to see this?", + "dont_want_to_see_this": "Tidak ingin melihat ini?", "when_you_see_something_you_dont_like_on_mastodon_you_can_remove_the_person_from_your_experience.": "When you see something you don’t like on Mastodon, you can remove the person from your experience.", - "unfollow": "Unfollow", + "unfollow": "Berhenti ikuti", "unfollowed": "Unfollowed", - "unfollow_user": "Unfollow %s", - "mute_user": "Mute %s", + "unfollow_user": "Berhenti ikuti %s", + "mute_user": "Senyapkan %s", "you_wont_see_their_posts_or_reblogs_in_your_home_feed_they_wont_know_they_ve_been_muted": "You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted.", - "block_user": "Block %s", + "block_user": "Blokir %s", "they_will_no_longer_be_able_to_follow_or_see_your_posts_but_they_can_see_if_theyve_been_blocked": "They will no longer be able to follow or see your posts, but they can see if they’ve been blocked.", "while_we_review_this_you_can_take_action_against_user": "While we review this, you can take action against %s" } }, "preview": { "keyboard": { - "close_preview": "Close Preview", - "show_next": "Show Next", - "show_previous": "Show Previous" + "close_preview": "Tutup Pratinjau", + "show_next": "Tampilkan Berikutnya", + "show_previous": "Tampilkan Sebelumnya" } }, "account_list": { - "tab_bar_hint": "Current selected profile: %s. Double tap then hold to show account switcher", + "tab_bar_hint": "Profil yang dipilih saat ini: %s. Ketuk dua kali kemudian tahan untuk tampilkan ikon beralih akun", "dismiss_account_switcher": "Dismiss Account Switcher", - "add_account": "Add Account" + "add_account": "Tambah Akun" }, "wizard": { - "new_in_mastodon": "New in Mastodon", + "new_in_mastodon": "Baru di Mastodon", "multiple_account_switch_intro_description": "Switch between multiple accounts by holding the profile button.", "accessibility_hint": "Double tap to dismiss this wizard" }, "bookmark": { - "title": "Bookmarks" + "title": "Tandai" + }, + "followed_tags": { + "title": "Followed Tags", + "header": { + "posts": "posts", + "participants": "participants", + "posts_today": "posts today" + }, + "actions": { + "follow": "Follow", + "unfollow": "Unfollow" + } } } } diff --git a/Localization/StringsConvertor/input/id.lproj/ios-infoPlist.json b/Localization/StringsConvertor/input/id.lproj/ios-infoPlist.json index 0dde7c29e..77bf594c3 100644 --- a/Localization/StringsConvertor/input/id.lproj/ios-infoPlist.json +++ b/Localization/StringsConvertor/input/id.lproj/ios-infoPlist.json @@ -1,6 +1,6 @@ { - "NSCameraUsageDescription": "Used to take photo for post status", - "NSPhotoLibraryAddUsageDescription": "Used to save photo into the Photo Library", + "NSCameraUsageDescription": "Gunakan untuk mengambil foto untuk postingan status", + "NSPhotoLibraryAddUsageDescription": "Gunakan untuk menyimpan foto ke dalam Galeri Foto", "NewPostShortcutItemTitle": "Postingan Baru", "SearchShortcutItemTitle": "Cari" } diff --git a/Localization/StringsConvertor/input/is.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/is.lproj/Localizable.stringsdict new file mode 100644 index 000000000..03b29f09b --- /dev/null +++ b/Localization/StringsConvertor/input/is.lproj/Localizable.stringsdict @@ -0,0 +1,465 @@ + + + + + a11y.plural.count.unread.notification + + NSStringLocalizedFormatKey + %#@notification_count_unread_notification@ + notification_count_unread_notification + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 ólesin tilkynning + other + %ld ólesnar tilkynningar + + + a11y.plural.count.input_limit_exceeds + + NSStringLocalizedFormatKey + Inntak fer fram úr takmörkunum %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 stafur + other + %ld stafir + + + a11y.plural.count.input_limit_remains + + NSStringLocalizedFormatKey + Inntakstakmörk haldast %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 stafur + other + %ld stafir + + + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ eftir + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 stafur + other + %ld stafir + + + plural.count.followed_by_and_mutual + + NSStringLocalizedFormatKey + %#@names@%#@count_mutual@ + names + + one + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + + + count_mutual + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + Fylgt af %1$@ og öðrum sameiginlegum + other + Fylgt af %1$@ og %ld sameiginlegum + + + plural.count.metric_formatted.post + + NSStringLocalizedFormatKey + %@ %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + færsla + other + færslur + + + plural.count.media + + NSStringLocalizedFormatKey + %#@media_count@ + media_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 gagnamiðill + other + %ld gagnamiðlar + + + plural.count.post + + NSStringLocalizedFormatKey + %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 færsla + other + %ld færslur + + + plural.count.favorite + + NSStringLocalizedFormatKey + %#@favorite_count@ + favorite_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 eftirlæti + other + %ld eftirlæti + + + plural.count.reblog + + NSStringLocalizedFormatKey + %#@reblog_count@ + reblog_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 endurbirting + other + %ld endurbirtingar + + + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 svar + other + %ld svör + + + plural.count.vote + + NSStringLocalizedFormatKey + %#@vote_count@ + vote_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 atkvæði + other + %ld atkvæði + + + plural.count.voter + + NSStringLocalizedFormatKey + %#@voter_count@ + voter_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 kjósandi + other + %ld kjósendur + + + plural.people_talking + + NSStringLocalizedFormatKey + %#@count_people_talking@ + count_people_talking + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 aðili að spjalla + other + %ld aðilar að spjalla + + + plural.count.following + + NSStringLocalizedFormatKey + %#@count_following@ + count_following + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 fylgist með + other + %ld fylgjast með + + + plural.count.follower + + NSStringLocalizedFormatKey + %#@count_follower@ + count_follower + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 fylgjandi + other + %ld fylgjendur + + + date.year.left + + NSStringLocalizedFormatKey + %#@count_year_left@ + count_year_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 ár eftir + other + %ld ár eftir + + + date.month.left + + NSStringLocalizedFormatKey + %#@count_month_left@ + count_month_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 mánuður eftir + other + %ld mánuðir eftir + + + date.day.left + + NSStringLocalizedFormatKey + %#@count_day_left@ + count_day_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 dagur eftir + other + %ld dagar eftir + + + date.hour.left + + NSStringLocalizedFormatKey + %#@count_hour_left@ + count_hour_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 klukkustund eftir + other + %ld klukkustundir eftir + + + date.minute.left + + NSStringLocalizedFormatKey + %#@count_minute_left@ + count_minute_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 mínúta eftir + other + %ld mínútur eftir + + + date.second.left + + NSStringLocalizedFormatKey + %#@count_second_left@ + count_second_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 sekúnda eftir + other + %ld sekúndur eftir + + + date.year.ago.abbr + + NSStringLocalizedFormatKey + %#@count_year_ago_abbr@ + count_year_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + Fyrir 1 ári síðan + other + Fyrir %ld árum síðan + + + date.month.ago.abbr + + NSStringLocalizedFormatKey + %#@count_month_ago_abbr@ + count_month_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + Fyrir 1mín síðan + other + Fyrir %ldmín síðan + + + date.day.ago.abbr + + NSStringLocalizedFormatKey + %#@count_day_ago_abbr@ + count_day_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + Fyrir 1 degi síðan + other + Fyrir %ld dögum síðan + + + date.hour.ago.abbr + + NSStringLocalizedFormatKey + %#@count_hour_ago_abbr@ + count_hour_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1klst síðan + other + %ldklst síðan + + + date.minute.ago.abbr + + NSStringLocalizedFormatKey + %#@count_minute_ago_abbr@ + count_minute_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1m síðan + other + %ldm síðan + + + date.second.ago.abbr + + NSStringLocalizedFormatKey + %#@count_second_ago_abbr@ + count_second_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1s síðan + other + %lds síðan + + + + diff --git a/Localization/StringsConvertor/input/is.lproj/app.json b/Localization/StringsConvertor/input/is.lproj/app.json new file mode 100644 index 000000000..3d01bd737 --- /dev/null +++ b/Localization/StringsConvertor/input/is.lproj/app.json @@ -0,0 +1,762 @@ +{ + "common": { + "alerts": { + "common": { + "please_try_again": "Endilega reyndu aftur.", + "please_try_again_later": "Reyndu aftur síðar." + }, + "sign_up_failure": { + "title": "Innskráning mistókst" + }, + "server_error": { + "title": "Villa á þjóni" + }, + "vote_failure": { + "title": "Greiðsla atkvæðis mistókst", + "poll_ended": "Könnuninni er lokið" + }, + "discard_post_content": { + "title": "Henda drögum", + "message": "Staðfestu til að henda efni úr saminni færslu." + }, + "publish_post_failure": { + "title": "Mistókst að birta", + "message": "Mistókst að birta færsluna.\nAthugaðu nettenginguna þína.", + "attachments_message": { + "video_attach_with_photo": "Ekki er hægt að hengja myndskeið við færslu sem þegar inniheldur myndir.", + "more_than_one_video": "Ekki er hægt að hengja við fleiri en eitt myndskeið." + } + }, + "edit_profile_failure": { + "title": "Villa við breytingu á notandasniði", + "message": "Mistókst að breyta notandasniði. Endilega reyndu aftur." + }, + "sign_out": { + "title": "Skrá út", + "message": "Ertu viss um að þú viljir skrá þig út?", + "confirm": "Skrá út" + }, + "block_domain": { + "title": "Ertu alveg algjörlega viss um að þú viljir loka á allt %s? Í flestum tilfellum er vænlegra að nota færri en markvissari útilokanir eða að þagga niður tiltekna aðila. Þú munt ekki sjá neitt efni frá þessu léni og fylgjendur þínir frá þessu léni verða fjarlægðir.", + "block_entire_domain": "Útiloka lén" + }, + "save_photo_failure": { + "title": "Mistókst að vista mynd", + "message": "Virkjaðu heimild til aðgangs að ljósmyndasafninu til að vista myndina." + }, + "delete_post": { + "title": "Eyða færslu", + "message": "Ertu viss um að þú viljir eyða þessari færslu?" + }, + "clean_cache": { + "title": "Hreinsa skyndiminni", + "message": "Tókst að hreinsa %s skyndiminni." + }, + "translation_failed": { + "title": "Athugasemd", + "message": "Þýðing mistókst. Mögulega hefur kerfisstjórinn ekki virkjað þýðingar á þessum netþjóni, eða að netþjónninn sé keyrður á eldri útgáfu Mastodon þar sem þýðingar séu ekki studdar.", + "button": "Í lagi" + } + }, + "controls": { + "actions": { + "back": "Til baka", + "next": "Næsta", + "previous": "Fyrri", + "open": "Opna", + "add": "Bæta við", + "remove": "Fjarlægja", + "edit": "Breyta", + "save": "Vista", + "ok": "Í lagi", + "done": "Lokið", + "confirm": "Staðfesta", + "continue": "Halda áfram", + "compose": "Skrifa", + "cancel": "Hætta við", + "discard": "Henda", + "try_again": "Reyna aftur", + "take_photo": "Taka ljósmynd", + "save_photo": "Vista mynd", + "copy_photo": "Afrita mynd", + "sign_in": "Skrá inn", + "sign_up": "Stofna notandaaðgang", + "see_more": "Sjá fleira", + "preview": "Forskoða", + "copy": "Afrita", + "share": "Deila", + "share_user": "Deila %s", + "share_post": "Deila færslu", + "open_in_safari": "Opna í Safari", + "open_in_browser": "Opna í vafra", + "find_people": "Finna fólk til að fylgjast með", + "manually_search": "Leita handvirkt í staðinn", + "skip": "Sleppa", + "reply": "Svara", + "report_user": "Kæra %s", + "block_domain": "Útiloka %s", + "unblock_domain": "Opna á %s", + "settings": "Stillingar", + "delete": "Eyða", + "translate_post": { + "title": "Þýða úr %s", + "unknown_language": "Óþekkt" + } + }, + "tabs": { + "home": "Heim", + "search_and_explore": "Leita og kanna", + "notifications": "Tilkynningar", + "profile": "Notandasnið" + }, + "keyboard": { + "common": { + "switch_to_tab": "Skipta yfir í %s", + "compose_new_post": "Semja nýja færslu", + "show_favorites": "Birta eftirlæti", + "open_settings": "Opna stillingar" + }, + "timeline": { + "previous_status": "Fyrri færsla", + "next_status": "Næsta færsla", + "open_status": "Opna færslu", + "open_author_profile": "Opna notandasnið höfundar", + "open_reblogger_profile": "Opna notandasnið þess sem endurbirtir", + "reply_status": "Svara færslu", + "toggle_reblog": "Víxla endurbirtingu færslu af/á", + "toggle_favorite": "Víxla eftirlæti færslu af/á", + "toggle_content_warning": "Víxla af/á viðvörun vegna efnis", + "preview_image": "Forskoða mynd" + }, + "segmented_control": { + "previous_section": "Fyrri hluti", + "next_section": "Næsti hluti" + } + }, + "status": { + "user_reblogged": "%s endurbirti", + "user_replied_to": "Svaraði %s", + "show_post": "Sýna færslu", + "show_user_profile": "Birta notandasnið", + "content_warning": "Viðvörun vegna efnis", + "sensitive_content": "Viðkvæmt efni", + "media_content_warning": "Ýttu hvar sem er til að birta", + "tap_to_reveal": "Ýttu til að birta", + "load_embed": "Hlaða inn ívöfnu", + "link_via_user": "%s með %s", + "poll": { + "vote": "Greiða atkvæði", + "closed": "Lokið" + }, + "meta_entity": { + "url": "Tengill: %s", + "hashtag": "Myllumerki: %s", + "mention": "Sýna notandasnið: %s", + "email": "Tölvupóstfang: %s" + }, + "actions": { + "reply": "Svara", + "reblog": "Endurbirta", + "unreblog": "Afturkalla endurbirtingu", + "favorite": "Eftirlæti", + "unfavorite": "Taka úr eftirlætum", + "menu": "Valmynd", + "hide": "Fela", + "show_image": "Sýna mynd", + "show_gif": "Birta GIF-myndir", + "show_video_player": "Sýna myndspilara", + "share_link_in_post": "Deila tengli í færslu", + "tap_then_hold_to_show_menu": "Ýttu og haltu til að sýna valmynd" + }, + "tag": { + "url": "URL-slóð", + "mention": "Minnst á", + "link": "Tengill", + "hashtag": "Myllumerki", + "email": "Tölvupóstur", + "emoji": "Tjáningartákn" + }, + "visibility": { + "unlisted": "Allir geta skoðað þessa færslu, en er ekki birt á opinberum tímalínum.", + "private": "Einungis fylgjendur þeirra geta séð þessa færslu.", + "private_from_me": "Einungis fylgjendur mínir geta séð þessa færslu.", + "direct": "Einungis notendur sem minnst er á geta séð þessa færslu." + }, + "translation": { + "translated_from": "Þýtt úr %s með %s", + "unknown_language": "Óþekkt", + "unknown_provider": "Óþekkt", + "show_original": "Birta upprunalegt" + } + }, + "friendship": { + "follow": "Fylgja", + "following": "Fylgist með", + "request": "Beiðni", + "pending": "Í bið", + "block": "Útilokun", + "block_user": "Útiloka %s", + "block_domain": "Útiloka %s", + "unblock": "Aflétta útilokun", + "unblock_user": "Opna á %s", + "blocked": "Útilokað", + "mute": "Þagga niður", + "mute_user": "Þagga niður í %s", + "unmute": "Afþagga", + "unmute_user": "Afþagga %s", + "muted": "Þaggað", + "edit_info": "Breyta upplýsingum", + "show_reblogs": "Sýna endurbirtingar", + "hide_reblogs": "Fela endurbirtingar" + }, + "timeline": { + "filtered": "Síað", + "timestamp": { + "now": "Núna" + }, + "loader": { + "load_missing_posts": "Hlaða inn færslum sem vantar", + "loading_missing_posts": "Hleð inn færslum sem vantar...", + "show_more_replies": "Birta fleiri svör" + }, + "header": { + "no_status_found": "Engar færslur fundust", + "blocking_warning": "Þú getur ekki séð snið þessa notanda\nfyrr en þú hættir að útiloka hann.\nSniðið þitt lítur svona út hjá honum.", + "user_blocking_warning": "Þú getur ekki séð sniðið hjá %s\nfyrr en þú hættir að útiloka hann.\nSniðið þitt lítur svona út hjá honum.", + "blocked_warning": "Þú getur ekki séð sniðið hjá þessum notanda\nfyrr en hann hættir að útiloka þig.", + "user_blocked_warning": "Þú getur ekki séð sniðið hjá %s\nfyrr en hann hættir að útiloka þig.", + "suspended_warning": "Þessi notandi hefur verið settur í frysti.", + "user_suspended_warning": "Notandaaðgangurinn %s hefur verið settur í frysti." + } + } + } + }, + "scene": { + "welcome": { + "slogan": "Samfélagsmiðlar\naftur í þínar hendur.", + "get_started": "Komast í gang", + "log_in": "Skrá inn" + }, + "login": { + "title": "Velkomin aftur", + "subtitle": "Skráðu þig inn á netþjóninum þar sem þú útbjóst aðganginn þinn.", + "server_search_field": { + "placeholder": "Settu inn slóð eða leitaðu að þjóninum þínum" + } + }, + "server_picker": { + "title": "Mastodon samanstendur af notendum á mismunandi netþjónum.", + "subtitle": "Veldu netþjón út frá svæðinu þínu, áhugamálum, nú eða einhvern almennan. Þú getur samt spjallað við hvern sem er á Mastodon, burtséð frá á hvaða netþjóni þú ert.", + "button": { + "category": { + "all": "Allt", + "all_accessiblity_description": "Flokkur: Allt", + "academia": "akademískt", + "activism": "aðgerðasinnar", + "food": "matur", + "furry": "loðið", + "games": "leikir", + "general": "almennt", + "journalism": "blaðamennska", + "lgbt": "lgbt", + "regional": "svæðisbundið", + "art": "listir", + "music": "tónlist", + "tech": "tækni" + }, + "see_less": "Sjá minna", + "see_more": "Sjá meira" + }, + "label": { + "language": "TUNGUMÁL", + "users": "NOTENDUR", + "category": "FLOKKUR" + }, + "input": { + "search_servers_or_enter_url": "Leitaðu að samfélögum eða settu inn slóð" + }, + "empty_state": { + "finding_servers": "Finn tiltæka netþjóna...", + "bad_network": "Eitthvað fór úrskeiðis við að hlaða inn gögnunum. Athugaðu nettenginguna þína.", + "no_results": "Engar niðurstöður" + } + }, + "register": { + "title": "Við skulum koma þér í gang á %s", + "lets_get_you_set_up_on_domain": "Við skulum koma þér í gang á %s", + "input": { + "avatar": { + "delete": "Eyða" + }, + "username": { + "placeholder": "notandanafn", + "duplicate_prompt": "Þetta notandanafn er þegar í notkun." + }, + "display_name": { + "placeholder": "birtingarnafn" + }, + "email": { + "placeholder": "tölvupóstur" + }, + "password": { + "placeholder": "lykilorð", + "require": "Lykilorðið þitt þarf að minnsta kosti:", + "character_limit": "8 stafi", + "accessibility": { + "checked": "merkt", + "unchecked": "ekki merkt" + }, + "hint": "Lykilorðið þitt verður að vera að minnsta kosti 8 stafa langt" + }, + "invite": { + "registration_user_invite_request": "Hvers vegna vilt þú taka þátt?" + } + }, + "error": { + "item": { + "username": "Notandanafn", + "email": "Tölvupóstur", + "password": "Lykilorð", + "agreement": "Notkunarskilmálar", + "locale": "Staðfærsla", + "reason": "Ástæða" + }, + "reason": { + "blocked": "%s notar óleyfilega tölvupóstþjónustu", + "unreachable": "%s virðist ekki vera til", + "taken": "%s er þegar í notkun", + "reserved": "%s er frátekið stikkorð", + "accepted": "%s verður að samþykkja", + "blank": "%s ier nauðsynlegt", + "invalid": "%s er ógilt", + "too_long": "%s er of langt", + "too_short": "%s er of stutt", + "inclusion": "%s er ekki stutt gildi" + }, + "special": { + "username_invalid": "Notendanöfn geta einungis innihaldið bókstafi og undirstrikun", + "username_too_long": "Notandanafnið er of langt (má ekki vera lengra en 30 stafir)", + "email_invalid": "Þetta lítur ekki út eins og löglegt tölvupóstfang", + "password_too_short": "Lykilorð er of stutt (verður að hafa minnst 8 stafi)" + } + } + }, + "server_rules": { + "title": "Nokkrar grunnreglur.", + "subtitle": "Þær eru settar og séð um að þeim sé fylgt af umsjónarmönnum %s.", + "prompt": "Með því að halda áfram samþykkir þú þjónustuskilmála og persónuverndarstefnu %s.", + "terms_of_service": "þjónustuskilmálar", + "privacy_policy": "persónuverndarstefna", + "button": { + "confirm": "Ég samþykki" + } + }, + "confirm_email": { + "title": "Eitt að lokum.", + "subtitle": "Ýttu á tengilinn sem við sendum þér til að staðfesta tölvupóstfangið þitt.", + "tap_the_link_we_emailed_to_you_to_verify_your_account": "Ýttu á tengilinn sem við sendum þér til að staðfesta tölvupóstfangið þitt", + "button": { + "open_email_app": "Opna tölvupóstforrit", + "resend": "Endursenda" + }, + "dont_receive_email": { + "title": "Athugaðu tölvupóstinn þinn", + "description": "Athugaðu hvort tölvupóstfangið þitt sé rétt auk þess að skoða í ruslpóstmöppuna þína ef þú hefur ekki gert það.", + "resend_email": "Endursenda tölvupóst" + }, + "open_email_app": { + "title": "Athugaðu pósthólfið þitt.", + "description": "Við vorum að senda þér tölvupóst. Skoðaðu í ruslpóstmöppuna þína ef þú hefur ekki gert það.", + "mail": "Tölvupóstur", + "open_email_client": "Opna tölvupóstforrit" + } + }, + "home_timeline": { + "title": "Heim", + "navigation_bar_state": { + "offline": "Ónettengt", + "new_posts": "Skoða nýjar færslur", + "published": "Birt!", + "Publishing": "Birti færslu...", + "accessibility": { + "logo_label": "Hnappur með táknmerki", + "logo_hint": "Ýttu til að skruna efst og ýttu aftur til að fara aftur á fyrri staðsetningu" + } + } + }, + "suggestion_account": { + "title": "Finndu fólk til að fylgjast með", + "follow_explain": "Þegar þú fylgist með einhverjum, muntu sjá færslur frá viðkomandi á streyminu þínu." + }, + "compose": { + "title": { + "new_post": "Ný færsla", + "new_reply": "Nýtt svar" + }, + "media_selection": { + "camera": "Taktu mynd", + "photo_library": "Myndasafn", + "browse": "Flakka" + }, + "content_input_placeholder": "Skrifaðu eða límdu það sem þér liggur á hjarta", + "compose_action": "Birta", + "replying_to_user": "svarar til @%s", + "attachment": { + "photo": "ljósmynd", + "video": "myndskeið", + "attachment_broken": "Þetta %s er skemmt og því ekki\nhægt að senda inn á Mastodon.", + "description_photo": "Lýstu myndinni fyrir sjónskerta...", + "description_video": "Lýstu myndskeiðinu fyrir sjónskerta...", + "load_failed": "Hleðsla mistókst", + "upload_failed": "Innsending mistókst", + "can_not_recognize_this_media_attachment": "Þekki ekki þetta myndviðhengi", + "attachment_too_large": "Viðhengi of stórt", + "compressing_state": "Þjappa...", + "server_processing_state": "Netþjónn er að vinna..." + }, + "poll": { + "duration_time": "Tímalengd: %s", + "thirty_minutes": "30 mínútur", + "one_hour": "1 klukkustund", + "six_hours": "6 klukkustundir", + "one_day": "1 dagur", + "three_days": "3 dagar", + "seven_days": "7 dagar", + "option_number": "Valkostur %ld", + "the_poll_is_invalid": "Könnunin er ógild", + "the_poll_has_empty_option": "Könnunin er með auðan valkost" + }, + "content_warning": { + "placeholder": "Skrifaðu nákvæma aðvörun hér..." + }, + "visibility": { + "public": "Opinbert", + "unlisted": "Óskráð", + "private": "Einungis fylgjendur", + "direct": "Einungis fólk sem ég minnist á" + }, + "auto_complete": { + "space_to_add": "Bil sem á að bæta við" + }, + "accessibility": { + "append_attachment": "Bæta við viðhengi", + "append_poll": "Bæta við könnun", + "remove_poll": "Fjarlægja könnun", + "custom_emoji_picker": "Sérsniðið emoji-tánmyndaval", + "enable_content_warning": "Virkja viðvörun vegna efnis", + "disable_content_warning": "Gera viðvörun vegna efnis óvirka", + "post_visibility_menu": "Sýnileikavalmynd færslu", + "post_options": "Valkostir færslu", + "posting_as": "Birti sem %s" + }, + "keyboard": { + "discard_post": "Henda færslu", + "publish_post": "Birta færslu", + "toggle_poll": "Víxla könnun af/á", + "toggle_content_warning": "Víxla af/á viðvörun vegna efnis", + "append_attachment_entry": "Bæta við viðhengi - %s", + "select_visibility_entry": "Veldu sýnileika - %s" + } + }, + "profile": { + "header": { + "follows_you": "Fylgist með þér" + }, + "dashboard": { + "my_posts": "færslur", + "my_following": "er fylgst með", + "my_followers": "fylgjendur", + "other_posts": "færslur", + "other_following": "er fylgst með", + "other_followers": "fylgjendur" + }, + "fields": { + "joined": "Gerðist þátttakandi", + "add_row": "Bæta við röð", + "placeholder": { + "label": "Skýring", + "content": "Efni" + }, + "verified": { + "short": "Sannreynt þann %s", + "long": "Eignarhald á þessum tengli var athugað þann %s" + } + }, + "segmented_control": { + "posts": "Færslur", + "replies": "Svör", + "posts_and_replies": "Færslur og svör", + "media": "Gagnamiðlar", + "about": "Um hugbúnaðinn" + }, + "relationship_action_alert": { + "confirm_mute_user": { + "title": "Þagga niður í aðgangi", + "message": "Staðfestu til að þagga niður í %s" + }, + "confirm_unmute_user": { + "title": "Hætta að þagga niður í aðgangi", + "message": "Staðfestu til hætta að að þagga niður í %s" + }, + "confirm_block_user": { + "title": "Útiloka notandaaðgang", + "message": "Staðfestu til að útiloka %s" + }, + "confirm_unblock_user": { + "title": "Aflétta útilokun aðgangs", + "message": "Staðfestu til að hætta að útiloka %s" + }, + "confirm_show_reblogs": { + "title": "Sýna endurbirtingar", + "message": "Staðfestu til að sýna endurbirtingar" + }, + "confirm_hide_reblogs": { + "title": "Fela endurbirtingar", + "message": "Staðfestu til að fela endurbirtingar" + } + }, + "accessibility": { + "show_avatar_image": "Sýna auðkennismynd", + "edit_avatar_image": "Breyta auðkennismynd", + "show_banner_image": "Sýna myndborða", + "double_tap_to_open_the_list": "Tvípikkaðu til að opna listann" + } + }, + "follower": { + "title": "fylgjandi", + "footer": "Fylgjendur af öðrum netþjónum birtast ekki." + }, + "following": { + "title": "fylgist með", + "footer": "Fylgjendur af öðrum netþjónum birtast ekki." + }, + "familiarFollowers": { + "title": "Fylgjendur sem þú kannast við", + "followed_by_names": "Fylgt af %s" + }, + "favorited_by": { + "title": "Sett í eftirlæti af" + }, + "reblogged_by": { + "title": "Endurbirt af" + }, + "search": { + "title": "Leita", + "search_bar": { + "placeholder": "Leita að myllumerkjum og notendum", + "cancel": "Hætta við" + }, + "recommend": { + "button_text": "Sjá allt", + "hash_tag": { + "title": "Vinsælt á Mastodon", + "description": "Myllumerki sem eru að fá þónokkra athygli", + "people_talking": "%s manns eru að spjalla" + }, + "accounts": { + "title": "Notandaaðgangar sem þú gætir haft áhuga á", + "description": "Þú gætir viljað fylgjast með þessum aðgöngum", + "follow": "Fylgjast með" + } + }, + "searching": { + "segment": { + "all": "Allt", + "people": "Fólk", + "hashtags": "Myllumerki", + "posts": "Færslur" + }, + "empty_state": { + "no_results": "Engar niðurstöður" + }, + "recent_search": "Nýlegar leitir", + "clear": "Hreinsa" + } + }, + "discovery": { + "tabs": { + "posts": "Færslur", + "hashtags": "Myllumerki", + "news": "Fréttir", + "community": "Samfélag", + "for_you": "Fyrir þig" + }, + "intro": "Þetta eru færslurnar sem eru að fá aukna athygli í þínu horni á Mastodon." + }, + "favorite": { + "title": "Eftirlætin þín" + }, + "notification": { + "title": { + "Everything": "Allt", + "Mentions": "Minnst á" + }, + "notification_description": { + "followed_you": "fylgdi þér", + "favorited_your_post": "setti færslu frá þér í eftirlæti", + "reblogged_your_post": "endurbirti færsluna þína", + "mentioned_you": "minntist á þig", + "request_to_follow_you": "bað um að fylgjast með þér", + "poll_has_ended": "könnun er lokið" + }, + "keyobard": { + "show_everything": "Sýna allt", + "show_mentions": "Sýna þegar minnst er á" + }, + "follow_request": { + "accept": "Samþykkja", + "accepted": "Samþykkt", + "reject": "hafna", + "rejected": "Hafnað" + } + }, + "thread": { + "back_title": "Færsla", + "title": "Færsla frá %s" + }, + "settings": { + "title": "Stillingar", + "section": { + "appearance": { + "title": "Útlit", + "automatic": "Sjálfvirkt", + "light": "Alltaf ljóst", + "dark": "Alltaf dökkt" + }, + "look_and_feel": { + "title": "Útlit og viðmót", + "use_system": "Nota stillingar kerfis", + "really_dark": "Mjög dökkt", + "sorta_dark": "Nokkuð dökkt", + "light": "Ljóst" + }, + "notifications": { + "title": "Tilkynningar", + "favorites": "Setur færsluna mína í eftirlæti", + "follows": "Fylgist með mér", + "boosts": "Endurbirtir færsluna mína", + "mentions": "Minnist á mig", + "trigger": { + "anyone": "hver sem er", + "follower": "fylgjandi", + "follow": "hverjum sá sem ég fylgi", + "noone": "enginn", + "title": "Tilkynna mér þegar" + } + }, + "preference": { + "title": "Kjörstillingar", + "true_black_dark_mode": "Sannur svartur dökkur hamur", + "disable_avatar_animation": "Gera auðkennismyndir með hreyfingu óvirkar", + "disable_emoji_animation": "Gera tjáningartákn með hreyfingu óvirkar", + "using_default_browser": "Nota sjálfgefinn vafra til að opna tengla", + "open_links_in_mastodon": "Opna tengla í Mastodon" + }, + "boring_zone": { + "title": "Óhressa svæðið", + "account_settings": "Stillingar aðgangs", + "terms": "Þjónustuskilmálar", + "privacy": "Meðferð persónuupplýsinga" + }, + "spicy_zone": { + "title": "Kryddaða svæðið", + "clear": "Hreinsa skyndiminni margmiðlunarefnis", + "signout": "Skrá út" + } + }, + "footer": { + "mastodon_description": "Mastodon er frjáls hugbúnaður með opinn grunnkóða. Þú getur tilkynnt vandamál í gegnum GitHub á %s (%s)" + }, + "keyboard": { + "close_settings_window": "Loka stillingaglugga" + } + }, + "report": { + "title_report": "Kæra", + "title": "Kæra %s", + "step1": "Skref 1 af 2", + "step2": "Skref 2 af 2", + "content1": "Eru einhverjar færslur sem þú myndir vilja bæta við kæruna?", + "content2": "Er eitthvað fleira sem umsjónarmenn ættu að vita varðandi þessa kæru?", + "report_sent_title": "Takk fyrir tilkynninguna, við munum skoða málið.", + "send": "Senda kæru", + "skip_to_send": "Senda án athugasemdar", + "text_placeholder": "Skrifaðu eða límdu aðrar athugasemdir", + "reported": "TILKYNNT", + "step_one": { + "step_1_of_4": "Skref 1 af 4", + "whats_wrong_with_this_post": "Hvað er athugavert við þessa færslu?", + "whats_wrong_with_this_account": "Hvað er athugavert við þennan notandaaðgang?", + "whats_wrong_with_this_username": "Hvað er athugavert við %s?", + "select_the_best_match": "Velja bestu samsvörun", + "i_dont_like_it": "Mér líkar það ekki", + "it_is_not_something_you_want_to_see": "Þetta er ekki eitthvað sem þið viljið sjá", + "its_spam": "Þetta er ruslpóstur", + "malicious_links_fake_engagement_or_repetetive_replies": "Slæmir tenglar, fölsk samskipti eða endurtekin svör", + "it_violates_server_rules": "Það gengur þvert á reglur fyrir netþjóninn", + "you_are_aware_that_it_breaks_specific_rules": "Þið eruð meðvituð um að þetta brýtur sértækar reglur", + "its_something_else": "Það er eitthvað annað", + "the_issue_does_not_fit_into_other_categories": "Vandamálið fellur ekki í aðra flokka" + }, + "step_two": { + "step_2_of_4": "Skref 2 af 4", + "which_rules_are_being_violated": "Hvaða reglur eru brotnar?", + "select_all_that_apply": "Veldu allt sem á við", + "i_just_don’t_like_it": "Mér bara líkar það ekki" + }, + "step_three": { + "step_3_of_4": "Skref 3 af 4", + "are_there_any_posts_that_back_up_this_report": "Eru einhverjar færslur sem styðja þessa kæru?", + "select_all_that_apply": "Veldu allt sem á við" + }, + "step_four": { + "step_4_of_4": "Skref 4 af 4", + "is_there_anything_else_we_should_know": "Er eitthvað fleira sem við ættum að vita?" + }, + "step_final": { + "dont_want_to_see_this": "Langar þig ekki að sjá þetta?", + "when_you_see_something_you_dont_like_on_mastodon_you_can_remove_the_person_from_your_experience.": "Þegar þú sér eitthvað á Mastodon sem þér líkar ekki, þá geturðu fjarlægt viðkomandi eintakling úr umhverfinu þínu.", + "unfollow": "Hætta að fylgjast með", + "unfollowed": "Hætti að fylgjast með", + "unfollow_user": "Hætta að fylgjast með %s", + "mute_user": "Þagga niður í %s", + "you_wont_see_their_posts_or_reblogs_in_your_home_feed_they_wont_know_they_ve_been_muted": "Þú munt ekki sjá færslur eða endurbirtingar frá viðkomandi á streyminu þínu. Viðkomandi aðilar munu ekki vita að þaggað hefur verið niður í þeim.", + "block_user": "Útiloka %s", + "they_will_no_longer_be_able_to_follow_or_see_your_posts_but_they_can_see_if_theyve_been_blocked": "Viðkomandi mun ekki lengur geta fylgst með eða séð færslurnar þínar, en munu sjá ef viðkomandi hefur verið útilokaður.", + "while_we_review_this_you_can_take_action_against_user": "Á meðan við yfirförum þetta, geturðu tekið til aðgerða gegn %s" + } + }, + "preview": { + "keyboard": { + "close_preview": "Loka forskoðun", + "show_next": "Sýna næsta", + "show_previous": "Sýna fyrri" + } + }, + "account_list": { + "tab_bar_hint": "Fyrirliggjandi valið notandasnið: %s. Tvípikkaðu og haltu niðri til að birta aðgangaskiptinn", + "dismiss_account_switcher": "Loka aðgangaskipti", + "add_account": "Bæta við notandaaðgangi" + }, + "wizard": { + "new_in_mastodon": "Nýtt í Mastodon", + "multiple_account_switch_intro_description": "Skiptu milli notandaaðganga með því að halda niðri notandasniðshnappnum.", + "accessibility_hint": "Tvípikkaðu til að loka þessum leiðarvísi" + }, + "bookmark": { + "title": "Bókamerki" + }, + "followed_tags": { + "title": "Myllumerki sem fylgst er með", + "header": { + "posts": "færslur", + "participants": "þátttakendur", + "posts_today": "færslur í dag" + }, + "actions": { + "follow": "Fylgjast með", + "unfollow": "Hætta að fylgjast með" + } + } + } +} diff --git a/Localization/StringsConvertor/input/is.lproj/ios-infoPlist.json b/Localization/StringsConvertor/input/is.lproj/ios-infoPlist.json new file mode 100644 index 000000000..24af18431 --- /dev/null +++ b/Localization/StringsConvertor/input/is.lproj/ios-infoPlist.json @@ -0,0 +1,6 @@ +{ + "NSCameraUsageDescription": "Notað til að taka mynd fyrir stöðufærslu", + "NSPhotoLibraryAddUsageDescription": "Notað til að vista mynd inn í ljósmyndasafnið", + "NewPostShortcutItemTitle": "Ný færsla", + "SearchShortcutItemTitle": "Leita" +} diff --git a/Localization/StringsConvertor/input/it.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/it.lproj/Localizable.stringsdict index 38f986521..3a8549914 100644 --- a/Localization/StringsConvertor/input/it.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/it.lproj/Localizable.stringsdict @@ -50,6 +50,22 @@ %ld caratteri + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ rimanenti + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 carattere + other + %ld caratteri + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/it.lproj/app.json b/Localization/StringsConvertor/input/it.lproj/app.json index 73f42d1eb..d355a4c10 100644 --- a/Localization/StringsConvertor/input/it.lproj/app.json +++ b/Localization/StringsConvertor/input/it.lproj/app.json @@ -51,6 +51,11 @@ "clean_cache": { "title": "Pulisci la cache", "message": "Cache %s pulita con successo." + }, + "translation_failed": { + "title": "Nota", + "message": "Traduzione fallita. Forse l'amministratore non ha abilitato le traduzioni su questo server o questo server sta eseguendo una versione precedente di Mastodon in cui le traduzioni non sono ancora supportate.", + "button": "OK" } }, "controls": { @@ -75,9 +80,10 @@ "save_photo": "Salva foto", "copy_photo": "Copia foto", "sign_in": "Accedi", - "sign_up": "Registrati", + "sign_up": "Crea un account", "see_more": "Visualizza altro", "preview": "Anteprima", + "copy": "Copia", "share": "Condividi", "share_user": "Condividi %s", "share_post": "Condividi il post", @@ -91,12 +97,16 @@ "block_domain": "Blocca %s", "unblock_domain": "Sblocca %s", "settings": "Impostazioni", - "delete": "Elimina" + "delete": "Elimina", + "translate_post": { + "title": "Traduci da %s", + "unknown_language": "Sconosciuto" + } }, "tabs": { "home": "Inizio", - "search": "Cerca", - "notification": "Notifiche", + "search_and_explore": "Cerca ed Esplora", + "notifications": "Notifiche", "profile": "Profilo" }, "keyboard": { @@ -132,6 +142,8 @@ "sensitive_content": "Contenuto sensibile", "media_content_warning": "Tocca ovunque per rivelare", "tap_to_reveal": "Tocca per rivelare", + "load_embed": "Carica Incorpora", + "link_via_user": "%s tramite %s", "poll": { "vote": "Vota", "closed": "Chiuso" @@ -153,13 +165,14 @@ "show_image": "Mostra immagine", "show_gif": "Mostra GIF", "show_video_player": "Mostra lettore video", + "share_link_in_post": "Condividi il collegamento nel post", "tap_then_hold_to_show_menu": "Tocca quindi tieni premuto per mostrare il menu" }, "tag": { "url": "URL", "mention": "Menzione", "link": "Collegamento", - "hashtag": "Etichetta", + "hashtag": "Hashtag", "email": "Email", "emoji": "Emoji" }, @@ -168,6 +181,12 @@ "private": "Solo i loro seguaci possono vedere questo post.", "private_from_me": "Solo i miei seguaci possono vedere questo post.", "direct": "Solo l'utente menzionato può vedere questo post." + }, + "translation": { + "translated_from": "Tradotto da %s utilizzando %s", + "unknown_language": "Sconosciuto", + "unknown_provider": "Sconosciuto", + "show_original": "Mostra l'originale" } }, "friendship": { @@ -218,10 +237,16 @@ "get_started": "Inizia", "log_in": "Accedi" }, + "login": { + "title": "Bentornato/a", + "subtitle": "Accedi al server sul quale hai creato il tuo account.", + "server_search_field": { + "placeholder": "Inserisci l'URL o cerca il tuo server" + } + }, "server_picker": { "title": "Mastodon è fatto di utenti in diverse comunità.", - "subtitle": "Scegli una comunità basata sui tuoi interessi, regione o uno scopo generale.", - "subtitle_extend": "Scegli una comunità basata sui tuoi interessi, regione o uno scopo generale. Ogni comunità è gestita da un'organizzazione completamente indipendente o individuale.", + "subtitle": "Scegli un server in base alla tua regione, ai tuoi interessi o uno generico. Puoi comunque chattare con chiunque su Mastodon, indipendentemente dai tuoi server.", "button": { "category": { "all": "Tutti", @@ -248,8 +273,7 @@ "category": "CATEGORIA" }, "input": { - "placeholder": "Cerca comunità", - "search_servers_or_enter_url": "Cerca i server o inserisci l'URL" + "search_servers_or_enter_url": "Cerca le comunità o inserisci l'URL" }, "empty_state": { "finding_servers": "Ricerca server disponibili...", @@ -386,7 +410,9 @@ "load_failed": "Caricamento fallito", "upload_failed": "Caricamento fallito", "can_not_recognize_this_media_attachment": "Impossibile riconoscere questo allegato multimediale", - "attachment_too_large": "Allegato troppo grande" + "attachment_too_large": "Allegato troppo grande", + "compressing_state": "Compressione in corso...", + "server_processing_state": "Elaborazione del server in corso..." }, "poll": { "duration_time": "Durata: %s", @@ -396,7 +422,9 @@ "one_day": "1 giorno", "three_days": "3 giorni", "seven_days": "7 giorni", - "option_number": "Opzione %ld" + "option_number": "Opzione %ld", + "the_poll_is_invalid": "Il sondaggio non è valido", + "the_poll_has_empty_option": "Il sondaggio ha un'opzione vuota" }, "content_warning": { "placeholder": "Scrivi un avviso accurato qui..." @@ -417,7 +445,9 @@ "custom_emoji_picker": "Selettore Emoji personalizzato", "enable_content_warning": "Abilita avvertimento contenuti", "disable_content_warning": "Disabilita avviso di contenuti", - "post_visibility_menu": "Menu di visibilità del post" + "post_visibility_menu": "Menu di visibilità del post", + "post_options": "Opzioni del messaggio", + "posting_as": "Pubblicazione come %s" }, "keyboard": { "discard_post": "Scarta post", @@ -433,15 +463,23 @@ "follows_you": "Ti segue" }, "dashboard": { - "posts": "post", - "following": "seguendo", - "followers": "seguaci" + "my_posts": "post", + "my_following": "seguendo", + "my_followers": "seguaci", + "other_posts": "post", + "other_following": "seguendo", + "other_followers": "seguaci" }, "fields": { + "joined": "Profilo iscritto", "add_row": "Aggiungi riga", "placeholder": { "label": "Etichetta", "content": "Contenuto" + }, + "verified": { + "short": "Verificato il %s", + "long": "La proprietà di questo collegamento è stata verificata il %s" } }, "segmented_control": { @@ -707,6 +745,18 @@ }, "bookmark": { "title": "Segnalibri" + }, + "followed_tags": { + "title": "Etichette seguite", + "header": { + "posts": "post", + "participants": "partecipanti", + "posts_today": "post di oggi" + }, + "actions": { + "follow": "Segui", + "unfollow": "Smetti di seguire" + } } } } diff --git a/Localization/StringsConvertor/input/ja.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/ja.lproj/Localizable.stringsdict index cbc999738..795a971b7 100644 --- a/Localization/StringsConvertor/input/ja.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/ja.lproj/Localizable.stringsdict @@ -44,6 +44,20 @@ %ld 文字 + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ left + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld characters + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/ja.lproj/app.json b/Localization/StringsConvertor/input/ja.lproj/app.json index 9ff2a60a6..c16f12dcf 100644 --- a/Localization/StringsConvertor/input/ja.lproj/app.json +++ b/Localization/StringsConvertor/input/ja.lproj/app.json @@ -51,6 +51,11 @@ "clean_cache": { "title": "キャッシュを消去", "message": "%sのキャッシュを消去しました。" + }, + "translation_failed": { + "title": "Note", + "message": "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported.", + "button": "OK" } }, "controls": { @@ -72,12 +77,13 @@ "discard": "破棄", "try_again": "再実行", "take_photo": "写真を撮る", - "save_photo": "写真を撮る", + "save_photo": "写真を保存", "copy_photo": "写真をコピー", - "sign_in": "サインイン", - "sign_up": "サインアップ", + "sign_in": "ログイン", + "sign_up": "アカウント作成", "see_more": "もっと見る", "preview": "プレビュー", + "copy": "Copy", "share": "共有", "share_user": "%sを共有", "share_post": "投稿を共有", @@ -91,12 +97,16 @@ "block_domain": "%sをブロック", "unblock_domain": "%sのブロックを解除", "settings": "設定", - "delete": "削除" + "delete": "削除", + "translate_post": { + "title": "Translate from %s", + "unknown_language": "Unknown" + } }, "tabs": { "home": "ホーム", - "search": "検索", - "notification": "通知", + "search_and_explore": "Search and Explore", + "notifications": "通知", "profile": "プロフィール" }, "keyboard": { @@ -132,15 +142,17 @@ "sensitive_content": "閲覧注意", "media_content_warning": "どこかをタップして表示", "tap_to_reveal": "タップして表示", + "load_embed": "Load Embed", + "link_via_user": "%s via %s", "poll": { "vote": "投票", "closed": "終了" }, "meta_entity": { - "url": "Link: %s", - "hashtag": "Hashtag: %s", - "mention": "Show Profile: %s", - "email": "Email address: %s" + "url": "リンク: %s", + "hashtag": "ハッシュタグ: %s", + "mention": "プロフィールを表示: %s", + "email": "メールアドレス: %s" }, "actions": { "reply": "返信", @@ -153,6 +165,7 @@ "show_image": "画像を表示", "show_gif": "GIFを表示", "show_video_player": "Show video player", + "share_link_in_post": "Share Link in Post", "tap_then_hold_to_show_menu": "Tap then hold to show menu" }, "tag": { @@ -168,6 +181,12 @@ "private": "この投稿はフォロワーに限り見ることができます。", "private_from_me": "この投稿はフォロワーに限り見ることができます。", "direct": "この投稿はメンションされたユーザーに限り見ることができます。" + }, + "translation": { + "translated_from": "Translated from %s using %s", + "unknown_language": "Unknown", + "unknown_provider": "Unknown", + "show_original": "Shown Original" } }, "friendship": { @@ -187,8 +206,8 @@ "unmute_user": "%sのミュートを解除", "muted": "ミュート済み", "edit_info": "編集", - "show_reblogs": "Show Reblogs", - "hide_reblogs": "Hide Reblogs" + "show_reblogs": "ブーストを表示", + "hide_reblogs": "ブーストを非表示" }, "timeline": { "filtered": "フィルター済み", @@ -218,10 +237,16 @@ "get_started": "はじめる", "log_in": "ログイン" }, + "login": { + "title": "Welcome back", + "subtitle": "アカウントを作成したサーバーにログインします。", + "server_search_field": { + "placeholder": "URLを入力またはサーバーを検索" + } + }, "server_picker": { "title": "サーバーを選択", - "subtitle": "あなたの興味分野・地域に合ったコミュニティや、汎用のものを選択してください。", - "subtitle_extend": "あなたの興味分野・地域に合ったコミュニティや、汎用のものを選択してください。各コミュニティはそれぞれ完全に独立した組織や個人によって運営されています。", + "subtitle": "お住まいの地域、興味、目的に基づいてサーバーを選択してください。 サーバーに関係なく、Mastodonの誰とでも話せます。", "button": { "category": { "all": "すべて", @@ -248,8 +273,7 @@ "category": "カテゴリー" }, "input": { - "placeholder": "サーバーを探す", - "search_servers_or_enter_url": "サーバーを検索またはURLを入力" + "search_servers_or_enter_url": "コミュニティを検索またはURLを入力" }, "empty_state": { "finding_servers": "利用可能なサーバーの検索...", @@ -330,7 +354,7 @@ "confirm_email": { "title": "さいごにもうひとつ。", "subtitle": "先程 %s にメールを送信しました。リンクをタップしてアカウントを確認してください。", - "tap_the_link_we_emailed_to_you_to_verify_your_account": "Tap the link we emailed to you to verify your account", + "tap_the_link_we_emailed_to_you_to_verify_your_account": "メールで送られたリンクへアクセスし、アカウントを認証してください", "button": { "open_email_app": "メールアプリを開く", "resend": "再送信" @@ -383,10 +407,12 @@ "attachment_broken": "%sは壊れていてMastodonにアップロードできません。", "description_photo": "閲覧が難しいユーザーへの画像説明", "description_video": "閲覧が難しいユーザーへの映像説明", - "load_failed": "Load Failed", - "upload_failed": "Upload Failed", - "can_not_recognize_this_media_attachment": "Can not regonize this media attachment", - "attachment_too_large": "Attachment too large" + "load_failed": "読み込みに失敗しました", + "upload_failed": "アップロードに失敗しました", + "can_not_recognize_this_media_attachment": "Can not recognize this media attachment", + "attachment_too_large": "添付ファイルが大きすぎます", + "compressing_state": "Compressing...", + "server_processing_state": "Server Processing..." }, "poll": { "duration_time": "期間: %s", @@ -396,7 +422,9 @@ "one_day": "1日", "three_days": "3日", "seven_days": "7日", - "option_number": "オプション %ld" + "option_number": "オプション %ld", + "the_poll_is_invalid": "The poll is invalid", + "the_poll_has_empty_option": "The poll has empty option" }, "content_warning": { "placeholder": "ここに警告を書いてください..." @@ -411,20 +439,22 @@ "space_to_add": "スペースを追加" }, "accessibility": { - "append_attachment": "アタッチメントの追加", + "append_attachment": "添付ファイルを追加", "append_poll": "投票を追加", "remove_poll": "投票を消去", "custom_emoji_picker": "カスタム絵文字ピッカー", "enable_content_warning": "閲覧注意を有効にする", "disable_content_warning": "閲覧注意を無効にする", - "post_visibility_menu": "投稿の表示メニュー" + "post_visibility_menu": "投稿の表示メニュー", + "post_options": "投稿オプション", + "posting_as": "Posting as %s" }, "keyboard": { "discard_post": "投稿を破棄", "publish_post": "投稿する", "toggle_poll": "投票を切り替える", "toggle_content_warning": "閲覧注意を切り替える", - "append_attachment_entry": "アタッチメントを追加 - %s", + "append_attachment_entry": "添付ファイルを追加 - %s", "select_visibility_entry": "公開設定を選択 - %s" } }, @@ -433,15 +463,23 @@ "follows_you": "フォローされています" }, "dashboard": { - "posts": "投稿", - "following": "フォロー", - "followers": "フォロワー" + "my_posts": "posts", + "my_following": "following", + "my_followers": "followers", + "other_posts": "posts", + "other_following": "following", + "other_followers": "followers" }, "fields": { + "joined": "Joined", "add_row": "行追加", "placeholder": { "label": "ラベル", "content": "コンテンツ" + }, + "verified": { + "short": "Verified on %s", + "long": "Ownership of this link was checked on %s" } }, "segmented_control": { @@ -469,12 +507,12 @@ "message": "%sのブロックを解除しますか?" }, "confirm_show_reblogs": { - "title": "Show Reblogs", - "message": "Confirm to show reblogs" + "title": "ブーストを表示", + "message": "ブーストを表示しますか?" }, "confirm_hide_reblogs": { - "title": "Hide Reblogs", - "message": "Confirm to hide reblogs" + "title": "ブーストを非表示", + "message": "ブーストを非表示にしますか?" } }, "accessibility": { @@ -497,10 +535,10 @@ "followed_by_names": "Followed by %s" }, "favorited_by": { - "title": "Favorited By" + "title": "お気に入り" }, "reblogged_by": { - "title": "Reblogged By" + "title": "ブースト" }, "search": { "title": "検索", @@ -663,28 +701,28 @@ "step_two": { "step_2_of_4": "ステップ 2/4", "which_rules_are_being_violated": "どのルールに違反していますか?", - "select_all_that_apply": "Select all that apply", - "i_just_don’t_like_it": "I just don’t like it" + "select_all_that_apply": "当てはまるものをすべて選んでください", + "i_just_don’t_like_it": "興味がありません" }, "step_three": { "step_3_of_4": "ステップ 3/4", - "are_there_any_posts_that_back_up_this_report": "Are there any posts that back up this report?", - "select_all_that_apply": "Select all that apply" + "are_there_any_posts_that_back_up_this_report": "この通報を裏付けるような投稿はありますか?", + "select_all_that_apply": "当てはまるものをすべて選んでください" }, "step_four": { "step_4_of_4": "ステップ 4/4", "is_there_anything_else_we_should_know": "その他に私たちに伝えておくべき事はありますか?" }, "step_final": { - "dont_want_to_see_this": "Don’t want to see this?", - "when_you_see_something_you_dont_like_on_mastodon_you_can_remove_the_person_from_your_experience.": "When you see something you don’t like on Mastodon, you can remove the person from your experience.", + "dont_want_to_see_this": "見えないようにしたいですか?", + "when_you_see_something_you_dont_like_on_mastodon_you_can_remove_the_person_from_your_experience.": "Mastodonで気に入らないものを見た場合、その人をあなたの体験から取り除くことができます。", "unfollow": "フォロー解除", "unfollowed": "フォロー解除しました", "unfollow_user": "%sをフォロー解除", "mute_user": "%sをミュート", - "you_wont_see_their_posts_or_reblogs_in_your_home_feed_they_wont_know_they_ve_been_muted": "You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted.", + "you_wont_see_their_posts_or_reblogs_in_your_home_feed_they_wont_know_they_ve_been_muted": "ホームに投稿やブーストは表示されなくなります。相手にミュートしたことは伝わりません。", "block_user": "%sをブロック", - "they_will_no_longer_be_able_to_follow_or_see_your_posts_but_they_can_see_if_theyve_been_blocked": "They will no longer be able to follow or see your posts, but they can see if they’ve been blocked.", + "they_will_no_longer_be_able_to_follow_or_see_your_posts_but_they_can_see_if_theyve_been_blocked": "相手はあなたの投稿を見たり、フォローしたりできなくなります。あなたにブロックされていることはわかります。", "while_we_review_this_you_can_take_action_against_user": "私たちが確認している間でも、あなたは%sさんに対して対応することができます。" } }, @@ -706,7 +744,19 @@ "accessibility_hint": "チュートリアルを閉じるには、ダブルタップしてください" }, "bookmark": { - "title": "Bookmarks" + "title": "ブックマーク" + }, + "followed_tags": { + "title": "Followed Tags", + "header": { + "posts": "posts", + "participants": "participants", + "posts_today": "posts today" + }, + "actions": { + "follow": "Follow", + "unfollow": "Unfollow" + } } } } diff --git a/Localization/StringsConvertor/input/kab.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/kab.lproj/Localizable.stringsdict index 7fc6a50bb..f18a906c0 100644 --- a/Localization/StringsConvertor/input/kab.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/kab.lproj/Localizable.stringsdict @@ -50,6 +50,22 @@ %ld yisekkilen + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ left + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 n usekkil + other + %ld n isekkilen + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey @@ -120,7 +136,7 @@ NSStringFormatValueTypeKey ld one - 1 tsuffeɣt + 1 n tsuffeɣt other %ld n tsuffaɣ @@ -296,9 +312,9 @@ NSStringFormatValueTypeKey ld one - Yeqqim-d 1 wass + Yeqqim-d 1 n wass other - Qqimen-d %ld wussan + Qqimen-d %ld n wussan date.hour.left @@ -312,9 +328,9 @@ NSStringFormatValueTypeKey ld one - Yeqqim-d 1 usrag + Yeqqim-d 1 n wesrag other - Qqimen-d %ld yisragen + Qqimen-d %ld n yisragen date.minute.left @@ -328,9 +344,9 @@ NSStringFormatValueTypeKey ld one - 1 tesdat i d-yeqqimen + 1 n tesdat i d-yeqqimen other - %ld tesdatin i d-yeqqimen + %ld n tesdatin i d-yeqqimen date.second.left @@ -344,9 +360,9 @@ NSStringFormatValueTypeKey ld one - 1 tasint i d-yeqqimen + 1 n tasint i d-yeqqimen other - %ld tsinin i d-yeqqimen + %ld n tasinin i d-yeqqimen date.year.ago.abbr @@ -360,9 +376,9 @@ NSStringFormatValueTypeKey ld one - 1 useggas aya + %ld n useggas aya other - %ld yiseggasen aya + %ld n yiseggasen aya date.month.ago.abbr @@ -376,9 +392,9 @@ NSStringFormatValueTypeKey ld one - 1 wayyur aya + %ld n wayyur aya other - %ld wayyuren aya + %ld n wayyuren aya date.day.ago.abbr @@ -392,9 +408,9 @@ NSStringFormatValueTypeKey ld one - 1 wass aya + %ld n wass aya other - %ld wussan aya + %ld n wussan aya date.hour.ago.abbr @@ -408,9 +424,9 @@ NSStringFormatValueTypeKey ld one - 1 usrag aya + %ld n wesrag aya other - %ld yisragen aya + %ld n yisragen aya date.minute.ago.abbr @@ -424,9 +440,9 @@ NSStringFormatValueTypeKey ld one - 1 tesdat aya + %ld n tesdat aya other - %ld tesdatin aya + %ld n tesdatin aya date.second.ago.abbr @@ -440,9 +456,9 @@ NSStringFormatValueTypeKey ld one - 1 tasint aya + %ld n tasint aya other - %ld tsinin aya + %ld n tasinin aya diff --git a/Localization/StringsConvertor/input/kab.lproj/app.json b/Localization/StringsConvertor/input/kab.lproj/app.json index 9c5d7659a..720f979cb 100644 --- a/Localization/StringsConvertor/input/kab.lproj/app.json +++ b/Localization/StringsConvertor/input/kab.lproj/app.json @@ -51,6 +51,11 @@ "clean_cache": { "title": "Sfeḍ tuffirt", "message": "Yettwasfeḍ %s n tkatut tuffirt akken iwata." + }, + "translation_failed": { + "title": "Note", + "message": "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported.", + "button": "OK" } }, "controls": { @@ -75,9 +80,10 @@ "save_photo": "Sekles tawlaft", "copy_photo": "Nɣel tawlaft", "sign_in": "Qqen", - "sign_up": "Jerred amiḍan", + "sign_up": "Snulfu-d amiḍan", "see_more": "Wali ugar", "preview": "Taskant", + "copy": "Copy", "share": "Bḍu", "share_user": "Bḍu %s", "share_post": "Bḍu tasuffeɣt", @@ -91,12 +97,16 @@ "block_domain": "Sewḥel %s", "unblock_domain": "Serreḥ i %s", "settings": "Iɣewwaṛen", - "delete": "Kkes" + "delete": "Kkes", + "translate_post": { + "title": "Translate from %s", + "unknown_language": "Unknown" + } }, "tabs": { "home": "Agejdan", - "search": "Nadi", - "notification": "Tilɣa", + "search_and_explore": "Search and Explore", + "notifications": "Notifications", "profile": "Amaɣnu" }, "keyboard": { @@ -132,15 +142,17 @@ "sensitive_content": "Agbur amḥulfu", "media_content_warning": "Sit anida tebɣiḍ i wakken ad twaliḍ", "tap_to_reveal": "Sit i uskan", + "load_embed": "Load Embed", + "link_via_user": "%s via %s", "poll": { "vote": "Dɣeṛ", "closed": "Ifukk" }, "meta_entity": { - "url": "Link: %s", - "hashtag": "Hashtag: %s", - "mention": "Show Profile: %s", - "email": "Email address: %s" + "url": "Asaɣ : %s", + "hashtag": "Ahacṭag : %s", + "mention": "Sken-d amaɣnu : %s", + "email": "Tansa imayl : %s" }, "actions": { "reply": "Err", @@ -153,6 +165,7 @@ "show_image": "Sken tugna", "show_gif": "Sken GIF", "show_video_player": "Sken ameɣri n tvidyut", + "share_link_in_post": "Share Link in Post", "tap_then_hold_to_show_menu": "Sit teǧǧeḍ aḍad-ik•im i wakken ad d-iffeɣ wumuɣ" }, "tag": { @@ -168,6 +181,12 @@ "private": "D ineḍfaren-is kan i izemren ad walin tsuffeɣ-a.", "private_from_me": "D ineḍfaren-is kan i izemren ad walin tsuffeɣ-a.", "direct": "D ineḍfaren-is kan i izemren ad walin tsuffeɣ-a." + }, + "translation": { + "translated_from": "Translated from %s using %s", + "unknown_language": "Unknown", + "unknown_provider": "Unknown", + "show_original": "Shown Original" } }, "friendship": { @@ -218,10 +237,16 @@ "get_started": "Aha bdu tura", "log_in": "Qqen" }, + "login": { + "title": "Ansuf yess·ek·em", + "subtitle": "Log you in on the server you created your account on.", + "server_search_field": { + "placeholder": "Sekcem URL neɣ nadi ɣef uqeddac-ik·im" + } + }, "server_picker": { "title": "Mastodon yettwaxdem i yiseqdacen deg waṭas n temɣiwnin.", - "subtitle": "Fren tamɣiwent almend n wayen tḥemmleḍ, n tmurt-ik neɣ n yiswi-inek amatu.", - "subtitle_extend": "Fren tamɣiwent almend n wayen tḥemmleḍ, n tmurt-ik neɣ n yiswi-inek amatu. Yal tamɣiwent tsedday-itt tkebbanit neɣ amdan ilelliyen.", + "subtitle": "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers.", "button": { "category": { "all": "Akk", @@ -248,7 +273,6 @@ "category": "TAGGAYT" }, "input": { - "placeholder": "Nadi timɣiwnin", "search_servers_or_enter_url": "Nadi timɣiwnin neɣ sekcem URL" }, "empty_state": { @@ -352,8 +376,8 @@ "navigation_bar_state": { "offline": "Beṛṛa n tuqqna", "new_posts": "Tissufaɣ timaynutin", - "published": "Yettwasuffeɣ!", - "Publishing": "Asuffeɣ tasuffeɣt...", + "published": "Tettwasuffeɣ!", + "Publishing": "Asuffeɣ n tasuffeɣt...", "accessibility": { "logo_label": "Taqeffalt n ulugu", "logo_hint": "Sit i wakken ad tɛeddiḍ i usawen, sit tikkelt-nniḍen i wakken ad tɛeddiḍ ɣer wadig yezrin" @@ -385,8 +409,10 @@ "description_video": "Glem-d tavidyut i wid yesɛan ugur deg yiẓri...", "load_failed": "Load Failed", "upload_failed": "Upload Failed", - "can_not_recognize_this_media_attachment": "Can not regonize this media attachment", - "attachment_too_large": "Attachment too large" + "can_not_recognize_this_media_attachment": "Can not recognize this media attachment", + "attachment_too_large": "Attachment too large", + "compressing_state": "Compressing...", + "server_processing_state": "Server Processing..." }, "poll": { "duration_time": "Tangazt: %s", @@ -396,7 +422,9 @@ "one_day": "1 n wass", "three_days": "3 n wussan", "seven_days": "7 n wussan", - "option_number": "Taxtiṛt %ld" + "option_number": "Taxtiṛt %ld", + "the_poll_is_invalid": "The poll is invalid", + "the_poll_has_empty_option": "The poll has empty option" }, "content_warning": { "placeholder": "Aru alɣu-inek s telqeyt da..." @@ -417,7 +445,9 @@ "custom_emoji_picker": "Amefran n yimujiten udmawanen", "enable_content_warning": "Rmed alɣu n ugbur", "disable_content_warning": "Sens alɣu n ugbur", - "post_visibility_menu": "Umuɣ n ubani n tsuffeɣt" + "post_visibility_menu": "Umuɣ n ubani n tsuffeɣt", + "post_options": "Post Options", + "posting_as": "Posting as %s" }, "keyboard": { "discard_post": "Sefsex tasuffeɣt", @@ -433,15 +463,23 @@ "follows_you": "Yeṭṭafaṛ-ik•im" }, "dashboard": { - "posts": "tisuffaɣ", - "following": "iṭafaṛ", - "followers": "imeḍfaren" + "my_posts": "posts", + "my_following": "following", + "my_followers": "followers", + "other_posts": "posts", + "other_following": "following", + "other_followers": "followers" }, "fields": { + "joined": "Joined", "add_row": "Rnu izirig", "placeholder": { "label": "Tabzimt", "content": "Agbur" + }, + "verified": { + "short": "Verified on %s", + "long": "Ownership of this link was checked on %s" } }, "segmented_control": { @@ -656,7 +694,7 @@ "its_spam": "D aspam", "malicious_links_fake_engagement_or_repetetive_replies": "Yir iseɣwan, yir agman d tririyin i d-yettuɣalen", "it_violates_server_rules": "Truẓi n yilugan n uqeddac", - "you_are_aware_that_it_breaks_specific_rules": "Teẓriḍ y•tettruẓu kra n yilugan", + "you_are_aware_that_it_breaks_specific_rules": "Teẓriḍ y·tettruẓu kra n yilugan", "its_something_else": "Ɣef ssebba-nniḍen", "the_issue_does_not_fit_into_other_categories": "Ugur ur yemṣada ara akk d taggayin-nniḍen" }, @@ -707,6 +745,18 @@ }, "bookmark": { "title": "Bookmarks" + }, + "followed_tags": { + "title": "Followed Tags", + "header": { + "posts": "posts", + "participants": "participants", + "posts_today": "posts today" + }, + "actions": { + "follow": "Follow", + "unfollow": "Unfollow" + } } } } diff --git a/Localization/StringsConvertor/input/kmr.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/kmr.lproj/Localizable.stringsdict index 77571439f..c904186d8 100644 --- a/Localization/StringsConvertor/input/kmr.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/kmr.lproj/Localizable.stringsdict @@ -50,6 +50,22 @@ %ld tîp + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ maye + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 peyv + other + %ld peyv + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/kmr.lproj/app.json b/Localization/StringsConvertor/input/kmr.lproj/app.json index 098f514ee..5e23f74b9 100644 --- a/Localization/StringsConvertor/input/kmr.lproj/app.json +++ b/Localization/StringsConvertor/input/kmr.lproj/app.json @@ -51,6 +51,11 @@ "clean_cache": { "title": "Pêşbîrê pak bike", "message": "Pêşbîra %s biserketî hate pakkirin." + }, + "translation_failed": { + "title": "Nîşe", + "message": "Werger têk çû. Dibe ku rêvebir werger li ser vê rajakarê çalak nekiribe an jî ev rajakar guhertoyek kevntir a Mastodon e ku werger hîn nehatiye piştgirîkirin.", + "button": "BAŞ E" } }, "controls": { @@ -75,9 +80,10 @@ "save_photo": "Wêneyê tomar bike", "copy_photo": "Wêneyê jê bigire", "sign_in": "Têkeve", - "sign_up": "Tomar bibe", + "sign_up": "Ajimêr biafirîne", "see_more": "Bêtir bibîne", "preview": "Pêşdîtin", + "copy": "Jê bigire", "share": "Parve bike", "share_user": "%s parve bike", "share_post": "Şandiyê parve bike", @@ -91,12 +97,16 @@ "block_domain": "%s asteng bike", "unblock_domain": "%s asteng neke", "settings": "Sazkarî", - "delete": "Jê bibe" + "delete": "Jê bibe", + "translate_post": { + "title": "Ji %s wergerîne", + "unknown_language": "Nenas" + } }, "tabs": { "home": "Serrûpel", - "search": "Bigere", - "notification": "Agahdarî", + "search_and_explore": "Bigere û vekole", + "notifications": "Agahdarî", "profile": "Profîl" }, "keyboard": { @@ -132,6 +142,8 @@ "sensitive_content": "Naveroka hestiyarî", "media_content_warning": "Ji bo eşkerekirinê li derekî bitikîne", "tap_to_reveal": "Ji bo dîtinê bitikîne", + "load_embed": "Load Embed", + "link_via_user": "%s bi riya %s", "poll": { "vote": "Deng bide", "closed": "Girtî" @@ -153,6 +165,7 @@ "show_image": "Wêneyê nîşan bide", "show_gif": "GIF nîşan bide", "show_video_player": "Lêdera vîdyoyê nîşan bide", + "share_link_in_post": "Girêdanê di şandiyê de parve bike", "tap_then_hold_to_show_menu": "Ji bo nîşandana menuyê dirêj bitikîne" }, "tag": { @@ -168,6 +181,12 @@ "private": "Tenê şopînerên wan dikarin vê şandiyê bibînin.", "private_from_me": "Tenê şopînerên min dikarin vê şandiyê bibînin.", "direct": "Tenê bikarhênerê qalkirî dikare vê şandiyê bibîne." + }, + "translation": { + "translated_from": "Hate wergerandin ji %s bi riya %s", + "unknown_language": "Nenas", + "unknown_provider": "Nenas", + "show_original": "A resen nîşan bide" } }, "friendship": { @@ -218,10 +237,16 @@ "get_started": "Dest pê bike", "log_in": "Têkeve" }, + "login": { + "title": "Dîsa bi xêr hatî", + "subtitle": "Têketinê bike ser rajekarê ku te ajimêrê xwe tê de çê kiriye.", + "server_search_field": { + "placeholder": "Girêdanê têxe an jî li rajekarê xwe bigere" + } + }, "server_picker": { "title": "Mastodon ji bikarhênerên di civakên cuda de pêk tê.", - "subtitle": "Li gorî berjewendî, herêm, an jî armancek gelemperî civakekê hilbijêre.", - "subtitle_extend": "Li gorî berjewendî, herêm, an jî armancek gelemperî civakekê hilbijêre. Her civakek ji hêla rêxistinek an kesek bi tevahî serbixwe ve tê xebitandin.", + "subtitle": "Li gorî herêm, berjewendî, an jî armanceke giştî rajekarekê hilbijêre. Tu hîn jî dikarî li ser Mastodon bi her kesî re biaxivî, her rajekarê te çi be.", "button": { "category": { "all": "Hemû", @@ -248,8 +273,7 @@ "category": "BEŞ" }, "input": { - "placeholder": "Li rajekaran bigere", - "search_servers_or_enter_url": "Li rajekaran bigere an jî girêdanê têxe" + "search_servers_or_enter_url": "Li civakan bigere an jî girêdanê têxe" }, "empty_state": { "finding_servers": "Peydakirina rajekarên berdest...", @@ -386,7 +410,9 @@ "load_failed": "Barkirin têk çû", "upload_failed": "Barkirin têk çû", "can_not_recognize_this_media_attachment": "Nikare ev pêveka medyayê nas bike", - "attachment_too_large": "Pêvek pir mezin e" + "attachment_too_large": "Pêvek pir mezin e", + "compressing_state": "Tê guvaştin...", + "server_processing_state": "Pêvajoya rajekar pêş de diçe..." }, "poll": { "duration_time": "Dirêjî: %s", @@ -396,7 +422,9 @@ "one_day": "1 Roj", "three_days": "3 Roj", "seven_days": "7 Roj", - "option_number": "Vebijêrk %ld" + "option_number": "Vebijêrk %ld", + "the_poll_is_invalid": "Ev dengdayîn ne derbasdar e", + "the_poll_has_empty_option": "Vebijêrkên vê dengdayînê vala ne" }, "content_warning": { "placeholder": "Li vir hişyariyek hûrgilî binivîsine..." @@ -417,7 +445,9 @@ "custom_emoji_picker": "Hilbijêrê emojî yên kesanekirî", "enable_content_warning": "Hişyariya naverokê çalak bike", "disable_content_warning": "Hişyariya naverokê neçalak bike", - "post_visibility_menu": "Kulîna xuyabûna şandiyê" + "post_visibility_menu": "Kulîna xuyabûna şandiyê", + "post_options": "Vebijêrkên şandiyê", + "posting_as": "Biweşîne wekî %s" }, "keyboard": { "discard_post": "Şandî paşguh bike", @@ -433,15 +463,23 @@ "follows_you": "Te dişopîne" }, "dashboard": { - "posts": "şandî", - "following": "dişopîne", - "followers": "şopîner" + "my_posts": "şandî", + "my_following": "dişopîne", + "my_followers": "şopîner", + "other_posts": "şandî", + "other_following": "dişopîne", + "other_followers": "şopîner" }, "fields": { + "joined": "Dîroka tevlîbûnê", "add_row": "Rêzê tevlî bike", "placeholder": { "label": "Nîşan", "content": "Naverok" + }, + "verified": { + "short": "Hate piştrastkirin li ser %s", + "long": "Xwedaniya li vê girêdanê di %s de hatiye kontrolkirin" } }, "segmented_control": { @@ -707,6 +745,18 @@ }, "bookmark": { "title": "Şûnpel" + }, + "followed_tags": { + "title": "Hashtagên şopandî", + "header": { + "posts": "şandî", + "participants": "beşdar", + "posts_today": "şandiyên îro" + }, + "actions": { + "follow": "Bişopîne", + "unfollow": "Neşopîne" + } } } } diff --git a/Localization/StringsConvertor/input/ko.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/ko.lproj/Localizable.stringsdict index 9628be614..77aac5569 100644 --- a/Localization/StringsConvertor/input/ko.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/ko.lproj/Localizable.stringsdict @@ -44,6 +44,20 @@ %ld 글자 + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ 글자 남음 + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld 글자 + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/ko.lproj/app.json b/Localization/StringsConvertor/input/ko.lproj/app.json index 826ac389c..7d4e48e09 100644 --- a/Localization/StringsConvertor/input/ko.lproj/app.json +++ b/Localization/StringsConvertor/input/ko.lproj/app.json @@ -51,6 +51,11 @@ "clean_cache": { "title": "캐시 삭제", "message": "Successfully cleaned %s cache." + }, + "translation_failed": { + "title": "Note", + "message": "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported.", + "button": "확인" } }, "controls": { @@ -75,9 +80,10 @@ "save_photo": "사진 저장", "copy_photo": "사진 복사", "sign_in": "로그인", - "sign_up": "회원가입", + "sign_up": "계정 생성", "see_more": "더 보기", "preview": "미리보기", + "copy": "Copy", "share": "공유", "share_user": "%s를 공유", "share_post": "게시물 공유", @@ -91,12 +97,16 @@ "block_domain": "%s 차단하기", "unblock_domain": "%s 차단 해제", "settings": "설정", - "delete": "삭제" + "delete": "삭제", + "translate_post": { + "title": "Translate from %s", + "unknown_language": "Unknown" + } }, "tabs": { "home": "홈", - "search": "검색", - "notification": "알림", + "search_and_explore": "Search and Explore", + "notifications": "Notifications", "profile": "프로필" }, "keyboard": { @@ -132,6 +142,8 @@ "sensitive_content": "민감한 콘텐츠", "media_content_warning": "아무 곳이나 눌러서 보기", "tap_to_reveal": "눌러서 확인", + "load_embed": "Load Embed", + "link_via_user": "%s via %s", "poll": { "vote": "투표", "closed": "마감" @@ -153,6 +165,7 @@ "show_image": "이미지 표시", "show_gif": "GIF 보기", "show_video_player": "비디오 플레이어 보기", + "share_link_in_post": "Share Link in Post", "tap_then_hold_to_show_menu": "Tap then hold to show menu" }, "tag": { @@ -168,6 +181,12 @@ "private": "Only their followers can see this post.", "private_from_me": "Only my followers can see this post.", "direct": "Only mentioned user can see this post." + }, + "translation": { + "translated_from": "%s에서 %s를 사용해 번역됨", + "unknown_language": "알 수 없음", + "unknown_provider": "알 수 없음", + "show_original": "원본 보기" } }, "friendship": { @@ -218,10 +237,16 @@ "get_started": "시작하기", "log_in": "로그인" }, + "login": { + "title": "돌아오신 것을 환영합니다", + "subtitle": "계정을 만든 서버에 로그인.", + "server_search_field": { + "placeholder": "URL을 입력하거나 서버를 검색" + } + }, "server_picker": { "title": "서버를 고르세요,\n아무 서버나 좋습니다.", - "subtitle": "Pick a server based on your interests, region, or a general purpose one.", - "subtitle_extend": "Pick a server based on your interests, region, or a general purpose one. Each server is operated by an entirely independent organization or individual.", + "subtitle": "당신의 지역이나, 관심사에 따라, 혹은 그냥 일반적인 목적의 서버를 고르세요. 어떤 서버를 고르더라도 마스토돈의 다른 모두와 소통할 수 있습니다.", "button": { "category": { "all": "모두", @@ -248,8 +273,7 @@ "category": "분류" }, "input": { - "placeholder": "서버 검색", - "search_servers_or_enter_url": "서버를 검색하거나 URL을 입력하세요" + "search_servers_or_enter_url": "커뮤니티를 검색하거나 URL을 입력" }, "empty_state": { "finding_servers": "사용 가능한 서버를 찾는 중입니다...", @@ -385,8 +409,10 @@ "description_video": "시각장애인을 위한 영상 설명…", "load_failed": "불러오기 실패", "upload_failed": "업로드 실패", - "can_not_recognize_this_media_attachment": "이 미디어 첨부파일을 인식할 수 없습니다", - "attachment_too_large": "첨부파일이 너무 큽니다" + "can_not_recognize_this_media_attachment": "Can not recognize this media attachment", + "attachment_too_large": "첨부파일이 너무 큽니다", + "compressing_state": "Compressing...", + "server_processing_state": "Server Processing..." }, "poll": { "duration_time": "기간: %s", @@ -396,7 +422,9 @@ "one_day": "1일", "three_days": "3일", "seven_days": "7일", - "option_number": "옵션 %ld" + "option_number": "옵션 %ld", + "the_poll_is_invalid": "The poll is invalid", + "the_poll_has_empty_option": "The poll has empty option" }, "content_warning": { "placeholder": "정확한 경고 문구를 여기에 작성하세요…" @@ -417,7 +445,9 @@ "custom_emoji_picker": "커스텀 에모지 선택기", "enable_content_warning": "열람 주의 설정", "disable_content_warning": "열람 주의 해제", - "post_visibility_menu": "게시물 공개범위 메뉴" + "post_visibility_menu": "게시물 공개범위 메뉴", + "post_options": "게시물 옵션", + "posting_as": "%s로 게시" }, "keyboard": { "discard_post": "글 버리기", @@ -433,15 +463,23 @@ "follows_you": "Follows You" }, "dashboard": { - "posts": "게시물", - "following": "팔로잉", - "followers": "팔로워" + "my_posts": "posts", + "my_following": "following", + "my_followers": "followers", + "other_posts": "posts", + "other_following": "following", + "other_followers": "followers" }, "fields": { + "joined": "가입일", "add_row": "행 추가", "placeholder": { "label": "라벨", "content": "내용" + }, + "verified": { + "short": "Verified on %s", + "long": "Ownership of this link was checked on %s" } }, "segmented_control": { @@ -707,6 +745,18 @@ }, "bookmark": { "title": "Bookmarks" + }, + "followed_tags": { + "title": "팔로우한 태그", + "header": { + "posts": "게시물", + "participants": "참가자", + "posts_today": "오늘" + }, + "actions": { + "follow": "팔로우", + "unfollow": "팔로우 해제" + } } } } diff --git a/Localization/StringsConvertor/input/lv.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/lv.lproj/Localizable.stringsdict index 25f32c98d..fd327d745 100644 --- a/Localization/StringsConvertor/input/lv.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/lv.lproj/Localizable.stringsdict @@ -13,17 +13,17 @@ NSStringFormatValueTypeKey ld zero - %ld unread notification + %ld nelasītu paziņojumu one - 1 unread notification + 1 nelasīts paziņojums other - %ld unread notification + %ld nelasīti paziņojumi a11y.plural.count.input_limit_exceeds NSStringLocalizedFormatKey - Input limit exceeds %#@character_count@ + Ievades ierobežojums pārsniedz %#@character_count@ character_count NSStringFormatSpecTypeKey @@ -31,17 +31,17 @@ NSStringFormatValueTypeKey ld zero - %ld characters + %ld rakstzīmju one - 1 character + 1 rakstzīme other - %ld characters + %ld rakstzīmes a11y.plural.count.input_limit_remains NSStringLocalizedFormatKey - Input limit remains %#@character_count@ + Ievades ierobežojums paliek %#@character_count@ character_count NSStringFormatSpecTypeKey @@ -49,11 +49,29 @@ NSStringFormatValueTypeKey ld zero - %ld characters + %ld rakstzīmju one - 1 character + 1 rakstzīme other - %ld characters + %ld rakstzīmes + + + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ palikušas + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + %ld rakstzīmju + one + 1 rakstzīme + other + %ld rakstzīmes plural.count.followed_by_and_mutual @@ -80,11 +98,11 @@ NSStringFormatValueTypeKey ld zero - Followed by %1$@, and %ld mutuals + Seko %1$@ un %ld kopīgi one - Followed by %1$@, and another mutual + Seko %1$@ un vēl viens kopīgs other - Followed by %1$@, and %ld mutuals + Seko %1$@ un %ld kopīgi plural.count.metric_formatted.post @@ -98,11 +116,11 @@ NSStringFormatValueTypeKey ld zero - posts + ziņu one - post + ziņa other - posts + ziņas plural.count.media @@ -116,11 +134,11 @@ NSStringFormatValueTypeKey ld zero - %ld media + %ld multivide one - 1 media + 1 multivide other - %ld media + %ld multivide plural.count.post @@ -134,11 +152,11 @@ NSStringFormatValueTypeKey ld zero - %ld posts + %ld ziņu one - 1 post + 1 ziņa other - %ld posts + %ld ziņas plural.count.favorite @@ -152,11 +170,11 @@ NSStringFormatValueTypeKey ld zero - %ld favorites + %ld iecienītu one - 1 favorite + 1 iecienīts other - %ld favorites + %ld iecienīti plural.count.reblog @@ -170,11 +188,11 @@ NSStringFormatValueTypeKey ld zero - %ld reblogs + %ld reblogu one - 1 reblog + 1 reblogs other - %ld reblogs + %ld reblogi plural.count.reply @@ -188,11 +206,11 @@ NSStringFormatValueTypeKey ld zero - %ld replies + %ld atbilžu one - 1 reply + 1 atbilde other - %ld replies + %ld atbildes plural.count.vote @@ -206,11 +224,11 @@ NSStringFormatValueTypeKey ld zero - %ld votes + %ld balsu one - 1 vote + 1 balss other - %ld votes + %ld balsis plural.count.voter @@ -224,11 +242,11 @@ NSStringFormatValueTypeKey ld zero - %ld voters + %ld balsotāju one - 1 voter + 1 balsotājs other - %ld voters + %ld balsotāji plural.people_talking @@ -242,11 +260,11 @@ NSStringFormatValueTypeKey ld zero - %ld people talking + %ld cilvēku apspriež one - 1 people talking + 1 cilvēks apspriež other - %ld people talking + %ld cilvēki apspriež plural.count.following @@ -260,11 +278,11 @@ NSStringFormatValueTypeKey ld zero - %ld following + %ld sekotāju one - 1 following + 1 sekotājs other - %ld following + %ld sekotāji plural.count.follower @@ -278,11 +296,11 @@ NSStringFormatValueTypeKey ld zero - %ld followers + %ld sekotāju one - 1 follower + 1 sekotājs other - %ld followers + %ld sekotāji date.year.left @@ -296,11 +314,11 @@ NSStringFormatValueTypeKey ld zero - %ld years left + palicis %ld gadu one - 1 year left + palicis 1 gads other - %ld years left + palikuši %ld gadi date.month.left @@ -314,11 +332,11 @@ NSStringFormatValueTypeKey ld zero - %ld months left + palicis %ld mēnešu one - 1 months left + palicis 1 mēnesis other - %ld months left + palikuši %ld mēneši date.day.left @@ -332,11 +350,11 @@ NSStringFormatValueTypeKey ld zero - %ld days left + palikušas %ld dienas one - 1 day left + palikusi 1 diena other - %ld days left + palikušas %ld dienas date.hour.left @@ -350,11 +368,11 @@ NSStringFormatValueTypeKey ld zero - %ld hours left + atlikušas %ld stundas one - 1 hour left + atlikusi 1 stunda other - %ld hours left + atlikušas %ld stundas date.minute.left @@ -368,11 +386,11 @@ NSStringFormatValueTypeKey ld zero - %ld minutes left + atlikušas %ld minūtes one - 1 minute left + atlikusi 1 minūte other - %ld minutes left + atlikušas %ld minūtes date.second.left @@ -386,11 +404,11 @@ NSStringFormatValueTypeKey ld zero - %ld seconds left + atlikušas %ld sekundes one - 1 second left + atlikusi 1 sekunde other - %ld seconds left + atlikušas %ld sekundes date.year.ago.abbr @@ -404,11 +422,11 @@ NSStringFormatValueTypeKey ld zero - %ldy ago + pirms %ld g. one - 1y ago + pirms 1 g. other - %ldy ago + pirms %ld g. date.month.ago.abbr @@ -422,11 +440,11 @@ NSStringFormatValueTypeKey ld zero - %ldM ago + pirms %ld M. one - 1M ago + pirms 1 M. other - %ldM ago + pirms %ld M. date.day.ago.abbr @@ -440,11 +458,11 @@ NSStringFormatValueTypeKey ld zero - %ldd ago + pirms %ld d. one - 1d ago + pirms 1 d. other - %ldd ago + pirms %ld d. date.hour.ago.abbr @@ -458,11 +476,11 @@ NSStringFormatValueTypeKey ld zero - %ldh ago + pirms %ld st. one - 1h ago + pirms 1 st. other - %ldh ago + pirms %ld st. date.minute.ago.abbr @@ -476,11 +494,11 @@ NSStringFormatValueTypeKey ld zero - %ldm ago + pirms %ld m. one - 1m ago + pirms 1 m. other - %ldm ago + pirms %ld m. date.second.ago.abbr @@ -494,11 +512,11 @@ NSStringFormatValueTypeKey ld zero - %lds ago + pirms %ld s. one - 1s ago + pirms 1 s. other - %lds ago + pirms %ld s. diff --git a/Localization/StringsConvertor/input/lv.lproj/app.json b/Localization/StringsConvertor/input/lv.lproj/app.json index 2835f0887..01a8d0515 100644 --- a/Localization/StringsConvertor/input/lv.lproj/app.json +++ b/Localization/StringsConvertor/input/lv.lproj/app.json @@ -6,30 +6,30 @@ "please_try_again_later": "Lūdzu, mēģiniet vēlreiz vēlāk." }, "sign_up_failure": { - "title": "Sign Up Failure" + "title": "Reģistrācijas Neveiksme" }, "server_error": { "title": "Servera kļūda" }, "vote_failure": { - "title": "Vote Failure", + "title": "Balsošanas Neveiksme", "poll_ended": "Balsošana beidzās" }, "discard_post_content": { "title": "Atmest malnrakstu", - "message": "Confirm to discard composed post content." + "message": "Apstiprini, lai atmestu izveidotās ziņas saturu." }, "publish_post_failure": { - "title": "Publish Failure", - "message": "Failed to publish the post.\nPlease check your internet connection.", + "title": "Publicēšanas Neveiksme", + "message": "Neizdevās publicēt ziņu.\nLūdzu, pārbaudi savu interneta savienojumu.", "attachments_message": { - "video_attach_with_photo": "Cannot attach a video to a post that already contains images.", - "more_than_one_video": "Cannot attach more than one video." + "video_attach_with_photo": "Nevar pievienot videoklipu ziņai, kurā jau ir attēli.", + "more_than_one_video": "Nevar pievienot vairāk kā vienu video." } }, "edit_profile_failure": { - "title": "Edit Profile Error", - "message": "Cannot edit profile. Please try again." + "title": "Profila Rediģēšanas Kļūda", + "message": "Nevar rediģēt profilu. Lūdzu mēģini vēlreiz." }, "sign_out": { "title": "Iziet", @@ -37,20 +37,25 @@ "confirm": "Iziet" }, "block_domain": { - "title": "Are you really, really sure you want to block the entire %s? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain and any of your followers from that domain will be removed.", - "block_entire_domain": "Block Domain" + "title": "Vai tiešām tiešām vēlies bloķēt visu %s? Vairumā gadījumu pietiek ar dažiem mērķtiecīgiem blokiem vai klusinātājiem, un tie ir vēlami. Tu neredzēsi saturu no šī domēna, un visi tavi sekotāji no šī domēna tiks noņemti.", + "block_entire_domain": "Bloķēt Domēnu" }, "save_photo_failure": { - "title": "Save Photo Failure", - "message": "Please enable the photo library access permission to save the photo." + "title": "Attēla Saglabāšanas Kļūda", + "message": "Lai saglabātu fotoattēlu, lūdzu, iespējo fotoattēlu bibliotēkas piekļuves atļauju." }, "delete_post": { "title": "Dzēst ierakstu", "message": "Vai tiešām vēlies dzēst ierakstu?" }, "clean_cache": { - "title": "Clean Cache", - "message": "Successfully cleaned %s cache." + "title": "Iztīrīt Kešatmiņu", + "message": "%s kešatmiņa ir veiksmīgi iztīrīta." + }, + "translation_failed": { + "title": "Piezīme", + "message": "Tulkošana neizdevās. Varbūt administrators nav iespējojis tulkojumus šajā serverī vai arī šajā serverī darbojas vecāka Mastodon versija, kurā tulkojumi vēl netiek atbalstīti.", + "button": "Labi" } }, "controls": { @@ -75,47 +80,52 @@ "save_photo": "Saglabāt bildi", "copy_photo": "Kopēt bildi", "sign_in": "Pieteikties", - "sign_up": "Reģistrēties", + "sign_up": "Izveidot kontu", "see_more": "Skatīt vairāk", "preview": "Priekšskatījums", + "copy": "Kopēt", "share": "Dalīties", - "share_user": "Share %s", - "share_post": "Share Post", + "share_user": "Kopīgot %s", + "share_post": "Kopīgot Ziņu", "open_in_safari": "Atvērt Safari", "open_in_browser": "Atvērt pārlūkprogrammā", "find_people": "Atrodi cilvēkus kam sekot", - "manually_search": "Manually search instead", + "manually_search": "Tā vietā meklēt manuāli", "skip": "Izlaist", "reply": "Atbildēt", "report_user": "Ziņot par lietotāju @%s", "block_domain": "Bloķēt %s", "unblock_domain": "Atbloķēt %s", "settings": "Iestatījumi", - "delete": "Dzēst" + "delete": "Dzēst", + "translate_post": { + "title": "Tulkot no %s", + "unknown_language": "Nezināms" + } }, "tabs": { "home": "Sākums", - "search": "Meklēšana", - "notification": "Paziņojums", + "search_and_explore": "Meklēt un Pārlūkot", + "notifications": "Paziņojumi", "profile": "Profils" }, "keyboard": { "common": { "switch_to_tab": "Pārslēgties uz: %s", "compose_new_post": "Veidot jaunu ziņu", - "show_favorites": "Show Favorites", + "show_favorites": "Parādīt Izlasi", "open_settings": "Atvērt iestatījumus" }, "timeline": { - "previous_status": "Previous Post", - "next_status": "Next Post", - "open_status": "Open Post", - "open_author_profile": "Open Author's Profile", - "open_reblogger_profile": "Open Reblogger's Profile", - "reply_status": "Reply to Post", - "toggle_reblog": "Toggle Reblog on Post", - "toggle_favorite": "Toggle Favorite on Post", - "toggle_content_warning": "Toggle Content Warning", + "previous_status": "Iepriekšējā Ziņa", + "next_status": "Nākamā Ziņa", + "open_status": "Atvērt Ziņu", + "open_author_profile": "Atvērt Autora Profilu", + "open_reblogger_profile": "Atvērt Reblogotāja Profilu", + "reply_status": "Atbildēt uz Ziņu", + "toggle_reblog": "Pārslēgt Reblogs uz Ziņu", + "toggle_favorite": "Pārslēgt Izlasi uz Ziņas", + "toggle_content_warning": "Pārslēgt Satura Brīdinājumu", "preview_image": "Priekšskata attēls" }, "segmented_control": { @@ -124,50 +134,59 @@ } }, "status": { - "user_reblogged": "%s reblogged", - "user_replied_to": "Replied to %s", - "show_post": "Show Post", + "user_reblogged": "%s reblogoja", + "user_replied_to": "Atbildēja %s", + "show_post": "Parādīt Ziņu", "show_user_profile": "Parādīt lietotāja profilu", "content_warning": "Satura brīdinājums", "sensitive_content": "Sensitīvs saturs", - "media_content_warning": "Tap anywhere to reveal", - "tap_to_reveal": "Tap to reveal", + "media_content_warning": "Pieskarieties jebkurā vietā, lai atklātu", + "tap_to_reveal": "Piest, lai atklātu", + "load_embed": "Ielādēt Iegultos", + "link_via_user": "%s caur %s", "poll": { "vote": "Balsot", "closed": "Aizvērts" }, "meta_entity": { - "url": "Link: %s", - "hashtag": "Hashtag: %s", - "mention": "Show Profile: %s", - "email": "Email address: %s" + "url": "Saite: %s", + "hashtag": "Sajaukt: %s", + "mention": "Rādīt Profilu: %s", + "email": "E-pasta adrese: %s" }, "actions": { "reply": "Atbildēt", "reblog": "Reblogot", - "unreblog": "Undo reblog", + "unreblog": "Atsaukt reblogu", "favorite": "Izlase", "unfavorite": "Izņemt no izlases", "menu": "Izvēlne", "hide": "Slēpt", "show_image": "Rādīt attēlu", "show_gif": "Rādīt GIF", - "show_video_player": "Show video player", - "tap_then_hold_to_show_menu": "Tap then hold to show menu" + "show_video_player": "Rādīt video atskaņotāju", + "share_link_in_post": "Kopīgot Saiti Ziņā", + "tap_then_hold_to_show_menu": "Pieskaries un turi, lai parādītu izvēlni" }, "tag": { "url": "URL", "mention": "Pieminēt", "link": "Saite", - "hashtag": "Hashtag", + "hashtag": "Tēmturis", "email": "E-pasts", "emoji": "Emocijzīmes" }, "visibility": { - "unlisted": "Everyone can see this post but not display in the public timeline.", - "private": "Only their followers can see this post.", - "private_from_me": "Only my followers can see this post.", - "direct": "Only mentioned user can see this post." + "unlisted": "Ikviens var redzēt šo ziņu, bet to nevar parādīt publiskajā laikrindā.", + "private": "Šo ziņu var redzēt tikai viņu sekotāji.", + "private_from_me": "Šo ziņu var redzēt tikai mani sekotāji.", + "direct": "Šo ziņu var redzēt tikai minētais lietotājs." + }, + "translation": { + "translated_from": "Tulkots no %s, izmantojot %s", + "unknown_language": "Nezināms", + "unknown_provider": "Nezināms", + "show_original": "Parādīts Oriģināls" } }, "friendship": { @@ -186,9 +205,9 @@ "unmute": "Noņemt apklusinājumu", "unmute_user": "Noņemt apklusinājumu @%s", "muted": "Apklusināts", - "edit_info": "Edit Info", - "show_reblogs": "Show Reblogs", - "hide_reblogs": "Hide Reblogs" + "edit_info": "Rediģēt", + "show_reblogs": "Rādīt Reblogus", + "hide_reblogs": "Paslēpt Reblogus" }, "timeline": { "filtered": "Filtrēts", @@ -196,42 +215,48 @@ "now": "Tagad" }, "loader": { - "load_missing_posts": "Load missing posts", - "loading_missing_posts": "Loading missing posts...", + "load_missing_posts": "Ielādēt trūkstošās ziņas", + "loading_missing_posts": "Ielādē trūkstošās ziņas...", "show_more_replies": "Rādīt vairāk atbildes" }, "header": { - "no_status_found": "No Post Found", - "blocking_warning": "You can’t view this user's profile\nuntil you unblock them.\nYour profile looks like this to them.", - "user_blocking_warning": "You can’t view %s’s profile\nuntil you unblock them.\nYour profile looks like this to them.", - "blocked_warning": "You can’t view this user’s profile\nuntil they unblock you.", - "user_blocked_warning": "You can’t view %s’s profile\nuntil they unblock you.", - "suspended_warning": "This user has been suspended.", - "user_suspended_warning": "%s’s account has been suspended." + "no_status_found": "Nav Atrastu Ziņu", + "blocking_warning": "Tu nevari skatīt šī lietotāja profilu,\nlīdz tu tos atbloķē.\nTavs profils viņiem izskatās šādi.", + "user_blocking_warning": "Tu nevari skatīt %s profilu,\nlīdz tu tos atbloķē.\nTavs profils viņiem izskatās šādi.", + "blocked_warning": "Tu nevari skatīt šī lietotāja profilu\nlīdz tie tevi atbloķē.", + "user_blocked_warning": "Tu nevari skatīt %s profilu,\nlīdz tie tevi atbloķē.", + "suspended_warning": "Šī lietotāja darbība ir apturēta.", + "user_suspended_warning": "%s konta darbība ir apturēta." } } } }, "scene": { "welcome": { - "slogan": "Social networking\nback in your hands.", - "get_started": "Get Started", + "slogan": "Sociālie tīkli\natpakaļ tavās rokās.", + "get_started": "Sāc", "log_in": "Pieteikties" }, + "login": { + "title": "Laipni lūdzam atpakaļ", + "subtitle": "Piesakies serverī, kurā izveidoji savu kontu.", + "server_search_field": { + "placeholder": "Ievadi URL vai meklē savu serveri" + } + }, "server_picker": { - "title": "Mastodon is made of users in different servers.", - "subtitle": "Pick a server based on your interests, region, or a general purpose one.", - "subtitle_extend": "Pick a server based on your interests, region, or a general purpose one. Each server is operated by an entirely independent organization or individual.", + "title": "Mastodon veido lietotāji dažādos serveros.", + "subtitle": "Izvēlieties serveri, pamatojoties uz savu reģionu, interesēm vai vispārīgu mērķi. Tu joprojām vari tērzēt ar jebkuru Mastodon lietotāju neatkarīgi no taviem serveriem.", "button": { "category": { "all": "Visi", "all_accessiblity_description": "Katekorija: Visi", - "academia": "academia", - "activism": "activism", + "academia": "akadēmija", + "activism": "aktīvisms", "food": "ēdiens", - "furry": "furry", + "furry": "pūkains", "games": "spēles", - "general": "general", + "general": "galvenais", "journalism": "žurnālisms", "lgbt": "lgbt", "regional": "regionāli", @@ -248,18 +273,17 @@ "category": "KATEGORIJA" }, "input": { - "placeholder": "Meklēt serverus", - "search_servers_or_enter_url": "Search servers or enter URL" + "search_servers_or_enter_url": "Meklēt kopienas vai ievadīt URL" }, "empty_state": { - "finding_servers": "Finding available servers...", - "bad_network": "Something went wrong while loading the data. Check your internet connection.", + "finding_servers": "Meklē piejamos serverus...", + "bad_network": "Ielādējot datus, radās problēma. Pārbaudi interneta savienojumu.", "no_results": "Nav rezultātu" } }, "register": { - "title": "Let’s get you set up on %s", - "lets_get_you_set_up_on_domain": "Let’s get you set up on %s", + "title": "Ļauj tevi iestatīt %s", + "lets_get_you_set_up_on_domain": "Ļauj tevi iestatīt %s", "input": { "avatar": { "delete": "Dzēst" @@ -276,7 +300,7 @@ }, "password": { "placeholder": "parole", - "require": "Your password needs at least:", + "require": "Tavai parolei ir nepieciešams vismaz:", "character_limit": "8 rakstzīmes", "accessibility": { "checked": "atzīmēts", @@ -298,29 +322,29 @@ "reason": "Iemesls" }, "reason": { - "blocked": "%s contains a disallowed email provider", + "blocked": "%s satur neatļautu e-pasta pakalpojumu sniedzēju", "unreachable": "%s šķiet, ka neeksistē", "taken": "%s jau tiek izmantots", - "reserved": "%s is a reserved keyword", - "accepted": "%s must be accepted", + "reserved": "%s ir rezervēts atslēgvārds", + "accepted": "%s jābūt apstiprinātām", "blank": "%s ir obligāts", "invalid": "%s ir nederīgs", "too_long": "%s ir pārāk garaš", "too_short": "%s ir pārāk īs", - "inclusion": "%s is not a supported value" + "inclusion": "%s nav atbalstīta vērtība" }, "special": { - "username_invalid": "Username must only contain alphanumeric characters and underscores", - "username_too_long": "Username is too long (can’t be longer than 30 characters)", - "email_invalid": "This is not a valid email address", - "password_too_short": "Password is too short (must be at least 8 characters)" + "username_invalid": "Lietotājvārdā drīkst būt tikai burtciparu rakstzīmes un zemsvītras", + "username_too_long": "Lietotājvārds ir par garu (nedrīkst būt garāks par 30 rakstzīmēm)", + "email_invalid": "Šī nav derīga e-pasta adrese", + "password_too_short": "Parole ir pārāk īsa (jābūt vismaz 8 rakstzīmēm)" } } }, "server_rules": { - "title": "Some ground rules.", - "subtitle": "These are set and enforced by the %s moderators.", - "prompt": "By continuing, you’re subject to the terms of service and privacy policy for %s.", + "title": "Daži pamatnoteikumi.", + "subtitle": "Tos iestata un ievieš %s moderatori.", + "prompt": "Turpinot, uz tevi attiecas %s pakalpojumu sniegšanas noteikumi un konfidencialitātes politika.", "terms_of_service": "pakalpojuma noteikumi", "privacy_policy": "privātuma nosacījumi", "button": { @@ -328,45 +352,45 @@ } }, "confirm_email": { - "title": "One last thing.", - "subtitle": "Tap the link we emailed to you to verify your account.", - "tap_the_link_we_emailed_to_you_to_verify_your_account": "Tap the link we emailed to you to verify your account", + "title": "Pēdējā lieta.", + "subtitle": "Pieskaries saitei, ko nosūtījām tev pa e-pastu, lai verificētu savu kontu.", + "tap_the_link_we_emailed_to_you_to_verify_your_account": "Pieskaries saitei, ko nosūtījām tev pa e-pastu, lai verificētu savu kontu", "button": { - "open_email_app": "Open Email App", + "open_email_app": "Atvērt E-pasta Lietotni", "resend": "Nosūtīt atkārtoti" }, "dont_receive_email": { "title": "Pārbaudi savu e-pastu", - "description": "Check if your email address is correct as well as your junk folder if you haven’t.", + "description": "Pārbaudi, vai tava e-pasta adrese ir pareiza, kā arī savu mēstuļu mapi, ja tā nav.", "resend_email": "Atkārtoti nosūtīt e-pastu" }, "open_email_app": { - "title": "Check your inbox.", - "description": "We just sent you an email. Check your junk folder if you haven’t.", - "mail": "Mail", - "open_email_client": "Open Email Client" + "title": "Pārbaudi savu iesūtni.", + "description": "Mēs tikko nosūtījām tev e-pastu. Pārbaudi savu mēstuļu mapi, ja neesi to saņēmis.", + "mail": "Pasts", + "open_email_client": "Atvērt E-pasta Klientu" } }, "home_timeline": { - "title": "Home", + "title": "Sākums", "navigation_bar_state": { - "offline": "Offline", - "new_posts": "See new posts", - "published": "Published!", - "Publishing": "Publishing post...", + "offline": "Bezsaistē", + "new_posts": "Skatīt jaunās ziņas", + "published": "Publicēts!", + "Publishing": "Publicē ziņu...", "accessibility": { - "logo_label": "Logo Button", - "logo_hint": "Tap to scroll to top and tap again to previous location" + "logo_label": "Logotipa Poga", + "logo_hint": "Pieskaries, lai ritinātu uz augšu, un vēlreiz pieskaries iepriekšējai atrašanās vietai" } } }, "suggestion_account": { - "title": "Find People to Follow", - "follow_explain": "When you follow someone, you’ll see their posts in your home feed." + "title": "Atrodi Cilvēkus kam Sekot", + "follow_explain": "Kad seko kādam, tu redzēsi viņu ziņas savā mājas plūsmā." }, "compose": { "title": { - "new_post": "New Post", + "new_post": "Jauna Ziņa", "new_reply": "Jauna atbilde" }, "media_selection": { @@ -374,32 +398,36 @@ "photo_library": "Attēlu krātuve", "browse": "Pārlūkot" }, - "content_input_placeholder": "Type or paste what’s on your mind", + "content_input_placeholder": "Ieraksti vai ielīmē to, ko domā", "compose_action": "Publicēt", - "replying_to_user": "replying to %s", + "replying_to_user": "atbildot uz %s", "attachment": { "photo": "attēls", "video": "video", - "attachment_broken": "This %s is broken and can’t be\nuploaded to Mastodon.", - "description_photo": "Describe the photo for the visually-impaired...", - "description_video": "Describe the video for the visually-impaired...", - "load_failed": "Load Failed", - "upload_failed": "Upload Failed", - "can_not_recognize_this_media_attachment": "Can not regonize this media attachment", - "attachment_too_large": "Attachment too large" + "attachment_broken": "Šis %s ir salauzts un nevar tikt augšuplādēts Mastodon.", + "description_photo": "Apraksti fotoattēlu vājredzīgajiem...", + "description_video": "Apraksti video vājredzīgajiem...", + "load_failed": "Ielāde Neizdevās", + "upload_failed": "Augšupielāde Neizdevās", + "can_not_recognize_this_media_attachment": "Nevar atpazīt šo multivides pielikumu", + "attachment_too_large": "Pārāk liels pielikums", + "compressing_state": "Saspiež...", + "server_processing_state": "Notiek servera apstrāde..." }, "poll": { - "duration_time": "Duration: %s", + "duration_time": "Ilgums: %s", "thirty_minutes": "30 minūtes", "one_hour": "1 Stunda", "six_hours": "6 stundas", "one_day": "1 Diena", "three_days": "3 Dienas", "seven_days": "7 Dienas", - "option_number": "Option %ld" + "option_number": "Izvēle %ld", + "the_poll_is_invalid": "Aptauja nav derīga", + "the_poll_has_empty_option": "Aptaujai ir tukša opcija" }, "content_warning": { - "placeholder": "Write an accurate warning here..." + "placeholder": "Uzraksti šeit precīzu brīdinājumu..." }, "visibility": { "public": "Publisks", @@ -408,24 +436,26 @@ "direct": "Tikai cilvēki, kurus es pieminu" }, "auto_complete": { - "space_to_add": "Space to add" + "space_to_add": "Vieta, ko pievienot" }, "accessibility": { "append_attachment": "Pievienot pielikumu", "append_poll": "Pievienot aptauju", "remove_poll": "Noņemt aptauju", - "custom_emoji_picker": "Custom Emoji Picker", - "enable_content_warning": "Enable Content Warning", - "disable_content_warning": "Disable Content Warning", - "post_visibility_menu": "Post Visibility Menu" + "custom_emoji_picker": "Pielāgoto Emocijzīmju Atlasītājs", + "enable_content_warning": "Iespējot Satura Brīdinājumu", + "disable_content_warning": "Atspējot Satura Brīdinājumu", + "post_visibility_menu": "Ziņu Redzamības Izvēlne", + "post_options": "Ziņas Iespējas", + "posting_as": "Publicēt kā %s" }, "keyboard": { - "discard_post": "Discard Post", - "publish_post": "Publish Post", - "toggle_poll": "Toggle Poll", - "toggle_content_warning": "Toggle Content Warning", + "discard_post": "Izmest Ziņu", + "publish_post": "Publicēt Ziņu", + "toggle_poll": "Pārslēgt Aptauju", + "toggle_content_warning": "Pārslēgt Satura Brīdinājumu", "append_attachment_entry": "Pievienot pielikumu - %s", - "select_visibility_entry": "Select Visibility - %s" + "select_visibility_entry": "Atlasīt Redzamību — %s" } }, "profile": { @@ -433,19 +463,27 @@ "follows_you": "Seko tev" }, "dashboard": { - "posts": "posts", - "following": "seko", - "followers": "sekottāji" + "my_posts": "ziņas", + "my_following": "seko", + "my_followers": "sekotāji", + "other_posts": "ziņas", + "other_following": "seko", + "other_followers": "sekotāji" }, "fields": { + "joined": "Pievienojās", "add_row": "Pievienot rindu", "placeholder": { - "label": "Label", + "label": "Marķējums", "content": "Saturs" + }, + "verified": { + "short": "Pārbaudīts %s", + "long": "Šīs saites piederība tika pārbaudīta %s" } }, "segmented_control": { - "posts": "Posts", + "posts": "Ziņas", "replies": "Atbildes", "posts_and_replies": "Ziņas un atbildes", "media": "Multivide", @@ -453,97 +491,97 @@ }, "relationship_action_alert": { "confirm_mute_user": { - "title": "Mute Account", - "message": "Confirm to mute %s" + "title": "Izslēgt Kontu", + "message": "Apstiprināt, lai izslēgtu %s skaņu" }, "confirm_unmute_user": { - "title": "Unmute Account", - "message": "Confirm to unmute %s" + "title": "Ieslēgt Kontu", + "message": "Apstiprināt, lai ieslēgtu %s skaņu" }, "confirm_block_user": { "title": "Bloķēts kontu", - "message": "Confirm to block %s" + "message": "Apstiprināt, lai bloķētu %s" }, "confirm_unblock_user": { "title": "Atbloķēt kontu", "message": "Apstiprini lai atbloķētu %s" }, "confirm_show_reblogs": { - "title": "Show Reblogs", - "message": "Confirm to show reblogs" + "title": "Rādīt Reblogus", + "message": "Apstiprināt, lai rādītu reblogus" }, "confirm_hide_reblogs": { - "title": "Hide Reblogs", - "message": "Confirm to hide reblogs" + "title": "Paslēpt Reblogus", + "message": "Apstiprināt, lai slēptu reblogus" } }, "accessibility": { - "show_avatar_image": "Show avatar image", - "edit_avatar_image": "Edit avatar image", - "show_banner_image": "Show banner image", - "double_tap_to_open_the_list": "Double tap to open the list" + "show_avatar_image": "Rādīt avatara attēlu", + "edit_avatar_image": "Rediģēt avatara attēlu", + "show_banner_image": "Rādīt bannera attēlu", + "double_tap_to_open_the_list": "Dubultskāriens, lai atvērtu sarakstu" } }, "follower": { "title": "sekottājs", - "footer": "Followers from other servers are not displayed." + "footer": "Sekotāji no citiem serveriem netiek rādīti." }, "following": { "title": "seko", - "footer": "Follows from other servers are not displayed." + "footer": "Sekojumi no citiem serveriem netiek rādīti." }, "familiarFollowers": { - "title": "Followers you familiar", - "followed_by_names": "Followed by %s" + "title": "Tev pazīstamie sekotāji", + "followed_by_names": "Seko %s" }, "favorited_by": { - "title": "Favorited By" + "title": "Pievienoja izlasei" }, "reblogged_by": { - "title": "Reblogged By" + "title": "Reblogoja" }, "search": { "title": "Meklēt", "search_bar": { - "placeholder": "Search hashtags and users", + "placeholder": "Meklēt tēmturus un lietotājus", "cancel": "Atcelt" }, "recommend": { "button_text": "Skatīt visu", "hash_tag": { - "title": "Trending on Mastodon", - "description": "Hashtags that are getting quite a bit of attention", - "people_talking": "%s people are talking" + "title": "Tendences vietnē Mastodon", + "description": "Tēmturi, kuriem tiek pievērsta diezgan liela uzmanība", + "people_talking": "%s cilvēki apspriež" }, "accounts": { - "title": "Accounts you might like", - "description": "You may like to follow these accounts", - "follow": "Follow" + "title": "Konti, kuri tev varētu patikt", + "description": "Iespējams, tu vēlēsies sekot šiem kontiem", + "follow": "Sekot" } }, "searching": { "segment": { - "all": "All", - "people": "People", - "hashtags": "Hashtags", - "posts": "Posts" + "all": "Visi", + "people": "Cilvēki", + "hashtags": "Tēmturi", + "posts": "Ziņas" }, "empty_state": { - "no_results": "No results" + "no_results": "Nav rezultātu" }, - "recent_search": "Recent searches", - "clear": "Clear" + "recent_search": "Nesen meklētais", + "clear": "Notīrīt" } }, "discovery": { "tabs": { "posts": "Ziņas", - "hashtags": "Hashtags", + "hashtags": "Tēmturi", "news": "Ziņas", - "community": "Community", + "community": "Kopiena", "for_you": "Priekš tevis" }, - "intro": "These are the posts gaining traction in your corner of Mastodon." + "intro": "Šīs ir ziņas, kas iekaro tavu Mastodon stūrīti." }, "favorite": { "title": "Tava izlase" @@ -554,16 +592,16 @@ "Mentions": "Pieminējumi" }, "notification_description": { - "followed_you": "followed you", - "favorited_your_post": "favorited your post", - "reblogged_your_post": "reblogged your post", + "followed_you": "tev sekoja", + "favorited_your_post": "izcēla tavu ziņu", + "reblogged_your_post": "reblogoja tavu ziņu", "mentioned_you": "pieminēja tevi", - "request_to_follow_you": "request to follow you", + "request_to_follow_you": "lūgums tev sekot", "poll_has_ended": "balsošana beidzās" }, "keyobard": { "show_everything": "Parādīt man visu", - "show_mentions": "Show Mentions" + "show_mentions": "Rādīt Pieminējumus" }, "follow_request": { "accept": "Pieņemt", @@ -574,7 +612,7 @@ }, "thread": { "back_title": "Ziņa", - "title": "Post from %s" + "title": "Ziņa no %s" }, "settings": { "title": "Iestatījumi", @@ -587,42 +625,42 @@ }, "look_and_feel": { "title": "Izskats", - "use_system": "Use System", + "use_system": "Lietot Sistēmas", "really_dark": "Ļoti tumšs", "sorta_dark": "Itkā tumšs", "light": "Gaišs" }, "notifications": { "title": "Paziņojumi", - "favorites": "Favorites my post", + "favorites": "Izceļ manu ziņu", "follows": "Seko man", - "boosts": "Reblogs my post", + "boosts": "Reblogo manu ziņu", "mentions": "Pieminējumi", "trigger": { "anyone": "jebkurš", "follower": "sekottājs", - "follow": "anyone I follow", + "follow": "jebkurš, kam sekoju", "noone": "neviens", - "title": "Notify me when" + "title": "Paziņot man, kad" } }, "preference": { "title": "Uzstādījumi", - "true_black_dark_mode": "True black dark mode", - "disable_avatar_animation": "Disable animated avatars", - "disable_emoji_animation": "Disable animated emojis", - "using_default_browser": "Use default browser to open links", - "open_links_in_mastodon": "Open links in Mastodon" + "true_black_dark_mode": "Īsti melns tumšais režīms", + "disable_avatar_animation": "Atspējot animētos avatarus", + "disable_emoji_animation": "Atspējot animētās emocijzīmes", + "using_default_browser": "Saišu atvēršana noklusētajā pārlūkā", + "open_links_in_mastodon": "Atvērt saites Mastodon" }, "boring_zone": { - "title": "The Boring Zone", + "title": "Garlaicīgā zona", "account_settings": "Konta iestatījumi", "terms": "Pakalpojuma noteikumi", "privacy": "Privātuma politika" }, "spicy_zone": { - "title": "The Spicy Zone", - "clear": "Clear Media Cache", + "title": "Pikantā zona", + "clear": "Notīrīt Multivides Kešatmiņu", "signout": "Iziet" } }, @@ -630,7 +668,7 @@ "mastodon_description": "Mastodon ir atvērtā koda programmatūra. Tu vari ziņot par problēmām GitHub %s (%s)" }, "keyboard": { - "close_settings_window": "Close Settings Window" + "close_settings_window": "Aizvērt Iestatījumu Logu" } }, "report": { @@ -638,24 +676,24 @@ "title": "Ziņot %s", "step1": "1. solis no 2", "step2": "2. solis no 2", - "content1": "Are there any other posts you’d like to add to the report?", - "content2": "Is there anything the moderators should know about this report?", - "report_sent_title": "Thanks for reporting, we’ll look into this.", + "content1": "Vai ir vēl kādas ziņas, kuras vēlies pievienot pārskatam?", + "content2": "Vai moderatoriem ir kaut kas jāzina par šo ziņojumu?", + "report_sent_title": "Paldies, ka ziņoji, mēs to izskatīsim.", "send": "Nosūtīt Sūdzību", "skip_to_send": "Sūtīt bez komentāra", - "text_placeholder": "Type or paste additional comments", - "reported": "REPORTED", + "text_placeholder": "Ieraksti vai ielīmē papildu komentārus", + "reported": "ZIŅOTS", "step_one": { "step_1_of_4": "1. solis no 4", - "whats_wrong_with_this_post": "What's wrong with this post?", - "whats_wrong_with_this_account": "What's wrong with this account?", - "whats_wrong_with_this_username": "What's wrong with %s?", + "whats_wrong_with_this_post": "Kas vainas šim ierakstam?", + "whats_wrong_with_this_account": "Kas vainas šim kontam?", + "whats_wrong_with_this_username": "Kas vainas %s?", "select_the_best_match": "Izvēlieties labāko atbilstību", "i_dont_like_it": "Man tas nepatīk", "it_is_not_something_you_want_to_see": "Tas nav kaut kas, ko tu vēlies redzēt", "its_spam": "Tas ir spams", - "malicious_links_fake_engagement_or_repetetive_replies": "Malicious links, fake engagement, or repetetive replies", - "it_violates_server_rules": "It violates server rules", + "malicious_links_fake_engagement_or_repetetive_replies": "Ļaunprātīgas saites, viltus iesaistīšana vai atkārtotas atbildes", + "it_violates_server_rules": "Tas pārkāpj servera noteikumus", "you_are_aware_that_it_breaks_specific_rules": "Tu zini, ka tas pārkāpj īpašus noteikumus", "its_something_else": "Tas ir kaut kas cits", "the_issue_does_not_fit_into_other_categories": "Šis jautājums neietilpst citās kategorijās" @@ -664,7 +702,7 @@ "step_2_of_4": "2. solis no 4", "which_rules_are_being_violated": "Kuri noteikumi tiek pārkāpti?", "select_all_that_apply": "Atlasi visus atbilstošos", - "i_just_don’t_like_it": "I just don’t like it" + "i_just_don’t_like_it": "Man vienkārši tas nepatīk" }, "step_three": { "step_3_of_4": "3. solis no 4", @@ -677,36 +715,48 @@ }, "step_final": { "dont_want_to_see_this": "Vai nevēlies to redzēt?", - "when_you_see_something_you_dont_like_on_mastodon_you_can_remove_the_person_from_your_experience.": "When you see something you don’t like on Mastodon, you can remove the person from your experience.", + "when_you_see_something_you_dont_like_on_mastodon_you_can_remove_the_person_from_your_experience.": "Kad pakalpojumā Mastodon redzi kaut ko, kas tev nepatīk, tu vari noņemt šo personu no savas pieredzes.", "unfollow": "Atsekot", "unfollowed": "Atsekoja", "unfollow_user": "Atsekot %s", "mute_user": "Apklusināt %s", - "you_wont_see_their_posts_or_reblogs_in_your_home_feed_they_wont_know_they_ve_been_muted": "You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted.", + "you_wont_see_their_posts_or_reblogs_in_your_home_feed_they_wont_know_they_ve_been_muted": "Tu neredzēsi viņu ziņas vai reblogus savā mājas plūsmā. Viņi nezinās, ka ir izslēgti.", "block_user": "Bloķēt %s", - "they_will_no_longer_be_able_to_follow_or_see_your_posts_but_they_can_see_if_theyve_been_blocked": "They will no longer be able to follow or see your posts, but they can see if they’ve been blocked.", + "they_will_no_longer_be_able_to_follow_or_see_your_posts_but_they_can_see_if_theyve_been_blocked": "Viņi vairs nevarēs sekot tavām ziņām vai redzēt tās, taču varēs redzēt, vai viņi ir bloķēti.", "while_we_review_this_you_can_take_action_against_user": "Kamēr mēs to izskatām, tu vari veikt darbības pret @%s" } }, "preview": { "keyboard": { - "close_preview": "Close Preview", - "show_next": "Show Next", - "show_previous": "Show Previous" + "close_preview": "Aizvērt Priekšskatījumu", + "show_next": "Rādīt Nākamo", + "show_previous": "Rādīt Iepriekšējo" } }, "account_list": { - "tab_bar_hint": "Current selected profile: %s. Double tap then hold to show account switcher", - "dismiss_account_switcher": "Dismiss Account Switcher", + "tab_bar_hint": "Pašreizējais atlasītais profils: %s. Veic dubultskārienu un pēc tam turi, lai parādītu konta pārslēdzēju", + "dismiss_account_switcher": "Noraidīt Konta Pārslēdzēju", "add_account": "Pievienot kontu" }, "wizard": { - "new_in_mastodon": "New in Mastodon", - "multiple_account_switch_intro_description": "Switch between multiple accounts by holding the profile button.", - "accessibility_hint": "Double tap to dismiss this wizard" + "new_in_mastodon": "Jaunums Mastodonā", + "multiple_account_switch_intro_description": "Pārslēdzies starp vairākiem kontiem, turot nospiestu profila pogu.", + "accessibility_hint": "Veic dubultskārienu, lai noraidītu šo vedni" }, "bookmark": { - "title": "Bookmarks" + "title": "Grāmatzīmes" + }, + "followed_tags": { + "title": "Sekotie Tēmturi", + "header": { + "posts": "ziņas", + "participants": "dalībnieki", + "posts_today": "ziņas šodien" + }, + "actions": { + "follow": "Sekot", + "unfollow": "Atsekot" + } } } } diff --git a/Localization/StringsConvertor/input/lv.lproj/ios-infoPlist.json b/Localization/StringsConvertor/input/lv.lproj/ios-infoPlist.json index c6db73de0..860333e51 100644 --- a/Localization/StringsConvertor/input/lv.lproj/ios-infoPlist.json +++ b/Localization/StringsConvertor/input/lv.lproj/ios-infoPlist.json @@ -1,6 +1,6 @@ { - "NSCameraUsageDescription": "Used to take photo for post status", - "NSPhotoLibraryAddUsageDescription": "Used to save photo into the Photo Library", - "NewPostShortcutItemTitle": "New Post", - "SearchShortcutItemTitle": "Search" + "NSCameraUsageDescription": "Izmanto, lai fotografētu ziņas statusu", + "NSPhotoLibraryAddUsageDescription": "Izmanto, lai saglabātu fotoattēlu Photo Library", + "NewPostShortcutItemTitle": "Jauna Ziņa", + "SearchShortcutItemTitle": "Meklēt" } diff --git a/Localization/StringsConvertor/input/my.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/my.lproj/Localizable.stringsdict new file mode 100644 index 000000000..8230962a5 --- /dev/null +++ b/Localization/StringsConvertor/input/my.lproj/Localizable.stringsdict @@ -0,0 +1,407 @@ + + + + + a11y.plural.count.unread.notification + + NSStringLocalizedFormatKey + %#@notification_count_unread_notification@ + notification_count_unread_notification + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + မဖတ်ရသေးသောအသိပေးချက် %ld ခု ရှိသည် + + + a11y.plural.count.input_limit_exceeds + + NSStringLocalizedFormatKey + စာလုံးရေ သတ်မှတ်ချက်ထက် %#@character_count@ လုံး ထက်ကျော်လွန် + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + စာလုံး %ld လုံး + + + a11y.plural.count.input_limit_remains + + NSStringLocalizedFormatKey + သတ်မှတ်စာလုံးရေ %#@character_count@ လုံး ကျန်ရှိ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + စာလုံး %ld လုံး + + + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + စာလုံးရေ %#@character_count@ လုံး ကျန်ရှိ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + စာလုံး %ld လုံး + + + plural.count.followed_by_and_mutual + + NSStringLocalizedFormatKey + %#@names@%#@count_mutual@ + names + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + + + count_mutual + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %1$@ နှင့် ဘုံသူငယ်ချင်း %ld ဦးမှ စောင့်ကြည့်နေသည် + + + plural.count.metric_formatted.post + + NSStringLocalizedFormatKey + %@ %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + ပို့စ်များ + + + plural.count.media + + NSStringLocalizedFormatKey + %#@media_count@ + media_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + ရုပ်သံ %ld ခု + + + plural.count.post + + NSStringLocalizedFormatKey + %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + ပို့စ် %ld ခု + + + plural.count.favorite + + NSStringLocalizedFormatKey + %#@favorite_count@ + favorite_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + အကြိုက်ဆုံး %ld ခု + + + plural.count.reblog + + NSStringLocalizedFormatKey + %#@reblog_count@ + reblog_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + ပြန်မျှဝေမှု %ld ခု + + + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + ပြန်စာ %ld ခု + + + plural.count.vote + + NSStringLocalizedFormatKey + %#@vote_count@ + vote_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + မဲ %ld မဲ + + + plural.count.voter + + NSStringLocalizedFormatKey + %#@voter_count@ + voter_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + မဲပေးသူ %ld ဦး + + + plural.people_talking + + NSStringLocalizedFormatKey + %#@count_people_talking@ + count_people_talking + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + လူ %ld ဦး ပြောနေသည် + + + plural.count.following + + NSStringLocalizedFormatKey + %#@count_following@ + count_following + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + စောင့်ကြည့်သူ %ld ဦး + + + plural.count.follower + + NSStringLocalizedFormatKey + %#@count_follower@ + count_follower + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + စောင့်ကြည့်သူ %ld ဦး + + + date.year.left + + NSStringLocalizedFormatKey + %#@count_year_left@ + count_year_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld နှစ် ကျန်ရှိ + + + date.month.left + + NSStringLocalizedFormatKey + %#@count_month_left@ + count_month_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld လ ကျန်ရှိ + + + date.day.left + + NSStringLocalizedFormatKey + %#@count_day_left@ + count_day_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld ရက် ကျန်ရှိ + + + date.hour.left + + NSStringLocalizedFormatKey + %#@count_hour_left@ + count_hour_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld နာရီ ကျန်ရှိ + + + date.minute.left + + NSStringLocalizedFormatKey + %#@count_minute_left@ + count_minute_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld မိနစ် ကျန်ရှိ + + + date.second.left + + NSStringLocalizedFormatKey + %#@count_second_left@ + count_second_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld စက္ကန့် ကျန်ရှိ + + + date.year.ago.abbr + + NSStringLocalizedFormatKey + %#@count_year_ago_abbr@ + count_year_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + လွန်ခဲ့သော %ld နှစ်က + + + date.month.ago.abbr + + NSStringLocalizedFormatKey + %#@count_month_ago_abbr@ + count_month_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + လွန်ခဲ့သော %ld လက + + + date.day.ago.abbr + + NSStringLocalizedFormatKey + %#@count_day_ago_abbr@ + count_day_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + လွန်ခဲ့သော %ld ရက်က + + + date.hour.ago.abbr + + NSStringLocalizedFormatKey + %#@count_hour_ago_abbr@ + count_hour_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + လွန်ခဲ့သော %ld နာရီက + + + date.minute.ago.abbr + + NSStringLocalizedFormatKey + %#@count_minute_ago_abbr@ + count_minute_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + လွန်ခဲ့သော %ld မိနစ်က + + + date.second.ago.abbr + + NSStringLocalizedFormatKey + %#@count_second_ago_abbr@ + count_second_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + လွန်ခဲ့သော %ld စက္ကန့်က + + + + diff --git a/Localization/StringsConvertor/input/my.lproj/app.json b/Localization/StringsConvertor/input/my.lproj/app.json new file mode 100644 index 000000000..ea235fa53 --- /dev/null +++ b/Localization/StringsConvertor/input/my.lproj/app.json @@ -0,0 +1,762 @@ +{ + "common": { + "alerts": { + "common": { + "please_try_again": "ပြန်လည်ကြိုးစားကြည့်ပါ", + "please_try_again_later": "နောက်မှ ပြန်လည်ကြိုးစားကြည့်ပါ" + }, + "sign_up_failure": { + "title": "အကောင့်ဖွင့်ခြင်း မအောင်မြင်ပါ" + }, + "server_error": { + "title": "ဆာဗာ အမှား" + }, + "vote_failure": { + "title": "မဲပေးမှု မအောင်မြင်ခြင်း", + "poll_ended": "စစ်တမ်းကောက်မှု ပြီးဆုံးပါပြီ" + }, + "discard_post_content": { + "title": "မူကြမ်းကို ပယ်ဖျက်ပါ", + "message": "ရေးသားထားသောမူကြမ်းကို ပယ်ဖျက်ရန် အတည်ပြုပါ" + }, + "publish_post_failure": { + "title": "ပို့စ်တင်ခြင်း မအောင်မြင်မှု", + "message": "ပို့စ်တင်ခြင်း မအောင်မြင်ပါ၊ သင်၏ အင်တာနက်ချိတ်ဆက်မှုကို စစ်ဆေးပါ။", + "attachments_message": { + "video_attach_with_photo": "ဓာတ်ပုံများပါဝင်သော ပို့စ်တွင် ဗီဒီိယိုကို တွဲတင်၍ မရပါ", + "more_than_one_video": "ဗီဒီိယို ၁ ခုထက်ပို၍ တွဲတင်၍ မရပါ" + } + }, + "edit_profile_failure": { + "title": "ပရိုဖိုင်ပြင်ဆင်ခြင်း အမှား", + "message": "ပရိုဖိုင်ကို ပြင်ဆင်၍ မရပါ၊ ပြန်လည်ကြိုးစားကြည့်ပါ။" + }, + "sign_out": { + "title": "ထွက်မည်", + "message": "အကောင့်မှ ထွက်ရန် သေချာပါသလား?", + "confirm": "ထွက်မည်" + }, + "block_domain": { + "title": "%s တစ်ခုလုံးကို ဘလော့လုပ်ရန် တကယ် သေချာပါသလား? များသောအားဖြင့် အနည်းစုကို ပစ်မှတ်ထား ဘလော့လုပ်ခြင်းသည် လုံလောက်ပါသည်။ ထို ဒိုမိန်းမှ အကြောင်းအရာ တစ်ခုမှ မြင်ရမည်မဟုတ်သည့်အပြင် ထို ဒိုမိန်းတွင်ရှိသော သင်၏ စောင့်ကြည့်သူများပါ ဖယ်ရှားပစ်မည်ဖြစ်သည်။", + "block_entire_domain": "ဒိုမိန်းကို ဘလော့လုပ်ရန်" + }, + "save_photo_failure": { + "title": "ဓာတ်ပုံသိမ်းဆည်းခြင်း အမှား", + "message": "ကျေးဇူးပြု၍ ဓာတ်ပုံသိမ်းဆည်းနိုင်ရန် ဓာတ်ပုံပြတိုက်သို့ ဝင်ရောက်ခွင့်ပေးပါ။" + }, + "delete_post": { + "title": "ပို့စ်ဖျက်ရန်", + "message": "ပို့စ်ကို ဖျက်ရန် သေချာပါသလား?" + }, + "clean_cache": { + "title": "Cache ကို ရှင်းပါ", + "message": "%s cache ကို အောင်မြင်စွာ ရှင်းလင်းပြီးပါပြီ" + }, + "translation_failed": { + "title": "Note", + "message": "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported.", + "button": "OK" + } + }, + "controls": { + "actions": { + "back": "ပြန်၍", + "next": "ရှေ့သို့", + "previous": "ယခင်", + "open": "ဖွင့်", + "add": "ထည့်", + "remove": "ဖယ်ရှား", + "edit": "တည်းဖြတ်", + "save": "သိမ်းဆည်း", + "ok": "အိုကေ", + "done": "ပြီးပြီ", + "confirm": "အတည်ပြု", + "continue": "ဆက်လက်", + "compose": "ရေးဖွဲ့", + "cancel": "ပယ်ဖျက်", + "discard": "ဖယ်ရှား", + "try_again": "ထပ်မံကြိုးစားပါ", + "take_photo": "ဓါတ်ပုံရိုက်", + "save_photo": "ဓါတ်ပုံသိမ်းဆည်း", + "copy_photo": "ဓာတ်ပုံကူး", + "sign_in": "လော့ဂ်အင်ဝင်", + "sign_up": "အကောင့်ဖန်တီး", + "see_more": "ပိုမိုကြည့်ရှုရန်", + "preview": "အစမ်းကြည့်", + "copy": "Copy", + "share": "မျှဝေ", + "share_user": "%s ကို မျှဝေပါ", + "share_post": "ပို့စ်ကို မျှဝေရန်", + "open_in_safari": "Safari တွင် ဖွင့်ရန်", + "open_in_browser": "Browser တွင် ဖွင့်ရန်", + "find_people": "စောင့်ကြည့်ရန် လူရှာပါ", + "manually_search": "ကိုယ်တိုင် ရှာဖွေရန်", + "skip": "ကျော်", + "reply": "စာပြန်", + "report_user": " %s ကို တိုင်ကြားရန်", + "block_domain": "%s ကို ဘလော့လုပ်ရန်", + "unblock_domain": "%s ကို ဘလော့ဖြုတ်ရန်", + "settings": "ဆက်တင်များ", + "delete": "ဖျက်", + "translate_post": { + "title": "Translate from %s", + "unknown_language": "Unknown" + } + }, + "tabs": { + "home": "အိမ်", + "search_and_explore": "Search and Explore", + "notifications": "အသိပေးချက်များ", + "profile": "ကိုယ်ရေးမှတ်တမ်း" + }, + "keyboard": { + "common": { + "switch_to_tab": "%s သို့ ပြောင်းရန်", + "compose_new_post": "ပို့စ်အသစ် ရေးဖွဲ့", + "show_favorites": "အကြိုက်ဆုံးများ ပြရန်", + "open_settings": "ဆက်တင်ကို ဖွင့်ရန်" + }, + "timeline": { + "previous_status": "ယခင်ပို့စ်", + "next_status": "နောက်ပို့စ်", + "open_status": "ပို့စ်ဖွင့်ရန်", + "open_author_profile": "စာရေးသူ၏ ပရိုဖိုင်ကို ဖွင့်ပါ", + "open_reblogger_profile": "ပြန်တင်သူ၏ ပရိုဖိုင်ကို ဖွင့်ပါ", + "reply_status": "ပို့စ်ကို စာပြန်", + "toggle_reblog": "ပို့စ်ကိုပြန်တင်ခွင့်ပေးခြင်းကို ဖွင့်၊ပိတ် လုပ်ပါ", + "toggle_favorite": "အကြိုက်ဆုံးလုပ်ခွင့်ပေးခြင်းကို ဖွင့်၊ပိတ် လုပ်ပါ", + "toggle_content_warning": "အကြောင်းအရာသတိပေးချက်ကို ဖွင့်၊ပိတ် လုပ်ပါ", + "preview_image": "ဓာတ်ပုံကို ကြိုကြည့်" + }, + "segmented_control": { + "previous_section": "ယခင်အပိုင်း", + "next_section": "နောက်အပိုင်း" + } + }, + "status": { + "user_reblogged": "%s ကို ပြန်တင်", + "user_replied_to": "%s ထံ စာပြန်", + "show_post": "ပို့စ်ကို ပြသ", + "show_user_profile": "အသုံးပြုသူ၏ ပရိုဖိုင်ကို ပြရန်", + "content_warning": "အကြောင်းအရာသတိပေးချက်", + "sensitive_content": "ထိလွယ်ရှလွယ် အကြောင်းအရာ", + "media_content_warning": "ဖော်ထုတ်ရန် မည်သည့်နေရာမဆို နှိပ်ပါ", + "tap_to_reveal": "ဖော်ထုတ်ရန် နှိပ်ပါ", + "load_embed": "Load Embed", + "link_via_user": "%s via %s", + "poll": { + "vote": "မဲပေး", + "closed": "ပိတ်သွားပြီ" + }, + "meta_entity": { + "url": "လင့်ခ်: %s", + "hashtag": "ဟက်ရှ်တက်ခ်: %s", + "mention": "ပရိုဖိုင် ပြသ: %s", + "email": "အီးမေးလ်လိပ်စာ: %s" + }, + "actions": { + "reply": "စာပြန်", + "reblog": "ပြန်တင်", + "unreblog": "ပြန်တင်ခြင်းကို ပယ်ဖျက်", + "favorite": "အကြိုက်ဆုံး", + "unfavorite": "အကြိုက်ဆုံးမှ ဖယ်ရှားရန်", + "menu": "မီနူး", + "hide": "ဝှက်ရန်", + "show_image": "ဓာတ်ပုံပြရန်", + "show_gif": "GIF ပြရန်", + "show_video_player": "ဗီဒီယိုဖွင့်စက် ပြရန်", + "share_link_in_post": "Share Link in Post", + "tap_then_hold_to_show_menu": "မီနူးပြရန် ဖိထားပါ" + }, + "tag": { + "url": "URL", + "mention": "ရည်ညွှန်း", + "link": "လင့်ခ်", + "hashtag": "ဟက်ရှ်တက်ခ်", + "email": "အီးမေးလ်", + "emoji": "အီမိုဂျီ" + }, + "visibility": { + "unlisted": "ဒီပို့စ်ကို လူတိုင်းမြင်နိုင်သည်၊ သို့သော် အများမြင်အလင်းစဉ်တွင် မပြသပါ။", + "private": "သူတို့၏ စောင့်ကြည့်သူများသာ ဒီပို့စ်ကို မြင်နိုင်သည်", + "private_from_me": "ကျွန်ုပ်၏ စောင့်ကြည့်သူများသာ ဒီပို့စ်ကို မြင်နိုင်သည်", + "direct": "ရည်ညွှန်းခံရသောအသုံးပြုသူများသာ ဒီပို့စ်ကို မြင်နိုင်သည်" + }, + "translation": { + "translated_from": "Translated from %s using %s", + "unknown_language": "Unknown", + "unknown_provider": "Unknown", + "show_original": "Shown Original" + } + }, + "friendship": { + "follow": "စောင့်ကြည့်ရန်", + "following": "စောင့်ကြည့်နေသည်", + "request": "တောင်းဆို", + "pending": "ဆိုင်းငံ့ထားသည်", + "block": "ဘလော့လုပ်ရန်", + "block_user": "%s ကို ဘလော့လုပ်ရန်", + "block_domain": "%s ကို ဘလော့လုပ်ရန်", + "unblock": "ဘလော့ဖြုတ်ရန်", + "unblock_user": "%s ကို ဘလော့ဖြုတ်ရန်", + "blocked": "ဘလော့ထားသည်", + "mute": "ပိတ်ထားရန်", + "mute_user": "%s ကို ပိတ်ထားရန်", + "unmute": "ပြန်ဖွင့်ရန်", + "unmute_user": "%s ကို ပြန်ဖွင့်ရန်", + "muted": "ပိတ်ထားဆဲ", + "edit_info": "အချက်အလက်တည်းဖြတ်", + "show_reblogs": "ပြန်တင်ထားတာတွေ ပြရန်", + "hide_reblogs": "ပြန်တင်ထားတာတွေ ဖျောက်ရန်" + }, + "timeline": { + "filtered": "စစ်ထုတ်ထားသည်", + "timestamp": { + "now": "ယခု" + }, + "loader": { + "load_missing_posts": "ပျောက်နေသော ပို့စ်များကို လုဒ်ပါ", + "loading_missing_posts": "ပျောက်နေသော ပို့စ်များကို လုဒ်ပါ...", + "show_more_replies": "ပြန်စာများထပ်ပြပါ" + }, + "header": { + "no_status_found": "ပို့စ်ရှာမတွေ့ပါ", + "blocking_warning": "ဒီအသုံးပြုသူ၏ ပရိုဖိုင်ကို ဘလော့မဖြုတ်မချင်း ကြည့်၍မရပါ၊ သင်၏ ပရိုဖိုင်သည် ထိုသူတို့ထံ ဤကဲ့သို့ ပေါ်နေပါမည်။", + "user_blocking_warning": "%s ၏ ပရိုဖိုင်ကို ဘလော့မဖြုတ်မချင်း ကြည့်၍မရပါ၊ သင်၏ ပရိုဖိုင်သည် ထိုသူ့ထံ ဤကဲ့သို့ ပေါ်နေပါမည်။", + "blocked_warning": "ဤပုဂ္ဂိုလ်မှ သင့်ကို ဘလော့မဖြုတ်မချင်း သူ၏ ပရိုဖိုင်သည် သင် ကြည့်၍မရပါ။", + "user_blocked_warning": "%s မှ သင့်ကို ဘလော့မဖြုတ်မချင်း သူ၏ ပရိုဖိုင်သည် သင် ကြည့်၍မရပါ။", + "suspended_warning": "ဤအသုံးပြုသူသည် ဆိုင်းငံ့ခံထားရသည်။", + "user_suspended_warning": "%s ၏အကောင့်သည် ဆိုင်းငံ့ခံထားရသည်။" + } + } + } + }, + "scene": { + "welcome": { + "slogan": "လူမှုကွန်ယက်ကို သင်၏လက်ထဲသို့ ပြန်လည်ထည့်ပေးလိုက်ပြီ။", + "get_started": "စတင်ရန်", + "log_in": "လော့ခ်အင်ဝင်ရန်" + }, + "login": { + "title": "ပြန်လည်ကြိုဆိုပါသည်", + "subtitle": "သင်၏အကောင့်ဖွင့်ခဲ့သော ဆာဗာပေါ်တွင် လော့ခ်အင်ဝင်ရောက်ပါ", + "server_search_field": { + "placeholder": "URL ကို ထည့်သွင်းပါ (သို့) သင်၏ ဆာဗာကို ရှာပါ" + } + }, + "server_picker": { + "title": "Mastodon ကို အသိုင်းအဝန်းပေါင်းစုံမှ အသုံးပြုသူများဖြင့် ဖွဲ့စည်းထားသည်။", + "subtitle": "သင်၏ ဒေသ၊ စိတ်ဝင်စားမှု အပေါ်အခြေခံသော ဆာဗာတစ်ခု ရွေးချယ်ပါ၊ မည်သည့်ဆာဗာကို ရွေးချယ်ထားစေကာမူ အခြားအသုံးပြုသူများနှင့် ပုံမှန်အတိုင်း ဆက်သွယ်နိုင်သည်။", + "button": { + "category": { + "all": "အားလုံး", + "all_accessiblity_description": "အမျိုးအစား - အားလုံး", + "academia": "ပညာရှင်", + "activism": "တက်ကြွလှုပ်ရှားမှု", + "food": "အစားအစာ", + "furry": "furry", + "games": "ဂိမ်း", + "general": "အထွေထွေ", + "journalism": "သတင်းစာပညာ", + "lgbt": "lgbt", + "regional": "နယ်မြေဆိုင်ရာ", + "art": "အနုပညာ", + "music": "ဂီတ", + "tech": "နည်းပညာ" + }, + "see_less": "လျှော့ ကြည့်ရန်", + "see_more": "ပိုမိုကြည့်ရှုရန်" + }, + "label": { + "language": "ဘာသာစကား", + "users": "အသုံးပြုသူများ", + "category": "အမျိုးအစား" + }, + "input": { + "search_servers_or_enter_url": "အသိုင်းအဝိုင်းများကို ရှာဖွေ (သို့) URL ကို ဝင်ရောက်" + }, + "empty_state": { + "finding_servers": "အဆင်သင့်သုံးရသော ဆာဗာများကို ရှာနေသည်...", + "bad_network": "ဒေတာဖွင့်နေစဉ် တစ်ခုခုမှားယွင်းသွားသည်၊ အင်တာနက်ချိတ်ဆက်မှုကို စစ်ဆေးပါ။", + "no_results": "ရလဒ်မရှိပါ" + } + }, + "register": { + "title": "သင့်ကို %s တွင် စတင်လိုက်ရအောင်", + "lets_get_you_set_up_on_domain": "သင့်ကို %s တွင် စတင်လိုက်ရအောင်", + "input": { + "avatar": { + "delete": "ဖျက်" + }, + "username": { + "placeholder": "အသုံးပြုသူအမည်", + "duplicate_prompt": "ဤအသုံးပြုသူအမည်သည် ရှိနှင့်ပြီးဖြစ်သည်။" + }, + "display_name": { + "placeholder": "ပြသမည့် အမည်" + }, + "email": { + "placeholder": "အီးမေးလ်" + }, + "password": { + "placeholder": "စကားဝှက်", + "require": "သင်၏စကားဝှက်သည် အနည်းဆုံးလိုအပ်သည်:", + "character_limit": "အက္ခရာ ၈ လုံး", + "accessibility": { + "checked": "စစ်ဆေးပြီးပြီ", + "unchecked": "မစစ်ဆေးခဲ့ပါ" + }, + "hint": "စကားဝှက်သည် အနည်းဆုံး အက္ခရာ ၈ လုံး ရှိရပါမည်။" + }, + "invite": { + "registration_user_invite_request": "သင် ဘာကြောင့် ပါဝင်ချင်တာပါလဲ?" + } + }, + "error": { + "item": { + "username": "အသုံးပြုသူအမည်", + "email": "အီးမေးလ်", + "password": "စကားဝှက်", + "agreement": "သဘောတူညီမှု", + "locale": "ဒေသဆိုင်ရာ", + "reason": "အကြောင်းပြချက်" + }, + "reason": { + "blocked": "%s တွင် ခွင့်မပြုထားသော အီးမေးလ်ထောက်ပံ့သူပါဝင်နေသည်။", + "unreachable": "%s တည်ရှိပုံ မပေါ်ပါ", + "taken": "%s ကို အသုံးပြုနေသူ ရှိနှင့်ပြီးဖြစ်သည်။", + "reserved": "%s သည် သီးသန့်ဖယ်ထားသောစကားလုံး ဖြစ်သည်။", + "accepted": "%s ကို လက်ခံရမည်ဖြစ်သည်", + "blank": "%s ကို လိုအပ်သည်", + "invalid": "%s သည် မခိုင်လုံပါ", + "too_long": "%s သည် ရှည်လွန်းသည်", + "too_short": "%s သည် တိုလွန်းသည်", + "inclusion": "%s သည် ထောက်ပံ့ထားသောတန်ဖိုး မဟုတ်ပါ" + }, + "special": { + "username_invalid": "အသုံးပြုသူအမည်တွင် ဂဏန်းအက္ခရာစာလုံးနှင့် အောက်မျဉ်း သာလျှင် ပါဝင်နိုင်သည်", + "username_too_long": "အသုံးပြုသူအမည်ရှည်လွန်းသည် (စာလုံး ၃၀ လုံးထက် ရှည်၍ မရပါ)", + "email_invalid": "ဤအီးမေးလ်လိပ်စာသည် ခိုင်လုံမှု မရှိပါ", + "password_too_short": "စကားဝှက်တိုလွန်းသည် (အနည်းဆုံး စာလုံး ၈ လုံး ရှိရမည်)" + } + } + }, + "server_rules": { + "title": "အခြေခံမှုအချို့", + "subtitle": "ဤစည်းမျဉ်းများကို ထိန်းညှိသူ %s ယောက်က သတ်မှတ်ကွပ်ကဲသည်။", + "prompt": "ဆက်လက်သွားမည်ဆိုပါက သင်သည် %s အတွက် ဝန်ဆောင်မှုနှင့် ကိုယ်ရေးကိုယ်တာမူဝါဒများကို လိုက်နာရမည်ဖြစ်သည်။", + "terms_of_service": "ဝန်ဆောင်မှုစည်းကမ်းချက်များ", + "privacy_policy": "ကိုယ်ရေးကိုယ်တာမူဝါဒ", + "button": { + "confirm": "သဘောတူညီသည်" + } + }, + "confirm_email": { + "title": "နောက််ဆုံးတစ်ခု", + "subtitle": "သင့်ကို ပို့လိုက်သောအီးမေးလ်တွင် ပါဝင်သည့်လင့်ခ်ကို နှိပ်ပါ။", + "tap_the_link_we_emailed_to_you_to_verify_your_account": "သင့်ကို ပို့လိုက်သောအီးမေးလ်တွင် ပါဝင်သည့်လင့်ခ်ကို နှိပ်ပါ။", + "button": { + "open_email_app": "အီးမေးလ်ကို ဖွင့်ပါ", + "resend": "ပြန်ပို့ပါ" + }, + "dont_receive_email": { + "title": "သင့်အီးမေးလ်ကို စစ်ကြည့်ပါ", + "description": "Check if your email address is correct as well as your junk folder if you haven’t.", + "resend_email": "အီးမေးလ်ကိုပြန်ပို့ပါ" + }, + "open_email_app": { + "title": "သင်၏စာဝင်ပုံးကို စစ်ဆေးပါ", + "description": "We just sent you an email. Check your junk folder if you haven’t.", + "mail": "မေးလ်", + "open_email_client": "Open Email Client" + } + }, + "home_timeline": { + "title": "အိမ်", + "navigation_bar_state": { + "offline": "အော့ဖ်လိုင်း", + "new_posts": "ပို့စ်အသစ်ကြည့်ရန်", + "published": "တင်လိုက်ပါပြီ!", + "Publishing": "ပို့စ်ကို တင်နေသည်...", + "accessibility": { + "logo_label": "လိုဂိုခလုတ်", + "logo_hint": "Tap to scroll to top and tap again to previous location" + } + } + }, + "suggestion_account": { + "title": "Find People to Follow", + "follow_explain": "When you follow someone, you’ll see their posts in your home feed." + }, + "compose": { + "title": { + "new_post": "ပို့စ်အသစ်", + "new_reply": "စာပြန်အသစ်" + }, + "media_selection": { + "camera": "ဓါတ်ပုံရိုက်", + "photo_library": "ဓာတ်ပုံပြတိုက်", + "browse": "ရှာဖွေ" + }, + "content_input_placeholder": "Type or paste what’s on your mind", + "compose_action": "ပို့စ်တင်", + "replying_to_user": "%s ထံ စာပြန်နေသည်", + "attachment": { + "photo": "ဓာတ်ပုံ", + "video": "ဗီဒီယို", + "attachment_broken": "This %s is broken and can’t be\nuploaded to Mastodon.", + "description_photo": "Describe the photo for the visually-impaired...", + "description_video": "Describe the video for the visually-impaired...", + "load_failed": "Load Failed", + "upload_failed": "Upload Failed", + "can_not_recognize_this_media_attachment": "Can not recognize this media attachment", + "attachment_too_large": "Attachment too large", + "compressing_state": "Compressing...", + "server_processing_state": "Server Processing..." + }, + "poll": { + "duration_time": "Duration: %s", + "thirty_minutes": "30 minutes", + "one_hour": "1 Hour", + "six_hours": "6 Hours", + "one_day": "1 Day", + "three_days": "3 Days", + "seven_days": "7 Days", + "option_number": "Option %ld", + "the_poll_is_invalid": "The poll is invalid", + "the_poll_has_empty_option": "The poll has empty option" + }, + "content_warning": { + "placeholder": "Write an accurate warning here..." + }, + "visibility": { + "public": "Public", + "unlisted": "Unlisted", + "private": "Followers only", + "direct": "Only people I mention" + }, + "auto_complete": { + "space_to_add": "Space to add" + }, + "accessibility": { + "append_attachment": "Add Attachment", + "append_poll": "Add Poll", + "remove_poll": "Remove Poll", + "custom_emoji_picker": "Custom Emoji Picker", + "enable_content_warning": "Enable Content Warning", + "disable_content_warning": "Disable Content Warning", + "post_visibility_menu": "Post Visibility Menu", + "post_options": "Post Options", + "posting_as": "Posting as %s" + }, + "keyboard": { + "discard_post": "Discard Post", + "publish_post": "Publish Post", + "toggle_poll": "Toggle Poll", + "toggle_content_warning": "Toggle Content Warning", + "append_attachment_entry": "Add Attachment - %s", + "select_visibility_entry": "မြင်နိုင်စွမ်း ရွေးချယ်ရန် - %s" + } + }, + "profile": { + "header": { + "follows_you": "Follows You" + }, + "dashboard": { + "my_posts": "posts", + "my_following": "following", + "my_followers": "followers", + "other_posts": "posts", + "other_following": "following", + "other_followers": "followers" + }, + "fields": { + "joined": "Joined", + "add_row": "Add Row", + "placeholder": { + "label": "Label", + "content": "Content" + }, + "verified": { + "short": "Verified on %s", + "long": "Ownership of this link was checked on %s" + } + }, + "segmented_control": { + "posts": "Posts", + "replies": "Replies", + "posts_and_replies": "Posts and Replies", + "media": "Media", + "about": "About" + }, + "relationship_action_alert": { + "confirm_mute_user": { + "title": "Mute Account", + "message": "Confirm to mute %s" + }, + "confirm_unmute_user": { + "title": "Unmute Account", + "message": "Confirm to unmute %s" + }, + "confirm_block_user": { + "title": "Block Account", + "message": "Confirm to block %s" + }, + "confirm_unblock_user": { + "title": "Unblock Account", + "message": "Confirm to unblock %s" + }, + "confirm_show_reblogs": { + "title": "Show Reblogs", + "message": "Confirm to show reblogs" + }, + "confirm_hide_reblogs": { + "title": "Hide Reblogs", + "message": "Confirm to hide reblogs" + } + }, + "accessibility": { + "show_avatar_image": "Show avatar image", + "edit_avatar_image": "Edit avatar image", + "show_banner_image": "Show banner image", + "double_tap_to_open_the_list": "Double tap to open the list" + } + }, + "follower": { + "title": "follower", + "footer": "Followers from other servers are not displayed." + }, + "following": { + "title": "following", + "footer": "Follows from other servers are not displayed." + }, + "familiarFollowers": { + "title": "Followers you familiar", + "followed_by_names": "Followed by %s" + }, + "favorited_by": { + "title": "Favorited By" + }, + "reblogged_by": { + "title": "Reblogged By" + }, + "search": { + "title": "Search", + "search_bar": { + "placeholder": "Search hashtags and users", + "cancel": "Cancel" + }, + "recommend": { + "button_text": "See All", + "hash_tag": { + "title": "Trending on Mastodon", + "description": "Hashtags that are getting quite a bit of attention", + "people_talking": "%s people are talking" + }, + "accounts": { + "title": "Accounts you might like", + "description": "You may like to follow these accounts", + "follow": "Follow" + } + }, + "searching": { + "segment": { + "all": "All", + "people": "People", + "hashtags": "Hashtags", + "posts": "Posts" + }, + "empty_state": { + "no_results": "No results" + }, + "recent_search": "Recent searches", + "clear": "Clear" + } + }, + "discovery": { + "tabs": { + "posts": "Posts", + "hashtags": "Hashtags", + "news": "News", + "community": "Community", + "for_you": "For You" + }, + "intro": "These are the posts gaining traction in your corner of Mastodon." + }, + "favorite": { + "title": "Your Favorites" + }, + "notification": { + "title": { + "Everything": "Everything", + "Mentions": "Mentions" + }, + "notification_description": { + "followed_you": "followed you", + "favorited_your_post": "favorited your post", + "reblogged_your_post": "reblogged your post", + "mentioned_you": "mentioned you", + "request_to_follow_you": "request to follow you", + "poll_has_ended": "poll has ended" + }, + "keyobard": { + "show_everything": "Show Everything", + "show_mentions": "Show Mentions" + }, + "follow_request": { + "accept": "Accept", + "accepted": "Accepted", + "reject": "reject", + "rejected": "Rejected" + } + }, + "thread": { + "back_title": "Post", + "title": "Post from %s" + }, + "settings": { + "title": "Settings", + "section": { + "appearance": { + "title": "Appearance", + "automatic": "Automatic", + "light": "Always Light", + "dark": "Always Dark" + }, + "look_and_feel": { + "title": "Look and Feel", + "use_system": "Use System", + "really_dark": "Really Dark", + "sorta_dark": "Sorta Dark", + "light": "Light" + }, + "notifications": { + "title": "Notifications", + "favorites": "Favorites my post", + "follows": "Follows me", + "boosts": "Reblogs my post", + "mentions": "Mentions me", + "trigger": { + "anyone": "anyone", + "follower": "a follower", + "follow": "anyone I follow", + "noone": "no one", + "title": "Notify me when" + } + }, + "preference": { + "title": "Preferences", + "true_black_dark_mode": "True black dark mode", + "disable_avatar_animation": "Disable animated avatars", + "disable_emoji_animation": "Disable animated emojis", + "using_default_browser": "Use default browser to open links", + "open_links_in_mastodon": "Open links in Mastodon" + }, + "boring_zone": { + "title": "The Boring Zone", + "account_settings": "Account Settings", + "terms": "Terms of Service", + "privacy": "Privacy Policy" + }, + "spicy_zone": { + "title": "The Spicy Zone", + "clear": "Clear Media Cache", + "signout": "Sign Out" + } + }, + "footer": { + "mastodon_description": "Mastodon is open source software. You can report issues on GitHub at %s (%s)" + }, + "keyboard": { + "close_settings_window": "Close Settings Window" + } + }, + "report": { + "title_report": "Report", + "title": "Report %s", + "step1": "Step 1 of 2", + "step2": "Step 2 of 2", + "content1": "Are there any other posts you’d like to add to the report?", + "content2": "Is there anything the moderators should know about this report?", + "report_sent_title": "Thanks for reporting, we’ll look into this.", + "send": "Send Report", + "skip_to_send": "Send without comment", + "text_placeholder": "Type or paste additional comments", + "reported": "REPORTED", + "step_one": { + "step_1_of_4": "Step 1 of 4", + "whats_wrong_with_this_post": "What's wrong with this post?", + "whats_wrong_with_this_account": "What's wrong with this account?", + "whats_wrong_with_this_username": "What's wrong with %s?", + "select_the_best_match": "Select the best match", + "i_dont_like_it": "I don’t like it", + "it_is_not_something_you_want_to_see": "It is not something you want to see", + "its_spam": "It’s spam", + "malicious_links_fake_engagement_or_repetetive_replies": "Malicious links, fake engagement, or repetetive replies", + "it_violates_server_rules": "It violates server rules", + "you_are_aware_that_it_breaks_specific_rules": "You are aware that it breaks specific rules", + "its_something_else": "It’s something else", + "the_issue_does_not_fit_into_other_categories": "The issue does not fit into other categories" + }, + "step_two": { + "step_2_of_4": "Step 2 of 4", + "which_rules_are_being_violated": "Which rules are being violated?", + "select_all_that_apply": "Select all that apply", + "i_just_don’t_like_it": "I just don’t like it" + }, + "step_three": { + "step_3_of_4": "Step 3 of 4", + "are_there_any_posts_that_back_up_this_report": "Are there any posts that back up this report?", + "select_all_that_apply": "Select all that apply" + }, + "step_four": { + "step_4_of_4": "Step 4 of 4", + "is_there_anything_else_we_should_know": "Is there anything else we should know?" + }, + "step_final": { + "dont_want_to_see_this": "Don’t want to see this?", + "when_you_see_something_you_dont_like_on_mastodon_you_can_remove_the_person_from_your_experience.": "When you see something you don’t like on Mastodon, you can remove the person from your experience.", + "unfollow": "Unfollow", + "unfollowed": "Unfollowed", + "unfollow_user": "Unfollow %s", + "mute_user": "Mute %s", + "you_wont_see_their_posts_or_reblogs_in_your_home_feed_they_wont_know_they_ve_been_muted": "You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted.", + "block_user": "Block %s", + "they_will_no_longer_be_able_to_follow_or_see_your_posts_but_they_can_see_if_theyve_been_blocked": "They will no longer be able to follow or see your posts, but they can see if they’ve been blocked.", + "while_we_review_this_you_can_take_action_against_user": "While we review this, you can take action against %s" + } + }, + "preview": { + "keyboard": { + "close_preview": "Close Preview", + "show_next": "Show Next", + "show_previous": "Show Previous" + } + }, + "account_list": { + "tab_bar_hint": "Current selected profile: %s. Double tap then hold to show account switcher", + "dismiss_account_switcher": "Dismiss Account Switcher", + "add_account": "Add Account" + }, + "wizard": { + "new_in_mastodon": "New in Mastodon", + "multiple_account_switch_intro_description": "Switch between multiple accounts by holding the profile button.", + "accessibility_hint": "Double tap to dismiss this wizard" + }, + "bookmark": { + "title": "Bookmarks" + }, + "followed_tags": { + "title": "Followed Tags", + "header": { + "posts": "posts", + "participants": "participants", + "posts_today": "posts today" + }, + "actions": { + "follow": "Follow", + "unfollow": "Unfollow" + } + } + } +} diff --git a/Localization/StringsConvertor/input/my.lproj/ios-infoPlist.json b/Localization/StringsConvertor/input/my.lproj/ios-infoPlist.json new file mode 100644 index 000000000..b56ff5096 --- /dev/null +++ b/Localization/StringsConvertor/input/my.lproj/ios-infoPlist.json @@ -0,0 +1,6 @@ +{ + "NSCameraUsageDescription": "ပို့စ်အခြေအနေအတွက် ပုံရိုက်ရန် အသုံးပြုခဲ့သည်", + "NSPhotoLibraryAddUsageDescription": "ဓာတ်ပုံပြခန်းတွင် ပုံသိမ်းရန် အသုံးပြုခဲ့သည်", + "NewPostShortcutItemTitle": "ပို့စ်အသစ်", + "SearchShortcutItemTitle": "ရှာဖွေရန်" +} diff --git a/Localization/StringsConvertor/input/nl.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/nl.lproj/Localizable.stringsdict index 314600ff7..29d0ec841 100644 --- a/Localization/StringsConvertor/input/nl.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/nl.lproj/Localizable.stringsdict @@ -15,7 +15,7 @@ one 1 unread notification other - %ld unread notification + %ld unread notifications a11y.plural.count.input_limit_exceeds @@ -50,6 +50,22 @@ %ld tekens + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ left + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 character + other + %ld characters + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/nl.lproj/app.json b/Localization/StringsConvertor/input/nl.lproj/app.json index e0b2872fb..938f40a07 100644 --- a/Localization/StringsConvertor/input/nl.lproj/app.json +++ b/Localization/StringsConvertor/input/nl.lproj/app.json @@ -51,6 +51,11 @@ "clean_cache": { "title": "Cache-geheugen Wissen", "message": "Cache-geheugen (%s) succesvol gewist." + }, + "translation_failed": { + "title": "Note", + "message": "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported.", + "button": "OK" } }, "controls": { @@ -74,10 +79,11 @@ "take_photo": "Maak foto", "save_photo": "Bewaar foto", "copy_photo": "Kopieer foto", - "sign_in": "Aanmelden", - "sign_up": "Registreren", + "sign_in": "Inloggen", + "sign_up": "Account aanmaken", "see_more": "Meer", "preview": "Voorvertoning", + "copy": "Copy", "share": "Deel", "share_user": "Delen %s", "share_post": "Deel bericht", @@ -91,12 +97,16 @@ "block_domain": "Blokkeer %s", "unblock_domain": "Deblokkeer %s", "settings": "Instellingen", - "delete": "Verwijder" + "delete": "Verwijder", + "translate_post": { + "title": "Translate from %s", + "unknown_language": "Unknown" + } }, "tabs": { "home": "Start", - "search": "Zoek", - "notification": "Melding", + "search_and_explore": "Search and Explore", + "notifications": "Notifications", "profile": "Profiel" }, "keyboard": { @@ -132,6 +142,8 @@ "sensitive_content": "Gevoelige inhoud", "media_content_warning": "Tap hier om te tonen", "tap_to_reveal": "Tik om te onthullen", + "load_embed": "Load Embed", + "link_via_user": "%s via %s", "poll": { "vote": "Stemmen", "closed": "Gesloten" @@ -139,8 +151,8 @@ "meta_entity": { "url": "Link: %s", "hashtag": "Hashtag: %s", - "mention": "Show Profile: %s", - "email": "Email address: %s" + "mention": "Profiel weergeven: %s", + "email": "E-mailadres: %s" }, "actions": { "reply": "Reageren", @@ -153,6 +165,7 @@ "show_image": "Toon afbeelding", "show_gif": "GIF weergeven", "show_video_player": "Toon videospeler", + "share_link_in_post": "Share Link in Post", "tap_then_hold_to_show_menu": "Tik en houd vast om menu te tonen" }, "tag": { @@ -168,6 +181,12 @@ "private": "Alleen hun volgers kunnen dit bericht zien.", "private_from_me": "Alleen mijn volgers kunnen dit bericht zien.", "direct": "Alleen de vermelde persoon kan dit bericht zien." + }, + "translation": { + "translated_from": "Translated from %s using %s", + "unknown_language": "Unknown", + "unknown_provider": "Unknown", + "show_original": "Shown Original" } }, "friendship": { @@ -187,8 +206,8 @@ "unmute_user": "%s niet langer negeren", "muted": "Genegeerd", "edit_info": "Bewerken", - "show_reblogs": "Show Reblogs", - "hide_reblogs": "Hide Reblogs" + "show_reblogs": "Toon reblogs", + "hide_reblogs": "Verberg reblogs" }, "timeline": { "filtered": "Gefilterd", @@ -218,10 +237,16 @@ "get_started": "Aan de slag", "log_in": "Log in" }, + "login": { + "title": "Welkom terug", + "subtitle": "Log je in op de server waarop je je account hebt aangemaakt.", + "server_search_field": { + "placeholder": "Voer URL in of zoek naar uw server" + } + }, "server_picker": { "title": "Kies een server, welke dan ook.", - "subtitle": "Kies een gemeenschap gebaseerd op jouw interesses, regio of een algemeen doel.", - "subtitle_extend": "Kies een gemeenschap gebaseerd op jouw interesses, regio, of een algemeen doel. Elke gemeenschap wordt beheerd door een volledig onafhankelijke organisatie of individu.", + "subtitle": "Kies een server gebaseerd op je regio, interesses, of een algemene server. Je kunt nog steeds chatten met iedereen op Mastodon, ongeacht op welke server je zit.", "button": { "category": { "all": "Alles", @@ -248,8 +273,7 @@ "category": "CATEGORIE" }, "input": { - "placeholder": "Zoek uw server of sluit u bij een nieuwe server aan...", - "search_servers_or_enter_url": "Search servers or enter URL" + "search_servers_or_enter_url": "Zoek naar gemeenschappen of voer URL in" }, "empty_state": { "finding_servers": "Beschikbare servers zoeken...", @@ -259,7 +283,7 @@ }, "register": { "title": "Vertel ons over uzelf.", - "lets_get_you_set_up_on_domain": "Let’s get you set up on %s", + "lets_get_you_set_up_on_domain": "Laten we je account instellen op %s", "input": { "avatar": { "delete": "Verwijderen" @@ -330,7 +354,7 @@ "confirm_email": { "title": "Nog één ding.", "subtitle": "We hebben een e-mail gestuurd naar %s,\nklik op de link om uw account te bevestigen.", - "tap_the_link_we_emailed_to_you_to_verify_your_account": "Tap the link we emailed to you to verify your account", + "tap_the_link_we_emailed_to_you_to_verify_your_account": "Tik op de link in de e-mail die je hebt ontvangen om uw account te verifiëren", "button": { "open_email_app": "Email Openen", "resend": "Verstuur opnieuw" @@ -355,8 +379,8 @@ "published": "Gepubliceerd!", "Publishing": "Bericht publiceren...", "accessibility": { - "logo_label": "Logo Button", - "logo_hint": "Tap to scroll to top and tap again to previous location" + "logo_label": "Logo knop", + "logo_hint": "Tik om naar boven te scrollen en tik nogmaals om terug te keren naar de vorige locatie" } } }, @@ -383,10 +407,12 @@ "attachment_broken": "Deze %s is corrupt en kan niet geüpload worden naar Mastodon.", "description_photo": "Omschrijf de foto voor mensen met een visuele beperking...", "description_video": "Omschrijf de video voor mensen met een visuele beperking...", - "load_failed": "Load Failed", - "upload_failed": "Upload Failed", - "can_not_recognize_this_media_attachment": "Can not regonize this media attachment", - "attachment_too_large": "Attachment too large" + "load_failed": "Laden mislukt", + "upload_failed": "Uploaden mislukt", + "can_not_recognize_this_media_attachment": "Kan de media in de bijlage niet herkennen", + "attachment_too_large": "Bijlage te groot", + "compressing_state": "Bezig met comprimeren...", + "server_processing_state": "Server is bezig met verwerken..." }, "poll": { "duration_time": "Duur: %s", @@ -396,7 +422,9 @@ "one_day": "1 Dag", "three_days": "3 Dagen", "seven_days": "7 Dagen", - "option_number": "Optie %ld" + "option_number": "Optie %ld", + "the_poll_is_invalid": "De peiling is ongeldig", + "the_poll_has_empty_option": "De peiling heeft een lege optie" }, "content_warning": { "placeholder": "Schrijf hier een nauwkeurige waarschuwing..." @@ -417,7 +445,9 @@ "custom_emoji_picker": "Eigen Emojikiezer", "enable_content_warning": "Inhoudswaarschuwing inschakelen", "disable_content_warning": "Inhoudswaarschuwing Uitschakelen", - "post_visibility_menu": "Berichtzichtbaarheidsmenu" + "post_visibility_menu": "Berichtzichtbaarheidsmenu", + "post_options": "Plaats Bericht Opties", + "posting_as": "Plaats bericht als %s" }, "keyboard": { "discard_post": "Bericht Verwijderen", @@ -430,18 +460,26 @@ }, "profile": { "header": { - "follows_you": "Follows You" + "follows_you": "Volgt jou" }, "dashboard": { - "posts": "berichten", - "following": "volgend", - "followers": "volgers" + "my_posts": "posts", + "my_following": "following", + "my_followers": "followers", + "other_posts": "posts", + "other_following": "following", + "other_followers": "followers" }, "fields": { + "joined": "Joined", "add_row": "Rij Toevoegen", "placeholder": { "label": "Etiket", "content": "Inhoud" + }, + "verified": { + "short": "Geverifieerd op %s", + "long": "Eigendom van deze link is gecontroleerd op %s" } }, "segmented_control": { @@ -469,12 +507,12 @@ "message": "Bevestig om %s te deblokkeren" }, "confirm_show_reblogs": { - "title": "Show Reblogs", - "message": "Confirm to show reblogs" + "title": "Toon reblogs", + "message": "Bevestig om reblogs te tonen" }, "confirm_hide_reblogs": { - "title": "Hide Reblogs", - "message": "Confirm to hide reblogs" + "title": "Verberg reblogs", + "message": "Bevestig om reblogs te verbergen" } }, "accessibility": { @@ -485,16 +523,16 @@ } }, "follower": { - "title": "follower", + "title": "volger", "footer": "Volgers van andere servers worden niet weergegeven." }, "following": { - "title": "following", + "title": "volgend", "footer": "Volgers van andere servers worden niet weergegeven." }, "familiarFollowers": { - "title": "Followers you familiar", - "followed_by_names": "Followed by %s" + "title": "Volgers die je kent", + "followed_by_names": "Gevolgd door %s" }, "favorited_by": { "title": "Favorited By" @@ -540,7 +578,7 @@ "posts": "Berichten", "hashtags": "Hashtags", "news": "Nieuws", - "community": "Community", + "community": "Gemeenschap", "for_you": "Voor jou" }, "intro": "Dit zijn de berichten die populair zijn in jouw Mastodon-kringen." @@ -566,10 +604,10 @@ "show_mentions": "Vermeldingen weergeven" }, "follow_request": { - "accept": "Accept", - "accepted": "Accepted", - "reject": "reject", - "rejected": "Rejected" + "accept": "Accepteren", + "accepted": "Geaccepteerd", + "reject": "afwijzen", + "rejected": "Afgewezen" } }, "thread": { @@ -646,11 +684,11 @@ "text_placeholder": "Schrijf of plak aanvullende opmerkingen", "reported": "Gerapporteerd", "step_one": { - "step_1_of_4": "Step 1 of 4", - "whats_wrong_with_this_post": "What's wrong with this post?", - "whats_wrong_with_this_account": "What's wrong with this account?", - "whats_wrong_with_this_username": "What's wrong with %s?", - "select_the_best_match": "Select the best match", + "step_1_of_4": "Stap 1 van 4", + "whats_wrong_with_this_post": "Wat is er mis met dit bericht?", + "whats_wrong_with_this_account": "Wat is er mis met dit bericht?", + "whats_wrong_with_this_username": "Wat is er mis met %s?", + "select_the_best_match": "Selecteer de beste overeenkomst", "i_dont_like_it": "I don’t like it", "it_is_not_something_you_want_to_see": "It is not something you want to see", "its_spam": "It’s spam", @@ -707,6 +745,18 @@ }, "bookmark": { "title": "Bookmarks" + }, + "followed_tags": { + "title": "Followed Tags", + "header": { + "posts": "posts", + "participants": "participants", + "posts_today": "posts today" + }, + "actions": { + "follow": "Follow", + "unfollow": "Unfollow" + } } } } diff --git a/Localization/StringsConvertor/input/pt-BR.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/pt-BR.lproj/Localizable.stringsdict index ba1532740..02fbf2d20 100644 --- a/Localization/StringsConvertor/input/pt-BR.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/pt-BR.lproj/Localizable.stringsdict @@ -50,6 +50,22 @@ %ld caracteres + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ restantes + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 carácter + other + %ld carácteres + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey @@ -72,9 +88,9 @@ NSStringFormatValueTypeKey ld one - Followed by %1$@, and another mutual + Seguido por %1$@, e outro em comum other - Followed by %1$@, and %ld mutuals + Seguido por %1$@, e %ld em comum plural.count.metric_formatted.post @@ -104,9 +120,9 @@ NSStringFormatValueTypeKey ld one - 1 media + 1 mídia other - %ld media + %ld mídias plural.count.post diff --git a/Localization/StringsConvertor/input/pt-BR.lproj/app.json b/Localization/StringsConvertor/input/pt-BR.lproj/app.json index 26e6edb76..e680db924 100644 --- a/Localization/StringsConvertor/input/pt-BR.lproj/app.json +++ b/Localization/StringsConvertor/input/pt-BR.lproj/app.json @@ -2,7 +2,7 @@ "common": { "alerts": { "common": { - "please_try_again": "Por favor tente novamente.", + "please_try_again": "Por favor, tente novamente.", "please_try_again_later": "Tente novamente mais tarde." }, "sign_up_failure": { @@ -51,6 +51,11 @@ "clean_cache": { "title": "Limpar Cache", "message": "%s do cache removidos com sucesso." + }, + "translation_failed": { + "title": "Note", + "message": "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported.", + "button": "OK" } }, "controls": { @@ -78,6 +83,7 @@ "sign_up": "Criar conta", "see_more": "Ver mais", "preview": "Pré-visualização", + "copy": "Copy", "share": "Compartilhar", "share_user": "Compartilhar %s", "share_post": "Compartilhar postagem", @@ -91,12 +97,16 @@ "block_domain": "Bloquear %s", "unblock_domain": "Desbloquear %s", "settings": "Configurações", - "delete": "Excluir" + "delete": "Excluir", + "translate_post": { + "title": "Translate from %s", + "unknown_language": "Unknown" + } }, "tabs": { "home": "Início", - "search": "Buscar", - "notification": "Notificação", + "search_and_explore": "Search and Explore", + "notifications": "Notifications", "profile": "Perfil" }, "keyboard": { @@ -132,6 +142,8 @@ "sensitive_content": "Conteúdo sensível", "media_content_warning": "Toque em qualquer lugar para revelar", "tap_to_reveal": "Toque para revelar", + "load_embed": "Load Embed", + "link_via_user": "%s via %s", "poll": { "vote": "Votar", "closed": "Fechado" @@ -153,6 +165,7 @@ "show_image": "Exibir imagem", "show_gif": "Exibir GIF", "show_video_player": "Mostrar reprodutor de vídeo", + "share_link_in_post": "Share Link in Post", "tap_then_hold_to_show_menu": "Toque e em seguida segure para exibir o menu" }, "tag": { @@ -164,10 +177,16 @@ "emoji": "Emoji" }, "visibility": { - "unlisted": "Everyone can see this post but not display in the public timeline.", + "unlisted": "Todos podem ver esta postagem, mas não são exibidos na linha do tempo pública.", "private": "Somente seus seguidores podem ver essa postagem.", "private_from_me": "Somente meus seguidores podem ver essa postagem.", "direct": "Somente o usuário mencionado pode ver essa postagem." + }, + "translation": { + "translated_from": "Translated from %s using %s", + "unknown_language": "Unknown", + "unknown_provider": "Unknown", + "show_original": "Shown Original" } }, "friendship": { @@ -202,8 +221,8 @@ }, "header": { "no_status_found": "Nenhuma postagem encontrada", - "blocking_warning": "You can’t view this user's profile\nuntil you unblock them.\nYour profile looks like this to them.", - "user_blocking_warning": "You can’t view %s’s profile\nuntil you unblock them.\nYour profile looks like this to them.", + "blocking_warning": "Você não pode ver o perfil deste usuário até desbloqueá-lo.\nSeu perfil aparece assim para esse usuário.", + "user_blocking_warning": "Você não pode ver o perfil de %s até desbloqueá-lo.\nSeu perfil aparece assim para esse usuário.", "blocked_warning": "Você não pode ver o perfil desse usuário até que ele o desbloqueie.", "user_blocked_warning": "Você não pode ver o perfil de %s até que ele o desbloqueie.", "suspended_warning": "Esse usuário foi suspenso.", @@ -218,10 +237,16 @@ "get_started": "Comece já", "log_in": "Entrar" }, + "login": { + "title": "Bem-vindo de volta", + "subtitle": "Logado na instância em que você criou a sua conta.", + "server_search_field": { + "placeholder": "Insira a URL ou procure pela sua instância" + } + }, "server_picker": { "title": "Mastodon é feito de usuários em instâncias diferentes.", - "subtitle": "Escolha uma instância baseada nos seus interesses, região, ou em uma proposta geral.", - "subtitle_extend": "Escolha uma instância baseada nos seus interesses, região, ou em uma proposta geral. Cada instância é operada por um indivíduo ou uma organização totalmente independente.", + "subtitle": "Escolha uma instância baseada na sua região, interesses, ou uma de uso geral. Você ainda poderá conversar com qualquer um no Mastodon, independente da instância.", "button": { "category": { "all": "Todos", @@ -248,8 +273,7 @@ "category": "Categoria" }, "input": { - "placeholder": "Procurar instâncias", - "search_servers_or_enter_url": "Procurar instâncias ou inserir URL" + "search_servers_or_enter_url": "Procurar comunidades ou inserir URL" }, "empty_state": { "finding_servers": "Procurando instâncias disponíveis...", @@ -259,7 +283,7 @@ }, "register": { "title": "Vamos configurar você em %s", - "lets_get_you_set_up_on_domain": "Let’s get you set up on %s", + "lets_get_you_set_up_on_domain": "Vamos configurar você em %s", "input": { "avatar": { "delete": "Excluir" @@ -279,13 +303,13 @@ "require": "Sua senha deve ter pelo menos:", "character_limit": "8 carácteres", "accessibility": { - "checked": "checked", - "unchecked": "unchecked" + "checked": "marcado", + "unchecked": "desmarcado" }, "hint": "Sua senha precisa ter pelo menos oito carácteres" }, "invite": { - "registration_user_invite_request": "Why do you want to join?" + "registration_user_invite_request": "Por que você deseja se inscrever?" } }, "error": { @@ -298,80 +322,80 @@ "reason": "Motivo" }, "reason": { - "blocked": "%s contains a disallowed email provider", + "blocked": "%s contém um provedor de e-mail não permitido", "unreachable": "%s parece não existir", "taken": "%s já está em uso", - "reserved": "%s is a reserved keyword", - "accepted": "%s must be accepted", - "blank": "%s is required", - "invalid": "%s is invalid", - "too_long": "%s is too long", - "too_short": "%s is too short", - "inclusion": "%s is not a supported value" + "reserved": "%s é uma palavra-chave reservada", + "accepted": "%s deve ser aceite", + "blank": "%s é obrigatório", + "invalid": "%s é inválido", + "too_long": "%s é muito longo", + "too_short": "%s é muito curto", + "inclusion": "%s não é um valor suportado" }, "special": { - "username_invalid": "Username must only contain alphanumeric characters and underscores", - "username_too_long": "Username is too long (can’t be longer than 30 characters)", - "email_invalid": "This is not a valid email address", - "password_too_short": "Password is too short (must be at least 8 characters)" + "username_invalid": "O nome de usuário só pode conter caracteres alfanuméricos e underlines (_)", + "username_too_long": "Nome de usuário é muito longo (não pode ter mais de 30 caracteres)", + "email_invalid": "Este não é um endereço de e-mail válido", + "password_too_short": "A senha é muito curta (deve ter pelo menos 8 caracteres)" } } }, "server_rules": { - "title": "Some ground rules.", - "subtitle": "These are set and enforced by the %s moderators.", - "prompt": "By continuing, you’re subject to the terms of service and privacy policy for %s.", - "terms_of_service": "terms of service", - "privacy_policy": "privacy policy", + "title": "Algumas regras básicas.", + "subtitle": "Estes são definidos e aplicados pelos moderadores da %s.", + "prompt": "Ao continuar, você estará sujeito aos termos de serviço e política de privacidade para %s.", + "terms_of_service": "termos de serviço", + "privacy_policy": "política de privacidade", "button": { - "confirm": "I Agree" + "confirm": "Eu concordo" } }, "confirm_email": { - "title": "One last thing.", - "subtitle": "Tap the link we emailed to you to verify your account.", - "tap_the_link_we_emailed_to_you_to_verify_your_account": "Tap the link we emailed to you to verify your account", + "title": "Uma última coisa.", + "subtitle": "Clique no link que te enviamos por e-mail para verificar a sua conta.", + "tap_the_link_we_emailed_to_you_to_verify_your_account": "Clique no link que te enviamos por e-mail para verificar a sua conta", "button": { - "open_email_app": "Open Email App", - "resend": "Resend" + "open_email_app": "Abrir aplicativo de e-mail", + "resend": "Reenviar" }, "dont_receive_email": { - "title": "Check your email", - "description": "Check if your email address is correct as well as your junk folder if you haven’t.", - "resend_email": "Resend Email" + "title": "Verifique o seu e-mail", + "description": "Verifique se o seu endereço de e-mail está correto, e também a sua pasta de spam caso não tenha verificado.", + "resend_email": "Reenviar e-mail" }, "open_email_app": { - "title": "Check your inbox.", - "description": "We just sent you an email. Check your junk folder if you haven’t.", - "mail": "Mail", - "open_email_client": "Open Email Client" + "title": "Verifique sua caixa de entrada.", + "description": "Enviamos um e-mail para você. Verifique sua pasta de spam caso ainda tenha verificado.", + "mail": "Correio", + "open_email_client": "Abrir Cliente de Email" } }, "home_timeline": { - "title": "Home", + "title": "Início", "navigation_bar_state": { - "offline": "Offline", - "new_posts": "See new posts", - "published": "Published!", - "Publishing": "Publishing post...", + "offline": "Desconectado", + "new_posts": "Ver novas postagens", + "published": "Publicado!", + "Publishing": "Publicando toot...", "accessibility": { - "logo_label": "Logo Button", - "logo_hint": "Tap to scroll to top and tap again to previous location" + "logo_label": "Botão do logotipo", + "logo_hint": "Toque para rolar para o topo e toque novamente para a localização anterior" } } }, "suggestion_account": { - "title": "Find People to Follow", - "follow_explain": "When you follow someone, you’ll see their posts in your home feed." + "title": "Encontre pessoas para seguir", + "follow_explain": "Ao seguir alguém, você verá as publicações dessa pessoa na sua página inicial." }, "compose": { "title": { - "new_post": "New Post", - "new_reply": "New Reply" + "new_post": "Novo toot", + "new_reply": "Nova resposta" }, "media_selection": { - "camera": "Take Photo", - "photo_library": "Photo Library", + "camera": "Tirar foto", + "photo_library": "Galeria", "browse": "Navegar" }, "content_input_placeholder": "Digite ou cole o que está na sua mente", @@ -380,13 +404,15 @@ "attachment": { "photo": "foto", "video": "vídeo", - "attachment_broken": "This %s is broken and can’t be\nuploaded to Mastodon.", - "description_photo": "Describe the photo for the visually-impaired...", - "description_video": "Describe the video for the visually-impaired...", - "load_failed": "Load Failed", - "upload_failed": "Upload Failed", - "can_not_recognize_this_media_attachment": "Can not regonize this media attachment", - "attachment_too_large": "Attachment too large" + "attachment_broken": "Este %s está quebrado e não pode ser\nenviado para o Mastodon.", + "description_photo": "Descreva a foto para deficientes visuais...", + "description_video": "Descreva o vídeo para os deficientes visuais...", + "load_failed": "Falha ao carregar", + "upload_failed": "Falha no carregamento", + "can_not_recognize_this_media_attachment": "Não é possível reconhecer este anexo de mídia", + "attachment_too_large": "O anexo é muito grande", + "compressing_state": "Compactando...", + "server_processing_state": "Servidor processando..." }, "poll": { "duration_time": "Duração: %s", @@ -396,128 +422,140 @@ "one_day": "1 dia", "three_days": "3 dias", "seven_days": "7 dias", - "option_number": "Opção %ld" + "option_number": "Opção %ld", + "the_poll_is_invalid": "A enquete é inválida", + "the_poll_has_empty_option": "A enquete tem uma opção vazia" }, "content_warning": { - "placeholder": "Write an accurate warning here..." + "placeholder": "Escreva um aviso de conteúdo preciso aqui..." }, "visibility": { - "public": "Public", - "unlisted": "Unlisted", - "private": "Followers only", - "direct": "Only people I mention" + "public": "Público", + "unlisted": "Não listado", + "private": "Apenas seguidores", + "direct": "Apenas pessoas que menciono" }, "auto_complete": { - "space_to_add": "Space to add" + "space_to_add": "Espaço a adicionar" }, "accessibility": { - "append_attachment": "Add Attachment", - "append_poll": "Add Poll", - "remove_poll": "Remove Poll", - "custom_emoji_picker": "Custom Emoji Picker", - "enable_content_warning": "Enable Content Warning", - "disable_content_warning": "Disable Content Warning", - "post_visibility_menu": "Post Visibility Menu" + "append_attachment": "Adicionar anexo", + "append_poll": "Adicionar enquete", + "remove_poll": "Remover enquete", + "custom_emoji_picker": "Seletor de emoji personalizado", + "enable_content_warning": "Ativar Aviso de Conteúdo", + "disable_content_warning": "Desativar Aviso de Conteúdo", + "post_visibility_menu": "Menu de Visibilidade do Post", + "post_options": "Opções de postagem", + "posting_as": "Publicando como %s" }, "keyboard": { - "discard_post": "Discard Post", - "publish_post": "Publish Post", - "toggle_poll": "Toggle Poll", - "toggle_content_warning": "Toggle Content Warning", - "append_attachment_entry": "Add Attachment - %s", - "select_visibility_entry": "Select Visibility - %s" + "discard_post": "Descartar postagem", + "publish_post": "Publicar postagem", + "toggle_poll": "Alternar enquete", + "toggle_content_warning": "Ativar/desativar Aviso de Conteúdo", + "append_attachment_entry": "Adicionar Anexo - %s", + "select_visibility_entry": "Selecionar Visibilidade - %s" } }, "profile": { "header": { - "follows_you": "Follows You" + "follows_you": "Segue você" }, "dashboard": { - "posts": "toots", - "following": "seguindo", - "followers": "seguidores" + "my_posts": "posts", + "my_following": "following", + "my_followers": "followers", + "other_posts": "posts", + "other_following": "following", + "other_followers": "followers" }, "fields": { - "add_row": "Add Row", + "joined": "Joined", + "add_row": "Adicionar linha", "placeholder": { - "label": "Label", - "content": "Content" + "label": "Descrição", + "content": "Conteúdo" + }, + "verified": { + "short": "Verificado em %s", + "long": "O link foi verificado em %s" } }, "segmented_control": { - "posts": "Posts", - "replies": "Replies", - "posts_and_replies": "Posts and Replies", - "media": "Media", - "about": "About" + "posts": "Toots", + "replies": "Respostas", + "posts_and_replies": "Toots e respostas", + "media": "Mídia", + "about": "Sobre" }, "relationship_action_alert": { "confirm_mute_user": { - "title": "Mute Account", - "message": "Confirm to mute %s" + "title": "Silenciar conta", + "message": "Confirme para silenciar %s" }, "confirm_unmute_user": { - "title": "Unmute Account", - "message": "Confirm to unmute %s" + "title": "Tirar conta do silenciado", + "message": "Confirme para tirar %s do silenciado" }, "confirm_block_user": { - "title": "Block Account", - "message": "Confirm to block %s" + "title": "Bloquear conta", + "message": "Confirme para bloquear %s" }, "confirm_unblock_user": { - "title": "Unblock Account", - "message": "Confirm to unblock %s" + "title": "Desbloquear conta", + "message": "Confirme para desbloquear %s" }, "confirm_show_reblogs": { - "title": "Show Reblogs", - "message": "Confirm to show reblogs" + "title": "Mostrar reblogs", + "message": "Confirmar para mostrar reblogs" }, "confirm_hide_reblogs": { - "title": "Hide Reblogs", - "message": "Confirm to hide reblogs" + "title": "Ocultar reblogs", + "message": "Confirmar para ocultar reblogs" } }, "accessibility": { - "show_avatar_image": "Show avatar image", - "edit_avatar_image": "Edit avatar image", - "show_banner_image": "Show banner image", - "double_tap_to_open_the_list": "Double tap to open the list" + "show_avatar_image": "Mostrar foto de perfil", + "edit_avatar_image": "Editar foto de perfil", + "show_banner_image": "Mostrar foto de capa", + "double_tap_to_open_the_list": "Toque duas vezes para abrir a lista" } }, "follower": { - "title": "follower", - "footer": "Followers from other servers are not displayed." + "title": "seguidor", + "footer": "Seguidores de outras instâncias não são exibidos." }, "following": { - "title": "following", - "footer": "Follows from other servers are not displayed." + "title": "seguindo", + "footer": "Contas que você segue de outras instâncias não são exibidas." }, "familiarFollowers": { - "title": "Followers you familiar", - "followed_by_names": "Followed by %s" + "title": "Seguidores que você conhece", + "followed_by_names": "Seguido por %s" }, "favorited_by": { - "title": "Favorited By" + "title": "Favoritado por" }, "reblogged_by": { - "title": "Reblogged By" + "title": "Reblogado por" }, "search": { - "title": "Search", + "title": "Buscar", "search_bar": { - "placeholder": "Search hashtags and users", - "cancel": "Cancel" + "placeholder": "Buscar hashtags e usuários", + "cancel": "Cancelar" }, "recommend": { - "button_text": "See All", + "button_text": "Ver tudo", "hash_tag": { - "title": "Trending on Mastodon", - "description": "Hashtags that are getting quite a bit of attention", - "people_talking": "%s people are talking" + "title": "Em tendência no Mastodon", + "description": "Hashtags que estão recebendo bastante atenção", + "people_talking": "%s pessoas estão falando" }, "accounts": { - "title": "Accounts you might like", - "description": "You may like to follow these accounts", + "title": "Contas que você deve gostar", + "description": "Você pode gostar de seguir estas contas", "follow": "Seguir" } }, @@ -543,170 +581,182 @@ "community": "Comunidade", "for_you": "Para você" }, - "intro": "These are the posts gaining traction in your corner of Mastodon." + "intro": "Esses são os posts que estão ganhando força no seu canto do Mastodon." }, "favorite": { - "title": "Your Favorites" + "title": "Seus favoritos" }, "notification": { "title": { - "Everything": "Everything", - "Mentions": "Mentions" + "Everything": "Tudo", + "Mentions": "Menções" }, "notification_description": { - "followed_you": "followed you", - "favorited_your_post": "favorited your post", - "reblogged_your_post": "reblogged your post", - "mentioned_you": "mentioned you", - "request_to_follow_you": "request to follow you", - "poll_has_ended": "poll has ended" + "followed_you": "seguiu você", + "favorited_your_post": "favoritou seu toot", + "reblogged_your_post": "reblogou seu toot", + "mentioned_you": "te mencionou", + "request_to_follow_you": "solicitação para te seguir", + "poll_has_ended": "enquete encerrada" }, "keyobard": { - "show_everything": "Show Everything", - "show_mentions": "Show Mentions" + "show_everything": "Mostrar tudo", + "show_mentions": "Mostrar menções" }, "follow_request": { - "accept": "Accept", - "accepted": "Accepted", - "reject": "reject", - "rejected": "Rejected" + "accept": "Aceitar", + "accepted": "Aceito", + "reject": "rejeitar", + "rejected": "Rejeitado" } }, "thread": { - "back_title": "Post", - "title": "Post from %s" + "back_title": "Toot", + "title": "Publicação de %s" }, "settings": { - "title": "Settings", + "title": "Configurações", "section": { "appearance": { - "title": "Appearance", - "automatic": "Automatic", - "light": "Always Light", - "dark": "Always Dark" + "title": "Aparência", + "automatic": "Automático", + "light": "Sempre Claro", + "dark": "Sempre Escuro" }, "look_and_feel": { - "title": "Look and Feel", - "use_system": "Use System", - "really_dark": "Really Dark", - "sorta_dark": "Sorta Dark", - "light": "Light" + "title": "Aparência e Comportamento", + "use_system": "Usar configuração do sistema", + "really_dark": "Bem escuro", + "sorta_dark": "Meio escuro", + "light": "Claro" }, "notifications": { - "title": "Notifications", - "favorites": "Favorites my post", - "follows": "Follows me", - "boosts": "Reblogs my post", - "mentions": "Mentions me", + "title": "Notificações", + "favorites": "Favoritaram minha publicação", + "follows": "Me segue", + "boosts": "Rebloga minha publicação", + "mentions": "Me menciona", "trigger": { - "anyone": "anyone", - "follower": "a follower", - "follow": "anyone I follow", - "noone": "no one", - "title": "Notify me when" + "anyone": "qualquer pessoa", + "follower": "um seguidor", + "follow": "qualquer um que eu siga", + "noone": "ninguém", + "title": "Me notificar quando" } }, "preference": { - "title": "Preferences", - "true_black_dark_mode": "True black dark mode", - "disable_avatar_animation": "Disable animated avatars", - "disable_emoji_animation": "Disable animated emojis", - "using_default_browser": "Use default browser to open links", - "open_links_in_mastodon": "Open links in Mastodon" + "title": "Preferências", + "true_black_dark_mode": "Modo preto", + "disable_avatar_animation": "Desativar fotos animadas", + "disable_emoji_animation": "Desativar emojis animados", + "using_default_browser": "Usar o navegador padrão pra abrir links", + "open_links_in_mastodon": "Abrir links no Mastodon" }, "boring_zone": { - "title": "The Boring Zone", - "account_settings": "Account Settings", - "terms": "Terms of Service", - "privacy": "Privacy Policy" + "title": "A zona chata", + "account_settings": "Configurações da conta", + "terms": "Termos de serviço", + "privacy": "Política de privacidade" }, "spicy_zone": { - "title": "The Spicy Zone", - "clear": "Clear Media Cache", - "signout": "Sign Out" + "title": "A zona apimentada", + "clear": "Limpar cachê de mídia", + "signout": "Sair" } }, "footer": { - "mastodon_description": "Mastodon is open source software. You can report issues on GitHub at %s (%s)" + "mastodon_description": "Mastodon é um software de código aberto. Você pode reportar problemas no GitHub em %s (%s)" }, "keyboard": { - "close_settings_window": "Close Settings Window" + "close_settings_window": "Fechar janela de configurações" } }, "report": { - "title_report": "Report", - "title": "Report %s", - "step1": "Step 1 of 2", - "step2": "Step 2 of 2", - "content1": "Are there any other posts you’d like to add to the report?", - "content2": "Is there anything the moderators should know about this report?", - "report_sent_title": "Thanks for reporting, we’ll look into this.", - "send": "Send Report", - "skip_to_send": "Send without comment", - "text_placeholder": "Type or paste additional comments", - "reported": "REPORTED", + "title_report": "Denunciar", + "title": "Denunciar %s", + "step1": "Passo 1 de 2", + "step2": "Passo 2 de 2", + "content1": "Há outras postagens que você gostaria de adicionar na denúncia?", + "content2": "Há algo que os moderadores deveriam saber sobre esta denúncia?", + "report_sent_title": "Obrigado por denunciar, iremos analisar.", + "send": "Enviar denúncia", + "skip_to_send": "Enviar sem comentário", + "text_placeholder": "Digite ou cole comentários adicionais", + "reported": "DENUNCIADO", "step_one": { - "step_1_of_4": "Step 1 of 4", - "whats_wrong_with_this_post": "What's wrong with this post?", - "whats_wrong_with_this_account": "What's wrong with this account?", - "whats_wrong_with_this_username": "What's wrong with %s?", - "select_the_best_match": "Select the best match", - "i_dont_like_it": "I don’t like it", - "it_is_not_something_you_want_to_see": "It is not something you want to see", - "its_spam": "It’s spam", - "malicious_links_fake_engagement_or_repetetive_replies": "Malicious links, fake engagement, or repetetive replies", - "it_violates_server_rules": "It violates server rules", - "you_are_aware_that_it_breaks_specific_rules": "You are aware that it breaks specific rules", - "its_something_else": "It’s something else", - "the_issue_does_not_fit_into_other_categories": "The issue does not fit into other categories" + "step_1_of_4": "Passo 1 de 4", + "whats_wrong_with_this_post": "O que há de errado com essa publicação?", + "whats_wrong_with_this_account": "O que há de errado com essa conta?", + "whats_wrong_with_this_username": "O que há de errado com %s?", + "select_the_best_match": "Selecione a melhor alternativa", + "i_dont_like_it": "Eu não gosto disso", + "it_is_not_something_you_want_to_see": "Não é algo que você gostaria de ver", + "its_spam": "É spam", + "malicious_links_fake_engagement_or_repetetive_replies": "Links maliciosos, engajamento falso, ou respostas repetitivas", + "it_violates_server_rules": "Isso viola as regras do servidor", + "you_are_aware_that_it_breaks_specific_rules": "Você está ciente que isso quebra regras específicas", + "its_something_else": "É outra coisa", + "the_issue_does_not_fit_into_other_categories": "O problema não se encaixa em outras categorias" }, "step_two": { - "step_2_of_4": "Step 2 of 4", - "which_rules_are_being_violated": "Which rules are being violated?", - "select_all_that_apply": "Select all that apply", - "i_just_don’t_like_it": "I just don’t like it" + "step_2_of_4": "Passo 2 de 4", + "which_rules_are_being_violated": "Quais regras estão sendo violadas?", + "select_all_that_apply": "Selecione todas que se aplicam", + "i_just_don’t_like_it": "Simplesmente não gosto" }, "step_three": { - "step_3_of_4": "Step 3 of 4", - "are_there_any_posts_that_back_up_this_report": "Are there any posts that back up this report?", - "select_all_that_apply": "Select all that apply" + "step_3_of_4": "Passo 3 de 4", + "are_there_any_posts_that_back_up_this_report": "Existem postagens que apoiam essa denúncia?", + "select_all_that_apply": "Selecione todos que se aplicam" }, "step_four": { - "step_4_of_4": "Step 4 of 4", - "is_there_anything_else_we_should_know": "Is there anything else we should know?" + "step_4_of_4": "Passo 4 de 4", + "is_there_anything_else_we_should_know": "Há algo a mais que deveríamos saber?" }, "step_final": { - "dont_want_to_see_this": "Don’t want to see this?", - "when_you_see_something_you_dont_like_on_mastodon_you_can_remove_the_person_from_your_experience.": "When you see something you don’t like on Mastodon, you can remove the person from your experience.", - "unfollow": "Unfollow", - "unfollowed": "Unfollowed", - "unfollow_user": "Unfollow %s", - "mute_user": "Mute %s", - "you_wont_see_their_posts_or_reblogs_in_your_home_feed_they_wont_know_they_ve_been_muted": "You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted.", - "block_user": "Block %s", - "they_will_no_longer_be_able_to_follow_or_see_your_posts_but_they_can_see_if_theyve_been_blocked": "They will no longer be able to follow or see your posts, but they can see if they’ve been blocked.", - "while_we_review_this_you_can_take_action_against_user": "While we review this, you can take action against %s" + "dont_want_to_see_this": "Não quer ver isso?", + "when_you_see_something_you_dont_like_on_mastodon_you_can_remove_the_person_from_your_experience.": "Quando você vê algo que não gosta no Mastodon, você pode remover essa pessoa da sua experiência.", + "unfollow": "Deixar de seguir", + "unfollowed": "Deixou de seguir", + "unfollow_user": "Deixar de seguir %s", + "mute_user": "Silenciar %s", + "you_wont_see_their_posts_or_reblogs_in_your_home_feed_they_wont_know_they_ve_been_muted": "Você não verá as postagens ou reblogs dessa conta na sua pagina inicial. Essa pessoa não saberá que foi silenciada.", + "block_user": "Bloquear %s", + "they_will_no_longer_be_able_to_follow_or_see_your_posts_but_they_can_see_if_theyve_been_blocked": "Essa conta não poderá mais te seguir ou ver suas postagens, mas ela poderá ver que foi bloqueada.", + "while_we_review_this_you_can_take_action_against_user": "Enquanto revisamos isso, você pode tomar medidas contra %s" } }, "preview": { "keyboard": { - "close_preview": "Close Preview", - "show_next": "Show Next", - "show_previous": "Show Previous" + "close_preview": "Fechar prévia", + "show_next": "Mostrar a próxima", + "show_previous": "Mostrar a anterior" } }, "account_list": { - "tab_bar_hint": "Current selected profile: %s. Double tap then hold to show account switcher", - "dismiss_account_switcher": "Dismiss Account Switcher", - "add_account": "Add Account" + "tab_bar_hint": "Perfil selecionado nesse momento: %s. Toque duas vezes e segure para mostrar o alternador de conta", + "dismiss_account_switcher": "Descartar alternador de conta", + "add_account": "Adicionar conta" }, "wizard": { - "new_in_mastodon": "New in Mastodon", - "multiple_account_switch_intro_description": "Switch between multiple accounts by holding the profile button.", - "accessibility_hint": "Double tap to dismiss this wizard" + "new_in_mastodon": "Novo no Mastodon", + "multiple_account_switch_intro_description": "Alterne entre múltiplas contas segurando o botão de perfil.", + "accessibility_hint": "Toque duas vezes para descartar este assistente" }, "bookmark": { - "title": "Bookmarks" + "title": "Marcados" + }, + "followed_tags": { + "title": "Followed Tags", + "header": { + "posts": "posts", + "participants": "participants", + "posts_today": "posts today" + }, + "actions": { + "follow": "Follow", + "unfollow": "Unfollow" + } } } } diff --git a/Localization/StringsConvertor/input/pt.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/pt.lproj/Localizable.stringsdict index bdcae6ac9..788eb95fc 100644 --- a/Localization/StringsConvertor/input/pt.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/pt.lproj/Localizable.stringsdict @@ -15,7 +15,7 @@ one 1 unread notification other - %ld unread notification + %ld unread notifications a11y.plural.count.input_limit_exceeds @@ -50,6 +50,22 @@ %ld characters + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ left + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 character + other + %ld characters + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/pt.lproj/app.json b/Localization/StringsConvertor/input/pt.lproj/app.json index a6a971860..4939d3afb 100644 --- a/Localization/StringsConvertor/input/pt.lproj/app.json +++ b/Localization/StringsConvertor/input/pt.lproj/app.json @@ -51,6 +51,11 @@ "clean_cache": { "title": "Clean Cache", "message": "Successfully cleaned %s cache." + }, + "translation_failed": { + "title": "Note", + "message": "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported.", + "button": "OK" } }, "controls": { @@ -74,10 +79,11 @@ "take_photo": "Take Photo", "save_photo": "Save Photo", "copy_photo": "Copy Photo", - "sign_in": "Sign In", - "sign_up": "Sign Up", + "sign_in": "Log in", + "sign_up": "Create account", "see_more": "See More", "preview": "Preview", + "copy": "Copy", "share": "Share", "share_user": "Share %s", "share_post": "Share Post", @@ -91,12 +97,16 @@ "block_domain": "Block %s", "unblock_domain": "Unblock %s", "settings": "Settings", - "delete": "Delete" + "delete": "Delete", + "translate_post": { + "title": "Translate from %s", + "unknown_language": "Unknown" + } }, "tabs": { "home": "Home", - "search": "Search", - "notification": "Notification", + "search_and_explore": "Search and Explore", + "notifications": "Notifications", "profile": "Profile" }, "keyboard": { @@ -132,6 +142,8 @@ "sensitive_content": "Sensitive Content", "media_content_warning": "Tap anywhere to reveal", "tap_to_reveal": "Tap to reveal", + "load_embed": "Load Embed", + "link_via_user": "%s via %s", "poll": { "vote": "Vote", "closed": "Closed" @@ -153,6 +165,7 @@ "show_image": "Show image", "show_gif": "Show GIF", "show_video_player": "Show video player", + "share_link_in_post": "Share Link in Post", "tap_then_hold_to_show_menu": "Tap then hold to show menu" }, "tag": { @@ -168,6 +181,12 @@ "private": "Only their followers can see this post.", "private_from_me": "Only my followers can see this post.", "direct": "Only mentioned user can see this post." + }, + "translation": { + "translated_from": "Translated from %s using %s", + "unknown_language": "Unknown", + "unknown_provider": "Unknown", + "show_original": "Shown Original" } }, "friendship": { @@ -218,10 +237,16 @@ "get_started": "Get Started", "log_in": "Log In" }, + "login": { + "title": "Welcome back", + "subtitle": "Log you in on the server you created your account on.", + "server_search_field": { + "placeholder": "Enter URL or search for your server" + } + }, "server_picker": { "title": "Mastodon is made of users in different servers.", - "subtitle": "Pick a server based on your interests, region, or a general purpose one.", - "subtitle_extend": "Pick a server based on your interests, region, or a general purpose one. Each server is operated by an entirely independent organization or individual.", + "subtitle": "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers.", "button": { "category": { "all": "All", @@ -248,8 +273,7 @@ "category": "CATEGORY" }, "input": { - "placeholder": "Search servers", - "search_servers_or_enter_url": "Search servers or enter URL" + "search_servers_or_enter_url": "Search communities or enter URL" }, "empty_state": { "finding_servers": "Finding available servers...", @@ -385,8 +409,10 @@ "description_video": "Describe the video for the visually-impaired...", "load_failed": "Load Failed", "upload_failed": "Upload Failed", - "can_not_recognize_this_media_attachment": "Can not regonize this media attachment", - "attachment_too_large": "Attachment too large" + "can_not_recognize_this_media_attachment": "Can not recognize this media attachment", + "attachment_too_large": "Attachment too large", + "compressing_state": "Compressing...", + "server_processing_state": "Server Processing..." }, "poll": { "duration_time": "Duration: %s", @@ -396,7 +422,9 @@ "one_day": "1 Day", "three_days": "3 Days", "seven_days": "7 Days", - "option_number": "Option %ld" + "option_number": "Option %ld", + "the_poll_is_invalid": "The poll is invalid", + "the_poll_has_empty_option": "The poll has empty option" }, "content_warning": { "placeholder": "Write an accurate warning here..." @@ -417,7 +445,9 @@ "custom_emoji_picker": "Custom Emoji Picker", "enable_content_warning": "Enable Content Warning", "disable_content_warning": "Disable Content Warning", - "post_visibility_menu": "Post Visibility Menu" + "post_visibility_menu": "Post Visibility Menu", + "post_options": "Post Options", + "posting_as": "Posting as %s" }, "keyboard": { "discard_post": "Discard Post", @@ -433,15 +463,23 @@ "follows_you": "Follows You" }, "dashboard": { - "posts": "posts", - "following": "following", - "followers": "followers" + "my_posts": "posts", + "my_following": "following", + "my_followers": "followers", + "other_posts": "posts", + "other_following": "following", + "other_followers": "followers" }, "fields": { + "joined": "Joined", "add_row": "Add Row", "placeholder": { "label": "Label", "content": "Content" + }, + "verified": { + "short": "Verified on %s", + "long": "Ownership of this link was checked on %s" } }, "segmented_control": { @@ -707,6 +745,18 @@ }, "bookmark": { "title": "Bookmarks" + }, + "followed_tags": { + "title": "Followed Tags", + "header": { + "posts": "posts", + "participants": "participants", + "posts_today": "posts today" + }, + "actions": { + "follow": "Follow", + "unfollow": "Unfollow" + } } } } diff --git a/Localization/StringsConvertor/input/ro.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/ro.lproj/Localizable.stringsdict index 7ae5a1c79..669272590 100644 --- a/Localization/StringsConvertor/input/ro.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/ro.lproj/Localizable.stringsdict @@ -15,9 +15,9 @@ one 1 unread notification few - %ld unread notification + %ld unread notifications other - %ld unread notification + %ld unread notifications a11y.plural.count.input_limit_exceeds @@ -56,6 +56,24 @@ %ld characters + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ left + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 character + few + %ld characters + other + %ld characters + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/ro.lproj/app.json b/Localization/StringsConvertor/input/ro.lproj/app.json index a9d3804fa..65ae8eb4e 100644 --- a/Localization/StringsConvertor/input/ro.lproj/app.json +++ b/Localization/StringsConvertor/input/ro.lproj/app.json @@ -13,7 +13,7 @@ }, "vote_failure": { "title": "Eșec la vot", - "poll_ended": "The poll has ended" + "poll_ended": "Sondajul tău s-a încheiat" }, "discard_post_content": { "title": "Șterge Schită", @@ -41,7 +41,7 @@ "block_entire_domain": "Block Domain" }, "save_photo_failure": { - "title": "Save Photo Failure", + "title": "Salvarea fotografiei a eșuat", "message": "Please enable the photo library access permission to save the photo." }, "delete_post": { @@ -51,17 +51,22 @@ "clean_cache": { "title": "Clean Cache", "message": "Successfully cleaned %s cache." + }, + "translation_failed": { + "title": "Note", + "message": "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported.", + "button": "OK" } }, "controls": { "actions": { - "back": "Back", - "next": "Next", + "back": "Înapoi", + "next": "Înainte", "previous": "Previous", "open": "Deschide", - "add": "Add", + "add": "Adaugă", "remove": "Elimină", - "edit": "Edit", + "edit": "Modifică", "save": "Salvează", "ok": "OK", "done": "Done", @@ -74,29 +79,34 @@ "take_photo": "Take Photo", "save_photo": "Save Photo", "copy_photo": "Copy Photo", - "sign_in": "Sign In", - "sign_up": "Sign Up", + "sign_in": "Log in", + "sign_up": "Create account", "see_more": "See More", "preview": "Preview", + "copy": "Copy", "share": "Share", "share_user": "Share %s", "share_post": "Share Post", "open_in_safari": "Open in Safari", - "open_in_browser": "Open in Browser", - "find_people": "Find people to follow", + "open_in_browser": "Deschide în browser", + "find_people": "Găsește persoane de urmărit", "manually_search": "Manually search instead", - "skip": "Skip", + "skip": "Treci peste", "reply": "Reply", "report_user": "Report %s", "block_domain": "Block %s", "unblock_domain": "Unblock %s", "settings": "Settings", - "delete": "Delete" + "delete": "Delete", + "translate_post": { + "title": "Translate from %s", + "unknown_language": "Unknown" + } }, "tabs": { - "home": "Home", - "search": "Search", - "notification": "Notification", + "home": "Acasă", + "search_and_explore": "Search and Explore", + "notifications": "Notifications", "profile": "Profile" }, "keyboard": { @@ -132,6 +142,8 @@ "sensitive_content": "Sensitive Content", "media_content_warning": "Tap anywhere to reveal", "tap_to_reveal": "Tap to reveal", + "load_embed": "Load Embed", + "link_via_user": "%s via %s", "poll": { "vote": "Vote", "closed": "Closed" @@ -153,6 +165,7 @@ "show_image": "Show image", "show_gif": "Show GIF", "show_video_player": "Show video player", + "share_link_in_post": "Share Link in Post", "tap_then_hold_to_show_menu": "Tap then hold to show menu" }, "tag": { @@ -168,6 +181,12 @@ "private": "Only their followers can see this post.", "private_from_me": "Only my followers can see this post.", "direct": "Only mentioned user can see this post." + }, + "translation": { + "translated_from": "Translated from %s using %s", + "unknown_language": "Unknown", + "unknown_provider": "Unknown", + "show_original": "Shown Original" } }, "friendship": { @@ -218,10 +237,16 @@ "get_started": "Get Started", "log_in": "Log In" }, + "login": { + "title": "Welcome back", + "subtitle": "Log you in on the server you created your account on.", + "server_search_field": { + "placeholder": "Enter URL or search for your server" + } + }, "server_picker": { "title": "Mastodon is made of users in different servers.", - "subtitle": "Pick a server based on your interests, region, or a general purpose one.", - "subtitle_extend": "Pick a server based on your interests, region, or a general purpose one. Each server is operated by an entirely independent organization or individual.", + "subtitle": "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers.", "button": { "category": { "all": "All", @@ -248,8 +273,7 @@ "category": "CATEGORY" }, "input": { - "placeholder": "Search servers", - "search_servers_or_enter_url": "Search servers or enter URL" + "search_servers_or_enter_url": "Search communities or enter URL" }, "empty_state": { "finding_servers": "Finding available servers...", @@ -385,8 +409,10 @@ "description_video": "Describe the video for the visually-impaired...", "load_failed": "Load Failed", "upload_failed": "Upload Failed", - "can_not_recognize_this_media_attachment": "Can not regonize this media attachment", - "attachment_too_large": "Attachment too large" + "can_not_recognize_this_media_attachment": "Can not recognize this media attachment", + "attachment_too_large": "Attachment too large", + "compressing_state": "Compressing...", + "server_processing_state": "Server Processing..." }, "poll": { "duration_time": "Duration: %s", @@ -396,7 +422,9 @@ "one_day": "1 Day", "three_days": "3 Days", "seven_days": "7 Days", - "option_number": "Option %ld" + "option_number": "Option %ld", + "the_poll_is_invalid": "The poll is invalid", + "the_poll_has_empty_option": "The poll has empty option" }, "content_warning": { "placeholder": "Write an accurate warning here..." @@ -417,7 +445,9 @@ "custom_emoji_picker": "Custom Emoji Picker", "enable_content_warning": "Enable Content Warning", "disable_content_warning": "Disable Content Warning", - "post_visibility_menu": "Post Visibility Menu" + "post_visibility_menu": "Post Visibility Menu", + "post_options": "Post Options", + "posting_as": "Posting as %s" }, "keyboard": { "discard_post": "Discard Post", @@ -433,15 +463,23 @@ "follows_you": "Follows You" }, "dashboard": { - "posts": "posts", - "following": "following", - "followers": "followers" + "my_posts": "posts", + "my_following": "following", + "my_followers": "followers", + "other_posts": "posts", + "other_following": "following", + "other_followers": "followers" }, "fields": { + "joined": "Joined", "add_row": "Add Row", "placeholder": { "label": "Label", "content": "Content" + }, + "verified": { + "short": "Verified on %s", + "long": "Ownership of this link was checked on %s" } }, "segmented_control": { @@ -707,6 +745,18 @@ }, "bookmark": { "title": "Bookmarks" + }, + "followed_tags": { + "title": "Followed Tags", + "header": { + "posts": "posts", + "participants": "participants", + "posts_today": "posts today" + }, + "actions": { + "follow": "Follow", + "unfollow": "Unfollow" + } } } } diff --git a/Localization/StringsConvertor/input/ru.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/ru.lproj/Localizable.stringsdict index afb29a6aa..d43386ad9 100644 --- a/Localization/StringsConvertor/input/ru.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/ru.lproj/Localizable.stringsdict @@ -15,11 +15,11 @@ one 1 unread notification few - %ld unread notification + %ld unread notifications many - %ld unread notification + %ld unread notifications other - %ld unread notification + %ld unread notifications a11y.plural.count.input_limit_exceeds @@ -62,6 +62,26 @@ %ld символа осталось + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ left + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 character + few + %ld characters + many + %ld characters + other + %ld characters + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/ru.lproj/app.json b/Localization/StringsConvertor/input/ru.lproj/app.json index 798cdb4c5..bb34e4c1c 100644 --- a/Localization/StringsConvertor/input/ru.lproj/app.json +++ b/Localization/StringsConvertor/input/ru.lproj/app.json @@ -51,6 +51,11 @@ "clean_cache": { "title": "Очистка кэша", "message": "Успешно очищено %s кэша." + }, + "translation_failed": { + "title": "Note", + "message": "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported.", + "button": "OK" } }, "controls": { @@ -74,10 +79,11 @@ "take_photo": "Сделать фото", "save_photo": "Сохранить изображение", "copy_photo": "Скопировать изображение", - "sign_in": "Войти", - "sign_up": "Зарегистрироваться", + "sign_in": "Log in", + "sign_up": "Create account", "see_more": "Ещё", "preview": "Предпросмотр", + "copy": "Copy", "share": "Поделиться", "share_user": "Поделиться %s", "share_post": "Поделиться постом", @@ -91,12 +97,16 @@ "block_domain": "Заблокировать %s", "unblock_domain": "Разблокировать %s", "settings": "Настройки", - "delete": "Удалить" + "delete": "Удалить", + "translate_post": { + "title": "Translate from %s", + "unknown_language": "Unknown" + } }, "tabs": { "home": "Главная", - "search": "Поиск", - "notification": "Уведомление", + "search_and_explore": "Search and Explore", + "notifications": "Notifications", "profile": "Профиль" }, "keyboard": { @@ -132,6 +142,8 @@ "sensitive_content": "Sensitive Content", "media_content_warning": "Нажмите в любом месте, чтобы показать", "tap_to_reveal": "Нажмите, чтобы показать", + "load_embed": "Load Embed", + "link_via_user": "%s via %s", "poll": { "vote": "Проголосовать", "closed": "Завершён" @@ -153,6 +165,7 @@ "show_image": "Показать изображение", "show_gif": "Показать GIF", "show_video_player": "Показать видеопроигрыватель", + "share_link_in_post": "Share Link in Post", "tap_then_hold_to_show_menu": "Нажмите и удерживайте, чтобы показать меню" }, "tag": { @@ -168,6 +181,12 @@ "private": "Only their followers can see this post.", "private_from_me": "Only my followers can see this post.", "direct": "Only mentioned user can see this post." + }, + "translation": { + "translated_from": "Translated from %s using %s", + "unknown_language": "Unknown", + "unknown_provider": "Unknown", + "show_original": "Shown Original" } }, "friendship": { @@ -218,10 +237,16 @@ "get_started": "Присоединиться", "log_in": "Вход" }, + "login": { + "title": "Welcome back", + "subtitle": "Log you in on the server you created your account on.", + "server_search_field": { + "placeholder": "Enter URL or search for your server" + } + }, "server_picker": { "title": "Выберите сервер,\nлюбой сервер.", - "subtitle": "Выберите сообщество на основе своих интересов, региона или общей тематики.", - "subtitle_extend": "Pick a server based on your interests, region, or a general purpose one. Each server is operated by an entirely independent organization or individual.", + "subtitle": "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers.", "button": { "category": { "all": "Все", @@ -248,8 +273,7 @@ "category": "КАТЕГОРИЯ" }, "input": { - "placeholder": "Найдите сервер или присоединитесь к своему...", - "search_servers_or_enter_url": "Поиск по серверам или ссылке" + "search_servers_or_enter_url": "Search communities or enter URL" }, "empty_state": { "finding_servers": "Ищем доступные сервера...", @@ -385,8 +409,10 @@ "description_video": "Опишите видео для людей с нарушениями зрения...", "load_failed": "Load Failed", "upload_failed": "Upload Failed", - "can_not_recognize_this_media_attachment": "Can not regonize this media attachment", - "attachment_too_large": "Attachment too large" + "can_not_recognize_this_media_attachment": "Can not recognize this media attachment", + "attachment_too_large": "Attachment too large", + "compressing_state": "Compressing...", + "server_processing_state": "Server Processing..." }, "poll": { "duration_time": "Продолжительность: %s", @@ -396,7 +422,9 @@ "one_day": "1 день", "three_days": "3 дня", "seven_days": "7 дней", - "option_number": "Вариант %ld" + "option_number": "Вариант %ld", + "the_poll_is_invalid": "The poll is invalid", + "the_poll_has_empty_option": "The poll has empty option" }, "content_warning": { "placeholder": "Напишите предупреждение здесь..." @@ -417,7 +445,9 @@ "custom_emoji_picker": "Меню пользовательских эмодзи", "enable_content_warning": "Добавить предупреждение о содержании", "disable_content_warning": "Убрать предупреждение о содержании", - "post_visibility_menu": "Меню видимости поста" + "post_visibility_menu": "Меню видимости поста", + "post_options": "Post Options", + "posting_as": "Posting as %s" }, "keyboard": { "discard_post": "Удалить пост", @@ -433,15 +463,23 @@ "follows_you": "Подписан(а) на вас" }, "dashboard": { - "posts": "посты", - "following": "подписки", - "followers": "подписчики" + "my_posts": "posts", + "my_following": "following", + "my_followers": "followers", + "other_posts": "posts", + "other_following": "following", + "other_followers": "followers" }, "fields": { + "joined": "Joined", "add_row": "Добавить строку", "placeholder": { "label": "Ярлык", "content": "Содержимое" + }, + "verified": { + "short": "Verified on %s", + "long": "Ownership of this link was checked on %s" } }, "segmented_control": { @@ -707,6 +745,18 @@ }, "bookmark": { "title": "Bookmarks" + }, + "followed_tags": { + "title": "Followed Tags", + "header": { + "posts": "posts", + "participants": "participants", + "posts_today": "posts today" + }, + "actions": { + "follow": "Follow", + "unfollow": "Unfollow" + } } } } diff --git a/Localization/StringsConvertor/input/si.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/si.lproj/Localizable.stringsdict index bdcae6ac9..788eb95fc 100644 --- a/Localization/StringsConvertor/input/si.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/si.lproj/Localizable.stringsdict @@ -15,7 +15,7 @@ one 1 unread notification other - %ld unread notification + %ld unread notifications a11y.plural.count.input_limit_exceeds @@ -50,6 +50,22 @@ %ld characters + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ left + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 character + other + %ld characters + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/si.lproj/app.json b/Localization/StringsConvertor/input/si.lproj/app.json index 816536440..364708892 100644 --- a/Localization/StringsConvertor/input/si.lproj/app.json +++ b/Localization/StringsConvertor/input/si.lproj/app.json @@ -51,6 +51,11 @@ "clean_cache": { "title": "Clean Cache", "message": "Successfully cleaned %s cache." + }, + "translation_failed": { + "title": "Note", + "message": "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported.", + "button": "OK" } }, "controls": { @@ -74,10 +79,11 @@ "take_photo": "Take Photo", "save_photo": "Save Photo", "copy_photo": "Copy Photo", - "sign_in": "පිවිසෙන්න", - "sign_up": "ලියාපදිංචිය", + "sign_in": "Log in", + "sign_up": "Create account", "see_more": "තව බලන්න", "preview": "පෙරදසුන", + "copy": "Copy", "share": "බෙදාගන්න", "share_user": "%s බෙදාගන්න", "share_post": "Share Post", @@ -91,12 +97,16 @@ "block_domain": "Block %s", "unblock_domain": "Unblock %s", "settings": "Settings", - "delete": "Delete" + "delete": "Delete", + "translate_post": { + "title": "Translate from %s", + "unknown_language": "Unknown" + } }, "tabs": { "home": "Home", - "search": "Search", - "notification": "Notification", + "search_and_explore": "Search and Explore", + "notifications": "Notifications", "profile": "Profile" }, "keyboard": { @@ -132,6 +142,8 @@ "sensitive_content": "Sensitive Content", "media_content_warning": "Tap anywhere to reveal", "tap_to_reveal": "Tap to reveal", + "load_embed": "Load Embed", + "link_via_user": "%s via %s", "poll": { "vote": "ඡන්දය", "closed": "වසා ඇත" @@ -153,6 +165,7 @@ "show_image": "Show image", "show_gif": "Show GIF", "show_video_player": "Show video player", + "share_link_in_post": "Share Link in Post", "tap_then_hold_to_show_menu": "Tap then hold to show menu" }, "tag": { @@ -168,6 +181,12 @@ "private": "Only their followers can see this post.", "private_from_me": "Only my followers can see this post.", "direct": "Only mentioned user can see this post." + }, + "translation": { + "translated_from": "Translated from %s using %s", + "unknown_language": "Unknown", + "unknown_provider": "Unknown", + "show_original": "Shown Original" } }, "friendship": { @@ -218,10 +237,16 @@ "get_started": "පටන් ගන්න", "log_in": "පිවිසෙන්න" }, + "login": { + "title": "Welcome back", + "subtitle": "Log you in on the server you created your account on.", + "server_search_field": { + "placeholder": "Enter URL or search for your server" + } + }, "server_picker": { "title": "Mastodon is made of users in different servers.", - "subtitle": "Pick a server based on your interests, region, or a general purpose one.", - "subtitle_extend": "Pick a server based on your interests, region, or a general purpose one. Each server is operated by an entirely independent organization or individual.", + "subtitle": "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers.", "button": { "category": { "all": "සියල්ල", @@ -248,8 +273,7 @@ "category": "CATEGORY" }, "input": { - "placeholder": "Search servers", - "search_servers_or_enter_url": "Search servers or enter URL" + "search_servers_or_enter_url": "Search communities or enter URL" }, "empty_state": { "finding_servers": "Finding available servers...", @@ -385,8 +409,10 @@ "description_video": "Describe the video for the visually-impaired...", "load_failed": "Load Failed", "upload_failed": "Upload Failed", - "can_not_recognize_this_media_attachment": "Can not regonize this media attachment", - "attachment_too_large": "Attachment too large" + "can_not_recognize_this_media_attachment": "Can not recognize this media attachment", + "attachment_too_large": "Attachment too large", + "compressing_state": "Compressing...", + "server_processing_state": "Server Processing..." }, "poll": { "duration_time": "Duration: %s", @@ -396,7 +422,9 @@ "one_day": "1 Day", "three_days": "3 Days", "seven_days": "7 Days", - "option_number": "Option %ld" + "option_number": "Option %ld", + "the_poll_is_invalid": "The poll is invalid", + "the_poll_has_empty_option": "The poll has empty option" }, "content_warning": { "placeholder": "Write an accurate warning here..." @@ -417,7 +445,9 @@ "custom_emoji_picker": "Custom Emoji Picker", "enable_content_warning": "Enable Content Warning", "disable_content_warning": "Disable Content Warning", - "post_visibility_menu": "Post Visibility Menu" + "post_visibility_menu": "Post Visibility Menu", + "post_options": "Post Options", + "posting_as": "Posting as %s" }, "keyboard": { "discard_post": "Discard Post", @@ -433,15 +463,23 @@ "follows_you": "Follows You" }, "dashboard": { - "posts": "posts", - "following": "following", - "followers": "followers" + "my_posts": "posts", + "my_following": "following", + "my_followers": "followers", + "other_posts": "posts", + "other_following": "following", + "other_followers": "followers" }, "fields": { + "joined": "Joined", "add_row": "Add Row", "placeholder": { "label": "නම්පත", "content": "අන්තර්ගතය" + }, + "verified": { + "short": "Verified on %s", + "long": "Ownership of this link was checked on %s" } }, "segmented_control": { @@ -707,6 +745,18 @@ }, "bookmark": { "title": "Bookmarks" + }, + "followed_tags": { + "title": "Followed Tags", + "header": { + "posts": "posts", + "participants": "participants", + "posts_today": "posts today" + }, + "actions": { + "follow": "Follow", + "unfollow": "Unfollow" + } } } } diff --git a/Localization/StringsConvertor/input/sl.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/sl.lproj/Localizable.stringsdict index 8f0bcb42b..87cc42142 100644 --- a/Localization/StringsConvertor/input/sl.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/sl.lproj/Localizable.stringsdict @@ -62,6 +62,26 @@ %ld znakov + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + preostaja %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld znak + two + %ld znaka + few + %ld znaki + other + %ld znakov + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/sl.lproj/app.json b/Localization/StringsConvertor/input/sl.lproj/app.json index 37b62a45d..3ce4e2daa 100644 --- a/Localization/StringsConvertor/input/sl.lproj/app.json +++ b/Localization/StringsConvertor/input/sl.lproj/app.json @@ -51,6 +51,11 @@ "clean_cache": { "title": "Počisti predpomnilnik", "message": "Uspešno počiščem predpomnilnik %s." + }, + "translation_failed": { + "title": "Opomba", + "message": "Prevod je spodletel. Morda skrbnik ni omogočil prevajanja na tem strežniku ali pa strežnik teče na starejši različici Masotodona, na kateri prevajanje še ni podprto.", + "button": "V redu" } }, "controls": { @@ -75,9 +80,10 @@ "save_photo": "Shrani fotografijo", "copy_photo": "Kopiraj fotografijo", "sign_in": "Prijava", - "sign_up": "Registracija", + "sign_up": "Ustvari račun", "see_more": "Pokaži več", "preview": "Predogled", + "copy": "Kopiraj", "share": "Deli", "share_user": "Deli %s", "share_post": "Deli objavo", @@ -91,12 +97,16 @@ "block_domain": "Blokiraj %s", "unblock_domain": "Odblokiraj %s", "settings": "Nastavitve", - "delete": "Izbriši" + "delete": "Izbriši", + "translate_post": { + "title": "Prevedi iz: %s", + "unknown_language": "Neznano" + } }, "tabs": { "home": "Domov", - "search": "Iskanje", - "notification": "Obvestilo", + "search_and_explore": "Poišči in razišči", + "notifications": "Obvestila", "profile": "Profil" }, "keyboard": { @@ -132,6 +142,8 @@ "sensitive_content": "Občutljiva vsebina", "media_content_warning": "Tapnite kamorkoli, da razkrijete", "tap_to_reveal": "Tapnite za razkritje", + "load_embed": "Naloži vdelano", + "link_via_user": "%s prek %s", "poll": { "vote": "Glasuj", "closed": "Zaprto" @@ -153,6 +165,7 @@ "show_image": "Pokaži sliko", "show_gif": "Pokaži GIF", "show_video_player": "Pokaži predvajalnik", + "share_link_in_post": "Deli povezavo v objavi", "tap_then_hold_to_show_menu": "Tapnite, nato držite, da se pojavi meni" }, "tag": { @@ -168,6 +181,12 @@ "private": "Samo sledilci osebe lahko vidijo to objavo.", "private_from_me": "Samo moji sledilci lahko vidijo to objavo.", "direct": "Samo omenjeni uporabnik lahko vidi to objavo." + }, + "translation": { + "translated_from": "Prevedeno iz %s s pomočjo %s", + "unknown_language": "Neznano", + "unknown_provider": "Neznano", + "show_original": "Pokaži izvirnik" } }, "friendship": { @@ -218,10 +237,16 @@ "get_started": "Začnite", "log_in": "Prijava" }, + "login": { + "title": "Dobrodošli nazaj", + "subtitle": "Prijavite se na strežniku, na katerem ste ustvarili račun.", + "server_search_field": { + "placeholder": "Vnesite URL ali poiščite svoj strežnik" + } + }, "server_picker": { "title": "Mastodon tvorijo uporabniki z različnih strežnikov.", - "subtitle": "Strežnik izberite glede na svoje interese, regijo ali pa izberite splošnega.", - "subtitle_extend": "Strežnik izberite glede na svoje interese, regijo ali pa izberite splošnega. Z vsakim strežnikom upravlja povsem neodvisna organizacija ali posameznik.", + "subtitle": "Strežnik izberite glede na svojo regijo, zanimanje ali pa kar splošno. Še vedno lahko klepetate s komer koli na Mastodonu, ne glede na strežnik.", "button": { "category": { "all": "Vse", @@ -248,8 +273,7 @@ "category": "KATEGORIJA" }, "input": { - "placeholder": "Išči strežnike", - "search_servers_or_enter_url": "Iščite strežnike ali vnesite URL" + "search_servers_or_enter_url": "Iščite po skupnostih ali vnesite URL" }, "empty_state": { "finding_servers": "Iskanje razpoložljivih strežnikov ...", @@ -383,10 +407,12 @@ "attachment_broken": "To %s je okvarjeno in ga ni\nmožno naložiti v Mastodon.", "description_photo": "Opiši fotografijo za slabovidne in osebe z okvaro vida ...", "description_video": "Opiši video za slabovidne in osebe z okvaro vida ...", - "load_failed": "Load Failed", - "upload_failed": "Upload Failed", - "can_not_recognize_this_media_attachment": "Can not regonize this media attachment", - "attachment_too_large": "Attachment too large" + "load_failed": "Nalaganje ni uspelo", + "upload_failed": "Nalaganje na strežnik ni uspelo", + "can_not_recognize_this_media_attachment": "Te medijske priponke ni mogoče prepoznati", + "attachment_too_large": "Priponka je prevelika", + "compressing_state": "Stiskanje ...", + "server_processing_state": "Obdelovanje na strežniku ..." }, "poll": { "duration_time": "Trajanje: %s", @@ -396,7 +422,9 @@ "one_day": "1 dan", "three_days": "3 dni", "seven_days": "7 dni", - "option_number": "Možnost %ld" + "option_number": "Možnost %ld", + "the_poll_is_invalid": "Anketa je neveljavna", + "the_poll_has_empty_option": "Anketa ima prazno izbiro" }, "content_warning": { "placeholder": "Tukaj zapišite opozorilo ..." @@ -417,7 +445,9 @@ "custom_emoji_picker": "Izbirnik čustvenčkov po meri", "enable_content_warning": "Omogoči opozorilo o vsebini", "disable_content_warning": "Onemogoči opozorilo o vsebini", - "post_visibility_menu": "Meni vidnosti objave" + "post_visibility_menu": "Meni vidnosti objave", + "post_options": "Možnosti objave", + "posting_as": "Objavljate kot %s" }, "keyboard": { "discard_post": "Opusti objavo", @@ -433,15 +463,23 @@ "follows_you": "Vam sledi" }, "dashboard": { - "posts": "Objave", - "following": "sledi", - "followers": "sledilcev" + "my_posts": "objav", + "my_following": "sledi", + "my_followers": "sledilcev", + "other_posts": "objav", + "other_following": "sledi", + "other_followers": "sledilcev" }, "fields": { + "joined": "Pridružen_a", "add_row": "Dodaj vrstico", "placeholder": { "label": "Oznaka", "content": "Vsebina" + }, + "verified": { + "short": "Preverjeno %s", + "long": "Lastništvo te povezave je bilo preverjeno %s" } }, "segmented_control": { @@ -707,6 +745,18 @@ }, "bookmark": { "title": "Zaznamki" + }, + "followed_tags": { + "title": "Sledene značke", + "header": { + "posts": "objav", + "participants": "udeležencev", + "posts_today": "objav danes" + }, + "actions": { + "follow": "Sledi", + "unfollow": "Prenehaj slediti" + } } } } diff --git a/Localization/StringsConvertor/input/sv.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/sv.lproj/Localizable.stringsdict index c7317903d..43a0ff8e4 100644 --- a/Localization/StringsConvertor/input/sv.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/sv.lproj/Localizable.stringsdict @@ -50,6 +50,22 @@ %ld tecken + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ kvar + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld tecken + other + %ld tecken + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey @@ -264,7 +280,7 @@ NSStringFormatValueTypeKey ld one - %ld år kvar + 1 år kvar other %ld år kvar @@ -280,7 +296,7 @@ NSStringFormatValueTypeKey ld one - %ld månad kvar + 1 månad kvar other %ld månader kvar @@ -296,7 +312,7 @@ NSStringFormatValueTypeKey ld one - %ld dag kvar + 1 dag kvar other %ld dagar kvar @@ -344,7 +360,7 @@ NSStringFormatValueTypeKey ld one - %ld sekund kvar + 1 sekund kvar other %ld sekunder kvar @@ -392,7 +408,7 @@ NSStringFormatValueTypeKey ld one - %ldd sedan + 1d sedan other %ldd sedan @@ -408,7 +424,7 @@ NSStringFormatValueTypeKey ld one - %ldt sedan + 1t sedan other %ldt sedan diff --git a/Localization/StringsConvertor/input/sv.lproj/app.json b/Localization/StringsConvertor/input/sv.lproj/app.json index c740609c9..400758cd9 100644 --- a/Localization/StringsConvertor/input/sv.lproj/app.json +++ b/Localization/StringsConvertor/input/sv.lproj/app.json @@ -51,6 +51,11 @@ "clean_cache": { "title": "Rensa cache", "message": "Rensade %s cache." + }, + "translation_failed": { + "title": "Anteckning", + "message": "Översättningen misslyckades. Det kan hända att administratören inte har aktiverat översättningar på den här servern eller att servern kör en äldre version av Mastodon som inte har stöd för översättningar ännu.", + "button": "OK" } }, "controls": { @@ -75,9 +80,10 @@ "save_photo": "Spara foto", "copy_photo": "Kopiera foto", "sign_in": "Logga in", - "sign_up": "Registrera dig", + "sign_up": "Skapa konto", "see_more": "Visa mer", "preview": "Förhandsvisa", + "copy": "Kopiera", "share": "Dela", "share_user": "Dela %s", "share_post": "Dela inlägg", @@ -91,12 +97,16 @@ "block_domain": "Blockera %s", "unblock_domain": "Avblockera %s", "settings": "Inställningar", - "delete": "Radera" + "delete": "Radera", + "translate_post": { + "title": "Översätt från %s", + "unknown_language": "Okänt" + } }, "tabs": { "home": "Hem", - "search": "Sök", - "notification": "Notis", + "search_and_explore": "Sök och utforska", + "notifications": "Notiser", "profile": "Profil" }, "keyboard": { @@ -132,6 +142,8 @@ "sensitive_content": "Känsligt innehåll", "media_content_warning": "Tryck var som helst för att visa", "tap_to_reveal": "Tryck för att visa", + "load_embed": "Ladda inbäddning", + "link_via_user": "%s via %s", "poll": { "vote": "Rösta", "closed": "Stängd" @@ -153,6 +165,7 @@ "show_image": "Visa bild", "show_gif": "Visa GIF", "show_video_player": "Visa videospelare", + "share_link_in_post": "Dela länk i inlägg", "tap_then_hold_to_show_menu": "Tryck och håll ned för att visa menyn" }, "tag": { @@ -168,6 +181,12 @@ "private": "Endast deras följare kan se detta inlägg.", "private_from_me": "Bara mina följare kan se det här inlägget.", "direct": "Endast omnämnda användare kan se detta inlägg." + }, + "translation": { + "translated_from": "Översatt från %s med %s", + "unknown_language": "Okänt", + "unknown_provider": "Okänd", + "show_original": "Visa original" } }, "friendship": { @@ -218,10 +237,16 @@ "get_started": "Kom igång", "log_in": "Logga in" }, + "login": { + "title": "Välkommen tillbaka", + "subtitle": "Logga in på servern där du skapade ditt konto.", + "server_search_field": { + "placeholder": "Ange URL eller sök efter din server" + } + }, "server_picker": { "title": "Mastodon utgörs av användare på olika servrar.", - "subtitle": "Välj en server baserat på dina intressen, region eller ett allmänt syfte.", - "subtitle_extend": "Välj en server baserat på dina intressen, region eller ett allmänt syfte. Varje server drivs av en helt oberoende organisation eller individ.", + "subtitle": "Välj en server baserat på dina intressen, region eller en allmän server. Du kan fortfarande nå alla, oavsett server.", "button": { "category": { "all": "Alla", @@ -248,8 +273,7 @@ "category": "KATEGORI" }, "input": { - "placeholder": "Sök gemenskaper", - "search_servers_or_enter_url": "Sök servrar eller ange URL" + "search_servers_or_enter_url": "Sök gemenskaper eller ange URL" }, "empty_state": { "finding_servers": "Söker tillgängliga servrar...", @@ -386,7 +410,9 @@ "load_failed": "Det gick inte att läsa in", "upload_failed": "Uppladdning misslyckades", "can_not_recognize_this_media_attachment": "Känner inte igen mediebilagan", - "attachment_too_large": "Bilagan är för stor" + "attachment_too_large": "Bilagan är för stor", + "compressing_state": "Komprimerar...", + "server_processing_state": "Behandlas av servern..." }, "poll": { "duration_time": "Längd: %s", @@ -396,7 +422,9 @@ "one_day": "1 dag", "three_days": "3 dagar", "seven_days": "7 dagar", - "option_number": "Alternativ %ld" + "option_number": "Alternativ %ld", + "the_poll_is_invalid": "Undersökningen är ogiltig", + "the_poll_has_empty_option": "Undersökningen har ett tomt alternativ" }, "content_warning": { "placeholder": "Skriv en noggrann varning här..." @@ -417,7 +445,9 @@ "custom_emoji_picker": "Anpassad emoji-väljare", "enable_content_warning": "Aktivera innehållsvarning", "disable_content_warning": "Inaktivera innehållsvarning", - "post_visibility_menu": "Inläggssynlighetsmeny" + "post_visibility_menu": "Inläggssynlighetsmeny", + "post_options": "Inläggsalternativ", + "posting_as": "Postar som %s" }, "keyboard": { "discard_post": "Släng inlägget", @@ -433,15 +463,23 @@ "follows_you": "Följer dig" }, "dashboard": { - "posts": "inlägg", - "following": "följer", - "followers": "följare" + "my_posts": "inlägg", + "my_following": "följer", + "my_followers": "följare", + "other_posts": "inlägg", + "other_following": "följer", + "other_followers": "följare" }, "fields": { + "joined": "Gick med", "add_row": "Lägg till rad", "placeholder": { "label": "Etikett", "content": "Innehåll" + }, + "verified": { + "short": "Verifierad på %s", + "long": "Ägarskap för denna länk kontrollerades den %s" } }, "segmented_control": { @@ -707,6 +745,18 @@ }, "bookmark": { "title": "Bokmärken" + }, + "followed_tags": { + "title": "Följda hashtaggar", + "header": { + "posts": "inlägg", + "participants": "deltagare", + "posts_today": "inlägg idag" + }, + "actions": { + "follow": "Följ", + "unfollow": "Avfölj" + } } } } diff --git a/Localization/StringsConvertor/input/th.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/th.lproj/Localizable.stringsdict index 897d07eca..f25561ad6 100644 --- a/Localization/StringsConvertor/input/th.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/th.lproj/Localizable.stringsdict @@ -44,6 +44,20 @@ %ld ตัวอักษร + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + เหลืออีก %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld ตัวอักษร + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/th.lproj/app.json b/Localization/StringsConvertor/input/th.lproj/app.json index 9b4316025..6b3421d47 100644 --- a/Localization/StringsConvertor/input/th.lproj/app.json +++ b/Localization/StringsConvertor/input/th.lproj/app.json @@ -51,6 +51,11 @@ "clean_cache": { "title": "ล้างแคช", "message": "ล้างแคช %s สำเร็จ" + }, + "translation_failed": { + "title": "หมายเหตุ", + "message": "การแปลล้มเหลว บางทีผู้ดูแลอาจไม่ได้เปิดใช้งานการแปลในเซิร์ฟเวอร์นี้หรือเซิร์ฟเวอร์นี้กำลังใช้ Mastodon รุ่นเก่ากว่าที่ยังไม่รองรับการแปล", + "button": "ตกลง" } }, "controls": { @@ -74,10 +79,11 @@ "take_photo": "ถ่ายรูป", "save_photo": "บันทึกรูปภาพ", "copy_photo": "คัดลอกรูปภาพ", - "sign_in": "ลงชื่อเข้า", - "sign_up": "ลงทะเบียน", + "sign_in": "เข้าสู่ระบบ", + "sign_up": "สร้างบัญชี", "see_more": "ดูเพิ่มเติม", "preview": "แสดงตัวอย่าง", + "copy": "คัดลอก", "share": "แบ่งปัน", "share_user": "แบ่งปัน %s", "share_post": "แบ่งปันโพสต์", @@ -91,12 +97,16 @@ "block_domain": "ปิดกั้น %s", "unblock_domain": "เลิกปิดกั้น %s", "settings": "การตั้งค่า", - "delete": "ลบ" + "delete": "ลบ", + "translate_post": { + "title": "แปลจาก %s", + "unknown_language": "ไม่รู้จัก" + } }, "tabs": { "home": "หน้าแรก", - "search": "ค้นหา", - "notification": "การแจ้งเตือน", + "search_and_explore": "ค้นหาและสำรวจ", + "notifications": "การแจ้งเตือน", "profile": "โปรไฟล์" }, "keyboard": { @@ -132,6 +142,8 @@ "sensitive_content": "เนื้อหาที่ละเอียดอ่อน", "media_content_warning": "แตะที่ใดก็ตามเพื่อเปิดเผย", "tap_to_reveal": "แตะเพื่อเปิดเผย", + "load_embed": "โหลดที่ฝังไว้", + "link_via_user": "%s ผ่าน %s", "poll": { "vote": "ลงคะแนน", "closed": "ปิดแล้ว" @@ -153,6 +165,7 @@ "show_image": "แสดงภาพ", "show_gif": "แสดง GIF", "show_video_player": "แสดงตัวเล่นวิดีโอ", + "share_link_in_post": "แบ่งปันลิงก์ในโพสต์", "tap_then_hold_to_show_menu": "แตะค้างไว้เพื่อแสดงเมนู" }, "tag": { @@ -168,6 +181,12 @@ "private": "เฉพาะผู้ติดตามของเขาเท่านั้นที่สามารถเห็นโพสต์นี้", "private_from_me": "เฉพาะผู้ติดตามของฉันเท่านั้นที่สามารถเห็นโพสต์นี้", "direct": "เฉพาะผู้ใช้ที่กล่าวถึงเท่านั้นที่สามารถเห็นโพสต์นี้" + }, + "translation": { + "translated_from": "แปลจาก %s โดยใช้ %s", + "unknown_language": "ไม่รู้จัก", + "unknown_provider": "ไม่รู้จัก", + "show_original": "แสดงดั้งเดิมอยู่" } }, "friendship": { @@ -218,10 +237,16 @@ "get_started": "เริ่มต้นใช้งาน", "log_in": "เข้าสู่ระบบ" }, + "login": { + "title": "ยินดีต้อนรับกลับมา", + "subtitle": "นำคุณเข้าสู่ระบบในเซิร์ฟเวอร์ที่คุณได้สร้างบัญชีของคุณไว้ใน", + "server_search_field": { + "placeholder": "ป้อน URL หรือค้นหาสำหรับเซิร์ฟเวอร์ของคุณ" + } + }, "server_picker": { "title": "Mastodon ประกอบด้วยผู้ใช้ในเซิร์ฟเวอร์ต่าง ๆ", - "subtitle": "เลือกเซิร์ฟเวอร์ตามความสนใจ, ภูมิภาค หรือวัตถุประสงค์ทั่วไปของคุณ", - "subtitle_extend": "เลือกเซิร์ฟเวอร์ตามความสนใจ, ภูมิภาค หรือวัตถุประสงค์ทั่วไปของคุณ แต่ละเซิร์ฟเวอร์ได้รับการดำเนินงานโดยองค์กรหรือบุคคลที่เป็นอิสระโดยสิ้นเชิง", + "subtitle": "เลือกเซิร์ฟเวอร์ตามภูมิภาค, ความสนใจ หรือวัตถุประสงค์ทั่วไปของคุณ คุณยังคงสามารถแชทกับใครก็ตามใน Mastodon โดยไม่คำนึงถึงเซิร์ฟเวอร์ของคุณ", "button": { "category": { "all": "ทั้งหมด", @@ -248,8 +273,7 @@ "category": "หมวดหมู่" }, "input": { - "placeholder": "ค้นหาเซิร์ฟเวอร์", - "search_servers_or_enter_url": "ค้นหาเซิร์ฟเวอร์หรือป้อน URL" + "search_servers_or_enter_url": "ค้นหาชุมชนหรือป้อน URL" }, "empty_state": { "finding_servers": "กำลังค้นหาเซิร์ฟเวอร์ที่พร้อมใช้งาน...", @@ -299,7 +323,7 @@ }, "reason": { "blocked": "%s มีผู้ให้บริการอีเมลที่ไม่ได้รับอนุญาต", - "unreachable": "ดูเหมือนว่า %s จะไม่มีอยู่", + "unreachable": "ดูเหมือนว่าจะไม่มี %s อยู่", "taken": "%s ถูกใช้งานแล้ว", "reserved": "%s เป็นคำสงวน", "accepted": "ต้องยอมรับ %s", @@ -337,12 +361,12 @@ }, "dont_receive_email": { "title": "ตรวจสอบอีเมลของคุณ", - "description": "หากคุณยังไม่ได้รับอีเมล ตรวจสอบว่าที่อยู่อีเมลของคุณถูกต้อง รวมถึงโฟลเดอร์อีเมลขยะของคุณ", + "description": "ตรวจสอบว่าที่อยู่อีเมลของคุณถูกต้องเช่นเดียวกับโฟลเดอร์อีเมลขยะหากคุณยังไม่ได้ทำ", "resend_email": "ส่งอีเมลใหม่" }, "open_email_app": { "title": "ตรวจสอบกล่องขาเข้าของคุณ", - "description": "เราเพิ่งส่งอีเมลหาคุณ หากคุณยังไม่ได้รับอีเมล โปรดตรวจสอบโฟลเดอร์อีเมลขยะ", + "description": "เราเพิ่งส่งอีเมลถึงคุณ ตรวจสอบโฟลเดอร์อีเมลขยะของคุณหากคุณยังไม่ได้ทำ", "mail": "จดหมาย", "open_email_client": "เปิดไคลเอ็นต์อีเมล" } @@ -385,8 +409,10 @@ "description_video": "อธิบายวิดีโอสำหรับผู้บกพร่องทางการมองเห็น...", "load_failed": "การโหลดล้มเหลว", "upload_failed": "การอัปโหลดล้มเหลว", - "can_not_recognize_this_media_attachment": "Can not regonize this media attachment", - "attachment_too_large": "ไฟล์แนบใหญ่เกินไป" + "can_not_recognize_this_media_attachment": "ไม่สามารถระบุไฟล์แนบสื่อนี้", + "attachment_too_large": "ไฟล์แนบใหญ่เกินไป", + "compressing_state": "กำลังบีบอัด...", + "server_processing_state": "เซิร์ฟเวอร์กำลังประมวลผล..." }, "poll": { "duration_time": "ระยะเวลา: %s", @@ -396,7 +422,9 @@ "one_day": "1 วัน", "three_days": "3 วัน", "seven_days": "7 วัน", - "option_number": "ตัวเลือก %ld" + "option_number": "ตัวเลือก %ld", + "the_poll_is_invalid": "การสำรวจความคิดเห็นไม่ถูกต้อง", + "the_poll_has_empty_option": "การสำรวจความคิดเห็นมีตัวเลือกที่ว่างเปล่า" }, "content_warning": { "placeholder": "เขียนคำเตือนที่ถูกต้องที่นี่..." @@ -417,7 +445,9 @@ "custom_emoji_picker": "ตัวเลือกอีโมจิที่กำหนดเอง", "enable_content_warning": "เปิดใช้งานคำเตือนเนื้อหา", "disable_content_warning": "ปิดใช้งานคำเตือนเนื้อหา", - "post_visibility_menu": "เมนูการมองเห็นโพสต์" + "post_visibility_menu": "เมนูการมองเห็นโพสต์", + "post_options": "ตัวเลือกโพสต์", + "posting_as": "กำลังโพสต์เป็น %s" }, "keyboard": { "discard_post": "ละทิ้งโพสต์", @@ -433,15 +463,23 @@ "follows_you": "ติดตามคุณ" }, "dashboard": { - "posts": "โพสต์", - "following": "กำลังติดตาม", - "followers": "ผู้ติดตาม" + "my_posts": "โพสต์", + "my_following": "กำลังติดตาม", + "my_followers": "ผู้ติดตาม", + "other_posts": "โพสต์", + "other_following": "กำลังติดตาม", + "other_followers": "ผู้ติดตาม" }, "fields": { + "joined": "เข้าร่วมเมื่อ", "add_row": "เพิ่มแถว", "placeholder": { "label": "ป้ายชื่อ", "content": "เนื้อหา" + }, + "verified": { + "short": "ตรวจสอบเมื่อ %s", + "long": "ตรวจสอบความเป็นเจ้าของของลิงก์นี้เมื่อ %s" } }, "segmented_control": { @@ -707,6 +745,18 @@ }, "bookmark": { "title": "ที่คั่นหน้า" + }, + "followed_tags": { + "title": "แท็กที่ติดตาม", + "header": { + "posts": "โพสต์", + "participants": "ผู้เข้าร่วม", + "posts_today": "โพสต์วันนี้" + }, + "actions": { + "follow": "ติดตาม", + "unfollow": "เลิกติดตาม" + } } } } diff --git a/Localization/StringsConvertor/input/tr.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/tr.lproj/Localizable.stringsdict index 29df92c2b..13552b607 100644 --- a/Localization/StringsConvertor/input/tr.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/tr.lproj/Localizable.stringsdict @@ -50,6 +50,22 @@ %ld karakter + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ kaldı + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 karakter + other + %ld karakter + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey @@ -72,9 +88,9 @@ NSStringFormatValueTypeKey ld one - Followed by %1$@, and another mutual + %1$@ ve bir ortak kişi tarafından takip edildi other - Followed by %1$@, and %ld mutuals + %1$@ ve %ld ortak kişi tarafından takip edildi plural.count.metric_formatted.post @@ -104,9 +120,9 @@ NSStringFormatValueTypeKey ld one - 1 media + 1 medya other - %ld media + %ld medya plural.count.post diff --git a/Localization/StringsConvertor/input/tr.lproj/app.json b/Localization/StringsConvertor/input/tr.lproj/app.json index 2abb92845..c22c6a3f9 100644 --- a/Localization/StringsConvertor/input/tr.lproj/app.json +++ b/Localization/StringsConvertor/input/tr.lproj/app.json @@ -51,6 +51,11 @@ "clean_cache": { "title": "Önbelleği Temizle", "message": "%s boyutunda önbellek temizlendi." + }, + "translation_failed": { + "title": "Note", + "message": "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported.", + "button": "OK" } }, "controls": { @@ -75,9 +80,10 @@ "save_photo": "Fotoğrafı Kaydet", "copy_photo": "Fotoğrafı Kopyala", "sign_in": "Giriş Yap", - "sign_up": "Kaydol", + "sign_up": "Hesap oluştur", "see_more": "Daha Fazla Gör", "preview": "Önizleme", + "copy": "Copy", "share": "Paylaş", "share_user": "%s ile paylaş", "share_post": "Gönderiyi Paylaş", @@ -91,12 +97,16 @@ "block_domain": "%s kişisini engelle", "unblock_domain": "%s kişisinin engelini kaldır", "settings": "Ayarlar", - "delete": "Sil" + "delete": "Sil", + "translate_post": { + "title": "Translate from %s", + "unknown_language": "Unknown" + } }, "tabs": { "home": "Ana Sayfa", - "search": "Arama", - "notification": "Bildirimler", + "search_and_explore": "Ara ve Keşfet", + "notifications": "Bildirimler", "profile": "Profil" }, "keyboard": { @@ -132,15 +142,17 @@ "sensitive_content": "Hassas İçerik", "media_content_warning": "Göstermek için herhangi bir yere basın", "tap_to_reveal": "Göstermek için basın", + "load_embed": "Load Embed", + "link_via_user": "%s via %s", "poll": { "vote": "Oy ver", "closed": "Kapandı" }, "meta_entity": { - "url": "Link: %s", - "hashtag": "Hashtag: %s", - "mention": "Show Profile: %s", - "email": "Email address: %s" + "url": "Bağlantı: %s", + "hashtag": "Etiket: %s", + "mention": "Profili Göster: %s", + "email": "E-posta adresi: %s" }, "actions": { "reply": "Yanıtla", @@ -153,6 +165,7 @@ "show_image": "Görüntüyü göster", "show_gif": "GIF'i göster", "show_video_player": "Video oynatıcıyı göster", + "share_link_in_post": "Share Link in Post", "tap_then_hold_to_show_menu": "Menüyü göstermek için dokunun ve basılı tutun" }, "tag": { @@ -168,6 +181,12 @@ "private": "Sadece gönderi sahibinin takipçileri bu gönderiyi görebilir.", "private_from_me": "Sadece benim takipçilerim bu gönderiyi görebilir.", "direct": "Sadece bahsedilen kullanıcı bu gönderiyi görebilir." + }, + "translation": { + "translated_from": "Translated from %s using %s", + "unknown_language": "Unknown", + "unknown_provider": "Unknown", + "show_original": "Shown Original" } }, "friendship": { @@ -187,8 +206,8 @@ "unmute_user": "Sesini aç %s", "muted": "Susturuldu", "edit_info": "Bilgiyi Düzenle", - "show_reblogs": "Show Reblogs", - "hide_reblogs": "Hide Reblogs" + "show_reblogs": "Yeniden Paylaşımları Göster", + "hide_reblogs": "Yeniden Paylaşımları Gizle" }, "timeline": { "filtered": "Filtrelenmiş", @@ -218,10 +237,16 @@ "get_started": "Başlayın", "log_in": "Oturum Aç" }, + "login": { + "title": "Tekrar hoş geldin", + "subtitle": "Hesabını oluşturduğun sunucuya giriş yap.", + "server_search_field": { + "placeholder": "Bir URL girin ya da sunucunuzu arayın" + } + }, "server_picker": { "title": "Mastodon, farklı topluluklardaki kullanıcılardan oluşur.", - "subtitle": "İlgi alanlarınıza, bölgenize veya genel amaçlı bir topluluk seçin.", - "subtitle_extend": "İlgi alanlarınıza, bölgenize veya genel amaçlı bir topluluk seçin. Her topluluk tamamen bağımsız bir kuruluş veya kişi tarafından işletilmektedir.", + "subtitle": "Bölgenize dayalı, ilginize dayalı ya da genel amaçlı bir sunucu seçin. Hangi sunucuda olduğunuz fark etmeksizin Mastodon'daki herkes ile konuşabilirsiniz.", "button": { "category": { "all": "Tümü", @@ -248,8 +273,7 @@ "category": "KATEGORİ" }, "input": { - "placeholder": "Toplulukları ara", - "search_servers_or_enter_url": "Sunucuları ara ya da bir bağlantı gir" + "search_servers_or_enter_url": "Topluluklar arayın ya da bir URL girin" }, "empty_state": { "finding_servers": "Mevcut sunucular aranıyor...", @@ -383,10 +407,12 @@ "attachment_broken": "Bu %s bozuk ve Mastodon'a\nyüklenemiyor.", "description_photo": "Görme engelliler için fotoğrafı tarif edin...", "description_video": "Görme engelliler için videoyu tarif edin...", - "load_failed": "Load Failed", - "upload_failed": "Upload Failed", - "can_not_recognize_this_media_attachment": "Can not regonize this media attachment", - "attachment_too_large": "Attachment too large" + "load_failed": "Yükleme Başarısız", + "upload_failed": "Yükleme Başarısız", + "can_not_recognize_this_media_attachment": "Ekteki medya uzantısı görüntülenemiyor", + "attachment_too_large": "Ek boyutu çok büyük", + "compressing_state": "Sıkıştırılıyor...", + "server_processing_state": "Sunucu İşliyor..." }, "poll": { "duration_time": "Süre: %s", @@ -396,7 +422,9 @@ "one_day": "1 Gün", "three_days": "3 Gün", "seven_days": "7 Gün", - "option_number": "Seçenek %ld" + "option_number": "Seçenek %ld", + "the_poll_is_invalid": "Anket geçersiz", + "the_poll_has_empty_option": "The poll has empty option" }, "content_warning": { "placeholder": "Buraya kesin bir uyarı yazın..." @@ -417,7 +445,9 @@ "custom_emoji_picker": "Özel Emoji Seçici", "enable_content_warning": "İçerik Uyarısını Etkinleştir", "disable_content_warning": "İçerik Uyarısını Kapat", - "post_visibility_menu": "Gönderi Görünürlüğü Menüsü" + "post_visibility_menu": "Gönderi Görünürlüğü Menüsü", + "post_options": "Gönderi Seçenekleri", + "posting_as": "%s olarak paylaşılıyor" }, "keyboard": { "discard_post": "Gönderiyi İptal Et", @@ -433,15 +463,23 @@ "follows_you": "Seni takip ediyor" }, "dashboard": { - "posts": "gönderiler", - "following": "takip ediliyor", - "followers": "takipçi" + "my_posts": "posts", + "my_following": "following", + "my_followers": "followers", + "other_posts": "posts", + "other_following": "following", + "other_followers": "followers" }, "fields": { + "joined": "Joined", "add_row": "Satır Ekle", "placeholder": { "label": "Etiket", "content": "İçerik" + }, + "verified": { + "short": "%s tarafında onaylı", + "long": "%s adresinin sahipliği kontrol edilmiş" } }, "segmented_control": { @@ -469,12 +507,12 @@ "message": "%s engellemeyi kaldırmayı onaylayın" }, "confirm_show_reblogs": { - "title": "Show Reblogs", - "message": "Confirm to show reblogs" + "title": "Yeniden Paylaşımları Göster", + "message": "Yeniden paylaşımları göstermeyi onayla" }, "confirm_hide_reblogs": { - "title": "Hide Reblogs", - "message": "Confirm to hide reblogs" + "title": "Yeniden Paylaşımları Gizle", + "message": "Yeniden paylaşımları gizlemeyi onayla" } }, "accessibility": { @@ -493,8 +531,8 @@ "footer": "Diğer sunucudaki takip edilenler gösterilemiyor." }, "familiarFollowers": { - "title": "Followers you familiar", - "followed_by_names": "Followed by %s" + "title": "Tanıyor olabileceğin takipçiler", + "followed_by_names": "%s tarafından takip ediliyor" }, "favorited_by": { "title": "Favorited By" @@ -566,10 +604,10 @@ "show_mentions": "Bahsetmeleri Göster" }, "follow_request": { - "accept": "Accept", - "accepted": "Accepted", - "reject": "reject", - "rejected": "Rejected" + "accept": "Kabul Et", + "accepted": "Kabul Edildi", + "reject": "Reddet", + "rejected": "Reddedildi" } }, "thread": { @@ -654,9 +692,9 @@ "i_dont_like_it": "Beğenmedim", "it_is_not_something_you_want_to_see": "Görmek isteyeceğim bir şey değil", "its_spam": "Spam", - "malicious_links_fake_engagement_or_repetetive_replies": "Malicious links, fake engagement, or repetetive replies", + "malicious_links_fake_engagement_or_repetetive_replies": "Kötü niyetli bağlantılar, sahte etkileşim veya tekrarlayan yanıtlar", "it_violates_server_rules": "Sunucu kurallarını ihlal ediyor", - "you_are_aware_that_it_breaks_specific_rules": "You are aware that it breaks specific rules", + "you_are_aware_that_it_breaks_specific_rules": "Belirli kuralları ihlal ettiğinin farkındasınız", "its_something_else": "Başka bir şey", "the_issue_does_not_fit_into_other_categories": "Sorun bunlardan biri değil" }, @@ -677,15 +715,15 @@ }, "step_final": { "dont_want_to_see_this": "Bunu görmek istemiyor musunuz?", - "when_you_see_something_you_dont_like_on_mastodon_you_can_remove_the_person_from_your_experience.": "When you see something you don’t like on Mastodon, you can remove the person from your experience.", + "when_you_see_something_you_dont_like_on_mastodon_you_can_remove_the_person_from_your_experience.": "Mastodon'da beğenmediğiniz bir şey gördüğünüzde, o kişiyi deneyiminizden çıkarabilirsiniz.", "unfollow": "Takibi bırak", - "unfollowed": "Unfollowed", + "unfollowed": "Takipten çıkıldı", "unfollow_user": "Takipten çık %s", "mute_user": "Sustur %s", "you_wont_see_their_posts_or_reblogs_in_your_home_feed_they_wont_know_they_ve_been_muted": "You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted.", - "block_user": "Block %s", - "they_will_no_longer_be_able_to_follow_or_see_your_posts_but_they_can_see_if_theyve_been_blocked": "They will no longer be able to follow or see your posts, but they can see if they’ve been blocked.", - "while_we_review_this_you_can_take_action_against_user": "While we review this, you can take action against %s" + "block_user": "%s kişisini engelle", + "they_will_no_longer_be_able_to_follow_or_see_your_posts_but_they_can_see_if_theyve_been_blocked": "Artık sizi takip edemez ve gönderilerinizi göremezler ama engellendiklerini görebilirler.", + "while_we_review_this_you_can_take_action_against_user": "Biz bunu incelerken siz %s hesabına karşı önlem alabilirsiniz" } }, "preview": { @@ -706,7 +744,19 @@ "accessibility_hint": "Bu yardımı kapatmak için çift tıklayın" }, "bookmark": { - "title": "Bookmarks" + "title": "Yer İmleri" + }, + "followed_tags": { + "title": "Takip Edilen Etiketler", + "header": { + "posts": "gönderiler", + "participants": "katılımcılar", + "posts_today": "posts today" + }, + "actions": { + "follow": "Takip et", + "unfollow": "Takibi bırak" + } } } } diff --git a/Localization/StringsConvertor/input/uk.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/uk.lproj/Localizable.stringsdict index cdf35477e..21681e1b9 100644 --- a/Localization/StringsConvertor/input/uk.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/uk.lproj/Localizable.stringsdict @@ -13,19 +13,19 @@ NSStringFormatValueTypeKey ld one - 1 unread notification + 1 не прочитане сповіщення few - %ld unread notification + %ld не прочитаних сповіщень many - %ld unread notification + %ld не прочитаних сповіщень other - %ld unread notification + %ld не прочитаних сповіщень a11y.plural.count.input_limit_exceeds NSStringLocalizedFormatKey - Input limit exceeds %#@character_count@ + Перевищено ліміт вводу на %#@character_count@ character_count NSStringFormatSpecTypeKey @@ -33,19 +33,19 @@ NSStringFormatValueTypeKey ld one - 1 character + 1 символ few - %ld characters + %ld символи many - %ld characters + %ld символів other - %ld characters + %ld символів a11y.plural.count.input_limit_remains NSStringLocalizedFormatKey - Input limit remains %#@character_count@ + Залишається вхідний ліміт %#@character_count@ character_count NSStringFormatSpecTypeKey @@ -53,13 +53,33 @@ NSStringFormatValueTypeKey ld one - 1 character + 1 символ few - %ld characters + %ld символи many - %ld characters + %ld символів other - %ld characters + %ld символів + + + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ ліворуч + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 символ + few + %ld символи + many + %ld символів + other + %ld символів plural.count.followed_by_and_mutual @@ -88,13 +108,13 @@ NSStringFormatValueTypeKey ld one - Followed by %1$@, and another mutual + Читають %1$@ та інші few - Followed by %1$@, and %ld mutuals + Читають %1$@, та %ld взаємних many - Followed by %1$@, and %ld mutuals + Читають %1$@, та %ld взаємних other - Followed by %1$@, and %ld mutuals + Читають %1$@, та %ld взаємних plural.count.metric_formatted.post @@ -108,13 +128,13 @@ NSStringFormatValueTypeKey ld one - post + допис few - posts + дописи many - posts + дописів other - posts + дописів plural.count.media @@ -128,13 +148,13 @@ NSStringFormatValueTypeKey ld one - 1 media + медіа few - %ld media + %ld медіа many - %ld media + %ld медіа other - %ld media + %ld медіа plural.count.post @@ -148,13 +168,13 @@ NSStringFormatValueTypeKey ld one - 1 post + 1 пост few - %ld posts + %ld пости many - %ld posts + %ld постів other - %ld posts + %ld постів plural.count.favorite @@ -168,13 +188,13 @@ NSStringFormatValueTypeKey ld one - 1 favorite + 1 улюблене few - %ld favorites + %ld улюблених many - %ld favorites + %ld улюблених other - %ld favorites + %ld улюблених plural.count.reblog @@ -188,13 +208,13 @@ NSStringFormatValueTypeKey ld one - 1 reblog + 1 репост few - %ld reblogs + %ld репости many - %ld reblogs + %ld репостів other - %ld reblogs + %ld репостів plural.count.reply @@ -208,13 +228,13 @@ NSStringFormatValueTypeKey ld one - 1 reply + 1 відповідь few - %ld replies + %ld відповіді many - %ld replies + %ld відповідей other - %ld replies + %ld відповідей plural.count.vote @@ -228,13 +248,13 @@ NSStringFormatValueTypeKey ld one - 1 vote + 1 голос few - %ld votes + %ld голоси many - %ld votes + %ld голосів other - %ld votes + %ld голосів plural.count.voter @@ -248,13 +268,13 @@ NSStringFormatValueTypeKey ld one - 1 voter + 1 учасник голосування few - %ld voters + %ld учасники голосування many - %ld voters + %ld учасників голосування other - %ld voters + %ld учасників голосування plural.people_talking @@ -268,13 +288,13 @@ NSStringFormatValueTypeKey ld one - 1 people talking + 1 людина говорить few - %ld people talking + %ld людей говорять many - %ld people talking + %ld людей говорять other - %ld people talking + %ld людей говорять plural.count.following @@ -288,13 +308,13 @@ NSStringFormatValueTypeKey ld one - 1 following + підписаний few - %ld following + Підписаний на %ld many - %ld following + Підписаний на %ld other - %ld following + Підписаний на %ld plural.count.follower @@ -308,13 +328,13 @@ NSStringFormatValueTypeKey ld one - 1 follower + 1 підписник few - %ld followers + %ld підписників many - %ld followers + %ld підписників other - %ld followers + %ld підписників date.year.left @@ -328,13 +348,13 @@ NSStringFormatValueTypeKey ld one - 1 year left + залишився 1 рік few - %ld years left + %ld років залишилося many - %ld years left + %ld років залишилося other - %ld years left + %ld років залишилося date.month.left @@ -348,13 +368,13 @@ NSStringFormatValueTypeKey ld one - 1 months left + 1 місяць залишився few - %ld months left + %ld місяців залишилося many - %ld months left + %ld місяців залишилося other - %ld months left + %ld місяців залишилося date.day.left @@ -368,13 +388,13 @@ NSStringFormatValueTypeKey ld one - 1 day left + Лишився 1 день few - %ld days left + %ld днів залишилося many - %ld days left + %ld днів залишилося other - %ld days left + %ld днів залишилося date.hour.left @@ -388,13 +408,13 @@ NSStringFormatValueTypeKey ld one - 1 hour left + Залишилася 1 година few - %ld hours left + %ld годин залишилося many - %ld hours left + %ld годин залишилося other - %ld hours left + %ld годин залишилося date.minute.left @@ -408,13 +428,13 @@ NSStringFormatValueTypeKey ld one - 1 minute left + Залишилась одна хвилина few - %ld minutes left + %ld хвилин залишилося many - %ld minutes left + %ld хвилин залишилося other - %ld minutes left + %ld хвилин залишилося date.second.left @@ -428,13 +448,13 @@ NSStringFormatValueTypeKey ld one - 1 second left + Залишилась одна секунда few - %ld seconds left + %ld секунд залишилося many - %ld seconds left + %ld секунд залишилося other - %ld seconds left + %ld секунд залишилося date.year.ago.abbr @@ -448,13 +468,13 @@ NSStringFormatValueTypeKey ld one - 1y ago + 1 рік тому few - %ldy ago + %ld роки тому many - %ldy ago + %ld років тому other - %ldy ago + %ld років тому date.month.ago.abbr @@ -468,13 +488,13 @@ NSStringFormatValueTypeKey ld one - 1M ago + 1 місяць тому few - %ldM ago + %ld місяці тому many - %ldM ago + %ld місяців тому other - %ldM ago + %ld Місяців тому date.day.ago.abbr @@ -488,13 +508,13 @@ NSStringFormatValueTypeKey ld one - 1d ago + 1 день тому few - %ldd ago + %ld дня тому many - %ldd ago + %ld днів тому other - %ldd ago + %ld днів тому date.hour.ago.abbr @@ -508,13 +528,13 @@ NSStringFormatValueTypeKey ld one - 1h ago + 1 годину тому few - %ldh ago + %ld години тому many - %ldh ago + %ld годин тому other - %ldh ago + %ld годин тому date.minute.ago.abbr @@ -528,13 +548,13 @@ NSStringFormatValueTypeKey ld one - 1m ago + 1 хвилину тому few - %ldm ago + %ld хвилини тому many - %ldm ago + %ld хвилин тому other - %ldm ago + %ld хвилин тому date.second.ago.abbr @@ -548,13 +568,13 @@ NSStringFormatValueTypeKey ld one - 1s ago + 1 секунду тому few - %lds ago + %ld секунди тому many - %lds ago + %ld секунд тому other - %lds ago + %ld секунд тому diff --git a/Localization/StringsConvertor/input/uk.lproj/app.json b/Localization/StringsConvertor/input/uk.lproj/app.json index a6a971860..230d61f7c 100644 --- a/Localization/StringsConvertor/input/uk.lproj/app.json +++ b/Localization/StringsConvertor/input/uk.lproj/app.json @@ -2,711 +2,761 @@ "common": { "alerts": { "common": { - "please_try_again": "Please try again.", - "please_try_again_later": "Please try again later." + "please_try_again": "Будь ласка, спробуйте ще раз.", + "please_try_again_later": "Будь ласка, спробуйте пізніше." }, "sign_up_failure": { - "title": "Sign Up Failure" + "title": "Помилка реєстрації" }, "server_error": { - "title": "Server Error" + "title": "Помилка сервера" }, "vote_failure": { - "title": "Vote Failure", - "poll_ended": "The poll has ended" + "title": "Помилка голосування", + "poll_ended": "Опитування завершено" }, "discard_post_content": { - "title": "Discard Draft", - "message": "Confirm to discard composed post content." + "title": "Видалити чернетку", + "message": "Підтвердьте, щоб відхилити створений вміст публікації." }, "publish_post_failure": { - "title": "Publish Failure", - "message": "Failed to publish the post.\nPlease check your internet connection.", + "title": "Помилка публікації", + "message": "Не вдалося опублікувати допис.\nПеревірте підключення до Інтернету.", "attachments_message": { - "video_attach_with_photo": "Cannot attach a video to a post that already contains images.", - "more_than_one_video": "Cannot attach more than one video." + "video_attach_with_photo": "Не можна додати відео до допису з зображенням.", + "more_than_one_video": "Не можна додати більше ніж 1 відео." } }, "edit_profile_failure": { - "title": "Edit Profile Error", - "message": "Cannot edit profile. Please try again." + "title": "Помилка редагування профілю", + "message": "Не вдалося редагувати профіль. Будь ласка, спробуйте ще раз." }, "sign_out": { - "title": "Sign Out", - "message": "Are you sure you want to sign out?", - "confirm": "Sign Out" + "title": "Вийти", + "message": "Ви справді бажаєте вийти?", + "confirm": "Вийти" }, "block_domain": { - "title": "Are you really, really sure you want to block the entire %s? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain and any of your followers from that domain will be removed.", - "block_entire_domain": "Block Domain" + "title": "Ви точно, точно впевнені, що хочете заблокувати весь домен %s? У більшості випадків для нормальної роботи краще заблокувати або приховати лише деяких користувачів. Ви не зможете бачити контент з цього домену у будь-яких стрічках або ваших сповіщеннях. Ваші підписники з цього домену будуть відписані від вас.", + "block_entire_domain": "Заблокувати домен" }, "save_photo_failure": { - "title": "Save Photo Failure", - "message": "Please enable the photo library access permission to save the photo." + "title": "Помилка збереження фото", + "message": "Будь ласка, увімкніть доступ до фотобібліотеки для збереження фотографій." }, "delete_post": { - "title": "Delete Post", - "message": "Are you sure you want to delete this post?" + "title": "Видалити допис", + "message": "Ви дійсно бажаєте видалити цей допис?" }, "clean_cache": { - "title": "Clean Cache", - "message": "Successfully cleaned %s cache." + "title": "Очистити кеш", + "message": "%s успішно очищено." + }, + "translation_failed": { + "title": "Note", + "message": "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported.", + "button": "OK" } }, "controls": { "actions": { - "back": "Back", - "next": "Next", - "previous": "Previous", - "open": "Open", - "add": "Add", - "remove": "Remove", - "edit": "Edit", - "save": "Save", + "back": "Назад", + "next": "Далі", + "previous": "Попередній", + "open": "Відкрити", + "add": "Додати", + "remove": "Видалити", + "edit": "Редагувати", + "save": "Зберегти", "ok": "OK", - "done": "Done", - "confirm": "Confirm", - "continue": "Continue", - "compose": "Compose", - "cancel": "Cancel", - "discard": "Discard", - "try_again": "Try Again", - "take_photo": "Take Photo", - "save_photo": "Save Photo", - "copy_photo": "Copy Photo", - "sign_in": "Sign In", - "sign_up": "Sign Up", - "see_more": "See More", - "preview": "Preview", - "share": "Share", - "share_user": "Share %s", - "share_post": "Share Post", - "open_in_safari": "Open in Safari", - "open_in_browser": "Open in Browser", - "find_people": "Find people to follow", - "manually_search": "Manually search instead", - "skip": "Skip", - "reply": "Reply", - "report_user": "Report %s", - "block_domain": "Block %s", - "unblock_domain": "Unblock %s", - "settings": "Settings", - "delete": "Delete" + "done": "Готово", + "confirm": "Підтвердити", + "continue": "Далі", + "compose": "Створити", + "cancel": "Скасувати", + "discard": "Відхилити", + "try_again": "Спробувати ще раз", + "take_photo": "Зробити фото", + "save_photo": "Зберегти фото", + "copy_photo": "Копіювати фото", + "sign_in": "Увійти", + "sign_up": "Створити обліковий запис", + "see_more": "Дивіться більше", + "preview": "Попередній перегляд", + "copy": "Copy", + "share": "Поділитись", + "share_user": "Поділитися %s", + "share_post": "Поділитися записом", + "open_in_safari": "Відкрити у Safari", + "open_in_browser": "Відкрити в браузері", + "find_people": "Знайти людей, аби підписатися", + "manually_search": "Натомість шукати вручну", + "skip": "Пропустити", + "reply": "Відповісти", + "report_user": "Поскаржитись на %s", + "block_domain": "Блокувати %s", + "unblock_domain": "Розблокувати %s", + "settings": "Налаштування", + "delete": "Видалити", + "translate_post": { + "title": "Translate from %s", + "unknown_language": "Unknown" + } }, "tabs": { - "home": "Home", - "search": "Search", - "notification": "Notification", - "profile": "Profile" + "home": "Головна", + "search_and_explore": "Search and Explore", + "notifications": "Notifications", + "profile": "Профіль" }, "keyboard": { "common": { - "switch_to_tab": "Switch to %s", - "compose_new_post": "Compose New Post", - "show_favorites": "Show Favorites", - "open_settings": "Open Settings" + "switch_to_tab": "Перейти до користувача %s", + "compose_new_post": "Написати новий допис", + "show_favorites": "Показати вибрані", + "open_settings": "Відкрити параметри" }, "timeline": { - "previous_status": "Previous Post", - "next_status": "Next Post", - "open_status": "Open Post", - "open_author_profile": "Open Author's Profile", - "open_reblogger_profile": "Open Reblogger's Profile", - "reply_status": "Reply to Post", - "toggle_reblog": "Toggle Reblog on Post", - "toggle_favorite": "Toggle Favorite on Post", - "toggle_content_warning": "Toggle Content Warning", - "preview_image": "Preview Image" + "previous_status": "Попередній допис", + "next_status": "Новий допис", + "open_status": "Відкрити допис", + "open_author_profile": "Відкрити профіль автора", + "open_reblogger_profile": "Відкрити профіль реблогера", + "reply_status": "Відповісти на допис", + "toggle_reblog": "Додати/прибрати Реблог", + "toggle_favorite": "Додати/прибрати з Обраного", + "toggle_content_warning": "Додати/прибрати попередження про вміст для дорослих", + "preview_image": "Попередній перегляд" }, "segmented_control": { - "previous_section": "Previous Section", - "next_section": "Next Section" + "previous_section": "Попередній розділ", + "next_section": "Наступний розділ" } }, "status": { - "user_reblogged": "%s reblogged", - "user_replied_to": "Replied to %s", - "show_post": "Show Post", - "show_user_profile": "Show user profile", - "content_warning": "Content Warning", - "sensitive_content": "Sensitive Content", - "media_content_warning": "Tap anywhere to reveal", - "tap_to_reveal": "Tap to reveal", + "user_reblogged": "%s Зробив репост", + "user_replied_to": "Відповів %s", + "show_post": "Показати дописи", + "show_user_profile": "Показати профіль користувача", + "content_warning": "Попередження про вміст", + "sensitive_content": "Контент 18+", + "media_content_warning": "Натисніть будь-де, щоб показати більше", + "tap_to_reveal": "Натисніть, щоб відобразити", + "load_embed": "Load Embed", + "link_via_user": "%s via %s", "poll": { - "vote": "Vote", - "closed": "Closed" + "vote": "Проголосувати", + "closed": "Зачинено" }, "meta_entity": { - "url": "Link: %s", - "hashtag": "Hashtag: %s", - "mention": "Show Profile: %s", - "email": "Email address: %s" + "url": "Посилання: %s", + "hashtag": "Гештег: %s", + "mention": "Показати профіль: %s", + "email": "Електронна адреса: %s" }, "actions": { - "reply": "Reply", - "reblog": "Reblog", - "unreblog": "Undo reblog", - "favorite": "Favorite", - "unfavorite": "Unfavorite", - "menu": "Menu", - "hide": "Hide", - "show_image": "Show image", - "show_gif": "Show GIF", - "show_video_player": "Show video player", - "tap_then_hold_to_show_menu": "Tap then hold to show menu" + "reply": "Відповісти", + "reblog": "Репост", + "unreblog": "Скасувати репост", + "favorite": "Улюблене", + "unfavorite": "Вилучити з улюбленого", + "menu": "Меню", + "hide": "Сховати", + "show_image": "Показати зображення", + "show_gif": "Показати GIF", + "show_video_player": "Показати відеоплеєр", + "share_link_in_post": "Share Link in Post", + "tap_then_hold_to_show_menu": "Натисніть та утримуйте, щоб показати меню" }, "tag": { - "url": "URL", - "mention": "Mention", - "link": "Link", - "hashtag": "Hashtag", - "email": "Email", - "emoji": "Emoji" + "url": "Адреса URL", + "mention": "Згадати", + "link": "Посилання", + "hashtag": "Гештег", + "email": "Електронна пошта", + "emoji": "Емодзі" }, "visibility": { - "unlisted": "Everyone can see this post but not display in the public timeline.", - "private": "Only their followers can see this post.", - "private_from_me": "Only my followers can see this post.", - "direct": "Only mentioned user can see this post." + "unlisted": "Хто завгодно може бачити цей допис, але його не буде відображено у публічній стрічці.", + "private": "Лише їхні підписники можуть бачити цю публікацію.", + "private_from_me": "Тільки мої підписники можуть бачити цю публікацію.", + "direct": "Тільки згаданий користувач може бачити цю публікацію." + }, + "translation": { + "translated_from": "Translated from %s using %s", + "unknown_language": "Unknown", + "unknown_provider": "Unknown", + "show_original": "Shown Original" } }, "friendship": { - "follow": "Follow", - "following": "Following", - "request": "Request", - "pending": "Pending", - "block": "Block", - "block_user": "Block %s", - "block_domain": "Block %s", - "unblock": "Unblock", - "unblock_user": "Unblock %s", - "blocked": "Blocked", - "mute": "Mute", - "mute_user": "Mute %s", - "unmute": "Unmute", - "unmute_user": "Unmute %s", - "muted": "Muted", - "edit_info": "Edit Info", - "show_reblogs": "Show Reblogs", - "hide_reblogs": "Hide Reblogs" + "follow": "Підписатися", + "following": "Підписаний", + "request": "Запит", + "pending": "Очікується", + "block": "Заблокувати", + "block_user": "Блокувати %s", + "block_domain": "Блокувати %s", + "unblock": "Розблокувати", + "unblock_user": "Розблокувати %s", + "blocked": "Заблоковано", + "mute": "Заглушити", + "mute_user": "Заглушити %s", + "unmute": "Сповіщати", + "unmute_user": "Сповіщати %s", + "muted": "Зам'ютити", + "edit_info": "Редагувати інформацію", + "show_reblogs": "Показати реблоги", + "hide_reblogs": "Сховати реблоги" }, "timeline": { - "filtered": "Filtered", + "filtered": "Відфільтровано", "timestamp": { - "now": "Now" + "now": "Щойно" }, "loader": { - "load_missing_posts": "Load missing posts", - "loading_missing_posts": "Loading missing posts...", - "show_more_replies": "Show more replies" + "load_missing_posts": "Завантажити пропущені дописи", + "loading_missing_posts": "Завантаження пропущених дописів...", + "show_more_replies": "Показати більше відповідей" }, "header": { - "no_status_found": "No Post Found", - "blocking_warning": "You can’t view this user's profile\nuntil you unblock them.\nYour profile looks like this to them.", - "user_blocking_warning": "You can’t view %s’s profile\nuntil you unblock them.\nYour profile looks like this to them.", - "blocked_warning": "You can’t view this user’s profile\nuntil they unblock you.", - "user_blocked_warning": "You can’t view %s’s profile\nuntil they unblock you.", - "suspended_warning": "This user has been suspended.", - "user_suspended_warning": "%s’s account has been suspended." + "no_status_found": "Публікацій не знайдено", + "blocking_warning": "Ви не можете переглянути профіль цього користувача, поки ви не розблокуєте їх.\nВаш профіль виглядає так.", + "user_blocking_warning": "Ви не можете переглядати профіль %s, поки ви не розблокуєте їх.\nВони бачать ваш профіль так.", + "blocked_warning": "Ви не можете переглянути профіль цього користувача, поки вони не розблокують вас.", + "user_blocked_warning": "Ви не можете переглянути профіль %s, поки він не розблокує вас.", + "suspended_warning": "Цього користувача було заблоковано.", + "user_suspended_warning": "Обліковий запис %s заблоковано." } } } }, "scene": { "welcome": { - "slogan": "Social networking\nback in your hands.", - "get_started": "Get Started", - "log_in": "Log In" + "slogan": "Соціальна мережа під вашим контролем.", + "get_started": "Почати", + "log_in": "Увійти" + }, + "login": { + "title": "З поверненням", + "subtitle": "Увійдіть на сервері, де ви створили свій обліковий запис.", + "server_search_field": { + "placeholder": "Введіть URL-адресу або адресу сервера" + } }, "server_picker": { - "title": "Mastodon is made of users in different servers.", - "subtitle": "Pick a server based on your interests, region, or a general purpose one.", - "subtitle_extend": "Pick a server based on your interests, region, or a general purpose one. Each server is operated by an entirely independent organization or individual.", + "title": "Mastodon складається з користувачів на різних серверах.", + "subtitle": "Оберіть сервер згідно з вашим регіоном, інтересами чи цілями. Ви можете спілкуватися з будь-ким на Mastodon, незалежно від обраних серверів.", "button": { "category": { - "all": "All", - "all_accessiblity_description": "Category: All", - "academia": "academia", - "activism": "activism", - "food": "food", - "furry": "furry", - "games": "games", - "general": "general", - "journalism": "journalism", - "lgbt": "lgbt", - "regional": "regional", - "art": "art", - "music": "music", - "tech": "tech" + "all": "Всі", + "all_accessiblity_description": "Категорія: Усі", + "academia": "академія", + "activism": "активізм", + "food": "їжа", + "furry": "фурі", + "games": "ігри", + "general": "загальне", + "journalism": "журналістика", + "lgbt": "лгбт", + "regional": "регіональний", + "art": "мистецтво", + "music": "музика", + "tech": "технології" }, - "see_less": "See Less", - "see_more": "See More" + "see_less": "Згорнути", + "see_more": "Показати більше" }, "label": { - "language": "LANGUAGE", - "users": "USERS", - "category": "CATEGORY" + "language": "МОВА", + "users": "КОРИСТУВАЧІ", + "category": "КАТЕГОРІЯ" }, "input": { - "placeholder": "Search servers", - "search_servers_or_enter_url": "Search servers or enter URL" + "search_servers_or_enter_url": "Знайти спільноти, або ввести URL" }, "empty_state": { - "finding_servers": "Finding available servers...", - "bad_network": "Something went wrong while loading the data. Check your internet connection.", - "no_results": "No results" + "finding_servers": "Пошук доступних серверів...", + "bad_network": "Сталася помилка під час завантаження даних. Перевірте підключення до Інтернету.", + "no_results": "Жодних результатів" } }, "register": { - "title": "Let’s get you set up on %s", - "lets_get_you_set_up_on_domain": "Let’s get you set up on %s", + "title": "Налаштуймо Вас на %s", + "lets_get_you_set_up_on_domain": "Налаштуймо Вас на %s", "input": { "avatar": { - "delete": "Delete" + "delete": "Видалити" }, "username": { - "placeholder": "username", - "duplicate_prompt": "This username is taken." + "placeholder": "ім'я користувача", + "duplicate_prompt": "Це ім'я користувача вже зайняте." }, "display_name": { - "placeholder": "display name" + "placeholder": "видиме ім'я" }, "email": { - "placeholder": "email" + "placeholder": "електронна пошта" }, "password": { - "placeholder": "password", - "require": "Your password needs at least:", - "character_limit": "8 characters", + "placeholder": "пароль", + "require": "Ваш пароль повинен містити як мінімум:", + "character_limit": "8 символів", "accessibility": { - "checked": "checked", - "unchecked": "unchecked" + "checked": "встановлено", + "unchecked": "знято позначку" }, - "hint": "Your password needs at least eight characters" + "hint": "Ваш пароль повинен містити принаймні вісім символів" }, "invite": { - "registration_user_invite_request": "Why do you want to join?" + "registration_user_invite_request": "Чому ви хочете приєднатися?" } }, "error": { "item": { - "username": "Username", - "email": "Email", - "password": "Password", - "agreement": "Agreement", - "locale": "Locale", - "reason": "Reason" + "username": "Ім'я користувача", + "email": "Електронна пошта", + "password": "Пароль", + "agreement": "Угода", + "locale": "Локаль", + "reason": "Підстава" }, "reason": { - "blocked": "%s contains a disallowed email provider", - "unreachable": "%s does not seem to exist", - "taken": "%s is already in use", - "reserved": "%s is a reserved keyword", - "accepted": "%s must be accepted", - "blank": "%s is required", - "invalid": "%s is invalid", - "too_long": "%s is too long", - "too_short": "%s is too short", - "inclusion": "%s is not a supported value" + "blocked": "%s містить заборонених провайдерів email", + "unreachable": "%s здається, не існує", + "taken": "%s вже використовується", + "reserved": "%s є зарезервованим ключовим словом", + "accepted": "%s має бути прийнято", + "blank": "%s необхідно", + "invalid": "%s є недійсним", + "too_long": "%s занадто довгий", + "too_short": "%s є закоротким", + "inclusion": "%s не є підтримуваним значенням" }, "special": { - "username_invalid": "Username must only contain alphanumeric characters and underscores", - "username_too_long": "Username is too long (can’t be longer than 30 characters)", - "email_invalid": "This is not a valid email address", - "password_too_short": "Password is too short (must be at least 8 characters)" + "username_invalid": "Ім'я користувача повинно містити лише літери, цифрові символи та знак підкреслення", + "username_too_long": "Ім'я користувача занадто довге (не може бути більше 30 символів)", + "email_invalid": "Це не дійсна адреса електронної пошти", + "password_too_short": "Пароль закороткий (має містити мінімум 8 символів)" } } }, "server_rules": { - "title": "Some ground rules.", - "subtitle": "These are set and enforced by the %s moderators.", - "prompt": "By continuing, you’re subject to the terms of service and privacy policy for %s.", - "terms_of_service": "terms of service", - "privacy_policy": "privacy policy", + "title": "Деякі основні правила.", + "subtitle": "Ці призначені для модераторів - %s.", + "prompt": "Продовжуючи, ви піддаєтеся умовам надання послуг та політики конфіденційності для хвороби %s.", + "terms_of_service": "умови використання", + "privacy_policy": "політика конфіденційності", "button": { - "confirm": "I Agree" + "confirm": "Я погоджуюся" } }, "confirm_email": { - "title": "One last thing.", - "subtitle": "Tap the link we emailed to you to verify your account.", - "tap_the_link_we_emailed_to_you_to_verify_your_account": "Tap the link we emailed to you to verify your account", + "title": "Остання річ.", + "subtitle": "Натисніть на посилання, яке ми надіслали на вашу пошту, щоб підтвердити свій обліковий запис.", + "tap_the_link_we_emailed_to_you_to_verify_your_account": "Натисніть на посилання, яке ми надіслали на вашу пошту, щоб підтвердити свій обліковий запис", "button": { - "open_email_app": "Open Email App", - "resend": "Resend" + "open_email_app": "Відкрити додаток Електронної пошти", + "resend": "Повторно надіслати" }, "dont_receive_email": { - "title": "Check your email", - "description": "Check if your email address is correct as well as your junk folder if you haven’t.", - "resend_email": "Resend Email" + "title": "Перевірте свою електронну пошту", + "description": "Перевірте правильність адреси електронної пошти, а також теку зі спамом, якщо ви ще не зробили цього.", + "resend_email": "Повторно надіслати лист" }, "open_email_app": { - "title": "Check your inbox.", - "description": "We just sent you an email. Check your junk folder if you haven’t.", - "mail": "Mail", - "open_email_client": "Open Email Client" + "title": "Перевірте вашу поштову скриньку.", + "description": "Ми щойно надіслали вам електронного листа. Перевірте вашу спам теку, якщо ви не зробили цього.", + "mail": "Пошта", + "open_email_client": "Відкрити поштового клієнта" } }, "home_timeline": { - "title": "Home", + "title": "Головна", "navigation_bar_state": { - "offline": "Offline", - "new_posts": "See new posts", - "published": "Published!", - "Publishing": "Publishing post...", + "offline": "В автономному режимі", + "new_posts": "До нових дописів", + "published": "Опубліковано!", + "Publishing": "Допис публікується...", "accessibility": { - "logo_label": "Logo Button", - "logo_hint": "Tap to scroll to top and tap again to previous location" + "logo_label": "Кнопка Логотипу", + "logo_hint": "Торкніться для прокручування вгору і натисніть ще раз на попереднє розташування" } } }, "suggestion_account": { - "title": "Find People to Follow", - "follow_explain": "When you follow someone, you’ll see their posts in your home feed." + "title": "Знайти людей, аби підписатися", + "follow_explain": "Коли ви підпишетесь на когось, ви побачите його дописи у своїй домашній стрічці." }, "compose": { "title": { - "new_post": "New Post", - "new_reply": "New Reply" + "new_post": "Новий допис", + "new_reply": "Нова відповідь" }, "media_selection": { - "camera": "Take Photo", - "photo_library": "Photo Library", - "browse": "Browse" + "camera": "Зробити фото", + "photo_library": "Галерея", + "browse": "Огляд" }, - "content_input_placeholder": "Type or paste what’s on your mind", - "compose_action": "Publish", - "replying_to_user": "replying to %s", + "content_input_placeholder": "Що у Вас на думці", + "compose_action": "Опублікувати", + "replying_to_user": "відповідь на: %s", "attachment": { - "photo": "photo", - "video": "video", - "attachment_broken": "This %s is broken and can’t be\nuploaded to Mastodon.", - "description_photo": "Describe the photo for the visually-impaired...", - "description_video": "Describe the video for the visually-impaired...", - "load_failed": "Load Failed", - "upload_failed": "Upload Failed", - "can_not_recognize_this_media_attachment": "Can not regonize this media attachment", - "attachment_too_large": "Attachment too large" + "photo": "фото", + "video": "відео", + "attachment_broken": "Цей %s пошкоджений і не може бути\nзавантажений в Mastodon.", + "description_photo": "Опишіть фото для людей з вадами зору...", + "description_video": "Опишіть відео для людей з вадами зору...", + "load_failed": "Не вдалося завантажити", + "upload_failed": "Не вдалося завантажити", + "can_not_recognize_this_media_attachment": "Неможливо розпізнати цей медіафайл", + "attachment_too_large": "Вкладення завелике", + "compressing_state": "Стиснення...", + "server_processing_state": "Обробка сервера..." }, "poll": { - "duration_time": "Duration: %s", - "thirty_minutes": "30 minutes", - "one_hour": "1 Hour", - "six_hours": "6 Hours", - "one_day": "1 Day", - "three_days": "3 Days", - "seven_days": "7 Days", - "option_number": "Option %ld" + "duration_time": "Тривалість: %s", + "thirty_minutes": "30 хвилин", + "one_hour": "1 Година", + "six_hours": "6 Годин", + "one_day": "1 День", + "three_days": "3 Дні", + "seven_days": "7 Днів", + "option_number": "Параметр %ld", + "the_poll_is_invalid": "Неприпустимий варіант опитування", + "the_poll_has_empty_option": "В опитуванні є порожній варіант" }, "content_warning": { - "placeholder": "Write an accurate warning here..." + "placeholder": "Напишіть своє попередження тут..." }, "visibility": { - "public": "Public", - "unlisted": "Unlisted", - "private": "Followers only", - "direct": "Only people I mention" + "public": "Публічно", + "unlisted": "Приховувати зі стрічок", + "private": "Тільки для підписників", + "direct": "Тільки людям, яких я згадав" }, "auto_complete": { - "space_to_add": "Space to add" + "space_to_add": "Пробіл, щоб додати" }, "accessibility": { - "append_attachment": "Add Attachment", - "append_poll": "Add Poll", - "remove_poll": "Remove Poll", - "custom_emoji_picker": "Custom Emoji Picker", - "enable_content_warning": "Enable Content Warning", - "disable_content_warning": "Disable Content Warning", - "post_visibility_menu": "Post Visibility Menu" + "append_attachment": "Додати вкладення", + "append_poll": "Додати опитування", + "remove_poll": "Видалити опитування", + "custom_emoji_picker": "Вибір власних емодзі", + "enable_content_warning": "Увімкнути попередження про контент", + "disable_content_warning": "Вимкнути попередження про контент", + "post_visibility_menu": "Меню видимості поста", + "post_options": "Параметри повідомлення", + "posting_as": "Опублікувати як %s" }, "keyboard": { - "discard_post": "Discard Post", - "publish_post": "Publish Post", - "toggle_poll": "Toggle Poll", - "toggle_content_warning": "Toggle Content Warning", - "append_attachment_entry": "Add Attachment - %s", - "select_visibility_entry": "Select Visibility - %s" + "discard_post": "Відхилити допис", + "publish_post": "Опублікувати повідомлення", + "toggle_poll": "Відкрити або закрити опитування", + "toggle_content_warning": "Попередження про вміст", + "append_attachment_entry": "Додати вкладення - %s", + "select_visibility_entry": "Оберіть видимість - %s" } }, "profile": { "header": { - "follows_you": "Follows You" + "follows_you": "Підписаний(-на) на вас" }, "dashboard": { - "posts": "posts", - "following": "following", - "followers": "followers" + "my_posts": "posts", + "my_following": "following", + "my_followers": "followers", + "other_posts": "posts", + "other_following": "following", + "other_followers": "followers" }, "fields": { - "add_row": "Add Row", + "joined": "Joined", + "add_row": "Додати рядок", "placeholder": { - "label": "Label", - "content": "Content" + "label": "Позначка", + "content": "Зміст" + }, + "verified": { + "short": "Перевірено %s", + "long": "Права власності на це посилання були перевірені %s" } }, "segmented_control": { - "posts": "Posts", - "replies": "Replies", - "posts_and_replies": "Posts and Replies", - "media": "Media", - "about": "About" + "posts": "Дописи", + "replies": "Відповіді", + "posts_and_replies": "Дописи й відповіді", + "media": "Медіа", + "about": "Про застосунок" }, "relationship_action_alert": { "confirm_mute_user": { - "title": "Mute Account", - "message": "Confirm to mute %s" + "title": "Заглушити обліковий запис", + "message": "Підтвердити заглушення %s" }, "confirm_unmute_user": { - "title": "Unmute Account", - "message": "Confirm to unmute %s" + "title": "Розглушити обліковий запис", + "message": "Підтвердить розглушення %s" }, "confirm_block_user": { - "title": "Block Account", - "message": "Confirm to block %s" + "title": "Заблокувати обліковий запис", + "message": "Підтвердити блокування %s" }, "confirm_unblock_user": { - "title": "Unblock Account", - "message": "Confirm to unblock %s" + "title": "Розблокувати обліковий запис", + "message": "Підтвердити розблокування %s" }, "confirm_show_reblogs": { - "title": "Show Reblogs", - "message": "Confirm to show reblogs" + "title": "Показати реблоги", + "message": "Підтвердити показ реблогів" }, "confirm_hide_reblogs": { - "title": "Hide Reblogs", - "message": "Confirm to hide reblogs" + "title": "Сховати реблоги", + "message": "Підтвердити, щоб приховати реблоги" } }, "accessibility": { - "show_avatar_image": "Show avatar image", - "edit_avatar_image": "Edit avatar image", - "show_banner_image": "Show banner image", - "double_tap_to_open_the_list": "Double tap to open the list" + "show_avatar_image": "Показати аватар користувача", + "edit_avatar_image": "Редагувати аватар", + "show_banner_image": "Показати зображення банера", + "double_tap_to_open_the_list": "Натисніть двічі, щоб відчинити список" } }, "follower": { - "title": "follower", - "footer": "Followers from other servers are not displayed." + "title": "підписник", + "footer": "Підписники з інших серверів не відображаються." }, "following": { - "title": "following", - "footer": "Follows from other servers are not displayed." + "title": "підписаний", + "footer": "Підписки з інших серверів не відображаються." }, "familiarFollowers": { - "title": "Followers you familiar", - "followed_by_names": "Followed by %s" + "title": "Підписники, яких ви знаєте", + "followed_by_names": "Має серед підписників %s" }, "favorited_by": { - "title": "Favorited By" + "title": "Є в обраних у" }, "reblogged_by": { - "title": "Reblogged By" + "title": "Репостнуто" }, "search": { - "title": "Search", + "title": "Пошук", "search_bar": { - "placeholder": "Search hashtags and users", - "cancel": "Cancel" + "placeholder": "Пошук за гештегами та користувачами", + "cancel": "Скасувати" }, "recommend": { - "button_text": "See All", + "button_text": "Переглянути всі", "hash_tag": { - "title": "Trending on Mastodon", - "description": "Hashtags that are getting quite a bit of attention", - "people_talking": "%s people are talking" + "title": "У Трендах на Мастодоні", + "description": "Гештеги, що привертають увагу", + "people_talking": "%s людей говорять" }, "accounts": { - "title": "Accounts you might like", - "description": "You may like to follow these accounts", - "follow": "Follow" + "title": "Акавнти, що можуть вам сподобатися", + "description": "Ви можете зацікавитися цими обліковими записами", + "follow": "Підписатися" } }, "searching": { "segment": { - "all": "All", - "people": "People", - "hashtags": "Hashtags", - "posts": "Posts" + "all": "Всі", + "people": "Громада", + "hashtags": "Гештеги", + "posts": "Дописи" }, "empty_state": { - "no_results": "No results" + "no_results": "Жодних результатів" }, - "recent_search": "Recent searches", - "clear": "Clear" + "recent_search": "Нещодавні запити", + "clear": "Очистити" } }, "discovery": { "tabs": { - "posts": "Posts", - "hashtags": "Hashtags", - "news": "News", - "community": "Community", - "for_you": "For You" + "posts": "Дописи", + "hashtags": "Гештеги", + "news": "Новини", + "community": "Спільнота", + "for_you": "Для Вас" }, - "intro": "These are the posts gaining traction in your corner of Mastodon." + "intro": "Ось найбільш популярні дописи серед вашого серверу Mastodon." }, "favorite": { - "title": "Your Favorites" + "title": "Ваші Вподобання" }, "notification": { "title": { - "Everything": "Everything", - "Mentions": "Mentions" + "Everything": "Все", + "Mentions": "Згадки" }, "notification_description": { - "followed_you": "followed you", - "favorited_your_post": "favorited your post", - "reblogged_your_post": "reblogged your post", - "mentioned_you": "mentioned you", - "request_to_follow_you": "request to follow you", - "poll_has_ended": "poll has ended" + "followed_you": "підписався(-лась) на вас", + "favorited_your_post": "вподобав(-ла) ваш допис", + "reblogged_your_post": "поширив(-ла) ваш допис", + "mentioned_you": "згадав(-ла) вас", + "request_to_follow_you": "запит на підписку", + "poll_has_ended": "опитування завершено" }, "keyobard": { - "show_everything": "Show Everything", - "show_mentions": "Show Mentions" + "show_everything": "Показати все", + "show_mentions": "Показати Згадки" }, "follow_request": { - "accept": "Accept", - "accepted": "Accepted", - "reject": "reject", - "rejected": "Rejected" + "accept": "Прийняти", + "accepted": "Прийнято", + "reject": "відхилити", + "rejected": "Відхилено" } }, "thread": { - "back_title": "Post", - "title": "Post from %s" + "back_title": "Допис", + "title": "Публікація від %s" }, "settings": { - "title": "Settings", + "title": "Налаштування", "section": { "appearance": { - "title": "Appearance", - "automatic": "Automatic", - "light": "Always Light", - "dark": "Always Dark" + "title": "Оформлення", + "automatic": "Автоматичне", + "light": "Завжди світле", + "dark": "Завжди темне" }, "look_and_feel": { - "title": "Look and Feel", - "use_system": "Use System", - "really_dark": "Really Dark", - "sorta_dark": "Sorta Dark", - "light": "Light" + "title": "Зовнішній вид", + "use_system": "Використати системний", + "really_dark": "Дуже темний", + "sorta_dark": "Трішкий темний", + "light": "Світлий" }, "notifications": { - "title": "Notifications", - "favorites": "Favorites my post", - "follows": "Follows me", - "boosts": "Reblogs my post", - "mentions": "Mentions me", + "title": "Сповіщення", + "favorites": "Вподобав ваш допис", + "follows": "Підписався на мене", + "boosts": "Реблог мого посту", + "mentions": "Згадує мене", "trigger": { - "anyone": "anyone", - "follower": "a follower", - "follow": "anyone I follow", - "noone": "no one", - "title": "Notify me when" + "anyone": "усі", + "follower": "підписник", + "follow": "хтось, за ким я слідкую", + "noone": "ніхто", + "title": "Повідомити мене, коли" } }, "preference": { - "title": "Preferences", - "true_black_dark_mode": "True black dark mode", - "disable_avatar_animation": "Disable animated avatars", - "disable_emoji_animation": "Disable animated emojis", - "using_default_browser": "Use default browser to open links", - "open_links_in_mastodon": "Open links in Mastodon" + "title": "Налаштування", + "true_black_dark_mode": "Чорний режим", + "disable_avatar_animation": "Вимкнути анімовані аватари", + "disable_emoji_animation": "Вимкнути анімовані емодзі", + "using_default_browser": "Використовувати браузер за замовчуванням, щоб відкрити посилання", + "open_links_in_mastodon": "Відкривати посилання в Mastodon" }, "boring_zone": { - "title": "The Boring Zone", - "account_settings": "Account Settings", - "terms": "Terms of Service", - "privacy": "Privacy Policy" + "title": "Нудна зона", + "account_settings": "Налаштування облікового запису", + "terms": "Умови використання", + "privacy": "Політика конфіденційності" }, "spicy_zone": { - "title": "The Spicy Zone", - "clear": "Clear Media Cache", - "signout": "Sign Out" + "title": "Гостра зона", + "clear": "Очистити кеш медіа", + "signout": "Вийти" } }, "footer": { - "mastodon_description": "Mastodon is open source software. You can report issues on GitHub at %s (%s)" + "mastodon_description": "Mastodon — програма з відкритим вихідним кодом. Ви можете повідомити про проблеми на GitHub на %s (%s)" }, "keyboard": { - "close_settings_window": "Close Settings Window" + "close_settings_window": "Закрити вікно налаштувань" } }, "report": { - "title_report": "Report", - "title": "Report %s", - "step1": "Step 1 of 2", - "step2": "Step 2 of 2", - "content1": "Are there any other posts you’d like to add to the report?", - "content2": "Is there anything the moderators should know about this report?", - "report_sent_title": "Thanks for reporting, we’ll look into this.", - "send": "Send Report", - "skip_to_send": "Send without comment", - "text_placeholder": "Type or paste additional comments", - "reported": "REPORTED", + "title_report": "Скарга", + "title": "Поскаржитись на %s", + "step1": "Крок 1 з 2", + "step2": "Крок 2 з 2", + "content1": "Чи є інші дописи, які Ви хотіли б додати до скарги?", + "content2": "Чи є що-небудь модератори повинні знати про цю скаргу?", + "report_sent_title": "Дякуємо за скаргу, ми розглянемо її.", + "send": "Надіслати скаргу", + "skip_to_send": "Надіслати без коментаря", + "text_placeholder": "Введіть або вставте додаткові коментарі", + "reported": "Мої скарги", "step_one": { - "step_1_of_4": "Step 1 of 4", - "whats_wrong_with_this_post": "What's wrong with this post?", - "whats_wrong_with_this_account": "What's wrong with this account?", - "whats_wrong_with_this_username": "What's wrong with %s?", - "select_the_best_match": "Select the best match", - "i_dont_like_it": "I don’t like it", - "it_is_not_something_you_want_to_see": "It is not something you want to see", - "its_spam": "It’s spam", - "malicious_links_fake_engagement_or_repetetive_replies": "Malicious links, fake engagement, or repetetive replies", - "it_violates_server_rules": "It violates server rules", - "you_are_aware_that_it_breaks_specific_rules": "You are aware that it breaks specific rules", - "its_something_else": "It’s something else", - "the_issue_does_not_fit_into_other_categories": "The issue does not fit into other categories" + "step_1_of_4": "Крок 1 з 4", + "whats_wrong_with_this_post": "Що не так з цим постом?", + "whats_wrong_with_this_account": "Що не так з цим обліковим записом?", + "whats_wrong_with_this_username": "Що з %s не так?", + "select_the_best_match": "Оберіть найвідповідніший збіг", + "i_dont_like_it": "Мені це не подобається", + "it_is_not_something_you_want_to_see": "Це не те, що ви хотіли б побачити", + "its_spam": "Це спам", + "malicious_links_fake_engagement_or_repetetive_replies": "Підозрілі посилання, фейкові розмови або відповіді", + "it_violates_server_rules": "Це порушує правила сервера", + "you_are_aware_that_it_breaks_specific_rules": "Ви впевнені, що це порушує певні правила", + "its_something_else": "Це щось інше", + "the_issue_does_not_fit_into_other_categories": "Ця проблема не відповідає жодній іншій категорії" }, "step_two": { - "step_2_of_4": "Step 2 of 4", - "which_rules_are_being_violated": "Which rules are being violated?", - "select_all_that_apply": "Select all that apply", - "i_just_don’t_like_it": "I just don’t like it" + "step_2_of_4": "Крок 2 з 4", + "which_rules_are_being_violated": "Які правила порушено?", + "select_all_that_apply": "Виберіть усі варіанти, що підходять", + "i_just_don’t_like_it": "Мені це не подобається" }, "step_three": { - "step_3_of_4": "Step 3 of 4", - "are_there_any_posts_that_back_up_this_report": "Are there any posts that back up this report?", - "select_all_that_apply": "Select all that apply" + "step_3_of_4": "Крок 3 з 4", + "are_there_any_posts_that_back_up_this_report": "Чи є публікації, які підтверджують цю скаргу?", + "select_all_that_apply": "Виберіть усі варіанти, що підходять" }, "step_four": { - "step_4_of_4": "Step 4 of 4", - "is_there_anything_else_we_should_know": "Is there anything else we should know?" + "step_4_of_4": "Крок 4 з 4", + "is_there_anything_else_we_should_know": "Чи є ще щось, що ми повинні знати?" }, "step_final": { - "dont_want_to_see_this": "Don’t want to see this?", - "when_you_see_something_you_dont_like_on_mastodon_you_can_remove_the_person_from_your_experience.": "When you see something you don’t like on Mastodon, you can remove the person from your experience.", - "unfollow": "Unfollow", - "unfollowed": "Unfollowed", - "unfollow_user": "Unfollow %s", - "mute_user": "Mute %s", - "you_wont_see_their_posts_or_reblogs_in_your_home_feed_they_wont_know_they_ve_been_muted": "You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted.", - "block_user": "Block %s", - "they_will_no_longer_be_able_to_follow_or_see_your_posts_but_they_can_see_if_theyve_been_blocked": "They will no longer be able to follow or see your posts, but they can see if they’ve been blocked.", - "while_we_review_this_you_can_take_action_against_user": "While we review this, you can take action against %s" + "dont_want_to_see_this": "Не хочете бачити це?", + "when_you_see_something_you_dont_like_on_mastodon_you_can_remove_the_person_from_your_experience.": "Якщо бачите щось, що вам не подобається в Mastodon, то можна вилучити людину зі свого оточення.", + "unfollow": "Відписатися", + "unfollowed": "Відписалися від", + "unfollow_user": "Відписатися від %s", + "mute_user": "Ігнорувати %s", + "you_wont_see_their_posts_or_reblogs_in_your_home_feed_they_wont_know_they_ve_been_muted": "Ви не побачите їхні дописи чи репости на вашій домашній стрічці. Вони не знатимуть, що ви ігноруєте їх.", + "block_user": "Заблокувати %s", + "they_will_no_longer_be_able_to_follow_or_see_your_posts_but_they_can_see_if_theyve_been_blocked": "Вони більше не зможуть стежити або бачити Ваші пости, але вони зможуть побачити, що вони були заблоковані.", + "while_we_review_this_you_can_take_action_against_user": "Поки ми переглядаємо це, ви можете вжити заходів проти %s" } }, "preview": { "keyboard": { - "close_preview": "Close Preview", - "show_next": "Show Next", - "show_previous": "Show Previous" + "close_preview": "Закрити перегляд", + "show_next": "Показати наступне", + "show_previous": "Показувати попереднє" } }, "account_list": { - "tab_bar_hint": "Current selected profile: %s. Double tap then hold to show account switcher", - "dismiss_account_switcher": "Dismiss Account Switcher", - "add_account": "Add Account" + "tab_bar_hint": "Поточний профіль: %s. Двічі торкніться, щоб показати перемикач ваших профілів", + "dismiss_account_switcher": "Відхилити зміну поточного облікового запису", + "add_account": "Додати обліковий запис" }, "wizard": { - "new_in_mastodon": "New in Mastodon", - "multiple_account_switch_intro_description": "Switch between multiple accounts by holding the profile button.", - "accessibility_hint": "Double tap to dismiss this wizard" + "new_in_mastodon": "Новий в Mastodon", + "multiple_account_switch_intro_description": "Натисніть для переходу між кількома обліковими записами, тримаючи кнопку профілю.", + "accessibility_hint": "Двічі торкніться, щоб закрити цей майстер" }, "bookmark": { - "title": "Bookmarks" + "title": "Закладки" + }, + "followed_tags": { + "title": "Followed Tags", + "header": { + "posts": "posts", + "participants": "participants", + "posts_today": "posts today" + }, + "actions": { + "follow": "Follow", + "unfollow": "Unfollow" + } } } } diff --git a/Localization/StringsConvertor/input/uk.lproj/ios-infoPlist.json b/Localization/StringsConvertor/input/uk.lproj/ios-infoPlist.json index c6db73de0..bc953a077 100644 --- a/Localization/StringsConvertor/input/uk.lproj/ios-infoPlist.json +++ b/Localization/StringsConvertor/input/uk.lproj/ios-infoPlist.json @@ -1,6 +1,6 @@ { - "NSCameraUsageDescription": "Used to take photo for post status", - "NSPhotoLibraryAddUsageDescription": "Used to save photo into the Photo Library", - "NewPostShortcutItemTitle": "New Post", - "SearchShortcutItemTitle": "Search" + "NSCameraUsageDescription": "Використовується, щоб зробити фотографію для статусу публікації", + "NSPhotoLibraryAddUsageDescription": "Використовується для збереження фото в бібліотеку", + "NewPostShortcutItemTitle": "Новий допис", + "SearchShortcutItemTitle": "Пошук" } diff --git a/Localization/StringsConvertor/input/vi.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/vi.lproj/Localizable.stringsdict index 6905b240e..4c772f014 100644 --- a/Localization/StringsConvertor/input/vi.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/vi.lproj/Localizable.stringsdict @@ -44,6 +44,20 @@ %ld ký tự + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ còn lại + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld ký tự + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/vi.lproj/app.json b/Localization/StringsConvertor/input/vi.lproj/app.json index 5b7696727..3a3c66a66 100644 --- a/Localization/StringsConvertor/input/vi.lproj/app.json +++ b/Localization/StringsConvertor/input/vi.lproj/app.json @@ -51,6 +51,11 @@ "clean_cache": { "title": "Xóa bộ nhớ đệm", "message": "Đã xóa %s bộ nhớ đệm." + }, + "translation_failed": { + "title": "Ghi chú", + "message": "Dịch không thành công. Có thể quản trị viên chưa bật dịch trên máy chủ này hoặc máy chủ này đang chạy phiên bản cũ hơn của Mastodon chưa hỗ trợ dịch.", + "button": "OK" } }, "controls": { @@ -75,9 +80,10 @@ "save_photo": "Lưu ảnh", "copy_photo": "Sao chép ảnh", "sign_in": "Đăng nhập", - "sign_up": "Đăng ký", + "sign_up": "Tạo tài khoản", "see_more": "Xem thêm", "preview": "Xem trước", + "copy": "Chép", "share": "Chia sẻ", "share_user": "Chia sẻ %s", "share_post": "Chia sẻ tút", @@ -91,12 +97,16 @@ "block_domain": "Chặn %s", "unblock_domain": "Bỏ chặn %s", "settings": "Cài đặt", - "delete": "Xóa" + "delete": "Xóa", + "translate_post": { + "title": "Dịch từ %s", + "unknown_language": "Chưa xác định" + } }, "tabs": { "home": "Bảng tin", - "search": "Tìm kiếm", - "notification": "Thông báo", + "search_and_explore": "Tìm và Khám Phá", + "notifications": "Thông báo", "profile": "Trang hồ sơ" }, "keyboard": { @@ -132,6 +142,8 @@ "sensitive_content": "Nội dung nhạy cảm", "media_content_warning": "Nhấn để hiển thị", "tap_to_reveal": "Nhấn để xem", + "load_embed": "Nạp mã nhúng", + "link_via_user": "%s bởi %s", "poll": { "vote": "Bình chọn", "closed": "Kết thúc" @@ -153,6 +165,7 @@ "show_image": "Hiển thị hình ảnh", "show_gif": "Hiển thị GIF", "show_video_player": "Hiện trình phát video", + "share_link_in_post": "Chia sẻ liên kết trong Tút", "tap_then_hold_to_show_menu": "Nhấn giữ để hiện menu" }, "tag": { @@ -168,6 +181,12 @@ "private": "Chỉ người theo dõi của họ có thể thấy tút này.", "private_from_me": "Chỉ người theo dõi tôi có thể thấy tút này.", "direct": "Chỉ người được nhắc đến có thể thấy tút." + }, + "translation": { + "translated_from": "Dịch từ %s bằng %s", + "unknown_language": "Không xác định", + "unknown_provider": "Không biết", + "show_original": "Bản gốc" } }, "friendship": { @@ -218,10 +237,16 @@ "get_started": "Bắt đầu", "log_in": "Đăng nhập" }, + "login": { + "title": "Chào mừng trở lại!", + "subtitle": "Đăng nhập vào máy chủ mà bạn đã tạo tài khoản.", + "server_search_field": { + "placeholder": "Nhập URL hoặc tìm máy chủ" + } + }, "server_picker": { "title": "Mastodon gồm nhiều máy chủ với thành viên riêng.", - "subtitle": "Chọn một máy chủ dựa theo sở thích, tôn giáo, hoặc ý muốn của bạn.", - "subtitle_extend": "Chọn một máy chủ dựa theo sở thích, tôn giáo, hoặc ý muốn của bạn. Mỗi máy chủ có thể được vận hành bởi một cá nhân hoặc một tổ chức.", + "subtitle": "Chọn một máy chủ dựa theo sở thích, tôn giáo, hoặc ý muốn của bạn. Bạn vẫn có thể giao tiếp với bất cứ ai mà không phụ thuộc vào máy chủ của họ.", "button": { "category": { "all": "Toàn bộ", @@ -248,8 +273,7 @@ "category": "PHÂN LOẠI" }, "input": { - "placeholder": "Tìm máy chủ", - "search_servers_or_enter_url": "Tìm máy chủ hoặc nhập URL" + "search_servers_or_enter_url": "Tìm một máy chủ hoặc nhập URL" }, "empty_state": { "finding_servers": "Đang tìm máy chủ hoạt động...", @@ -376,17 +400,19 @@ }, "content_input_placeholder": "Cho thế giới biết bạn đang nghĩ gì", "compose_action": "Đăng", - "replying_to_user": "trả lời %s", + "replying_to_user": "%s viết tiếp", "attachment": { "photo": "ảnh", "video": "video", "attachment_broken": "%s này bị lỗi và không thể\ntải lên Mastodon.", "description_photo": "Mô tả hình ảnh cho người khiếm thị...", "description_video": "Mô tả video cho người khiếm thị...", - "load_failed": "Load Failed", - "upload_failed": "Upload Failed", - "can_not_recognize_this_media_attachment": "Can not regonize this media attachment", - "attachment_too_large": "Attachment too large" + "load_failed": "Tải thất bại", + "upload_failed": "Tải lên thất bại", + "can_not_recognize_this_media_attachment": "Không xem được tập tin đính kèm", + "attachment_too_large": "Tập tin đính kèm quá lớn", + "compressing_state": "Đang nén...", + "server_processing_state": "Máy chủ đang xử lý..." }, "poll": { "duration_time": "Thời hạn: %s", @@ -396,7 +422,9 @@ "one_day": "1 ngày", "three_days": "3 ngày", "seven_days": "7 ngày", - "option_number": "Lựa chọn %ld" + "option_number": "Lựa chọn %ld", + "the_poll_is_invalid": "Bình chọn không hợp lệ", + "the_poll_has_empty_option": "Thiếu lựa chọn" }, "content_warning": { "placeholder": "Viết nội dung ẩn của bạn ở đây..." @@ -417,7 +445,9 @@ "custom_emoji_picker": "Chọn emoji", "enable_content_warning": "Bật nội dung ẩn", "disable_content_warning": "Tắt nội dung ẩn", - "post_visibility_menu": "Menu hiển thị tút" + "post_visibility_menu": "Menu hiển thị tút", + "post_options": "Tùy chọn đăng", + "posting_as": "Đăng dưới dạng %s" }, "keyboard": { "discard_post": "Hủy đăng tút", @@ -433,15 +463,23 @@ "follows_you": "Đang theo dõi bạn" }, "dashboard": { - "posts": "tút", - "following": "theo dõi", - "followers": "người theo dõi" + "my_posts": "posts", + "my_following": "following", + "my_followers": "followers", + "other_posts": "posts", + "other_following": "following", + "other_followers": "followers" }, "fields": { + "joined": "Đã tham gia", "add_row": "Thêm hàng", "placeholder": { "label": "Nhãn", "content": "Nội dung" + }, + "verified": { + "short": "Đã xác minh %s", + "long": "Liên kết này đã được xác minh trên %s" } }, "segmented_control": { @@ -707,6 +745,18 @@ }, "bookmark": { "title": "Tút đã lưu" + }, + "followed_tags": { + "title": "Hashtag Theo Dõi", + "header": { + "posts": "tút", + "participants": "người thảo luận", + "posts_today": "tút hôm nay" + }, + "actions": { + "follow": "Theo dõi", + "unfollow": "Ngưng theo dõi" + } } } } diff --git a/Localization/StringsConvertor/input/zh-Hans.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/zh-Hans.lproj/Localizable.stringsdict index 5a7af3752..362d55c4f 100644 --- a/Localization/StringsConvertor/input/zh-Hans.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/zh-Hans.lproj/Localizable.stringsdict @@ -44,6 +44,20 @@ %ld 个字符 + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ 剩余 + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld 个字符 + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/zh-Hans.lproj/app.json b/Localization/StringsConvertor/input/zh-Hans.lproj/app.json index ddf89e159..efe5c422e 100644 --- a/Localization/StringsConvertor/input/zh-Hans.lproj/app.json +++ b/Localization/StringsConvertor/input/zh-Hans.lproj/app.json @@ -51,6 +51,11 @@ "clean_cache": { "title": "清除缓存", "message": "成功清除 %s 缓存。" + }, + "translation_failed": { + "title": "Note", + "message": "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported.", + "button": "OK" } }, "controls": { @@ -75,9 +80,10 @@ "save_photo": "保存照片", "copy_photo": "拷贝照片", "sign_in": "登录", - "sign_up": "注册", + "sign_up": "创建账户", "see_more": "查看更多", "preview": "预览", + "copy": "Copy", "share": "分享", "share_user": "分享 %s", "share_post": "分享帖子", @@ -91,12 +97,16 @@ "block_domain": "屏蔽 %s", "unblock_domain": "解除屏蔽 %s", "settings": "设置", - "delete": "删除" + "delete": "删除", + "translate_post": { + "title": "Translate from %s", + "unknown_language": "Unknown" + } }, "tabs": { "home": "主页", - "search": "搜索", - "notification": "通知", + "search_and_explore": "Search and Explore", + "notifications": "通知", "profile": "个人资料" }, "keyboard": { @@ -132,15 +142,17 @@ "sensitive_content": "敏感内容", "media_content_warning": "点击任意位置显示", "tap_to_reveal": "点击以显示", + "load_embed": "Load Embed", + "link_via_user": "%s via %s", "poll": { "vote": "投票", "closed": "已关闭" }, "meta_entity": { - "url": "Link: %s", - "hashtag": "Hashtag: %s", - "mention": "Show Profile: %s", - "email": "Email address: %s" + "url": "链接:%s", + "hashtag": "话题:%s", + "mention": "显示用户资料:%s", + "email": "邮箱地址:%s" }, "actions": { "reply": "回复", @@ -153,6 +165,7 @@ "show_image": "显示图片", "show_gif": "显示 GIF", "show_video_player": "显示视频播放器", + "share_link_in_post": "Share Link in Post", "tap_then_hold_to_show_menu": "长按以显示菜单" }, "tag": { @@ -168,6 +181,12 @@ "private": "只有作者的关注者才能看到此帖子。", "private_from_me": "只有我的关注者才能看到此帖子。", "direct": "只有提到的用户才能看到此帖子。" + }, + "translation": { + "translated_from": "Translated from %s using %s", + "unknown_language": "Unknown", + "unknown_provider": "Unknown", + "show_original": "Shown Original" } }, "friendship": { @@ -187,8 +206,8 @@ "unmute_user": "取消静音 %s", "muted": "已静音", "edit_info": "编辑", - "show_reblogs": "Show Reblogs", - "hide_reblogs": "Hide Reblogs" + "show_reblogs": "显示转发", + "hide_reblogs": "隐藏转发" }, "timeline": { "filtered": "已过滤", @@ -218,10 +237,16 @@ "get_started": "开始使用", "log_in": "登录" }, + "login": { + "title": "欢迎回来", + "subtitle": "登入您账户所在的服务器。", + "server_search_field": { + "placeholder": "输入网址或搜索您的服务器" + } + }, "server_picker": { "title": "挑选一个服务器,\n任意服务器。", - "subtitle": "根据你的兴趣、区域或一般目的选择一个社区。", - "subtitle_extend": "根据你的兴趣、区域或一般目的选择一个社区。每个社区都由完全独立的组织或个人管理。", + "subtitle": "根据你的地区、兴趣挑选一个服务器。无论你选择哪个服务器,你都可以跟其他服务器的任何人一起聊天。", "button": { "category": { "all": "全部", @@ -248,8 +273,7 @@ "category": "类别" }, "input": { - "placeholder": "查找或加入你自己的服务器...", - "search_servers_or_enter_url": "搜索服务器或输入 URL" + "search_servers_or_enter_url": "搜索社区或输入 URL" }, "empty_state": { "finding_servers": "正在查找可用的服务器...", @@ -383,10 +407,12 @@ "attachment_broken": "%s已损坏\n无法上传到 Mastodon", "description_photo": "为视觉障碍人士添加照片的文字说明...", "description_video": "为视觉障碍人士添加视频的文字说明...", - "load_failed": "Load Failed", - "upload_failed": "Upload Failed", - "can_not_recognize_this_media_attachment": "Can not regonize this media attachment", - "attachment_too_large": "Attachment too large" + "load_failed": "加载失败", + "upload_failed": "上传失败", + "can_not_recognize_this_media_attachment": "无法识别此媒体", + "attachment_too_large": "附件太大", + "compressing_state": "压缩中...", + "server_processing_state": "服务器正在处理..." }, "poll": { "duration_time": "时长:%s", @@ -396,7 +422,9 @@ "one_day": "1 天", "three_days": "3 天", "seven_days": "7 天", - "option_number": "选项 %ld" + "option_number": "选项 %ld", + "the_poll_is_invalid": "投票无效", + "the_poll_has_empty_option": "投票含有空选项" }, "content_warning": { "placeholder": "在这里写下内容的警告消息..." @@ -417,7 +445,9 @@ "custom_emoji_picker": "自定义表情选择器", "enable_content_warning": "启用内容警告", "disable_content_warning": "关闭内容警告", - "post_visibility_menu": "帖子可见性" + "post_visibility_menu": "帖子可见性", + "post_options": "帖子选项", + "posting_as": "以 %s 身份发布" }, "keyboard": { "discard_post": "丢弃帖子", @@ -433,15 +463,23 @@ "follows_you": "关注了你" }, "dashboard": { - "posts": "帖子", - "following": "正在关注", - "followers": "关注者" + "my_posts": "posts", + "my_following": "following", + "my_followers": "followers", + "other_posts": "posts", + "other_following": "following", + "other_followers": "followers" }, "fields": { + "joined": "Joined", "add_row": "添加", "placeholder": { "label": "标签", "content": "内容" + }, + "verified": { + "short": "验证于 %s", + "long": "此链接的所有权已在 %s 上检查通过" } }, "segmented_control": { @@ -469,12 +507,12 @@ "message": "确认取消屏蔽 %s" }, "confirm_show_reblogs": { - "title": "Show Reblogs", - "message": "Confirm to show reblogs" + "title": "显示转发", + "message": "确认显示转发" }, "confirm_hide_reblogs": { - "title": "Hide Reblogs", - "message": "Confirm to hide reblogs" + "title": "隐藏转发", + "message": "确认隐藏转发" } }, "accessibility": { @@ -706,7 +744,19 @@ "accessibility_hint": "双击关闭此向导" }, "bookmark": { - "title": "Bookmarks" + "title": "书签" + }, + "followed_tags": { + "title": "Followed Tags", + "header": { + "posts": "posts", + "participants": "participants", + "posts_today": "posts today" + }, + "actions": { + "follow": "Follow", + "unfollow": "Unfollow" + } } } } diff --git a/Localization/StringsConvertor/input/zh-Hant.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/zh-Hant.lproj/Localizable.stringsdict index c0ce0f9a2..d545fd6a4 100644 --- a/Localization/StringsConvertor/input/zh-Hant.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/zh-Hant.lproj/Localizable.stringsdict @@ -44,6 +44,20 @@ %ld 個字 + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + 剩餘 %#@character_count@ 字 + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld 個字 + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/zh-Hant.lproj/app.json b/Localization/StringsConvertor/input/zh-Hant.lproj/app.json index ab7343c99..711084407 100644 --- a/Localization/StringsConvertor/input/zh-Hant.lproj/app.json +++ b/Localization/StringsConvertor/input/zh-Hant.lproj/app.json @@ -51,6 +51,11 @@ "clean_cache": { "title": "清除快取", "message": "成功清除 %s 快取。" + }, + "translation_failed": { + "title": "備註", + "message": "翻譯失敗。也許管理員未於此伺服器啟用翻譯功能,或此伺服器為未支援翻譯功能之舊版本 Mastodon。", + "button": "OK" } }, "controls": { @@ -75,9 +80,10 @@ "save_photo": "儲存照片", "copy_photo": "複製照片", "sign_in": "登入", - "sign_up": "註冊", + "sign_up": "新增帳號", "see_more": "檢視更多", "preview": "預覽", + "copy": "複製", "share": "分享", "share_user": "分享 %s", "share_post": "分享嘟文", @@ -91,12 +97,16 @@ "block_domain": "封鎖 %s", "unblock_domain": "解除封鎖 %s", "settings": "設定", - "delete": "刪除" + "delete": "刪除", + "translate_post": { + "title": "翻譯自 %s", + "unknown_language": "未知" + } }, "tabs": { "home": "首頁", - "search": "搜尋", - "notification": "通知", + "search_and_explore": "搜尋與探索", + "notifications": "通知", "profile": "個人檔案" }, "keyboard": { @@ -132,6 +142,8 @@ "sensitive_content": "敏感內容", "media_content_warning": "輕觸任何地方以顯示", "tap_to_reveal": "輕觸以顯示", + "load_embed": "讀取嵌入內容", + "link_via_user": "%s 透過 %s", "poll": { "vote": "投票", "closed": "已關閉" @@ -153,6 +165,7 @@ "show_image": "顯示圖片", "show_gif": "顯示 GIF", "show_video_player": "顯示影片播放器", + "share_link_in_post": "於嘟文中分享鏈結", "tap_then_hold_to_show_menu": "輕觸然後按住以顯示選單" }, "tag": { @@ -168,6 +181,12 @@ "private": "只有他們的跟隨者能看到此嘟文。", "private_from_me": "只有我的跟隨者能看到此嘟文。", "direct": "只有被提及的使用者能看到此嘟文。" + }, + "translation": { + "translated_from": "透過 %s 翻譯 %s", + "unknown_language": "未知", + "unknown_provider": "未知", + "show_original": "顯示原文" } }, "friendship": { @@ -218,10 +237,16 @@ "get_started": "新手上路", "log_in": "登入" }, + "login": { + "title": "歡迎回來", + "subtitle": "登入您新增帳號之伺服器", + "server_search_field": { + "placeholder": "請輸入 URL 或搜尋您的伺服器" + } + }, "server_picker": { "title": "Mastodon 由不同伺服器的使用者組成。", - "subtitle": "基於您的興趣、地區、或一般用途選定一個伺服器。", - "subtitle_extend": "基於您的興趣、地區、或一般用途選定一個伺服器。每個伺服器是由完全獨立的組織或個人營運。", + "subtitle": "基於您的興趣、地區、或一般用途選定一個伺服器。您仍會與任何伺服器中的每個人連結。", "button": { "category": { "all": "全部", @@ -248,8 +273,7 @@ "category": "分類" }, "input": { - "placeholder": "搜尋伺服器", - "search_servers_or_enter_url": "搜尋伺服器或輸入網址" + "search_servers_or_enter_url": "搜尋社群或輸入 URL 地址" }, "empty_state": { "finding_servers": "尋找可用的伺服器...", @@ -383,10 +407,12 @@ "attachment_broken": "此 %s 已損毀,並無法被上傳至 Mastodon。", "description_photo": "為視障人士提供圖片說明...", "description_video": "為視障人士提供影片說明...", - "load_failed": "Load Failed", - "upload_failed": "Upload Failed", - "can_not_recognize_this_media_attachment": "Can not regonize this media attachment", - "attachment_too_large": "Attachment too large" + "load_failed": "讀取失敗", + "upload_failed": "上傳失敗", + "can_not_recognize_this_media_attachment": "無法識別此媒體附加檔案", + "attachment_too_large": "附加檔案大小過大", + "compressing_state": "正在壓縮...", + "server_processing_state": "伺服器處理中..." }, "poll": { "duration_time": "持續時間:%s", @@ -396,7 +422,9 @@ "one_day": "一天", "three_days": "三天", "seven_days": "七天", - "option_number": "選項 %ld" + "option_number": "選項 %ld", + "the_poll_is_invalid": "此投票是無效的", + "the_poll_has_empty_option": "此投票有空白選項" }, "content_warning": { "placeholder": "請於此處寫下精準的警告..." @@ -417,7 +445,9 @@ "custom_emoji_picker": "自訂 emoji 選擇器", "enable_content_warning": "啟用內容警告", "disable_content_warning": "停用內容警告", - "post_visibility_menu": "嘟文可見性選單" + "post_visibility_menu": "嘟文可見性選單", + "post_options": "嘟文選項", + "posting_as": "以 %s 發嘟" }, "keyboard": { "discard_post": "捨棄嘟文", @@ -433,15 +463,23 @@ "follows_you": "跟隨了您" }, "dashboard": { - "posts": "嘟文", - "following": "跟隨中", - "followers": "跟隨者" + "my_posts": "嘟文", + "my_following": "正在跟隨", + "my_followers": "跟隨者", + "other_posts": "嘟文", + "other_following": "正在跟隨", + "other_followers": "跟隨者" }, "fields": { + "joined": "加入時間", "add_row": "新增列", "placeholder": { "label": "標籤", "content": "內容" + }, + "verified": { + "short": "於 %s 上已驗證", + "long": "已在 %s 檢查此連結的擁有者權限" } }, "segmented_control": { @@ -707,6 +745,18 @@ }, "bookmark": { "title": "書籤" + }, + "followed_tags": { + "title": "已跟隨主題標籤", + "header": { + "posts": "嘟文", + "participants": "參與者", + "posts_today": "本日嘟文" + }, + "actions": { + "follow": "跟隨", + "unfollow": "取消跟隨" + } } } } diff --git a/Localization/app.json b/Localization/app.json index 30566d8d6..963b4aed1 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -51,6 +51,11 @@ "clean_cache": { "title": "Clean Cache", "message": "Successfully cleaned %s cache." + }, + "translation_failed": { + "title": "Note", + "message": "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported.", + "button": "OK" } }, "controls": { @@ -74,10 +79,11 @@ "take_photo": "Take Photo", "save_photo": "Save Photo", "copy_photo": "Copy Photo", - "sign_in": "Sign In", - "sign_up": "Sign Up", + "sign_in": "Log in", + "sign_up": "Create account", "see_more": "See More", "preview": "Preview", + "copy": "Copy", "share": "Share", "share_user": "Share %s", "share_post": "Share Post", @@ -91,12 +97,16 @@ "block_domain": "Block %s", "unblock_domain": "Unblock %s", "settings": "Settings", - "delete": "Delete" + "delete": "Delete", + "translate_post": { + "title": "Translate from %s", + "unknown_language": "Unknown" + } }, "tabs": { "home": "Home", - "search": "Search", - "notification": "Notification", + "search_and_explore": "Search and Explore", + "notifications": "Notifications", "profile": "Profile" }, "keyboard": { @@ -132,6 +142,8 @@ "sensitive_content": "Sensitive Content", "media_content_warning": "Tap anywhere to reveal", "tap_to_reveal": "Tap to reveal", + "load_embed": "Load Embed", + "link_via_user": "%s via %s", "poll": { "vote": "Vote", "closed": "Closed" @@ -153,6 +165,7 @@ "show_image": "Show image", "show_gif": "Show GIF", "show_video_player": "Show video player", + "share_link_in_post": "Share Link in Post", "tap_then_hold_to_show_menu": "Tap then hold to show menu" }, "tag": { @@ -168,6 +181,18 @@ "private": "Only their followers can see this post.", "private_from_me": "Only my followers can see this post.", "direct": "Only mentioned user can see this post." + }, + "translation": { + "translated_from": "Translated from %s using %s", + "unknown_language": "Unknown", + "unknown_provider": "Unknown", + "show_original": "Shown Original" + }, + "media": { + "accessibility_label": "%s, attachment %d of %d", + "expand_image_hint": "Expands the image. Double-tap and hold to show actions", + "expand_gif_hint": "Expands the GIF. Double-tap and hold to show actions", + "expand_video_hint": "Shows the video player. Double-tap and hold to show actions" } }, "friendship": { @@ -218,10 +243,16 @@ "get_started": "Get Started", "log_in": "Log In" }, + "login": { + "title": "Welcome back", + "subtitle": "Log you in on the server you created your account on.", + "server_search_field": { + "placeholder": "Enter URL or search for your server" + } + }, "server_picker": { "title": "Mastodon is made of users in different servers.", - "subtitle": "Pick a server based on your interests, region, or a general purpose one.", - "subtitle_extend": "Pick a server based on your interests, region, or a general purpose one. Each server is operated by an entirely independent organization or individual.", + "subtitle": "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers.", "button": { "category": { "all": "All", @@ -248,8 +279,7 @@ "category": "CATEGORY" }, "input": { - "placeholder": "Search servers", - "search_servers_or_enter_url": "Search servers or enter URL" + "search_servers_or_enter_url": "Search communities or enter URL" }, "empty_state": { "finding_servers": "Finding available servers...", @@ -355,7 +385,7 @@ "published": "Published!", "Publishing": "Publishing post...", "accessibility": { - "logo_label": "Logo Button", + "logo_label": "Mastodon", "logo_hint": "Tap to scroll to top and tap again to previous location" } } @@ -439,11 +469,15 @@ "follows_you": "Follows You" }, "dashboard": { - "posts": "posts", - "following": "following", - "followers": "followers" + "my_posts": "posts", + "my_following": "following", + "my_followers": "followers", + "other_posts": "posts", + "other_following": "following", + "other_followers": "followers" }, "fields": { + "joined": "Joined", "add_row": "Add Row", "placeholder": { "label": "Label", @@ -717,6 +751,19 @@ }, "bookmark": { "title": "Bookmarks" + + }, + "followed_tags": { + "title": "Followed Tags", + "header": { + "posts": "posts", + "participants": "participants", + "posts_today": "posts today" + }, + "actions": { + "follow": "Follow", + "unfollow": "Unfollow" + } } } -} \ No newline at end of file +} diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index c8b2674b5..3f70434b6 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -23,6 +23,17 @@ 0FB3D33825E6401400AAD544 /* PickServerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D33725E6401400AAD544 /* PickServerCell.swift */; }; 164F0EBC267D4FE400249499 /* BoopSound.caf in Resources */ = {isa = PBXBuildFile; fileRef = 164F0EBB267D4FE400249499 /* BoopSound.caf */; }; 18BC7629F65E6DB12CB8416D /* Pods_Mastodon_MastodonUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C030226D3C73DCC23D67452 /* Pods_Mastodon_MastodonUITests.framework */; }; + 27D701F5292FC2D60031BCBB /* DataSourceFacade+URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27D701F4292FC2D60031BCBB /* DataSourceFacade+URL.swift */; }; + 2A1FE47C2938BB2600784BF1 /* FollowedTagsViewModel+DiffableDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A1FE47B2938BB2600784BF1 /* FollowedTagsViewModel+DiffableDataSource.swift */; }; + 2A1FE47E2938C11200784BF1 /* Collection+IsNotEmpty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A1FE47D2938C11200784BF1 /* Collection+IsNotEmpty.swift */; }; + 2A3F6FE3292ECB5E002E6DA7 /* FollowedTagsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A3F6FE2292ECB5E002E6DA7 /* FollowedTagsViewModel.swift */; }; + 2A3F6FE5292F6E44002E6DA7 /* FollowedTagsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A3F6FE4292F6E44002E6DA7 /* FollowedTagsTableViewCell.swift */; }; + 2A506CF4292CD85800059C37 /* FollowedTagsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A506CF3292CD85800059C37 /* FollowedTagsViewController.swift */; }; + 2A506CF6292D040100059C37 /* HashtagTimelineHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A506CF5292D040100059C37 /* HashtagTimelineHeaderView.swift */; }; + 2A76F75C2930D94700B3388D /* HashtagTimelineHeaderViewActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A76F75B2930D94700B3388D /* HashtagTimelineHeaderViewActionButton.swift */; }; + 2A82294F29262EE000D2A1F7 /* AppContext+NextAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A82294E29262EE000D2A1F7 /* AppContext+NextAccount.swift */; }; + 2AB12E4629362F27006BC925 /* DataSourceFacade+Translate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AB12E4529362F27006BC925 /* DataSourceFacade+Translate.swift */; }; + 2AE244482927831100BDBF7C /* UIImage+SFSymbols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AE244472927831100BDBF7C /* UIImage+SFSymbols.swift */; }; 2D198643261BF09500F0B013 /* SearchResultItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D198642261BF09500F0B013 /* SearchResultItem.swift */; }; 2D198649261C0B8500F0B013 /* SearchResultSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D198648261C0B8500F0B013 /* SearchResultSection.swift */; }; 2D206B8625F5FB0900143C56 /* Double.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D206B8525F5FB0900143C56 /* Double.swift */; }; @@ -64,7 +75,11 @@ 2DAC9E46262FC9FD0062E1A6 /* SuggestionAccountTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DAC9E45262FC9FD0062E1A6 /* SuggestionAccountTableViewCell.swift */; }; 2DCB73FD2615C13900EC03D4 /* SearchRecommendCollectionHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DCB73FC2615C13900EC03D4 /* SearchRecommendCollectionHeader.swift */; }; 2DE0FACE2615F7AD00CDF649 /* RecommendAccountSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DE0FACD2615F7AD00CDF649 /* RecommendAccountSection.swift */; }; - 2DF123A725C3B0210020F248 /* ActiveLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DF123A625C3B0210020F248 /* ActiveLabel.swift */; }; + 357FEEAF29523D470021C9DC /* MastodonSDKDynamic in Frameworks */ = {isa = PBXBuildFile; productRef = 357FEEAE29523D470021C9DC /* MastodonSDKDynamic */; }; + 357FEEB029523D470021C9DC /* MastodonSDKDynamic in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 357FEEAE29523D470021C9DC /* MastodonSDKDynamic */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; + 357FEEB229523D510021C9DC /* MastodonSDKDynamic in Frameworks */ = {isa = PBXBuildFile; productRef = 357FEEB129523D510021C9DC /* MastodonSDKDynamic */; }; + 357FEEB629523D5C0021C9DC /* MastodonSDKDynamic in Frameworks */ = {isa = PBXBuildFile; productRef = 357FEEB529523D5C0021C9DC /* MastodonSDKDynamic */; }; + 357FEEBA29523D660021C9DC /* MastodonSDKDynamic in Frameworks */ = {isa = PBXBuildFile; productRef = 357FEEB929523D660021C9DC /* MastodonSDKDynamic */; }; 5B24BBDA262DB14800A9381B /* ReportViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B24BBD7262DB14800A9381B /* ReportViewModel.swift */; }; 5B24BBDB262DB14800A9381B /* ReportStatusViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B24BBD8262DB14800A9381B /* ReportStatusViewModel+Diffable.swift */; }; 5B90C45E262599800002E742 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B90C456262599800002E742 /* SettingsViewModel.swift */; }; @@ -86,8 +101,16 @@ 62FD27D12893707600B205C5 /* BookmarkViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62FD27D02893707600B205C5 /* BookmarkViewController.swift */; }; 62FD27D32893707B00B205C5 /* BookmarkViewController+DataSourceProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62FD27D22893707B00B205C5 /* BookmarkViewController+DataSourceProvider.swift */; }; 62FD27D52893708A00B205C5 /* BookmarkViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62FD27D42893708A00B205C5 /* BookmarkViewModel+Diffable.swift */; }; + 85904C02293BC0EB0011C817 /* ImageProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85904C01293BC0EB0011C817 /* ImageProvider.swift */; }; + 85904C04293BC1940011C817 /* URLActivityItemWithMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85904C03293BC1940011C817 /* URLActivityItemWithMetadata.swift */; }; + 85BC11B32932414900E191CD /* AltViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85BC11B22932414900E191CD /* AltViewController.swift */; }; 87FFDA5D898A5C42ADCB35E7 /* Pods_Mastodon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A4ABE34829701A4496C5BB64 /* Pods_Mastodon.framework */; }; C24C97032922F30500BAE8CB /* RefreshControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = C24C97022922F30500BAE8CB /* RefreshControl.swift */; }; + D87BFC8B291D5C6B00FEE264 /* MastodonLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D87BFC8A291D5C6B00FEE264 /* MastodonLoginView.swift */; }; + D87BFC8D291EB81200FEE264 /* MastodonLoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D87BFC8C291EB81200FEE264 /* MastodonLoginViewModel.swift */; }; + D87BFC8F291EC26A00FEE264 /* MastodonLoginServerTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D87BFC8E291EC26A00FEE264 /* MastodonLoginServerTableViewCell.swift */; }; + D8916DC029211BE500124085 /* ContentSizedTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8916DBF29211BE500124085 /* ContentSizedTableView.swift */; }; + D8A6AB6C291C5136003AB663 /* MastodonLoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8A6AB6B291C5136003AB663 /* MastodonLoginViewController.swift */; }; DB0009A626AEE5DC009B9D2D /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = DB0009A926AEE5DC009B9D2D /* Intents.intentdefinition */; settings = {ATTRIBUTES = (codegen, ); }; }; DB0009A726AEE5DC009B9D2D /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = DB0009A926AEE5DC009B9D2D /* Intents.intentdefinition */; }; DB0140CF25C42AEE00F9F3CF /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140CE25C42AEE00F9F3CF /* OSLog.swift */; }; @@ -112,7 +135,6 @@ DB0618032785A7100030EE79 /* RegisterSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0618022785A7100030EE79 /* RegisterSection.swift */; }; DB0618052785A73D0030EE79 /* RegisterItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0618042785A73D0030EE79 /* RegisterItem.swift */; }; DB0A322E280EE9FD001729D2 /* DiscoveryIntroBannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0A322D280EE9FD001729D2 /* DiscoveryIntroBannerView.swift */; }; - DB0C947726A7FE840088FB11 /* NotificationAvatarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0C947626A7FE840088FB11 /* NotificationAvatarButton.swift */; }; DB0EF72B26FDB1D200347686 /* SidebarListCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0EF72A26FDB1D200347686 /* SidebarListCollectionViewCell.swift */; }; DB0EF72E26FDB24F00347686 /* SidebarListContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0EF72D26FDB24F00347686 /* SidebarListContentView.swift */; }; DB0F8150264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0F814F264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift */; }; @@ -152,10 +174,6 @@ DB1FD44425F26CCC004CFCFC /* PickServerSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1FD44325F26CCC004CFCFC /* PickServerSection.swift */; }; DB1FD45025F26FA1004CFCFC /* MastodonPickServerViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1FD44F25F26FA1004CFCFC /* MastodonPickServerViewModel+Diffable.swift */; }; DB1FD45A25F27898004CFCFC /* CategoryPickerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1FD45925F27898004CFCFC /* CategoryPickerItem.swift */; }; - DB22C92228E700A10082A9E9 /* MastodonSDK in Frameworks */ = {isa = PBXBuildFile; productRef = DB22C92128E700A10082A9E9 /* MastodonSDK */; }; - DB22C92428E700A80082A9E9 /* MastodonSDK in Frameworks */ = {isa = PBXBuildFile; productRef = DB22C92328E700A80082A9E9 /* MastodonSDK */; }; - DB22C92628E700AF0082A9E9 /* MastodonSDK in Frameworks */ = {isa = PBXBuildFile; productRef = DB22C92528E700AF0082A9E9 /* MastodonSDK */; }; - DB22C92828E700B70082A9E9 /* MastodonSDK in Frameworks */ = {isa = PBXBuildFile; productRef = DB22C92728E700B70082A9E9 /* MastodonSDK */; }; DB2B3ABC25E37E15007045F9 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DB2B3ABE25E37E15007045F9 /* InfoPlist.strings */; }; DB2F073525E8ECF000957B2D /* AuthenticationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2F073325E8ECF000957B2D /* AuthenticationViewModel.swift */; }; DB2FF510260B113300ADA9FE /* ComposeStatusPollExpiresOptionCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2FF50F260B113300ADA9FE /* ComposeStatusPollExpiresOptionCollectionViewCell.swift */; }; @@ -289,8 +307,6 @@ DB72602725E36A6F00235243 /* MastodonServerRulesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB72602625E36A6F00235243 /* MastodonServerRulesViewModel.swift */; }; DB7274F4273BB9B200577D95 /* ListBatchFetchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7274F3273BB9B200577D95 /* ListBatchFetchViewModel.swift */; }; DB73B490261F030A002E9E9F /* SafariActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB73B48F261F030A002E9E9F /* SafariActivity.swift */; }; - DB73BF4927140BA300781945 /* UICollectionViewDiffableDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB73BF4827140BA300781945 /* UICollectionViewDiffableDataSource.swift */; }; - DB73BF4B27140C0800781945 /* UITableViewDiffableDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB73BF4A27140C0800781945 /* UITableViewDiffableDataSource.swift */; }; DB75BF1E263C1C1B00EDBF1F /* CustomScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB75BF1D263C1C1B00EDBF1F /* CustomScheduler.swift */; }; DB789A0B25F9F2950071ACA0 /* ComposeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB789A0A25F9F2950071ACA0 /* ComposeViewController.swift */; }; DB789A1225F9F2CC0071ACA0 /* ComposeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB789A1125F9F2CC0071ACA0 /* ComposeViewModel.swift */; }; @@ -475,6 +491,7 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( + 357FEEB029523D470021C9DC /* MastodonSDKDynamic in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -513,6 +530,17 @@ 0FB3D33725E6401400AAD544 /* PickServerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerCell.swift; sourceTree = ""; }; 164F0EBB267D4FE400249499 /* BoopSound.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = BoopSound.caf; sourceTree = ""; }; 1D6D967E77A5357E2C6110D9 /* Pods-Mastodon.asdk - debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon.asdk - debug.xcconfig"; path = "Target Support Files/Pods-Mastodon/Pods-Mastodon.asdk - debug.xcconfig"; sourceTree = ""; }; + 27D701F4292FC2D60031BCBB /* DataSourceFacade+URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+URL.swift"; sourceTree = ""; }; + 2A1FE47B2938BB2600784BF1 /* FollowedTagsViewModel+DiffableDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FollowedTagsViewModel+DiffableDataSource.swift"; sourceTree = ""; }; + 2A1FE47D2938C11200784BF1 /* Collection+IsNotEmpty.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Collection+IsNotEmpty.swift"; sourceTree = ""; }; + 2A3F6FE2292ECB5E002E6DA7 /* FollowedTagsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowedTagsViewModel.swift; sourceTree = ""; }; + 2A3F6FE4292F6E44002E6DA7 /* FollowedTagsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowedTagsTableViewCell.swift; sourceTree = ""; }; + 2A506CF3292CD85800059C37 /* FollowedTagsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowedTagsViewController.swift; sourceTree = ""; }; + 2A506CF5292D040100059C37 /* HashtagTimelineHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagTimelineHeaderView.swift; sourceTree = ""; }; + 2A76F75B2930D94700B3388D /* HashtagTimelineHeaderViewActionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagTimelineHeaderViewActionButton.swift; sourceTree = ""; }; + 2A82294E29262EE000D2A1F7 /* AppContext+NextAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppContext+NextAccount.swift"; sourceTree = ""; }; + 2AB12E4529362F27006BC925 /* DataSourceFacade+Translate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+Translate.swift"; sourceTree = ""; }; + 2AE244472927831100BDBF7C /* UIImage+SFSymbols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+SFSymbols.swift"; sourceTree = ""; }; 2D198642261BF09500F0B013 /* SearchResultItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultItem.swift; sourceTree = ""; }; 2D198648261C0B8500F0B013 /* SearchResultSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultSection.swift; sourceTree = ""; }; 2D206B8525F5FB0900143C56 /* Double.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Double.swift; sourceTree = ""; }; @@ -555,7 +583,6 @@ 2DAC9E45262FC9FD0062E1A6 /* SuggestionAccountTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionAccountTableViewCell.swift; sourceTree = ""; }; 2DCB73FC2615C13900EC03D4 /* SearchRecommendCollectionHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchRecommendCollectionHeader.swift; sourceTree = ""; }; 2DE0FACD2615F7AD00CDF649 /* RecommendAccountSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecommendAccountSection.swift; sourceTree = ""; }; - 2DF123A625C3B0210020F248 /* ActiveLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveLabel.swift; sourceTree = ""; }; 2E1F6A67FDF9771D3E064FDC /* Pods-Mastodon.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon.debug.xcconfig"; path = "Target Support Files/Pods-Mastodon/Pods-Mastodon.debug.xcconfig"; sourceTree = ""; }; 3B7FD8F28DDA8FBCE5562B78 /* Pods-NotificationService.asdk - debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.asdk - debug.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.asdk - debug.xcconfig"; sourceTree = ""; }; 3C030226D3C73DCC23D67452 /* Pods_Mastodon_MastodonUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Mastodon_MastodonUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -591,6 +618,9 @@ 7CB58D292DA7ACEF179A9050 /* Pods-Mastodon.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon.profile.xcconfig"; path = "Target Support Files/Pods-Mastodon/Pods-Mastodon.profile.xcconfig"; sourceTree = ""; }; 7CEFFAE9AF9284B13C0A758D /* Pods-MastodonTests.asdk - debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MastodonTests.asdk - debug.xcconfig"; path = "Target Support Files/Pods-MastodonTests/Pods-MastodonTests.asdk - debug.xcconfig"; sourceTree = ""; }; 819CEC9DCAD8E8E7BD85A7BB /* Pods-Mastodon.asdk.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon.asdk.xcconfig"; path = "Target Support Files/Pods-Mastodon/Pods-Mastodon.asdk.xcconfig"; sourceTree = ""; }; + 85904C01293BC0EB0011C817 /* ImageProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageProvider.swift; sourceTree = ""; }; + 85904C03293BC1940011C817 /* URLActivityItemWithMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLActivityItemWithMetadata.swift; sourceTree = ""; }; + 85BC11B22932414900E191CD /* AltViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AltViewController.swift; sourceTree = ""; }; 8850E70A1D5FF51432E43653 /* Pods-Mastodon-MastodonUITests.asdk - release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-MastodonUITests.asdk - release.xcconfig"; path = "Target Support Files/Pods-Mastodon-MastodonUITests/Pods-Mastodon-MastodonUITests.asdk - release.xcconfig"; sourceTree = ""; }; 8E79CCBE51FBC3F7FE8CF49F /* Pods-MastodonTests.release snapshot.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MastodonTests.release snapshot.xcconfig"; path = "Target Support Files/Pods-MastodonTests/Pods-MastodonTests.release snapshot.xcconfig"; sourceTree = ""; }; 8ED8C4B1F1BA2DCFF2926BB1 /* Pods-Mastodon-NotificationService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-NotificationService.debug.xcconfig"; path = "Target Support Files/Pods-Mastodon-NotificationService/Pods-Mastodon-NotificationService.debug.xcconfig"; sourceTree = ""; }; @@ -608,6 +638,11 @@ C3789232A52F43529CA67E95 /* Pods-MastodonIntent.asdk - debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MastodonIntent.asdk - debug.xcconfig"; path = "Target Support Files/Pods-MastodonIntent/Pods-MastodonIntent.asdk - debug.xcconfig"; sourceTree = ""; }; CD92E0F10BDE4FE7C4B999F2 /* Pods_MastodonTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MastodonTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D7D7CF93E262178800077512 /* Pods-Mastodon-AppShared.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-AppShared.debug.xcconfig"; path = "Target Support Files/Pods-Mastodon-AppShared/Pods-Mastodon-AppShared.debug.xcconfig"; sourceTree = ""; }; + D87BFC8A291D5C6B00FEE264 /* MastodonLoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginView.swift; sourceTree = ""; }; + D87BFC8C291EB81200FEE264 /* MastodonLoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginViewModel.swift; sourceTree = ""; }; + D87BFC8E291EC26A00FEE264 /* MastodonLoginServerTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginServerTableViewCell.swift; sourceTree = ""; }; + D8916DBF29211BE500124085 /* ContentSizedTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentSizedTableView.swift; sourceTree = ""; }; + D8A6AB6B291C5136003AB663 /* MastodonLoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginViewController.swift; sourceTree = ""; }; DB0009A826AEE5DC009B9D2D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; name = Base; path = Base.lproj/Intents.intentdefinition; sourceTree = ""; }; DB0009AD26AEE5E4009B9D2D /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Intents.strings; sourceTree = ""; }; DB0140CE25C42AEE00F9F3CF /* OSLog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OSLog.swift; sourceTree = ""; }; @@ -632,9 +667,7 @@ DB0618022785A7100030EE79 /* RegisterSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterSection.swift; sourceTree = ""; }; DB0618042785A73D0030EE79 /* RegisterItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterItem.swift; sourceTree = ""; }; DB0618062785A8880030EE79 /* MastodonRegisterViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonRegisterViewModel+Diffable.swift"; sourceTree = ""; }; - DB0618092785B2AB0030EE79 /* MastodonRegisterAvatarTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonRegisterAvatarTableViewCell.swift; sourceTree = ""; }; DB0A322D280EE9FD001729D2 /* DiscoveryIntroBannerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryIntroBannerView.swift; sourceTree = ""; }; - DB0C947626A7FE840088FB11 /* NotificationAvatarButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationAvatarButton.swift; sourceTree = ""; }; DB0EF72A26FDB1D200347686 /* SidebarListCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarListCollectionViewCell.swift; sourceTree = ""; }; DB0EF72D26FDB24F00347686 /* SidebarListContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarListContentView.swift; sourceTree = ""; }; DB0F814E264CFFD300F2A12B /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -846,8 +879,6 @@ DB72602625E36A6F00235243 /* MastodonServerRulesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonServerRulesViewModel.swift; sourceTree = ""; }; DB7274F3273BB9B200577D95 /* ListBatchFetchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListBatchFetchViewModel.swift; sourceTree = ""; }; DB73B48F261F030A002E9E9F /* SafariActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariActivity.swift; sourceTree = ""; }; - DB73BF4827140BA300781945 /* UICollectionViewDiffableDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UICollectionViewDiffableDataSource.swift; sourceTree = ""; }; - DB73BF4A27140C0800781945 /* UITableViewDiffableDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITableViewDiffableDataSource.swift; sourceTree = ""; }; DB75BF1D263C1C1B00EDBF1F /* CustomScheduler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomScheduler.swift; sourceTree = ""; }; DB789A0A25F9F2950071ACA0 /* ComposeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeViewController.swift; sourceTree = ""; }; DB789A1125F9F2CC0071ACA0 /* ComposeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeViewModel.swift; sourceTree = ""; }; @@ -855,8 +886,6 @@ DB7A9F922818F33C0016AF98 /* MastodonServerRulesViewController+Debug.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonServerRulesViewController+Debug.swift"; sourceTree = ""; }; DB7F48442620241000796008 /* ProfileHeaderViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileHeaderViewModel.swift; sourceTree = ""; }; DB8190C52601FF0400020C08 /* AttachmentContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentContainerView.swift; sourceTree = ""; }; - DB8481142788121200BBEABA /* MastodonRegisterTextFieldTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonRegisterTextFieldTableViewCell.swift; sourceTree = ""; }; - DB84811627883C2600BBEABA /* MastodonRegisterPasswordHintTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonRegisterPasswordHintTableViewCell.swift; sourceTree = ""; }; DB848E32282B62A800A302CC /* ReportResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportResultView.swift; sourceTree = ""; }; DB852D1826FAEB6B00FC9D81 /* SidebarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarViewController.swift; sourceTree = ""; }; DB852D1B26FB021500FC9D81 /* RootSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootSplitViewController.swift; sourceTree = ""; }; @@ -888,6 +917,12 @@ DB938F0826240F3C00E5B6C1 /* RemoteThreadViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteThreadViewModel.swift; sourceTree = ""; }; DB938F0E2624119800E5B6C1 /* ThreadViewModel+LoadThreadState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ThreadViewModel+LoadThreadState.swift"; sourceTree = ""; }; DB938F1E2624382F00E5B6C1 /* ThreadViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ThreadViewModel+Diffable.swift"; sourceTree = ""; }; + DB96C25D292505FE00F3B85D /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Intents.strings; sourceTree = ""; }; + DB96C25E292505FF00F3B85D /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/InfoPlist.strings; sourceTree = ""; }; + DB96C25F292505FF00F3B85D /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = cs; path = cs.lproj/Intents.stringsdict; sourceTree = ""; }; + DB96C260292506D600F3B85D /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/Intents.strings; sourceTree = ""; }; + DB96C261292506D700F3B85D /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/InfoPlist.strings; sourceTree = ""; }; + DB96C262292506D700F3B85D /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = sl; path = sl.lproj/Intents.stringsdict; sourceTree = ""; }; DB98EB4627B0DFAA0082E365 /* ReportStatusViewModel+State.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ReportStatusViewModel+State.swift"; sourceTree = ""; }; DB98EB4827B0F0CD0082E365 /* ReportStatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportStatusTableViewCell.swift; sourceTree = ""; }; DB98EB4B27B0F2BC0082E365 /* ReportStatusTableViewCell+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ReportStatusTableViewCell+ViewModel.swift"; sourceTree = ""; }; @@ -1048,7 +1083,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DB22C92428E700A80082A9E9 /* MastodonSDK in Frameworks */, + 357FEEAF29523D470021C9DC /* MastodonSDKDynamic in Frameworks */, DBF96326262EC0A6001D8D25 /* AuthenticationServices.framework in Frameworks */, 87FFDA5D898A5C42ADCB35E7 /* Pods_Mastodon.framework in Frameworks */, ); @@ -1075,7 +1110,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DB22C92828E700B70082A9E9 /* MastodonSDK in Frameworks */, + 357FEEBA29523D660021C9DC /* MastodonSDKDynamic in Frameworks */, DB8FABC726AEC7B2008E5AF4 /* Intents.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1084,7 +1119,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DB22C92628E700AF0082A9E9 /* MastodonSDK in Frameworks */, + 357FEEB629523D5C0021C9DC /* MastodonSDKDynamic in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1092,7 +1127,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DB22C92228E700A10082A9E9 /* MastodonSDK in Frameworks */, + 357FEEB229523D510021C9DC /* MastodonSDKDynamic in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1107,6 +1142,8 @@ 0F202200261326E6000C64BF /* HashtagTimelineViewModel.swift */, 0F20220626134DA4000C64BF /* HashtagTimelineViewModel+Diffable.swift */, 0F20222C261457EE000C64BF /* HashtagTimelineViewModel+State.swift */, + 2A506CF5292D040100059C37 /* HashtagTimelineHeaderView.swift */, + 2A76F75B2930D94700B3388D /* HashtagTimelineHeaderViewActionButton.swift */, ); path = HashtagTimeline; sourceTree = ""; @@ -1209,6 +1246,17 @@ path = Pods; sourceTree = ""; }; + 2A506CF2292CD83B00059C37 /* FollowedTags */ = { + isa = PBXGroup; + children = ( + 2A506CF3292CD85800059C37 /* FollowedTagsViewController.swift */, + 2A3F6FE2292ECB5E002E6DA7 /* FollowedTagsViewModel.swift */, + 2A1FE47B2938BB2600784BF1 /* FollowedTagsViewModel+DiffableDataSource.swift */, + 2A3F6FE4292F6E44002E6DA7 /* FollowedTagsTableViewCell.swift */, + ); + path = FollowedTags; + sourceTree = ""; + }; 2D152A8A25C295B8009AA50C /* Content */ = { isa = PBXGroup; children = ( @@ -1497,9 +1545,22 @@ path = Bookmark; sourceTree = ""; }; + D8A6AB68291C50F3003AB663 /* Login */ = { + isa = PBXGroup; + children = ( + D8A6AB6B291C5136003AB663 /* MastodonLoginViewController.swift */, + D87BFC8A291D5C6B00FEE264 /* MastodonLoginView.swift */, + D87BFC8C291EB81200FEE264 /* MastodonLoginViewModel.swift */, + D87BFC8E291EC26A00FEE264 /* MastodonLoginServerTableViewCell.swift */, + D8916DBF29211BE500124085 /* ContentSizedTableView.swift */, + ); + path = Login; + sourceTree = ""; + }; DB01409B25C40BB600F9F3CF /* Onboarding */ = { isa = PBXGroup; children = ( + D8A6AB68291C50F3003AB663 /* Login */, DB68A03825E900CC00CFDF14 /* Share */, 0FAA0FDD25E0B5700017CCDE /* Welcome */, 0FAA102525E1125D0017CCDE /* PickServer */, @@ -1564,16 +1625,6 @@ path = Cell; sourceTree = ""; }; - DB06180B2785B2AF0030EE79 /* Cell */ = { - isa = PBXGroup; - children = ( - DB0618092785B2AB0030EE79 /* MastodonRegisterAvatarTableViewCell.swift */, - DB8481142788121200BBEABA /* MastodonRegisterTextFieldTableViewCell.swift */, - DB84811627883C2600BBEABA /* MastodonRegisterPasswordHintTableViewCell.swift */, - ); - path = Cell; - sourceTree = ""; - }; DB0A322F280EEA00001729D2 /* View */ = { isa = PBXGroup; children = ( @@ -1582,14 +1633,6 @@ path = View; sourceTree = ""; }; - DB0C947826A7FE950088FB11 /* Button */ = { - isa = PBXGroup; - children = ( - DB0C947626A7FE840088FB11 /* NotificationAvatarButton.swift */, - ); - path = Button; - sourceTree = ""; - }; DB0EF72C26FDB1D600347686 /* View */ = { isa = PBXGroup; children = ( @@ -1922,6 +1965,7 @@ DB6180F026391CAB0018D199 /* Image */, DB6180E1263919780018D199 /* Paging */, DB6180DC263918E30018D199 /* MediaPreviewViewController.swift */, + 85BC11B22932414900E191CD /* AltViewController.swift */, DB6180F926391F2E0018D199 /* MediaPreviewViewModel.swift */, ); path = MediaPreview; @@ -2069,10 +2113,12 @@ DB63F778279ABF9C00455B82 /* DataSourceFacade+Reblog.swift */, DB63F77A279ACAE500455B82 /* DataSourceFacade+Favorite.swift */, DB0FCB67279507EF006C02E2 /* DataSourceFacade+Meta.swift */, + 27D701F4292FC2D60031BCBB /* DataSourceFacade+URL.swift */, DB0FCB79279576A2006C02E2 /* DataSourceFacade+Thread.swift */, DB63F74627990B0600455B82 /* DataSourceFacade+Hashtag.swift */, DB63F7532799491600455B82 /* DataSourceFacade+SearchHistory.swift */, DB159C2A27A17BAC0068DC77 /* DataSourceFacade+Media.swift */, + 2AB12E4529362F27006BC925 /* DataSourceFacade+Translate.swift */, DB697DD5278F4C29004EF2F7 /* DataSourceProvider.swift */, DB697DDA278F4DE3004EF2F7 /* DataSourceProvider+StatusTableViewCellDelegate.swift */, DB023D2927A0FE5C005AC798 /* DataSourceProvider+NotificationTableViewCellDelegate.swift */, @@ -2205,7 +2251,7 @@ DB8AF56225C138BC002E6C99 /* Extension */ = { isa = PBXGroup; children = ( - 2DF123A625C3B0210020F248 /* ActiveLabel.swift */, + 2A82294E29262EE000D2A1F7 /* AppContext+NextAccount.swift */, 5DF1056325F887CB00D6C0D4 /* AVPlayer.swift */, 2D206B8525F5FB0900143C56 /* Double.swift */, DBB3BA2926A81C020004F2D4 /* FLAnimatedImageView.swift */, @@ -2225,9 +2271,9 @@ 2D3F9E0325DFA133004262D9 /* UITapGestureRecognizer.swift */, 2D84350425FF858100EECE90 /* UIScrollView.swift */, DB9E0D6E25EE008500CFDD76 /* UIInterpolatingMotionEffect.swift */, + 2AE244472927831100BDBF7C /* UIImage+SFSymbols.swift */, DBCC3B2F261440A50045B23D /* UITabBarController.swift */, - DB73BF4827140BA300781945 /* UICollectionViewDiffableDataSource.swift */, - DB73BF4A27140C0800781945 /* UITableViewDiffableDataSource.swift */, + 2A1FE47D2938C11200784BF1 /* Collection+IsNotEmpty.swift */, ); path = Extension; sourceTree = ""; @@ -2336,7 +2382,6 @@ isa = PBXGroup; children = ( DB63F765279A5E5600455B82 /* NotificationTimeline */, - DB0C947826A7FE950088FB11 /* Button */, 2D35237F26256F470031AF25 /* Cell */, DB9D6BF725E4F5690051B173 /* NotificationViewController.swift */, 2D607AD726242FC500B70763 /* NotificationViewModel.swift */, @@ -2347,6 +2392,7 @@ DB9D6C0825E4F5A60051B173 /* Profile */ = { isa = PBXGroup; children = ( + 2A506CF2292CD83B00059C37 /* FollowedTags */, 62047EBE28874C8F00A3BA5D /* Bookmark */, DBB525462611ED57002F1F29 /* Header */, DBB525262611EBDA002F1F29 /* Paging */, @@ -2495,6 +2541,8 @@ isa = PBXGroup; children = ( DBF3B7402733EB9400E21627 /* MastodonLocalCode.swift */, + 85904C01293BC0EB0011C817 /* ImageProvider.swift */, + 85904C03293BC1940011C817 /* URLActivityItemWithMetadata.swift */, ); path = Helper; sourceTree = ""; @@ -2540,7 +2588,6 @@ DBE0821A25CD382900FD6BBD /* Register */ = { isa = PBXGroup; children = ( - DB06180B2785B2AF0030EE79 /* Cell */, DBE0821425CD382600FD6BBD /* MastodonRegisterViewController.swift */, 2D939AE725EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift */, DBE0822325CD3F1E00FD6BBD /* MastodonRegisterViewModel.swift */, @@ -2683,9 +2730,9 @@ DB427DCF25BAA00100D1B89D /* Frameworks */, DB89BA0825C10FD0008580ED /* Embed Frameworks */, DBF8AE1B263293E400C9C23C /* Embed Foundation Extensions */, - DB3D100425BAA71500EAA174 /* ShellScript */, - DB025B8E278D6448002F581E /* ShellScript */, - DB697DD2278F48D5004EF2F7 /* ShellScript */, + DB3D100425BAA71500EAA174 /* Run SwiftGen */, + DB025B8E278D6448002F581E /* Run Sourcery: Core Data */, + DB697DD2278F48D5004EF2F7 /* Run Sourcery */, ); buildRules = ( ); @@ -2696,7 +2743,7 @@ ); name = Mastodon; packageProductDependencies = ( - DB22C92328E700A80082A9E9 /* MastodonSDK */, + 357FEEAE29523D470021C9DC /* MastodonSDKDynamic */, ); productName = Mastodon; productReference = DB427DD225BAA00100D1B89D /* Mastodon.app */; @@ -2755,7 +2802,7 @@ ); name = MastodonIntent; packageProductDependencies = ( - DB22C92728E700B70082A9E9 /* MastodonSDK */, + 357FEEB929523D660021C9DC /* MastodonSDKDynamic */, ); productName = MastodonIntent; productReference = DB8FABC626AEC7B2008E5AF4 /* MastodonIntent.appex */; @@ -2775,7 +2822,7 @@ ); name = ShareActionExtension; packageProductDependencies = ( - DB22C92528E700AF0082A9E9 /* MastodonSDK */, + 357FEEB529523D5C0021C9DC /* MastodonSDKDynamic */, ); productName = ShareActionExtension; productReference = DBC6461226A170AB00B0E31B /* ShareActionExtension.appex */; @@ -2795,7 +2842,7 @@ ); name = NotificationService; packageProductDependencies = ( - DB22C92128E700A10082A9E9 /* MastodonSDK */, + 357FEEB129523D510021C9DC /* MastodonSDKDynamic */, ); productName = NotificationService; productReference = DBF8AE13263293E400C9C23C /* NotificationService.appex */; @@ -2863,6 +2910,8 @@ gd, "es-AR", fi, + cs, + sl, ); mainGroup = DB427DC925BAA00100D1B89D; packageReferences = ( @@ -3001,7 +3050,7 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - DB025B8E278D6448002F581E /* ShellScript */ = { + DB025B8E278D6448002F581E /* Run Sourcery: Core Data */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 12; @@ -3011,6 +3060,7 @@ ); inputPaths = ( ); + name = "Run Sourcery: Core Data"; outputFileListPaths = ( ); outputPaths = ( @@ -3019,7 +3069,7 @@ shellPath = /bin/sh; shellScript = "if [[ -f \"${PODS_ROOT}/Sourcery/bin/sourcery\" ]]; then\n \"${PODS_ROOT}/Sourcery/bin/sourcery\" --config ./MastodonSDK/Sources/CoreDataStack\nelse\n echo \"warning: Sourcery is not installed. Run 'pod install --repo-update' to install it.\"\nfi\n"; }; - DB3D100425BAA71500EAA174 /* ShellScript */ = { + DB3D100425BAA71500EAA174 /* Run SwiftGen */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 12; @@ -3029,6 +3079,7 @@ ); inputPaths = ( ); + name = "Run SwiftGen"; outputFileListPaths = ( ); outputPaths = ( @@ -3037,7 +3088,7 @@ shellPath = /bin/sh; shellScript = "if [[ -f \"${PODS_ROOT}/SwiftGen/bin/swiftgen\" ]]; then\n \"${PODS_ROOT}/SwiftGen/bin/swiftgen\" \nelse\n echo \"warning: SwiftGen is not installed. Run 'pod install --repo-update' to install it.\"\nfi\n"; }; - DB697DD2278F48D5004EF2F7 /* ShellScript */ = { + DB697DD2278F48D5004EF2F7 /* Run Sourcery */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 12; @@ -3047,6 +3098,7 @@ ); inputPaths = ( ); + name = "Run Sourcery"; outputFileListPaths = ( ); outputPaths = ( @@ -3116,6 +3168,7 @@ DB03A793272A7E5700EE37C5 /* SidebarListHeaderView.swift in Sources */, DB4FFC2B269EC39600D62E92 /* SearchToSearchDetailViewControllerAnimatedTransitioning.swift in Sources */, DB5B7298273112C800081888 /* FollowingListViewModel.swift in Sources */, + 27D701F5292FC2D60031BCBB /* DataSourceFacade+URL.swift in Sources */, 0FB3D2F725E4C24D00AAD544 /* MastodonPickServerViewModel.swift in Sources */, DB5B54AE2833C15F00DEF8B2 /* UserListViewModel+Diffable.swift in Sources */, DB482A3F261331E8008AE74C /* UserTimelineViewModel+State.swift in Sources */, @@ -3142,6 +3195,7 @@ 62FD27D52893708A00B205C5 /* BookmarkViewModel+Diffable.swift in Sources */, DB72602725E36A6F00235243 /* MastodonServerRulesViewModel.swift in Sources */, 2D364F7225E66D7500204FDC /* MastodonResendEmailViewController.swift in Sources */, + 85904C04293BC1940011C817 /* URLActivityItemWithMetadata.swift in Sources */, DB68A06325E905E000CFDF14 /* UIApplication.swift in Sources */, DB02CDAB26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift in Sources */, DBEFCD80282A2AA900C0ABEA /* ReportServerRulesViewModel.swift in Sources */, @@ -3185,6 +3239,7 @@ DB5B54A32833BD1A00DEF8B2 /* UserListViewModel.swift in Sources */, DBF156DF2701B17600EC00B7 /* SidebarAddAccountCollectionViewCell.swift in Sources */, DB0617F1278413D00030EE79 /* PickServerServerSectionTableHeaderView.swift in Sources */, + D87BFC8F291EC26A00FEE264 /* MastodonLoginServerTableViewCell.swift in Sources */, DB0FCB7E27958957006C02E2 /* StatusThreadRootTableViewCell+ViewModel.swift in Sources */, DB789A0B25F9F2950071ACA0 /* ComposeViewController.swift in Sources */, DB938F0926240F3C00E5B6C1 /* RemoteThreadViewModel.swift in Sources */, @@ -3207,7 +3262,6 @@ DB603113279EBEBA00A935FE /* DataSourceFacade+Block.swift in Sources */, DB63F777279A9A2A00455B82 /* NotificationView+Configuration.swift in Sources */, DB029E95266A20430062874E /* MastodonAuthenticationController.swift in Sources */, - DB0C947726A7FE840088FB11 /* NotificationAvatarButton.swift in Sources */, 5B90C461262599800002E742 /* SettingsLinkTableViewCell.swift in Sources */, DB6180DD263918E30018D199 /* MediaPreviewViewController.swift in Sources */, DBE3CDEC261C6B2900430CC6 /* FavoriteViewController.swift in Sources */, @@ -3230,6 +3284,7 @@ DBF1572F27046F1A00EC00B7 /* SecondaryPlaceholderViewController.swift in Sources */, 2D4AD8A826316D3500613EFC /* SelectedAccountItem.swift in Sources */, DBE3CDFB261C6CA500430CC6 /* FavoriteViewModel.swift in Sources */, + 2AB12E4629362F27006BC925 /* DataSourceFacade+Translate.swift in Sources */, DBE3CE01261D623D00430CC6 /* FavoriteViewModel+State.swift in Sources */, 2D82BA0525E7897700E36F0F /* MastodonResendEmailViewModelNavigationDelegateShim.swift in Sources */, 2D38F1EB25CD477000561493 /* HomeTimelineViewModel+LoadLatestState.swift in Sources */, @@ -3246,12 +3301,11 @@ DB5B549D2833A67400DEF8B2 /* FamiliarFollowersViewModel.swift in Sources */, DBB9759C262462E1004620BD /* ThreadMetaView.swift in Sources */, DB5B729E273113F300081888 /* FollowingListViewModel+State.swift in Sources */, - 2DF123A725C3B0210020F248 /* ActiveLabel.swift in Sources */, DBF9814C265E339500E4BA07 /* ProfileFieldAddEntryCollectionViewCell.swift in Sources */, DB63F76227996B6600455B82 /* SearchHistoryViewController+DataSourceProvider.swift in Sources */, - DB73BF4927140BA300781945 /* UICollectionViewDiffableDataSource.swift in Sources */, DBA5E7AB263BD3F5004598BB /* TimelineTableViewCellContextMenuConfiguration.swift in Sources */, DB73B490261F030A002E9E9F /* SafariActivity.swift in Sources */, + 2AE244482927831100BDBF7C /* UIImage+SFSymbols.swift in Sources */, DB63F7492799126300455B82 /* FollowerListViewController+DataSourceProvider.swift in Sources */, 2D198643261BF09500F0B013 /* SearchResultItem.swift in Sources */, 2DAC9E38262FC2320062E1A6 /* SuggestionAccountViewController.swift in Sources */, @@ -3262,20 +3316,24 @@ 2D82B9FF25E7863200E36F0F /* OnboardingViewControllerAppearance.swift in Sources */, DB025B78278D606A002F581E /* StatusItem.swift in Sources */, DB697DD4278F4927004EF2F7 /* StatusTableViewCellDelegate.swift in Sources */, + 2A506CF6292D040100059C37 /* HashtagTimelineHeaderView.swift in Sources */, DBA5E7A5263BD28C004598BB /* ContextMenuImagePreviewViewModel.swift in Sources */, DB3E6FF52807C40300B035AE /* DiscoveryForYouViewController.swift in Sources */, 2D38F1E525CD46C100561493 /* HomeTimelineViewModel.swift in Sources */, DB0FCB842796B2A2006C02E2 /* FavoriteViewController+DataSourceProvider.swift in Sources */, DB0FCB68279507EF006C02E2 /* DataSourceFacade+Meta.swift in Sources */, + 2A1FE47E2938C11200784BF1 /* Collection+IsNotEmpty.swift in Sources */, 2D84350525FF858100EECE90 /* UIScrollView.swift in Sources */, 6213AF5A28939C8400BCADB6 /* BookmarkViewModel.swift in Sources */, 5B24BBDB262DB14800A9381B /* ReportStatusViewModel+Diffable.swift in Sources */, DB4F0968269ED8AD00D62E92 /* SearchHistoryTableHeaderView.swift in Sources */, 0FB3D2FE25E4CB6400AAD544 /* OnboardingHeadlineTableViewCell.swift in Sources */, 5DA732CC2629CEF500A92342 /* UIView+Remove.swift in Sources */, + 2A506CF4292CD85800059C37 /* FollowedTagsViewController.swift in Sources */, DB1D843026566512000346B3 /* KeyboardPreference.swift in Sources */, DB852D1926FAEB6B00FC9D81 /* SidebarViewController.swift in Sources */, 2D206B9225F60EA700143C56 /* UIControl.swift in Sources */, + 85904C02293BC0EB0011C817 /* ImageProvider.swift in Sources */, DBDFF1932805554900557A48 /* DiscoveryPostsViewModel.swift in Sources */, DB3E6FE72806A7A200B035AE /* DiscoveryItem.swift in Sources */, DB8AF55D25C138B7002E6C99 /* UIViewController.swift in Sources */, @@ -3304,9 +3362,11 @@ DB63F769279A5EBB00455B82 /* NotificationTimelineViewModel+Diffable.swift in Sources */, DBFEEC9B279BDDD9004F81DD /* ProfileAboutViewModel+Diffable.swift in Sources */, DBB525562611EDCA002F1F29 /* UserTimelineViewModel.swift in Sources */, + D8916DC029211BE500124085 /* ContentSizedTableView.swift in Sources */, DB0618012785732C0030EE79 /* ServerRulesTableViewCell.swift in Sources */, DB98EB5C27B10A730082E365 /* ReportSupplementaryViewModel.swift in Sources */, DB0617EF277F12720030EE79 /* NavigationActionView.swift in Sources */, + D87BFC8D291EB81200FEE264 /* MastodonLoginViewModel.swift in Sources */, DB1FD43625F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift in Sources */, 2D939AE825EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift in Sources */, DB1D186C25EF5BA7003F1F23 /* PollTableView.swift in Sources */, @@ -3332,7 +3392,6 @@ DB1E346825F518E20079D7DF /* CategoryPickerSection.swift in Sources */, DB7274F4273BB9B200577D95 /* ListBatchFetchViewModel.swift in Sources */, DB0618052785A73D0030EE79 /* RegisterItem.swift in Sources */, - DB73BF4B27140C0800781945 /* UITableViewDiffableDataSource.swift in Sources */, DBB525642612C988002F1F29 /* MeProfileViewModel.swift in Sources */, DB3EA8EF281B837000598866 /* DiscoveryCommunityViewController+DataSourceProvider.swift in Sources */, DB6B74EF272FB55000C70B6E /* FollowerListViewController.swift in Sources */, @@ -3343,6 +3402,7 @@ DB938EED2623F79B00E5B6C1 /* ThreadViewModel.swift in Sources */, DB6988DE2848D11C002398EF /* PagerTabStripNavigateable.swift in Sources */, 2DCB73FD2615C13900EC03D4 /* SearchRecommendCollectionHeader.swift in Sources */, + 2A1FE47C2938BB2600784BF1 /* FollowedTagsViewModel+DiffableDataSource.swift in Sources */, DB852D1C26FB021500FC9D81 /* RootSplitViewController.swift in Sources */, DB697DD1278F4871004EF2F7 /* AutoGenerateTableViewDelegate.swift in Sources */, DB02CDBF2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift in Sources */, @@ -3375,6 +3435,7 @@ DBCC3B8F26148F7B0045B23D /* CachedProfileViewModel.swift in Sources */, DB4F097526A037F500D62E92 /* SearchHistoryViewModel.swift in Sources */, DB3EA8E9281B7A3700598866 /* DiscoveryCommunityViewModel.swift in Sources */, + D87BFC8B291D5C6B00FEE264 /* MastodonLoginView.swift in Sources */, DB6180F826391D660018D199 /* MediaPreviewingViewController.swift in Sources */, DBEFCD71282A12B200C0ABEA /* ReportReasonViewController.swift in Sources */, DB0140CF25C42AEE00F9F3CF /* OSLog.swift in Sources */, @@ -3433,16 +3494,20 @@ DBCC3B30261440A50045B23D /* UITabBarController.swift in Sources */, DB3E6FE42806A5B800B035AE /* DiscoverySection.swift in Sources */, DB8190C62601FF0400020C08 /* AttachmentContainerView.swift in Sources */, + 2A76F75C2930D94700B3388D /* HashtagTimelineHeaderViewActionButton.swift in Sources */, DB697DDB278F4DE3004EF2F7 /* DataSourceProvider+StatusTableViewCellDelegate.swift in Sources */, DB87D4512609CF1E00D12C0D /* ComposeStatusPollOptionAppendEntryCollectionViewCell.swift in Sources */, DBB45B5627B39FC9002DC5A7 /* MediaPreviewVideoViewController.swift in Sources */, + D8A6AB6C291C5136003AB663 /* MastodonLoginViewController.swift in Sources */, DB0FCB8027968F70006C02E2 /* MastodonStatusThreadViewModel.swift in Sources */, DB67D08627312E67006A36CF /* WizardViewController.swift in Sources */, DB6746EB278ED8B0008A6B94 /* PollOptionView+Configuration.swift in Sources */, DB0F9D54283EB3C000379AF8 /* ProfileHeaderView+ViewModel.swift in Sources */, + 2A3F6FE3292ECB5E002E6DA7 /* FollowedTagsViewModel.swift in Sources */, DBFEEC99279BDCDE004F81DD /* ProfileAboutViewModel.swift in Sources */, 2D198649261C0B8500F0B013 /* SearchResultSection.swift in Sources */, DB4F097B26A039FF00D62E92 /* SearchHistorySection.swift in Sources */, + 2A82294F29262EE000D2A1F7 /* AppContext+NextAccount.swift in Sources */, DBB525302611EBF3002F1F29 /* ProfilePagingViewModel.swift in Sources */, DB9F58EC26EF435000E7BBE9 /* AccountViewController.swift in Sources */, 2D5A3D6225CFD9CB002347D6 /* HomeTimelineViewController+DebugAction.swift in Sources */, @@ -3455,6 +3520,7 @@ DB9D6BFF25E4F5940051B173 /* ProfileViewController.swift in Sources */, 2D4AD8A226316CD200613EFC /* SelectedAccountSection.swift in Sources */, DB3EA8F1281B9EF600598866 /* DiscoveryCommunityViewModel+Diffable.swift in Sources */, + 85BC11B32932414900E191CD /* AltViewController.swift in Sources */, DB63F775279A997D00455B82 /* NotificationTableViewCell+ViewModel.swift in Sources */, DB98EB5927B109890082E365 /* ReportSupplementaryViewController.swift in Sources */, DB0617EB277EF3820030EE79 /* GradientBorderView.swift in Sources */, @@ -3469,6 +3535,7 @@ 5BB04FF5262F0E6D0043BFF6 /* ReportSection.swift in Sources */, DBEFCD82282A2AB100C0ABEA /* ReportServerRulesView.swift in Sources */, DBA94436265CBB7400C537E1 /* ProfileFieldItem.swift in Sources */, + 2A3F6FE5292F6E44002E6DA7 /* FollowedTagsTableViewCell.swift in Sources */, C24C97032922F30500BAE8CB /* RefreshControl.swift in Sources */, DB023D2A27A0FE5C005AC798 /* DataSourceProvider+NotificationTableViewCellDelegate.swift in Sources */, DB98EB6027B10E150082E365 /* ReportCommentTableViewCell.swift in Sources */, @@ -3593,6 +3660,8 @@ DBC9E3A6282E15190063A4D9 /* gd */, DBC9E3A9282E17DF0063A4D9 /* es-AR */, DB8F40042835EE5E006E7513 /* fi */, + DB96C25D292505FE00F3B85D /* cs */, + DB96C260292506D600F3B85D /* sl */, ); name = Intents.intentdefinition; sourceTree = ""; @@ -3624,6 +3693,8 @@ DBC9E3A7282E15190063A4D9 /* gd */, DBC9E3AA282E17DF0063A4D9 /* es-AR */, DB8F40052835EE5E006E7513 /* fi */, + DB96C25E292505FF00F3B85D /* cs */, + DB96C261292506D700F3B85D /* sl */, ); name = InfoPlist.strings; sourceTree = ""; @@ -3671,6 +3742,8 @@ DBC9E3A8282E15190063A4D9 /* gd */, DBC9E3AB282E17DF0063A4D9 /* es-AR */, DB8F40062835EE5E006E7513 /* fi */, + DB96C25F292505FF00F3B85D /* cs */, + DB96C262292506D700F3B85D /* sl */, ); name = Intents.stringsdict; sourceTree = ""; @@ -3740,7 +3813,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INTENTS_CODEGEN_LANGUAGE = Swift; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -3798,7 +3871,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INTENTS_CODEGEN_LANGUAGE = Swift; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; @@ -3825,7 +3898,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.4.7; + MARKETING_VERSION = 1.4.10; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3849,12 +3922,13 @@ CODE_SIGN_STYLE = Automatic; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; + EXCLUDED_SOURCE_FILE_NAMES = "Mastodon/Resources/Preview\\ Assets.xcassets"; INFOPLIST_FILE = Mastodon/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.4.7; + MARKETING_VERSION = 1.4.10; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -4000,7 +4074,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INTENTS_CODEGEN_LANGUAGE = Swift; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -4027,7 +4101,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.4.7; + MARKETING_VERSION = 1.4.10; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -4282,7 +4356,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INTENTS_CODEGEN_LANGUAGE = Swift; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; @@ -4310,7 +4384,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.4.7; + MARKETING_VERSION = 1.4.10; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -4555,21 +4629,21 @@ /* End XCConfigurationList section */ /* Begin XCSwiftPackageProductDependency section */ - DB22C92128E700A10082A9E9 /* MastodonSDK */ = { + 357FEEAE29523D470021C9DC /* MastodonSDKDynamic */ = { isa = XCSwiftPackageProductDependency; - productName = MastodonSDK; + productName = MastodonSDKDynamic; }; - DB22C92328E700A80082A9E9 /* MastodonSDK */ = { + 357FEEB129523D510021C9DC /* MastodonSDKDynamic */ = { isa = XCSwiftPackageProductDependency; - productName = MastodonSDK; + productName = MastodonSDKDynamic; }; - DB22C92528E700AF0082A9E9 /* MastodonSDK */ = { + 357FEEB529523D5C0021C9DC /* MastodonSDKDynamic */ = { isa = XCSwiftPackageProductDependency; - productName = MastodonSDK; + productName = MastodonSDKDynamic; }; - DB22C92728E700B70082A9E9 /* MastodonSDK */ = { + 357FEEB929523D660021C9DC /* MastodonSDKDynamic */ = { isa = XCSwiftPackageProductDependency; - productName = MastodonSDK; + productName = MastodonSDKDynamic; }; /* End XCSwiftPackageProductDependency section */ }; diff --git a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist index 78a3a9e70..b2b5b312a 100644 --- a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist @@ -117,12 +117,12 @@ NotificationService.xcscheme_^#shared#^_ orderHint - 16 + 17 ShareActionExtension.xcscheme_^#shared#^_ orderHint - 17 + 16 SuppressBuildableAutocreation diff --git a/Mastodon/Activity/SafariActivity.swift b/Mastodon/Activity/SafariActivity.swift index 62a193eaf..e20a9f815 100644 --- a/Mastodon/Activity/SafariActivity.swift +++ b/Mastodon/Activity/SafariActivity.swift @@ -58,7 +58,7 @@ final class SafariActivity: UIActivity { } Task { - await sceneCoordinator?.present(scene: .safari(url: url as URL), from: nil, transition: .safariPresent(animated: true, completion: nil)) + _ = await sceneCoordinator?.present(scene: .safari(url: url as URL), from: nil, transition: .safariPresent(animated: true, completion: nil)) activityDidFinish(true) } } diff --git a/Mastodon/Coordinator/SceneCoordinator.swift b/Mastodon/Coordinator/SceneCoordinator.swift index 8a0825969..3dc73c020 100644 --- a/Mastodon/Coordinator/SceneCoordinator.swift +++ b/Mastodon/Coordinator/SceneCoordinator.swift @@ -71,8 +71,8 @@ final public class SceneCoordinator { self.setup() try await Task.sleep(nanoseconds: .second * 1) - // redirect to notification tab - self.switchToTabBar(tab: .notification) + // redirect to notifications tab + self.switchToTabBar(tab: .notifications) // Delay in next run loop DispatchQueue.main.async { [weak self] in @@ -149,6 +149,7 @@ extension SceneCoordinator { case mastodonConfirmEmail(viewModel: MastodonConfirmEmailViewModel) case mastodonResendEmail(viewModel: MastodonResendEmailViewModel) case mastodonWebView(viewModel: WebViewModel) + case mastodonLogin // search case searchDetail(viewModel: SearchDetailViewModel) @@ -172,6 +173,7 @@ extension SceneCoordinator { case rebloggedBy(viewModel: UserListViewModel) case favoritedBy(viewModel: UserListViewModel) case bookmark(viewModel: BookmarkViewModel) + case followedTags(viewModel: FollowedTagsViewModel) // setting case settings(viewModel: SettingsViewModel) @@ -199,6 +201,7 @@ extension SceneCoordinator { case .welcome, .mastodonPickServer, .mastodonRegister, + .mastodonLogin, .mastodonServerRules, .mastodonConfirmEmail, .mastodonResendEmail: @@ -339,7 +342,7 @@ extension SceneCoordinator { case .custom(let transitioningDelegate): viewController.modalPresentationStyle = .custom viewController.transitioningDelegate = transitioningDelegate - // viewController.modalPresentationCapturesStatusBarAppearance = true + viewController.modalPresentationCapturesStatusBarAppearance = true (splitViewController ?? presentingViewController)?.present(viewController, animated: true, completion: nil) case .customPush(let animated): @@ -403,6 +406,13 @@ private extension SceneCoordinator { let _viewController = MastodonConfirmEmailViewController() _viewController.viewModel = viewModel viewController = _viewController + case .mastodonLogin: + let loginViewController = MastodonLoginViewController(appContext: appContext, + authenticationViewModel: AuthenticationViewModel(context: appContext, coordinator: self, isAuthenticationExist: false), + sceneCoordinator: self) + loginViewController.delegate = self + + viewController = loginViewController case .mastodonResendEmail(let viewModel): let _viewController = MastodonResendEmailViewController() _viewController.viewModel = viewModel @@ -439,6 +449,10 @@ private extension SceneCoordinator { let _viewController = BookmarkViewController() _viewController.viewModel = viewModel viewController = _viewController + case .followedTags(let viewModel): + let _viewController = FollowedTagsViewController() + _viewController.viewModel = viewModel + viewController = _viewController case .favorite(let viewModel): let _viewController = FavoriteViewController() _viewController.viewModel = viewModel @@ -529,5 +543,16 @@ private extension SceneCoordinator { needs?.context = appContext needs?.coordinator = self } - +} + +//MARK: - MastodonLoginViewControllerDelegate + +extension SceneCoordinator: MastodonLoginViewControllerDelegate { + func backButtonPressed(_ viewController: MastodonLoginViewController) { + viewController.navigationController?.popViewController(animated: true) + } + + func nextButtonPressed(_ viewController: MastodonLoginViewController) { + viewController.login() + } } diff --git a/Mastodon/Diffable/Notification/NotificationSection.swift b/Mastodon/Diffable/Notification/NotificationSection.swift index 387affbc7..0271aac20 100644 --- a/Mastodon/Diffable/Notification/NotificationSection.swift +++ b/Mastodon/Diffable/Notification/NotificationSection.swift @@ -76,6 +76,7 @@ extension NotificationSection { viewModel: NotificationTableViewCell.ViewModel, configuration: Configuration ) { + cell.notificationView.viewModel.context = context cell.notificationView.viewModel.authContext = configuration.authContext StatusSection.setupStatusPollDataSource( diff --git a/Mastodon/Diffable/Onboarding/CategoryPickerSection.swift b/Mastodon/Diffable/Onboarding/CategoryPickerSection.swift index b53b378d6..191d4d166 100644 --- a/Mastodon/Diffable/Onboarding/CategoryPickerSection.swift +++ b/Mastodon/Diffable/Onboarding/CategoryPickerSection.swift @@ -21,7 +21,6 @@ extension CategoryPickerSection { UICollectionViewDiffableDataSource(collectionView: collectionView) { [weak dependency] collectionView, indexPath, item -> UICollectionViewCell? in guard let _ = dependency else { return nil } let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: PickServerCategoryCollectionViewCell.self), for: indexPath) as! PickServerCategoryCollectionViewCell - cell.categoryView.emojiLabel.text = item.emoji cell.categoryView.titleLabel.text = item.title cell.observe(\.isSelected, options: [.initial, .new]) { cell, _ in cell.categoryView.highlightedIndicatorView.alpha = cell.isSelected ? 1 : 0 diff --git a/Mastodon/Diffable/Onboarding/PickServerSection.swift b/Mastodon/Diffable/Onboarding/PickServerSection.swift index 01a31f6f6..1af5b23c6 100644 --- a/Mastodon/Diffable/Onboarding/PickServerSection.swift +++ b/Mastodon/Diffable/Onboarding/PickServerSection.swift @@ -18,16 +18,14 @@ enum PickServerSection: Equatable, Hashable { extension PickServerSection { static func tableViewDiffableDataSource( for tableView: UITableView, - dependency: NeedsDependency, - pickServerCellDelegate: PickServerCellDelegate + dependency: NeedsDependency ) -> UITableViewDiffableDataSource { tableView.register(OnboardingHeadlineTableViewCell.self, forCellReuseIdentifier: String(describing: OnboardingHeadlineTableViewCell.self)) tableView.register(PickServerCell.self, forCellReuseIdentifier: String(describing: PickServerCell.self)) tableView.register(PickServerLoaderTableViewCell.self, forCellReuseIdentifier: String(describing: PickServerLoaderTableViewCell.self)) return UITableViewDiffableDataSource(tableView: tableView) { [ - weak dependency, - weak pickServerCellDelegate + weak dependency ] tableView, indexPath, item -> UITableViewCell? in guard let _ = dependency else { return nil } switch item { @@ -37,7 +35,6 @@ extension PickServerSection { case .server(let server, let attribute): let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PickServerCell.self), for: indexPath) as! PickServerCell PickServerSection.configure(cell: cell, server: server, attribute: attribute) - cell.delegate = pickServerCellDelegate return cell case .loader(let attribute): let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PickServerLoaderTableViewCell.self), for: indexPath) as! PickServerLoaderTableViewCell @@ -80,7 +77,7 @@ extension PickServerSection { let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.lineHeightMultiple = 1.12 let valueAttributedString = NSAttributedString( - string: parseUsersCount(server.totalUsers), + string: server.totalUsers.asAbbreviatedCountString(), attributes: [ .paragraphStyle: paragraphStyle ] @@ -128,17 +125,6 @@ extension PickServerSection { } .store(in: &cell.disposeBag) } - - private static func parseUsersCount(_ usersCount: Int) -> String { - switch usersCount { - case 0..<1000: - return "\(usersCount)" - default: - let usersCountInThousand = Float(usersCount) / 1000.0 - return String(format: "%.1fK", usersCountInThousand) - } - } - } extension PickServerSection { diff --git a/Mastodon/Diffable/Profile/ProfileFieldItem.swift b/Mastodon/Diffable/Profile/ProfileFieldItem.swift index e33a2f883..37d08bc52 100644 --- a/Mastodon/Diffable/Profile/ProfileFieldItem.swift +++ b/Mastodon/Diffable/Profile/ProfileFieldItem.swift @@ -11,10 +11,10 @@ import MastodonSDK import MastodonMeta enum ProfileFieldItem: Hashable { + case createdAt(date: Date) case field(field: FieldValue) case editField(field: FieldValue) case addEntry - case noResult } extension ProfileFieldItem { diff --git a/Mastodon/Diffable/Profile/ProfileFieldSection.swift b/Mastodon/Diffable/Profile/ProfileFieldSection.swift index 6e57e6af9..10946915d 100644 --- a/Mastodon/Diffable/Profile/ProfileFieldSection.swift +++ b/Mastodon/Diffable/Profile/ProfileFieldSection.swift @@ -33,44 +33,60 @@ extension ProfileFieldSection { collectionView.register(ProfileFieldCollectionViewHeaderFooterView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: ProfileFieldCollectionViewHeaderFooterView.footerReuseIdentifer) let fieldCellRegistration = UICollectionView.CellRegistration { cell, indexPath, item in - guard case let .field(field) = item else { return } + let key, value: String + let emojiMeta: MastodonContent.Emojis + let verified: Bool + + switch item { + case .field(field: let field): + key = field.name.value + value = field.value.value + emojiMeta = field.emojiMeta + verified = field.verifiedAt.value != nil + case .createdAt(date: let date): + key = L10n.Scene.Profile.Fields.joined + let formatter = DateFormatter() + formatter.dateStyle = .medium + formatter.timeStyle = .none + value = formatter.string(from: date) + emojiMeta = [:] + verified = false + default: return + } // set key + let keyColor = verified ? Asset.Scene.Profile.About.bioAboutFieldVerifiedText.color : Asset.Colors.Label.secondary.color do { - let mastodonContent = MastodonContent(content: field.name.value, emojis: field.emojiMeta) + let mastodonContent = MastodonContent(content: key, emojis: emojiMeta) let metaContent = try MastodonMetaContent.convert(document: mastodonContent) + cell.keyMetaLabel.textAttributes[.foregroundColor] = keyColor cell.keyMetaLabel.configure(content: metaContent) } catch { - let content = PlaintextMetaContent(string: field.name.value) + let content = PlaintextMetaContent(string: key) +// cell.keyMetaLabel.textAttributes[.foregroundColor] = keyColor cell.keyMetaLabel.configure(content: content) } // set value + let linkColor = verified ? Asset.Scene.Profile.About.bioAboutFieldVerifiedText.color : Asset.Colors.brand.color do { - let mastodonContent = MastodonContent(content: field.value.value, emojis: field.emojiMeta) + let mastodonContent = MastodonContent(content: value, emojis: emojiMeta) let metaContent = try MastodonMetaContent.convert(document: mastodonContent) - cell.valueMetaLabel.linkAttributes[.foregroundColor] = Asset.Colors.brand.color - if field.verifiedAt.value != nil { - cell.valueMetaLabel.linkAttributes[.foregroundColor] = Asset.Scene.Profile.About.bioAboutFieldVerifiedLink.color - } + cell.valueMetaLabel.linkAttributes[.foregroundColor] = linkColor cell.valueMetaLabel.configure(content: metaContent) } catch { - let content = PlaintextMetaContent(string: field.value.value) + let content = PlaintextMetaContent(string: value) + cell.valueMetaLabel.linkAttributes[.foregroundColor] = linkColor cell.valueMetaLabel.configure(content: content) } // set background var backgroundConfiguration = UIBackgroundConfiguration.listPlainCell() - backgroundConfiguration.backgroundColor = UIColor.secondarySystemBackground - if (field.verifiedAt.value != nil) { - backgroundConfiguration.backgroundColor = Asset.Scene.Profile.About.bioAboutFieldVerifiedBackground.color - } + backgroundConfiguration.backgroundColor = verified ? Asset.Scene.Profile.About.bioAboutFieldVerifiedBackground.color : UIColor.secondarySystemBackground cell.backgroundConfiguration = backgroundConfiguration // set checkmark and edit menu label - cell.checkmark.isHidden = true - cell.checkmarkPopoverString = nil - if let verifiedAt = field.verifiedAt.value { + if case .field(let field) = item, let verifiedAt = field.verifiedAt.value { cell.checkmark.isHidden = false let formatter = DateFormatter() formatter.dateStyle = .medium @@ -78,6 +94,9 @@ extension ProfileFieldSection { let dateString = formatter.string(from: verifiedAt) cell.checkmark.accessibilityLabel = L10n.Scene.Profile.Fields.Verified.long(dateString) cell.checkmarkPopoverString = L10n.Scene.Profile.Fields.Verified.short(dateString) + } else { + cell.checkmark.isHidden = true + cell.checkmarkPopoverString = nil } cell.delegate = configuration.profileFieldCollectionViewCellDelegate @@ -128,26 +147,10 @@ extension ProfileFieldSection { } cell.backgroundConfiguration = backgroundConfiguration } - - let noResultCellRegistration = UICollectionView.CellRegistration { cell, indexPath, item in - guard case .noResult = item else { return } - - var contentConfiguration = cell.defaultContentConfiguration() - contentConfiguration.text = L10n.Scene.Search.Searching.EmptyState.noResults // FIXME: - contentConfiguration.textProperties.alignment = .center - cell.contentConfiguration = contentConfiguration - - - var backgroundConfiguration = UIBackgroundConfiguration.listPlainCell() - backgroundConfiguration.backgroundColorTransformer = .init { _ in - return .secondarySystemBackground - } - cell.backgroundConfiguration = backgroundConfiguration - } - + let dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, item in switch item { - case .field: + case .field, .createdAt: return collectionView.dequeueConfiguredReusableCell( using: fieldCellRegistration, for: indexPath, @@ -165,12 +168,6 @@ extension ProfileFieldSection { for: indexPath, item: item ) - case .noResult: - return collectionView.dequeueConfiguredReusableCell( - using: noResultCellRegistration, - for: indexPath, - item: item - ) } } diff --git a/Mastodon/Diffable/Report/ReportSection.swift b/Mastodon/Diffable/Report/ReportSection.swift index ba3c5525a..4c8fd4345 100644 --- a/Mastodon/Diffable/Report/ReportSection.swift +++ b/Mastodon/Diffable/Report/ReportSection.swift @@ -107,6 +107,7 @@ extension ReportSection { statusView: cell.statusView ) + cell.statusView.viewModel.context = context cell.statusView.viewModel.authContext = configuration.authContext cell.configure( diff --git a/Mastodon/Diffable/Search/SearchResultSection.swift b/Mastodon/Diffable/Search/SearchResultSection.swift index 8a5d7e75f..e4dcad891 100644 --- a/Mastodon/Diffable/Search/SearchResultSection.swift +++ b/Mastodon/Diffable/Search/SearchResultSection.swift @@ -104,6 +104,7 @@ extension SearchResultSection { statusView: cell.statusView ) + cell.statusView.viewModel.context = context cell.statusView.viewModel.authContext = configuration.authContext cell.configure( diff --git a/Mastodon/Diffable/Status/StatusSection.swift b/Mastodon/Diffable/Status/StatusSection.swift index 38b8e641f..ac02273e9 100644 --- a/Mastodon/Diffable/Status/StatusSection.swift +++ b/Mastodon/Diffable/Status/StatusSection.swift @@ -27,6 +27,7 @@ extension StatusSection { static let logger = Logger(subsystem: "StatusSection", category: "logic") struct Configuration { + let context: AppContext let authContext: AuthContext weak var statusTableViewCellDelegate: StatusTableViewCellDelegate? weak var timelineMiddleLoaderTableViewCellDelegate: TimelineMiddleLoaderTableViewCellDelegate? @@ -227,11 +228,7 @@ extension StatusSection { } var _snapshot = NSDiffableDataSourceSnapshot() _snapshot.appendSections([.main]) - if #available(iOS 15.0, *) { - statusView.pollTableViewDiffableDataSource?.applySnapshotUsingReloadData(_snapshot) - } else { - statusView.pollTableViewDiffableDataSource?.apply(_snapshot, animatingDifferences: false) - } + statusView.pollTableViewDiffableDataSource?.applySnapshotUsingReloadData(_snapshot) } } @@ -250,6 +247,7 @@ extension StatusSection { statusView: cell.statusView ) + cell.statusView.viewModel.context = configuration.context cell.statusView.viewModel.authContext = configuration.authContext cell.configure( @@ -277,6 +275,7 @@ extension StatusSection { statusView: cell.statusView ) + cell.statusView.viewModel.context = configuration.context cell.statusView.viewModel.authContext = configuration.authContext cell.configure( diff --git a/Mastodon/Extension/ActiveLabel.swift b/Mastodon/Extension/ActiveLabel.swift deleted file mode 100644 index 4b23d4eb3..000000000 --- a/Mastodon/Extension/ActiveLabel.swift +++ /dev/null @@ -1,66 +0,0 @@ -//extension ActiveEntity { -// -// var accessibilityLabelDescription: String { -// switch self.type { -// case .email: return L10n.Common.Controls.Status.Tag.email -// case .hashtag: return L10n.Common.Controls.Status.Tag.hashtag -// case .mention: return L10n.Common.Controls.Status.Tag.mention -// case .url: return L10n.Common.Controls.Status.Tag.url -// case .emoji: return L10n.Common.Controls.Status.Tag.emoji -// } -// } -// -// var accessibilityValueDescription: String { -// switch self.type { -// case .email(let text, _): return text -// case .hashtag(let text, _): return text -// case .mention(let text, _): return text -// case .url(_, let trimmed, _, _): return trimmed -// case .emoji(let text, _, _): return text -// } -// } -// -// func accessibilityElement(in accessibilityContainer: Any) -> ActiveLabelAccessibilityElement? { -// if case .emoji = self.type { -// return nil -// } -// -// let element = ActiveLabelAccessibilityElement(accessibilityContainer: accessibilityContainer) -// element.accessibilityTraits = .button -// element.accessibilityLabel = accessibilityLabelDescription -// element.accessibilityValue = accessibilityValueDescription -// return element -// } -//} - -//final class ActiveLabelAccessibilityElement: UIAccessibilityElement { -// var index: Int! -//} -// -// MARK: - UIAccessibilityContainer -//extension ActiveLabel { -// -// func createAccessibilityElements() -> [UIAccessibilityElement] { -// var elements: [UIAccessibilityElement] = [] -// -// let element = ActiveLabelAccessibilityElement(accessibilityContainer: self) -// element.accessibilityTraits = .staticText -// element.accessibilityLabel = accessibilityLabel -// element.accessibilityFrame = superview!.convert(frame, to: nil) -// element.accessibilityLanguage = accessibilityLanguage -// elements.append(element) -// -// for entity in activeEntities { -// guard let element = entity.accessibilityElement(in: self) else { continue } -// var glyphRange = NSRange() -// layoutManager.characterRange(forGlyphRange: entity.range, actualGlyphRange: &glyphRange) -// let rect = layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer) -// element.accessibilityFrame = self.convert(rect, to: nil) -// element.accessibilityContainer = self -// elements.append(element) -// } -// -// return elements -// } -// -//} diff --git a/Mastodon/Extension/AppContext+NextAccount.swift b/Mastodon/Extension/AppContext+NextAccount.swift new file mode 100644 index 000000000..a8eae1e13 --- /dev/null +++ b/Mastodon/Extension/AppContext+NextAccount.swift @@ -0,0 +1,47 @@ +// +// AppContext+NextAccount.swift +// Mastodon +// +// Created by Marcus Kida on 17.11.22. +// + +import CoreData +import CoreDataStack +import MastodonCore +import MastodonSDK + +extension AppContext { + func nextAccount(in authContext: AuthContext) -> MastodonAuthentication? { + let request = MastodonAuthentication.sortedFetchRequest + guard + let accounts = try? managedObjectContext.fetch(request), + accounts.count > 1 + else { return nil } + + let nextSelectedAccountIndex: Int? = { + for (index, account) in accounts.enumerated() { + guard account == authContext.mastodonAuthenticationBox + .authenticationRecord + .object(in: managedObjectContext) + else { continue } + + let nextAccountIndex = index + 1 + + if accounts.count > nextAccountIndex { + return nextAccountIndex + } else { + return 0 + } + } + + return nil + }() + + guard + let nextSelectedAccountIndex = nextSelectedAccountIndex, + accounts.count > nextSelectedAccountIndex + else { return nil } + + return accounts[nextSelectedAccountIndex] + } +} diff --git a/Mastodon/Extension/Collection+IsNotEmpty.swift b/Mastodon/Extension/Collection+IsNotEmpty.swift new file mode 100644 index 000000000..d59b8e2fe --- /dev/null +++ b/Mastodon/Extension/Collection+IsNotEmpty.swift @@ -0,0 +1,14 @@ +// +// Array+IsNotEmpty.swift +// Mastodon +// +// Created by Marcus Kida on 01.12.22. +// + +import Foundation + +extension Collection { + var isNotEmpty: Bool { + !isEmpty + } +} diff --git a/Mastodon/Extension/String.swift b/Mastodon/Extension/String.swift index bf70c8937..0aa8acb3a 100644 --- a/Mastodon/Extension/String.swift +++ b/Mastodon/Extension/String.swift @@ -15,6 +15,8 @@ extension String { mutating func capitalizeFirstLetter() { self = self.capitalizingFirstLetter() } + + static let empty = "" } extension String { diff --git a/Mastodon/Extension/UIApplication.swift b/Mastodon/Extension/UIApplication.swift index 38080fdab..74019b0ae 100644 --- a/Mastodon/Extension/UIApplication.swift +++ b/Mastodon/Extension/UIApplication.swift @@ -22,5 +22,13 @@ extension UIApplication { return version == build ? "v\(version)" : "v\(version) (\(build))" } + + func getKeyWindow() -> UIWindow? { + return UIApplication + .shared + .connectedScenes + .flatMap { ($0 as? UIWindowScene)?.windows ?? [] } + .first { $0.isKeyWindow } + } } diff --git a/Mastodon/Extension/UICollectionViewDiffableDataSource.swift b/Mastodon/Extension/UICollectionViewDiffableDataSource.swift deleted file mode 100644 index 07d2fbd12..000000000 --- a/Mastodon/Extension/UICollectionViewDiffableDataSource.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// UICollectionViewDiffableDataSource.swift -// Mastodon -// -// Created by Cirno MainasuK on 2021-10-11. -// - -import UIKit - -// ref: https://www.jessesquires.com/blog/2021/07/08/diffable-data-source-behavior-changes-and-reconfiguring-cells-in-ios-15/ -extension UICollectionViewDiffableDataSource { - func reloadData( - snapshot: NSDiffableDataSourceSnapshot, - completion: (() -> Void)? = nil - ) { - if #available(iOS 15.0, *) { - self.applySnapshotUsingReloadData(snapshot, completion: completion) - } else { - self.apply(snapshot, animatingDifferences: false, completion: completion) - } - } - - func applySnapshot( - _ snapshot: NSDiffableDataSourceSnapshot, - animated: Bool, - completion: (() -> Void)? = nil) { - - if #available(iOS 15.0, *) { - self.apply(snapshot, animatingDifferences: animated, completion: completion) - } else { - if animated { - self.apply(snapshot, animatingDifferences: true, completion: completion) - } else { - UIView.performWithoutAnimation { - self.apply(snapshot, animatingDifferences: true, completion: completion) - } - } - } - } -} diff --git a/Mastodon/Extension/UIImage+SFSymbols.swift b/Mastodon/Extension/UIImage+SFSymbols.swift new file mode 100644 index 000000000..cf20055ea --- /dev/null +++ b/Mastodon/Extension/UIImage+SFSymbols.swift @@ -0,0 +1,12 @@ +// +// UIImage+SFSymbols.swift +// Mastodon +// +// Created by Marcus Kida on 18.11.22. +// + +import UIKit + +extension UIImage { + static let chevronUpChevronDown = UIImage(systemName: "chevron.up.chevron.down") +} diff --git a/Mastodon/Extension/UITableViewDiffableDataSource.swift b/Mastodon/Extension/UITableViewDiffableDataSource.swift deleted file mode 100644 index 5006417a4..000000000 --- a/Mastodon/Extension/UITableViewDiffableDataSource.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// UITableViewDiffableDataSource.swift -// Mastodon -// -// Created by Cirno MainasuK on 2021-10-11. -// - -import UIKit - -// ref: https://www.jessesquires.com/blog/2021/07/08/diffable-data-source-behavior-changes-and-reconfiguring-cells-in-ios-15/ -extension UITableViewDiffableDataSource { - func reloadData( - snapshot: NSDiffableDataSourceSnapshot, - completion: (() -> Void)? = nil - ) { - if #available(iOS 15.0, *) { - self.applySnapshotUsingReloadData(snapshot, completion: completion) - } else { - self.apply(snapshot, animatingDifferences: false, completion: completion) - } - } - - func applySnapshot( - _ snapshot: NSDiffableDataSourceSnapshot, - animated: Bool, - completion: (() -> Void)? = nil) { - - if #available(iOS 15.0, *) { - self.apply(snapshot, animatingDifferences: animated, completion: completion) - } else { - if animated { - self.apply(snapshot, animatingDifferences: true, completion: completion) - } else { - UIView.performWithoutAnimation { - self.apply(snapshot, animatingDifferences: true, completion: completion) - } - } - } - } -} diff --git a/Mastodon/Helper/ImageProvider.swift b/Mastodon/Helper/ImageProvider.swift new file mode 100644 index 000000000..11ea6d46e --- /dev/null +++ b/Mastodon/Helper/ImageProvider.swift @@ -0,0 +1,39 @@ +// +// ImageProvider.swift +// Mastodon +// +// Created by Jed Fox on 2022-12-03. +// + +import Foundation +import AlamofireImage +import UniformTypeIdentifiers +import UIKit + +class ImageProvider: NSObject, NSItemProviderWriting { + let url: URL + let filter: ImageFilter? + + init(url: URL, filter: ImageFilter? = nil) { + self.url = url + self.filter = filter + } + + var itemProvider: NSItemProvider { + NSItemProvider(object: self) + } + + static var writableTypeIdentifiersForItemProvider: [String] { + [UTType.png.identifier] + } + + func loadData(withTypeIdentifier typeIdentifier: String, forItemProviderCompletionHandler completionHandler: @escaping @Sendable (Data?, Error?) -> Void) -> Progress? { + let receipt = UIImageView.af.sharedImageDownloader.download(URLRequest(url: url), filter: filter, completion: { response in + switch response.result { + case .failure(let error): completionHandler(nil, error) + case .success(let image): completionHandler(image.pngData(), nil) + } + }) + return receipt?.request.downloadProgress + } +} diff --git a/Mastodon/Helper/URLActivityItemWithMetadata.swift b/Mastodon/Helper/URLActivityItemWithMetadata.swift new file mode 100644 index 000000000..82d1747fe --- /dev/null +++ b/Mastodon/Helper/URLActivityItemWithMetadata.swift @@ -0,0 +1,33 @@ +// +// URLActivityItemWithMetadata.swift +// Mastodon +// +// Created by Jed Fox on 2022-12-03. +// + +import UIKit +import LinkPresentation + +class URLActivityItemWithMetadata: NSObject, UIActivityItemSource { + init(url: URL, configureMetadata: (LPLinkMetadata) -> Void) { + self.url = url + self.metadata = LPLinkMetadata() + metadata.url = url + configureMetadata(metadata) + } + + let url: URL + let metadata: LPLinkMetadata + + func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any { + url + } + + func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? { + url + } + + func activityViewControllerLinkMetadata(_ activityViewController: UIActivityViewController) -> LPLinkMetadata? { + metadata + } +} diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Block.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Block.swift index 2747f4735..2cf6e3419 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Block.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Block.swift @@ -16,10 +16,17 @@ extension DataSourceFacade { ) async throws { let selectionFeedbackGenerator = await UISelectionFeedbackGenerator() await selectionFeedbackGenerator.selectionChanged() - - _ = try await dependency.context.apiService.toggleBlock( + + let apiService = dependency.context.apiService + let authBox = dependency.authContext.mastodonAuthenticationBox + + _ = try await apiService.toggleBlock( user: user, - authenticationBox: dependency.authContext.mastodonAuthenticationBox + authenticationBox: authBox + ) + + try await dependency.context.apiService.getBlocked( + authenticationBox: authBox ) } // end func } diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Follow.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Follow.swift index b3812f198..a85de703a 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Follow.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Follow.swift @@ -94,7 +94,7 @@ extension DataSourceFacade { let alertController = await UIAlertController(for: error, title: nil, preferredStyle: .alert) let okAction = await UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default) await alertController.addAction(okAction) - await dependency.coordinator.present( + _ = await dependency.coordinator.present( scene: .alertController(alertController: alertController), from: nil, transition: .alertController(animated: true, completion: nil) diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Hashtag.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Hashtag.swift index 43d6b954b..6135c904a 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Hashtag.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Hashtag.swift @@ -35,7 +35,7 @@ extension DataSourceFacade { hashtag: tag.name ) - provider.coordinator.present( + _ = provider.coordinator.present( scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel), from: provider, transition: .show @@ -61,7 +61,7 @@ extension DataSourceFacade { hashtag: name ) - provider.coordinator.present( + _ = provider.coordinator.present( scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel), from: provider, transition: .show diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Media.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Media.swift index 3e767f2d3..9dd97f38a 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Media.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Media.swift @@ -24,7 +24,7 @@ extension DataSourceFacade { item: mediaPreviewItem, transitionItem: mediaPreviewTransitionItem ) - dependency.coordinator.present( + _ = dependency.coordinator.present( scene: .mediaPreview(viewModel: mediaPreviewViewModel), from: dependency, transition: .custom(transitioningDelegate: dependency.mediaPreviewTransitionController) diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Meta.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Meta.swift index 7e0ed37fc..ca3bbd474 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Meta.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Meta.swift @@ -48,21 +48,15 @@ extension DataSourceFacade { assertionFailure() return } - let domain = provider.authContext.mastodonAuthenticationBox.domain - if url.host == domain, - url.pathComponents.count >= 4, - url.pathComponents[0] == "/", - url.pathComponents[1] == "web", - url.pathComponents[2] == "statuses" { - let statusID = url.pathComponents[3] - let threadViewModel = RemoteThreadViewModel(context: provider.context, authContext: provider.authContext, statusID: statusID) - await provider.coordinator.present(scene: .thread(viewModel: threadViewModel), from: nil, transition: .show) - } else { - await provider.coordinator.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil)) - } + + await responseToURLAction( + provider: provider, + status: status, + url: url + ) case .hashtag(_, let hashtag, _): let hashtagTimelineViewModel = HashtagTimelineViewModel(context: provider.context, authContext: provider.authContext, hashtag: hashtag) - await provider.coordinator.present(scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel), from: provider, transition: .show) + _ = await provider.coordinator.present(scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel), from: provider, transition: .show) case .mention(_, let mention, let userInfo): await coordinateToProfileScene( provider: provider, diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Profile.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Profile.swift index ef01b8394..3c0509d2c 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Profile.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Profile.swift @@ -47,7 +47,7 @@ extension DataSourceFacade { mastodonUser: user ) - provider.coordinator.present( + _ = provider.coordinator.present( scene: .profile(viewModel: profileViewModel), from: provider, transition: .show @@ -75,7 +75,7 @@ extension DataSourceFacade { } guard let mention = mentions?.first(where: { $0.username == mention }) else { - await provider.coordinator.present( + _ = await provider.coordinator.present( scene: .safari(url: url), from: provider, transition: .safariPresent(animated: true, completion: nil) @@ -102,7 +102,7 @@ extension DataSourceFacade { } }() - await provider.coordinator.present( + _ = await provider.coordinator.present( scene: .profile(viewModel: profileViewModel), from: provider, transition: .show diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift index 332ed75e4..90b5b184e 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift @@ -43,7 +43,7 @@ extension DataSourceFacade { dependency: provider, status: status ) - provider.coordinator.present( + _ = provider.coordinator.present( scene: .activityViewController( activityViewController: activityViewController, sourceView: button, @@ -59,8 +59,18 @@ extension DataSourceFacade { status: ManagedObjectRecord ) async throws -> UIActivityViewController { var activityItems: [Any] = try await dependency.context.managedObjectContext.perform { - guard let status = status.object(in: dependency.context.managedObjectContext) else { return [] } - return [StatusActivityItem(status: status)].compactMap { $0 } as [Any] + guard let status = status.object(in: dependency.context.managedObjectContext), + let url = URL(string: status.url ?? status.uri) + else { return [] } + return [ + URLActivityItemWithMetadata(url: url) { metadata in + metadata.title = "\(status.author.displayName) (@\(status.author.acctWithDomain))" + metadata.iconProvider = ImageProvider( + url: status.author.avatarImageURLWithFallback(domain: status.author.domain), + filter: ScaledToSizeFilter(size: CGSize.authorAvatarButtonSize) + ).itemProvider + } + ] as [Any] } var applicationActivities: [UIActivity] = [ SafariActivity(sceneCoordinator: dependency.coordinator), // open URL @@ -77,54 +87,6 @@ extension DataSourceFacade { ) return activityViewController } - - private class StatusActivityItem: NSObject, UIActivityItemSource { - init?(status: Status) { - guard let url = URL(string: status.url ?? status.uri) else { return nil } - self.url = url - self.metadata = LPLinkMetadata() - metadata.url = url - metadata.title = "\(status.author.displayName) (@\(status.author.username)@\(status.author.domain))" - metadata.iconProvider = NSItemProvider(object: IconProvider(url: status.author.avatarImageURLWithFallback(domain: status.author.domain))) - } - - let url: URL - let metadata: LPLinkMetadata - - func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any { - url - } - - func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? { - url - } - - func activityViewControllerLinkMetadata(_ activityViewController: UIActivityViewController) -> LPLinkMetadata? { - metadata - } - - private class IconProvider: NSObject, NSItemProviderWriting { - let url: URL - init(url: URL) { - self.url = url - } - - static var writableTypeIdentifiersForItemProvider: [String] { - [UTType.png.identifier] - } - - func loadData(withTypeIdentifier typeIdentifier: String, forItemProviderCompletionHandler completionHandler: @escaping @Sendable (Data?, Error?) -> Void) -> Progress? { - let filter = ScaledToSizeFilter(size: CGSize.authorAvatarButtonSize) - let receipt = UIImageView.af.sharedImageDownloader.download(URLRequest(url: url), filter: filter, completion: { response in - switch response.result { - case .failure(let error): completionHandler(nil, error) - case .success(let image): completionHandler(image.pngData(), nil) - } - }) - return receipt?.request.downloadProgress - } - } - } } // ActionToolBar @@ -155,7 +117,7 @@ extension DataSourceFacade { let composeViewModel = ComposeViewModel( context: provider.context, authContext: provider.authContext, - kind: .reply(status: status) + destination: .reply(parent: status) ) _ = provider.coordinator.present( scene: .compose(viewModel: composeViewModel), @@ -358,7 +320,8 @@ extension DataSourceFacade { dependency: dependency, status: status ) - await dependency.coordinator.present( + + _ = dependency.coordinator.present( scene: .activityViewController( activityViewController: activityViewController, sourceView: menuContext.button, @@ -392,6 +355,18 @@ extension DataSourceFacade { alertController.addAction(cancelAction) dependency.present(alertController, animated: true) + case .translateStatus: + guard let status = menuContext.status else { return } + do { + try await DataSourceFacade.translateStatus( + provider: dependency, + status: status + ) + } catch TranslationFailure.emptyOrInvalidResponse { + let alertController = UIAlertController(title: L10n.Common.Alerts.TranslationFailed.title, message: L10n.Common.Alerts.TranslationFailed.message, preferredStyle: .alert) + alertController.addAction(UIAlertAction(title: L10n.Common.Alerts.TranslationFailed.button, style: .default)) + dependency.present(alertController, animated: true) + } } } // end func } diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Translate.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Translate.swift new file mode 100644 index 000000000..8ce9c2447 --- /dev/null +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Translate.swift @@ -0,0 +1,70 @@ +// +// DataSourceFacade+Translate.swift +// Mastodon +// +// Created by Marcus Kida on 29.11.22. +// + +import UIKit +import CoreData +import CoreDataStack +import MastodonCore + +typealias Provider = UIViewController & NeedsDependency & AuthContextProvider + +extension DataSourceFacade { + enum TranslationFailure: Error { + case emptyOrInvalidResponse + } + + public static func translateStatus( + provider: Provider, + status: ManagedObjectRecord + ) async throws { + let selectionFeedbackGenerator = await UISelectionFeedbackGenerator() + await selectionFeedbackGenerator.selectionChanged() + + guard + let status = status.object(in: provider.context.managedObjectContext) + else { + return + } + + if let reblog = status.reblog { + try await translateAndApply(provider: provider, status: reblog) + } else { + try await translateAndApply(provider: provider, status: status) + } + } +} + +private extension DataSourceFacade { + static func translateStatus(provider: Provider, status: Status) async throws -> Status.TranslatedContent? { + do { + let value = try await provider.context + .apiService + .translateStatus( + statusID: status.id, + authenticationBox: provider.authContext.mastodonAuthenticationBox + ).value + + guard let content = value.content else { + throw TranslationFailure.emptyOrInvalidResponse + } + + return Status.TranslatedContent(content: content, provider: value.provider) + } catch { + throw TranslationFailure.emptyOrInvalidResponse + } + } + + static func translateAndApply(provider: Provider, status: Status) async throws { + do { + let translated = try await translateStatus(provider: provider, status: status) + status.update(translatedContent: translated) + } catch { + status.update(translatedContent: nil) + throw TranslationFailure.emptyOrInvalidResponse + } + } +} diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+URL.swift b/Mastodon/Protocol/Provider/DataSourceFacade+URL.swift new file mode 100644 index 000000000..a65de9537 --- /dev/null +++ b/Mastodon/Protocol/Provider/DataSourceFacade+URL.swift @@ -0,0 +1,32 @@ +// +// DataSourceFacade+URL.swift +// Mastodon +// +// Created by Kyle Bashour on 11/24/22. +// + +import Foundation +import CoreDataStack +import MetaTextKit +import MastodonCore + +extension DataSourceFacade { + static func responseToURLAction( + provider: DataSourceProvider & AuthContextProvider, + status: ManagedObjectRecord, + url: URL + ) async { + let domain = provider.authContext.mastodonAuthenticationBox.domain + if url.host == domain, + url.pathComponents.count >= 4, + url.pathComponents[0] == "/", + url.pathComponents[1] == "web", + url.pathComponents[2] == "statuses" { + let statusID = url.pathComponents[3] + let threadViewModel = RemoteThreadViewModel(context: provider.context, authContext: provider.authContext, statusID: statusID) + _ = await provider.coordinator.present(scene: .thread(viewModel: threadViewModel), from: nil, transition: .show) + } else { + _ = await provider.coordinator.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil)) + } + } +} diff --git a/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift b/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift index 82c25d040..0b4cc3e8f 100644 --- a/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift +++ b/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift @@ -10,6 +10,9 @@ import CoreDataStack import MetaTextKit import MastodonCore import MastodonUI +import MastodonLocalization +import MastodonAsset +import LinkPresentation // MARK: - header extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider { @@ -120,7 +123,127 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthConte ) } } - + +} + +// MARK: - card +extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider { + + func tableViewCell( + _ cell: UITableViewCell, + statusView: StatusView, + didTapCardWithURL url: URL + ) { + Task { + let source = DataSourceItem.Source(tableViewCell: cell, indexPath: nil) + guard let item = await item(from: source) else { + assertionFailure() + return + } + guard case let .status(status) = item else { + assertionFailure("only works for status data provider") + return + } + + await DataSourceFacade.responseToURLAction( + provider: self, + status: status, + url: url + ) + } + } + + func tableViewCell( + _ cell: UITableViewCell, + statusView: StatusView, + cardControl: StatusCardControl, + didTapURL url: URL + ) { + Task { + let source = DataSourceItem.Source(tableViewCell: cell, indexPath: nil) + guard let item = await item(from: source) else { + assertionFailure() + return + } + guard case let .status(status) = item else { + assertionFailure("only works for status data provider") + return + } + + await DataSourceFacade.responseToURLAction( + provider: self, + status: status, + url: url + ) + } + } + + func tableViewCell( + _ cell: UITableViewCell, + statusView: StatusView, + cardControlMenu statusCardControl: StatusCardControl + ) -> UIMenu? { + guard let card = statusView.viewModel.card, + let url = card.url else { + return nil + } + + return UIMenu(children: [ + UIAction( + title: L10n.Common.Controls.Actions.copy, + image: UIImage(systemName: "doc.on.doc") + ) { _ in + UIPasteboard.general.url = url + }, + + UIAction( + title: L10n.Common.Controls.Actions.share, + image: Asset.Arrow.squareAndArrowUp.image.withRenderingMode(.alwaysTemplate) + ) { _ in + DispatchQueue.main.async { + let activityViewController = UIActivityViewController( + activityItems: [ + URLActivityItemWithMetadata(url: url) { metadata in + metadata.title = card.title + + if let image = card.imageURL { + metadata.iconProvider = ImageProvider(url: image, filter: nil).itemProvider + } + } + ], + applicationActivities: [] + ) + self.coordinator.present( + scene: .activityViewController( + activityViewController: activityViewController, + sourceView: statusCardControl, barButtonItem: nil + ), + from: self, + transition: .activityViewControllerPresent(animated: true) + ) + } + }, + + UIAction( + title: L10n.Common.Controls.Status.Actions.shareLinkInPost, + image: Asset.ObjectsAndTools.squareAndPencil.image.withRenderingMode(.alwaysTemplate) + ) { _ in + DispatchQueue.main.async { + self.coordinator.present( + scene: .compose(viewModel: ComposeViewModel( + context: self.context, + authContext: self.authContext, + destination: .topLevel, + initialContent: L10n.Common.Controls.Status.linkViaUser(url.absoluteString, "@" + (statusView.viewModel.authorUsername ?? "")) + )), + from: self, + transition: .modal(animated: true) + ) + } + } + ]) + } + } // MARK: - media @@ -360,6 +483,12 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthConte return } + if let cell = cell as? StatusTableViewCell { + DispatchQueue.main.async { + cell.statusView.viewModel.isCurrentlyTranslating = true + } + } + try await DataSourceFacade.responseToMenuAction( dependency: self, action: action, @@ -487,7 +616,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthConte authContext: authContext, kind: .rebloggedBy(status: status) ) - await coordinator.present( + _ = await coordinator.present( scene: .rebloggedBy(viewModel: userListViewModel), from: self, transition: .show @@ -511,7 +640,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthConte authContext: authContext, kind: .favoritedBy(status: status) ) - await coordinator.present( + _ = await coordinator.present( scene: .favoritedBy(viewModel: userListViewModel), from: self, transition: .show diff --git a/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewControllerNavigateable.swift b/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewControllerNavigateable.swift index e7b55f91c..0e0a24c9b 100644 --- a/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewControllerNavigateable.swift +++ b/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewControllerNavigateable.swift @@ -100,7 +100,7 @@ extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvid let composeViewModel = ComposeViewModel( context: self.context, authContext: authContext, - kind: .reply(status: status) + destination: .reply(parent: status) ) _ = self.coordinator.present( scene: .compose(viewModel: composeViewModel), diff --git a/Mastodon/Protocol/Provider/DataSourceProvider+UITableViewDelegate.swift b/Mastodon/Protocol/Provider/DataSourceProvider+UITableViewDelegate.swift index 3a71e5346..6ced42601 100644 --- a/Mastodon/Protocol/Provider/DataSourceProvider+UITableViewDelegate.swift +++ b/Mastodon/Protocol/Provider/DataSourceProvider+UITableViewDelegate.swift @@ -143,7 +143,7 @@ extension UITableViewDelegate where Self: DataSourceProvider & MediaPreviewableV title: L10n.Common.Alerts.SavePhotoFailure.title, message: L10n.Common.Alerts.SavePhotoFailure.message ) - self.coordinator.present( + _ = self.coordinator.present( scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil) diff --git a/Mastodon/Resources/Base.lproj/infoPlist.strings b/Mastodon/Resources/Base.lproj/infoPlist.strings new file mode 100644 index 000000000..710865573 --- /dev/null +++ b/Mastodon/Resources/Base.lproj/infoPlist.strings @@ -0,0 +1,4 @@ +"NSCameraUsageDescription" = "Used to take photo for post status"; +"NSPhotoLibraryAddUsageDescription" = "Used to save photo into the Photo Library"; +"NewPostShortcutItemTitle" = "New Post"; +"SearchShortcutItemTitle" = "Search"; \ No newline at end of file diff --git a/Mastodon/Resources/ar.lproj/InfoPlist.strings b/Mastodon/Resources/ar.lproj/InfoPlist.strings index c3b26f14a..ecb81ddd4 100644 --- a/Mastodon/Resources/ar.lproj/InfoPlist.strings +++ b/Mastodon/Resources/ar.lproj/InfoPlist.strings @@ -1,4 +1,4 @@ "NSCameraUsageDescription" = "يُستخدم لالتقاط الصورة عِندَ نشر الحالات"; "NSPhotoLibraryAddUsageDescription" = "يُستخدم لحِفظ الصورة في مكتبة الصور"; -"NewPostShortcutItemTitle" = "منشور جديد"; +"NewPostShortcutItemTitle" = "مَنشُورٌ جَديد"; "SearchShortcutItemTitle" = "البحث"; \ No newline at end of file diff --git a/Mastodon/Resources/ckb.lproj/InfoPlist.strings b/Mastodon/Resources/ckb.lproj/InfoPlist.strings index 710865573..8c4946d2d 100644 --- a/Mastodon/Resources/ckb.lproj/InfoPlist.strings +++ b/Mastodon/Resources/ckb.lproj/InfoPlist.strings @@ -1,4 +1,4 @@ -"NSCameraUsageDescription" = "Used to take photo for post status"; -"NSPhotoLibraryAddUsageDescription" = "Used to save photo into the Photo Library"; -"NewPostShortcutItemTitle" = "New Post"; -"SearchShortcutItemTitle" = "Search"; \ No newline at end of file +"NSCameraUsageDescription" = "بەکار دێت بۆ گرتنی وێنەیەک بۆ پۆستەکە"; +"NSPhotoLibraryAddUsageDescription" = "بەکار دێت بۆ هەڵگرتنی وێنە"; +"NewPostShortcutItemTitle" = "پۆستی نوێ"; +"SearchShortcutItemTitle" = "بگەڕێ"; \ No newline at end of file diff --git a/Mastodon/Resources/cs.lproj/InfoPlist.strings b/Mastodon/Resources/cs.lproj/InfoPlist.strings new file mode 100644 index 000000000..6989cfcb1 --- /dev/null +++ b/Mastodon/Resources/cs.lproj/InfoPlist.strings @@ -0,0 +1,4 @@ +"NSCameraUsageDescription" = "Slouží k pořízení fotografie pro příspěvek"; +"NSPhotoLibraryAddUsageDescription" = "Slouží k uložení fotografie do knihovny fotografií"; +"NewPostShortcutItemTitle" = "Nový příspěvek"; +"SearchShortcutItemTitle" = "Hledat"; \ No newline at end of file diff --git a/Mastodon/Resources/de.lproj/infoPlist.strings b/Mastodon/Resources/de.lproj/infoPlist.strings index 9c5feae53..9c8438653 100644 --- a/Mastodon/Resources/de.lproj/infoPlist.strings +++ b/Mastodon/Resources/de.lproj/infoPlist.strings @@ -1,4 +1,4 @@ -"NSCameraUsageDescription" = "Verwendet um Fotos für neue Beiträge aufzunehmen"; -"NSPhotoLibraryAddUsageDescription" = "Verwendet um Fotos zu speichern"; +"NSCameraUsageDescription" = "Wird verwendet, um Fotos für neue Beiträge aufzunehmen"; +"NSPhotoLibraryAddUsageDescription" = "Wird verwendet, um Foto in der Foto-Mediathek zu speichern"; "NewPostShortcutItemTitle" = "Neuer Beitrag"; -"SearchShortcutItemTitle" = "Suche"; \ No newline at end of file +"SearchShortcutItemTitle" = "Suchen"; \ No newline at end of file diff --git a/Mastodon/Resources/eu.lproj/InfoPlist.strings b/Mastodon/Resources/eu.lproj/InfoPlist.strings index 710865573..e9d36a901 100644 --- a/Mastodon/Resources/eu.lproj/InfoPlist.strings +++ b/Mastodon/Resources/eu.lproj/InfoPlist.strings @@ -1,4 +1,4 @@ -"NSCameraUsageDescription" = "Used to take photo for post status"; -"NSPhotoLibraryAddUsageDescription" = "Used to save photo into the Photo Library"; -"NewPostShortcutItemTitle" = "New Post"; -"SearchShortcutItemTitle" = "Search"; \ No newline at end of file +"NSCameraUsageDescription" = "Bidalketetarako argazkiak ateratzeko erabiltzen da"; +"NSPhotoLibraryAddUsageDescription" = "Argazkiak Argazki-liburutegian gordetzeko erabiltzen da"; +"NewPostShortcutItemTitle" = "Bidalketa berria"; +"SearchShortcutItemTitle" = "Bilatu"; \ No newline at end of file diff --git a/Mastodon/Resources/fi.lproj/InfoPlist.strings b/Mastodon/Resources/fi.lproj/InfoPlist.strings index 710865573..040b25d69 100644 --- a/Mastodon/Resources/fi.lproj/InfoPlist.strings +++ b/Mastodon/Resources/fi.lproj/InfoPlist.strings @@ -1,4 +1,4 @@ -"NSCameraUsageDescription" = "Used to take photo for post status"; -"NSPhotoLibraryAddUsageDescription" = "Used to save photo into the Photo Library"; -"NewPostShortcutItemTitle" = "New Post"; -"SearchShortcutItemTitle" = "Search"; \ No newline at end of file +"NSCameraUsageDescription" = "Käytetään kuvan ottamiseen julkaisua varten"; +"NSPhotoLibraryAddUsageDescription" = "Käytetään kuvan tallentamiseen kuvakirjastoon"; +"NewPostShortcutItemTitle" = "Uusi julkaisu"; +"SearchShortcutItemTitle" = "Haku"; \ No newline at end of file diff --git a/Mastodon/Resources/gd.lproj/InfoPlist.strings b/Mastodon/Resources/gd.lproj/InfoPlist.strings index 710865573..ccb39b44e 100644 --- a/Mastodon/Resources/gd.lproj/InfoPlist.strings +++ b/Mastodon/Resources/gd.lproj/InfoPlist.strings @@ -1,4 +1,4 @@ -"NSCameraUsageDescription" = "Used to take photo for post status"; -"NSPhotoLibraryAddUsageDescription" = "Used to save photo into the Photo Library"; -"NewPostShortcutItemTitle" = "New Post"; -"SearchShortcutItemTitle" = "Search"; \ No newline at end of file +"NSCameraUsageDescription" = "’Ga chleachdadh airson dealbh a thogail do staid puist"; +"NSPhotoLibraryAddUsageDescription" = "’Ga chleachdadh airson dealbh a shàbhaladh ann an tasg-lann nan dealbhan"; +"NewPostShortcutItemTitle" = "Post ùr"; +"SearchShortcutItemTitle" = "Lorg"; \ No newline at end of file diff --git a/Mastodon/Resources/gl.lproj/InfoPlist.strings b/Mastodon/Resources/gl.lproj/InfoPlist.strings index 710865573..3d6df3f0f 100644 --- a/Mastodon/Resources/gl.lproj/InfoPlist.strings +++ b/Mastodon/Resources/gl.lproj/InfoPlist.strings @@ -1,4 +1,4 @@ -"NSCameraUsageDescription" = "Used to take photo for post status"; -"NSPhotoLibraryAddUsageDescription" = "Used to save photo into the Photo Library"; -"NewPostShortcutItemTitle" = "New Post"; -"SearchShortcutItemTitle" = "Search"; \ No newline at end of file +"NSCameraUsageDescription" = "Utilizado para facer foto e publicar estado"; +"NSPhotoLibraryAddUsageDescription" = "Utilizado para gardar a foto na Biblioteca"; +"NewPostShortcutItemTitle" = "Nova publicación"; +"SearchShortcutItemTitle" = "Buscar"; \ No newline at end of file diff --git a/Mastodon/Resources/it.lproj/InfoPlist.strings b/Mastodon/Resources/it.lproj/InfoPlist.strings index 710865573..0da468639 100644 --- a/Mastodon/Resources/it.lproj/InfoPlist.strings +++ b/Mastodon/Resources/it.lproj/InfoPlist.strings @@ -1,4 +1,4 @@ -"NSCameraUsageDescription" = "Used to take photo for post status"; -"NSPhotoLibraryAddUsageDescription" = "Used to save photo into the Photo Library"; -"NewPostShortcutItemTitle" = "New Post"; -"SearchShortcutItemTitle" = "Search"; \ No newline at end of file +"NSCameraUsageDescription" = "Usato per scattare foto per lo stato del post"; +"NSPhotoLibraryAddUsageDescription" = "Utilizzato per salvare la foto nella galleria immagini"; +"NewPostShortcutItemTitle" = "Nuovo post"; +"SearchShortcutItemTitle" = "Cerca"; \ No newline at end of file diff --git a/Mastodon/Resources/kab.lproj/InfoPlist.strings b/Mastodon/Resources/kab.lproj/InfoPlist.strings index 710865573..42adc4cfc 100644 --- a/Mastodon/Resources/kab.lproj/InfoPlist.strings +++ b/Mastodon/Resources/kab.lproj/InfoPlist.strings @@ -1,4 +1,4 @@ -"NSCameraUsageDescription" = "Used to take photo for post status"; -"NSPhotoLibraryAddUsageDescription" = "Used to save photo into the Photo Library"; -"NewPostShortcutItemTitle" = "New Post"; -"SearchShortcutItemTitle" = "Search"; \ No newline at end of file +"NSCameraUsageDescription" = "Yettwaseqdac i tuṭṭfa n tewlafin deg usuffeɣ n waddaden"; +"NSPhotoLibraryAddUsageDescription" = "Yettwaseqdac i usekles n tewlafin deg temkarḍit n tewlafin"; +"NewPostShortcutItemTitle" = "Tasuffeɣt tamaynut"; +"SearchShortcutItemTitle" = "Nadi"; \ No newline at end of file diff --git a/Mastodon/Resources/ku.lproj/InfoPlist.strings b/Mastodon/Resources/ku.lproj/InfoPlist.strings index 710865573..669ecfacf 100644 --- a/Mastodon/Resources/ku.lproj/InfoPlist.strings +++ b/Mastodon/Resources/ku.lproj/InfoPlist.strings @@ -1,4 +1,4 @@ -"NSCameraUsageDescription" = "Used to take photo for post status"; -"NSPhotoLibraryAddUsageDescription" = "Used to save photo into the Photo Library"; -"NewPostShortcutItemTitle" = "New Post"; -"SearchShortcutItemTitle" = "Search"; \ No newline at end of file +"NSCameraUsageDescription" = "Bo kişandina wêneyê ji bo rewşa şandiyan tê bikaranîn"; +"NSPhotoLibraryAddUsageDescription" = "Ji bo tomarkirina wêneyê di pirtûkxaneya wêneyan de tê bikaranîn"; +"NewPostShortcutItemTitle" = "Şandiya nû"; +"SearchShortcutItemTitle" = "Bigere"; \ No newline at end of file diff --git a/Mastodon/Resources/nl.lproj/InfoPlist.strings b/Mastodon/Resources/nl.lproj/InfoPlist.strings index a45991068..36b6e9802 100644 --- a/Mastodon/Resources/nl.lproj/InfoPlist.strings +++ b/Mastodon/Resources/nl.lproj/InfoPlist.strings @@ -1,4 +1,4 @@ "NSCameraUsageDescription" = "Gebruikt om foto's te nemen voor je berichten"; "NSPhotoLibraryAddUsageDescription" = "Gebruikt om foto's op te slaan in de fotobibliotheek"; "NewPostShortcutItemTitle" = "Nieuw Bericht"; -"SearchShortcutItemTitle" = "Zoeken"; \ No newline at end of file +"SearchShortcutItemTitle" = "Zoek"; \ No newline at end of file diff --git a/Mastodon/Resources/sl.lproj/InfoPlist.strings b/Mastodon/Resources/sl.lproj/InfoPlist.strings new file mode 100644 index 000000000..57c8319f5 --- /dev/null +++ b/Mastodon/Resources/sl.lproj/InfoPlist.strings @@ -0,0 +1,4 @@ +"NSCameraUsageDescription" = "Uporabljeno za zajem fotografij za stanje objave"; +"NSPhotoLibraryAddUsageDescription" = "Uporabljeno za shranjevanje fotografije v knjižnico fotografij"; +"NewPostShortcutItemTitle" = "Nova objava"; +"SearchShortcutItemTitle" = "Iskanje"; \ No newline at end of file diff --git a/Mastodon/Resources/sv.lproj/InfoPlist.strings b/Mastodon/Resources/sv.lproj/InfoPlist.strings index 710865573..e0facc1d2 100644 --- a/Mastodon/Resources/sv.lproj/InfoPlist.strings +++ b/Mastodon/Resources/sv.lproj/InfoPlist.strings @@ -1,4 +1,4 @@ -"NSCameraUsageDescription" = "Used to take photo for post status"; -"NSPhotoLibraryAddUsageDescription" = "Used to save photo into the Photo Library"; -"NewPostShortcutItemTitle" = "New Post"; -"SearchShortcutItemTitle" = "Search"; \ No newline at end of file +"NSCameraUsageDescription" = "Används för att ta foto till inlägg"; +"NSPhotoLibraryAddUsageDescription" = "Används för att spara foto till bildbiblioteket"; +"NewPostShortcutItemTitle" = "Nytt inlägg"; +"SearchShortcutItemTitle" = "Sök"; \ No newline at end of file diff --git a/Mastodon/Resources/tr.lproj/InfoPlist.strings b/Mastodon/Resources/tr.lproj/InfoPlist.strings index 710865573..1c4e05659 100644 --- a/Mastodon/Resources/tr.lproj/InfoPlist.strings +++ b/Mastodon/Resources/tr.lproj/InfoPlist.strings @@ -1,4 +1,4 @@ -"NSCameraUsageDescription" = "Used to take photo for post status"; -"NSPhotoLibraryAddUsageDescription" = "Used to save photo into the Photo Library"; -"NewPostShortcutItemTitle" = "New Post"; -"SearchShortcutItemTitle" = "Search"; \ No newline at end of file +"NSCameraUsageDescription" = "Fotoğraf çekerek durum paylaşmak için kullanılır"; +"NSPhotoLibraryAddUsageDescription" = "Fotoğraf Albümü'ne fotoğraf kaydetmek için kullanılır"; +"NewPostShortcutItemTitle" = "Yeni Gönderi"; +"SearchShortcutItemTitle" = "Arama"; \ No newline at end of file diff --git a/Mastodon/Resources/vi.lproj/InfoPlist.strings b/Mastodon/Resources/vi.lproj/InfoPlist.strings index 710865573..5dd27b7bc 100644 --- a/Mastodon/Resources/vi.lproj/InfoPlist.strings +++ b/Mastodon/Resources/vi.lproj/InfoPlist.strings @@ -1,4 +1,4 @@ -"NSCameraUsageDescription" = "Used to take photo for post status"; -"NSPhotoLibraryAddUsageDescription" = "Used to save photo into the Photo Library"; -"NewPostShortcutItemTitle" = "New Post"; -"SearchShortcutItemTitle" = "Search"; \ No newline at end of file +"NSCameraUsageDescription" = "Được sử dụng để chụp ảnh cho tút"; +"NSPhotoLibraryAddUsageDescription" = "Được sử dụng để lưu ảnh vào Thư viện ảnh"; +"NewPostShortcutItemTitle" = "Viết tút"; +"SearchShortcutItemTitle" = "Tìm kiếm"; \ No newline at end of file diff --git a/Mastodon/Resources/zh-Hant.lproj/InfoPlist.strings b/Mastodon/Resources/zh-Hant.lproj/InfoPlist.strings index 710865573..737d2f857 100644 --- a/Mastodon/Resources/zh-Hant.lproj/InfoPlist.strings +++ b/Mastodon/Resources/zh-Hant.lproj/InfoPlist.strings @@ -1,4 +1,4 @@ -"NSCameraUsageDescription" = "Used to take photo for post status"; -"NSPhotoLibraryAddUsageDescription" = "Used to save photo into the Photo Library"; -"NewPostShortcutItemTitle" = "New Post"; -"SearchShortcutItemTitle" = "Search"; \ No newline at end of file +"NSCameraUsageDescription" = "用來拍照發嘟文"; +"NSPhotoLibraryAddUsageDescription" = "用來儲存照片到圖片庫"; +"NewPostShortcutItemTitle" = "新增嘟文"; +"SearchShortcutItemTitle" = "搜尋"; \ No newline at end of file diff --git a/Mastodon/Scene/Account/AccountListViewModel.swift b/Mastodon/Scene/Account/AccountListViewModel.swift index e0aaf97fc..2b8c75b8d 100644 --- a/Mastodon/Scene/Account/AccountListViewModel.swift +++ b/Mastodon/Scene/Account/AccountListViewModel.swift @@ -52,7 +52,7 @@ final class AccountListViewModel: NSObject { mastodonAuthenticationFetchedResultsController.delegate = self do { try mastodonAuthenticationFetchedResultsController.performFetch() - authentications = mastodonAuthenticationFetchedResultsController.fetchedObjects?.compactMap { $0.asRecrod } ?? [] + authentications = mastodonAuthenticationFetchedResultsController.fetchedObjects?.compactMap { $0.asRecord } ?? [] } catch { assertionFailure(error.localizedDescription) } @@ -166,7 +166,7 @@ extension AccountListViewModel { cell.badgeButton.accessibilityLabel ] .compactMap { $0 } - .joined(separator: " ") + .joined(separator: ", ") } } @@ -183,7 +183,7 @@ extension AccountListViewModel: NSFetchedResultsControllerDelegate { return } - authentications = mastodonAuthenticationFetchedResultsController.fetchedObjects?.compactMap { $0.asRecrod } ?? [] + authentications = mastodonAuthenticationFetchedResultsController.fetchedObjects?.compactMap { $0.asRecord } ?? [] } } diff --git a/Mastodon/Scene/Account/AccountViewController.swift b/Mastodon/Scene/Account/AccountViewController.swift index e25c75b01..7a0e529cc 100644 --- a/Mastodon/Scene/Account/AccountViewController.swift +++ b/Mastodon/Scene/Account/AccountViewController.swift @@ -34,7 +34,9 @@ final class AccountListViewController: UIViewController, NeedsDependency { return barButtonItem }() - let dragIndicatorView = DragIndicatorView() + lazy var dragIndicatorView = DragIndicatorView { [weak self] in + self?.dismiss(animated: true, completion: nil) + } var hasLoaded = false private(set) lazy var tableView: UITableView = { @@ -130,14 +132,6 @@ extension AccountListViewController { self.panModalTransition(to: .shortForm) } .store(in: &disposeBag) - - if UIAccessibility.isVoiceOverRunning { - let dragIndicatorTapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer - dragIndicatorView.addGestureRecognizer(dragIndicatorTapGestureRecognizer) - dragIndicatorTapGestureRecognizer.addTarget(self, action: #selector(AccountListViewController.dragIndicatorTapGestureRecognizerHandler(_:))) - dragIndicatorView.isAccessibilityElement = true - dragIndicatorView.accessibilityLabel = L10n.Scene.AccountList.dismissAccountSwitcher - } } private func setupBackgroundColor(theme: Theme) { @@ -160,10 +154,11 @@ extension AccountListViewController { logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") _ = coordinator.present(scene: .welcome, from: self, transition: .modal(animated: true, completion: nil)) } - - @objc private func dragIndicatorTapGestureRecognizerHandler(_ sender: UITapGestureRecognizer) { + + override func accessibilityPerformEscape() -> Bool { logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") dismiss(animated: true, completion: nil) + return true } } diff --git a/Mastodon/Scene/Account/Cell/AccountListTableViewCell.swift b/Mastodon/Scene/Account/Cell/AccountListTableViewCell.swift index 66f49efe8..cd214f2c7 100644 --- a/Mastodon/Scene/Account/Cell/AccountListTableViewCell.swift +++ b/Mastodon/Scene/Account/Cell/AccountListTableViewCell.swift @@ -69,6 +69,7 @@ extension AccountListTableViewCell { ]) avatarButton.setContentHuggingPriority(.defaultLow, for: .horizontal) avatarButton.setContentHuggingPriority(.defaultLow, for: .vertical) + avatarButton.isAccessibilityElement = false let labelContainerStackView = UIStackView() labelContainerStackView.axis = .vertical @@ -124,6 +125,8 @@ extension AccountListTableViewCell { badgeButton.setBadge(number: 0) checkmarkImageView.isHidden = true + + accessibilityTraits.insert(.button) } } diff --git a/Mastodon/Scene/Account/Cell/AddAccountTableViewCell.swift b/Mastodon/Scene/Account/Cell/AddAccountTableViewCell.swift index 3ff3066a2..bc47aef43 100644 --- a/Mastodon/Scene/Account/Cell/AddAccountTableViewCell.swift +++ b/Mastodon/Scene/Account/Cell/AddAccountTableViewCell.swift @@ -108,6 +108,8 @@ extension AddAccountTableViewCell { separatorLine.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), separatorLine.heightAnchor.constraint(equalToConstant: UIView.separatorLineHeight(of: contentView)), ]) + + accessibilityTraits.insert(.button) } } diff --git a/Mastodon/Scene/Account/View/DragIndicatorView.swift b/Mastodon/Scene/Account/View/DragIndicatorView.swift index 9e0ab77d5..a04d9cd8c 100644 --- a/Mastodon/Scene/Account/View/DragIndicatorView.swift +++ b/Mastodon/Scene/Account/View/DragIndicatorView.swift @@ -15,17 +15,17 @@ final class DragIndicatorView: UIView { let barView = UIView() let separatorLine = UIView.separatorLine + let onDismiss: () -> Void - override init(frame: CGRect) { - super.init(frame: frame) + init(onDismiss: @escaping () -> Void) { + self.onDismiss = onDismiss + super.init(frame: .zero) _init() } required init?(coder: NSCoder) { - super.init(coder: coder) - _init() + fatalError("init(coder:) is not supported") } - } extension DragIndicatorView { @@ -52,6 +52,14 @@ extension DragIndicatorView { separatorLine.bottomAnchor.constraint(equalTo: bottomAnchor), separatorLine.heightAnchor.constraint(equalToConstant: UIView.separatorLineHeight(of: self)), ]) + + isAccessibilityElement = true + accessibilityTraits = .button + accessibilityLabel = L10n.Scene.AccountList.dismissAccountSwitcher } + override func accessibilityActivate() -> Bool { + self.onDismiss() + return true + } } diff --git a/Mastodon/Scene/Compose/ComposeViewController.swift b/Mastodon/Scene/Compose/ComposeViewController.swift index fd5ed5817..9f287dbf8 100644 --- a/Mastodon/Scene/Compose/ComposeViewController.swift +++ b/Mastodon/Scene/Compose/ComposeViewController.swift @@ -34,7 +34,8 @@ final class ComposeViewController: UIViewController, NeedsDependency { return ComposeContentViewModel( context: context, authContext: viewModel.authContext, - kind: viewModel.kind + destination: viewModel.destination, + initialContent: viewModel.initialContent ) }() private(set) lazy var composeContentViewController: ComposeContentViewController = { @@ -58,12 +59,7 @@ final class ComposeViewController: UIViewController, NeedsDependency { let shadowBackgroundContainer = ShadowBackgroundContainer() publishButton.translatesAutoresizingMaskIntoConstraints = false shadowBackgroundContainer.addSubview(publishButton) - NSLayoutConstraint.activate([ - publishButton.topAnchor.constraint(equalTo: shadowBackgroundContainer.topAnchor), - publishButton.leadingAnchor.constraint(equalTo: shadowBackgroundContainer.leadingAnchor), - publishButton.trailingAnchor.constraint(equalTo: shadowBackgroundContainer.trailingAnchor), - publishButton.bottomAnchor.constraint(equalTo: shadowBackgroundContainer.bottomAnchor), - ]) + publishButton.pinToParent() let barButtonItem = UIBarButtonItem(customView: shadowBackgroundContainer) return barButtonItem }() @@ -93,10 +89,7 @@ extension ComposeViewController { .sink { [weak self] _ in guard let self = self else { return } guard self.traitCollection.userInterfaceIdiom == .pad else { return } - var items = [self.publishBarButtonItem] - // if self.traitCollection.horizontalSizeClass == .regular { - // items.append(self.characterCountBarButtonItem) - // } + let items = [self.publishBarButtonItem] self.navigationItem.rightBarButtonItems = items } .store(in: &disposeBag) @@ -105,12 +98,7 @@ extension ComposeViewController { addChild(composeContentViewController) composeContentViewController.view.translatesAutoresizingMaskIntoConstraints = false view.addSubview(composeContentViewController.view) - NSLayoutConstraint.activate([ - composeContentViewController.view.topAnchor.constraint(equalTo: view.topAnchor), - composeContentViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), - composeContentViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), - composeContentViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + composeContentViewController.view.pinToParent() composeContentViewController.didMove(toParent: self) // bind title @@ -175,7 +163,7 @@ extension ComposeViewController { let alertController = UIAlertController(for: error, title: nil, preferredStyle: .alert) let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default, handler: nil) alertController.addAction(okAction) - coordinator.present(scene: .alertController(alertController: alertController), from: nil, transition: .alertController(animated: true, completion: nil)) + _ = coordinator.present(scene: .alertController(alertController: alertController), from: nil, transition: .alertController(animated: true, completion: nil)) return } @@ -222,6 +210,7 @@ extension ComposeViewController { api: viewModel.context.apiService, authContext: viewModel.authContext, input: .image(image), + sizeLimit: composeContentViewModel.sizeLimit, delegate: composeContentViewModel ) } diff --git a/Mastodon/Scene/Compose/ComposeViewModel.swift b/Mastodon/Scene/Compose/ComposeViewModel.swift index bf234b095..0dcdd9a2d 100644 --- a/Mastodon/Scene/Compose/ComposeViewModel.swift +++ b/Mastodon/Scene/Compose/ComposeViewModel.swift @@ -29,7 +29,8 @@ final class ComposeViewModel { // input let context: AppContext let authContext: AuthContext - let kind: ComposeContentViewModel.Kind + let destination: ComposeContentViewModel.Destination + let initialContent: String let traitCollectionDidChangePublisher = CurrentValueSubject(Void()) // use CurrentValueSubject to make initial event emit @@ -41,17 +42,19 @@ final class ComposeViewModel { init( context: AppContext, authContext: AuthContext, - kind: ComposeContentViewModel.Kind + destination: ComposeContentViewModel.Destination, + initialContent: String = "" ) { self.context = context self.authContext = authContext - self.kind = kind + self.destination = destination + self.initialContent = initialContent // end init self.title = { - switch kind { - case .post, .hashtag, .mention: return L10n.Scene.Compose.Title.newPost - case .reply: return L10n.Scene.Compose.Title.newReply + switch destination { + case .topLevel: return L10n.Scene.Compose.Title.newPost + case .reply: return L10n.Scene.Compose.Title.newReply } }() } diff --git a/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewController.swift b/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewController.swift index 31635dc85..870bfc72a 100644 --- a/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewController.swift +++ b/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewController.swift @@ -57,12 +57,7 @@ extension DiscoveryCommunityViewController { tableView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(tableView) - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: view.topAnchor), - tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + tableView.pinToParent() tableView.refreshControl = refreshControl refreshControl.addTarget(self, action: #selector(DiscoveryCommunityViewController.refreshControlValueChanged(_:)), for: .valueChanged) diff --git a/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewModel+Diffable.swift b/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewModel+Diffable.swift index 64b4d3b6a..a25dbaf1c 100644 --- a/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewModel+Diffable.swift +++ b/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewModel+Diffable.swift @@ -18,6 +18,7 @@ extension DiscoveryCommunityViewModel { tableView: tableView, context: context, configuration: StatusSection.Configuration( + context: context, authContext: authContext, statusTableViewCellDelegate: statusTableViewCellDelegate, timelineMiddleLoaderTableViewCellDelegate: nil, @@ -58,7 +59,7 @@ extension DiscoveryCommunityViewModel { } } - diffableDataSource.applySnapshot(snapshot, animated: false) + diffableDataSource.apply(snapshot, animatingDifferences: false) } .store(in: &disposeBag) } diff --git a/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewModel+State.swift b/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewModel+State.swift index 476832b69..5045e825a 100644 --- a/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewModel+State.swift +++ b/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewModel+State.swift @@ -125,7 +125,7 @@ extension DiscoveryCommunityViewModel.State { override func didEnter(from previousState: GKState?) { super.didEnter(from: previousState) - guard let viewModel = viewModel, let stateMachine = stateMachine else { return } + guard let viewModel else { return } switch previousState { case is Reloading: diff --git a/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewController.swift b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewController.swift index 52e5e1856..dec1d9c72 100644 --- a/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewController.swift +++ b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewController.swift @@ -56,12 +56,7 @@ extension DiscoveryForYouViewController { tableView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(tableView) - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: view.topAnchor), - tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + tableView.pinToParent() tableView.delegate = self viewModel.setupDiffableDataSource( diff --git a/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel+Diffable.swift b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel+Diffable.swift index af8d6ff47..31b14c55d 100644 --- a/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel+Diffable.swift +++ b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel+Diffable.swift @@ -41,7 +41,7 @@ extension DiscoveryForYouViewModel { let items = records.map { DiscoveryItem.user($0) } snapshot.appendItems(items, toSection: .forYou) - diffableDataSource.applySnapshot(snapshot, animated: false) + diffableDataSource.apply(snapshot, animatingDifferences: false) } .store(in: &disposeBag) } diff --git a/Mastodon/Scene/Discovery/Hashtags/DiscoveryHashtagsViewController.swift b/Mastodon/Scene/Discovery/Hashtags/DiscoveryHashtagsViewController.swift index 1c855c254..64d1231fe 100644 --- a/Mastodon/Scene/Discovery/Hashtags/DiscoveryHashtagsViewController.swift +++ b/Mastodon/Scene/Discovery/Hashtags/DiscoveryHashtagsViewController.swift @@ -56,12 +56,7 @@ extension DiscoveryHashtagsViewController { tableView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(tableView) - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: view.topAnchor), - tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + tableView.pinToParent() tableView.refreshControl = refreshControl refreshControl.addTarget(self, action: #selector(DiscoveryHashtagsViewController.refreshControlValueChanged(_:)), for: .valueChanged) @@ -108,7 +103,7 @@ extension DiscoveryHashtagsViewController: UITableViewDelegate { logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(indexPath)") guard case let .hashtag(tag) = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { return } let hashtagTimelineViewModel = HashtagTimelineViewModel(context: context, authContext: viewModel.authContext, hashtag: tag.name) - coordinator.present( + _ = coordinator.present( scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel), from: self, transition: .show @@ -218,7 +213,7 @@ extension DiscoveryHashtagsViewController: TableViewControllerNavigateable { guard case let .hashtag(tag) = item else { return } let hashtagTimelineViewModel = HashtagTimelineViewModel(context: context, authContext: viewModel.authContext, hashtag: tag.name) - coordinator.present( + _ = coordinator.present( scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel), from: self, transition: .show diff --git a/Mastodon/Scene/Discovery/News/DiscoveryNewsViewController.swift b/Mastodon/Scene/Discovery/News/DiscoveryNewsViewController.swift index d1634db82..4884aafac 100644 --- a/Mastodon/Scene/Discovery/News/DiscoveryNewsViewController.swift +++ b/Mastodon/Scene/Discovery/News/DiscoveryNewsViewController.swift @@ -56,12 +56,7 @@ extension DiscoveryNewsViewController { tableView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(tableView) - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: view.topAnchor), - tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + tableView.pinToParent() tableView.delegate = self viewModel.setupDiffableDataSource( @@ -117,7 +112,7 @@ extension DiscoveryNewsViewController: UITableViewDelegate { logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(indexPath)") guard case let .link(link) = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { return } guard let url = URL(string: link.url) else { return } - coordinator.present( + _ = coordinator.present( scene: .safari(url: url), from: self, transition: .safariPresent(animated: true, completion: nil) @@ -214,7 +209,7 @@ extension DiscoveryNewsViewController: TableViewControllerNavigateable { guard case let .link(link) = item else { return } guard let url = URL(string: link.url) else { return } - coordinator.present( + _ = coordinator.present( scene: .safari(url: url), from: self, transition: .safariPresent(animated: true, completion: nil) diff --git a/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel+Diffable.swift b/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel+Diffable.swift index 11334dee8..deb8f48a7 100644 --- a/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel+Diffable.swift +++ b/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel+Diffable.swift @@ -52,7 +52,7 @@ extension DiscoveryNewsViewModel { } } - diffableDataSource.applySnapshot(snapshot, animated: false) + diffableDataSource.apply(snapshot, animatingDifferences: false) } .store(in: &disposeBag) } diff --git a/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel+State.swift b/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel+State.swift index 7c802cde3..8170d5b1c 100644 --- a/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel+State.swift +++ b/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel+State.swift @@ -125,8 +125,7 @@ extension DiscoveryNewsViewModel.State { override func didEnter(from previousState: GKState?) { super.didEnter(from: previousState) - guard let viewModel = viewModel, let stateMachine = stateMachine else { return } - + guard let viewModel else { return } switch previousState { case is Reloading: diff --git a/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewController.swift b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewController.swift index 893f564a3..9d772a27e 100644 --- a/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewController.swift +++ b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewController.swift @@ -58,12 +58,7 @@ extension DiscoveryPostsViewController { tableView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(tableView) - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: view.topAnchor), - tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + tableView.pinToParent() discoveryIntroBannerView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(discoveryIntroBannerView) diff --git a/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel+Diffable.swift b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel+Diffable.swift index afa0594d5..99d68796d 100644 --- a/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel+Diffable.swift +++ b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel+Diffable.swift @@ -18,6 +18,7 @@ extension DiscoveryPostsViewModel { tableView: tableView, context: context, configuration: StatusSection.Configuration( + context: context, authContext: authContext, statusTableViewCellDelegate: statusTableViewCellDelegate, timelineMiddleLoaderTableViewCellDelegate: nil, @@ -58,7 +59,7 @@ extension DiscoveryPostsViewModel { } } - diffableDataSource.applySnapshot(snapshot, animated: false) + diffableDataSource.apply(snapshot, animatingDifferences: false) } .store(in: &disposeBag) } diff --git a/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel+State.swift b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel+State.swift index 3ed245e99..fd413927e 100644 --- a/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel+State.swift +++ b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel+State.swift @@ -126,7 +126,7 @@ extension DiscoveryPostsViewModel.State { override func didEnter(from previousState: GKState?) { super.didEnter(from: previousState) - guard let viewModel = viewModel, let stateMachine = stateMachine else { return } + guard let viewModel else { return } switch previousState { case is Reloading: diff --git a/Mastodon/Scene/HashtagTimeline/HashtagTimelineHeaderView.swift b/Mastodon/Scene/HashtagTimeline/HashtagTimelineHeaderView.swift new file mode 100644 index 000000000..3af2cf07b --- /dev/null +++ b/Mastodon/Scene/HashtagTimeline/HashtagTimelineHeaderView.swift @@ -0,0 +1,173 @@ +// +// HashtagTimelineHeaderView.swift +// Mastodon +// +// Created by Marcus Kida on 22.11.22. +// + +import UIKit +import CoreDataStack +import MastodonSDK +import MastodonUI +import MastodonAsset +import MastodonLocalization + +fileprivate extension CGFloat { + static let padding: CGFloat = 16 + static let descriptionLabelSpacing: CGFloat = 12 +} + +final class HashtagTimelineHeaderView: UIView { + struct Data { + let name: String + let following: Bool + let postCount: Int + let participantsCount: Int + let postsTodayCount: Int + + static func from(_ entity: Mastodon.Entity.Tag) -> Self { + Data( + name: entity.name, + following: entity.following == true, + postCount: (entity.history ?? []).reduce(0) { res, acc in + res + (Int(acc.uses) ?? 0) + }, + participantsCount: (entity.history ?? []).reduce(0) { res, acc in + res + (Int(acc.accounts) ?? 0) + }, + postsTodayCount: Int(entity.history?.first?.uses ?? "0") ?? 0 + ) + } + + static func from(_ entity: Tag) -> Self { + Data( + name: entity.name, + following: entity.following, + postCount: entity.histories.reduce(0) { res, acc in + res + (Int(acc.uses) ?? 0) + }, + participantsCount: entity.histories.reduce(0) { res, acc in + res + (Int(acc.accounts) ?? 0) + }, + postsTodayCount: Int(entity.histories.first?.uses ?? "0") ?? 0 + ) + } + } + + let titleLabel = UILabel() + + let postCountLabel = UILabel() + let participantsLabel = UILabel() + let postsTodayLabel = UILabel() + + let postCountDescLabel = UILabel() + let participantsDescLabel = UILabel() + let postsTodayDescLabel = UILabel() + + private var widthConstraint: NSLayoutConstraint! + + var onButtonTapped: (() -> Void)? + + let followButton: UIButton = { + let button = HashtagTimelineHeaderViewActionButton() + button.cornerRadius = 10 + button.contentEdgeInsets = UIEdgeInsets(top: 6, left: 16, bottom: 5, right: 16) // set 28pt height + button.titleLabel?.font = .systemFont(ofSize: 14, weight: .bold) + return button + }() + + init() { + super.init(frame: .zero) + setupLayout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +private extension HashtagTimelineHeaderView { + func setupLayout() { + [titleLabel, postCountLabel, participantsLabel, postsTodayLabel, postCountDescLabel, participantsDescLabel, postsTodayDescLabel, followButton].forEach { + $0.translatesAutoresizingMaskIntoConstraints = false + addSubview($0) + } + + // hashtag name / title + titleLabel.font = UIFontMetrics(forTextStyle: .title2).scaledFont(for: .systemFont(ofSize: 22, weight: .bold)) + + [postCountLabel, participantsLabel, postsTodayLabel].forEach { + $0.font = UIFontMetrics(forTextStyle: .title3).scaledFont(for: .systemFont(ofSize: 20, weight: .bold)) + $0.text = "999" + } + + [postCountDescLabel, participantsDescLabel, postsTodayDescLabel].forEach { + $0.font = UIFontMetrics(forTextStyle: .footnote).scaledFont(for: .systemFont(ofSize: 13, weight: .regular)) + } + + postCountDescLabel.text = L10n.Scene.FollowedTags.Header.posts + participantsDescLabel.text = L10n.Scene.FollowedTags.Header.participants + postsTodayDescLabel.text = L10n.Scene.FollowedTags.Header.postsToday + + followButton.addAction(UIAction(handler: { [weak self] _ in + self?.onButtonTapped?() + }), for: .touchUpInside) + + widthConstraint = widthAnchor.constraint(greaterThanOrEqualToConstant: 0) + + NSLayoutConstraint.activate([ + widthConstraint, + + titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: .padding), + titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: .padding), + titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -CGFloat.padding), + + postCountLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: .padding), + postCountLabel.centerXAnchor.constraint(equalTo: postCountDescLabel.centerXAnchor), + postCountDescLabel.leadingAnchor.constraint(equalTo: titleLabel.leadingAnchor), + + participantsDescLabel.leadingAnchor.constraint(equalTo: postCountDescLabel.trailingAnchor, constant: .descriptionLabelSpacing), + participantsLabel.centerXAnchor.constraint(equalTo: participantsDescLabel.centerXAnchor), + participantsLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: .padding), + + postsTodayDescLabel.leadingAnchor.constraint(equalTo: participantsDescLabel.trailingAnchor, constant: .descriptionLabelSpacing), + postsTodayLabel.centerXAnchor.constraint(equalTo: postsTodayDescLabel.centerXAnchor), + postsTodayLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: .padding), + + postCountDescLabel.topAnchor.constraint(equalTo: postCountLabel.bottomAnchor), + participantsDescLabel.topAnchor.constraint(equalTo: participantsLabel.bottomAnchor), + postsTodayDescLabel.topAnchor.constraint(equalTo: postsTodayLabel.bottomAnchor), + + postCountDescLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -CGFloat.padding), + participantsDescLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -CGFloat.padding), + postsTodayDescLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -CGFloat.padding), + + followButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -CGFloat.padding), + followButton.bottomAnchor.constraint(equalTo: postsTodayDescLabel.bottomAnchor), + followButton.topAnchor.constraint(equalTo: postsTodayLabel.topAnchor) + ]) + } +} + +extension HashtagTimelineHeaderView { + func update(_ entity: HashtagTimelineHeaderView.Data) { + titleLabel.text = "#\(entity.name)" + followButton.setTitle(entity.following == true ? L10n.Scene.FollowedTags.Actions.unfollow : L10n.Scene.FollowedTags.Actions.follow, for: .normal) + + followButton.backgroundColor = entity.following == true ? Asset.Colors.Button.tagUnfollow.color : Asset.Colors.Button.tagFollow.color + + followButton.setTitleColor( + entity.following == true ? Asset.Colors.Button.tagFollow.color : Asset.Colors.Button.tagUnfollow.color, + for: .normal + ) + + postCountLabel.text = String(entity.postCount) + participantsLabel.text = String(entity.participantsCount) + postsTodayLabel.text = String(entity.postsTodayCount) + } + + func updateWidthConstraint(_ constant: CGFloat) { + widthConstraint.constant = constant + } +} diff --git a/Mastodon/Scene/HashtagTimeline/HashtagTimelineHeaderViewActionButton.swift b/Mastodon/Scene/HashtagTimeline/HashtagTimelineHeaderViewActionButton.swift new file mode 100644 index 000000000..7efeb15d0 --- /dev/null +++ b/Mastodon/Scene/HashtagTimeline/HashtagTimelineHeaderViewActionButton.swift @@ -0,0 +1,47 @@ +// +// HashtagTimelineHeaderViewActionButton.swift +// Mastodon +// +// Created by Marcus Kida on 25.11.22. +// + +import UIKit +import MastodonUI +import MastodonAsset + +class HashtagTimelineHeaderViewActionButton: RoundedEdgesButton { + + init() { + super.init(frame: .zero) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func layoutSubviews() { + super.layoutSubviews() + + let shadowColor: UIColor = { + switch traitCollection.userInterfaceStyle { + case .dark: + return .darkGray + default: + return .lightGray + } + }() + + layer.setupShadow( + color: shadowColor, + alpha: 1, + x: 0, + y: 1, + blur: 2, + spread: 0, + roundedRect: bounds, + byRoundingCorners: .allCorners, + cornerRadii: CGSize(width: cornerRadius, height: cornerRadius) + ) + } +} + diff --git a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController.swift b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController.swift index aad97283c..75d9029e2 100644 --- a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController.swift +++ b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController.swift @@ -15,6 +15,7 @@ import MastodonAsset import MastodonCore import MastodonUI import MastodonLocalization +import MastodonSDK final class HashtagTimelineViewController: UIViewController, NeedsDependency, MediaPreviewableViewController { @@ -27,6 +28,17 @@ final class HashtagTimelineViewController: UIViewController, NeedsDependency, Me var disposeBag = Set() var viewModel: HashtagTimelineViewModel! + + private lazy var headerView: HashtagTimelineHeaderView = { + let headerView = HashtagTimelineHeaderView() + headerView.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + headerView.heightAnchor.constraint(equalToConstant: 118), + ]) + + return headerView + }() let composeBarButtonItem: UIBarButtonItem = { let barButtonItem = UIBarButtonItem() @@ -80,12 +92,7 @@ extension HashtagTimelineViewController { tableView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(tableView) - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: view.topAnchor), - tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + tableView.pinToParent() tableView.delegate = self viewModel.setupDiffableDataSource( @@ -119,11 +126,20 @@ extension HashtagTimelineViewController { self?.updatePromptTitle() } .store(in: &disposeBag) + + viewModel.hashtagDetails + .receive(on: DispatchQueue.main) + .sink { [weak self] tag in + guard let tag = tag else { return } + self?.updateHeaderView(with: tag) + } + .store(in: &disposeBag) } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) + viewModel.viewWillAppear() tableView.deselectRow(with: transitionCoordinator, animated: animated) } @@ -153,7 +169,30 @@ extension HashtagTimelineViewController { subtitle = L10n.Plural.peopleTalking(peopleTalkingNumber) } } +} +extension HashtagTimelineViewController { + private func updateHeaderView(with tag: Mastodon.Entity.Tag) { + if tableView.tableHeaderView == nil { + tableView.tableHeaderView = headerView + } + headerView.update(HashtagTimelineHeaderView.Data.from(tag)) + headerView.onButtonTapped = { [weak self] in + switch tag.following { + case .some(false): + self?.viewModel.followTag() + case .some(true): + self?.viewModel.unfollowTag() + default: + break + } + } + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + headerView.updateWidthConstraint(tableView.bounds.width) + } } extension HashtagTimelineViewController { @@ -167,10 +206,13 @@ extension HashtagTimelineViewController { @objc private func composeBarButtonItemPressed(_ sender: UIBarButtonItem) { os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + let hashtag = "#" + viewModel.hashtag + UITextChecker.learnWord(hashtag) let composeViewModel = ComposeViewModel( context: context, authContext: viewModel.authContext, - kind: .hashtag(hashtag: viewModel.hashtag) + destination: .topLevel, + initialContent: hashtag ) _ = coordinator.present(scene: .compose(viewModel: composeViewModel), from: self, transition: .modal(animated: true, completion: nil)) } diff --git a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel+Diffable.swift b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel+Diffable.swift index 8d8b0126a..c7c0a3bd7 100644 --- a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel+Diffable.swift +++ b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel+Diffable.swift @@ -20,6 +20,7 @@ extension HashtagTimelineViewModel { tableView: tableView, context: context, configuration: StatusSection.Configuration( + context: context, authContext: authContext, statusTableViewCellDelegate: statusTableViewCellDelegate, timelineMiddleLoaderTableViewCellDelegate: nil, diff --git a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel+State.swift b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel+State.swift index deb9de0a2..c35715f1e 100644 --- a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel+State.swift +++ b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel+State.swift @@ -70,7 +70,7 @@ extension HashtagTimelineViewModel.State { override func didEnter(from previousState: GKState?) { super.didEnter(from: previousState) - guard let viewModel = viewModel, let stateMachine = stateMachine else { return } + guard let stateMachine = stateMachine else { return } stateMachine.enter(Loading.self) } @@ -127,7 +127,7 @@ extension HashtagTimelineViewModel.State { override func didEnter(from previousState: GKState?) { super.didEnter(from: previousState) - guard let viewModel = viewModel, let stateMachine = stateMachine else { return } + guard let viewModel else { return } switch previousState { case is Reloading: diff --git a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel.swift b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel.swift index af4d2a01a..888bc720c 100644 --- a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel.swift +++ b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel.swift @@ -23,7 +23,7 @@ final class HashtagTimelineViewModel { var disposeBag = Set() var needLoadMiddleIndex: Int? = nil - + // input let context: AppContext let authContext: AuthContext @@ -32,10 +32,11 @@ final class HashtagTimelineViewModel { let timelinePredicate = CurrentValueSubject(nil) let hashtagEntity = CurrentValueSubject(nil) let listBatchFetchViewModel = ListBatchFetchViewModel() - + // output var diffableDataSource: UITableViewDiffableDataSource? let didLoadLatest = PassthroughSubject() + let hashtagDetails = CurrentValueSubject(nil) // bottom loader private(set) lazy var stateMachine: GKStateMachine = { @@ -61,6 +62,7 @@ final class HashtagTimelineViewModel { domain: authContext.mastodonAuthenticationBox.domain, additionalTweetPredicate: nil ) + updateTagInformation() // end init } @@ -68,5 +70,55 @@ final class HashtagTimelineViewModel { os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s:", ((#file as NSString).lastPathComponent), #line, #function) } + func viewWillAppear() { + let predicate = Tag.predicate( + domain: authContext.mastodonAuthenticationBox.domain, + name: hashtag + ) + + guard + let object = Tag.findOrFetch(in: context.managedObjectContext, matching: predicate) + else { + return hashtagDetails.send(hashtagDetails.value?.copy(following: false)) + } + + hashtagDetails.send(hashtagDetails.value?.copy(following: object.following)) + } } +extension HashtagTimelineViewModel { + func followTag() { + self.hashtagDetails.send(hashtagDetails.value?.copy(following: true)) + Task { @MainActor in + let tag = try? await context.apiService.followTag( + for: hashtag, + authenticationBox: authContext.mastodonAuthenticationBox + ).value + self.hashtagDetails.send(tag) + } + } + + func unfollowTag() { + self.hashtagDetails.send(hashtagDetails.value?.copy(following: false)) + Task { @MainActor in + let tag = try? await context.apiService.unfollowTag( + for: hashtag, + authenticationBox: authContext.mastodonAuthenticationBox + ).value + self.hashtagDetails.send(tag) + } + } +} + +private extension HashtagTimelineViewModel { + func updateTagInformation() { + Task { @MainActor in + let tag = try? await context.apiService.getTagInformation( + for: hashtag, + authenticationBox: authContext.mastodonAuthenticationBox + ).value + + self.hashtagDetails.send(tag) + } + } +} diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift index d057c0376..8ad798165 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift @@ -122,6 +122,10 @@ extension HomeTimelineViewController { identifier: nil, options: [], children: [ + UIAction(title: "Toggle Visible Touches", image: UIImage(systemName: "hand.tap"), attributes: []) { _ in + guard let window = UIApplication.shared.getKeyWindow() as? TouchesVisibleWindow else { return } + window.touchesVisible = !window.touchesVisible + }, UIAction(title: "Toggle EmptyView", image: UIImage(systemName: "clear"), attributes: []) { [weak self] action in guard let self = self else { return } if self.emptyView.superview != nil { @@ -300,7 +304,7 @@ extension HomeTimelineViewController { } @objc private func showWelcomeAction(_ sender: UIAction) { - coordinator.present(scene: .welcome, from: self, transition: .modal(animated: true, completion: nil)) + _ = coordinator.present(scene: .welcome, from: self, transition: .modal(animated: true, completion: nil)) } @objc private func showRegisterAction(_ sender: UIAction) { @@ -332,12 +336,12 @@ extension HomeTimelineViewController { @objc private func showConfirmEmail(_ sender: UIAction) { let mastodonConfirmEmailViewModel = MastodonConfirmEmailViewModel() - coordinator.present(scene: .mastodonConfirmEmail(viewModel: mastodonConfirmEmailViewModel), from: nil, transition: .modal(animated: true, completion: nil)) + _ = coordinator.present(scene: .mastodonConfirmEmail(viewModel: mastodonConfirmEmailViewModel), from: nil, transition: .modal(animated: true, completion: nil)) } @objc private func showAccountList(_ sender: UIAction) { let accountListViewModel = AccountListViewModel(context: context, authContext: viewModel.authContext) - coordinator.present(scene: .accountList(viewModel: accountListViewModel), from: self, transition: .modal(animated: true, completion: nil)) + _ = coordinator.present(scene: .accountList(viewModel: accountListViewModel), from: self, transition: .modal(animated: true, completion: nil)) } @objc private func showProfileAction(_ sender: UIAction) { @@ -347,12 +351,12 @@ extension HomeTimelineViewController { guard let self = self else { return } guard let textField = alertController?.textFields?.first else { return } let profileViewModel = RemoteProfileViewModel(context: self.context, authContext: self.viewModel.authContext, userID: textField.text ?? "") - self.coordinator.present(scene: .profile(viewModel: profileViewModel), from: self, transition: .show) + _ = self.coordinator.present(scene: .profile(viewModel: profileViewModel), from: self, transition: .show) } alertController.addAction(showAction) let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) alertController.addAction(cancelAction) - coordinator.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil)) + _ = coordinator.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil)) } @objc private func showThreadAction(_ sender: UIAction) { @@ -362,12 +366,12 @@ extension HomeTimelineViewController { guard let self = self else { return } guard let textField = alertController?.textFields?.first else { return } let threadViewModel = RemoteThreadViewModel(context: self.context, authContext: self.viewModel.authContext, statusID: textField.text ?? "") - self.coordinator.present(scene: .thread(viewModel: threadViewModel), from: self, transition: .show) + _ = self.coordinator.present(scene: .thread(viewModel: threadViewModel), from: self, transition: .show) } alertController.addAction(showAction) let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) alertController.addAction(cancelAction) - coordinator.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil)) + _ = coordinator.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil)) } private func showNotification(_ sender: UIAction, notificationType: Mastodon.Entity.Notification.NotificationType) { @@ -436,7 +440,7 @@ extension HomeTimelineViewController { authContext: viewModel.authContext, setting: currentSetting ) - coordinator.present( + _ = coordinator.present( scene: .settings(viewModel: settingsViewModel), from: self, transition: .modal(animated: true, completion: nil) diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift index e83101796..3caafef3d 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift @@ -158,12 +158,7 @@ extension HomeTimelineViewController { tableView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(tableView) - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: view.topAnchor), - tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + tableView.pinToParent() // // layout publish progress publishProgressView.translatesAutoresizingMaskIntoConstraints = false @@ -388,14 +383,14 @@ extension HomeTimelineViewController { @objc private func manuallySearchButtonPressed(_ sender: UIButton) { os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) let searchDetailViewModel = SearchDetailViewModel(authContext: viewModel.authContext) - coordinator.present(scene: .searchDetail(viewModel: searchDetailViewModel), from: self, transition: .modal(animated: true, completion: nil)) + _ = coordinator.present(scene: .searchDetail(viewModel: searchDetailViewModel), from: self, transition: .modal(animated: true, completion: nil)) } @objc private func settingBarButtonItemPressed(_ sender: UIBarButtonItem) { os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) guard let setting = context.settingService.currentSetting.value else { return } let settingsViewModel = SettingsViewModel(context: context, authContext: viewModel.authContext, setting: setting) - coordinator.present(scene: .settings(viewModel: settingsViewModel), from: self, transition: .modal(animated: true, completion: nil)) + _ = coordinator.present(scene: .settings(viewModel: settingsViewModel), from: self, transition: .modal(animated: true, completion: nil)) } @objc private func refreshControlValueChanged(_ sender: RefreshControl) { diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+Diffable.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+Diffable.swift index cabc655c9..3d59b3403 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+Diffable.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+Diffable.swift @@ -22,6 +22,7 @@ extension HomeTimelineViewModel { tableView: tableView, context: context, configuration: StatusSection.Configuration( + context: context, authContext: authContext, statusTableViewCellDelegate: statusTableViewCellDelegate, timelineMiddleLoaderTableViewCellDelegate: timelineMiddleLoaderTableViewCellDelegate, @@ -41,7 +42,7 @@ extension HomeTimelineViewModel { guard let self = self else { return } guard let diffableDataSource = self.diffableDataSource else { return } self.logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): incoming \(records.count) objects") - Task { + Task { @MainActor in let start = CACurrentMediaTime() defer { let end = CACurrentMediaTime() @@ -98,22 +99,22 @@ extension HomeTimelineViewModel { self.logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): snapshot has changes") } - guard let difference = await self.calculateReloadSnapshotDifference( + guard let difference = self.calculateReloadSnapshotDifference( tableView: tableView, oldSnapshot: oldSnapshot, newSnapshot: newSnapshot ) else { - await self.updateSnapshotUsingReloadData(snapshot: newSnapshot) + self.updateSnapshotUsingReloadData(snapshot: newSnapshot) self.didLoadLatest.send() self.logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): applied new snapshot") return } - await self.updateSnapshotUsingReloadData(snapshot: newSnapshot) - await tableView.scrollToRow(at: difference.targetIndexPath, at: .top, animated: false) - var contentOffset = await tableView.contentOffset - contentOffset.y = await tableView.contentOffset.y - difference.sourceDistanceToTableViewTopEdge - await tableView.setContentOffset(contentOffset, animated: false) + self.updateSnapshotUsingReloadData(snapshot: newSnapshot) + tableView.scrollToRow(at: difference.targetIndexPath, at: .top, animated: false) + var contentOffset = tableView.contentOffset + contentOffset.y = tableView.contentOffset.y - difference.sourceDistanceToTableViewTopEdge + tableView.setContentOffset(contentOffset, animated: false) self.didLoadLatest.send() self.logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): applied new snapshot") } // end Task @@ -130,17 +131,13 @@ extension HomeTimelineViewModel { snapshot: NSDiffableDataSourceSnapshot, animatingDifferences: Bool ) async { - diffableDataSource?.apply(snapshot, animatingDifferences: animatingDifferences) + await diffableDataSource?.apply(snapshot, animatingDifferences: animatingDifferences) } @MainActor func updateSnapshotUsingReloadData( snapshot: NSDiffableDataSourceSnapshot - ) async { - if #available(iOS 15.0, *) { - await self.diffableDataSource?.applySnapshotUsingReloadData(snapshot) - } else { - diffableDataSource?.applySnapshot(snapshot, animated: false, completion: nil) - } + ) { + self.diffableDataSource?.applySnapshotUsingReloadData(snapshot) } struct Difference { diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+LoadLatestState.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+LoadLatestState.swift index 41cea6b58..d243c9a96 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+LoadLatestState.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+LoadLatestState.swift @@ -62,7 +62,7 @@ extension HomeTimelineViewModel.LoadLatestState { override func didEnter(from previousState: GKState?) { super.didEnter(from: previousState) - guard let viewModel = viewModel, let stateMachine = stateMachine else { return } + guard let viewModel else { return } let latestFeedRecords = viewModel.fetchedResultsController.records.prefix(APIService.onceRequestStatusMaxCount) let parentManagedObjectContext = viewModel.fetchedResultsController.fetchedResultsController.managedObjectContext @@ -86,6 +86,8 @@ extension HomeTimelineViewModel.LoadLatestState { await enter(state: Idle.self) viewModel.homeTimelineNavigationBarTitleViewModel.receiveLoadingStateCompletion(.finished) + viewModel.context.instanceService.updateMutesAndBlocks() + // stop refresher if no new statuses let statuses = response.value let newStatuses = statuses.filter { !latestStatusIDs.contains($0.id) } diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift index edd431e52..c7c967305 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift @@ -14,7 +14,6 @@ import CoreData import CoreDataStack import GameplayKit import AlamofireImage -import DateToolsSwift import MastodonCore import MastodonUI @@ -149,12 +148,7 @@ extension HomeTimelineViewModel { } // reconfigure item - if #available(iOS 15.0, *) { - snapshot.reconfigureItems([item]) - } else { - // Fallback on earlier versions - snapshot.reloadItems([item]) - } + snapshot.reconfigureItems([item]) await updateSnapshotUsingReloadData(snapshot: snapshot) // fetch data @@ -177,15 +171,10 @@ extension HomeTimelineViewModel { } // reconfigure item again - if #available(iOS 15.0, *) { - snapshot.reconfigureItems([item]) - } else { - // Fallback on earlier versions - snapshot.reloadItems([item]) - } + snapshot.reconfigureItems([item]) await updateSnapshotUsingReloadData(snapshot: snapshot) } - + } // MARK: - SuggestionAccountViewModelDelegate diff --git a/Mastodon/Scene/HomeTimeline/View/HomeTimelineNavigationBarTitleView.swift b/Mastodon/Scene/HomeTimeline/View/HomeTimelineNavigationBarTitleView.swift index 400b4ee98..7e51057bc 100644 --- a/Mastodon/Scene/HomeTimeline/View/HomeTimelineNavigationBarTitleView.swift +++ b/Mastodon/Scene/HomeTimeline/View/HomeTimelineNavigationBarTitleView.swift @@ -47,12 +47,7 @@ extension HomeTimelineNavigationBarTitleView { private func _init() { containerView.translatesAutoresizingMaskIntoConstraints = false addSubview(containerView) - NSLayoutConstraint.activate([ - containerView.topAnchor.constraint(equalTo: topAnchor), - containerView.leadingAnchor.constraint(equalTo: leadingAnchor), - containerView.trailingAnchor.constraint(equalTo: trailingAnchor), - containerView.bottomAnchor.constraint(equalTo: bottomAnchor), - ]) + containerView.pinToParent() containerView.addArrangedSubview(logoButton) button.translatesAutoresizingMaskIntoConstraints = false @@ -108,8 +103,8 @@ extension HomeTimelineNavigationBarTitleView { logoButton.setImage(Asset.Asset.mastodonTextLogo.image.withRenderingMode(.alwaysTemplate), for: .normal) logoButton.contentMode = .center logoButton.isHidden = false - logoButton.accessibilityLabel = "Logo Button" // TODO :i18n - logoButton.accessibilityHint = "Tap to scroll to top and tap again to previous location" + logoButton.accessibilityLabel = L10n.Scene.HomeTimeline.NavigationBarState.Accessibility.logoLabel // TODO :i18n + logoButton.accessibilityHint = L10n.Scene.HomeTimeline.NavigationBarState.Accessibility.logoHint case .newPostButton: configureButton( title: L10n.Scene.HomeTimeline.NavigationBarState.newPosts, diff --git a/Mastodon/Scene/MediaPreview/AltViewController.swift b/Mastodon/Scene/MediaPreview/AltViewController.swift new file mode 100644 index 000000000..05a3e5b09 --- /dev/null +++ b/Mastodon/Scene/MediaPreview/AltViewController.swift @@ -0,0 +1,92 @@ +// +// AltViewController.swift +// Mastodon +// +// Created by Jed Fox on 2022-11-26. +// + +import SwiftUI + +class AltViewController: UIViewController { + let textView = { + let textView: UITextView + + if #available(iOS 16, *) { + // TODO: update code below to use TextKit 2 when dropping iOS 15 support + textView = UITextView(usingTextLayoutManager: false) + } else { + textView = UITextView() + } + + textView.textContainer.maximumNumberOfLines = 0 + textView.textContainer.lineBreakMode = .byWordWrapping + textView.font = .preferredFont(forTextStyle: .callout) + textView.isScrollEnabled = true + textView.backgroundColor = .clear + textView.isOpaque = false + textView.isEditable = false + textView.tintColor = .white + textView.textContainerInset = UIEdgeInsets(top: 12, left: 8, bottom: 8, right: 8) + textView.contentInsetAdjustmentBehavior = .always + textView.verticalScrollIndicatorInsets.bottom = 4 + + return textView + }() + + init(alt: String, sourceView: UIView?) { + textView.text = alt + super.init(nibName: nil, bundle: nil) + self.modalPresentationStyle = .popover + self.popoverPresentationController?.delegate = self + self.popoverPresentationController?.permittedArrowDirections = .up + self.popoverPresentationController?.sourceView = sourceView + self.overrideUserInterfaceStyle = .dark + } + + @MainActor required dynamic init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + super.loadView() + view.translatesAutoresizingMaskIntoConstraints = false + } + + override func viewDidLoad() { + super.viewDidLoad() + + textView.translatesAutoresizingMaskIntoConstraints = false + view.backgroundColor = .systemBackground + view.addSubview(textView) + + textView.pinToParent() + NSLayoutConstraint.activate([ + textView.widthAnchor.constraint(lessThanOrEqualToConstant: 400), + ]) + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + UIView.performWithoutAnimation { + + let size = textView.layoutManager.boundingRect(forGlyphRange: NSMakeRange(0, (textView.textStorage.string as NSString).length), in: textView.textContainer).size + + preferredContentSize = CGSize( + width: size.width + (8 + textView.textContainer.lineFragmentPadding) * 2, + height: size.height + 12 + (textView.textContainer.lineFragmentPadding) * 2 + ) + } + } + + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + textView.font = .preferredFont(forTextStyle: .callout) + } +} + +// MARK: UIPopoverPresentationControllerDelegate +extension AltViewController: UIPopoverPresentationControllerDelegate { + func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle { + .none + } +} diff --git a/Mastodon/Scene/MediaPreview/Image/MediaPreviewImageView.swift b/Mastodon/Scene/MediaPreview/Image/MediaPreviewImageView.swift index 05f2ce70f..7df8861b0 100644 --- a/Mastodon/Scene/MediaPreview/Image/MediaPreviewImageView.swift +++ b/Mastodon/Scene/MediaPreview/Image/MediaPreviewImageView.swift @@ -9,6 +9,7 @@ import os.log import func AVFoundation.AVMakeRect import UIKit import FLAnimatedImage +import VisionKit final class MediaPreviewImageView: UIScrollView { @@ -28,9 +29,21 @@ final class MediaPreviewImageView: UIScrollView { tapGestureRecognizer.numberOfTapsRequired = 2 return tapGestureRecognizer }() - + private var containerFrame: CGRect? - + + private var _interaction: UIInteraction? = { + if #available(iOS 16.0, *) { + return ImageAnalysisInteraction() + } else { + return nil + } + }() + @available(iOS 16.0, *) + var liveTextInteraction: ImageAnalysisInteraction { + _interaction as! ImageAnalysisInteraction + } + override init(frame: CGRect) { super.init(frame: frame) _init() @@ -55,10 +68,13 @@ extension MediaPreviewImageView { maximumZoomScale = 4.0 addSubview(imageView) - + doubleTapGestureRecognizer.addTarget(self, action: #selector(MediaPreviewImageView.doubleTapGestureRecognizerHandler(_:))) imageView.addGestureRecognizer(doubleTapGestureRecognizer) - + if #available(iOS 16.0, *) { + imageView.addInteraction(liveTextInteraction) + } + delegate = self } @@ -112,23 +128,30 @@ extension MediaPreviewImageView { // reset to normal zoomScale = minimumZoomScale - let imageViewSize = AVMakeRect(aspectRatio: image.size, insideRect: container.bounds).size - let imageContentInset: UIEdgeInsets = { - if imageViewSize.width == container.bounds.width { - return UIEdgeInsets(top: 0.5 * (container.bounds.height - imageViewSize.height), left: 0, bottom: 0, right: 0) - } else { - return UIEdgeInsets(top: 0, left: 0.5 * (container.bounds.width - imageViewSize.width), bottom: 0, right: 0) - } - }() + let imageViewSize = AVMakeRect(aspectRatio: image.size, insideRect: container.bounds.inset(by: container.safeAreaInsets)).size imageView.frame = CGRect(origin: .zero, size: imageViewSize) if imageView.image == nil { imageView.image = image } contentSize = imageViewSize - contentInset = imageContentInset centerScrollViewContents() - contentOffset = CGPoint(x: -contentInset.left, y: -contentInset.top) + + if #available(iOS 16.0, *) { + Task.detached(priority: .userInitiated) { + do { + let analysis = try await ImageAnalyzer.shared.analyze(image, configuration: ImageAnalyzer.Configuration([.text, .machineReadableCode])) + await MainActor.run { + self.liveTextInteraction.analysis = analysis + self.liveTextInteraction.preferredInteractionTypes = .automatic + } + } catch { + await MainActor.run { + self.liveTextInteraction.preferredInteractionTypes = [] + } + } + } + } os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: setup image for container %s", ((#file as NSString).lastPathComponent), #line, #function, container.frame.debugDescription) } @@ -192,10 +215,7 @@ extension MediaPreviewImageView { frame.size = realImageSize imageView.frame = frame - let screenSize = self.frame.size - let offsetX = screenSize.width > realImageSize.width ? (screenSize.width - realImageSize.width) / 2 : 0 - let offsetY = screenSize.height > realImageSize.height ? (screenSize.height - realImageSize.height) / 2 : 0 - contentInset = UIEdgeInsets(top: offsetY, left: offsetX, bottom: offsetY, right: offsetX) + contentInset = self.safeAreaInsets // The scroll view has zoomed, so you need to re-center the contents let scrollViewSize = scrollViewVisibleSize diff --git a/Mastodon/Scene/MediaPreview/Image/MediaPreviewImageViewController.swift b/Mastodon/Scene/MediaPreview/Image/MediaPreviewImageViewController.swift index 127c4c0c0..68bc0219f 100644 --- a/Mastodon/Scene/MediaPreview/Image/MediaPreviewImageViewController.swift +++ b/Mastodon/Scene/MediaPreview/Image/MediaPreviewImageViewController.swift @@ -11,6 +11,7 @@ import Combine import MastodonAsset import MastodonLocalization import FLAnimatedImage +import VisionKit protocol MediaPreviewImageViewControllerDelegate: AnyObject { func mediaPreviewImageViewController(_ viewController: MediaPreviewImageViewController, tapGestureRecognizerDidTrigger tapGestureRecognizer: UITapGestureRecognizer) @@ -31,7 +32,7 @@ final class MediaPreviewImageViewController: UIViewController { let tapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer let longPressGestureRecognizer = UILongPressGestureRecognizer() - + deinit { os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) previewImageView.imageView.af.cancelImageRequest() @@ -42,7 +43,10 @@ extension MediaPreviewImageViewController { override func viewDidLoad() { super.viewDidLoad() - + + if #available(iOS 16.0, *) { + previewImageView.liveTextInteraction.delegate = self + } previewImageView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(previewImageView) NSLayoutConstraint.activate([ @@ -53,7 +57,9 @@ extension MediaPreviewImageViewController { ]) tapGestureRecognizer.addTarget(self, action: #selector(MediaPreviewImageViewController.tapGestureRecognizerHandler(_:))) + tapGestureRecognizer.delegate = self longPressGestureRecognizer.addTarget(self, action: #selector(MediaPreviewImageViewController.longPressGestureRecognizerHandler(_:))) + longPressGestureRecognizer.delegate = self tapGestureRecognizer.require(toFail: previewImageView.doubleTapGestureRecognizer) tapGestureRecognizer.require(toFail: longPressGestureRecognizer) previewImageView.addGestureRecognizer(tapGestureRecognizer) @@ -62,30 +68,21 @@ extension MediaPreviewImageViewController { let previewImageViewContextMenuInteraction = UIContextMenuInteraction(delegate: self) previewImageView.addInteraction(previewImageViewContextMenuInteraction) - switch viewModel.item { - case .remote(let imageContext): - previewImageView.imageView.accessibilityLabel = imageContext.altText - - if let thumbnail = imageContext.thumbnail { - previewImageView.imageView.image = thumbnail - previewImageView.setup(image: thumbnail, container: self.previewImageView, forceUpdate: true) - } - - previewImageView.imageView.setImage( - url: imageContext.assetURL, - placeholder: imageContext.thumbnail, - scaleToSize: nil - ) { [weak self] image in - guard let self = self else { return } - guard let image = image else { return } - self.previewImageView.setup(image: image, container: self.previewImageView, forceUpdate: true) - } - - case .local(let imageContext): - let image = imageContext.image - previewImageView.imageView.image = image - previewImageView.setup(image: image, container: previewImageView, forceUpdate: true) - + previewImageView.imageView.accessibilityLabel = viewModel.item.altText + + if let thumbnail = viewModel.item.thumbnail { + previewImageView.imageView.image = thumbnail + previewImageView.setup(image: thumbnail, container: self.previewImageView, forceUpdate: true) + } + + previewImageView.imageView.setImage( + url: viewModel.item.assetURL, + placeholder: viewModel.item.thumbnail, + scaleToSize: nil + ) { [weak self] image in + guard let self = self else { return } + guard let image = image else { return } + self.previewImageView.setup(image: image, container: self.previewImageView, forceUpdate: true) } } @@ -105,10 +102,54 @@ extension MediaPreviewImageViewController { } +extension MediaPreviewImageViewController: MediaPreviewPage { + func setShowingChrome(_ showingChrome: Bool) { + if #available(iOS 16.0, *) { + UIView.animate(withDuration: 0.3) { + self.previewImageView.liveTextInteraction.setSupplementaryInterfaceHidden(!showingChrome, animated: true) + } + } + } +} + +// MARK: - ImageAnalysisInteractionDelegate +@available(iOS 16.0, *) +extension MediaPreviewImageViewController: ImageAnalysisInteractionDelegate { + func presentingViewController(for interaction: ImageAnalysisInteraction) -> UIViewController? { + self + } +} + +// MARK: - UIGestureRecognizerDelegate +extension MediaPreviewImageViewController: UIGestureRecognizerDelegate { + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { + if #available(iOS 16.0, *) { + let location = touch.location(in: previewImageView.imageView) + // for tap gestures, only items that can be tapped are relevant + if gestureRecognizer is UITapGestureRecognizer { + return !previewImageView.liveTextInteraction.hasSupplementaryInterface(at: location) + && !previewImageView.liveTextInteraction.hasDataDetector(at: location) + } else { + // for long press, block out everything + return !previewImageView.liveTextInteraction.hasInteractiveItem(at: location) + } + } else { + return true + } + } +} + // MARK: - UIContextMenuInteractionDelegate extension MediaPreviewImageViewController: UIContextMenuInteractionDelegate { func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? { os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + + if #available(iOS 16.0, *) { + if previewImageView.liveTextInteraction.hasInteractiveItem(at: previewImageView.imageView.convert(location, from: previewImageView)) { + return nil + } + } + let previewProvider: UIContextMenuContentPreviewProvider = { () -> UIViewController? in return nil diff --git a/Mastodon/Scene/MediaPreview/Image/MediaPreviewImageViewModel.swift b/Mastodon/Scene/MediaPreview/Image/MediaPreviewImageViewModel.swift index e82118f78..3a4d9edd2 100644 --- a/Mastodon/Scene/MediaPreview/Image/MediaPreviewImageViewModel.swift +++ b/Mastodon/Scene/MediaPreview/Image/MediaPreviewImageViewModel.swift @@ -30,19 +30,10 @@ class MediaPreviewImageViewModel { extension MediaPreviewImageViewModel { - public enum ImagePreviewItem { - case remote(RemoteImageContext) - case local(LocalImageContext) - } - - public struct RemoteImageContext { + public struct ImagePreviewItem { let assetURL: URL? let thumbnail: UIImage? let altText: String? } - - public struct LocalImageContext { - let image: UIImage - } } diff --git a/Mastodon/Scene/MediaPreview/MediaPreviewViewController.swift b/Mastodon/Scene/MediaPreview/MediaPreviewViewController.swift index c6552bcba..ede0be296 100644 --- a/Mastodon/Scene/MediaPreview/MediaPreviewViewController.swift +++ b/Mastodon/Scene/MediaPreview/MediaPreviewViewController.swift @@ -16,8 +16,6 @@ import MastodonLocalization final class MediaPreviewViewController: UIViewController, NeedsDependency { - static let closeButtonSize = CGSize(width: 30, height: 30) - weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } @@ -26,24 +24,23 @@ final class MediaPreviewViewController: UIViewController, NeedsDependency { let visualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .systemMaterial)) let pagingViewController = MediaPreviewPagingViewController() - - let closeButtonBackground: UIVisualEffectView = { - let backgroundView = UIVisualEffectView(effect: UIBlurEffect(style: .systemUltraThinMaterial)) - backgroundView.alpha = 0.9 - backgroundView.layer.masksToBounds = true - backgroundView.layer.cornerRadius = MediaPreviewViewController.closeButtonSize.width * 0.5 - return backgroundView + + let topToolbar: UIStackView = { + let stackView = TouchTransparentStackView() + stackView.axis = .horizontal + stackView.distribution = .equalSpacing + stackView.alignment = .fill + stackView.translatesAutoresizingMaskIntoConstraints = false + return stackView }() - - let closeButtonBackgroundVisualEffectView = UIVisualEffectView(effect: UIVibrancyEffect(blurEffect: UIBlurEffect(style: .systemUltraThinMaterial))) - - let closeButton: UIButton = { - let button = HighlightDimmableButton() - button.expandEdgeInsets = UIEdgeInsets(top: -10, left: -10, bottom: -10, right: -10) - button.imageView?.tintColor = .label + + let closeButton = HUDButton { button in button.setImage(UIImage(systemName: "xmark", withConfiguration: UIImage.SymbolConfiguration(pointSize: 16, weight: .bold))!, for: .normal) - return button - }() + } + + let altButton = HUDButton { button in + button.setTitle("ALT", for: .normal) + } deinit { os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) @@ -65,42 +62,32 @@ extension MediaPreviewViewController { pagingViewController.view.translatesAutoresizingMaskIntoConstraints = false addChild(pagingViewController) visualEffectView.contentView.addSubview(pagingViewController.view) - NSLayoutConstraint.activate([ - visualEffectView.topAnchor.constraint(equalTo: pagingViewController.view.topAnchor), - visualEffectView.bottomAnchor.constraint(equalTo: pagingViewController.view.bottomAnchor), - visualEffectView.leadingAnchor.constraint(equalTo: pagingViewController.view.leadingAnchor), - visualEffectView.trailingAnchor.constraint(equalTo: pagingViewController.view.trailingAnchor), - ]) + visualEffectView.pinTo(to: pagingViewController.view) pagingViewController.didMove(toParent: self) - - closeButtonBackground.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(closeButtonBackground) - NSLayoutConstraint.activate([ - closeButtonBackground.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor, constant: 12), - closeButtonBackground.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor) - ]) - closeButtonBackgroundVisualEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight] - closeButtonBackground.contentView.addSubview(closeButtonBackgroundVisualEffectView) - closeButton.translatesAutoresizingMaskIntoConstraints = false - closeButtonBackgroundVisualEffectView.contentView.addSubview(closeButton) + view.addSubview(topToolbar) NSLayoutConstraint.activate([ - closeButton.topAnchor.constraint(equalTo: closeButtonBackgroundVisualEffectView.topAnchor), - closeButton.leadingAnchor.constraint(equalTo: closeButtonBackgroundVisualEffectView.leadingAnchor), - closeButtonBackgroundVisualEffectView.trailingAnchor.constraint(equalTo: closeButton.trailingAnchor), - closeButtonBackgroundVisualEffectView.bottomAnchor.constraint(equalTo: closeButton.bottomAnchor), - closeButton.heightAnchor.constraint(equalToConstant: MediaPreviewViewController.closeButtonSize.height).priority(.defaultHigh), - closeButton.widthAnchor.constraint(equalToConstant: MediaPreviewViewController.closeButtonSize.width).priority(.defaultHigh), + topToolbar.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor, constant: 12), + topToolbar.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor), + topToolbar.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor), ]) - + + topToolbar.addArrangedSubview(closeButton) + NSLayoutConstraint.activate([ + closeButton.widthAnchor.constraint(equalToConstant: HUDButton.height).priority(.defaultHigh), + ]) + + topToolbar.addArrangedSubview(altButton) + viewModel.mediaPreviewImageViewControllerDelegate = self pagingViewController.interPageSpacing = 10 pagingViewController.delegate = self pagingViewController.dataSource = viewModel - closeButton.addTarget(self, action: #selector(MediaPreviewViewController.closeButtonPressed(_:)), for: .touchUpInside) - + closeButton.button.addTarget(self, action: #selector(MediaPreviewViewController.closeButtonPressed(_:)), for: .touchUpInside) + altButton.button.addTarget(self, action: #selector(MediaPreviewViewController.altButtonPressed(_:)), for: .touchUpInside) + // bind view model viewModel.$currentPage .receive(on: DispatchQueue.main) @@ -131,12 +118,37 @@ extension MediaPreviewViewController { let attachment = previewContext.attachments[index] return attachment.kind == .video // not hide buttno for audio }() - self.closeButtonBackground.isHidden = needsHideCloseButton + self.closeButton.isHidden = needsHideCloseButton default: break } } .store(in: &disposeBag) + + viewModel.$altText + .receive(on: DispatchQueue.main) + .sink { [weak self] altText in + guard let self else { return } + UIView.animate(withDuration: 0.3) { + if altText == nil { + self.altButton.alpha = 0 + } else { + self.altButton.alpha = 1 + } + } + } + .store(in: &disposeBag) + + viewModel.$showingChrome + .receive(on: DispatchQueue.main) + .removeDuplicates() + .sink { [weak self] showingChrome in + UIView.animate(withDuration: 0.3) { + self?.setNeedsStatusBarAppearanceUpdate() + self?.topToolbar.alpha = showingChrome ? 1 : 0 + } + } + .store(in: &disposeBag) // viewModel.$isPoping // .receive(on: DispatchQueue.main) @@ -153,13 +165,25 @@ extension MediaPreviewViewController { } +extension MediaPreviewViewController { + + override var prefersStatusBarHidden: Bool { + !viewModel.showingChrome + } + +} + extension MediaPreviewViewController { @objc private func closeButtonPressed(_ sender: UIButton) { - os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) dismiss(animated: true, completion: nil) } - + + @objc private func altButtonPressed(_ sender: UIButton) { + guard let alt = viewModel.altText else { return } + + present(AltViewController(alt: alt, sourceView: sender), animated: true) + } } // MARK: - MediaPreviewingViewController @@ -239,8 +263,11 @@ extension MediaPreviewViewController: MediaPreviewImageViewControllerDelegate { let location = tapGestureRecognizer.location(in: viewController.previewImageView.imageView) let isContainsTap = viewController.previewImageView.imageView.frame.contains(location) - guard !isContainsTap else { return } - dismiss(animated: true, completion: nil) + if isContainsTap { + self.viewModel.showingChrome.toggle() + } else { + dismiss(animated: true, completion: nil) + } } func mediaPreviewImageViewController(_ viewController: MediaPreviewImageViewController, longPressGestureRecognizerDidTrigger longPressGestureRecognizer: UILongPressGestureRecognizer) { @@ -253,19 +280,8 @@ extension MediaPreviewViewController: MediaPreviewImageViewControllerDelegate { ) { switch action { case .savePhoto: - let _savePublisher: AnyPublisher? = { - switch viewController.viewModel.item { - case .remote(let previewContext): - guard let assetURL = previewContext.assetURL else { return nil } - return context.photoLibraryService.save(imageSource: .url(assetURL)) - case .local(let previewContext): - return context.photoLibraryService.save(imageSource: .image(previewContext.image)) - } - }() - guard let savePublisher = _savePublisher else { - return - } - savePublisher + guard let assetURL = viewController.viewModel.item.assetURL else { return } + context.photoLibraryService.save(imageSource: .url(assetURL)) .sink { [weak self] completion in guard let self = self else { return } switch completion { @@ -276,7 +292,7 @@ extension MediaPreviewViewController: MediaPreviewImageViewControllerDelegate { title: L10n.Common.Alerts.SavePhotoFailure.title, message: L10n.Common.Alerts.SavePhotoFailure.message ) - self.coordinator.present( + _ = self.coordinator.present( scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil) @@ -289,20 +305,9 @@ extension MediaPreviewViewController: MediaPreviewImageViewControllerDelegate { } .store(in: &context.disposeBag) case .copyPhoto: - let _copyPublisher: AnyPublisher? = { - switch viewController.viewModel.item { - case .remote(let previewContext): - guard let assetURL = previewContext.assetURL else { return nil } - return context.photoLibraryService.copy(imageSource: .url(assetURL)) - case .local(let previewContext): - return context.photoLibraryService.copy(imageSource: .image(previewContext.image)) - } - }() - guard let copyPublisher = _copyPublisher else { - return - } + guard let assetURL = viewController.viewModel.item.assetURL else { return } - copyPublisher + context.photoLibraryService.copy(imageSource: .url(assetURL)) .sink { completion in switch completion { case .failure(let error): @@ -321,13 +326,8 @@ extension MediaPreviewViewController: MediaPreviewImageViewControllerDelegate { let activityViewController = UIActivityViewController( activityItems: { var activityItems: [Any] = [] - switch viewController.viewModel.item { - case .remote(let previewContext): - if let assetURL = previewContext.assetURL { - activityItems.append(assetURL) - } - case .local(let previewContext): - activityItems.append(previewContext.image) + if let assetURL = viewController.viewModel.item.assetURL { + activityItems.append(assetURL) } return activityItems }(), @@ -342,12 +342,12 @@ extension MediaPreviewViewController: MediaPreviewImageViewControllerDelegate { extension MediaPreviewViewController { - var closeKeyCommand: UIKeyCommand { + func closeKeyCommand(input: String) -> UIKeyCommand { UIKeyCommand( title: L10n.Scene.Preview.Keyboard.closePreview, image: nil, action: #selector(MediaPreviewViewController.closePreviewKeyCommandHandler(_:)), - input: "i", + input: input, modifierFlags: [], propertyList: nil, alternates: [], @@ -391,7 +391,8 @@ extension MediaPreviewViewController { override var keyCommands: [UIKeyCommand] { return [ - closeKeyCommand, + closeKeyCommand(input: UIKeyCommand.inputEscape), + closeKeyCommand(input: "i"), showNextKeyCommand, showPreviousKeyCommand, ] diff --git a/Mastodon/Scene/MediaPreview/MediaPreviewViewModel.swift b/Mastodon/Scene/MediaPreview/MediaPreviewViewModel.swift index c43d24945..a6b604d6f 100644 --- a/Mastodon/Scene/MediaPreview/MediaPreviewViewModel.swift +++ b/Mastodon/Scene/MediaPreview/MediaPreviewViewModel.swift @@ -12,6 +12,10 @@ import CoreDataStack import Pageboy import MastodonCore +protocol MediaPreviewPage: UIViewController { + func setShowingChrome(_ showingChrome: Bool) +} + final class MediaPreviewViewModel: NSObject { weak var mediaPreviewImageViewControllerDelegate: MediaPreviewImageViewControllerDelegate? @@ -22,9 +26,13 @@ final class MediaPreviewViewModel: NSObject { let transitionItem: MediaPreviewTransitionItem @Published var currentPage: Int - + @Published var showingChrome = true + @Published var altText: String? + // output - let viewControllers: [UIViewController] + let viewControllers: [MediaPreviewPage] + + private var disposeBag: Set = [] init( context: AppContext, @@ -34,9 +42,12 @@ final class MediaPreviewViewModel: NSObject { self.context = context self.item = item var currentPage = 0 - var viewControllers: [UIViewController] = [] + var viewControllers: [MediaPreviewPage] = [] + var getAltText = { (page: Int) -> String? in nil } switch item { case .attachment(let previewContext): + getAltText = { previewContext.attachments[$0].altDescription } + currentPage = previewContext.initialIndex for (i, attachment) in previewContext.attachments.enumerated() { switch attachment.kind { @@ -44,11 +55,11 @@ final class MediaPreviewViewModel: NSObject { let viewController = MediaPreviewImageViewController() let viewModel = MediaPreviewImageViewModel( context: context, - item: .remote(.init( + item: .init( assetURL: attachment.assetURL.flatMap { URL(string: $0) }, thumbnail: previewContext.thumbnail(at: i), altText: attachment.altDescription - )) + ) ) viewController.viewModel = viewModel viewControllers.append(viewController) @@ -58,7 +69,8 @@ final class MediaPreviewViewModel: NSObject { context: context, item: .gif(.init( assetURL: attachment.assetURL.flatMap { URL(string: $0) }, - previewURL: attachment.previewURL.flatMap { URL(string: $0) } + previewURL: attachment.previewURL.flatMap { URL(string: $0) }, + altText: attachment.altDescription )) ) viewController.viewModel = viewModel @@ -69,7 +81,8 @@ final class MediaPreviewViewModel: NSObject { context: context, item: .video(.init( assetURL: attachment.assetURL.flatMap { URL(string: $0) }, - previewURL: attachment.previewURL.flatMap { URL(string: $0) } + previewURL: attachment.previewURL.flatMap { URL(string: $0) }, + altText: attachment.altDescription )) ) viewController.viewModel = viewModel @@ -80,11 +93,11 @@ final class MediaPreviewViewModel: NSObject { let viewController = MediaPreviewImageViewController() let viewModel = MediaPreviewImageViewModel( context: context, - item: .remote(.init( + item: .init( assetURL: previewContext.assetURL.flatMap { URL(string: $0) }, thumbnail: previewContext.thumbnail, altText: nil - )) + ) ) viewController.viewModel = viewModel viewControllers.append(viewController) @@ -92,11 +105,11 @@ final class MediaPreviewViewModel: NSObject { let viewController = MediaPreviewImageViewController() let viewModel = MediaPreviewImageViewModel( context: context, - item: .remote(.init( + item: .init( assetURL: previewContext.assetURL.flatMap { URL(string: $0) }, thumbnail: previewContext.thumbnail, altText: nil - )) + ) ) viewController.viewModel = viewModel viewControllers.append(viewController) @@ -106,6 +119,18 @@ final class MediaPreviewViewModel: NSObject { self.currentPage = currentPage self.transitionItem = transitionItem super.init() + + self.$currentPage + .map(getAltText) + .assign(to: &$altText) + + for viewController in viewControllers { + self.$showingChrome + .sink { [weak viewController] showingChrome in + viewController?.setShowingChrome(showingChrome) + } + .store(in: &disposeBag) + } } } diff --git a/Mastodon/Scene/MediaPreview/Video/MediaPreviewVideoViewController.swift b/Mastodon/Scene/MediaPreview/Video/MediaPreviewVideoViewController.swift index 7bdbbfed2..e924f38d4 100644 --- a/Mastodon/Scene/MediaPreview/Video/MediaPreviewVideoViewController.swift +++ b/Mastodon/Scene/MediaPreview/Video/MediaPreviewVideoViewController.swift @@ -39,23 +39,13 @@ extension MediaPreviewVideoViewController { addChild(playerViewController) playerViewController.view.translatesAutoresizingMaskIntoConstraints = false view.addSubview(playerViewController.view) - NSLayoutConstraint.activate([ - playerViewController.view.centerXAnchor.constraint(equalTo: view.centerXAnchor), - playerViewController.view.centerYAnchor.constraint(equalTo: view.centerYAnchor), - playerViewController.view.widthAnchor.constraint(equalTo: view.widthAnchor), - playerViewController.view.heightAnchor.constraint(equalTo: view.heightAnchor), - ]) + playerViewController.view.pinToParent() playerViewController.didMove(toParent: self) if let contentOverlayView = playerViewController.contentOverlayView { previewImageView.translatesAutoresizingMaskIntoConstraints = false contentOverlayView.addSubview(previewImageView) - NSLayoutConstraint.activate([ - previewImageView.topAnchor.constraint(equalTo: contentOverlayView.topAnchor), - previewImageView.leadingAnchor.constraint(equalTo: contentOverlayView.leadingAnchor), - previewImageView.trailingAnchor.constraint(equalTo: contentOverlayView.trailingAnchor), - previewImageView.bottomAnchor.constraint(equalTo: contentOverlayView.bottomAnchor), - ]) + previewImageView.pinToParent() } playerViewController.delegate = self @@ -90,6 +80,12 @@ extension MediaPreviewVideoViewController { } } + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + playerViewController.didMove(toParent: self) + } + } // MARK: - ShareActivityProvider @@ -111,6 +107,12 @@ extension MediaPreviewVideoViewController { // } //} +extension MediaPreviewVideoViewController: MediaPreviewPage { + func setShowingChrome(_ showingChrome: Bool) { + // TODO: does this do anything? + } +} + // MARK: - AVPlayerViewControllerDelegate extension MediaPreviewVideoViewController: AVPlayerViewControllerDelegate { diff --git a/Mastodon/Scene/MediaPreview/Video/MediaPreviewVideoViewModel.swift b/Mastodon/Scene/MediaPreview/Video/MediaPreviewVideoViewModel.swift index 97e5f955b..a6542d464 100644 --- a/Mastodon/Scene/MediaPreview/Video/MediaPreviewVideoViewModel.swift +++ b/Mastodon/Scene/MediaPreview/Video/MediaPreviewVideoViewModel.swift @@ -130,12 +130,14 @@ extension MediaPreviewVideoViewModel { struct RemoteVideoContext { let assetURL: URL? let previewURL: URL? + let altText: String? // let thumbnail: UIImage? } struct RemoteGIFContext { let assetURL: URL? let previewURL: URL? + let altText: String? } } diff --git a/Mastodon/Scene/Notification/Button/NotificationAvatarButton.swift b/Mastodon/Scene/Notification/Button/NotificationAvatarButton.swift deleted file mode 100644 index 26abfbd23..000000000 --- a/Mastodon/Scene/Notification/Button/NotificationAvatarButton.swift +++ /dev/null @@ -1,86 +0,0 @@ -// -// NotificationAvatarButton.swift -// Mastodon -// -// Created by MainasuK Cirno on 2021-7-21. -// - -import UIKit -import FLAnimatedImage -import MastodonUI - -final class NotificationAvatarButton: AvatarButton { - - // Size fixed - static let containerSize = CGSize(width: 35, height: 35) - static let badgeImageViewSize = CGSize(width: 24, height: 24) - static let badgeImageMaskSize = CGSize(width: badgeImageViewSize.width + 4, height: badgeImageViewSize.height + 4) - - let badgeImageView: UIImageView = { - let imageView = RoundedImageView() - imageView.contentMode = .center - imageView.isOpaque = true - imageView.layer.shouldRasterize = true - imageView.layer.rasterizationScale = UIScreen.main.scale - return imageView - }() - - override func _init() { - super._init() - - size = CGSize(width: 35, height: 35) - - let path: CGPath = { - let path = CGMutablePath() - path.addRect(CGRect(origin: .zero, size: NotificationAvatarButton.containerSize)) - let x: CGFloat = { - if UIApplication.shared.userInterfaceLayoutDirection == .rightToLeft { - return -0.5 * NotificationAvatarButton.badgeImageMaskSize.width - } else { - return NotificationAvatarButton.containerSize.width - 0.5 * NotificationAvatarButton.badgeImageMaskSize.width - } - }() - path.addPath(UIBezierPath( - ovalIn: CGRect( - x: x, - y: NotificationAvatarButton.containerSize.height - 0.5 * NotificationAvatarButton.badgeImageMaskSize.width, - width: NotificationAvatarButton.badgeImageMaskSize.width, - height: NotificationAvatarButton.badgeImageMaskSize.height - ) - ).cgPath) - return path - }() - - let maskShapeLayer = CAShapeLayer() - maskShapeLayer.backgroundColor = UIColor.black.cgColor - maskShapeLayer.fillRule = .evenOdd - maskShapeLayer.path = path - avatarImageView.layer.mask = maskShapeLayer - - badgeImageView.translatesAutoresizingMaskIntoConstraints = false - addSubview(badgeImageView) - NSLayoutConstraint.activate([ - badgeImageView.centerXAnchor.constraint(equalTo: trailingAnchor), - badgeImageView.centerYAnchor.constraint(equalTo: bottomAnchor), - badgeImageView.widthAnchor.constraint(equalToConstant: NotificationAvatarButton.badgeImageViewSize.width).priority(.required - 1), - badgeImageView.heightAnchor.constraint(equalToConstant: NotificationAvatarButton.badgeImageViewSize.height).priority(.required - 1), - ]) - } - - override func updateAppearance() { - super.updateAppearance() - badgeImageView.alpha = primaryActionState.contains(.highlighted) ? 0.6 : 1.0 - } - -} - -final class RoundedImageView: UIImageView { - - override func layoutSubviews() { - super.layoutSubviews() - - layer.masksToBounds = true - layer.cornerRadius = bounds.width / 2 - layer.cornerCurve = .circular - } -} diff --git a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift index d088bb783..9b7416afc 100644 --- a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift +++ b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift @@ -55,12 +55,7 @@ extension NotificationTimelineViewController { tableView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(tableView) - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: view.topAnchor), - tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + tableView.pinToParent() tableView.delegate = self viewModel.setupDiffableDataSource( diff --git a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+Diffable.swift b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+Diffable.swift index cb623ff15..0331f401e 100644 --- a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+Diffable.swift +++ b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+Diffable.swift @@ -111,17 +111,13 @@ extension NotificationTimelineViewModel { snapshot: NSDiffableDataSourceSnapshot, animatingDifferences: Bool ) async { - diffableDataSource?.apply(snapshot, animatingDifferences: animatingDifferences) + await diffableDataSource?.apply(snapshot, animatingDifferences: animatingDifferences) } @MainActor func updateSnapshotUsingReloadData( snapshot: NSDiffableDataSourceSnapshot ) async { - if #available(iOS 15.0, *) { - await self.diffableDataSource?.applySnapshotUsingReloadData(snapshot) - } else { - diffableDataSource?.applySnapshot(snapshot, animated: false, completion: nil) - } + await self.diffableDataSource?.applySnapshotUsingReloadData(snapshot) } } diff --git a/Mastodon/Scene/Onboarding/ConfirmEmail/MastodonConfirmEmailViewController.swift b/Mastodon/Scene/Onboarding/ConfirmEmail/MastodonConfirmEmailViewController.swift index a90da1e81..ae8a4933e 100644 --- a/Mastodon/Scene/Onboarding/ConfirmEmail/MastodonConfirmEmailViewController.swift +++ b/Mastodon/Scene/Onboarding/ConfirmEmail/MastodonConfirmEmailViewController.swift @@ -189,7 +189,7 @@ extension MastodonConfirmEmailViewController { alertController.addAction(openEmailAction) alertController.addAction(cancelAction) alertController.preferredAction = openEmailAction - self.coordinator.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil)) + _ = self.coordinator.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil)) } @objc private func resendButtonPressed(_ sender: UIButton) { @@ -197,18 +197,17 @@ extension MastodonConfirmEmailViewController { let resendAction = UIAlertAction(title: L10n.Scene.ConfirmEmail.DontReceiveEmail.resendEmail, style: .default) { _ in let url = Mastodon.API.resendEmailURL(domain: self.viewModel.authenticateInfo.domain) let viewModel = MastodonResendEmailViewModel(resendEmailURL: url, email: self.viewModel.email) - self.coordinator.present(scene: .mastodonResendEmail(viewModel: viewModel), from: self, transition: .modal(animated: true, completion: nil)) + _ = self.coordinator.present(scene: .mastodonResendEmail(viewModel: viewModel), from: self, transition: .modal(animated: true, completion: nil)) } let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default) { _ in } alertController.addAction(resendAction) alertController.addAction(okAction) - self.coordinator.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil)) + _ = self.coordinator.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil)) } func showEmailAppAlert() { let clients = ThirdPartyMailClient.clients - let application = UIApplication.shared let availableClients = clients.filter { client -> Bool in ThirdPartyMailer.isMailClientAvailable(client) } @@ -220,14 +219,14 @@ extension MastodonConfirmEmailViewController { alertController.addAction(alertAction) _ = availableClients.compactMap { client -> UIAlertAction in let alertAction = UIAlertAction(title: client.name, style: .default) { _ in - _ = ThirdPartyMailer.open(client, completionHandler: nil) + ThirdPartyMailer.open(client, completionHandler: nil) } alertController.addAction(alertAction) return alertAction } let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .cancel, handler: nil) alertController.addAction(cancelAction) - self.coordinator.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil)) + _ = self.coordinator.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil)) } } diff --git a/Mastodon/Scene/Onboarding/Login/ContentSizedTableView.swift b/Mastodon/Scene/Onboarding/Login/ContentSizedTableView.swift new file mode 100644 index 000000000..e0c8a9228 --- /dev/null +++ b/Mastodon/Scene/Onboarding/Login/ContentSizedTableView.swift @@ -0,0 +1,22 @@ +// +// MastodonLoginTableView.swift +// Mastodon +// +// Created by Nathan Mattes on 13.11.22. +// + +import UIKit + +// Source: https://stackoverflow.com/a/48623673 +final class ContentSizedTableView: UITableView { + override var contentSize:CGSize { + didSet { + invalidateIntrinsicContentSize() + } + } + + override var intrinsicContentSize: CGSize { + layoutIfNeeded() + return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height) + } +} diff --git a/Mastodon/Scene/Onboarding/Login/MastodonLoginServerTableViewCell.swift b/Mastodon/Scene/Onboarding/Login/MastodonLoginServerTableViewCell.swift new file mode 100644 index 000000000..2fb599015 --- /dev/null +++ b/Mastodon/Scene/Onboarding/Login/MastodonLoginServerTableViewCell.swift @@ -0,0 +1,12 @@ +// +// MastodonLoginServerTableViewCell.swift +// Mastodon +// +// Created by Nathan Mattes on 11.11.22. +// + +import UIKit + +class MastodonLoginServerTableViewCell: UITableViewCell { + static let reuseIdentifier = "MastodonLoginServerTableViewCell" +} diff --git a/Mastodon/Scene/Onboarding/Login/MastodonLoginView.swift b/Mastodon/Scene/Onboarding/Login/MastodonLoginView.swift new file mode 100644 index 000000000..d5f8b51d4 --- /dev/null +++ b/Mastodon/Scene/Onboarding/Login/MastodonLoginView.swift @@ -0,0 +1,151 @@ +// +// MastodonLoginView.swift +// Mastodon +// +// Created by Nathan Mattes on 10.11.22. +// + +import UIKit +import MastodonAsset +import MastodonLocalization + +class MastodonLoginView: UIView { + + // List with (filtered) domains + + let titleLabel: UILabel + let subtitleLabel: UILabel + private let headerStackView: UIStackView + + let searchTextField: UITextField + private let searchTextFieldLeftView: UIView + private let searchTextFieldMagnifyingGlass: UIImageView + private let searchContainerLeftPaddingView: UIView + + let tableView: UITableView + let navigationActionView: NavigationActionView + var bottomConstraint: NSLayoutConstraint? + + override init(frame: CGRect) { + + titleLabel = UILabel() + titleLabel.font = MastodonLoginViewController.largeTitleFont + titleLabel.textColor = MastodonLoginViewController.largeTitleTextColor + titleLabel.text = L10n.Scene.Login.title + titleLabel.numberOfLines = 0 + + subtitleLabel = UILabel() + subtitleLabel.font = MastodonLoginViewController.subTitleFont + subtitleLabel.textColor = MastodonLoginViewController.subTitleTextColor + subtitleLabel.text = L10n.Scene.Login.subtitle + subtitleLabel.numberOfLines = 0 + + headerStackView = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel]) + headerStackView.axis = .vertical + headerStackView.spacing = 16 + headerStackView.translatesAutoresizingMaskIntoConstraints = false + + searchTextFieldMagnifyingGlass = UIImageView(image: UIImage( + systemName: "magnifyingglass", + withConfiguration: UIImage.SymbolConfiguration(pointSize: 15, weight: .regular) + )) + searchTextFieldMagnifyingGlass.tintColor = Asset.Colors.Label.secondary.color.withAlphaComponent(0.6) + searchTextFieldMagnifyingGlass.translatesAutoresizingMaskIntoConstraints = false + + searchContainerLeftPaddingView = UIView() + searchContainerLeftPaddingView.translatesAutoresizingMaskIntoConstraints = false + + searchTextFieldLeftView = UIView() + searchTextFieldLeftView.addSubview(searchTextFieldMagnifyingGlass) + searchTextFieldLeftView.addSubview(searchContainerLeftPaddingView) + + searchTextField = UITextField() + searchTextField.translatesAutoresizingMaskIntoConstraints = false + searchTextField.backgroundColor = Asset.Scene.Onboarding.searchBarBackground.color + searchTextField.placeholder = L10n.Scene.Login.ServerSearchField.placeholder + searchTextField.leftView = searchTextFieldLeftView + searchTextField.leftViewMode = .always + searchTextField.layer.cornerRadius = 10 + searchTextField.keyboardType = .URL + searchTextField.autocorrectionType = .no + searchTextField.autocapitalizationType = .none + + tableView = ContentSizedTableView() + tableView.translatesAutoresizingMaskIntoConstraints = false + tableView.backgroundColor = Asset.Scene.Onboarding.textFieldBackground.color + tableView.keyboardDismissMode = .onDrag + tableView.layer.cornerRadius = 10 + + navigationActionView = NavigationActionView() + navigationActionView.translatesAutoresizingMaskIntoConstraints = false + + super.init(frame: frame) + + addSubview(headerStackView) + addSubview(searchTextField) + addSubview(tableView) + addSubview(navigationActionView) + backgroundColor = Asset.Scene.Onboarding.background.color + + setupConstraints() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupConstraints() { + + let bottomConstraint = safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: navigationActionView.bottomAnchor) + + let constraints = [ + + headerStackView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor), + headerStackView.leadingAnchor.constraint(equalTo: readableContentGuide.leadingAnchor), + headerStackView.trailingAnchor.constraint(equalTo: readableContentGuide.trailingAnchor), + + searchTextField.topAnchor.constraint(equalTo: headerStackView.bottomAnchor, constant: 32), + searchTextField.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16), + searchTextField.heightAnchor.constraint(equalToConstant: 55), + trailingAnchor.constraint(equalTo: searchTextField.trailingAnchor, constant: 16), + + searchTextFieldMagnifyingGlass.topAnchor.constraint(equalTo: searchTextFieldLeftView.topAnchor), + searchTextFieldMagnifyingGlass.leadingAnchor.constraint(equalTo: searchTextFieldLeftView.leadingAnchor, constant: 8), + searchTextFieldMagnifyingGlass.bottomAnchor.constraint(equalTo: searchTextFieldLeftView.bottomAnchor), + + searchContainerLeftPaddingView.topAnchor.constraint(equalTo: searchTextFieldLeftView.topAnchor), + searchContainerLeftPaddingView.leadingAnchor.constraint(equalTo: searchTextFieldMagnifyingGlass.trailingAnchor), + searchContainerLeftPaddingView.trailingAnchor.constraint(equalTo: searchTextFieldLeftView.trailingAnchor), + searchContainerLeftPaddingView.bottomAnchor.constraint(equalTo: searchTextFieldLeftView.bottomAnchor), + searchContainerLeftPaddingView.widthAnchor.constraint(equalToConstant: 4).priority(.defaultHigh), + + tableView.topAnchor.constraint(equalTo: searchTextField.bottomAnchor, constant: 2), + tableView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16), + trailingAnchor.constraint(equalTo: tableView.trailingAnchor, constant: 16), + tableView.bottomAnchor.constraint(lessThanOrEqualTo: navigationActionView.topAnchor), + + navigationActionView.leadingAnchor.constraint(equalTo: leadingAnchor), + navigationActionView.trailingAnchor.constraint(equalTo: trailingAnchor), + bottomConstraint, + ] + + self.bottomConstraint = bottomConstraint + NSLayoutConstraint.activate(constraints) + } + + func updateCorners(numberOfResults: Int = 0) { + + tableView.isHidden = (numberOfResults == 0) + tableView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner] + + let maskedCorners: CACornerMask + + if numberOfResults == 0 { + maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMinYCorner, .layerMaxXMaxYCorner] + } else { + maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] + } + + searchTextField.layer.maskedCorners = maskedCorners + } +} diff --git a/Mastodon/Scene/Onboarding/Login/MastodonLoginViewController.swift b/Mastodon/Scene/Onboarding/Login/MastodonLoginViewController.swift new file mode 100644 index 000000000..9f9ce5d75 --- /dev/null +++ b/Mastodon/Scene/Onboarding/Login/MastodonLoginViewController.swift @@ -0,0 +1,288 @@ +// +// MastodonLoginViewController.swift +// Mastodon +// +// Created by Nathan Mattes on 09.11.22. +// + +import UIKit +import MastodonSDK +import MastodonCore +import MastodonAsset +import Combine +import AuthenticationServices + +protocol MastodonLoginViewControllerDelegate: AnyObject { + func backButtonPressed(_ viewController: MastodonLoginViewController) + func nextButtonPressed(_ viewController: MastodonLoginViewController) +} + +enum MastodonLoginViewSection: Hashable { + case servers +} + +class MastodonLoginViewController: UIViewController, NeedsDependency { + + weak var delegate: MastodonLoginViewControllerDelegate? + var dataSource: UITableViewDiffableDataSource? + let viewModel: MastodonLoginViewModel + let authenticationViewModel: AuthenticationViewModel + var mastodonAuthenticationController: MastodonAuthenticationController? + + weak var context: AppContext! + weak var coordinator: SceneCoordinator! + + var disposeBag = Set() + + var contentView: MastodonLoginView { + view as! MastodonLoginView + } + + init(appContext: AppContext, authenticationViewModel: AuthenticationViewModel, sceneCoordinator: SceneCoordinator) { + + viewModel = MastodonLoginViewModel(appContext: appContext) + self.authenticationViewModel = authenticationViewModel + self.context = appContext + self.coordinator = sceneCoordinator + + super.init(nibName: nil, bundle: nil) + viewModel.delegate = self + + navigationItem.hidesBackButton = true + } + + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + override func loadView() { + let loginView = MastodonLoginView() + + loginView.navigationActionView.nextButton.addTarget(self, action: #selector(MastodonLoginViewController.nextButtonPressed(_:)), for: .touchUpInside) + loginView.navigationActionView.backButton.addTarget(self, action: #selector(MastodonLoginViewController.backButtonPressed(_:)), for: .touchUpInside) + loginView.searchTextField.addTarget(self, action: #selector(MastodonLoginViewController.textfieldDidChange(_:)), for: .editingChanged) + loginView.tableView.delegate = self + loginView.tableView.register(MastodonLoginServerTableViewCell.self, forCellReuseIdentifier: MastodonLoginServerTableViewCell.reuseIdentifier) + loginView.navigationActionView.nextButton.isEnabled = false + + view = loginView + } + + override func viewDidLoad() { + super.viewDidLoad() + + NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShowNotification(_:)), name: UIResponder.keyboardWillShowNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHideNotification(_:)), name: UIResponder.keyboardWillHideNotification, object: nil) + + let dataSource = UITableViewDiffableDataSource(tableView: contentView.tableView) { [weak self] tableView, indexPath, itemIdentifier in + guard let cell = tableView.dequeueReusableCell(withIdentifier: MastodonLoginServerTableViewCell.reuseIdentifier, for: indexPath) as? MastodonLoginServerTableViewCell, + let self = self else { + fatalError("Wrong cell") + } + + let server = self.viewModel.filteredServers[indexPath.row] + var configuration = cell.defaultContentConfiguration() + configuration.text = server.domain + + cell.contentConfiguration = configuration + cell.accessoryType = .disclosureIndicator + + cell.backgroundColor = Asset.Scene.Onboarding.textFieldBackground.color + + return cell + } + + contentView.tableView.dataSource = dataSource + self.dataSource = dataSource + + contentView.updateCorners() + + defer { setupNavigationBarBackgroundView() } + setupOnboardingAppearance() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + viewModel.updateServers() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + contentView.searchTextField.becomeFirstResponder() + } + + //MARK: - Actions + + @objc func backButtonPressed(_ sender: Any) { + contentView.searchTextField.resignFirstResponder() + delegate?.backButtonPressed(self) + } + + @objc func nextButtonPressed(_ sender: Any) { + contentView.searchTextField.resignFirstResponder() + delegate?.nextButtonPressed(self) + } + + @objc func login() { + guard let server = viewModel.selectedServer else { return } + + authenticationViewModel + .authenticated + .asyncMap { domain, user -> Result in + do { + let result = try await self.context.authenticationService.activeMastodonUser(domain: domain, userID: user.id) + return .success(result) + } catch { + return .failure(error) + } + } + .receive(on: DispatchQueue.main) + .sink { [weak self] result in + guard let self = self else { return } + switch result { + case .failure(let error): + assertionFailure(error.localizedDescription) + case .success(let isActived): + assert(isActived) + self.coordinator.setup() + } + } + .store(in: &disposeBag) + + authenticationViewModel.isAuthenticating.send(true) + context.apiService.createApplication(domain: server.domain) + .tryMap { response -> AuthenticationViewModel.AuthenticateInfo in + let application = response.value + guard let info = AuthenticationViewModel.AuthenticateInfo( + domain: server.domain, + application: application, + redirectURI: response.value.redirectURI ?? APIService.oauthCallbackURL + ) else { + throw APIService.APIError.explicit(.badResponse) + } + return info + } + .receive(on: DispatchQueue.main) + .sink { [weak self] completion in + guard let self = self else { return } + self.authenticationViewModel.isAuthenticating.send(false) + + switch completion { + case .failure(let error): + let alert = UIAlertController.standardAlert(of: error) + self.present(alert, animated: true) + case .finished: + // do nothing. There's a subscriber above resulting in `coordinator.setup()` + break + } + } receiveValue: { [weak self] info in + guard let self else { return } + let authenticationController = MastodonAuthenticationController( + context: self.context, + authenticateURL: info.authorizeURL + ) + + self.mastodonAuthenticationController = authenticationController + authenticationController.authenticationSession?.presentationContextProvider = self + authenticationController.authenticationSession?.start() + + self.authenticationViewModel.authenticate( + info: info, + pinCodePublisher: authenticationController.pinCodePublisher + ) + } + .store(in: &disposeBag) + } + + @objc func textfieldDidChange(_ textField: UITextField) { + viewModel.filterServers(withText: textField.text) + + + if let text = textField.text, + let domain = AuthenticationViewModel.parseDomain(from: text) { + + viewModel.selectedServer = .init(domain: domain, instance: .init(domain: domain)) + contentView.navigationActionView.nextButton.isEnabled = true + } else { + viewModel.selectedServer = nil + contentView.navigationActionView.nextButton.isEnabled = false + } + } + + // MARK: - Notifications + @objc func keyboardWillShowNotification(_ notification: Notification) { + + guard let userInfo = notification.userInfo, + let keyboardFrameValue = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue, + let duration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber + else { return } + + // inspired by https://stackoverflow.com/a/30245044 + let keyboardFrame = keyboardFrameValue.cgRectValue + + let keyboardOrigin = view.convert(keyboardFrame.origin, from: nil) + let intersectionY = CGRectGetMaxY(view.frame) - keyboardOrigin.y; + + if intersectionY >= 0 { + contentView.bottomConstraint?.constant = intersectionY - view.safeAreaInsets.bottom + } + + UIView.animate(withDuration: duration.doubleValue, delay: 0, options: .curveEaseInOut) { + self.view.layoutIfNeeded() + } + } + + @objc func keyboardWillHideNotification(_ notification: Notification) { + + guard let userInfo = notification.userInfo, + let duration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber + else { return } + + contentView.bottomConstraint?.constant = 0 + + UIView.animate(withDuration: duration.doubleValue, delay: 0, options: .curveEaseInOut) { + self.view.layoutIfNeeded() + } + } +} + +// MARK: - OnboardingViewControllerAppearance +extension MastodonLoginViewController: OnboardingViewControllerAppearance { } + +// MARK: - UITableViewDelegate +extension MastodonLoginViewController: UITableViewDelegate { + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let server = viewModel.filteredServers[indexPath.row] + viewModel.selectedServer = server + + contentView.searchTextField.text = server.domain + viewModel.filterServers(withText: " ") + + contentView.navigationActionView.nextButton.isEnabled = true + tableView.deselectRow(at: indexPath, animated: true) + } +} + +// MARK: - MastodonLoginViewModelDelegate +extension MastodonLoginViewController: MastodonLoginViewModelDelegate { + func serversUpdated(_ viewModel: MastodonLoginViewModel) { + var snapshot = NSDiffableDataSourceSnapshot() + + snapshot.appendSections([MastodonLoginViewSection.servers]) + snapshot.appendItems(viewModel.filteredServers) + + dataSource?.apply(snapshot, animatingDifferences: false) + + OperationQueue.main.addOperation { + let numberOfResults = viewModel.filteredServers.count + self.contentView.updateCorners(numberOfResults: numberOfResults) + } + } +} + +// MARK: - ASWebAuthenticationPresentationContextProviding +extension MastodonLoginViewController: ASWebAuthenticationPresentationContextProviding { + func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { + return view.window! + } +} diff --git a/Mastodon/Scene/Onboarding/Login/MastodonLoginViewModel.swift b/Mastodon/Scene/Onboarding/Login/MastodonLoginViewModel.swift new file mode 100644 index 000000000..61311a1b9 --- /dev/null +++ b/Mastodon/Scene/Onboarding/Login/MastodonLoginViewModel.swift @@ -0,0 +1,57 @@ +// +// MastodonLoginViewModel.swift +// Mastodon +// +// Created by Nathan Mattes on 11.11.22. +// + +import Foundation +import MastodonSDK +import MastodonCore +import Combine + +protocol MastodonLoginViewModelDelegate: AnyObject { + func serversUpdated(_ viewModel: MastodonLoginViewModel) +} + +class MastodonLoginViewModel { + + private var serverList: [Mastodon.Entity.Server] = [] + var selectedServer: Mastodon.Entity.Server? + var filteredServers: [Mastodon.Entity.Server] = [] + + weak var appContext: AppContext? + weak var delegate: MastodonLoginViewModelDelegate? + var disposeBag = Set() + + init(appContext: AppContext) { + self.appContext = appContext + } + + func updateServers() { + appContext?.apiService.servers().sink(receiveCompletion: { [weak self] completion in + switch completion { + case .finished: + guard let self = self else { return } + + self.delegate?.serversUpdated(self) + case .failure(let error): + print(error) + } + }, receiveValue: { content in + let servers = content.value + self.serverList = servers + }).store(in: &disposeBag) + } + + func filterServers(withText query: String?) { + guard let query else { + filteredServers = serverList + delegate?.serversUpdated(self) + return + } + + filteredServers = serverList.filter { $0.domain.lowercased().contains(query) }.sorted {$0.totalUsers > $1.totalUsers } + delegate?.serversUpdated(self) + } +} diff --git a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewController.swift b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewController.swift index eb26f75be..057c742c1 100644 --- a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewController.swift +++ b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewController.swift @@ -43,11 +43,7 @@ final class MastodonPickServerViewController: UIViewController, NeedsDependency tableView.separatorStyle = .none tableView.backgroundColor = .clear tableView.keyboardDismissMode = .onDrag - if #available(iOS 15.0, *) { - tableView.sectionHeaderTopPadding = .leastNonzeroMagnitude - } else { - // Fallback on earlier versions - } + tableView.sectionHeaderTopPadding = .leastNonzeroMagnitude return tableView }() @@ -143,8 +139,7 @@ extension MastodonPickServerViewController { viewModel.setupDiffableDataSource( for: tableView, dependency: self, - pickServerServerSectionTableHeaderViewDelegate: self, - pickServerCellDelegate: self + pickServerServerSectionTableHeaderViewDelegate: self ) KeyboardResponderService @@ -172,7 +167,7 @@ extension MastodonPickServerViewController { let alertController = UIAlertController(for: error, title: "Error", preferredStyle: .alert) let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default, handler: nil) alertController.addAction(okAction) - self.coordinator.present( + _ = self.coordinator.present( scene: .alertController(alertController: alertController), from: nil, transition: .alertController(animated: true, completion: nil) @@ -271,59 +266,6 @@ extension MastodonPickServerViewController { } @objc private func nextButtonDidPressed(_ sender: UIButton) { - switch viewModel.mode { - case .signIn: doSignIn() - case .signUp: doSignUp() - } - } - - private func doSignIn() { - guard let server = viewModel.selectedServer.value else { return } - authenticationViewModel.isAuthenticating.send(true) - context.apiService.createApplication(domain: server.domain) - .tryMap { response -> AuthenticationViewModel.AuthenticateInfo in - let application = response.value - guard let info = AuthenticationViewModel.AuthenticateInfo( - domain: server.domain, - application: application, - redirectURI: response.value.redirectURI ?? APIService.oauthCallbackURL - ) else { - throw APIService.APIError.explicit(.badResponse) - } - return info - } - .receive(on: DispatchQueue.main) - .sink { [weak self] completion in - guard let self = self else { return } - self.authenticationViewModel.isAuthenticating.send(false) - - switch completion { - case .failure(let error): - os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: sign in fail: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription) - self.viewModel.error.send(error) - case .finished: - break - } - } receiveValue: { [weak self] info in - guard let self = self else { return } - let authenticationController = MastodonAuthenticationController( - context: self.context, - authenticateURL: info.authorizeURL - ) - - self.mastodonAuthenticationController = authenticationController - authenticationController.authenticationSession?.presentationContextProvider = self - authenticationController.authenticationSession?.start() - - self.authenticationViewModel.authenticate( - info: info, - pinCodePublisher: authenticationController.pinCodePublisher - ) - } - .store(in: &disposeBag) - } - - private func doSignUp() { os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) guard let server = viewModel.selectedServer.value else { return } authenticationViewModel.isAuthenticating.send(true) @@ -394,7 +336,7 @@ extension MastodonPickServerViewController { instance: response.instance.value, applicationToken: response.applicationToken.value ) - self.coordinator.present(scene: .mastodonServerRules(viewModel: mastodonServerRulesViewModel), from: self, transition: .show) + _ = self.coordinator.present(scene: .mastodonServerRules(viewModel: mastodonServerRulesViewModel), from: self, transition: .show) } else { let mastodonRegisterViewModel = MastodonRegisterViewModel( context: self.context, @@ -403,7 +345,7 @@ extension MastodonPickServerViewController { instance: response.instance.value, applicationToken: response.applicationToken.value ) - self.coordinator.present(scene: .mastodonRegister(viewModel: mastodonRegisterViewModel), from: nil, transition: .show) + _ = self.coordinator.present(scene: .mastodonRegister(viewModel: mastodonRegisterViewModel), from: nil, transition: .show) } } .store(in: &disposeBag) @@ -503,17 +445,5 @@ extension MastodonPickServerViewController: PickServerServerSectionTableHeaderVi } } -// MARK: - PickServerCellDelegate -extension MastodonPickServerViewController: PickServerCellDelegate { - -} - // MARK: - OnboardingViewControllerAppearance extension MastodonPickServerViewController: OnboardingViewControllerAppearance { } - -// MARK: - ASWebAuthenticationPresentationContextProviding -extension MastodonPickServerViewController: ASWebAuthenticationPresentationContextProviding { - func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { - return view.window! - } -} diff --git a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel+Diffable.swift b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel+Diffable.swift index 35de40b8f..5db98c218 100644 --- a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel+Diffable.swift +++ b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel+Diffable.swift @@ -13,8 +13,7 @@ extension MastodonPickServerViewModel { func setupDiffableDataSource( for tableView: UITableView, dependency: NeedsDependency, - pickServerServerSectionTableHeaderViewDelegate: PickServerServerSectionTableHeaderViewDelegate, - pickServerCellDelegate: PickServerCellDelegate + pickServerServerSectionTableHeaderViewDelegate: PickServerServerSectionTableHeaderViewDelegate ) { // set section header serverSectionHeaderView.diffableDataSource = CategoryPickerSection.collectionViewDiffableDataSource( @@ -25,7 +24,7 @@ extension MastodonPickServerViewModel { sectionHeaderSnapshot.appendSections([.main]) sectionHeaderSnapshot.appendItems(categoryPickerItems, toSection: .main) serverSectionHeaderView.delegate = pickServerServerSectionTableHeaderViewDelegate - serverSectionHeaderView.diffableDataSource?.applySnapshot(sectionHeaderSnapshot, animated: false) { [weak self] in + serverSectionHeaderView.diffableDataSource?.apply(sectionHeaderSnapshot, animatingDifferences: false) { [weak self] in guard let self = self else { return } guard let indexPath = self.serverSectionHeaderView.diffableDataSource?.indexPath(for: .all) else { return } self.serverSectionHeaderView.collectionView.selectItem(at: indexPath, animated: false, scrollPosition: .centeredHorizontally) @@ -34,8 +33,7 @@ extension MastodonPickServerViewModel { // set tableView diffableDataSource = PickServerSection.tableViewDiffableDataSource( for: tableView, - dependency: dependency, - pickServerCellDelegate: pickServerCellDelegate + dependency: dependency ) var snapshot = NSDiffableDataSourceSnapshot() diff --git a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel.swift b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel.swift index 50c1d7aac..ebbcfe7fd 100644 --- a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel.swift +++ b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel.swift @@ -17,12 +17,7 @@ import MastodonCore import MastodonUI class MastodonPickServerViewModel: NSObject { - - enum PickServerMode { - case signUp - case signIn - } - + enum EmptyStateViewState { case none case loading @@ -34,7 +29,6 @@ class MastodonPickServerViewModel: NSObject { let serverSectionHeaderView = PickServerServerSectionTableHeaderView() // input - let mode: PickServerMode let context: AppContext var categoryPickerItems: [CategoryPickerItem] = { var items: [CategoryPickerItem] = [] @@ -72,9 +66,8 @@ class MastodonPickServerViewModel: NSObject { let loadingIndexedServersError = CurrentValueSubject(nil) let emptyStateViewState = CurrentValueSubject(.none) - init(context: AppContext, mode: PickServerMode) { + init(context: AppContext) { self.context = context - self.mode = mode super.init() configure() @@ -115,9 +108,7 @@ extension MastodonPickServerViewModel { .map { indexedServers, selectCategoryItem, searchText -> [Mastodon.Entity.Server] in // ignore approval required servers when sign-up var indexedServers = indexedServers - if self.mode == .signUp { - indexedServers = indexedServers.filter { !$0.approvalRequired } - } + indexedServers = indexedServers.filter { !$0.approvalRequired } // Note: // sort by calculate last week users count // and make medium size (~800) server to top diff --git a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerCell.swift b/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerCell.swift index 669067770..4cbe77b9c 100644 --- a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerCell.swift +++ b/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerCell.swift @@ -14,14 +14,8 @@ import Kanna import MastodonAsset import MastodonLocalization -protocol PickServerCellDelegate: AnyObject { -// func pickServerCell(_ cell: PickServerCell, expandButtonPressed button: UIButton) -} - class PickServerCell: UITableViewCell { - weak var delegate: PickServerCellDelegate? - var disposeBag = Set() let containerView: UIStackView = { @@ -88,7 +82,7 @@ class PickServerCell: UITableViewCell { label.adjustsFontForContentSizeCategory = true return label }() - + private var collapseConstraints: [NSLayoutConstraint] = [] private var expandConstraints: [NSLayoutConstraint] = [] diff --git a/Mastodon/Scene/Onboarding/PickServer/View/PickServerCategoryView.swift b/Mastodon/Scene/Onboarding/PickServer/View/PickServerCategoryView.swift index 9d6cfc85f..aba5a87dc 100644 --- a/Mastodon/Scene/Onboarding/PickServer/View/PickServerCategoryView.swift +++ b/Mastodon/Scene/Onboarding/PickServer/View/PickServerCategoryView.swift @@ -19,13 +19,6 @@ class PickServerCategoryView: UIView { return view }() - let emojiLabel: UILabel = { - let label = UILabel() - label.textAlignment = .center - label.font = .systemFont(ofSize: 34, weight: .regular) - return label - }() - let titleLabel: UILabel = { let label = UILabel() label.textAlignment = .center @@ -50,23 +43,18 @@ extension PickServerCategoryView { private func configure() { let container = UIStackView() container.axis = .vertical + container.spacing = 2 container.distribution = .fillProportionally container.translatesAutoresizingMaskIntoConstraints = false addSubview(container) - NSLayoutConstraint.activate([ - container.topAnchor.constraint(equalTo: topAnchor), - container.leadingAnchor.constraint(equalTo: leadingAnchor), - container.trailingAnchor.constraint(equalTo: trailingAnchor), - container.bottomAnchor.constraint(equalTo: bottomAnchor), - ]) + container.pinToParent() - container.addArrangedSubview(emojiLabel) container.addArrangedSubview(titleLabel) highlightedIndicatorView.translatesAutoresizingMaskIntoConstraints = false container.addArrangedSubview(highlightedIndicatorView) NSLayoutConstraint.activate([ - highlightedIndicatorView.heightAnchor.constraint(equalToConstant: 3).priority(.required - 1), + highlightedIndicatorView.heightAnchor.constraint(equalToConstant: 3)//.priority(.required - 1), ]) titleLabel.setContentHuggingPriority(.required - 1, for: .vertical) } diff --git a/Mastodon/Scene/Onboarding/PickServer/View/PickServerServerSectionTableHeaderView.swift b/Mastodon/Scene/Onboarding/PickServer/View/PickServerServerSectionTableHeaderView.swift index d50c62afe..ae8c16bb8 100644 --- a/Mastodon/Scene/Onboarding/PickServer/View/PickServerServerSectionTableHeaderView.swift +++ b/Mastodon/Scene/Onboarding/PickServer/View/PickServerServerSectionTableHeaderView.swift @@ -19,7 +19,7 @@ protocol PickServerServerSectionTableHeaderViewDelegate: AnyObject { final class PickServerServerSectionTableHeaderView: UIView { - static let collectionViewHeight: CGFloat = 88 + static let collectionViewHeight: CGFloat = 30 static let searchTextFieldHeight: CGFloat = 38 static let spacing: CGFloat = 11 @@ -177,7 +177,6 @@ extension PickServerServerSectionTableHeaderView { extension PickServerServerSectionTableHeaderView: UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: indexPath: %s", ((#file as NSString).lastPathComponent), #line, #function, indexPath.debugDescription) collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .centeredHorizontally) delegate?.pickServerServerSectionTableHeaderView(self, collectionView: collectionView, didSelectItemAt: indexPath) } @@ -205,5 +204,5 @@ extension PickServerServerSectionTableHeaderView: UITextFieldDelegate { textField.resignFirstResponder() return false } - + } diff --git a/Mastodon/Scene/Onboarding/Register/Cell/MastodonRegisterAvatarTableViewCell.swift b/Mastodon/Scene/Onboarding/Register/Cell/MastodonRegisterAvatarTableViewCell.swift deleted file mode 100644 index 154385e6a..000000000 --- a/Mastodon/Scene/Onboarding/Register/Cell/MastodonRegisterAvatarTableViewCell.swift +++ /dev/null @@ -1,116 +0,0 @@ -// -// MastodonRegisterAvatarTableViewCell.swift -// Mastodon -// -// Created by MainasuK on 2022-1-5. -// - -import UIKit -import Combine -import MastodonAsset -import MastodonLocalization - -final class MastodonRegisterAvatarTableViewCell: UITableViewCell { - - static let containerSize = CGSize(width: 88, height: 88) - - var disposeBag = Set() - - let containerView: UIView = { - let view = UIView() - view.backgroundColor = .clear - view.layer.masksToBounds = true - view.layer.cornerCurve = .continuous - view.layer.cornerRadius = 22 - return view - }() - - let avatarButton: HighlightDimmableButton = { - let button = HighlightDimmableButton() - button.backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color - button.setImage(Asset.Scene.Onboarding.avatarPlaceholder.image, for: .normal) - return button - }() - - let editBannerView: UIView = { - let bannerView = UIView() - bannerView.backgroundColor = UIColor.black.withAlphaComponent(0.5) - bannerView.isUserInteractionEnabled = false - - let label: UILabel = { - let label = UILabel() - label.textColor = .white - label.text = L10n.Common.Controls.Actions.edit - label.font = .systemFont(ofSize: 13, weight: .semibold) - label.textAlignment = .center - label.minimumScaleFactor = 0.5 - label.adjustsFontSizeToFitWidth = true - return label - }() - - label.translatesAutoresizingMaskIntoConstraints = false - bannerView.addSubview(label) - NSLayoutConstraint.activate([ - label.topAnchor.constraint(equalTo: bannerView.topAnchor), - label.leadingAnchor.constraint(equalTo: bannerView.leadingAnchor), - label.trailingAnchor.constraint(equalTo: bannerView.trailingAnchor), - label.bottomAnchor.constraint(equalTo: bannerView.bottomAnchor), - ]) - - return bannerView - }() - - override func prepareForReuse() { - super.prepareForReuse() - - disposeBag.removeAll() - } - - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - _init() - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - _init() - } - -} - -extension MastodonRegisterAvatarTableViewCell { - - private func _init() { - selectionStyle = .none - backgroundColor = .clear - - containerView.translatesAutoresizingMaskIntoConstraints = false - contentView.addSubview(containerView) - NSLayoutConstraint.activate([ - containerView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 22), - containerView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor), - contentView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: 8), - containerView.widthAnchor.constraint(equalToConstant: MastodonRegisterAvatarTableViewCell.containerSize.width).priority(.required - 1), - containerView.heightAnchor.constraint(equalToConstant: MastodonRegisterAvatarTableViewCell.containerSize.height).priority(.required - 1), - ]) - - avatarButton.translatesAutoresizingMaskIntoConstraints = false - containerView.addSubview(avatarButton) - NSLayoutConstraint.activate([ - avatarButton.topAnchor.constraint(equalTo: containerView.topAnchor), - avatarButton.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), - avatarButton.trailingAnchor.constraint(equalTo: containerView.trailingAnchor), - avatarButton.bottomAnchor.constraint(equalTo: containerView.bottomAnchor), - ]) - - editBannerView.translatesAutoresizingMaskIntoConstraints = false - containerView.addSubview(editBannerView) - NSLayoutConstraint.activate([ - editBannerView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), - editBannerView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor), - editBannerView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor), - editBannerView.heightAnchor.constraint(equalToConstant: 22), - ]) - } - -} diff --git a/Mastodon/Scene/Onboarding/Register/Cell/MastodonRegisterPasswordHintTableViewCell.swift b/Mastodon/Scene/Onboarding/Register/Cell/MastodonRegisterPasswordHintTableViewCell.swift deleted file mode 100644 index 1324c2822..000000000 --- a/Mastodon/Scene/Onboarding/Register/Cell/MastodonRegisterPasswordHintTableViewCell.swift +++ /dev/null @@ -1,50 +0,0 @@ -// -// MastodonRegisterPasswordHintTableViewCell.swift -// Mastodon -// -// Created by MainasuK on 2022-1-7. -// - -import UIKit -import MastodonAsset -import MastodonLocalization - -final class MastodonRegisterPasswordHintTableViewCell: UITableViewCell { - - let passwordRuleLabel: UILabel = { - let label = UILabel() - label.font = .preferredFont(forTextStyle: .footnote) - label.textColor = Asset.Colors.Label.secondary.color - label.text = L10n.Scene.Register.Input.Password.hint - return label - }() - - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - _init() - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - _init() - } - -} - -extension MastodonRegisterPasswordHintTableViewCell { - - private func _init() { - selectionStyle = .none - backgroundColor = .clear - - passwordRuleLabel.translatesAutoresizingMaskIntoConstraints = false - contentView.addSubview(passwordRuleLabel) - NSLayoutConstraint.activate([ - passwordRuleLabel.topAnchor.constraint(equalTo: contentView.topAnchor), - passwordRuleLabel.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor), - passwordRuleLabel.trailingAnchor.constraint(equalTo: contentView.readableContentGuide.trailingAnchor), - passwordRuleLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), - ]) - } - -} diff --git a/Mastodon/Scene/Onboarding/Register/Cell/MastodonRegisterTextFieldTableViewCell.swift b/Mastodon/Scene/Onboarding/Register/Cell/MastodonRegisterTextFieldTableViewCell.swift deleted file mode 100644 index 15f234834..000000000 --- a/Mastodon/Scene/Onboarding/Register/Cell/MastodonRegisterTextFieldTableViewCell.swift +++ /dev/null @@ -1,142 +0,0 @@ -// -// MastodonRegisterTextFieldTableViewCell.swift -// Mastodon -// -// Created by MainasuK on 2022-1-7. -// - -import UIKit -import Combine -import MastodonUI -import MastodonAsset -import MastodonLocalization - -final class MastodonRegisterTextFieldTableViewCell: UITableViewCell { - - static let textFieldHeight: CGFloat = 50 - static let textFieldLabelFont = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold), maximumPointSize: 22) - - var disposeBag = Set() - - let textFieldShadowContainer = ShadowBackgroundContainer() - let textField: UITextField = { - let textField = UITextField() - textField.font = MastodonRegisterTextFieldTableViewCell.textFieldLabelFont - textField.backgroundColor = Asset.Scene.Onboarding.textFieldBackground.color - textField.layer.masksToBounds = true - textField.layer.cornerRadius = 10 - textField.layer.cornerCurve = .continuous - return textField - }() - - override func prepareForReuse() { - super.prepareForReuse() - - disposeBag.removeAll() - textFieldShadowContainer.shadowColor = .black - textFieldShadowContainer.shadowAlpha = 0.25 - resetTextField() - } - - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - _init() - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - _init() - } - -} - -extension MastodonRegisterTextFieldTableViewCell { - - private func _init() { - selectionStyle = .none - backgroundColor = .clear - - textFieldShadowContainer.translatesAutoresizingMaskIntoConstraints = false - contentView.addSubview(textFieldShadowContainer) - NSLayoutConstraint.activate([ - textFieldShadowContainer.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 6), - textFieldShadowContainer.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor), - textFieldShadowContainer.trailingAnchor.constraint(equalTo: contentView.readableContentGuide.trailingAnchor), - contentView.bottomAnchor.constraint(equalTo: textFieldShadowContainer.bottomAnchor, constant: 6), - ]) - - textField.translatesAutoresizingMaskIntoConstraints = false - textFieldShadowContainer.addSubview(textField) - NSLayoutConstraint.activate([ - textField.topAnchor.constraint(equalTo: textFieldShadowContainer.topAnchor), - textField.leadingAnchor.constraint(equalTo: textFieldShadowContainer.leadingAnchor), - textField.trailingAnchor.constraint(equalTo: textFieldShadowContainer.trailingAnchor), - textField.bottomAnchor.constraint(equalTo: textFieldShadowContainer.bottomAnchor), - textField.heightAnchor.constraint(equalToConstant: MastodonRegisterTextFieldTableViewCell.textFieldHeight).priority(.required - 1), - ]) - - resetTextField() - } - -} - -extension MastodonRegisterTextFieldTableViewCell { - func resetTextField() { - textField.keyboardType = .default - textField.autocorrectionType = .default - textField.autocapitalizationType = .none - textField.attributedPlaceholder = nil - textField.isSecureTextEntry = false - textField.textAlignment = .natural - textField.semanticContentAttribute = .unspecified - - let paddingRect = CGRect(x: 0, y: 0, width: 16, height: 10) - textField.leftView = UIView(frame: paddingRect) - textField.leftViewMode = .always - textField.rightView = UIView(frame: paddingRect) - textField.rightViewMode = .always - } - - func setupTextViewRightView(text: String) { - textField.rightView = { - let containerView = UIView() - - let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 8, height: MastodonRegisterTextFieldTableViewCell.textFieldHeight)) - paddingView.translatesAutoresizingMaskIntoConstraints = false - containerView.addSubview(paddingView) - NSLayoutConstraint.activate([ - paddingView.topAnchor.constraint(equalTo: containerView.topAnchor), - paddingView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), - paddingView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor), - paddingView.widthAnchor.constraint(equalToConstant: 8).priority(.defaultHigh), - ]) - - let label = UILabel() - label.font = MastodonRegisterTextFieldTableViewCell.textFieldLabelFont - label.textColor = Asset.Colors.Label.primary.color - label.text = text - label.lineBreakMode = .byTruncatingMiddle - - label.translatesAutoresizingMaskIntoConstraints = false - containerView.addSubview(label) - NSLayoutConstraint.activate([ - label.topAnchor.constraint(equalTo: containerView.topAnchor), - label.leadingAnchor.constraint(equalTo: paddingView.trailingAnchor), - containerView.trailingAnchor.constraint(equalTo: label.trailingAnchor, constant: 16), - label.bottomAnchor.constraint(equalTo: containerView.bottomAnchor), - label.widthAnchor.constraint(lessThanOrEqualToConstant: 180).priority(.required - 1), - ]) - return containerView - }() - } - - func setupTextViewPlaceholder(text: String) { - textField.attributedPlaceholder = NSAttributedString( - string: text, - attributes: [ - .foregroundColor: Asset.Colors.Label.secondary.color, - .font: MastodonRegisterTextFieldTableViewCell.textFieldLabelFont - ] - ) - } -} diff --git a/Mastodon/Scene/Onboarding/Register/MastodonRegisterView.swift b/Mastodon/Scene/Onboarding/Register/MastodonRegisterView.swift index 2be7c61d7..e5ca2798a 100644 --- a/Mastodon/Scene/Onboarding/Register/MastodonRegisterView.swift +++ b/Mastodon/Scene/Onboarding/Register/MastodonRegisterView.swift @@ -55,19 +55,10 @@ struct MastodonRegisterView: View { // Delete if viewModel.avatarImage != nil { Divider() - if #available(iOS 15.0, *) { - Button(role: .destructive) { - viewModel.avatarMediaMenuActionPublisher.send(.delete) - } label: { - Label(L10n.Scene.Register.Input.Avatar.delete, systemImage: "delete.left") - } - } else { - // Fallback on earlier ve rsions - Button { - viewModel.avatarMediaMenuActionPublisher.send(.delete) - } label: { - Label(L10n.Scene.Register.Input.Avatar.delete, systemImage: "delete.left") - } + Button(role: .destructive) { + viewModel.avatarMediaMenuActionPublisher.send(.delete) + } label: { + Label(L10n.Scene.Register.Input.Avatar.delete, systemImage: "delete.left") } } } label: { @@ -168,21 +159,27 @@ struct MastodonRegisterView: View { func body(content: Content) -> some View { ZStack { - let shadowColor: Color = { + let borderColor: Color = { switch validateState { - case .empty: return .black.opacity(0.125) - case .invalid: return Color(Asset.Colors.TextField.invalid.color) - case .valid: return Color(Asset.Colors.TextField.valid.color) + case .empty: return Color(Asset.Scene.Onboarding.textFieldBackground.color) + case .invalid: return Color(Asset.Colors.TextField.invalid.color) + case .valid: return Color(Asset.Scene.Onboarding.textFieldBackground.color) } }() + Color(Asset.Scene.Onboarding.textFieldBackground.color) .cornerRadius(10) - .shadow(color: shadowColor, radius: 1, x: 0, y: 2) - .animation(.easeInOut, value: validateState) + .shadow(color: .black.opacity(0.125), radius: 1, x: 0, y: 2) + content .padding() .background(Color(Asset.Scene.Onboarding.textFieldBackground.color)) .cornerRadius(10) + .overlay( + RoundedRectangle(cornerRadius: 10) + .stroke(borderColor, lineWidth: 1) + .animation(.easeInOut, value: validateState) + ) } } } diff --git a/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController+Avatar.swift b/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController+Avatar.swift index 9260f9e21..81924d812 100644 --- a/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController+Avatar.swift +++ b/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController+Avatar.swift @@ -49,7 +49,7 @@ extension MastodonRegisterViewController: PHPickerViewControllerDelegate { let alertController = UIAlertController(for: error, title: "", preferredStyle: .alert) let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default, handler: nil) alertController.addAction(okAction) - self.coordinator.present( + _ = self.coordinator.present( scene: .alertController(alertController: alertController), from: nil, transition: .alertController(animated: true, completion: nil) diff --git a/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController.swift b/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController.swift index 9b10bd48e..f239e59b7 100644 --- a/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController.swift +++ b/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController.swift @@ -85,12 +85,7 @@ extension MastodonRegisterViewController { addChild(hostingViewController) hostingViewController.view.translatesAutoresizingMaskIntoConstraints = false view.addSubview(hostingViewController.view) - NSLayoutConstraint.activate([ - hostingViewController.view.topAnchor.constraint(equalTo: view.topAnchor), - hostingViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), - hostingViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), - hostingViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + hostingViewController.view.pinToParent() navigationActionView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(navigationActionView) @@ -145,7 +140,7 @@ extension MastodonRegisterViewController { let alertController = UIAlertController(for: error, title: "Sign Up Failure", preferredStyle: .alert) let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default, handler: nil) alertController.addAction(okAction) - self.coordinator.present( + _ = self.coordinator.present( scene: .alertController(alertController: alertController), from: nil, transition: .alertController(animated: true, completion: nil) @@ -322,7 +317,7 @@ extension MastodonRegisterViewController { ) }() let viewModel = MastodonConfirmEmailViewModel(context: self.context, email: email, authenticateInfo: self.viewModel.authenticateInfo, userToken: userToken, updateCredentialQuery: updateCredentialQuery) - self.coordinator.present(scene: .mastodonConfirmEmail(viewModel: viewModel), from: self, transition: .show) + _ = self.coordinator.present(scene: .mastodonConfirmEmail(viewModel: viewModel), from: self, transition: .show) } .store(in: &disposeBag) } diff --git a/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewModel+Diffable.swift b/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewModel+Diffable.swift index dda59843a..b3e2a2cdb 100644 --- a/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewModel+Diffable.swift +++ b/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewModel+Diffable.swift @@ -10,145 +10,6 @@ import Combine import MastodonAsset import MastodonLocalization -extension MastodonRegisterViewModel { - func setupDiffableDataSource( - tableView: UITableView - ) { - tableView.register(OnboardingHeadlineTableViewCell.self, forCellReuseIdentifier: String(describing: OnboardingHeadlineTableViewCell.self)) - tableView.register(MastodonRegisterAvatarTableViewCell.self, forCellReuseIdentifier: String(describing: MastodonRegisterAvatarTableViewCell.self)) - tableView.register(MastodonRegisterTextFieldTableViewCell.self, forCellReuseIdentifier: String(describing: MastodonRegisterTextFieldTableViewCell.self)) - tableView.register(MastodonRegisterPasswordHintTableViewCell.self, forCellReuseIdentifier: String(describing: MastodonRegisterPasswordHintTableViewCell.self)) - - diffableDataSource = UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, item in - switch item { - case .header(let domain): - let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: OnboardingHeadlineTableViewCell.self), for: indexPath) as! OnboardingHeadlineTableViewCell - cell.titleLabel.text = L10n.Scene.Register.letsGetYouSetUpOnDomain(domain) - cell.subTitleLabel.isHidden = true - return cell - case .avatar: - let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: MastodonRegisterAvatarTableViewCell.self), for: indexPath) as! MastodonRegisterAvatarTableViewCell - self.configureAvatar(cell: cell) - return cell - case .name: - let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: MastodonRegisterTextFieldTableViewCell.self), for: indexPath) as! MastodonRegisterTextFieldTableViewCell - cell.setupTextViewPlaceholder(text: L10n.Scene.Register.Input.DisplayName.placeholder) - cell.textField.keyboardType = .default - cell.textField.autocapitalizationType = .words - cell.textField.text = self.name - NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: cell.textField) - .receive(on: DispatchQueue.main) - .compactMap { notification in - guard let textField = notification.object as? UITextField else { - assertionFailure() - return nil - } - return textField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" - } - .assign(to: \.name, on: self) - .store(in: &cell.disposeBag) - return cell - case .username: - let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: MastodonRegisterTextFieldTableViewCell.self), for: indexPath) as! MastodonRegisterTextFieldTableViewCell - cell.setupTextViewRightView(text: "@" + self.domain) - cell.setupTextViewPlaceholder(text: L10n.Scene.Register.Input.Username.placeholder) - cell.textField.keyboardType = .alphabet - cell.textField.autocorrectionType = .no - cell.textField.text = self.username - cell.textField.textAlignment = .left - cell.textField.semanticContentAttribute = .forceLeftToRight - NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: cell.textField) - .receive(on: DispatchQueue.main) - .compactMap { notification in - guard let textField = notification.object as? UITextField else { - assertionFailure() - return nil - } - return textField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" - } - .assign(to: \.username, on: self) - .store(in: &cell.disposeBag) - self.configureTextFieldCell(cell: cell, validateState: self.$usernameValidateState) - return cell - case .email: - let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: MastodonRegisterTextFieldTableViewCell.self), for: indexPath) as! MastodonRegisterTextFieldTableViewCell - cell.setupTextViewPlaceholder(text: L10n.Scene.Register.Input.Email.placeholder) - cell.textField.keyboardType = .emailAddress - cell.textField.autocorrectionType = .no - cell.textField.text = self.email - NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: cell.textField) - .receive(on: DispatchQueue.main) - .compactMap { notification in - guard let textField = notification.object as? UITextField else { - assertionFailure() - return nil - } - return textField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" - } - .assign(to: \.email, on: self) - .store(in: &cell.disposeBag) - self.configureTextFieldCell(cell: cell, validateState: self.$emailValidateState) - return cell - case .password: - let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: MastodonRegisterTextFieldTableViewCell.self), for: indexPath) as! MastodonRegisterTextFieldTableViewCell - cell.setupTextViewPlaceholder(text: L10n.Scene.Register.Input.Password.placeholder) - cell.textField.keyboardType = .alphabet - cell.textField.autocorrectionType = .no - cell.textField.isSecureTextEntry = true - cell.textField.text = self.password - cell.textField.textAlignment = .left - cell.textField.semanticContentAttribute = .forceLeftToRight - NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: cell.textField) - .receive(on: DispatchQueue.main) - .compactMap { notification in - guard let textField = notification.object as? UITextField else { - assertionFailure() - return nil - } - return textField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" - } - .assign(to: \.password, on: self) - .store(in: &cell.disposeBag) - self.configureTextFieldCell(cell: cell, validateState: self.$passwordValidateState) - return cell - case .hint: - let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: MastodonRegisterPasswordHintTableViewCell.self), for: indexPath) as! MastodonRegisterPasswordHintTableViewCell - return cell - case .reason: - let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: MastodonRegisterTextFieldTableViewCell.self), for: indexPath) as! MastodonRegisterTextFieldTableViewCell - cell.setupTextViewPlaceholder(text: L10n.Scene.Register.Input.Invite.registrationUserInviteRequest) - cell.textField.keyboardType = .default - cell.textField.text = self.reason - NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: cell.textField) - .receive(on: DispatchQueue.main) - .compactMap { notification in - guard let textField = notification.object as? UITextField else { - assertionFailure() - return nil - } - return textField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" - } - .assign(to: \.reason, on: self) - .store(in: &cell.disposeBag) - self.configureTextFieldCell(cell: cell, validateState: self.$reasonValidateState) - return cell - default: - assertionFailure() - return UITableViewCell() - } - } - - var snapshot = NSDiffableDataSourceSnapshot() - snapshot.appendSections([.main]) - snapshot.appendItems([.header(domain: domain)], toSection: .main) - snapshot.appendItems([.avatar, .name, .username, .email, .password, .hint], toSection: .main) - if approvalRequired { - snapshot.appendItems([.reason], toSection: .main) - } - diffableDataSource?.applySnapshot(snapshot, animated: false, completion: nil) - } -} - extension MastodonRegisterViewModel { private func configureAvatar(cell: MastodonRegisterAvatarTableViewCell) { self.$avatarImage diff --git a/Mastodon/Scene/Onboarding/ResendEmail/MastodonResendEmailViewController.swift b/Mastodon/Scene/Onboarding/ResendEmail/MastodonResendEmailViewController.swift index 178d489be..d6870785c 100644 --- a/Mastodon/Scene/Onboarding/ResendEmail/MastodonResendEmailViewController.swift +++ b/Mastodon/Scene/Onboarding/ResendEmail/MastodonResendEmailViewController.swift @@ -49,12 +49,7 @@ extension MastodonResendEmailViewController { webView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(webView) - NSLayoutConstraint.activate([ - webView.topAnchor.constraint(equalTo: view.topAnchor), - webView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - webView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - webView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + webView.pinToParent() let request = URLRequest(url: viewModel.resendEmailURL) webView.navigationDelegate = self.viewModel.navigationDelegate diff --git a/Mastodon/Scene/Onboarding/ServerRules/MastodonServerRulesViewController.swift b/Mastodon/Scene/Onboarding/ServerRules/MastodonServerRulesViewController.swift index 0d4e27d98..f850991f2 100644 --- a/Mastodon/Scene/Onboarding/ServerRules/MastodonServerRulesViewController.swift +++ b/Mastodon/Scene/Onboarding/ServerRules/MastodonServerRulesViewController.swift @@ -37,11 +37,7 @@ final class MastodonServerRulesViewController: UIViewController, NeedsDependency tableView.separatorStyle = .none tableView.backgroundColor = .clear tableView.keyboardDismissMode = .onDrag - if #available(iOS 15.0, *) { - tableView.sectionHeaderTopPadding = 0 - } else { - // Fallback on earlier versions - } + tableView.sectionHeaderTopPadding = 0 return tableView }() @@ -69,12 +65,7 @@ extension MastodonServerRulesViewController { tableView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(tableView) - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: view.topAnchor), - tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + tableView.pinToParent() navigationActionView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(navigationActionView) @@ -127,7 +118,7 @@ extension MastodonServerRulesViewController { instance: viewModel.instance, applicationToken: viewModel.applicationToken ) - coordinator.present(scene: .mastodonRegister(viewModel: viewModel), from: self, transition: .show) + _ = coordinator.present(scene: .mastodonRegister(viewModel: viewModel), from: self, transition: .show) } } diff --git a/Mastodon/Scene/Onboarding/ServerRules/MastodonServerRulesViewModel+Diffable.swift b/Mastodon/Scene/Onboarding/ServerRules/MastodonServerRulesViewModel+Diffable.swift index f6385a529..ecb868eb0 100644 --- a/Mastodon/Scene/Onboarding/ServerRules/MastodonServerRulesViewModel+Diffable.swift +++ b/Mastodon/Scene/Onboarding/ServerRules/MastodonServerRulesViewModel+Diffable.swift @@ -21,6 +21,6 @@ extension MastodonServerRulesViewModel { return ServerRuleItem.rule(ruleContext) } snapshot.appendItems(ruleItems, toSection: .rules) - diffableDataSource?.applySnapshot(snapshot, animated: false, completion: nil) + diffableDataSource?.apply(snapshot, animatingDifferences: false) } } diff --git a/Mastodon/Scene/Onboarding/Share/NavigationActionView.swift b/Mastodon/Scene/Onboarding/Share/NavigationActionView.swift index c3236bdb4..fce853959 100644 --- a/Mastodon/Scene/Onboarding/Share/NavigationActionView.swift +++ b/Mastodon/Scene/Onboarding/Share/NavigationActionView.swift @@ -13,6 +13,7 @@ import MastodonLocalization final class NavigationActionView: UIView { static let buttonHeight: CGFloat = 50 + static let minimumBackButtonWidth: CGFloat = 100 private var observations = Set() @@ -27,6 +28,8 @@ final class NavigationActionView: UIView { let backButton: PrimaryActionButton = { let button = PrimaryActionButton() button.action = .back + button.contentEdgeInsets = WelcomeViewController.actionButtonPadding + button.titleLabel?.adjustsFontForContentSizeCategory = true button.setTitle(L10n.Common.Controls.Actions.back, for: .normal) return button }() @@ -35,6 +38,8 @@ final class NavigationActionView: UIView { let nextButton: PrimaryActionButton = { let button = PrimaryActionButton() button.action = .next + button.contentEdgeInsets = WelcomeViewController.actionButtonPadding + button.titleLabel?.adjustsFontForContentSizeCategory = true button.setTitle(L10n.Common.Controls.Actions.next, for: .normal) return button }() @@ -77,27 +82,27 @@ extension NavigationActionView { nextButtonShadowContainer.translatesAutoresizingMaskIntoConstraints = false buttonContainer.addArrangedSubview(nextButtonShadowContainer) NSLayoutConstraint.activate([ - backButtonShadowContainer.heightAnchor.constraint(equalToConstant: NavigationActionView.buttonHeight).priority(.required - 1), - nextButtonShadowContainer.heightAnchor.constraint(equalToConstant: NavigationActionView.buttonHeight).priority(.required - 1), - nextButtonShadowContainer.widthAnchor.constraint(equalTo: backButtonShadowContainer.widthAnchor, multiplier: 2).priority(.required - 1), + backButtonShadowContainer.heightAnchor.constraint(greaterThanOrEqualToConstant: NavigationActionView.buttonHeight).priority(.required - 1), + nextButtonShadowContainer.heightAnchor.constraint(greaterThanOrEqualToConstant: NavigationActionView.buttonHeight).priority(.required - 1), ]) backButton.translatesAutoresizingMaskIntoConstraints = false backButtonShadowContainer.addSubview(backButton) - NSLayoutConstraint.activate([ - backButton.topAnchor.constraint(equalTo: backButtonShadowContainer.topAnchor), - backButton.leadingAnchor.constraint(equalTo: backButtonShadowContainer.leadingAnchor), - backButton.trailingAnchor.constraint(equalTo: backButtonShadowContainer.trailingAnchor), - backButton.bottomAnchor.constraint(equalTo: backButtonShadowContainer.bottomAnchor), - ]) + backButton.pinToParent() nextButton.translatesAutoresizingMaskIntoConstraints = false - nextButtonShadowContainer.addSubview(nextButton) + nextButtonShadowContainer.addSubview(nextButton) + nextButton.pinToParent() + + // We want the back button to be as small as possible, allowing the next button to take up + // any remaining space. .defaultLow is "the priority level at which a button hugs its + // contents horizontally". Setting this on backButton allows nextButton to eat up remaining + // space. Note that we have to set this on the backButton, not the container, because it's + // backButton's size that determines the compression amount. + backButton.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) + // Ensure that the back button has a reasonable minimum tap area. NSLayoutConstraint.activate([ - nextButton.topAnchor.constraint(equalTo: nextButtonShadowContainer.topAnchor), - nextButton.leadingAnchor.constraint(equalTo: nextButtonShadowContainer.leadingAnchor), - nextButton.trailingAnchor.constraint(equalTo: nextButtonShadowContainer.trailingAnchor), - nextButton.bottomAnchor.constraint(equalTo: nextButtonShadowContainer.bottomAnchor), + backButton.widthAnchor.constraint(greaterThanOrEqualToConstant: NavigationActionView.minimumBackButtonWidth).priority(.defaultLow - 1) ]) } diff --git a/Mastodon/Scene/Onboarding/Share/OnboardingNavigationController.swift b/Mastodon/Scene/Onboarding/Share/OnboardingNavigationController.swift index 537102dc9..ac2e5b171 100644 --- a/Mastodon/Scene/Onboarding/Share/OnboardingNavigationController.swift +++ b/Mastodon/Scene/Onboarding/Share/OnboardingNavigationController.swift @@ -20,12 +20,7 @@ extension OnboardingNavigationController { gradientBorderView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(gradientBorderView) - NSLayoutConstraint.activate([ - gradientBorderView.topAnchor.constraint(equalTo: view.topAnchor), - gradientBorderView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - gradientBorderView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - gradientBorderView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + gradientBorderView.pinToParent() updateBorderViewDisplay() } diff --git a/Mastodon/Scene/Onboarding/Share/OnboardingViewControllerAppearance.swift b/Mastodon/Scene/Onboarding/Share/OnboardingViewControllerAppearance.swift index ba1eecfc5..89d77435d 100644 --- a/Mastodon/Scene/Onboarding/Share/OnboardingViewControllerAppearance.swift +++ b/Mastodon/Scene/Onboarding/Share/OnboardingViewControllerAppearance.swift @@ -22,6 +22,10 @@ extension OnboardingViewControllerAppearance { static var actionButtonMarginExtend: CGFloat { return 80 } static var viewBottomPaddingHeight: CGFloat { return 11 } static var viewBottomPaddingHeightExtend: CGFloat { return 22 } + + // Typically assigned to the button's contentEdgeInsets. Ensures space around content, even when + // content is large due to Dynamic Type. + static var actionButtonPadding: UIEdgeInsets { return UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8) } static var largeTitleFont: UIFont { return UIFontMetrics(forTextStyle: .largeTitle).scaledFont(for: .systemFont(ofSize: 28, weight: .bold)) @@ -62,11 +66,7 @@ extension OnboardingViewControllerAppearance { navigationItem.standardAppearance = barAppearance navigationItem.compactAppearance = barAppearance navigationItem.scrollEdgeAppearance = barAppearance - if #available(iOS 15.0, *) { - navigationItem.compactScrollEdgeAppearance = barAppearance - } else { - // Fallback on earlier versions - } + navigationItem.compactScrollEdgeAppearance = barAppearance } func setupNavigationBarBackgroundView() { diff --git a/Mastodon/Scene/Onboarding/Welcome/View/WelcomeIllustrationView.swift b/Mastodon/Scene/Onboarding/Welcome/View/WelcomeIllustrationView.swift index 0530539a2..1fca1afef 100644 --- a/Mastodon/Scene/Onboarding/Welcome/View/WelcomeIllustrationView.swift +++ b/Mastodon/Scene/Onboarding/Welcome/View/WelcomeIllustrationView.swift @@ -96,12 +96,7 @@ extension WelcomeIllustrationView { ].forEach { imageView in imageView.translatesAutoresizingMaskIntoConstraints = false addSubview(imageView) - NSLayoutConstraint.activate([ - imageView.topAnchor.constraint(equalTo: cloudBaseImageView.topAnchor), - imageView.leadingAnchor.constraint(equalTo: cloudBaseImageView.leadingAnchor), - imageView.trailingAnchor.constraint(equalTo: cloudBaseImageView.trailingAnchor), - imageView.bottomAnchor.constraint(equalTo: cloudBaseImageView.bottomAnchor), - ]) + imageView.pinTo(to: cloudBaseImageView) } aspectLayoutConstraint = cloudBaseImageView.widthAnchor.constraint(equalTo: cloudBaseImageView.heightAnchor, multiplier: layout.artworkImageSize.width / layout.artworkImageSize.height) diff --git a/Mastodon/Scene/Onboarding/Welcome/WelcomeViewController.swift b/Mastodon/Scene/Onboarding/Welcome/WelcomeViewController.swift index a2b8df83a..b0d2f3c71 100644 --- a/Mastodon/Scene/Onboarding/Welcome/WelcomeViewController.swift +++ b/Mastodon/Scene/Onboarding/Welcome/WelcomeViewController.swift @@ -51,6 +51,8 @@ final class WelcomeViewController: UIViewController, NeedsDependency { private(set) lazy var signUpButton: PrimaryActionButton = { let button = PrimaryActionButton() button.adjustsBackgroundImageWhenUserInterfaceStyleChanges = false + button.contentEdgeInsets = WelcomeViewController.actionButtonPadding + button.titleLabel?.adjustsFontForContentSizeCategory = true button.titleLabel?.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold)) button.setTitle(L10n.Common.Controls.Actions.signUp, for: .normal) let backgroundImageColor: UIColor = .white @@ -65,6 +67,8 @@ final class WelcomeViewController: UIViewController, NeedsDependency { private(set) lazy var signInButton: PrimaryActionButton = { let button = PrimaryActionButton() button.adjustsBackgroundImageWhenUserInterfaceStyleChanges = false + button.contentEdgeInsets = WelcomeViewController.actionButtonPadding + button.titleLabel?.adjustsFontForContentSizeCategory = true button.titleLabel?.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold)) button.setTitle(L10n.Scene.Welcome.logIn, for: .normal) let backgroundImageColor = Asset.Scene.Welcome.signInButtonBackground.color @@ -113,33 +117,23 @@ extension WelcomeViewController { signUpButton.translatesAutoresizingMaskIntoConstraints = false buttonContainer.addArrangedSubview(signUpButton) NSLayoutConstraint.activate([ - signUpButton.heightAnchor.constraint(equalToConstant: WelcomeViewController.actionButtonHeight).priority(.required - 1), + signUpButton.heightAnchor.constraint(greaterThanOrEqualToConstant: WelcomeViewController.actionButtonHeight).priority(.required - 1), ]) signInButton.translatesAutoresizingMaskIntoConstraints = false buttonContainer.addArrangedSubview(signInButton) NSLayoutConstraint.activate([ - signInButton.heightAnchor.constraint(equalToConstant: WelcomeViewController.actionButtonHeight).priority(.required - 1), + signInButton.heightAnchor.constraint(greaterThanOrEqualToConstant: WelcomeViewController.actionButtonHeight).priority(.required - 1), ]) signUpButtonShadowView.translatesAutoresizingMaskIntoConstraints = false buttonContainer.addSubview(signUpButtonShadowView) buttonContainer.sendSubviewToBack(signUpButtonShadowView) - NSLayoutConstraint.activate([ - signUpButtonShadowView.topAnchor.constraint(equalTo: signUpButton.topAnchor), - signUpButtonShadowView.leadingAnchor.constraint(equalTo: signUpButton.leadingAnchor), - signUpButtonShadowView.trailingAnchor.constraint(equalTo: signUpButton.trailingAnchor), - signUpButtonShadowView.bottomAnchor.constraint(equalTo: signUpButton.bottomAnchor), - ]) + signUpButtonShadowView.pinTo(to: signUpButton) signInButtonShadowView.translatesAutoresizingMaskIntoConstraints = false buttonContainer.addSubview(signInButtonShadowView) buttonContainer.sendSubviewToBack(signInButtonShadowView) - NSLayoutConstraint.activate([ - signInButtonShadowView.topAnchor.constraint(equalTo: signInButton.topAnchor), - signInButtonShadowView.leadingAnchor.constraint(equalTo: signInButton.leadingAnchor), - signInButtonShadowView.trailingAnchor.constraint(equalTo: signInButton.trailingAnchor), - signInButtonShadowView.bottomAnchor.constraint(equalTo: signInButton.bottomAnchor), - ]) + signInButtonShadowView.pinTo(to: signInButton) signUpButton.addTarget(self, action: #selector(signUpButtonDidClicked(_:)), for: .touchUpInside) signInButton.addTarget(self, action: #selector(signInButtonDidClicked(_:)), for: .touchUpInside) @@ -172,7 +166,9 @@ extension WelcomeViewController { override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) - + + view.layoutIfNeeded() + setupIllustrationLayout() setupButtonShadowView() } @@ -189,7 +185,7 @@ extension WelcomeViewController { y: 1, blur: 2, spread: 0, - roundedRect: signInButtonShadowView.bounds, + roundedRect: signUpButtonShadowView.bounds, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: 10, height: 10) ) @@ -243,7 +239,7 @@ extension WelcomeViewController { logoImageView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor), logoImageView.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor, constant: 35), view.readableContentGuide.trailingAnchor.constraint(equalTo: logoImageView.trailingAnchor, constant: 35), - logoImageView.heightAnchor.constraint(equalTo: logoImageView.widthAnchor, multiplier: 65.4/265.1), + logoImageView.heightAnchor.constraint(equalTo: logoImageView.widthAnchor, multiplier: 75.0/269.0), ]) logoImageView.setContentHuggingPriority(.defaultHigh, for: .vertical) } @@ -317,12 +313,12 @@ extension WelcomeViewController { extension WelcomeViewController { @objc private func signUpButtonDidClicked(_ sender: UIButton) { - coordinator.present(scene: .mastodonPickServer(viewMode: MastodonPickServerViewModel(context: context, mode: .signUp)), from: self, transition: .show) + _ = coordinator.present(scene: .mastodonPickServer(viewMode: MastodonPickServerViewModel(context: context)), from: self, transition: .show) } @objc private func signInButtonDidClicked(_ sender: UIButton) { - coordinator.present(scene: .mastodonPickServer(viewMode: MastodonPickServerViewModel(context: context, mode: .signIn)), from: self, transition: .show) + _ = coordinator.present(scene: .mastodonLogin, from: self, transition: .show) } @objc @@ -340,11 +336,7 @@ extension WelcomeViewController: OnboardingViewControllerAppearance { navigationItem.standardAppearance = barAppearance navigationItem.compactAppearance = barAppearance navigationItem.scrollEdgeAppearance = barAppearance - if #available(iOS 15.0, *) { - navigationItem.compactScrollEdgeAppearance = barAppearance - } else { - // Fallback on earlier versions - } + navigationItem.compactScrollEdgeAppearance = barAppearance } } diff --git a/Mastodon/Scene/Profile/About/Cell/ProfileFieldCollectionViewCell.swift b/Mastodon/Scene/Profile/About/Cell/ProfileFieldCollectionViewCell.swift index 1ed76a485..171ce1ac3 100644 --- a/Mastodon/Scene/Profile/About/Cell/ProfileFieldCollectionViewCell.swift +++ b/Mastodon/Scene/Profile/About/Cell/ProfileFieldCollectionViewCell.swift @@ -58,7 +58,7 @@ extension ProfileFieldCollectionViewCell { private func _init() { // Setup colors - checkmark.tintColor = Asset.Scene.Profile.About.bioAboutFieldVerifiedCheckmark.color; + checkmark.tintColor = Asset.Scene.Profile.About.bioAboutFieldVerifiedText.color; // Setup gestures tapGesture.addTarget(self, action: #selector(ProfileFieldCollectionViewCell.didTapCheckmark(_:))) @@ -68,6 +68,11 @@ extension ProfileFieldCollectionViewCell { checkmark.addInteraction(editMenuInteraction) } + // Setup Accessibility + checkmark.isAccessibilityElement = true + checkmark.accessibilityTraits = .none + keyMetaLabel.accessibilityTraits = .none + // containerStackView: V - [ metaContainer | plainContainer ] let containerStackView = UIStackView() containerStackView.axis = .vertical diff --git a/Mastodon/Scene/Profile/About/ProfileAboutViewController.swift b/Mastodon/Scene/Profile/About/ProfileAboutViewController.swift index eb1e6b39c..fe92d2ac4 100644 --- a/Mastodon/Scene/Profile/About/ProfileAboutViewController.swift +++ b/Mastodon/Scene/Profile/About/ProfileAboutViewController.swift @@ -60,12 +60,7 @@ extension ProfileAboutViewController { collectionView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(collectionView) - NSLayoutConstraint.activate([ - collectionView.topAnchor.constraint(equalTo: view.topAnchor), - collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + collectionView.pinToParent() collectionView.delegate = self viewModel.setupDiffableDataSource( diff --git a/Mastodon/Scene/Profile/About/ProfileAboutViewModel+Diffable.swift b/Mastodon/Scene/Profile/About/ProfileAboutViewModel+Diffable.swift index 0a11a71f6..eb7a6aaae 100644 --- a/Mastodon/Scene/Profile/About/ProfileAboutViewModel+Diffable.swift +++ b/Mastodon/Scene/Profile/About/ProfileAboutViewModel+Diffable.swift @@ -50,23 +50,33 @@ extension ProfileAboutViewModel { var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([.main]) diffableDataSource.apply(snapshot) - - Publishers.CombineLatest4( + + let fields = Publishers.CombineLatest3( $isEditing.removeDuplicates(), profileInfo.$fields.removeDuplicates(), - profileInfoEditing.$fields.removeDuplicates(), + profileInfoEditing.$fields.removeDuplicates() + ).map { isEditing, displayFields, editingFields in + isEditing ? editingFields : displayFields + } + + + Publishers.CombineLatest4( + $isEditing.removeDuplicates(), + $createdAt.removeDuplicates(), + fields, $emojiMeta.removeDuplicates() ) .throttle(for: 0.3, scheduler: DispatchQueue.main, latest: true) - .sink { [weak self] isEditing, displayFields, editingFields, emojiMeta in + .sink { [weak self] isEditing, createdAt, fields, emojiMeta in guard let self = self else { return } guard let diffableDataSource = self.diffableDataSource else { return } var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([.main]) - let fields: [ProfileFieldItem.FieldValue] = isEditing ? editingFields : displayFields - var items: [ProfileFieldItem] = fields.map { field in + var items: [ProfileFieldItem] = [ + .createdAt(date: createdAt), + ] + fields.map { field in if isEditing { return ProfileFieldItem.editField(field: field) } else { @@ -78,10 +88,6 @@ extension ProfileAboutViewModel { items.append(.addEntry) } - if !isEditing, items.isEmpty { - items.append(.noResult) - } - snapshot.appendItems(items, toSection: .main) diffableDataSource.apply(snapshot, animatingDifferences: false, completion: nil) diff --git a/Mastodon/Scene/Profile/About/ProfileAboutViewModel.swift b/Mastodon/Scene/Profile/About/ProfileAboutViewModel.swift index 044894b8a..8ab427c39 100644 --- a/Mastodon/Scene/Profile/About/ProfileAboutViewModel.swift +++ b/Mastodon/Scene/Profile/About/ProfileAboutViewModel.swift @@ -31,6 +31,7 @@ final class ProfileAboutViewModel { @Published var fields: [MastodonField] = [] @Published var emojiMeta: MastodonContent.Emojis = [:] + @Published var createdAt: Date = Date() init(context: AppContext) { self.context = context @@ -46,6 +47,11 @@ final class ProfileAboutViewModel { .compactMap { $0 } .flatMap { $0.publisher(for: \.fields) } .assign(to: &$fields) + + $user + .compactMap { $0 } + .flatMap { $0.publisher(for: \.createdAt) } + .assign(to: &$createdAt) Publishers.CombineLatest( $fields, diff --git a/Mastodon/Scene/Profile/Bookmark/BookmarkViewController.swift b/Mastodon/Scene/Profile/Bookmark/BookmarkViewController.swift index 5edec2618..458c47335 100644 --- a/Mastodon/Scene/Profile/Bookmark/BookmarkViewController.swift +++ b/Mastodon/Scene/Profile/Bookmark/BookmarkViewController.swift @@ -64,12 +64,7 @@ extension BookmarkViewController { tableView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(tableView) - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: view.topAnchor), - tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + tableView.pinToParent() tableView.delegate = self viewModel.setupDiffableDataSource( diff --git a/Mastodon/Scene/Profile/Bookmark/BookmarkViewModel+Diffable.swift b/Mastodon/Scene/Profile/Bookmark/BookmarkViewModel+Diffable.swift index 69075a8ce..d52309e92 100644 --- a/Mastodon/Scene/Profile/Bookmark/BookmarkViewModel+Diffable.swift +++ b/Mastodon/Scene/Profile/Bookmark/BookmarkViewModel+Diffable.swift @@ -17,6 +17,7 @@ extension BookmarkViewModel { tableView: tableView, context: context, configuration: StatusSection.Configuration( + context: context, authContext: authContext, statusTableViewCellDelegate: statusTableViewCellDelegate, timelineMiddleLoaderTableViewCellDelegate: nil, @@ -58,7 +59,7 @@ extension BookmarkViewModel { } } - diffableDataSource.applySnapshot(snapshot, animated: false) + diffableDataSource.apply(snapshot, animatingDifferences: false) } .store(in: &disposeBag) } diff --git a/Mastodon/Scene/Profile/FamiliarFollowers/FamiliarFollowersViewController.swift b/Mastodon/Scene/Profile/FamiliarFollowers/FamiliarFollowersViewController.swift index b6d6f8313..3595303d9 100644 --- a/Mastodon/Scene/Profile/FamiliarFollowers/FamiliarFollowersViewController.swift +++ b/Mastodon/Scene/Profile/FamiliarFollowers/FamiliarFollowersViewController.swift @@ -53,12 +53,7 @@ extension FamiliarFollowersViewController { tableView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(tableView) - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: view.topAnchor), - tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + tableView.pinToParent() tableView.delegate = self viewModel.setupDiffableDataSource( diff --git a/Mastodon/Scene/Profile/Favorite/FavoriteViewController.swift b/Mastodon/Scene/Profile/Favorite/FavoriteViewController.swift index c15adcf83..19f25f3ad 100644 --- a/Mastodon/Scene/Profile/Favorite/FavoriteViewController.swift +++ b/Mastodon/Scene/Profile/Favorite/FavoriteViewController.swift @@ -67,12 +67,7 @@ extension FavoriteViewController { tableView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(tableView) - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: view.topAnchor), - tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + tableView.pinToParent() tableView.delegate = self viewModel.setupDiffableDataSource( diff --git a/Mastodon/Scene/Profile/Favorite/FavoriteViewModel+Diffable.swift b/Mastodon/Scene/Profile/Favorite/FavoriteViewModel+Diffable.swift index 3723dae5d..367a4d51f 100644 --- a/Mastodon/Scene/Profile/Favorite/FavoriteViewModel+Diffable.swift +++ b/Mastodon/Scene/Profile/Favorite/FavoriteViewModel+Diffable.swift @@ -17,6 +17,7 @@ extension FavoriteViewModel { tableView: tableView, context: context, configuration: StatusSection.Configuration( + context: context, authContext: authContext, statusTableViewCellDelegate: statusTableViewCellDelegate, timelineMiddleLoaderTableViewCellDelegate: nil, @@ -58,7 +59,7 @@ extension FavoriteViewModel { } } - diffableDataSource.applySnapshot(snapshot, animated: false) + diffableDataSource.apply(snapshot, animatingDifferences: false) } .store(in: &disposeBag) } diff --git a/Mastodon/Scene/Profile/Favorite/FavoriteViewModel+State.swift b/Mastodon/Scene/Profile/Favorite/FavoriteViewModel+State.swift index 803a9d45e..c2dc27201 100644 --- a/Mastodon/Scene/Profile/Favorite/FavoriteViewModel+State.swift +++ b/Mastodon/Scene/Profile/Favorite/FavoriteViewModel+State.swift @@ -46,7 +46,7 @@ extension FavoriteViewModel { extension FavoriteViewModel.State { class Initial: FavoriteViewModel.State { override func isValidNextState(_ stateClass: AnyClass) -> Bool { - guard let viewModel = viewModel else { return false } + guard viewModel != nil else { return false } switch stateClass { case is Reloading.Type: return true @@ -130,13 +130,12 @@ extension FavoriteViewModel.State { override func didEnter(from previousState: GKState?) { super.didEnter(from: previousState) - guard let viewModel = viewModel, let stateMachine = stateMachine else { return } + guard let viewModel else { return } if previousState is Reloading { maxID = nil } - Task { do { let response = try await viewModel.context.apiService.favoritedStatuses( diff --git a/Mastodon/Scene/Profile/FollowedTags/FollowedTagsTableViewCell.swift b/Mastodon/Scene/Profile/FollowedTags/FollowedTagsTableViewCell.swift new file mode 100644 index 000000000..6adb15a9c --- /dev/null +++ b/Mastodon/Scene/Profile/FollowedTags/FollowedTagsTableViewCell.swift @@ -0,0 +1,78 @@ +// +// FollowedTagsTableViewCell.swift +// Mastodon +// +// Created by Marcus Kida on 24.11.22. +// + +import UIKit +import CoreDataStack + +final class FollowedTagsTableViewCell: UITableViewCell { + private var hashtagView: HashtagTimelineHeaderView! + private let separatorLine = UIView.separatorLine + private weak var viewModel: FollowedTagsViewModel? + private weak var hashtag: Tag? + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setup() + } + + required init?(coder: NSCoder) { + fatalError("Not implemented") + } + + override func prepareForReuse() { + hashtagView.removeFromSuperview() + viewModel = nil + hashtagView = nil + super.prepareForReuse() + setup() + } +} + +private extension FollowedTagsTableViewCell { + func setup() { + selectionStyle = .none + + hashtagView = HashtagTimelineHeaderView() + hashtagView.translatesAutoresizingMaskIntoConstraints = false + + contentView.addSubview(hashtagView) + contentView.backgroundColor = .clear + + NSLayoutConstraint.activate([ + hashtagView.heightAnchor.constraint(equalToConstant: 118).priority(.required), + hashtagView.topAnchor.constraint(equalTo: contentView.topAnchor), + hashtagView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + hashtagView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + hashtagView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + ]) + + separatorLine.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(separatorLine) + NSLayoutConstraint.activate([ + separatorLine.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + separatorLine.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + separatorLine.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + separatorLine.heightAnchor.constraint(equalToConstant: UIView.separatorLineHeight(of: contentView)), + ]) + + hashtagView.onButtonTapped = { [weak self] in + guard let self = self, let tag = self.hashtag else { return } + self.viewModel?.followOrUnfollow(tag) + } + } +} + +extension FollowedTagsTableViewCell { + func populate(with tag: Tag) { + self.hashtag = tag + hashtagView.update(HashtagTimelineHeaderView.Data.from(tag)) + } + + func setup(_ viewModel: FollowedTagsViewModel) { + self.viewModel = viewModel + } +} diff --git a/Mastodon/Scene/Profile/FollowedTags/FollowedTagsViewController.swift b/Mastodon/Scene/Profile/FollowedTags/FollowedTagsViewController.swift new file mode 100644 index 000000000..9a31fe0c3 --- /dev/null +++ b/Mastodon/Scene/Profile/FollowedTags/FollowedTagsViewController.swift @@ -0,0 +1,77 @@ +// +// FollowedTagsViewController.swift +// Mastodon +// +// Created by Marcus Kida on 22.11.22. +// + +import os +import UIKit +import Combine +import MastodonAsset +import MastodonCore +import MastodonUI +import MastodonLocalization + +final class FollowedTagsViewController: UIViewController, NeedsDependency { + let logger = Logger(subsystem: String(describing: FollowedTagsViewController.self), category: "ViewController") + + weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } + weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } + + var disposeBag = Set() + var viewModel: FollowedTagsViewModel! + + let titleView = DoubleTitleLabelNavigationBarTitleView() + + lazy var tableView: UITableView = { + let tableView = UITableView() + tableView.register(FollowedTagsTableViewCell.self, forCellReuseIdentifier: String(describing: FollowedTagsTableViewCell.self)) + tableView.rowHeight = UITableView.automaticDimension + tableView.separatorStyle = .none + tableView.backgroundColor = .clear + return tableView + }() + + deinit { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + } +} + +extension FollowedTagsViewController { + override func viewDidLoad() { + super.viewDidLoad() + + let _title = L10n.Scene.FollowedTags.title + title = _title + titleView.update(title: _title, subtitle: nil) + + navigationItem.titleView = titleView + + view.backgroundColor = ThemeService.shared.currentTheme.value.secondarySystemBackgroundColor + ThemeService.shared.currentTheme + .receive(on: RunLoop.main) + .sink { [weak self] theme in + guard let self = self else { return } + self.view.backgroundColor = theme.secondarySystemBackgroundColor + } + .store(in: &disposeBag) + + tableView.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(tableView) + tableView.pinToParent() + viewModel.setupTableView(tableView) + + viewModel.presentHashtagTimeline + .receive(on: DispatchQueue.main) + .sink { [weak self] hashtagTimelineViewModel in + guard let self = self else { return } + _ = self.coordinator.present( + scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel), + from: self, + transition: .show + ) + } + .store(in: &disposeBag) + } +} diff --git a/Mastodon/Scene/Profile/FollowedTags/FollowedTagsViewModel+DiffableDataSource.swift b/Mastodon/Scene/Profile/FollowedTags/FollowedTagsViewModel+DiffableDataSource.swift new file mode 100644 index 000000000..91f2cc5e9 --- /dev/null +++ b/Mastodon/Scene/Profile/FollowedTags/FollowedTagsViewModel+DiffableDataSource.swift @@ -0,0 +1,51 @@ +// +// FollowedTagsViewModel+DiffableDataSource.swift +// Mastodon +// +// Created by Marcus Kida on 01.12.22. +// + +import UIKit +import Combine +import CoreData +import CoreDataStack +import MastodonSDK +import MastodonCore + +extension FollowedTagsViewModel { + enum Section: Hashable { + case main + } + + enum Item: Hashable { + case hashtag(Tag) + } + + func tableViewDiffableDataSource( + for tableView: UITableView + ) -> UITableViewDiffableDataSource { + UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, item in + switch item { + case let .hashtag(tag): + guard let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: FollowedTagsTableViewCell.self), for: indexPath) as? FollowedTagsTableViewCell else { + assertionFailure() + return UITableViewCell() + } + + cell.setup(self) + cell.populate(with: tag) + return cell + } + } + } + + func setupDiffableDataSource( + tableView: UITableView + ) { + diffableDataSource = tableViewDiffableDataSource(for: tableView) + + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.main]) + diffableDataSource?.apply(snapshot) + } +} diff --git a/Mastodon/Scene/Profile/FollowedTags/FollowedTagsViewModel.swift b/Mastodon/Scene/Profile/FollowedTags/FollowedTagsViewModel.swift new file mode 100644 index 000000000..dbcf4d756 --- /dev/null +++ b/Mastodon/Scene/Profile/FollowedTags/FollowedTagsViewModel.swift @@ -0,0 +1,109 @@ +// +// FollowedTagsViewModel.swift +// Mastodon +// +// Created by Marcus Kida on 23.11.22. +// + +import os +import UIKit +import Combine +import CoreData +import CoreDataStack +import MastodonSDK +import MastodonCore + +final class FollowedTagsViewModel: NSObject { + let logger = Logger(subsystem: String(describing: FollowedTagsViewModel.self), category: "ViewModel") + var disposeBag = Set() + let fetchedResultsController: FollowedTagsFetchedResultController + + private weak var tableView: UITableView? + var diffableDataSource: UITableViewDiffableDataSource? + + // input + let context: AppContext + let authContext: AuthContext + + // output + let presentHashtagTimeline = PassthroughSubject() + + init(context: AppContext, authContext: AuthContext) { + self.context = context + self.authContext = authContext + self.fetchedResultsController = FollowedTagsFetchedResultController( + managedObjectContext: context.managedObjectContext, + domain: authContext.mastodonAuthenticationBox.domain, + user: authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)!.user + ) + + super.init() + + self.fetchedResultsController + .$records + .receive(on: DispatchQueue.main) + .sink { [weak self] records in + guard let self = self else { return } + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.main]) + snapshot.appendItems(records.map {.hashtag($0) }) + self.diffableDataSource?.apply(snapshot, animatingDifferences: true) + } + .store(in: &disposeBag) + } +} + +extension FollowedTagsViewModel { + func setupTableView(_ tableView: UITableView) { + self.tableView = tableView + setupDiffableDataSource(tableView: tableView) + tableView.delegate = self + + fetchFollowedTags() + } + + func fetchFollowedTags() { + Task { @MainActor in + try await context.apiService.getFollowedTags( + domain: authContext.mastodonAuthenticationBox.domain, + query: Mastodon.API.Account.FollowedTagsQuery(limit: nil), + authenticationBox: authContext.mastodonAuthenticationBox + ) + } + } + + func followOrUnfollow(_ tag: Tag) { + Task { @MainActor in + switch tag.following { + case true: + _ = try? await context.apiService.unfollowTag( + for: tag.name, + authenticationBox: authContext.mastodonAuthenticationBox + ) + case false: + _ = try? await context.apiService.followTag( + for: tag.name, + authenticationBox: authContext.mastodonAuthenticationBox + ) + } + fetchFollowedTags() + } + } +} + +extension FollowedTagsViewModel: UITableViewDelegate { + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(indexPath)") + tableView.deselectRow(at: indexPath, animated: true) + + let object = fetchedResultsController.records[indexPath.row] + + let hashtagTimelineViewModel = HashtagTimelineViewModel( + context: self.context, + authContext: self.authContext, + hashtag: object.name + ) + + presentHashtagTimeline.send(hashtagTimelineViewModel) + } +} diff --git a/Mastodon/Scene/Profile/Follower/FollowerListViewController.swift b/Mastodon/Scene/Profile/Follower/FollowerListViewController.swift index 190fa27e5..c111d13d0 100644 --- a/Mastodon/Scene/Profile/Follower/FollowerListViewController.swift +++ b/Mastodon/Scene/Profile/Follower/FollowerListViewController.swift @@ -58,12 +58,7 @@ extension FollowerListViewController { tableView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(tableView) - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: view.topAnchor), - tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + tableView.pinToParent() tableView.delegate = self viewModel.setupDiffableDataSource( diff --git a/Mastodon/Scene/Profile/Follower/FollowerListViewModel+Diffable.swift b/Mastodon/Scene/Profile/Follower/FollowerListViewModel+Diffable.swift index 7a30c3234..199879c83 100644 --- a/Mastodon/Scene/Profile/Follower/FollowerListViewModel+Diffable.swift +++ b/Mastodon/Scene/Profile/Follower/FollowerListViewModel+Diffable.swift @@ -27,12 +27,7 @@ extension FollowerListViewModel { var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([.main]) snapshot.appendItems([.bottomLoader], toSection: .main) - if #available(iOS 15.0, *) { - diffableDataSource?.applySnapshotUsingReloadData(snapshot, completion: nil) - } else { - // Fallback on earlier versions - diffableDataSource?.apply(snapshot, animatingDifferences: false) - } + diffableDataSource?.applySnapshotUsingReloadData(snapshot, completion: nil) userFetchedResultsController.$records .receive(on: DispatchQueue.main) diff --git a/Mastodon/Scene/Profile/Following/FollowingListViewController.swift b/Mastodon/Scene/Profile/Following/FollowingListViewController.swift index e16b600c2..3fa65918a 100644 --- a/Mastodon/Scene/Profile/Following/FollowingListViewController.swift +++ b/Mastodon/Scene/Profile/Following/FollowingListViewController.swift @@ -58,12 +58,7 @@ extension FollowingListViewController { tableView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(tableView) - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: view.topAnchor), - tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + tableView.pinToParent() tableView.delegate = self viewModel.setupDiffableDataSource( diff --git a/Mastodon/Scene/Profile/Following/FollowingListViewModel+Diffable.swift b/Mastodon/Scene/Profile/Following/FollowingListViewModel+Diffable.swift index e022c5736..f139a3b79 100644 --- a/Mastodon/Scene/Profile/Following/FollowingListViewModel+Diffable.swift +++ b/Mastodon/Scene/Profile/Following/FollowingListViewModel+Diffable.swift @@ -28,13 +28,8 @@ extension FollowingListViewModel { var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([.main]) snapshot.appendItems([.bottomLoader], toSection: .main) - if #available(iOS 15.0, *) { - diffableDataSource?.applySnapshotUsingReloadData(snapshot, completion: nil) - } else { - // Fallback on earlier versions - diffableDataSource?.apply(snapshot, animatingDifferences: false) - } - + diffableDataSource?.applySnapshotUsingReloadData(snapshot) + userFetchedResultsController.$records .receive(on: DispatchQueue.main) .sink { [weak self] records in diff --git a/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift b/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift index c5dbbecd4..55e73a1e8 100644 --- a/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift +++ b/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift @@ -60,7 +60,8 @@ final class ProfileHeaderViewController: UIViewController, NeedsDependency, Medi // private var isAdjustBannerImageViewForSafeAreaInset = false private var containerSafeAreaInset: UIEdgeInsets = .zero - + + private var currentImageType = ImageType.avatar private(set) lazy var imagePicker: PHPickerViewController = { var configuration = PHPickerConfiguration() configuration.filter = .images @@ -125,7 +126,9 @@ extension ProfileHeaderViewController { } .store(in: &disposeBag) - profileHeaderView.editAvatarButtonOverlayIndicatorView.menu = createAvatarContextMenu() + profileHeaderView.editBannerButton.menu = createImageContextMenu(.banner) + profileHeaderView.editBannerButton.showsMenuAsPrimaryAction = true + profileHeaderView.editAvatarButtonOverlayIndicatorView.menu = createImageContextMenu(.avatar) profileHeaderView.editAvatarButtonOverlayIndicatorView.showsMenuAsPrimaryAction = true profileHeaderView.delegate = self @@ -150,12 +153,18 @@ extension ProfileHeaderViewController { viewModel.$relationshipActionOptionSet .assign(to: \.relationshipActionOptionSet, on: profileHeaderView.viewModel) .store(in: &disposeBag) + viewModel.$isMyself + .assign(to: \.isMyself, on: profileHeaderView.viewModel) + .store(in: &disposeBag) viewModel.$isEditing .assign(to: \.isEditing, on: profileHeaderView.viewModel) .store(in: &disposeBag) viewModel.$isUpdating .assign(to: \.isUpdating, on: profileHeaderView.viewModel) .store(in: &disposeBag) + viewModel.profileInfoEditing.$header + .assign(to: \.headerImageEditing, on: profileHeaderView.viewModel) + .store(in: &disposeBag) viewModel.profileInfoEditing.$avatar .assign(to: \.avatarImageEditing, on: profileHeaderView.viewModel) .store(in: &disposeBag) @@ -173,7 +182,7 @@ extension ProfileHeaderViewController { profileHeaderView.viewModel.viewDidAppear.send() // set display after view appear - profileHeaderView.setupAvatarOverlayViews() + profileHeaderView.setupImageOverlayViews() } override func viewDidLayoutSubviews() { @@ -185,11 +194,16 @@ extension ProfileHeaderViewController { } extension ProfileHeaderViewController { - private func createAvatarContextMenu() -> UIMenu { + fileprivate enum ImageType { + case avatar + case banner + } + private func createImageContextMenu(_ type: ImageType) -> UIMenu { var children: [UIMenuElement] = [] let photoLibraryAction = UIAction(title: L10n.Scene.Compose.MediaSelection.photoLibrary, image: UIImage(systemName: "rectangle.on.rectangle"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak self] _ in guard let self = self else { return } os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: mediaSelectionType: .photoLibaray", ((#file as NSString).lastPathComponent), #line, #function) + self.currentImageType = type self.present(self.imagePicker, animated: true, completion: nil) } children.append(photoLibraryAction) @@ -197,6 +211,7 @@ extension ProfileHeaderViewController { let cameraAction = UIAction(title: L10n.Scene.Compose.MediaSelection.camera, image: UIImage(systemName: "camera"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off, handler: { [weak self] _ in guard let self = self else { return } os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: mediaSelectionType: .camera", ((#file as NSString).lastPathComponent), #line, #function) + self.currentImageType = type self.present(self.imagePickerController, animated: true, completion: nil) }) children.append(cameraAction) @@ -204,6 +219,7 @@ extension ProfileHeaderViewController { let browseAction = UIAction(title: L10n.Scene.Compose.MediaSelection.browse, image: UIImage(systemName: "ellipsis"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak self] _ in guard let self = self else { return } os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: mediaSelectionType: .browse", ((#file as NSString).lastPathComponent), #line, #function) + self.currentImageType = type self.present(self.documentPickerController, animated: true, completion: nil) } children.append(browseAction) @@ -215,7 +231,13 @@ extension ProfileHeaderViewController { DispatchQueue.main.async { let cropController = CropViewController(croppingStyle: .default, image: image) cropController.delegate = self - cropController.setAspectRatioPreset(.presetSquare, animated: true) + switch self.currentImageType { + case .banner: + cropController.customAspectRatio = CGSize(width: 3, height: 1) + cropController.setAspectRatioPreset(.presetCustom, animated: true) + case .avatar: + cropController.setAspectRatioPreset(.presetSquare, animated: true) + } cropController.aspectRatioPickerButtonHidden = true cropController.aspectRatioLockEnabled = true pickerViewController.dismiss(animated: true, completion: { @@ -443,7 +465,12 @@ extension ProfileHeaderViewController: UIDocumentPickerDelegate { // MARK: - CropViewControllerDelegate extension ProfileHeaderViewController: CropViewControllerDelegate { public func cropViewController(_ cropViewController: CropViewController, didCropToImage image: UIImage, withRect cropRect: CGRect, angle: Int) { - viewModel.profileInfoEditing.avatar = image + switch currentImageType { + case .banner: + viewModel.profileInfoEditing.header = image + case .avatar: + viewModel.profileInfoEditing.avatar = image + } cropViewController.dismiss(animated: true, completion: nil) } } diff --git a/Mastodon/Scene/Profile/Header/ProfileHeaderViewModel.swift b/Mastodon/Scene/Profile/Header/ProfileHeaderViewModel.swift index 65f15efa7..3025907e0 100644 --- a/Mastodon/Scene/Profile/Header/ProfileHeaderViewModel.swift +++ b/Mastodon/Scene/Profile/Header/ProfileHeaderViewModel.swift @@ -18,6 +18,7 @@ import MastodonUI final class ProfileHeaderViewModel { static let avatarImageMaxSizeInPixel = CGSize(width: 400, height: 400) + static let bannerImageMaxSizeInPixel = CGSize(width: 1500, height: 500) static let maxProfileFieldCount = 4 var disposeBag = Set() @@ -29,6 +30,7 @@ final class ProfileHeaderViewModel { @Published var user: MastodonUser? @Published var relationshipActionOptionSet: RelationshipActionOptionSet = .none + @Published var isMyself = false @Published var isEditing = false @Published var isUpdating = false @@ -52,6 +54,9 @@ final class ProfileHeaderViewModel { .sink { [weak self] account in guard let self = self else { return } guard let account = account else { return } + // banner + self.profileInfo.header = nil + self.profileInfoEditing.header = nil // avatar self.profileInfo.avatar = nil self.profileInfoEditing.avatar = nil @@ -72,6 +77,7 @@ final class ProfileHeaderViewModel { extension ProfileHeaderViewModel { class ProfileInfo { // input + @Published var header: UIImage? @Published var avatar: UIImage? @Published var name: String? @Published var note: String? @@ -99,6 +105,7 @@ extension ProfileHeaderViewModel: ProfileViewModelEditable { var isEdited: Bool { guard isEditing else { return false } + guard profileInfoEditing.header == nil else { return true } guard profileInfoEditing.avatar == nil else { return true } guard profileInfo.name == profileInfoEditing.name else { return true } guard profileInfo.note == profileInfoEditing.note else { return true } diff --git a/Mastodon/Scene/Profile/Header/View/ProfileHeaderView+ViewModel.swift b/Mastodon/Scene/Profile/Header/View/ProfileHeaderView+ViewModel.swift index c51ccfab3..c5389958a 100644 --- a/Mastodon/Scene/Profile/Header/View/ProfileHeaderView+ViewModel.swift +++ b/Mastodon/Scene/Profile/Header/View/ProfileHeaderView+ViewModel.swift @@ -28,6 +28,7 @@ extension ProfileHeaderView { @Published var emojiMeta: MastodonContent.Emojis = [:] @Published var headerImageURL: URL? + @Published var headerImageEditing: UIImage? @Published var avatarImageURL: URL? @Published var avatarImageEditing: UIImage? @@ -47,6 +48,7 @@ extension ProfileHeaderView { @Published var relationshipActionOptionSet: RelationshipActionOptionSet = .none @Published var isRelationshipActionButtonHidden = false + @Published var isMyself = false init() { $relationshipActionOptionSet @@ -61,14 +63,19 @@ extension ProfileHeaderView.ViewModel { func bind(view: ProfileHeaderView) { // header - Publishers.CombineLatest( + Publishers.CombineLatest4( $headerImageURL, + $headerImageEditing, + $isEditing, viewDidAppear ) - .sink { headerImageURL, _ in + .sink { headerImageURL, headerImageEditing, isEditing, _ in view.bannerImageView.af.cancelImageRequest() - let placeholder = UIImage.placeholder(color: ProfileHeaderView.bannerImageViewPlaceholderColor) - guard let bannerImageURL = headerImageURL else { + let defaultPlaceholder = UIImage.placeholder(color: ProfileHeaderView.bannerImageViewPlaceholderColor) + let placeholder = isEditing ? (headerImageEditing ?? defaultPlaceholder) : defaultPlaceholder + guard let bannerImageURL = headerImageURL, + !isEditing || headerImageEditing == nil + else { view.bannerImageView.image = placeholder return } @@ -183,6 +190,19 @@ extension ProfileHeaderView.ViewModel { } .store(in: &disposeBag) // dashboard + $isMyself + .sink { isMyself in + if isMyself { + view.statusDashboardView.postDashboardMeterView.textLabel.text = L10n.Scene.Profile.Dashboard.myPosts + view.statusDashboardView.followingDashboardMeterView.textLabel.text = L10n.Scene.Profile.Dashboard.myFollowing + view.statusDashboardView.followersDashboardMeterView.textLabel.text = L10n.Scene.Profile.Dashboard.myFollowers + } else { + view.statusDashboardView.postDashboardMeterView.textLabel.text = L10n.Scene.Profile.Dashboard.otherPosts + view.statusDashboardView.followingDashboardMeterView.textLabel.text = L10n.Scene.Profile.Dashboard.otherFollowing + view.statusDashboardView.followersDashboardMeterView.textLabel.text = L10n.Scene.Profile.Dashboard.otherFollowers + } + } + .store(in: &disposeBag) $statusesCount .sink { count in let text = count.flatMap { MastodonMetricFormatter().string(from: $0) } ?? "-" @@ -262,22 +282,29 @@ extension ProfileHeaderView { animator.addAnimations { self.bannerImageViewOverlayVisualEffectView.backgroundColor = ProfileHeaderView.bannerImageViewOverlayViewBackgroundNormalColor self.nameTextFieldBackgroundView.backgroundColor = .clear + self.editBannerButton.alpha = 0 self.editAvatarBackgroundView.alpha = 0 } animator.addCompletion { _ in + self.editBannerButton.isHidden = true self.editAvatarBackgroundView.isHidden = true + self.bannerImageViewSingleTapGestureRecognizer.isEnabled = true } case .editing: nameMetaText.textView.alpha = 0 nameTextField.isEnabled = true nameTextField.alpha = 1 + editBannerButton.isHidden = false + editBannerButton.alpha = 0 editAvatarBackgroundView.isHidden = false editAvatarBackgroundView.alpha = 0 bioMetaText.textView.backgroundColor = .clear + bannerImageViewSingleTapGestureRecognizer.isEnabled = false animator.addAnimations { self.bannerImageViewOverlayVisualEffectView.backgroundColor = ProfileHeaderView.bannerImageViewOverlayViewBackgroundEditingColor self.nameTextFieldBackgroundView.backgroundColor = Asset.Scene.Profile.Banner.nameEditBackgroundGray.color + self.editBannerButton.alpha = 1 self.editAvatarBackgroundView.alpha = 1 self.bioMetaText.textView.backgroundColor = Asset.Scene.Profile.Banner.bioEditBackgroundGray.color } diff --git a/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift b/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift index a2194758b..5204cf567 100644 --- a/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift +++ b/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift @@ -28,7 +28,6 @@ final class ProfileHeaderView: UIView { static let avatarImageViewSize = CGSize(width: 98, height: 98) static let avatarImageViewCornerRadius: CGFloat = 25 - static let avatarImageViewBorderColor = UIColor.white static let avatarImageViewBorderWidth: CGFloat = 2 static let friendshipActionButtonSize = CGSize(width: 108, height: 34) static let bannerImageViewPlaceholderColor = UIColor.systemGray @@ -50,6 +49,7 @@ final class ProfileHeaderView: UIView { return viewModel }() + let bannerImageViewSingleTapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer let bannerContainerView = UIView() let bannerImageView: UIImageView = { let imageView = UIImageView() @@ -89,7 +89,6 @@ final class ProfileHeaderView: UIView { view.layer.masksToBounds = true view.layer.cornerRadius = ProfileHeaderView.avatarImageViewCornerRadius view.layer.cornerCurve = .continuous - view.layer.borderColor = ProfileHeaderView.avatarImageViewBorderColor.cgColor view.layer.borderWidth = ProfileHeaderView.avatarImageViewBorderWidth return view }() @@ -101,7 +100,9 @@ final class ProfileHeaderView: UIView { return button }() - func setupAvatarOverlayViews() { + func setupImageOverlayViews() { + editBannerButton.tintColor = .white + editAvatarBackgroundView.backgroundColor = UIColor.black.withAlphaComponent(0.6) editAvatarButtonOverlayIndicatorView.tintColor = .white } @@ -113,6 +114,13 @@ final class ProfileHeaderView: UIView { return visualEffectView }() + let editBannerButton: HighlightDimmableButton = { + let button = HighlightDimmableButton() + button.setImage(UIImage(systemName: "photo", withConfiguration: UIImage.SymbolConfiguration(pointSize: 28)), for: .normal) + button.tintColor = .clear + return button + }() + let editAvatarBackgroundView: UIView = { let view = UIView() view.backgroundColor = .clear // set value after view appeared @@ -232,12 +240,13 @@ final class ProfileHeaderView: UIView { extension ProfileHeaderView { private func _init() { - backgroundColor = ThemeService.shared.currentTheme.value.systemBackgroundColor - ThemeService.shared.currentTheme + let currentTheme = ThemeService.shared.currentTheme + setColors(from: currentTheme.value) + + currentTheme .receive(on: DispatchQueue.main) .sink { [weak self] theme in - guard let self = self else { return } - self.backgroundColor = theme.systemBackgroundColor + self?.setColors(from: theme) } .store(in: &_disposeBag) @@ -265,13 +274,13 @@ extension ProfileHeaderView { bannerImageViewOverlayVisualEffectView.translatesAutoresizingMaskIntoConstraints = false bannerImageView.addSubview(bannerImageViewOverlayVisualEffectView) - NSLayoutConstraint.activate([ - bannerImageViewOverlayVisualEffectView.topAnchor.constraint(equalTo: bannerImageView.topAnchor), - bannerImageViewOverlayVisualEffectView.leadingAnchor.constraint(equalTo: bannerImageView.leadingAnchor), - bannerImageViewOverlayVisualEffectView.trailingAnchor.constraint(equalTo: bannerImageView.trailingAnchor), - bannerImageViewOverlayVisualEffectView.bottomAnchor.constraint(equalTo: bannerImageView.bottomAnchor), - ]) - + bannerImageViewOverlayVisualEffectView.pinToParent() + + editBannerButton.translatesAutoresizingMaskIntoConstraints = false + bannerContainerView.addSubview(editBannerButton) + editBannerButton.pinTo(to: bannerImageView) + bannerContainerView.isUserInteractionEnabled = true + // follows you followsYouBlurEffectView.translatesAutoresizingMaskIntoConstraints = false addSubview(followsYouBlurEffectView) @@ -286,12 +295,7 @@ extension ProfileHeaderView { followsYouVibrantEffectView.translatesAutoresizingMaskIntoConstraints = false followsYouBlurEffectView.contentView.addSubview(followsYouVibrantEffectView) - NSLayoutConstraint.activate([ - followsYouVibrantEffectView.topAnchor.constraint(equalTo: followsYouBlurEffectView.topAnchor), - followsYouVibrantEffectView.leadingAnchor.constraint(equalTo: followsYouBlurEffectView.leadingAnchor), - followsYouVibrantEffectView.trailingAnchor.constraint(equalTo: followsYouBlurEffectView.trailingAnchor), - followsYouVibrantEffectView.bottomAnchor.constraint(equalTo: followsYouBlurEffectView.bottomAnchor), - ]) + followsYouVibrantEffectView.pinTo(to: followsYouBlurEffectView) followsYouLabel.translatesAutoresizingMaskIntoConstraints = false followsYouVibrantEffectView.contentView.addSubview(followsYouLabel) @@ -317,40 +321,25 @@ extension ProfileHeaderView { avatarButton.translatesAutoresizingMaskIntoConstraints = false avatarImageViewBackgroundView.addSubview(avatarButton) NSLayoutConstraint.activate([ - avatarButton.topAnchor.constraint(equalTo: avatarImageViewBackgroundView.topAnchor, constant: 0.5 * ProfileHeaderView.avatarImageViewBorderWidth), - avatarButton.leadingAnchor.constraint(equalTo: avatarImageViewBackgroundView.leadingAnchor, constant: 0.5 * ProfileHeaderView.avatarImageViewBorderWidth), - avatarImageViewBackgroundView.trailingAnchor.constraint(equalTo: avatarButton.trailingAnchor, constant: 0.5 * ProfileHeaderView.avatarImageViewBorderWidth), - avatarImageViewBackgroundView.bottomAnchor.constraint(equalTo: avatarButton.bottomAnchor, constant: 0.5 * ProfileHeaderView.avatarImageViewBorderWidth), + avatarButton.topAnchor.constraint(equalTo: avatarImageViewBackgroundView.topAnchor, constant: ProfileHeaderView.avatarImageViewBorderWidth), + avatarButton.leadingAnchor.constraint(equalTo: avatarImageViewBackgroundView.leadingAnchor, constant: ProfileHeaderView.avatarImageViewBorderWidth), + avatarImageViewBackgroundView.trailingAnchor.constraint(equalTo: avatarButton.trailingAnchor, constant: ProfileHeaderView.avatarImageViewBorderWidth), + avatarImageViewBackgroundView.bottomAnchor.constraint(equalTo: avatarButton.bottomAnchor, constant: ProfileHeaderView.avatarImageViewBorderWidth), avatarButton.widthAnchor.constraint(equalToConstant: ProfileHeaderView.avatarImageViewSize.width).priority(.required - 1), avatarButton.heightAnchor.constraint(equalToConstant: ProfileHeaderView.avatarImageViewSize.height).priority(.required - 1), ]) avatarImageViewOverlayVisualEffectView.translatesAutoresizingMaskIntoConstraints = false avatarImageViewBackgroundView.addSubview(avatarImageViewOverlayVisualEffectView) - NSLayoutConstraint.activate([ - avatarImageViewOverlayVisualEffectView.topAnchor.constraint(equalTo: avatarImageViewBackgroundView.topAnchor), - avatarImageViewOverlayVisualEffectView.leadingAnchor.constraint(equalTo: avatarImageViewBackgroundView.leadingAnchor), - avatarImageViewOverlayVisualEffectView.trailingAnchor.constraint(equalTo: avatarImageViewBackgroundView.trailingAnchor), - avatarImageViewOverlayVisualEffectView.bottomAnchor.constraint(equalTo: avatarImageViewBackgroundView.bottomAnchor), - ]) + avatarImageViewOverlayVisualEffectView.pinToParent() editAvatarBackgroundView.translatesAutoresizingMaskIntoConstraints = false avatarButton.addSubview(editAvatarBackgroundView) - NSLayoutConstraint.activate([ - editAvatarBackgroundView.topAnchor.constraint(equalTo: avatarButton.topAnchor), - editAvatarBackgroundView.leadingAnchor.constraint(equalTo: avatarButton.leadingAnchor), - editAvatarBackgroundView.trailingAnchor.constraint(equalTo: avatarButton.trailingAnchor), - editAvatarBackgroundView.bottomAnchor.constraint(equalTo: avatarButton.bottomAnchor), - ]) + editAvatarBackgroundView.pinToParent() editAvatarButtonOverlayIndicatorView.translatesAutoresizingMaskIntoConstraints = false editAvatarBackgroundView.addSubview(editAvatarButtonOverlayIndicatorView) - NSLayoutConstraint.activate([ - editAvatarButtonOverlayIndicatorView.topAnchor.constraint(equalTo: editAvatarBackgroundView.topAnchor), - editAvatarButtonOverlayIndicatorView.leadingAnchor.constraint(equalTo: editAvatarBackgroundView.leadingAnchor), - editAvatarButtonOverlayIndicatorView.trailingAnchor.constraint(equalTo: editAvatarBackgroundView.trailingAnchor), - editAvatarButtonOverlayIndicatorView.bottomAnchor.constraint(equalTo: editAvatarBackgroundView.bottomAnchor), - ]) + editAvatarButtonOverlayIndicatorView.pinToParent() editAvatarBackgroundView.isUserInteractionEnabled = true avatarButton.isUserInteractionEnabled = true @@ -436,11 +425,8 @@ extension ProfileHeaderView { relationshipActionButton.translatesAutoresizingMaskIntoConstraints = false relationshipActionButtonShadowContainer.addSubview(relationshipActionButton) + relationshipActionButton.pinToParent() NSLayoutConstraint.activate([ - relationshipActionButton.topAnchor.constraint(equalTo: relationshipActionButtonShadowContainer.topAnchor), - relationshipActionButton.leadingAnchor.constraint(equalTo: relationshipActionButtonShadowContainer.leadingAnchor), - relationshipActionButton.trailingAnchor.constraint(equalTo: relationshipActionButtonShadowContainer.trailingAnchor), - relationshipActionButton.bottomAnchor.constraint(equalTo: relationshipActionButtonShadowContainer.bottomAnchor), relationshipActionButton.widthAnchor.constraint(greaterThanOrEqualToConstant: ProfileHeaderView.friendshipActionButtonSize.width).priority(.required - 1), relationshipActionButton.heightAnchor.constraint(equalToConstant: ProfileHeaderView.friendshipActionButtonSize.height).priority(.defaultHigh), ]) @@ -455,8 +441,7 @@ extension ProfileHeaderView { statusDashboardView.delegate = self bioMetaText.textView.delegate = self bioMetaText.textView.linkDelegate = self - - let bannerImageViewSingleTapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer + bannerImageView.addGestureRecognizer(bannerImageViewSingleTapGestureRecognizer) bannerImageViewSingleTapGestureRecognizer.addTarget(self, action: #selector(ProfileHeaderView.bannerImageViewDidPressed(_:))) @@ -467,6 +452,12 @@ extension ProfileHeaderView { updateLayoutMargins() } + + private func setColors(from theme: Theme) { + backgroundColor = theme.systemBackgroundColor + avatarButton.backgroundColor = theme.secondarySystemBackgroundColor + avatarImageViewBackgroundView.layer.borderColor = theme.systemBackgroundColor.cgColor + } override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) diff --git a/Mastodon/Scene/Profile/Paging/ProfilePagingViewController.swift b/Mastodon/Scene/Profile/Paging/ProfilePagingViewController.swift index db92617b7..27c539d18 100644 --- a/Mastodon/Scene/Profile/Paging/ProfilePagingViewController.swift +++ b/Mastodon/Scene/Profile/Paging/ProfilePagingViewController.swift @@ -99,12 +99,7 @@ extension ProfilePagingViewController { if let buttonBarView = self.buttonBarView { buttonBarShadowView.translatesAutoresizingMaskIntoConstraints = false view.insertSubview(buttonBarShadowView, belowSubview: buttonBarView) - NSLayoutConstraint.activate([ - buttonBarShadowView.topAnchor.constraint(equalTo: buttonBarView.topAnchor), - buttonBarShadowView.leadingAnchor.constraint(equalTo: buttonBarView.leadingAnchor), - buttonBarShadowView.trailingAnchor.constraint(equalTo: buttonBarView.trailingAnchor), - buttonBarShadowView.bottomAnchor.constraint(equalTo: buttonBarView.bottomAnchor), - ]) + buttonBarShadowView.pinTo(to: buttonBarView) viewModel.$needsSetupBottomShadow .receive(on: DispatchQueue.main) diff --git a/Mastodon/Scene/Profile/ProfileViewController.swift b/Mastodon/Scene/Profile/ProfileViewController.swift index d9e56630f..fa10bed9b 100644 --- a/Mastodon/Scene/Profile/ProfileViewController.swift +++ b/Mastodon/Scene/Profile/ProfileViewController.swift @@ -104,6 +104,13 @@ final class ProfileViewController: UIViewController, NeedsDependency, MediaPrevi barButtonItem.accessibilityLabel = L10n.Common.Controls.Actions.seeMore return barButtonItem }() + + private(set) lazy var followedTagsBarButtonItem: UIBarButtonItem = { + let barButtonItem = UIBarButtonItem(image: UIImage(systemName: "number"), style: .plain, target: self, action: #selector(ProfileViewController.followedTagsItemPressed(_:))) + barButtonItem.tintColor = .white + barButtonItem.accessibilityLabel = L10n.Scene.FollowedTags.title + return barButtonItem + }() let refreshControl: RefreshControl = { let refreshControl = RefreshControl() @@ -243,6 +250,11 @@ extension ProfileViewController { items.append(self.shareBarButtonItem) items.append(self.favoriteBarButtonItem) items.append(self.bookmarkBarButtonItem) + + if self.currentInstance?.canFollowTags == true { + items.append(self.followedTagsBarButtonItem) + } + return } @@ -259,12 +271,7 @@ extension ProfileViewController { tabBarPagerController.view.translatesAutoresizingMaskIntoConstraints = false view.addSubview(tabBarPagerController.view) tabBarPagerController.didMove(toParent: self) - NSLayoutConstraint.activate([ - tabBarPagerController.view.topAnchor.constraint(equalTo: view.topAnchor), - tabBarPagerController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), - tabBarPagerController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), - tabBarPagerController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + tabBarPagerController.view.pinToParent() tabBarPagerController.delegate = self tabBarPagerController.dataSource = self @@ -304,6 +311,9 @@ extension ProfileViewController { viewModel.$isUpdating .assign(to: \.isUpdating, on: headerViewModel) .store(in: &disposeBag) + viewModel.relationshipViewModel.$isMyself + .assign(to: \.isMyself, on: headerViewModel) + .store(in: &disposeBag) viewModel.relationshipViewModel.$optionSet .map { $0 ?? .none } .assign(to: \.relationshipActionOptionSet, on: headerViewModel) @@ -503,7 +513,7 @@ extension ProfileViewController { os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) guard let setting = context.settingService.currentSetting.value else { return } let settingsViewModel = SettingsViewModel(context: context, authContext: viewModel.authContext, setting: setting) - coordinator.present(scene: .settings(viewModel: settingsViewModel), from: self, transition: .modal(animated: true, completion: nil)) + _ = coordinator.present(scene: .settings(viewModel: settingsViewModel), from: self, transition: .modal(animated: true, completion: nil)) } @objc private func shareBarButtonItemPressed(_ sender: UIBarButtonItem) { @@ -543,13 +553,23 @@ extension ProfileViewController { @objc private func replyBarButtonItemPressed(_ sender: UIBarButtonItem) { os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) guard let mastodonUser = viewModel.user else { return } + let mention = "@" + mastodonUser.acct + UITextChecker.learnWord(mention) let composeViewModel = ComposeViewModel( context: context, authContext: viewModel.authContext, - kind: .mention(user: mastodonUser.asRecrod) + destination: .topLevel, + initialContent: mention ) _ = coordinator.present(scene: .compose(viewModel: composeViewModel), from: self, transition: .modal(animated: true, completion: nil)) } + + @objc private func followedTagsItemPressed(_ sender: UIBarButtonItem) { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + + let followedTagsViewModel = FollowedTagsViewModel(context: context, authContext: viewModel.authContext) + _ = coordinator.present(scene: .followedTags(viewModel: followedTagsViewModel), from: self, transition: .show) + } @objc private func refreshControlValueChanged(_ sender: RefreshControl) { os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) @@ -890,7 +910,7 @@ extension ProfileViewController: MastodonMenuDelegate { // MARK: - ScrollViewContainer extension ProfileViewController: ScrollViewContainer { var scrollView: UIScrollView { - return tabBarPagerController.containerScrollView + return tabBarPagerController.relayScrollView } } @@ -919,3 +939,13 @@ extension ProfileViewController: PagerTabStripNavigateable { } +private extension ProfileViewController { + var currentInstance: Instance? { + guard let authenticationRecord = authContext.mastodonAuthenticationBox + .authenticationRecord + .object(in: context.managedObjectContext) + else { return nil } + + return authenticationRecord.instance + } +} diff --git a/Mastodon/Scene/Profile/ProfileViewModel.swift b/Mastodon/Scene/Profile/ProfileViewModel.swift index e23b465d4..5c81c7920 100644 --- a/Mastodon/Scene/Profile/ProfileViewModel.swift +++ b/Mastodon/Scene/Profile/ProfileViewModel.swift @@ -215,8 +215,17 @@ extension ProfileViewModel { let authenticationBox = authContext.mastodonAuthenticationBox let domain = authenticationBox.domain let authorization = authenticationBox.userAuthorization - - let _image: UIImage? = { + + // TODO: constrain size? + let _header: UIImage? = { + guard let image = headerProfileInfo.header else { return nil } + guard image.size.width <= ProfileHeaderViewModel.bannerImageMaxSizeInPixel.width else { + return image.af.imageScaled(to: ProfileHeaderViewModel.bannerImageMaxSizeInPixel) + } + return image + }() + + let _avatar: UIImage? = { guard let image = headerProfileInfo.avatar else { return nil } guard image.size.width <= ProfileHeaderViewModel.avatarImageMaxSizeInPixel.width else { return image.af.imageScaled(to: ProfileHeaderViewModel.avatarImageMaxSizeInPixel) @@ -233,8 +242,8 @@ extension ProfileViewModel { bot: nil, displayName: headerProfileInfo.name, note: headerProfileInfo.note, - avatar: _image.flatMap { Mastodon.Query.MediaAttachment.png($0.pngData()) }, - header: nil, + avatar: _avatar.flatMap { Mastodon.Query.MediaAttachment.png($0.pngData()) }, + header: _header.flatMap { Mastodon.Query.MediaAttachment.png($0.pngData()) }, locked: nil, source: nil, fieldsAttributes: fieldsAttributes diff --git a/Mastodon/Scene/Profile/Timeline/UserTimelineViewController.swift b/Mastodon/Scene/Profile/Timeline/UserTimelineViewController.swift index 8a983da33..6cdd6a594 100644 --- a/Mastodon/Scene/Profile/Timeline/UserTimelineViewController.swift +++ b/Mastodon/Scene/Profile/Timeline/UserTimelineViewController.swift @@ -60,12 +60,7 @@ extension UserTimelineViewController { tableView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(tableView) - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: view.topAnchor), - tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + tableView.pinToParent() tableView.delegate = self viewModel.setupDiffableDataSource( diff --git a/Mastodon/Scene/Profile/Timeline/UserTimelineViewModel+Diffable.swift b/Mastodon/Scene/Profile/Timeline/UserTimelineViewModel+Diffable.swift index 863d7b44e..67f2b8035 100644 --- a/Mastodon/Scene/Profile/Timeline/UserTimelineViewModel+Diffable.swift +++ b/Mastodon/Scene/Profile/Timeline/UserTimelineViewModel+Diffable.swift @@ -18,6 +18,7 @@ extension UserTimelineViewModel { tableView: tableView, context: context, configuration: StatusSection.Configuration( + context: context, authContext: authContext, statusTableViewCellDelegate: statusTableViewCellDelegate, timelineMiddleLoaderTableViewCellDelegate: nil, @@ -82,7 +83,7 @@ extension UserTimelineViewModel { } } - diffableDataSource.applySnapshot(snapshot, animated: false) + diffableDataSource.apply(snapshot, animatingDifferences: false) } .store(in: &disposeBag) } diff --git a/Mastodon/Scene/Profile/UserLIst/FavoritedBy/FavoritedByViewController.swift b/Mastodon/Scene/Profile/UserLIst/FavoritedBy/FavoritedByViewController.swift index ebce374e7..07316cd3d 100644 --- a/Mastodon/Scene/Profile/UserLIst/FavoritedBy/FavoritedByViewController.swift +++ b/Mastodon/Scene/Profile/UserLIst/FavoritedBy/FavoritedByViewController.swift @@ -61,12 +61,7 @@ extension FavoritedByViewController { tableView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(tableView) - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: view.topAnchor), - tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + tableView.pinToParent() tableView.delegate = self viewModel.setupDiffableDataSource( diff --git a/Mastodon/Scene/Profile/UserLIst/RebloggedBy/RebloggedByViewController.swift b/Mastodon/Scene/Profile/UserLIst/RebloggedBy/RebloggedByViewController.swift index 0688bcccb..a45c491fc 100644 --- a/Mastodon/Scene/Profile/UserLIst/RebloggedBy/RebloggedByViewController.swift +++ b/Mastodon/Scene/Profile/UserLIst/RebloggedBy/RebloggedByViewController.swift @@ -61,12 +61,7 @@ extension RebloggedByViewController { tableView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(tableView) - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: view.topAnchor), - tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + tableView.pinToParent() tableView.delegate = self viewModel.setupDiffableDataSource( diff --git a/Mastodon/Scene/Profile/UserLIst/UserListViewModel+Diffable.swift b/Mastodon/Scene/Profile/UserLIst/UserListViewModel+Diffable.swift index acd225f0c..e843dd005 100644 --- a/Mastodon/Scene/Profile/UserLIst/UserListViewModel+Diffable.swift +++ b/Mastodon/Scene/Profile/UserLIst/UserListViewModel+Diffable.swift @@ -28,13 +28,8 @@ extension UserListViewModel { var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([.main]) snapshot.appendItems([.bottomLoader], toSection: .main) - if #available(iOS 15.0, *) { - diffableDataSource?.applySnapshotUsingReloadData(snapshot, completion: nil) - } else { - // Fallback on earlier versions - diffableDataSource?.apply(snapshot, animatingDifferences: false) - } - + diffableDataSource?.applySnapshotUsingReloadData(snapshot) + // trigger initial loading stateMachine.enter(UserListViewModel.State.Reloading.self) diff --git a/Mastodon/Scene/Profile/UserLIst/UserListViewModel+State.swift b/Mastodon/Scene/Profile/UserLIst/UserListViewModel+State.swift index c7b3e20cd..d0f7feb88 100644 --- a/Mastodon/Scene/Profile/UserLIst/UserListViewModel+State.swift +++ b/Mastodon/Scene/Profile/UserLIst/UserListViewModel+State.swift @@ -134,7 +134,7 @@ extension UserListViewModel.State { maxID = nil } - guard let viewModel = viewModel, let stateMachine = stateMachine else { return } + guard let viewModel = viewModel else { return } let maxID = self.maxID diff --git a/Mastodon/Scene/Report/Report/ReportViewController.swift b/Mastodon/Scene/Report/Report/ReportViewController.swift index f1418c5a1..fb0417a7f 100644 --- a/Mastodon/Scene/Report/Report/ReportViewController.swift +++ b/Mastodon/Scene/Report/Report/ReportViewController.swift @@ -61,12 +61,7 @@ extension ReportViewController { reportReasonViewController.view.translatesAutoresizingMaskIntoConstraints = false view.addSubview(reportReasonViewController.view) reportReasonViewController.didMove(toParent: self) - NSLayoutConstraint.activate([ - reportReasonViewController.view.topAnchor.constraint(equalTo: view.topAnchor), - reportReasonViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), - reportReasonViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), - reportReasonViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + reportReasonViewController.view.pinToParent() } } @@ -126,7 +121,7 @@ extension ReportViewController: ReportServerRulesViewControllerDelegate { return } - coordinator.present( + _ = coordinator.present( scene: .reportStatus(viewModel: viewModel.reportStatusViewModel), from: self, transition: .show diff --git a/Mastodon/Scene/Report/ReportReason/ReportReasonViewController.swift b/Mastodon/Scene/Report/ReportReason/ReportReasonViewController.swift index 2e8e53d18..517873abe 100644 --- a/Mastodon/Scene/Report/ReportReason/ReportReasonViewController.swift +++ b/Mastodon/Scene/Report/ReportReason/ReportReasonViewController.swift @@ -57,12 +57,7 @@ extension ReportReasonViewController { addChild(hostingViewController) hostingViewController.view.translatesAutoresizingMaskIntoConstraints = false view.addSubview(hostingViewController.view) - NSLayoutConstraint.activate([ - hostingViewController.view.topAnchor.constraint(equalTo: view.topAnchor), - hostingViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), - hostingViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), - hostingViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + hostingViewController.view.pinToParent() navigationActionView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(navigationActionView) diff --git a/Mastodon/Scene/Report/ReportResult/ReportResultViewController.swift b/Mastodon/Scene/Report/ReportResult/ReportResultViewController.swift index 10dcdf373..1d67ad6c3 100644 --- a/Mastodon/Scene/Report/ReportResult/ReportResultViewController.swift +++ b/Mastodon/Scene/Report/ReportResult/ReportResultViewController.swift @@ -60,12 +60,7 @@ extension ReportResultViewController { addChild(hostingViewController) hostingViewController.view.translatesAutoresizingMaskIntoConstraints = false view.addSubview(hostingViewController.view) - NSLayoutConstraint.activate([ - hostingViewController.view.topAnchor.constraint(equalTo: view.topAnchor), - hostingViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), - hostingViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), - hostingViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + hostingViewController.view.pinToParent() navigationActionView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(navigationActionView) diff --git a/Mastodon/Scene/Report/ReportServerRules/ReportServerRulesViewController.swift b/Mastodon/Scene/Report/ReportServerRules/ReportServerRulesViewController.swift index 3f1cdf331..00be4d800 100644 --- a/Mastodon/Scene/Report/ReportServerRules/ReportServerRulesViewController.swift +++ b/Mastodon/Scene/Report/ReportServerRules/ReportServerRulesViewController.swift @@ -63,12 +63,7 @@ extension ReportServerRulesViewController { addChild(hostingViewController) hostingViewController.view.translatesAutoresizingMaskIntoConstraints = false view.addSubview(hostingViewController.view) - NSLayoutConstraint.activate([ - hostingViewController.view.topAnchor.constraint(equalTo: view.topAnchor), - hostingViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), - hostingViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), - hostingViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + hostingViewController.view.pinToParent() navigationActionView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(navigationActionView) diff --git a/Mastodon/Scene/Report/ReportStatus/ReportStatusViewController.swift b/Mastodon/Scene/Report/ReportStatus/ReportStatusViewController.swift index d45c196cd..3385a12ef 100644 --- a/Mastodon/Scene/Report/ReportStatus/ReportStatusViewController.swift +++ b/Mastodon/Scene/Report/ReportStatus/ReportStatusViewController.swift @@ -47,11 +47,7 @@ class ReportStatusViewController: UIViewController, NeedsDependency, ReportViewC tableView.backgroundColor = .clear tableView.keyboardDismissMode = .onDrag tableView.allowsMultipleSelection = true - if #available(iOS 15.0, *) { - tableView.sectionHeaderTopPadding = .leastNonzeroMagnitude - } else { - // Fallback on earlier versions - } + tableView.sectionHeaderTopPadding = .leastNonzeroMagnitude return tableView }() @@ -80,12 +76,7 @@ extension ReportStatusViewController { tableView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(tableView) - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: view.topAnchor), - tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + tableView.pinToParent() tableView.delegate = self viewModel.setupDiffableDataSource( @@ -128,6 +119,10 @@ extension ReportStatusViewController { .assign(to: \.isEnabled, on: navigationActionView.nextButton) .store(in: &disposeBag) + if !viewModel.selectStatuses.isEmpty { + navigationActionView.hidesBackButton = true + } + navigationActionView.backButton.addTarget(self, action: #selector(ReportStatusViewController.skipButtonDidPressed(_:)), for: .touchUpInside) navigationActionView.nextButton.addTarget(self, action: #selector(ReportStatusViewController.nextButtonDidPressed(_:)), for: .touchUpInside) } diff --git a/Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel+Diffable.swift b/Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel+Diffable.swift index 9879863d6..fce56a2b9 100644 --- a/Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel+Diffable.swift +++ b/Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel+Diffable.swift @@ -65,7 +65,7 @@ extension ReportStatusViewModel { break } - diffableDataSource.applySnapshot(snapshot, animated: false) { [weak self] in + diffableDataSource.apply(snapshot, animatingDifferences: false) { [weak self] in guard let self = self else { return } guard let diffableDataSource = self.diffableDataSource else { return } diff --git a/Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel+State.swift b/Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel+State.swift index 01e8715d1..79807cf0f 100644 --- a/Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel+State.swift +++ b/Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel+State.swift @@ -75,7 +75,7 @@ extension ReportStatusViewModel.State { override func didEnter(from previousState: GKState?) { super.didEnter(from: previousState) - guard let viewModel = viewModel, let stateMachine = stateMachine else { return } + guard let viewModel else { return } let maxID = viewModel.statusFetchedResultsController.statusIDs.last diff --git a/Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel.swift b/Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel.swift index 5b80a9f3a..c1c79af48 100644 --- a/Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel.swift +++ b/Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel.swift @@ -71,9 +71,7 @@ class ReportStatusViewModel { } $selectStatuses - .map { statuses -> Bool in - return status == nil ? !statuses.isEmpty : statuses.count > 1 - } + .map { !$0.isEmpty } .assign(to: &$isNextButtonEnabled) } diff --git a/Mastodon/Scene/Report/ReportSupplementary/ReportSupplementaryViewController.swift b/Mastodon/Scene/Report/ReportSupplementary/ReportSupplementaryViewController.swift index e644c29ea..a84a3a650 100644 --- a/Mastodon/Scene/Report/ReportSupplementary/ReportSupplementaryViewController.swift +++ b/Mastodon/Scene/Report/ReportSupplementary/ReportSupplementaryViewController.swift @@ -52,11 +52,7 @@ final class ReportSupplementaryViewController: UIViewController, NeedsDependency tableView.separatorStyle = .none tableView.backgroundColor = .clear tableView.keyboardDismissMode = .onDrag - if #available(iOS 15.0, *) { - tableView.sectionHeaderTopPadding = .leastNonzeroMagnitude - } else { - // Fallback on earlier versions - } + tableView.sectionHeaderTopPadding = .leastNonzeroMagnitude return tableView }() @@ -93,12 +89,7 @@ extension ReportSupplementaryViewController { tableView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(tableView) - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: view.topAnchor), - tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + tableView.pinToParent() tableView.delegate = self viewModel.setupDiffableDataSource( diff --git a/Mastodon/Scene/Report/Share/Cell/ReportCommentTableViewCell.swift b/Mastodon/Scene/Report/Share/Cell/ReportCommentTableViewCell.swift index d735a094c..ad6ce252a 100644 --- a/Mastodon/Scene/Report/Share/Cell/ReportCommentTableViewCell.swift +++ b/Mastodon/Scene/Report/Share/Cell/ReportCommentTableViewCell.swift @@ -74,11 +74,8 @@ extension ReportCommentTableViewCell { commentTextView.translatesAutoresizingMaskIntoConstraints = false commentTextViewShadowBackgroundContainer.addSubview(commentTextView) + commentTextView.pinToParent() NSLayoutConstraint.activate([ - commentTextView.topAnchor.constraint(equalTo: commentTextViewShadowBackgroundContainer.topAnchor), - commentTextView.leadingAnchor.constraint(equalTo: commentTextViewShadowBackgroundContainer.leadingAnchor), - commentTextView.trailingAnchor.constraint(equalTo: commentTextViewShadowBackgroundContainer.trailingAnchor), - commentTextView.bottomAnchor.constraint(equalTo: commentTextViewShadowBackgroundContainer.bottomAnchor), commentTextView.heightAnchor.constraint(greaterThanOrEqualToConstant: 100).priority(.defaultHigh), ]) } diff --git a/Mastodon/Scene/Report/Share/Cell/ReportResultActionTableViewCell.swift b/Mastodon/Scene/Report/Share/Cell/ReportResultActionTableViewCell.swift index 9b605a0c7..1828035a6 100644 --- a/Mastodon/Scene/Report/Share/Cell/ReportResultActionTableViewCell.swift +++ b/Mastodon/Scene/Report/Share/Cell/ReportResultActionTableViewCell.swift @@ -103,12 +103,7 @@ extension ReportResultActionTableViewCell { reportBannerLabel.translatesAutoresizingMaskIntoConstraints = false reportBannerShadowContainer.addSubview(reportBannerLabel) - NSLayoutConstraint.activate([ - reportBannerLabel.topAnchor.constraint(equalTo: reportBannerShadowContainer.topAnchor), - reportBannerLabel.leadingAnchor.constraint(equalTo: reportBannerShadowContainer.leadingAnchor), - reportBannerLabel.trailingAnchor.constraint(equalTo: reportBannerShadowContainer.trailingAnchor), - reportBannerLabel.bottomAnchor.constraint(equalTo: reportBannerShadowContainer.bottomAnchor), - ]) + reportBannerLabel.pinToParent() } diff --git a/Mastodon/Scene/Report/Share/ReportViewControllerAppearance.swift b/Mastodon/Scene/Report/Share/ReportViewControllerAppearance.swift index fb9bcd63f..34d59a437 100644 --- a/Mastodon/Scene/Report/Share/ReportViewControllerAppearance.swift +++ b/Mastodon/Scene/Report/Share/ReportViewControllerAppearance.swift @@ -42,11 +42,7 @@ extension ReportViewControllerAppearance { navigationItem.standardAppearance = barAppearance navigationItem.compactAppearance = barAppearance navigationItem.scrollEdgeAppearance = barAppearance - if #available(iOS 15.0, *) { - navigationItem.compactScrollEdgeAppearance = barAppearance - } else { - // Fallback on earlier versions - } + navigationItem.compactScrollEdgeAppearance = barAppearance } func setupNavigationBarBackgroundView() { diff --git a/Mastodon/Scene/Root/ContentSplitViewController.swift b/Mastodon/Scene/Root/ContentSplitViewController.swift index 3f4758e8e..a10f0ed9b 100644 --- a/Mastodon/Scene/Root/ContentSplitViewController.swift +++ b/Mastodon/Scene/Root/ContentSplitViewController.swift @@ -124,4 +124,17 @@ extension ContentSplitViewController: SidebarViewControllerDelegate { accountListViewController.preferredContentSize = CGSize(width: 375, height: 400) } + func sidebarViewController(_ sidebarViewController: SidebarViewController, didDoubleTapItem item: SidebarViewModel.Item, sourceView: UIView) { + guard case let .tab(tab) = item, tab == .me else { return } + guard let authContext = authContext else { return } + assert(Thread.isMainThread) + + guard let nextAccount = context.nextAccount(in: authContext) else { return } + + Task { @MainActor in + let isActive = try await context.authenticationService.activeMastodonUser(domain: nextAccount.domain, userID: nextAccount.userID) + guard isActive else { return } + self.coordinator.setup() + } + } } diff --git a/Mastodon/Scene/Root/MainTab/MainTabBarController.swift b/Mastodon/Scene/Root/MainTab/MainTabBarController.swift index 987c1141b..b0af9b6d6 100644 --- a/Mastodon/Scene/Root/MainTab/MainTabBarController.swift +++ b/Mastodon/Scene/Root/MainTab/MainTabBarController.swift @@ -8,6 +8,7 @@ import os.log import UIKit import Combine +import CoreDataStack import SafariServices import MastodonAsset import MastodonCore @@ -42,6 +43,7 @@ class MainTabBarController: UITabBarController { static let avatarButtonSize = CGSize(width: 25, height: 25) let avatarButton = CircleAvatarButton() + let accountSwitcherChevron = UIImageView(image: .chevronUpChevronDown) @Published var currentTab: Tab = .home @@ -49,7 +51,7 @@ class MainTabBarController: UITabBarController { case home case search case compose - case notification + case notifications case me var tag: Int { @@ -59,9 +61,9 @@ class MainTabBarController: UITabBarController { var title: String { switch self { case .home: return L10n.Common.Controls.Tabs.home - case .search: return L10n.Common.Controls.Tabs.search + case .search: return L10n.Common.Controls.Tabs.searchAndExplore case .compose: return L10n.Common.Controls.Actions.compose - case .notification: return L10n.Common.Controls.Tabs.notification + case .notifications: return L10n.Common.Controls.Tabs.notifications case .me: return L10n.Common.Controls.Tabs.profile } } @@ -71,7 +73,7 @@ class MainTabBarController: UITabBarController { case .home: return Asset.ObjectsAndTools.house.image.withRenderingMode(.alwaysTemplate) case .search: return Asset.ObjectsAndTools.magnifyingglass.image.withRenderingMode(.alwaysTemplate) case .compose: return Asset.ObjectsAndTools.squareAndPencil.image.withRenderingMode(.alwaysTemplate) - case .notification: return Asset.ObjectsAndTools.bell.image.withRenderingMode(.alwaysTemplate) + case .notifications: return Asset.ObjectsAndTools.bell.image.withRenderingMode(.alwaysTemplate) case .me: return UIImage(systemName: "person")! } } @@ -81,7 +83,7 @@ class MainTabBarController: UITabBarController { case .home: return Asset.ObjectsAndTools.houseFill.image.withRenderingMode(.alwaysTemplate) case .search: return Asset.ObjectsAndTools.magnifyingglassFill.image.withRenderingMode(.alwaysTemplate) case .compose: return Asset.ObjectsAndTools.squareAndPencil.image.withRenderingMode(.alwaysTemplate) - case .notification: return Asset.ObjectsAndTools.bellFill.image.withRenderingMode(.alwaysTemplate) + case .notifications: return Asset.ObjectsAndTools.bellFill.image.withRenderingMode(.alwaysTemplate) case .me: return UIImage(systemName: "person.fill")! } } @@ -91,7 +93,7 @@ class MainTabBarController: UITabBarController { case .home: return Asset.ObjectsAndTools.house.image.withRenderingMode(.alwaysTemplate).resized(size: CGSize(width: 80, height: 80)) case .search: return Asset.ObjectsAndTools.magnifyingglass.image.withRenderingMode(.alwaysTemplate).resized(size: CGSize(width: 80, height: 80)) case .compose: return Asset.ObjectsAndTools.squareAndPencil.image.withRenderingMode(.alwaysTemplate).resized(size: CGSize(width: 80, height: 80)) - case .notification: return Asset.ObjectsAndTools.bell.image.withRenderingMode(.alwaysTemplate).resized(size: CGSize(width: 80, height: 80)) + case .notifications: return Asset.ObjectsAndTools.bell.image.withRenderingMode(.alwaysTemplate).resized(size: CGSize(width: 80, height: 80)) case .me: return UIImage(systemName: "person", withConfiguration: UIImage.SymbolConfiguration(pointSize: 80))! } } @@ -101,7 +103,7 @@ class MainTabBarController: UITabBarController { case .home: return Asset.ObjectsAndTools.house.image.withRenderingMode(.alwaysTemplate) case .search: return Asset.ObjectsAndTools.magnifyingglass.image.withRenderingMode(.alwaysTemplate) case .compose: return Asset.ObjectsAndTools.squareAndPencil.image.withRenderingMode(.alwaysTemplate) - case .notification: return Asset.ObjectsAndTools.bell.image.withRenderingMode(.alwaysTemplate) + case .notifications: return Asset.ObjectsAndTools.bell.image.withRenderingMode(.alwaysTemplate) case .me: return UIImage(systemName: "person")! } } @@ -127,7 +129,7 @@ class MainTabBarController: UITabBarController { viewController = _viewController case .compose: viewController = UIViewController() - case .notification: + case .notifications: let _viewController = NotificationViewController() _viewController.context = context _viewController.coordinator = coordinator @@ -218,7 +220,7 @@ extension MainTabBarController { let alertController = UIAlertController(for: error, title: nil, preferredStyle: .alert) let okAction = UIAlertAction(title: "OK", style: .default, handler: nil) alertController.addAction(okAction) - coordinator.present( + _ = coordinator.present( scene: .alertController(alertController: alertController), from: nil, transition: .alertController(animated: true, completion: nil) @@ -272,7 +274,7 @@ extension MainTabBarController { } ?? false let image: UIImage = { - if currentTab == .notification { + if currentTab == .notifications { return hasUnreadPushNotification ? Asset.ObjectsAndTools.bellBadgeFill.image.withRenderingMode(.alwaysTemplate) : Asset.ObjectsAndTools.bellFill.image.withRenderingMode(.alwaysTemplate) } else { return hasUnreadPushNotification ? Asset.ObjectsAndTools.bellBadge.image.withRenderingMode(.alwaysTemplate) : Asset.ObjectsAndTools.bell.image.withRenderingMode(.alwaysTemplate) @@ -306,12 +308,11 @@ extension MainTabBarController { guard user.managedObjectContext != nil else { return } self.avatarURL = user.avatarImageURL() } - + // a11y let _profileTabItem = self.tabBar.items?.first { item in item.tag == Tab.me.tag } guard let profileTabItem = _profileTabItem else { return } - let currentUserDisplayName = user.displayNameWithFallback ?? "no user" - profileTabItem.accessibilityHint = L10n.Scene.AccountList.tabBarHint(currentUserDisplayName) + profileTabItem.accessibilityHint = L10n.Scene.AccountList.tabBarHint(user.displayNameWithFallback) context.authenticationService.updateActiveUserAccountPublisher .sink { [weak self] in @@ -325,7 +326,14 @@ extension MainTabBarController { let tabBarLongPressGestureRecognizer = UILongPressGestureRecognizer() tabBarLongPressGestureRecognizer.addTarget(self, action: #selector(MainTabBarController.tabBarLongPressGestureRecognizerHandler(_:))) tabBar.addGestureRecognizer(tabBarLongPressGestureRecognizer) - + + // todo: reconsider the "double tap to change account" feature -> https://github.com/mastodon/mastodon-ios/issues/628 +// let tabBarDoubleTapGestureRecognizer = UITapGestureRecognizer() +// tabBarDoubleTapGestureRecognizer.numberOfTapsRequired = 2 +// tabBarDoubleTapGestureRecognizer.addTarget(self, action: #selector(MainTabBarController.tabBarDoubleTapGestureRecognizerHandler(_:))) +// tabBarDoubleTapGestureRecognizer.delaysTouchesEnded = false +// tabBar.addGestureRecognizer(tabBarDoubleTapGestureRecognizer) + self.isReadyForWizardAvatarButton = authContext != nil $currentTab @@ -371,14 +379,12 @@ extension MainTabBarController { let composeViewModel = ComposeViewModel( context: context, authContext: authContext, - kind: .post + destination: .topLevel ) _ = coordinator.present(scene: .compose(viewModel: composeViewModel), from: nil, transition: .modal(animated: true, completion: nil)) } - @objc private func tabBarLongPressGestureRecognizerHandler(_ sender: UILongPressGestureRecognizer) { - guard sender.state == .began else { return } - + private func touchedTab(by sender: UIGestureRecognizer) -> Tab? { var _tab: Tab? let location = sender.location(in: tabBar) for item in tabBar.items ?? [] { @@ -390,7 +396,34 @@ extension MainTabBarController { break } - guard let tab = _tab else { return } + return _tab + } + + @objc private func tabBarDoubleTapGestureRecognizerHandler(_ sender: UITapGestureRecognizer) { + guard sender.state == .ended else { return } + guard let tab = touchedTab(by: sender) else { return } + logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): double tap \(tab.title) tab") + + switch tab { + case .me: + guard let authContext = authContext else { return } + assert(Thread.isMainThread) + + guard let nextAccount = context.nextAccount(in: authContext) else { return } + + Task { @MainActor in + let isActive = try await context.authenticationService.activeMastodonUser(domain: nextAccount.domain, userID: nextAccount.userID) + guard isActive else { return } + self.coordinator.setup() + } + default: + break + } + } + + @objc private func tabBarLongPressGestureRecognizerHandler(_ sender: UILongPressGestureRecognizer) { + guard sender.state == .began else { return } + guard let tab = touchedTab(by: sender) else { return } logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): long press \(tab.title) tab") switch tab { @@ -443,12 +476,7 @@ extension MainTabBarController { composeButton.translatesAutoresizingMaskIntoConstraints = false composeButttonShadowBackgroundContainer.addSubview(composeButton) - NSLayoutConstraint.activate([ - composeButton.topAnchor.constraint(equalTo: composeButttonShadowBackgroundContainer.topAnchor), - composeButton.leadingAnchor.constraint(equalTo: composeButttonShadowBackgroundContainer.leadingAnchor), - composeButton.trailingAnchor.constraint(equalTo: composeButttonShadowBackgroundContainer.trailingAnchor), - composeButton.bottomAnchor.constraint(equalTo: composeButttonShadowBackgroundContainer.bottomAnchor), - ]) + composeButton.pinToParent() composeButton.setContentHuggingPriority(.required - 1, for: .horizontal) composeButton.setContentHuggingPriority(.required - 1, for: .vertical) } @@ -474,13 +502,20 @@ extension MainTabBarController { } anchorImageView.alpha = 0 + accountSwitcherChevron.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(accountSwitcherChevron) + self.avatarButton.translatesAutoresizingMaskIntoConstraints = false view.addSubview(self.avatarButton) NSLayoutConstraint.activate([ - self.avatarButton.centerXAnchor.constraint(equalTo: anchorImageView.centerXAnchor), + self.avatarButton.centerXAnchor.constraint(equalTo: anchorImageView.centerXAnchor, constant: -16), self.avatarButton.centerYAnchor.constraint(equalTo: anchorImageView.centerYAnchor), self.avatarButton.widthAnchor.constraint(equalToConstant: MainTabBarController.avatarButtonSize.width).priority(.required - 1), self.avatarButton.heightAnchor.constraint(equalToConstant: MainTabBarController.avatarButtonSize.height).priority(.required - 1), + accountSwitcherChevron.widthAnchor.constraint(equalToConstant: 10), + accountSwitcherChevron.heightAnchor.constraint(equalToConstant: 18), + accountSwitcherChevron.leadingAnchor.constraint(equalTo: avatarButton.trailingAnchor, constant: 8), + accountSwitcherChevron.centerYAnchor.constraint(equalTo: avatarButton.centerYAnchor) ]) self.avatarButton.setContentHuggingPriority(.required - 1, for: .horizontal) self.avatarButton.setContentHuggingPriority(.required - 1, for: .vertical) @@ -488,6 +523,7 @@ extension MainTabBarController { } private func updateAvatarButtonAppearance() { + accountSwitcherChevron.tintColor = currentTab == .me ? .label : .secondaryLabel avatarButton.borderColor = currentTab == .me ? .label : .systemFill avatarButton.setNeedsLayout() } @@ -533,25 +569,24 @@ extension MainTabBarController: UITabBarControllerDelegate { composeButtonDidPressed(tabBarController) return false } + + // Assert index is as same as the tab rawValue. This check needs to be done `shouldSelect` + // because the nav controller has already popped in `didSelect`. + if currentTab.rawValue == tabBarController.selectedIndex, + let navigationController = viewController as? UINavigationController, + navigationController.viewControllers.count == 1, + let scrollViewContainer = navigationController.topViewController as? ScrollViewContainer { + scrollViewContainer.scrollToTop(animated: true) + } + return true } func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) { os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: select %s", ((#file as NSString).lastPathComponent), #line, #function, viewController.debugDescription) - defer { - if let tab = Tab(rawValue: viewController.tabBarItem.tag) { - currentTab = tab - } + if let tab = Tab(rawValue: viewController.tabBarItem.tag) { + currentTab = tab } - // assert index is as same as the tab rawValue - guard currentTab.rawValue == tabBarController.selectedIndex, - let navigationController = viewController as? UINavigationController, - navigationController.viewControllers.count == 1, - let scrollViewContainer = navigationController.topViewController as? ScrollViewContainer else { - return - } - - scrollViewContainer.scrollToTop(animated: true) } } @@ -618,7 +653,7 @@ extension MainTabBarController { let tabs: [Tab] = [ .home, .search, - .notification, + .notifications, .me ] for (i, tab) in tabs.enumerated() { @@ -768,7 +803,7 @@ extension MainTabBarController { let composeViewModel = ComposeViewModel( context: context, authContext: authContext, - kind: .post + destination: .topLevel ) _ = coordinator.present(scene: .compose(viewModel: composeViewModel), from: nil, transition: .modal(animated: true, completion: nil)) } diff --git a/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift b/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift index 70e1239b6..0ffcd4c8b 100644 --- a/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift +++ b/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift @@ -15,6 +15,7 @@ import MastodonUI protocol SidebarViewControllerDelegate: AnyObject { func sidebarViewController(_ sidebarViewController: SidebarViewController, didSelectTab tab: MainTabBarController.Tab) func sidebarViewController(_ sidebarViewController: SidebarViewController, didLongPressItem item: SidebarViewModel.Item, sourceView: UIView) + func sidebarViewController(_ sidebarViewController: SidebarViewController, didDoubleTapItem item: SidebarViewModel.Item, sourceView: UIView) } final class SidebarViewController: UIViewController, NeedsDependency { @@ -101,12 +102,7 @@ extension SidebarViewController { collectionView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(collectionView) - NSLayoutConstraint.activate([ - collectionView.topAnchor.constraint(equalTo: view.topAnchor), - collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + collectionView.pinToParent() secondaryCollectionView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(secondaryCollectionView) @@ -143,6 +139,15 @@ extension SidebarViewController { let sidebarLongPressGestureRecognizer = UILongPressGestureRecognizer() sidebarLongPressGestureRecognizer.addTarget(self, action: #selector(SidebarViewController.sidebarLongPressGestureRecognizerHandler(_:))) collectionView.addGestureRecognizer(sidebarLongPressGestureRecognizer) + + // todo: reconsider the "double tap to change account" feature -> https://github.com/mastodon/mastodon-ios/issues/628 +// let sidebarDoubleTapGestureRecognizer = UITapGestureRecognizer() +// sidebarDoubleTapGestureRecognizer.numberOfTapsRequired = 2 +// sidebarDoubleTapGestureRecognizer.addTarget(self, action: #selector(SidebarViewController.sidebarDoubleTapGestureRecognizerHandler(_:))) +// sidebarDoubleTapGestureRecognizer.delaysTouchesEnded = false +// sidebarDoubleTapGestureRecognizer.cancelsTouchesInView = true +// collectionView.addGestureRecognizer(sidebarDoubleTapGestureRecognizer) + } private func setupBackground(theme: Theme) { @@ -176,6 +181,20 @@ extension SidebarViewController { guard let cell = collectionView.cellForItem(at: indexPath) else { return } delegate?.sidebarViewController(self, didLongPressItem: item, sourceView: cell) } + + @objc private func sidebarDoubleTapGestureRecognizerHandler(_ sender: UITapGestureRecognizer) { + guard sender.state == .ended else { return } + + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") + assert(sender.view === collectionView) + + let position = sender.location(in: collectionView) + guard let indexPath = collectionView.indexPathForItem(at: position) else { return } + guard let diffableDataSource = viewModel.diffableDataSource else { return } + guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return } + guard let cell = collectionView.cellForItem(at: indexPath) else { return } + delegate?.sidebarViewController(self, didDoubleTapItem: item, sourceView: cell) + } } @@ -208,7 +227,7 @@ extension SidebarViewController: UICollectionViewDelegate { let composeViewModel = ComposeViewModel( context: context, authContext: authContext, - kind: .post + destination: .topLevel ) _ = coordinator.present(scene: .compose(viewModel: composeViewModel), from: self, transition: .modal(animated: true, completion: nil)) default: diff --git a/Mastodon/Scene/Root/Sidebar/SidebarViewModel.swift b/Mastodon/Scene/Root/Sidebar/SidebarViewModel.swift index c3f9e3e36..f6e90dcd9 100644 --- a/Mastodon/Scene/Root/Sidebar/SidebarViewModel.swift +++ b/Mastodon/Scene/Root/Sidebar/SidebarViewModel.swift @@ -16,7 +16,6 @@ import MastodonCore import MastodonLocalization final class SidebarViewModel { - var disposeBag = Set() // input @@ -80,6 +79,7 @@ extension SidebarViewModel { }() cell.item = SidebarListContentView.Item( isActive: false, + accessoryImage: item == .me ? .chevronUpChevronDown : nil, title: item.title, image: item.image, activeImage: item.selectedImage, @@ -100,7 +100,7 @@ extension SidebarViewModel { .store(in: &cell.disposeBag) switch item { - case .notification: + case .notifications: Publishers.CombineLatest( self.context.notificationService.unreadNotificationCountDidUpdate, self.$currentTab @@ -116,7 +116,7 @@ extension SidebarViewModel { }() let image: UIImage = { - if currentTab == .notification { + if currentTab == .notifications { return hasUnreadPushNotification ? Asset.ObjectsAndTools.bellBadgeFill.image.withRenderingMode(.alwaysTemplate) : Asset.ObjectsAndTools.bellFill.image.withRenderingMode(.alwaysTemplate) } else { return hasUnreadPushNotification ? Asset.ObjectsAndTools.bellBadge.image.withRenderingMode(.alwaysTemplate) : Asset.ObjectsAndTools.bell.image.withRenderingMode(.alwaysTemplate) @@ -166,6 +166,7 @@ extension SidebarViewModel { case .compose: let item = SidebarListContentView.Item( isActive: false, + accessoryImage: self.currentTab == .me ? .chevronUpChevronDown : nil, title: L10n.Common.Controls.Actions.compose, image: Asset.ObjectsAndTools.squareAndPencil.image.withRenderingMode(.alwaysTemplate), activeImage: Asset.ObjectsAndTools.squareAndPencil.image.withRenderingMode(.alwaysTemplate), @@ -192,7 +193,7 @@ extension SidebarViewModel { let items: [Item] = [ .tab(.home), .tab(.search), - .tab(.notification), + .tab(.notifications), .tab(.me), .setting, ] diff --git a/Mastodon/Scene/Root/Sidebar/View/SidebarListContentView.swift b/Mastodon/Scene/Root/Sidebar/View/SidebarListContentView.swift index 515988405..c2aa1a4f5 100644 --- a/Mastodon/Scene/Root/Sidebar/View/SidebarListContentView.swift +++ b/Mastodon/Scene/Root/Sidebar/View/SidebarListContentView.swift @@ -23,7 +23,8 @@ final class SidebarListContentView: UIView, UIContentView { button.borderColor = UIColor.label return button }() - + private let accessoryImageView = UIImageView(image: nil) + private var currentConfiguration: ContentConfiguration! var configuration: UIContentConfiguration { get { @@ -60,6 +61,9 @@ extension SidebarListContentView { imageView.widthAnchor.constraint(equalToConstant: 40).priority(.required - 1), imageView.heightAnchor.constraint(equalToConstant: 40).priority(.required - 1), ]) + + accessoryImageView.translatesAutoresizingMaskIntoConstraints = false + addSubview(accessoryImageView) avatarButton.translatesAutoresizingMaskIntoConstraints = false addSubview(avatarButton) @@ -68,6 +72,10 @@ extension SidebarListContentView { avatarButton.centerYAnchor.constraint(equalTo: imageView.centerYAnchor), avatarButton.widthAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: 1.0).priority(.required - 2), avatarButton.heightAnchor.constraint(equalTo: imageView.heightAnchor, multiplier: 1.0).priority(.required - 2), + accessoryImageView.widthAnchor.constraint(equalToConstant: 12), + accessoryImageView.heightAnchor.constraint(equalToConstant: 22), + accessoryImageView.leadingAnchor.constraint(equalTo: avatarButton.trailingAnchor, constant: 4), + accessoryImageView.centerYAnchor.constraint(equalTo: avatarButton.centerYAnchor) ]) avatarButton.setContentHuggingPriority(.defaultLow - 10, for: .vertical) avatarButton.setContentHuggingPriority(.defaultLow - 10, for: .horizontal) @@ -96,6 +104,9 @@ extension SidebarListContentView { imageView.isHidden = item.imageURL != nil avatarButton.isHidden = item.imageURL == nil imageView.image = item.isActive ? item.activeImage.withRenderingMode(.alwaysTemplate) : item.image.withRenderingMode(.alwaysTemplate) + accessoryImageView.image = item.accessoryImage + accessoryImageView.isHidden = item.accessoryImage == nil + accessoryImageView.tintColor = item.isActive ? .label : .secondaryLabel avatarButton.avatarImageView.setImage( url: item.imageURL, placeholder: avatarButton.avatarImageView.image ?? .placeholder(color: .systemFill), // reuse to avoid blink @@ -112,7 +123,8 @@ extension SidebarListContentView { var isSelected: Bool = false var isHighlighted: Bool = false var isActive: Bool - + var accessoryImage: UIImage? = nil + // model let title: String var image: UIImage @@ -124,6 +136,7 @@ extension SidebarListContentView { return lhs.isSelected == rhs.isSelected && lhs.isHighlighted == rhs.isHighlighted && lhs.isActive == rhs.isActive + && lhs.accessoryImage == rhs.accessoryImage && lhs.title == rhs.title && lhs.image == rhs.image && lhs.activeImage == rhs.activeImage @@ -134,6 +147,7 @@ extension SidebarListContentView { hasher.combine(isSelected) hasher.combine(isHighlighted) hasher.combine(isActive) + hasher.combine(accessoryImage) hasher.combine(title) hasher.combine(image) hasher.combine(activeImage) diff --git a/Mastodon/Scene/Search/Search/Cell/TrendCollectionViewCell.swift b/Mastodon/Scene/Search/Search/Cell/TrendCollectionViewCell.swift index 30f618625..4436459fb 100644 --- a/Mastodon/Scene/Search/Search/Cell/TrendCollectionViewCell.swift +++ b/Mastodon/Scene/Search/Search/Cell/TrendCollectionViewCell.swift @@ -48,12 +48,7 @@ extension TrendCollectionViewCell { trendView.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(trendView) - NSLayoutConstraint.activate([ - trendView.topAnchor.constraint(equalTo: contentView.topAnchor), - trendView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), - trendView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), - trendView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), - ]) + trendView.pinToParent() } override func updateConfiguration(using state: UICellConfigurationState) { diff --git a/Mastodon/Scene/Search/Search/SearchViewController.swift b/Mastodon/Scene/Search/Search/SearchViewController.swift index 581fa08b3..e1505121d 100644 --- a/Mastodon/Scene/Search/Search/SearchViewController.swift +++ b/Mastodon/Scene/Search/Search/SearchViewController.swift @@ -105,12 +105,7 @@ extension SearchViewController { addChild(discoveryViewController) discoveryViewController.view.translatesAutoresizingMaskIntoConstraints = false view.addSubview(discoveryViewController.view) - NSLayoutConstraint.activate([ - discoveryViewController.view.topAnchor.constraint(equalTo: view.topAnchor), - discoveryViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), - discoveryViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), - discoveryViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + discoveryViewController.view.pinToParent() // discoveryViewController.view.isHidden = true @@ -140,10 +135,7 @@ extension SearchViewController { navigationItem.standardAppearance = navigationBarAppearance navigationItem.scrollEdgeAppearance = navigationBarAppearance navigationItem.compactAppearance = navigationBarAppearance - - if #available(iOS 15, *) { - navigationItem.compactScrollEdgeAppearance = navigationBarAppearance - } + navigationItem.compactScrollEdgeAppearance = navigationBarAppearance } private func setupSearchBar() { @@ -151,12 +143,7 @@ extension SearchViewController { searchBar.delegate = self searchBar.translatesAutoresizingMaskIntoConstraints = false titleViewContainer.addSubview(searchBar) - NSLayoutConstraint.activate([ - searchBar.topAnchor.constraint(equalTo: titleViewContainer.topAnchor), - searchBar.leadingAnchor.constraint(equalTo: titleViewContainer.leadingAnchor), - searchBar.trailingAnchor.constraint(equalTo: titleViewContainer.trailingAnchor), - searchBar.bottomAnchor.constraint(equalTo: titleViewContainer.bottomAnchor), - ]) + searchBar.pinToParent() searchBar.setContentHuggingPriority(.required, for: .horizontal) searchBar.setContentHuggingPriority(.required, for: .vertical) navigationItem.titleView = titleViewContainer @@ -174,7 +161,7 @@ extension SearchViewController { // FIXME: // use `.customPush(animated: false)` false to disable navigation bar animation for searchBar layout // but that should be a fade transition whe fixed size searchBar - self.coordinator.present(scene: .searchDetail(viewModel: searchDetailViewModel), from: self, transition: .customPush(animated: false)) + _ = self.coordinator.present(scene: .searchDetail(viewModel: searchDetailViewModel), from: self, transition: .customPush(animated: false)) } .store(in: &disposeBag) } diff --git a/Mastodon/Scene/Search/Search/View/SearchRecommendCollectionHeader.swift b/Mastodon/Scene/Search/Search/View/SearchRecommendCollectionHeader.swift index 1d4788ac8..0a65c6a25 100644 --- a/Mastodon/Scene/Search/Search/View/SearchRecommendCollectionHeader.swift +++ b/Mastodon/Scene/Search/Search/View/SearchRecommendCollectionHeader.swift @@ -62,12 +62,7 @@ extension SearchRecommendCollectionHeader { containerStackView.isLayoutMarginsRelativeArrangement = true containerStackView.translatesAutoresizingMaskIntoConstraints = false addSubview(containerStackView) - NSLayoutConstraint.activate([ - containerStackView.topAnchor.constraint(equalTo: topAnchor), - containerStackView.leadingAnchor.constraint(equalTo: leadingAnchor), - containerStackView.bottomAnchor.constraint(equalTo: bottomAnchor), - containerStackView.trailingAnchor.constraint(equalTo: trailingAnchor) - ]) + containerStackView.pinToParent() let horizontalStackView = UIStackView() horizontalStackView.spacing = 8 diff --git a/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift b/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift index 62dd1a2cc..4ec9bce31 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift @@ -60,7 +60,7 @@ final class SearchDetailViewController: PageboyViewController, NeedsDependency { let searchController: CustomSearchController = { let searchController = CustomSearchController() searchController.automaticallyShowsScopeBar = false - searchController.dimsBackgroundDuringPresentation = false + searchController.obscuresBackgroundDuringPresentation = false return searchController }() private(set) lazy var searchBar: UISearchBar = { @@ -116,12 +116,7 @@ extension SearchDetailViewController { searchHistoryViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) } else { - NSLayoutConstraint.activate([ - searchHistoryViewController.view.topAnchor.constraint(equalTo: view.topAnchor), - searchHistoryViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), - searchHistoryViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), - searchHistoryViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + searchHistoryViewController.view.pinToParent() } transition = Transition(style: .fade, duration: 0.1) @@ -289,12 +284,7 @@ extension SearchDetailViewController { navigationBarVisualEffectBackgroundView.translatesAutoresizingMaskIntoConstraints = false view.insertSubview(navigationBarVisualEffectBackgroundView, belowSubview: navigationBarBackgroundView) - NSLayoutConstraint.activate([ - navigationBarVisualEffectBackgroundView.topAnchor.constraint(equalTo: navigationBarBackgroundView.topAnchor), - navigationBarVisualEffectBackgroundView.leadingAnchor.constraint(equalTo: navigationBarBackgroundView.leadingAnchor), - navigationBarVisualEffectBackgroundView.trailingAnchor.constraint(equalTo: navigationBarBackgroundView.trailingAnchor), - navigationBarVisualEffectBackgroundView.bottomAnchor.constraint(equalTo: navigationBarBackgroundView.bottomAnchor), - ]) + navigationBarVisualEffectBackgroundView.pinTo(to: navigationBarBackgroundView) } else { navigationItem.setHidesBackButton(true, animated: false) navigationItem.titleView = nil diff --git a/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewController.swift b/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewController.swift index 52d0ffb9c..ac08e7906 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewController.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewController.swift @@ -47,12 +47,7 @@ extension SearchHistoryViewController { collectionView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(collectionView) - NSLayoutConstraint.activate([ - collectionView.topAnchor.constraint(equalTo: view.topAnchor), - collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + collectionView.pinToParent() collectionView.delegate = self viewModel.setupDiffableDataSource( diff --git a/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewModel+Diffable.swift b/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewModel+Diffable.swift index c559523a7..201fa10d2 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewModel+Diffable.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewModel+Diffable.swift @@ -54,7 +54,7 @@ extension SearchHistoryViewModel { var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([.main]) snapshot.appendItems(items, toSection: .main) - diffableDataSource.apply(snapshot, animatingDifferences: false) + await diffableDataSource.apply(snapshot, animatingDifferences: false) } catch { // do nothing } diff --git a/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewController.swift b/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewController.swift index 67de62bf3..6a840f9b5 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewController.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewController.swift @@ -49,12 +49,7 @@ extension SearchResultViewController { tableView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(tableView) - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: view.topAnchor), - tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + tableView.pinToParent() tableView.delegate = self // tableView.prefetchDataSource = self diff --git a/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewModel+Diffable.swift b/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewModel+Diffable.swift index 7d243b1fa..b9eb5a9ae 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewModel+Diffable.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewModel+Diffable.swift @@ -33,7 +33,7 @@ extension SearchResultViewModel { userFetchedResultsController.$records, $hashtags ) - .map { statusRecrods, userRecords, hashtags in + .map { statusRecords, userRecords, hashtags in var items: [SearchResultItem] = [] let userItems = userRecords.map { SearchResultItem.user($0) } @@ -42,7 +42,7 @@ extension SearchResultViewModel { let hashtagItems = hashtags.map { SearchResultItem.hashtag(tag: $0) } items.append(contentsOf: hashtagItems) - let statusItems = statusRecrods.map { SearchResultItem.status($0) } + let statusItems = statusRecords.map { SearchResultItem.status($0) } items.append(contentsOf: statusItems) return items diff --git a/Mastodon/Scene/Settings/Cell/SettingsAppearanceTableViewCell.swift b/Mastodon/Scene/Settings/Cell/SettingsAppearanceTableViewCell.swift index 44b6b39c3..efad9693d 100644 --- a/Mastodon/Scene/Settings/Cell/SettingsAppearanceTableViewCell.swift +++ b/Mastodon/Scene/Settings/Cell/SettingsAppearanceTableViewCell.swift @@ -101,12 +101,7 @@ extension SettingsAppearanceTableViewCell { stackView.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(stackView) - NSLayoutConstraint.activate([ - stackView.topAnchor.constraint(equalTo: contentView.topAnchor), - stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), - stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), - stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), - ]) + stackView.pinToParent() stackView.addArrangedSubview(systemAppearanceView) stackView.addArrangedSubview(darkAppearanceView) diff --git a/Mastodon/Scene/Settings/Cell/SettingsToggleTableViewCell.swift b/Mastodon/Scene/Settings/Cell/SettingsToggleTableViewCell.swift index 4926dbfce..05d5ecea2 100644 --- a/Mastodon/Scene/Settings/Cell/SettingsToggleTableViewCell.swift +++ b/Mastodon/Scene/Settings/Cell/SettingsToggleTableViewCell.swift @@ -86,7 +86,7 @@ extension SettingsToggleTableViewCell { return nil default: // set tint black for Light Mode - return self.contentView.window?.tintColor ?? nil + return self.contentView.window?.tintColor } }() } diff --git a/Mastodon/Scene/Settings/SettingsViewController.swift b/Mastodon/Scene/Settings/SettingsViewController.swift index 53a856fd0..fa6948ced 100644 --- a/Mastodon/Scene/Settings/SettingsViewController.swift +++ b/Mastodon/Scene/Settings/SettingsViewController.swift @@ -225,12 +225,7 @@ extension SettingsViewController { setupNavigation() view.addSubview(tableView) - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: view.topAnchor), - tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + tableView.pinToParent() setupTableView() updateSectionHeaderStackViewLayout() @@ -401,7 +396,7 @@ extension SettingsViewController: UITableViewDelegate { ) let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default, handler: nil) alertController.addAction(okAction) - self.coordinator.present(scene: .alertController(alertController: alertController), from: nil, transition: .alertController(animated: true, completion: nil)) + _ = self.coordinator.present(scene: .alertController(alertController: alertController), from: nil, transition: .alertController(animated: true, completion: nil)) } .store(in: &disposeBag) case .signOut: @@ -549,7 +544,7 @@ extension SettingsViewController: MetaLabelDelegate { switch meta { case .url(_, _, let url, _): guard let url = URL(string: url) else { return } - coordinator.present(scene: .safari(url: url), from: self, transition: .safariPresent(animated: true, completion: nil)) + _ = coordinator.present(scene: .safari(url: url), from: self, transition: .safariPresent(animated: true, completion: nil)) default: assertionFailure() } diff --git a/Mastodon/Scene/Settings/View/AppearanceView.swift b/Mastodon/Scene/Settings/View/AppearanceView.swift index cdc29100b..a09401537 100644 --- a/Mastodon/Scene/Settings/View/AppearanceView.swift +++ b/Mastodon/Scene/Settings/View/AppearanceView.swift @@ -85,12 +85,7 @@ extension AppearanceView { private func setupUI() { imageView.translatesAutoresizingMaskIntoConstraints = false imageViewShadowBackgroundContainer.addSubview(imageView) - NSLayoutConstraint.activate([ - imageView.topAnchor.constraint(equalTo: imageViewShadowBackgroundContainer.topAnchor), - imageView.leadingAnchor.constraint(equalTo: imageViewShadowBackgroundContainer.leadingAnchor), - imageView.trailingAnchor.constraint(equalTo: imageViewShadowBackgroundContainer.trailingAnchor), - imageView.bottomAnchor.constraint(equalTo: imageViewShadowBackgroundContainer.bottomAnchor), - ]) + imageView.pinToParent() imageViewShadowBackgroundContainer.cornerRadius = 4 stackView.addArrangedSubview(imageViewShadowBackgroundContainer) @@ -100,11 +95,8 @@ extension AppearanceView { addSubview(stackView) translatesAutoresizingMaskIntoConstraints = false stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.pinToParent() NSLayoutConstraint.activate([ - stackView.topAnchor.constraint(equalTo: self.topAnchor), - stackView.leadingAnchor.constraint(equalTo: self.leadingAnchor), - stackView.bottomAnchor.constraint(equalTo: self.bottomAnchor), - stackView.trailingAnchor.constraint(equalTo: self.trailingAnchor), imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: 121.0 / 100.0), // height / width ]) } diff --git a/Mastodon/Scene/Share/ContextMenu/ImagePreview/ContextMenuImagePreviewViewController.swift b/Mastodon/Scene/Share/ContextMenu/ImagePreview/ContextMenuImagePreviewViewController.swift index 0d3c4f574..12820e627 100644 --- a/Mastodon/Scene/Share/ContextMenu/ImagePreview/ContextMenuImagePreviewViewController.swift +++ b/Mastodon/Scene/Share/ContextMenu/ImagePreview/ContextMenuImagePreviewViewController.swift @@ -31,12 +31,7 @@ extension ContextMenuImagePreviewViewController { imageView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(imageView) - NSLayoutConstraint.activate([ - imageView.topAnchor.constraint(equalTo: view.topAnchor), - imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - imageView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - imageView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + imageView.pinToParent() imageView.image = viewModel.thumbnail diff --git a/Mastodon/Scene/Share/NavigationController/AdaptiveStatusBarStyleNavigationController.swift b/Mastodon/Scene/Share/NavigationController/AdaptiveStatusBarStyleNavigationController.swift index 80df8938e..88441f9ad 100644 --- a/Mastodon/Scene/Share/NavigationController/AdaptiveStatusBarStyleNavigationController.swift +++ b/Mastodon/Scene/Share/NavigationController/AdaptiveStatusBarStyleNavigationController.swift @@ -14,7 +14,7 @@ class AdaptiveStatusBarStyleNavigationController: UINavigationController { // Make status bar style adaptive for child view controller // SeeAlso: `modalPresentationCapturesStatusBarAppearance` override var childForStatusBarStyle: UIViewController? { - visibleViewController + topViewController } } diff --git a/Mastodon/Scene/Share/View/Content/ContentWarningOverlayView.swift b/Mastodon/Scene/Share/View/Content/ContentWarningOverlayView.swift index ca46193b6..eb55dc575 100644 --- a/Mastodon/Scene/Share/View/Content/ContentWarningOverlayView.swift +++ b/Mastodon/Scene/Share/View/Content/ContentWarningOverlayView.swift @@ -93,12 +93,7 @@ extension ContentWarningOverlayView { vibrancyVisualEffectView.translatesAutoresizingMaskIntoConstraints = false blurVisualEffectView.contentView.addSubview(vibrancyVisualEffectView) - NSLayoutConstraint.activate([ - vibrancyVisualEffectView.topAnchor.constraint(equalTo: blurVisualEffectView.topAnchor), - vibrancyVisualEffectView.leadingAnchor.constraint(equalTo: blurVisualEffectView.leadingAnchor), - vibrancyVisualEffectView.trailingAnchor.constraint(equalTo: blurVisualEffectView.trailingAnchor), - vibrancyVisualEffectView.bottomAnchor.constraint(equalTo: blurVisualEffectView.bottomAnchor), - ]) + vibrancyVisualEffectView.pinTo(to: blurVisualEffectView) vibrancyContentWarningLabel.translatesAutoresizingMaskIntoConstraints = false vibrancyVisualEffectView.contentView.addSubview(vibrancyContentWarningLabel) @@ -110,12 +105,7 @@ extension ContentWarningOverlayView { blurVisualEffectView.translatesAutoresizingMaskIntoConstraints = false addSubview(blurVisualEffectView) - NSLayoutConstraint.activate([ - blurVisualEffectView.topAnchor.constraint(equalTo: topAnchor), - blurVisualEffectView.leadingAnchor.constraint(equalTo: leadingAnchor), - blurVisualEffectView.trailingAnchor.constraint(equalTo: trailingAnchor), - blurVisualEffectView.bottomAnchor.constraint(equalTo: bottomAnchor), - ]) + blurVisualEffectView.pinToParent() // blur image style contentOverlayView.translatesAutoresizingMaskIntoConstraints = false @@ -134,12 +124,7 @@ extension ContentWarningOverlayView { blurContentWarningLabelContainer.translatesAutoresizingMaskIntoConstraints = false contentOverlayView.addSubview(blurContentWarningLabelContainer) - NSLayoutConstraint.activate([ - blurContentWarningLabelContainer.topAnchor.constraint(equalTo: topAnchor), - blurContentWarningLabelContainer.leadingAnchor.constraint(equalTo: leadingAnchor), - blurContentWarningLabelContainer.trailingAnchor.constraint(equalTo: trailingAnchor), - blurContentWarningLabelContainer.bottomAnchor.constraint(equalTo: bottomAnchor), - ]) + blurContentWarningLabelContainer.pinTo(to: self) let topPaddingView = UIView() let bottomPaddingView = UIView() diff --git a/Mastodon/Scene/Share/View/Content/DoubleTitleLabelNavigationBarTitleView.swift b/Mastodon/Scene/Share/View/Content/DoubleTitleLabelNavigationBarTitleView.swift index 6734b7b77..52d9c9f1a 100644 --- a/Mastodon/Scene/Share/View/Content/DoubleTitleLabelNavigationBarTitleView.swift +++ b/Mastodon/Scene/Share/View/Content/DoubleTitleLabelNavigationBarTitleView.swift @@ -47,25 +47,24 @@ extension DoubleTitleLabelNavigationBarTitleView { containerView.distribution = .fill containerView.translatesAutoresizingMaskIntoConstraints = false addSubview(containerView) - NSLayoutConstraint.activate([ - containerView.topAnchor.constraint(equalTo: topAnchor), - containerView.leadingAnchor.constraint(equalTo: leadingAnchor), - containerView.trailingAnchor.constraint(equalTo: trailingAnchor), - containerView.bottomAnchor.constraint(equalTo: bottomAnchor), - ]) + containerView.pinToParent() containerView.addArrangedSubview(titleLabel) containerView.addArrangedSubview(subtitleLabel) + + isAccessibilityElement = true } func update(title: String, subtitle: String?) { titleLabel.configure(content: PlaintextMetaContent(string: title)) update(subtitle: subtitle) + accessibilityLabel = subtitle.map { "\(title), \($0)" } ?? title } func update(titleMetaContent: MetaContent, subtitle: String?) { titleLabel.configure(content: titleMetaContent) update(subtitle: subtitle) + accessibilityLabel = subtitle.map { "\(titleMetaContent.string), \($0)" } ?? titleMetaContent.string } func update(subtitle: String?) { diff --git a/Mastodon/Scene/Share/View/Content/NotificationView+Configuration.swift b/Mastodon/Scene/Share/View/Content/NotificationView+Configuration.swift index 98d06fd92..018628473 100644 --- a/Mastodon/Scene/Share/View/Content/NotificationView+Configuration.swift +++ b/Mastodon/Scene/Share/View/Content/NotificationView+Configuration.swift @@ -111,6 +111,7 @@ extension NotificationView { self.viewModel.notificationIndicatorText = nil return } + self.viewModel.type = type func createMetaContent(text: String, emojis: MastodonContent.Emojis) -> MetaContent { let content = MastodonContent(content: text, emojis: emojis) @@ -154,7 +155,7 @@ extension NotificationView { ) case .status: self.viewModel.notificationIndicatorText = createMetaContent( - text: L10n.Scene.Notification.NotificationDescription.mentionedYou, + text: .empty, emojis: emojis.asDictionary ) case ._other: diff --git a/Mastodon/Scene/Share/View/Content/PollOptionView+Configuration.swift b/Mastodon/Scene/Share/View/Content/PollOptionView+Configuration.swift index 334c9ce15..cbc7c61f8 100644 --- a/Mastodon/Scene/Share/View/Content/PollOptionView+Configuration.swift +++ b/Mastodon/Scene/Share/View/Content/PollOptionView+Configuration.swift @@ -33,12 +33,12 @@ extension PollOptionView { .store(in: &disposeBag) // percentage Publishers.CombineLatest( - option.poll.publisher(for: \.votesCount), + option.poll.publisher(for: \.votersCount), option.publisher(for: \.votesCount) ) - .map { pollVotesCount, optionVotesCount -> Double? in - guard pollVotesCount > 0, optionVotesCount >= 0 else { return 0 } - return Double(optionVotesCount) / Double(pollVotesCount) + .map { pollVotersCount, optionVotesCount -> Double? in + guard pollVotersCount > 0, optionVotesCount >= 0 else { return 0 } + return Double(optionVotesCount) / Double(pollVotersCount) } .assign(to: \.percentage, on: viewModel) .store(in: &disposeBag) diff --git a/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell+ViewModel.swift b/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell+ViewModel.swift index 85184d406..2702d726b 100644 --- a/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell+ViewModel.swift +++ b/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell+ViewModel.swift @@ -67,6 +67,21 @@ extension StatusTableViewCell { } } .store(in: &disposeBag) + + statusView.viewModel.$card + .removeDuplicates() + .dropFirst() + .receive(on: DispatchQueue.main) + .sink { [weak tableView, weak self] _ in + guard let tableView = tableView else { return } + guard let _ = self else { return } + + UIView.performWithoutAnimation { + tableView.beginUpdates() + tableView.endUpdates() + } + } + .store(in: &disposeBag) } } diff --git a/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift b/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift index c9850a0d3..b8482c564 100644 --- a/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift +++ b/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift @@ -86,6 +86,14 @@ extension StatusTableViewCell { self.accessibilityLabel = accessibilityLabel } .store(in: &_disposeBag) + + statusView.viewModel + .$translatedFromLanguage + .receive(on: DispatchQueue.main) + .sink(receiveValue: { [weak self] _ in + self?.invalidateIntrinsicContentSize() + }) + .store(in: &_disposeBag) } override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { diff --git a/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCellDelegate.swift b/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCellDelegate.swift index 034985c03..f21e0573c 100644 --- a/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCellDelegate.swift +++ b/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCellDelegate.swift @@ -27,6 +27,7 @@ protocol StatusTableViewCellDelegate: AnyObject, AutoGenerateProtocolDelegate { func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, authorAvatarButtonDidPressed button: AvatarButton) func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, contentSensitiveeToggleButtonDidPressed button: UIButton) func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, metaText: MetaText, didSelectMeta meta: Meta) + func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, didTapCardWithURL url: URL) func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, mediaGridContainerView: MediaGridContainerView, mediaView: MediaView, didSelectMediaViewAt index: Int) func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, pollTableView tableView: UITableView, didSelectRowAt indexPath: IndexPath) func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, pollVoteButtonPressed button: UIButton) @@ -36,6 +37,8 @@ protocol StatusTableViewCellDelegate: AnyObject, AutoGenerateProtocolDelegate { func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, mediaGridContainerView: MediaGridContainerView, mediaSensitiveButtonDidPressed button: UIButton) func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, statusMetricView: StatusMetricView, reblogButtonDidPressed button: UIButton) func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, statusMetricView: StatusMetricView, favoriteButtonDidPressed button: UIButton) + func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, cardControl: StatusCardControl, didTapURL url: URL) + func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, cardControlMenu: StatusCardControl) -> UIMenu? func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, accessibilityActivate: Void) // sourcery:end } @@ -61,6 +64,10 @@ extension StatusViewDelegate where Self: StatusViewContainerTableViewCell { delegate?.tableViewCell(self, statusView: statusView, metaText: metaText, didSelectMeta: meta) } + func statusView(_ statusView: StatusView, didTapCardWithURL url: URL) { + delegate?.tableViewCell(self, statusView: statusView, didTapCardWithURL: url) + } + func statusView(_ statusView: StatusView, mediaGridContainerView: MediaGridContainerView, mediaView: MediaView, didSelectMediaViewAt index: Int) { delegate?.tableViewCell(self, statusView: statusView, mediaGridContainerView: mediaGridContainerView, mediaView: mediaView, didSelectMediaViewAt: index) } @@ -97,6 +104,14 @@ extension StatusViewDelegate where Self: StatusViewContainerTableViewCell { delegate?.tableViewCell(self, statusView: statusView, statusMetricView: statusMetricView, favoriteButtonDidPressed: button) } + func statusView(_ statusView: StatusView, cardControl: StatusCardControl, didTapURL url: URL) { + delegate?.tableViewCell(self, statusView: statusView, cardControl: cardControl, didTapURL: url) + } + + func statusView(_ statusView: StatusView, cardControlMenu: StatusCardControl) -> UIMenu? { + return delegate?.tableViewCell(self, statusView: statusView, cardControlMenu: cardControlMenu) + } + func statusView(_ statusView: StatusView, accessibilityActivate: Void) { delegate?.tableViewCell(self, statusView: statusView, accessibilityActivate: accessibilityActivate) } diff --git a/Mastodon/Scene/Share/View/TableviewCell/StatusThreadRootTableViewCell.swift b/Mastodon/Scene/Share/View/TableviewCell/StatusThreadRootTableViewCell.swift index 64f3456b5..73b700e27 100644 --- a/Mastodon/Scene/Share/View/TableviewCell/StatusThreadRootTableViewCell.swift +++ b/Mastodon/Scene/Share/View/TableviewCell/StatusThreadRootTableViewCell.swift @@ -81,6 +81,14 @@ extension StatusThreadRootTableViewCell { // a11y statusView.contentMetaText.textView.isAccessibilityElement = true statusView.contentMetaText.textView.isSelectable = true + + statusView.viewModel + .$translatedFromLanguage + .receive(on: DispatchQueue.main) + .sink(receiveValue: { [weak self] _ in + self?.invalidateIntrinsicContentSize() + }) + .store(in: &disposeBag) } override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { @@ -96,7 +104,6 @@ extension StatusThreadRootTableViewCell { override var accessibilityElements: [Any]? { get { var elements = [ - statusView.headerContainerView, statusView.authorView, statusView.viewModel.isContentReveal ? statusView.contentMetaText.textView diff --git a/Mastodon/Scene/Share/Webview/WebViewController.swift b/Mastodon/Scene/Share/Webview/WebViewController.swift index cb690de93..209ee483d 100644 --- a/Mastodon/Scene/Share/Webview/WebViewController.swift +++ b/Mastodon/Scene/Share/Webview/WebViewController.swift @@ -49,12 +49,7 @@ extension WebViewController { webView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(webView) - NSLayoutConstraint.activate([ - webView.topAnchor.constraint(equalTo: view.topAnchor), - webView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - webView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - webView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + webView.pinToParent() let request = URLRequest(url: viewModel.url) webView.load(request) diff --git a/Mastodon/Scene/SuggestionAccount/CollectionViewCell/SuggestionAccountCollectionViewCell.swift b/Mastodon/Scene/SuggestionAccount/CollectionViewCell/SuggestionAccountCollectionViewCell.swift index 2fb467fbd..296935521 100644 --- a/Mastodon/Scene/SuggestionAccount/CollectionViewCell/SuggestionAccountCollectionViewCell.swift +++ b/Mastodon/Scene/SuggestionAccount/CollectionViewCell/SuggestionAccountCollectionViewCell.swift @@ -53,11 +53,6 @@ extension SuggestionAccountCollectionViewCell { private func configure() { contentView.addSubview(imageView) imageView.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - imageView.topAnchor.constraint(equalTo: contentView.topAnchor), - imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), - imageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), - imageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), - ]) + imageView.pinToParent() } } diff --git a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift index 13c8311c7..7eacdc205 100644 --- a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift +++ b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift @@ -161,7 +161,7 @@ extension SuggestionAccountViewController: UITableViewDelegate { case .account(let record): guard let account = record.object(in: context.managedObjectContext) else { return } let cachedProfileViewModel = CachedProfileViewModel(context: context, authContext: viewModel.authContext, mastodonUser: account) - coordinator.present( + _ = coordinator.present( scene: .profile(viewModel: cachedProfileViewModel), from: self, transition: .show diff --git a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel+Diffable.swift b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel+Diffable.swift index 35ba305bc..afb3eb7ec 100644 --- a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel+Diffable.swift +++ b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel+Diffable.swift @@ -34,12 +34,7 @@ extension SuggestionAccountViewModel { let items: [RecommendAccountItem] = records.map { RecommendAccountItem.account($0) } snapshot.appendItems(items, toSection: .main) - if #available(iOS 15.0, *) { - tableViewDiffableDataSource.applySnapshotUsingReloadData(snapshot, completion: nil) - } else { - // Fallback on earlier versions - tableViewDiffableDataSource.applySnapshot(snapshot, animated: false, completion: nil) - } + tableViewDiffableDataSource.applySnapshotUsingReloadData(snapshot, completion: nil) } .store(in: &disposeBag) } @@ -71,13 +66,7 @@ extension SuggestionAccountViewModel { } snapshot.appendItems(items, toSection: .main) - - if #available(iOS 15.0, *) { - collectionViewDiffableDataSource.applySnapshotUsingReloadData(snapshot, completion: nil) - } else { - // Fallback on earlier versions - collectionViewDiffableDataSource.applySnapshot(snapshot, animated: false, completion: nil) - } + collectionViewDiffableDataSource.applySnapshotUsingReloadData(snapshot, completion: nil) } .store(in: &disposeBag) } diff --git a/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell.swift b/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell.swift index 47bd9d6b3..d259ebed4 100644 --- a/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell.swift +++ b/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell.swift @@ -97,12 +97,7 @@ extension SuggestionAccountTableViewCell { containerStackView.isLayoutMarginsRelativeArrangement = true containerStackView.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(containerStackView) - NSLayoutConstraint.activate([ - containerStackView.topAnchor.constraint(equalTo: contentView.topAnchor), - containerStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), - containerStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), - containerStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), - ]) + containerStackView.pinToParent() avatarButton.translatesAutoresizingMaskIntoConstraints = false containerStackView.addArrangedSubview(avatarButton) diff --git a/Mastodon/Scene/Thread/ThreadViewController.swift b/Mastodon/Scene/Thread/ThreadViewController.swift index 5dee182c6..1b386aa6a 100644 --- a/Mastodon/Scene/Thread/ThreadViewController.swift +++ b/Mastodon/Scene/Thread/ThreadViewController.swift @@ -87,12 +87,7 @@ extension ThreadViewController { tableView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(tableView) - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: view.topAnchor), - tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + tableView.pinToParent() tableView.delegate = self viewModel.setupDiffableDataSource( @@ -122,7 +117,7 @@ extension ThreadViewController { let composeViewModel = ComposeViewModel( context: context, authContext: viewModel.authContext, - kind: .reply(status: threadContext.status) + destination: .reply(parent: threadContext.status) ) _ = coordinator.present( scene: .compose(viewModel: composeViewModel), diff --git a/Mastodon/Scene/Thread/ThreadViewModel+Diffable.swift b/Mastodon/Scene/Thread/ThreadViewModel+Diffable.swift index 834d478e6..850c6bab2 100644 --- a/Mastodon/Scene/Thread/ThreadViewModel+Diffable.swift +++ b/Mastodon/Scene/Thread/ThreadViewModel+Diffable.swift @@ -24,6 +24,7 @@ extension ThreadViewModel { tableView: tableView, context: context, configuration: StatusSection.Configuration( + context: context, authContext: authContext, statusTableViewCellDelegate: statusTableViewCellDelegate, timelineMiddleLoaderTableViewCellDelegate: nil, @@ -153,17 +154,13 @@ extension ThreadViewModel { snapshot: NSDiffableDataSourceSnapshot, animatingDifferences: Bool ) async { - diffableDataSource?.apply(snapshot, animatingDifferences: animatingDifferences) + await diffableDataSource?.apply(snapshot, animatingDifferences: animatingDifferences) } @MainActor func updateSnapshotUsingReloadData( snapshot: NSDiffableDataSourceSnapshot ) async { - if #available(iOS 15.0, *) { - await self.diffableDataSource?.applySnapshotUsingReloadData(snapshot) - } else { - diffableDataSource?.applySnapshot(snapshot, animated: false, completion: nil) - } + await self.diffableDataSource?.applySnapshotUsingReloadData(snapshot) } // Some UI tweaks to present replies and conversation smoothly diff --git a/Mastodon/Scene/Thread/ThreadViewModel+LoadThreadState.swift b/Mastodon/Scene/Thread/ThreadViewModel+LoadThreadState.swift index 050670be7..1c6c40190 100644 --- a/Mastodon/Scene/Thread/ThreadViewModel+LoadThreadState.swift +++ b/Mastodon/Scene/Thread/ThreadViewModel+LoadThreadState.swift @@ -92,19 +92,27 @@ extension ThreadViewModel.LoadThreadState { from: response.value.ancestors ) ) + // deprecated: Tree mode replies + // viewModel.mastodonStatusThreadViewModel.appendDescendant( + // domain: threadContext.domain, + // nodes: MastodonStatusThreadViewModel.Node.children( + // of: threadContext.statusID, + // from: response.value.descendants + // ) + // ) + + // new: the same order from API viewModel.mastodonStatusThreadViewModel.appendDescendant( domain: threadContext.domain, - nodes: MastodonStatusThreadViewModel.Node.children( - of: threadContext.statusID, - from: response.value.descendants - ) + nodes: response.value.descendants.map { status in + return .init(statusID: status.id, children: []) + } ) } catch { logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): fetch status context for \(threadContext.statusID) fail: \(error.localizedDescription)") await enter(state: Fail.self) } - - } + } // end Task } } diff --git a/Mastodon/Scene/Thread/ThreadViewModel.swift b/Mastodon/Scene/Thread/ThreadViewModel.swift index 735d85cd4..c845ce64c 100644 --- a/Mastodon/Scene/Thread/ThreadViewModel.swift +++ b/Mastodon/Scene/Thread/ThreadViewModel.swift @@ -28,12 +28,6 @@ class ThreadViewModel { let context: AppContext let authContext: AuthContext let mastodonStatusThreadViewModel: MastodonStatusThreadViewModel - -// let cellFrameCache = NSCache() -// let existStatusFetchedResultsController: StatusFetchedResultsController - -// weak var contentOffsetAdjustableTimelineViewControllerDelegate: ContentOffsetAdjustableTimelineViewControllerDelegate? -// weak var tableView: UITableView? // output var diffableDataSource: UITableViewDiffableDataSource? @@ -62,12 +56,6 @@ class ThreadViewModel { self.authContext = authContext self.root = optionalRoot self.mastodonStatusThreadViewModel = MastodonStatusThreadViewModel(context: context) -// self.rootNode = CurrentValueSubject(optionalStatus.flatMap { RootNode(domain: $0.domain, statusID: $0.id, replyToID: $0.inReplyToID) }) -// self.rootItem = CurrentValueSubject(optionalStatus.flatMap { Item.root(statusObjectID: $0.objectID, attribute: Item.StatusAttribute()) }) -// self.existStatusFetchedResultsController = StatusFetchedResultsController(managedObjectContext: context.managedObjectContext, domain: nil, additionalTweetPredicate: nil) -// self.navigationBarTitle = CurrentValueSubject( -// optionalStatus.flatMap { L10n.Scene.Thread.title($0.author.displayNameWithFallback) }) -// self.navigationBarTitleEmojiMeta = CurrentValueSubject(optionalStatus.flatMap { $0.author.emojis.asDictionary } ?? [:]) // end init ManagedObjectObserver.observe(context: context.managedObjectContext) @@ -85,24 +73,6 @@ class ThreadViewModel { }) .store(in: &disposeBag) -// // bind fetcher domain -// context.authenticationService.activeMastodonAuthenticationBox -// .receive(on: RunLoop.main) -// .sink { [weak self] box in -// guard let self = self else { return } -// self.existStatusFetchedResultsController.domain.value = box?.domain -// } -// .store(in: &disposeBag) -// -// rootNode -// .receive(on: DispatchQueue.main) -// .sink { [weak self] rootNode in -// guard let self = self else { return } -// guard rootNode != nil else { return } -// self.loadThreadStateMachine.enter(LoadThreadState.Loading.self) -// } -// .store(in: &disposeBag) - $root .receive(on: DispatchQueue.main) .sink { [weak self] root in @@ -125,102 +95,6 @@ class ThreadViewModel { }() } .store(in: &disposeBag) - -// rootItem -// .receive(on: DispatchQueue.main) -// .sink { [weak self] rootItem in -// guard let self = self else { return } -// guard case let .root(objectID, _) = rootItem else { return } -// self.context.managedObjectContext.perform { -// guard let status = self.context.managedObjectContext.object(with: objectID) as? Status else { -// return -// } -// self.rootItemObserver = ManagedObjectObserver.observe(object: status) -// .receive(on: DispatchQueue.main) -// .sink(receiveCompletion: { _ in -// // do nothing -// }, receiveValue: { [weak self] change in -// guard let self = self else { return } -// switch change.changeType { -// case .delete: -// self.rootItem.value = nil -// default: -// break -// } -// }) -// } -// } -// .store(in: &disposeBag) -// -// ancestorNodes -// .receive(on: DispatchQueue.main) -// .compactMap { [weak self] nodes -> [Item]? in -// guard let self = self else { return nil } -// guard !nodes.isEmpty else { return [] } -// -// guard let diffableDataSource = self.diffableDataSource else { return nil } -// let oldSnapshot = diffableDataSource.snapshot() -// var oldSnapshotAttributeDict: [NSManagedObjectID : Item.StatusAttribute] = [:] -// for item in oldSnapshot.itemIdentifiers { -// switch item { -// case .reply(let objectID, let attribute): -// oldSnapshotAttributeDict[objectID] = attribute -// default: -// break -// } -// } -// -// var items: [Item] = [] -// for node in nodes { -// let attribute = oldSnapshotAttributeDict[node.statusObjectID] ?? Item.StatusAttribute() -// items.append(Item.reply(statusObjectID: node.statusObjectID, attribute: attribute)) -// } -// -// return items.reversed() -// } -// .assign(to: \.value, on: ancestorItems) -// .store(in: &disposeBag) -// -// descendantNodes -// .receive(on: DispatchQueue.main) -// .compactMap { [weak self] nodes -> [Item]? in -// guard let self = self else { return nil } -// guard !nodes.isEmpty else { return [] } -// -// guard let diffableDataSource = self.diffableDataSource else { return nil } -// let oldSnapshot = diffableDataSource.snapshot() -// var oldSnapshotAttributeDict: [NSManagedObjectID : Item.StatusAttribute] = [:] -// for item in oldSnapshot.itemIdentifiers { -// switch item { -// case .leaf(let objectID, let attribute): -// oldSnapshotAttributeDict[objectID] = attribute -// default: -// break -// } -// } -// -// var items: [Item] = [] -// -// func buildThread(node: LeafNode) { -// let attribute = oldSnapshotAttributeDict[node.objectID] ?? Item.StatusAttribute() -// items.append(Item.leaf(statusObjectID: node.objectID, attribute: attribute)) -// // only expand the first child -// if let firstChild = node.children.first { -// if !node.isChildrenExpanded { -// items.append(Item.leafBottomLoader(statusObjectID: node.objectID)) -// } else { -// buildThread(node: firstChild) -// } -// } -// } -// -// for node in nodes { -// buildThread(node: node) -// } -// return items -// } -// .assign(to: \.value, on: descendantItems) -// .store(in: &disposeBag) } deinit { diff --git a/Mastodon/Scene/Transition/MediaPreview/MediaHostToMediaPreviewViewControllerAnimatedTransitioning.swift b/Mastodon/Scene/Transition/MediaPreview/MediaHostToMediaPreviewViewControllerAnimatedTransitioning.swift index 5381feb0d..ce9cfca96 100644 --- a/Mastodon/Scene/Transition/MediaPreview/MediaHostToMediaPreviewViewControllerAnimatedTransitioning.swift +++ b/Mastodon/Scene/Transition/MediaPreview/MediaHostToMediaPreviewViewControllerAnimatedTransitioning.swift @@ -65,7 +65,7 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning { let initialFrame = transitionItem.initialFrame ?? toViewEndFrame let transitionTargetFrame: CGRect = { let aspectRatio = transitionItem.aspectRatio ?? CGSize(width: initialFrame.width, height: initialFrame.height) - return AVMakeRect(aspectRatio: aspectRatio, insideRect: toView.bounds) + return AVMakeRect(aspectRatio: aspectRatio, insideRect: toView.bounds.inset(by: toView.safeAreaInsets)) }() let transitionImageView: UIImageView = { let imageView = UIImageView(frame: transitionContext.containerView.convert(initialFrame, from: nil)) @@ -81,8 +81,8 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning { transitionItem.transitionView = transitionImageView transitionContext.containerView.addSubview(transitionImageView) - toVC.closeButtonBackground.alpha = 0 - + toVC.topToolbar.alpha = 0 + if UIAccessibility.isReduceTransparencyEnabled { toVC.visualEffectView.alpha = 0 } @@ -101,7 +101,7 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning { toVC.pagingViewController.view.alpha = 1 transitionImageView.removeFromSuperview() UIView.animate(withDuration: 0.33, delay: 0, options: [.curveEaseInOut]) { - toVC.closeButtonBackground.alpha = 1 + toVC.topToolbar.alpha = 1 } transitionContext.completeTransition(position == .end) } @@ -131,14 +131,20 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning { } return animator } + + if let toVC = transitionContext.viewController(forKey: .to) { + animator.addCompletion { _ in + toVC.setNeedsStatusBarAppearanceUpdate() + } + } - // update close button + // update top toolbar UIView.animate(withDuration: 0.33, delay: 0, options: [.curveEaseInOut]) { - fromVC.closeButtonBackground.alpha = 0 + fromVC.topToolbar.alpha = 0 } animator.addCompletion { position in UIView.animate(withDuration: 0.33, delay: 0, options: [.curveEaseInOut]) { - fromVC.closeButtonBackground.alpha = position == .end ? 0 : 1 + fromVC.topToolbar.alpha = position == .end ? 0 : 1 } } @@ -196,7 +202,7 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning { mediaPreviewTransitionContext.snapshot.contentMode = .scaleAspectFill mediaPreviewTransitionContext.snapshot.clipsToBounds = true transitionMaskView.addSubview(mediaPreviewTransitionContext.snapshot) - fromVC.view.bringSubviewToFront(fromVC.closeButtonBackground) + fromVC.view.bringSubviewToFront(fromVC.topToolbar) transitionItem.transitionView = mediaPreviewTransitionContext.transitionView transitionItem.snapshotTransitioning = mediaPreviewTransitionContext.snapshot @@ -247,23 +253,6 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning { return rect }() let maskLayerToPath = maskLayerToRect.flatMap { UIBezierPath(rect: $0) }?.cgPath - let maskLayerToFinalRect: CGRect? = { - guard case .attachments = transitionItem.source else { return nil } - var rect = maskLayerToRect ?? transitionMaskView.frame - // clip tabBar when bar visible - guard let tabBarController = toVC.tabBarController, - !tabBarController.tabBar.isHidden, - let tabBarSuperView = tabBarController.tabBar.superview - else { return rect } - let tabBarFrameInWindow = tabBarSuperView.convert(tabBarController.tabBar.frame, to: nil) - let offset = rect.maxY - tabBarFrameInWindow.minY - guard offset > 0 else { return rect } - rect.size.height -= offset - return rect - }() - - // FIXME: - let maskLayerToFinalPath = maskLayerToFinalRect.flatMap { UIBezierPath(rect: $0) }?.cgPath if let maskLayerToPath = maskLayerToPath { maskLayer.path = maskLayerToPath @@ -528,7 +517,7 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning { let scaleTransform = CGAffineTransform(scaleX: (itemWidth / size.width), y: (itemHeight / size.height)) let scaledOffset = transitionItem.touchOffset.apply(transform: scaleTransform) - let center = transitionView.convert(transitionView.center, to: nil) + let center = transitionView.convert(CGPoint(x: transitionView.bounds.midX, y: transitionView.bounds.midY), to: nil) snapshot.center = (center + (translation + (transitionItem.touchOffset - scaledOffset))).point snapshot.bounds = CGRect(origin: CGPoint.zero, size: CGSize(width: itemWidth, height: itemHeight)) transitionItem.touchOffset = scaledOffset diff --git a/Mastodon/Supporting Files/AppDelegate.swift b/Mastodon/Supporting Files/AppDelegate.swift index 3bf38f6da..84b819a5c 100644 --- a/Mastodon/Supporting Files/AppDelegate.swift +++ b/Mastodon/Supporting Files/AppDelegate.swift @@ -65,11 +65,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { extension AppDelegate { func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask { - #if DEBUG - return .all - #else return UIDevice.current.userInterfaceIdiom == .phone ? .portrait : .all - #endif } } diff --git a/Mastodon/Supporting Files/SceneDelegate.swift b/Mastodon/Supporting Files/SceneDelegate.swift index bf02b8031..e2f045a7b 100644 --- a/Mastodon/Supporting Files/SceneDelegate.swift +++ b/Mastodon/Supporting Files/SceneDelegate.swift @@ -11,6 +11,7 @@ import Combine import CoreDataStack import MastodonCore import MastodonExtension +import MastodonUI #if PROFILE import FPSIndicator @@ -35,8 +36,13 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = scene as? UIWindowScene else { return } + #if DEBUG + let window = TouchesVisibleWindow(windowScene: windowScene) + self.window = window + #else let window = UIWindow(windowScene: windowScene) self.window = window + #endif // set tint color window.tintColor = UIColor.label @@ -112,6 +118,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { // trigger authenticated user account update AppContext.shared.authenticationService.updateActiveUserAccountPublisher.send() + + // update mutes and blocks and remove related data + AppContext.shared.instanceService.updateMutesAndBlocks() if let shortcutItem = savedShortCutItem { Task { @@ -175,7 +184,7 @@ extension SceneDelegate { return false } - coordinator.switchToTabBar(tab: .notification) + coordinator.switchToTabBar(tab: .notifications) case "org.joinmastodon.app.new-post": if coordinator?.tabBarController.topMost is ComposeViewController { @@ -185,7 +194,7 @@ extension SceneDelegate { let composeViewModel = ComposeViewModel( context: AppContext.shared, authContext: authContext, - kind: .post + destination: .topLevel ) _ = coordinator?.present(scene: .compose(viewModel: composeViewModel), from: nil, transition: .modal(animated: true, completion: nil)) logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): present compose scene") diff --git a/Mastodon/Template/AutoGenerateProtocolDelegate.swifttemplate b/Mastodon/Template/AutoGenerateProtocolDelegate.swifttemplate index 47eb4ce19..e8677d970 100644 --- a/Mastodon/Template/AutoGenerateProtocolDelegate.swifttemplate +++ b/Mastodon/Template/AutoGenerateProtocolDelegate.swifttemplate @@ -1,3 +1,17 @@ +<% +func methodDeclaration(_ method: SourceryRuntime.Method, newName: String) -> String { + var result = newName + if method.throws { + result = result + " throws" + } else if method.rethrows { + result = result + " rethrows" + } + if method.returnTypeName.isVoid { + return result + } + return result + " -> \(method.returnTypeName)" +} +-%> <% for type in types.implementing["AutoGenerateProtocolDelegate"] { guard let replaceOf = type.annotations["replaceOf"] as? String else { continue } guard let replaceWith = type.annotations["replaceWith"] as? String else { continue } @@ -5,7 +19,7 @@ guard let aProtocol = types.protocols.first(where: { $0.name == protocolToGenerate }) else { continue } -%> // sourcery:inline:<%= type.name %>.AutoGenerateProtocolDelegate <% for method in aProtocol.methods { -%> -<%= method.name.replacingOccurrences(of: replaceOf, with: replaceWith) %> +<%= methodDeclaration(method, newName: method.name.replacingOccurrences(of: replaceOf, with: replaceWith)) %> <% } -%> // sourcery:end <% } %> diff --git a/Mastodon/Template/AutoGenerateProtocolRelayDelegate.swifttemplate b/Mastodon/Template/AutoGenerateProtocolRelayDelegate.swifttemplate index b57f26038..d2b0dd58c 100644 --- a/Mastodon/Template/AutoGenerateProtocolRelayDelegate.swifttemplate +++ b/Mastodon/Template/AutoGenerateProtocolRelayDelegate.swifttemplate @@ -6,6 +6,9 @@ func methodDeclaration(_ method: SourceryRuntime.Method) -> String { } else if method.rethrows { result = result + " rethrows" } + if method.returnTypeName.isVoid { + return result + } return result + " -> \(method.returnTypeName)" } -%> @@ -42,7 +45,7 @@ func methodCall( guard let aProtocol = types.protocols.first(where: { $0.name == protocolToGenerate }) else { continue } -%> // sourcery:inline:<%= type.name %>.AutoGenerateProtocolRelayDelegate <% for method in aProtocol.methods { -%> -func <%= method.name -%> { +func <%= methodDeclaration(method) -%> { <%= methodCall(method, replaceOf: replaceOf, replaceWith: replaceWith) %> } diff --git a/MastodonIntent/ca.lproj/Intents.strings b/MastodonIntent/ca.lproj/Intents.strings index 6b92eb263..c02ac08cf 100644 --- a/MastodonIntent/ca.lproj/Intents.strings +++ b/MastodonIntent/ca.lproj/Intents.strings @@ -22,7 +22,7 @@ "ZbSjzC" = "Visibilitat"; -"Zo4jgJ" = "Visibilitat de la Publicació"; +"Zo4jgJ" = "Visibilitat de la publicació"; "apSxMG-dYQ5NN" = "Hi ha ${count} opcions que coincideixen amb ‘Públic’."; @@ -30,9 +30,9 @@ "ayoYEb-dYQ5NN" = "${content}, Públic"; -"ayoYEb-ehFLjY" = "${content}, Només Seguidors"; +"ayoYEb-ehFLjY" = "${content}, Només seguidors"; -"dUyuGg" = "Publicació"; +"dUyuGg" = "Publica a Mastodon"; "dYQ5NN" = "Públic"; diff --git a/MastodonIntent/cs.lproj/Intents.strings b/MastodonIntent/cs.lproj/Intents.strings new file mode 100644 index 000000000..6f29830a1 --- /dev/null +++ b/MastodonIntent/cs.lproj/Intents.strings @@ -0,0 +1,51 @@ +"16wxgf" = "Příspěvek na Mastodon"; + +"751xkl" = "Textový obsah"; + +"CsR7G2" = "Příspěvek na Mastodon"; + +"HZSGTr" = "Jaký obsah se má přidat?"; + +"HdGikU" = "Odeslání se nezdařilo"; + +"KDNTJ4" = "Důvod selhání"; + +"RHxKOw" = "Odeslat příspěvek s textovým obsahem"; + +"RxSqsb" = "Příspěvek"; + +"WCIR3D" = "Zveřejnit ${content} na Mastodon"; + +"ZKJSNu" = "Příspěvek"; + +"ZS1XaK" = "${content}"; + +"ZbSjzC" = "Viditelnost"; + +"Zo4jgJ" = "Viditelnost příspěvku"; + +"apSxMG-dYQ5NN" = "Existuje ${count} možností odpovídajících 'Veřejný'."; + +"apSxMG-ehFLjY" = "Existuje ${count} možností, které odpovídají „jen sledujícím“."; + +"ayoYEb-dYQ5NN" = "${content}, veřejné"; + +"ayoYEb-ehFLjY" = "${content}, pouze sledující"; + +"dUyuGg" = "Příspěvek na Mastodon"; + +"dYQ5NN" = "Veřejný"; + +"ehFLjY" = "Pouze sledující"; + +"gfePDu" = "Odeslání se nezdařilo. ${failureReason}"; + +"k7dbKQ" = "Příspěvek byl úspěšně odeslán."; + +"oGiqmY-dYQ5NN" = "Jen pro kontrolu, chtěli jste „Veřejný“?"; + +"oGiqmY-ehFLjY" = "Jen pro kontrolu, chtěli jste „Pouze sledující“?"; + +"rM6dvp" = "URL"; + +"ryJLwG" = "Příspěvek byl úspěšně odeslán. "; diff --git a/MastodonIntent/cs.lproj/Intents.stringsdict b/MastodonIntent/cs.lproj/Intents.stringsdict new file mode 100644 index 000000000..5a39d5e64 --- /dev/null +++ b/MastodonIntent/cs.lproj/Intents.stringsdict @@ -0,0 +1,54 @@ + + + + + There are ${count} options matching ‘${content}’. - 2 + + NSStringLocalizedFormatKey + There are %#@count_option@ matching ‘${content}’. + count_option + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + %ld + zero + 0 options + one + 1 option + two + 2 options + few + %ld options + many + %ld options + other + %ld options + + + There are ${count} options matching ‘${visibility}’. + + NSStringLocalizedFormatKey + There are %#@count_option@ matching ‘${visibility}’. + count_option + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + %ld + zero + 0 options + one + 1 option + two + 2 options + few + %ld options + many + %ld options + other + %ld options + + + + diff --git a/MastodonIntent/de.lproj/Intents.strings b/MastodonIntent/de.lproj/Intents.strings index fd3fbd40f..5d0056766 100644 --- a/MastodonIntent/de.lproj/Intents.strings +++ b/MastodonIntent/de.lproj/Intents.strings @@ -1,12 +1,12 @@ -"16wxgf" = "Auf Mastodon posten"; +"16wxgf" = "Auf Mastodon veröffentlichen"; "751xkl" = "Textinhalt"; -"CsR7G2" = "Auf Mastodon posten"; +"CsR7G2" = "Auf Mastodon veröffentlichen"; -"HZSGTr" = "Welcher Inhalt soll gepostet werden?"; +"HZSGTr" = "Welcher Inhalt soll veröffentlicht werden?"; -"HdGikU" = "Posten fehlgeschlagen"; +"HdGikU" = "Veröffentlichen fehlgeschlagen"; "KDNTJ4" = "Fehlerursache"; diff --git a/MastodonIntent/sl.lproj/Intents.strings b/MastodonIntent/sl.lproj/Intents.strings new file mode 100644 index 000000000..72de87df2 --- /dev/null +++ b/MastodonIntent/sl.lproj/Intents.strings @@ -0,0 +1,51 @@ +"16wxgf" = "Objavi na Mastodonu"; + +"751xkl" = "besedilo"; + +"CsR7G2" = "Objavi na Mastodonu"; + +"HZSGTr" = "Kakšno vsebino želite objaviti?"; + +"HdGikU" = "Objava ni uspela"; + +"KDNTJ4" = "Vzrok za neuspeh"; + +"RHxKOw" = "Pošlji objavo z besedilom"; + +"RxSqsb" = "Objavi"; + +"WCIR3D" = "Objavite ${content} na Mastodonu"; + +"ZKJSNu" = "Objavi"; + +"ZS1XaK" = "${content}"; + +"ZbSjzC" = "Vidnost"; + +"Zo4jgJ" = "Vidnost objave"; + +"apSxMG-dYQ5NN" = "Z \"Javno\" se ujema ${count} možnosti."; + +"apSxMG-ehFLjY" = "S \"Samo sledilci\" se ujema ${count} možnosti."; + +"ayoYEb-dYQ5NN" = "${content}, javno"; + +"ayoYEb-ehFLjY" = "${content}, samo sledilci"; + +"dUyuGg" = "Objavi na Mastodonu"; + +"dYQ5NN" = "Javno"; + +"ehFLjY" = "Samo sledilci"; + +"gfePDu" = "Objava je spodletela. ${failureReason}"; + +"k7dbKQ" = "Uspešno poslana objava."; + +"oGiqmY-dYQ5NN" = "Da ne bo nesporazuma - želeli ste \"Javno\"?"; + +"oGiqmY-ehFLjY" = "Da ne bo nesporazuma - želeli ste \"Samo sledilci\"?"; + +"rM6dvp" = "URL"; + +"ryJLwG" = "Uspešno poslana objava. "; diff --git a/MastodonIntent/sl.lproj/Intents.stringsdict b/MastodonIntent/sl.lproj/Intents.stringsdict new file mode 100644 index 000000000..5a39d5e64 --- /dev/null +++ b/MastodonIntent/sl.lproj/Intents.stringsdict @@ -0,0 +1,54 @@ + + + + + There are ${count} options matching ‘${content}’. - 2 + + NSStringLocalizedFormatKey + There are %#@count_option@ matching ‘${content}’. + count_option + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + %ld + zero + 0 options + one + 1 option + two + 2 options + few + %ld options + many + %ld options + other + %ld options + + + There are ${count} options matching ‘${visibility}’. + + NSStringLocalizedFormatKey + There are %#@count_option@ matching ‘${visibility}’. + count_option + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + %ld + zero + 0 options + one + 1 option + two + 2 options + few + %ld options + many + %ld options + other + %ld options + + + + diff --git a/MastodonSDK/Package.resolved b/MastodonSDK/Package.resolved index 06843faa3..4a4fa51de 100644 --- a/MastodonSDK/Package.resolved +++ b/MastodonSDK/Package.resolved @@ -1,241 +1,257 @@ { - "object": { - "pins": [ - { - "package": "Alamofire", - "repositoryURL": "https://github.com/Alamofire/Alamofire.git", - "state": { - "branch": null, - "revision": "8dd85aee02e39dd280c75eef88ffdb86eed4b07b", - "version": "5.6.2" - } - }, - { - "package": "AlamofireImage", - "repositoryURL": "https://github.com/Alamofire/AlamofireImage.git", - "state": { - "branch": null, - "revision": "98cbb00ce0ec5fc8e52a5b50a6bfc08d3e5aee10", - "version": "4.2.0" - } - }, - { - "package": "CommonOSLog", - "repositoryURL": "https://github.com/MainasuK/CommonOSLog", - "state": { - "branch": null, - "revision": "c121624a30698e9886efe38aebb36ff51c01b6c2", - "version": "0.1.1" - } - }, - { - "package": "FaviconFinder", - "repositoryURL": "https://github.com/will-lumley/FaviconFinder.git", - "state": { - "branch": null, - "revision": "1f74844f77f79b95c0bb0130b3a87d4f340e6d3a", - "version": "3.3.0" - } - }, - { - "package": "FLAnimatedImage", - "repositoryURL": "https://github.com/Flipboard/FLAnimatedImage.git", - "state": { - "branch": null, - "revision": "d4f07b6f164d53c1212c3e54d6460738b1981e9f", - "version": "1.0.17" - } - }, - { - "package": "FPSIndicator", - "repositoryURL": "https://github.com/MainasuK/FPSIndicator.git", - "state": { - "branch": null, - "revision": "e4a5067ccd5293b024c767f09e51056afd4a4796", - "version": "1.1.0" - } - }, - { - "package": "Fuzi", - "repositoryURL": "https://github.com/cezheng/Fuzi.git", - "state": { - "branch": null, - "revision": "f08c8323da21e985f3772610753bcfc652c2103f", - "version": "3.1.3" - } - }, - { - "package": "KeychainAccess", - "repositoryURL": "https://github.com/kishikawakatsumi/KeychainAccess.git", - "state": { - "branch": null, - "revision": "84e546727d66f1adc5439debad16270d0fdd04e7", - "version": "4.2.2" - } - }, - { - "package": "MetaTextKit", - "repositoryURL": "https://github.com/TwidereProject/MetaTextKit.git", - "state": { - "branch": null, - "revision": "dcd5255d6930c2fab408dc8562c577547e477624", - "version": "2.2.5" - } - }, - { - "package": "Nuke", - "repositoryURL": "https://github.com/kean/Nuke.git", - "state": { - "branch": null, - "revision": "a002b7fd786f2df2ed4333fe73a9727499fd9d97", - "version": "10.11.2" - } - }, - { - "package": "NukeFLAnimatedImagePlugin", - "repositoryURL": "https://github.com/kean/Nuke-FLAnimatedImage-Plugin.git", - "state": { - "branch": null, - "revision": "b59c346a7d536336db3b0f12c72c6e53ee709e16", - "version": "8.0.0" - } - }, - { - "package": "Pageboy", - "repositoryURL": "https://github.com/uias/Pageboy", - "state": { - "branch": null, - "revision": "af8fa81788b893205e1ff42ddd88c5b0b315d7c5", - "version": "3.7.0" - } - }, - { - "package": "PanModal", - "repositoryURL": "https://github.com/slackhq/PanModal.git", - "state": { - "branch": null, - "revision": "b012aecb6b67a8e46369227f893c12544846613f", - "version": "1.2.7" - } - }, - { - "package": "SDWebImage", - "repositoryURL": "https://github.com/SDWebImage/SDWebImage.git", - "state": { - "branch": null, - "revision": "9248fe561a2a153916fb9597e3af4434784c6d32", - "version": "5.13.4" - } - }, - { - "package": "swift-collections", - "repositoryURL": "https://github.com/apple/swift-collections.git", - "state": { - "branch": null, - "revision": "f504716c27d2e5d4144fa4794b12129301d17729", - "version": "1.0.3" - } - }, - { - "package": "swift-nio", - "repositoryURL": "https://github.com/apple/swift-nio.git", - "state": { - "branch": null, - "revision": "546610d52b19be3e19935e0880bb06b9c03f5cef", - "version": "1.14.4" - } - }, - { - "package": "swift-nio-zlib-support", - "repositoryURL": "https://github.com/apple/swift-nio-zlib-support.git", - "state": { - "branch": null, - "revision": "37760e9a52030bb9011972c5213c3350fa9d41fd", - "version": "1.0.0" - } - }, - { - "package": "SwiftSoup", - "repositoryURL": "https://github.com/scinfu/SwiftSoup.git", - "state": { - "branch": null, - "revision": "6778575285177365cbad3e5b8a72f2a20583cfec", - "version": "2.4.3" - } - }, - { - "package": "Introspect", - "repositoryURL": "https://github.com/siteline/SwiftUI-Introspect.git", - "state": { - "branch": null, - "revision": "f2616860a41f9d9932da412a8978fec79c06fe24", - "version": "0.1.4" - } - }, - { - "package": "SwiftyJSON", - "repositoryURL": "https://github.com/SwiftyJSON/SwiftyJSON.git", - "state": { - "branch": null, - "revision": "b3dcd7dbd0d488e1a7077cb33b00f2083e382f07", - "version": "5.0.1" - } - }, - { - "package": "TabBarPager", - "repositoryURL": "https://github.com/TwidereProject/TabBarPager.git", - "state": { - "branch": null, - "revision": "488aa66d157a648901b61721212c0dec23d27ee5", - "version": "0.1.0" - } - }, - { - "package": "Tabman", - "repositoryURL": "https://github.com/uias/Tabman", - "state": { - "branch": null, - "revision": "4a4f7c755b875ffd4f9ef10d67a67883669d2465", - "version": "2.13.0" - } - }, - { - "package": "ThirdPartyMailer", - "repositoryURL": "https://github.com/vtourraine/ThirdPartyMailer.git", - "state": { - "branch": null, - "revision": "44c1cfaa6969963f22691aa67f88a69e3b6d651f", - "version": "2.1.0" - } - }, - { - "package": "TOCropViewController", - "repositoryURL": "https://github.com/TimOliver/TOCropViewController.git", - "state": { - "branch": null, - "revision": "d0470491f56e734731bbf77991944c0dfdee3e0e", - "version": "2.6.1" - } - }, - { - "package": "UIHostingConfigurationBackport", - "repositoryURL": "https://github.com/woxtu/UIHostingConfigurationBackport.git", - "state": { - "branch": null, - "revision": "6091f2d38faa4b24fc2ca0389c651e2f666624a3", - "version": "0.1.0" - } - }, - { - "package": "UITextView+Placeholder", - "repositoryURL": "https://github.com/MainasuK/UITextView-Placeholder.git", - "state": { - "branch": null, - "revision": "20f513ded04a040cdf5467f0891849b1763ede3b", - "version": "1.4.1" - } + "pins" : [ + { + "identity" : "alamofire", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Alamofire/Alamofire.git", + "state" : { + "revision" : "8dd85aee02e39dd280c75eef88ffdb86eed4b07b", + "version" : "5.6.2" } - ] - }, - "version": 1 + }, + { + "identity" : "alamofireimage", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Alamofire/AlamofireImage.git", + "state" : { + "revision" : "98cbb00ce0ec5fc8e52a5b50a6bfc08d3e5aee10", + "version" : "4.2.0" + } + }, + { + "identity" : "commonoslog", + "kind" : "remoteSourceControl", + "location" : "https://github.com/MainasuK/CommonOSLog", + "state" : { + "revision" : "c121624a30698e9886efe38aebb36ff51c01b6c2", + "version" : "0.1.1" + } + }, + { + "identity" : "faviconfinder", + "kind" : "remoteSourceControl", + "location" : "https://github.com/will-lumley/FaviconFinder.git", + "state" : { + "revision" : "1f74844f77f79b95c0bb0130b3a87d4f340e6d3a", + "version" : "3.3.0" + } + }, + { + "identity" : "flanimatedimage", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Flipboard/FLAnimatedImage.git", + "state" : { + "revision" : "d4f07b6f164d53c1212c3e54d6460738b1981e9f", + "version" : "1.0.17" + } + }, + { + "identity" : "fpsindicator", + "kind" : "remoteSourceControl", + "location" : "https://github.com/MainasuK/FPSIndicator.git", + "state" : { + "revision" : "e4a5067ccd5293b024c767f09e51056afd4a4796", + "version" : "1.1.0" + } + }, + { + "identity" : "fuzi", + "kind" : "remoteSourceControl", + "location" : "https://github.com/cezheng/Fuzi.git", + "state" : { + "revision" : "f08c8323da21e985f3772610753bcfc652c2103f", + "version" : "3.1.3" + } + }, + { + "identity" : "keychainaccess", + "kind" : "remoteSourceControl", + "location" : "https://github.com/kishikawakatsumi/KeychainAccess.git", + "state" : { + "revision" : "84e546727d66f1adc5439debad16270d0fdd04e7", + "version" : "4.2.2" + } + }, + { + "identity" : "kingfisher", + "kind" : "remoteSourceControl", + "location" : "https://github.com/onevcat/Kingfisher.git", + "state" : { + "revision" : "44e891bdb61426a95e31492a67c7c0dfad1f87c5", + "version" : "7.4.1" + } + }, + { + "identity" : "metatextkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/TwidereProject/MetaTextKit.git", + "state" : { + "revision" : "dcd5255d6930c2fab408dc8562c577547e477624", + "version" : "2.2.5" + } + }, + { + "identity" : "nextlevelsessionexporter", + "kind" : "remoteSourceControl", + "location" : "https://github.com/NextLevel/NextLevelSessionExporter.git", + "state" : { + "revision" : "b6c0cce1aa37fe1547d694f958fac3c3524b74da", + "version" : "0.4.6" + } + }, + { + "identity" : "nuke", + "kind" : "remoteSourceControl", + "location" : "https://github.com/kean/Nuke.git", + "state" : { + "revision" : "a002b7fd786f2df2ed4333fe73a9727499fd9d97", + "version" : "10.11.2" + } + }, + { + "identity" : "nuke-flanimatedimage-plugin", + "kind" : "remoteSourceControl", + "location" : "https://github.com/kean/Nuke-FLAnimatedImage-Plugin.git", + "state" : { + "revision" : "b59c346a7d536336db3b0f12c72c6e53ee709e16", + "version" : "8.0.0" + } + }, + { + "identity" : "pageboy", + "kind" : "remoteSourceControl", + "location" : "https://github.com/uias/Pageboy", + "state" : { + "revision" : "af8fa81788b893205e1ff42ddd88c5b0b315d7c5", + "version" : "3.7.0" + } + }, + { + "identity" : "panmodal", + "kind" : "remoteSourceControl", + "location" : "https://github.com/slackhq/PanModal.git", + "state" : { + "revision" : "b012aecb6b67a8e46369227f893c12544846613f", + "version" : "1.2.7" + } + }, + { + "identity" : "sdwebimage", + "kind" : "remoteSourceControl", + "location" : "https://github.com/SDWebImage/SDWebImage.git", + "state" : { + "revision" : "9248fe561a2a153916fb9597e3af4434784c6d32", + "version" : "5.13.4" + } + }, + { + "identity" : "stripes", + "kind" : "remoteSourceControl", + "location" : "https://github.com/eneko/Stripes.git", + "state" : { + "revision" : "d533fd44b8043a3abbf523e733599173d6f98c11", + "version" : "0.2.0" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections.git", + "state" : { + "revision" : "f504716c27d2e5d4144fa4794b12129301d17729", + "version" : "1.0.3" + } + }, + { + "identity" : "swift-nio", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio.git", + "state" : { + "revision" : "546610d52b19be3e19935e0880bb06b9c03f5cef", + "version" : "1.14.4" + } + }, + { + "identity" : "swift-nio-zlib-support", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-zlib-support.git", + "state" : { + "revision" : "37760e9a52030bb9011972c5213c3350fa9d41fd", + "version" : "1.0.0" + } + }, + { + "identity" : "swiftsoup", + "kind" : "remoteSourceControl", + "location" : "https://github.com/scinfu/SwiftSoup.git", + "state" : { + "revision" : "6778575285177365cbad3e5b8a72f2a20583cfec", + "version" : "2.4.3" + } + }, + { + "identity" : "swiftui-introspect", + "kind" : "remoteSourceControl", + "location" : "https://github.com/siteline/SwiftUI-Introspect.git", + "state" : { + "revision" : "f2616860a41f9d9932da412a8978fec79c06fe24", + "version" : "0.1.4" + } + }, + { + "identity" : "tabbarpager", + "kind" : "remoteSourceControl", + "location" : "https://github.com/TwidereProject/TabBarPager.git", + "state" : { + "revision" : "488aa66d157a648901b61721212c0dec23d27ee5", + "version" : "0.1.0" + } + }, + { + "identity" : "tabman", + "kind" : "remoteSourceControl", + "location" : "https://github.com/uias/Tabman", + "state" : { + "revision" : "4a4f7c755b875ffd4f9ef10d67a67883669d2465", + "version" : "2.13.0" + } + }, + { + "identity" : "thirdpartymailer", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vtourraine/ThirdPartyMailer.git", + "state" : { + "revision" : "44c1cfaa6969963f22691aa67f88a69e3b6d651f", + "version" : "2.1.0" + } + }, + { + "identity" : "tocropviewcontroller", + "kind" : "remoteSourceControl", + "location" : "https://github.com/TimOliver/TOCropViewController.git", + "state" : { + "revision" : "d0470491f56e734731bbf77991944c0dfdee3e0e", + "version" : "2.6.1" + } + }, + { + "identity" : "uihostingconfigurationbackport", + "kind" : "remoteSourceControl", + "location" : "https://github.com/woxtu/UIHostingConfigurationBackport.git", + "state" : { + "revision" : "6091f2d38faa4b24fc2ca0389c651e2f666624a3", + "version" : "0.1.0" + } + }, + { + "identity" : "uitextview-placeholder", + "kind" : "remoteSourceControl", + "location" : "https://github.com/MainasuK/UITextView-Placeholder.git", + "state" : { + "revision" : "20f513ded04a040cdf5467f0891849b1763ede3b", + "version" : "1.4.1" + } + } + ], + "version" : 2 } diff --git a/MastodonSDK/Package.swift b/MastodonSDK/Package.swift index 7a4201894..32dbfb2c8 100644 --- a/MastodonSDK/Package.swift +++ b/MastodonSDK/Package.swift @@ -3,25 +3,35 @@ import PackageDescription +let publicLibraryTargets = [ + "CoreDataStack", + "MastodonAsset", + "MastodonCommon", + "MastodonCore", + "MastodonExtension", + "MastodonLocalization", + "MastodonSDK", + "MastodonUI", +] + let package = Package( name: "MastodonSDK", defaultLocalization: "en", platforms: [ - .iOS(.v14), + .iOS(.v15), ], products: [ + // Static Library .library( name: "MastodonSDK", - targets: [ - "CoreDataStack", - "MastodonAsset", - "MastodonCommon", - "MastodonCore", - "MastodonExtension", - "MastodonLocalization", - "MastodonSDK", - "MastodonUI", - ]) + targets: publicLibraryTargets + ), + // Dynamic Library + .library( + name: "MastodonSDKDynamic", + type: .dynamic, + targets: publicLibraryTargets + ) ], dependencies: [ .package(name: "ArkanaKeys", path: "../dependencies/ArkanaKeys"), @@ -32,7 +42,6 @@ let package = Package( .package(url: "https://github.com/Alamofire/AlamofireImage.git", from: "4.1.0"), .package(url: "https://github.com/apple/swift-collections.git", from: "1.0.3"), .package(url: "https://github.com/apple/swift-nio.git", from: "1.0.0"), - .package(url: "https://github.com/cezheng/Fuzi.git", from: "3.1.3"), .package(url: "https://github.com/Flipboard/FLAnimatedImage.git", from: "1.0.0"), .package(url: "https://github.com/kean/Nuke-FLAnimatedImage-Plugin.git", from: "8.0.0"), .package(url: "https://github.com/kean/Nuke.git", from: "10.3.1"), diff --git a/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/.xccurrentversion b/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/.xccurrentversion index 1d5ea989f..b2d4c7f6e 100644 --- a/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/.xccurrentversion +++ b/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - CoreData 4.xcdatamodel + CoreData 7.xcdatamodel diff --git a/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/CoreData 5.xcdatamodel/contents b/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/CoreData 5.xcdatamodel/contents new file mode 100644 index 000000000..9f07cca78 --- /dev/null +++ b/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/CoreData 5.xcdatamodel/contents @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/CoreData 6.xcdatamodel/contents b/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/CoreData 6.xcdatamodel/contents new file mode 100644 index 000000000..30e89ccb4 --- /dev/null +++ b/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/CoreData 6.xcdatamodel/contents @@ -0,0 +1,260 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/CoreData 7.xcdatamodel/contents b/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/CoreData 7.xcdatamodel/contents new file mode 100644 index 000000000..7198c75c6 --- /dev/null +++ b/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/CoreData 7.xcdatamodel/contents @@ -0,0 +1,278 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Card.swift b/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Card.swift new file mode 100644 index 000000000..64f5e2120 --- /dev/null +++ b/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Card.swift @@ -0,0 +1,180 @@ +// +// Card.swift +// CoreDataStack +// +// Created by Kyle Bashour on 11/23/22. +// + +import Foundation +import CoreData + +public final class Card: NSManagedObject { + // sourcery: autoGenerateProperty + @NSManaged public private(set) var urlRaw: String + public var url: URL? { + URL(string: urlRaw) + } + + // sourcery: autoGenerateProperty + @NSManaged public private(set) var title: String + // sourcery: autoGenerateProperty + @NSManaged public private(set) var desc: String + + @NSManaged public private(set) var typeRaw: String + // sourcery: autoGenerateProperty + public var type: MastodonCardType { + get { MastodonCardType(rawValue: typeRaw) } + set { typeRaw = newValue.rawValue } + } + + // sourcery: autoGenerateProperty + @NSManaged public private(set) var authorName: String? + // sourcery: autoGenerateProperty + @NSManaged public private(set) var authorURLRaw: String? + // sourcery: autoGenerateProperty + @NSManaged public private(set) var providerName: String? + // sourcery: autoGenerateProperty + @NSManaged public private(set) var providerURLRaw: String? + // sourcery: autoGenerateProperty + @NSManaged public private(set) var width: Int64 + // sourcery: autoGenerateProperty + @NSManaged public private(set) var height: Int64 + // sourcery: autoGenerateProperty + @NSManaged public private(set) var image: String? + public var imageURL: URL? { + image.flatMap(URL.init) + } + + // sourcery: autoGenerateProperty + @NSManaged public private(set) var embedURLRaw: String? + // sourcery: autoGenerateProperty + @NSManaged public private(set) var blurhash: String? + // sourcery: autoGenerateProperty + @NSManaged public private(set) var html: String? + + // sourcery: autoGenerateRelationship + @NSManaged public private(set) var status: Status +} + +extension Card { + + @discardableResult + public static func insert( + into context: NSManagedObjectContext, + property: Property + ) -> Card { + let object: Card = context.insertObject() + + object.configure(property: property) + + return object + } + +} + +extension Card: Managed { + public static var defaultSortDescriptors: [NSSortDescriptor] { + return [] + } +} + +// MARK: - AutoGenerateProperty +extension Card: AutoGenerateProperty { + // sourcery:inline:Card.AutoGenerateProperty + + // Generated using Sourcery + // DO NOT EDIT + public struct Property { + public let urlRaw: String + public let title: String + public let desc: String + public let type: MastodonCardType + public let authorName: String? + public let authorURLRaw: String? + public let providerName: String? + public let providerURLRaw: String? + public let width: Int64 + public let height: Int64 + public let image: String? + public let embedURLRaw: String? + public let blurhash: String? + public let html: String? + + public init( + urlRaw: String, + title: String, + desc: String, + type: MastodonCardType, + authorName: String?, + authorURLRaw: String?, + providerName: String?, + providerURLRaw: String?, + width: Int64, + height: Int64, + image: String?, + embedURLRaw: String?, + blurhash: String?, + html: String? + ) { + self.urlRaw = urlRaw + self.title = title + self.desc = desc + self.type = type + self.authorName = authorName + self.authorURLRaw = authorURLRaw + self.providerName = providerName + self.providerURLRaw = providerURLRaw + self.width = width + self.height = height + self.image = image + self.embedURLRaw = embedURLRaw + self.blurhash = blurhash + self.html = html + } + } + + public func configure(property: Property) { + self.urlRaw = property.urlRaw + self.title = property.title + self.desc = property.desc + self.type = property.type + self.authorName = property.authorName + self.authorURLRaw = property.authorURLRaw + self.providerName = property.providerName + self.providerURLRaw = property.providerURLRaw + self.width = property.width + self.height = property.height + self.image = property.image + self.embedURLRaw = property.embedURLRaw + self.blurhash = property.blurhash + self.html = property.html + } + + public func update(property: Property) { + } + + // sourcery:end +} + +// MARK: - AutoGenerateRelationship +extension Card: AutoGenerateRelationship { + // sourcery:inline:Card.AutoGenerateRelationship + + // Generated using Sourcery + // DO NOT EDIT + public struct Relationship { + public let status: Status + + public init( + status: Status + ) { + self.status = status + } + } + + public func configure(relationship: Relationship) { + self.status = relationship.status + } + + // sourcery:end +} diff --git a/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Instance.swift b/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Instance.swift index 8976097ef..c11a92b76 100644 --- a/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Instance.swift +++ b/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Instance.swift @@ -10,12 +10,14 @@ import CoreData public final class Instance: NSManagedObject { @NSManaged public var domain: String - + @NSManaged public var version: String? + @NSManaged public private(set) var createdAt: Date @NSManaged public private(set) var updatedAt: Date @NSManaged public private(set) var configurationRaw: Data? - + @NSManaged public private(set) var configurationV2Raw: Data? + // MARK: one-to-many relationships @NSManaged public var authentications: Set } @@ -35,6 +37,7 @@ extension Instance { ) -> Instance { let instance: Instance = context.insertObject() instance.domain = property.domain + instance.version = property.version return instance } @@ -42,6 +45,10 @@ extension Instance { self.configurationRaw = configurationRaw } + public func update(configurationV2Raw: Data?) { + self.configurationV2Raw = configurationV2Raw + } + public func didUpdate(at networkDate: Date) { self.updatedAt = networkDate } @@ -50,9 +57,11 @@ extension Instance { extension Instance { public struct Property { public let domain: String - - public init(domain: String) { + public let version: String? + + public init(domain: String, version: String?) { self.domain = domain + self.version = version } } } diff --git a/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/MastodonUser.swift b/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/MastodonUser.swift index 760985d68..fe668ccb9 100644 --- a/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/MastodonUser.swift +++ b/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/MastodonUser.swift @@ -77,6 +77,7 @@ final public class MastodonUser: NSManagedObject { @NSManaged public private(set) var votePollOptions: Set @NSManaged public private(set) var votePolls: Set // relationships + @NSManaged public private(set) var followedTags: Set @NSManaged public private(set) var following: Set @NSManaged public private(set) var followingBy: Set @NSManaged public private(set) var followRequested: Set diff --git a/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Status.swift b/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Status.swift index 0c7291913..08f58ca3f 100644 --- a/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Status.swift +++ b/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Status.swift @@ -11,6 +11,16 @@ import Foundation public final class Status: NSManagedObject { public typealias ID = String + public class TranslatedContent: NSObject { + public let content: String + public let provider: String? + + public init(content: String, provider: String?) { + self.content = content + self.provider = provider + } + } + // sourcery: autoGenerateProperty @NSManaged public private(set) var identifier: ID // sourcery: autoGenerateProperty @@ -84,7 +94,9 @@ public final class Status: NSManagedObject { @NSManaged public private(set) var pinnedBy: MastodonUser? // sourcery: autoGenerateRelationship @NSManaged public private(set) var poll: Poll? - + // sourcery: autoGenerateRelationship + @NSManaged public private(set) var card: Card? + // one-to-many relationship @NSManaged public private(set) var feeds: Set @@ -99,6 +111,9 @@ public final class Status: NSManagedObject { @NSManaged public private(set) var deletedAt: Date? // sourcery: autoUpdatableObject @NSManaged public private(set) var revealedAt: Date? + + // sourcery: autoUpdatableObject + @NSManaged public private(set) var translatedContent: TranslatedContent? } extension Status { @@ -379,15 +394,18 @@ extension Status: AutoGenerateRelationship { public let author: MastodonUser public let reblog: Status? public let poll: Poll? + public let card: Card? public init( author: MastodonUser, reblog: Status?, - poll: Poll? + poll: Poll?, + card: Card? ) { self.author = author self.reblog = reblog self.poll = poll + self.card = card } } @@ -395,6 +413,7 @@ extension Status: AutoGenerateRelationship { self.author = relationship.author self.reblog = relationship.reblog self.poll = relationship.poll + self.card = relationship.card } // sourcery:end } @@ -495,6 +514,11 @@ extension Status: AutoUpdatableObject { self.revealedAt = revealedAt } } + public func update(translatedContent: TranslatedContent?) { + if self.translatedContent != translatedContent { + self.translatedContent = translatedContent + } + } public func update(attachments: [MastodonAttachment]) { if self.attachments != attachments { self.attachments = attachments diff --git a/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Tag.swift b/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Tag.swift index b5c335db3..8332f3d4c 100644 --- a/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Tag.swift +++ b/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Tag.swift @@ -24,10 +24,13 @@ public final class Tag: NSManagedObject { @NSManaged public private(set) var name: String // sourcery: autoUpdatableObject, autoGenerateProperty @NSManaged public private(set) var url: String - + // sourcery: autoUpdatableObject, autoGenerateProperty + @NSManaged public private(set) var following: Bool + // one-to-one relationship // many-to-many relationship + @NSManaged public private(set) var followedBy: Set // one-to-many relationship @NSManaged public private(set) var searchHistories: Set @@ -88,7 +91,16 @@ public extension Tag { } static func predicate(name: String) -> NSPredicate { - NSPredicate(format: "%K == %@", #keyPath(Tag.name), name) + // use case-insensitive query as tags #CaN #BE #speLLed #USiNG #arbITRARy #cASe + NSPredicate(format: "%K MATCHES[c] %@", #keyPath(Tag.name), name) + } + + static func predicate(domain: String, following: Bool) -> NSPredicate { + NSPredicate(format: "%K == %@ AND %K == %d", #keyPath(Tag.domain), domain, #keyPath(Tag.following), following) + } + + static func predicate(followedBy user: MastodonUser) -> NSPredicate { + NSPredicate(format: "ANY %K.%K == %@", #keyPath(Tag.followedBy), #keyPath(MastodonUser.id), user.id) } static func predicate(domain: String, name: String) -> NSPredicate { @@ -97,6 +109,13 @@ public extension Tag { predicate(name: name), ]) } + + static func predicate(domain: String, following: Bool, by user: MastodonUser) -> NSPredicate { + NSCompoundPredicate(andPredicateWithSubpredicates: [ + predicate(domain: domain, following: following), + predicate(followedBy: user) + ]) + } } // MARK: - AutoGenerateProperty @@ -112,6 +131,7 @@ extension Tag: AutoGenerateProperty { public let updatedAt: Date public let name: String public let url: String + public let following: Bool public let histories: [MastodonTagHistory] public init( @@ -121,6 +141,7 @@ extension Tag: AutoGenerateProperty { updatedAt: Date, name: String, url: String, + following: Bool, histories: [MastodonTagHistory] ) { self.identifier = identifier @@ -129,6 +150,7 @@ extension Tag: AutoGenerateProperty { self.updatedAt = updatedAt self.name = name self.url = url + self.following = following self.histories = histories } } @@ -140,12 +162,14 @@ extension Tag: AutoGenerateProperty { self.updatedAt = property.updatedAt self.name = property.name self.url = property.url + self.following = property.following self.histories = property.histories } public func update(property: Property) { update(updatedAt: property.updatedAt) update(url: property.url) + update(following: property.following) update(histories: property.histories) } // sourcery:end @@ -167,12 +191,30 @@ extension Tag: AutoUpdatableObject { self.url = url } } + public func update(following: Bool) { + if self.following != following { + self.following = following + } + } public func update(histories: [MastodonTagHistory]) { if self.histories != histories { self.histories = histories } } // sourcery:end + + public func update(followed: Bool, by mastodonUser: MastodonUser) { + if following { + if !self.followedBy.contains(mastodonUser) { + self.mutableSetValue(forKey: #keyPath(Tag.followedBy)).add(mastodonUser) + } + } else { + if self.followedBy.contains(mastodonUser) { + self.mutableSetValue(forKey: #keyPath(Tag.followedBy)).remove(mastodonUser) + } + } + } + } diff --git a/MastodonSDK/Sources/CoreDataStack/Entity/Transient/MastodonCardType.swift b/MastodonSDK/Sources/CoreDataStack/Entity/Transient/MastodonCardType.swift new file mode 100644 index 000000000..59401cf4a --- /dev/null +++ b/MastodonSDK/Sources/CoreDataStack/Entity/Transient/MastodonCardType.swift @@ -0,0 +1,34 @@ +// +// MastodonCardType.swift +// CoreDataStack +// +// Created by Kyle Bashour on 11/23/22. +// + +import Foundation + +public enum MastodonCardType: RawRepresentable, Equatable { + case link + case photo + case video + + case _other(String) + + public init(rawValue: String) { + switch rawValue { + case "link": self = .link + case "photo": self = .photo + case "video": self = .video + default: self = ._other(rawValue) + } + } + + public var rawValue: String { + switch self { + case .link: return "link" + case .photo: return "photo" + case .video: return "video" + case ._other(let value): return value + } + } +} diff --git a/MastodonSDK/Sources/CoreDataStack/Entity/Transient/MastodonNotificationType.swift b/MastodonSDK/Sources/CoreDataStack/Entity/Transient/MastodonNotificationType.swift index 9e3029f26..455230d5e 100644 --- a/MastodonSDK/Sources/CoreDataStack/Entity/Transient/MastodonNotificationType.swift +++ b/MastodonSDK/Sources/CoreDataStack/Entity/Transient/MastodonNotificationType.swift @@ -7,7 +7,7 @@ import Foundation -public enum MastodonNotificationType: RawRepresentable { +public enum MastodonNotificationType: RawRepresentable, Equatable { case follow case followRequest case mention diff --git a/MastodonSDK/Sources/CoreDataStack/Extension/NSManagedObjectContext.swift b/MastodonSDK/Sources/CoreDataStack/Extension/NSManagedObjectContext.swift index b921de819..3b4818992 100644 --- a/MastodonSDK/Sources/CoreDataStack/Extension/NSManagedObjectContext.swift +++ b/MastodonSDK/Sources/CoreDataStack/Extension/NSManagedObjectContext.swift @@ -50,43 +50,16 @@ extension NSManagedObjectContext { extension NSManagedObjectContext { public func perform(block: @escaping () throws -> T) async throws -> T { - if #available(iOS 15.0, *) { - return try await perform(schedule: .enqueued) { - try block() - } - } else { - return try await withCheckedThrowingContinuation { continuation in - self.perform { - do { - let value = try block() - continuation.resume(returning: value) - } catch { - continuation.resume(throwing: error) - } - } - } // end return + return try await perform(schedule: .enqueued) { + try block() } } - + public func performChanges(block: @escaping () throws -> T) async throws -> T { - if #available(iOS 15.0, *) { - return try await perform(schedule: .enqueued) { - let value = try block() - try self.saveOrRollback() - return value - } - } else { - return try await withCheckedThrowingContinuation { continuation in - self.perform { - do { - let value = try block() - try self.saveOrRollback() - continuation.resume(returning: value) - } catch { - continuation.resume(throwing: error) - } - } - } // end return + return try await perform(schedule: .enqueued) { + let value = try block() + try self.saveOrRollback() + return value } } // end func } diff --git a/MastodonSDK/Sources/CoreDataStack/Extension/UIFont.swift b/MastodonSDK/Sources/CoreDataStack/Extension/UIFont.swift index a1a97a112..97fb2f397 100644 --- a/MastodonSDK/Sources/CoreDataStack/Extension/UIFont.swift +++ b/MastodonSDK/Sources/CoreDataStack/Extension/UIFont.swift @@ -22,9 +22,9 @@ extension UIFont { let fontDescription = UIFontDescriptor.preferredFontDescriptor(withTextStyle: textStyle).addingAttributes([ UIFontDescriptor.AttributeName.featureSettings: [ [ - UIFontDescriptor.FeatureKey.featureIdentifier: + UIFontDescriptor.FeatureKey.type: kNumberSpacingType, - UIFontDescriptor.FeatureKey.typeIdentifier: + UIFontDescriptor.FeatureKey.selector: kMonospacedNumbersSelector ] ] diff --git a/MastodonSDK/Sources/CoreDataStack/Utility/ManagedObjectRecord.swift b/MastodonSDK/Sources/CoreDataStack/Utility/ManagedObjectRecord.swift index 4910145cf..88b08fc3c 100644 --- a/MastodonSDK/Sources/CoreDataStack/Utility/ManagedObjectRecord.swift +++ b/MastodonSDK/Sources/CoreDataStack/Utility/ManagedObjectRecord.swift @@ -32,7 +32,7 @@ public class ManagedObjectRecord: Hashable { } extension Managed where Self: NSManagedObject { - public var asRecrod: ManagedObjectRecord { + public var asRecord: ManagedObjectRecord { return .init(objectID: objectID) } } diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Profile/About/bio.about.field.verified.link.colorset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Colors/Button/tagFollow.colorset/Contents.json similarity index 76% rename from MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Profile/About/bio.about.field.verified.link.colorset/Contents.json rename to MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Colors/Button/tagFollow.colorset/Contents.json index f5112f04f..e7e41c6ff 100644 --- a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Profile/About/bio.about.field.verified.link.colorset/Contents.json +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Colors/Button/tagFollow.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0.371", - "green" : "0.565", - "red" : "0.290" + "blue" : "0x38", + "green" : "0x29", + "red" : "0x2B" } }, "idiom" : "universal" @@ -23,9 +23,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0.603", - "green" : "0.742", - "red" : "0.476" + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFF" } }, "idiom" : "universal" diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Colors/Button/tagUnfollow.colorset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Colors/Button/tagUnfollow.colorset/Contents.json new file mode 100644 index 000000000..e7fbb60a6 --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Colors/Button/tagUnfollow.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFF" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x38", + "green" : "0x29", + "red" : "0x2B" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Profile/About/bio.about.field.verified.checkmark.colorset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Profile/About/bio.about.field.verified.text.colorset/Contents.json similarity index 100% rename from MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Profile/About/bio.about.field.verified.checkmark.colorset/Contents.json rename to MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Profile/About/bio.about.field.verified.text.colorset/Contents.json diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.black.imageset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.black.imageset/Contents.json deleted file mode 100644 index 3018f4b9a..000000000 --- a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.black.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "mastodon.logo.black.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.black.imageset/mastodon.logo.black.pdf b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.black.imageset/mastodon.logo.black.pdf deleted file mode 100644 index 773ed5e77..000000000 --- a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.black.imageset/mastodon.logo.black.pdf +++ /dev/null @@ -1,339 +0,0 @@ -%PDF-1.7 - -1 0 obj - << /BBox [ 0.000000 0.000000 480.000000 119.097778 ] - /Resources << >> - /Subtype /Form - /Length 2 0 R - /Group << /Type /Group - /S /Transparency - >> - /Type /XObject - >> -stream -/DeviceRGB CS -/DeviceRGB cs -q -1.000000 0.000000 -0.000000 1.000000 0.001709 -0.290527 cm -0.188235 0.533333 0.831373 scn -107.682541 47.532677 m -106.063889 39.212074 93.195923 30.106506 78.416977 28.341690 c -70.709908 27.421394 63.121937 26.576881 55.030510 26.946808 c -41.798027 27.553123 31.357124 30.104706 31.357124 30.104706 c -31.357124 28.818085 31.436523 27.591019 31.595320 26.443352 c -33.315022 13.385902 44.544495 12.602745 55.180283 12.238235 c -65.917122 11.870117 75.475624 14.885460 75.475624 14.885460 c -75.917725 5.177185 l -75.917725 5.177185 68.407349 1.147720 55.030510 0.406067 c -47.655472 0.000053 38.495773 0.590118 27.827501 3.414185 c -4.693666 9.538696 0.712913 34.197334 0.106597 59.224106 c --0.079267 66.653275 0.034417 73.660202 0.034417 79.517647 c -0.034417 105.105621 16.798328 112.606972 16.798328 112.606972 c -25.250660 116.488472 39.757122 118.121559 54.837425 118.244263 c -55.207348 118.244263 l -70.287651 118.121559 84.801338 116.488472 93.255470 112.606972 c -93.255470 112.606972 110.019386 105.105621 110.019386 79.517647 c -110.019386 79.517647 110.230507 60.638844 107.682541 47.532677 c -h -f -n -Q -q -1.000000 0.000000 -0.000000 1.000000 23.245789 45.780518 cm -0.121569 0.137255 0.168627 scn -0.000000 39.648720 m -0.000000 43.373230 3.018947 46.392181 6.743458 46.392181 c -10.467969 46.392181 13.486919 43.373230 13.486919 39.648720 c -13.486919 35.924210 10.467969 32.905262 6.743458 32.905262 c -3.018947 32.905262 0.000000 35.924210 0.000000 39.648720 c -h -96.718201 31.461655 m -96.718201 0.478188 l -84.443916 0.478188 l -84.443916 30.553986 l -84.443916 36.893234 81.776840 40.110676 76.440903 40.110676 c -70.541954 40.110676 67.586166 36.292332 67.586166 28.745865 c -67.586166 12.286915 l -55.384064 12.286915 l -55.384064 28.745865 l -55.384064 36.294136 52.426468 40.110676 46.529324 40.110676 c -41.193382 40.110676 38.524509 36.893234 38.524509 30.553986 c -38.524509 0.481804 l -26.252031 0.481804 l -26.252031 31.465263 l -26.252031 37.795486 27.865263 42.828270 31.104361 46.550980 c -34.442707 50.273685 38.816845 52.181053 44.246616 52.181053 c -50.526318 52.181053 55.284817 49.768425 58.430077 44.939552 c -61.486919 39.814735 l -64.543762 44.939552 l -67.689026 49.768425 72.445709 52.182858 78.727219 52.182858 c -84.156990 52.182858 88.529327 50.273685 91.869476 46.552784 c -95.108574 42.828270 96.720009 37.795490 96.720009 31.463459 c -96.718201 31.461655 l -h -139.005112 16.060150 m -141.536835 18.736240 142.758499 22.105263 142.758499 26.170826 c -142.758499 30.236389 141.536835 33.605415 139.005112 36.184063 c -136.565414 38.860153 133.468872 40.148571 129.715500 40.148571 c -125.962112 40.148571 122.867371 38.860153 120.427673 36.184063 c -117.987968 33.605415 116.768120 30.236389 116.768120 26.170826 c -116.768120 22.107067 117.987968 18.736240 120.427673 16.060150 c -122.867371 13.483311 125.962112 12.194881 129.715500 12.194881 c -133.468872 12.194881 136.565414 13.483311 139.005112 16.060150 c -139.005112 16.060150 l -h -142.758499 50.953987 m -154.859543 50.953987 l -154.859543 1.389473 l -142.754898 1.389473 l -142.754898 7.236088 l -139.097153 2.378342 134.030075 -0.000008 127.463463 -0.000008 c -121.176544 -0.000008 115.827965 2.477585 111.323906 7.533829 c -106.915489 12.590069 104.665268 18.835487 104.665268 26.169022 c -104.665268 33.405113 106.915489 39.650528 111.323906 44.706768 c -115.827965 49.763008 121.176544 52.339851 127.463463 52.339851 c -134.030075 52.339851 139.097153 49.959702 142.754898 45.103760 c -142.754898 50.950378 l -142.758499 50.953987 l -h -195.581955 27.062256 m -199.147659 24.387970 200.930527 20.620148 200.836685 15.863457 c -200.836685 10.807217 199.053848 6.840900 195.394302 4.065559 c -191.734756 1.389473 187.326309 0.001801 181.977737 0.001801 c -172.314575 0.001801 165.746170 3.966316 162.274292 11.797894 c -172.783768 18.041504 l -174.191299 13.781052 177.286011 11.599396 181.977737 11.599396 c -186.294128 11.599396 188.452347 12.988873 188.452347 15.863457 c -188.452347 17.944057 185.637283 19.827969 179.913376 21.313084 c -177.755188 21.908573 175.972336 22.504059 174.566620 23.000301 c -172.596085 23.792480 170.907074 24.685715 169.499557 25.775639 c -166.027664 28.451729 164.246628 32.019249 164.246628 36.579250 c -164.246628 41.436996 165.933823 45.302254 169.311874 48.079399 c -172.783752 50.953987 177.004501 52.341656 182.071579 52.341656 c -190.141342 52.341656 196.051117 48.871578 199.898331 41.833984 c -189.578354 35.886318 l -188.076996 39.255341 185.543457 40.940754 182.071579 40.940754 c -178.412018 40.940754 176.630966 39.553085 176.630966 36.876991 c -176.630966 34.796391 179.444214 32.912483 185.168121 31.425564 c -189.578339 30.433083 193.048416 28.947971 195.581955 27.064060 c -195.581955 27.062256 l -h -234.052322 38.661655 m -223.449036 38.661655 l -223.449036 18.043308 l -223.449036 15.563908 224.389175 14.078796 226.170227 13.384060 c -227.483917 12.887817 230.111267 12.788570 234.052322 12.987068 c -234.052322 1.389473 l -225.890518 0.396988 219.978958 1.190971 216.507080 3.868866 c -213.037003 6.445709 211.346176 11.202404 211.346176 18.043308 c -211.346176 38.661655 l -203.184372 38.661655 l -203.184372 50.953987 l -211.346176 50.953987 l -211.346176 60.965416 l -223.449036 64.830681 l -223.449036 50.953987 l -234.052322 50.953987 l -234.052322 38.661655 l -234.052322 38.661655 l -h -272.614716 16.357895 m -275.054413 18.936543 276.274261 22.208122 276.274261 26.172634 c -276.274261 30.137146 275.054413 33.408722 272.614716 35.985565 c -270.176819 38.562408 267.174133 39.850830 263.514587 39.850830 c -259.855011 39.850830 256.854126 38.562408 254.414429 35.985565 c -252.068558 33.309475 250.848709 30.037895 250.848709 26.172634 c -250.848709 22.305565 252.068558 19.033985 254.414429 16.357895 c -256.854126 13.781052 259.855011 12.492626 263.514587 12.492626 c -267.174133 12.492626 270.176819 13.781052 272.614716 16.357895 c -h -245.875488 7.535637 m -241.091721 12.590076 238.745850 18.736240 238.745850 26.172634 c -238.745850 33.507973 241.091721 39.652328 245.875488 44.708572 c -250.661057 49.763008 256.570801 52.341656 263.514587 52.341656 c -270.458344 52.341656 276.368134 49.763008 281.153687 44.708572 c -285.939240 39.652328 288.377136 33.408722 288.377136 26.172634 c -288.377136 18.835491 285.939240 12.590076 281.153687 7.535637 c -276.368134 2.479401 270.552155 0.001801 263.514587 0.001801 c -256.476990 0.001801 250.661057 2.479401 245.875488 7.535637 c -h -328.818054 16.060150 m -331.257751 18.736240 332.475769 22.105263 332.475769 26.170826 c -332.475769 30.236389 331.257751 33.605415 328.818054 36.184063 c -326.378357 38.860153 323.281799 40.148571 319.528412 40.148571 c -315.775024 40.148571 312.680298 38.860153 310.146759 36.184063 c -307.708893 33.605415 306.487213 30.236389 306.487213 26.170826 c -306.487213 22.107067 307.708893 18.736240 310.146759 16.060150 c -312.680298 13.483311 315.870667 12.194881 319.528412 12.194881 c -323.281799 12.194881 326.378357 13.483311 328.818054 16.060150 c -328.818054 16.060150 l -h -332.475769 70.780151 m -344.580475 70.780151 l -344.580475 1.389473 l -332.475769 1.389473 l -332.475769 7.236088 l -328.911865 2.378342 323.844818 -0.000008 317.278198 -0.000008 c -310.991272 -0.000008 305.550690 2.477585 301.046631 7.533829 c -296.636414 12.590069 294.384369 18.835487 294.384369 26.169022 c -294.384369 33.405113 296.636414 39.650528 301.046631 44.706768 c -305.550690 49.763008 310.991272 52.339851 317.278198 52.339851 c -323.844818 52.339851 328.911865 49.959702 332.475769 45.103760 c -332.475769 70.780151 l -332.475769 70.780151 l -h -387.083923 16.357895 m -389.523621 18.936543 390.743469 22.208122 390.743469 26.172634 c -390.743469 30.137146 389.523621 33.408722 387.083923 35.985565 c -384.644226 38.562408 381.643311 39.850830 377.983765 39.850830 c -374.324219 39.850830 371.321503 38.562408 368.881805 35.985565 c -366.535950 33.309475 365.316101 30.037895 365.316101 26.172634 c -365.316101 22.305565 366.535950 19.033985 368.881805 16.357895 c -371.321503 13.781052 374.324219 12.492626 377.983765 12.492626 c -381.643311 12.492626 384.644226 13.781052 387.083923 16.357895 c -387.083923 16.357895 l -h -360.344666 7.535637 m -355.559082 12.590076 353.215027 18.736240 353.215027 26.172634 c -353.215027 33.507973 355.559082 39.652328 360.344666 44.708572 c -365.130219 49.763008 371.040009 52.341656 377.983765 52.341656 c -384.925720 52.341656 390.837280 49.763008 395.622864 44.708572 c -400.408417 39.652328 402.846313 33.408722 402.846313 26.172634 c -402.846313 18.835491 400.408417 12.590076 395.622864 7.535637 c -390.837280 2.479401 385.019562 0.001801 377.983765 0.001801 c -370.946167 0.001801 365.130219 2.479401 360.344666 7.535637 c -360.344666 7.535637 l -h -455.202423 31.822556 m -455.202423 1.389473 l -443.097748 1.389473 l -443.097748 30.236389 l -443.097748 33.507969 442.255035 35.985565 440.566010 37.869476 c -438.970825 39.553085 436.718811 40.446320 433.809937 40.446320 c -426.960022 40.446320 423.489929 36.382557 423.489929 28.153984 c -423.489929 1.389473 l -411.387054 1.389473 l -411.387054 50.953987 l -423.489929 50.953987 l -423.489929 45.403309 l -426.398773 50.060753 430.994904 52.341656 437.469482 52.341656 c -442.630371 52.341656 446.851135 50.556992 450.135345 46.888420 c -453.513397 43.221657 455.202423 38.264664 455.202423 31.820751 c -455.202423 31.822556 l -h -f -n -Q - -endstream -endobj - -2 0 obj - 9224 -endobj - -3 0 obj - << /BBox [ 0.000000 0.000000 480.000000 119.097778 ] - /Resources << >> - /Subtype /Form - /Length 4 0 R - /Group << /Type /Group - /S /Transparency - >> - /Type /XObject - >> -stream -/DeviceRGB CS -/DeviceRGB cs -q -1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm -0.000000 0.000000 0.000000 scn -0.000000 119.097778 m -480.000000 119.097778 l -480.000000 0.000031 l -0.000000 0.000031 l -0.000000 119.097778 l -h -f -n -Q - -endstream -endobj - -4 0 obj - 237 -endobj - -5 0 obj - << /XObject << /X1 1 0 R >> - /ExtGState << /E1 << /SMask << /Type /Mask - /G 3 0 R - /S /Alpha - >> - /Type /ExtGState - >> >> - >> -endobj - -6 0 obj - << /Length 7 0 R >> -stream -/DeviceRGB CS -/DeviceRGB cs -q -/E1 gs -/X1 Do -Q - -endstream -endobj - -7 0 obj - 46 -endobj - -8 0 obj - << /Annots [] - /Type /Page - /MediaBox [ 0.000000 0.000000 480.000000 119.097778 ] - /Resources 5 0 R - /Contents 6 0 R - /Parent 9 0 R - >> -endobj - -9 0 obj - << /Kids [ 8 0 R ] - /Count 1 - /Type /Pages - >> -endobj - -10 0 obj - << /Type /Catalog - /Pages 9 0 R - >> -endobj - -xref -0 11 -0000000000 65535 f -0000000010 00000 n -0000009484 00000 n -0000009507 00000 n -0000009994 00000 n -0000010016 00000 n -0000010314 00000 n -0000010416 00000 n -0000010437 00000 n -0000010612 00000 n -0000010686 00000 n -trailer -<< /ID [ (some) (id) ] - /Root 10 0 R - /Size 11 ->> -startxref -10746 -%%EOF \ No newline at end of file diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.black.large.imageset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.black.large.imageset/Contents.json deleted file mode 100644 index fefc19832..000000000 --- a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.black.large.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "mastodon.logo.black.large.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.black.large.imageset/mastodon.logo.black.large.pdf b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.black.large.imageset/mastodon.logo.black.large.pdf deleted file mode 100644 index b6244d04e..000000000 --- a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.black.large.imageset/mastodon.logo.black.large.pdf +++ /dev/null @@ -1,339 +0,0 @@ -%PDF-1.7 - -1 0 obj - << /BBox [ 0.000000 0.000000 960.000000 238.195496 ] - /Resources << >> - /Subtype /Form - /Length 2 0 R - /Group << /Type /Group - /S /Transparency - >> - /Type /XObject - >> -stream -/DeviceRGB CS -/DeviceRGB cs -q -1.000000 0.000000 -0.000000 1.000000 0.003357 -0.580933 cm -0.188235 0.533333 0.831373 scn -215.365082 95.065247 m -212.127777 78.424042 186.391846 60.212906 156.833954 56.683273 c -141.419815 54.842682 126.243874 53.153656 110.061020 53.893509 c -83.596054 55.106140 62.714249 60.209290 62.714249 60.209290 c -62.714249 57.636063 62.873047 55.181931 63.190639 52.886597 c -66.630043 26.771698 89.088989 25.205383 110.360565 24.476364 c -131.834244 23.740112 150.951248 29.770813 150.951248 29.770813 c -151.835449 10.354263 l -151.835449 10.354263 136.814697 2.295334 110.061020 0.812027 c -95.310944 0.000000 76.991547 1.180130 55.655003 6.828262 c -9.387332 19.077271 1.425826 68.394562 0.213194 118.448097 c --0.158535 133.306442 0.068834 147.320282 0.068834 159.035172 c -0.068834 210.211121 33.596657 225.213821 33.596657 225.213821 c -50.501320 232.976822 79.514244 236.242996 109.674850 236.488403 c -110.414696 236.488403 l -140.575302 236.242996 169.602676 232.976822 186.510941 225.213821 c -186.510941 225.213821 220.038773 210.211121 220.038773 159.035172 c -220.038773 159.035172 220.461014 121.277573 215.365082 95.065247 c -h -f -n -Q -q -1.000000 0.000000 -0.000000 1.000000 46.491440 91.561096 cm -0.121569 0.137255 0.168627 scn -0.000000 79.297432 m -0.000000 86.746460 6.037893 92.784363 13.486916 92.784363 c -20.935938 92.784363 26.973839 86.746460 26.973839 79.297432 c -26.973839 71.848412 20.935938 65.810516 13.486916 65.810516 c -6.037893 65.810516 0.000000 71.848412 0.000000 79.297432 c -h -193.436401 62.923302 m -193.436401 0.956360 l -168.887833 0.956360 l -168.887833 61.107964 l -168.887833 73.786461 163.553680 80.221344 152.881805 80.221344 c -141.083908 80.221344 135.172333 72.584648 135.172333 57.491714 c -135.172333 24.573814 l -110.768127 24.573814 l -110.768127 57.491714 l -110.768127 72.588257 104.852936 80.221344 93.058647 80.221344 c -82.386765 80.221344 77.049019 73.786461 77.049019 61.107964 c -77.049019 0.963593 l -52.504063 0.963593 l -52.504063 62.930511 l -52.504063 75.590965 55.730526 85.656540 62.208721 93.101944 c -68.885414 100.547363 77.633690 104.362106 88.493233 104.362106 c -101.052635 104.362106 110.569633 99.536835 116.860153 89.879089 c -122.973839 79.629471 l -129.087524 89.879089 l -135.378052 99.536835 144.891418 104.365707 157.454437 104.365707 c -168.313980 104.365707 177.058655 100.547363 183.738953 93.105560 c -190.217148 85.656540 193.440018 75.590973 193.440018 62.926910 c -193.436401 62.923302 l -h -278.010223 32.120285 m -283.073669 37.472473 285.516998 44.210510 285.516998 52.341637 c -285.516998 60.472771 283.073669 67.210823 278.010223 72.368118 c -273.130829 77.720299 266.937744 80.297134 259.431000 80.297134 c -251.924225 80.297134 245.734741 77.720299 240.855347 72.368118 c -235.975937 67.210823 233.536240 60.472771 233.536240 52.341637 c -233.536240 44.214119 235.975937 37.472473 240.855347 32.120285 c -245.734741 26.966606 251.924225 24.389748 259.431000 24.389748 c -266.937744 24.389748 273.130829 26.966606 278.010223 32.120285 c -278.010223 32.120285 l -h -285.516998 101.907967 m -309.719086 101.907967 l -309.719086 2.778915 l -285.509796 2.778915 l -285.509796 14.472160 l -278.194305 4.756668 268.060150 -0.000031 254.926926 -0.000031 c -242.353088 -0.000031 231.655930 4.955154 222.647812 15.067642 c -213.830978 25.180122 209.330536 37.670959 209.330536 52.338036 c -209.330536 66.810219 213.830978 79.301048 222.647812 89.413528 c -231.655930 99.526016 242.353088 104.679695 254.926926 104.679695 c -268.060150 104.679695 278.194305 99.919395 285.509796 90.207512 c -285.509796 101.900742 l -285.516998 101.907967 l -h -391.163910 54.124504 m -398.295319 48.775932 401.861053 41.240288 401.673370 31.726898 c -401.673370 21.614418 398.107697 13.681786 390.788605 8.131104 c -383.469513 2.778931 374.652618 0.003571 363.955475 0.003571 c -344.629150 0.003571 331.492340 7.932617 324.548584 23.595772 c -345.567535 36.082993 l -348.382599 27.562088 354.572021 23.198776 363.955475 23.198776 c -372.588257 23.198776 376.904694 25.977730 376.904694 31.726898 c -376.904694 35.888107 371.274567 39.655930 359.826752 42.626152 c -355.510376 43.817131 351.944672 45.008102 349.133240 46.000587 c -345.192169 47.584946 341.814148 49.371414 338.999115 51.551262 c -332.055328 56.903450 328.493256 64.038490 328.493256 73.158493 c -328.493256 82.873978 331.867645 90.604507 338.623749 96.158791 c -345.567505 101.907967 354.009003 104.683304 364.143158 104.683304 c -380.282684 104.683304 392.102234 97.743149 399.796661 83.667961 c -379.156708 71.772621 l -376.153992 78.510666 371.086914 81.881500 364.143158 81.881500 c -356.824036 81.881500 353.261932 79.106155 353.261932 73.753975 c -353.261932 69.592773 358.888428 65.824951 370.336243 62.851120 c -379.156677 60.866158 386.096832 57.895927 391.163910 54.128113 c -391.163910 54.124504 l -h -468.104645 77.323303 m -446.898071 77.323303 l -446.898071 36.086601 l -446.898071 31.127808 448.778351 28.157578 452.340454 26.768105 c -454.967834 25.775620 460.222534 25.577126 468.104645 25.974121 c -468.104645 2.778915 l -451.781036 0.793961 439.957916 2.381927 433.014160 7.737717 c -426.074005 12.891403 422.692352 22.404793 422.692352 36.086601 c -422.692352 77.323303 l -406.368744 77.323303 l -406.368744 101.907967 l -422.692352 101.907967 l -422.692352 121.930832 l -446.898071 129.661346 l -446.898071 101.907967 l -468.104645 101.907967 l -468.104645 77.323303 l -468.104645 77.323303 l -h -545.229431 32.715775 m -550.108826 37.873070 552.548523 44.416229 552.548523 52.345253 c -552.548523 60.274277 550.108826 66.817436 545.229431 71.971123 c -540.353638 77.124809 534.348267 79.701645 527.029175 79.701645 c -519.710022 79.701645 513.708252 77.124809 508.828857 71.971123 c -504.137115 66.618942 501.697418 60.075783 501.697418 52.345253 c -501.697418 44.611115 504.137115 38.067955 508.828857 32.715775 c -513.708252 27.562088 519.710022 24.985237 527.029175 24.985237 c -534.348267 24.985237 540.353638 27.562088 545.229431 32.715775 c -h -491.750977 15.071259 m -482.183441 25.180138 477.491699 37.472473 477.491699 52.345253 c -477.491699 67.015930 482.183441 79.304657 491.750977 89.417137 c -501.322113 99.526016 513.141602 104.683304 527.029175 104.683304 c -540.916687 104.683304 552.736267 99.526016 562.307373 89.417137 c -571.878479 79.304657 576.754272 66.817436 576.754272 52.345253 c -576.754272 37.670967 571.878479 25.180138 562.307373 15.071259 c -552.736267 4.958771 541.104309 0.003571 527.029175 0.003571 c -512.953979 0.003571 501.322113 4.958771 491.750977 15.071259 c -h -657.636108 32.120285 m -662.515503 37.472473 664.951538 44.210510 664.951538 52.341637 c -664.951538 60.472771 662.515503 67.210823 657.636108 72.368118 c -652.756714 77.720299 646.563599 80.297134 639.056824 80.297134 c -631.550049 80.297134 625.360596 77.720299 620.293518 72.368118 c -615.417786 67.210823 612.974426 60.472771 612.974426 52.341637 c -612.974426 44.214119 615.417786 37.472473 620.293518 32.120285 c -625.360596 26.966606 631.741333 24.389748 639.056824 24.389748 c -646.563599 24.389748 652.756714 26.966606 657.636108 32.120285 c -657.636108 32.120285 l -h -664.951538 141.560303 m -689.160950 141.560303 l -689.160950 2.778915 l -664.951538 2.778915 l -664.951538 14.472160 l -657.823730 4.756668 647.689636 -0.000031 634.556396 -0.000031 c -621.982544 -0.000031 611.101379 4.955154 602.093262 15.067642 c -593.272827 25.180122 588.768738 37.670959 588.768738 52.338036 c -588.768738 66.810219 593.272827 79.301048 602.093262 89.413528 c -611.101379 99.526016 621.982544 104.679695 634.556396 104.679695 c -647.689636 104.679695 657.823730 99.919395 664.951538 90.207512 c -664.951538 141.560303 l -664.951538 141.560303 l -h -774.167847 32.715775 m -779.047241 37.873070 781.486938 44.416229 781.486938 52.345253 c -781.486938 60.274277 779.047241 66.817436 774.167847 71.971123 c -769.288452 77.124809 763.286621 79.701645 755.967529 79.701645 c -748.648438 79.701645 742.643005 77.124809 737.763611 71.971123 c -733.071899 66.618942 730.632202 60.075783 730.632202 52.345253 c -730.632202 44.611115 733.071899 38.067955 737.763611 32.715775 c -742.643005 27.562088 748.648438 24.985237 755.967529 24.985237 c -763.286621 24.985237 769.288452 27.562088 774.167847 32.715775 c -774.167847 32.715775 l -h -720.689331 15.071259 m -711.118164 25.180138 706.430054 37.472473 706.430054 52.345253 c -706.430054 67.015930 711.118164 79.304657 720.689331 89.417137 c -730.260437 99.526016 742.080017 104.683304 755.967529 104.683304 c -769.851440 104.683304 781.674561 99.526016 791.245728 89.417137 c -800.816833 79.304657 805.692627 66.817436 805.692627 52.345253 c -805.692627 37.670967 800.816833 25.180138 791.245728 15.071259 c -781.674561 4.958771 770.039124 0.003571 755.967529 0.003571 c -741.892334 0.003571 730.260437 4.958771 720.689331 15.071259 c -720.689331 15.071259 l -h -910.404846 63.645103 m -910.404846 2.778915 l -886.195496 2.778915 l -886.195496 60.472771 l -886.195496 67.015930 884.510071 71.971123 881.132019 75.738945 c -877.941650 79.106163 873.437622 80.892624 867.619873 80.892624 c -853.920044 80.892624 846.979858 72.765106 846.979858 56.307961 c -846.979858 2.778915 l -822.774109 2.778915 l -822.774109 101.907967 l -846.979858 101.907967 l -846.979858 90.806610 l -852.797546 100.121506 861.989807 104.683304 874.938965 104.683304 c -885.260742 104.683304 893.702271 101.113983 900.270691 93.776840 c -907.026794 86.443298 910.404846 76.529312 910.404846 63.641495 c -910.404846 63.645103 l -h -f -n -Q - -endstream -endobj - -2 0 obj - 9343 -endobj - -3 0 obj - << /BBox [ 0.000000 0.000000 960.000000 238.195496 ] - /Resources << >> - /Subtype /Form - /Length 4 0 R - /Group << /Type /Group - /S /Transparency - >> - /Type /XObject - >> -stream -/DeviceRGB CS -/DeviceRGB cs -q -1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm -0.000000 0.000000 0.000000 scn -0.000000 238.195496 m -960.000000 238.195496 l -960.000000 0.000000 l -0.000000 0.000000 l -0.000000 238.195496 l -h -f -n -Q - -endstream -endobj - -4 0 obj - 237 -endobj - -5 0 obj - << /XObject << /X1 1 0 R >> - /ExtGState << /E1 << /SMask << /Type /Mask - /G 3 0 R - /S /Alpha - >> - /Type /ExtGState - >> >> - >> -endobj - -6 0 obj - << /Length 7 0 R >> -stream -/DeviceRGB CS -/DeviceRGB cs -q -/E1 gs -/X1 Do -Q - -endstream -endobj - -7 0 obj - 46 -endobj - -8 0 obj - << /Annots [] - /Type /Page - /MediaBox [ 0.000000 0.000000 960.000000 238.195496 ] - /Resources 5 0 R - /Contents 6 0 R - /Parent 9 0 R - >> -endobj - -9 0 obj - << /Kids [ 8 0 R ] - /Count 1 - /Type /Pages - >> -endobj - -10 0 obj - << /Type /Catalog - /Pages 9 0 R - >> -endobj - -xref -0 11 -0000000000 65535 f -0000000010 00000 n -0000009603 00000 n -0000009626 00000 n -0000010113 00000 n -0000010135 00000 n -0000010433 00000 n -0000010535 00000 n -0000010556 00000 n -0000010731 00000 n -0000010805 00000 n -trailer -<< /ID [ (some) (id) ] - /Root 10 0 R - /Size 11 ->> -startxref -10865 -%%EOF \ No newline at end of file diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.imageset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.imageset/Contents.json index 6a0bfc87a..76d42c15e 100644 --- a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.imageset/Contents.json +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "logotypeFull1.pdf", + "filename" : "logo.small.pdf", "idiom" : "universal" } ], diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.imageset/logo.small.pdf b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.imageset/logo.small.pdf new file mode 100644 index 000000000..581801f38 --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.imageset/logo.small.pdf @@ -0,0 +1,648 @@ +%PDF-1.7 + +1 0 obj + << /Type /XObject + /Length 2 0 R + /Group << /Type /Group + /S /Transparency + >> + /Subtype /Form + /Resources << >> + /BBox [ 0.000000 0.000000 269.000000 75.000000 ] + >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 4.000000 4.000000 cm +0.000000 0.000000 0.000000 scn +0.000000 67.000000 m +261.000000 67.000000 l +261.000000 0.000000 l +0.000000 0.000000 l +0.000000 67.000000 l +h +f +n +Q + +endstream +endobj + +2 0 obj + 234 +endobj + +3 0 obj + << /Length 4 0 R + /Range [ 0.000000 1.000000 0.000000 1.000000 0.000000 1.000000 ] + /Domain [ 0.000000 1.000000 ] + /FunctionType 4 + >> +stream +{ 0.388235 exch 0.392157 exch 1.000000 exch dup 0.000000 gt { exch pop exch pop exch pop dup 0.000000 sub -0.050980 mul 0.388235 add exch dup 0.000000 sub -0.164706 mul 0.392157 add exch dup 0.000000 sub -0.200000 mul 1.000000 add exch } if dup 1.000000 gt { exch pop exch pop exch pop 0.337255 exch 0.227451 exch 0.800000 exch } if pop } +endstream +endobj + +4 0 obj + 339 +endobj + +5 0 obj + << /Type /XObject + /Length 6 0 R + /Group << /Type /Group + /S /Transparency + >> + /Subtype /Form + /Resources << /Pattern << /P1 << /Matrix [ 0.000000 -65.993195 65.993195 0.000000 -61.993195 70.224548 ] + /Shading << /Coords [ 0.000000 0.000000 1.000000 0.000000 ] + /ColorSpace /DeviceRGB + /Function 3 0 R + /Domain [ 0.000000 1.000000 ] + /ShadingType 2 + /Extend [ true true ] + >> + /PatternType 2 + /Type /Pattern + >> >> >> + /BBox [ 0.000000 0.000000 269.000000 75.000000 ] + >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 4.000000 3.632446 cm +/Pattern cs +/P1 scn +60.826767 51.982300 m +59.885666 59.068882 53.787518 64.663071 46.568073 65.739265 c +45.346546 65.922020 40.730431 66.592102 30.036348 66.592102 c +29.956215 66.592102 l +19.252211 66.592102 16.959164 65.922020 15.737550 65.739265 c +8.708311 64.683380 2.299911 59.667900 0.737860 52.489872 c +-0.003115 48.956699 -0.083220 45.037697 0.056964 41.443653 c +0.257227 36.286026 0.297280 31.148746 0.757885 26.011383 c +1.078305 22.600037 1.629032 19.219128 2.420071 15.889084 c +3.902017 9.736488 9.889900 4.619389 15.757648 2.538147 c +22.035824 0.365425 28.794806 0.000015 35.263187 1.492470 c +35.974140 1.665001 36.675091 1.857872 37.376038 2.081261 c +38.948124 2.588921 40.790466 3.157455 42.152252 4.152424 c +42.172348 4.162594 42.182354 4.182858 42.192364 4.203201 c +42.202370 4.223465 42.212376 4.243816 42.212376 4.274334 c +42.212376 9.249176 l +42.212376 9.249176 42.212376 9.289791 42.192364 9.310051 c +42.192364 9.330315 42.172348 9.350658 42.152252 9.360832 c +42.132240 9.370922 42.112228 9.381180 42.092300 9.391270 c +42.072121 9.391270 42.052189 9.391270 42.032177 9.391270 c +37.886612 8.386127 33.631145 7.878468 29.375511 7.888641 c +22.035824 7.888641 20.063231 11.421898 19.502539 12.883831 c +19.051918 14.152893 18.761566 15.482990 18.641405 16.823097 c +18.641405 16.843441 18.641405 16.863705 18.651411 16.884052 c +18.651411 16.904316 18.671425 16.924664 18.691521 16.934837 c +18.711451 16.944927 18.731462 16.955097 18.751476 16.965271 c +18.821604 16.965271 l +22.896957 15.970303 27.082464 15.462643 31.277977 15.462643 c +32.289371 15.462643 33.290596 15.462646 34.301991 15.493084 c +38.517517 15.614994 42.963440 15.828209 47.118843 16.650486 c +47.218906 16.670830 47.329144 16.691097 47.419201 16.711441 c +53.967884 17.990677 60.196030 21.990898 60.826767 32.123367 c +60.846947 32.519287 60.906982 36.306290 60.906982 36.712379 c +60.906982 38.123699 61.357521 46.692589 60.836857 51.961868 c +60.826767 51.982300 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 16.353134 47.918640 cm +1.000000 1.000000 1.000000 scn +0.000000 3.736245 m +0.000000 5.807401 1.632209 7.472382 3.654834 7.472382 c +5.677542 7.472382 7.309585 5.797228 7.309585 3.736245 c +7.309585 1.675262 5.677542 0.000028 3.654834 0.000028 c +1.632209 0.000028 0.000000 1.675262 0.000000 3.736245 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 32.524460 30.973694 cm +1.000000 1.000000 1.000000 scn +38.200230 17.462723 m +38.200230 0.284256 l +31.541471 0.284256 l +31.541471 16.955149 l +31.541471 20.467976 30.099640 22.244776 27.205791 22.244776 c +24.011585 22.244776 22.399469 20.122755 22.399469 15.950090 c +22.399469 6.822678 l +15.790834 6.822678 l +15.790834 15.950090 l +15.790834 20.143185 14.198734 22.244776 10.984432 22.244776 c +8.100758 22.244776 6.648746 20.467976 6.648746 16.955149 c +6.648746 0.294346 l +0.000000 0.294346 l +0.000000 17.462723 l +0.000000 20.965542 0.871139 23.757576 2.623425 25.828648 c +4.435832 27.899887 6.808930 28.945639 9.742804 28.945639 c +13.147311 28.945639 15.730712 27.605450 17.432966 24.925154 c +19.095194 22.082256 l +20.757338 24.925154 l +22.459507 27.595276 25.032986 28.945639 28.447416 28.945639 c +31.381290 28.945639 33.754387 27.889629 35.566792 25.828648 c +37.319077 23.757576 38.190220 20.985806 38.190220 17.462723 c +38.200230 17.462723 l +h +61.110268 8.924355 m +62.491982 10.416807 63.143234 12.264570 63.143234 14.518509 c +63.143234 16.772449 62.481976 18.640476 61.110268 20.061882 c +59.788589 21.554337 58.106689 22.265121 56.073723 22.265121 c +54.041008 22.265121 52.368858 21.554337 51.037174 20.061882 c +49.715416 18.640476 49.054577 16.772449 49.054577 14.518509 c +49.054577 12.264570 49.715416 10.396461 51.037174 8.924355 c +52.358852 7.502947 54.041008 6.781986 56.073723 6.781986 c +58.106689 6.781986 59.778584 7.492773 61.110268 8.924355 c +h +63.143234 28.255198 m +69.701591 28.255198 l +69.701591 0.781738 l +63.143234 0.781738 l +63.143234 4.020473 l +61.160301 1.330006 58.416882 -0.000011 54.852188 -0.000011 c +51.287495 -0.000011 48.543831 1.370613 46.110611 4.172821 c +43.717499 6.974941 42.505974 10.437069 42.505974 14.498163 c +42.505974 18.559340 43.727505 21.970684 46.110611 24.772808 c +48.553837 27.574930 51.457687 28.996338 54.852188 28.996338 c +58.246773 28.996338 61.160301 27.676495 63.143234 24.996117 c +63.143234 28.234852 l +63.143234 28.255198 l +h +91.770676 15.036257 m +93.703575 13.543886 94.665031 11.462475 94.615005 8.832880 c +94.615005 6.030758 93.653549 3.827599 91.670616 2.304710 c +89.688515 0.812176 87.294495 0.050774 84.390968 0.050774 c +79.154297 0.050774 75.599525 2.253845 73.716660 6.579025 c +79.405289 10.041067 l +80.164940 7.685646 81.837677 6.467285 84.390968 6.467285 c +86.734138 6.467285 87.895714 7.228771 87.895714 8.822790 c +87.895714 9.980196 86.373070 11.025948 83.269424 11.838133 c +82.097839 12.163090 81.127220 12.498138 80.375908 12.772230 c +79.303558 13.208757 78.392975 13.706240 77.631653 14.315380 c +75.749619 15.807833 74.789009 17.777590 74.789009 20.305622 c +74.789009 22.996006 75.699593 25.138290 77.531593 26.681526 c +79.415298 28.275459 81.706749 29.037029 84.451004 29.037029 c +88.826294 29.037029 92.020851 27.118137 94.113853 23.219398 c +88.526115 19.929964 l +87.715591 21.798073 86.333038 22.732088 84.451004 22.732088 c +82.468071 22.732088 81.506630 21.970686 81.506630 20.478231 c +81.506630 19.320744 83.029274 18.274992 86.132919 17.462723 c +88.526115 16.914539 90.408142 16.092180 91.770676 15.036257 c +91.780693 15.036257 l +91.770676 15.036257 l +h +112.618164 21.452854 m +106.870323 21.452854 l +106.870323 10.020803 l +106.870323 8.650179 107.381485 7.817646 108.352104 7.441906 c +109.063393 7.167816 110.485130 7.117115 112.628166 7.218597 c +112.628166 0.791912 l +108.212013 0.243645 105.008308 0.690430 103.126274 2.162537 c +101.243401 3.583942 100.331985 6.233803 100.331985 10.010632 c +100.331985 21.452854 l +95.915833 21.452854 l +95.915833 28.265369 l +100.331985 28.265369 l +100.331985 33.808784 l +106.890335 35.951027 l +106.890335 28.255198 l +112.638176 28.255198 l +112.638176 21.442680 l +112.628166 21.442680 l +112.618164 21.452854 l +h +133.525681 9.086706 m +134.847351 10.508114 135.507782 12.325527 135.507782 14.528599 c +135.507782 16.731840 134.847351 18.528820 133.525681 19.970573 c +132.193161 21.391897 130.571289 22.112776 128.589188 22.112776 c +126.606262 22.112776 124.984390 21.402071 123.651871 19.970573 c +122.381058 18.478121 121.719803 16.680973 121.719803 14.528599 c +121.719803 12.376308 122.381058 10.579159 123.651871 9.086706 c +124.974380 7.665382 126.606262 6.944508 128.589188 6.944508 c +130.571289 6.944508 132.193161 7.655127 133.525681 9.086706 c +h +119.037262 4.203255 m +116.443100 7.005379 115.171455 10.406551 115.171455 14.528599 c +115.171455 18.650732 116.443100 22.001038 119.037262 24.803242 c +121.629745 27.605450 124.834297 29.026855 128.589188 29.026855 c +132.343262 29.026855 135.558640 27.605450 138.141953 24.803242 c +140.725266 22.001038 142.056961 18.538994 142.056961 14.528599 c +142.056961 10.518290 140.725266 7.005379 138.141953 4.203255 c +135.547806 1.401051 132.394119 0.030510 128.589188 0.030510 c +124.784264 0.030510 121.619743 1.401051 119.037262 4.203255 c +h +163.985123 8.934444 m +165.306808 10.426897 165.968063 12.274660 165.968063 14.528599 c +165.968063 16.782539 165.306808 18.650732 163.985123 20.072056 c +162.664291 21.564592 160.982376 22.275211 158.948578 22.275211 c +156.916458 22.275211 155.233719 21.564592 153.862839 20.072056 c +152.540329 18.650732 151.879898 16.782539 151.879898 14.528599 c +151.879898 12.274660 152.540329 10.406551 153.862839 8.934444 c +155.243713 7.513037 156.966492 6.792244 158.948578 6.792244 c +160.932358 6.792244 162.654282 7.502947 163.985123 8.934444 c +h +165.968063 39.260834 m +172.526413 39.260834 l +172.526413 0.791912 l +165.968063 0.791912 l +165.968063 4.030647 l +164.035995 1.340179 161.291748 0.010162 157.727798 0.010162 c +154.163025 0.010162 151.379578 1.380703 148.925522 4.182991 c +146.532318 6.985115 145.321548 10.447161 145.321548 14.508337 c +145.321548 18.569429 146.542328 21.980774 148.925522 24.782898 c +151.358734 27.585186 154.312286 29.006512 157.727798 29.006512 c +161.141647 29.006512 164.035995 27.686666 165.968063 25.006289 c +165.968063 39.250683 l +165.968063 39.260834 l +h +195.566971 9.117228 m +196.888641 10.538635 197.549896 12.355879 197.549896 14.559118 c +197.549896 16.762192 196.888641 18.559340 195.566971 20.001011 c +194.245285 21.422335 192.623413 22.143211 190.630478 22.143211 c +188.638367 22.143211 187.026520 21.432508 185.694000 20.001011 c +184.422363 18.508474 183.761093 16.711493 183.761093 14.559118 c +183.761093 12.406662 184.422363 10.609680 185.694000 9.117228 c +187.016510 7.695736 188.648376 6.974945 190.630478 6.974945 c +192.613403 6.974945 194.235291 7.685648 195.566971 9.117228 c +h +181.077713 4.233692 m +178.495224 7.035900 177.212753 10.437071 177.212753 14.559118 c +177.212753 18.681084 178.485229 22.031557 181.077713 24.833679 c +183.671036 27.635885 186.875580 29.057293 190.630478 29.057293 c +194.385376 29.057293 197.599915 27.635885 200.183228 24.833679 c +202.775726 22.031557 204.098236 18.569429 204.098236 14.559118 c +204.098236 10.548725 202.775726 7.035900 200.183228 4.233692 c +197.589905 1.431572 194.435410 0.060863 190.630478 0.060863 c +186.825546 0.060863 183.661026 1.431572 181.077713 4.233692 c +h +232.475540 17.696289 m +232.475540 0.822433 l +225.917206 0.822433 l +225.917206 16.812975 l +225.917206 18.630386 225.466064 20.001011 224.535477 21.036505 c +223.674088 21.970686 222.452469 22.457994 220.879807 22.457994 c +217.175766 22.457994 215.292923 20.204056 215.292923 15.645479 c +215.292923 0.812172 l +208.734528 0.812172 l +208.734528 28.265369 l +215.292923 28.265369 l +215.292923 25.178900 l +216.864761 27.757797 219.367996 29.026855 222.862732 29.026855 c +225.656174 29.026855 227.949310 28.041977 229.732117 26.011431 c +231.564957 23.980885 232.475540 21.229462 232.475540 17.665854 c +f +n +Q + +endstream +endobj + +6 0 obj + 9965 +endobj + +7 0 obj + << /Type /XObject + /Subtype /Image + /BitsPerComponent 8 + /Length 8 0 R + /Height 148 + /Width 538 + /ColorSpace /DeviceGray + /Filter [ /FlateDecode ] + >> +stream +xC#[ֆ;ݍ;$$@pwwwwww~wW̽LPU[׻׿ }ӿ>_a?CL]0qoAtUǏ>񣯗QoN|ȥ\ Q;iϢАPCa6OP8u!!\@?Qw 3̡̞ǯIL4DFFDEECQ11(66&6oGEGErN 5?%fSHlDEEBDDd$6vdq.11!!!>..N0 <~ՑqDdt\BfіkΗ` .dNMMK@^×E/zҠTt$; 2)1!.&:2tpP;?G&:4&1͕+J O!1q IvGM3|9yy~*r ?//77';;+3ӗ e6~ }gsz|yy>k-$ŧEL|-gfWVVUT։E7TWUUVVfefx)(A+(iчq6WFnQyUM]mUyqn_PZϖddWU646wtvwYo~0/Mu:bPLf|1$<:˯klb"~).z";pxdtl|brrjjzzzffv2 LOMMMNL tu46TWfz]†ShW"#P@K2"cщɉс<#."XCqI73pdlbjfn~~qiyeuu ZX؄:0?7;=59>:4TWU +6#˜|@sI 9bxbDdZ~ Jp:VV:|΄пMfv.ovAiuckW*@;888<<:<:::X萓{{;;[dianzbd('HD02X0s4xkY>| +Jtg5O.mn.M gѼ`8['odrf~emc{wJtͷ!>\svvzrrr R@(!6/ M0X'B DucKͫ<`F;sla1Ca1Ԍ殁sp}ss'7B X&gLJkKC=exEaEF&$=wпn~ +{ +j'wOnNV&k _CfhB#cS<9EUMKG'gW7@Woo|0Ĺ/ϟ??~6`gcynb8ۓ+@a˲Mz}YDbS 1Hw>ڽ뇗,t{ ˇsb¢bmΌÓ˛ϰ~,?'tpngd@{}Y7%)NRE£ѠiB"6eYH5! }2f6XNW3 E_7awgV6u O-n] b4 O?!.7(pwceA+)6ׯ0nT pgW5( &eO1!cvl}Q=6,/odjXT=5wlnm\FPi?}Ï!_<^͍TZ֠)1n ݙm==y丈пo& q2$332^eԶL.n\ DAHx^lLJ˓Bb|#bəARB!3&&B ذ)!SrRK:Wvϯ%.`%?E rws~4eKx7o|vy}}ea`Bb_@B2a!H O`KD-5cxfuᵼy;h{Y(HID!JQ\DWficp[eaE?G dFQ2mſ UY~Wd +o>c]YhgvO.Ӣ|gh@/J(Jɷ't5q8P(?5&º뛫=:\]D*El\կ*M u_ 3eFQd+ϙ&T +hUP G9c *haNеO~e^Eg`ڐZi(r%M􌾙y9x8h9c,S)2S r#sf4 /o 3yNT˹BGk*VVK_giӧPDY(9x `Ɍt;LM +")H̄Ez3G?,I_*Ц,U%CW!Tmd|{|iWb__G_XJPgj +ҝ&M~52M09%Lےb5 + %<$2/6!fUgJJÖ 4R9D%|E]{wZH2snjUi#RI q{hPѥxu#]Lt\W܀#p6y 2U6жLvmqt \*A-)[D=H0y,$UkK8Og T]4OVBSc#,+/I#[+͛efffx=n90lNY9ʀTxDL!iYY>Оde\%hY?tgq8r$iUsEē{sY8ݓLA s+ l +4]Ѥa&M 2eR=Li;# Ño4ՆhU%1AvmՎe WOz`8]:цE&j{VoP%/ O8C`Vh3p%EY-K3L:2r JJ⢂\R^Eb|6ŕz<$H훈N㓒^_N~QqiTZ\ɚF 0ݾΑ/_?ߞNTfzF^S`KIMX,%)+#Hƫ@S,wek4%E9T+5=ypnӊgo*ob MXWTofnA!iAy)> 0 1dgMKu;S$ť.i'i'  +'cta~?//:\$B{ +|l2/A51 v730N—,dX ,/Mb#5=u%'wЁ!.~15.6{[=)&+SHxtlӓ_ZIJchkH̜ںd+r+=o`hlk(I<Ρ[\/g{ks] 5FTm"9p5E/NZCV o'ɇH6I kH J˞peV5vNk6879P,MM Ԑ!ف)"*Ԑ#]ZY֝řɡlaOvW4v M.`'{k 3(#"&%XX;25KkdZjJrt#1)MrysKjZzҢGK3`odn}d F?mwL-nljTˋ3-WWKe!6?&SHL!MMN2G֎_Yh(p +Cr%Z淰<1 6K,8?^)VR0h< + +0=93kۻrxzrr|:?5630:98;TjT)k[?<>=TX+HusK뻆6/Af`wkmif-5nң]=9;=;==>:^_l,A@(Ϗo\P981.nM&# cge$:-12t| mj*Ԗ#:)&3wtziumei~zbtxhxlrvimꁙũ +$ +*P sly6#3`|'H, xIR61/WexLB7aP+lC:?;Z[չC`O)Z;<]{KC Y9EUS[VLoڷ//OWgk=vSkDo;%c.oon뫋ݍ暢T,wT6wfQUs̊յFsCɳեٹ @̠I+@W`fc{ *սٱڒl--1"šΑٕ}hsmyq~nYspr~Ino)~r푟'e52!hMkr^90 .`Q@[uaFJbWy]#3+׷څſ hώvVz3R" O%՝! j(/-,[Җ_%t +Brmy(#9>ٌJ>9Gmw58;]_2MXXwVQMk=Cѣ㽝u9fAegH2DeeS7?m5-[&dosqj4HB!/EKnoNO8"rgq04ĥJq"5g?Wo7G+-yWM ٓW3:w|s߾@̜fpoG*F8ڜl.tX1S2j4;Fh?PsufdmӰgJ|(jїuUPGm W @o=')xL?<T +2H|˞fD}cm4^ w5 &rȌ2EI)/NOYg֧R4>k+mti7QadN/@&ӏQw'zʲ 4L[&6N̂f +4YW15&+5-kbex&?9_g2^OaѶzU(@Jq7of0xFQჴ4C|`.[mvF@⹓ +]QU3!27q~6VWTs%+>)`%Jb 0G5? u7ItAhDݛW:0{|q𙥮^-Ui,1N #3ƞ27/d)ʛ:an YNEvE.#F3(Bw]a]8VV !)Z;x2 $Y]ߠku˞`ѫ "Co $=sxbi Yx|o d0'Q1*3[Wpi0t+ ޅѮʪV;'ZJU3{k}řRe5 +9T|s@Vz/3V]Spem3iI$ƥXq-P0h4 M(l?QSET*D԰h\~K@loe$3,O#h-+z+do +~E߾]+LwseDm {Qx{(3raFB%3bcHhN! +5%Nv8\Xm./nV&5o,uf8IvXA77؜8bsdWfSE_c2%`u-cP;ɌS 54#2B Ci@4Q>蒢3U܃C +KK˫)1Kڲ1&d$ƒ*oÞ ckeafjbjVAʼnڶ=\1 4Nv)1?ph[Gv}~BCwt`7 MfWf1ɯ,U(#|Ehi ud,Ја8e2Loful]p{Mɐmc N2d`0<;|0r7_.Vzғ"pm@e /X( +&oѫ}E}6@߿]Oel梠Ev"^S彾DcT*~ 2$3ܶ8)3X +gS#== +\3@u(&>D vLIqWnX䋓uSgWuۖtHg º!O -*BT>9>:22J܉8~$c4 dhxïBLMOOw":b/X c]Rv!kh8FICN46-Bhk~B N@Fd3mlqmJ?<3~,/@ƽ5%r.oie7fzmm!޳{t,3*@" r49PWOnmhlznanf '\y}[1B&Hꊨ=!x¬=mNWX!`. }]]ݽ +. )2ьނ2Svm~jx[kKkg7ulSp#7x&*2a` G;]Aj[ #%Ȑ6GfoBzMc 1.h 55yiIVd$r*;V f!2V3t4;8}~A 5N#ºq #F{uTt6WWW^::?2+Sڅ=qxe,~H2RK]qwԖHI{3IAIieÖRd=-@+Fz;jkH,SE;Kݵ^ Q ά2k_Н|T$[+mo(++"?>GLʖ̸o\wEm]GBӔoml UExlwe Qa xvw 3xsmJ;`qCXH՝8Ә~ ҁ'|-ଠ$>30o 0L͎4הeggeW `&S~dH$$&7ɞܫS-5yY*X\Ĥ+nOv{&M!*Caj$|uyQ~v;ّ=AU`kqreYqA^^^aIe=ٝUݍ{.-_KVO,g~uE%Pe %ٙMMS1A|*a eDNF X$;za>mWԶtϬ6R +O{HB#1&* # 3~=?ؘ)*gdU5u +t&\ꓡ2>F%5ieLu0TSco_'x|r2U戏#SӁ~J>QCEAvzr=Z}̰̌ Q8 +|f N6Wfgx}%eD%s{Ǘ$:XS6[JZ*U[ }5eY4ח[TIvfuW;\8k`eNK֖ezSN+ UV>Ht2d01,&DƑiNm)l<9$;&R +)>&yXŀ5ۋcݤ|idg/{L#gM|=^n L$]530$7Dq +:,޺2.!'%sq9;he&$$$S^!HQِЈH쬒nc<|FF",QNyxO~~FyHH v7t1eY =u^%9rb  + sniS"-$!'JFLZ>|Lk=RD|?';8'Y{I*c۬DLQOWf9ke ̔eΰ h*+ 1㢴 IrT"tI/EnHǛ@$HXXgtAuyĈAG ~FgYmuPv3(՗)i mG i>}R9#(#ՈGKW)bǒb:ژhIv7f4{0x=wal%Ȱ2HJ_djUAb}:T=YJMIv7w^h.WYPl鴼vɆQ $k۰:`l.YnZAnV#T,p懀NVy_:ܐţM@4H'f,T V&cFVdz'u"PQ<_z&-xAá0/EhS+bRD0WSmcĐdحz`mN%#/$KvEː*HٷWfg"W[B7!΃Ka,֡$,m[Zn0nX^ h"8u gxQ8{a,U4G;p8Y@} d`fڝ$;!tń}o!h;]Ѵ{kD@S;@3CNAR2ŧ̈֡fYe=e 3n޲30DP&ƐLl:rqA|hoC݀'mB|HYV r~L +juqvb ˺:AYx|J' 36@FGо&/bZxV6x2ZX>RA}iA^~y8n +T><Y0pmB&OUN0+[Lr5˥)h^bƾE\IH {QE0?.W" M +D @42 Tw ݥ\;5 XV7qQ@;y!E18d+IR];q7VIv t5ה s_IJV)?l'dhA] )Gɣ'~bGJ# p%y08 8U|$KR"H#2+oB4*f1@)@()4J[tF1g(0 3\) . sl_~21Ƞٳn!C3zT.@O` X'̣drniukW-U/Gj2Ƒ~}bZ`z@LgaZ:>;&r/*R$CyQAQt|u]PpyS{ +†,zd$6l2,>-Rm[{ aAސf}4v|M@FB4O5F)/=YZq9;0!!0Ila& ruGT)m_Z=DgqV ("w1u?7Ԣg ]1DL#At3 +g` 1F_'i4C ;B 3R[Q6qmYm$F xӑbbOXP]DIؖz -O1I Į gRɷE% +25tH|H@:j~ Xk!?athib]̼1:=[BY|L Lц6I?2^@5y\b&^ʓH3"$JIJ*k@`⏁*AA M@V=)(i#*@ao"LxkB-OU<];5C'Ef[$%.I3kpg1ےU26N.9IJW:?Bg@AF˕5V& " ah PҬML0+' ˘,%ד_VnaqyU}sGuˇ<@) 2uԅF&X r%B)H/pIآi1weAvVNɛ7 % h"2jp6IH<5J+o2@ m`6Lb(ΜJ:*7q攓DFf00 #2g dP8ۺ,d~,zD&g? aLqgbY  +4ګ4WlvXPj:$\,vT)0T$~$B+r-dS,,A|+)$$42:ޖJzӽH-+j]ǘ":!3)6Ǡ%30XD1崤Dw}q*(-^nB +!>IJ)ͽE2>:2lj'X dX2d'dV(R< eɌ7 ׼H'+kA(16zBe2ƺX~D|>P+|dW)2|DGfѢT(i]?@0#qNea FGRģ +2k XȨ0Eㆻ-PIkhLln2yt|9Akh3e1DCm*ՑkHТcdn\g>P.ȈMpx zRek3CmE$WNZ{Pi51};Q3:e< |&[2D ew%Q|&O( 2٩DklK&Aw|9d["HO,3Qt%A;gV766qioAeKnL_6t;pí1:ԋT-䤄X&?Sg ,e.LwQf8:Q˨J0C':,KۛSf]#>6֦SB-mLUgݞ_9<i;kY2 6 8EБ ßQÎpg[Q7ahamzLdy#3FZG.H뤓7qeԘM.&jjڻz:Kuksw?BJ +#FfP"RSvwNY*ZPͦ ozfnQݡm]NR#VKVs2v&_w- {ӀU^jJue=P3kj!Ҵ8.:VxbYuBF%3P|D@fLTdb́ 82H>3`~ՠdJ@ܤ[Hw3?!;}`bqֺmPZUl 7pWSmyIQA~AQie}[٦D1< ; g;, !na خrˌHd2'Ή%YG }e@_b=fњ @gk}MeEyEUmlW] 22z;F +^o \XY[[i,-**M [,|%Et_1OVgG;bnFjÑB`,{B2;PWSSgn&Xs5k՚I$؍D+}MMEeu]KkՍƿ&9Р<*lluvl@m[DJ"I6b;@; #44DȘ6QdHfϐ*wd +M~z~zBL?<5s{$3M*sIIނj=qc`oken|NsǺ5[쑭 + GX aQǾ<% m~D ?78:9fnxw2T$2u/<6v/ɵ1=jh;{f6ca$TWyO(@}CDu9x43ЂgŸW5O/> #OhoBh k,P!2'nӏ$.>Af& #:"`/Bc|i%pgcy~zrltz_!mBNglqhocynbo`dR_=`f‚M`N!%Ndy[k3cؙX.e2_1C=?cvzjrzf*iHDasְd @lLzSНݽSs8 +2n!c<܂Mpc{Q]fEDHH9EQţ>ד6*s=xvWyVN<;9>NN/nSVyvdzfqC_Qc3ݽcpqEӥ&X}&vE`x'19!C=8@O3m qj2ׄωceIguת_f2YV !@oî$>?#sq< "YD ThqЌ z(dw{k=& c&)GT/j?ijkd6pcnPN2:S6WOx_ 6m[M$.?>Vhq|TU-}zGAOt^,X[+kIᗰޙ| 8, Cu3lcv6)LSLhB nweNaN_i39\0ab ;d~}y]# =YKXaō~NyWٷD@}P֙ e #YZwLKI1e"}%J\|$xsirli OLRz3+w&z9b#q`5֦w%5º 9z_Xv%ܣTYT +[^Zճ=ON7G:kCf6W4u,o]GZSvg N^c<-3nb{AzVw.lccv9+d6AF0bS9>:eI}e WzuLra9&M7Wh4.\8g\;ZC*//6\vC:nyT,66ĄcGUy\3}p.z"'".-5H 0|=h'.1N3Ssz[ d_&BCA.mw LUcA]h>h{j;G| xC+[;[ks]ڌ4Œ1S;}FǪ]J+ P3:M +:w6 +m-As4XΌ~=aŇ (=ӼiI!gnJqHz1eOqXIT-L\2CJt'wtj~ymc "F6! kV>ț/ɉbKCؼ`P}MKЙ.lMtϜ#\<𵞤FmH$>Oh 𪊓㣣}^hڎ +\twyyASG`Q!F3t(jf5"hɌ DZ~89kȆ`m*dh[QZ=4>mosy憆Ɩξщq#5>xpB#YazYK4QQlcpo[}y~ۆ:6yȨh-;zEss3#Uv*.M6VkJTf-h\w.K;[[V B%ⰹ3 *zh;hP큮ڒt +ԧPd90:1>:M??+~<}cCeH ٟP"@s-?ǠHo$a^Ġy!I ܢf2biYzb8HtX,ili'uPUQMpd簮>/,HO[CeQי 4i-:/oh.͠V&JÕ[R?2>e*Wa͵eYS1[UT>%+&538*QrzbuEiIIYe]S[gWwW{smy~ރa!>> +=Aӝ7mO ͣb#iڔ*hhhgP66+#;kXAfDaL걏FfBnnqe 6$ #[wb 0-ѫ^{鑺d8?[ +K+kjkʊ!Ӷ𞚑[T4(Zi,m!]M3G5ezaoJB1j|-,R=@7IJ+@FkA _O .NR`ca|8d~BHpA ֖榆:LOu9SzsVۑŭXJUm}cSKsKScAPHzzP2 vg7JJJ 3R 1SkoL-%$+j閖z(O/NE̓#]D +F%e赴02j<5fgx6KPXtN *H`:̠Ɔ:.8 hSB2sJŽE&JaVSbfstOnEzI~澤?RPdNhs'A' +MtJ\|$ [N _70qP`I3rԨ%,$c/򿄽>Oj-^ SakԽ>_zǝbҺRǖ;Y%9{^УOzy=56!ɞty4Q",W.,qM5LhNzPn=L%cgu Ye,%Fn&nvEnMGJ}|d }ŹY43qV!١p8N%Ӽ*t@OxE`2&.>fmIpØ(.l0ѱthf/#< ԞDpW>ʰ,i.!H9Iw`#&>L;m q$}H* +#mu>y5ps1 ':6>1[U +"X zi.mq4MJcpO/5\ħ5C^6gfۀ\ +߿=if,wbObhu$ә`'LhL&6!)iV"bg{RҖD+L!Å'bY|8QL42=_5X@ lI& 7w)&ӣ0ȴX U{"U`A%Lぶ)@?i:"rWB ts~0WD1D2mQ,l^~~Rd=,82]c6caV||D2oPp \Z`V,Wi E\JGWsO ft?OxF2E5(6&&:BL&,͊82(KUcj(or э-|1gtEgS'.gQR|%MSLrtE/trML!""RO{ִi"9S)1fʩZŕ (dQ)?R.A2rnX;龹ӿEL72%E,Q?] UWQVTBہP 7EqZOSDpJ Ϸ1~F͆o$| +?I^G!3eߚ]鿔φ(jN2bj+S\ }DJGɔ=*z.8.9R!5qPR:TcqϛU!?#үR%c5E-=)i@|1g"UΏHx߿E ,%-CWw8ީu!s_z_rJn ]bzMW?R/|"A*6OO%J@mќ38Bn$I˟^ӯG4" hTV ?N + ߱1 ~Γ4/)My/Gf +~BO Ll";1mw'h7x_p"SJq}_Cc?o~AD>~ԖnfI$B]q>[Ok + :$zojy@hwun`yHbբkq[ 3L7Db/<=P%!l%%9'InCKWc߿}f^G, N +`km~bN/[0EƯL8'QCzS,W[?W b0["r!H|B!mV,O҇zSCy5â0[Ve4@~ BVLJ󓣃m,bch#;0~e9{̓sʩihϏwחvހGHABnyq~ +*7W8벑F-yGƯMʩ929xDR;ã o,'puuyyOOOv6֖f&GJ|f_>V%$ ߿j?:>9%|C|<bwg{K,/L t͹y>=Z?sA a&[6ۺHp{uavjbbbjvnaiyuu}}sl80?;=5162XWYR'w@Ry&>{c}=]]=C#cS3ss Ylomj*/.iV]b-H/H( [ +?@@`s~o``phxhhhoU%EYVPoA04bt7Ry +I5dƭ0PYZXPXT\Z^QUSSW񧩩Lss2ۙl0,O w{D4S'a ɾʢ\_7=#3;'7!ҽ EEyy9YYnv8lB[B1<;2.2h:&p&?2zrGNx<ތ ϗwϗy4L1[j]C#ƮGl-"4@Ɨކdxd$%řb~`aw{i=:Phk;.nDuUǮ^N5`}'h 4mzB#&V7=ă~&ZR""lGTXxߐ>v#4<\n/f(nnynz|8@DK;Ntg#4f.gГͽC.:uX$8 w=HԣϏ?65)[zy*.Wo>AJHcs(֣WW qBDcܣ$n8o}L̍y!X"͖2MaUWXx&s\Iq{BdrDXQZ`hP;}IqHNM{.ZsSSi|g#IeKiq ՕFpxw~81^_^=))1Id+tR~)գGߝ>~ +sxr˛&/Hl =ZKon w +R<^⡙YyzLPiiIQ^fZqniFhT|7ory6}];ݭ 5eEH g+#V@8+'nh~_OU-@3s7wxosynbںzҮ v6Us5ϷT  )DB??^m,LOa]wfƭGbeߞah|U} 'hW 4<;:\]Zפw/,.mlln-NuTMO|-ԈzG؜rs/E``w{sc}}U[WV76wv6{`wmO IX=:kxfyԼ†9;;6}58;$&PxGߟH zƤyw̵y)|[ЋIno./u ]-gwww7'+2MޑO ?4R%5-zHt׆}8 ϟ??{<@{Ѯ|]3BwfT4t ϭl굹x#_~E߆,^_ V #]=-5C+z9@ra"gB/*;9^+$?TC CILFl׵ M.od} HB9\EeS] 3qq 4*kYЛO2+ac<;=>X]oo(&'G?1/19Eu͝}k{zӝS}q~~mow"M%zܒ CdSm]}CSsz۶Y0i)qpWkcui~G C#E!a1InoV^qyMCkG,Ps}}sc3p`oGKCMyq~e] }JWZB>sȐT_,}#1<$@܈%<YeMwڻzzD=E荈iwKȍP.sY9z7\Yeِ!(/mY>oq=O- +8bwz3|YٹFxwq;SzQ=2G*7jk+JzGg:(Nl՛"ts\|BRf!͖d +RGqV""*L !>, +|-Bt}i + +*u>w@dHND0;BY +endstream +endobj + +8 0 obj + 25939 +endobj + +9 0 obj + << /Type /XObject + /Subtype /Image + /BitsPerComponent 8 + /Length 10 0 R + /Height 148 + /SMask 7 0 R + /Width 538 + /ColorSpace /DeviceRGB + /Filter [ /FlateDecode ] + >> +stream +xK-IrfiCGHӸ=69Df [4uFWQQQ1}{8~G{v5wt!bč?O 7|=7[a1b.Vu]cA q,q, b4k K8&78V8'ψc=oVp+8?nɆznJ%bW\`uWþ}h߳Ho]3\PILmNjfk8=tM7Z(xutPWյ~.)xخl K֟.ǐc{֫v{?wM81젉ɂ4{cwA +8JA7sН|ͯZwJop [4^/nB7ixm:zA#bC#b~61Td O#}ͦ#[*"C|"`X* a֟S@NE" +K޹}q7E87m¹r{;xu@q~WE79` e#)YM1oG*h7)EڰZ?o~-yIopv=1J4c}Mڠo~n8WoV/X^ggİ7?3[s :>+ϊEMyu}jiX4KzHvЁ?u_q-FZ:/DX;F9}rfdy읿$Պ66MjfFX$k"]Ldͻsey;}S?k{qU™;aqX"ki}fu#(lm<(ckRo~z[yڲ]%7u`1½]dSlpE =^8+fxka+a]6z'?H @ t,H#vrfMtq)[l;䰶͟[qf6ћ +<4/şP{_ 7Ek vDNh~gZy_k|Jlk#9 ``wƓɂ np, >^ +me,\>(,=9m7M9^9YMlD69}h|/Efѝꖱ B!{Gf?/v8ןJ4w]4[tEE" F$O-`o Dq!p!9a\~2i–uΡp\ +oJ"!o~͍qGUښuxf_rςq>%2DdV6FA ,F<] qOO4,,HX%MX4AVRC?#:B6_pqڲ9l~"5nl_"?~H(-4Ʋk pn.4zs&8^F򲃕ၽ=y4j45zUuEƹ^04? 0:j; +@wGym%?k.k6aݙ|R;h>o yzbXXC|A!\8枋c؏d_I]/j,αEKBpdkq”ThA 0)A\ۉg~;+cpAnky6K'\ٟGY uaesq-$ȼccʀ8<bxmv=1,j#Ƶ8V1XS6hZji!qp4@ZId4}l: 9хc{ªP +V64?V soRm؏׎#Y )uxsk ;oXOƿ . .6ԎG[cB* THxRczv6xg׬U&`YN)Q 0]!ʀ$pf),k?x8Ry4MS0)eڞHaFƱ0+  ueCX;#XsӳPUP! Ű @ Q AX"kq}<NJwXHȪmlz8%X.=!9kX7r + MC* ~ +4=^<;Ǩ6ŒHC<2."`ȱ{ϔJZ&2j5~$X%,mȀ볣xZ7G}y%]3{nzqlFnM ;1H k jCc1ߩ౳Sb&]( ̒FB hvyzD]%L7EӁ_B$щlH!Ġ ",bD5wOVa`ZuH6Pǂ[{hs=e +:13( T!?í'ƟȂ@" ? offM  vx@,Ň]д#ŻibΞrtDDO`[sg7 p=u2]+]T kh">sDD[7oF/VWć ? lH;xvV3o~|PWkth;HE0[bnza^Uv<ǧ(Gn͝+Br v9y[u)*ؠ4`ߢ+h$XHdwLj GGPm;!;z +ۉASV*iv5{hqcclDՉ,ٝݓb3Cv젃j©ky'cIA+ p$ؠFňA3LuP1xyX60E [sk8kbLXːU`Ƃ9 CM&;L0Z#v#\mX|_ݭw<*(gɂ]c#p @ yVRD ⺮c{`C !&X$XX @ {sClw{ g6l @ Hgc44vYb/+Gk,٠h  @] Bp;hFMΛH5i1F 7h ,0]a4a7"-La.a]C_ fW &rf#4Q4XoZAS7Hg`wAN#BAD4vq#X7r +6vAȹ#\[ɩ&hT78C$Ћ xkAX5%CB]eѨAAsHgA a`1i$>`,Mѓ_ <;#-N 4oɪ Bs4G IX7r ndAQ!jB(R+L3_I}||XyGPbD72:.f9x}M|J0_R`(gt;yX@"a `"h$_d7 bЌ6"Jfi 4'p;w8֛t㜃'UpJ-F&%,I"ȦW;< RTYY&57ӱHsЕ<өT*!İ!hJŖANRᜀ3Cb! opB)d2Čf/@! MqHk-O{c[b`c7Y$؟m<'w)&@Ð2@$c5q]ױ 9TCI$ HEk t=." Ñ``@j@SZ(Ǭlgʳ FL}ЅڼeugQrcQ6ve/ykNۃf Hɡ+[cc2*!Y$tH2Yܾ}l2jJS@Fa^Xj +HHHCM$eE7JS{c<8^:Z,a%DSf]ff]Qʍu] +~&j}xz0PH\kg-҄HXȳbƿ  @DFEb b#fwC oD6zI&rA?u 8O_u0/ @Hk""7?yqC nR{R52>(+ˁ<`Ԅx ~0kV<2\A~bԦH0FY|z9f4|W.?Y]!RUy  <2B j7r* `Rʙ5K .,l6 M쩌 Fb HQ78uXX4V* C3L"KF9!S<17 Hv*wZ׬Z?,Y4! H{cd}ιҲr *c! A#t#bo"1v53t!xF<."h$X,vCv' @̌Jdk,'< фy%! 'L4p^(މ厳?Q$d+Fd]Ps06hͬ2Gf=5{tOGi GoޠA%\V}r2X2nly"m%JBηE;,IAI]"{"' ]Cuq^ ՄY EfM͢f@ SKbj?Xks`Rc^i, F;E6*.f^V [|cZrgD3 kd5-GP0y`A wxvxɓ ݂xK]H `w ' 0-z,h _hr@_ lseqYwK:!NєJBii,X]He`_UbPYH I!)zj;z_xU!,}M 3s]|Xv±~RYVrEc_xR)bH ž[`#s׃KYf,~=i]!R4Y_@VF(Uv1t1jAS{IDzhf-]ˆIk Pu*޼fFZyAO2] aƆC:ղI oH^D ,Z=;S^t4f';<1g 03 $04F ͂čE`;lE M ~Au?A !1/=aHCS{MޱpCI+FpА Bre[s X88ן b +6D/dt` v-Z^0+y¿cMBf[V/DZHqe44rF,0H5e!b V? S녧VFeKV[s\u?):$Y݂B,Q'z_0.ɁiTej% 9D!RQjⲙ5$rBb?Co;!vxMO?"4c1 5~-foՉ[tE46=bm& yX0y5qO-B^aOeVSC3)!!+355"%_#F˥ ˂0)*m"+$ +QO!t֜s4a,F7W sІE/l{w?C$`j򀐖xehl_NU?'$9 &&ؠop1Nb'1͛"FMEMf?C/&;v9* "VT9aHM ~H(X}a=v]ױ& c! 2CfNO e-U%,il +"i] Dz(ӨW0n cY3S{sUc=%%4/] RΧMbPf]& ",?Q^1!7/#3\YMjg^ +E_0Ih}Mg`Q-OiSyPN.j 8rB[Z-@` `A &p4c_ 7~F,gXæ6J8i9QQu\X +\_VuM4JXf,OHu6Jy_tُFQ` dٞk0dTt\B^Dt? -, +w?o$1)](=5mvBSw +tкBxΝs1x?GѫޛI9iSd!ukrIA56 N/ & "<$jげy],FȟXk`&pL;&vDЄMa;`riO 1 y0@9m$ Y ,ޤa^C t4Zk +:oo] a`CB~HB% c%lf1W0X$!LTr"z)(py@Zx%Y +S~\u0ks_HMWنK2 }PjmEК[1~1hAb"`E4zgyәUSJF^V!*4\Sͬc&cc`C) :x)4 9 ~)(ceyI(FI y`F ň콻IHOCM6ugWCͶvNh7k%$3'!2 @j0VU-a5aBb-?h*_ cCA;xS`䄴jP ٠+hX DNMwA l"eCXi ų<<̓V3klvbPm!-*+) C aP  [ځdarD`Cf†;kǻn"iuAQDz%).A^=i>d"XCcYIXA0#@ ` 1V厙b&dDc+9++;QYL6bHАͲ[pgkٶ꯫c` * BSĠ!$F'BX5f>D6pABkv-!ě +) /+~)Nȏ5u/B|%tPEE :hfoudF"7?b欙SHA4A؂ω G8 ^[F@;^0 +feEI<$T7y|JXׇʏ7$H8!XEHP*Fp BB0;ДE=`ԃI A]4'x@ɂIX +Z yfV`5`odŰ0 d&IB&N@QB)8a ؁4CX@"ne$!vxa"9kL([ּ b.aa`Qs5-0/O^ά}dSZhT=hy vx@@ňDБfA [$ϸ~-F$X[ >#GeS 1vG N q#-:G72ȁؠy5 +& c#"11y MVAp*$O5O z CdÈN1;yIeJ,[y];5V5) +gh o,fbbDߨ(d;hgщ,xK4{#gvn"]q#m;=u֜N)m$_P@  >leTCi:<6*4HH&n"  zۆ wT0bςxАf;,Doщ,xKcMHFx;;ǻyNW͞@@rHd`  n]&;uf{˝/|Yy 60[,v,|&v @P5|M*R z}g#<,ɲ%?XX 2wf^ˑO 00223\#jpK{d G9[I*M#L|`͡f +CM F>tne%;kCT?룿1``ӝ"鎈әTܚSZ>G>=,AÀ<8xnz5FXϿj,t3&$5x<*0ӀtL5hfם0`vP0`vDvSFf` 0xh܋kaG9F>4̡1?`U3TRq<2) ':]ܕM|Ԋ>_ַ4F``IAFa +nU|\8s + P1/k9ӝ0O0(.+>eZ7Wgr. A 0C4 0-COrk.IYad(7 +"O!M)ܔ. 6J81NƧ`DY0{dY΅2Sd<"5 A4x0Ȥ̧&0&2V>)nMؗnߵ>&j`NA#E& Y`:|Ajvr *҇u9S0L>vN8IJLxf +O=aq4Y>>ө>!B幵ΖTt(\ 2Xޓ4M0` `"a<9&4#1~7<<}q(gZQC)NCA$n``dF*ys钞c A=[=0Ԕ Bcib hd,> AO +6zR>fYy}ݵ:X%SK d(2ncƷ\u]obq}.$L1Úh)c x݂#AHŖEU625KtC$24x0Ѝ 5 5̥ԗL$1hRlH*7M!BwVɢaLY@n24?4N])O3u"OM0yP0<(2L~45k&0` +wAωGw|= k"&UNo &4M# 5S0xqUzA0P\9)A$r gov +Gw$'? +|:h(̧J +2~1Ww)Nd3lf +&4pGJ :|JSthg¤A vR 14!==?0`f1/%ʰc. +M0` D#0a4x @9Ͼ#IFkpˡYu-c4(|(wi<<ȏ<41fxD4wD0&acv +Ȍw:D1yw +tЄ;#*%W)wh`=Gw>&aF3tA dsOd<{>G2뵲"N +P %C?e1pVuzp}?2@s׏)7@nK<>L}+е\3쮉jc!B0GM&-,]k+aCM}uaC15)4RA"CC3  +]4JzQњ[[[;]F3 4e,G` Aƍ͠}xHx:'&ŘpN! 03ԍFGTaٓQx|p|ҲuJi2L4 <r-;YII[>A eخeR0Q0 qjfDEC}Ph + +ƅ!dΠ-2=H.n`Ew%lh^^(sEPi@tpHz:EXVK*A3t;K01 .1`&1H3h-ZSCtG^? ʀ Du5q+됧NJGιfa@#"2+ҟˡҦ;rS`YSA3v&d `|&4OGf:7-oX`PfzHze=.z1郂Tix\gϞ&5=3TgSOWϭʓH3ч,mR)7:t44\#F멖f5a=_Tz\`K)w mu׬P.0p!dJ%e,/`݌Cp[g Ky% j:4,؊1a2/8Æ&+6:Z_Zk<]`!Cș3h:D~hV֌!swj&+,=frQ&z;Bõ[mz64CH==ú4;څIJJj]"IG& ~7~Ќ<1Gb3U9&aM4AF;ơz钡<:Zq^`RŹF04 AЇ:gAbU?ͥij0ߠ>a\85K"4xC56ڧ[/}0P5xA7;cf20Ks!, 'pW= ccRuH,MH>fq-s b(^)F聩`~ɀ)JwL#"CѪ=nGω;(9VI-8=f A9P8SsZG=T=TSP As؛I)Fn+`d"aa:NsER2r4K*>;'-fFD0tt&`9 +f"C CI01ȧwb̎`5G̠1h+^{Ad4 &?$`~":7zBD|YS  :Ch@Mxl!"[MZND01g(ZI6)2⵨Szע tԅމAO_k チ7;d/@ %5*}TB |j|O;'I 0CtȰ7tjֶ?_z+߳z]kݾpB`>֟R}^34i#Z%P^(݀  44{-Y3 ht^wG`qJLp 5:N4ﵞ ׏_kL j?h)(kf + TBA:LjȘH1;P0`0f(~C`O18ӪJ|%߆y^]1N|M0?f}yrO߃?hj6g7Q=OJ"0)s40ߠ1C|&24C`%aIam˯{qmxW?3tHbe&[/|(`Q0x#SBL`O)> fScaJEb|YzSnB?QhE% 0`"OD>&xdFMd(9|LwD|} D0`aV{~ybr]p8~M(no=Uj#2tGvi2`A&ᇚ4wL$"C &D Lq1Sbx8wl%/[O% "4x0L| v1mfiZFJڣ[^?Q?n9BfM@ePI + "Ȥ蠉1 4ǼPiȧbaa%PJ赾jᏁ}&P'c_>h{|Q`|&Efסf̮;+xg o%W~ص}5k~8Uo7a@ +#T?_9 +f؛`؛<ƀy0x"CA +f+ಕ0ýp8Hx7~tT1TG4vGAGw1:< +L~8 jY6&Qo%Ԫ[ɽ8$T6%.dhpP?5 ERdR0`b||<dQ3]>#. }Ps4p8+kKPJFMi{̧CM + 2tCd؛@Ȥii zGo%6np8TBaT!JUWL +fI4a||ͤ:LtAO$/w^Iv?0J/*W}7WrS@cipi$)2cg),}oZp8Tt|S!<2t݄!H(  P04#OM=Ԥ-06[1Kga0$ٝIϵ_Pc&`j` f* "MdA31}`5l"~,Gzý/=]ً<S:DGưp{Www?Ԥ}G^@&ba?ϾԲx_,p8jO'B +~.f؛<0L1&LK7iu;x0`ٙȘh4cPĘPshZ?A4m"~d-j}߯ӉmRdAs;m%M+&U}}$2av +z>p(3<΅ 755}5ĂBMr-ziH_Pm|/2xE^WC<F> } )p/F]]CKOFPX]Vchp8﨟 +ؽ,n"d8f1lt0{Iʻx ;;iRpJ'¡x2<~ bAv8TU<#7y004a Bg*Ф2R}Vc,p8|*G~6h8( Mo@X3 ^" ^ˤ<V>x}p}k3Z| ?ߊJ|{MCWF"݄&As4>Pqڮkq"#DC\z%I t}p8 ^O}Ag*꿍 4)2 &}j0~A6~F꺮תx=a^A0"סku-^뺨ь  lnr8?%: 'q:ګᨍ 1ȏ`0>5ˆb3j;/(@aנpww?-:̞Җd5oY-U[*4PE /|i;Ϧ .eIW@2CP  +&x0L| G94GBЁA`j eҰ?@ rP|RUɰ ;{3S$~}'bqg:Xz8A%Z9(.aFGx6DC]ã 3m;}˰(6?"a?xbCn1-` ̧̡1GG)a؛|\~:q.\"p8BꗵAjɃ*@1)LOnnB&nr8j)7+/~ڞФPCA>؃ L#y?w/ïJ6m(~Tg0PSd(f1#퉚e7app8~=W^_~k{ކɚ3260`>C ]1 A|WAQ {CE_~_W)PWCD~̎J(DpOzעzu]k'KJc^RDC~b34x0w&€ 9@$$$̻_s1pbCske} +)JBͨ۱{}ŻDy`I#Ofb❋c.p8?ҷ^、}eOGu2 #<(NMd]$$Vҫu]MS^RS) +8(ã v>m: <"ST%,2:6p8~CB}]%*6n{ +sӊ⫭^I|ދFy8߀j}߯cZګ +SVƩ[x0x8i|0kGDoR>o^p8c, ' +;Txu">75B7 l"mg.AV<(y0p ÆBUiۊ=EݶVl+LxSP|^]``w  +42l"~1̦fIȵ~mkMO mrETa/m + +;"pA ;_3D^ȇp8^׏ۊ +ǿ.o41h"CuΥv:;Ț^;{)(bPCg E,^k(fav0p3Qq}{` +\,{CŒp8`z0qo{."e{Epf:1p z +endstream +endobj + +10 0 obj + 32389 +endobj + +11 0 obj + << /ExtGState << /E1 << /SMask << /Type /Mask + /G 1 0 R + /S /Alpha + >> + /Type /ExtGState + >> >> + /XObject << /X2 5 0 R + /X1 9 0 R + >> + >> +endobj + +12 0 obj + << /Length 13 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +269.000000 0.000000 -0.000000 74.234528 0.000000 0.000000 cm +/X1 Do +Q +q +/E1 gs +/X2 Do +Q + +endstream +endobj + +13 0 obj + 118 +endobj + +14 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 269.000000 75.000000 ] + /Resources 11 0 R + /Contents 12 0 R + /Parent 15 0 R + >> +endobj + +15 0 obj + << /Kids [ 14 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +16 0 obj + << /Pages 15 0 R + /Type /Catalog + >> +endobj + +xref +0 17 +0000000000 65535 f +0000000010 00000 n +0000000493 00000 n +0000000515 00000 n +0000001038 00000 n +0000001060 00000 n +0000012016 00000 n +0000012039 00000 n +0000038194 00000 n +0000038218 00000 n +0000070841 00000 n +0000070866 00000 n +0000071206 00000 n +0000071382 00000 n +0000071405 00000 n +0000071583 00000 n +0000071659 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 16 0 R + /Size 17 +>> +startxref +71720 +%%EOF \ No newline at end of file diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.imageset/logotypeFull1.pdf b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.imageset/logotypeFull1.pdf deleted file mode 100644 index 1420a5d7f..000000000 --- a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.imageset/logotypeFull1.pdf +++ /dev/null @@ -1,513 +0,0 @@ -%PDF-1.7 - -1 0 obj - << /BBox [ 0.000000 0.000000 261.000000 67.000000 ] - /Resources << >> - /Subtype /Form - /Length 2 0 R - /Group << /Type /Group - /S /Transparency - >> - /Type /XObject - >> -stream -/DeviceRGB CS -/DeviceRGB cs -q -1.000000 0.000000 -0.000000 1.000000 2.000000 2.000000 cm -0.000000 0.000000 0.000000 scn -0.000000 63.000000 m -257.000000 63.000000 l -257.000000 0.000000 l -0.000000 0.000000 l -0.000000 63.000000 l -h -f -n -Q - -endstream -endobj - -2 0 obj - 234 -endobj - -3 0 obj - << /BBox [ 0.000000 0.000000 261.000000 67.000000 ] - /Resources << >> - /Subtype /Form - /Length 4 0 R - /Group << /Type /Group - /S /Transparency - >> - /Type /XObject - >> -stream -/DeviceRGB CS -/DeviceRGB cs -q -1.000000 0.000000 -0.000000 1.000000 2.000000 1.844727 cm -0.168627 0.564706 0.850980 scn -57.842663 25.387310 m -56.973518 20.943096 50.061783 16.079765 42.122841 15.137188 c -37.982918 14.645527 33.907391 14.194614 29.561213 14.392532 c -22.453136 14.716278 16.844669 16.079762 16.844669 16.079762 c -16.844669 15.391525 16.887453 14.736427 16.972567 14.123863 c -17.896652 7.149250 23.928431 6.731026 29.641823 6.536243 c -35.408352 6.340115 40.542614 7.950329 40.542614 7.950329 c -40.779942 2.765934 l -40.779942 2.765934 36.746300 0.612568 29.561213 0.216728 c -25.598721 0.000004 20.679268 0.315689 14.948763 1.823364 c -2.521337 5.094391 0.384049 18.266270 0.057106 31.631596 c --0.042868 35.599815 0.018828 39.341911 0.018828 42.470993 c -0.018828 56.138123 9.024163 60.143955 9.024163 60.143955 c -13.564885 62.217625 21.356571 63.089451 29.456736 63.155273 c -29.655783 63.155273 l -37.755947 63.089451 45.552582 62.217625 50.093304 60.143955 c -50.093304 60.143955 59.098644 56.138123 59.098644 42.470993 c -59.098644 42.470993 59.211227 32.387894 57.842663 25.387310 c -h -f -n -Q -q -1.000000 0.000000 -0.000000 1.000000 14.485992 26.452484 cm -0.996078 1.000000 0.996078 scn -0.000000 21.175804 m -0.000000 23.165289 1.622104 24.777744 3.622489 24.777744 c -5.623325 24.777744 7.244979 23.165289 7.244979 21.175804 c -7.244979 19.186768 5.623325 17.573866 3.622489 17.573866 c -1.622104 17.573866 0.000000 19.186768 0.000000 21.175804 c -h -51.953178 16.803417 m -51.953178 0.255280 l -45.359833 0.255280 l -45.359833 16.317129 l -45.359833 19.703238 43.926872 21.421368 41.060944 21.421368 c -37.892841 21.421368 36.304512 19.382627 36.304512 15.351715 c -36.304512 6.560461 l -29.749895 6.560461 l -29.749895 15.351715 l -29.749895 19.382627 28.161566 21.421368 24.993464 21.421368 c -22.127537 21.421368 20.694574 19.703238 20.694574 16.317129 c -20.694574 0.255280 l -14.101229 0.255280 l -14.101229 16.803417 l -14.101229 20.185495 14.967223 22.873068 16.706865 24.861656 c -18.500998 26.849798 20.850389 27.868944 23.766304 27.868944 c -27.140659 27.868944 29.695858 26.579786 31.384611 24.000576 c -33.027431 21.262854 l -34.669796 24.000576 l -36.359001 26.579786 38.913750 27.868944 42.288555 27.868944 c -45.204472 27.868944 47.553417 26.849798 49.347549 24.861656 c -51.087193 22.873068 51.953178 20.185495 51.953178 16.803417 c -51.953178 16.803417 l -h -74.667320 8.577215 m -76.027779 10.006529 76.683022 11.806602 76.683022 13.977438 c -76.683022 16.148273 76.027779 17.948345 74.667320 19.324818 c -73.357300 20.754578 71.693764 21.442368 69.678070 21.442368 c -67.661919 21.442368 65.998833 20.754578 64.688812 19.324818 c -63.378338 17.948345 62.723106 16.148273 62.723106 13.977438 c -62.723106 11.806602 63.378338 10.006529 64.688812 8.577215 c -65.998833 7.200743 67.661919 6.512505 69.678070 6.512505 c -71.693764 6.512505 73.357300 7.200743 74.667320 8.577215 c -h -76.683022 27.213799 m -83.184502 27.213799 l -83.184502 0.741074 l -76.683022 0.741074 l -76.683022 3.865234 l -74.717758 1.270798 71.995941 0.000000 68.468475 0.000000 c -65.091866 0.000000 62.219185 1.323635 59.799988 4.024193 c -57.431683 6.724304 56.222076 10.059816 56.222076 13.977438 c -56.222076 17.842222 57.431683 21.178179 59.799988 23.878288 c -62.219185 26.578400 65.091866 27.954872 68.468475 27.954872 c -71.995941 27.954872 74.717758 26.684074 76.683022 24.090088 c -76.683022 27.213799 l -76.683022 27.213799 l -h -105.058311 14.453876 m -106.973137 13.024565 107.931007 11.012690 107.880569 8.471540 c -107.880569 5.770981 106.922699 3.653431 104.957443 2.170834 c -102.991730 0.741074 100.623421 0.000000 97.750740 0.000000 c -92.559738 0.000000 89.031815 2.118000 87.166977 6.300707 c -92.811928 9.635767 l -93.567589 7.359705 95.230667 6.194584 97.750740 6.194584 c -100.068611 6.194584 101.228233 6.936104 101.228233 8.471540 c -101.228233 9.583378 99.716003 10.589090 96.642021 11.383003 c -95.482407 11.700928 94.524994 12.018402 93.769333 12.283487 c -92.711052 12.706638 91.804077 13.183523 91.047966 13.765636 c -89.183136 15.194948 88.225716 17.101593 88.225716 19.536619 c -88.225716 22.131054 89.132698 24.195765 90.947098 25.678362 c -92.811928 27.213799 95.079361 27.954872 97.801178 27.954872 c -102.135201 27.954872 105.310501 26.101961 107.376183 22.342852 c -101.833023 19.166304 l -101.026474 20.965929 99.666016 21.865967 97.801178 21.865967 c -95.835472 21.865967 94.878067 21.124891 94.878067 19.695580 c -94.878067 18.583744 96.389832 17.578032 99.464264 16.783670 c -101.833023 16.254395 103.697403 15.460037 105.058311 14.453876 c -105.058311 14.453876 l -h -125.722229 20.648455 m -120.027290 20.648455 l -120.027290 9.635767 l -120.027290 8.312132 120.531677 7.518219 121.489082 7.147905 c -122.194763 6.882818 123.605659 6.829983 125.722229 6.936106 c -125.722229 0.741074 l -121.338226 0.211800 118.162918 0.635399 116.298080 2.065159 c -114.433701 3.441635 113.526268 5.982782 113.526268 9.635767 c -113.526268 20.648455 l -109.141808 20.648455 l -109.141808 27.213799 l -113.526268 27.213799 l -113.526268 32.561180 l -120.027290 34.625893 l -120.027290 27.213799 l -125.722229 27.213799 l -125.722229 20.648455 l -125.722229 20.648455 l -h -146.436966 8.735956 m -147.747437 10.112877 148.402237 11.860113 148.402237 13.977663 c -148.402237 16.095211 147.747437 17.842445 146.436966 19.218920 c -145.126495 20.595840 143.513840 21.283630 141.548141 21.283630 c -139.582886 21.283630 137.970245 20.595840 136.659760 19.218920 c -135.399734 17.789608 134.744492 16.042374 134.744492 13.977663 c -134.744492 11.912504 135.399734 10.165268 136.659760 8.735956 c -137.970245 7.359482 139.582886 6.671242 141.548141 6.671242 c -143.513840 6.671242 145.126495 7.359482 146.436966 8.735956 c -146.436966 8.735956 l -h -132.073563 4.023972 m -129.503494 6.724081 128.243469 10.006754 128.243469 13.977663 c -128.243469 17.895733 129.503494 21.177954 132.073563 23.878063 c -134.643616 26.578175 137.818924 27.954649 141.548141 27.954649 c -145.277802 27.954649 148.452667 26.578175 151.023178 23.878063 c -153.593689 21.177954 154.903702 17.842447 154.903702 13.977663 c -154.903702 10.059591 153.593689 6.724081 151.023178 4.023972 c -148.452667 1.323414 145.328247 0.000225 141.548141 0.000225 c -137.768478 0.000225 134.643616 1.323414 132.073563 4.023972 c -h -176.626083 8.577215 m -177.936554 10.006529 178.591339 11.806602 178.591339 13.977438 c -178.591339 16.148273 177.936554 17.948345 176.626083 19.324818 c -175.316055 20.754578 173.652527 21.442368 171.636826 21.442368 c -169.620682 21.442368 167.957596 20.754578 166.597137 19.324818 c -165.287109 17.948345 164.631424 16.148273 164.631424 13.977438 c -164.631424 11.806602 165.287109 10.006529 166.597137 8.577215 c -167.957596 7.200743 169.671112 6.512505 171.636826 6.512505 c -173.652527 6.512505 175.316055 7.200743 176.626083 8.577215 c -h -178.591339 37.802887 m -185.092789 37.802887 l -185.092789 0.741074 l -178.591339 0.741074 l -178.591339 3.865234 l -176.676529 1.270798 173.954697 0.000000 170.427216 0.000000 c -167.050613 0.000000 164.127945 1.323635 161.708755 4.024193 c -159.339996 6.724304 158.130402 10.059816 158.130402 13.977438 c -158.130402 17.842222 159.339996 21.178179 161.708755 23.878288 c -164.127945 26.578400 167.050613 27.954872 170.427216 27.954872 c -173.954697 27.954872 176.676529 26.684074 178.591339 24.090088 c -178.591339 37.802887 l -178.591339 37.802887 l -h -207.924301 8.735956 m -209.234329 10.112877 209.889572 11.860113 209.889572 13.977663 c -209.889572 16.095211 209.234329 17.842445 207.924301 19.218920 c -206.613831 20.595840 205.001190 21.283630 203.035477 21.283630 c -201.070221 21.283630 199.457123 20.595840 198.147110 19.218920 c -196.886612 17.789608 196.231812 16.042374 196.231812 13.977663 c -196.231812 11.912504 196.886612 10.165268 198.147110 8.735956 c -199.457123 7.359482 201.070221 6.671242 203.035477 6.671242 c -205.001190 6.671242 206.613831 7.359482 207.924301 8.735956 c -207.924301 8.735956 l -h -193.560883 4.023972 m -190.990372 6.724081 189.730804 10.006754 189.730804 13.977663 c -189.730804 17.895733 190.990372 21.177954 193.560883 23.878063 c -196.131393 26.578175 199.306259 27.954649 203.035477 27.954649 c -206.765137 27.954649 209.940002 26.578175 212.510498 23.878063 c -215.081009 21.177954 216.391037 17.842447 216.391037 13.977663 c -216.391037 10.059591 215.081009 6.724081 212.510498 4.023972 c -209.940002 1.323414 206.815582 0.000225 203.035477 0.000225 c -199.255814 0.000225 196.131393 1.323414 193.560883 4.023972 c -h -244.513977 16.995380 m -244.513977 0.741432 l -238.012482 0.741432 l -238.012482 16.148182 l -238.012482 17.895418 237.559006 19.219053 236.652023 20.224766 c -235.795044 21.124802 234.585434 21.601686 233.023239 21.601686 c -229.343994 21.601686 227.479614 19.430855 227.479614 15.036346 c -227.479614 0.741432 l -220.978149 0.741432 l -220.978149 27.213709 l -227.479614 27.213709 l -227.479614 24.248959 l -229.041824 26.737270 231.511002 27.954782 234.988937 27.954782 c -237.760742 27.954782 240.028625 27.001907 241.792587 25.042873 c -243.606537 23.083838 244.513977 20.436565 244.513977 16.995380 c -f -n -Q - -endstream -endobj - -4 0 obj - 8970 -endobj - -5 0 obj - << /Filter [ /FlateDecode ] - /ColorSpace /DeviceGray - /Width 522 - /Length 6 0 R - /Height 134 - /BitsPerComponent 8 - /Subtype /Image - /Type /XObject - >> -stream -x_T6 1twH H-"ݭ}׽֞ <=\Zk{^9bX[, p>:T*700ɍMMMMLrL - !Q@eGjCϗHrSsKk;'W7wO/o___o/Ow77'G{[kKsSc#C}t*J$bXut Ḽlݽ:x̹/$'?w6))!.6:DhpLzHb}# k;KS}?D}O@Hx3ɗJ '7yQQqqIii벲WEE/?yqڥgc"Bz8Y1\R [ p6j @hpĩ3)d>*(*}˷Ɩή޾ޞ榆ߕ<κ{-l\d\OGgρ[ą[22z[AUK&7u JLEهo -=FF''g恅ŅŅٙ-5?.˺sL.Kykj}Vg?D:2c+g >U7u[XZY[R(m -xT(676Vf&dž:k*^?˹XvH !2p:U5TUzbD"tnuFAUK:T>RoܷCS [ۼ]qq@MH`&L%VۊwOIn2Փ[Yo-i677GM"8,[٠@Py]6Wd'w5FǺFv~AvRX5If֫Bic '09" iGĭw=Jj0j5\L!Yq@ W#TH \L+~|J?$#J ' T߿9{ ZM~< mnR /vj'73U>^d%)A$ׂN0mJFFDPsfjbd8XDpt,@=(ܕt@a7.>coEQ?YnN0\~ n\*p*4Pseٗb('~nW_mlmmkiz7KcsK7nݾ}֭1C3s N((ͳ˧Z`x;]S)mci֝[1Gm ԑ"X*r;v*-#׺֖O/ŇzIOjxaϵM-mBY\ Yy%iI4:p84cp\ZFAɧ,˧C0:,="^Is6A=|K]S P]^{l'Bkx}:ډZ #% F~bHWuÔO+4{N`wɛCS333SC?k=SeenǸkV;*=cIGmڪ_?zޘJ.H~XR^YU3\'z*)wbݵs`fTJ?7uLP3=-__L -q? F"3c 7vPUyt%#s6:HNA]D=?}q'GzZros1ۓ4-_{EG]zHwçwt5\A$ 8 NY2E <)l̂^ލ9P9o{Gg6h_Z~zk'5E0Xj^chCHHomY`'lz"ߵ]d@`_T U`'/?zS54s\_ZD[yolvTQЅD A<%f|W3<J]>6eU dhp㴄wkz6 -|e~N&sd"Gu@ЏtFe~{@`E 8 Qn ~>hc"]kS{׷hb`5V>O^\M/n^Z[][]]}=Rh4chJU% ζbm~ g]˩ܺ`}S8Cn#ʵޏY 2J-nb~ʼns49` i럘QR7:Ơܢ Uܾ{} "}@by*GHzkuaޙ JZ՚|Q - -2q# -ّuX~z9$J*3gMV6Ԁ.ha=H?)m0<8 tсw/^~>N6x!}hja-Vh -K7)ksuEu-DŲ8OI_kUwCU]Z >S]O@Fbcq2 WSJU]>AtUd͋W?ջo3Cs>u"iV"ߨю'ia x 1EtJXjk$( Y `h}~*ktÏMޖ&z G3D`<#'?>$XiAow6>7thWC] - -(׆@ B/,l(UyǏ/H /9%T9v@ |ǔ/}ӫ\haugkQnRJU_7 -b/24QW5³\3:#8P,<,G̼XYj)M|SJLB;fqZd/}u8bʞɆ Odp4\%SA)_s}c\wmReGr*f OJZ_Q6Xy+؊ǠcR;j HË`{K:Z8wIYeɋӚy``C[0f˞SM`rsl*dP,Ccs; 4 AgH# 6y,/ 5bS8% 噉yhTfrsi[sVd4fBIō PF4m\'tAN؀Na OTb ȃ.'$p96@eƗB 큲ʪ1O8 PYBcZ1xqCKDٸ @Jť`$@&'av" 8H6S-ٰ&1oy["GҸ̠̪gTL,ql/x븝) lCO@U"ƖP -:D 'd6? %l,MO.o@ ;ϊ>~ |t^Ojpߡ "Q, _ԌCw\P6!Яa~dE*̽"iH' 6ór{kKHO@ -HJ`%7ΰX[Y\XL\ څ1]kj7f@:.XpFAS -'\ $_/lB4%)#H\A*%P@ ScC/9"BPHrK\n soߚ:7AZ;$淖 :A3sPѰŒkF,%^y64ѕɩY,`bkb&*Q+})[&((oO ,K*AH JwW}LSVfEzV=sWct܁QYrNY`(lT?ZlUn\yPn -J 4K<;Hd'wsQZ\dxĩ䌢ogVg[p ghvMխV\[3OFXИKߛ*˞7`zՂ:jY7&rfU^o<7;'1:W?FIÅVSE'9rh&yl@bs$Fvm{ݛ'WS.\HcRFqs0C.)>uT&AG^UFqBpF -ؚi-L:Eg} ~Р7r5 Ҝ -$Rw?lP<>72IU@wφYXx\,zŷ1'9w/ ;}ރAqBҟmċ9F++*E:%߸ǵhIxRNG;|<'hZ<@ Nj| 7Ͽx*{o?~173.Ƴ uҐzَpwS->,7( g+z^59"t*9}@Z*@,ـnhY+ҎQEGS;fide>Lsq -sA#Abڅg~/ҏOzX"|sEq/cD#82TDŽ]O;cP#qB!L}yv31쨻]X3k;@]qN dKS&H7;lWSd0Pq/eV!?|_^+g0$yؚ MmCdmW@esb:%GKb/POWG+78VPݿB 9<(A>aW6S)$mdjr.6ɂEIW HL/ `)H#%nQwt:ܯo"eGDF;Lpx`E`R=r"̂U2 !uc#ez@zea^TDffB 9;dY5lNF 3 ;l.ąm(60Wl #:bs>vIPn}x`g>Sw_wLdͩf uF`p:/;8 oj-uҝYؠgeR r׸6lMIg'@j/>c _Na4=k~~J#x~mD9 KJ - itь̤`R"@9uE]+o/!+XYi&fݱg -C$'Cڅ: -~JQ@|{dfkhnH̴kh$S&?\bbyqQ+4&D=s=h2*b߻AN?t?copڜk_B+w\2.E%'Pct/ i3~ZIѷI|Xտ iGAhAǽ>q7ܞ "U岞y _ -Hj nrP7jDAm"`} `hᙢ$$Y g~:a8\p\P6 @N @k|r #䰳_]pQ'Kc}҂pvMD SCoW '[_Zvk8D2S ,S"嵑LHdS7!(G=N4%H1^0mgQ% =~Ib}Ej 6mᢆXBa] Îbe)Ր -[|^$r)_$JSC89jRսc^Nfr:J yF޲ޚO[֤F8hi#dڦ[ӭnrvDޔ\:]ܺ5d@e$`;\ PDҏ;pU )\܊ n(zs1єZ6kJF1w`+Kb#ϤgX-9casqs1G-OKJ(UIJ ;rg)Ÿ9 6pW5BVSk&BP<+ ҳ>vE$AYfF ?Sey=?r[<UAqm/t$%cTcMJ]/a6$iky~d̐ *F7 hq W~ QYJH.A[g,l~k^<ꇟ+<2j+Xi,a=n#لe|V08=?I3}9DdUsMFTt,XҔ1P4J@TwCmP܌GGF =*4d) QǨ4P? n/ ]FJj܄.*'"/VNKpňa߁Qzstf%vO4;1MA[#^!*2m549׳|P398fH-@ W+5~;UkDH$ t^#$1Va. -9,03W4dO.(Wb%lt5(72rtɬ2iMdk 78T ZAc% DG ؘlx\ؘ)`\, lfs.;!(@!&:Iѹ9%@ x  gɂ0:@8nJgL\UC=pB#]f(fJ@VN>%ig}wHdryZA[(W3=~ٷt\`2T}izoT!F {(8<:E(3X~XݠE$xCi`gQAȱٗo "6P5U*([4 B 4: -g15ӆ/,' 12bK R 0"=I%]a'G(AD|3t8t>)ohAEhe6W -BNPUbvSBv-Q&Zps`7x[ 4r1:5 :o(3)\#Xj2N-@p -@KtyagxU}^1b:ˮBJ#(+3܅ } c(?êy޷ .^<.7`fIim}{m$F9%OP 1DC.[݉L~ b"2y5%0~%(@ ;P۠L`J 7. nTkB8X,-(̎ h@@spΎb ֠Cb,rJ 9A3:t͜bR=}W;49BXCY#)E/&'2.xCfZ X0"2}9$74~`MpE t膒F6,8wyTj#0')PE< pW`La*FP8`[ hT@'0wl,V9qY8`ۡ8[j71'_f-J-"/(lZ\bJb移a‡EF ă -a Z)B^'V_P "7/oYWDɉ^-3䰍mAIKξ|\>,,+%/İ(bt@{(jl?!Y昢5Z3*,@\N^wߗa??^ v!aS#vx ґ=#W6}XTO+˚0:B -990HL5RVc2F Rˠ|/PB&f)8?Ay%}aEbCO ~ \NE&!tS~,SِC,t#i$FU 9"(3hmVyp@͞q"R='32KPxEtA:!'`'*4$yX"ڜKc{Cbb(Sk9*YT*FiPx%gڱ#O6',1D"2j -5 hAkV-,$Hdq$]a6 $]3*$DlPh >Q,$Ú'di=vQn_A <`1qhXt#3}'`= Z0 'luX W *jA$72518%,\'^N 'h ] wm@p;o߷KNo5\z¤cc[ 4KLДꦜj~zJ# ^1:<K&qO!qR,}GdzAJX`9 BSb ,xQp$g5:#Y؅*~*t3KQ¬ G -}A(m2ʕ8w][F(: EHbԚ;D:^v&`& Ll=Bg/U!e u= -k?0eTaG2p^2vI: :cksbYS2*B({']1"48x=X#qDCy_ ;N"W£aFGHzc6;M6\$FQ^@,cqjK&uBP=Π@1Ha  @@Ybrm\NFڛ"'AvHbTQDǜx#G_E17G;rJ0%Sk[~ݗ^cʏ23TkB䝷tz QE Ggw"JקJwS}+sOjG1@&a<2S}HA5$2Sw 0BAIÉN0V'T#e} [`0 -|\Π6 -  И` dT /;  'Z^^ s6UXg~Ċ%Zcd퇁5̝')!˪"Mȵ7I;ce@ g^vaQ^pГn oF)P~ᄭ%}ܑ}sZ&H;kVֳ*Zd!#hb6{ݍyBWؙ%lzH߆N=VYԺ*Ylz39F/^),2\,LDɁ+JdrM.v `F=q *IGX*2.pk2D,ё:$fbg@ ǀ/CJ"]3t}N - b-o[cC}}==$UO #e{{ur=:t/9,naǓq)Fr'O{XeÖcЙO;o p: Ų\h`n@uef~qK # 7#߲ Q PW D ha_CŠ51A`֤B0ГZO*# do "aHj|%9o.׿pÎ^01vHS AkdM_\8H/JeFo#^dQ]O_&}.іiJv ;[m^wvp󋼐yv|4@8~SŁw⏹SYXYRs+zfXYxŀ'HpDŗ!;S"(i+;YoF({^_ dI%%j3(D[5Dh# Gn ْ - PQUGb^ㄊ)7FZ>^K8o*@ Y>()eu܏y)k{Oj`).+KGhv@ktMuݼ{򄿓Xlx}xG:~ZBTXHHXT|ڽU]ߒp)WGjrNE^瞵R=Je?QT}5rP '2HgNGNx{bhͣ874]<rT PB2F9<\}3/;p!7'h&#JMP1pk %lCoFWn-Oս-#-99ꝜCs  - M -Umsk퍙^O9vxd܊^uvDѳGX,BbQ:{uX\V0삲$'G. -_}xzy([Ko 3o\rΣUmХP9XRCJH% ;y%Uk -3W$PhW* gh?hAx> `Mq j`a\1HȭY2(X_s7%e+:p'N'3>tNmo V=񠠬w1~w5@8®.Qc?Z}HJ9"?}_@mKmKm~#<__yY8 la8bge)+EY;Tea -"%xcDƧB{ZjYMlvg{u[͚[܃Ea>;ts9!iA7׷(lt+1'J B^^%Ҁ@ƫ٘7*mh?L1nte>NtT:x)DRSD!=@7h %UBc/0Y$/}^?Bndt`τ͹ˁر#@0 5 P2k#WD:75& d:wC,//Je!3( -}ΩE Gta7J&6HLo:PҪ²1% d%.hQ`T\ZO.<sbn ̀0 ZK/':G1%"!F#4^C?޵O=*BVnLv}J»WMKo$hyevblj%@NP7vc߶a< ~tQpD<~fWabӾS GA)nJW;X.@731F O`vym?-4m-i5ֈJf>Yc䈬@N7ZJp{>u 5^qe e;VZHjq#yWL(D_[K4qe1wZj*hی8ómF l -CWUH%` N>B*P -S aPӤ4(A#u7^VVQ}^6!o\LYIVMyZEC۠ Oz& R! 'JnE3b}o3ډ0:$z17}F䇁JK`e8=o"lE\}=V\ix~`CJn%8)ሎ]йw̓3mtQ@\ϮGkT!$5sx0A$1peUSγ?N&ѕ֎cN4AkhU\=06=7,M 6Uzv҇ -zSIk|q6'jZKLtl;29e4> RM4UgIZdyS__Wq;0O|QQW߀T>Ҩު\=-/0U - j[p[{a턟m555},*6VA$lZ泷:{z{{:?-H3LQgյ'x7 !{;[{`7 Zh@pܵGEť%%/ 3/jɕ#3sx7[34?=%:L e1s -aSn?@ҨyٷROEZ%/Ϡ'kttԉ w+m&Z{;%{̋VfdPA0ƛX4 - h DO)q'B,5X?,& gbNxؚHb}cK;GGG; /30q 8E!/^ptLX[iD7dmwںG%p19|R\TQW[3+@$-<O$Knk#X9$֪hhH䩤Ɉ\ҩУn ~gPfVYl>^.6Hc*ũo`hdllld$7<@xM]^|@/V 7rd.DG CYr'Y;S>,i3oXDGDH=dYV=Ͼܒڊtʠe<³j҄[rceZ%dRd8"TTXNj=0e/D"r-+7@Yw Db@s7i@MFmZ0 -&Qn)XMCгV}1s\ ٨\z$ ,3ӈAD럦!Hm/Ʃ[4qF\W,B7higgsiJQ0kGP gA7`b\jl]=u$fKph@K"@Ś?̠DgWw!^20/jb.P=ӎrsi"7%i! Л@M: EH[ `!*b+M =(ҧЦh<\ - RȠ%MVG:ވx?RPJ0A7` @;;۠WY珻9;ğs'ݒ ܎T<@Lpء&ώtה>LpH.yx>`m FfBknm) B`M09Q]s96pd!2t}$j@d/²iCx4 VkK E. -t{Uq}=?G襢m$ ~͍Օ兹Oŏo_p:Ep?z{y]eMc[щUz5p X[[]YZTSÛɧB},iC z:C+ -4؞Qs+rZs`hdtlbbrjzrbb|ltdh{og[s}uէ%n% q1LSEGK1Yr5?$"6)ʭ{ٹE%ߗW~ohllhRYmgy9w_JN >Hz͙C9ZP(?s+;'woQg/^q;~VǹdgϸsjsC+T!GߞG4̀(}M_rc3+[{'Wo_'#cbbcOF8|,T}fIG2L;Z Гr+7 @V66t<ё[[Y>=]:"yH@J[DQ0[8}PG/ӓtuu@φXv2#1;ON# Y_؞.I\!wI4^hp8"=_ 92J[R8,tL<E@\CBgrMp3(7g{ާt7;$ 1r{X=".n5v&KoG{X -W 0 -LRZ&װ݌B +c-e N2Z8<"H#5%C 1p {}DwV>䤝w132""U$P(@, Mm]]t!1x=>A -Yi<`fl$7407744곅O`XL\q=_&͍UXƦbcuӍCJC$5v -clq] 6(GRZ\Pl)Rloөw8quV!%!5HWZcjySGF"wo OռSK[$/qPqqf +7-)PKpҝg-?V60f47΀' [% ?Os`|viuC r@'pU`8=1[uC`js̍Gu}C +[L,Dk̐/ 秆zZ><d{x"WplozG&fTf -兙]U EzтOHws~Z}`xtbjfnnaqqq /s/%9[ZHGċ3|󩪺pݫ2^8fKG9D"T_nnv2|<+*~UC7/''Dzؚ(0LM,\< =x셋 ؏ ;hkalM߀X#302c]=<&||<ݜl,͌/ ~!D5]]kddlbjbbblldh'+8ALe> -stream -x͎FaLvmc -/۹\/EO)J,gY| -)"e_.,˲㏆~jO?Qm\.K]weYwrLJ~믿_W*zA4e_T˗W[*QE/jC'_Z:37re6? |SґW㊫7 2H4'iAz,|@O=˳p.EO8TrCSӇFk| -q^/˲ ۹z+_|_PbMA)jA5j`6cgNS˲e - 0h+B;d8X@Y5WOnOeY. - B\>wǑ.j9$Fc8x"㧉7Bg-5ay~7qwG;̕Beu(="Gk2U5 T7\T^VGa;/Ç@)GRAC!lGiDy7Gq_ }uykKoLypUiG+zT3ZS5_%{g_0P7d^k b}vx'G~q4"8h J&xl M{`yY],^³}ySċ %s,8:|H˻լ`Hڌ'ʹz,)=sn D|MM=P_zA;wө{<| L@B5 *Hgp6\'^k(75 !cgU:C TPEnF =ԓ@PvN{ƉM>3S -6W|_ky @ 6X5.U{0P5AH4ĎGSiQ867e -jPA'#3jcZuy巂j^kz g8p5=V%Da4'a6 9@vK~齩G$ǫXS^4@e9j@ź;|ʣ%T=4Ah줂 9$@=vs;W{pz;Gu|.J;p6'|Ȋٴ6&HPkN+ Vx=~p|47gso p"GkjY9ZM{@@]X -@ғ0U5[?/㧘o;O(8A4>&|au'Oq;OET9d 0[eV SI=D?'1jpU!m ~gêwr>nm9 D IyP#p^/[ BVú {f,ח}ƃٹl?< -``Y^}2׊1`bZcEl6[a1c{T|S:?%͋C^Ŝm6^A`ue_M|fqOa%$ GR *&\+=+ _ߠW/oͶ=PrQ+W>Vp@?}o`Bm 0γxSKj'Gl6<4*ٲO##̈́c4Aڬ9촊 M ' O& ꫯί= rapBrM :ڋǯV^ &fzmo>ʑgm`7*3l*ڦF{KZ^JT̙b @ςZOk䔖[KE -N T$ 9A4A5 až蘭zM=kR_97Uܼ_,w='mZVM<쁊oř-3` >;O]mϢņ}-SQ{mI9i_/WKZ_ߡgW3A! ^Kf/%j5^j=hiaj|6 ̈́N^cD\/jgg>oX*az?AXiYIR:x)P>dlR(~Zڀqoѽ =JC1wX+"`>}pk`S9+H]gў<xլcFk>I@jjAf֦u&1XP4 15j5n$[{3= 83{~u*[@+Yu7y Tucx{Pe۶ݜԵc<{2JU͍S] ->Ʒ=Td3^W=o7*\ -qN$3=^nd&LPmAlawlvx|U4 ah!B=n02!Af uL 0FdEzjjC'H9A{ӽ`b65y|o =㣵 B'veYfx 뽉gDu|WkfM0V VZ?MFl'տxzsg3nϑŃffrB66v1 T6?Q]=X*s OuQف QgKNC6s}FVY65A8Є1fT$`/OM >meGއpgT]6NE@@*Wwׄ:gSt` e~gt1 Ԧvk').b/T"0ŭ ǘ:N& Ԫ*rS;0PYsu -Sqp揦ӎfBE{ڣg.AdI~[@0P3fMsWI{lSu -S<j~o:g͢G[$ 9O$LɯN1}igY@`} fYf*+z@8P=ӳwy ?8}4N84]vA8;jSgO,kW\JLGII@ȕkG'W~}+Yf*+vH}:U=Nw8m5>:N2$3ΏlzU; ަ~jҟedLҙФtaCVA* TЂ5L{ 0V'tb*Ua0PpID֘u\m5B0IE2^uA᭲DXȑfB΄&Vn|AکȧfA* TeͣK3PA:?!|D=溂`" -jos&T9h3W,2)rM,"P`E½ ΚQ56 M` M  ȡin̠քISO6͐fZsm#VȘ@ wnmTa|k5LA5a d0yHA½Ռl˶5 *2 4YJydpRLgmf]9⬠\EfKU<X5曜Ep{wDM{Ȧ@ YPSTT3h5j @E_gŽs{3۶] -&'|/dx7V|W!,zyy_Wt&RV3q\*ida۶ˮVm֬f{Y'5ږG} -oԆNz:BLT*z@0PA ʂ*2[5U; T"  vhWSH=-cͬEFrM " ƀ $z^g>I -ƀdRMeg 5eP*O)ME AA2[<0P_޾I'' !/BsSSؽu~̵Ko92^i+eqS5c:mZIa滌 PLUm ֩:4qWS51fgrѡ̣ oњxFUPF"/1MuEmhG#fm|urkfamgϣzUmnxm,ˆIQ{ڪU7;Tk"#_cMwȔ <A֬Cf"7ǝ yOSsΚњo,7Gv.+_'.bo{Z]9Ax#~Nl5 7o{:yscbm;||{⍴եN3*6ub'RH3aO@ P 5h@Ig$ZAp,B65&?ߢ0-\:jj6i -f -BDM*& \$;>#MK 09 ~xLޠ2-{x?B m |L9eՑf+=^N׽e]W`"S<Bcga -cz@PHs@f͢!Qk==!'?VQ0 ceE0(`@@³OfP.n$"L4ZcZOkbxTfT{(MC Χ`"'SL/Ap7Mo.<&$'pfJ8 恁 -^w*@zj۶˿s~kz5P6aW;lJ{7Nji ǫ/\lt֌WpӤ66CB5wZGG{l {PYr#UǓE>ps@4AFm"PTJ'xNrfןSn@⤢] -0Pa\u]eLH ^u?<;/;ͺz(׫Ga*k3vZGp^/zkfc:\[<\ -YbἶT&٪xwFS7XDdռ zD`@AdP)>P8xR՗̞nTh6cg^MnRwA+9E>o 8 -u TxLJ,DͬF\QS'SȭX_RTɎbIvAo@eq* B΄Y:Ah줂71 f={9NZ֓j},X}mUsYd3,Hio|\ sиU7|5`,QYC.S ǐiI@ ˰iA5#̈́L'M*zD*0PE3#xzI #~{pF>c.0S5Vb믹oYEU$xR*ھR[Jir*H. 8ދcD=6A@B88ʍ?؞q lXIPk|&<-fل,kM**SCњSfjҟ7*\aoF۶]^~6^`y"k05PA@½\Њ̾|O?+\ic8"krDjl)T -b<~]=u֧pKh}[7l A& c9x>TsL;Mi4A4j=˿a)<5 q}T^SԪmOlcFWi RE|vڜbfÓם9dcnRahԛk._Yd$5p}xMޚQ<8]iV6hI[0jLQoM#jVzi,(3Ud7 S7@Y72{F}#?SUqSm$lS,a\mN7 S٘d,P1}G(uk~$RA8ͨ_m硂=T>Md#] Si%@Pͨ͛95&Uz=TAЙ,{yģ ..E ymOi ػ;lvw\̽GTY{ V)4g\&2zɄT3})=)PU`Zdy.,x{jcT|AǶm)ǖ%޸V1p Q^ Hpu7x 3 M: 0PtZPjnCcyglZ{68rNM/./ո'#i7m^HAT!pkϖDM8XWktNqPGYU m.сLNUjْ" 4d:d&ȡ9Ah朜^|pRS`;i^U6PQCpdYϒD%#S7`HG;bQƃ0U9{` a@Bi F:3jK$^ӆAdG۶]^n-wMp ~v&T}(> a@q>X Ϩ ` h0 B5v?M/ѣL8`@̟ӻñLR `XCjIᤃy(iUrZ EX/aV&bi Sbf$ymmjR:x_7+ryAjI3qa- MԚ B&QppZAHɀ02Ir݋Mk: 63̈́3`T V]\ -Ud;aӪ#* PNq j'UAtP=J?LOm.Re U֕PΚN*pzNemZgYmPA@lZAwG~w>Wg V4PAH38ڑޜ'pz,/vVN;:-ZӝVf >#1^quOM T%@j='xO aM\ z&5$4Apĝfa44?^$<* -aohhM Toa a4Cjش9_2y'lR#MA< FꪹgI 79jZS\1ƀ rش0fj臁Amaʓaʥ`.n=ZE! *[…ڟ<Z3mMz+jc_78 -'gjpQA|[7KTwK9̵q'0kc 9Z'͊@Z= -Ud T1XAyThMUdqAϮ, QzZZOHd9{|z3[Nb h:eZ$ lO!ӂ2@ _1p#%5Z@[ *[`j5ɭ=Vj=Nܰ9ԃu+J''uuʰ=@@BE'a - hh1l՘a)VwB 1j*'DT 'ۖȴ3[|J\t*WMYN۶]un;.T 6y@/pp?>u kYf+ ֻHWH[gNRz@NLPޅRx  0XAaTVc4 @Ad*z@@hд1<954y0geYJ&\R0&n?kt]0[Us|zdǪA@h{z˂[ LSӋIbE (ȻtMA\=prX#A8ڙ\@%  5Ԛ - @x*3Pcz(6Գ*𰼡rSqf7Ҋ.6S廝թN[tX1 M^n-9& 2#?j=Ys5*32PaְiՃ[!O9 @,W+[(F U]<ߢ<#66y ROba"򸮆u!d`j])9:nTS _ERmvdxFm./\BgmZG{ Tњ\4[Sse SOc -Be.n^^r@W 3|e)Sf_{k-W?ϫqwME~h& |lon SuQ9ٓ *y=l'{1 m٨U^r&ZMQ5c$ ҟ@m65ϨcQ@B]I'qc& a6n MeExQMu>/%W/Wkpg^'9rhVul6[ӡfbWSz^!3%+'׸E y ~:|0@k{8pbp!S@@ pUfi: M5eg* ʹsδ6Ldýyx)%^^$aض1d^:qNƫ -(^]"Y~:՛Rorkע9T\˙xf[5B 5yǶm'O*rbu}^{ka'7Q0NsjiC{1* rBشM5o!=^epwNq (5hf{d? Bz4X+{{5sCáeYώ}5cB@8cihf|0=cczDͲ,gÞ\ A"M#Ǫ+D\eY= Ad:ADzPIEj'T#7փ,%B.h*3j=4aHEB35O jn۶˲,˗Άu87~* 3)3U{6#GeY/ߐP׽RY{2$p#A:ahvg˲,_4 9:x@[5N# eA{,,Oik,@jO2!h>foYA@62~e.˲I* xUa)5A `@nc²,*@ a.T/V_s ڴNVcOj,'70zu -7,rOjS/5 4jW_O)7*l֦}h Ҳ, gEs'hj?o뽣R4,:+?CZei/7qG4 1^G]#wx#} ضmu/Xe9/j{샠iSqyVzaYedS+z@4T5o.˲,#E"c]eY>_~?mO·yyYe,5=ҙ3_T¯,˲z' s8˲,k|~' B,9%=W"LEUGʟA[,rw:Oaޣt۶˛_U,Ȼ=|Z=j,r TՌ4'Tԋy/2s3~,˲ܴ}~i;JNz@T5&\/˲,w \oE\TfkEjh\W*iA>\ eY1?0~.͞4 V$(̈́& ;PmeYeyͥ\+:AڬY7{j\3SX,FN L塄=u^t&TtuwXey/׿ֿ6Se"ajhM=N,˲<,.i&Dz Td6vRA@B=UzY: :3j4AVcC]eYwyfG"= = -KeY|zp/o |;ʖeYxuݶR}i7 .T1+eY-xQ?]i0²,~?mU[_ -1ƀ0]5߽>/,˲C?6p*.@}PJ|Zv@8ǶmeYv.ԹsE>̗W߿?aYѲ,TߟAEr^eY`]eYP@ -endstream -endobj - -8 0 obj - 14846 -endobj - -9 0 obj - << /ExtGState << /E1 << /SMask << /Type /Mask - /G 1 0 R - /S /Alpha - >> - /Type /ExtGState - >> >> - /XObject << /X2 3 0 R - /X1 7 0 R - >> - >> -endobj - -10 0 obj - << /Length 11 0 R >> -stream -/DeviceRGB CS -/DeviceRGB cs -q -261.000000 0.000000 -0.000000 67.000000 0.000000 0.000000 cm -/X1 Do -Q -q -/E1 gs -/X2 Do -Q - -endstream -endobj - -11 0 obj - 118 -endobj - -12 0 obj - << /Annots [] - /Type /Page - /MediaBox [ 0.000000 0.000000 261.000000 67.000000 ] - /Resources 9 0 R - /Contents 10 0 R - /Parent 13 0 R - >> -endobj - -13 0 obj - << /Kids [ 12 0 R ] - /Count 1 - /Type /Pages - >> -endobj - -14 0 obj - << /Type /Catalog - /Pages 13 0 R - >> -endobj - -xref -0 15 -0000000000 65535 f -0000000010 00000 n -0000000493 00000 n -0000000515 00000 n -0000009734 00000 n -0000009757 00000 n -0000031385 00000 n -0000031409 00000 n -0000046488 00000 n -0000046512 00000 n -0000046851 00000 n -0000047027 00000 n -0000047050 00000 n -0000047227 00000 n -0000047303 00000 n -trailer -<< /ID [ (some) (id) ] - /Root 14 0 R - /Size 15 ->> -startxref -47364 -%%EOF \ No newline at end of file diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.large.imageset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.large.imageset/Contents.json deleted file mode 100644 index e26db2fac..000000000 --- a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.large.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "logotypeFull1.large.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.large.imageset/logotypeFull1.large.pdf b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.large.imageset/logotypeFull1.large.pdf deleted file mode 100644 index d4d478ef6..000000000 Binary files a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Welcome/mastodon.logo.large.imageset/logotypeFull1.large.pdf and /dev/null differ diff --git a/MastodonSDK/Sources/MastodonAsset/Generated/Assets.swift b/MastodonSDK/Sources/MastodonAsset/Generated/Assets.swift index 25dcc168e..27914629d 100644 --- a/MastodonSDK/Sources/MastodonAsset/Generated/Assets.swift +++ b/MastodonSDK/Sources/MastodonAsset/Generated/Assets.swift @@ -49,6 +49,8 @@ public enum Asset { public static let actionToolbar = ColorAsset(name: "Colors/Button/action.toolbar") public static let disabled = ColorAsset(name: "Colors/Button/disabled") public static let inactive = ColorAsset(name: "Colors/Button/inactive") + public static let tagFollow = ColorAsset(name: "Colors/Button/tagFollow") + public static let tagUnfollow = ColorAsset(name: "Colors/Button/tagUnfollow") } public enum Icon { public static let plus = ColorAsset(name: "Colors/Icon/plus") @@ -173,8 +175,7 @@ public enum Asset { public enum Profile { public enum About { public static let bioAboutFieldVerifiedBackground = ColorAsset(name: "Scene/Profile/About/bio.about.field.verified.background") - public static let bioAboutFieldVerifiedCheckmark = ColorAsset(name: "Scene/Profile/About/bio.about.field.verified.checkmark") - public static let bioAboutFieldVerifiedLink = ColorAsset(name: "Scene/Profile/About/bio.about.field.verified.link") + public static let bioAboutFieldVerifiedText = ColorAsset(name: "Scene/Profile/About/bio.about.field.verified.text") } public enum Banner { public static let bioEditBackgroundGray = ColorAsset(name: "Scene/Profile/Banner/bio.edit.background.gray") @@ -209,10 +210,7 @@ public enum Asset { public static let elephantThreeOnGrassWithTreeThree = ImageAsset(name: "Scene/Welcome/illustration/elephant.three.on.grass.with.tree.three") public static let elephantThreeOnGrassWithTreeTwo = ImageAsset(name: "Scene/Welcome/illustration/elephant.three.on.grass.with.tree.two") } - public static let mastodonLogoBlack = ImageAsset(name: "Scene/Welcome/mastodon.logo.black") - public static let mastodonLogoBlackLarge = ImageAsset(name: "Scene/Welcome/mastodon.logo.black.large") public static let mastodonLogo = ImageAsset(name: "Scene/Welcome/mastodon.logo") - public static let mastodonLogoLarge = ImageAsset(name: "Scene/Welcome/mastodon.logo.large") public static let signInButtonBackground = ColorAsset(name: "Scene/Welcome/sign.in.button.background") } } diff --git a/MastodonSDK/Sources/MastodonCore/AppContext.swift b/MastodonSDK/Sources/MastodonCore/AppContext.swift index d44c1ea5a..d8aa06fae 100644 --- a/MastodonSDK/Sources/MastodonCore/AppContext.swift +++ b/MastodonSDK/Sources/MastodonCore/AppContext.swift @@ -115,6 +115,10 @@ public class AppContext: ObservableObject { .store(in: &disposeBag) } + deinit { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + } + } extension AppContext { diff --git a/MastodonSDK/Sources/MastodonCore/Extension/CoreDataStack/Instance.swift b/MastodonSDK/Sources/MastodonCore/Extension/CoreDataStack/Instance.swift index 4192b68a2..eebb16be4 100644 --- a/MastodonSDK/Sources/MastodonCore/Extension/CoreDataStack/Instance.swift +++ b/MastodonSDK/Sources/MastodonCore/Extension/CoreDataStack/Instance.swift @@ -23,3 +23,47 @@ extension Instance { return try? JSONEncoder().encode(configuration) } } + +extension Instance { + public var configurationV2: Mastodon.Entity.V2.Instance.Configuration? { + guard + let configurationRaw = configurationV2Raw, + let configuration = try? JSONDecoder().decode( + Mastodon.Entity.V2.Instance.Configuration.self, + from: configurationRaw + ) + else { + return nil + } + + return configuration + } + + static func encodeV2(configuration: Mastodon.Entity.V2.Instance.Configuration) -> Data? { + return try? JSONEncoder().encode(configuration) + } +} + +extension Instance { + public var canFollowTags: Bool { + version?.majorServerVersion(greaterThanOrEquals: 4) ?? false // following Tags is support beginning with Mastodon v4.0.0 + } + + var isTranslationEnabled: Bool { + if let configuration = configurationV2 { + return configuration.translation?.enabled == true + } + return false + } +} + +extension String { + public func majorServerVersion(greaterThanOrEquals comparedVersion: Int) -> Bool { + guard + let majorVersionString = split(separator: ".").first, + let majorVersionInt = Int(majorVersionString) + else { return false } + + return majorVersionInt >= comparedVersion + } +} diff --git a/MastodonSDK/Sources/MastodonCore/Extension/CoreDataStack/Status+Property.swift b/MastodonSDK/Sources/MastodonCore/Extension/CoreDataStack/Status+Property.swift index c4508a997..9e92e0d2c 100644 --- a/MastodonSDK/Sources/MastodonCore/Extension/CoreDataStack/Status+Property.swift +++ b/MastodonSDK/Sources/MastodonCore/Extension/CoreDataStack/Status+Property.swift @@ -51,14 +51,28 @@ extension Mastodon.Entity.Status { guard let mediaAttachments = mediaAttachments else { return [] } let attachments = mediaAttachments.compactMap { media -> MastodonAttachment? in - guard let kind = media.attachmentKind, - let meta = media.meta, - let original = meta.original, - let width = original.width, // audio has width/height - let height = original.height + guard let kind = media.attachmentKind else { return nil } - - let durationMS: Int? = original.duration.flatMap { Int($0 * 1000) } + + let width: Int; + let height: Int; + let durationMS: Int?; + + if let meta = media.meta, + let original = meta.original, + let originalWidth = original.width, + let originalHeight = original.height { + width = originalWidth // audio has width/height + height = originalHeight + durationMS = original.duration.map { Int($0 * 1000) } + } + else { + // In case metadata field is missing, use default values. + width = 32; + height = 32; + durationMS = nil; + } + return MastodonAttachment( id: media.id, kind: kind, diff --git a/MastodonSDK/Sources/MastodonCore/Extension/CoreDataStack/Tag+Property.swift b/MastodonSDK/Sources/MastodonCore/Extension/CoreDataStack/Tag+Property.swift index 633f7bddf..7411fd960 100644 --- a/MastodonSDK/Sources/MastodonCore/Extension/CoreDataStack/Tag+Property.swift +++ b/MastodonSDK/Sources/MastodonCore/Extension/CoreDataStack/Tag+Property.swift @@ -22,6 +22,7 @@ extension Tag.Property { updatedAt: networkDate, name: entity.name, url: entity.url, + following: entity.following ?? false, histories: { guard let histories = entity.history else { return [] } let result: [MastodonTagHistory] = histories.map { history in diff --git a/MastodonSDK/Sources/MastodonCore/Extension/NSItemProvider.swift b/MastodonSDK/Sources/MastodonCore/Extension/NSItemProvider.swift index c6fbff4f5..c5b8d513c 100644 --- a/MastodonSDK/Sources/MastodonCore/Extension/NSItemProvider.swift +++ b/MastodonSDK/Sources/MastodonCore/Extension/NSItemProvider.swift @@ -65,7 +65,7 @@ extension NSItemProvider { } let data = NSMutableData() - guard let imageDestination = CGImageDestinationCreateWithData(data, kUTTypeJPEG, 1, nil) else { + guard let imageDestination = CGImageDestinationCreateWithData(data, UTType.jpeg.identifier as CFString, 1, nil) else { continuation.resume(with: .success(nil)) assertionFailure() return diff --git a/MastodonSDK/Sources/MastodonCore/FetchedResultsController/FollowedTagsFetchedResultController.swift b/MastodonSDK/Sources/MastodonCore/FetchedResultsController/FollowedTagsFetchedResultController.swift new file mode 100644 index 000000000..8c5c64b58 --- /dev/null +++ b/MastodonSDK/Sources/MastodonCore/FetchedResultsController/FollowedTagsFetchedResultController.swift @@ -0,0 +1,77 @@ +// +// FollowedTagsFetchedResultController.swift +// +// +// Created by Marcus Kida on 23.11.22. +// + +import os.log +import UIKit +import Combine +import CoreData +import CoreDataStack +import MastodonSDK + +public final class FollowedTagsFetchedResultController: NSObject { + + var disposeBag = Set() + + let fetchedResultsController: NSFetchedResultsController + + // input + @Published public var domain: String? = nil + @Published public var user: MastodonUser? = nil + + // output + @Published public private(set) var records: [Tag] = [] + + public init(managedObjectContext: NSManagedObjectContext, domain: String, user: MastodonUser) { + self.domain = domain + self.fetchedResultsController = { + let fetchRequest = Tag.sortedFetchRequest + fetchRequest.predicate = Tag.predicate(domain: domain, following: true, by: user) + fetchRequest.sortDescriptors = Tag.defaultSortDescriptors + fetchRequest.returnsObjectsAsFaults = false + fetchRequest.fetchBatchSize = 20 + let controller = NSFetchedResultsController( + fetchRequest: fetchRequest, + managedObjectContext: managedObjectContext, + sectionNameKeyPath: nil, + cacheName: nil + ) + + return controller + }() + super.init() + + fetchedResultsController.delegate = self + try? fetchedResultsController.performFetch() + + Publishers.CombineLatest( + self.$domain, + self.$user + ) + .receive(on: DispatchQueue.main) + .sink { [weak self] domain, user in + guard let self = self, let domain = domain, let user = user else { return } + self.fetchedResultsController.fetchRequest.predicate = Tag.predicate(domain: domain, following: true, by: user) + do { + try self.fetchedResultsController.performFetch() + } catch { + assertionFailure(error.localizedDescription) + } + } + .store(in: &disposeBag) + } + +} + +// MARK: - NSFetchedResultsControllerDelegate +extension FollowedTagsFetchedResultController: NSFetchedResultsControllerDelegate { + public func controller(_ controller: NSFetchedResultsController, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) { + os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + + let objects = fetchedResultsController.fetchedObjects ?? [] + self.records = objects + } +} diff --git a/MastodonSDK/Sources/MastodonCore/Persistence/Persistence+Card.swift b/MastodonSDK/Sources/MastodonCore/Persistence/Persistence+Card.swift new file mode 100644 index 000000000..9ab8a817c --- /dev/null +++ b/MastodonSDK/Sources/MastodonCore/Persistence/Persistence+Card.swift @@ -0,0 +1,95 @@ +// +// Persistence+Card.swift +// +// +// Created by MainasuK on 2021-12-9. +// + +import CoreData +import CoreDataStack +import Foundation +import MastodonSDK +import os.log + +extension Persistence.Card { + + public struct PersistContext { + public let domain: String + public let entity: Mastodon.Entity.Card + public let me: MastodonUser? + public let log = Logger(subsystem: "Card", category: "Persistence") + public init( + domain: String, + entity: Mastodon.Entity.Card, + me: MastodonUser? + ) { + self.domain = domain + self.entity = entity + self.me = me + } + } + + public struct PersistResult { + public let card: Card + public let isNewInsertion: Bool + + public init( + card: Card, + isNewInsertion: Bool + ) { + self.card = card + self.isNewInsertion = isNewInsertion + } + + #if DEBUG + public let logger = Logger(subsystem: "Persistence.MastodonCard.PersistResult", category: "Persist") + public func log() { + let pollInsertionFlag = isNewInsertion ? "+" : "-" + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(pollInsertionFlag)](\(card.title)):") + } + #endif + } + + public static func create( + in managedObjectContext: NSManagedObjectContext, + context: PersistContext + ) -> PersistResult { + var type: MastodonCardType { + switch context.entity.type { + case .link: return .link + case .photo: return .photo + case .video: return .video + case .rich: return ._other(context.entity.type.rawValue) + case ._other(let rawValue): return ._other(rawValue) + } + } + + let property = Card.Property( + urlRaw: context.entity.url, + title: context.entity.title, + desc: context.entity.description, + type: type, + authorName: context.entity.authorName, + authorURLRaw: context.entity.authorURL, + providerName: context.entity.providerName, + providerURLRaw: context.entity.providerURL, + width: Int64(context.entity.width ?? 0), + height: Int64(context.entity.height ?? 0), + image: context.entity.image, + embedURLRaw: context.entity.embedURL, + blurhash: context.entity.blurhash, + html: context.entity.html.flatMap { $0.isEmpty ? nil : $0 } + ) + + let card = Card.insert( + into: managedObjectContext, + property: property + ) + + return PersistResult( + card: card, + isNewInsertion: true + ) + } + +} diff --git a/MastodonSDK/Sources/MastodonCore/Persistence/Persistence+Status.swift b/MastodonSDK/Sources/MastodonCore/Persistence/Persistence+Status.swift index a15e974e4..aa5eb8546 100644 --- a/MastodonSDK/Sources/MastodonCore/Persistence/Persistence+Status.swift +++ b/MastodonSDK/Sources/MastodonCore/Persistence/Persistence+Status.swift @@ -87,7 +87,7 @@ extension Persistence.Status { } if let oldStatus = fetch(in: managedObjectContext, context: context) { - merge(mastodonStatus: oldStatus, context: context) + merge(in: managedObjectContext, mastodonStatus: oldStatus, context: context) return PersistResult( status: oldStatus, isNewInsertion: false, @@ -107,7 +107,9 @@ extension Persistence.Status { ) return result.poll }() - + + let card = createCard(in: managedObjectContext, context: context) + let authorResult = Persistence.MastodonUser.createOrMerge( in: managedObjectContext, context: Persistence.MastodonUser.PersistContext( @@ -122,7 +124,8 @@ extension Persistence.Status { let relationship = Status.Relationship( author: author, reblog: reblog, - poll: poll + poll: poll, + card: card ) let status = create( in: managedObjectContext, @@ -182,6 +185,7 @@ extension Persistence.Status { } public static func merge( + in managedObjectContext: NSManagedObjectContext, mastodonStatus status: Status, context: PersistContext ) { @@ -203,8 +207,31 @@ extension Persistence.Status { ) ) } + + if status.card == nil, context.entity.card != nil { + let card = createCard(in: managedObjectContext, context: context) + let relationship = Card.Relationship(status: status) + card?.configure(relationship: relationship) + } + update(status: status, context: context) } + + private static func createCard( + in managedObjectContext: NSManagedObjectContext, + context: PersistContext + ) -> Card? { + guard let entity = context.entity.card else { return nil } + let result = Persistence.Card.create( + in: managedObjectContext, + context: Persistence.Card.PersistContext( + domain: context.domain, + entity: entity, + me: context.me + ) + ) + return result.card + } private static func update( status: Status, diff --git a/MastodonSDK/Sources/MastodonCore/Persistence/Persistence+Tag.swift b/MastodonSDK/Sources/MastodonCore/Persistence/Persistence+Tag.swift index 5c7130618..3071fed0d 100644 --- a/MastodonSDK/Sources/MastodonCore/Persistence/Persistence+Tag.swift +++ b/MastodonSDK/Sources/MastodonCore/Persistence/Persistence+Tag.swift @@ -103,6 +103,9 @@ extension Persistence.Tag { property: property ) update(tag: object, context: context) + if let followingUser = context.me { + object.update(followed: property.following, by: followingUser) + } return object } @@ -116,7 +119,11 @@ extension Persistence.Tag { domain: context.domain, networkDate: context.networkDate ) + tag.update(property: property) + if let followingUser = context.me { + tag.update(followed: property.following, by: followingUser) + } update(tag: tag, context: context) } diff --git a/MastodonSDK/Sources/MastodonCore/Persistence/Persistence.swift b/MastodonSDK/Sources/MastodonCore/Persistence/Persistence.swift index 350b603cc..3a36dec41 100644 --- a/MastodonSDK/Sources/MastodonCore/Persistence/Persistence.swift +++ b/MastodonSDK/Sources/MastodonCore/Persistence/Persistence.swift @@ -15,6 +15,7 @@ extension Persistence { public enum MastodonUser { } public enum Status { } public enum Poll { } + public enum Card { } public enum PollOption { } public enum Tag { } public enum SearchHistory { } diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+APIError.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+APIError.swift index 6fdb973da..52b6d4678 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+APIError.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+APIError.swift @@ -43,7 +43,7 @@ extension APIService.APIError: LocalizedError { public var errorDescription: String? { switch errorReason { - case .authenticationMissing: return "Fail to Authenticatie" + case .authenticationMissing: return "Fail to Authenticate" case .badRequest: return "Bad Request" case .badResponse: return "Bad Response" case .requestThrottle: return "Request Throttled" diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Account.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Account.swift index 1b6a57a83..8f89b4b76 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Account.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Account.swift @@ -161,3 +161,41 @@ extension APIService { } } + +extension APIService { + @discardableResult + public func getFollowedTags( + domain: String, + query: Mastodon.API.Account.FollowedTagsQuery, + authenticationBox: MastodonAuthenticationBox + ) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Tag]> { + let domain = authenticationBox.domain + let authorization = authenticationBox.userAuthorization + + let response = try await Mastodon.API.Account.followedTags( + session: session, + domain: domain, + query: query, + authorization: authorization + ).singleOutput() + + let managedObjectContext = self.backgroundManagedObjectContext + try await managedObjectContext.performChanges { + let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user + + for entity in response.value { + _ = Persistence.Tag.createOrMerge( + in: managedObjectContext, + context: Persistence.Tag.PersistContext( + domain: domain, + entity: entity, + me: me, + networkDate: response.networkDate + ) + ) + } + } + + return response + } // end func +} diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Block.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Block.swift index 7c78a65f7..19c5ff437 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Block.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Block.swift @@ -22,6 +22,45 @@ extension APIService { let isFollowing: Bool } + @discardableResult + public func getBlocked( + authenticationBox: MastodonAuthenticationBox + ) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Account]> { + try await _getBlocked(sinceID: nil, limit: nil, authenticationBox: authenticationBox) + } + + private func _getBlocked( + sinceID: Mastodon.Entity.Status.ID?, + limit: Int?, + authenticationBox: MastodonAuthenticationBox + ) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Account]> { + let managedObjectContext = backgroundManagedObjectContext + let response = try await Mastodon.API.Account.blocks( + session: session, + domain: authenticationBox.domain, + sinceID: sinceID, + limit: limit, + authorization: authenticationBox.userAuthorization + ).singleOutput() + + let userIDs = response.value.map { $0.id } + let predicate = MastodonUser.predicate(domain: authenticationBox.domain, ids: userIDs) + + let fetchRequest = MastodonUser.fetchRequest() + fetchRequest.predicate = predicate + fetchRequest.includesPropertyValues = false + + try await managedObjectContext.performChanges { + let users = try managedObjectContext.fetch(fetchRequest) as! [MastodonUser] + + for user in users { + user.deleteStatusAndNotificationFeeds(in: managedObjectContext) + } + } + + return response + } + public func toggleBlock( user: ManagedObjectRecord, authenticationBox: MastodonAuthenticationBox @@ -110,3 +149,21 @@ extension APIService { } } + +extension MastodonUser { + func deleteStatusAndNotificationFeeds(in context: NSManagedObjectContext) { + statuses.map { + $0.feeds + .union($0.reblogFrom.map { $0.feeds }.flatMap { $0 }) + .union($0.notifications.map { $0.feeds }.flatMap { $0 }) + } + .flatMap { $0 } + .forEach(context.delete) + + notifications.map { + $0.feeds + } + .flatMap { $0 } + .forEach(context.delete) + } +} diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Instance.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Instance.swift index 93bfcf09a..eb39b5585 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Instance.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Instance.swift @@ -20,4 +20,9 @@ extension APIService { return Mastodon.API.Instance.instance(session: session, domain: domain) } + public func instanceV2( + domain: String + ) -> AnyPublisher, Error> { + return Mastodon.API.V2.Instance.instance(session: session, domain: domain) + } } diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Mute.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Mute.swift index ee43ddce8..cc46872f4 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Mute.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Mute.swift @@ -21,6 +21,45 @@ extension APIService { let isMuting: Bool } + @discardableResult + public func getMutes( + authenticationBox: MastodonAuthenticationBox + ) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Account]> { + try await _getMutes(sinceID: nil, limit: nil, authenticationBox: authenticationBox) + } + + private func _getMutes( + sinceID: Mastodon.Entity.Status.ID?, + limit: Int?, + authenticationBox: MastodonAuthenticationBox + ) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Account]> { + let managedObjectContext = backgroundManagedObjectContext + let response = try await Mastodon.API.Account.mutes( + session: session, + domain: authenticationBox.domain, + sinceID: sinceID, + limit: limit, + authorization: authenticationBox.userAuthorization + ).singleOutput() + + let userIDs = response.value.map { $0.id } + let predicate = MastodonUser.predicate(domain: authenticationBox.domain, ids: userIDs) + + let fetchRequest = MastodonUser.fetchRequest() + fetchRequest.predicate = predicate + fetchRequest.includesPropertyValues = false + + try await managedObjectContext.performChanges { + let users = try managedObjectContext.fetch(fetchRequest) as! [MastodonUser] + + for user in users { + user.deleteStatusAndNotificationFeeds(in: managedObjectContext) + } + } + + return response + } + public func toggleMute( user: ManagedObjectRecord, authenticationBox: MastodonAuthenticationBox @@ -58,6 +97,7 @@ extension APIService { accountID: muteContext.targetUserID, authorization: authenticationBox.userAuthorization ).singleOutput() + try await getMutes(authenticationBox: authenticationBox) result = .success(response) } else { let response = try await Mastodon.API.Account.mute( @@ -66,6 +106,7 @@ extension APIService { accountID: muteContext.targetUserID, authorization: authenticationBox.userAuthorization ).singleOutput() + try await getMutes(authenticationBox: authenticationBox) result = .success(response) } } catch { diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Onboarding.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Onboarding.swift index 383763359..84104d838 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Onboarding.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Onboarding.swift @@ -12,8 +12,8 @@ import MastodonSDK extension APIService { public func servers( - language: String?, - category: String? + language: String? = nil, + category: String? = nil ) -> AnyPublisher, Error> { let query = Mastodon.API.Onboarding.ServersQuery(language: language, category: category) return Mastodon.API.Onboarding.servers(session: session, query: query) diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Status+Translate.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Status+Translate.swift new file mode 100644 index 000000000..a802d6489 --- /dev/null +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Status+Translate.swift @@ -0,0 +1,34 @@ +// +// APIService+Status+Translate.swift +// Mastodon +// +// Created by Marcus Kida on 02.12.2022. +// + +import Foundation +import Combine +import CoreData +import CoreDataStack +import CommonOSLog +import MastodonSDK + +extension APIService { + + public func translateStatus( + statusID: Mastodon.Entity.Status.ID, + authenticationBox: MastodonAuthenticationBox + ) async throws -> Mastodon.Response.Content { + let domain = authenticationBox.domain + let authorization = authenticationBox.userAuthorization + + let response = try await Mastodon.API.Statuses.translate( + session: session, + domain: domain, + statusID: statusID, + authorization: authorization + ).singleOutput() + + return response + } + +} diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Tags.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Tags.swift new file mode 100644 index 000000000..0b0320078 --- /dev/null +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Tags.swift @@ -0,0 +1,92 @@ +// +// APIService+Tags.swift +// +// +// Created by Marcus Kida on 23.11.22. +// + +import os.log +import Foundation +import Combine +import CoreData +import CoreDataStack +import MastodonSDK + +extension APIService { + + public func getTagInformation( + for tag: String, + authenticationBox: MastodonAuthenticationBox + ) async throws -> Mastodon.Response.Content { + let domain = authenticationBox.domain + let authorization = authenticationBox.userAuthorization + + let response = try await Mastodon.API.Tags.getTagInformation( + session: session, + domain: domain, + tagId: tag, + authorization: authorization + ).singleOutput() + + return try await persistTag(from: response, domain: domain, authenticationBox: authenticationBox) + } // end func + + public func followTag( + for tag: String, + authenticationBox: MastodonAuthenticationBox + ) async throws -> Mastodon.Response.Content { + let domain = authenticationBox.domain + let authorization = authenticationBox.userAuthorization + + let response = try await Mastodon.API.Tags.followTag( + session: session, + domain: domain, + tagId: tag, + authorization: authorization + ).singleOutput() + + return try await persistTag(from: response, domain: domain, authenticationBox: authenticationBox) + } // end func + + public func unfollowTag( + for tag: String, + authenticationBox: MastodonAuthenticationBox + ) async throws -> Mastodon.Response.Content { + let domain = authenticationBox.domain + let authorization = authenticationBox.userAuthorization + + let response = try await Mastodon.API.Tags.unfollowTag( + session: session, + domain: domain, + tagId: tag, + authorization: authorization + ).singleOutput() + + return try await persistTag(from: response, domain: domain, authenticationBox: authenticationBox) + } // end func +} + +fileprivate extension APIService { + func persistTag( + from response: Mastodon.Response.Content, + domain: String, + authenticationBox: MastodonAuthenticationBox + ) async throws -> Mastodon.Response.Content { + let managedObjectContext = self.backgroundManagedObjectContext + try await managedObjectContext.performChanges { + let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user + + _ = Persistence.Tag.createOrMerge( + in: managedObjectContext, + context: Persistence.Tag.PersistContext( + domain: domain, + entity: response.value, + me: me, + networkDate: response.networkDate + ) + ) + } + + return response + } +} diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/CoreData/APIService+CoreData+Instance.swift b/MastodonSDK/Sources/MastodonCore/Service/API/CoreData/APIService+CoreData+Instance.swift index 2127a2981..d9566f6d4 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/CoreData/APIService+CoreData+Instance.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/CoreData/APIService+CoreData+Instance.swift @@ -46,7 +46,7 @@ extension APIService.CoreData { } else { let instance = Instance.insert( into: managedObjectContext, - property: Instance.Property(domain: domain) + property: Instance.Property(domain: domain, version: entity.version) ) let configurationRaw = entity.configuration.flatMap { Instance.encode(configuration: $0) } instance.update(configurationRaw: configurationRaw) @@ -69,7 +69,8 @@ extension APIService.CoreData { let configurationRaw = entity.configuration.flatMap { Instance.encode(configuration: $0) } instance.update(configurationRaw: configurationRaw) - + instance.version = entity.version + instance.didUpdate(at: networkDate) } diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/CoreData/APIService+CoreData+InstanceV2.swift b/MastodonSDK/Sources/MastodonCore/Service/API/CoreData/APIService+CoreData+InstanceV2.swift new file mode 100644 index 000000000..19e188133 --- /dev/null +++ b/MastodonSDK/Sources/MastodonCore/Service/API/CoreData/APIService+CoreData+InstanceV2.swift @@ -0,0 +1,81 @@ +import os.log +import Foundation +import CoreData +import CoreDataStack +import MastodonSDK + +extension APIService.CoreData { + + public struct PersistContext { + public let domain: String + public let entity: Mastodon.Entity.V2.Instance + public let networkDate: Date + public let log: Logger + + public init( + domain: String, + entity: Mastodon.Entity.V2.Instance, + networkDate: Date, + log: Logger + ) { + self.domain = domain + self.entity = entity + self.networkDate = networkDate + self.log = log + } + } + + static func createOrMergeInstance( + in managedObjectContext: NSManagedObjectContext, + context: PersistContext + ) -> (instance: Instance, isCreated: Bool) { + // fetch old mastodon user + let old: Instance? = { + let request = Instance.sortedFetchRequest + request.predicate = Instance.predicate(domain: context.domain) + request.fetchLimit = 1 + request.returnsObjectsAsFaults = false + do { + return try managedObjectContext.fetch(request).first + } catch { + assertionFailure(error.localizedDescription) + return nil + } + }() + + if let old = old { + APIService.CoreData.merge( + instance: old, + context: context + ) + return (old, false) + } else { + let instance = Instance.insert( + into: managedObjectContext, + property: Instance.Property(domain: context.domain, version: context.entity.version) + ) + let configurationRaw = context.entity.configuration.flatMap { Instance.encodeV2(configuration: $0) } + instance.update(configurationV2Raw: configurationRaw) + + return (instance, true) + } + } + +} + +extension APIService.CoreData { + + static func merge( + instance: Instance, + context: PersistContext + ) { + guard context.networkDate > instance.updatedAt else { return } + + let configurationRaw = context.entity.configuration.flatMap { Instance.encodeV2(configuration: $0) } + instance.update(configurationV2Raw: configurationRaw) + instance.version = context.entity.version + + instance.didUpdate(at: context.networkDate) + } + +} diff --git a/MastodonSDK/Sources/MastodonCore/Service/AuthenticationService.swift b/MastodonSDK/Sources/MastodonCore/Service/AuthenticationService.swift index 0e5160679..00b0e46cd 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/AuthenticationService.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/AuthenticationService.swift @@ -68,7 +68,7 @@ public final class AuthenticationService: NSObject { try mastodonAuthenticationFetchedResultsController.performFetch() mastodonAuthentications = mastodonAuthenticationFetchedResultsController.fetchedObjects? .sorted(by: { $0.activedAt > $1.activedAt }) - .compactMap { $0.asRecrod } ?? [] + .compactMap { $0.asRecord } ?? [] } catch { assertionFailure(error.localizedDescription) } @@ -149,7 +149,7 @@ extension AuthenticationService: NSFetchedResultsControllerDelegate { mastodonAuthentications = mastodonAuthenticationFetchedResultsController.fetchedObjects? .sorted(by: { $0.activedAt > $1.activedAt }) - .compactMap { $0.asRecrod } ?? [] + .compactMap { $0.asRecord } ?? [] } } diff --git a/MastodonSDK/Sources/MastodonCore/Service/InstanceService.swift b/MastodonSDK/Sources/MastodonCore/Service/InstanceService.swift index 4cd804036..0745e2f37 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/InstanceService.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/InstanceService.swift @@ -50,42 +50,18 @@ extension InstanceService { func updateInstance(domain: String) { guard let apiService = self.apiService else { return } apiService.instance(domain: domain) - .flatMap { response -> AnyPublisher, Error> in - let managedObjectContext = self.backgroundManagedObjectContext - return managedObjectContext.performChanges { - // get instance - let (instance, _) = APIService.CoreData.createOrMergeInstance( - into: managedObjectContext, - domain: domain, - entity: response.value, - networkDate: response.networkDate, - log: Logger(subsystem: "Update", category: "InstanceService") - ) - - // update relationship - let request = MastodonAuthentication.sortedFetchRequest - request.predicate = MastodonAuthentication.predicate(domain: domain) - request.returnsObjectsAsFaults = false - do { - let authentications = try managedObjectContext.fetch(request) - for authentication in authentications { - authentication.update(instance: instance) - } - } catch { - assertionFailure(error.localizedDescription) - } + .flatMap { [unowned self] response -> AnyPublisher in + if response.value.version?.majorServerVersion(greaterThanOrEquals: 4) == true { + return apiService.instanceV2(domain: domain) + .flatMap { return self.updateInstanceV2(domain: domain, response: $0) } + .eraseToAnyPublisher() + } else { + return self.updateInstance(domain: domain, response: response) } - .setFailureType(to: Error.self) - .tryMap { result -> Mastodon.Response.Content in - switch result { - case .success: - return response - case .failure(let error): - throw error - } - } - .eraseToAnyPublisher() } +// .flatMap { [unowned self] response -> AnyPublisher in +// return +// } .sink { [weak self] completion in guard let self = self else { return } switch completion { @@ -100,4 +76,102 @@ extension InstanceService { } .store(in: &disposeBag) } + + private func updateInstance(domain: String, response: Mastodon.Response.Content) -> AnyPublisher { + let managedObjectContext = self.backgroundManagedObjectContext + return managedObjectContext.performChanges { + // get instance + let (instance, _) = APIService.CoreData.createOrMergeInstance( + into: managedObjectContext, + domain: domain, + entity: response.value, + networkDate: response.networkDate, + log: Logger(subsystem: "Update", category: "InstanceService") + ) + + // update relationship + let request = MastodonAuthentication.sortedFetchRequest + request.predicate = MastodonAuthentication.predicate(domain: domain) + request.returnsObjectsAsFaults = false + do { + let authentications = try managedObjectContext.fetch(request) + for authentication in authentications { + authentication.update(instance: instance) + } + } catch { + assertionFailure(error.localizedDescription) + } + } + .setFailureType(to: Error.self) + .tryMap { result in + switch result { + case .success: + break + case .failure(let error): + throw error + } + } + .eraseToAnyPublisher() + } + + private func updateInstanceV2(domain: String, response: Mastodon.Response.Content) -> AnyPublisher { + let managedObjectContext = self.backgroundManagedObjectContext + return managedObjectContext.performChanges { + // get instance + let (instance, _) = APIService.CoreData.createOrMergeInstance( + in: managedObjectContext, + context: .init( + domain: domain, + entity: response.value, + networkDate: response.networkDate, + log: Logger(subsystem: "Update", category: "InstanceService") + ) + ) + + // update relationship + let request = MastodonAuthentication.sortedFetchRequest + request.predicate = MastodonAuthentication.predicate(domain: domain) + request.returnsObjectsAsFaults = false + do { + let authentications = try managedObjectContext.fetch(request) + for authentication in authentications { + authentication.update(instance: instance) + } + } catch { + assertionFailure(error.localizedDescription) + } + } + .setFailureType(to: Error.self) + .tryMap { result in + switch result { + case .success: + break + case .failure(let error): + throw error + } + } + .eraseToAnyPublisher() + } +} + +public extension InstanceService { + func updateMutesAndBlocks() { + Task { + for authBox in authenticationService?.mastodonAuthenticationBoxes ?? [] { + do { + try await apiService?.getMutes( + authenticationBox: authBox + ) + + try await apiService?.getBlocked( + authenticationBox: authBox + ) + + self.logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [Instance] update mutes and blocks succeeded") + } catch { + self.logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [Instance] update mutes and blocks failure: \(error.localizedDescription)") + } + } + } + } } diff --git a/MastodonSDK/Sources/MastodonCore/Service/Theme/ThemeService.swift b/MastodonSDK/Sources/MastodonCore/Service/Theme/ThemeService.swift index 869e6875f..446fb5b63 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/Theme/ThemeService.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/Theme/ThemeService.swift @@ -52,9 +52,7 @@ extension ThemeService { UINavigationBar.appearance().standardAppearance = appearance UINavigationBar.appearance().compactAppearance = appearance UINavigationBar.appearance().scrollEdgeAppearance = appearance - if #available(iOS 15.0, *) { - UINavigationBar.appearance().compactScrollEdgeAppearance = appearance - } + UINavigationBar.appearance().compactScrollEdgeAppearance = appearance // set tab bar appearance let tabBarAppearance = UITabBarAppearance() @@ -76,11 +74,7 @@ extension ThemeService { tabBarAppearance.backgroundColor = theme.tabBarBackgroundColor tabBarAppearance.selectionIndicatorTintColor = ThemeService.tintColor UITabBar.appearance().standardAppearance = tabBarAppearance - if #available(iOS 15.0, *) { - UITabBar.appearance().scrollEdgeAppearance = tabBarAppearance - } else { - // Fallback on earlier versions - } + UITabBar.appearance().scrollEdgeAppearance = tabBarAppearance UITabBar.appearance().barTintColor = theme.tabBarBackgroundColor // set table view cell appearance diff --git a/MastodonSDK/Sources/MastodonCore/Vendor/ItemProviderLoader.swift b/MastodonSDK/Sources/MastodonCore/Vendor/ItemProviderLoader.swift index 9899620fe..ad26e0ceb 100644 --- a/MastodonSDK/Sources/MastodonCore/Vendor/ItemProviderLoader.swift +++ b/MastodonSDK/Sources/MastodonCore/Vendor/ItemProviderLoader.swift @@ -48,13 +48,13 @@ extension ItemProviderLoader { let maxPixelSize: Int = 1536 // fit 120MB RAM limit #endif - let downsampleOptions = [ + let downsampleOptions: [CFString: Any] = [ kCGImageSourceCreateThumbnailFromImageAlways: true, kCGImageSourceCreateThumbnailWithTransform: true, kCGImageSourceThumbnailMaxPixelSize: maxPixelSize, - ] as CFDictionary + ] - guard let cgImage = CGImageSourceCreateThumbnailAtIndex(source, 0, downsampleOptions) else { + guard let cgImage = CGImageSourceCreateThumbnailAtIndex(source, 0, downsampleOptions as CFDictionary) else { // fallback to loadItem when create thumbnail failure itemProvider.loadItem(forTypeIdentifier: UTType.image.identifier, options: nil) { image, error in if let error = error { @@ -77,7 +77,7 @@ extension ItemProviderLoader { } let data = NSMutableData() - guard let imageDestination = CGImageDestinationCreateWithData(data, kUTTypeJPEG, 1, nil) else { + guard let imageDestination = CGImageDestinationCreateWithData(data, UTType.jpeg.identifier as CFString, 1, nil) else { promise(.success(nil)) return } diff --git a/MastodonSDK/Sources/MastodonExtension/ImageAnalyzer.swift b/MastodonSDK/Sources/MastodonExtension/ImageAnalyzer.swift new file mode 100644 index 000000000..cf8a0f437 --- /dev/null +++ b/MastodonSDK/Sources/MastodonExtension/ImageAnalyzer.swift @@ -0,0 +1,13 @@ +// +// ImageAnalyzer.swift +// +// +// Created by Jed Fox on 2022-11-14. +// + +import VisionKit + +@available(iOS 16.0, *) +extension ImageAnalyzer { + public static let shared = ImageAnalyzer() +} diff --git a/MastodonSDK/Sources/MastodonExtension/Int.swift b/MastodonSDK/Sources/MastodonExtension/Int.swift new file mode 100644 index 000000000..e9c5ceb1e --- /dev/null +++ b/MastodonSDK/Sources/MastodonExtension/Int.swift @@ -0,0 +1,31 @@ +// +// Int.swift +// +// +// Created by Marcus Kida on 28.12.22. +// + +import Foundation + +public extension Int { + func asAbbreviatedCountString() -> String { + switch self { + case ..<1_000: + return String(format: "%d", locale: Locale.current, self) + case 1_000 ..< 999_999: + return String(format: "%.1fK", locale: Locale.current, Double(self) / 1_000) + .sanitizedAbbreviatedCountString(for: "K") + default: + return String(format: "%.1fM", locale: Locale.current, Double(self) / 1_000_000) + .sanitizedAbbreviatedCountString(for: "M") + } + } +} + +fileprivate extension String { + func sanitizedAbbreviatedCountString(for value: String) -> String { + [".0", ",0", "٫٠"].reduce(self) { res, acc in + return res.replacingOccurrences(of: "\(acc)\(value)", with: value) + } + } +} diff --git a/MastodonSDK/Sources/MastodonExtension/NSLayoutConstraint.swift b/MastodonSDK/Sources/MastodonExtension/NSLayoutConstraint.swift index 057b17859..3251eb58b 100644 --- a/MastodonSDK/Sources/MastodonExtension/NSLayoutConstraint.swift +++ b/MastodonSDK/Sources/MastodonExtension/NSLayoutConstraint.swift @@ -17,4 +17,16 @@ extension NSLayoutConstraint { self.identifier = identifier return self } + + @discardableResult + public func activate() -> Self { + self.isActive = true + return self + } + + @discardableResult + public func deactivate() -> Self { + self.isActive = false + return self + } } diff --git a/MastodonSDK/Sources/MastodonExtension/UIEdgeInsets.swift b/MastodonSDK/Sources/MastodonExtension/UIEdgeInsets.swift new file mode 100644 index 000000000..ea69bcefc --- /dev/null +++ b/MastodonSDK/Sources/MastodonExtension/UIEdgeInsets.swift @@ -0,0 +1,17 @@ +// +// UIEdgeInsets.swift +// +// +// Created by Jed Fox on 2022-11-24. +// + +import UIKit + +extension UIEdgeInsets { + public init(horizontal: CGFloat, vertical: CGFloat) { + self.init(top: vertical, left: horizontal, bottom: vertical, right: horizontal) + } + public static func constant(_ offset: CGFloat) -> Self { + UIEdgeInsets(top: offset, left: offset, bottom: offset, right: offset) + } +} diff --git a/MastodonSDK/Sources/MastodonExtension/UIImage.swift b/MastodonSDK/Sources/MastodonExtension/UIImage.swift index e3560af63..bb50a1428 100644 --- a/MastodonSDK/Sources/MastodonExtension/UIImage.swift +++ b/MastodonSDK/Sources/MastodonExtension/UIImage.swift @@ -48,7 +48,7 @@ extension UIImage { guard let outputImage = filter.outputImage else { return nil } var bitmap = [UInt8](repeating: 0, count: 4) - let context = CIContext(options: [.workingColorSpace: kCFNull]) + let context = CIContext(options: [.workingColorSpace: kCFNull as Any]) context.render(outputImage, toBitmap: &bitmap, rowBytes: 4, bounds: CGRect(x: 0, y: 0, width: 1, height: 1), format: .RGBA8, colorSpace: nil) return UIColor(red: CGFloat(bitmap[0]) / 255, green: CGFloat(bitmap[1]) / 255, blue: CGFloat(bitmap[2]) / 255, alpha: CGFloat(bitmap[3]) / 255) diff --git a/MastodonSDK/Sources/MastodonExtension/UIView.swift b/MastodonSDK/Sources/MastodonExtension/UIView.swift index f96d1618a..84f87eb20 100644 --- a/MastodonSDK/Sources/MastodonExtension/UIView.swift +++ b/MastodonSDK/Sources/MastodonExtension/UIView.swift @@ -46,3 +46,23 @@ extension UIView { return self } } + +public extension UIView { + @discardableResult + func pinToParent(padding: UIEdgeInsets = .zero) -> [NSLayoutConstraint] { + pinTo(to: self.superview, padding: padding) + } + + @discardableResult + func pinTo(to view: UIView?, padding: UIEdgeInsets = .zero) -> [NSLayoutConstraint] { + guard let pinToView = view else { return [] } + let constraints = [ + topAnchor.constraint(equalTo: pinToView.topAnchor, constant: padding.top), + leadingAnchor.constraint(equalTo: pinToView.leadingAnchor, constant: padding.left), + trailingAnchor.constraint(equalTo: pinToView.trailingAnchor, constant: -padding.right), + bottomAnchor.constraint(equalTo: pinToView.bottomAnchor, constant: -padding.bottom), + ] + NSLayoutConstraint.activate(constraints) + return constraints + } +} diff --git a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift index 1ad98b0ea..b974ba7bc 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift +++ b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift @@ -87,6 +87,14 @@ public enum L10n { /// Sign Up Failure public static let title = L10n.tr("Localizable", "Common.Alerts.SignUpFailure.Title", fallback: "Sign Up Failure") } + public enum TranslationFailed { + /// OK + public static let button = L10n.tr("Localizable", "Common.Alerts.TranslationFailed.Button", fallback: "OK") + /// Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported. + public static let message = L10n.tr("Localizable", "Common.Alerts.TranslationFailed.Message", fallback: "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported.") + /// Note + public static let title = L10n.tr("Localizable", "Common.Alerts.TranslationFailed.Title", fallback: "Note") + } public enum VoteFailure { /// The poll has ended public static let pollEnded = L10n.tr("Localizable", "Common.Alerts.VoteFailure.PollEnded", fallback: "The poll has ended") @@ -112,6 +120,8 @@ public enum L10n { public static let confirm = L10n.tr("Localizable", "Common.Controls.Actions.Confirm", fallback: "Confirm") /// Continue public static let `continue` = L10n.tr("Localizable", "Common.Controls.Actions.Continue", fallback: "Continue") + /// Copy + public static let copy = L10n.tr("Localizable", "Common.Controls.Actions.Copy", fallback: "Copy") /// Copy Photo public static let copyPhoto = L10n.tr("Localizable", "Common.Controls.Actions.CopyPhoto", fallback: "Copy Photo") /// Delete @@ -164,10 +174,10 @@ public enum L10n { public static func shareUser(_ p1: Any) -> String { return L10n.tr("Localizable", "Common.Controls.Actions.ShareUser", String(describing: p1), fallback: "Share %@") } - /// Sign In - public static let signIn = L10n.tr("Localizable", "Common.Controls.Actions.SignIn", fallback: "Sign In") - /// Sign Up - public static let signUp = L10n.tr("Localizable", "Common.Controls.Actions.SignUp", fallback: "Sign Up") + /// Log in + public static let signIn = L10n.tr("Localizable", "Common.Controls.Actions.SignIn", fallback: "Log in") + /// Create account + public static let signUp = L10n.tr("Localizable", "Common.Controls.Actions.SignUp", fallback: "Create account") /// Skip public static let skip = L10n.tr("Localizable", "Common.Controls.Actions.Skip", fallback: "Skip") /// Take Photo @@ -178,6 +188,14 @@ public enum L10n { public static func unblockDomain(_ p1: Any) -> String { return L10n.tr("Localizable", "Common.Controls.Actions.UnblockDomain", String(describing: p1), fallback: "Unblock %@") } + public enum TranslatePost { + /// Translate from %@ + public static func title(_ p1: Any) -> String { + return L10n.tr("Localizable", "Common.Controls.Actions.TranslatePost.Title", String(describing: p1), fallback: "Translate from %@") + } + /// Unknown + public static let unknownLanguage = L10n.tr("Localizable", "Common.Controls.Actions.TranslatePost.UnknownLanguage", fallback: "Unknown") + } } public enum Friendship { /// Block @@ -272,6 +290,12 @@ public enum L10n { public enum Status { /// Content Warning public static let contentWarning = L10n.tr("Localizable", "Common.Controls.Status.ContentWarning", fallback: "Content Warning") + /// %@ via %@ + public static func linkViaUser(_ p1: Any, _ p2: Any) -> String { + return L10n.tr("Localizable", "Common.Controls.Status.LinkViaUser", String(describing: p1), String(describing: p2), fallback: "%@ via %@") + } + /// Load Embed + public static let loadEmbed = L10n.tr("Localizable", "Common.Controls.Status.LoadEmbed", fallback: "Load Embed") /// Tap anywhere to reveal public static let mediaContentWarning = L10n.tr("Localizable", "Common.Controls.Status.MediaContentWarning", fallback: "Tap anywhere to reveal") /// Sensitive Content @@ -301,6 +325,8 @@ public enum L10n { public static let reblog = L10n.tr("Localizable", "Common.Controls.Status.Actions.Reblog", fallback: "Reblog") /// Reply public static let reply = L10n.tr("Localizable", "Common.Controls.Status.Actions.Reply", fallback: "Reply") + /// Share Link in Post + public static let shareLinkInPost = L10n.tr("Localizable", "Common.Controls.Status.Actions.ShareLinkInPost", fallback: "Share Link in Post") /// Show GIF public static let showGif = L10n.tr("Localizable", "Common.Controls.Status.Actions.ShowGif", fallback: "Show GIF") /// Show image @@ -314,6 +340,18 @@ public enum L10n { /// Undo reblog public static let unreblog = L10n.tr("Localizable", "Common.Controls.Status.Actions.Unreblog", fallback: "Undo reblog") } + public enum Media { + /// %@, attachment %d of %d + public static func accessibilityLabel(_ p1: Any, _ p2: Int, _ p3: Int) -> String { + return L10n.tr("Localizable", "Common.Controls.Status.Media.AccessibilityLabel", String(describing: p1), p2, p3, fallback: "%@, attachment %d of %d") + } + /// Expands the GIF. Double-tap and hold to show actions + public static let expandGifHint = L10n.tr("Localizable", "Common.Controls.Status.Media.ExpandGifHint", fallback: "Expands the GIF. Double-tap and hold to show actions") + /// Expands the image. Double-tap and hold to show actions + public static let expandImageHint = L10n.tr("Localizable", "Common.Controls.Status.Media.ExpandImageHint", fallback: "Expands the image. Double-tap and hold to show actions") + /// Shows the video player. Double-tap and hold to show actions + public static let expandVideoHint = L10n.tr("Localizable", "Common.Controls.Status.Media.ExpandVideoHint", fallback: "Shows the video player. Double-tap and hold to show actions") + } public enum MetaEntity { /// Email address: %@ public static func email(_ p1: Any) -> String { @@ -352,6 +390,18 @@ public enum L10n { /// URL public static let url = L10n.tr("Localizable", "Common.Controls.Status.Tag.Url", fallback: "URL") } + public enum Translation { + /// Shown Original + public static let showOriginal = L10n.tr("Localizable", "Common.Controls.Status.Translation.ShowOriginal", fallback: "Shown Original") + /// Translated from %@ using %@ + public static func translatedFrom(_ p1: Any, _ p2: Any) -> String { + return L10n.tr("Localizable", "Common.Controls.Status.Translation.TranslatedFrom", String(describing: p1), String(describing: p2), fallback: "Translated from %@ using %@") + } + /// Unknown + public static let unknownLanguage = L10n.tr("Localizable", "Common.Controls.Status.Translation.UnknownLanguage", fallback: "Unknown") + /// Unknown + public static let unknownProvider = L10n.tr("Localizable", "Common.Controls.Status.Translation.UnknownProvider", fallback: "Unknown") + } public enum Visibility { /// Only mentioned user can see this post. public static let direct = L10n.tr("Localizable", "Common.Controls.Status.Visibility.Direct", fallback: "Only mentioned user can see this post.") @@ -366,12 +416,12 @@ public enum L10n { public enum Tabs { /// Home public static let home = L10n.tr("Localizable", "Common.Controls.Tabs.Home", fallback: "Home") - /// Notification - public static let notification = L10n.tr("Localizable", "Common.Controls.Tabs.Notification", fallback: "Notification") + /// Notifications + public static let notifications = L10n.tr("Localizable", "Common.Controls.Tabs.Notifications", fallback: "Notifications") /// Profile public static let profile = L10n.tr("Localizable", "Common.Controls.Tabs.Profile", fallback: "Profile") - /// Search - public static let search = L10n.tr("Localizable", "Common.Controls.Tabs.Search", fallback: "Search") + /// Search and Explore + public static let searchAndExplore = L10n.tr("Localizable", "Common.Controls.Tabs.SearchAndExplore", fallback: "Search and Explore") } public enum Timeline { /// Filtered @@ -633,6 +683,24 @@ public enum L10n { /// Favorited By public static let title = L10n.tr("Localizable", "Scene.FavoritedBy.Title", fallback: "Favorited By") } + public enum FollowedTags { + /// Followed Tags + public static let title = L10n.tr("Localizable", "Scene.FollowedTags.Title", fallback: "Followed Tags") + public enum Actions { + /// Follow + public static let follow = L10n.tr("Localizable", "Scene.FollowedTags.Actions.Follow", fallback: "Follow") + /// Unfollow + public static let unfollow = L10n.tr("Localizable", "Scene.FollowedTags.Actions.Unfollow", fallback: "Unfollow") + } + public enum Header { + /// participants + public static let participants = L10n.tr("Localizable", "Scene.FollowedTags.Header.Participants", fallback: "participants") + /// posts + public static let posts = L10n.tr("Localizable", "Scene.FollowedTags.Header.Posts", fallback: "posts") + /// posts today + public static let postsToday = L10n.tr("Localizable", "Scene.FollowedTags.Header.PostsToday", fallback: "posts today") + } + } public enum Follower { /// Followers from other servers are not displayed. public static let footer = L10n.tr("Localizable", "Scene.Follower.Footer", fallback: "Followers from other servers are not displayed.") @@ -660,11 +728,21 @@ public enum L10n { public enum Accessibility { /// Tap to scroll to top and tap again to previous location public static let logoHint = L10n.tr("Localizable", "Scene.HomeTimeline.NavigationBarState.Accessibility.LogoHint", fallback: "Tap to scroll to top and tap again to previous location") - /// Logo Button - public static let logoLabel = L10n.tr("Localizable", "Scene.HomeTimeline.NavigationBarState.Accessibility.LogoLabel", fallback: "Logo Button") + /// Mastodon + public static let logoLabel = L10n.tr("Localizable", "Scene.HomeTimeline.NavigationBarState.Accessibility.LogoLabel", fallback: "Mastodon") } } } + public enum Login { + /// Log you in on the server you created your account on. + public static let subtitle = L10n.tr("Localizable", "Scene.Login.Subtitle", fallback: "Log you in on the server you created your account on.") + /// Welcome back + public static let title = L10n.tr("Localizable", "Scene.Login.Title", fallback: "Welcome back") + public enum ServerSearchField { + /// Enter URL or search for your server + public static let placeholder = L10n.tr("Localizable", "Scene.Login.ServerSearchField.Placeholder", fallback: "Enter URL or search for your server") + } + } public enum Notification { public enum FollowRequest { /// Accept @@ -726,15 +804,23 @@ public enum L10n { } public enum Dashboard { /// followers - public static let followers = L10n.tr("Localizable", "Scene.Profile.Dashboard.Followers", fallback: "followers") + public static let myFollowers = L10n.tr("Localizable", "Scene.Profile.Dashboard.MyFollowers", fallback: "followers") /// following - public static let following = L10n.tr("Localizable", "Scene.Profile.Dashboard.Following", fallback: "following") + public static let myFollowing = L10n.tr("Localizable", "Scene.Profile.Dashboard.MyFollowing", fallback: "following") /// posts - public static let posts = L10n.tr("Localizable", "Scene.Profile.Dashboard.Posts", fallback: "posts") + public static let myPosts = L10n.tr("Localizable", "Scene.Profile.Dashboard.MyPosts", fallback: "posts") + /// followers + public static let otherFollowers = L10n.tr("Localizable", "Scene.Profile.Dashboard.OtherFollowers", fallback: "followers") + /// following + public static let otherFollowing = L10n.tr("Localizable", "Scene.Profile.Dashboard.OtherFollowing", fallback: "following") + /// posts + public static let otherPosts = L10n.tr("Localizable", "Scene.Profile.Dashboard.OtherPosts", fallback: "posts") } public enum Fields { /// Add Row public static let addRow = L10n.tr("Localizable", "Scene.Profile.Fields.AddRow", fallback: "Add Row") + /// Joined + public static let joined = L10n.tr("Localizable", "Scene.Profile.Fields.Joined", fallback: "Joined") public enum Placeholder { /// Content public static let content = L10n.tr("Localizable", "Scene.Profile.Fields.Placeholder.Content", fallback: "Content") @@ -1100,10 +1186,8 @@ public enum L10n { } } public enum ServerPicker { - /// Pick a server based on your interests, region, or a general purpose one. - public static let subtitle = L10n.tr("Localizable", "Scene.ServerPicker.Subtitle", fallback: "Pick a server based on your interests, region, or a general purpose one.") - /// Pick a server based on your interests, region, or a general purpose one. Each server is operated by an entirely independent organization or individual. - public static let subtitleExtend = L10n.tr("Localizable", "Scene.ServerPicker.SubtitleExtend", fallback: "Pick a server based on your interests, region, or a general purpose one. Each server is operated by an entirely independent organization or individual.") + /// Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers. + public static let subtitle = L10n.tr("Localizable", "Scene.ServerPicker.Subtitle", fallback: "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers.") /// Mastodon is made of users in different servers. public static let title = L10n.tr("Localizable", "Scene.ServerPicker.Title", fallback: "Mastodon is made of users in different servers.") public enum Button { @@ -1151,10 +1235,8 @@ public enum L10n { public static let noResults = L10n.tr("Localizable", "Scene.ServerPicker.EmptyState.NoResults", fallback: "No results") } public enum Input { - /// Search servers - public static let placeholder = L10n.tr("Localizable", "Scene.ServerPicker.Input.Placeholder", fallback: "Search servers") - /// Search servers or enter URL - public static let searchServersOrEnterUrl = L10n.tr("Localizable", "Scene.ServerPicker.Input.SearchServersOrEnterUrl", fallback: "Search servers or enter URL") + /// Search communities or enter URL + public static let searchServersOrEnterUrl = L10n.tr("Localizable", "Scene.ServerPicker.Input.SearchServersOrEnterUrl", fallback: "Search communities or enter URL") } public enum Label { /// CATEGORY @@ -1314,9 +1396,9 @@ public enum L10n { public enum A11y { public enum Plural { public enum Count { - /// Plural format key: "%#@character_count@ left" + /// Plural format key: "%#@character_count@" public static func charactersLeft(_ p1: Int) -> String { - return L10n.tr("Localizable", "a11y.plural.count.characters_left", p1, fallback: "Plural format key: \"%#@character_count@ left\"") + return L10n.tr("Localizable", "a11y.plural.count.characters_left", p1, fallback: "Plural format key: \"%#@character_count@\"") } /// Plural format key: "Input limit exceeds %#@character_count@" public static func inputLimitExceeds(_ p1: Int) -> String { diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings index 73bc292cf..fb3e483cf 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings @@ -22,6 +22,9 @@ Please check your internet connection."; "Common.Alerts.SignOut.Message" = "Are you sure you want to sign out?"; "Common.Alerts.SignOut.Title" = "Sign Out"; "Common.Alerts.SignUpFailure.Title" = "Sign Up Failure"; +"Common.Alerts.TranslationFailed.Button" = "OK"; +"Common.Alerts.TranslationFailed.Message" = "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported."; +"Common.Alerts.TranslationFailed.Title" = "Note"; "Common.Alerts.VoteFailure.PollEnded" = "The poll has ended"; "Common.Alerts.VoteFailure.Title" = "Vote Failure"; "Common.Controls.Actions.Add" = "Add"; @@ -31,6 +34,7 @@ Please check your internet connection."; "Common.Controls.Actions.Compose" = "Compose"; "Common.Controls.Actions.Confirm" = "Confirm"; "Common.Controls.Actions.Continue" = "Continue"; +"Common.Controls.Actions.Copy" = "Copy"; "Common.Controls.Actions.CopyPhoto" = "Copy Photo"; "Common.Controls.Actions.Delete" = "Delete"; "Common.Controls.Actions.Discard" = "Discard"; @@ -55,10 +59,12 @@ Please check your internet connection."; "Common.Controls.Actions.Share" = "Share"; "Common.Controls.Actions.SharePost" = "Share Post"; "Common.Controls.Actions.ShareUser" = "Share %@"; -"Common.Controls.Actions.SignIn" = "Sign In"; -"Common.Controls.Actions.SignUp" = "Sign Up"; +"Common.Controls.Actions.SignIn" = "Log in"; +"Common.Controls.Actions.SignUp" = "Create account"; "Common.Controls.Actions.Skip" = "Skip"; "Common.Controls.Actions.TakePhoto" = "Take Photo"; +"Common.Controls.Actions.TranslatePost.Title" = "Translate from %@"; +"Common.Controls.Actions.TranslatePost.UnknownLanguage" = "Unknown"; "Common.Controls.Actions.TryAgain" = "Try Again"; "Common.Controls.Actions.UnblockDomain" = "Unblock %@"; "Common.Controls.Friendship.Block" = "Block"; @@ -100,6 +106,7 @@ Please check your internet connection."; "Common.Controls.Status.Actions.Menu" = "Menu"; "Common.Controls.Status.Actions.Reblog" = "Reblog"; "Common.Controls.Status.Actions.Reply" = "Reply"; +"Common.Controls.Status.Actions.ShareLinkInPost" = "Share Link in Post"; "Common.Controls.Status.Actions.ShowGif" = "Show GIF"; "Common.Controls.Status.Actions.ShowImage" = "Show image"; "Common.Controls.Status.Actions.ShowVideoPlayer" = "Show video player"; @@ -107,6 +114,12 @@ Please check your internet connection."; "Common.Controls.Status.Actions.Unfavorite" = "Unfavorite"; "Common.Controls.Status.Actions.Unreblog" = "Undo reblog"; "Common.Controls.Status.ContentWarning" = "Content Warning"; +"Common.Controls.Status.LinkViaUser" = "%@ via %@"; +"Common.Controls.Status.LoadEmbed" = "Load Embed"; +"Common.Controls.Status.Media.AccessibilityLabel" = "%@, attachment %d of %d"; +"Common.Controls.Status.Media.ExpandGifHint" = "Expands the GIF. Double-tap and hold to show actions"; +"Common.Controls.Status.Media.ExpandImageHint" = "Expands the image. Double-tap and hold to show actions"; +"Common.Controls.Status.Media.ExpandVideoHint" = "Shows the video player. Double-tap and hold to show actions"; "Common.Controls.Status.MediaContentWarning" = "Tap anywhere to reveal"; "Common.Controls.Status.MetaEntity.Email" = "Email address: %@"; "Common.Controls.Status.MetaEntity.Hashtag" = "Hashtag: %@"; @@ -124,6 +137,10 @@ Please check your internet connection."; "Common.Controls.Status.Tag.Mention" = "Mention"; "Common.Controls.Status.Tag.Url" = "URL"; "Common.Controls.Status.TapToReveal" = "Tap to reveal"; +"Common.Controls.Status.Translation.ShowOriginal" = "Shown Original"; +"Common.Controls.Status.Translation.TranslatedFrom" = "Translated from %@ using %@"; +"Common.Controls.Status.Translation.UnknownLanguage" = "Unknown"; +"Common.Controls.Status.Translation.UnknownProvider" = "Unknown"; "Common.Controls.Status.UserReblogged" = "%@ reblogged"; "Common.Controls.Status.UserRepliedTo" = "Replied to %@"; "Common.Controls.Status.Visibility.Direct" = "Only mentioned user can see this post."; @@ -131,9 +148,9 @@ Please check your internet connection."; "Common.Controls.Status.Visibility.PrivateFromMe" = "Only my followers can see this post."; "Common.Controls.Status.Visibility.Unlisted" = "Everyone can see this post but not display in the public timeline."; "Common.Controls.Tabs.Home" = "Home"; -"Common.Controls.Tabs.Notification" = "Notification"; +"Common.Controls.Tabs.Notifications" = "Notifications"; "Common.Controls.Tabs.Profile" = "Profile"; -"Common.Controls.Tabs.Search" = "Search"; +"Common.Controls.Tabs.SearchAndExplore" = "Search and Explore"; "Common.Controls.Timeline.Filtered" = "Filtered"; "Common.Controls.Timeline.Header.BlockedWarning" = "You can’t view this user’s profile until they unblock you."; @@ -229,17 +246,26 @@ uploaded to Mastodon."; "Scene.Familiarfollowers.Title" = "Followers you familiar"; "Scene.Favorite.Title" = "Your Favorites"; "Scene.FavoritedBy.Title" = "Favorited By"; +"Scene.FollowedTags.Actions.Follow" = "Follow"; +"Scene.FollowedTags.Actions.Unfollow" = "Unfollow"; +"Scene.FollowedTags.Header.Participants" = "participants"; +"Scene.FollowedTags.Header.Posts" = "posts"; +"Scene.FollowedTags.Header.PostsToday" = "posts today"; +"Scene.FollowedTags.Title" = "Followed Tags"; "Scene.Follower.Footer" = "Followers from other servers are not displayed."; "Scene.Follower.Title" = "follower"; "Scene.Following.Footer" = "Follows from other servers are not displayed."; "Scene.Following.Title" = "following"; "Scene.HomeTimeline.NavigationBarState.Accessibility.LogoHint" = "Tap to scroll to top and tap again to previous location"; -"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoLabel" = "Logo Button"; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoLabel" = "Mastodon"; "Scene.HomeTimeline.NavigationBarState.NewPosts" = "See new posts"; "Scene.HomeTimeline.NavigationBarState.Offline" = "Offline"; "Scene.HomeTimeline.NavigationBarState.Published" = "Published!"; "Scene.HomeTimeline.NavigationBarState.Publishing" = "Publishing post..."; "Scene.HomeTimeline.Title" = "Home"; +"Scene.Login.ServerSearchField.Placeholder" = "Enter URL or search for your server"; +"Scene.Login.Subtitle" = "Log you in on the server you created your account on."; +"Scene.Login.Title" = "Welcome back"; "Scene.Notification.FollowRequest.Accept" = "Accept"; "Scene.Notification.FollowRequest.Accepted" = "Accepted"; "Scene.Notification.FollowRequest.Reject" = "reject"; @@ -261,10 +287,14 @@ uploaded to Mastodon."; "Scene.Profile.Accessibility.EditAvatarImage" = "Edit avatar image"; "Scene.Profile.Accessibility.ShowAvatarImage" = "Show avatar image"; "Scene.Profile.Accessibility.ShowBannerImage" = "Show banner image"; -"Scene.Profile.Dashboard.Followers" = "followers"; -"Scene.Profile.Dashboard.Following" = "following"; -"Scene.Profile.Dashboard.Posts" = "posts"; +"Scene.Profile.Dashboard.MyFollowers" = "followers"; +"Scene.Profile.Dashboard.MyFollowing" = "following"; +"Scene.Profile.Dashboard.MyPosts" = "posts"; +"Scene.Profile.Dashboard.OtherFollowers" = "followers"; +"Scene.Profile.Dashboard.OtherFollowing" = "following"; +"Scene.Profile.Dashboard.OtherPosts" = "posts"; "Scene.Profile.Fields.AddRow" = "Add Row"; +"Scene.Profile.Fields.Joined" = "Joined"; "Scene.Profile.Fields.Placeholder.Content" = "Content"; "Scene.Profile.Fields.Placeholder.Label" = "Label"; "Scene.Profile.Fields.Verified.Long" = "Ownership of this link was checked on %@"; @@ -401,13 +431,11 @@ uploaded to Mastodon."; "Scene.ServerPicker.EmptyState.BadNetwork" = "Something went wrong while loading the data. Check your internet connection."; "Scene.ServerPicker.EmptyState.FindingServers" = "Finding available servers..."; "Scene.ServerPicker.EmptyState.NoResults" = "No results"; -"Scene.ServerPicker.Input.Placeholder" = "Search servers"; -"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Search servers or enter URL"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Search communities or enter URL"; "Scene.ServerPicker.Label.Category" = "CATEGORY"; "Scene.ServerPicker.Label.Language" = "LANGUAGE"; "Scene.ServerPicker.Label.Users" = "USERS"; -"Scene.ServerPicker.Subtitle" = "Pick a server based on your interests, region, or a general purpose one."; -"Scene.ServerPicker.SubtitleExtend" = "Pick a server based on your interests, region, or a general purpose one. Each server is operated by an entirely independent organization or individual."; +"Scene.ServerPicker.Subtitle" = "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers."; "Scene.ServerPicker.Title" = "Mastodon is made of users in different servers."; "Scene.ServerRules.Button.Confirm" = "I Agree"; "Scene.ServerRules.PrivacyPolicy" = "privacy policy"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.stringsdict index cd97825f4..37ce1f032 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.stringsdict @@ -13,15 +13,15 @@ NSStringFormatValueTypeKey ld zero - no unread notification + no unread notifications one 1 unread notification few %ld unread notifications many - %ld unread notification + %ld unread notifications other - %ld unread notification + %ld unread notifications a11y.plural.count.input_limit_exceeds @@ -71,7 +71,7 @@ a11y.plural.count.characters_left NSStringLocalizedFormatKey - %#@character_count@ left + %#@character_count@ character_count NSStringFormatSpecTypeKey @@ -79,15 +79,15 @@ NSStringFormatValueTypeKey ld zero - no characters + no characters left one - 1 character + 1 character left few - %ld characters + %ld characters left many - %ld characters + %ld characters left other - %ld characters + %ld characters left plural.count.followed_by_and_mutual diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/ar.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/ar.lproj/Localizable.strings index 943d56a9f..8ecefd32e 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/ar.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/ar.lproj/Localizable.strings @@ -22,6 +22,9 @@ "Common.Alerts.SignOut.Message" = "هل أنت متأكد من رغبتك في تسجيل الخُروج؟"; "Common.Alerts.SignOut.Title" = "تَسجيلُ الخُروج"; "Common.Alerts.SignUpFailure.Title" = "إخفاقٌ فِي التَّسجيل"; +"Common.Alerts.TranslationFailed.Button" = "حسنًا"; +"Common.Alerts.TranslationFailed.Message" = "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported."; +"Common.Alerts.TranslationFailed.Title" = "مُلاحظة"; "Common.Alerts.VoteFailure.PollEnded" = "اِنتَهَى اِستِطلاعُ الرَّأي"; "Common.Alerts.VoteFailure.Title" = "إخفاقٌ فِي التَّصويت"; "Common.Controls.Actions.Add" = "إضافة"; @@ -31,6 +34,7 @@ "Common.Controls.Actions.Compose" = "تأليف"; "Common.Controls.Actions.Confirm" = "تأكيد"; "Common.Controls.Actions.Continue" = "واصل"; +"Common.Controls.Actions.Copy" = "نَسخ"; "Common.Controls.Actions.CopyPhoto" = "نسخ الصورة"; "Common.Controls.Actions.Delete" = "حذف"; "Common.Controls.Actions.Discard" = "تجاهُل"; @@ -55,10 +59,12 @@ "Common.Controls.Actions.Share" = "المُشارك"; "Common.Controls.Actions.SharePost" = "مشارك المنشور"; "Common.Controls.Actions.ShareUser" = "مُشارَكَةُ %@"; -"Common.Controls.Actions.SignIn" = "تسجيل الدخول"; -"Common.Controls.Actions.SignUp" = "إنشاء حِساب"; +"Common.Controls.Actions.SignIn" = "تسجيلُ الدخول"; +"Common.Controls.Actions.SignUp" = "إنشاءُ حِساب"; "Common.Controls.Actions.Skip" = "تخطي"; "Common.Controls.Actions.TakePhoto" = "اِلتِقاطُ صُورَة"; +"Common.Controls.Actions.TranslatePost.Title" = "Translate from %@"; +"Common.Controls.Actions.TranslatePost.UnknownLanguage" = "Unknown"; "Common.Controls.Actions.TryAgain" = "المُحاولة مرة أُخرى"; "Common.Controls.Actions.UnblockDomain" = "رفع الحظر عن %@"; "Common.Controls.Friendship.Block" = "حظر"; @@ -100,6 +106,7 @@ "Common.Controls.Status.Actions.Menu" = "القائمة"; "Common.Controls.Status.Actions.Reblog" = "إعادة النشر"; "Common.Controls.Status.Actions.Reply" = "الرَّد"; +"Common.Controls.Status.Actions.ShareLinkInPost" = "Share Link in Post"; "Common.Controls.Status.Actions.ShowGif" = "أظْهِر GIF"; "Common.Controls.Status.Actions.ShowImage" = "أظْهِرِ الصُّورَة"; "Common.Controls.Status.Actions.ShowVideoPlayer" = "أظْهِر مُشَغِّلَ المَقاطِعِ المَرئِيَّة"; @@ -107,6 +114,8 @@ "Common.Controls.Status.Actions.Unfavorite" = "إزالة التفضيل"; "Common.Controls.Status.Actions.Unreblog" = "التراجُع عن إعادة النشر"; "Common.Controls.Status.ContentWarning" = "تحذير المُحتوى"; +"Common.Controls.Status.LinkViaUser" = "%@ via %@"; +"Common.Controls.Status.LoadEmbed" = "Load Embed"; "Common.Controls.Status.MediaContentWarning" = "اُنقُر لِلكَشف"; "Common.Controls.Status.MetaEntity.Email" = "عُنوان البريد الإلكتُروني: %@"; "Common.Controls.Status.MetaEntity.Hashtag" = "وَسْم: %@"; @@ -124,6 +133,10 @@ "Common.Controls.Status.Tag.Mention" = "إشارة"; "Common.Controls.Status.Tag.Url" = "عنوان URL"; "Common.Controls.Status.TapToReveal" = "اُنقُر لِلكَشف"; +"Common.Controls.Status.Translation.ShowOriginal" = "Shown Original"; +"Common.Controls.Status.Translation.TranslatedFrom" = "Translated from %@ using %@"; +"Common.Controls.Status.Translation.UnknownLanguage" = "Unknown"; +"Common.Controls.Status.Translation.UnknownProvider" = "Unknown"; "Common.Controls.Status.UserReblogged" = "أعادَ %@ تَدوينَها"; "Common.Controls.Status.UserRepliedTo" = "رَدًا على %@"; "Common.Controls.Status.Visibility.Direct" = "المُستخدمِونَ المُشارِ إليهم فَقَطْ مَن يُمكِنُهُم رُؤيَةُ هَذَا المَنشُور."; @@ -131,9 +144,9 @@ "Common.Controls.Status.Visibility.PrivateFromMe" = "فَقَطْ مُتابِعيني أنَا مَن يُمكِنُهُم رُؤيَةُ هَذَا المَنشُور."; "Common.Controls.Status.Visibility.Unlisted" = "يُمكِنُ لِلجَميعِ رُؤيَةُ هَذَا المَنشورِ وَلكِنَّهُ لَا يُعرَضُ فِي الخَطِّ الزَمنيّ العام."; "Common.Controls.Tabs.Home" = "الرَّئِيسَة"; -"Common.Controls.Tabs.Notification" = "الإشعارات"; +"Common.Controls.Tabs.Notifications" = "الإشعارات"; "Common.Controls.Tabs.Profile" = "المِلَفُّ التَّعريفِيّ"; -"Common.Controls.Tabs.Search" = "البَحث"; +"Common.Controls.Tabs.SearchAndExplore" = "البَحث وَالاِستِكشاف"; "Common.Controls.Timeline.Filtered" = "مُصفَّى"; "Common.Controls.Timeline.Header.BlockedWarning" = "لا يُمكِنُكَ عَرض الملف التَعريفي لهذا المُستخدِم حتَّى يَرفَعَ الحَظرَ عَنك."; @@ -161,17 +174,21 @@ "Scene.Compose.Accessibility.CustomEmojiPicker" = "منتقي الرموز التعبيرية المُخصَّص"; "Scene.Compose.Accessibility.DisableContentWarning" = "تعطيل تحذير المُحتَوى"; "Scene.Compose.Accessibility.EnableContentWarning" = "تفعيل تحذير المُحتَوى"; +"Scene.Compose.Accessibility.PostOptions" = "خياراتُ المَنشور"; "Scene.Compose.Accessibility.PostVisibilityMenu" = "قائمة ظهور المنشور"; +"Scene.Compose.Accessibility.PostingAs" = "نَشر كَـ %@"; "Scene.Compose.Accessibility.RemovePoll" = "إزالة الاستطلاع"; "Scene.Compose.Attachment.AttachmentBroken" = "هذا ال%@ مُعطَّل ويتعذَّرُ رفعُه إلى ماستودون."; -"Scene.Compose.Attachment.AttachmentTooLarge" = "Attachment too large"; -"Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "Can not regonize this media attachment"; +"Scene.Compose.Attachment.AttachmentTooLarge" = "المُرفَق كَبيرٌ جِدًّا"; +"Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "يتعذَّرُ التعرُّفُ على وسائِطِ هذا المُرفَق"; +"Scene.Compose.Attachment.CompressingState" = "يجري الضغط..."; "Scene.Compose.Attachment.DescriptionPhoto" = "صِف الصورة للمَكفوفين..."; "Scene.Compose.Attachment.DescriptionVideo" = "صِف المقطع المرئي للمَكفوفين..."; -"Scene.Compose.Attachment.LoadFailed" = "Load Failed"; +"Scene.Compose.Attachment.LoadFailed" = "فَشَلَ التَّحميل"; "Scene.Compose.Attachment.Photo" = "صورة"; -"Scene.Compose.Attachment.UploadFailed" = "Upload Failed"; +"Scene.Compose.Attachment.ServerProcessingState" = "مُعالجة الخادم جارِيَة..."; +"Scene.Compose.Attachment.UploadFailed" = "فَشَلَ الرَّفع"; "Scene.Compose.Attachment.Video" = "مقطع مرئي"; "Scene.Compose.AutoComplete.SpaceToAdd" = "انقر على مساحة لإضافتِها"; "Scene.Compose.ComposeAction" = "نَشر"; @@ -192,6 +209,8 @@ "Scene.Compose.Poll.OptionNumber" = "الخيار %ld"; "Scene.Compose.Poll.SevenDays" = "سبعةُ أيام"; "Scene.Compose.Poll.SixHours" = "سِتُّ ساعات"; +"Scene.Compose.Poll.ThePollHasEmptyOption" = "يوجَدُ خِيارٌ فارِغٌ فِي الاِستِطلاع"; +"Scene.Compose.Poll.ThePollIsInvalid" = "الاِستِطلاعُ غيرُ صالِح"; "Scene.Compose.Poll.ThirtyMinutes" = "ثلاثون دقيقة"; "Scene.Compose.Poll.ThreeDays" = "ثلاثةُ أيام"; "Scene.Compose.ReplyingToUser" = "رَدًا على %@"; @@ -223,6 +242,12 @@ "Scene.Familiarfollowers.Title" = "مُتابِعُونَ مَألُوفُونَ بِالنِّسبَةِ لَك"; "Scene.Favorite.Title" = "مُفضَّلَتُك"; "Scene.FavoritedBy.Title" = "مُفَضَّلٌ مِن قِبَلِ"; +"Scene.FollowedTags.Actions.Follow" = "مُتابَعَة"; +"Scene.FollowedTags.Actions.Unfollow" = "إلغاءُ المُتابَعَة"; +"Scene.FollowedTags.Header.Participants" = "المُشارِكُون"; +"Scene.FollowedTags.Header.Posts" = "مَنشورات"; +"Scene.FollowedTags.Header.PostsToday" = "مَنشوراتُ اليَوم"; +"Scene.FollowedTags.Title" = "وُسُومُ المُتابَع"; "Scene.Follower.Footer" = "لا يُمكِن عَرض المُتابِعين مِنَ الخوادم الأُخرى."; "Scene.Follower.Title" = "مُتابِعِين"; "Scene.Following.Footer" = "لا يُمكِن عَرض المُتابَعات مِنَ الخوادم الأُخرى."; @@ -234,6 +259,9 @@ "Scene.HomeTimeline.NavigationBarState.Published" = "تمَّ النَّشر!"; "Scene.HomeTimeline.NavigationBarState.Publishing" = "يَجري نَشر المُشارَكَة..."; "Scene.HomeTimeline.Title" = "الرَّئِيسَة"; +"Scene.Login.ServerSearchField.Placeholder" = "أدخِل عُنوانَ URL أو اِبحَث عَنِ الخادِمِ الخاصّ بِك"; +"Scene.Login.Subtitle" = "سَجِّل دُخولَكَ إلى الخادِم الَّذي أنشأتَ حِسابَكَ فيه."; +"Scene.Login.Title" = "مَرحَبًا بِكَ مُجَدَّدًا"; "Scene.Notification.FollowRequest.Accept" = "قَبُول"; "Scene.Notification.FollowRequest.Accepted" = "مَقبُول"; "Scene.Notification.FollowRequest.Reject" = "رَفض"; @@ -259,8 +287,11 @@ "Scene.Profile.Dashboard.Following" = "مُتابَع"; "Scene.Profile.Dashboard.Posts" = "مَنشورات"; "Scene.Profile.Fields.AddRow" = "إضافة صف"; +"Scene.Profile.Fields.Joined" = "Joined"; "Scene.Profile.Fields.Placeholder.Content" = "المُحتَوى"; "Scene.Profile.Fields.Placeholder.Label" = "التسمية"; +"Scene.Profile.Fields.Verified.Long" = "تمَّ التَّحقق مِن مِلكية هذا الرابِطِ بِتاريخ %@"; +"Scene.Profile.Fields.Verified.Short" = "تمَّ التَّحقق بِتاريخ %@"; "Scene.Profile.Header.FollowsYou" = "يُتابِعُك"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Message" = "تأكيدُ حَظر %@"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title" = "حَظرُ الحِساب"; @@ -393,13 +424,11 @@ "Scene.ServerPicker.EmptyState.BadNetwork" = "حدث خطأٌ ما أثناء تحميل البيانات. تحقَّق من اتصالك بالإنترنت."; "Scene.ServerPicker.EmptyState.FindingServers" = "يجري إيجاد خوادم متوفِّرَة..."; "Scene.ServerPicker.EmptyState.NoResults" = "لا توجد نتائج"; -"Scene.ServerPicker.Input.Placeholder" = "اِبحَث عن خادِم أو انضم إلى آخر خاص بك..."; -"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "اِبحَث فِي الخَوادِم أو أدخِل رابِط"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "اِبحث عَن مُجتَمَعَات أو أدخِل عُنوانَ URL"; "Scene.ServerPicker.Label.Category" = "الفئة"; "Scene.ServerPicker.Label.Language" = "اللُّغَة"; "Scene.ServerPicker.Label.Users" = "مُستَخدِم"; -"Scene.ServerPicker.Subtitle" = "اختر مجتمعًا بناءً على اهتماماتك، منطقتك أو يمكنك حتى اختيارُ مجتمعٍ ذي غرضٍ عام."; -"Scene.ServerPicker.SubtitleExtend" = "اختر مجتمعًا بناءً على اهتماماتك، منطقتك أو يمكنك حتى اختيارُ مجتمعٍ ذي غرضٍ عام. تُشغَّل جميعُ المجتمعِ مِن قِبَلِ مُنظمَةٍ أو فردٍ مُستقلٍ تمامًا."; +"Scene.ServerPicker.Subtitle" = "اِختر خادمًا بناءً على منطقتك، اِهتماماتك أو يُمكنك حتى اِختيارُ مجتمعٍ ذِي غرضٍ عام. بِإمكانِكَ الدردشة مع أي شخص على مَاستودُون، بغض النظر عن الخادم الخاصة بك."; "Scene.ServerPicker.Title" = "اِختر خادِم، أيًّا مِنهُم."; "Scene.ServerRules.Button.Confirm" = "أنا مُوافِق"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/ar.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/ar.lproj/Localizable.stringsdict index 862d98184..35727c0d6 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/ar.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/ar.lproj/Localizable.stringsdict @@ -74,6 +74,30 @@ %ld حَرف + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + يتبقى %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + لَا حَرف + one + حَرفٌ واحِد + two + حَرفانِ اِثنان + few + %ld أحرُف + many + %ld حَرفًا + other + %ld حَرف + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/ca.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/ca.lproj/Localizable.strings index fca658aef..acd53cc03 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/ca.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/ca.lproj/Localizable.strings @@ -1,36 +1,40 @@ -"Common.Alerts.BlockDomain.BlockEntireDomain" = "Bloquejar Domini"; -"Common.Alerts.BlockDomain.Title" = "Estàs segur, realment segur que vols bloquejar totalment %@? En la majoria dels casos bloquejar o silenciar uns pocs objectius és suficient i preferible. No veureu contingut d’aquest domini i se suprimirà qualsevol dels vostres seguidors d’aquest domini."; +"Common.Alerts.BlockDomain.BlockEntireDomain" = "Bloca el domini"; +"Common.Alerts.BlockDomain.Title" = "Estàs totalment segur que vols bloquejar per complet %@? En la majoria dels casos bloquejar o silenciar uns pocs objectius és suficient i preferible. No veureu contingut d’aquest domini i se suprimirà qualsevol dels vostres seguidors d’aquest domini."; "Common.Alerts.CleanCache.Message" = "S'ha netejat correctament la memòria cau de %@."; "Common.Alerts.CleanCache.Title" = "Neteja la memòria cau"; -"Common.Alerts.Common.PleaseTryAgain" = "Si us plau intenta-ho de nou."; -"Common.Alerts.Common.PleaseTryAgainLater" = "Si us plau, prova-ho més tard."; -"Common.Alerts.DeletePost.Message" = "Estàs segur que vols suprimir aquesta publicació?"; -"Common.Alerts.DeletePost.Title" = "Esborrar Publicació"; -"Common.Alerts.DiscardPostContent.Message" = "Confirma per a descartar el contingut de la publicació composta."; +"Common.Alerts.Common.PleaseTryAgain" = "Torna-ho a provar."; +"Common.Alerts.Common.PleaseTryAgainLater" = "Prova-ho més tard."; +"Common.Alerts.DeletePost.Message" = "Segur que vols eliminar aquesta publicació?"; +"Common.Alerts.DeletePost.Title" = "Eliminar la publicació"; +"Common.Alerts.DiscardPostContent.Message" = "Confirma per a descartar el contingut de la publicació."; "Common.Alerts.DiscardPostContent.Title" = "Descarta l'esborrany"; -"Common.Alerts.EditProfileFailure.Message" = "No es pot editar el perfil. Si us plau torna-ho a provar."; -"Common.Alerts.EditProfileFailure.Title" = "Error al Editar el Perfil"; -"Common.Alerts.PublishPostFailure.AttachmentsMessage.MoreThanOneVideo" = "No pots adjuntar més d'un vídeo."; +"Common.Alerts.EditProfileFailure.Message" = "No es pot editar el perfil. Torna-ho a provar."; +"Common.Alerts.EditProfileFailure.Title" = "Error en editar el perfil"; +"Common.Alerts.PublishPostFailure.AttachmentsMessage.MoreThanOneVideo" = "No es pot adjuntar més d'un vídeo."; "Common.Alerts.PublishPostFailure.AttachmentsMessage.VideoAttachWithPhoto" = "No es pot adjuntar un vídeo a una publicació que ja contingui imatges."; "Common.Alerts.PublishPostFailure.Message" = "No s'ha pogut enviar la publicació. -Comprova la teva connexió a Internet."; -"Common.Alerts.PublishPostFailure.Title" = "Error de Publicació"; -"Common.Alerts.SavePhotoFailure.Message" = "Activa el permís d'accés a la biblioteca de fotos per desar-la."; -"Common.Alerts.SavePhotoFailure.Title" = "Error al Desar la Foto"; -"Common.Alerts.ServerError.Title" = "Error del Servidor"; -"Common.Alerts.SignOut.Confirm" = "Tancar Sessió"; -"Common.Alerts.SignOut.Message" = "Estàs segur que vols tancar la sessió?"; -"Common.Alerts.SignOut.Title" = "Tancar Sessió"; +Comprova la connexió a Internet."; +"Common.Alerts.PublishPostFailure.Title" = "Error en publicar"; +"Common.Alerts.SavePhotoFailure.Message" = "Activa el permís d'accés a la biblioteca de fotos per a desar-la."; +"Common.Alerts.SavePhotoFailure.Title" = "Error en desar la foto"; +"Common.Alerts.ServerError.Title" = "Error del servidor"; +"Common.Alerts.SignOut.Confirm" = "Tanca la sessió"; +"Common.Alerts.SignOut.Message" = "Segur que vols tancar la sessió?"; +"Common.Alerts.SignOut.Title" = "Tanca la sessió"; "Common.Alerts.SignUpFailure.Title" = "Error en el registre"; +"Common.Alerts.TranslationFailed.Button" = "D'acord"; +"Common.Alerts.TranslationFailed.Message" = "La traducció ha fallat. Potser l'administrador d'aquest servidor no ha activat les traduccions o està executant una versió vella de Mastodon on les traduccions encara no eren suportades."; +"Common.Alerts.TranslationFailed.Title" = "Nota"; "Common.Alerts.VoteFailure.PollEnded" = "L'enquesta ha finalitzat"; -"Common.Alerts.VoteFailure.Title" = "Error del Vot"; +"Common.Alerts.VoteFailure.Title" = "Error en votar"; "Common.Controls.Actions.Add" = "Afegeix"; "Common.Controls.Actions.Back" = "Enrere"; "Common.Controls.Actions.BlockDomain" = "Bloqueja %@"; "Common.Controls.Actions.Cancel" = "Cancel·la"; -"Common.Controls.Actions.Compose" = "Composa"; +"Common.Controls.Actions.Compose" = "Redacta"; "Common.Controls.Actions.Confirm" = "Confirma"; "Common.Controls.Actions.Continue" = "Continua"; +"Common.Controls.Actions.Copy" = "Copia"; "Common.Controls.Actions.CopyPhoto" = "Copia la foto"; "Common.Controls.Actions.Delete" = "Suprimeix"; "Common.Controls.Actions.Discard" = "Descarta"; @@ -50,21 +54,23 @@ Comprova la teva connexió a Internet."; "Common.Controls.Actions.ReportUser" = "Informa sobre %@"; "Common.Controls.Actions.Save" = "Desa"; "Common.Controls.Actions.SavePhoto" = "Desa la foto"; -"Common.Controls.Actions.SeeMore" = "Veure més"; +"Common.Controls.Actions.SeeMore" = "Mostra'n més"; "Common.Controls.Actions.Settings" = "Configuració"; "Common.Controls.Actions.Share" = "Comparteix"; -"Common.Controls.Actions.SharePost" = "Compartir Publicació"; -"Common.Controls.Actions.ShareUser" = "Compartir %@"; -"Common.Controls.Actions.SignIn" = "Iniciar sessió"; -"Common.Controls.Actions.SignUp" = "Registre"; +"Common.Controls.Actions.SharePost" = "Comparteix la publicació"; +"Common.Controls.Actions.ShareUser" = "Comparteix %@"; +"Common.Controls.Actions.SignIn" = "Inicia sessió"; +"Common.Controls.Actions.SignUp" = "Crea un compte"; "Common.Controls.Actions.Skip" = "Omet"; "Common.Controls.Actions.TakePhoto" = "Fes una foto"; +"Common.Controls.Actions.TranslatePost.Title" = "Traduït del %@"; +"Common.Controls.Actions.TranslatePost.UnknownLanguage" = "Desconegut"; "Common.Controls.Actions.TryAgain" = "Torna a provar"; "Common.Controls.Actions.UnblockDomain" = "Desbloqueja %@"; -"Common.Controls.Friendship.Block" = "Bloqueja"; -"Common.Controls.Friendship.BlockDomain" = "Bloqueja %@"; -"Common.Controls.Friendship.BlockUser" = "Bloqueja %@"; -"Common.Controls.Friendship.Blocked" = "Bloquejat"; +"Common.Controls.Friendship.Block" = "Bloca"; +"Common.Controls.Friendship.BlockDomain" = "Bloca %@"; +"Common.Controls.Friendship.BlockUser" = "Bloca %@"; +"Common.Controls.Friendship.Blocked" = "Blocat"; "Common.Controls.Friendship.EditInfo" = "Edita"; "Common.Controls.Friendship.Follow" = "Segueix"; "Common.Controls.Friendship.Following" = "Seguint"; @@ -73,10 +79,10 @@ Comprova la teva connexió a Internet."; "Common.Controls.Friendship.MuteUser" = "Silencia %@"; "Common.Controls.Friendship.Muted" = "Silenciat"; "Common.Controls.Friendship.Pending" = "Pendent"; -"Common.Controls.Friendship.Request" = "Petició"; +"Common.Controls.Friendship.Request" = "Sol·licitud"; "Common.Controls.Friendship.ShowReblogs" = "Mostra els impulsos"; -"Common.Controls.Friendship.Unblock" = "Desbloqueja"; -"Common.Controls.Friendship.UnblockUser" = "Desbloqueja %@"; +"Common.Controls.Friendship.Unblock" = "Desbloca"; +"Common.Controls.Friendship.UnblockUser" = "Desbloca %@"; "Common.Controls.Friendship.Unmute" = "Deixa de silenciar"; "Common.Controls.Friendship.UnmuteUser" = "Treure silenci de %@"; "Common.Controls.Keyboard.Common.ComposeNewPost" = "Redacta un nova publicació"; @@ -86,31 +92,34 @@ Comprova la teva connexió a Internet."; "Common.Controls.Keyboard.SegmentedControl.NextSection" = "Secció Següent"; "Common.Controls.Keyboard.SegmentedControl.PreviousSection" = "Secció Anterior"; "Common.Controls.Keyboard.Timeline.NextStatus" = "Publicació següent"; -"Common.Controls.Keyboard.Timeline.OpenAuthorProfile" = "Obre el Perfil de l'Autor"; -"Common.Controls.Keyboard.Timeline.OpenRebloggerProfile" = "Obre el Perfil del Impulsor"; +"Common.Controls.Keyboard.Timeline.OpenAuthorProfile" = "Obre el perfil de l'autor"; +"Common.Controls.Keyboard.Timeline.OpenRebloggerProfile" = "Obre el perfil de l'impuls"; "Common.Controls.Keyboard.Timeline.OpenStatus" = "Obre la publicació"; "Common.Controls.Keyboard.Timeline.PreviewImage" = "Vista prèvia de l'Imatge"; "Common.Controls.Keyboard.Timeline.PreviousStatus" = "Publicació anterior"; -"Common.Controls.Keyboard.Timeline.ReplyStatus" = "Respon a la Publicació"; +"Common.Controls.Keyboard.Timeline.ReplyStatus" = "Respon a la publicació"; "Common.Controls.Keyboard.Timeline.ToggleContentWarning" = "Commuta l'Avís de Contingut"; "Common.Controls.Keyboard.Timeline.ToggleFavorite" = "Commuta el Favorit de la Publicació"; "Common.Controls.Keyboard.Timeline.ToggleReblog" = "Commuta l'Impuls de la Publicació"; "Common.Controls.Status.Actions.Favorite" = "Favorit"; "Common.Controls.Status.Actions.Hide" = "Amaga"; "Common.Controls.Status.Actions.Menu" = "Menú"; -"Common.Controls.Status.Actions.Reblog" = "Impuls"; +"Common.Controls.Status.Actions.Reblog" = "Impulsa"; "Common.Controls.Status.Actions.Reply" = "Respon"; +"Common.Controls.Status.Actions.ShareLinkInPost" = "Compartir l'Enllaç en el Tut"; "Common.Controls.Status.Actions.ShowGif" = "Mostra el GIF"; "Common.Controls.Status.Actions.ShowImage" = "Mostra la imatge"; "Common.Controls.Status.Actions.ShowVideoPlayer" = "Mostra el reproductor de vídeo"; "Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Toca i manté per a veure el menú"; -"Common.Controls.Status.Actions.Unfavorite" = "Desfer Favorit"; -"Common.Controls.Status.Actions.Unreblog" = "Desfer l'impuls"; +"Common.Controls.Status.Actions.Unfavorite" = "Desfés el favorit"; +"Common.Controls.Status.Actions.Unreblog" = "Desfés l'impuls"; "Common.Controls.Status.ContentWarning" = "Advertència de Contingut"; +"Common.Controls.Status.LinkViaUser" = "%@ través de %@"; +"Common.Controls.Status.LoadEmbed" = "Carregar incrustat"; "Common.Controls.Status.MediaContentWarning" = "Toca qualsevol lloc per a mostrar"; "Common.Controls.Status.MetaEntity.Email" = "Correu electrònic: %@"; -"Common.Controls.Status.MetaEntity.Hashtag" = "Etiqueta %@"; -"Common.Controls.Status.MetaEntity.Mention" = "Mostra el Perfil: %@"; +"Common.Controls.Status.MetaEntity.Hashtag" = "Etiqueta: %@"; +"Common.Controls.Status.MetaEntity.Mention" = "Mostra el perfil: %@"; "Common.Controls.Status.MetaEntity.Url" = "Enllaç: %@"; "Common.Controls.Status.Poll.Closed" = "Finalitzada"; "Common.Controls.Status.Poll.Vote" = "Vota"; @@ -118,38 +127,42 @@ Comprova la teva connexió a Internet."; "Common.Controls.Status.ShowPost" = "Mostra la Publicació"; "Common.Controls.Status.ShowUserProfile" = "Mostra el perfil de l'usuari"; "Common.Controls.Status.Tag.Email" = "Correu electrònic"; -"Common.Controls.Status.Tag.Emoji" = "Emoji"; +"Common.Controls.Status.Tag.Emoji" = "Emojis"; "Common.Controls.Status.Tag.Hashtag" = "Etiqueta"; "Common.Controls.Status.Tag.Link" = "Enllaç"; "Common.Controls.Status.Tag.Mention" = "Menciona"; "Common.Controls.Status.Tag.Url" = "URL"; "Common.Controls.Status.TapToReveal" = "Toca per a mostrar"; +"Common.Controls.Status.Translation.ShowOriginal" = "Mostra l'original"; +"Common.Controls.Status.Translation.TranslatedFrom" = "Translated from %@ using %@"; +"Common.Controls.Status.Translation.UnknownLanguage" = "Desconegut"; +"Common.Controls.Status.Translation.UnknownProvider" = "Unknown"; "Common.Controls.Status.UserReblogged" = "%@ ha impulsat"; "Common.Controls.Status.UserRepliedTo" = "Ha respòs a %@"; "Common.Controls.Status.Visibility.Direct" = "Només l'usuari mencionat pot veure aquesta publicació."; "Common.Controls.Status.Visibility.Private" = "Només els seus seguidors poden veure aquesta publicació."; "Common.Controls.Status.Visibility.PrivateFromMe" = "Només els meus seguidors poden veure aquesta publicació."; -"Common.Controls.Status.Visibility.Unlisted" = "Tothom pot veure aquesta publicació però no es mostra en la línia de temps pública."; +"Common.Controls.Status.Visibility.Unlisted" = "Tothom pot veure aquesta publicació, però no es mostra en la línia de temps pública."; "Common.Controls.Tabs.Home" = "Inici"; -"Common.Controls.Tabs.Notification" = "Notificació"; +"Common.Controls.Tabs.Notifications" = "Notificacions"; "Common.Controls.Tabs.Profile" = "Perfil"; -"Common.Controls.Tabs.Search" = "Cerca"; +"Common.Controls.Tabs.SearchAndExplore" = "Cerca i Explora"; "Common.Controls.Timeline.Filtered" = "Filtrat"; "Common.Controls.Timeline.Header.BlockedWarning" = "No pots veure el perfil d'aquest usuari -fins que et desbloquegi."; +fins que et desbloqui."; "Common.Controls.Timeline.Header.BlockingWarning" = "No pots veure el perfil d'aquest usuari - fins que el desbloquegis. +fins que el desbloquis. El teu perfil els sembla així."; "Common.Controls.Timeline.Header.NoStatusFound" = "No s'ha trobat cap publicació"; "Common.Controls.Timeline.Header.SuspendedWarning" = "Aquest usuari ha estat suspès."; "Common.Controls.Timeline.Header.UserBlockedWarning" = "No pots veure el perfil de %@ - fins que et desbloquegi."; + fins que et desbloqui."; "Common.Controls.Timeline.Header.UserBlockingWarning" = "No pots veure el perfil de %@ - fins que el desbloquegis. +fins que el desbloquis. El teu perfil els sembla així."; "Common.Controls.Timeline.Header.UserSuspendedWarning" = "El compte de %@ ha estat suspès."; -"Common.Controls.Timeline.Loader.LoadMissingPosts" = "Carrega les publicacions faltants"; -"Common.Controls.Timeline.Loader.LoadingMissingPosts" = "Carregant les publicacions faltants..."; +"Common.Controls.Timeline.Loader.LoadMissingPosts" = "Carrega les publicacions restants"; +"Common.Controls.Timeline.Loader.LoadingMissingPosts" = "Carregant les publicacions restants..."; "Common.Controls.Timeline.Loader.ShowMoreReplies" = "Mostra més respostes"; "Common.Controls.Timeline.Timestamp.Now" = "Ara"; "Scene.AccountList.AddAccount" = "Afegir compte"; @@ -161,16 +174,20 @@ El teu perfil els sembla així."; "Scene.Compose.Accessibility.CustomEmojiPicker" = "Selector d'Emoji Personalitzat"; "Scene.Compose.Accessibility.DisableContentWarning" = "Desactiva l'Avís de Contingut"; "Scene.Compose.Accessibility.EnableContentWarning" = "Activa l'Avís de Contingut"; -"Scene.Compose.Accessibility.PostVisibilityMenu" = "Menú de Visibilitat de Publicació"; +"Scene.Compose.Accessibility.PostOptions" = "Opcions del Tut"; +"Scene.Compose.Accessibility.PostVisibilityMenu" = "Menú de Visibilitat del Tut"; +"Scene.Compose.Accessibility.PostingAs" = "Publicant com a %@"; "Scene.Compose.Accessibility.RemovePoll" = "Eliminar Enquesta"; "Scene.Compose.Attachment.AttachmentBroken" = "Aquest %@ està trencat i no pot ser carregat a Mastodon."; "Scene.Compose.Attachment.AttachmentTooLarge" = "El fitxer adjunt és massa gran"; -"Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "No es pot reconèixer l'adjunt multimèdia"; -"Scene.Compose.Attachment.DescriptionPhoto" = "Descriu la foto per als disminuïts visuals..."; -"Scene.Compose.Attachment.DescriptionVideo" = "Descriu el vídeo per als disminuïts visuals..."; +"Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "No es pot reconèixer aquest adjunt multimèdia"; +"Scene.Compose.Attachment.CompressingState" = "Comprimint..."; +"Scene.Compose.Attachment.DescriptionPhoto" = "Descriu la foto per a les persones amb diversitat funcional..."; +"Scene.Compose.Attachment.DescriptionVideo" = "Descriu el vídeo per a les persones amb diversitat funcional..."; "Scene.Compose.Attachment.LoadFailed" = "Ha fallat la càrrega"; "Scene.Compose.Attachment.Photo" = "foto"; +"Scene.Compose.Attachment.ServerProcessingState" = "Servidor processant..."; "Scene.Compose.Attachment.UploadFailed" = "Pujada fallida"; "Scene.Compose.Attachment.Video" = "vídeo"; "Scene.Compose.AutoComplete.SpaceToAdd" = "Espai per afegir"; @@ -178,8 +195,8 @@ carregat a Mastodon."; "Scene.Compose.ContentInputPlaceholder" = "Escriu o enganxa el que tinguis en ment"; "Scene.Compose.ContentWarning.Placeholder" = "Escriu un advertiment precís aquí..."; "Scene.Compose.Keyboard.AppendAttachmentEntry" = "Afegeix Adjunt - %@"; -"Scene.Compose.Keyboard.DiscardPost" = "Descarta la Publicació"; -"Scene.Compose.Keyboard.PublishPost" = "Envia la Publicació"; +"Scene.Compose.Keyboard.DiscardPost" = "Descarta el Tut"; +"Scene.Compose.Keyboard.PublishPost" = "Envia el Tut"; "Scene.Compose.Keyboard.SelectVisibilityEntry" = "Selecciona la Visibilitat - %@"; "Scene.Compose.Keyboard.ToggleContentWarning" = "Commuta l'Avís de Contingut"; "Scene.Compose.Keyboard.TogglePoll" = "Commuta l'enquesta"; @@ -192,10 +209,12 @@ carregat a Mastodon."; "Scene.Compose.Poll.OptionNumber" = "Opció %ld"; "Scene.Compose.Poll.SevenDays" = "7 Dies"; "Scene.Compose.Poll.SixHours" = "6 Hores"; +"Scene.Compose.Poll.ThePollHasEmptyOption" = "L'enquesta té una opció buida"; +"Scene.Compose.Poll.ThePollIsInvalid" = "L'enquesta no és vàlida"; "Scene.Compose.Poll.ThirtyMinutes" = "30 minuts"; "Scene.Compose.Poll.ThreeDays" = "3 Dies"; "Scene.Compose.ReplyingToUser" = "responent a %@"; -"Scene.Compose.Title.NewPost" = "Nova publicació"; +"Scene.Compose.Title.NewPost" = "Nou Tut"; "Scene.Compose.Title.NewReply" = "Nova Resposta"; "Scene.Compose.Visibility.Direct" = "Només les persones que menciono"; "Scene.Compose.Visibility.Private" = "Només seguidors"; @@ -213,16 +232,22 @@ carregat a Mastodon."; "Scene.ConfirmEmail.Subtitle" = "Toca l'enllaç del correu electrònic que t'hem enviat per a confirmar el teu compte."; "Scene.ConfirmEmail.TapTheLinkWeEmailedToYouToVerifyYourAccount" = "Toca l'enllaç del correu electrònic que t'hem enviat per a confirmar el teu compte"; "Scene.ConfirmEmail.Title" = "Una última cosa."; -"Scene.Discovery.Intro" = "Aquestes son les publicacions que criden l'atenció en el teu racó de Mastodon."; +"Scene.Discovery.Intro" = "Aquests son els tuts que criden l'atenció en el teu racó de Mastodon."; "Scene.Discovery.Tabs.Community" = "Comunitat"; "Scene.Discovery.Tabs.ForYou" = "Per a tu"; "Scene.Discovery.Tabs.Hashtags" = "Etiquetes"; "Scene.Discovery.Tabs.News" = "Notícies"; -"Scene.Discovery.Tabs.Posts" = "Publicacions"; +"Scene.Discovery.Tabs.Posts" = "Tuts"; "Scene.Familiarfollowers.FollowedByNames" = "Seguit per %@"; "Scene.Familiarfollowers.Title" = "Seguidors coneguts"; "Scene.Favorite.Title" = "Els teus Favorits"; "Scene.FavoritedBy.Title" = "Preferit per"; +"Scene.FollowedTags.Actions.Follow" = "Segueix"; +"Scene.FollowedTags.Actions.Unfollow" = "Deixa de seguir"; +"Scene.FollowedTags.Header.Participants" = "participants"; +"Scene.FollowedTags.Header.Posts" = "tuts"; +"Scene.FollowedTags.Header.PostsToday" = "tuts d'avui"; +"Scene.FollowedTags.Title" = "Etiquetes seguides"; "Scene.Follower.Footer" = "Els seguidors d'altres servidors no son mostrats."; "Scene.Follower.Title" = "seguidor"; "Scene.Following.Footer" = "Els seguits d'altres servidors no son mostrats."; @@ -234,6 +259,9 @@ carregat a Mastodon."; "Scene.HomeTimeline.NavigationBarState.Published" = "Publicat!"; "Scene.HomeTimeline.NavigationBarState.Publishing" = "S'està publicant..."; "Scene.HomeTimeline.Title" = "Inici"; +"Scene.Login.ServerSearchField.Placeholder" = "Insereix la URL o cerca el teu servidor"; +"Scene.Login.Subtitle" = "T'inicia sessió en el servidor on has creat el teu compte."; +"Scene.Login.Title" = "Ben tornat"; "Scene.Notification.FollowRequest.Accept" = "Acceptar"; "Scene.Notification.FollowRequest.Accepted" = "Acceptat"; "Scene.Notification.FollowRequest.Reject" = "rebutjar"; @@ -257,27 +285,30 @@ carregat a Mastodon."; "Scene.Profile.Accessibility.ShowBannerImage" = "Mostra l'imatge del bàner"; "Scene.Profile.Dashboard.Followers" = "seguidors"; "Scene.Profile.Dashboard.Following" = "seguint"; -"Scene.Profile.Dashboard.Posts" = "publicacions"; +"Scene.Profile.Dashboard.Posts" = "tuts"; "Scene.Profile.Fields.AddRow" = "Afegeix fila"; +"Scene.Profile.Fields.Joined" = "S'hi va unir"; "Scene.Profile.Fields.Placeholder.Content" = "Contingut"; "Scene.Profile.Fields.Placeholder.Label" = "Etiqueta"; +"Scene.Profile.Fields.Verified.Long" = "La propietat d'aquest enllaç es va verificar el dia %@"; +"Scene.Profile.Fields.Verified.Short" = "Verificat a %@"; "Scene.Profile.Header.FollowsYou" = "Et segueix"; -"Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Message" = "Confirma per a bloquejar %@"; -"Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title" = "Bloqueja el Compte"; +"Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Message" = "Confirma per a blocar %@"; +"Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title" = "Bloca el Compte"; "Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Message" = "Confirma per a amagar els impulsos"; "Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Title" = "Amaga Impulsos"; "Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Message" = "Confirma per a silenciar %@"; "Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Title" = "Silencia el Compte"; "Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Message" = "Confirma per a mostrar els impulsos"; "Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Title" = "Mostra els Impulsos"; -"Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Message" = "Confirma per a desbloquejar %@"; -"Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Title" = "Desbloqueja el Compte"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Message" = "Confirma per a desblocar %@"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Title" = "Desbloca el Compte"; "Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.Message" = "Confirma deixar de silenciar a %@"; "Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.Title" = "Desfer silenciar compte"; "Scene.Profile.SegmentedControl.About" = "Quant a"; "Scene.Profile.SegmentedControl.Media" = "Mèdia"; -"Scene.Profile.SegmentedControl.Posts" = "Publicacions"; -"Scene.Profile.SegmentedControl.PostsAndReplies" = "Publicacions i Respostes"; +"Scene.Profile.SegmentedControl.Posts" = "Tuts"; +"Scene.Profile.SegmentedControl.PostsAndReplies" = "Tuts i Respostes"; "Scene.Profile.SegmentedControl.Replies" = "Respostes"; "Scene.RebloggedBy.Title" = "Impulsat per"; "Scene.Register.Error.Item.Agreement" = "Acord"; @@ -314,7 +345,7 @@ carregat a Mastodon."; "Scene.Register.Input.Username.Placeholder" = "nom d'usuari"; "Scene.Register.LetsGetYouSetUpOnDomain" = "Anem a configurar-te a %@"; "Scene.Register.Title" = "Anem a configurar-te a %@"; -"Scene.Report.Content1" = "Hi ha alguna altre publicació que vulguis afegir a l'informe?"; +"Scene.Report.Content1" = "Hi ha algun altre tut que vulguis afegir a l'informe?"; "Scene.Report.Content2" = "Hi ha alguna cosa que els moderadors hagin de saber sobre aquest informe?"; "Scene.Report.ReportSentTitle" = "Gràcies per informar, ho investigarem."; "Scene.Report.Reported" = "REPORTAT"; @@ -325,13 +356,13 @@ carregat a Mastodon."; "Scene.Report.StepFinal.BlockUser" = "Bloca %@"; "Scene.Report.StepFinal.DontWantToSeeThis" = "No vols veure això?"; "Scene.Report.StepFinal.MuteUser" = "Silencia %@"; -"Scene.Report.StepFinal.TheyWillNoLongerBeAbleToFollowOrSeeYourPostsButTheyCanSeeIfTheyveBeenBlocked" = "Ja no podran seguir ni veure les teves publicacions, però poden veure si han estat bloquejats."; +"Scene.Report.StepFinal.TheyWillNoLongerBeAbleToFollowOrSeeYourPostsButTheyCanSeeIfTheyveBeenBlocked" = "Ja no podran seguir ni veure els teus tus, però poden veure si han estat blocats."; "Scene.Report.StepFinal.Unfollow" = "Deixa de seguir"; "Scene.Report.StepFinal.UnfollowUser" = "Deixa de seguir %@"; "Scene.Report.StepFinal.Unfollowed" = "S'ha deixat de seguir"; -"Scene.Report.StepFinal.WhenYouSeeSomethingYouDontLikeOnMastodonYouCanRemoveThePersonFromYourExperience." = "Quan veus alguna cosa que no t'agrada a Mastodon, pots eliminar la persona de la vostra experiència."; +"Scene.Report.StepFinal.WhenYouSeeSomethingYouDontLikeOnMastodonYouCanRemoveThePersonFromYourExperience." = "Quan veus alguna cosa que no t'agrada a Mastodon, pots eliminar la persona de la teva experiència."; "Scene.Report.StepFinal.WhileWeReviewThisYouCanTakeActionAgainstUser" = "Mentre ho revisem, pots prendre mesures contra %@"; -"Scene.Report.StepFinal.YouWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted" = "No veuràs les seves publicacions o impulsos a la teva línia de temps personal. No sabran que han estat silenciats."; +"Scene.Report.StepFinal.YouWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted" = "No veuràs els seus tuts o impulsos a la teva línia de temps personal. No sabran que han estat silenciats."; "Scene.Report.StepFour.IsThereAnythingElseWeShouldKnow" = "Hi ha res més que hauríem de saber?"; "Scene.Report.StepFour.Step4Of4" = "Pas 4 de 4"; "Scene.Report.StepOne.IDontLikeIt" = "No m'agrada"; @@ -344,10 +375,10 @@ carregat a Mastodon."; "Scene.Report.StepOne.Step1Of4" = "Pas 1 de 4"; "Scene.Report.StepOne.TheIssueDoesNotFitIntoOtherCategories" = "El problema no encaixa en altres categories"; "Scene.Report.StepOne.WhatsWrongWithThisAccount" = "Quin és el problema amb aquest compte?"; -"Scene.Report.StepOne.WhatsWrongWithThisPost" = "Quin és el problema amb aquesta publicació?"; +"Scene.Report.StepOne.WhatsWrongWithThisPost" = "Quin és el problema amb aquest tut?"; "Scene.Report.StepOne.WhatsWrongWithThisUsername" = "Quin és el problema amb %@?"; "Scene.Report.StepOne.YouAreAwareThatItBreaksSpecificRules" = "Ets conscient que incompleix normes específiques"; -"Scene.Report.StepThree.AreThereAnyPostsThatBackUpThisReport" = "Hi ha alguna publicació que recolzi aquest informe?"; +"Scene.Report.StepThree.AreThereAnyPostsThatBackUpThisReport" = "Hi ha alguns tuts que recolzin aquest informe?"; "Scene.Report.StepThree.SelectAllThatApply" = "Selecciona tot el que correspongui"; "Scene.Report.StepThree.Step3Of4" = "Pas 3 de 4"; "Scene.Report.StepTwo.IJustDon’tLikeIt" = "Simplement no m'agrada"; @@ -372,7 +403,7 @@ carregat a Mastodon."; "Scene.Search.Searching.Segment.All" = "Tots"; "Scene.Search.Searching.Segment.Hashtags" = "Etiquetes"; "Scene.Search.Searching.Segment.People" = "Gent"; -"Scene.Search.Searching.Segment.Posts" = "Publicacions"; +"Scene.Search.Searching.Segment.Posts" = "Tuts"; "Scene.Search.Title" = "Cerca"; "Scene.ServerPicker.Button.Category.Academia" = "acadèmia"; "Scene.ServerPicker.Button.Category.Activism" = "activisme"; @@ -393,14 +424,12 @@ carregat a Mastodon."; "Scene.ServerPicker.EmptyState.BadNetwork" = "Alguna cosa no ha anat bé en carregar les dades. Comprova la teva connexió a Internet."; "Scene.ServerPicker.EmptyState.FindingServers" = "Cercant els servidors disponibles..."; "Scene.ServerPicker.EmptyState.NoResults" = "No hi ha resultats"; -"Scene.ServerPicker.Input.Placeholder" = "Cerca servidors"; -"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Cerca servidors o introdueix l'enllaç"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Cerca comunitats o introdueix l'URL"; "Scene.ServerPicker.Label.Category" = "CATEGORIA"; "Scene.ServerPicker.Label.Language" = "LLENGUATGE"; "Scene.ServerPicker.Label.Users" = "USUARIS"; -"Scene.ServerPicker.Subtitle" = "Tria una comunitat segons els teus interessos, regió o una de propòsit general."; -"Scene.ServerPicker.SubtitleExtend" = "Tria una comunitat segons els teus interessos, regió o una de propòsit general. Cada comunitat és operada per una organització totalment independent o individualment."; -"Scene.ServerPicker.Title" = "Mastodon està fet d'usuaris en diferents comunitats."; +"Scene.ServerPicker.Subtitle" = "Tria un servidor en funció de la teva regió, interessos o un de propòsit general. Seguiràs podent connectar amb tothom a Mastodon, independentment del servidor."; +"Scene.ServerPicker.Title" = "Mastodon està fet d'usuaris en diferents servidors."; "Scene.ServerRules.Button.Confirm" = "Hi estic d'acord"; "Scene.ServerRules.PrivacyPolicy" = "política de privadesa"; "Scene.ServerRules.Prompt" = "Al continuar, estàs subjecte als termes de servei i a la política de privacitat de %@."; @@ -422,8 +451,8 @@ carregat a Mastodon."; "Scene.Settings.Section.LookAndFeel.SortaDark" = "Una Mena de Fosc"; "Scene.Settings.Section.LookAndFeel.Title" = "Aspecte i Comportament"; "Scene.Settings.Section.LookAndFeel.UseSystem" = "Usa el del Sistema"; -"Scene.Settings.Section.Notifications.Boosts" = "Ha impulsat el meu estat"; -"Scene.Settings.Section.Notifications.Favorites" = "Ha afavorit el meu estat"; +"Scene.Settings.Section.Notifications.Boosts" = "Ha impulsat el meu tut"; +"Scene.Settings.Section.Notifications.Favorites" = "Ha afavorit el meu tut"; "Scene.Settings.Section.Notifications.Follows" = "Em segueix"; "Scene.Settings.Section.Notifications.Mentions" = "M'ha mencionat"; "Scene.Settings.Section.Notifications.Title" = "Notificacions"; @@ -442,10 +471,10 @@ carregat a Mastodon."; "Scene.Settings.Section.SpicyZone.Signout" = "Tancar Sessió"; "Scene.Settings.Section.SpicyZone.Title" = "La Zona Picant"; "Scene.Settings.Title" = "Configuració"; -"Scene.SuggestionAccount.FollowExplain" = "Quan segueixes algú, veuràs les seves publicacions a Inici."; +"Scene.SuggestionAccount.FollowExplain" = "Quan segueixes algú, veuràs els seus tuts a Inici."; "Scene.SuggestionAccount.Title" = "Cerca Persones a Seguir"; -"Scene.Thread.BackTitle" = "Publicació"; -"Scene.Thread.Title" = "Publicació de %@"; +"Scene.Thread.BackTitle" = "Tut"; +"Scene.Thread.Title" = "Tut de %@"; "Scene.Welcome.GetStarted" = "Comença"; "Scene.Welcome.LogIn" = "Inicia sessió"; "Scene.Welcome.Slogan" = "Xarxa social diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/ca.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/ca.lproj/Localizable.stringsdict index cc28edbc6..615fd0c48 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/ca.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/ca.lproj/Localizable.stringsdict @@ -50,6 +50,22 @@ %ld caràcters + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + resten %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 caràcter + other + %ld caràcters + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey @@ -392,7 +408,7 @@ NSStringFormatValueTypeKey ld one - fa 1 día + fa 1 dia other fa %ld dies @@ -408,7 +424,7 @@ NSStringFormatValueTypeKey ld one - fa 1h + fa 1 h other fa %ld hores @@ -424,9 +440,9 @@ NSStringFormatValueTypeKey ld one - fa 1 minut + fa 1 min other - fa %ld minuts + fa %ld min date.second.ago.abbr @@ -440,9 +456,9 @@ NSStringFormatValueTypeKey ld one - fa 1 segon + fa 1 s other - fa %ld segons + fa %ld s diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/ckb.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/ckb.lproj/Localizable.strings index 05c575520..5f6f124a1 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/ckb.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/ckb.lproj/Localizable.strings @@ -22,6 +22,9 @@ "Common.Alerts.SignOut.Message" = "دڵنیایت دەتەوێت دەربچیت؟"; "Common.Alerts.SignOut.Title" = "دەربچۆ"; "Common.Alerts.SignUpFailure.Title" = "تۆمارکردنەکە سەرکەوتوو نەبوو"; +"Common.Alerts.TranslationFailed.Button" = "OK"; +"Common.Alerts.TranslationFailed.Message" = "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported."; +"Common.Alerts.TranslationFailed.Title" = "Note"; "Common.Alerts.VoteFailure.PollEnded" = "دەنگدانەکە کۆتایی هاتووە"; "Common.Alerts.VoteFailure.Title" = "نەتوانرا دەنگ بدرێت"; "Common.Controls.Actions.Add" = "زیادی بکە"; @@ -31,6 +34,7 @@ "Common.Controls.Actions.Compose" = "پۆست بکە"; "Common.Controls.Actions.Confirm" = "پشتڕاستی بکەوە"; "Common.Controls.Actions.Continue" = "بەردەوام بە"; +"Common.Controls.Actions.Copy" = "Copy"; "Common.Controls.Actions.CopyPhoto" = "لەبەری بگرەوە"; "Common.Controls.Actions.Delete" = "بیسڕەوە"; "Common.Controls.Actions.Discard" = "وازی لێ بێنە"; @@ -55,10 +59,12 @@ "Common.Controls.Actions.Share" = "هاوبەشی بکە"; "Common.Controls.Actions.SharePost" = "هاوبەشی بکە"; "Common.Controls.Actions.ShareUser" = "%@ هاوبەش بکە"; -"Common.Controls.Actions.SignIn" = "بچۆ ژوورەوە"; -"Common.Controls.Actions.SignUp" = "خۆت تۆمار بکە"; +"Common.Controls.Actions.SignIn" = "Log in"; +"Common.Controls.Actions.SignUp" = "Create account"; "Common.Controls.Actions.Skip" = "بیپەڕێنە"; "Common.Controls.Actions.TakePhoto" = "وێنە بگرە"; +"Common.Controls.Actions.TranslatePost.Title" = "Translate from %@"; +"Common.Controls.Actions.TranslatePost.UnknownLanguage" = "Unknown"; "Common.Controls.Actions.TryAgain" = "هەوڵ بدەوە"; "Common.Controls.Actions.UnblockDomain" = "%@ ئاستەنگ مەکە"; "Common.Controls.Friendship.Block" = "ئاستەنگی بکە"; @@ -100,6 +106,7 @@ "Common.Controls.Status.Actions.Menu" = "پێڕست"; "Common.Controls.Status.Actions.Reblog" = "پۆستی بکەوە"; "Common.Controls.Status.Actions.Reply" = "وەڵامی بدەوە"; +"Common.Controls.Status.Actions.ShareLinkInPost" = "Share Link in Post"; "Common.Controls.Status.Actions.ShowGif" = "گیفەکە نیشان بدە"; "Common.Controls.Status.Actions.ShowImage" = "وێنەکە نیشان بدە"; "Common.Controls.Status.Actions.ShowVideoPlayer" = "ڤیدیۆکە لێ بدە"; @@ -107,6 +114,8 @@ "Common.Controls.Status.Actions.Unfavorite" = "بەدڵبوونەکە بگەڕێنەوە"; "Common.Controls.Status.Actions.Unreblog" = "پۆستکردنەکە بگەڕێنەوە"; "Common.Controls.Status.ContentWarning" = "ئاگاداریی ناوەڕۆک"; +"Common.Controls.Status.LinkViaUser" = "%@ via %@"; +"Common.Controls.Status.LoadEmbed" = "Load Embed"; "Common.Controls.Status.MediaContentWarning" = "دەستی پیا بنێ بۆ نیشاندانی"; "Common.Controls.Status.MetaEntity.Email" = "Email address: %@"; "Common.Controls.Status.MetaEntity.Hashtag" = "Hashtag: %@"; @@ -124,6 +133,10 @@ "Common.Controls.Status.Tag.Mention" = "ئاماژە"; "Common.Controls.Status.Tag.Url" = "بەستەر"; "Common.Controls.Status.TapToReveal" = "دەستی پیا بنێ بۆ نیشاندانی"; +"Common.Controls.Status.Translation.ShowOriginal" = "Shown Original"; +"Common.Controls.Status.Translation.TranslatedFrom" = "Translated from %@ using %@"; +"Common.Controls.Status.Translation.UnknownLanguage" = "Unknown"; +"Common.Controls.Status.Translation.UnknownProvider" = "Unknown"; "Common.Controls.Status.UserReblogged" = "%@ پۆست کرایەوە"; "Common.Controls.Status.UserRepliedTo" = "لە وەڵامدا بۆ %@"; "Common.Controls.Status.Visibility.Direct" = "تەنیا بەکارهێنەرە ئاماژە پێکراوەکە دەتوانێت ئەم پۆستە ببینێت."; @@ -131,9 +144,9 @@ "Common.Controls.Status.Visibility.PrivateFromMe" = "تەنیا شوێنکەوتووەکانم دەتوانن ئەم پۆستە ببینن."; "Common.Controls.Status.Visibility.Unlisted" = "هەرکەسێک دەتوانێت ئەم پۆستە ببینێت بەڵام ناچێتە بەردەمیان."; "Common.Controls.Tabs.Home" = "ماڵەوە"; -"Common.Controls.Tabs.Notification" = "ئاگادارکردنەوەکان"; +"Common.Controls.Tabs.Notifications" = "Notifications"; "Common.Controls.Tabs.Profile" = "پرۆفایل"; -"Common.Controls.Tabs.Search" = "بگەڕێ"; +"Common.Controls.Tabs.SearchAndExplore" = "Search and Explore"; "Common.Controls.Timeline.Filtered" = "پاڵێوراو"; "Common.Controls.Timeline.Header.BlockedWarning" = "ناتوانیت پرۆفایلی ئەم بەکارهێنەرە ببینیت تا ئەو کاتەی ئاستەنگەکەت لادەبات."; @@ -161,15 +174,19 @@ "Scene.Compose.Accessibility.CustomEmojiPicker" = "هەڵبژێری ئیمۆجی"; "Scene.Compose.Accessibility.DisableContentWarning" = "ئاگاداریی ناوەڕۆک ناچالاک بکە"; "Scene.Compose.Accessibility.EnableContentWarning" = "ئاگاداریی ناوەڕۆک چالاک بکە"; +"Scene.Compose.Accessibility.PostOptions" = "Post Options"; "Scene.Compose.Accessibility.PostVisibilityMenu" = "پێڕستی شێوازی دەرکەوتنی پۆست"; +"Scene.Compose.Accessibility.PostingAs" = "Posting as %@"; "Scene.Compose.Accessibility.RemovePoll" = "دانگدانەکە لابە"; "Scene.Compose.Attachment.AttachmentBroken" = "ئەم %@ـە تێک چووە و ناتوانیت بەرزی بکەیتەوە."; "Scene.Compose.Attachment.AttachmentTooLarge" = "Attachment too large"; -"Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "Can not regonize this media attachment"; +"Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "Can not recognize this media attachment"; +"Scene.Compose.Attachment.CompressingState" = "Compressing..."; "Scene.Compose.Attachment.DescriptionPhoto" = "وێنەکەت بۆ نابیناکان باس بکە..."; "Scene.Compose.Attachment.DescriptionVideo" = "ڤیدیۆکەت بۆ نابیناکان باس بکە..."; "Scene.Compose.Attachment.LoadFailed" = "Load Failed"; "Scene.Compose.Attachment.Photo" = "وێنە"; +"Scene.Compose.Attachment.ServerProcessingState" = "Server Processing..."; "Scene.Compose.Attachment.UploadFailed" = "Upload Failed"; "Scene.Compose.Attachment.Video" = "ڤیدیۆ"; "Scene.Compose.AutoComplete.SpaceToAdd" = "بۆشایی دابنێ بۆ زیادکردن"; @@ -191,6 +208,8 @@ "Scene.Compose.Poll.OptionNumber" = "بژاردەی %ld"; "Scene.Compose.Poll.SevenDays" = "7 ڕۆژ"; "Scene.Compose.Poll.SixHours" = "6 کاتژمێر"; +"Scene.Compose.Poll.ThePollHasEmptyOption" = "The poll has empty option"; +"Scene.Compose.Poll.ThePollIsInvalid" = "The poll is invalid"; "Scene.Compose.Poll.ThirtyMinutes" = "30 خولەک"; "Scene.Compose.Poll.ThreeDays" = "3 ڕۆژ"; "Scene.Compose.ReplyingToUser" = "لە وەڵامدا بۆ %@"; @@ -222,6 +241,12 @@ "Scene.Familiarfollowers.Title" = "Followers you familiar"; "Scene.Favorite.Title" = "بەدڵبووەکانت"; "Scene.FavoritedBy.Title" = "Favorited By"; +"Scene.FollowedTags.Actions.Follow" = "Follow"; +"Scene.FollowedTags.Actions.Unfollow" = "Unfollow"; +"Scene.FollowedTags.Header.Participants" = "participants"; +"Scene.FollowedTags.Header.Posts" = "posts"; +"Scene.FollowedTags.Header.PostsToday" = "posts today"; +"Scene.FollowedTags.Title" = "Followed Tags"; "Scene.Follower.Footer" = "شوێنکەوتووەکانی لە ڕاژەکارەکانی ترەوە نیشان نادرێت."; "Scene.Follower.Title" = "follower"; "Scene.Following.Footer" = "شوێنکەوتنەکانی بۆ هەژماری ڕاژەکارەکانی تر نیشان نادرێت."; @@ -233,6 +258,9 @@ "Scene.HomeTimeline.NavigationBarState.Published" = "بڵاوکرایەوە!"; "Scene.HomeTimeline.NavigationBarState.Publishing" = "پۆستەکە بڵاو دەکرێتەوە..."; "Scene.HomeTimeline.Title" = "ماڵەوە"; +"Scene.Login.ServerSearchField.Placeholder" = "Enter URL or search for your server"; +"Scene.Login.Subtitle" = "Log you in on the server you created your account on."; +"Scene.Login.Title" = "Welcome back"; "Scene.Notification.FollowRequest.Accept" = "Accept"; "Scene.Notification.FollowRequest.Accepted" = "Accepted"; "Scene.Notification.FollowRequest.Reject" = "reject"; @@ -258,8 +286,11 @@ "Scene.Profile.Dashboard.Following" = "شوێنکەوتن"; "Scene.Profile.Dashboard.Posts" = "پۆستەکان"; "Scene.Profile.Fields.AddRow" = "ڕیز زیاد بکە"; +"Scene.Profile.Fields.Joined" = "Joined"; "Scene.Profile.Fields.Placeholder.Content" = "ناوەڕۆک"; "Scene.Profile.Fields.Placeholder.Label" = "ناونیشان"; +"Scene.Profile.Fields.Verified.Long" = "Ownership of this link was checked on %@"; +"Scene.Profile.Fields.Verified.Short" = "Verified on %@"; "Scene.Profile.Header.FollowsYou" = "Follows You"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Message" = "دڵنیا ببەوە بۆ ئاستەنگکردنی %@"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title" = "ئاستەنگی بکە"; @@ -392,13 +423,11 @@ "Scene.ServerPicker.EmptyState.BadNetwork" = "هەڵەیەک ڕوویدا لە کاتی بارکردن. لە هەبوونی هێڵی ئینتەرنێت دڵنیا بە."; "Scene.ServerPicker.EmptyState.FindingServers" = "ڕاژەکار دەدۆزرێتەوە..."; "Scene.ServerPicker.EmptyState.NoResults" = "ئەنجام نییە"; -"Scene.ServerPicker.Input.Placeholder" = "بگەڕێ"; -"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Search servers or enter URL"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Search communities or enter URL"; "Scene.ServerPicker.Label.Category" = "بەش"; "Scene.ServerPicker.Label.Language" = "زمان"; "Scene.ServerPicker.Label.Users" = "بەکارهێنەر"; -"Scene.ServerPicker.Subtitle" = "ڕاژەکارێکێکی گشتی یان دانەیەک لەسەر بنەمای حەزەکانت و هەرێمەکەت هەڵبژێرە."; -"Scene.ServerPicker.SubtitleExtend" = "ڕاژەکارێکێکی گشتی یان دانەیەک لەسەر بنەمای حەزەکانت و هەرێمەکەت هەڵبژێرە. هەر ڕاژەکارێک لەلایەن ڕێکخراوێک یان تاکەکەسێک بەڕێوە دەبرێت."; +"Scene.ServerPicker.Subtitle" = "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers."; "Scene.ServerPicker.Title" = "ماستۆدۆن لە چەندان بەکارهێنەر پێک دێت کە لە ڕاژەکاری جیاواز دان."; "Scene.ServerRules.Button.Confirm" = "ڕازیم"; "Scene.ServerRules.PrivacyPolicy" = "سیاسەتی تایبەتێتی"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/ckb.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/ckb.lproj/Localizable.stringsdict index 001a8a608..8116226ec 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/ckb.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/ckb.lproj/Localizable.stringsdict @@ -50,6 +50,22 @@ %ld نووسە + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ left + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 character + other + %ld characters + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/cs.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/cs.lproj/Localizable.strings new file mode 100644 index 000000000..8a8a977b7 --- /dev/null +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/cs.lproj/Localizable.strings @@ -0,0 +1,479 @@ +"Common.Alerts.BlockDomain.BlockEntireDomain" = "Blokovat doménu"; +"Common.Alerts.BlockDomain.Title" = "Opravdu chcete blokovat celou doménu %@? Ve většině případů stačí zablokovat nebo skrýt pár konkrétních uživatelů, což také doporučujeme. Z této domény neuvidíte obsah v žádné veřejné časové ose ani v oznámeních. Vaši sledující z této domény budou odstraněni."; +"Common.Alerts.CleanCache.Message" = "Úspěšně vyčištěno %@ mezipaměti."; +"Common.Alerts.CleanCache.Title" = "Vyčistit mezipaměť"; +"Common.Alerts.Common.PleaseTryAgain" = "Zkuste to prosím znovu."; +"Common.Alerts.Common.PleaseTryAgainLater" = "Zkuste to prosím znovu později."; +"Common.Alerts.DeletePost.Message" = "Opravdu chcete smazat tento příspěvek?"; +"Common.Alerts.DeletePost.Title" = "Odstranit příspěvek"; +"Common.Alerts.DiscardPostContent.Message" = "Potvrďte odstranění obsahu složeného příspěvku."; +"Common.Alerts.DiscardPostContent.Title" = "Zahodit koncept"; +"Common.Alerts.EditProfileFailure.Message" = "Nelze upravit profil. Zkuste to prosím znovu."; +"Common.Alerts.EditProfileFailure.Title" = "Chyba při úpravě profilu"; +"Common.Alerts.PublishPostFailure.AttachmentsMessage.MoreThanOneVideo" = "Nelze připojit více než jedno video."; +"Common.Alerts.PublishPostFailure.AttachmentsMessage.VideoAttachWithPhoto" = "K příspěvku, který již obsahuje obrázky, nelze připojit video."; +"Common.Alerts.PublishPostFailure.Message" = "Nepodařilo se publikovat příspěvek. +Zkontrolujte prosím připojení k internetu."; +"Common.Alerts.PublishPostFailure.Title" = "Publikování selhalo"; +"Common.Alerts.SavePhotoFailure.Message" = "Pro uložení fotografie povolte přístup k knihovně fotografií."; +"Common.Alerts.SavePhotoFailure.Title" = "Uložení fotografie se nezdařilo"; +"Common.Alerts.ServerError.Title" = "Chyba serveru"; +"Common.Alerts.SignOut.Confirm" = "Odhlásit se"; +"Common.Alerts.SignOut.Message" = "Opravdu se chcete odhlásit?"; +"Common.Alerts.SignOut.Title" = "Odhlásit se"; +"Common.Alerts.SignUpFailure.Title" = "Registrace selhala"; +"Common.Alerts.TranslationFailed.Button" = "OK"; +"Common.Alerts.TranslationFailed.Message" = "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported."; +"Common.Alerts.TranslationFailed.Title" = "Note"; +"Common.Alerts.VoteFailure.PollEnded" = "Anketa skončila"; +"Common.Alerts.VoteFailure.Title" = "Selhání hlasování"; +"Common.Controls.Actions.Add" = "Přidat"; +"Common.Controls.Actions.Back" = "Zpět"; +"Common.Controls.Actions.BlockDomain" = "Blokovat %@"; +"Common.Controls.Actions.Cancel" = "Zrušit"; +"Common.Controls.Actions.Compose" = "Napsat"; +"Common.Controls.Actions.Confirm" = "Potvrdit"; +"Common.Controls.Actions.Continue" = "Pokračovat"; +"Common.Controls.Actions.Copy" = "Copy"; +"Common.Controls.Actions.CopyPhoto" = "Kopírovat fotografii"; +"Common.Controls.Actions.Delete" = "Smazat"; +"Common.Controls.Actions.Discard" = "Zahodit"; +"Common.Controls.Actions.Done" = "Hotovo"; +"Common.Controls.Actions.Edit" = "Upravit"; +"Common.Controls.Actions.FindPeople" = "Najít lidi ke sledování"; +"Common.Controls.Actions.ManuallySearch" = "Místo toho ručně vyhledat"; +"Common.Controls.Actions.Next" = "Další"; +"Common.Controls.Actions.Ok" = "OK"; +"Common.Controls.Actions.Open" = "Otevřít"; +"Common.Controls.Actions.OpenInBrowser" = "Otevřít v prohlížeči"; +"Common.Controls.Actions.OpenInSafari" = "Otevřít v Safari"; +"Common.Controls.Actions.Preview" = "Náhled"; +"Common.Controls.Actions.Previous" = "Předchozí"; +"Common.Controls.Actions.Remove" = "Odstranit"; +"Common.Controls.Actions.Reply" = "Odpovědět"; +"Common.Controls.Actions.ReportUser" = "Nahlásit %@"; +"Common.Controls.Actions.Save" = "Uložit"; +"Common.Controls.Actions.SavePhoto" = "Uložit fotku"; +"Common.Controls.Actions.SeeMore" = "Zobrazit více"; +"Common.Controls.Actions.Settings" = "Nastavení"; +"Common.Controls.Actions.Share" = "Sdílet"; +"Common.Controls.Actions.SharePost" = "Sdílet příspěvek"; +"Common.Controls.Actions.ShareUser" = "Sdílet %@"; +"Common.Controls.Actions.SignIn" = "Přihlásit se"; +"Common.Controls.Actions.SignUp" = "Vytvořit účet"; +"Common.Controls.Actions.Skip" = "Přeskočit"; +"Common.Controls.Actions.TakePhoto" = "Vyfotit"; +"Common.Controls.Actions.TranslatePost.Title" = "Translate from %@"; +"Common.Controls.Actions.TranslatePost.UnknownLanguage" = "Unknown"; +"Common.Controls.Actions.TryAgain" = "Zkusit znovu"; +"Common.Controls.Actions.UnblockDomain" = "Odblokovat %@"; +"Common.Controls.Friendship.Block" = "Blokovat"; +"Common.Controls.Friendship.BlockDomain" = "Blokovat %@"; +"Common.Controls.Friendship.BlockUser" = "Blokovat %@"; +"Common.Controls.Friendship.Blocked" = "Blokovaný"; +"Common.Controls.Friendship.EditInfo" = "Upravit informace"; +"Common.Controls.Friendship.Follow" = "Sledovat"; +"Common.Controls.Friendship.Following" = "Sleduji"; +"Common.Controls.Friendship.HideReblogs" = "Hide Reblogs"; +"Common.Controls.Friendship.Mute" = "Skrýt"; +"Common.Controls.Friendship.MuteUser" = "Skrýt %@"; +"Common.Controls.Friendship.Muted" = "Skrytý"; +"Common.Controls.Friendship.Pending" = "Čekající"; +"Common.Controls.Friendship.Request" = "Požadavek"; +"Common.Controls.Friendship.ShowReblogs" = "Show Reblogs"; +"Common.Controls.Friendship.Unblock" = "Odblokovat"; +"Common.Controls.Friendship.UnblockUser" = "Odblokovat %@"; +"Common.Controls.Friendship.Unmute" = "Odkrýt"; +"Common.Controls.Friendship.UnmuteUser" = "Odkrýt %@"; +"Common.Controls.Keyboard.Common.ComposeNewPost" = "Vytvořit nový příspěvek"; +"Common.Controls.Keyboard.Common.OpenSettings" = "Otevřít Nastavení"; +"Common.Controls.Keyboard.Common.ShowFavorites" = "Zobrazit Oblíbené"; +"Common.Controls.Keyboard.Common.SwitchToTab" = "Přepnout na %@"; +"Common.Controls.Keyboard.SegmentedControl.NextSection" = "Další sekce"; +"Common.Controls.Keyboard.SegmentedControl.PreviousSection" = "Předchozí sekce"; +"Common.Controls.Keyboard.Timeline.NextStatus" = "Další příspěvek"; +"Common.Controls.Keyboard.Timeline.OpenAuthorProfile" = "Otevřít profil autora"; +"Common.Controls.Keyboard.Timeline.OpenRebloggerProfile" = "Otevřít rebloggerův profil"; +"Common.Controls.Keyboard.Timeline.OpenStatus" = "Otevřít příspěvek"; +"Common.Controls.Keyboard.Timeline.PreviewImage" = "Náhled obrázku"; +"Common.Controls.Keyboard.Timeline.PreviousStatus" = "Předchozí příspěvek"; +"Common.Controls.Keyboard.Timeline.ReplyStatus" = "Odpovědět na příspěvek"; +"Common.Controls.Keyboard.Timeline.ToggleContentWarning" = "Přepnout varování obsahu"; +"Common.Controls.Keyboard.Timeline.ToggleFavorite" = "Toggle Favorite on Post"; +"Common.Controls.Keyboard.Timeline.ToggleReblog" = "Přepnout Reblog na příspěvku"; +"Common.Controls.Status.Actions.Favorite" = "Oblíbit"; +"Common.Controls.Status.Actions.Hide" = "Skrýt"; +"Common.Controls.Status.Actions.Menu" = "Nabídka"; +"Common.Controls.Status.Actions.Reblog" = "Boostnout"; +"Common.Controls.Status.Actions.Reply" = "Odpovědět"; +"Common.Controls.Status.Actions.ShareLinkInPost" = "Share Link in Post"; +"Common.Controls.Status.Actions.ShowGif" = "Zobrazit GIF"; +"Common.Controls.Status.Actions.ShowImage" = "Zobrazit obrázek"; +"Common.Controls.Status.Actions.ShowVideoPlayer" = "Zobrazit video přehrávač"; +"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Klepnutím podržte pro zobrazení nabídky"; +"Common.Controls.Status.Actions.Unfavorite" = "Odebrat z oblízených"; +"Common.Controls.Status.Actions.Unreblog" = "Undo reblog"; +"Common.Controls.Status.ContentWarning" = "Varování o obsahu"; +"Common.Controls.Status.LinkViaUser" = "%@ via %@"; +"Common.Controls.Status.LoadEmbed" = "Load Embed"; +"Common.Controls.Status.MediaContentWarning" = "Klepnutím kdekoli zobrazíte"; +"Common.Controls.Status.MetaEntity.Email" = "E-mailová adresa: %@"; +"Common.Controls.Status.MetaEntity.Hashtag" = "Hashtag: %@"; +"Common.Controls.Status.MetaEntity.Mention" = "Zobrazit profil: %@"; +"Common.Controls.Status.MetaEntity.Url" = "Odkaz: %@"; +"Common.Controls.Status.Poll.Closed" = "Uzavřeno"; +"Common.Controls.Status.Poll.Vote" = "Hlasovat"; +"Common.Controls.Status.SensitiveContent" = "Citlivý obsah"; +"Common.Controls.Status.ShowPost" = "Zobrazit příspěvek"; +"Common.Controls.Status.ShowUserProfile" = "Zobrazit profil uživatele"; +"Common.Controls.Status.Tag.Email" = "E-mail"; +"Common.Controls.Status.Tag.Emoji" = "Emoji"; +"Common.Controls.Status.Tag.Hashtag" = "Hashtag"; +"Common.Controls.Status.Tag.Link" = "Odkaz"; +"Common.Controls.Status.Tag.Mention" = "Zmínka"; +"Common.Controls.Status.Tag.Url" = "URL"; +"Common.Controls.Status.TapToReveal" = "Klepnutím zobrazit"; +"Common.Controls.Status.Translation.ShowOriginal" = "Shown Original"; +"Common.Controls.Status.Translation.TranslatedFrom" = "Translated from %@ using %@"; +"Common.Controls.Status.Translation.UnknownLanguage" = "Unknown"; +"Common.Controls.Status.Translation.UnknownProvider" = "Unknown"; +"Common.Controls.Status.UserReblogged" = "%@ reblogged"; +"Common.Controls.Status.UserRepliedTo" = "Odpověděl %@"; +"Common.Controls.Status.Visibility.Direct" = "Pouze zmíněný uživatel může vidět tento příspěvek."; +"Common.Controls.Status.Visibility.Private" = "Pouze jejich sledující mohou vidět tento příspěvek."; +"Common.Controls.Status.Visibility.PrivateFromMe" = "Pouze moji sledující mohou vidět tento příspěvek."; +"Common.Controls.Status.Visibility.Unlisted" = "Každý může vidět tento příspěvek, ale nezobrazovat ve veřejné časové ose."; +"Common.Controls.Tabs.Home" = "Domů"; +"Common.Controls.Tabs.Notifications" = "Oznámení"; +"Common.Controls.Tabs.Profile" = "Profil"; +"Common.Controls.Tabs.SearchAndExplore" = "Search and Explore"; +"Common.Controls.Timeline.Filtered" = "Filtrováno"; +"Common.Controls.Timeline.Header.BlockedWarning" = "Nemůžeš zobrazit profil tohoto uživatele, dokud tě neodblokují."; +"Common.Controls.Timeline.Header.BlockingWarning" = "Nemůžete zobrazit profil tohoto uživatele, dokud ho neodblokujete. +Váš profil pro něj vypadá takto."; +"Common.Controls.Timeline.Header.NoStatusFound" = "Nebyl nalezen žádný příspěvek"; +"Common.Controls.Timeline.Header.SuspendedWarning" = "Tento uživatel byl pozastaven."; +"Common.Controls.Timeline.Header.UserBlockedWarning" = "Nemůžete zobrazit profil %@, dokud vás neodblokuje."; +"Common.Controls.Timeline.Header.UserBlockingWarning" = "Nemůžete zobrazit profil %@, dokud ho neodblokujete. +Váš profil pro něj vypadá takto."; +"Common.Controls.Timeline.Header.UserSuspendedWarning" = "Účet %@ byl pozastaven."; +"Common.Controls.Timeline.Loader.LoadMissingPosts" = "Načíst chybějící příspěvky"; +"Common.Controls.Timeline.Loader.LoadingMissingPosts" = "Načíst chybějící příspěvky..."; +"Common.Controls.Timeline.Loader.ShowMoreReplies" = "Zobrazit více odovědí"; +"Common.Controls.Timeline.Timestamp.Now" = "Nyní"; +"Scene.AccountList.AddAccount" = "Přidat účet"; +"Scene.AccountList.DismissAccountSwitcher" = "Zrušit přepínač účtů"; +"Scene.AccountList.TabBarHint" = "Aktuální vybraný profil: %@. Dvojitým poklepáním zobrazíte přepínač účtů"; +"Scene.Bookmark.Title" = "Záložky"; +"Scene.Compose.Accessibility.AppendAttachment" = "Přidat přílohu"; +"Scene.Compose.Accessibility.AppendPoll" = "Přidat anketu"; +"Scene.Compose.Accessibility.CustomEmojiPicker" = "Vlastní výběr Emoji"; +"Scene.Compose.Accessibility.DisableContentWarning" = "Vypnout upozornění na obsah"; +"Scene.Compose.Accessibility.EnableContentWarning" = "Povolit upozornění na obsah"; +"Scene.Compose.Accessibility.PostOptions" = "Možnosti příspěvku"; +"Scene.Compose.Accessibility.PostVisibilityMenu" = "Menu viditelnosti příspěvku"; +"Scene.Compose.Accessibility.PostingAs" = "Odesílání jako %@"; +"Scene.Compose.Accessibility.RemovePoll" = "Odstranit anketu"; +"Scene.Compose.Attachment.AttachmentBroken" = "Tento %@ je poškozený a nemůže být +nahrán do Mastodonu."; +"Scene.Compose.Attachment.AttachmentTooLarge" = "Příloha je příliš velká"; +"Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "Nelze rozpoznat toto medium přílohy"; +"Scene.Compose.Attachment.CompressingState" = "Probíhá komprese..."; +"Scene.Compose.Attachment.DescriptionPhoto" = "Popište fotografii pro zrakově postižené osoby..."; +"Scene.Compose.Attachment.DescriptionVideo" = "Popište video pro zrakově postižené..."; +"Scene.Compose.Attachment.LoadFailed" = "Načtení se nezdařilo"; +"Scene.Compose.Attachment.Photo" = "fotka"; +"Scene.Compose.Attachment.ServerProcessingState" = "Zpracování serveru..."; +"Scene.Compose.Attachment.UploadFailed" = "Nahrání selhalo"; +"Scene.Compose.Attachment.Video" = "video"; +"Scene.Compose.AutoComplete.SpaceToAdd" = "Mezera k přidání"; +"Scene.Compose.ComposeAction" = "Zveřejnit"; +"Scene.Compose.ContentInputPlaceholder" = "Napište nebo vložte, co je na mysli"; +"Scene.Compose.ContentWarning.Placeholder" = "Zde napište přesné varování..."; +"Scene.Compose.Keyboard.AppendAttachmentEntry" = "Přidat přílohu - %@"; +"Scene.Compose.Keyboard.DiscardPost" = "Zahodit příspěvek"; +"Scene.Compose.Keyboard.PublishPost" = "Publikovat příspěvek"; +"Scene.Compose.Keyboard.SelectVisibilityEntry" = "Vyberte viditelnost - %@"; +"Scene.Compose.Keyboard.ToggleContentWarning" = "Přepnout varování obsahu"; +"Scene.Compose.Keyboard.TogglePoll" = "Přepnout anketu"; +"Scene.Compose.MediaSelection.Browse" = "Procházet"; +"Scene.Compose.MediaSelection.Camera" = "Vyfotit"; +"Scene.Compose.MediaSelection.PhotoLibrary" = "Knihovna fotografií"; +"Scene.Compose.Poll.DurationTime" = "Doba trvání: %@"; +"Scene.Compose.Poll.OneDay" = "1 den"; +"Scene.Compose.Poll.OneHour" = "1 hodina"; +"Scene.Compose.Poll.OptionNumber" = "Možnost %ld"; +"Scene.Compose.Poll.SevenDays" = "7 dní"; +"Scene.Compose.Poll.SixHours" = "6 hodin"; +"Scene.Compose.Poll.ThePollHasEmptyOption" = "Anketa má prázdnou možnost"; +"Scene.Compose.Poll.ThePollIsInvalid" = "Anketa je neplatná"; +"Scene.Compose.Poll.ThirtyMinutes" = "30 minut"; +"Scene.Compose.Poll.ThreeDays" = "3 dny"; +"Scene.Compose.ReplyingToUser" = "odpovídá na %@"; +"Scene.Compose.Title.NewPost" = "Nový příspěvek"; +"Scene.Compose.Title.NewReply" = "Nová odpověď"; +"Scene.Compose.Visibility.Direct" = "Pouze lidé, které zmíním"; +"Scene.Compose.Visibility.Private" = "Pouze sledující"; +"Scene.Compose.Visibility.Public" = "Veřejný"; +"Scene.Compose.Visibility.Unlisted" = "Neuvedeno"; +"Scene.ConfirmEmail.Button.OpenEmailApp" = "Otevřít e-mailovou aplikaci"; +"Scene.ConfirmEmail.Button.Resend" = "Poslat znovu"; +"Scene.ConfirmEmail.DontReceiveEmail.Description" = "Zkontrolujte, zda je vaše e-mailová adresa správná, stejně jako složka nevyžádané pošty, pokud ji máte."; +"Scene.ConfirmEmail.DontReceiveEmail.ResendEmail" = "Znovu odeslat e-mail"; +"Scene.ConfirmEmail.DontReceiveEmail.Title" = "Zkontrolujte svůj e-mail"; +"Scene.ConfirmEmail.OpenEmailApp.Description" = "Právě jsme vám poslali e-mail. Zkontrolujte složku nevyžádané zprávy, pokud ji máte."; +"Scene.ConfirmEmail.OpenEmailApp.Mail" = "Pošta"; +"Scene.ConfirmEmail.OpenEmailApp.OpenEmailClient" = "Otevřít e-mailového klienta"; +"Scene.ConfirmEmail.OpenEmailApp.Title" = "Zkontrolujte doručenou poštu."; +"Scene.ConfirmEmail.Subtitle" = "Klepněte na odkaz, který jsme vám poslali e-mailem, abyste ověřili Váš účet."; +"Scene.ConfirmEmail.TapTheLinkWeEmailedToYouToVerifyYourAccount" = "Klepněte na odkaz, který jsme vám poslali e-mailem, abyste ověřili Váš účet"; +"Scene.ConfirmEmail.Title" = "Ještě jedna věc."; +"Scene.Discovery.Intro" = "Toto jsou příspěvky, které získávají pozornost ve vašem koutu Mastodonu."; +"Scene.Discovery.Tabs.Community" = "Komunita"; +"Scene.Discovery.Tabs.ForYou" = "Pro vás"; +"Scene.Discovery.Tabs.Hashtags" = "Hashtagy"; +"Scene.Discovery.Tabs.News" = "Zprávy"; +"Scene.Discovery.Tabs.Posts" = "Příspěvky"; +"Scene.Familiarfollowers.FollowedByNames" = "Sledován od %@"; +"Scene.Familiarfollowers.Title" = "Sledující, které znáte"; +"Scene.Favorite.Title" = "Vaše oblíbené"; +"Scene.FavoritedBy.Title" = "Oblíben"; +"Scene.FollowedTags.Actions.Follow" = "Sledovat"; +"Scene.FollowedTags.Actions.Unfollow" = "Přestat sledovat"; +"Scene.FollowedTags.Header.Participants" = "účastníci"; +"Scene.FollowedTags.Header.Posts" = "příspěvky"; +"Scene.FollowedTags.Header.PostsToday" = "příspěvky dnes"; +"Scene.FollowedTags.Title" = "Sledované štítky"; +"Scene.Follower.Footer" = "Sledující z jiných serverů nejsou zobrazeni."; +"Scene.Follower.Title" = "sledující"; +"Scene.Following.Footer" = "Sledování z jiných serverů není zobrazeno."; +"Scene.Following.Title" = "sledování"; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoHint" = "Klepnutím přejdete nahoru a znovu klepněte na předchozí místo"; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoLabel" = "Tlačítko s logem"; +"Scene.HomeTimeline.NavigationBarState.NewPosts" = "Nové příspěvky"; +"Scene.HomeTimeline.NavigationBarState.Offline" = "Offline"; +"Scene.HomeTimeline.NavigationBarState.Published" = "Publikováno!"; +"Scene.HomeTimeline.NavigationBarState.Publishing" = "Publikování příspěvku..."; +"Scene.HomeTimeline.Title" = "Domů"; +"Scene.Login.ServerSearchField.Placeholder" = "Zadejte URL nebo vyhledávejte váš server"; +"Scene.Login.Subtitle" = "Přihlaste se na serveru, na kterém jste si vytvořili účet."; +"Scene.Login.Title" = "Vítejte zpět"; +"Scene.Notification.FollowRequest.Accept" = "Přijmout"; +"Scene.Notification.FollowRequest.Accepted" = "Přijato"; +"Scene.Notification.FollowRequest.Reject" = "odmítnout"; +"Scene.Notification.FollowRequest.Rejected" = "Zamítnuto"; +"Scene.Notification.Keyobard.ShowEverything" = "Zobrazit vše"; +"Scene.Notification.Keyobard.ShowMentions" = "Zobrazit zmínky"; +"Scene.Notification.NotificationDescription.FavoritedYourPost" = "si oblíbil váš příspěvek"; +"Scene.Notification.NotificationDescription.FollowedYou" = "vás sleduje"; +"Scene.Notification.NotificationDescription.MentionedYou" = "vás zmínil/a"; +"Scene.Notification.NotificationDescription.PollHasEnded" = "anketa skončila"; +"Scene.Notification.NotificationDescription.RebloggedYourPost" = "boostnul váš příspěvek"; +"Scene.Notification.NotificationDescription.RequestToFollowYou" = "požádat vás o sledování"; +"Scene.Notification.Title.Everything" = "Všechno"; +"Scene.Notification.Title.Mentions" = "Zmínky"; +"Scene.Preview.Keyboard.ClosePreview" = "Zavřít náhled"; +"Scene.Preview.Keyboard.ShowNext" = "Zobrazit další"; +"Scene.Preview.Keyboard.ShowPrevious" = "Zobrazit předchozí"; +"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "Dvojitým poklepáním otevřete seznam"; +"Scene.Profile.Accessibility.EditAvatarImage" = "Upravit obrázek avataru"; +"Scene.Profile.Accessibility.ShowAvatarImage" = "Zobrazit obrázek avataru"; +"Scene.Profile.Accessibility.ShowBannerImage" = "Zobrazit obrázek banneru"; +"Scene.Profile.Dashboard.Followers" = "sledující"; +"Scene.Profile.Dashboard.Following" = "sledování"; +"Scene.Profile.Dashboard.Posts" = "příspěvky"; +"Scene.Profile.Fields.AddRow" = "Přidat řádek"; +"Scene.Profile.Fields.Joined" = "Joined"; +"Scene.Profile.Fields.Placeholder.Content" = "Obsah"; +"Scene.Profile.Fields.Placeholder.Label" = "Označení"; +"Scene.Profile.Fields.Verified.Long" = "Vlastnictví tohoto odkazu bylo zkontrolováno na %@"; +"Scene.Profile.Fields.Verified.Short" = "Ověřeno na %@"; +"Scene.Profile.Header.FollowsYou" = "Sleduje vás"; +"Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Message" = "Potvrdit blokování %@"; +"Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title" = "Blokovat účet"; +"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Message" = "Confirm to hide reblogs"; +"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Title" = "Hide Reblogs"; +"Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Message" = "Potvrdit skrytí %@"; +"Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Title" = "Skrýt účet"; +"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Message" = "Confirm to show reblogs"; +"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Title" = "Show Reblogs"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Message" = "Potvrďte odblokování %@"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Title" = "Odblokovat účet"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.Message" = "Potvrďte zrušení ztlumení %@"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.Title" = "Zrušit skrytí účtu"; +"Scene.Profile.SegmentedControl.About" = "O uživateli"; +"Scene.Profile.SegmentedControl.Media" = "Média"; +"Scene.Profile.SegmentedControl.Posts" = "Příspěvky"; +"Scene.Profile.SegmentedControl.PostsAndReplies" = "Příspěvky a odpovědi"; +"Scene.Profile.SegmentedControl.Replies" = "Odpovědí"; +"Scene.RebloggedBy.Title" = "Reblogged By"; +"Scene.Register.Error.Item.Agreement" = "Souhlas"; +"Scene.Register.Error.Item.Email" = "E-mail"; +"Scene.Register.Error.Item.Locale" = "Jazyk"; +"Scene.Register.Error.Item.Password" = "Heslo"; +"Scene.Register.Error.Item.Reason" = "Důvod"; +"Scene.Register.Error.Item.Username" = "Uživatelské jméno"; +"Scene.Register.Error.Reason.Accepted" = "%@ musí být přijato"; +"Scene.Register.Error.Reason.Blank" = "%@ je vyžadováno"; +"Scene.Register.Error.Reason.Blocked" = "%@ používá zakázanou e-mailovou službu"; +"Scene.Register.Error.Reason.Inclusion" = "%@ není podporovaná hodnota"; +"Scene.Register.Error.Reason.Invalid" = "%@ je neplatné"; +"Scene.Register.Error.Reason.Reserved" = "%@ je rezervované klíčové slovo"; +"Scene.Register.Error.Reason.Taken" = "%@ se již používá"; +"Scene.Register.Error.Reason.TooLong" = "%@ je příliš dlouhé"; +"Scene.Register.Error.Reason.TooShort" = "%@ je příliš krátké"; +"Scene.Register.Error.Reason.Unreachable" = "%@ pravděpodobně neexistuje"; +"Scene.Register.Error.Special.EmailInvalid" = "Toto není platná e-mailová adresa"; +"Scene.Register.Error.Special.PasswordTooShort" = "Heslo je příliš krátké (musí mít alespoň 8 znaků)"; +"Scene.Register.Error.Special.UsernameInvalid" = "Uživatelské jméno musí obsahovat pouze alfanumerické znaky a podtržítka"; +"Scene.Register.Error.Special.UsernameTooLong" = "Uživatelské jméno je příliš dlouhé (nemůže být delší než 30 znaků)"; +"Scene.Register.Input.Avatar.Delete" = "Smazat"; +"Scene.Register.Input.DisplayName.Placeholder" = "zobrazované jméno"; +"Scene.Register.Input.Email.Placeholder" = "e-mail"; +"Scene.Register.Input.Invite.RegistrationUserInviteRequest" = "Proč se chcete připojit?"; +"Scene.Register.Input.Password.Accessibility.Checked" = "zaškrtnuto"; +"Scene.Register.Input.Password.Accessibility.Unchecked" = "nezaškrtnuto"; +"Scene.Register.Input.Password.CharacterLimit" = "8 znaků"; +"Scene.Register.Input.Password.Hint" = "Vaše heslo musí obsahovat alespoň 8 znaků"; +"Scene.Register.Input.Password.Placeholder" = "heslo"; +"Scene.Register.Input.Password.Require" = "Heslo musí být alespoň:"; +"Scene.Register.Input.Username.DuplicatePrompt" = "Toto uživatelské jméno je použito."; +"Scene.Register.Input.Username.Placeholder" = "uživatelské jméno"; +"Scene.Register.LetsGetYouSetUpOnDomain" = "Pojďme si nastavit %@"; +"Scene.Register.Title" = "Pojďme si nastavit %@"; +"Scene.Report.Content1" = "Existují nějaké další příspěvky, které byste chtěli přidat do zprávy?"; +"Scene.Report.Content2" = "Je o tomto hlášení něco, co by měli vědět moderátoři?"; +"Scene.Report.ReportSentTitle" = "Děkujeme za nahlášení, podíváme se na to."; +"Scene.Report.Reported" = "NAHLÁŠEN"; +"Scene.Report.Send" = "Odeslat hlášení"; +"Scene.Report.SkipToSend" = "Odeslat bez komentáře"; +"Scene.Report.Step1" = "Krok 1 ze 2"; +"Scene.Report.Step2" = "Krok 2 ze 2"; +"Scene.Report.StepFinal.BlockUser" = "Blokovat %@"; +"Scene.Report.StepFinal.DontWantToSeeThis" = "Nechcete to vidět?"; +"Scene.Report.StepFinal.MuteUser" = "Skrýt %@"; +"Scene.Report.StepFinal.TheyWillNoLongerBeAbleToFollowOrSeeYourPostsButTheyCanSeeIfTheyveBeenBlocked" = "Už nebudou moci sledovat nebo vidět vaše příspěvky, ale mohou vidět, zda byly zablokovány."; +"Scene.Report.StepFinal.Unfollow" = "Přestat sledovat"; +"Scene.Report.StepFinal.UnfollowUser" = "Přestat sledovat %@"; +"Scene.Report.StepFinal.Unfollowed" = "Už nesledujete"; +"Scene.Report.StepFinal.WhenYouSeeSomethingYouDontLikeOnMastodonYouCanRemoveThePersonFromYourExperience." = "Když uvidíte něco, co se vám nelíbí na Mastodonu, můžete odstranit tuto osobu ze svého zážitku."; +"Scene.Report.StepFinal.WhileWeReviewThisYouCanTakeActionAgainstUser" = "Zatímco to posuzujeme, můžete podniknout kroky proti %@"; +"Scene.Report.StepFinal.YouWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted" = "Neuvidíte jejich příspěvky nebo boostnutí v domovském kanálu. Nebudou vědět, že jsou skrytí."; +"Scene.Report.StepFour.IsThereAnythingElseWeShouldKnow" = "Je ještě něco jiného, co bychom měli vědět?"; +"Scene.Report.StepFour.Step4Of4" = "Krok 4 ze 4"; +"Scene.Report.StepOne.IDontLikeIt" = "Nelíbí se mi"; +"Scene.Report.StepOne.ItIsNotSomethingYouWantToSee" = "Není to něco, co chcete vidět"; +"Scene.Report.StepOne.ItViolatesServerRules" = "Porušuje pravidla serveru"; +"Scene.Report.StepOne.ItsSomethingElse" = "Jde o něco jiného"; +"Scene.Report.StepOne.ItsSpam" = "Je to spam"; +"Scene.Report.StepOne.MaliciousLinksFakeEngagementOrRepetetiveReplies" = "Škodlivé odkazy, falešné zapojení nebo opakující se odpovědi"; +"Scene.Report.StepOne.SelectTheBestMatch" = "Vyberte nejbližší možnost"; +"Scene.Report.StepOne.Step1Of4" = "Krok 1 ze 4"; +"Scene.Report.StepOne.TheIssueDoesNotFitIntoOtherCategories" = "Problém neodpovídá ostatním kategoriím"; +"Scene.Report.StepOne.WhatsWrongWithThisAccount" = "Co je špatně s tímto účtem?"; +"Scene.Report.StepOne.WhatsWrongWithThisPost" = "Co je na tomto příspěvku špatně?"; +"Scene.Report.StepOne.WhatsWrongWithThisUsername" = "Co je špatně na %@?"; +"Scene.Report.StepOne.YouAreAwareThatItBreaksSpecificRules" = "Máte za to, že porušuje konkrétní pravidla"; +"Scene.Report.StepThree.AreThereAnyPostsThatBackUpThisReport" = "Existují příspěvky dokládající toto hlášení?"; +"Scene.Report.StepThree.SelectAllThatApply" = "Vyberte všechna relevantní"; +"Scene.Report.StepThree.Step3Of4" = "Krok 3 ze 4"; +"Scene.Report.StepTwo.IJustDon’tLikeIt" = "Jen se mi to nelíbí"; +"Scene.Report.StepTwo.SelectAllThatApply" = "Vyberte všechna relevantní"; +"Scene.Report.StepTwo.Step2Of4" = "Krok 2 ze 4"; +"Scene.Report.StepTwo.WhichRulesAreBeingViolated" = "Jaká pravidla jsou porušována?"; +"Scene.Report.TextPlaceholder" = "Napište nebo vložte další komentáře"; +"Scene.Report.Title" = "Nahlásit %@"; +"Scene.Report.TitleReport" = "Nahlásit"; +"Scene.Search.Recommend.Accounts.Description" = "Možná budete chtít sledovat tyto účty"; +"Scene.Search.Recommend.Accounts.Follow" = "Sledovat"; +"Scene.Search.Recommend.Accounts.Title" = "Účty, které by se vám mohly líbit"; +"Scene.Search.Recommend.ButtonText" = "Zobrazit vše"; +"Scene.Search.Recommend.HashTag.Description" = "Hashtagy, kterým se dostává dosti pozornosti"; +"Scene.Search.Recommend.HashTag.PeopleTalking" = "%@ lidí mluví"; +"Scene.Search.Recommend.HashTag.Title" = "Populární na Mastodonu"; +"Scene.Search.SearchBar.Cancel" = "Zrušit"; +"Scene.Search.SearchBar.Placeholder" = "Hledat hashtagy a uživatele"; +"Scene.Search.Searching.Clear" = "Vymazat"; +"Scene.Search.Searching.EmptyState.NoResults" = "Žádné výsledky"; +"Scene.Search.Searching.RecentSearch" = "Nedávná hledání"; +"Scene.Search.Searching.Segment.All" = "Vše"; +"Scene.Search.Searching.Segment.Hashtags" = "Hashtagy"; +"Scene.Search.Searching.Segment.People" = "Lidé"; +"Scene.Search.Searching.Segment.Posts" = "Příspěvky"; +"Scene.Search.Title" = "Hledat"; +"Scene.ServerPicker.Button.Category.Academia" = "akademická sféra"; +"Scene.ServerPicker.Button.Category.Activism" = "aktivismus"; +"Scene.ServerPicker.Button.Category.All" = "Vše"; +"Scene.ServerPicker.Button.Category.AllAccessiblityDescription" = "Kategorie: Vše"; +"Scene.ServerPicker.Button.Category.Art" = "umění"; +"Scene.ServerPicker.Button.Category.Food" = "jídlo"; +"Scene.ServerPicker.Button.Category.Furry" = "furry"; +"Scene.ServerPicker.Button.Category.Games" = "hry"; +"Scene.ServerPicker.Button.Category.General" = "obecné"; +"Scene.ServerPicker.Button.Category.Journalism" = "žurnalistika"; +"Scene.ServerPicker.Button.Category.Lgbt" = "lgbt"; +"Scene.ServerPicker.Button.Category.Music" = "hudba"; +"Scene.ServerPicker.Button.Category.Regional" = "regionální"; +"Scene.ServerPicker.Button.Category.Tech" = "technologie"; +"Scene.ServerPicker.Button.SeeLess" = "Zobrazit méně"; +"Scene.ServerPicker.Button.SeeMore" = "Zobrazit více"; +"Scene.ServerPicker.EmptyState.BadNetwork" = "Při načítání dat nastala chyba. Zkontrolujte připojení k internetu."; +"Scene.ServerPicker.EmptyState.FindingServers" = "Hledání dostupných serverů..."; +"Scene.ServerPicker.EmptyState.NoResults" = "Žádné výsledky"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Hledejte komunity nebo zadejte URL"; +"Scene.ServerPicker.Label.Category" = "KATEGORIE"; +"Scene.ServerPicker.Label.Language" = "JAZYK"; +"Scene.ServerPicker.Label.Users" = "UŽIVATELÉ"; +"Scene.ServerPicker.Subtitle" = "Vyberte server založený ve vašem regionu, podle zájmů nebo podle obecného účelu. Stále můžete chatovat s kýmkoli na Mastodonu bez ohledu na vaše servery."; +"Scene.ServerPicker.Title" = "Mastodon tvoří uživatelé z různých serverů."; +"Scene.ServerRules.Button.Confirm" = "Souhlasím"; +"Scene.ServerRules.PrivacyPolicy" = "zásady ochrany osobních údajů"; +"Scene.ServerRules.Prompt" = "Pokračováním budete podléhat podmínkám služby a zásad ochrany osobních údajů pro uživatele %@."; +"Scene.ServerRules.Subtitle" = "Ty nastavují a prosazují moderátoři %@."; +"Scene.ServerRules.TermsOfService" = "podmínky služby"; +"Scene.ServerRules.Title" = "Některá základní pravidla."; +"Scene.Settings.Footer.MastodonDescription" = "Mastodon je open source software. Na GitHub můžete nahlásit problémy na %@ (%@)"; +"Scene.Settings.Keyboard.CloseSettingsWindow" = "Zavřít okno nastavení"; +"Scene.Settings.Section.Appearance.Automatic" = "Automaticky"; +"Scene.Settings.Section.Appearance.Dark" = "Vždy tmavý"; +"Scene.Settings.Section.Appearance.Light" = "Vždy světlý"; +"Scene.Settings.Section.Appearance.Title" = "Vzhled"; +"Scene.Settings.Section.BoringZone.AccountSettings" = "Nastavení účtu"; +"Scene.Settings.Section.BoringZone.Privacy" = "Zásady ochrany osobních údajů"; +"Scene.Settings.Section.BoringZone.Terms" = "Podmínky služby"; +"Scene.Settings.Section.BoringZone.Title" = "Nudná část"; +"Scene.Settings.Section.LookAndFeel.Light" = "Světlý"; +"Scene.Settings.Section.LookAndFeel.ReallyDark" = "Skutečně tmavý"; +"Scene.Settings.Section.LookAndFeel.SortaDark" = "Sorta Dark"; +"Scene.Settings.Section.LookAndFeel.Title" = "Vzhled a chování"; +"Scene.Settings.Section.LookAndFeel.UseSystem" = "Použít systém"; +"Scene.Settings.Section.Notifications.Boosts" = "Boostnul můj příspěvek"; +"Scene.Settings.Section.Notifications.Favorites" = "Oblíbil si můj příspěvek"; +"Scene.Settings.Section.Notifications.Follows" = "Sleduje mě"; +"Scene.Settings.Section.Notifications.Mentions" = "Zmiňuje mě"; +"Scene.Settings.Section.Notifications.Title" = "Upozornění"; +"Scene.Settings.Section.Notifications.Trigger.Anyone" = "kdokoliv"; +"Scene.Settings.Section.Notifications.Trigger.Follow" = "kdokoli, koho sleduji"; +"Scene.Settings.Section.Notifications.Trigger.Follower" = "sledující"; +"Scene.Settings.Section.Notifications.Trigger.Noone" = "nikdo"; +"Scene.Settings.Section.Notifications.Trigger.Title" = "Upozornit, když"; +"Scene.Settings.Section.Preference.DisableAvatarAnimation" = "Zakázat animované avatary"; +"Scene.Settings.Section.Preference.DisableEmojiAnimation" = "Zakázat animované emoji"; +"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "Otevřít odkazy v Mastodonu"; +"Scene.Settings.Section.Preference.Title" = "Předvolby"; +"Scene.Settings.Section.Preference.TrueBlackDarkMode" = "Skutečný černý tmavý režim"; +"Scene.Settings.Section.Preference.UsingDefaultBrowser" = "Použít výchozí prohlížeč pro otevírání odkazů"; +"Scene.Settings.Section.SpicyZone.Clear" = "Vymazat mezipaměť médií"; +"Scene.Settings.Section.SpicyZone.Signout" = "Odhlásit se"; +"Scene.Settings.Section.SpicyZone.Title" = "Ostrá část"; +"Scene.Settings.Title" = "Nastavení"; +"Scene.SuggestionAccount.FollowExplain" = "Když někoho sledujete, uvidíte jejich příspěvky ve vašem domovském kanálu."; +"Scene.SuggestionAccount.Title" = "Najít lidi pro sledování"; +"Scene.Thread.BackTitle" = "Příspěvek"; +"Scene.Thread.Title" = "Příspěvek od %@"; +"Scene.Welcome.GetStarted" = "Začínáme"; +"Scene.Welcome.LogIn" = "Přihlásit se"; +"Scene.Welcome.Slogan" = "Sociální sítě opět ve vašich rukou."; +"Scene.Wizard.AccessibilityHint" = "Dvojitým poklepáním tohoto průvodce odmítnete"; +"Scene.Wizard.MultipleAccountSwitchIntroDescription" = "Přepínání mezi více účty podržením tlačítka profilu."; +"Scene.Wizard.NewInMastodon" = "Nový v Mastodonu"; \ No newline at end of file diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/cs.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/cs.lproj/Localizable.stringsdict new file mode 100644 index 000000000..6e44e9f0a --- /dev/null +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/cs.lproj/Localizable.stringsdict @@ -0,0 +1,581 @@ + + + + + a11y.plural.count.unread.notification + + NSStringLocalizedFormatKey + %#@notification_count_unread_notification@ + notification_count_unread_notification + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 nepřečtené oznámení + few + %ld nepřečtené oznámení + many + %ld nepřečtených oznámení + other + %ld nepřečtených oznámení + + + a11y.plural.count.input_limit_exceeds + + NSStringLocalizedFormatKey + Vstupní limit přesahuje %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 znak + few + %ld znaky + many + %ld znaků + other + %ld znaků + + + a11y.plural.count.input_limit_remains + + NSStringLocalizedFormatKey + Vstupní limit zůstává %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 znak + few + %ld znaky + many + %ld znaků + other + %ld znaků + + + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ left + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 znak + few + %ld znaky + many + %ld znaků + other + %ld znaků + + + plural.count.followed_by_and_mutual + + NSStringLocalizedFormatKey + %#@names@%#@count_mutual@ + names + + one + + few + + many + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + + + count_mutual + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + Followed by %1$@, and another mutual + few + Followed by %1$@, and %ld mutuals + many + Followed by %1$@, and %ld mutuals + other + Followed by %1$@, and %ld mutuals + + + plural.count.metric_formatted.post + + NSStringLocalizedFormatKey + %@ %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + příspěvek + few + příspěvky + many + příspěvků + other + příspěvků + + + plural.count.media + + NSStringLocalizedFormatKey + %#@media_count@ + media_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 médium + few + %ld média + many + %ld médií + other + %ld médií + + + plural.count.post + + NSStringLocalizedFormatKey + %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 příspěvek + few + %ld příspěvky + many + %ld příspěvků + other + %ld příspěvků + + + plural.count.favorite + + NSStringLocalizedFormatKey + %#@favorite_count@ + favorite_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 oblíbený + few + %ld favorites + many + %ld favorites + other + %ld favorites + + + plural.count.reblog + + NSStringLocalizedFormatKey + %#@reblog_count@ + reblog_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 reblog + few + %ld reblogs + many + %ld reblogs + other + %ld reblogs + + + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 odpověď + few + %ld odpovědi + many + %ld odpovědí + other + %ld odpovědí + + + plural.count.vote + + NSStringLocalizedFormatKey + %#@vote_count@ + vote_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 hlas + few + %ld hlasy + many + %ld hlasů + other + %ld hlasů + + + plural.count.voter + + NSStringLocalizedFormatKey + %#@voter_count@ + voter_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 hlasující + few + %ld hlasující + many + %ld hlasujících + other + %ld hlasujících + + + plural.people_talking + + NSStringLocalizedFormatKey + %#@count_people_talking@ + count_people_talking + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 people talking + few + %ld people talking + many + %ld people talking + other + %ld people talking + + + plural.count.following + + NSStringLocalizedFormatKey + %#@count_following@ + count_following + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 sledující + few + %ld sledující + many + %ld sledujících + other + %ld sledujících + + + plural.count.follower + + NSStringLocalizedFormatKey + %#@count_follower@ + count_follower + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 follower + few + %ld followers + many + %ld followers + other + %ld followers + + + date.year.left + + NSStringLocalizedFormatKey + %#@count_year_left@ + count_year_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + Zbývá 1 rok + few + Zbývají %ld roky + many + Zbývá %ld roků + other + Zbývá %ld roků + + + date.month.left + + NSStringLocalizedFormatKey + %#@count_month_left@ + count_month_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + Zbývá 1 měsíc + few + %ld months left + many + %ld months left + other + %ld months left + + + date.day.left + + NSStringLocalizedFormatKey + %#@count_day_left@ + count_day_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 day left + few + %ld days left + many + %ld days left + other + %ld days left + + + date.hour.left + + NSStringLocalizedFormatKey + %#@count_hour_left@ + count_hour_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 hour left + few + %ld hours left + many + %ld hours left + other + %ld hours left + + + date.minute.left + + NSStringLocalizedFormatKey + %#@count_minute_left@ + count_minute_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 minute left + few + %ld minutes left + many + %ld minutes left + other + %ld minutes left + + + date.second.left + + NSStringLocalizedFormatKey + %#@count_second_left@ + count_second_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 second left + few + %ld seconds left + many + %ld seconds left + other + %ld seconds left + + + date.year.ago.abbr + + NSStringLocalizedFormatKey + %#@count_year_ago_abbr@ + count_year_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1y ago + few + %ldy ago + many + %ldy ago + other + %ldy ago + + + date.month.ago.abbr + + NSStringLocalizedFormatKey + %#@count_month_ago_abbr@ + count_month_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1M ago + few + %ldM ago + many + %ldM ago + other + %ldM ago + + + date.day.ago.abbr + + NSStringLocalizedFormatKey + %#@count_day_ago_abbr@ + count_day_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1d ago + few + %ldd ago + many + %ldd ago + other + %ldd ago + + + date.hour.ago.abbr + + NSStringLocalizedFormatKey + %#@count_hour_ago_abbr@ + count_hour_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1h ago + few + %ldh ago + many + %ldh ago + other + %ldh ago + + + date.minute.ago.abbr + + NSStringLocalizedFormatKey + %#@count_minute_ago_abbr@ + count_minute_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1m ago + few + %ldm ago + many + %ldm ago + other + %ldm ago + + + date.second.ago.abbr + + NSStringLocalizedFormatKey + %#@count_second_ago_abbr@ + count_second_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1s ago + few + %lds ago + many + %lds ago + other + %lds ago + + + + diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/de.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/de.lproj/Localizable.strings index 4f9a15906..74dbaeea6 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/de.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/de.lproj/Localizable.strings @@ -5,7 +5,7 @@ "Common.Alerts.Common.PleaseTryAgain" = "Bitte versuche es erneut."; "Common.Alerts.Common.PleaseTryAgainLater" = "Bitte versuche es später nochmal."; "Common.Alerts.DeletePost.Message" = "Bist du dir sicher, dass du diesen Beitrag löschen willst?"; -"Common.Alerts.DeletePost.Title" = "Bist du dir sicher, dass du diesen Beitrag löschen möchtest?"; +"Common.Alerts.DeletePost.Title" = "Beiträge löschen"; "Common.Alerts.DiscardPostContent.Message" = "Bestätige, um den Beitrag zu verwerfen."; "Common.Alerts.DiscardPostContent.Title" = "Entwurf verwerfen"; "Common.Alerts.EditProfileFailure.Message" = "Profil kann nicht bearbeitet werden. Bitte versuche es erneut."; @@ -22,6 +22,9 @@ Bitte überprüfe deine Internetverbindung."; "Common.Alerts.SignOut.Message" = "Bist du sicher, dass du dich abmelden möchten?"; "Common.Alerts.SignOut.Title" = "Abmelden"; "Common.Alerts.SignUpFailure.Title" = "Registrierungsfehler"; +"Common.Alerts.TranslationFailed.Button" = "OK"; +"Common.Alerts.TranslationFailed.Message" = "Übersetzung fehlgeschlagen. Möglicherweise hat der/die Administrator*in die Übersetzungen auf diesem Server nicht aktiviert oder dieser Server läuft mit einer älteren Version von Mastodon, in der Übersetzungen noch nicht unterstützt wurden."; +"Common.Alerts.TranslationFailed.Title" = "Hinweis"; "Common.Alerts.VoteFailure.PollEnded" = "Die Umfrage ist beendet"; "Common.Alerts.VoteFailure.Title" = "Fehler bei Abstimmung"; "Common.Controls.Actions.Add" = "Hinzufügen"; @@ -30,13 +33,14 @@ Bitte überprüfe deine Internetverbindung."; "Common.Controls.Actions.Cancel" = "Abbrechen"; "Common.Controls.Actions.Compose" = "Neue Nachricht"; "Common.Controls.Actions.Confirm" = "Bestätigen"; -"Common.Controls.Actions.Continue" = "Fortfahren"; +"Common.Controls.Actions.Continue" = "Weiter"; +"Common.Controls.Actions.Copy" = "Kopieren"; "Common.Controls.Actions.CopyPhoto" = "Foto kopieren"; "Common.Controls.Actions.Delete" = "Löschen"; "Common.Controls.Actions.Discard" = "Verwerfen"; "Common.Controls.Actions.Done" = "Fertig"; "Common.Controls.Actions.Edit" = "Bearbeiten"; -"Common.Controls.Actions.FindPeople" = "Finde Personen zum Folgen"; +"Common.Controls.Actions.FindPeople" = "Personen zum Folgen finden"; "Common.Controls.Actions.ManuallySearch" = "Stattdessen manuell suchen"; "Common.Controls.Actions.Next" = "Weiter"; "Common.Controls.Actions.Ok" = "OK"; @@ -56,9 +60,11 @@ Bitte überprüfe deine Internetverbindung."; "Common.Controls.Actions.SharePost" = "Beitrag teilen"; "Common.Controls.Actions.ShareUser" = "%@ teilen"; "Common.Controls.Actions.SignIn" = "Anmelden"; -"Common.Controls.Actions.SignUp" = "Registrieren"; +"Common.Controls.Actions.SignUp" = "Konto erstellen"; "Common.Controls.Actions.Skip" = "Überspringen"; "Common.Controls.Actions.TakePhoto" = "Foto aufnehmen"; +"Common.Controls.Actions.TranslatePost.Title" = "Von %@ übersetzen"; +"Common.Controls.Actions.TranslatePost.UnknownLanguage" = "Unbekannt"; "Common.Controls.Actions.TryAgain" = "Nochmals versuchen"; "Common.Controls.Actions.UnblockDomain" = "Blockierung von %@ aufheben"; "Common.Controls.Friendship.Block" = "Blockieren"; @@ -67,14 +73,14 @@ Bitte überprüfe deine Internetverbindung."; "Common.Controls.Friendship.Blocked" = "Blockiert"; "Common.Controls.Friendship.EditInfo" = "Information bearbeiten"; "Common.Controls.Friendship.Follow" = "Folgen"; -"Common.Controls.Friendship.Following" = "Folge Ich"; -"Common.Controls.Friendship.HideReblogs" = "Reblogs ausblenden"; +"Common.Controls.Friendship.Following" = "Gefolgt"; +"Common.Controls.Friendship.HideReblogs" = "Teilen ausblenden"; "Common.Controls.Friendship.Mute" = "Stummschalten"; "Common.Controls.Friendship.MuteUser" = "%@ stummschalten"; "Common.Controls.Friendship.Muted" = "Stummgeschaltet"; "Common.Controls.Friendship.Pending" = "In Warteschlange"; "Common.Controls.Friendship.Request" = "Anfragen"; -"Common.Controls.Friendship.ShowReblogs" = "Reblogs anzeigen"; +"Common.Controls.Friendship.ShowReblogs" = "Teilen anzeigen"; "Common.Controls.Friendship.Unblock" = "Blockierung aufheben"; "Common.Controls.Friendship.UnblockUser" = "Blockierung von %@ aufheben"; "Common.Controls.Friendship.Unmute" = "Nicht mehr stummschalten"; @@ -100,6 +106,7 @@ Bitte überprüfe deine Internetverbindung."; "Common.Controls.Status.Actions.Menu" = "Menü"; "Common.Controls.Status.Actions.Reblog" = "Teilen"; "Common.Controls.Status.Actions.Reply" = "Antworten"; +"Common.Controls.Status.Actions.ShareLinkInPost" = "Link im Beitrag teilen"; "Common.Controls.Status.Actions.ShowGif" = "GIF anzeigen"; "Common.Controls.Status.Actions.ShowImage" = "Bild anzeigen"; "Common.Controls.Status.Actions.ShowVideoPlayer" = "Zeige Video-Player"; @@ -107,10 +114,12 @@ Bitte überprüfe deine Internetverbindung."; "Common.Controls.Status.Actions.Unfavorite" = "Aus Favoriten entfernen"; "Common.Controls.Status.Actions.Unreblog" = "Nicht mehr teilen"; "Common.Controls.Status.ContentWarning" = "Inhaltswarnung"; +"Common.Controls.Status.LinkViaUser" = "%@ via %@"; +"Common.Controls.Status.LoadEmbed" = "Eingebettetes laden"; "Common.Controls.Status.MediaContentWarning" = "Tippe irgendwo zum Anzeigen"; -"Common.Controls.Status.MetaEntity.Email" = "Email address: %@"; +"Common.Controls.Status.MetaEntity.Email" = "E-Mail-Adresse: %@"; "Common.Controls.Status.MetaEntity.Hashtag" = "Hashtag: %@"; -"Common.Controls.Status.MetaEntity.Mention" = "Show Profile: %@"; +"Common.Controls.Status.MetaEntity.Mention" = "Profil anzeigen: %@"; "Common.Controls.Status.MetaEntity.Url" = "Link: %@"; "Common.Controls.Status.Poll.Closed" = "Beendet"; "Common.Controls.Status.Poll.Vote" = "Abstimmen"; @@ -124,16 +133,20 @@ Bitte überprüfe deine Internetverbindung."; "Common.Controls.Status.Tag.Mention" = "Erwähnung"; "Common.Controls.Status.Tag.Url" = "URL"; "Common.Controls.Status.TapToReveal" = "Zum Anzeigen tippen"; +"Common.Controls.Status.Translation.ShowOriginal" = "Original anzeigen"; +"Common.Controls.Status.Translation.TranslatedFrom" = "Translated from %@ using %@"; +"Common.Controls.Status.Translation.UnknownLanguage" = "Unbekannt"; +"Common.Controls.Status.Translation.UnknownProvider" = "Unknown"; "Common.Controls.Status.UserReblogged" = "%@ teilte"; "Common.Controls.Status.UserRepliedTo" = "Antwortet auf %@"; "Common.Controls.Status.Visibility.Direct" = "Nur erwähnte Benutzer können diesen Beitrag sehen."; -"Common.Controls.Status.Visibility.Private" = "Nur Follower des Authors können diesen Beitrag sehen."; -"Common.Controls.Status.Visibility.PrivateFromMe" = "Nur meine Follower können diesen Beitrag sehen."; +"Common.Controls.Status.Visibility.Private" = "Nur die, die dem Autor folgen, können diesen Beitrag sehen."; +"Common.Controls.Status.Visibility.PrivateFromMe" = "Nur die, die mir folgen, können diesen Beitrag sehen."; "Common.Controls.Status.Visibility.Unlisted" = "Jeder kann diesen Post sehen, aber nicht in der öffentlichen Timeline zeigen."; "Common.Controls.Tabs.Home" = "Startseite"; -"Common.Controls.Tabs.Notification" = "Benachrichtigungen"; +"Common.Controls.Tabs.Notifications" = "Mitteilungen"; "Common.Controls.Tabs.Profile" = "Profil"; -"Common.Controls.Tabs.Search" = "Suche"; +"Common.Controls.Tabs.SearchAndExplore" = "Suchen und Entdecken"; "Common.Controls.Timeline.Filtered" = "Gefiltert"; "Common.Controls.Timeline.Header.BlockedWarning" = "Das Profil dieses Benutzers kann nicht angezeigt werden, bis er dich entsperrt."; @@ -149,7 +162,7 @@ solange du diesen Benutzer nicht entsperrst. Dein Profil sieht für diesen Benutzer auch so aus."; "Common.Controls.Timeline.Header.UserSuspendedWarning" = "Das Konto von %@ wurde gesperrt."; "Common.Controls.Timeline.Loader.LoadMissingPosts" = "Fehlende Beiträge laden"; -"Common.Controls.Timeline.Loader.LoadingMissingPosts" = "Lade fehlende Beiträge..."; +"Common.Controls.Timeline.Loader.LoadingMissingPosts" = "Fehlende Beiträge werden geladen …"; "Common.Controls.Timeline.Loader.ShowMoreReplies" = "Weitere Antworten anzeigen"; "Common.Controls.Timeline.Timestamp.Now" = "Gerade"; "Scene.AccountList.AddAccount" = "Konto hinzufügen"; @@ -161,22 +174,26 @@ Dein Profil sieht für diesen Benutzer auch so aus."; "Scene.Compose.Accessibility.CustomEmojiPicker" = "Benutzerdefinierter Emojiwähler"; "Scene.Compose.Accessibility.DisableContentWarning" = "Inhaltswarnung ausschalten"; "Scene.Compose.Accessibility.EnableContentWarning" = "Inhaltswarnung einschalten"; +"Scene.Compose.Accessibility.PostOptions" = "Beitragsoptionen"; "Scene.Compose.Accessibility.PostVisibilityMenu" = "Sichtbarkeitsmenü"; +"Scene.Compose.Accessibility.PostingAs" = "Veröffentlichen als %@"; "Scene.Compose.Accessibility.RemovePoll" = "Umfrage entfernen"; "Scene.Compose.Attachment.AttachmentBroken" = "Dieses %@ scheint defekt zu sein und kann nicht auf Mastodon hochgeladen werden."; "Scene.Compose.Attachment.AttachmentTooLarge" = "Anhang zu groß"; "Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "Medienanhang wurde nicht erkannt"; -"Scene.Compose.Attachment.DescriptionPhoto" = "Für Menschen mit Sehbehinderung beschreiben..."; -"Scene.Compose.Attachment.DescriptionVideo" = "Für Menschen mit Sehbehinderung beschreiben..."; +"Scene.Compose.Attachment.CompressingState" = "wird komprimiert …"; +"Scene.Compose.Attachment.DescriptionPhoto" = "Für Menschen mit Sehbehinderung beschreiben …"; +"Scene.Compose.Attachment.DescriptionVideo" = "Für Menschen mit Sehbehinderung beschreiben …"; "Scene.Compose.Attachment.LoadFailed" = "Laden fehlgeschlagen"; "Scene.Compose.Attachment.Photo" = "Foto"; +"Scene.Compose.Attachment.ServerProcessingState" = "Serververarbeitung …"; "Scene.Compose.Attachment.UploadFailed" = "Upload fehlgeschlagen"; "Scene.Compose.Attachment.Video" = "Video"; "Scene.Compose.AutoComplete.SpaceToAdd" = "Leerzeichen um hinzuzufügen"; "Scene.Compose.ComposeAction" = "Veröffentlichen"; "Scene.Compose.ContentInputPlaceholder" = "Tippe oder füge ein, was dir am Herzen liegt"; -"Scene.Compose.ContentWarning.Placeholder" = "Schreibe eine Inhaltswarnung hier..."; +"Scene.Compose.ContentWarning.Placeholder" = "Hier eine Inhaltswarnung schreiben …"; "Scene.Compose.Keyboard.AppendAttachmentEntry" = "Anhang hinzufügen - %@"; "Scene.Compose.Keyboard.DiscardPost" = "Beitrag verwerfen"; "Scene.Compose.Keyboard.PublishPost" = "Beitrag veröffentlichen"; @@ -192,6 +209,8 @@ kann nicht auf Mastodon hochgeladen werden."; "Scene.Compose.Poll.OptionNumber" = "Auswahlmöglichkeit %ld"; "Scene.Compose.Poll.SevenDays" = "7 Tage"; "Scene.Compose.Poll.SixHours" = "6 Stunden"; +"Scene.Compose.Poll.ThePollHasEmptyOption" = "Die Umfrage hat eine leere Option"; +"Scene.Compose.Poll.ThePollIsInvalid" = "Die Umfrage ist ungültig"; "Scene.Compose.Poll.ThirtyMinutes" = "30 Minuten"; "Scene.Compose.Poll.ThreeDays" = "3 Tage"; "Scene.Compose.ReplyingToUser" = "antwortet auf %@"; @@ -207,7 +226,7 @@ kann nicht auf Mastodon hochgeladen werden."; "Scene.ConfirmEmail.DontReceiveEmail.ResendEmail" = "E-Mail erneut versenden"; "Scene.ConfirmEmail.DontReceiveEmail.Title" = "Bitte überprüfe deine E-Mails"; "Scene.ConfirmEmail.OpenEmailApp.Description" = "Wir haben dir gerade eine E-Mail geschickt. Überprüfe deinen Spam-Ordner, falls du es noch nicht getan hast."; -"Scene.ConfirmEmail.OpenEmailApp.Mail" = "Mail"; +"Scene.ConfirmEmail.OpenEmailApp.Mail" = "E-Mail"; "Scene.ConfirmEmail.OpenEmailApp.OpenEmailClient" = "E-Mail-Client öffnen"; "Scene.ConfirmEmail.OpenEmailApp.Title" = "Überprüfe deinen Posteingang."; "Scene.ConfirmEmail.Subtitle" = "Schaue kurz in dein E-Mail-Postfach und tippe den Link an, den wir dir gesendet haben."; @@ -220,20 +239,29 @@ kann nicht auf Mastodon hochgeladen werden."; "Scene.Discovery.Tabs.News" = "Nachrichten"; "Scene.Discovery.Tabs.Posts" = "Beiträge"; "Scene.Familiarfollowers.FollowedByNames" = "Gefolgt von %@"; -"Scene.Familiarfollowers.Title" = "Follower, die dir bekannt vorkommen"; +"Scene.Familiarfollowers.Title" = "Folgende, die du kennst"; "Scene.Favorite.Title" = "Deine Favoriten"; "Scene.FavoritedBy.Title" = "Favorisiert von"; -"Scene.Follower.Footer" = "Folger, die nicht auf deinem Server registriert sind, werden nicht angezeigt."; -"Scene.Follower.Title" = "Follower"; +"Scene.FollowedTags.Actions.Follow" = "Folgen"; +"Scene.FollowedTags.Actions.Unfollow" = "Entfolgen"; +"Scene.FollowedTags.Header.Participants" = "Teilnehmer*innen"; +"Scene.FollowedTags.Header.Posts" = "Beiträge"; +"Scene.FollowedTags.Header.PostsToday" = "Beiträge heute"; +"Scene.FollowedTags.Title" = "Gefolgte Hashtags"; +"Scene.Follower.Footer" = "Folgende, die nicht auf deinem Server registriert sind, werden nicht angezeigt."; +"Scene.Follower.Title" = "Folgende"; "Scene.Following.Footer" = "Gefolgte, die nicht auf deinem Server registriert sind, werden nicht angezeigt."; -"Scene.Following.Title" = "Folgende"; +"Scene.Following.Title" = "Gefolgte"; "Scene.HomeTimeline.NavigationBarState.Accessibility.LogoHint" = "Zum Scrollen nach oben tippen und zum vorherigen Ort erneut tippen"; "Scene.HomeTimeline.NavigationBarState.Accessibility.LogoLabel" = "Logo-Button"; "Scene.HomeTimeline.NavigationBarState.NewPosts" = "Neue Beiträge anzeigen"; "Scene.HomeTimeline.NavigationBarState.Offline" = "Offline"; "Scene.HomeTimeline.NavigationBarState.Published" = "Veröffentlicht!"; -"Scene.HomeTimeline.NavigationBarState.Publishing" = "Beitrag wird veröffentlicht..."; +"Scene.HomeTimeline.NavigationBarState.Publishing" = "Beitrag wird veröffentlicht …"; "Scene.HomeTimeline.Title" = "Startseite"; +"Scene.Login.ServerSearchField.Placeholder" = "URL eingeben oder nach Server suchen"; +"Scene.Login.Subtitle" = "Melden Sie sich auf dem Server an, auf dem Sie Ihr Konto erstellt haben."; +"Scene.Login.Title" = "Willkommen zurück"; "Scene.Notification.FollowRequest.Accept" = "Akzeptieren"; "Scene.Notification.FollowRequest.Accepted" = "Akzeptiert"; "Scene.Notification.FollowRequest.Reject" = "Ablehnen"; @@ -259,17 +287,20 @@ kann nicht auf Mastodon hochgeladen werden."; "Scene.Profile.Dashboard.Following" = "Gefolgte"; "Scene.Profile.Dashboard.Posts" = "Beiträge"; "Scene.Profile.Fields.AddRow" = "Zeile hinzufügen"; +"Scene.Profile.Fields.Joined" = "Beigetreten"; "Scene.Profile.Fields.Placeholder.Content" = "Inhalt"; "Scene.Profile.Fields.Placeholder.Label" = "Bezeichnung"; +"Scene.Profile.Fields.Verified.Long" = "Besitz des Links wurde überprüft am %@"; +"Scene.Profile.Fields.Verified.Short" = "Überprüft am %@"; "Scene.Profile.Header.FollowsYou" = "Folgt dir"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Message" = "Bestätige %@ zu blockieren"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title" = "Konto blockieren"; -"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Message" = "Confirm to hide reblogs"; -"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Title" = "Reblogs ausblenden"; +"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Message" = "Bestätigen, um Teilen auszublenden"; +"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Title" = "Teilen ausblenden"; "Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Message" = "Bestätige %@ stumm zu schalten"; "Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Title" = "Konto stummschalten"; -"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Message" = "Bestätigen um Reblogs anzuzeigen"; -"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Title" = "Reblogs anzeigen"; +"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Message" = "Bestätigen, um Teilen anzuzeigen"; +"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Title" = "Teilen anzeigen"; "Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Message" = "Bestätige %@ zu entsperren"; "Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Title" = "Konto entsperren"; "Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.Message" = "Bestätige um %@ nicht mehr stummzuschalten"; @@ -357,7 +388,7 @@ kann nicht auf Mastodon hochgeladen werden."; "Scene.Report.TextPlaceholder" = "Zusätzliche Kommentare eingeben oder einfügen"; "Scene.Report.Title" = "%@ melden"; "Scene.Report.TitleReport" = "Melden"; -"Scene.Search.Recommend.Accounts.Description" = "Vielleicht gefallen dir diese Benutzer"; +"Scene.Search.Recommend.Accounts.Description" = "Vielleicht gefallen dir diese Konten"; "Scene.Search.Recommend.Accounts.Follow" = "Folgen"; "Scene.Search.Recommend.Accounts.Title" = "Konten, die dir gefallen könnten"; "Scene.Search.Recommend.ButtonText" = "Alle anzeigen"; @@ -391,15 +422,13 @@ kann nicht auf Mastodon hochgeladen werden."; "Scene.ServerPicker.Button.SeeLess" = "Weniger anzeigen"; "Scene.ServerPicker.Button.SeeMore" = "Mehr anzeigen"; "Scene.ServerPicker.EmptyState.BadNetwork" = "Beim Laden der Daten ist etwas schief gelaufen. Überprüfe deine Internetverbindung."; -"Scene.ServerPicker.EmptyState.FindingServers" = "Verfügbare Server werden gesucht..."; +"Scene.ServerPicker.EmptyState.FindingServers" = "Verfügbare Server werden gesucht …"; "Scene.ServerPicker.EmptyState.NoResults" = "Keine Ergebnisse"; -"Scene.ServerPicker.Input.Placeholder" = "Nach Server suchen oder URL eingeben"; -"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Nach Server suchen oder URL eingeben"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Suche nach einer Community oder gib eine URL ein"; "Scene.ServerPicker.Label.Category" = "KATEGORIE"; "Scene.ServerPicker.Label.Language" = "SPRACHE"; "Scene.ServerPicker.Label.Users" = "BENUTZER"; -"Scene.ServerPicker.Subtitle" = "Wähle eine Gemeinschaft, die auf deinen Interessen, Region oder einem allgemeinen Zweck basiert."; -"Scene.ServerPicker.SubtitleExtend" = "Wähle eine Gemeinschaft basierend auf deinen Interessen, deiner Region oder einem allgemeinen Zweck. Jede Gemeinschaft wird von einer völlig unabhängigen Organisation oder Einzelperson betrieben."; +"Scene.ServerPicker.Subtitle" = "Wähle einen Server basierend auf deinen Interessen oder deiner Region – oder einfach einen allgemeinen. Du kannst trotzdem mit jedem interagieren, egal auf welchem Server."; "Scene.ServerPicker.Title" = "Wähle einen Server, beliebigen Server."; "Scene.ServerRules.Button.Confirm" = "Ich stimme zu"; @@ -443,7 +472,7 @@ beliebigen Server."; "Scene.Settings.Section.SpicyZone.Signout" = "Abmelden"; "Scene.Settings.Section.SpicyZone.Title" = "Der Gefährliche Bereich"; "Scene.Settings.Title" = "Einstellungen"; -"Scene.SuggestionAccount.FollowExplain" = "Wenn du jemandem folgst, dann siehst du deren Beiträge in deinem Home-Feed."; +"Scene.SuggestionAccount.FollowExplain" = "Sobald du anderen folgst, siehst du deren Beiträge in deinem Home-Feed."; "Scene.SuggestionAccount.Title" = "Finde Personen zum Folgen"; "Scene.Thread.BackTitle" = "Beitrag"; "Scene.Thread.Title" = "Beitrag von %@"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/de.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/de.lproj/Localizable.stringsdict index f60c6b0d7..9d07f80d1 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/de.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/de.lproj/Localizable.stringsdict @@ -37,7 +37,23 @@ a11y.plural.count.input_limit_remains NSStringLocalizedFormatKey - Noch %#@character_count@ übrig + Noch %#@character_count@ Zeichen übrig + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 Zeichen + other + %ld Zeichen + + + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ übrig character_count NSStringFormatSpecTypeKey diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.strings index 07ccd2c1b..3ba305a4b 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.strings @@ -22,6 +22,9 @@ Please check your internet connection."; "Common.Alerts.SignOut.Message" = "Are you sure you want to sign out?"; "Common.Alerts.SignOut.Title" = "Sign Out"; "Common.Alerts.SignUpFailure.Title" = "Sign Up Failure"; +"Common.Alerts.TranslationFailed.Button" = "OK"; +"Common.Alerts.TranslationFailed.Message" = "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported."; +"Common.Alerts.TranslationFailed.Title" = "Note"; "Common.Alerts.VoteFailure.PollEnded" = "The poll has ended"; "Common.Alerts.VoteFailure.Title" = "Vote Failure"; "Common.Controls.Actions.Add" = "Add"; @@ -31,6 +34,7 @@ Please check your internet connection."; "Common.Controls.Actions.Compose" = "Compose"; "Common.Controls.Actions.Confirm" = "Confirm"; "Common.Controls.Actions.Continue" = "Continue"; +"Common.Controls.Actions.Copy" = "Copy"; "Common.Controls.Actions.CopyPhoto" = "Copy Photo"; "Common.Controls.Actions.Delete" = "Delete"; "Common.Controls.Actions.Discard" = "Discard"; @@ -55,8 +59,8 @@ Please check your internet connection."; "Common.Controls.Actions.Share" = "Share"; "Common.Controls.Actions.SharePost" = "Share Post"; "Common.Controls.Actions.ShareUser" = "Share %@"; -"Common.Controls.Actions.SignIn" = "Sign In"; -"Common.Controls.Actions.SignUp" = "Sign Up"; +"Common.Controls.Actions.SignIn" = "Log in"; +"Common.Controls.Actions.SignUp" = "Create account"; "Common.Controls.Actions.Skip" = "Skip"; "Common.Controls.Actions.TakePhoto" = "Take Photo"; "Common.Controls.Actions.TryAgain" = "Try Again"; @@ -100,6 +104,7 @@ Please check your internet connection."; "Common.Controls.Status.Actions.Menu" = "Menu"; "Common.Controls.Status.Actions.Reblog" = "Reblog"; "Common.Controls.Status.Actions.Reply" = "Reply"; +"Common.Controls.Status.Actions.ShareLinkInPost" = "Share Link in Post"; "Common.Controls.Status.Actions.ShowGif" = "Show GIF"; "Common.Controls.Status.Actions.ShowImage" = "Show image"; "Common.Controls.Status.Actions.ShowVideoPlayer" = "Show video player"; @@ -107,6 +112,8 @@ Please check your internet connection."; "Common.Controls.Status.Actions.Unfavorite" = "Unfavorite"; "Common.Controls.Status.Actions.Unreblog" = "Undo reblog"; "Common.Controls.Status.ContentWarning" = "Content Warning"; +"Common.Controls.Status.LinkViaUser" = "%@ via %@"; +"Common.Controls.Status.LoadEmbed" = "Load Embed"; "Common.Controls.Status.MediaContentWarning" = "Tap anywhere to reveal"; "Common.Controls.Status.MetaEntity.Email" = "Email address: %@"; "Common.Controls.Status.MetaEntity.Hashtag" = "Hashtag: %@"; @@ -124,6 +131,10 @@ Please check your internet connection."; "Common.Controls.Status.Tag.Mention" = "Mention"; "Common.Controls.Status.Tag.Url" = "URL"; "Common.Controls.Status.TapToReveal" = "Tap to reveal"; +"Common.Controls.Status.Translation.ShowOriginal" = "Shown Original"; +"Common.Controls.Status.Translation.TranslatedFrom" = "Translated from %@ using %@"; +"Common.Controls.Status.Translation.UnknownLanguage" = "Unknown"; +"Common.Controls.Status.Translation.UnknownProvider" = "Unknown"; "Common.Controls.Status.UserReblogged" = "%@ reblogged"; "Common.Controls.Status.UserRepliedTo" = "Replied to %@"; "Common.Controls.Status.Visibility.Direct" = "Only mentioned user can see this post."; @@ -131,9 +142,9 @@ Please check your internet connection."; "Common.Controls.Status.Visibility.PrivateFromMe" = "Only my followers can see this post."; "Common.Controls.Status.Visibility.Unlisted" = "Everyone can see this post but not display in the public timeline."; "Common.Controls.Tabs.Home" = "Home"; -"Common.Controls.Tabs.Notification" = "Notification"; +"Common.Controls.Tabs.Notifications" = "Notifications"; "Common.Controls.Tabs.Profile" = "Profile"; -"Common.Controls.Tabs.Search" = "Search"; +"Common.Controls.Tabs.SearchAndExplore" = "Search and Explore"; "Common.Controls.Timeline.Filtered" = "Filtered"; "Common.Controls.Timeline.Header.BlockedWarning" = "You can’t view this user’s profile until they unblock you."; @@ -168,11 +179,13 @@ Your profile looks like this to them."; "Scene.Compose.Attachment.AttachmentBroken" = "This %@ is broken and can’t be uploaded to Mastodon."; "Scene.Compose.Attachment.AttachmentTooLarge" = "Attachment too large"; -"Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "Can not regonize this media attachment"; +"Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "Can not recognize this media attachment"; +"Scene.Compose.Attachment.CompressingState" = "Compressing..."; "Scene.Compose.Attachment.DescriptionPhoto" = "Describe the photo for the visually-impaired..."; "Scene.Compose.Attachment.DescriptionVideo" = "Describe the video for the visually-impaired..."; "Scene.Compose.Attachment.LoadFailed" = "Load Failed"; "Scene.Compose.Attachment.Photo" = "photo"; +"Scene.Compose.Attachment.ServerProcessingState" = "Server Processing..."; "Scene.Compose.Attachment.UploadFailed" = "Upload Failed"; "Scene.Compose.Attachment.Video" = "video"; "Scene.Compose.AutoComplete.SpaceToAdd" = "Space to add"; @@ -194,6 +207,8 @@ uploaded to Mastodon."; "Scene.Compose.Poll.OptionNumber" = "Option %ld"; "Scene.Compose.Poll.SevenDays" = "7 Days"; "Scene.Compose.Poll.SixHours" = "6 Hours"; +"Scene.Compose.Poll.ThePollHasEmptyOption" = "The poll has empty option"; +"Scene.Compose.Poll.ThePollIsInvalid" = "The poll is invalid"; "Scene.Compose.Poll.ThirtyMinutes" = "30 minutes"; "Scene.Compose.Poll.ThreeDays" = "3 Days"; "Scene.Compose.ReplyingToUser" = "replying to %@"; @@ -225,17 +240,26 @@ uploaded to Mastodon."; "Scene.Familiarfollowers.Title" = "Followers you familiar"; "Scene.Favorite.Title" = "Your Favorites"; "Scene.FavoritedBy.Title" = "Favorited By"; +"Scene.FollowedTags.Actions.Follow" = "Follow"; +"Scene.FollowedTags.Actions.Unfollow" = "Unfollow"; +"Scene.FollowedTags.Header.Participants" = "participants"; +"Scene.FollowedTags.Header.Posts" = "posts"; +"Scene.FollowedTags.Header.PostsToday" = "posts today"; +"Scene.FollowedTags.Title" = "Followed Tags"; "Scene.Follower.Footer" = "Followers from other servers are not displayed."; "Scene.Follower.Title" = "follower"; "Scene.Following.Footer" = "Follows from other servers are not displayed."; "Scene.Following.Title" = "following"; "Scene.HomeTimeline.NavigationBarState.Accessibility.LogoHint" = "Tap to scroll to top and tap again to previous location"; -"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoLabel" = "Logo Button"; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoLabel" = "Mastodon"; "Scene.HomeTimeline.NavigationBarState.NewPosts" = "See new posts"; "Scene.HomeTimeline.NavigationBarState.Offline" = "Offline"; "Scene.HomeTimeline.NavigationBarState.Published" = "Published!"; "Scene.HomeTimeline.NavigationBarState.Publishing" = "Publishing post..."; "Scene.HomeTimeline.Title" = "Home"; +"Scene.Login.ServerSearchField.Placeholder" = "Enter URL or search for your server"; +"Scene.Login.Subtitle" = "Log you in on the server you created your account on."; +"Scene.Login.Title" = "Welcome back"; "Scene.Notification.FollowRequest.Accept" = "Accept"; "Scene.Notification.FollowRequest.Accepted" = "Accepted"; "Scene.Notification.FollowRequest.Reject" = "reject"; @@ -261,8 +285,11 @@ uploaded to Mastodon."; "Scene.Profile.Dashboard.Following" = "following"; "Scene.Profile.Dashboard.Posts" = "posts"; "Scene.Profile.Fields.AddRow" = "Add Row"; +"Scene.Profile.Fields.Joined" = "Liitytty"; "Scene.Profile.Fields.Placeholder.Content" = "Content"; "Scene.Profile.Fields.Placeholder.Label" = "Label"; +"Scene.Profile.Fields.Verified.Long" = "Ownership of this link was checked on %@"; +"Scene.Profile.Fields.Verified.Short" = "Verified on %@"; "Scene.Profile.Header.FollowsYou" = "Follows You"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Message" = "Confirm to block %@"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title" = "Block Account"; @@ -395,13 +422,11 @@ uploaded to Mastodon."; "Scene.ServerPicker.EmptyState.BadNetwork" = "Something went wrong while loading the data. Check your internet connection."; "Scene.ServerPicker.EmptyState.FindingServers" = "Finding available servers..."; "Scene.ServerPicker.EmptyState.NoResults" = "No results"; -"Scene.ServerPicker.Input.Placeholder" = "Search servers"; -"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Search servers or enter URL"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Search communities or enter URL"; "Scene.ServerPicker.Label.Category" = "CATEGORY"; "Scene.ServerPicker.Label.Language" = "LANGUAGE"; "Scene.ServerPicker.Label.Users" = "USERS"; -"Scene.ServerPicker.Subtitle" = "Pick a server based on your interests, region, or a general purpose one."; -"Scene.ServerPicker.SubtitleExtend" = "Pick a server based on your interests, region, or a general purpose one. Each server is operated by an entirely independent organization or individual."; +"Scene.ServerPicker.Subtitle" = "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers."; "Scene.ServerPicker.Title" = "Mastodon is made of users in different servers."; "Scene.ServerRules.Button.Confirm" = "I Agree"; "Scene.ServerRules.PrivacyPolicy" = "privacy policy"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.stringsdict index 297e6675a..788eb95fc 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.stringsdict @@ -15,7 +15,7 @@ one 1 unread notification other - %ld unread notification + %ld unread notifications a11y.plural.count.input_limit_exceeds @@ -60,14 +60,8 @@ NSStringPluralRuleType NSStringFormatValueTypeKey ld - zero - no characters one 1 character - few - %ld characters - many - %ld characters other %ld characters diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/es.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/es.lproj/Localizable.strings index c16bec6cf..d9d6a6ff6 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/es.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/es.lproj/Localizable.strings @@ -22,6 +22,9 @@ Por favor, revise su conexión a internet."; "Common.Alerts.SignOut.Message" = "¿Estás seguro de que deseas cerrar la sesión?"; "Common.Alerts.SignOut.Title" = "Cerrar Sesión"; "Common.Alerts.SignUpFailure.Title" = "Error al registrarse"; +"Common.Alerts.TranslationFailed.Button" = "Aceptar"; +"Common.Alerts.TranslationFailed.Message" = "Error al traducir. Tal vez el administrador no ha habilitado las traducciones en este servidor o este servidor está ejecutando una versión antigua de Mastodon donde las traducciones aún no están soportadas."; +"Common.Alerts.TranslationFailed.Title" = "Nota"; "Common.Alerts.VoteFailure.PollEnded" = "La encuesta ha terminado"; "Common.Alerts.VoteFailure.Title" = "Voto fallido"; "Common.Controls.Actions.Add" = "Añadir"; @@ -31,6 +34,7 @@ Por favor, revise su conexión a internet."; "Common.Controls.Actions.Compose" = "Redactar"; "Common.Controls.Actions.Confirm" = "Confirmar"; "Common.Controls.Actions.Continue" = "Continuar"; +"Common.Controls.Actions.Copy" = "Copiar"; "Common.Controls.Actions.CopyPhoto" = "Copiar foto"; "Common.Controls.Actions.Delete" = "Borrar"; "Common.Controls.Actions.Discard" = "Descartar"; @@ -56,9 +60,11 @@ Por favor, revise su conexión a internet."; "Common.Controls.Actions.SharePost" = "Compartir publicación"; "Common.Controls.Actions.ShareUser" = "Compartir %@"; "Common.Controls.Actions.SignIn" = "Iniciar sesión"; -"Common.Controls.Actions.SignUp" = "Regístrate"; +"Common.Controls.Actions.SignUp" = "Crear cuenta"; "Common.Controls.Actions.Skip" = "Omitir"; "Common.Controls.Actions.TakePhoto" = "Tomar foto"; +"Common.Controls.Actions.TranslatePost.Title" = "Traducir desde %@"; +"Common.Controls.Actions.TranslatePost.UnknownLanguage" = "Desconocido"; "Common.Controls.Actions.TryAgain" = "Inténtalo de nuevo"; "Common.Controls.Actions.UnblockDomain" = "Desbloquear %@"; "Common.Controls.Friendship.Block" = "Bloquear"; @@ -68,13 +74,13 @@ Por favor, revise su conexión a internet."; "Common.Controls.Friendship.EditInfo" = "Editar Info"; "Common.Controls.Friendship.Follow" = "Seguir"; "Common.Controls.Friendship.Following" = "Siguiendo"; -"Common.Controls.Friendship.HideReblogs" = "Hide Reblogs"; +"Common.Controls.Friendship.HideReblogs" = "Ocultar reblogs"; "Common.Controls.Friendship.Mute" = "Silenciar"; "Common.Controls.Friendship.MuteUser" = "Silenciar a %@"; "Common.Controls.Friendship.Muted" = "Silenciado"; "Common.Controls.Friendship.Pending" = "Pendiente"; "Common.Controls.Friendship.Request" = "Solicitud"; -"Common.Controls.Friendship.ShowReblogs" = "Show Reblogs"; +"Common.Controls.Friendship.ShowReblogs" = "Mostrar reblogs"; "Common.Controls.Friendship.Unblock" = "Desbloquear"; "Common.Controls.Friendship.UnblockUser" = "Desbloquear a %@"; "Common.Controls.Friendship.Unmute" = "Desmutear"; @@ -100,6 +106,7 @@ Por favor, revise su conexión a internet."; "Common.Controls.Status.Actions.Menu" = "Menú"; "Common.Controls.Status.Actions.Reblog" = "Rebloguear"; "Common.Controls.Status.Actions.Reply" = "Responder"; +"Common.Controls.Status.Actions.ShareLinkInPost" = "Compartir enlace en publicación"; "Common.Controls.Status.Actions.ShowGif" = "Mostrar GIF"; "Common.Controls.Status.Actions.ShowImage" = "Mostrar imagen"; "Common.Controls.Status.Actions.ShowVideoPlayer" = "Mostrar reproductor de vídeo"; @@ -107,11 +114,13 @@ Por favor, revise su conexión a internet."; "Common.Controls.Status.Actions.Unfavorite" = "No favorito"; "Common.Controls.Status.Actions.Unreblog" = "Deshacer reblogueo"; "Common.Controls.Status.ContentWarning" = "Advertencia de Contenido"; +"Common.Controls.Status.LinkViaUser" = "%@ vía %@"; +"Common.Controls.Status.LoadEmbed" = "Cargar incrustado"; "Common.Controls.Status.MediaContentWarning" = "Pulsa en cualquier sitio para mostrar"; -"Common.Controls.Status.MetaEntity.Email" = "Email address: %@"; -"Common.Controls.Status.MetaEntity.Hashtag" = "Hashtag: %@"; -"Common.Controls.Status.MetaEntity.Mention" = "Show Profile: %@"; -"Common.Controls.Status.MetaEntity.Url" = "Link: %@"; +"Common.Controls.Status.MetaEntity.Email" = "Dirección de correo electrónico: %@"; +"Common.Controls.Status.MetaEntity.Hashtag" = "Etiqueta: %@"; +"Common.Controls.Status.MetaEntity.Mention" = "Mostrar Perfil: %@"; +"Common.Controls.Status.MetaEntity.Url" = "Enlace: %@"; "Common.Controls.Status.Poll.Closed" = "Cerrado"; "Common.Controls.Status.Poll.Vote" = "Vota"; "Common.Controls.Status.SensitiveContent" = "Contenido sensible"; @@ -124,6 +133,10 @@ Por favor, revise su conexión a internet."; "Common.Controls.Status.Tag.Mention" = "Mención"; "Common.Controls.Status.Tag.Url" = "URL"; "Common.Controls.Status.TapToReveal" = "Tocar para revelar"; +"Common.Controls.Status.Translation.ShowOriginal" = "Mostrar Original"; +"Common.Controls.Status.Translation.TranslatedFrom" = "Translated from %@ using %@"; +"Common.Controls.Status.Translation.UnknownLanguage" = "Desconocido"; +"Common.Controls.Status.Translation.UnknownProvider" = "Unknown"; "Common.Controls.Status.UserReblogged" = "%@ lo reblogueó"; "Common.Controls.Status.UserRepliedTo" = "En respuesta a %@"; "Common.Controls.Status.Visibility.Direct" = "Sólo el usuario mencionado puede ver este mensaje."; @@ -131,9 +144,9 @@ Por favor, revise su conexión a internet."; "Common.Controls.Status.Visibility.PrivateFromMe" = "Sólo mis seguidores pueden ver este mensaje."; "Common.Controls.Status.Visibility.Unlisted" = "Todo el mundo puede ver este post pero no mostrar en la línea de tiempo pública."; "Common.Controls.Tabs.Home" = "Inicio"; -"Common.Controls.Tabs.Notification" = "Notificación"; +"Common.Controls.Tabs.Notifications" = "Notificaciones"; "Common.Controls.Tabs.Profile" = "Perfil"; -"Common.Controls.Tabs.Search" = "Buscar"; +"Common.Controls.Tabs.SearchAndExplore" = "Buscar y explorar"; "Common.Controls.Timeline.Filtered" = "Filtrado"; "Common.Controls.Timeline.Header.BlockedWarning" = "No puedes ver el perfil de este usuario hasta que te desbloquee."; @@ -155,23 +168,27 @@ Tu perfil se ve así para él."; "Scene.AccountList.AddAccount" = "Añadir cuenta"; "Scene.AccountList.DismissAccountSwitcher" = "Descartar el selector de cuentas"; "Scene.AccountList.TabBarHint" = "Perfil seleccionado actualmente: %@. Haz un doble toque y mantén pulsado para mostrar el selector de cuentas"; -"Scene.Bookmark.Title" = "Bookmarks"; +"Scene.Bookmark.Title" = "Marcadores"; "Scene.Compose.Accessibility.AppendAttachment" = "Añadir Adjunto"; "Scene.Compose.Accessibility.AppendPoll" = "Añadir Encuesta"; "Scene.Compose.Accessibility.CustomEmojiPicker" = "Selector de Emojis Personalizados"; "Scene.Compose.Accessibility.DisableContentWarning" = "Desactivar Advertencia de Contenido"; "Scene.Compose.Accessibility.EnableContentWarning" = "Activar Advertencia de Contenido"; +"Scene.Compose.Accessibility.PostOptions" = "Opciones de Publicación"; "Scene.Compose.Accessibility.PostVisibilityMenu" = "Menú de Visibilidad de la Publicación"; +"Scene.Compose.Accessibility.PostingAs" = "Publicado como %@"; "Scene.Compose.Accessibility.RemovePoll" = "Eliminar Encuesta"; "Scene.Compose.Attachment.AttachmentBroken" = "Este %@ está roto y no puede subirse a Mastodon."; -"Scene.Compose.Attachment.AttachmentTooLarge" = "Attachment too large"; -"Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "Can not regonize this media attachment"; +"Scene.Compose.Attachment.AttachmentTooLarge" = "Adjunto demasiado grande"; +"Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "No se puede reconocer este archivo adjunto"; +"Scene.Compose.Attachment.CompressingState" = "Comprimiendo..."; "Scene.Compose.Attachment.DescriptionPhoto" = "Describe la foto para los usuarios con dificultad visual..."; "Scene.Compose.Attachment.DescriptionVideo" = "Describe el vídeo para los usuarios con dificultad visual..."; -"Scene.Compose.Attachment.LoadFailed" = "Load Failed"; +"Scene.Compose.Attachment.LoadFailed" = "Carga fallida"; "Scene.Compose.Attachment.Photo" = "foto"; -"Scene.Compose.Attachment.UploadFailed" = "Upload Failed"; +"Scene.Compose.Attachment.ServerProcessingState" = "Procesando en el servidor..."; +"Scene.Compose.Attachment.UploadFailed" = "Error al cargar"; "Scene.Compose.Attachment.Video" = "vídeo"; "Scene.Compose.AutoComplete.SpaceToAdd" = "Espacio para añadir"; "Scene.Compose.ComposeAction" = "Publicar"; @@ -192,6 +209,8 @@ subirse a Mastodon."; "Scene.Compose.Poll.OptionNumber" = "Opción %ld"; "Scene.Compose.Poll.SevenDays" = "7 Días"; "Scene.Compose.Poll.SixHours" = "6 Horas"; +"Scene.Compose.Poll.ThePollHasEmptyOption" = "La encuesta tiene una opción vacía"; +"Scene.Compose.Poll.ThePollIsInvalid" = "La encuesta no es válida"; "Scene.Compose.Poll.ThirtyMinutes" = "30 minutos"; "Scene.Compose.Poll.ThreeDays" = "4 Días"; "Scene.Compose.ReplyingToUser" = "en respuesta a %@"; @@ -224,6 +243,12 @@ pulsa en el enlace para confirmar tu cuenta."; "Scene.Familiarfollowers.Title" = "Seguidores que conoces"; "Scene.Favorite.Title" = "Tus Favoritos"; "Scene.FavoritedBy.Title" = "Hecho favorito por"; +"Scene.FollowedTags.Actions.Follow" = "Seguir"; +"Scene.FollowedTags.Actions.Unfollow" = "Dejar de seguir"; +"Scene.FollowedTags.Header.Participants" = "participantes"; +"Scene.FollowedTags.Header.Posts" = "publicaciones"; +"Scene.FollowedTags.Header.PostsToday" = "publicaciones de hoy"; +"Scene.FollowedTags.Title" = "Etiquetas seguidas"; "Scene.Follower.Footer" = "No se muestran los seguidores de otros servidores."; "Scene.Follower.Title" = "seguidor"; "Scene.Following.Footer" = "No se muestran los seguidos de otros servidores."; @@ -235,6 +260,9 @@ pulsa en el enlace para confirmar tu cuenta."; "Scene.HomeTimeline.NavigationBarState.Published" = "¡Publicado!"; "Scene.HomeTimeline.NavigationBarState.Publishing" = "Publicación en curso..."; "Scene.HomeTimeline.Title" = "Inicio"; +"Scene.Login.ServerSearchField.Placeholder" = "Introduzca la URL o busque su servidor"; +"Scene.Login.Subtitle" = "Inicie sesión en el servidor en el que creó su cuenta."; +"Scene.Login.Title" = "Bienvenido de nuevo"; "Scene.Notification.FollowRequest.Accept" = "Aceptar"; "Scene.Notification.FollowRequest.Accepted" = "Aceptado"; "Scene.Notification.FollowRequest.Reject" = "rechazar"; @@ -260,17 +288,20 @@ pulsa en el enlace para confirmar tu cuenta."; "Scene.Profile.Dashboard.Following" = "siguiendo"; "Scene.Profile.Dashboard.Posts" = "publicaciones"; "Scene.Profile.Fields.AddRow" = "Añadir Fila"; +"Scene.Profile.Fields.Joined" = "Joined"; "Scene.Profile.Fields.Placeholder.Content" = "Contenido"; "Scene.Profile.Fields.Placeholder.Label" = "Nombre para el campo"; +"Scene.Profile.Fields.Verified.Long" = "La propiedad de este enlace fue verificada el %@"; +"Scene.Profile.Fields.Verified.Short" = "Verificado en %@"; "Scene.Profile.Header.FollowsYou" = "Te sigue"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Message" = "Confirmar para bloquear a %@"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title" = "Bloquear cuenta"; -"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Message" = "Confirm to hide reblogs"; -"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Title" = "Hide Reblogs"; +"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Message" = "Confirmar para ocultar reblogs"; +"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Title" = "Ocultar reblogs"; "Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Message" = "Confirmar para silenciar %@"; "Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Title" = "Silenciar cuenta"; -"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Message" = "Confirm to show reblogs"; -"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Title" = "Show Reblogs"; +"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Message" = "Confirmar para mostrar reblogs"; +"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Title" = "Mostrar reblogs"; "Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Message" = "Confirmar para desbloquear a %@"; "Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Title" = "Desbloquear cuenta"; "Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.Message" = "Confirmar para dejar de silenciar a %@"; @@ -394,13 +425,11 @@ pulsa en el enlace para confirmar tu cuenta."; "Scene.ServerPicker.EmptyState.BadNetwork" = "Algo ha ido mal al cargar los datos. Comprueba tu conexión a Internet."; "Scene.ServerPicker.EmptyState.FindingServers" = "Encontrando servidores disponibles..."; "Scene.ServerPicker.EmptyState.NoResults" = "Sin resultados"; -"Scene.ServerPicker.Input.Placeholder" = "Encuentra un servidor o únete al tuyo propio..."; -"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Buscar servidores o introducir la URL"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Buscar comunidades o introducir URL"; "Scene.ServerPicker.Label.Category" = "CATEGORÍA"; "Scene.ServerPicker.Label.Language" = "IDIOMA"; "Scene.ServerPicker.Label.Users" = "USUARIOS"; -"Scene.ServerPicker.Subtitle" = "Elige una comunidad relacionada con tus intereses, con tu región o una más genérica."; -"Scene.ServerPicker.SubtitleExtend" = "Elige una comunidad relacionada con tus intereses, con tu región o una más genérica. Cada comunidad está operada por una organización o individuo completamente independiente."; +"Scene.ServerPicker.Subtitle" = "Escoge un servidor basado en tu región, intereses o un propósito general. Aún puedes chatear con cualquiera en Mastodon, independientemente de tus servidores."; "Scene.ServerPicker.Title" = "Elige un servidor, cualquier servidor."; "Scene.ServerRules.Button.Confirm" = "Acepto"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/es.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/es.lproj/Localizable.stringsdict index def3d7bba..0a904fcfd 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/es.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/es.lproj/Localizable.stringsdict @@ -50,6 +50,22 @@ %ld caracteres + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + Quedan %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 carácter + other + %ld caracteres + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/eu.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/eu.lproj/Localizable.strings index aef7a7507..882e05ec6 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/eu.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/eu.lproj/Localizable.strings @@ -22,6 +22,9 @@ Egiaztatu Interneteko konexioa."; "Common.Alerts.SignOut.Message" = "Ziur saioa amaitu nahi duzula?"; "Common.Alerts.SignOut.Title" = "Amaitu saioa"; "Common.Alerts.SignUpFailure.Title" = "Hutsegitea izen-ematean"; +"Common.Alerts.TranslationFailed.Button" = "OK"; +"Common.Alerts.TranslationFailed.Message" = "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported."; +"Common.Alerts.TranslationFailed.Title" = "Note"; "Common.Alerts.VoteFailure.PollEnded" = "Inkesta amaitu da"; "Common.Alerts.VoteFailure.Title" = "Hutsegitea botoa ematean"; "Common.Controls.Actions.Add" = "Gehitu"; @@ -31,6 +34,7 @@ Egiaztatu Interneteko konexioa."; "Common.Controls.Actions.Compose" = "Idatzi"; "Common.Controls.Actions.Confirm" = "Berretsi"; "Common.Controls.Actions.Continue" = "Jarraitu"; +"Common.Controls.Actions.Copy" = "Copy"; "Common.Controls.Actions.CopyPhoto" = "Kopiatu argazkia"; "Common.Controls.Actions.Delete" = "Ezabatu"; "Common.Controls.Actions.Discard" = "Baztertu"; @@ -56,9 +60,11 @@ Egiaztatu Interneteko konexioa."; "Common.Controls.Actions.SharePost" = "Partekatu bidalketa"; "Common.Controls.Actions.ShareUser" = "Partekatu %@"; "Common.Controls.Actions.SignIn" = "Hasi saioa"; -"Common.Controls.Actions.SignUp" = "Eman Izena"; +"Common.Controls.Actions.SignUp" = "Sortu kontua"; "Common.Controls.Actions.Skip" = "Saltatu"; "Common.Controls.Actions.TakePhoto" = "Atera argazkia"; +"Common.Controls.Actions.TranslatePost.Title" = "Translate from %@"; +"Common.Controls.Actions.TranslatePost.UnknownLanguage" = "Unknown"; "Common.Controls.Actions.TryAgain" = "Saiatu berriro"; "Common.Controls.Actions.UnblockDomain" = "Desblokeatu %@"; "Common.Controls.Friendship.Block" = "Blokeatu"; @@ -68,13 +74,13 @@ Egiaztatu Interneteko konexioa."; "Common.Controls.Friendship.EditInfo" = "Editatu informazioa"; "Common.Controls.Friendship.Follow" = "Jarraitu"; "Common.Controls.Friendship.Following" = "Jarraitzen"; -"Common.Controls.Friendship.HideReblogs" = "Hide Reblogs"; +"Common.Controls.Friendship.HideReblogs" = "Ezkutatu bultzadak"; "Common.Controls.Friendship.Mute" = "Mututu"; "Common.Controls.Friendship.MuteUser" = "Mututu %@"; "Common.Controls.Friendship.Muted" = "Mutututa"; "Common.Controls.Friendship.Pending" = "Zain"; "Common.Controls.Friendship.Request" = "Eskaera"; -"Common.Controls.Friendship.ShowReblogs" = "Show Reblogs"; +"Common.Controls.Friendship.ShowReblogs" = "Ikusi bultzadak"; "Common.Controls.Friendship.Unblock" = "Desblokeatu"; "Common.Controls.Friendship.UnblockUser" = "Desblokeatu %@"; "Common.Controls.Friendship.Unmute" = "Desmututu"; @@ -100,6 +106,7 @@ Egiaztatu Interneteko konexioa."; "Common.Controls.Status.Actions.Menu" = "Menua"; "Common.Controls.Status.Actions.Reblog" = "Bultzada"; "Common.Controls.Status.Actions.Reply" = "Erantzun"; +"Common.Controls.Status.Actions.ShareLinkInPost" = "Share Link in Post"; "Common.Controls.Status.Actions.ShowGif" = "Erakutsi GIFa"; "Common.Controls.Status.Actions.ShowImage" = "Erakutsi irudia"; "Common.Controls.Status.Actions.ShowVideoPlayer" = "Erakutsi bideo-erreproduzigailua"; @@ -107,14 +114,16 @@ Egiaztatu Interneteko konexioa."; "Common.Controls.Status.Actions.Unfavorite" = "Kendu gogokoa"; "Common.Controls.Status.Actions.Unreblog" = "Desegin bultzada"; "Common.Controls.Status.ContentWarning" = "Edukiaren abisua"; +"Common.Controls.Status.LinkViaUser" = "%@ via %@"; +"Common.Controls.Status.LoadEmbed" = "Load Embed"; "Common.Controls.Status.MediaContentWarning" = "Ukitu edonon bistaratzeko"; -"Common.Controls.Status.MetaEntity.Email" = "Email address: %@"; -"Common.Controls.Status.MetaEntity.Hashtag" = "Hashtag: %@"; -"Common.Controls.Status.MetaEntity.Mention" = "Show Profile: %@"; -"Common.Controls.Status.MetaEntity.Url" = "Link: %@"; +"Common.Controls.Status.MetaEntity.Email" = "E-posta helbidea: %@"; +"Common.Controls.Status.MetaEntity.Hashtag" = "Traolak: %@"; +"Common.Controls.Status.MetaEntity.Mention" = "Erakutsi Profila: %@"; +"Common.Controls.Status.MetaEntity.Url" = "Lotura: %@"; "Common.Controls.Status.Poll.Closed" = "Itxita"; "Common.Controls.Status.Poll.Vote" = "Bozkatu"; -"Common.Controls.Status.SensitiveContent" = "Sensitive Content"; +"Common.Controls.Status.SensitiveContent" = "Eduki hunkigarria"; "Common.Controls.Status.ShowPost" = "Erakutsi bidalketa"; "Common.Controls.Status.ShowUserProfile" = "Erakutsi erabiltzailearen profila"; "Common.Controls.Status.Tag.Email" = "Eposta"; @@ -124,6 +133,10 @@ Egiaztatu Interneteko konexioa."; "Common.Controls.Status.Tag.Mention" = "Aipatu"; "Common.Controls.Status.Tag.Url" = "URLa"; "Common.Controls.Status.TapToReveal" = "Sakatu erakusteko"; +"Common.Controls.Status.Translation.ShowOriginal" = "Shown Original"; +"Common.Controls.Status.Translation.TranslatedFrom" = "Translated from %@ using %@"; +"Common.Controls.Status.Translation.UnknownLanguage" = "Unknown"; +"Common.Controls.Status.Translation.UnknownProvider" = "Unknown"; "Common.Controls.Status.UserReblogged" = "%@ erabiltzaileak bultzada eman dio"; "Common.Controls.Status.UserRepliedTo" = "%@(r)i erantzuten"; "Common.Controls.Status.Visibility.Direct" = "Aipatutako erabiltzaileek soilik ikus dezakete bidalketa hau."; @@ -131,9 +144,9 @@ Egiaztatu Interneteko konexioa."; "Common.Controls.Status.Visibility.PrivateFromMe" = "Nire jarraitzaileek soilik ikus dezakete bidalketa hau."; "Common.Controls.Status.Visibility.Unlisted" = "Edozeinek ikusi dezake bidalketa hau baina ez da denbora-lerro publikoan bistaratuko."; "Common.Controls.Tabs.Home" = "Hasiera"; -"Common.Controls.Tabs.Notification" = "Jakinarazpena"; +"Common.Controls.Tabs.Notifications" = "Notifications"; "Common.Controls.Tabs.Profile" = "Profila"; -"Common.Controls.Tabs.Search" = "Bilatu"; +"Common.Controls.Tabs.SearchAndExplore" = "Search and Explore"; "Common.Controls.Timeline.Filtered" = "Iragazita"; "Common.Controls.Timeline.Header.BlockedWarning" = "Ezin duzu erabiltzaile honen profila ikusi desblokeatzen zaituen arte."; @@ -155,23 +168,27 @@ Zure profilak itxura hau du berarentzat."; "Scene.AccountList.AddAccount" = "Gehitu kontua"; "Scene.AccountList.DismissAccountSwitcher" = "Baztertu kontu-aldatzailea"; "Scene.AccountList.TabBarHint" = "Unean hautatutako profila: %@. Ukitu birritan, ondoren eduki sakatuta kontu-aldatzailea erakusteko"; -"Scene.Bookmark.Title" = "Bookmarks"; +"Scene.Bookmark.Title" = "Laster-markak"; "Scene.Compose.Accessibility.AppendAttachment" = "Gehitu eranskina"; "Scene.Compose.Accessibility.AppendPoll" = "Gehitu inkesta"; "Scene.Compose.Accessibility.CustomEmojiPicker" = "Emoji pertsonalizatuen hautatzailea"; "Scene.Compose.Accessibility.DisableContentWarning" = "Desgaitu edukiaren abisua"; "Scene.Compose.Accessibility.EnableContentWarning" = "Gaitu edukiaren abisua"; +"Scene.Compose.Accessibility.PostOptions" = "Bildalketaren aukerak"; "Scene.Compose.Accessibility.PostVisibilityMenu" = "Bidalketaren ikusgaitasunaren menua"; +"Scene.Compose.Accessibility.PostingAs" = "Posting as %@"; "Scene.Compose.Accessibility.RemovePoll" = "Kendu inkesta"; "Scene.Compose.Attachment.AttachmentBroken" = "%@ hondatuta dago eta ezin da Mastodonera igo."; -"Scene.Compose.Attachment.AttachmentTooLarge" = "Attachment too large"; -"Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "Can not regonize this media attachment"; +"Scene.Compose.Attachment.AttachmentTooLarge" = "Eranskina handiegia da"; +"Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "Can not recognize this media attachment"; +"Scene.Compose.Attachment.CompressingState" = "Konprimatzen..."; "Scene.Compose.Attachment.DescriptionPhoto" = "Deskribatu argazkia ikusmen arazoak dituztenentzat..."; "Scene.Compose.Attachment.DescriptionVideo" = "Deskribatu bideoa ikusmen arazoak dituztenentzat..."; "Scene.Compose.Attachment.LoadFailed" = "Load Failed"; "Scene.Compose.Attachment.Photo" = "argazkia"; -"Scene.Compose.Attachment.UploadFailed" = "Upload Failed"; +"Scene.Compose.Attachment.ServerProcessingState" = "Server Processing..."; +"Scene.Compose.Attachment.UploadFailed" = "Kargatzeak huts egin du"; "Scene.Compose.Attachment.Video" = "bideoa"; "Scene.Compose.AutoComplete.SpaceToAdd" = "Sakatu zuriunea gehitzeko"; "Scene.Compose.ComposeAction" = "Argitaratu"; @@ -192,6 +209,8 @@ Mastodonera igo."; "Scene.Compose.Poll.OptionNumber" = "%ld aukera"; "Scene.Compose.Poll.SevenDays" = "7 egun"; "Scene.Compose.Poll.SixHours" = "6 ordu"; +"Scene.Compose.Poll.ThePollHasEmptyOption" = "The poll has empty option"; +"Scene.Compose.Poll.ThePollIsInvalid" = "Inkesta ez da balekoa"; "Scene.Compose.Poll.ThirtyMinutes" = "30 minutu"; "Scene.Compose.Poll.ThreeDays" = "3 egun"; "Scene.Compose.ReplyingToUser" = "%@(r)i erantzuten"; @@ -211,10 +230,10 @@ Mastodonera igo."; "Scene.ConfirmEmail.OpenEmailApp.OpenEmailClient" = "Ireki eposta bezeroa"; "Scene.ConfirmEmail.OpenEmailApp.Title" = "Egiaztatu zure sarrerako ontzia."; "Scene.ConfirmEmail.Subtitle" = "Sakatu epostaz bidali dizugun loturan zure kontua egiaztatzeko."; -"Scene.ConfirmEmail.TapTheLinkWeEmailedToYouToVerifyYourAccount" = "Tap the link we emailed to you to verify your account"; +"Scene.ConfirmEmail.TapTheLinkWeEmailedToYouToVerifyYourAccount" = "Sakatu epostaz bidali dizugun loturan zure kontua egiaztatzeko"; "Scene.ConfirmEmail.Title" = "Eta azkenik..."; -"Scene.Discovery.Intro" = "These are the posts gaining traction in your corner of Mastodon."; -"Scene.Discovery.Tabs.Community" = "Community"; +"Scene.Discovery.Intro" = "Hauek dira zure Mastodon txokoan beraien lekua hartzen ari diren argitalpenak."; +"Scene.Discovery.Tabs.Community" = "Komunitatea"; "Scene.Discovery.Tabs.ForYou" = "Zuretzat"; "Scene.Discovery.Tabs.Hashtags" = "Traolak"; "Scene.Discovery.Tabs.News" = "Albisteak"; @@ -223,21 +242,30 @@ Mastodonera igo."; "Scene.Familiarfollowers.Title" = "Followers you familiar"; "Scene.Favorite.Title" = "Zure gogokoak"; "Scene.FavoritedBy.Title" = "Favorited By"; +"Scene.FollowedTags.Actions.Follow" = "Follow"; +"Scene.FollowedTags.Actions.Unfollow" = "Unfollow"; +"Scene.FollowedTags.Header.Participants" = "participants"; +"Scene.FollowedTags.Header.Posts" = "posts"; +"Scene.FollowedTags.Header.PostsToday" = "posts today"; +"Scene.FollowedTags.Title" = "Followed Tags"; "Scene.Follower.Footer" = "Beste zerbitzarietako jarraitzaileak ez dira bistaratzen."; -"Scene.Follower.Title" = "follower"; +"Scene.Follower.Title" = "jarraitzaile"; "Scene.Following.Footer" = "Beste zerbitzarietan jarraitutakoak ez dira bistaratzen."; -"Scene.Following.Title" = "following"; +"Scene.Following.Title" = "jarraitzen"; "Scene.HomeTimeline.NavigationBarState.Accessibility.LogoHint" = "Tap to scroll to top and tap again to previous location"; -"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoLabel" = "Logo Button"; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoLabel" = "Logo botoia"; "Scene.HomeTimeline.NavigationBarState.NewPosts" = "Ikusi bidal. berriak"; "Scene.HomeTimeline.NavigationBarState.Offline" = "Konexio gabe"; "Scene.HomeTimeline.NavigationBarState.Published" = "Argitaratua!"; "Scene.HomeTimeline.NavigationBarState.Publishing" = "Bidalketa argitaratzen..."; "Scene.HomeTimeline.Title" = "Hasiera"; -"Scene.Notification.FollowRequest.Accept" = "Accept"; -"Scene.Notification.FollowRequest.Accepted" = "Accepted"; -"Scene.Notification.FollowRequest.Reject" = "reject"; -"Scene.Notification.FollowRequest.Rejected" = "Rejected"; +"Scene.Login.ServerSearchField.Placeholder" = "Enter URL or search for your server"; +"Scene.Login.Subtitle" = "Log you in on the server you created your account on."; +"Scene.Login.Title" = "Ongi etorri berriro ere"; +"Scene.Notification.FollowRequest.Accept" = "Onartu"; +"Scene.Notification.FollowRequest.Accepted" = "Onartuta"; +"Scene.Notification.FollowRequest.Reject" = "ukatu"; +"Scene.Notification.FollowRequest.Rejected" = "Ukatua"; "Scene.Notification.Keyobard.ShowEverything" = "Erakutsi guztia"; "Scene.Notification.Keyobard.ShowMentions" = "Erakutsi aipamenak"; "Scene.Notification.NotificationDescription.FavoritedYourPost" = "(e)k zure bidalketa gogoko du"; @@ -259,17 +287,20 @@ Mastodonera igo."; "Scene.Profile.Dashboard.Following" = "jarraitzen"; "Scene.Profile.Dashboard.Posts" = "bidalketa"; "Scene.Profile.Fields.AddRow" = "Gehitu errenkada"; +"Scene.Profile.Fields.Joined" = "Joined"; "Scene.Profile.Fields.Placeholder.Content" = "Edukia"; "Scene.Profile.Fields.Placeholder.Label" = "Etiketa"; -"Scene.Profile.Header.FollowsYou" = "Follows You"; +"Scene.Profile.Fields.Verified.Long" = "Esteka honen jabetzaren egiaztaketa data: %@"; +"Scene.Profile.Fields.Verified.Short" = "Verified on %@"; +"Scene.Profile.Header.FollowsYou" = "Jarraitzen zaitu"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Message" = "Berretsi %@ blokeatzea"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title" = "Blokeatu kontua"; -"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Message" = "Confirm to hide reblogs"; -"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Title" = "Hide Reblogs"; +"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Message" = "Berretsi birbidalketak ezkutatzea"; +"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Title" = "Ezkutatu bultzadak"; "Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Message" = "Berretsi %@ mututzea"; "Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Title" = "Mututu kontua"; -"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Message" = "Confirm to show reblogs"; -"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Title" = "Show Reblogs"; +"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Message" = "Berretsi birbidalketak ikustea"; +"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Title" = "Ikusi bultzadak"; "Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Message" = "Berretsi %@ desblokeatzea"; "Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Title" = "Desblokeatu kontua"; "Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.Message" = "Berretsi %@ desmututzea"; @@ -312,7 +343,7 @@ Mastodonera igo."; "Scene.Register.Input.Password.Require" = "Zure pasahitzak izan behar ditu gutxienez:"; "Scene.Register.Input.Username.DuplicatePrompt" = "Erabiltzaile-izen hau hartuta dago."; "Scene.Register.Input.Username.Placeholder" = "erabiltzaile-izena"; -"Scene.Register.LetsGetYouSetUpOnDomain" = "Let’s get you set up on %@"; +"Scene.Register.LetsGetYouSetUpOnDomain" = "%@ zerbitzariko kontua prestatuko dizugu"; "Scene.Register.Title" = "Hitz egin iezaguzu zuri buruz."; "Scene.Report.Content1" = "Salaketan beste bidalketarik gehitu nahi duzu?"; "Scene.Report.Content2" = "Moderatzaileek besterik jakin behar dute salaketa honi buruz?"; @@ -322,38 +353,38 @@ Mastodonera igo."; "Scene.Report.SkipToSend" = "Bidali iruzkinik gabe"; "Scene.Report.Step1" = "1. urratsa 2tik"; "Scene.Report.Step2" = "2. urratsa 2tik"; -"Scene.Report.StepFinal.BlockUser" = "Block %@"; -"Scene.Report.StepFinal.DontWantToSeeThis" = "Don’t want to see this?"; -"Scene.Report.StepFinal.MuteUser" = "Mute %@"; -"Scene.Report.StepFinal.TheyWillNoLongerBeAbleToFollowOrSeeYourPostsButTheyCanSeeIfTheyveBeenBlocked" = "They will no longer be able to follow or see your posts, but they can see if they’ve been blocked."; -"Scene.Report.StepFinal.Unfollow" = "Unfollow"; -"Scene.Report.StepFinal.UnfollowUser" = "Unfollow %@"; +"Scene.Report.StepFinal.BlockUser" = "Blokeatu %@"; +"Scene.Report.StepFinal.DontWantToSeeThis" = "Ez duzu hau ikusi nahi?"; +"Scene.Report.StepFinal.MuteUser" = "Mututu %@"; +"Scene.Report.StepFinal.TheyWillNoLongerBeAbleToFollowOrSeeYourPostsButTheyCanSeeIfTheyveBeenBlocked" = "Ezin izango dituzte zure bidalketak jarraitu edo ikusi, baina blokeatuta dauden ikusi ahal izango dute."; +"Scene.Report.StepFinal.Unfollow" = "Utzi jarraitzeari"; +"Scene.Report.StepFinal.UnfollowUser" = "%@ jarraitzeari utzi"; "Scene.Report.StepFinal.Unfollowed" = "Unfollowed"; -"Scene.Report.StepFinal.WhenYouSeeSomethingYouDontLikeOnMastodonYouCanRemoveThePersonFromYourExperience." = "When you see something you don’t like on Mastodon, you can remove the person from your experience."; -"Scene.Report.StepFinal.WhileWeReviewThisYouCanTakeActionAgainstUser" = "While we review this, you can take action against %@"; -"Scene.Report.StepFinal.YouWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted" = "You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted."; -"Scene.Report.StepFour.IsThereAnythingElseWeShouldKnow" = "Is there anything else we should know?"; -"Scene.Report.StepFour.Step4Of4" = "Step 4 of 4"; -"Scene.Report.StepOne.IDontLikeIt" = "I don’t like it"; -"Scene.Report.StepOne.ItIsNotSomethingYouWantToSee" = "It is not something you want to see"; -"Scene.Report.StepOne.ItViolatesServerRules" = "It violates server rules"; -"Scene.Report.StepOne.ItsSomethingElse" = "It’s something else"; -"Scene.Report.StepOne.ItsSpam" = "It’s spam"; -"Scene.Report.StepOne.MaliciousLinksFakeEngagementOrRepetetiveReplies" = "Malicious links, fake engagement, or repetetive replies"; -"Scene.Report.StepOne.SelectTheBestMatch" = "Select the best match"; -"Scene.Report.StepOne.Step1Of4" = "Step 1 of 4"; -"Scene.Report.StepOne.TheIssueDoesNotFitIntoOtherCategories" = "The issue does not fit into other categories"; -"Scene.Report.StepOne.WhatsWrongWithThisAccount" = "What's wrong with this account?"; -"Scene.Report.StepOne.WhatsWrongWithThisPost" = "What's wrong with this post?"; -"Scene.Report.StepOne.WhatsWrongWithThisUsername" = "What's wrong with %@?"; -"Scene.Report.StepOne.YouAreAwareThatItBreaksSpecificRules" = "You are aware that it breaks specific rules"; -"Scene.Report.StepThree.AreThereAnyPostsThatBackUpThisReport" = "Are there any posts that back up this report?"; -"Scene.Report.StepThree.SelectAllThatApply" = "Select all that apply"; -"Scene.Report.StepThree.Step3Of4" = "Step 3 of 4"; +"Scene.Report.StepFinal.WhenYouSeeSomethingYouDontLikeOnMastodonYouCanRemoveThePersonFromYourExperience." = "Mastodonen gustuko ez duzun zerbait ikusten duzunean, zure esperientziatik atera dezakezu pertsona hori."; +"Scene.Report.StepFinal.WhileWeReviewThisYouCanTakeActionAgainstUser" = "Hau berrikusten dugun bitartean, %@ erabiltzailearen aurkako neurriak hartu ditzakezu"; +"Scene.Report.StepFinal.YouWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted" = "Ez dituzu bere bidalketa eta birbidalketak zure hasierako jarioan ikusiko. Ez dute jakingo isilarazi dituztenik."; +"Scene.Report.StepFour.IsThereAnythingElseWeShouldKnow" = "Beste zerbait jakin beharko genuke?"; +"Scene.Report.StepFour.Step4Of4" = "4. urratsa 4tik"; +"Scene.Report.StepOne.IDontLikeIt" = "Ez dut gustukoa"; +"Scene.Report.StepOne.ItIsNotSomethingYouWantToSee" = "Ikusi nahi ez dudan zerbait da"; +"Scene.Report.StepOne.ItViolatesServerRules" = "Zerbitzariaren arauak hausten ditu"; +"Scene.Report.StepOne.ItsSomethingElse" = "Beste zerbait da"; +"Scene.Report.StepOne.ItsSpam" = "Spama da"; +"Scene.Report.StepOne.MaliciousLinksFakeEngagementOrRepetetiveReplies" = "Esteka maltzurrak, gezurrezko elkarrekintzak edo erantzun errepikakorrak"; +"Scene.Report.StepOne.SelectTheBestMatch" = "Aukeratu egokiena"; +"Scene.Report.StepOne.Step1Of4" = "1. urratsa 4tik"; +"Scene.Report.StepOne.TheIssueDoesNotFitIntoOtherCategories" = "Arazoa ezin da beste kategorietan sailkatu"; +"Scene.Report.StepOne.WhatsWrongWithThisAccount" = "Zer du txarra kontu honek?"; +"Scene.Report.StepOne.WhatsWrongWithThisPost" = "Zer du txarra argitalpen honek?"; +"Scene.Report.StepOne.WhatsWrongWithThisUsername" = "Zer du txarra %@?"; +"Scene.Report.StepOne.YouAreAwareThatItBreaksSpecificRules" = "Arau zehatzak urratzen dituela badakizu"; +"Scene.Report.StepThree.AreThereAnyPostsThatBackUpThisReport" = "Salaketa hau babesten duen bidalketarik badago?"; +"Scene.Report.StepThree.SelectAllThatApply" = "Hautatu dagozkion guztiak"; +"Scene.Report.StepThree.Step3Of4" = "3. urratsa 4tik"; "Scene.Report.StepTwo.IJustDon’tLikeIt" = "I just don’t like it"; -"Scene.Report.StepTwo.SelectAllThatApply" = "Select all that apply"; -"Scene.Report.StepTwo.Step2Of4" = "Step 2 of 4"; -"Scene.Report.StepTwo.WhichRulesAreBeingViolated" = "Which rules are being violated?"; +"Scene.Report.StepTwo.SelectAllThatApply" = "Hautatu dagozkion guztiak"; +"Scene.Report.StepTwo.Step2Of4" = "2. urratsa 4tik"; +"Scene.Report.StepTwo.WhichRulesAreBeingViolated" = "Ze arau hautsi ditu?"; "Scene.Report.TextPlaceholder" = "Idatzi edo itsatsi iruzkin gehigarriak"; "Scene.Report.Title" = "Salatu %@"; "Scene.Report.TitleReport" = "Salatu"; @@ -393,13 +424,11 @@ Mastodonera igo."; "Scene.ServerPicker.EmptyState.BadNetwork" = "Arazoren bat egon da datuak kargatzean. Egiaztatu zure Interneteko konexioa."; "Scene.ServerPicker.EmptyState.FindingServers" = "Erabilgarri dauden zerbitzariak bilatzen..."; "Scene.ServerPicker.EmptyState.NoResults" = "Emaitzarik ez"; -"Scene.ServerPicker.Input.Placeholder" = "Bilatu zerbitzari bat edo sortu zurea..."; -"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Search servers or enter URL"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Search communities or enter URL"; "Scene.ServerPicker.Label.Category" = "KATEGORIA"; "Scene.ServerPicker.Label.Language" = "HIZKUNTZA"; "Scene.ServerPicker.Label.Users" = "ERABILTZAILEAK"; -"Scene.ServerPicker.Subtitle" = "Aukeratu komunitate bat zure interes edo lurraldearen arabera, edo erabilera orokorreko bat."; -"Scene.ServerPicker.SubtitleExtend" = "Aukeratu komunitate bat zure interes edo lurraldearen arabera, edo erabilera orokorreko bat. Komunitate bakoitza erakunde edo norbanako independente batek kudeatzen du."; +"Scene.ServerPicker.Subtitle" = "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers."; "Scene.ServerPicker.Title" = "Aukeratu zerbitzari bat, edozein zerbitzari."; "Scene.ServerRules.Button.Confirm" = "Ados nago"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/eu.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/eu.lproj/Localizable.stringsdict index 0159a7da9..404deebd3 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/eu.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/eu.lproj/Localizable.stringsdict @@ -50,10 +50,26 @@ %ld karaktere + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ left + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 character + other + %ld karaktere + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey - %#@names@%#@count_mutual@ + %#@names@: "%#@count_mutual@ names one diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/fi.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/fi.lproj/Localizable.strings index 11259ace5..de4bf3101 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/fi.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/fi.lproj/Localizable.strings @@ -22,6 +22,9 @@ Tarkista internet-yhteytesi."; "Common.Alerts.SignOut.Message" = "Haluatko varmasti kirjautua ulos?"; "Common.Alerts.SignOut.Title" = "Kirjaudu ulos"; "Common.Alerts.SignUpFailure.Title" = "Rekisteröinti epäonnistui"; +"Common.Alerts.TranslationFailed.Button" = "OK"; +"Common.Alerts.TranslationFailed.Message" = "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported."; +"Common.Alerts.TranslationFailed.Title" = "Note"; "Common.Alerts.VoteFailure.PollEnded" = "Kysely on päättynyt"; "Common.Alerts.VoteFailure.Title" = "Vote Failure"; "Common.Controls.Actions.Add" = "Lisää"; @@ -31,6 +34,7 @@ Tarkista internet-yhteytesi."; "Common.Controls.Actions.Compose" = "Koosta"; "Common.Controls.Actions.Confirm" = "Vahvista"; "Common.Controls.Actions.Continue" = "Jatka"; +"Common.Controls.Actions.Copy" = "Copy"; "Common.Controls.Actions.CopyPhoto" = "Kopioi kuva"; "Common.Controls.Actions.Delete" = "Poista"; "Common.Controls.Actions.Discard" = "Hylkää"; @@ -55,10 +59,12 @@ Tarkista internet-yhteytesi."; "Common.Controls.Actions.Share" = "Jaa"; "Common.Controls.Actions.SharePost" = "Jaa julkaisu"; "Common.Controls.Actions.ShareUser" = "Jaa %@"; -"Common.Controls.Actions.SignIn" = "Kirjaudu sisään"; -"Common.Controls.Actions.SignUp" = "Rekisteröidy"; +"Common.Controls.Actions.SignIn" = "Log in"; +"Common.Controls.Actions.SignUp" = "Create account"; "Common.Controls.Actions.Skip" = "Ohita"; "Common.Controls.Actions.TakePhoto" = "Ota kuva"; +"Common.Controls.Actions.TranslatePost.Title" = "Translate from %@"; +"Common.Controls.Actions.TranslatePost.UnknownLanguage" = "Unknown"; "Common.Controls.Actions.TryAgain" = "Yritä uudelleen"; "Common.Controls.Actions.UnblockDomain" = "Poista esto %@"; "Common.Controls.Friendship.Block" = "Estä"; @@ -100,6 +106,7 @@ Tarkista internet-yhteytesi."; "Common.Controls.Status.Actions.Menu" = "Valikko"; "Common.Controls.Status.Actions.Reblog" = "Jaa edelleen"; "Common.Controls.Status.Actions.Reply" = "Vastaa"; +"Common.Controls.Status.Actions.ShareLinkInPost" = "Share Link in Post"; "Common.Controls.Status.Actions.ShowGif" = "Show GIF"; "Common.Controls.Status.Actions.ShowImage" = "Show image"; "Common.Controls.Status.Actions.ShowVideoPlayer" = "Show video player"; @@ -107,6 +114,8 @@ Tarkista internet-yhteytesi."; "Common.Controls.Status.Actions.Unfavorite" = "Unfavorite"; "Common.Controls.Status.Actions.Unreblog" = "Peru edelleen jako"; "Common.Controls.Status.ContentWarning" = "Sisältövaroitus"; +"Common.Controls.Status.LinkViaUser" = "%@ via %@"; +"Common.Controls.Status.LoadEmbed" = "Load Embed"; "Common.Controls.Status.MediaContentWarning" = "Napauta mistä tahansa paljastaaksesi"; "Common.Controls.Status.MetaEntity.Email" = "Email address: %@"; "Common.Controls.Status.MetaEntity.Hashtag" = "Hashtag: %@"; @@ -124,6 +133,10 @@ Tarkista internet-yhteytesi."; "Common.Controls.Status.Tag.Mention" = "Mention"; "Common.Controls.Status.Tag.Url" = "URL"; "Common.Controls.Status.TapToReveal" = "Tap to reveal"; +"Common.Controls.Status.Translation.ShowOriginal" = "Shown Original"; +"Common.Controls.Status.Translation.TranslatedFrom" = "Translated from %@ using %@"; +"Common.Controls.Status.Translation.UnknownLanguage" = "Unknown"; +"Common.Controls.Status.Translation.UnknownProvider" = "Unknown"; "Common.Controls.Status.UserReblogged" = "%@ jakoi edelleen"; "Common.Controls.Status.UserRepliedTo" = "Vastasi %@:lle"; "Common.Controls.Status.Visibility.Direct" = "Only mentioned user can see this post."; @@ -131,9 +144,9 @@ Tarkista internet-yhteytesi."; "Common.Controls.Status.Visibility.PrivateFromMe" = "Only my followers can see this post."; "Common.Controls.Status.Visibility.Unlisted" = "Everyone can see this post but not display in the public timeline."; "Common.Controls.Tabs.Home" = "Koti"; -"Common.Controls.Tabs.Notification" = "Ilmoitus"; +"Common.Controls.Tabs.Notifications" = "Notifications"; "Common.Controls.Tabs.Profile" = "Profiili"; -"Common.Controls.Tabs.Search" = "Haku"; +"Common.Controls.Tabs.SearchAndExplore" = "Search and Explore"; "Common.Controls.Timeline.Filtered" = "Suodatettu"; "Common.Controls.Timeline.Header.BlockedWarning" = "Et voi tarkastella tämän tilin profiilia ennen kuin hän poistaa eston."; @@ -161,16 +174,20 @@ Profiilisi näyttää tältä hänelle."; "Scene.Compose.Accessibility.CustomEmojiPicker" = "Mukautettu emojivalitsin"; "Scene.Compose.Accessibility.DisableContentWarning" = "Poista sisältövaroitus käytöstä"; "Scene.Compose.Accessibility.EnableContentWarning" = "Ota sisältövaroitus käyttöön"; +"Scene.Compose.Accessibility.PostOptions" = "Post Options"; "Scene.Compose.Accessibility.PostVisibilityMenu" = "Julkaisun näkyvyysvalikko"; +"Scene.Compose.Accessibility.PostingAs" = "Posting as %@"; "Scene.Compose.Accessibility.RemovePoll" = "Poista kysely"; "Scene.Compose.Attachment.AttachmentBroken" = "This %@ is broken and can’t be uploaded to Mastodon."; "Scene.Compose.Attachment.AttachmentTooLarge" = "Attachment too large"; -"Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "Can not regonize this media attachment"; +"Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "Can not recognize this media attachment"; +"Scene.Compose.Attachment.CompressingState" = "Compressing..."; "Scene.Compose.Attachment.DescriptionPhoto" = "Kuvaile kuva näkövammaisille..."; "Scene.Compose.Attachment.DescriptionVideo" = "Kuvaile video näkövammaisille..."; "Scene.Compose.Attachment.LoadFailed" = "Load Failed"; "Scene.Compose.Attachment.Photo" = "kuva"; +"Scene.Compose.Attachment.ServerProcessingState" = "Server Processing..."; "Scene.Compose.Attachment.UploadFailed" = "Upload Failed"; "Scene.Compose.Attachment.Video" = "video"; "Scene.Compose.AutoComplete.SpaceToAdd" = "Space to add"; @@ -192,6 +209,8 @@ uploaded to Mastodon."; "Scene.Compose.Poll.OptionNumber" = "Vaihtoehto %ld"; "Scene.Compose.Poll.SevenDays" = "7 päivää"; "Scene.Compose.Poll.SixHours" = "6 tuntia"; +"Scene.Compose.Poll.ThePollHasEmptyOption" = "The poll has empty option"; +"Scene.Compose.Poll.ThePollIsInvalid" = "The poll is invalid"; "Scene.Compose.Poll.ThirtyMinutes" = "30 minuuttia"; "Scene.Compose.Poll.ThreeDays" = "3 päivää"; "Scene.Compose.ReplyingToUser" = "vastaamassa tilille %@"; @@ -223,6 +242,12 @@ uploaded to Mastodon."; "Scene.Familiarfollowers.Title" = "Followers you familiar"; "Scene.Favorite.Title" = "Omat suosikit"; "Scene.FavoritedBy.Title" = "Favorited By"; +"Scene.FollowedTags.Actions.Follow" = "Follow"; +"Scene.FollowedTags.Actions.Unfollow" = "Unfollow"; +"Scene.FollowedTags.Header.Participants" = "participants"; +"Scene.FollowedTags.Header.Posts" = "posts"; +"Scene.FollowedTags.Header.PostsToday" = "posts today"; +"Scene.FollowedTags.Title" = "Followed Tags"; "Scene.Follower.Footer" = "Seuraajia muilta palvelimilta ei näytetä."; "Scene.Follower.Title" = "follower"; "Scene.Following.Footer" = "Seurauksia muilta palvelimilta ei näytetä."; @@ -234,6 +259,9 @@ uploaded to Mastodon."; "Scene.HomeTimeline.NavigationBarState.Published" = "Julkaistu!"; "Scene.HomeTimeline.NavigationBarState.Publishing" = "Julkaistaan julkaisua..."; "Scene.HomeTimeline.Title" = "Koti"; +"Scene.Login.ServerSearchField.Placeholder" = "Enter URL or search for your server"; +"Scene.Login.Subtitle" = "Log you in on the server you created your account on."; +"Scene.Login.Title" = "Welcome back"; "Scene.Notification.FollowRequest.Accept" = "Accept"; "Scene.Notification.FollowRequest.Accepted" = "Accepted"; "Scene.Notification.FollowRequest.Reject" = "reject"; @@ -259,8 +287,11 @@ uploaded to Mastodon."; "Scene.Profile.Dashboard.Following" = "seurataan"; "Scene.Profile.Dashboard.Posts" = "julkaisut"; "Scene.Profile.Fields.AddRow" = "Lisää rivi"; +"Scene.Profile.Fields.Joined" = "Joined"; "Scene.Profile.Fields.Placeholder.Content" = "Sisältö"; "Scene.Profile.Fields.Placeholder.Label" = "Nimi"; +"Scene.Profile.Fields.Verified.Long" = "Ownership of this link was checked on %@"; +"Scene.Profile.Fields.Verified.Short" = "Verified on %@"; "Scene.Profile.Header.FollowsYou" = "Follows You"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Message" = "Confirm to block %@"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title" = "Block Account"; @@ -393,13 +424,11 @@ uploaded to Mastodon."; "Scene.ServerPicker.EmptyState.BadNetwork" = "Jokin meni pieleen dataa ladatessa. Tarkista internet-yhteytesi."; "Scene.ServerPicker.EmptyState.FindingServers" = "Etsistään saatavilla olevia palvelimia..."; "Scene.ServerPicker.EmptyState.NoResults" = "Ei hakutuloksia"; -"Scene.ServerPicker.Input.Placeholder" = "Etsi palvelin tai liity omaan..."; -"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Search servers or enter URL"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Search communities or enter URL"; "Scene.ServerPicker.Label.Category" = "KATEGORIA"; "Scene.ServerPicker.Label.Language" = "KIELI"; "Scene.ServerPicker.Label.Users" = "TILIÄ"; -"Scene.ServerPicker.Subtitle" = "Pick a server based on your interests, region, or a general purpose one."; -"Scene.ServerPicker.SubtitleExtend" = "Pick a server based on your interests, region, or a general purpose one. Each server is operated by an entirely independent organization or individual."; +"Scene.ServerPicker.Subtitle" = "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers."; "Scene.ServerPicker.Title" = "Valitse palvelin, mikä tahansa palvelin."; "Scene.ServerRules.Button.Confirm" = "Hyväksyn"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/fi.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/fi.lproj/Localizable.stringsdict index 8048edf2d..ccfee35c9 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/fi.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/fi.lproj/Localizable.stringsdict @@ -50,6 +50,22 @@ %ld merkkiä + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ left + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 character + other + %ld characters + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/fr.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/fr.lproj/Localizable.strings index 931219c21..0bc619b34 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/fr.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/fr.lproj/Localizable.strings @@ -22,6 +22,9 @@ Veuillez vérifier votre accès à Internet."; "Common.Alerts.SignOut.Message" = "Voulez-vous vraiment vous déconnecter ?"; "Common.Alerts.SignOut.Title" = "Se déconnecter"; "Common.Alerts.SignUpFailure.Title" = "Échec de l'inscription"; +"Common.Alerts.TranslationFailed.Button" = "OK"; +"Common.Alerts.TranslationFailed.Message" = "La traduction a échoué. Peut-être que l'administrateur n'a pas activé les traductions sur ce serveur ou que ce serveur utilise une ancienne version de Mastodon où les traductions ne sont pas encore prises en charge."; +"Common.Alerts.TranslationFailed.Title" = "Note"; "Common.Alerts.VoteFailure.PollEnded" = "Le sondage est terminé"; "Common.Alerts.VoteFailure.Title" = "Échec du vote"; "Common.Controls.Actions.Add" = "Ajouter"; @@ -31,6 +34,7 @@ Veuillez vérifier votre accès à Internet."; "Common.Controls.Actions.Compose" = "Rédiger"; "Common.Controls.Actions.Confirm" = "Confirmer"; "Common.Controls.Actions.Continue" = "Continuer"; +"Common.Controls.Actions.Copy" = "Copier"; "Common.Controls.Actions.CopyPhoto" = "Copier la photo"; "Common.Controls.Actions.Delete" = "Supprimer"; "Common.Controls.Actions.Discard" = "Abandonner"; @@ -59,6 +63,8 @@ Veuillez vérifier votre accès à Internet."; "Common.Controls.Actions.SignUp" = "Créer un compte"; "Common.Controls.Actions.Skip" = "Passer"; "Common.Controls.Actions.TakePhoto" = "Prendre une photo"; +"Common.Controls.Actions.TranslatePost.Title" = "Traduit depuis %@"; +"Common.Controls.Actions.TranslatePost.UnknownLanguage" = "Inconnu"; "Common.Controls.Actions.TryAgain" = "Réessayer"; "Common.Controls.Actions.UnblockDomain" = "Débloquer %@"; "Common.Controls.Friendship.Block" = "Bloquer"; @@ -100,6 +106,7 @@ Veuillez vérifier votre accès à Internet."; "Common.Controls.Status.Actions.Menu" = "Menu"; "Common.Controls.Status.Actions.Reblog" = "Rebloguer"; "Common.Controls.Status.Actions.Reply" = "Répondre"; +"Common.Controls.Status.Actions.ShareLinkInPost" = "Partager le lien dans le message"; "Common.Controls.Status.Actions.ShowGif" = "Afficher le GIF"; "Common.Controls.Status.Actions.ShowImage" = "Afficher l’image"; "Common.Controls.Status.Actions.ShowVideoPlayer" = "Afficher le lecteur vidéo"; @@ -107,6 +114,8 @@ Veuillez vérifier votre accès à Internet."; "Common.Controls.Status.Actions.Unfavorite" = "Retirer des favoris"; "Common.Controls.Status.Actions.Unreblog" = "Annuler le reblog"; "Common.Controls.Status.ContentWarning" = "Avertissement de contenu"; +"Common.Controls.Status.LinkViaUser" = "%@ via %@"; +"Common.Controls.Status.LoadEmbed" = "Charger l'intégration"; "Common.Controls.Status.MediaContentWarning" = "Tapotez n’importe où pour révéler la publication"; "Common.Controls.Status.MetaEntity.Email" = "Adresse e-mail : %@"; "Common.Controls.Status.MetaEntity.Hashtag" = "Hashtag : %@"; @@ -124,6 +133,10 @@ Veuillez vérifier votre accès à Internet."; "Common.Controls.Status.Tag.Mention" = "Mention"; "Common.Controls.Status.Tag.Url" = "URL"; "Common.Controls.Status.TapToReveal" = "Appuyer pour afficher"; +"Common.Controls.Status.Translation.ShowOriginal" = "Afficher l’original"; +"Common.Controls.Status.Translation.TranslatedFrom" = "Translated from %@ using %@"; +"Common.Controls.Status.Translation.UnknownLanguage" = "Inconnu"; +"Common.Controls.Status.Translation.UnknownProvider" = "Unknown"; "Common.Controls.Status.UserReblogged" = "%@ a reblogué"; "Common.Controls.Status.UserRepliedTo" = "À répondu à %@"; "Common.Controls.Status.Visibility.Direct" = "Seul·e l’utilisateur·rice mentionnée peut voir ce message."; @@ -131,9 +144,9 @@ Veuillez vérifier votre accès à Internet."; "Common.Controls.Status.Visibility.PrivateFromMe" = "Seul·e·s mes abonné·e·s peuvent voir ce message."; "Common.Controls.Status.Visibility.Unlisted" = "Tout le monde peut voir ce message mais ne sera pas affiché sur le fil public."; "Common.Controls.Tabs.Home" = "Accueil"; -"Common.Controls.Tabs.Notification" = "Notification"; +"Common.Controls.Tabs.Notifications" = "Notifications"; "Common.Controls.Tabs.Profile" = "Profil"; -"Common.Controls.Tabs.Search" = "Rechercher"; +"Common.Controls.Tabs.SearchAndExplore" = "Rechercher et explorer"; "Common.Controls.Timeline.Filtered" = "Filtré"; "Common.Controls.Timeline.Header.BlockedWarning" = "Vous ne pouvez pas voir le profil de cet utilisateur tant qu'il ne vous aura pas débloqué."; @@ -161,17 +174,21 @@ Votre profil ressemble à ça pour lui."; "Scene.Compose.Accessibility.CustomEmojiPicker" = "Sélecteur d’émojis personnalisés"; "Scene.Compose.Accessibility.DisableContentWarning" = "Désactiver l'avertissement de contenu"; "Scene.Compose.Accessibility.EnableContentWarning" = "Basculer l’avertissement de contenu"; +"Scene.Compose.Accessibility.PostOptions" = "Options de publication"; "Scene.Compose.Accessibility.PostVisibilityMenu" = "Menu de Visibilité de la publication"; +"Scene.Compose.Accessibility.PostingAs" = "Publié en tant que %@"; "Scene.Compose.Accessibility.RemovePoll" = "Retirer le sondage"; "Scene.Compose.Attachment.AttachmentBroken" = "Ce %@ est brisé et ne peut pas être téléversé sur Mastodon."; -"Scene.Compose.Attachment.AttachmentTooLarge" = "Attachment too large"; -"Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "Can not regonize this media attachment"; +"Scene.Compose.Attachment.AttachmentTooLarge" = "La pièce jointe est trop volumineuse"; +"Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "Impossible de reconnaître cette pièce jointe"; +"Scene.Compose.Attachment.CompressingState" = "Compression..."; "Scene.Compose.Attachment.DescriptionPhoto" = "Décrire cette photo pour les personnes malvoyantes..."; "Scene.Compose.Attachment.DescriptionVideo" = "Décrire cette vidéo pour les personnes malvoyantes..."; -"Scene.Compose.Attachment.LoadFailed" = "Load Failed"; +"Scene.Compose.Attachment.LoadFailed" = "Échec du chargement"; "Scene.Compose.Attachment.Photo" = "photo"; -"Scene.Compose.Attachment.UploadFailed" = "Upload Failed"; +"Scene.Compose.Attachment.ServerProcessingState" = "Traitement du serveur..."; +"Scene.Compose.Attachment.UploadFailed" = "Échec de l’envoi"; "Scene.Compose.Attachment.Video" = "vidéo"; "Scene.Compose.AutoComplete.SpaceToAdd" = "Espace à ajouter"; "Scene.Compose.ComposeAction" = "Publier"; @@ -192,6 +209,8 @@ téléversé sur Mastodon."; "Scene.Compose.Poll.OptionNumber" = "Option %ld"; "Scene.Compose.Poll.SevenDays" = "7 jour"; "Scene.Compose.Poll.SixHours" = "6 Heures"; +"Scene.Compose.Poll.ThePollHasEmptyOption" = "Le sondage n'a pas d'options"; +"Scene.Compose.Poll.ThePollIsInvalid" = "Le sondage est invalide"; "Scene.Compose.Poll.ThirtyMinutes" = "30 minutes"; "Scene.Compose.Poll.ThreeDays" = "3 jour"; "Scene.Compose.ReplyingToUser" = "répondre à %@"; @@ -223,6 +242,12 @@ téléversé sur Mastodon."; "Scene.Familiarfollowers.Title" = "Abonné·e·s que vous connaissez"; "Scene.Favorite.Title" = "Vos favoris"; "Scene.FavoritedBy.Title" = "Favoris par"; +"Scene.FollowedTags.Actions.Follow" = "Suivre"; +"Scene.FollowedTags.Actions.Unfollow" = "Ne plus suivre"; +"Scene.FollowedTags.Header.Participants" = "participants"; +"Scene.FollowedTags.Header.Posts" = "messages"; +"Scene.FollowedTags.Header.PostsToday" = "messages aujourd'hui"; +"Scene.FollowedTags.Title" = "Tags suivis"; "Scene.Follower.Footer" = "Les abonné·e·s issus des autres serveurs ne sont pas affiché·e·s."; "Scene.Follower.Title" = "abonné·e"; "Scene.Following.Footer" = "Les abonnés issus des autres serveurs ne sont pas affichés."; @@ -234,6 +259,9 @@ téléversé sur Mastodon."; "Scene.HomeTimeline.NavigationBarState.Published" = "Publié!"; "Scene.HomeTimeline.NavigationBarState.Publishing" = "Publication en cours ..."; "Scene.HomeTimeline.Title" = "Accueil"; +"Scene.Login.ServerSearchField.Placeholder" = "Entrez l'URL ou recherchez votre serveur"; +"Scene.Login.Subtitle" = "Connectez-vous sur le serveur sur lequel vous avez créé votre compte."; +"Scene.Login.Title" = "Content de vous revoir"; "Scene.Notification.FollowRequest.Accept" = "Accepter"; "Scene.Notification.FollowRequest.Accepted" = "Accepté"; "Scene.Notification.FollowRequest.Reject" = "rejeter"; @@ -259,8 +287,11 @@ téléversé sur Mastodon."; "Scene.Profile.Dashboard.Following" = "abonnements"; "Scene.Profile.Dashboard.Posts" = "publications"; "Scene.Profile.Fields.AddRow" = "Ajouter une rangée"; +"Scene.Profile.Fields.Joined" = "Ici depuis"; "Scene.Profile.Fields.Placeholder.Content" = "Contenu"; "Scene.Profile.Fields.Placeholder.Label" = "Étiquette"; +"Scene.Profile.Fields.Verified.Long" = "La propriété de ce lien a été vérifiée le %@"; +"Scene.Profile.Fields.Verified.Short" = "Vérifié le %@"; "Scene.Profile.Header.FollowsYou" = "Vous suit"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Message" = "Confirmer le blocage de %@"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title" = "Bloquer le compte"; @@ -393,13 +424,11 @@ téléversé sur Mastodon."; "Scene.ServerPicker.EmptyState.BadNetwork" = "Une erreur s'est produite lors du chargement des données. Vérifiez votre connexion Internet."; "Scene.ServerPicker.EmptyState.FindingServers" = "Recherche des serveurs disponibles..."; "Scene.ServerPicker.EmptyState.NoResults" = "Aucun résultat"; -"Scene.ServerPicker.Input.Placeholder" = "Trouvez un serveur ou rejoignez le vôtre..."; -"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Rechercher des serveurs ou entrer une URL"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Rechercher parmi les communautés ou renseigner une URL"; "Scene.ServerPicker.Label.Category" = "CATÉGORIE"; "Scene.ServerPicker.Label.Language" = "LANGUE"; "Scene.ServerPicker.Label.Users" = "UTILISATEUR·RICE·S"; -"Scene.ServerPicker.Subtitle" = "Choisissez une communauté en fonction de vos intérêts, de votre région ou de votre objectif général."; -"Scene.ServerPicker.SubtitleExtend" = "Choisissez une communauté basée sur vos intérêts, votre région ou un but général. Chaque communauté est gérée par une organisation ou un individu entièrement indépendant."; +"Scene.ServerPicker.Subtitle" = "Choisissez un serveur basé sur votre région, vos intérêts ou un généraliste. Vous pouvez toujours discuter avec n'importe qui sur Mastodon, indépendamment de vos serveurs."; "Scene.ServerPicker.Title" = "Choisissez un serveur, n'importe quel serveur."; "Scene.ServerRules.Button.Confirm" = "J’accepte"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/fr.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/fr.lproj/Localizable.stringsdict index d9d860a47..4eb068697 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/fr.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/fr.lproj/Localizable.stringsdict @@ -50,6 +50,22 @@ %ld caractères + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ restants + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 caractère + other + %ld caractères + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/gd.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/gd.lproj/Localizable.strings index ce1764eac..e37be3344 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/gd.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/gd.lproj/Localizable.strings @@ -22,6 +22,9 @@ Thoir sùil air a’ cheangal agad ris an eadar-lìon."; "Common.Alerts.SignOut.Message" = "A bheil thu cinnteach gu bheil thu airson clàradh a-mach?"; "Common.Alerts.SignOut.Title" = "Clàraich a-mach"; "Common.Alerts.SignUpFailure.Title" = "Dh’fhàillig leis a’ chlàradh"; +"Common.Alerts.TranslationFailed.Button" = "OK"; +"Common.Alerts.TranslationFailed.Message" = "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported."; +"Common.Alerts.TranslationFailed.Title" = "Note"; "Common.Alerts.VoteFailure.PollEnded" = "Thàinig an cunntas-bheachd gu crìoch"; "Common.Alerts.VoteFailure.Title" = "Dh’fhàillig leis a’ bhòt"; "Common.Controls.Actions.Add" = "Cuir ris"; @@ -31,6 +34,7 @@ Thoir sùil air a’ cheangal agad ris an eadar-lìon."; "Common.Controls.Actions.Compose" = "Sgrìobh"; "Common.Controls.Actions.Confirm" = "Dearbh"; "Common.Controls.Actions.Continue" = "Lean air adhart"; +"Common.Controls.Actions.Copy" = "Copy"; "Common.Controls.Actions.CopyPhoto" = "Dèan lethbhreac dhen dealbh"; "Common.Controls.Actions.Delete" = "Sguab às"; "Common.Controls.Actions.Discard" = "Tilg air falbh"; @@ -56,9 +60,11 @@ Thoir sùil air a’ cheangal agad ris an eadar-lìon."; "Common.Controls.Actions.SharePost" = "Co-roinn am post"; "Common.Controls.Actions.ShareUser" = "Co-roinn %@"; "Common.Controls.Actions.SignIn" = "Clàraich a-steach"; -"Common.Controls.Actions.SignUp" = "Clàraich leinn"; +"Common.Controls.Actions.SignUp" = "Cruthaich cunntas"; "Common.Controls.Actions.Skip" = "Leum thairis air"; "Common.Controls.Actions.TakePhoto" = "Tog dealbh"; +"Common.Controls.Actions.TranslatePost.Title" = "Translate from %@"; +"Common.Controls.Actions.TranslatePost.UnknownLanguage" = "Unknown"; "Common.Controls.Actions.TryAgain" = "Feuch ris a-rithist"; "Common.Controls.Actions.UnblockDomain" = "Dì-bhac %@"; "Common.Controls.Friendship.Block" = "Bac"; @@ -68,13 +74,13 @@ Thoir sùil air a’ cheangal agad ris an eadar-lìon."; "Common.Controls.Friendship.EditInfo" = "Deasaich"; "Common.Controls.Friendship.Follow" = "Lean"; "Common.Controls.Friendship.Following" = "’Ga leantainn"; -"Common.Controls.Friendship.HideReblogs" = "Hide Reblogs"; +"Common.Controls.Friendship.HideReblogs" = "Falaich na brosnachaidhean"; "Common.Controls.Friendship.Mute" = "Mùch"; "Common.Controls.Friendship.MuteUser" = "Mùch %@"; "Common.Controls.Friendship.Muted" = "’Ga mhùchadh"; "Common.Controls.Friendship.Pending" = "Ri dhèiligeadh"; "Common.Controls.Friendship.Request" = "Iarrtas"; -"Common.Controls.Friendship.ShowReblogs" = "Show Reblogs"; +"Common.Controls.Friendship.ShowReblogs" = "Seall na brosnachaidhean"; "Common.Controls.Friendship.Unblock" = "Dì-bhac"; "Common.Controls.Friendship.UnblockUser" = "Dì-bhac %@"; "Common.Controls.Friendship.Unmute" = "Dì-mhùch"; @@ -100,6 +106,7 @@ Thoir sùil air a’ cheangal agad ris an eadar-lìon."; "Common.Controls.Status.Actions.Menu" = "Clàr-taice"; "Common.Controls.Status.Actions.Reblog" = "Brosnaich"; "Common.Controls.Status.Actions.Reply" = "Freagair"; +"Common.Controls.Status.Actions.ShareLinkInPost" = "Share Link in Post"; "Common.Controls.Status.Actions.ShowGif" = "Seall an GIF"; "Common.Controls.Status.Actions.ShowImage" = "Seall an dealbh"; "Common.Controls.Status.Actions.ShowVideoPlayer" = "Seall cluicheadair video"; @@ -107,11 +114,13 @@ Thoir sùil air a’ cheangal agad ris an eadar-lìon."; "Common.Controls.Status.Actions.Unfavorite" = "Thoir air falbh o na h-annsachdan"; "Common.Controls.Status.Actions.Unreblog" = "Na brosnaich tuilleadh"; "Common.Controls.Status.ContentWarning" = "Rabhadh susbainte"; +"Common.Controls.Status.LinkViaUser" = "%@ via %@"; +"Common.Controls.Status.LoadEmbed" = "Load Embed"; "Common.Controls.Status.MediaContentWarning" = "Thoir gnogag àite sam bith gus a nochdadh"; -"Common.Controls.Status.MetaEntity.Email" = "Email address: %@"; -"Common.Controls.Status.MetaEntity.Hashtag" = "Hashtag: %@"; -"Common.Controls.Status.MetaEntity.Mention" = "Show Profile: %@"; -"Common.Controls.Status.MetaEntity.Url" = "Link: %@"; +"Common.Controls.Status.MetaEntity.Email" = "Seòladh puist-d: %@"; +"Common.Controls.Status.MetaEntity.Hashtag" = "Taga hais: %@"; +"Common.Controls.Status.MetaEntity.Mention" = "Seall a’ phròifil: %@"; +"Common.Controls.Status.MetaEntity.Url" = "Ceangal: %@"; "Common.Controls.Status.Poll.Closed" = "Dùinte"; "Common.Controls.Status.Poll.Vote" = "Cuir bhòt"; "Common.Controls.Status.SensitiveContent" = "Susbaint fhrionasach"; @@ -124,6 +133,10 @@ Thoir sùil air a’ cheangal agad ris an eadar-lìon."; "Common.Controls.Status.Tag.Mention" = "Iomradh"; "Common.Controls.Status.Tag.Url" = "URL"; "Common.Controls.Status.TapToReveal" = "Thoir gnogag gus a nochdadh"; +"Common.Controls.Status.Translation.ShowOriginal" = "Shown Original"; +"Common.Controls.Status.Translation.TranslatedFrom" = "Translated from %@ using %@"; +"Common.Controls.Status.Translation.UnknownLanguage" = "Unknown"; +"Common.Controls.Status.Translation.UnknownProvider" = "Unknown"; "Common.Controls.Status.UserReblogged" = "Tha %@ ’ga bhrosnachadh"; "Common.Controls.Status.UserRepliedTo" = "Air %@ fhreagairt"; "Common.Controls.Status.Visibility.Direct" = "Chan fhaic ach an cleachdaiche air an dugadh iomradh am post seo."; @@ -131,9 +144,9 @@ Thoir sùil air a’ cheangal agad ris an eadar-lìon."; "Common.Controls.Status.Visibility.PrivateFromMe" = "Chan fhaic ach an luchd-leantainn agam am post seo."; "Common.Controls.Status.Visibility.Unlisted" = "Chì a h-uile duine am post seo ach cha nochd e air an loidhne-ama phoblach."; "Common.Controls.Tabs.Home" = "Dachaigh"; -"Common.Controls.Tabs.Notification" = "Brath"; +"Common.Controls.Tabs.Notifications" = "Notifications"; "Common.Controls.Tabs.Profile" = "Pròifil"; -"Common.Controls.Tabs.Search" = "Lorg"; +"Common.Controls.Tabs.SearchAndExplore" = "Search and Explore"; "Common.Controls.Timeline.Filtered" = "Criathraichte"; "Common.Controls.Timeline.Header.BlockedWarning" = "Chan fhaic thu pròifil a’ chleachdaiche seo mus dì-bhac iad thu."; @@ -155,23 +168,27 @@ Seo an coltas a th’ air a’ phròifil agad dhaibh-san."; "Scene.AccountList.AddAccount" = "Cuir cunntas ris"; "Scene.AccountList.DismissAccountSwitcher" = "Leig seachad taghadh a’ chunntais"; "Scene.AccountList.TabBarHint" = "A’ phròifil air a taghadh: %@. Thoir gnogag dhùbailte is cùm sìos a ghearradh leum gu cunntas eile"; -"Scene.Bookmark.Title" = "Bookmarks"; +"Scene.Bookmark.Title" = "Comharran-lìn"; "Scene.Compose.Accessibility.AppendAttachment" = "Cuir ceanglachan ris"; "Scene.Compose.Accessibility.AppendPoll" = "Cuir cunntas-bheachd ris"; "Scene.Compose.Accessibility.CustomEmojiPicker" = "Roghnaichear nan Emoji gnàthaichte"; "Scene.Compose.Accessibility.DisableContentWarning" = "Cuir rabhadh susbainte à comas"; "Scene.Compose.Accessibility.EnableContentWarning" = "Cuir rabhadh susbainte an comas"; +"Scene.Compose.Accessibility.PostOptions" = "Roghainnean postaidh"; "Scene.Compose.Accessibility.PostVisibilityMenu" = "Clàr-taice faicsinneachd a’ phuist"; +"Scene.Compose.Accessibility.PostingAs" = "A’ postadh mar %@"; "Scene.Compose.Accessibility.RemovePoll" = "Thoir air falbh an cunntas-bheachd"; "Scene.Compose.Attachment.AttachmentBroken" = "Seo %@ a tha briste is cha ghabh a luchdadh suas gu Mastodon."; -"Scene.Compose.Attachment.AttachmentTooLarge" = "Attachment too large"; -"Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "Can not regonize this media attachment"; +"Scene.Compose.Attachment.AttachmentTooLarge" = "Tha an ceanglachan ro mhòr"; +"Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "Cha do dh’aithnich sinn an ceanglachan meadhain seo"; +"Scene.Compose.Attachment.CompressingState" = "’Ga dhùmhlachadh…"; "Scene.Compose.Attachment.DescriptionPhoto" = "Mìnich an dealbh dhan fheadhainn air a bheil cion-lèirsinne…"; "Scene.Compose.Attachment.DescriptionVideo" = "Mìnich a’ video dhan fheadhainn air a bheil cion-lèirsinne…"; -"Scene.Compose.Attachment.LoadFailed" = "Load Failed"; +"Scene.Compose.Attachment.LoadFailed" = "Dh’fhàillig leis an luchdadh"; "Scene.Compose.Attachment.Photo" = "dealbh"; -"Scene.Compose.Attachment.UploadFailed" = "Upload Failed"; +"Scene.Compose.Attachment.ServerProcessingState" = "Tha am frithealaiche ’ga phròiseasadh…"; +"Scene.Compose.Attachment.UploadFailed" = "Dh’fhàillig leis an luchdadh suas"; "Scene.Compose.Attachment.Video" = "video"; "Scene.Compose.AutoComplete.SpaceToAdd" = "Brùth air Space gus a chur ris"; "Scene.Compose.ComposeAction" = "Foillsich"; @@ -192,6 +209,8 @@ a luchdadh suas gu Mastodon."; "Scene.Compose.Poll.OptionNumber" = "Roghainn %ld"; "Scene.Compose.Poll.SevenDays" = "Seachdain"; "Scene.Compose.Poll.SixHours" = "6 uairean a thìde"; +"Scene.Compose.Poll.ThePollHasEmptyOption" = "Tha roghainn fhalamh aig a’ chunntas-bheachd"; +"Scene.Compose.Poll.ThePollIsInvalid" = "Tha an cunntas-bheachd mì-dhligheach"; "Scene.Compose.Poll.ThirtyMinutes" = "Leth-uair a thìde"; "Scene.Compose.Poll.ThreeDays" = "3 làithean"; "Scene.Compose.ReplyingToUser" = "a’ freagairt gu %@"; @@ -223,6 +242,12 @@ a luchdadh suas gu Mastodon."; "Scene.Familiarfollowers.Title" = "Luchd-leantainn aithnichte"; "Scene.Favorite.Title" = "Na h-annsachdan agad"; "Scene.FavoritedBy.Title" = "’Na annsachd aig"; +"Scene.FollowedTags.Actions.Follow" = "Follow"; +"Scene.FollowedTags.Actions.Unfollow" = "Unfollow"; +"Scene.FollowedTags.Header.Participants" = "participants"; +"Scene.FollowedTags.Header.Posts" = "posts"; +"Scene.FollowedTags.Header.PostsToday" = "posts today"; +"Scene.FollowedTags.Title" = "Followed Tags"; "Scene.Follower.Footer" = "Cha dèid luchd-leantainn o fhrithealaichean eile a shealltainn."; "Scene.Follower.Title" = "neach-leantainn"; "Scene.Following.Footer" = "Cha dèid cò a leanas tu air frithealaichean eile a shealltainn."; @@ -234,6 +259,9 @@ a luchdadh suas gu Mastodon."; "Scene.HomeTimeline.NavigationBarState.Published" = "Chaidh fhoillseachadh!"; "Scene.HomeTimeline.NavigationBarState.Publishing" = "A’ foillseachadh a’ phuist…"; "Scene.HomeTimeline.Title" = "Dachaigh"; +"Scene.Login.ServerSearchField.Placeholder" = "Cuir a-steach URL an fhrithealaiche agad"; +"Scene.Login.Subtitle" = "Clàraich a-steach air an fhrithealaiche far an do chruthaich thu an cunntas agad."; +"Scene.Login.Title" = "Fàilte air ais"; "Scene.Notification.FollowRequest.Accept" = "Gabh ris"; "Scene.Notification.FollowRequest.Accepted" = "Air a ghabhail ris"; "Scene.Notification.FollowRequest.Reject" = "diùlt"; @@ -259,17 +287,20 @@ a luchdadh suas gu Mastodon."; "Scene.Profile.Dashboard.Following" = "a’ leantainn"; "Scene.Profile.Dashboard.Posts" = "postaichean"; "Scene.Profile.Fields.AddRow" = "Cuir ràgh ris"; +"Scene.Profile.Fields.Joined" = "Joined"; "Scene.Profile.Fields.Placeholder.Content" = "Susbaint"; "Scene.Profile.Fields.Placeholder.Label" = "Leubail"; +"Scene.Profile.Fields.Verified.Long" = "Chaidh dearbhadh cò leis a tha an ceangal seo %@"; +"Scene.Profile.Fields.Verified.Short" = "Air a dhearbhadh %@"; "Scene.Profile.Header.FollowsYou" = "’Gad leantainn"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Message" = "Dearbh bacadh %@"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title" = "Bac an cunntas"; -"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Message" = "Confirm to hide reblogs"; -"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Title" = "Hide Reblogs"; +"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Message" = "Dearbh falach nam brosnachaidhean"; +"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Title" = "Falaich na brosnachaidhean"; "Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Message" = "Dearbh mùchadh %@"; "Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Title" = "Mùch an cunntas"; -"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Message" = "Confirm to show reblogs"; -"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Title" = "Show Reblogs"; +"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Message" = "Dearbh sealladh nam brosnachaidhean"; +"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Title" = "Seall na brosnachaidhean"; "Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Message" = "Dearbh dì-bhacadh %@"; "Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Title" = "Dì-bhac an cunntas"; "Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.Message" = "Dearbh dì-mhùchadh %@"; @@ -393,13 +424,11 @@ a luchdadh suas gu Mastodon."; "Scene.ServerPicker.EmptyState.BadNetwork" = "Chaidh rudeigin ceàrr le luchdadh an dàta. Thoir sùil air a’ cheangal agad ris an eadar-lìon."; "Scene.ServerPicker.EmptyState.FindingServers" = "A’ lorg nam frithealaichean ri am faighinn…"; "Scene.ServerPicker.EmptyState.NoResults" = "Gun toradh"; -"Scene.ServerPicker.Input.Placeholder" = "Lorg frithealaiche"; -"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Lorg frithealaiche no cuir a-steach URL"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Lorg coimhearsnachd no cuir a-steach URL"; "Scene.ServerPicker.Label.Category" = "ROINN-SEÒRSA"; "Scene.ServerPicker.Label.Language" = "CÀNAN"; "Scene.ServerPicker.Label.Users" = "CLEACHDAICHEAN"; -"Scene.ServerPicker.Subtitle" = "Tagh frithealaiche stèidhichte air d’ ùidhean, air far a bheil thu no fear coitcheann."; -"Scene.ServerPicker.SubtitleExtend" = "Tagh frithealaiche stèidhichte air d’ ùidhean, air far a bheil thu no fear coitcheann. Tha gach frithealaiche fo stiùireadh buidhinn no neach neo-eisimeilich fa leth."; +"Scene.ServerPicker.Subtitle" = "Tagh frithealaiche stèidhichte air na sgìre agad, d’ ùidhean, air far a bheil thu no fear coitcheann. ’S urrainn dhut fhathast conaltradh le duine sam bith air Mastodon ge b’ e na frithealaichean agaibh-se."; "Scene.ServerPicker.Title" = "Tha cleachdaichean Mhastodon air iomadh frithealaiche eadar-dhealaichte."; "Scene.ServerRules.Button.Confirm" = "Gabhaidh mi ris"; "Scene.ServerRules.PrivacyPolicy" = "poileasaidh prìobhaideachd"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/gd.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/gd.lproj/Localizable.stringsdict index d0ccb5f41..9b3e69ea7 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/gd.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/gd.lproj/Localizable.stringsdict @@ -62,6 +62,26 @@ %ld caractar + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ air fhàgail + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld charactar + two + %ld charactar + few + %ld caractaran + other + %ld caractar + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/gl.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/gl.lproj/Localizable.strings index 3087f33c5..35a465661 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/gl.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/gl.lproj/Localizable.strings @@ -22,6 +22,9 @@ Comproba a conexión a internet."; "Common.Alerts.SignOut.Message" = "Tes a certeza de queres pechar a sesión?"; "Common.Alerts.SignOut.Title" = "Pechar sesión"; "Common.Alerts.SignUpFailure.Title" = "Fallou o rexistro"; +"Common.Alerts.TranslationFailed.Button" = "OK"; +"Common.Alerts.TranslationFailed.Message" = "Fallou a tradución. É posible que a administración non activase a tradución neste servidor ou que o servidor teña unha versión antiga de Mastodon que non ten soporte para a tradución."; +"Common.Alerts.TranslationFailed.Title" = "Nota"; "Common.Alerts.VoteFailure.PollEnded" = "A enquisa rematou"; "Common.Alerts.VoteFailure.Title" = "Fallou a votación"; "Common.Controls.Actions.Add" = "Engadir"; @@ -31,6 +34,7 @@ Comproba a conexión a internet."; "Common.Controls.Actions.Compose" = "Escribir"; "Common.Controls.Actions.Confirm" = "Confirmar"; "Common.Controls.Actions.Continue" = "Continuar"; +"Common.Controls.Actions.Copy" = "Copiar"; "Common.Controls.Actions.CopyPhoto" = "Copiar foto"; "Common.Controls.Actions.Delete" = "Eliminar"; "Common.Controls.Actions.Discard" = "Descartar"; @@ -56,9 +60,11 @@ Comproba a conexión a internet."; "Common.Controls.Actions.SharePost" = "Compartir publicación"; "Common.Controls.Actions.ShareUser" = "Compartir %@"; "Common.Controls.Actions.SignIn" = "Acceder"; -"Common.Controls.Actions.SignUp" = "Inscribirse"; +"Common.Controls.Actions.SignUp" = "Crear conta"; "Common.Controls.Actions.Skip" = "Omitir"; "Common.Controls.Actions.TakePhoto" = "Facer foto"; +"Common.Controls.Actions.TranslatePost.Title" = "Traducido do %@"; +"Common.Controls.Actions.TranslatePost.UnknownLanguage" = "Descoñecido"; "Common.Controls.Actions.TryAgain" = "Intentar de novo"; "Common.Controls.Actions.UnblockDomain" = "Desbloquear a %@"; "Common.Controls.Friendship.Block" = "Bloquear"; @@ -100,6 +106,7 @@ Comproba a conexión a internet."; "Common.Controls.Status.Actions.Menu" = "Menú"; "Common.Controls.Status.Actions.Reblog" = "Promover"; "Common.Controls.Status.Actions.Reply" = "Responder"; +"Common.Controls.Status.Actions.ShareLinkInPost" = "Compartir Ligazón na Publicación"; "Common.Controls.Status.Actions.ShowGif" = "Mostrar GIF"; "Common.Controls.Status.Actions.ShowImage" = "Mostrar a imaxe"; "Common.Controls.Status.Actions.ShowVideoPlayer" = "Mostrar reprodutor de vídeo"; @@ -107,6 +114,8 @@ Comproba a conexión a internet."; "Common.Controls.Status.Actions.Unfavorite" = "Eliminar dos favoritos"; "Common.Controls.Status.Actions.Unreblog" = "Retirar promoción"; "Common.Controls.Status.ContentWarning" = "Aviso sobre o contido"; +"Common.Controls.Status.LinkViaUser" = "%@ vía %@"; +"Common.Controls.Status.LoadEmbed" = "Cargar o contido"; "Common.Controls.Status.MediaContentWarning" = "Toca nalgures para mostrar"; "Common.Controls.Status.MetaEntity.Email" = "Enderezo de email: %@"; "Common.Controls.Status.MetaEntity.Hashtag" = "Cancelo: %@"; @@ -124,6 +133,10 @@ Comproba a conexión a internet."; "Common.Controls.Status.Tag.Mention" = "Mención"; "Common.Controls.Status.Tag.Url" = "URL"; "Common.Controls.Status.TapToReveal" = "Toca para mostrar"; +"Common.Controls.Status.Translation.ShowOriginal" = "Mostrar o orixinal"; +"Common.Controls.Status.Translation.TranslatedFrom" = "Translated from %@ using %@"; +"Common.Controls.Status.Translation.UnknownLanguage" = "Descoñecido"; +"Common.Controls.Status.Translation.UnknownProvider" = "Unknown"; "Common.Controls.Status.UserReblogged" = "%@ promoveu"; "Common.Controls.Status.UserRepliedTo" = "Respondeu a %@"; "Common.Controls.Status.Visibility.Direct" = "Só a usuaria mencionada pode ver a publicación."; @@ -131,9 +144,9 @@ Comproba a conexión a internet."; "Common.Controls.Status.Visibility.PrivateFromMe" = "Só as miñas seguidoras poden ver esta publicación."; "Common.Controls.Status.Visibility.Unlisted" = "A publicación é visible para calquera pero non aparece na cronoloxía pública."; "Common.Controls.Tabs.Home" = "Inicio"; -"Common.Controls.Tabs.Notification" = "Notificación"; +"Common.Controls.Tabs.Notifications" = "Notificacións"; "Common.Controls.Tabs.Profile" = "Perfil"; -"Common.Controls.Tabs.Search" = "Busca"; +"Common.Controls.Tabs.SearchAndExplore" = "Buscar e Explorar"; "Common.Controls.Timeline.Filtered" = "Filtrado"; "Common.Controls.Timeline.Header.BlockedWarning" = "Non podes ver o perfil desta usuaria ata que te desbloquee."; @@ -161,16 +174,20 @@ Así se ve o teu perfil."; "Scene.Compose.Accessibility.CustomEmojiPicker" = "Selector emoji personalizado"; "Scene.Compose.Accessibility.DisableContentWarning" = "Retirar Aviso sobre o contido"; "Scene.Compose.Accessibility.EnableContentWarning" = "Marcar con Aviso sobre o contido"; +"Scene.Compose.Accessibility.PostOptions" = "Opcións da publicación"; "Scene.Compose.Accessibility.PostVisibilityMenu" = "Visibilidade da publicación"; +"Scene.Compose.Accessibility.PostingAs" = "Publicando como %@"; "Scene.Compose.Accessibility.RemovePoll" = "Eliminar enquisa"; "Scene.Compose.Attachment.AttachmentBroken" = "Este %@ está estragado e non pode ser subido a Mastodon."; "Scene.Compose.Attachment.AttachmentTooLarge" = "Adxunto demasiado grande"; "Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "Non se recoñece o tipo de multimedia"; +"Scene.Compose.Attachment.CompressingState" = "Comprimindo..."; "Scene.Compose.Attachment.DescriptionPhoto" = "Describe a foto para persoas con problemas visuais..."; "Scene.Compose.Attachment.DescriptionVideo" = "Describe o vídeo para persoas con problemas visuais..."; "Scene.Compose.Attachment.LoadFailed" = "Fallou a carga"; "Scene.Compose.Attachment.Photo" = "foto"; +"Scene.Compose.Attachment.ServerProcessingState" = "Procesando no servidor..."; "Scene.Compose.Attachment.UploadFailed" = "Erro na subida"; "Scene.Compose.Attachment.Video" = "vídeo"; "Scene.Compose.AutoComplete.SpaceToAdd" = "Barra de espazo engade"; @@ -192,6 +209,8 @@ ser subido a Mastodon."; "Scene.Compose.Poll.OptionNumber" = "Opción %ld"; "Scene.Compose.Poll.SevenDays" = "7 Días"; "Scene.Compose.Poll.SixHours" = "6 Horas"; +"Scene.Compose.Poll.ThePollHasEmptyOption" = "A enquisa ten unha opción baleira"; +"Scene.Compose.Poll.ThePollIsInvalid" = "A enquisa non é válida"; "Scene.Compose.Poll.ThirtyMinutes" = "30 minutos"; "Scene.Compose.Poll.ThreeDays" = "3 Días"; "Scene.Compose.ReplyingToUser" = "en resposta a %@"; @@ -223,6 +242,12 @@ ser subido a Mastodon."; "Scene.Familiarfollowers.Title" = "Seguimentos próximos"; "Scene.Favorite.Title" = "Publicacións Favoritas"; "Scene.FavoritedBy.Title" = "Favorecido por"; +"Scene.FollowedTags.Actions.Follow" = "Seguir"; +"Scene.FollowedTags.Actions.Unfollow" = "Deixar de seguir"; +"Scene.FollowedTags.Header.Participants" = "participantes"; +"Scene.FollowedTags.Header.Posts" = "publicacións"; +"Scene.FollowedTags.Header.PostsToday" = "publicacións de hoxe"; +"Scene.FollowedTags.Title" = "Cancelos seguidos"; "Scene.Follower.Footer" = "Non se mostran seguidoras desde outros servidores."; "Scene.Follower.Title" = "seguidora"; "Scene.Following.Footer" = "Non se mostran os seguimentos desde outros servidores."; @@ -234,6 +259,9 @@ ser subido a Mastodon."; "Scene.HomeTimeline.NavigationBarState.Published" = "Publicado!"; "Scene.HomeTimeline.NavigationBarState.Publishing" = "Publicando..."; "Scene.HomeTimeline.Title" = "Inicio"; +"Scene.Login.ServerSearchField.Placeholder" = "Escribe o URL ou busca o teu servidor"; +"Scene.Login.Subtitle" = "Conéctate ao servidor no que creaches a conta."; +"Scene.Login.Title" = "Benvido outra vez"; "Scene.Notification.FollowRequest.Accept" = "Aceptar"; "Scene.Notification.FollowRequest.Accepted" = "Aceptada"; "Scene.Notification.FollowRequest.Reject" = "rexeitar"; @@ -259,8 +287,11 @@ ser subido a Mastodon."; "Scene.Profile.Dashboard.Following" = "seguindo"; "Scene.Profile.Dashboard.Posts" = "publicacións"; "Scene.Profile.Fields.AddRow" = "Engadir fila"; +"Scene.Profile.Fields.Joined" = "Uniuse"; "Scene.Profile.Fields.Placeholder.Content" = "Contido"; "Scene.Profile.Fields.Placeholder.Label" = "Etiqueta"; +"Scene.Profile.Fields.Verified.Long" = "A propiedade desta ligazón foi verificada o %@"; +"Scene.Profile.Fields.Verified.Short" = "Verificada en %@"; "Scene.Profile.Header.FollowsYou" = "Séguete"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Message" = "Confirma o bloqueo de %@"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title" = "Bloquear Conta"; @@ -393,13 +424,11 @@ ser subido a Mastodon."; "Scene.ServerPicker.EmptyState.BadNetwork" = "Algo fallou ao cargar os datos. Comproba a conexión a internet."; "Scene.ServerPicker.EmptyState.FindingServers" = "Buscando servidores dispoñibles..."; "Scene.ServerPicker.EmptyState.NoResults" = "Sen resultados"; -"Scene.ServerPicker.Input.Placeholder" = "Buscar comunidades"; -"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Busca un servidor ou escribe URL"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Busca comunidades ou escribe URL"; "Scene.ServerPicker.Label.Category" = "CATEGORÍA"; "Scene.ServerPicker.Label.Language" = "IDIOMA"; "Scene.ServerPicker.Label.Users" = "USUARIAS"; -"Scene.ServerPicker.Subtitle" = "Elixe unha comunidade en función dos teus intereses, rexión ou unha de propósito xeral."; -"Scene.ServerPicker.SubtitleExtend" = "Elixe unha comunidade en función dos teus intereses, rexión ou unha de propósito xeral. Cada comunidade está xestionada por unha organización totalmente independente ou unha única persoa."; +"Scene.ServerPicker.Subtitle" = "Elixe un servidor en función dos teus intereses, rexión o un de propósito xeral. Poderás conversar con calquera en Mastodon, independentemente do servidor que elixas."; "Scene.ServerPicker.Title" = "Mastodon fórmano as persoas das diferentes comunidades."; "Scene.ServerRules.Button.Confirm" = "Acepto"; "Scene.ServerRules.PrivacyPolicy" = "polícica de privacidade"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/gl.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/gl.lproj/Localizable.stringsdict index ff9d87c18..51b146ed4 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/gl.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/gl.lproj/Localizable.stringsdict @@ -50,6 +50,22 @@ %ld caracteres + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ restantes + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 caracter + other + %ld caracteres + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/it.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/it.lproj/Localizable.strings index 8f99028ed..96af84e22 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/it.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/it.lproj/Localizable.strings @@ -22,6 +22,9 @@ Per favore verifica la tua connessione internet."; "Common.Alerts.SignOut.Message" = "Vuoi davvero scollegarti?"; "Common.Alerts.SignOut.Title" = "Esci"; "Common.Alerts.SignUpFailure.Title" = "Iscrizione fallita"; +"Common.Alerts.TranslationFailed.Button" = "OK"; +"Common.Alerts.TranslationFailed.Message" = "Traduzione fallita. Forse l'amministratore non ha abilitato le traduzioni su questo server o questo server sta eseguendo una versione precedente di Mastodon in cui le traduzioni non sono ancora supportate."; +"Common.Alerts.TranslationFailed.Title" = "Nota"; "Common.Alerts.VoteFailure.PollEnded" = "Il sondaggio è terminato"; "Common.Alerts.VoteFailure.Title" = "Voto fallito"; "Common.Controls.Actions.Add" = "Aggiungi"; @@ -31,6 +34,7 @@ Per favore verifica la tua connessione internet."; "Common.Controls.Actions.Compose" = "Scrivi"; "Common.Controls.Actions.Confirm" = "Conferma"; "Common.Controls.Actions.Continue" = "Continua"; +"Common.Controls.Actions.Copy" = "Copia"; "Common.Controls.Actions.CopyPhoto" = "Copia foto"; "Common.Controls.Actions.Delete" = "Elimina"; "Common.Controls.Actions.Discard" = "Abbandona"; @@ -56,9 +60,11 @@ Per favore verifica la tua connessione internet."; "Common.Controls.Actions.SharePost" = "Condividi il post"; "Common.Controls.Actions.ShareUser" = "Condividi %@"; "Common.Controls.Actions.SignIn" = "Accedi"; -"Common.Controls.Actions.SignUp" = "Registrati"; +"Common.Controls.Actions.SignUp" = "Crea un account"; "Common.Controls.Actions.Skip" = "Salta"; "Common.Controls.Actions.TakePhoto" = "Scatta foto"; +"Common.Controls.Actions.TranslatePost.Title" = "Traduci da %@"; +"Common.Controls.Actions.TranslatePost.UnknownLanguage" = "Sconosciuto"; "Common.Controls.Actions.TryAgain" = "Riprova"; "Common.Controls.Actions.UnblockDomain" = "Sblocca %@"; "Common.Controls.Friendship.Block" = "Blocca"; @@ -100,6 +106,7 @@ Per favore verifica la tua connessione internet."; "Common.Controls.Status.Actions.Menu" = "Menù"; "Common.Controls.Status.Actions.Reblog" = "Condivisione"; "Common.Controls.Status.Actions.Reply" = "Rispondi"; +"Common.Controls.Status.Actions.ShareLinkInPost" = "Condividi il collegamento nel post"; "Common.Controls.Status.Actions.ShowGif" = "Mostra GIF"; "Common.Controls.Status.Actions.ShowImage" = "Mostra immagine"; "Common.Controls.Status.Actions.ShowVideoPlayer" = "Mostra lettore video"; @@ -107,6 +114,8 @@ Per favore verifica la tua connessione internet."; "Common.Controls.Status.Actions.Unfavorite" = "Non preferito"; "Common.Controls.Status.Actions.Unreblog" = "Annulla condivisione"; "Common.Controls.Status.ContentWarning" = "Avviso sul contenuto"; +"Common.Controls.Status.LinkViaUser" = "%@ tramite %@"; +"Common.Controls.Status.LoadEmbed" = "Carica Incorpora"; "Common.Controls.Status.MediaContentWarning" = "Tocca ovunque per rivelare"; "Common.Controls.Status.MetaEntity.Email" = "Indirizzo email: %@"; "Common.Controls.Status.MetaEntity.Hashtag" = "Hashtag: %@"; @@ -119,11 +128,15 @@ Per favore verifica la tua connessione internet."; "Common.Controls.Status.ShowUserProfile" = "Mostra il profilo dell'utente"; "Common.Controls.Status.Tag.Email" = "Email"; "Common.Controls.Status.Tag.Emoji" = "Emoji"; -"Common.Controls.Status.Tag.Hashtag" = "Etichetta"; +"Common.Controls.Status.Tag.Hashtag" = "Hashtag"; "Common.Controls.Status.Tag.Link" = "Collegamento"; "Common.Controls.Status.Tag.Mention" = "Menzione"; "Common.Controls.Status.Tag.Url" = "URL"; "Common.Controls.Status.TapToReveal" = "Tocca per rivelare"; +"Common.Controls.Status.Translation.ShowOriginal" = "Mostra l'originale"; +"Common.Controls.Status.Translation.TranslatedFrom" = "Translated from %@ using %@"; +"Common.Controls.Status.Translation.UnknownLanguage" = "Sconosciuto"; +"Common.Controls.Status.Translation.UnknownProvider" = "Unknown"; "Common.Controls.Status.UserReblogged" = "%@ ha condiviso"; "Common.Controls.Status.UserRepliedTo" = "Risposta a %@"; "Common.Controls.Status.Visibility.Direct" = "Solo l'utente menzionato può vedere questo post."; @@ -131,9 +144,9 @@ Per favore verifica la tua connessione internet."; "Common.Controls.Status.Visibility.PrivateFromMe" = "Solo i miei seguaci possono vedere questo post."; "Common.Controls.Status.Visibility.Unlisted" = "Tutti possono vedere questo post ma non mostrare nella cronologia pubblica."; "Common.Controls.Tabs.Home" = "Inizio"; -"Common.Controls.Tabs.Notification" = "Notifiche"; +"Common.Controls.Tabs.Notifications" = "Notifiche"; "Common.Controls.Tabs.Profile" = "Profilo"; -"Common.Controls.Tabs.Search" = "Cerca"; +"Common.Controls.Tabs.SearchAndExplore" = "Cerca ed Esplora"; "Common.Controls.Timeline.Filtered" = "Filtrato"; "Common.Controls.Timeline.Header.BlockedWarning" = "Non puoi visualizzare il profilo di questo utente fino a quando non ti sbloccano."; @@ -161,16 +174,20 @@ Il tuo profilo sembra questo per loro."; "Scene.Compose.Accessibility.CustomEmojiPicker" = "Selettore Emoji personalizzato"; "Scene.Compose.Accessibility.DisableContentWarning" = "Disabilita avviso di contenuti"; "Scene.Compose.Accessibility.EnableContentWarning" = "Abilita avvertimento contenuti"; +"Scene.Compose.Accessibility.PostOptions" = "Opzioni del messaggio"; "Scene.Compose.Accessibility.PostVisibilityMenu" = "Menu di visibilità del post"; +"Scene.Compose.Accessibility.PostingAs" = "Pubblicazione come %@"; "Scene.Compose.Accessibility.RemovePoll" = "Elimina sondaggio"; "Scene.Compose.Attachment.AttachmentBroken" = "Questo %@ è rotto e non può essere caricato su Mastodon."; "Scene.Compose.Attachment.AttachmentTooLarge" = "Allegato troppo grande"; "Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "Impossibile riconoscere questo allegato multimediale"; +"Scene.Compose.Attachment.CompressingState" = "Compressione in corso..."; "Scene.Compose.Attachment.DescriptionPhoto" = "Descrivi la foto per gli utenti ipovedenti..."; "Scene.Compose.Attachment.DescriptionVideo" = "Descrivi il filmato per gli utenti ipovedenti..."; "Scene.Compose.Attachment.LoadFailed" = "Caricamento fallito"; "Scene.Compose.Attachment.Photo" = "foto"; +"Scene.Compose.Attachment.ServerProcessingState" = "Elaborazione del server in corso..."; "Scene.Compose.Attachment.UploadFailed" = "Caricamento fallito"; "Scene.Compose.Attachment.Video" = "filmato"; "Scene.Compose.AutoComplete.SpaceToAdd" = "Spazio da aggiungere"; @@ -192,6 +209,8 @@ caricato su Mastodon."; "Scene.Compose.Poll.OptionNumber" = "Opzione %ld"; "Scene.Compose.Poll.SevenDays" = "7 giorni"; "Scene.Compose.Poll.SixHours" = "6 ore"; +"Scene.Compose.Poll.ThePollHasEmptyOption" = "Il sondaggio ha un'opzione vuota"; +"Scene.Compose.Poll.ThePollIsInvalid" = "Il sondaggio non è valido"; "Scene.Compose.Poll.ThirtyMinutes" = "30 minuti"; "Scene.Compose.Poll.ThreeDays" = "3 giorni"; "Scene.Compose.ReplyingToUser" = "rispondendo a %@"; @@ -223,6 +242,12 @@ caricato su Mastodon."; "Scene.Familiarfollowers.Title" = "Seguaci che conosci"; "Scene.Favorite.Title" = "I tuoi preferiti"; "Scene.FavoritedBy.Title" = "Preferito Da"; +"Scene.FollowedTags.Actions.Follow" = "Segui"; +"Scene.FollowedTags.Actions.Unfollow" = "Smetti di seguire"; +"Scene.FollowedTags.Header.Participants" = "partecipanti"; +"Scene.FollowedTags.Header.Posts" = "post"; +"Scene.FollowedTags.Header.PostsToday" = "post di oggi"; +"Scene.FollowedTags.Title" = "Etichette seguite"; "Scene.Follower.Footer" = "I seguaci da altri server non vengono visualizzati."; "Scene.Follower.Title" = "seguace"; "Scene.Following.Footer" = "I follow da altri server non vengono visualizzati."; @@ -234,6 +259,9 @@ caricato su Mastodon."; "Scene.HomeTimeline.NavigationBarState.Published" = "Pubblicato!"; "Scene.HomeTimeline.NavigationBarState.Publishing" = "Pubblicazione post..."; "Scene.HomeTimeline.Title" = "Inizio"; +"Scene.Login.ServerSearchField.Placeholder" = "Inserisci l'URL o cerca il tuo server"; +"Scene.Login.Subtitle" = "Accedi al server sul quale hai creato il tuo account."; +"Scene.Login.Title" = "Bentornato/a"; "Scene.Notification.FollowRequest.Accept" = "Accetta"; "Scene.Notification.FollowRequest.Accepted" = "Richiesta accettata"; "Scene.Notification.FollowRequest.Reject" = "rifiuta"; @@ -259,8 +287,11 @@ caricato su Mastodon."; "Scene.Profile.Dashboard.Following" = "seguendo"; "Scene.Profile.Dashboard.Posts" = "post"; "Scene.Profile.Fields.AddRow" = "Aggiungi riga"; +"Scene.Profile.Fields.Joined" = "Profilo iscritto"; "Scene.Profile.Fields.Placeholder.Content" = "Contenuto"; "Scene.Profile.Fields.Placeholder.Label" = "Etichetta"; +"Scene.Profile.Fields.Verified.Long" = "La proprietà di questo collegamento è stata verificata il %@"; +"Scene.Profile.Fields.Verified.Short" = "Verificato il %@"; "Scene.Profile.Header.FollowsYou" = "Ti segue"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Message" = "Confermi di bloccare %@"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title" = "Blocca account"; @@ -393,13 +424,11 @@ caricato su Mastodon."; "Scene.ServerPicker.EmptyState.BadNetwork" = "Qualcosa è andato storto durante il caricamento dei dati. Controlla la tua connessione internet."; "Scene.ServerPicker.EmptyState.FindingServers" = "Ricerca server disponibili..."; "Scene.ServerPicker.EmptyState.NoResults" = "Nessun risultato"; -"Scene.ServerPicker.Input.Placeholder" = "Cerca comunità"; -"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Cerca i server o inserisci l'URL"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Cerca le comunità o inserisci l'URL"; "Scene.ServerPicker.Label.Category" = "CATEGORIA"; "Scene.ServerPicker.Label.Language" = "LINGUA"; "Scene.ServerPicker.Label.Users" = "UTENTI"; -"Scene.ServerPicker.Subtitle" = "Scegli una comunità basata sui tuoi interessi, regione o uno scopo generale."; -"Scene.ServerPicker.SubtitleExtend" = "Scegli una comunità basata sui tuoi interessi, regione o uno scopo generale. Ogni comunità è gestita da un'organizzazione completamente indipendente o individuale."; +"Scene.ServerPicker.Subtitle" = "Scegli un server in base alla tua regione, ai tuoi interessi o uno generico. Puoi comunque chattare con chiunque su Mastodon, indipendentemente dai tuoi server."; "Scene.ServerPicker.Title" = "Mastodon è fatto di utenti in diverse comunità."; "Scene.ServerRules.Button.Confirm" = "Accetto"; "Scene.ServerRules.PrivacyPolicy" = "privacy policy"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/it.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/it.lproj/Localizable.stringsdict index 38f986521..3a8549914 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/it.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/it.lproj/Localizable.stringsdict @@ -50,6 +50,22 @@ %ld caratteri + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ rimanenti + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 carattere + other + %ld caratteri + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/ja.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/ja.lproj/Localizable.strings index cad44f531..3d32c96d1 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/ja.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/ja.lproj/Localizable.strings @@ -22,6 +22,9 @@ "Common.Alerts.SignOut.Message" = "本当にサインアウトしますか?"; "Common.Alerts.SignOut.Title" = "サインアウト"; "Common.Alerts.SignUpFailure.Title" = "サインアップに失敗しました"; +"Common.Alerts.TranslationFailed.Button" = "OK"; +"Common.Alerts.TranslationFailed.Message" = "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported."; +"Common.Alerts.TranslationFailed.Title" = "Note"; "Common.Alerts.VoteFailure.PollEnded" = "投票は終了しました"; "Common.Alerts.VoteFailure.Title" = "投票の失敗"; "Common.Controls.Actions.Add" = "追加"; @@ -31,6 +34,7 @@ "Common.Controls.Actions.Compose" = "新規作成"; "Common.Controls.Actions.Confirm" = "確認"; "Common.Controls.Actions.Continue" = "続ける"; +"Common.Controls.Actions.Copy" = "Copy"; "Common.Controls.Actions.CopyPhoto" = "写真をコピー"; "Common.Controls.Actions.Delete" = "削除"; "Common.Controls.Actions.Discard" = "破棄"; @@ -49,16 +53,18 @@ "Common.Controls.Actions.Reply" = "返信"; "Common.Controls.Actions.ReportUser" = "%@を通報"; "Common.Controls.Actions.Save" = "保存"; -"Common.Controls.Actions.SavePhoto" = "写真を撮る"; +"Common.Controls.Actions.SavePhoto" = "写真を保存"; "Common.Controls.Actions.SeeMore" = "もっと見る"; "Common.Controls.Actions.Settings" = "設定"; "Common.Controls.Actions.Share" = "共有"; "Common.Controls.Actions.SharePost" = "投稿を共有"; "Common.Controls.Actions.ShareUser" = "%@を共有"; -"Common.Controls.Actions.SignIn" = "サインイン"; -"Common.Controls.Actions.SignUp" = "サインアップ"; +"Common.Controls.Actions.SignIn" = "ログイン"; +"Common.Controls.Actions.SignUp" = "アカウント作成"; "Common.Controls.Actions.Skip" = "スキップ"; "Common.Controls.Actions.TakePhoto" = "写真を撮る"; +"Common.Controls.Actions.TranslatePost.Title" = "Translate from %@"; +"Common.Controls.Actions.TranslatePost.UnknownLanguage" = "Unknown"; "Common.Controls.Actions.TryAgain" = "再実行"; "Common.Controls.Actions.UnblockDomain" = "%@のブロックを解除"; "Common.Controls.Friendship.Block" = "ブロック"; @@ -68,13 +74,13 @@ "Common.Controls.Friendship.EditInfo" = "編集"; "Common.Controls.Friendship.Follow" = "フォロー"; "Common.Controls.Friendship.Following" = "フォロー中"; -"Common.Controls.Friendship.HideReblogs" = "Hide Reblogs"; +"Common.Controls.Friendship.HideReblogs" = "ブーストを非表示"; "Common.Controls.Friendship.Mute" = "ミュート"; "Common.Controls.Friendship.MuteUser" = "%@をミュート"; "Common.Controls.Friendship.Muted" = "ミュート済み"; "Common.Controls.Friendship.Pending" = "保留"; "Common.Controls.Friendship.Request" = "リクエスト"; -"Common.Controls.Friendship.ShowReblogs" = "Show Reblogs"; +"Common.Controls.Friendship.ShowReblogs" = "ブーストを表示"; "Common.Controls.Friendship.Unblock" = "ブロックを解除"; "Common.Controls.Friendship.UnblockUser" = "%@のブロックを解除"; "Common.Controls.Friendship.Unmute" = "ミュートを解除"; @@ -100,6 +106,7 @@ "Common.Controls.Status.Actions.Menu" = "メニュー"; "Common.Controls.Status.Actions.Reblog" = "ブースト"; "Common.Controls.Status.Actions.Reply" = "返信"; +"Common.Controls.Status.Actions.ShareLinkInPost" = "Share Link in Post"; "Common.Controls.Status.Actions.ShowGif" = "GIFを表示"; "Common.Controls.Status.Actions.ShowImage" = "画像を表示"; "Common.Controls.Status.Actions.ShowVideoPlayer" = "Show video player"; @@ -107,11 +114,13 @@ "Common.Controls.Status.Actions.Unfavorite" = "お気に入り登録を取り消す"; "Common.Controls.Status.Actions.Unreblog" = "ブーストを戻す"; "Common.Controls.Status.ContentWarning" = "コンテンツ警告"; +"Common.Controls.Status.LinkViaUser" = "%@ via %@"; +"Common.Controls.Status.LoadEmbed" = "Load Embed"; "Common.Controls.Status.MediaContentWarning" = "どこかをタップして表示"; -"Common.Controls.Status.MetaEntity.Email" = "Email address: %@"; -"Common.Controls.Status.MetaEntity.Hashtag" = "Hashtag: %@"; -"Common.Controls.Status.MetaEntity.Mention" = "Show Profile: %@"; -"Common.Controls.Status.MetaEntity.Url" = "Link: %@"; +"Common.Controls.Status.MetaEntity.Email" = "メールアドレス: %@"; +"Common.Controls.Status.MetaEntity.Hashtag" = "ハッシュタグ: %@"; +"Common.Controls.Status.MetaEntity.Mention" = "プロフィールを表示: %@"; +"Common.Controls.Status.MetaEntity.Url" = "リンク: %@"; "Common.Controls.Status.Poll.Closed" = "終了"; "Common.Controls.Status.Poll.Vote" = "投票"; "Common.Controls.Status.SensitiveContent" = "閲覧注意"; @@ -124,6 +133,10 @@ "Common.Controls.Status.Tag.Mention" = "メンション"; "Common.Controls.Status.Tag.Url" = "URL"; "Common.Controls.Status.TapToReveal" = "タップして表示"; +"Common.Controls.Status.Translation.ShowOriginal" = "Shown Original"; +"Common.Controls.Status.Translation.TranslatedFrom" = "Translated from %@ using %@"; +"Common.Controls.Status.Translation.UnknownLanguage" = "Unknown"; +"Common.Controls.Status.Translation.UnknownProvider" = "Unknown"; "Common.Controls.Status.UserReblogged" = "%@がブースト"; "Common.Controls.Status.UserRepliedTo" = "%@に返信"; "Common.Controls.Status.Visibility.Direct" = "この投稿はメンションされたユーザーに限り見ることができます。"; @@ -131,9 +144,9 @@ "Common.Controls.Status.Visibility.PrivateFromMe" = "この投稿はフォロワーに限り見ることができます。"; "Common.Controls.Status.Visibility.Unlisted" = "この投稿は誰でも見ることができますが、公開タイムラインには表示されません。"; "Common.Controls.Tabs.Home" = "ホーム"; -"Common.Controls.Tabs.Notification" = "通知"; +"Common.Controls.Tabs.Notifications" = "通知"; "Common.Controls.Tabs.Profile" = "プロフィール"; -"Common.Controls.Tabs.Search" = "検索"; +"Common.Controls.Tabs.SearchAndExplore" = "Search and Explore"; "Common.Controls.Timeline.Filtered" = "フィルター済み"; "Common.Controls.Timeline.Header.BlockedWarning" = "ブロックされているようです..."; "Common.Controls.Timeline.Header.BlockingWarning" = "ブロックを解除するまでこのユーザーをみることはできません。 @@ -151,28 +164,32 @@ "Scene.AccountList.AddAccount" = "アカウントを追加"; "Scene.AccountList.DismissAccountSwitcher" = "アカウント切替画面を閉じます"; "Scene.AccountList.TabBarHint" = "現在のアカウント: %@. ダブルタップしてアカウント切替画面を表示します"; -"Scene.Bookmark.Title" = "Bookmarks"; -"Scene.Compose.Accessibility.AppendAttachment" = "アタッチメントの追加"; +"Scene.Bookmark.Title" = "ブックマーク"; +"Scene.Compose.Accessibility.AppendAttachment" = "添付ファイルを追加"; "Scene.Compose.Accessibility.AppendPoll" = "投票を追加"; "Scene.Compose.Accessibility.CustomEmojiPicker" = "カスタム絵文字ピッカー"; "Scene.Compose.Accessibility.DisableContentWarning" = "閲覧注意を無効にする"; "Scene.Compose.Accessibility.EnableContentWarning" = "閲覧注意を有効にする"; +"Scene.Compose.Accessibility.PostOptions" = "投稿オプション"; "Scene.Compose.Accessibility.PostVisibilityMenu" = "投稿の表示メニュー"; +"Scene.Compose.Accessibility.PostingAs" = "Posting as %@"; "Scene.Compose.Accessibility.RemovePoll" = "投票を消去"; "Scene.Compose.Attachment.AttachmentBroken" = "%@は壊れていてMastodonにアップロードできません。"; -"Scene.Compose.Attachment.AttachmentTooLarge" = "Attachment too large"; -"Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "Can not regonize this media attachment"; +"Scene.Compose.Attachment.AttachmentTooLarge" = "添付ファイルが大きすぎます"; +"Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "Can not recognize this media attachment"; +"Scene.Compose.Attachment.CompressingState" = "Compressing..."; "Scene.Compose.Attachment.DescriptionPhoto" = "閲覧が難しいユーザーへの画像説明"; "Scene.Compose.Attachment.DescriptionVideo" = "閲覧が難しいユーザーへの映像説明"; -"Scene.Compose.Attachment.LoadFailed" = "Load Failed"; +"Scene.Compose.Attachment.LoadFailed" = "読み込みに失敗しました"; "Scene.Compose.Attachment.Photo" = "写真"; -"Scene.Compose.Attachment.UploadFailed" = "Upload Failed"; +"Scene.Compose.Attachment.ServerProcessingState" = "Server Processing..."; +"Scene.Compose.Attachment.UploadFailed" = "アップロードに失敗しました"; "Scene.Compose.Attachment.Video" = "動画"; "Scene.Compose.AutoComplete.SpaceToAdd" = "スペースを追加"; "Scene.Compose.ComposeAction" = "投稿"; "Scene.Compose.ContentInputPlaceholder" = "気になることを入力またはペースト"; "Scene.Compose.ContentWarning.Placeholder" = "ここに警告を書いてください..."; -"Scene.Compose.Keyboard.AppendAttachmentEntry" = "アタッチメントを追加 - %@"; +"Scene.Compose.Keyboard.AppendAttachmentEntry" = "添付ファイルを追加 - %@"; "Scene.Compose.Keyboard.DiscardPost" = "投稿を破棄"; "Scene.Compose.Keyboard.PublishPost" = "投稿する"; "Scene.Compose.Keyboard.SelectVisibilityEntry" = "公開設定を選択 - %@"; @@ -187,6 +204,8 @@ "Scene.Compose.Poll.OptionNumber" = "オプション %ld"; "Scene.Compose.Poll.SevenDays" = "7日"; "Scene.Compose.Poll.SixHours" = "6時間"; +"Scene.Compose.Poll.ThePollHasEmptyOption" = "The poll has empty option"; +"Scene.Compose.Poll.ThePollIsInvalid" = "The poll is invalid"; "Scene.Compose.Poll.ThirtyMinutes" = "30分"; "Scene.Compose.Poll.ThreeDays" = "3日"; "Scene.Compose.ReplyingToUser" = "%@に返信"; @@ -206,7 +225,7 @@ "Scene.ConfirmEmail.OpenEmailApp.OpenEmailClient" = "メールアプリを開く"; "Scene.ConfirmEmail.OpenEmailApp.Title" = "メールを確認"; "Scene.ConfirmEmail.Subtitle" = "先程 %@ にメールを送信しました。リンクをタップしてアカウントを確認してください。"; -"Scene.ConfirmEmail.TapTheLinkWeEmailedToYouToVerifyYourAccount" = "Tap the link we emailed to you to verify your account"; +"Scene.ConfirmEmail.TapTheLinkWeEmailedToYouToVerifyYourAccount" = "メールで送られたリンクへアクセスし、アカウントを認証してください"; "Scene.ConfirmEmail.Title" = "さいごにもうひとつ。"; "Scene.Discovery.Intro" = "あなたのMastodonサーバーで注目を集めている投稿がここに表示されます。"; "Scene.Discovery.Tabs.Community" = "コミュニティ"; @@ -217,7 +236,13 @@ "Scene.Familiarfollowers.FollowedByNames" = "Followed by %@"; "Scene.Familiarfollowers.Title" = "Followers you familiar"; "Scene.Favorite.Title" = "お気に入り"; -"Scene.FavoritedBy.Title" = "Favorited By"; +"Scene.FavoritedBy.Title" = "お気に入り"; +"Scene.FollowedTags.Actions.Follow" = "Follow"; +"Scene.FollowedTags.Actions.Unfollow" = "Unfollow"; +"Scene.FollowedTags.Header.Participants" = "participants"; +"Scene.FollowedTags.Header.Posts" = "posts"; +"Scene.FollowedTags.Header.PostsToday" = "posts today"; +"Scene.FollowedTags.Title" = "Followed Tags"; "Scene.Follower.Footer" = "他のサーバーからのフォロワーは表示されません。"; "Scene.Follower.Title" = "フォロワー"; "Scene.Following.Footer" = "他のサーバーにいるフォローは表示されません。"; @@ -229,6 +254,9 @@ "Scene.HomeTimeline.NavigationBarState.Published" = "投稿しました!"; "Scene.HomeTimeline.NavigationBarState.Publishing" = "投稿中..."; "Scene.HomeTimeline.Title" = "ホーム"; +"Scene.Login.ServerSearchField.Placeholder" = "URLを入力またはサーバーを検索"; +"Scene.Login.Subtitle" = "アカウントを作成したサーバーにログインします。"; +"Scene.Login.Title" = "Welcome back"; "Scene.Notification.FollowRequest.Accept" = "承認"; "Scene.Notification.FollowRequest.Accepted" = "承諾済み"; "Scene.Notification.FollowRequest.Reject" = "拒否"; @@ -254,17 +282,20 @@ "Scene.Profile.Dashboard.Following" = "フォロー"; "Scene.Profile.Dashboard.Posts" = "投稿"; "Scene.Profile.Fields.AddRow" = "行追加"; +"Scene.Profile.Fields.Joined" = "Joined"; "Scene.Profile.Fields.Placeholder.Content" = "コンテンツ"; "Scene.Profile.Fields.Placeholder.Label" = "ラベル"; +"Scene.Profile.Fields.Verified.Long" = "Ownership of this link was checked on %@"; +"Scene.Profile.Fields.Verified.Short" = "Verified on %@"; "Scene.Profile.Header.FollowsYou" = "フォローされています"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Message" = "%@をブロックしますか?"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title" = "アカウントをブロック"; -"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Message" = "Confirm to hide reblogs"; -"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Title" = "Hide Reblogs"; +"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Message" = "ブーストを非表示にしますか?"; +"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Title" = "ブーストを非表示"; "Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Message" = "%@をミュートしますか?"; "Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Title" = "アカウントをミュート"; -"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Message" = "Confirm to show reblogs"; -"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Title" = "Show Reblogs"; +"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Message" = "ブーストを表示しますか?"; +"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Title" = "ブーストを表示"; "Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Message" = "%@のブロックを解除しますか?"; "Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Title" = "アカウントのブロックを解除"; "Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.Message" = "%@をミュートしますか?"; @@ -274,7 +305,7 @@ "Scene.Profile.SegmentedControl.Posts" = "投稿"; "Scene.Profile.SegmentedControl.PostsAndReplies" = "投稿と返信"; "Scene.Profile.SegmentedControl.Replies" = "返信"; -"Scene.RebloggedBy.Title" = "Reblogged By"; +"Scene.RebloggedBy.Title" = "ブースト"; "Scene.Register.Error.Item.Agreement" = "契約"; "Scene.Register.Error.Item.Email" = "メール"; "Scene.Register.Error.Item.Locale" = "地域"; @@ -318,15 +349,15 @@ "Scene.Report.Step1" = "ステップ 1/2"; "Scene.Report.Step2" = "ステップ 2/2"; "Scene.Report.StepFinal.BlockUser" = "%@をブロック"; -"Scene.Report.StepFinal.DontWantToSeeThis" = "Don’t want to see this?"; +"Scene.Report.StepFinal.DontWantToSeeThis" = "見えないようにしたいですか?"; "Scene.Report.StepFinal.MuteUser" = "%@をミュート"; -"Scene.Report.StepFinal.TheyWillNoLongerBeAbleToFollowOrSeeYourPostsButTheyCanSeeIfTheyveBeenBlocked" = "They will no longer be able to follow or see your posts, but they can see if they’ve been blocked."; +"Scene.Report.StepFinal.TheyWillNoLongerBeAbleToFollowOrSeeYourPostsButTheyCanSeeIfTheyveBeenBlocked" = "相手はあなたの投稿を見たり、フォローしたりできなくなります。あなたにブロックされていることはわかります。"; "Scene.Report.StepFinal.Unfollow" = "フォロー解除"; "Scene.Report.StepFinal.UnfollowUser" = "%@をフォロー解除"; "Scene.Report.StepFinal.Unfollowed" = "フォロー解除しました"; -"Scene.Report.StepFinal.WhenYouSeeSomethingYouDontLikeOnMastodonYouCanRemoveThePersonFromYourExperience." = "When you see something you don’t like on Mastodon, you can remove the person from your experience."; +"Scene.Report.StepFinal.WhenYouSeeSomethingYouDontLikeOnMastodonYouCanRemoveThePersonFromYourExperience." = "Mastodonで気に入らないものを見た場合、その人をあなたの体験から取り除くことができます。"; "Scene.Report.StepFinal.WhileWeReviewThisYouCanTakeActionAgainstUser" = "私たちが確認している間でも、あなたは%@さんに対して対応することができます。"; -"Scene.Report.StepFinal.YouWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted" = "You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted."; +"Scene.Report.StepFinal.YouWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted" = "ホームに投稿やブーストは表示されなくなります。相手にミュートしたことは伝わりません。"; "Scene.Report.StepFour.IsThereAnythingElseWeShouldKnow" = "その他に私たちに伝えておくべき事はありますか?"; "Scene.Report.StepFour.Step4Of4" = "ステップ 4/4"; "Scene.Report.StepOne.IDontLikeIt" = "興味がありません"; @@ -342,11 +373,11 @@ "Scene.Report.StepOne.WhatsWrongWithThisPost" = "この投稿のどこが問題ですか?"; "Scene.Report.StepOne.WhatsWrongWithThisUsername" = "%@さんのどこが問題ですか?"; "Scene.Report.StepOne.YouAreAwareThatItBreaksSpecificRules" = "ルールに違反しているのを見つけた場合"; -"Scene.Report.StepThree.AreThereAnyPostsThatBackUpThisReport" = "Are there any posts that back up this report?"; -"Scene.Report.StepThree.SelectAllThatApply" = "Select all that apply"; +"Scene.Report.StepThree.AreThereAnyPostsThatBackUpThisReport" = "この通報を裏付けるような投稿はありますか?"; +"Scene.Report.StepThree.SelectAllThatApply" = "当てはまるものをすべて選んでください"; "Scene.Report.StepThree.Step3Of4" = "ステップ 3/4"; -"Scene.Report.StepTwo.IJustDon’tLikeIt" = "I just don’t like it"; -"Scene.Report.StepTwo.SelectAllThatApply" = "Select all that apply"; +"Scene.Report.StepTwo.IJustDon’tLikeIt" = "興味がありません"; +"Scene.Report.StepTwo.SelectAllThatApply" = "当てはまるものをすべて選んでください"; "Scene.Report.StepTwo.Step2Of4" = "ステップ 2/4"; "Scene.Report.StepTwo.WhichRulesAreBeingViolated" = "どのルールに違反していますか?"; "Scene.Report.TextPlaceholder" = "追加コメントを入力"; @@ -388,13 +419,11 @@ "Scene.ServerPicker.EmptyState.BadNetwork" = "データの読み込み中に何か問題が発生しました。インターネットの接続状況を確認してください。"; "Scene.ServerPicker.EmptyState.FindingServers" = "利用可能なサーバーの検索..."; "Scene.ServerPicker.EmptyState.NoResults" = "なし"; -"Scene.ServerPicker.Input.Placeholder" = "サーバーを探す"; -"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "サーバーを検索またはURLを入力"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "コミュニティを検索またはURLを入力"; "Scene.ServerPicker.Label.Category" = "カテゴリー"; "Scene.ServerPicker.Label.Language" = "言語"; "Scene.ServerPicker.Label.Users" = "ユーザー"; -"Scene.ServerPicker.Subtitle" = "あなたの興味分野・地域に合ったコミュニティや、汎用のものを選択してください。"; -"Scene.ServerPicker.SubtitleExtend" = "あなたの興味分野・地域に合ったコミュニティや、汎用のものを選択してください。各コミュニティはそれぞれ完全に独立した組織や個人によって運営されています。"; +"Scene.ServerPicker.Subtitle" = "お住まいの地域、興味、目的に基づいてサーバーを選択してください。 サーバーに関係なく、Mastodonの誰とでも話せます。"; "Scene.ServerPicker.Title" = "サーバーを選択"; "Scene.ServerRules.Button.Confirm" = "同意する"; "Scene.ServerRules.PrivacyPolicy" = "プライバシーポリシー"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/ja.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/ja.lproj/Localizable.stringsdict index cbc999738..795a971b7 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/ja.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/ja.lproj/Localizable.stringsdict @@ -44,6 +44,20 @@ %ld 文字 + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ left + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld characters + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/kab.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/kab.lproj/Localizable.strings index 03108a25a..43a1d385a 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/kab.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/kab.lproj/Localizable.strings @@ -22,6 +22,9 @@ Ma ulac aɣilif, senqed tuqqna-inek internet."; "Common.Alerts.SignOut.Message" = "Tebɣiḍ ad teffɣeḍ?"; "Common.Alerts.SignOut.Title" = "Ffeɣ"; "Common.Alerts.SignUpFailure.Title" = "Tuccḍa deg unekcum"; +"Common.Alerts.TranslationFailed.Button" = "OK"; +"Common.Alerts.TranslationFailed.Message" = "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported."; +"Common.Alerts.TranslationFailed.Title" = "Note"; "Common.Alerts.VoteFailure.PollEnded" = "Tafrant tfuk"; "Common.Alerts.VoteFailure.Title" = "Tuccḍa deg ufran"; "Common.Controls.Actions.Add" = "Rnu"; @@ -31,6 +34,7 @@ Ma ulac aɣilif, senqed tuqqna-inek internet."; "Common.Controls.Actions.Compose" = "Sudes"; "Common.Controls.Actions.Confirm" = "Sentem"; "Common.Controls.Actions.Continue" = "Kemmel"; +"Common.Controls.Actions.Copy" = "Copy"; "Common.Controls.Actions.CopyPhoto" = "Nɣel tawlaft"; "Common.Controls.Actions.Delete" = "Kkes"; "Common.Controls.Actions.Discard" = "Sefsex"; @@ -56,9 +60,11 @@ Ma ulac aɣilif, senqed tuqqna-inek internet."; "Common.Controls.Actions.SharePost" = "Bḍu tasuffeɣt"; "Common.Controls.Actions.ShareUser" = "Bḍu %@"; "Common.Controls.Actions.SignIn" = "Qqen"; -"Common.Controls.Actions.SignUp" = "Jerred amiḍan"; +"Common.Controls.Actions.SignUp" = "Snulfu-d amiḍan"; "Common.Controls.Actions.Skip" = "Zgel"; "Common.Controls.Actions.TakePhoto" = "Ṭṭef tawlaft"; +"Common.Controls.Actions.TranslatePost.Title" = "Translate from %@"; +"Common.Controls.Actions.TranslatePost.UnknownLanguage" = "Unknown"; "Common.Controls.Actions.TryAgain" = "Ɛreḍ tikkelt-nniḍen"; "Common.Controls.Actions.UnblockDomain" = "Serreḥ i %@"; "Common.Controls.Friendship.Block" = "Sewḥel"; @@ -100,6 +106,7 @@ Ma ulac aɣilif, senqed tuqqna-inek internet."; "Common.Controls.Status.Actions.Menu" = "Umuɣ"; "Common.Controls.Status.Actions.Reblog" = "Aɛiwed n usuffeɣ"; "Common.Controls.Status.Actions.Reply" = "Err"; +"Common.Controls.Status.Actions.ShareLinkInPost" = "Share Link in Post"; "Common.Controls.Status.Actions.ShowGif" = "Sken GIF"; "Common.Controls.Status.Actions.ShowImage" = "Sken tugna"; "Common.Controls.Status.Actions.ShowVideoPlayer" = "Sken ameɣri n tvidyut"; @@ -107,11 +114,13 @@ Ma ulac aɣilif, senqed tuqqna-inek internet."; "Common.Controls.Status.Actions.Unfavorite" = "Kkes seg yismenyifen"; "Common.Controls.Status.Actions.Unreblog" = "Sefsex allus n usuffeɣ"; "Common.Controls.Status.ContentWarning" = "Alɣu n ugbur"; +"Common.Controls.Status.LinkViaUser" = "%@ via %@"; +"Common.Controls.Status.LoadEmbed" = "Load Embed"; "Common.Controls.Status.MediaContentWarning" = "Sit anida tebɣiḍ i wakken ad twaliḍ"; -"Common.Controls.Status.MetaEntity.Email" = "Email address: %@"; -"Common.Controls.Status.MetaEntity.Hashtag" = "Hashtag: %@"; -"Common.Controls.Status.MetaEntity.Mention" = "Show Profile: %@"; -"Common.Controls.Status.MetaEntity.Url" = "Link: %@"; +"Common.Controls.Status.MetaEntity.Email" = "Tansa imayl : %@"; +"Common.Controls.Status.MetaEntity.Hashtag" = "Ahacṭag : %@"; +"Common.Controls.Status.MetaEntity.Mention" = "Sken-d amaɣnu : %@"; +"Common.Controls.Status.MetaEntity.Url" = "Asaɣ : %@"; "Common.Controls.Status.Poll.Closed" = "Ifukk"; "Common.Controls.Status.Poll.Vote" = "Dɣeṛ"; "Common.Controls.Status.SensitiveContent" = "Agbur amḥulfu"; @@ -124,6 +133,10 @@ Ma ulac aɣilif, senqed tuqqna-inek internet."; "Common.Controls.Status.Tag.Mention" = "Tabdart"; "Common.Controls.Status.Tag.Url" = "URL"; "Common.Controls.Status.TapToReveal" = "Sit i uskan"; +"Common.Controls.Status.Translation.ShowOriginal" = "Shown Original"; +"Common.Controls.Status.Translation.TranslatedFrom" = "Translated from %@ using %@"; +"Common.Controls.Status.Translation.UnknownLanguage" = "Unknown"; +"Common.Controls.Status.Translation.UnknownProvider" = "Unknown"; "Common.Controls.Status.UserReblogged" = "Tettwasuffeɣ-d %@ i tikkelt-nniḍen"; "Common.Controls.Status.UserRepliedTo" = "Yerra ɣef %@"; "Common.Controls.Status.Visibility.Direct" = "D ineḍfaren-is kan i izemren ad walin tsuffeɣ-a."; @@ -131,9 +144,9 @@ Ma ulac aɣilif, senqed tuqqna-inek internet."; "Common.Controls.Status.Visibility.PrivateFromMe" = "D ineḍfaren-is kan i izemren ad walin tsuffeɣ-a."; "Common.Controls.Status.Visibility.Unlisted" = "Yal wa yezmer ad iwali tsuffeɣt-a maca ur d-tettwaskaneḍ ara deg yizirig n wakud azayaz."; "Common.Controls.Tabs.Home" = "Agejdan"; -"Common.Controls.Tabs.Notification" = "Tilɣa"; +"Common.Controls.Tabs.Notifications" = "Notifications"; "Common.Controls.Tabs.Profile" = "Amaɣnu"; -"Common.Controls.Tabs.Search" = "Nadi"; +"Common.Controls.Tabs.SearchAndExplore" = "Search and Explore"; "Common.Controls.Timeline.Filtered" = "Yettwasizdeg"; "Common.Controls.Timeline.Header.BlockedWarning" = "Ur tezmireḍ ara ad twaliḍ amaɣnu n useqdac-a Akka i as-d-yettban umaɣnu-inek."; @@ -161,16 +174,20 @@ Akka i as-d-yettban umaɣnu-inek."; "Scene.Compose.Accessibility.CustomEmojiPicker" = "Amefran n yimujiten udmawanen"; "Scene.Compose.Accessibility.DisableContentWarning" = "Sens alɣu n ugbur"; "Scene.Compose.Accessibility.EnableContentWarning" = "Rmed alɣu n ugbur"; +"Scene.Compose.Accessibility.PostOptions" = "Post Options"; "Scene.Compose.Accessibility.PostVisibilityMenu" = "Umuɣ n ubani n tsuffeɣt"; +"Scene.Compose.Accessibility.PostingAs" = "Posting as %@"; "Scene.Compose.Accessibility.RemovePoll" = "Kkes asenqed"; "Scene.Compose.Attachment.AttachmentBroken" = "%@-a yerreẓ, ur yezmir ara Ad d-yettwasali ɣef Mastodon."; "Scene.Compose.Attachment.AttachmentTooLarge" = "Attachment too large"; -"Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "Can not regonize this media attachment"; +"Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "Can not recognize this media attachment"; +"Scene.Compose.Attachment.CompressingState" = "Compressing..."; "Scene.Compose.Attachment.DescriptionPhoto" = "Glem-d tawlaft i wid yesɛan ugur deg yiẓri..."; "Scene.Compose.Attachment.DescriptionVideo" = "Glem-d tavidyut i wid yesɛan ugur deg yiẓri..."; "Scene.Compose.Attachment.LoadFailed" = "Load Failed"; "Scene.Compose.Attachment.Photo" = "tawlaft"; +"Scene.Compose.Attachment.ServerProcessingState" = "Server Processing..."; "Scene.Compose.Attachment.UploadFailed" = "Upload Failed"; "Scene.Compose.Attachment.Video" = "tavidyutt"; "Scene.Compose.AutoComplete.SpaceToAdd" = "Tallunt ara yettwarnun"; @@ -192,6 +209,8 @@ Ad d-yettwasali ɣef Mastodon."; "Scene.Compose.Poll.OptionNumber" = "Taxtiṛt %ld"; "Scene.Compose.Poll.SevenDays" = "7 n wussan"; "Scene.Compose.Poll.SixHours" = "6 n yisragen"; +"Scene.Compose.Poll.ThePollHasEmptyOption" = "The poll has empty option"; +"Scene.Compose.Poll.ThePollIsInvalid" = "The poll is invalid"; "Scene.Compose.Poll.ThirtyMinutes" = "30 n tesdatin"; "Scene.Compose.Poll.ThreeDays" = "3 n wussan"; "Scene.Compose.ReplyingToUser" = "tiririt ɣef %@"; @@ -223,6 +242,12 @@ Ad d-yettwasali ɣef Mastodon."; "Scene.Familiarfollowers.Title" = "Ineḍfaren i tessneḍ"; "Scene.Favorite.Title" = "Ismenyifen-ik·im"; "Scene.FavoritedBy.Title" = "Ismenyaf-it"; +"Scene.FollowedTags.Actions.Follow" = "Follow"; +"Scene.FollowedTags.Actions.Unfollow" = "Unfollow"; +"Scene.FollowedTags.Header.Participants" = "participants"; +"Scene.FollowedTags.Header.Posts" = "posts"; +"Scene.FollowedTags.Header.PostsToday" = "posts today"; +"Scene.FollowedTags.Title" = "Followed Tags"; "Scene.Follower.Footer" = "Ineḍfaren seg yiqeddacen-nniḍen ur d-ttwaskanen ara."; "Scene.Follower.Title" = "aneḍfar"; "Scene.Following.Footer" = "Ineḍfaren seg yiqeddacen-nniḍen ur d-ttwaskanen ara."; @@ -231,9 +256,12 @@ Ad d-yettwasali ɣef Mastodon."; "Scene.HomeTimeline.NavigationBarState.Accessibility.LogoLabel" = "Taqeffalt n ulugu"; "Scene.HomeTimeline.NavigationBarState.NewPosts" = "Tissufaɣ timaynutin"; "Scene.HomeTimeline.NavigationBarState.Offline" = "Beṛṛa n tuqqna"; -"Scene.HomeTimeline.NavigationBarState.Published" = "Yettwasuffeɣ!"; -"Scene.HomeTimeline.NavigationBarState.Publishing" = "Asuffeɣ tasuffeɣt..."; +"Scene.HomeTimeline.NavigationBarState.Published" = "Tettwasuffeɣ!"; +"Scene.HomeTimeline.NavigationBarState.Publishing" = "Asuffeɣ n tasuffeɣt..."; "Scene.HomeTimeline.Title" = "Agejdan"; +"Scene.Login.ServerSearchField.Placeholder" = "Sekcem URL neɣ nadi ɣef uqeddac-ik·im"; +"Scene.Login.Subtitle" = "Log you in on the server you created your account on."; +"Scene.Login.Title" = "Ansuf yess·ek·em"; "Scene.Notification.FollowRequest.Accept" = "Accept"; "Scene.Notification.FollowRequest.Accepted" = "Accepted"; "Scene.Notification.FollowRequest.Reject" = "agi"; @@ -259,8 +287,11 @@ Ad d-yettwasali ɣef Mastodon."; "Scene.Profile.Dashboard.Following" = "iṭafaṛ"; "Scene.Profile.Dashboard.Posts" = "tisuffaɣ"; "Scene.Profile.Fields.AddRow" = "Rnu izirig"; +"Scene.Profile.Fields.Joined" = "Joined"; "Scene.Profile.Fields.Placeholder.Content" = "Agbur"; "Scene.Profile.Fields.Placeholder.Label" = "Tabzimt"; +"Scene.Profile.Fields.Verified.Long" = "Ownership of this link was checked on %@"; +"Scene.Profile.Fields.Verified.Short" = "Verified on %@"; "Scene.Profile.Header.FollowsYou" = "Yeṭṭafaṛ-ik•im"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Message" = "Sentem asewḥel n %@"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title" = "Sewḥel amiḍan"; @@ -346,7 +377,7 @@ Ad d-yettwasali ɣef Mastodon."; "Scene.Report.StepOne.WhatsWrongWithThisAccount" = "Acu n wugur yellan deg umiḍan-a?"; "Scene.Report.StepOne.WhatsWrongWithThisPost" = "Acu n wugur yellan d tsuffeɣt-a?"; "Scene.Report.StepOne.WhatsWrongWithThisUsername" = "Acu n wugur yellan d %@?"; -"Scene.Report.StepOne.YouAreAwareThatItBreaksSpecificRules" = "Teẓriḍ y•tettruẓu kra n yilugan"; +"Scene.Report.StepOne.YouAreAwareThatItBreaksSpecificRules" = "Teẓriḍ y·tettruẓu kra n yilugan"; "Scene.Report.StepThree.AreThereAnyPostsThatBackUpThisReport" = "Llant tsuffaɣ ara isdemren aneqqis-a?"; "Scene.Report.StepThree.SelectAllThatApply" = "Fren akk tifrat ara yettusnasen"; "Scene.Report.StepThree.Step3Of4" = "Aḥric 3 seg 4"; @@ -393,13 +424,11 @@ Ad d-yettwasali ɣef Mastodon."; "Scene.ServerPicker.EmptyState.BadNetwork" = "Tella-d tuccḍa lawan n usali n yisefka. Senqed tuqqna-ink internet."; "Scene.ServerPicker.EmptyState.FindingServers" = "Tifin n yiqeddacen yellan..."; "Scene.ServerPicker.EmptyState.NoResults" = "Ulac igemmaḍ"; -"Scene.ServerPicker.Input.Placeholder" = "Nadi timɣiwnin"; "Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Nadi timɣiwnin neɣ sekcem URL"; "Scene.ServerPicker.Label.Category" = "TAGGAYT"; "Scene.ServerPicker.Label.Language" = "TUTLAYT"; "Scene.ServerPicker.Label.Users" = "ISEQDACEN"; -"Scene.ServerPicker.Subtitle" = "Fren tamɣiwent almend n wayen tḥemmleḍ, n tmurt-ik neɣ n yiswi-inek amatu."; -"Scene.ServerPicker.SubtitleExtend" = "Fren tamɣiwent almend n wayen tḥemmleḍ, n tmurt-ik neɣ n yiswi-inek amatu. Yal tamɣiwent tsedday-itt tkebbanit neɣ amdan ilelliyen."; +"Scene.ServerPicker.Subtitle" = "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers."; "Scene.ServerPicker.Title" = "Mastodon yettwaxdem i yiseqdacen deg waṭas n temɣiwnin."; "Scene.ServerRules.Button.Confirm" = "Qebleɣ"; "Scene.ServerRules.PrivacyPolicy" = "tasertit tabaḍnit"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/kab.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/kab.lproj/Localizable.stringsdict index 7fc6a50bb..f18a906c0 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/kab.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/kab.lproj/Localizable.stringsdict @@ -50,6 +50,22 @@ %ld yisekkilen + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ left + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 n usekkil + other + %ld n isekkilen + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey @@ -120,7 +136,7 @@ NSStringFormatValueTypeKey ld one - 1 tsuffeɣt + 1 n tsuffeɣt other %ld n tsuffaɣ @@ -296,9 +312,9 @@ NSStringFormatValueTypeKey ld one - Yeqqim-d 1 wass + Yeqqim-d 1 n wass other - Qqimen-d %ld wussan + Qqimen-d %ld n wussan date.hour.left @@ -312,9 +328,9 @@ NSStringFormatValueTypeKey ld one - Yeqqim-d 1 usrag + Yeqqim-d 1 n wesrag other - Qqimen-d %ld yisragen + Qqimen-d %ld n yisragen date.minute.left @@ -328,9 +344,9 @@ NSStringFormatValueTypeKey ld one - 1 tesdat i d-yeqqimen + 1 n tesdat i d-yeqqimen other - %ld tesdatin i d-yeqqimen + %ld n tesdatin i d-yeqqimen date.second.left @@ -344,9 +360,9 @@ NSStringFormatValueTypeKey ld one - 1 tasint i d-yeqqimen + 1 n tasint i d-yeqqimen other - %ld tsinin i d-yeqqimen + %ld n tasinin i d-yeqqimen date.year.ago.abbr @@ -360,9 +376,9 @@ NSStringFormatValueTypeKey ld one - 1 useggas aya + %ld n useggas aya other - %ld yiseggasen aya + %ld n yiseggasen aya date.month.ago.abbr @@ -376,9 +392,9 @@ NSStringFormatValueTypeKey ld one - 1 wayyur aya + %ld n wayyur aya other - %ld wayyuren aya + %ld n wayyuren aya date.day.ago.abbr @@ -392,9 +408,9 @@ NSStringFormatValueTypeKey ld one - 1 wass aya + %ld n wass aya other - %ld wussan aya + %ld n wussan aya date.hour.ago.abbr @@ -408,9 +424,9 @@ NSStringFormatValueTypeKey ld one - 1 usrag aya + %ld n wesrag aya other - %ld yisragen aya + %ld n yisragen aya date.minute.ago.abbr @@ -424,9 +440,9 @@ NSStringFormatValueTypeKey ld one - 1 tesdat aya + %ld n tesdat aya other - %ld tesdatin aya + %ld n tesdatin aya date.second.ago.abbr @@ -440,9 +456,9 @@ NSStringFormatValueTypeKey ld one - 1 tasint aya + %ld n tasint aya other - %ld tsinin aya + %ld n tasinin aya diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/ku.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/ku.lproj/Localizable.strings index 10d88488a..6eaa474f0 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/ku.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/ku.lproj/Localizable.strings @@ -22,6 +22,9 @@ Jkx girêdana înternetê xwe kontrol bike."; "Common.Alerts.SignOut.Message" = "Ma tu dixwazî ku derkevî?"; "Common.Alerts.SignOut.Title" = "Derkeve"; "Common.Alerts.SignUpFailure.Title" = "Tomarkirin têkçû"; +"Common.Alerts.TranslationFailed.Button" = "BAŞ E"; +"Common.Alerts.TranslationFailed.Message" = "Werger têk çû. Dibe ku rêvebir werger li ser vê rajakarê çalak nekiribe an jî ev rajakar guhertoyek kevntir a Mastodon e ku werger hîn nehatiye piştgirîkirin."; +"Common.Alerts.TranslationFailed.Title" = "Nîşe"; "Common.Alerts.VoteFailure.PollEnded" = "Rapirsîya qediya"; "Common.Alerts.VoteFailure.Title" = "Dengdayîn têkçû"; "Common.Controls.Actions.Add" = "Tevlî bike"; @@ -31,6 +34,7 @@ Jkx girêdana înternetê xwe kontrol bike."; "Common.Controls.Actions.Compose" = "Binivîsîne"; "Common.Controls.Actions.Confirm" = "Bipejirîne"; "Common.Controls.Actions.Continue" = "Bidomîne"; +"Common.Controls.Actions.Copy" = "Copy"; "Common.Controls.Actions.CopyPhoto" = "Wêneyê jê bigire"; "Common.Controls.Actions.Delete" = "Jê bibe"; "Common.Controls.Actions.Discard" = "Biavêje"; @@ -56,9 +60,11 @@ Jkx girêdana înternetê xwe kontrol bike."; "Common.Controls.Actions.SharePost" = "Şandiyê parve bike"; "Common.Controls.Actions.ShareUser" = "%@ parve bike"; "Common.Controls.Actions.SignIn" = "Têkeve"; -"Common.Controls.Actions.SignUp" = "Tomar bibe"; +"Common.Controls.Actions.SignUp" = "Ajimêr biafirîne"; "Common.Controls.Actions.Skip" = "Derbas bike"; "Common.Controls.Actions.TakePhoto" = "Wêne bikişîne"; +"Common.Controls.Actions.TranslatePost.Title" = "Ji %@ hate wergerandin"; +"Common.Controls.Actions.TranslatePost.UnknownLanguage" = "Nenas"; "Common.Controls.Actions.TryAgain" = "Dîsa biceribîne"; "Common.Controls.Actions.UnblockDomain" = "%@ asteng neke"; "Common.Controls.Friendship.Block" = "Asteng bike"; @@ -100,6 +106,7 @@ Jkx girêdana înternetê xwe kontrol bike."; "Common.Controls.Status.Actions.Menu" = "Kulîn"; "Common.Controls.Status.Actions.Reblog" = "Ji nû ve nivîsandin"; "Common.Controls.Status.Actions.Reply" = "Bersivê bide"; +"Common.Controls.Status.Actions.ShareLinkInPost" = "Share Link in Post"; "Common.Controls.Status.Actions.ShowGif" = "GIF nîşan bide"; "Common.Controls.Status.Actions.ShowImage" = "Wêneyê nîşan bide"; "Common.Controls.Status.Actions.ShowVideoPlayer" = "Lêdera vîdyoyê nîşan bide"; @@ -107,6 +114,8 @@ Jkx girêdana înternetê xwe kontrol bike."; "Common.Controls.Status.Actions.Unfavorite" = "Nebijarte"; "Common.Controls.Status.Actions.Unreblog" = "Ji nû ve nivîsandinê vegere"; "Common.Controls.Status.ContentWarning" = "Hişyariya naverokê"; +"Common.Controls.Status.LinkViaUser" = "%@ via %@"; +"Common.Controls.Status.LoadEmbed" = "Load Embed"; "Common.Controls.Status.MediaContentWarning" = "Ji bo eşkerekirinê li derekî bitikîne"; "Common.Controls.Status.MetaEntity.Email" = "Navnîşanên e-nameyê: %@"; "Common.Controls.Status.MetaEntity.Hashtag" = "Hashtagê: %@"; @@ -124,6 +133,10 @@ Jkx girêdana înternetê xwe kontrol bike."; "Common.Controls.Status.Tag.Mention" = "Qalkirin"; "Common.Controls.Status.Tag.Url" = "URL"; "Common.Controls.Status.TapToReveal" = "Ji bo dîtinê bitikîne"; +"Common.Controls.Status.Translation.ShowOriginal" = "A resen nîşan bide"; +"Common.Controls.Status.Translation.TranslatedFrom" = "Translated from %@ using %@"; +"Common.Controls.Status.Translation.UnknownLanguage" = "Nenas"; +"Common.Controls.Status.Translation.UnknownProvider" = "Unknown"; "Common.Controls.Status.UserReblogged" = "%@ ji nû ve nivîsand"; "Common.Controls.Status.UserRepliedTo" = "Bersiv da %@"; "Common.Controls.Status.Visibility.Direct" = "Tenê bikarhênerê qalkirî dikare vê şandiyê bibîne."; @@ -131,9 +144,9 @@ Jkx girêdana înternetê xwe kontrol bike."; "Common.Controls.Status.Visibility.PrivateFromMe" = "Tenê şopînerên min dikarin vê şandiyê bibînin."; "Common.Controls.Status.Visibility.Unlisted" = "Her kes dikare vê şandiyê bibîne lê nayê nîşandan di demnameya gelemperî de."; "Common.Controls.Tabs.Home" = "Serrûpel"; -"Common.Controls.Tabs.Notification" = "Agahdarî"; +"Common.Controls.Tabs.Notifications" = "Agahdarî"; "Common.Controls.Tabs.Profile" = "Profîl"; -"Common.Controls.Tabs.Search" = "Bigere"; +"Common.Controls.Tabs.SearchAndExplore" = "Bigere û vekole"; "Common.Controls.Timeline.Filtered" = "Parzûnkirî"; "Common.Controls.Timeline.Header.BlockedWarning" = "Tu nikarî profîla vî/ê bikarhênerî bibînî heya ku ew astengiyê li ser te rakin."; @@ -161,16 +174,20 @@ Profîla te ji wan ra wiha xuya dike."; "Scene.Compose.Accessibility.CustomEmojiPicker" = "Hilbijêrê emojî yên kesanekirî"; "Scene.Compose.Accessibility.DisableContentWarning" = "Hişyariya naverokê neçalak bike"; "Scene.Compose.Accessibility.EnableContentWarning" = "Hişyariya naverokê çalak bike"; +"Scene.Compose.Accessibility.PostOptions" = "Vebijêrkên şandiyê"; "Scene.Compose.Accessibility.PostVisibilityMenu" = "Kulîna xuyabûna şandiyê"; +"Scene.Compose.Accessibility.PostingAs" = "Biweşîne wekî %@"; "Scene.Compose.Accessibility.RemovePoll" = "Rapirsî rake"; "Scene.Compose.Attachment.AttachmentBroken" = "Ev %@ naxebite û nayê barkirin li ser Mastodon."; "Scene.Compose.Attachment.AttachmentTooLarge" = "Pêvek pir mezin e"; "Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "Nikare ev pêveka medyayê nas bike"; +"Scene.Compose.Attachment.CompressingState" = "Tê guvaştin..."; "Scene.Compose.Attachment.DescriptionPhoto" = "Wêneyê ji bo kêmbînên dîtbar bide nasîn..."; "Scene.Compose.Attachment.DescriptionVideo" = "Vîdyoyê ji bo kêmbînên dîtbar bide nasîn..."; "Scene.Compose.Attachment.LoadFailed" = "Barkirin têk çû"; "Scene.Compose.Attachment.Photo" = "wêne"; +"Scene.Compose.Attachment.ServerProcessingState" = "Pêvajoya rajekar pêş de diçe..."; "Scene.Compose.Attachment.UploadFailed" = "Barkirin têk çû"; "Scene.Compose.Attachment.Video" = "vîdyo"; "Scene.Compose.AutoComplete.SpaceToAdd" = "Bicîhkirinê tevlî bike"; @@ -192,6 +209,8 @@ Profîla te ji wan ra wiha xuya dike."; "Scene.Compose.Poll.OptionNumber" = "Vebijêrk %ld"; "Scene.Compose.Poll.SevenDays" = "7 Roj"; "Scene.Compose.Poll.SixHours" = "6 Demjimêr"; +"Scene.Compose.Poll.ThePollHasEmptyOption" = "Vebijêrkên vê dengdayînê vala ne"; +"Scene.Compose.Poll.ThePollIsInvalid" = "Ev dengdayîn ne derbasdar e"; "Scene.Compose.Poll.ThirtyMinutes" = "30 xulek"; "Scene.Compose.Poll.ThreeDays" = "3 Roj"; "Scene.Compose.ReplyingToUser" = "bersiv bide %@"; @@ -224,6 +243,12 @@ girêdanê bitikne da ku ajimêra xwe bidî piştrastkirin."; "Scene.Familiarfollowers.Title" = "Şopînerên ku tu wan nas dikî"; "Scene.Favorite.Title" = "Bijarteyên te"; "Scene.FavoritedBy.Title" = "Hatiye hezkirin ji aliyê"; +"Scene.FollowedTags.Actions.Follow" = "Bişopîne"; +"Scene.FollowedTags.Actions.Unfollow" = "Neşopîne"; +"Scene.FollowedTags.Header.Participants" = "beşdar"; +"Scene.FollowedTags.Header.Posts" = "şandî"; +"Scene.FollowedTags.Header.PostsToday" = "şandiyên îro"; +"Scene.FollowedTags.Title" = "Hashtagên şopandî"; "Scene.Follower.Footer" = "Şopîner ji rajekerên din nayê dîtin."; "Scene.Follower.Title" = "şopîner"; "Scene.Following.Footer" = "Şopandin ji rajekerên din nayê dîtin."; @@ -235,6 +260,9 @@ girêdanê bitikne da ku ajimêra xwe bidî piştrastkirin."; "Scene.HomeTimeline.NavigationBarState.Published" = "Hate weşandin!"; "Scene.HomeTimeline.NavigationBarState.Publishing" = "Şandî tê weşandin..."; "Scene.HomeTimeline.Title" = "Serrûpel"; +"Scene.Login.ServerSearchField.Placeholder" = "Girêdanê têxe an jî li rajekarê xwe bigere"; +"Scene.Login.Subtitle" = "Têketinê bike ser rajekarê ku te ajimêrê xwe tê de çê kiriye."; +"Scene.Login.Title" = "Dîsa bi xêr hatî"; "Scene.Notification.FollowRequest.Accept" = "Bipejirîne"; "Scene.Notification.FollowRequest.Accepted" = "Pejirandî"; "Scene.Notification.FollowRequest.Reject" = "nepejirîne"; @@ -260,8 +288,11 @@ girêdanê bitikne da ku ajimêra xwe bidî piştrastkirin."; "Scene.Profile.Dashboard.Following" = "dişopîne"; "Scene.Profile.Dashboard.Posts" = "şandî"; "Scene.Profile.Fields.AddRow" = "Rêzê tevlî bike"; +"Scene.Profile.Fields.Joined" = "Dîroka tevlîbûnê"; "Scene.Profile.Fields.Placeholder.Content" = "Naverok"; "Scene.Profile.Fields.Placeholder.Label" = "Nîşan"; +"Scene.Profile.Fields.Verified.Long" = "Xwedaniya li vê girêdanê di %@ de hatiye kontrolkirin"; +"Scene.Profile.Fields.Verified.Short" = "Hate piştrastkirin li ser %@"; "Scene.Profile.Header.FollowsYou" = "Te dişopîne"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Message" = "Ji bo rakirina astengkirinê %@ bipejirîne"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title" = "Ajimêr asteng bike"; @@ -394,13 +425,11 @@ girêdanê bitikne da ku ajimêra xwe bidî piştrastkirin."; "Scene.ServerPicker.EmptyState.BadNetwork" = "Di dema barkirina daneyan da çewtî derket. Girêdana xwe ya înternetê kontrol bike."; "Scene.ServerPicker.EmptyState.FindingServers" = "Peydakirina rajekarên berdest..."; "Scene.ServerPicker.EmptyState.NoResults" = "Encam tune"; -"Scene.ServerPicker.Input.Placeholder" = "Li rajekaran bigere"; -"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Li rajekaran bigere an jî girêdanê têxe"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Li civakan bigere an jî girêdanê têxe"; "Scene.ServerPicker.Label.Category" = "BEŞ"; "Scene.ServerPicker.Label.Language" = "ZIMAN"; "Scene.ServerPicker.Label.Users" = "BIKARHÊNER"; -"Scene.ServerPicker.Subtitle" = "Li gorî berjewendî, herêm, an jî armancek gelemperî civakekê hilbijêre."; -"Scene.ServerPicker.SubtitleExtend" = "Li gorî berjewendî, herêm, an jî armancek gelemperî civakekê hilbijêre. Her civakek ji hêla rêxistinek an kesek bi tevahî serbixwe ve tê xebitandin."; +"Scene.ServerPicker.Subtitle" = "Li gorî herêm, berjewendî, an jî armanceke giştî rajekarekê hilbijêre. Tu hîn jî dikarî li ser Mastodon bi her kesî re biaxivî, her rajekarê te çi be."; "Scene.ServerPicker.Title" = "Mastodon ji bikarhênerên di civakên cuda de pêk tê."; "Scene.ServerRules.Button.Confirm" = "Ez dipejirînim"; "Scene.ServerRules.PrivacyPolicy" = "polîtikaya nihêniyê"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/ku.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/ku.lproj/Localizable.stringsdict index 77571439f..c904186d8 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/ku.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/ku.lproj/Localizable.stringsdict @@ -50,6 +50,22 @@ %ld tîp + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ maye + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 peyv + other + %ld peyv + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/nl.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/nl.lproj/Localizable.strings index e719583aa..6e7839852 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/nl.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/nl.lproj/Localizable.strings @@ -21,6 +21,9 @@ "Common.Alerts.SignOut.Message" = "Weet je zeker dat je wilt uitloggen?"; "Common.Alerts.SignOut.Title" = "Log-uit"; "Common.Alerts.SignUpFailure.Title" = "Registratiefout"; +"Common.Alerts.TranslationFailed.Button" = "OK"; +"Common.Alerts.TranslationFailed.Message" = "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported."; +"Common.Alerts.TranslationFailed.Title" = "Note"; "Common.Alerts.VoteFailure.PollEnded" = "De peiling is beëindigd"; "Common.Alerts.VoteFailure.Title" = "Stemmen Mislukt"; "Common.Controls.Actions.Add" = "Voeg toe"; @@ -30,6 +33,7 @@ "Common.Controls.Actions.Compose" = "Schrijf bericht"; "Common.Controls.Actions.Confirm" = "Bevestigen"; "Common.Controls.Actions.Continue" = "Ga verder"; +"Common.Controls.Actions.Copy" = "Copy"; "Common.Controls.Actions.CopyPhoto" = "Kopieer foto"; "Common.Controls.Actions.Delete" = "Verwijder"; "Common.Controls.Actions.Discard" = "Weggooien"; @@ -54,10 +58,12 @@ "Common.Controls.Actions.Share" = "Deel"; "Common.Controls.Actions.SharePost" = "Deel bericht"; "Common.Controls.Actions.ShareUser" = "Delen %@"; -"Common.Controls.Actions.SignIn" = "Aanmelden"; -"Common.Controls.Actions.SignUp" = "Registreren"; +"Common.Controls.Actions.SignIn" = "Inloggen"; +"Common.Controls.Actions.SignUp" = "Account aanmaken"; "Common.Controls.Actions.Skip" = "Overslaan"; "Common.Controls.Actions.TakePhoto" = "Maak foto"; +"Common.Controls.Actions.TranslatePost.Title" = "Translate from %@"; +"Common.Controls.Actions.TranslatePost.UnknownLanguage" = "Unknown"; "Common.Controls.Actions.TryAgain" = "Probeer Opnieuw"; "Common.Controls.Actions.UnblockDomain" = "Deblokkeer %@"; "Common.Controls.Friendship.Block" = "Blokkeren"; @@ -67,13 +73,13 @@ "Common.Controls.Friendship.EditInfo" = "Bewerken"; "Common.Controls.Friendship.Follow" = "Volgen"; "Common.Controls.Friendship.Following" = "Gevolgd"; -"Common.Controls.Friendship.HideReblogs" = "Hide Reblogs"; +"Common.Controls.Friendship.HideReblogs" = "Verberg reblogs"; "Common.Controls.Friendship.Mute" = "Negeren"; "Common.Controls.Friendship.MuteUser" = "Negeer %@"; "Common.Controls.Friendship.Muted" = "Genegeerd"; "Common.Controls.Friendship.Pending" = "In afwachting"; "Common.Controls.Friendship.Request" = "Verzoeken"; -"Common.Controls.Friendship.ShowReblogs" = "Show Reblogs"; +"Common.Controls.Friendship.ShowReblogs" = "Toon reblogs"; "Common.Controls.Friendship.Unblock" = "Deblokkeer"; "Common.Controls.Friendship.UnblockUser" = "Deblokkeer %@"; "Common.Controls.Friendship.Unmute" = "Niet langer negeren"; @@ -99,6 +105,7 @@ "Common.Controls.Status.Actions.Menu" = "Menu"; "Common.Controls.Status.Actions.Reblog" = "Delen"; "Common.Controls.Status.Actions.Reply" = "Reageren"; +"Common.Controls.Status.Actions.ShareLinkInPost" = "Share Link in Post"; "Common.Controls.Status.Actions.ShowGif" = "GIF weergeven"; "Common.Controls.Status.Actions.ShowImage" = "Toon afbeelding"; "Common.Controls.Status.Actions.ShowVideoPlayer" = "Toon videospeler"; @@ -106,10 +113,12 @@ "Common.Controls.Status.Actions.Unfavorite" = "Verwijderen uit Favorieten"; "Common.Controls.Status.Actions.Unreblog" = "Delen ongedaan maken"; "Common.Controls.Status.ContentWarning" = "Inhoudswaarschuwing"; +"Common.Controls.Status.LinkViaUser" = "%@ via %@"; +"Common.Controls.Status.LoadEmbed" = "Load Embed"; "Common.Controls.Status.MediaContentWarning" = "Tap hier om te tonen"; -"Common.Controls.Status.MetaEntity.Email" = "Email address: %@"; +"Common.Controls.Status.MetaEntity.Email" = "E-mailadres: %@"; "Common.Controls.Status.MetaEntity.Hashtag" = "Hashtag: %@"; -"Common.Controls.Status.MetaEntity.Mention" = "Show Profile: %@"; +"Common.Controls.Status.MetaEntity.Mention" = "Profiel weergeven: %@"; "Common.Controls.Status.MetaEntity.Url" = "Link: %@"; "Common.Controls.Status.Poll.Closed" = "Gesloten"; "Common.Controls.Status.Poll.Vote" = "Stemmen"; @@ -123,6 +132,10 @@ "Common.Controls.Status.Tag.Mention" = "Vermelden"; "Common.Controls.Status.Tag.Url" = "URL"; "Common.Controls.Status.TapToReveal" = "Tik om te onthullen"; +"Common.Controls.Status.Translation.ShowOriginal" = "Shown Original"; +"Common.Controls.Status.Translation.TranslatedFrom" = "Translated from %@ using %@"; +"Common.Controls.Status.Translation.UnknownLanguage" = "Unknown"; +"Common.Controls.Status.Translation.UnknownProvider" = "Unknown"; "Common.Controls.Status.UserReblogged" = "%@ heeft gedeeld"; "Common.Controls.Status.UserRepliedTo" = "Reactie op %@"; "Common.Controls.Status.Visibility.Direct" = "Alleen de vermelde persoon kan dit bericht zien."; @@ -130,9 +143,9 @@ "Common.Controls.Status.Visibility.PrivateFromMe" = "Alleen mijn volgers kunnen dit bericht zien."; "Common.Controls.Status.Visibility.Unlisted" = "Iedereen kan dit bericht zien maar het wordt niet in de publieke tijdlijn getoond."; "Common.Controls.Tabs.Home" = "Start"; -"Common.Controls.Tabs.Notification" = "Melding"; +"Common.Controls.Tabs.Notifications" = "Notifications"; "Common.Controls.Tabs.Profile" = "Profiel"; -"Common.Controls.Tabs.Search" = "Zoek"; +"Common.Controls.Tabs.SearchAndExplore" = "Search and Explore"; "Common.Controls.Timeline.Filtered" = "Gefilterd"; "Common.Controls.Timeline.Header.BlockedWarning" = "U kunt het profiel van deze gebruiker niet bekijken zolang u geblokkeerd bent."; "Common.Controls.Timeline.Header.BlockingWarning" = "U kunt het profiel van deze geblokkeerde gebruiker niet bekijken. @@ -156,16 +169,20 @@ Uw profiel ziet er zo uit voor hen."; "Scene.Compose.Accessibility.CustomEmojiPicker" = "Eigen Emojikiezer"; "Scene.Compose.Accessibility.DisableContentWarning" = "Inhoudswaarschuwing Uitschakelen"; "Scene.Compose.Accessibility.EnableContentWarning" = "Inhoudswaarschuwing inschakelen"; +"Scene.Compose.Accessibility.PostOptions" = "Plaats Bericht Opties"; "Scene.Compose.Accessibility.PostVisibilityMenu" = "Berichtzichtbaarheidsmenu"; +"Scene.Compose.Accessibility.PostingAs" = "Plaats bericht als %@"; "Scene.Compose.Accessibility.RemovePoll" = "Peiling verwijderen"; "Scene.Compose.Attachment.AttachmentBroken" = "Deze %@ is corrupt en kan niet geüpload worden naar Mastodon."; -"Scene.Compose.Attachment.AttachmentTooLarge" = "Attachment too large"; -"Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "Can not regonize this media attachment"; +"Scene.Compose.Attachment.AttachmentTooLarge" = "Bijlage te groot"; +"Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "Kan de media in de bijlage niet herkennen"; +"Scene.Compose.Attachment.CompressingState" = "Bezig met comprimeren..."; "Scene.Compose.Attachment.DescriptionPhoto" = "Omschrijf de foto voor mensen met een visuele beperking..."; "Scene.Compose.Attachment.DescriptionVideo" = "Omschrijf de video voor mensen met een visuele beperking..."; -"Scene.Compose.Attachment.LoadFailed" = "Load Failed"; +"Scene.Compose.Attachment.LoadFailed" = "Laden mislukt"; "Scene.Compose.Attachment.Photo" = "foto"; -"Scene.Compose.Attachment.UploadFailed" = "Upload Failed"; +"Scene.Compose.Attachment.ServerProcessingState" = "Server is bezig met verwerken..."; +"Scene.Compose.Attachment.UploadFailed" = "Uploaden mislukt"; "Scene.Compose.Attachment.Video" = "video"; "Scene.Compose.AutoComplete.SpaceToAdd" = "Spaties toe te voegen"; "Scene.Compose.ComposeAction" = "Publiceren"; @@ -186,6 +203,8 @@ Uw profiel ziet er zo uit voor hen."; "Scene.Compose.Poll.OptionNumber" = "Optie %ld"; "Scene.Compose.Poll.SevenDays" = "7 Dagen"; "Scene.Compose.Poll.SixHours" = "6 Uur"; +"Scene.Compose.Poll.ThePollHasEmptyOption" = "De peiling heeft een lege optie"; +"Scene.Compose.Poll.ThePollIsInvalid" = "De peiling is ongeldig"; "Scene.Compose.Poll.ThirtyMinutes" = "30 minuten"; "Scene.Compose.Poll.ThreeDays" = "3 Dagen"; "Scene.Compose.ReplyingToUser" = "reageren op %@"; @@ -206,33 +225,42 @@ Uw profiel ziet er zo uit voor hen."; "Scene.ConfirmEmail.OpenEmailApp.Title" = "Ga naar uw inbox."; "Scene.ConfirmEmail.Subtitle" = "We hebben een e-mail gestuurd naar %@, klik op de link om uw account te bevestigen."; -"Scene.ConfirmEmail.TapTheLinkWeEmailedToYouToVerifyYourAccount" = "Tap the link we emailed to you to verify your account"; +"Scene.ConfirmEmail.TapTheLinkWeEmailedToYouToVerifyYourAccount" = "Tik op de link in de e-mail die je hebt ontvangen om uw account te verifiëren"; "Scene.ConfirmEmail.Title" = "Nog één ding."; "Scene.Discovery.Intro" = "Dit zijn de berichten die populair zijn in jouw Mastodon-kringen."; -"Scene.Discovery.Tabs.Community" = "Community"; +"Scene.Discovery.Tabs.Community" = "Gemeenschap"; "Scene.Discovery.Tabs.ForYou" = "Voor jou"; "Scene.Discovery.Tabs.Hashtags" = "Hashtags"; "Scene.Discovery.Tabs.News" = "Nieuws"; "Scene.Discovery.Tabs.Posts" = "Berichten"; -"Scene.Familiarfollowers.FollowedByNames" = "Followed by %@"; -"Scene.Familiarfollowers.Title" = "Followers you familiar"; +"Scene.Familiarfollowers.FollowedByNames" = "Gevolgd door %@"; +"Scene.Familiarfollowers.Title" = "Volgers die je kent"; "Scene.Favorite.Title" = "Uw favorieten"; "Scene.FavoritedBy.Title" = "Favorited By"; +"Scene.FollowedTags.Actions.Follow" = "Follow"; +"Scene.FollowedTags.Actions.Unfollow" = "Unfollow"; +"Scene.FollowedTags.Header.Participants" = "participants"; +"Scene.FollowedTags.Header.Posts" = "posts"; +"Scene.FollowedTags.Header.PostsToday" = "posts today"; +"Scene.FollowedTags.Title" = "Followed Tags"; "Scene.Follower.Footer" = "Volgers van andere servers worden niet weergegeven."; -"Scene.Follower.Title" = "follower"; +"Scene.Follower.Title" = "volger"; "Scene.Following.Footer" = "Volgers van andere servers worden niet weergegeven."; -"Scene.Following.Title" = "following"; -"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoHint" = "Tap to scroll to top and tap again to previous location"; -"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoLabel" = "Logo Button"; +"Scene.Following.Title" = "volgend"; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoHint" = "Tik om naar boven te scrollen en tik nogmaals om terug te keren naar de vorige locatie"; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoLabel" = "Logo knop"; "Scene.HomeTimeline.NavigationBarState.NewPosts" = "Bekijk nieuwe berichten"; "Scene.HomeTimeline.NavigationBarState.Offline" = "Offline"; "Scene.HomeTimeline.NavigationBarState.Published" = "Gepubliceerd!"; "Scene.HomeTimeline.NavigationBarState.Publishing" = "Bericht publiceren..."; "Scene.HomeTimeline.Title" = "Start"; -"Scene.Notification.FollowRequest.Accept" = "Accept"; -"Scene.Notification.FollowRequest.Accepted" = "Accepted"; -"Scene.Notification.FollowRequest.Reject" = "reject"; -"Scene.Notification.FollowRequest.Rejected" = "Rejected"; +"Scene.Login.ServerSearchField.Placeholder" = "Voer URL in of zoek naar uw server"; +"Scene.Login.Subtitle" = "Log je in op de server waarop je je account hebt aangemaakt."; +"Scene.Login.Title" = "Welkom terug"; +"Scene.Notification.FollowRequest.Accept" = "Accepteren"; +"Scene.Notification.FollowRequest.Accepted" = "Geaccepteerd"; +"Scene.Notification.FollowRequest.Reject" = "afwijzen"; +"Scene.Notification.FollowRequest.Rejected" = "Afgewezen"; "Scene.Notification.Keyobard.ShowEverything" = "Alles weergeven"; "Scene.Notification.Keyobard.ShowMentions" = "Vermeldingen weergeven"; "Scene.Notification.NotificationDescription.FavoritedYourPost" = "heeft je bericht als favoriet gemrkeerd"; @@ -254,17 +282,20 @@ klik op de link om uw account te bevestigen."; "Scene.Profile.Dashboard.Following" = "volgend"; "Scene.Profile.Dashboard.Posts" = "berichten"; "Scene.Profile.Fields.AddRow" = "Rij Toevoegen"; +"Scene.Profile.Fields.Joined" = "Joined"; "Scene.Profile.Fields.Placeholder.Content" = "Inhoud"; "Scene.Profile.Fields.Placeholder.Label" = "Etiket"; -"Scene.Profile.Header.FollowsYou" = "Follows You"; +"Scene.Profile.Fields.Verified.Long" = "Eigendom van deze link is gecontroleerd op %@"; +"Scene.Profile.Fields.Verified.Short" = "Geverifieerd op %@"; +"Scene.Profile.Header.FollowsYou" = "Volgt jou"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Message" = "Bevestig om %@ te blokkeren"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title" = "Blokkeer account"; -"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Message" = "Confirm to hide reblogs"; -"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Title" = "Hide Reblogs"; +"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Message" = "Bevestig om reblogs te verbergen"; +"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Title" = "Verberg reblogs"; "Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Message" = "Bevestig om %@ te negeren"; "Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Title" = "Negeer account"; -"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Message" = "Confirm to show reblogs"; -"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Title" = "Show Reblogs"; +"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Message" = "Bevestig om reblogs te tonen"; +"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Title" = "Toon reblogs"; "Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Message" = "Bevestig om %@ te deblokkeren"; "Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Title" = "Deblokkeer Account"; "Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.Message" = "Bevestig om %@ te negeren"; @@ -307,7 +338,7 @@ klik op de link om uw account te bevestigen."; "Scene.Register.Input.Password.Require" = "Je wachtwoord moet ten minste:"; "Scene.Register.Input.Username.DuplicatePrompt" = "Deze gebruikersnaam is al in gebruik."; "Scene.Register.Input.Username.Placeholder" = "gebruikersnaam"; -"Scene.Register.LetsGetYouSetUpOnDomain" = "Let’s get you set up on %@"; +"Scene.Register.LetsGetYouSetUpOnDomain" = "Laten we je account instellen op %@"; "Scene.Register.Title" = "Vertel ons over uzelf."; "Scene.Report.Content1" = "Zijn er nog meer berichten die u aan het rapport wilt toevoegen?"; "Scene.Report.Content2" = "Is er iets anders over dit rapport dat de moderators zouden moeten weten?"; @@ -335,12 +366,12 @@ klik op de link om uw account te bevestigen."; "Scene.Report.StepOne.ItsSomethingElse" = "It’s something else"; "Scene.Report.StepOne.ItsSpam" = "It’s spam"; "Scene.Report.StepOne.MaliciousLinksFakeEngagementOrRepetetiveReplies" = "Malicious links, fake engagement, or repetetive replies"; -"Scene.Report.StepOne.SelectTheBestMatch" = "Select the best match"; -"Scene.Report.StepOne.Step1Of4" = "Step 1 of 4"; +"Scene.Report.StepOne.SelectTheBestMatch" = "Selecteer de beste overeenkomst"; +"Scene.Report.StepOne.Step1Of4" = "Stap 1 van 4"; "Scene.Report.StepOne.TheIssueDoesNotFitIntoOtherCategories" = "The issue does not fit into other categories"; -"Scene.Report.StepOne.WhatsWrongWithThisAccount" = "What's wrong with this account?"; -"Scene.Report.StepOne.WhatsWrongWithThisPost" = "What's wrong with this post?"; -"Scene.Report.StepOne.WhatsWrongWithThisUsername" = "What's wrong with %@?"; +"Scene.Report.StepOne.WhatsWrongWithThisAccount" = "Wat is er mis met dit bericht?"; +"Scene.Report.StepOne.WhatsWrongWithThisPost" = "Wat is er mis met dit bericht?"; +"Scene.Report.StepOne.WhatsWrongWithThisUsername" = "Wat is er mis met %@?"; "Scene.Report.StepOne.YouAreAwareThatItBreaksSpecificRules" = "You are aware that it breaks specific rules"; "Scene.Report.StepThree.AreThereAnyPostsThatBackUpThisReport" = "Are there any posts that back up this report?"; "Scene.Report.StepThree.SelectAllThatApply" = "Select all that apply"; @@ -388,13 +419,11 @@ klik op de link om uw account te bevestigen."; "Scene.ServerPicker.EmptyState.BadNetwork" = "Er is een fout opgetreden bij het laden van de gegevens. Controleer uw internetverbinding."; "Scene.ServerPicker.EmptyState.FindingServers" = "Beschikbare servers zoeken..."; "Scene.ServerPicker.EmptyState.NoResults" = "Geen resultaten"; -"Scene.ServerPicker.Input.Placeholder" = "Zoek uw server of sluit u bij een nieuwe server aan..."; -"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Search servers or enter URL"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Zoek naar gemeenschappen of voer URL in"; "Scene.ServerPicker.Label.Category" = "CATEGORIE"; "Scene.ServerPicker.Label.Language" = "TAAL"; "Scene.ServerPicker.Label.Users" = "GEBRUIKERS"; -"Scene.ServerPicker.Subtitle" = "Kies een gemeenschap gebaseerd op jouw interesses, regio of een algemeen doel."; -"Scene.ServerPicker.SubtitleExtend" = "Kies een gemeenschap gebaseerd op jouw interesses, regio, of een algemeen doel. Elke gemeenschap wordt beheerd door een volledig onafhankelijke organisatie of individu."; +"Scene.ServerPicker.Subtitle" = "Kies een server gebaseerd op je regio, interesses, of een algemene server. Je kunt nog steeds chatten met iedereen op Mastodon, ongeacht op welke server je zit."; "Scene.ServerPicker.Title" = "Kies een server, welke dan ook."; "Scene.ServerRules.Button.Confirm" = "Ik Ga Akkoord"; "Scene.ServerRules.PrivacyPolicy" = "privacybeleid"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/nl.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/nl.lproj/Localizable.stringsdict index 314600ff7..29d0ec841 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/nl.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/nl.lproj/Localizable.stringsdict @@ -15,7 +15,7 @@ one 1 unread notification other - %ld unread notification + %ld unread notifications a11y.plural.count.input_limit_exceeds @@ -50,6 +50,22 @@ %ld tekens + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ left + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 character + other + %ld characters + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/ru.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/ru.lproj/Localizable.strings index 65d4fa65e..8275902d4 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/ru.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/ru.lproj/Localizable.strings @@ -22,6 +22,9 @@ "Common.Alerts.SignOut.Message" = "Вы действительно хотите выйти из учётной записи?"; "Common.Alerts.SignOut.Title" = "Выход"; "Common.Alerts.SignUpFailure.Title" = "Ошибка регистрации"; +"Common.Alerts.TranslationFailed.Button" = "OK"; +"Common.Alerts.TranslationFailed.Message" = "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported."; +"Common.Alerts.TranslationFailed.Title" = "Note"; "Common.Alerts.VoteFailure.PollEnded" = "Опрос уже завершился"; "Common.Alerts.VoteFailure.Title" = "Не удалось проголосовать"; "Common.Controls.Actions.Add" = "Добавить"; @@ -31,6 +34,7 @@ "Common.Controls.Actions.Compose" = "Написать"; "Common.Controls.Actions.Confirm" = "Подтвердить"; "Common.Controls.Actions.Continue" = "Продолжить"; +"Common.Controls.Actions.Copy" = "Copy"; "Common.Controls.Actions.CopyPhoto" = "Скопировать изображение"; "Common.Controls.Actions.Delete" = "Удалить"; "Common.Controls.Actions.Discard" = "Отмена"; @@ -55,10 +59,12 @@ "Common.Controls.Actions.Share" = "Поделиться"; "Common.Controls.Actions.SharePost" = "Поделиться постом"; "Common.Controls.Actions.ShareUser" = "Поделиться %@"; -"Common.Controls.Actions.SignIn" = "Войти"; -"Common.Controls.Actions.SignUp" = "Зарегистрироваться"; +"Common.Controls.Actions.SignIn" = "Log in"; +"Common.Controls.Actions.SignUp" = "Create account"; "Common.Controls.Actions.Skip" = "Пропустить"; "Common.Controls.Actions.TakePhoto" = "Сделать фото"; +"Common.Controls.Actions.TranslatePost.Title" = "Translate from %@"; +"Common.Controls.Actions.TranslatePost.UnknownLanguage" = "Unknown"; "Common.Controls.Actions.TryAgain" = "Попробовать снова"; "Common.Controls.Actions.UnblockDomain" = "Разблокировать %@"; "Common.Controls.Friendship.Block" = "Заблокировать"; @@ -100,6 +106,7 @@ "Common.Controls.Status.Actions.Menu" = "Меню"; "Common.Controls.Status.Actions.Reblog" = "Продвинуть"; "Common.Controls.Status.Actions.Reply" = "Ответить"; +"Common.Controls.Status.Actions.ShareLinkInPost" = "Share Link in Post"; "Common.Controls.Status.Actions.ShowGif" = "Показать GIF"; "Common.Controls.Status.Actions.ShowImage" = "Показать изображение"; "Common.Controls.Status.Actions.ShowVideoPlayer" = "Показать видеопроигрыватель"; @@ -107,6 +114,8 @@ "Common.Controls.Status.Actions.Unfavorite" = "Убрать из избранного"; "Common.Controls.Status.Actions.Unreblog" = "Убрать продвижение"; "Common.Controls.Status.ContentWarning" = "Предупреждение о содержании"; +"Common.Controls.Status.LinkViaUser" = "%@ via %@"; +"Common.Controls.Status.LoadEmbed" = "Load Embed"; "Common.Controls.Status.MediaContentWarning" = "Нажмите в любом месте, чтобы показать"; "Common.Controls.Status.MetaEntity.Email" = "Email address: %@"; "Common.Controls.Status.MetaEntity.Hashtag" = "Hashtag: %@"; @@ -124,6 +133,10 @@ "Common.Controls.Status.Tag.Mention" = "Упоминание"; "Common.Controls.Status.Tag.Url" = "Ссылка"; "Common.Controls.Status.TapToReveal" = "Нажмите, чтобы показать"; +"Common.Controls.Status.Translation.ShowOriginal" = "Shown Original"; +"Common.Controls.Status.Translation.TranslatedFrom" = "Translated from %@ using %@"; +"Common.Controls.Status.Translation.UnknownLanguage" = "Unknown"; +"Common.Controls.Status.Translation.UnknownProvider" = "Unknown"; "Common.Controls.Status.UserReblogged" = "%@ продвинул(а)"; "Common.Controls.Status.UserRepliedTo" = "Ответил(а) %@"; "Common.Controls.Status.Visibility.Direct" = "Only mentioned user can see this post."; @@ -131,9 +144,9 @@ "Common.Controls.Status.Visibility.PrivateFromMe" = "Only my followers can see this post."; "Common.Controls.Status.Visibility.Unlisted" = "Everyone can see this post but not display in the public timeline."; "Common.Controls.Tabs.Home" = "Главная"; -"Common.Controls.Tabs.Notification" = "Уведомление"; +"Common.Controls.Tabs.Notifications" = "Notifications"; "Common.Controls.Tabs.Profile" = "Профиль"; -"Common.Controls.Tabs.Search" = "Поиск"; +"Common.Controls.Tabs.SearchAndExplore" = "Search and Explore"; "Common.Controls.Timeline.Filtered" = "Отфильтровано"; "Common.Controls.Timeline.Header.BlockedWarning" = "Вы не можете просматривать профиль этого пользователя @@ -169,16 +182,20 @@ "Scene.Compose.Accessibility.CustomEmojiPicker" = "Меню пользовательских эмодзи"; "Scene.Compose.Accessibility.DisableContentWarning" = "Убрать предупреждение о содержании"; "Scene.Compose.Accessibility.EnableContentWarning" = "Добавить предупреждение о содержании"; +"Scene.Compose.Accessibility.PostOptions" = "Post Options"; "Scene.Compose.Accessibility.PostVisibilityMenu" = "Меню видимости поста"; +"Scene.Compose.Accessibility.PostingAs" = "Posting as %@"; "Scene.Compose.Accessibility.RemovePoll" = "Убрать опрос"; "Scene.Compose.Attachment.AttachmentBroken" = "Это %@ повреждено и не может быть отправлено в Mastodon."; "Scene.Compose.Attachment.AttachmentTooLarge" = "Attachment too large"; -"Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "Can not regonize this media attachment"; +"Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "Can not recognize this media attachment"; +"Scene.Compose.Attachment.CompressingState" = "Compressing..."; "Scene.Compose.Attachment.DescriptionPhoto" = "Опишите фото для людей с нарушениями зрения..."; "Scene.Compose.Attachment.DescriptionVideo" = "Опишите видео для людей с нарушениями зрения..."; "Scene.Compose.Attachment.LoadFailed" = "Load Failed"; "Scene.Compose.Attachment.Photo" = "изображение"; +"Scene.Compose.Attachment.ServerProcessingState" = "Server Processing..."; "Scene.Compose.Attachment.UploadFailed" = "Upload Failed"; "Scene.Compose.Attachment.Video" = "видео"; "Scene.Compose.AutoComplete.SpaceToAdd" = "Пробел, чтобы добавить"; @@ -200,6 +217,8 @@ "Scene.Compose.Poll.OptionNumber" = "Вариант %ld"; "Scene.Compose.Poll.SevenDays" = "7 дней"; "Scene.Compose.Poll.SixHours" = "6 часов"; +"Scene.Compose.Poll.ThePollHasEmptyOption" = "The poll has empty option"; +"Scene.Compose.Poll.ThePollIsInvalid" = "The poll is invalid"; "Scene.Compose.Poll.ThirtyMinutes" = "30 минут"; "Scene.Compose.Poll.ThreeDays" = "3 дня"; "Scene.Compose.ReplyingToUser" = "ответ %@"; @@ -234,6 +253,12 @@ "Scene.Familiarfollowers.Title" = "Followers you familiar"; "Scene.Favorite.Title" = "Ваше избранное"; "Scene.FavoritedBy.Title" = "Favorited By"; +"Scene.FollowedTags.Actions.Follow" = "Follow"; +"Scene.FollowedTags.Actions.Unfollow" = "Unfollow"; +"Scene.FollowedTags.Header.Participants" = "participants"; +"Scene.FollowedTags.Header.Posts" = "posts"; +"Scene.FollowedTags.Header.PostsToday" = "posts today"; +"Scene.FollowedTags.Title" = "Followed Tags"; "Scene.Follower.Footer" = "Followers from other servers are not displayed."; "Scene.Follower.Title" = "follower"; "Scene.Following.Footer" = "Follows from other servers are not displayed."; @@ -245,6 +270,9 @@ "Scene.HomeTimeline.NavigationBarState.Published" = "Опубликовано!"; "Scene.HomeTimeline.NavigationBarState.Publishing" = "Публикуем пост..."; "Scene.HomeTimeline.Title" = "Главная"; +"Scene.Login.ServerSearchField.Placeholder" = "Enter URL or search for your server"; +"Scene.Login.Subtitle" = "Log you in on the server you created your account on."; +"Scene.Login.Title" = "Welcome back"; "Scene.Notification.FollowRequest.Accept" = "Accept"; "Scene.Notification.FollowRequest.Accepted" = "Accepted"; "Scene.Notification.FollowRequest.Reject" = "reject"; @@ -270,8 +298,11 @@ "Scene.Profile.Dashboard.Following" = "подписки"; "Scene.Profile.Dashboard.Posts" = "посты"; "Scene.Profile.Fields.AddRow" = "Добавить строку"; +"Scene.Profile.Fields.Joined" = "Joined"; "Scene.Profile.Fields.Placeholder.Content" = "Содержимое"; "Scene.Profile.Fields.Placeholder.Label" = "Ярлык"; +"Scene.Profile.Fields.Verified.Long" = "Ownership of this link was checked on %@"; +"Scene.Profile.Fields.Verified.Short" = "Verified on %@"; "Scene.Profile.Header.FollowsYou" = "Подписан(а) на вас"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Message" = "Confirm to block %@"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title" = "Block Account"; @@ -404,13 +435,11 @@ "Scene.ServerPicker.EmptyState.BadNetwork" = "Что-то пошло не так при загрузке данных. Проверьте подключение к интернету."; "Scene.ServerPicker.EmptyState.FindingServers" = "Ищем доступные сервера..."; "Scene.ServerPicker.EmptyState.NoResults" = "Нет результатов"; -"Scene.ServerPicker.Input.Placeholder" = "Найдите сервер или присоединитесь к своему..."; -"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Поиск по серверам или ссылке"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Search communities or enter URL"; "Scene.ServerPicker.Label.Category" = "КАТЕГОРИЯ"; "Scene.ServerPicker.Label.Language" = "ЯЗЫК"; "Scene.ServerPicker.Label.Users" = "ПОЛЬЗОВАТЕЛИ"; -"Scene.ServerPicker.Subtitle" = "Выберите сообщество на основе своих интересов, региона или общей тематики."; -"Scene.ServerPicker.SubtitleExtend" = "Pick a server based on your interests, region, or a general purpose one. Each server is operated by an entirely independent organization or individual."; +"Scene.ServerPicker.Subtitle" = "Pick a server based on your region, interests, or a general purpose one. You can still chat with anyone on Mastodon, regardless of your servers."; "Scene.ServerPicker.Title" = "Выберите сервер, любой сервер."; "Scene.ServerRules.Button.Confirm" = "Принимаю"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/ru.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/ru.lproj/Localizable.stringsdict index afb29a6aa..d43386ad9 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/ru.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/ru.lproj/Localizable.stringsdict @@ -15,11 +15,11 @@ one 1 unread notification few - %ld unread notification + %ld unread notifications many - %ld unread notification + %ld unread notifications other - %ld unread notification + %ld unread notifications a11y.plural.count.input_limit_exceeds @@ -62,6 +62,26 @@ %ld символа осталось + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ left + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 character + few + %ld characters + many + %ld characters + other + %ld characters + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/sl.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/sl.lproj/Localizable.strings new file mode 100644 index 000000000..1aa2aab73 --- /dev/null +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/sl.lproj/Localizable.strings @@ -0,0 +1,484 @@ +"Common.Alerts.BlockDomain.BlockEntireDomain" = "Blokiraj domeno"; +"Common.Alerts.BlockDomain.Title" = "Ali ste res, res prepričani, da želite blokirati celotno %@? V večini primerov je nekaj ciljnih blokiranj ali utišanj dovolj in boljše. Vsebine iz te domene ne boste videli na javnih časovnicah ali obvestilih. Vaši sledilci iz te domene bodo odstranjeni."; +"Common.Alerts.CleanCache.Message" = "Uspešno počiščem predpomnilnik %@."; +"Common.Alerts.CleanCache.Title" = "Počisti predpomnilnik"; +"Common.Alerts.Common.PleaseTryAgain" = "Poskusite znova."; +"Common.Alerts.Common.PleaseTryAgainLater" = "Poskusite znova pozneje."; +"Common.Alerts.DeletePost.Message" = "Ali ste prepričani, da želite izbrisati to objavo?"; +"Common.Alerts.DeletePost.Title" = "Izbriši objavo"; +"Common.Alerts.DiscardPostContent.Message" = "Potrdite za opustitev sestavljene vsebine objave."; +"Common.Alerts.DiscardPostContent.Title" = "Zavrzi osnutek"; +"Common.Alerts.EditProfileFailure.Message" = "Profila ni mogoče urejati. Poskusite znova."; +"Common.Alerts.EditProfileFailure.Title" = "Napaka urejanja profila"; +"Common.Alerts.PublishPostFailure.AttachmentsMessage.MoreThanOneVideo" = "Ni možno priložiti več kot enega videoposnetka."; +"Common.Alerts.PublishPostFailure.AttachmentsMessage.VideoAttachWithPhoto" = "Videoposnetka ni mogoče priložiti objavi, ki že vsebuje slike."; +"Common.Alerts.PublishPostFailure.Message" = "Objava je spodletela. +Preverite svojo internetno povezavo."; +"Common.Alerts.PublishPostFailure.Title" = "Spodletela objava"; +"Common.Alerts.SavePhotoFailure.Message" = "Za shranjevanje fotografije omogočite pravice za dostop do knjižnice fotografij."; +"Common.Alerts.SavePhotoFailure.Title" = "Neuspelo shranjevanje fotografije"; +"Common.Alerts.ServerError.Title" = "Napaka strežnika"; +"Common.Alerts.SignOut.Confirm" = "Odjava"; +"Common.Alerts.SignOut.Message" = "Ali ste prepričani, da se želite odjaviti?"; +"Common.Alerts.SignOut.Title" = "Odjava"; +"Common.Alerts.SignUpFailure.Title" = "Neuspela registracija"; +"Common.Alerts.TranslationFailed.Button" = "V redu"; +"Common.Alerts.TranslationFailed.Message" = "Prevod je spodletel. Morda skrbnik ni omogočil prevajanja na tem strežniku ali pa strežnik teče na starejši različici Masotodona, na kateri prevajanje še ni podprto."; +"Common.Alerts.TranslationFailed.Title" = "Opomba"; +"Common.Alerts.VoteFailure.PollEnded" = "Anketa je zaključena"; +"Common.Alerts.VoteFailure.Title" = "Napaka glasovanja"; +"Common.Controls.Actions.Add" = "Dodaj"; +"Common.Controls.Actions.Back" = "Nazaj"; +"Common.Controls.Actions.BlockDomain" = "Blokiraj %@"; +"Common.Controls.Actions.Cancel" = "Prekliči"; +"Common.Controls.Actions.Compose" = "Sestavi"; +"Common.Controls.Actions.Confirm" = "Potrdi"; +"Common.Controls.Actions.Continue" = "Nadaljuj"; +"Common.Controls.Actions.Copy" = "Kopiraj"; +"Common.Controls.Actions.CopyPhoto" = "Kopiraj fotografijo"; +"Common.Controls.Actions.Delete" = "Izbriši"; +"Common.Controls.Actions.Discard" = "Opusti"; +"Common.Controls.Actions.Done" = "Opravljeno"; +"Common.Controls.Actions.Edit" = "Uredi"; +"Common.Controls.Actions.FindPeople" = "Poiščite osebe, ki jim želite slediti"; +"Common.Controls.Actions.ManuallySearch" = "Raje išči ročno"; +"Common.Controls.Actions.Next" = "Naslednji"; +"Common.Controls.Actions.Ok" = "V redu"; +"Common.Controls.Actions.Open" = "Odpri"; +"Common.Controls.Actions.OpenInBrowser" = "Odpri v brskalniku"; +"Common.Controls.Actions.OpenInSafari" = "Odpri v Safariju"; +"Common.Controls.Actions.Preview" = "Predogled"; +"Common.Controls.Actions.Previous" = "Prejšnji"; +"Common.Controls.Actions.Remove" = "Odstrani"; +"Common.Controls.Actions.Reply" = "Odgovori"; +"Common.Controls.Actions.ReportUser" = "Prijavi %@"; +"Common.Controls.Actions.Save" = "Shrani"; +"Common.Controls.Actions.SavePhoto" = "Shrani fotografijo"; +"Common.Controls.Actions.SeeMore" = "Pokaži več"; +"Common.Controls.Actions.Settings" = "Nastavitve"; +"Common.Controls.Actions.Share" = "Deli"; +"Common.Controls.Actions.SharePost" = "Deli objavo"; +"Common.Controls.Actions.ShareUser" = "Deli %@"; +"Common.Controls.Actions.SignIn" = "Prijava"; +"Common.Controls.Actions.SignUp" = "Ustvari račun"; +"Common.Controls.Actions.Skip" = "Preskoči"; +"Common.Controls.Actions.TakePhoto" = "Posnemi fotografijo"; +"Common.Controls.Actions.TranslatePost.Title" = "Prevedi iz: %@"; +"Common.Controls.Actions.TranslatePost.UnknownLanguage" = "Neznano"; +"Common.Controls.Actions.TryAgain" = "Poskusi ponovno"; +"Common.Controls.Actions.UnblockDomain" = "Odblokiraj %@"; +"Common.Controls.Friendship.Block" = "Blokiraj"; +"Common.Controls.Friendship.BlockDomain" = "Blokiraj %@"; +"Common.Controls.Friendship.BlockUser" = "Blokiraj %@"; +"Common.Controls.Friendship.Blocked" = "Blokirano"; +"Common.Controls.Friendship.EditInfo" = "Uredi podatke"; +"Common.Controls.Friendship.Follow" = "Sledi"; +"Common.Controls.Friendship.Following" = "Sledi"; +"Common.Controls.Friendship.HideReblogs" = "Skrij poobjave"; +"Common.Controls.Friendship.Mute" = "Utišaj"; +"Common.Controls.Friendship.MuteUser" = "Utišaj %@"; +"Common.Controls.Friendship.Muted" = "Utišan"; +"Common.Controls.Friendship.Pending" = "Na čakanju"; +"Common.Controls.Friendship.Request" = "Zahteva"; +"Common.Controls.Friendship.ShowReblogs" = "Pokaži poobjave"; +"Common.Controls.Friendship.Unblock" = "Odblokiraj"; +"Common.Controls.Friendship.UnblockUser" = "Odblokiraj %@"; +"Common.Controls.Friendship.Unmute" = "Odtišaj"; +"Common.Controls.Friendship.UnmuteUser" = "Odtišaj %@"; +"Common.Controls.Keyboard.Common.ComposeNewPost" = "Sestavi novo objavo"; +"Common.Controls.Keyboard.Common.OpenSettings" = "Odpri nastavitve"; +"Common.Controls.Keyboard.Common.ShowFavorites" = "Pokaži priljubljene"; +"Common.Controls.Keyboard.Common.SwitchToTab" = "Preklopi na %@"; +"Common.Controls.Keyboard.SegmentedControl.NextSection" = "Naslednji odsek"; +"Common.Controls.Keyboard.SegmentedControl.PreviousSection" = "Prejšnji odsek"; +"Common.Controls.Keyboard.Timeline.NextStatus" = "Naslednja objava"; +"Common.Controls.Keyboard.Timeline.OpenAuthorProfile" = "Pokaži profil avtorja"; +"Common.Controls.Keyboard.Timeline.OpenRebloggerProfile" = "Odpri profil poobjavitelja"; +"Common.Controls.Keyboard.Timeline.OpenStatus" = "Odpri objavo"; +"Common.Controls.Keyboard.Timeline.PreviewImage" = "Predogled slike"; +"Common.Controls.Keyboard.Timeline.PreviousStatus" = "Prejšnja objava"; +"Common.Controls.Keyboard.Timeline.ReplyStatus" = "Odgovori"; +"Common.Controls.Keyboard.Timeline.ToggleContentWarning" = "Preklopi opozorilo o vsebini"; +"Common.Controls.Keyboard.Timeline.ToggleFavorite" = "Preklopi priljubljenost objave"; +"Common.Controls.Keyboard.Timeline.ToggleReblog" = "Preklopi poobjavo za objavo"; +"Common.Controls.Status.Actions.Favorite" = "Priljubljen"; +"Common.Controls.Status.Actions.Hide" = "Skrij"; +"Common.Controls.Status.Actions.Menu" = "Meni"; +"Common.Controls.Status.Actions.Reblog" = "Poobjavi"; +"Common.Controls.Status.Actions.Reply" = "Odgovori"; +"Common.Controls.Status.Actions.ShareLinkInPost" = "Deli povezavo v objavi"; +"Common.Controls.Status.Actions.ShowGif" = "Pokaži GIF"; +"Common.Controls.Status.Actions.ShowImage" = "Pokaži sliko"; +"Common.Controls.Status.Actions.ShowVideoPlayer" = "Pokaži predvajalnik"; +"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Tapnite, nato držite, da se pojavi meni"; +"Common.Controls.Status.Actions.Unfavorite" = "Odstrani iz priljubljenih"; +"Common.Controls.Status.Actions.Unreblog" = "Razveljavi poobjavo"; +"Common.Controls.Status.ContentWarning" = "Opozorilo o vsebini"; +"Common.Controls.Status.LinkViaUser" = "%@ prek %@"; +"Common.Controls.Status.LoadEmbed" = "Naloži vdelano"; +"Common.Controls.Status.MediaContentWarning" = "Tapnite kamorkoli, da razkrijete"; +"Common.Controls.Status.MetaEntity.Email" = "E-naslov: %@"; +"Common.Controls.Status.MetaEntity.Hashtag" = "Ključnik: %@"; +"Common.Controls.Status.MetaEntity.Mention" = "Pokaži profil: %@"; +"Common.Controls.Status.MetaEntity.Url" = "Povezava: %@"; +"Common.Controls.Status.Poll.Closed" = "Zaprto"; +"Common.Controls.Status.Poll.Vote" = "Glasuj"; +"Common.Controls.Status.SensitiveContent" = "Občutljiva vsebina"; +"Common.Controls.Status.ShowPost" = "Pokaži objavo"; +"Common.Controls.Status.ShowUserProfile" = "Prikaži uporabnikov profil"; +"Common.Controls.Status.Tag.Email" = "E-naslov"; +"Common.Controls.Status.Tag.Emoji" = "Emotikon"; +"Common.Controls.Status.Tag.Hashtag" = "Ključnik"; +"Common.Controls.Status.Tag.Link" = "Povezava"; +"Common.Controls.Status.Tag.Mention" = "Omeni"; +"Common.Controls.Status.Tag.Url" = "URL"; +"Common.Controls.Status.TapToReveal" = "Tapnite za razkritje"; +"Common.Controls.Status.Translation.ShowOriginal" = "Pokaži izvirnik"; +"Common.Controls.Status.Translation.TranslatedFrom" = "Translated from %@ using %@"; +"Common.Controls.Status.Translation.UnknownLanguage" = "Neznano"; +"Common.Controls.Status.Translation.UnknownProvider" = "Unknown"; +"Common.Controls.Status.UserReblogged" = "%@ je poobjavil_a"; +"Common.Controls.Status.UserRepliedTo" = "Odgovarja %@"; +"Common.Controls.Status.Visibility.Direct" = "Samo omenjeni uporabnik lahko vidi to objavo."; +"Common.Controls.Status.Visibility.Private" = "Samo sledilci osebe lahko vidijo to objavo."; +"Common.Controls.Status.Visibility.PrivateFromMe" = "Samo moji sledilci lahko vidijo to objavo."; +"Common.Controls.Status.Visibility.Unlisted" = "Vsak lahko vidi to objavo, ni pa prikazana na javni časovnici."; +"Common.Controls.Tabs.Home" = "Domov"; +"Common.Controls.Tabs.Notifications" = "Obvestila"; +"Common.Controls.Tabs.Profile" = "Profil"; +"Common.Controls.Tabs.SearchAndExplore" = "Poišči in razišči"; +"Common.Controls.Timeline.Filtered" = "Filtrirano"; +"Common.Controls.Timeline.Header.BlockedWarning" = "Profila tega uporabnika ne morete +videti, dokler vas ne odblokirajo."; +"Common.Controls.Timeline.Header.BlockingWarning" = "Profila tega uporabnika ne morete +videti, dokler jih ne odblokirate. +Vaš profil je zanje videti tako."; +"Common.Controls.Timeline.Header.NoStatusFound" = "Ne najdem nobenih objav"; +"Common.Controls.Timeline.Header.SuspendedWarning" = "Ta oseba je bila suspendirana."; +"Common.Controls.Timeline.Header.UserBlockedWarning" = "Profila uporabnika %@ ne morete +videti, dokler vas ne odblokirajo."; +"Common.Controls.Timeline.Header.UserBlockingWarning" = "Profila uporabnika %@ ne morete +videti, dokler jih ne odblokirate. +Vaš profil je zanje videti tako."; +"Common.Controls.Timeline.Header.UserSuspendedWarning" = "Račun osebe %@ je suspendiran."; +"Common.Controls.Timeline.Loader.LoadMissingPosts" = "Naloži manjkajoče objave"; +"Common.Controls.Timeline.Loader.LoadingMissingPosts" = "Nalaganje manjkajočih objav ..."; +"Common.Controls.Timeline.Loader.ShowMoreReplies" = "Pokaži več odgovorov"; +"Common.Controls.Timeline.Timestamp.Now" = "Zdaj"; +"Scene.AccountList.AddAccount" = "Dodaj račun"; +"Scene.AccountList.DismissAccountSwitcher" = "Umakni preklopnik med računi"; +"Scene.AccountList.TabBarHint" = "Trenutno izbran profil: %@. Dvakrat tapnite, nato držite, da se pojavi preklopnik med računi."; +"Scene.Bookmark.Title" = "Zaznamki"; +"Scene.Compose.Accessibility.AppendAttachment" = "Dodaj priponko"; +"Scene.Compose.Accessibility.AppendPoll" = "Dodaj anketo"; +"Scene.Compose.Accessibility.CustomEmojiPicker" = "Izbirnik čustvenčkov po meri"; +"Scene.Compose.Accessibility.DisableContentWarning" = "Onemogoči opozorilo o vsebini"; +"Scene.Compose.Accessibility.EnableContentWarning" = "Omogoči opozorilo o vsebini"; +"Scene.Compose.Accessibility.PostOptions" = "Možnosti objave"; +"Scene.Compose.Accessibility.PostVisibilityMenu" = "Meni vidnosti objave"; +"Scene.Compose.Accessibility.PostingAs" = "Objavljate kot %@"; +"Scene.Compose.Accessibility.RemovePoll" = "Odstrani anketo"; +"Scene.Compose.Attachment.AttachmentBroken" = "To %@ je okvarjeno in ga ni +možno naložiti v Mastodon."; +"Scene.Compose.Attachment.AttachmentTooLarge" = "Priponka je prevelika"; +"Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "Te medijske priponke ni mogoče prepoznati"; +"Scene.Compose.Attachment.CompressingState" = "Stiskanje ..."; +"Scene.Compose.Attachment.DescriptionPhoto" = "Opiši fotografijo za slabovidne in osebe z okvaro vida ..."; +"Scene.Compose.Attachment.DescriptionVideo" = "Opiši video za slabovidne in osebe z okvaro vida ..."; +"Scene.Compose.Attachment.LoadFailed" = "Nalaganje ni uspelo"; +"Scene.Compose.Attachment.Photo" = "fotografija"; +"Scene.Compose.Attachment.ServerProcessingState" = "Obdelovanje na strežniku ..."; +"Scene.Compose.Attachment.UploadFailed" = "Nalaganje na strežnik ni uspelo"; +"Scene.Compose.Attachment.Video" = "video"; +"Scene.Compose.AutoComplete.SpaceToAdd" = "Preslednica za dodajanje"; +"Scene.Compose.ComposeAction" = "Objavi"; +"Scene.Compose.ContentInputPlaceholder" = "Vnesite ali prilepite, kar vam leži na duši"; +"Scene.Compose.ContentWarning.Placeholder" = "Tukaj zapišite opozorilo ..."; +"Scene.Compose.Keyboard.AppendAttachmentEntry" = "Dodaj priponko - %@"; +"Scene.Compose.Keyboard.DiscardPost" = "Opusti objavo"; +"Scene.Compose.Keyboard.PublishPost" = "Objavi objavo"; +"Scene.Compose.Keyboard.SelectVisibilityEntry" = "Izberite vidnost - %@"; +"Scene.Compose.Keyboard.ToggleContentWarning" = "Preklopi opozorilo o vsebini"; +"Scene.Compose.Keyboard.TogglePoll" = "Preklopi anketo"; +"Scene.Compose.MediaSelection.Browse" = "Prebrskaj"; +"Scene.Compose.MediaSelection.Camera" = "Posnemi fotografijo"; +"Scene.Compose.MediaSelection.PhotoLibrary" = "Knjižnica fotografij"; +"Scene.Compose.Poll.DurationTime" = "Trajanje: %@"; +"Scene.Compose.Poll.OneDay" = "1 dan"; +"Scene.Compose.Poll.OneHour" = "1 ura"; +"Scene.Compose.Poll.OptionNumber" = "Možnost %ld"; +"Scene.Compose.Poll.SevenDays" = "7 dni"; +"Scene.Compose.Poll.SixHours" = "6 ur"; +"Scene.Compose.Poll.ThePollHasEmptyOption" = "Anketa ima prazno izbiro"; +"Scene.Compose.Poll.ThePollIsInvalid" = "Anketa je neveljavna"; +"Scene.Compose.Poll.ThirtyMinutes" = "30 minut"; +"Scene.Compose.Poll.ThreeDays" = "3 dni"; +"Scene.Compose.ReplyingToUser" = "odgovarja %@"; +"Scene.Compose.Title.NewPost" = "Nova objava"; +"Scene.Compose.Title.NewReply" = "Nov odgovor"; +"Scene.Compose.Visibility.Direct" = "Samo osebe, ki jih omenjam"; +"Scene.Compose.Visibility.Private" = "Samo sledilci"; +"Scene.Compose.Visibility.Public" = "Javno"; +"Scene.Compose.Visibility.Unlisted" = "Ni prikazano"; +"Scene.ConfirmEmail.Button.OpenEmailApp" = "Odpri aplikacijo za e-pošto"; +"Scene.ConfirmEmail.Button.Resend" = "Ponovno pošlji"; +"Scene.ConfirmEmail.DontReceiveEmail.Description" = "Preverite, ali je vaš e-naslov pravilen, pa tudi vsebino mape neželene pošte, če tega še niste storili."; +"Scene.ConfirmEmail.DontReceiveEmail.ResendEmail" = "Ponovno pošlji e-pošto"; +"Scene.ConfirmEmail.DontReceiveEmail.Title" = "Preverite svojo e-pošto"; +"Scene.ConfirmEmail.OpenEmailApp.Description" = "Ravnokar smo vam poslali e-sporočilo. Preverite neželeno pošto, če sporočila ne najdete med dohodno pošto."; +"Scene.ConfirmEmail.OpenEmailApp.Mail" = "E-pošta"; +"Scene.ConfirmEmail.OpenEmailApp.OpenEmailClient" = "Odpri odjemalca e-pošte"; +"Scene.ConfirmEmail.OpenEmailApp.Title" = "Preverite svojo dohodno e-pošto."; +"Scene.ConfirmEmail.Subtitle" = "Tapnite povezavo, ki smo vam jo poslali po e-pošti, da overite svoj račun."; +"Scene.ConfirmEmail.TapTheLinkWeEmailedToYouToVerifyYourAccount" = "Tapnite povezavo, ki smo vam jo poslali po e-pošti, da overite svoj račun"; +"Scene.ConfirmEmail.Title" = "Še zadnja stvar."; +"Scene.Discovery.Intro" = "To so objave, ki plenijo pozornost na vašem koncu Mastodona."; +"Scene.Discovery.Tabs.Community" = "Skupnost"; +"Scene.Discovery.Tabs.ForYou" = "Za vas"; +"Scene.Discovery.Tabs.Hashtags" = "Ključniki"; +"Scene.Discovery.Tabs.News" = "Novice"; +"Scene.Discovery.Tabs.Posts" = "Objave"; +"Scene.Familiarfollowers.FollowedByNames" = "Sledijo %@"; +"Scene.Familiarfollowers.Title" = "Znani sledilci"; +"Scene.Favorite.Title" = "Vaši priljubljeni"; +"Scene.FavoritedBy.Title" = "Med priljubljene dal_a"; +"Scene.FollowedTags.Actions.Follow" = "Sledi"; +"Scene.FollowedTags.Actions.Unfollow" = "Prenehaj slediti"; +"Scene.FollowedTags.Header.Participants" = "udeležencev"; +"Scene.FollowedTags.Header.Posts" = "objav"; +"Scene.FollowedTags.Header.PostsToday" = "objav danes"; +"Scene.FollowedTags.Title" = "Sledene značke"; +"Scene.Follower.Footer" = "Sledilci z drugih strežnikov niso prikazani."; +"Scene.Follower.Title" = "sledilec"; +"Scene.Following.Footer" = "Sledenje z drugih strežnikov ni prikazano."; +"Scene.Following.Title" = "sledi"; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoHint" = "Tapnite, da podrsate na vrh; tapnite znova, da se pomaknete na prejšnji položaj"; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoLabel" = "Gumb logotipa"; +"Scene.HomeTimeline.NavigationBarState.NewPosts" = "Pokaži nove objave"; +"Scene.HomeTimeline.NavigationBarState.Offline" = "Nepovezan"; +"Scene.HomeTimeline.NavigationBarState.Published" = "Objavljeno!"; +"Scene.HomeTimeline.NavigationBarState.Publishing" = "Objavljanje objave ..."; +"Scene.HomeTimeline.Title" = "Domov"; +"Scene.Login.ServerSearchField.Placeholder" = "Vnesite URL ali poiščite svoj strežnik"; +"Scene.Login.Subtitle" = "Prijavite se na strežniku, na katerem ste ustvarili račun."; +"Scene.Login.Title" = "Dobrodošli nazaj"; +"Scene.Notification.FollowRequest.Accept" = "Sprejmi"; +"Scene.Notification.FollowRequest.Accepted" = "Sprejeto"; +"Scene.Notification.FollowRequest.Reject" = "Zavrni"; +"Scene.Notification.FollowRequest.Rejected" = "Zavrnjeno"; +"Scene.Notification.Keyobard.ShowEverything" = "Pokaži vse"; +"Scene.Notification.Keyobard.ShowMentions" = "Pokaži omembe"; +"Scene.Notification.NotificationDescription.FavoritedYourPost" = "je vzljubil/a vašo objavo"; +"Scene.Notification.NotificationDescription.FollowedYou" = "vam sledi"; +"Scene.Notification.NotificationDescription.MentionedYou" = "vas je omenil/a"; +"Scene.Notification.NotificationDescription.PollHasEnded" = "anketa je zaključena"; +"Scene.Notification.NotificationDescription.RebloggedYourPost" = "je poobjavil_a vašo objavo"; +"Scene.Notification.NotificationDescription.RequestToFollowYou" = "vas je zaprosil za sledenje"; +"Scene.Notification.Title.Everything" = "Vse"; +"Scene.Notification.Title.Mentions" = "Omembe"; +"Scene.Preview.Keyboard.ClosePreview" = "Zapri predogled"; +"Scene.Preview.Keyboard.ShowNext" = "Pokaži naslednje"; +"Scene.Preview.Keyboard.ShowPrevious" = "Pokaži prejšnje"; +"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "Dvakrat tapnite, da se odpre seznam"; +"Scene.Profile.Accessibility.EditAvatarImage" = "Uredi sliko avatarja"; +"Scene.Profile.Accessibility.ShowAvatarImage" = "Pokaži sliko avatarja"; +"Scene.Profile.Accessibility.ShowBannerImage" = "Pokaži sliko pasice"; +"Scene.Profile.Dashboard.Followers" = "sledilcev"; +"Scene.Profile.Dashboard.Following" = "sledi"; +"Scene.Profile.Dashboard.Posts" = "Objave"; +"Scene.Profile.Fields.AddRow" = "Dodaj vrstico"; +"Scene.Profile.Fields.Joined" = "Joined"; +"Scene.Profile.Fields.Placeholder.Content" = "Vsebina"; +"Scene.Profile.Fields.Placeholder.Label" = "Oznaka"; +"Scene.Profile.Fields.Verified.Long" = "Lastništvo te povezave je bilo preverjeno %@"; +"Scene.Profile.Fields.Verified.Short" = "Preverjeno %@"; +"Scene.Profile.Header.FollowsYou" = "Vam sledi"; +"Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Message" = "Potrdite za blokado %@"; +"Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title" = "Blokiraj račun"; +"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Message" = "Potrdite, da poobjave ne bodo prikazane"; +"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Title" = "Skrij poobjave"; +"Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Message" = "Potrdite utišanje %@"; +"Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Title" = "Utišaj račun"; +"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Message" = "Potrdite, da bodo poobjave prikazane"; +"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Title" = "Pokaži poobjave"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Message" = "Potrdite za umik blokade %@"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Title" = "Odblokiraj račun"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.Message" = "Potrdite umik utišanja %@"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.Title" = "Odtišaj račun"; +"Scene.Profile.SegmentedControl.About" = "O programu"; +"Scene.Profile.SegmentedControl.Media" = "Mediji"; +"Scene.Profile.SegmentedControl.Posts" = "Objave"; +"Scene.Profile.SegmentedControl.PostsAndReplies" = "Objave in odgovori"; +"Scene.Profile.SegmentedControl.Replies" = "Odgovori"; +"Scene.RebloggedBy.Title" = "Poobjavil_a"; +"Scene.Register.Error.Item.Agreement" = "Sporazum"; +"Scene.Register.Error.Item.Email" = "E-pošta"; +"Scene.Register.Error.Item.Locale" = "Krajevne nastavitve"; +"Scene.Register.Error.Item.Password" = "Geslo"; +"Scene.Register.Error.Item.Reason" = "Razlog"; +"Scene.Register.Error.Item.Username" = "Uporabniško ime"; +"Scene.Register.Error.Reason.Accepted" = "%@ mora biti sprejet"; +"Scene.Register.Error.Reason.Blank" = "%@ je zahtevan"; +"Scene.Register.Error.Reason.Blocked" = "%@ vsebuje nedovoljenega ponudnika e-poštnih storitev"; +"Scene.Register.Error.Reason.Inclusion" = "%@ ni podprta vrednost"; +"Scene.Register.Error.Reason.Invalid" = "%@ ni veljavno"; +"Scene.Register.Error.Reason.Reserved" = "%@ je rezervirana ključna beseda"; +"Scene.Register.Error.Reason.Taken" = "%@ je že v uporabi"; +"Scene.Register.Error.Reason.TooLong" = "%@ je predolgo"; +"Scene.Register.Error.Reason.TooShort" = "%@ je prekratko"; +"Scene.Register.Error.Reason.Unreachable" = "%@ kot kaže ne obstaja"; +"Scene.Register.Error.Special.EmailInvalid" = "E-naslov ni veljaven"; +"Scene.Register.Error.Special.PasswordTooShort" = "Geslo je prekratko (dolgo mora biti vsaj 8 znakov)"; +"Scene.Register.Error.Special.UsernameInvalid" = "Uporabniško ime lahko vsebuje samo alfanumerične znake ter podčrtaje."; +"Scene.Register.Error.Special.UsernameTooLong" = "Uporabniško ime je predolgo (ne more biti daljše od 30 znakov)"; +"Scene.Register.Input.Avatar.Delete" = "Izbriši"; +"Scene.Register.Input.DisplayName.Placeholder" = "pojavno ime"; +"Scene.Register.Input.Email.Placeholder" = "e-pošta"; +"Scene.Register.Input.Invite.RegistrationUserInviteRequest" = "Zakaj se želite pridružiti?"; +"Scene.Register.Input.Password.Accessibility.Checked" = "potrjeno"; +"Scene.Register.Input.Password.Accessibility.Unchecked" = "nepotrjeno"; +"Scene.Register.Input.Password.CharacterLimit" = "8 znakov"; +"Scene.Register.Input.Password.Hint" = "Geslo mora biti dolgo najmanj 8 znakov."; +"Scene.Register.Input.Password.Placeholder" = "geslo"; +"Scene.Register.Input.Password.Require" = "Vaše geslo potrebuje vsaj:"; +"Scene.Register.Input.Username.DuplicatePrompt" = "To ime je že zasedeno."; +"Scene.Register.Input.Username.Placeholder" = "uporabniško ime"; +"Scene.Register.LetsGetYouSetUpOnDomain" = "Naj vas namestimo na %@"; +"Scene.Register.Title" = "Naj vas namestimo na %@"; +"Scene.Report.Content1" = "Ali so še kakšne druge objave, ki bi jih želeli dodati k prijavi?"; +"Scene.Report.Content2" = "Je kaj, kar bi moderatorji morali vedeti o tem poročilu?"; +"Scene.Report.ReportSentTitle" = "Hvala za poročilo, bomo preverili."; +"Scene.Report.Reported" = "PRIJAVLJEN"; +"Scene.Report.Send" = "Pošlji poročilo"; +"Scene.Report.SkipToSend" = "Pošlji brez komentarja"; +"Scene.Report.Step1" = "Korak 1 od 2"; +"Scene.Report.Step2" = "Korak 2 od 2"; +"Scene.Report.StepFinal.BlockUser" = "Blokiraj %@"; +"Scene.Report.StepFinal.DontWantToSeeThis" = "Ne želite videti tega?"; +"Scene.Report.StepFinal.MuteUser" = "Utišaj %@"; +"Scene.Report.StepFinal.TheyWillNoLongerBeAbleToFollowOrSeeYourPostsButTheyCanSeeIfTheyveBeenBlocked" = "Nič več ne bodo mogli slediti ali videti vaše objave, lahko pa vidijo, če so blokirani."; +"Scene.Report.StepFinal.Unfollow" = "Prenehaj slediti"; +"Scene.Report.StepFinal.UnfollowUser" = "Prenehaj slediti %@"; +"Scene.Report.StepFinal.Unfollowed" = "Ne sledi več"; +"Scene.Report.StepFinal.WhenYouSeeSomethingYouDontLikeOnMastodonYouCanRemoveThePersonFromYourExperience." = "Če vidite nekaj, česar na Masodonu ne želite, lahko odstranite osebo iz svoje izkušnje."; +"Scene.Report.StepFinal.WhileWeReviewThisYouCanTakeActionAgainstUser" = "Medtem, ko to pregledujemo, lahko proti %@ ukrepate"; +"Scene.Report.StepFinal.YouWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted" = "Njihovih objav ali poobjav ne boste videli v svojem domačem viru. Ne bodo vedeli, da so utišani."; +"Scene.Report.StepFour.IsThereAnythingElseWeShouldKnow" = "Je še kaj, za kar menite, da bi morali vedeti?"; +"Scene.Report.StepFour.Step4Of4" = "Korak 4 od 4"; +"Scene.Report.StepOne.IDontLikeIt" = "Ni mi všeč"; +"Scene.Report.StepOne.ItIsNotSomethingYouWantToSee" = "To ni tisto, kar želite videti"; +"Scene.Report.StepOne.ItViolatesServerRules" = "Krši strežniška pravila"; +"Scene.Report.StepOne.ItsSomethingElse" = "Gre za nekaj drugega"; +"Scene.Report.StepOne.ItsSpam" = "To je neželena vsebina"; +"Scene.Report.StepOne.MaliciousLinksFakeEngagementOrRepetetiveReplies" = "Škodljive povezave, lažno prizadevanje ali ponavljajoči se odgovori"; +"Scene.Report.StepOne.SelectTheBestMatch" = "Izberite najboljši zadetek"; +"Scene.Report.StepOne.Step1Of4" = "Korak 1 od 4"; +"Scene.Report.StepOne.TheIssueDoesNotFitIntoOtherCategories" = "Težava ne sodi v druge kategorije"; +"Scene.Report.StepOne.WhatsWrongWithThisAccount" = "Kaj je narobe s tem računom?"; +"Scene.Report.StepOne.WhatsWrongWithThisPost" = "Kaj je narobe s to objavo?"; +"Scene.Report.StepOne.WhatsWrongWithThisUsername" = "Kaj je narobe s/z %@?"; +"Scene.Report.StepOne.YouAreAwareThatItBreaksSpecificRules" = "Zavedate se, da krši določena pravila"; +"Scene.Report.StepThree.AreThereAnyPostsThatBackUpThisReport" = "Ali so kakšne objave, ki dokazujejo trditve iz tega poročila?"; +"Scene.Report.StepThree.SelectAllThatApply" = "Izberite vse, kar ustreza"; +"Scene.Report.StepThree.Step3Of4" = "Korak 3 od 4"; +"Scene.Report.StepTwo.IJustDon’tLikeIt" = "Ni mi všeč"; +"Scene.Report.StepTwo.SelectAllThatApply" = "Izberite vse, kar ustreza"; +"Scene.Report.StepTwo.Step2Of4" = "Korak 2 od 4"; +"Scene.Report.StepTwo.WhichRulesAreBeingViolated" = "Katera pravila so kršena?"; +"Scene.Report.TextPlaceholder" = "Vnesite ali prilepite dodatne komentarje"; +"Scene.Report.Title" = "Prijavi %@"; +"Scene.Report.TitleReport" = "Poročaj"; +"Scene.Search.Recommend.Accounts.Description" = "Morda želite slediti tem računom"; +"Scene.Search.Recommend.Accounts.Follow" = "Sledi"; +"Scene.Search.Recommend.Accounts.Title" = "Računi, ki vam bi bili morda všeč"; +"Scene.Search.Recommend.ButtonText" = "Prikaži vse"; +"Scene.Search.Recommend.HashTag.Description" = "Ključniki, ki imajo veliko pozornosti"; +"Scene.Search.Recommend.HashTag.PeopleTalking" = "%@ oseb se pogovarja"; +"Scene.Search.Recommend.HashTag.Title" = "V trendu na Mastodonu"; +"Scene.Search.SearchBar.Cancel" = "Prekliči"; +"Scene.Search.SearchBar.Placeholder" = "Išči ključnike in uporabnike"; +"Scene.Search.Searching.Clear" = "Počisti"; +"Scene.Search.Searching.EmptyState.NoResults" = "Ni rezultatov"; +"Scene.Search.Searching.RecentSearch" = "Nedavna iskanja"; +"Scene.Search.Searching.Segment.All" = "Vse"; +"Scene.Search.Searching.Segment.Hashtags" = "Ključniki"; +"Scene.Search.Searching.Segment.People" = "Ljudje"; +"Scene.Search.Searching.Segment.Posts" = "Objave"; +"Scene.Search.Title" = "Iskanje"; +"Scene.ServerPicker.Button.Category.Academia" = "akademsko"; +"Scene.ServerPicker.Button.Category.Activism" = "aktivizem"; +"Scene.ServerPicker.Button.Category.All" = "Vse"; +"Scene.ServerPicker.Button.Category.AllAccessiblityDescription" = "Kategorija: vse"; +"Scene.ServerPicker.Button.Category.Art" = "umetnost"; +"Scene.ServerPicker.Button.Category.Food" = "hrana"; +"Scene.ServerPicker.Button.Category.Furry" = "Kosmato"; +"Scene.ServerPicker.Button.Category.Games" = "igre"; +"Scene.ServerPicker.Button.Category.General" = "splošno"; +"Scene.ServerPicker.Button.Category.Journalism" = "novinarstvo"; +"Scene.ServerPicker.Button.Category.Lgbt" = "lgbq+"; +"Scene.ServerPicker.Button.Category.Music" = "glasba"; +"Scene.ServerPicker.Button.Category.Regional" = "regionalno"; +"Scene.ServerPicker.Button.Category.Tech" = "tehnologija"; +"Scene.ServerPicker.Button.SeeLess" = "Pokaži manj"; +"Scene.ServerPicker.Button.SeeMore" = "Pokaži več"; +"Scene.ServerPicker.EmptyState.BadNetwork" = "Med nalaganjem podatkov je prišlo do napake. Preverite svojo internetno povezavo."; +"Scene.ServerPicker.EmptyState.FindingServers" = "Iskanje razpoložljivih strežnikov ..."; +"Scene.ServerPicker.EmptyState.NoResults" = "Ni rezultatov"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Iščite po skupnostih ali vnesite URL"; +"Scene.ServerPicker.Label.Category" = "KATEGORIJA"; +"Scene.ServerPicker.Label.Language" = "JEZIK"; +"Scene.ServerPicker.Label.Users" = "UPORABNIKI"; +"Scene.ServerPicker.Subtitle" = "Strežnik izberite glede na svojo regijo, zanimanje ali pa kar splošno. Še vedno lahko klepetate s komer koli na Mastodonu, ne glede na strežnik."; +"Scene.ServerPicker.Title" = "Mastodon tvorijo uporabniki z različnih strežnikov."; +"Scene.ServerRules.Button.Confirm" = "Strinjam se"; +"Scene.ServerRules.PrivacyPolicy" = "pravilnik o zasebnosti"; +"Scene.ServerRules.Prompt" = "Če boste nadaljevali, za vas veljajo pogoji storitve in pravilnik o zasebnosti za %@."; +"Scene.ServerRules.Subtitle" = "Slednje določajo in njihovo spoštovanje zagotavljajo moderatorji %@."; +"Scene.ServerRules.TermsOfService" = "pogoji uporabe"; +"Scene.ServerRules.Title" = "Nekaj osnovnih pravil."; +"Scene.Settings.Footer.MastodonDescription" = "Mastodon je odprtokodna programska oprema. Na GitHubu na %@ (%@) lahko poročate o napakah"; +"Scene.Settings.Keyboard.CloseSettingsWindow" = "Zapri okno nastavitev"; +"Scene.Settings.Section.Appearance.Automatic" = "Samodejno"; +"Scene.Settings.Section.Appearance.Dark" = "Vedno temno"; +"Scene.Settings.Section.Appearance.Light" = "Vedno svetlo"; +"Scene.Settings.Section.Appearance.Title" = "Videz"; +"Scene.Settings.Section.BoringZone.AccountSettings" = "Nastavitve računa"; +"Scene.Settings.Section.BoringZone.Privacy" = "Pravilnik o zasebnosti"; +"Scene.Settings.Section.BoringZone.Terms" = "Pogoji uporabe"; +"Scene.Settings.Section.BoringZone.Title" = "Cona dolgočasja"; +"Scene.Settings.Section.LookAndFeel.Light" = "Svetlo"; +"Scene.Settings.Section.LookAndFeel.ReallyDark" = "Zares temno"; +"Scene.Settings.Section.LookAndFeel.SortaDark" = "Nekako temno"; +"Scene.Settings.Section.LookAndFeel.Title" = "Videz in občutek"; +"Scene.Settings.Section.LookAndFeel.UseSystem" = "Uporabi sistemsko"; +"Scene.Settings.Section.Notifications.Boosts" = "prepošlje mojo objavo"; +"Scene.Settings.Section.Notifications.Favorites" = "mojo objavo da med priljubljene"; +"Scene.Settings.Section.Notifications.Follows" = "me sledi"; +"Scene.Settings.Section.Notifications.Mentions" = "me omeni"; +"Scene.Settings.Section.Notifications.Title" = "Obvestila"; +"Scene.Settings.Section.Notifications.Trigger.Anyone" = "kdor koli"; +"Scene.Settings.Section.Notifications.Trigger.Follow" = "nekdo, ki mu sledim,"; +"Scene.Settings.Section.Notifications.Trigger.Follower" = "sledilec/ka"; +"Scene.Settings.Section.Notifications.Trigger.Noone" = "nihče"; +"Scene.Settings.Section.Notifications.Trigger.Title" = "Obvesti me, ko"; +"Scene.Settings.Section.Preference.DisableAvatarAnimation" = "Onemogoči animirane avatarje"; +"Scene.Settings.Section.Preference.DisableEmojiAnimation" = "Onemogoči animirane emotikone"; +"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "Odpri povezave v Mastodonu"; +"Scene.Settings.Section.Preference.Title" = "Nastavitve"; +"Scene.Settings.Section.Preference.TrueBlackDarkMode" = "Resnično črni temni način"; +"Scene.Settings.Section.Preference.UsingDefaultBrowser" = "Uporabi privzeti brskalnik za odpiranje povezav"; +"Scene.Settings.Section.SpicyZone.Clear" = "Počisti medijski predpomnilnik"; +"Scene.Settings.Section.SpicyZone.Signout" = "Odjava"; +"Scene.Settings.Section.SpicyZone.Title" = "Pikantna cona"; +"Scene.Settings.Title" = "Nastavitve"; +"Scene.SuggestionAccount.FollowExplain" = "Ko nekomu sledite, vidite njihove objave v svojem domačem viru."; +"Scene.SuggestionAccount.Title" = "Poiščite osebe, ki jim želite slediti"; +"Scene.Thread.BackTitle" = "Objavi"; +"Scene.Thread.Title" = "Objavil/a"; +"Scene.Welcome.GetStarted" = "Začnite"; +"Scene.Welcome.LogIn" = "Prijava"; +"Scene.Welcome.Slogan" = "Družbeno mreženje +spet v vaših rokah."; +"Scene.Wizard.AccessibilityHint" = "Dvakrat tapnite, da zapustite tega čarovnika"; +"Scene.Wizard.MultipleAccountSwitchIntroDescription" = "Preklopite med več računi s pritiskom gumba profila."; +"Scene.Wizard.NewInMastodon" = "Novo v Mastodonu"; \ No newline at end of file diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/sl.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/sl.lproj/Localizable.stringsdict new file mode 100644 index 000000000..87cc42142 --- /dev/null +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/sl.lproj/Localizable.stringsdict @@ -0,0 +1,581 @@ + + + + + a11y.plural.count.unread.notification + + NSStringLocalizedFormatKey + %#@notification_count_unread_notification@ + notification_count_unread_notification + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld neprebrano obvestilo + two + %ld neprebrani obvestili + few + %ld neprebrana obvestila + other + %ld neprebranih obvestil + + + a11y.plural.count.input_limit_exceeds + + NSStringLocalizedFormatKey + Omejitev vnosa presega %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld znak + two + %ld znaka + few + %ld znaki + other + %ld znakov + + + a11y.plural.count.input_limit_remains + + NSStringLocalizedFormatKey + Omejitev vnosa ostaja %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld znak + two + %ld znaka + few + %ld znaki + other + %ld znakov + + + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + preostaja %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld znak + two + %ld znaka + few + %ld znaki + other + %ld znakov + + + plural.count.followed_by_and_mutual + + NSStringLocalizedFormatKey + %#@names@%#@count_mutual@ + names + + one + + two + + few + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + + + count_mutual + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + Sledijo %1$@ in %ld skupni + two + Sledijo %1$@ in %ld skupna + few + Sledijo %1$@ in %ld skupni + other + Sledijo %1$@ in %ld skupnih + + + plural.count.metric_formatted.post + + NSStringLocalizedFormatKey + %@ %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + objava + two + objavi + few + objave + other + objav + + + plural.count.media + + NSStringLocalizedFormatKey + %#@media_count@ + media_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld medij + two + %ld medija + few + %ld mediji + other + %ld medijev + + + plural.count.post + + NSStringLocalizedFormatKey + %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld objava + two + %ld objavi + few + %ld objave + other + %ld objav + + + plural.count.favorite + + NSStringLocalizedFormatKey + %#@favorite_count@ + favorite_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld priljubljeni + two + %ld priljubljena + few + %ld priljubljeni + other + %ld priljubljenih + + + plural.count.reblog + + NSStringLocalizedFormatKey + %#@reblog_count@ + reblog_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld poobjava + two + %ld poobjavi + few + %ld poobjave + other + %ld poobjav + + + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld odgovor + two + %ld odgovora + few + %ld odgovori + other + %ld odgovorov + + + plural.count.vote + + NSStringLocalizedFormatKey + %#@vote_count@ + vote_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld glas + two + %ld glasova + few + %ld glasovi + other + %ld glasov + + + plural.count.voter + + NSStringLocalizedFormatKey + %#@voter_count@ + voter_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld glasovalec + two + %ld glasovalca + few + %ld glasovalci + other + %ld glasovalcev + + + plural.people_talking + + NSStringLocalizedFormatKey + %#@count_people_talking@ + count_people_talking + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld oseba se pogovarja + two + %ld osebi se pogovarjata + few + %ld osebe se pogovarjajo + other + %ld oseb se pogovarja + + + plural.count.following + + NSStringLocalizedFormatKey + %#@count_following@ + count_following + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld sledi + two + %ld sledita + few + %ld sledijo + other + %ld sledijo + + + plural.count.follower + + NSStringLocalizedFormatKey + %#@count_follower@ + count_follower + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld sledilec + two + %ld sledilca + few + %ld sledilci + other + %ld sledilcev + + + date.year.left + + NSStringLocalizedFormatKey + %#@count_year_left@ + count_year_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + na voljo še %ld leto + two + na voljo še %ld leti + few + na voljo še %ld leta + other + na voljo še %ld let + + + date.month.left + + NSStringLocalizedFormatKey + %#@count_month_left@ + count_month_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + na voljo še %ld mesec + two + na voljo še %ld meseca + few + na voljo še %ld mesece + other + na voljo še %ld mesecev + + + date.day.left + + NSStringLocalizedFormatKey + %#@count_day_left@ + count_day_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + še %ld dan + two + še %ld dneva + few + še %ld dnevi + other + še %ld dni + + + date.hour.left + + NSStringLocalizedFormatKey + %#@count_hour_left@ + count_hour_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + na voljo še %ld uro + two + na voljo še %ld uri + few + na voljo še %ld ure + other + na voljo še %ld ur + + + date.minute.left + + NSStringLocalizedFormatKey + %#@count_minute_left@ + count_minute_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + Še %ld min. + two + Še %ld min. + few + Še %ld min. + other + Še %ld min. + + + date.second.left + + NSStringLocalizedFormatKey + %#@count_second_left@ + count_second_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + Preostane še %ld s + two + Preostaneta še %ld s + few + Preostanejo še %ld s + other + Preostane še %ld s + + + date.year.ago.abbr + + NSStringLocalizedFormatKey + %#@count_year_ago_abbr@ + count_year_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + pred %ld letom + two + pred %ld letoma + few + pred %ld leti + other + pred %ld leti + + + date.month.ago.abbr + + NSStringLocalizedFormatKey + %#@count_month_ago_abbr@ + count_month_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + pred %ld mesecem + two + pred %ld mesecema + few + pred %ld meseci + other + pred %ld meseci + + + date.day.ago.abbr + + NSStringLocalizedFormatKey + %#@count_day_ago_abbr@ + count_day_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + pred %ld dnem + two + pred %ld dnevoma + few + pred %ld dnemi + other + pred %ld dnemi + + + date.hour.ago.abbr + + NSStringLocalizedFormatKey + %#@count_hour_ago_abbr@ + count_hour_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + pred %ld uro + two + pred %ld urama + few + pred %ld urami + other + pred %ld urami + + + date.minute.ago.abbr + + NSStringLocalizedFormatKey + %#@count_minute_ago_abbr@ + count_minute_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + pred %ld min + two + pred %ld min + few + pred %ld min + other + pred %ld min + + + date.second.ago.abbr + + NSStringLocalizedFormatKey + %#@count_second_ago_abbr@ + count_second_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + pred %ld s + two + pred %ld s + few + pred %ld s + other + pred %ld s + + + + diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/sv.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/sv.lproj/Localizable.strings index dbba5ddda..4da00cad1 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/sv.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/sv.lproj/Localizable.strings @@ -22,6 +22,9 @@ Kontrollera din internetanslutning."; "Common.Alerts.SignOut.Message" = "Är du säker på att du vill logga ut?"; "Common.Alerts.SignOut.Title" = "Logga ut"; "Common.Alerts.SignUpFailure.Title" = "Registrering misslyckades"; +"Common.Alerts.TranslationFailed.Button" = "OK"; +"Common.Alerts.TranslationFailed.Message" = "Översättningen misslyckades. Det kan hända att administratören inte har aktiverat översättningar på den här servern eller att servern kör en äldre version av Mastodon som inte har stöd för översättningar ännu."; +"Common.Alerts.TranslationFailed.Title" = "Anteckning"; "Common.Alerts.VoteFailure.PollEnded" = "Omröstningen har avslutats"; "Common.Alerts.VoteFailure.Title" = "Röstning misslyckades"; "Common.Controls.Actions.Add" = "Lägg till"; @@ -31,6 +34,7 @@ Kontrollera din internetanslutning."; "Common.Controls.Actions.Compose" = "Skriv"; "Common.Controls.Actions.Confirm" = "Bekräfta"; "Common.Controls.Actions.Continue" = "Fortsätt"; +"Common.Controls.Actions.Copy" = "Kopiera"; "Common.Controls.Actions.CopyPhoto" = "Kopiera foto"; "Common.Controls.Actions.Delete" = "Radera"; "Common.Controls.Actions.Discard" = "Släng"; @@ -56,9 +60,11 @@ Kontrollera din internetanslutning."; "Common.Controls.Actions.SharePost" = "Dela inlägg"; "Common.Controls.Actions.ShareUser" = "Dela %@"; "Common.Controls.Actions.SignIn" = "Logga in"; -"Common.Controls.Actions.SignUp" = "Registrera dig"; +"Common.Controls.Actions.SignUp" = "Skapa konto"; "Common.Controls.Actions.Skip" = "Hoppa över"; "Common.Controls.Actions.TakePhoto" = "Ta foto"; +"Common.Controls.Actions.TranslatePost.Title" = "Översätt från %@"; +"Common.Controls.Actions.TranslatePost.UnknownLanguage" = "Okänt"; "Common.Controls.Actions.TryAgain" = "Försök igen"; "Common.Controls.Actions.UnblockDomain" = "Avblockera %@"; "Common.Controls.Friendship.Block" = "Blockera"; @@ -100,6 +106,7 @@ Kontrollera din internetanslutning."; "Common.Controls.Status.Actions.Menu" = "Meny"; "Common.Controls.Status.Actions.Reblog" = "Boosta"; "Common.Controls.Status.Actions.Reply" = "Svara"; +"Common.Controls.Status.Actions.ShareLinkInPost" = "Dela länk i inlägg"; "Common.Controls.Status.Actions.ShowGif" = "Visa GIF"; "Common.Controls.Status.Actions.ShowImage" = "Visa bild"; "Common.Controls.Status.Actions.ShowVideoPlayer" = "Visa videospelare"; @@ -107,6 +114,8 @@ Kontrollera din internetanslutning."; "Common.Controls.Status.Actions.Unfavorite" = "Ta bort favorit"; "Common.Controls.Status.Actions.Unreblog" = "Ångra boost"; "Common.Controls.Status.ContentWarning" = "Innehållsvarning"; +"Common.Controls.Status.LinkViaUser" = "%@ via %@"; +"Common.Controls.Status.LoadEmbed" = "Ladda inbäddning"; "Common.Controls.Status.MediaContentWarning" = "Tryck var som helst för att visa"; "Common.Controls.Status.MetaEntity.Email" = "E-postadress: %@"; "Common.Controls.Status.MetaEntity.Hashtag" = "Hashtag: %@"; @@ -124,6 +133,10 @@ Kontrollera din internetanslutning."; "Common.Controls.Status.Tag.Mention" = "Omnämn"; "Common.Controls.Status.Tag.Url" = "URL"; "Common.Controls.Status.TapToReveal" = "Tryck för att visa"; +"Common.Controls.Status.Translation.ShowOriginal" = "Visa original"; +"Common.Controls.Status.Translation.TranslatedFrom" = "Translated from %@ using %@"; +"Common.Controls.Status.Translation.UnknownLanguage" = "Okänt"; +"Common.Controls.Status.Translation.UnknownProvider" = "Unknown"; "Common.Controls.Status.UserReblogged" = "%@ boostade"; "Common.Controls.Status.UserRepliedTo" = "Svarade på %@"; "Common.Controls.Status.Visibility.Direct" = "Endast omnämnda användare kan se detta inlägg."; @@ -131,9 +144,9 @@ Kontrollera din internetanslutning."; "Common.Controls.Status.Visibility.PrivateFromMe" = "Bara mina följare kan se det här inlägget."; "Common.Controls.Status.Visibility.Unlisted" = "Alla kan se detta inlägg men det visas inte i den offentliga tidslinjen."; "Common.Controls.Tabs.Home" = "Hem"; -"Common.Controls.Tabs.Notification" = "Notis"; +"Common.Controls.Tabs.Notifications" = "Notiser"; "Common.Controls.Tabs.Profile" = "Profil"; -"Common.Controls.Tabs.Search" = "Sök"; +"Common.Controls.Tabs.SearchAndExplore" = "Sök och utforska"; "Common.Controls.Timeline.Filtered" = "Filtrerat"; "Common.Controls.Timeline.Header.BlockedWarning" = "Du kan inte visa den här användarens profil förrän de avblockerar dig."; @@ -161,16 +174,20 @@ Din profil ser ut så här för dem."; "Scene.Compose.Accessibility.CustomEmojiPicker" = "Anpassad emoji-väljare"; "Scene.Compose.Accessibility.DisableContentWarning" = "Inaktivera innehållsvarning"; "Scene.Compose.Accessibility.EnableContentWarning" = "Aktivera innehållsvarning"; +"Scene.Compose.Accessibility.PostOptions" = "Inläggsalternativ"; "Scene.Compose.Accessibility.PostVisibilityMenu" = "Inläggssynlighetsmeny"; +"Scene.Compose.Accessibility.PostingAs" = "Postar som %@"; "Scene.Compose.Accessibility.RemovePoll" = "Ta bort omröstning"; "Scene.Compose.Attachment.AttachmentBroken" = "Denna %@ är trasig och kan inte laddas upp till Mastodon."; "Scene.Compose.Attachment.AttachmentTooLarge" = "Bilagan är för stor"; "Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "Känner inte igen mediebilagan"; +"Scene.Compose.Attachment.CompressingState" = "Komprimerar..."; "Scene.Compose.Attachment.DescriptionPhoto" = "Beskriv fotot för synskadade..."; "Scene.Compose.Attachment.DescriptionVideo" = "Beskriv videon för de synskadade..."; "Scene.Compose.Attachment.LoadFailed" = "Det gick inte att läsa in"; "Scene.Compose.Attachment.Photo" = "foto"; +"Scene.Compose.Attachment.ServerProcessingState" = "Behandlas av servern..."; "Scene.Compose.Attachment.UploadFailed" = "Uppladdning misslyckades"; "Scene.Compose.Attachment.Video" = "video"; "Scene.Compose.AutoComplete.SpaceToAdd" = "Mellanslag för att lägga till"; @@ -192,6 +209,8 @@ laddas upp till Mastodon."; "Scene.Compose.Poll.OptionNumber" = "Alternativ %ld"; "Scene.Compose.Poll.SevenDays" = "7 dagar"; "Scene.Compose.Poll.SixHours" = "6 timmar"; +"Scene.Compose.Poll.ThePollHasEmptyOption" = "Undersökningen har ett tomt alternativ"; +"Scene.Compose.Poll.ThePollIsInvalid" = "Undersökningen är ogiltig"; "Scene.Compose.Poll.ThirtyMinutes" = "30 minuter"; "Scene.Compose.Poll.ThreeDays" = "3 dagar"; "Scene.Compose.ReplyingToUser" = "svarar %@"; @@ -223,6 +242,12 @@ laddas upp till Mastodon."; "Scene.Familiarfollowers.Title" = "Följare du liknar"; "Scene.Favorite.Title" = "Dina favoriter"; "Scene.FavoritedBy.Title" = "Favoriserad av"; +"Scene.FollowedTags.Actions.Follow" = "Följ"; +"Scene.FollowedTags.Actions.Unfollow" = "Avfölj"; +"Scene.FollowedTags.Header.Participants" = "deltagare"; +"Scene.FollowedTags.Header.Posts" = "inlägg"; +"Scene.FollowedTags.Header.PostsToday" = "inlägg idag"; +"Scene.FollowedTags.Title" = "Följda hashtaggar"; "Scene.Follower.Footer" = "Följare från andra servrar visas inte."; "Scene.Follower.Title" = "följare"; "Scene.Following.Footer" = "Följda på andra servrar visas inte."; @@ -234,6 +259,9 @@ laddas upp till Mastodon."; "Scene.HomeTimeline.NavigationBarState.Published" = "Publicerat!"; "Scene.HomeTimeline.NavigationBarState.Publishing" = "Publicerar inlägget..."; "Scene.HomeTimeline.Title" = "Hem"; +"Scene.Login.ServerSearchField.Placeholder" = "Ange URL eller sök efter din server"; +"Scene.Login.Subtitle" = "Logga in på servern där du skapade ditt konto."; +"Scene.Login.Title" = "Välkommen tillbaka"; "Scene.Notification.FollowRequest.Accept" = "Godkänn"; "Scene.Notification.FollowRequest.Accepted" = "Godkänd"; "Scene.Notification.FollowRequest.Reject" = "avvisa"; @@ -259,8 +287,11 @@ laddas upp till Mastodon."; "Scene.Profile.Dashboard.Following" = "följer"; "Scene.Profile.Dashboard.Posts" = "inlägg"; "Scene.Profile.Fields.AddRow" = "Lägg till rad"; +"Scene.Profile.Fields.Joined" = "Gick med"; "Scene.Profile.Fields.Placeholder.Content" = "Innehåll"; "Scene.Profile.Fields.Placeholder.Label" = "Etikett"; +"Scene.Profile.Fields.Verified.Long" = "Ägarskap för denna länk kontrollerades den %@"; +"Scene.Profile.Fields.Verified.Short" = "Verifierad på %@"; "Scene.Profile.Header.FollowsYou" = "Följer dig"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Message" = "Bekräfta för att blockera %@"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title" = "Blockera konto"; @@ -393,13 +424,11 @@ laddas upp till Mastodon."; "Scene.ServerPicker.EmptyState.BadNetwork" = "Något gick fel när data laddades. Försök igen eller kontrollera din internetanslutning."; "Scene.ServerPicker.EmptyState.FindingServers" = "Söker tillgängliga servrar..."; "Scene.ServerPicker.EmptyState.NoResults" = "Inga resultat"; -"Scene.ServerPicker.Input.Placeholder" = "Sök gemenskaper"; -"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Sök servrar eller ange URL"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Sök gemenskaper eller ange URL"; "Scene.ServerPicker.Label.Category" = "KATEGORI"; "Scene.ServerPicker.Label.Language" = "SPRÅK"; "Scene.ServerPicker.Label.Users" = "ANVÄNDARE"; -"Scene.ServerPicker.Subtitle" = "Välj en server baserat på dina intressen, region eller ett allmänt syfte."; -"Scene.ServerPicker.SubtitleExtend" = "Välj en server baserat på dina intressen, region eller ett allmänt syfte. Varje server drivs av en helt oberoende organisation eller individ."; +"Scene.ServerPicker.Subtitle" = "Välj en server baserat på dina intressen, region eller en allmän server. Du kan fortfarande nå alla, oavsett server."; "Scene.ServerPicker.Title" = "Mastodon utgörs av användare på olika servrar."; "Scene.ServerRules.Button.Confirm" = "Jag godkänner"; "Scene.ServerRules.PrivacyPolicy" = "integritetspolicy"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/sv.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/sv.lproj/Localizable.stringsdict index c7317903d..43a0ff8e4 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/sv.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/sv.lproj/Localizable.stringsdict @@ -50,6 +50,22 @@ %ld tecken + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ kvar + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld tecken + other + %ld tecken + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey @@ -264,7 +280,7 @@ NSStringFormatValueTypeKey ld one - %ld år kvar + 1 år kvar other %ld år kvar @@ -280,7 +296,7 @@ NSStringFormatValueTypeKey ld one - %ld månad kvar + 1 månad kvar other %ld månader kvar @@ -296,7 +312,7 @@ NSStringFormatValueTypeKey ld one - %ld dag kvar + 1 dag kvar other %ld dagar kvar @@ -344,7 +360,7 @@ NSStringFormatValueTypeKey ld one - %ld sekund kvar + 1 sekund kvar other %ld sekunder kvar @@ -392,7 +408,7 @@ NSStringFormatValueTypeKey ld one - %ldd sedan + 1d sedan other %ldd sedan @@ -408,7 +424,7 @@ NSStringFormatValueTypeKey ld one - %ldt sedan + 1t sedan other %ldt sedan diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/th.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/th.lproj/Localizable.strings index de982308d..6aeeb2a74 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/th.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/th.lproj/Localizable.strings @@ -22,6 +22,9 @@ "Common.Alerts.SignOut.Message" = "คุณแน่ใจหรือไม่ว่าต้องการลงชื่อออก?"; "Common.Alerts.SignOut.Title" = "ลงชื่อออก"; "Common.Alerts.SignUpFailure.Title" = "การลงทะเบียนล้มเหลว"; +"Common.Alerts.TranslationFailed.Button" = "ตกลง"; +"Common.Alerts.TranslationFailed.Message" = "การแปลล้มเหลว บางทีผู้ดูแลอาจไม่ได้เปิดใช้งานการแปลในเซิร์ฟเวอร์นี้หรือเซิร์ฟเวอร์นี้กำลังใช้ Mastodon รุ่นเก่ากว่าที่ยังไม่รองรับการแปล"; +"Common.Alerts.TranslationFailed.Title" = "หมายเหตุ"; "Common.Alerts.VoteFailure.PollEnded" = "การสำรวจความคิดเห็นได้สิ้นสุดแล้ว"; "Common.Alerts.VoteFailure.Title" = "การลงคะแนนล้มเหลว"; "Common.Controls.Actions.Add" = "เพิ่ม"; @@ -31,6 +34,7 @@ "Common.Controls.Actions.Compose" = "เขียน"; "Common.Controls.Actions.Confirm" = "ยืนยัน"; "Common.Controls.Actions.Continue" = "ดำเนินการต่อ"; +"Common.Controls.Actions.Copy" = "คัดลอก"; "Common.Controls.Actions.CopyPhoto" = "คัดลอกรูปภาพ"; "Common.Controls.Actions.Delete" = "ลบ"; "Common.Controls.Actions.Discard" = "ละทิ้ง"; @@ -55,10 +59,12 @@ "Common.Controls.Actions.Share" = "แบ่งปัน"; "Common.Controls.Actions.SharePost" = "แบ่งปันโพสต์"; "Common.Controls.Actions.ShareUser" = "แบ่งปัน %@"; -"Common.Controls.Actions.SignIn" = "ลงชื่อเข้า"; -"Common.Controls.Actions.SignUp" = "ลงทะเบียน"; +"Common.Controls.Actions.SignIn" = "เข้าสู่ระบบ"; +"Common.Controls.Actions.SignUp" = "สร้างบัญชี"; "Common.Controls.Actions.Skip" = "ข้าม"; "Common.Controls.Actions.TakePhoto" = "ถ่ายรูป"; +"Common.Controls.Actions.TranslatePost.Title" = "แปลจาก %@"; +"Common.Controls.Actions.TranslatePost.UnknownLanguage" = "ไม่รู้จัก"; "Common.Controls.Actions.TryAgain" = "ลองอีกครั้ง"; "Common.Controls.Actions.UnblockDomain" = "เลิกปิดกั้น %@"; "Common.Controls.Friendship.Block" = "ปิดกั้น"; @@ -100,6 +106,7 @@ "Common.Controls.Status.Actions.Menu" = "เมนู"; "Common.Controls.Status.Actions.Reblog" = "ดัน"; "Common.Controls.Status.Actions.Reply" = "ตอบกลับ"; +"Common.Controls.Status.Actions.ShareLinkInPost" = "แบ่งปันลิงก์ในโพสต์"; "Common.Controls.Status.Actions.ShowGif" = "แสดง GIF"; "Common.Controls.Status.Actions.ShowImage" = "แสดงภาพ"; "Common.Controls.Status.Actions.ShowVideoPlayer" = "แสดงตัวเล่นวิดีโอ"; @@ -107,6 +114,8 @@ "Common.Controls.Status.Actions.Unfavorite" = "เลิกชื่นชอบ"; "Common.Controls.Status.Actions.Unreblog" = "เลิกทำการดัน"; "Common.Controls.Status.ContentWarning" = "คำเตือนเนื้อหา"; +"Common.Controls.Status.LinkViaUser" = "%@ ผ่าน %@"; +"Common.Controls.Status.LoadEmbed" = "โหลดที่ฝังไว้"; "Common.Controls.Status.MediaContentWarning" = "แตะที่ใดก็ตามเพื่อเปิดเผย"; "Common.Controls.Status.MetaEntity.Email" = "ที่อยู่อีเมล: %@"; "Common.Controls.Status.MetaEntity.Hashtag" = "แฮชแท็ก: %@"; @@ -124,6 +133,10 @@ "Common.Controls.Status.Tag.Mention" = "กล่าวถึง"; "Common.Controls.Status.Tag.Url" = "URL"; "Common.Controls.Status.TapToReveal" = "แตะเพื่อเปิดเผย"; +"Common.Controls.Status.Translation.ShowOriginal" = "แสดงดั้งเดิมอยู่"; +"Common.Controls.Status.Translation.TranslatedFrom" = "Translated from %@ using %@"; +"Common.Controls.Status.Translation.UnknownLanguage" = "ไม่รู้จัก"; +"Common.Controls.Status.Translation.UnknownProvider" = "Unknown"; "Common.Controls.Status.UserReblogged" = "%@ ได้ดัน"; "Common.Controls.Status.UserRepliedTo" = "ตอบกลับ %@"; "Common.Controls.Status.Visibility.Direct" = "เฉพาะผู้ใช้ที่กล่าวถึงเท่านั้นที่สามารถเห็นโพสต์นี้"; @@ -131,9 +144,9 @@ "Common.Controls.Status.Visibility.PrivateFromMe" = "เฉพาะผู้ติดตามของฉันเท่านั้นที่สามารถเห็นโพสต์นี้"; "Common.Controls.Status.Visibility.Unlisted" = "ทุกคนสามารถเห็นโพสต์นี้แต่ไม่แสดงในเส้นเวลาสาธารณะ"; "Common.Controls.Tabs.Home" = "หน้าแรก"; -"Common.Controls.Tabs.Notification" = "การแจ้งเตือน"; +"Common.Controls.Tabs.Notifications" = "การแจ้งเตือน"; "Common.Controls.Tabs.Profile" = "โปรไฟล์"; -"Common.Controls.Tabs.Search" = "ค้นหา"; +"Common.Controls.Tabs.SearchAndExplore" = "ค้นหาและสำรวจ"; "Common.Controls.Timeline.Filtered" = "กรองอยู่"; "Common.Controls.Timeline.Header.BlockedWarning" = "คุณไม่สามารถดูโปรไฟล์ของผู้ใช้นี้ จนกว่าเขาจะเลิกปิดกั้นคุณ"; @@ -161,16 +174,20 @@ "Scene.Compose.Accessibility.CustomEmojiPicker" = "ตัวเลือกอีโมจิที่กำหนดเอง"; "Scene.Compose.Accessibility.DisableContentWarning" = "ปิดใช้งานคำเตือนเนื้อหา"; "Scene.Compose.Accessibility.EnableContentWarning" = "เปิดใช้งานคำเตือนเนื้อหา"; +"Scene.Compose.Accessibility.PostOptions" = "ตัวเลือกโพสต์"; "Scene.Compose.Accessibility.PostVisibilityMenu" = "เมนูการมองเห็นโพสต์"; +"Scene.Compose.Accessibility.PostingAs" = "กำลังโพสต์เป็น %@"; "Scene.Compose.Accessibility.RemovePoll" = "เอาการสำรวจความคิดเห็นออก"; "Scene.Compose.Attachment.AttachmentBroken" = "%@ นี้เสียหายและไม่สามารถ อัปโหลดไปยัง Mastodon"; "Scene.Compose.Attachment.AttachmentTooLarge" = "ไฟล์แนบใหญ่เกินไป"; -"Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "Can not regonize this media attachment"; +"Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "ไม่สามารถระบุไฟล์แนบสื่อนี้"; +"Scene.Compose.Attachment.CompressingState" = "กำลังบีบอัด..."; "Scene.Compose.Attachment.DescriptionPhoto" = "อธิบายรูปภาพสำหรับผู้บกพร่องทางการมองเห็น..."; "Scene.Compose.Attachment.DescriptionVideo" = "อธิบายวิดีโอสำหรับผู้บกพร่องทางการมองเห็น..."; "Scene.Compose.Attachment.LoadFailed" = "การโหลดล้มเหลว"; "Scene.Compose.Attachment.Photo" = "รูปภาพ"; +"Scene.Compose.Attachment.ServerProcessingState" = "เซิร์ฟเวอร์กำลังประมวลผล..."; "Scene.Compose.Attachment.UploadFailed" = "การอัปโหลดล้มเหลว"; "Scene.Compose.Attachment.Video" = "วิดีโอ"; "Scene.Compose.AutoComplete.SpaceToAdd" = "เว้นวรรคเพื่อเพิ่ม"; @@ -192,6 +209,8 @@ "Scene.Compose.Poll.OptionNumber" = "ตัวเลือก %ld"; "Scene.Compose.Poll.SevenDays" = "7 วัน"; "Scene.Compose.Poll.SixHours" = "6 ชั่วโมง"; +"Scene.Compose.Poll.ThePollHasEmptyOption" = "การสำรวจความคิดเห็นมีตัวเลือกที่ว่างเปล่า"; +"Scene.Compose.Poll.ThePollIsInvalid" = "การสำรวจความคิดเห็นไม่ถูกต้อง"; "Scene.Compose.Poll.ThirtyMinutes" = "30 นาที"; "Scene.Compose.Poll.ThreeDays" = "3 วัน"; "Scene.Compose.ReplyingToUser" = "กำลังตอบกลับ %@"; @@ -203,10 +222,10 @@ "Scene.Compose.Visibility.Unlisted" = "ไม่อยู่ในรายการ"; "Scene.ConfirmEmail.Button.OpenEmailApp" = "เปิดแอปอีเมล"; "Scene.ConfirmEmail.Button.Resend" = "ส่งใหม่"; -"Scene.ConfirmEmail.DontReceiveEmail.Description" = "หากคุณยังไม่ได้รับอีเมล ตรวจสอบว่าที่อยู่อีเมลของคุณถูกต้อง รวมถึงโฟลเดอร์อีเมลขยะของคุณ"; +"Scene.ConfirmEmail.DontReceiveEmail.Description" = "ตรวจสอบว่าที่อยู่อีเมลของคุณถูกต้องเช่นเดียวกับโฟลเดอร์อีเมลขยะหากคุณยังไม่ได้ทำ"; "Scene.ConfirmEmail.DontReceiveEmail.ResendEmail" = "ส่งอีเมลใหม่"; "Scene.ConfirmEmail.DontReceiveEmail.Title" = "ตรวจสอบอีเมลของคุณ"; -"Scene.ConfirmEmail.OpenEmailApp.Description" = "เราเพิ่งส่งอีเมลหาคุณ หากคุณยังไม่ได้รับอีเมล โปรดตรวจสอบโฟลเดอร์อีเมลขยะ"; +"Scene.ConfirmEmail.OpenEmailApp.Description" = "เราเพิ่งส่งอีเมลถึงคุณ ตรวจสอบโฟลเดอร์อีเมลขยะของคุณหากคุณยังไม่ได้ทำ"; "Scene.ConfirmEmail.OpenEmailApp.Mail" = "จดหมาย"; "Scene.ConfirmEmail.OpenEmailApp.OpenEmailClient" = "เปิดไคลเอ็นต์อีเมล"; "Scene.ConfirmEmail.OpenEmailApp.Title" = "ตรวจสอบกล่องขาเข้าของคุณ"; @@ -223,6 +242,12 @@ "Scene.Familiarfollowers.Title" = "ผู้ติดตามที่คุณคุ้นเคย"; "Scene.Favorite.Title" = "รายการโปรดของคุณ"; "Scene.FavoritedBy.Title" = "ชื่นชอบโดย"; +"Scene.FollowedTags.Actions.Follow" = "ติดตาม"; +"Scene.FollowedTags.Actions.Unfollow" = "เลิกติดตาม"; +"Scene.FollowedTags.Header.Participants" = "ผู้เข้าร่วม"; +"Scene.FollowedTags.Header.Posts" = "โพสต์"; +"Scene.FollowedTags.Header.PostsToday" = "โพสต์วันนี้"; +"Scene.FollowedTags.Title" = "แท็กที่ติดตาม"; "Scene.Follower.Footer" = "ไม่ได้แสดงผู้ติดตามจากเซิร์ฟเวอร์อื่น ๆ"; "Scene.Follower.Title" = "ผู้ติดตาม"; "Scene.Following.Footer" = "ไม่ได้แสดงการติดตามจากเซิร์ฟเวอร์อื่น ๆ"; @@ -234,6 +259,9 @@ "Scene.HomeTimeline.NavigationBarState.Published" = "เผยแพร่แล้ว!"; "Scene.HomeTimeline.NavigationBarState.Publishing" = "กำลังเผยแพร่โพสต์..."; "Scene.HomeTimeline.Title" = "หน้าแรก"; +"Scene.Login.ServerSearchField.Placeholder" = "ป้อน URL หรือค้นหาสำหรับเซิร์ฟเวอร์ของคุณ"; +"Scene.Login.Subtitle" = "นำคุณเข้าสู่ระบบในเซิร์ฟเวอร์ที่คุณได้สร้างบัญชีของคุณไว้ใน"; +"Scene.Login.Title" = "ยินดีต้อนรับกลับมา"; "Scene.Notification.FollowRequest.Accept" = "ยอมรับ"; "Scene.Notification.FollowRequest.Accepted" = "ยอมรับแล้ว"; "Scene.Notification.FollowRequest.Reject" = "ปฏิเสธ"; @@ -259,8 +287,11 @@ "Scene.Profile.Dashboard.Following" = "กำลังติดตาม"; "Scene.Profile.Dashboard.Posts" = "โพสต์"; "Scene.Profile.Fields.AddRow" = "เพิ่มแถว"; +"Scene.Profile.Fields.Joined" = "Joined"; "Scene.Profile.Fields.Placeholder.Content" = "เนื้อหา"; "Scene.Profile.Fields.Placeholder.Label" = "ป้ายชื่อ"; +"Scene.Profile.Fields.Verified.Long" = "ตรวจสอบความเป็นเจ้าของของลิงก์นี้เมื่อ %@"; +"Scene.Profile.Fields.Verified.Short" = "ตรวจสอบเมื่อ %@"; "Scene.Profile.Header.FollowsYou" = "ติดตามคุณ"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Message" = "ยืนยันเพื่อปิดกั้น %@"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title" = "ปิดกั้นบัญชี"; @@ -295,7 +326,7 @@ "Scene.Register.Error.Reason.Taken" = "%@ ถูกใช้งานแล้ว"; "Scene.Register.Error.Reason.TooLong" = "%@ ยาวเกินไป"; "Scene.Register.Error.Reason.TooShort" = "%@ สั้นเกินไป"; -"Scene.Register.Error.Reason.Unreachable" = "ดูเหมือนว่า %@ จะไม่มีอยู่"; +"Scene.Register.Error.Reason.Unreachable" = "ดูเหมือนว่าจะไม่มี %@ อยู่"; "Scene.Register.Error.Special.EmailInvalid" = "นี่ไม่ใช่ที่อยู่อีเมลที่ถูกต้อง"; "Scene.Register.Error.Special.PasswordTooShort" = "รหัสผ่านสั้นเกินไป (ต้องมีอย่างน้อย 8 ตัวอักษร)"; "Scene.Register.Error.Special.UsernameInvalid" = "ชื่อผู้ใช้ต้องมีเฉพาะตัวอักษรและตัวเลขและขีดล่างเท่านั้น"; @@ -393,13 +424,11 @@ "Scene.ServerPicker.EmptyState.BadNetwork" = "มีบางอย่างผิดพลาดขณะโหลดข้อมูล ตรวจสอบการเชื่อมต่ออินเทอร์เน็ตของคุณ"; "Scene.ServerPicker.EmptyState.FindingServers" = "กำลังค้นหาเซิร์ฟเวอร์ที่พร้อมใช้งาน..."; "Scene.ServerPicker.EmptyState.NoResults" = "ไม่มีผลลัพธ์"; -"Scene.ServerPicker.Input.Placeholder" = "ค้นหาเซิร์ฟเวอร์"; -"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "ค้นหาเซิร์ฟเวอร์หรือป้อน URL"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "ค้นหาชุมชนหรือป้อน URL"; "Scene.ServerPicker.Label.Category" = "หมวดหมู่"; "Scene.ServerPicker.Label.Language" = "ภาษา"; "Scene.ServerPicker.Label.Users" = "ผู้ใช้"; -"Scene.ServerPicker.Subtitle" = "เลือกเซิร์ฟเวอร์ตามความสนใจ, ภูมิภาค หรือวัตถุประสงค์ทั่วไปของคุณ"; -"Scene.ServerPicker.SubtitleExtend" = "เลือกเซิร์ฟเวอร์ตามความสนใจ, ภูมิภาค หรือวัตถุประสงค์ทั่วไปของคุณ แต่ละเซิร์ฟเวอร์ได้รับการดำเนินงานโดยองค์กรหรือบุคคลที่เป็นอิสระโดยสิ้นเชิง"; +"Scene.ServerPicker.Subtitle" = "เลือกเซิร์ฟเวอร์ตามภูมิภาค, ความสนใจ หรือวัตถุประสงค์ทั่วไปของคุณ คุณยังคงสามารถแชทกับใครก็ตามใน Mastodon โดยไม่คำนึงถึงเซิร์ฟเวอร์ของคุณ"; "Scene.ServerPicker.Title" = "Mastodon ประกอบด้วยผู้ใช้ในเซิร์ฟเวอร์ต่าง ๆ"; "Scene.ServerRules.Button.Confirm" = "ฉันเห็นด้วย"; "Scene.ServerRules.PrivacyPolicy" = "นโยบายความเป็นส่วนตัว"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/th.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/th.lproj/Localizable.stringsdict index 897d07eca..f25561ad6 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/th.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/th.lproj/Localizable.stringsdict @@ -44,6 +44,20 @@ %ld ตัวอักษร + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + เหลืออีก %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld ตัวอักษร + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/tr.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/tr.lproj/Localizable.strings index 614ea6dc5..c71fb332e 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/tr.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/tr.lproj/Localizable.strings @@ -21,6 +21,9 @@ "Common.Alerts.SignOut.Message" = "Oturumu kapatmak istediğinize emin misiniz?"; "Common.Alerts.SignOut.Title" = "Oturumu Kapat"; "Common.Alerts.SignUpFailure.Title" = "Kaydolma Başarısız"; +"Common.Alerts.TranslationFailed.Button" = "OK"; +"Common.Alerts.TranslationFailed.Message" = "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported."; +"Common.Alerts.TranslationFailed.Title" = "Note"; "Common.Alerts.VoteFailure.PollEnded" = "Anket bitti"; "Common.Alerts.VoteFailure.Title" = "Oy Verme Başarısız"; "Common.Controls.Actions.Add" = "Ekle"; @@ -30,6 +33,7 @@ "Common.Controls.Actions.Compose" = "Yaz"; "Common.Controls.Actions.Confirm" = "Onayla"; "Common.Controls.Actions.Continue" = "Devam et"; +"Common.Controls.Actions.Copy" = "Copy"; "Common.Controls.Actions.CopyPhoto" = "Fotoğrafı Kopyala"; "Common.Controls.Actions.Delete" = "Sil"; "Common.Controls.Actions.Discard" = "Vazgeç"; @@ -55,9 +59,11 @@ "Common.Controls.Actions.SharePost" = "Gönderiyi Paylaş"; "Common.Controls.Actions.ShareUser" = "%@ ile paylaş"; "Common.Controls.Actions.SignIn" = "Giriş Yap"; -"Common.Controls.Actions.SignUp" = "Kaydol"; +"Common.Controls.Actions.SignUp" = "Hesap oluştur"; "Common.Controls.Actions.Skip" = "Atla"; "Common.Controls.Actions.TakePhoto" = "Fotoğraf Çek"; +"Common.Controls.Actions.TranslatePost.Title" = "Translate from %@"; +"Common.Controls.Actions.TranslatePost.UnknownLanguage" = "Unknown"; "Common.Controls.Actions.TryAgain" = "Tekrar Deneyin"; "Common.Controls.Actions.UnblockDomain" = "%@ kişisinin engelini kaldır"; "Common.Controls.Friendship.Block" = "Engelle"; @@ -67,13 +73,13 @@ "Common.Controls.Friendship.EditInfo" = "Bilgiyi Düzenle"; "Common.Controls.Friendship.Follow" = "Takip et"; "Common.Controls.Friendship.Following" = "Takip ediliyor"; -"Common.Controls.Friendship.HideReblogs" = "Hide Reblogs"; +"Common.Controls.Friendship.HideReblogs" = "Yeniden Paylaşımları Gizle"; "Common.Controls.Friendship.Mute" = "Sessize al"; "Common.Controls.Friendship.MuteUser" = "Sustur %@"; "Common.Controls.Friendship.Muted" = "Susturuldu"; "Common.Controls.Friendship.Pending" = "Bekliyor"; "Common.Controls.Friendship.Request" = "İstek"; -"Common.Controls.Friendship.ShowReblogs" = "Show Reblogs"; +"Common.Controls.Friendship.ShowReblogs" = "Yeniden Paylaşımları Göster"; "Common.Controls.Friendship.Unblock" = "Engeli kaldır"; "Common.Controls.Friendship.UnblockUser" = "%@ kişisinin engelini kaldır"; "Common.Controls.Friendship.Unmute" = "Susturmayı kaldır"; @@ -99,6 +105,7 @@ "Common.Controls.Status.Actions.Menu" = "Menü"; "Common.Controls.Status.Actions.Reblog" = "Yeniden paylaş"; "Common.Controls.Status.Actions.Reply" = "Yanıtla"; +"Common.Controls.Status.Actions.ShareLinkInPost" = "Share Link in Post"; "Common.Controls.Status.Actions.ShowGif" = "GIF'i göster"; "Common.Controls.Status.Actions.ShowImage" = "Görüntüyü göster"; "Common.Controls.Status.Actions.ShowVideoPlayer" = "Video oynatıcıyı göster"; @@ -106,11 +113,13 @@ "Common.Controls.Status.Actions.Unfavorite" = "Favorilerden Çıkar"; "Common.Controls.Status.Actions.Unreblog" = "Yeniden paylaşımı geri al"; "Common.Controls.Status.ContentWarning" = "İçerik Uyarısı"; +"Common.Controls.Status.LinkViaUser" = "%@ via %@"; +"Common.Controls.Status.LoadEmbed" = "Load Embed"; "Common.Controls.Status.MediaContentWarning" = "Göstermek için herhangi bir yere basın"; -"Common.Controls.Status.MetaEntity.Email" = "Email address: %@"; -"Common.Controls.Status.MetaEntity.Hashtag" = "Hashtag: %@"; -"Common.Controls.Status.MetaEntity.Mention" = "Show Profile: %@"; -"Common.Controls.Status.MetaEntity.Url" = "Link: %@"; +"Common.Controls.Status.MetaEntity.Email" = "E-posta adresi: %@"; +"Common.Controls.Status.MetaEntity.Hashtag" = "Etiket: %@"; +"Common.Controls.Status.MetaEntity.Mention" = "Profili Göster: %@"; +"Common.Controls.Status.MetaEntity.Url" = "Bağlantı: %@"; "Common.Controls.Status.Poll.Closed" = "Kapandı"; "Common.Controls.Status.Poll.Vote" = "Oy ver"; "Common.Controls.Status.SensitiveContent" = "Hassas İçerik"; @@ -123,6 +132,10 @@ "Common.Controls.Status.Tag.Mention" = "Bahset"; "Common.Controls.Status.Tag.Url" = "Bağlantı"; "Common.Controls.Status.TapToReveal" = "Göstermek için basın"; +"Common.Controls.Status.Translation.ShowOriginal" = "Shown Original"; +"Common.Controls.Status.Translation.TranslatedFrom" = "Translated from %@ using %@"; +"Common.Controls.Status.Translation.UnknownLanguage" = "Unknown"; +"Common.Controls.Status.Translation.UnknownProvider" = "Unknown"; "Common.Controls.Status.UserReblogged" = "%@ yeniden paylaştı"; "Common.Controls.Status.UserRepliedTo" = "%@ kullanıcısına yanıt verdi"; "Common.Controls.Status.Visibility.Direct" = "Sadece bahsedilen kullanıcı bu gönderiyi görebilir."; @@ -130,9 +143,9 @@ "Common.Controls.Status.Visibility.PrivateFromMe" = "Sadece benim takipçilerim bu gönderiyi görebilir."; "Common.Controls.Status.Visibility.Unlisted" = "Bu gönderiyi herkes görebilir, fakat herkese açık zaman tünelinde gösterilmez."; "Common.Controls.Tabs.Home" = "Ana Sayfa"; -"Common.Controls.Tabs.Notification" = "Bildirimler"; +"Common.Controls.Tabs.Notifications" = "Bildirimler"; "Common.Controls.Tabs.Profile" = "Profil"; -"Common.Controls.Tabs.Search" = "Arama"; +"Common.Controls.Tabs.SearchAndExplore" = "Ara ve Keşfet"; "Common.Controls.Timeline.Filtered" = "Filtrelenmiş"; "Common.Controls.Timeline.Header.BlockedWarning" = "Bu kişi sizin engelinizi kaldırana kadar onun profilini göremezsiniz."; @@ -154,23 +167,27 @@ Bu kişiye göre profiliniz böyle gözüküyor."; "Scene.AccountList.AddAccount" = "Hesap Ekle"; "Scene.AccountList.DismissAccountSwitcher" = "Hesap Değiştiriciyi Kapat"; "Scene.AccountList.TabBarHint" = "Şu anki seçili profil: %@. Hesap değiştiriciyi göstermek için iki kez dokunun ve basılı tutun"; -"Scene.Bookmark.Title" = "Bookmarks"; +"Scene.Bookmark.Title" = "Yer İmleri"; "Scene.Compose.Accessibility.AppendAttachment" = "Dosya Ekle"; "Scene.Compose.Accessibility.AppendPoll" = "Anket Ekle"; "Scene.Compose.Accessibility.CustomEmojiPicker" = "Özel Emoji Seçici"; "Scene.Compose.Accessibility.DisableContentWarning" = "İçerik Uyarısını Kapat"; "Scene.Compose.Accessibility.EnableContentWarning" = "İçerik Uyarısını Etkinleştir"; +"Scene.Compose.Accessibility.PostOptions" = "Gönderi Seçenekleri"; "Scene.Compose.Accessibility.PostVisibilityMenu" = "Gönderi Görünürlüğü Menüsü"; +"Scene.Compose.Accessibility.PostingAs" = "%@ olarak paylaşılıyor"; "Scene.Compose.Accessibility.RemovePoll" = "Anketi Kaldır"; "Scene.Compose.Attachment.AttachmentBroken" = "Bu %@ bozuk ve Mastodon'a yüklenemiyor."; -"Scene.Compose.Attachment.AttachmentTooLarge" = "Attachment too large"; -"Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "Can not regonize this media attachment"; +"Scene.Compose.Attachment.AttachmentTooLarge" = "Ek boyutu çok büyük"; +"Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "Ekteki medya uzantısı görüntülenemiyor"; +"Scene.Compose.Attachment.CompressingState" = "Sıkıştırılıyor..."; "Scene.Compose.Attachment.DescriptionPhoto" = "Görme engelliler için fotoğrafı tarif edin..."; "Scene.Compose.Attachment.DescriptionVideo" = "Görme engelliler için videoyu tarif edin..."; -"Scene.Compose.Attachment.LoadFailed" = "Load Failed"; +"Scene.Compose.Attachment.LoadFailed" = "Yükleme Başarısız"; "Scene.Compose.Attachment.Photo" = "fotoğraf"; -"Scene.Compose.Attachment.UploadFailed" = "Upload Failed"; +"Scene.Compose.Attachment.ServerProcessingState" = "Sunucu İşliyor..."; +"Scene.Compose.Attachment.UploadFailed" = "Yükleme Başarısız"; "Scene.Compose.Attachment.Video" = "video"; "Scene.Compose.AutoComplete.SpaceToAdd" = "Eklemek için boşluk tuşuna basın"; "Scene.Compose.ComposeAction" = "Yayınla"; @@ -191,6 +208,8 @@ yüklenemiyor."; "Scene.Compose.Poll.OptionNumber" = "Seçenek %ld"; "Scene.Compose.Poll.SevenDays" = "7 Gün"; "Scene.Compose.Poll.SixHours" = "6 Saat"; +"Scene.Compose.Poll.ThePollHasEmptyOption" = "The poll has empty option"; +"Scene.Compose.Poll.ThePollIsInvalid" = "Anket geçersiz"; "Scene.Compose.Poll.ThirtyMinutes" = "30 dakika"; "Scene.Compose.Poll.ThreeDays" = "3 Gün"; "Scene.Compose.ReplyingToUser" = "yanıtlanıyor: %@"; @@ -218,10 +237,16 @@ yüklenemiyor."; "Scene.Discovery.Tabs.Hashtags" = "Etiketler"; "Scene.Discovery.Tabs.News" = "Haberler"; "Scene.Discovery.Tabs.Posts" = "Gönderiler"; -"Scene.Familiarfollowers.FollowedByNames" = "Followed by %@"; -"Scene.Familiarfollowers.Title" = "Followers you familiar"; +"Scene.Familiarfollowers.FollowedByNames" = "%@ tarafından takip ediliyor"; +"Scene.Familiarfollowers.Title" = "Tanıyor olabileceğin takipçiler"; "Scene.Favorite.Title" = "Favorilerin"; "Scene.FavoritedBy.Title" = "Favorited By"; +"Scene.FollowedTags.Actions.Follow" = "Takip et"; +"Scene.FollowedTags.Actions.Unfollow" = "Takibi bırak"; +"Scene.FollowedTags.Header.Participants" = "katılımcılar"; +"Scene.FollowedTags.Header.Posts" = "gönderiler"; +"Scene.FollowedTags.Header.PostsToday" = "posts today"; +"Scene.FollowedTags.Title" = "Takip Edilen Etiketler"; "Scene.Follower.Footer" = "Diğer sunucudaki takipçiler gösterilemiyor."; "Scene.Follower.Title" = "takipçi"; "Scene.Following.Footer" = "Diğer sunucudaki takip edilenler gösterilemiyor."; @@ -233,10 +258,13 @@ yüklenemiyor."; "Scene.HomeTimeline.NavigationBarState.Published" = "Yayınlandı!"; "Scene.HomeTimeline.NavigationBarState.Publishing" = "Gönderi yayınlanıyor..."; "Scene.HomeTimeline.Title" = "Ana Sayfa"; -"Scene.Notification.FollowRequest.Accept" = "Accept"; -"Scene.Notification.FollowRequest.Accepted" = "Accepted"; -"Scene.Notification.FollowRequest.Reject" = "reject"; -"Scene.Notification.FollowRequest.Rejected" = "Rejected"; +"Scene.Login.ServerSearchField.Placeholder" = "Bir URL girin ya da sunucunuzu arayın"; +"Scene.Login.Subtitle" = "Hesabını oluşturduğun sunucuya giriş yap."; +"Scene.Login.Title" = "Tekrar hoş geldin"; +"Scene.Notification.FollowRequest.Accept" = "Kabul Et"; +"Scene.Notification.FollowRequest.Accepted" = "Kabul Edildi"; +"Scene.Notification.FollowRequest.Reject" = "Reddet"; +"Scene.Notification.FollowRequest.Rejected" = "Reddedildi"; "Scene.Notification.Keyobard.ShowEverything" = "Her Şeyi Göster"; "Scene.Notification.Keyobard.ShowMentions" = "Bahsetmeleri Göster"; "Scene.Notification.NotificationDescription.FavoritedYourPost" = "gönderini favoriledi"; @@ -258,17 +286,20 @@ yüklenemiyor."; "Scene.Profile.Dashboard.Following" = "takip ediliyor"; "Scene.Profile.Dashboard.Posts" = "gönderiler"; "Scene.Profile.Fields.AddRow" = "Satır Ekle"; +"Scene.Profile.Fields.Joined" = "Joined"; "Scene.Profile.Fields.Placeholder.Content" = "İçerik"; "Scene.Profile.Fields.Placeholder.Label" = "Etiket"; +"Scene.Profile.Fields.Verified.Long" = "%@ adresinin sahipliği kontrol edilmiş"; +"Scene.Profile.Fields.Verified.Short" = "%@ tarafında onaylı"; "Scene.Profile.Header.FollowsYou" = "Seni takip ediyor"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Message" = "%@ engellemeyi onayla"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title" = "Hesabı Engelle"; -"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Message" = "Confirm to hide reblogs"; -"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Title" = "Hide Reblogs"; +"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Message" = "Yeniden paylaşımları gizlemeyi onayla"; +"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Title" = "Yeniden Paylaşımları Gizle"; "Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Message" = "%@ susturmak için onaylayın"; "Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Title" = "Hesabı sustur"; -"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Message" = "Confirm to show reblogs"; -"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Title" = "Show Reblogs"; +"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Message" = "Yeniden paylaşımları göstermeyi onayla"; +"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Title" = "Yeniden Paylaşımları Göster"; "Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Message" = "%@ engellemeyi kaldırmayı onaylayın"; "Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Title" = "Hesabın Engelini Kaldır"; "Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.Message" = "%@ susturmasını kaldırmak için onaylayın"; @@ -321,15 +352,15 @@ yüklenemiyor."; "Scene.Report.SkipToSend" = "Yorum yapmadan gönder"; "Scene.Report.Step1" = "Adım 1/2"; "Scene.Report.Step2" = "Adım 2/2"; -"Scene.Report.StepFinal.BlockUser" = "Block %@"; +"Scene.Report.StepFinal.BlockUser" = "%@ kişisini engelle"; "Scene.Report.StepFinal.DontWantToSeeThis" = "Bunu görmek istemiyor musunuz?"; "Scene.Report.StepFinal.MuteUser" = "Sustur %@"; -"Scene.Report.StepFinal.TheyWillNoLongerBeAbleToFollowOrSeeYourPostsButTheyCanSeeIfTheyveBeenBlocked" = "They will no longer be able to follow or see your posts, but they can see if they’ve been blocked."; +"Scene.Report.StepFinal.TheyWillNoLongerBeAbleToFollowOrSeeYourPostsButTheyCanSeeIfTheyveBeenBlocked" = "Artık sizi takip edemez ve gönderilerinizi göremezler ama engellendiklerini görebilirler."; "Scene.Report.StepFinal.Unfollow" = "Takibi bırak"; "Scene.Report.StepFinal.UnfollowUser" = "Takipten çık %@"; -"Scene.Report.StepFinal.Unfollowed" = "Unfollowed"; -"Scene.Report.StepFinal.WhenYouSeeSomethingYouDontLikeOnMastodonYouCanRemoveThePersonFromYourExperience." = "When you see something you don’t like on Mastodon, you can remove the person from your experience."; -"Scene.Report.StepFinal.WhileWeReviewThisYouCanTakeActionAgainstUser" = "While we review this, you can take action against %@"; +"Scene.Report.StepFinal.Unfollowed" = "Takipten çıkıldı"; +"Scene.Report.StepFinal.WhenYouSeeSomethingYouDontLikeOnMastodonYouCanRemoveThePersonFromYourExperience." = "Mastodon'da beğenmediğiniz bir şey gördüğünüzde, o kişiyi deneyiminizden çıkarabilirsiniz."; +"Scene.Report.StepFinal.WhileWeReviewThisYouCanTakeActionAgainstUser" = "Biz bunu incelerken siz %@ hesabına karşı önlem alabilirsiniz"; "Scene.Report.StepFinal.YouWontSeeTheirPostsOrReblogsInYourHomeFeedTheyWontKnowTheyVeBeenMuted" = "You won’t see their posts or reblogs in your home feed. They won’t know they’ve been muted."; "Scene.Report.StepFour.IsThereAnythingElseWeShouldKnow" = "Bilmemiz gereken başka bir şey var mı?"; "Scene.Report.StepFour.Step4Of4" = "Adım 4/4"; @@ -338,14 +369,14 @@ yüklenemiyor."; "Scene.Report.StepOne.ItViolatesServerRules" = "Sunucu kurallarını ihlal ediyor"; "Scene.Report.StepOne.ItsSomethingElse" = "Başka bir şey"; "Scene.Report.StepOne.ItsSpam" = "Spam"; -"Scene.Report.StepOne.MaliciousLinksFakeEngagementOrRepetetiveReplies" = "Malicious links, fake engagement, or repetetive replies"; +"Scene.Report.StepOne.MaliciousLinksFakeEngagementOrRepetetiveReplies" = "Kötü niyetli bağlantılar, sahte etkileşim veya tekrarlayan yanıtlar"; "Scene.Report.StepOne.SelectTheBestMatch" = "En iyi seçeneceği seçiniz"; "Scene.Report.StepOne.Step1Of4" = "Adım 1/4"; "Scene.Report.StepOne.TheIssueDoesNotFitIntoOtherCategories" = "Sorun bunlardan biri değil"; "Scene.Report.StepOne.WhatsWrongWithThisAccount" = "Bu hesap ile ilgili sorun nedir?"; "Scene.Report.StepOne.WhatsWrongWithThisPost" = "Bu gönderi ile ilgili sorun nedir?"; "Scene.Report.StepOne.WhatsWrongWithThisUsername" = "%@ kişisinin sorunu nedir?"; -"Scene.Report.StepOne.YouAreAwareThatItBreaksSpecificRules" = "You are aware that it breaks specific rules"; +"Scene.Report.StepOne.YouAreAwareThatItBreaksSpecificRules" = "Belirli kuralları ihlal ettiğinin farkındasınız"; "Scene.Report.StepThree.AreThereAnyPostsThatBackUpThisReport" = "Bu bildirimi destekleyecek herhangi bir gönderi var mı?"; "Scene.Report.StepThree.SelectAllThatApply" = "Geçerli olanların tümünü seçiniz"; "Scene.Report.StepThree.Step3Of4" = "Adım 3/4"; @@ -392,13 +423,11 @@ yüklenemiyor."; "Scene.ServerPicker.EmptyState.BadNetwork" = "Veriyi yüklerken bir hata oluştu. Lütfen internet bağlantınızı kontrol edin."; "Scene.ServerPicker.EmptyState.FindingServers" = "Mevcut sunucular aranıyor..."; "Scene.ServerPicker.EmptyState.NoResults" = "Sonuç yok"; -"Scene.ServerPicker.Input.Placeholder" = "Toplulukları ara"; -"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Sunucuları ara ya da bir bağlantı gir"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Topluluklar arayın ya da bir URL girin"; "Scene.ServerPicker.Label.Category" = "KATEGORİ"; "Scene.ServerPicker.Label.Language" = "DİL"; "Scene.ServerPicker.Label.Users" = "KULLANICILAR"; -"Scene.ServerPicker.Subtitle" = "İlgi alanlarınıza, bölgenize veya genel amaçlı bir topluluk seçin."; -"Scene.ServerPicker.SubtitleExtend" = "İlgi alanlarınıza, bölgenize veya genel amaçlı bir topluluk seçin. Her topluluk tamamen bağımsız bir kuruluş veya kişi tarafından işletilmektedir."; +"Scene.ServerPicker.Subtitle" = "Bölgenize dayalı, ilginize dayalı ya da genel amaçlı bir sunucu seçin. Hangi sunucuda olduğunuz fark etmeksizin Mastodon'daki herkes ile konuşabilirsiniz."; "Scene.ServerPicker.Title" = "Mastodon, farklı topluluklardaki kullanıcılardan oluşur."; "Scene.ServerRules.Button.Confirm" = "Kabul Ediyorum"; "Scene.ServerRules.PrivacyPolicy" = "gizlilik politikası"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/tr.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/tr.lproj/Localizable.stringsdict index 29df92c2b..13552b607 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/tr.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/tr.lproj/Localizable.stringsdict @@ -50,6 +50,22 @@ %ld karakter + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ kaldı + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 karakter + other + %ld karakter + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey @@ -72,9 +88,9 @@ NSStringFormatValueTypeKey ld one - Followed by %1$@, and another mutual + %1$@ ve bir ortak kişi tarafından takip edildi other - Followed by %1$@, and %ld mutuals + %1$@ ve %ld ortak kişi tarafından takip edildi plural.count.metric_formatted.post @@ -104,9 +120,9 @@ NSStringFormatValueTypeKey ld one - 1 media + 1 medya other - %ld media + %ld medya plural.count.post diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/vi.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/vi.lproj/Localizable.strings index 41ac9aa20..dc66003cc 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/vi.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/vi.lproj/Localizable.strings @@ -22,6 +22,9 @@ Vui lòng kiểm tra kết nối mạng."; "Common.Alerts.SignOut.Message" = "Bạn có chắc muốn đăng xuất không?"; "Common.Alerts.SignOut.Title" = "Đăng xuất"; "Common.Alerts.SignUpFailure.Title" = "Đăng ký không thành công"; +"Common.Alerts.TranslationFailed.Button" = "OK"; +"Common.Alerts.TranslationFailed.Message" = "Dịch không thành công. Có thể quản trị viên chưa bật dịch trên máy chủ này hoặc máy chủ này đang chạy phiên bản cũ hơn của Mastodon chưa hỗ trợ dịch."; +"Common.Alerts.TranslationFailed.Title" = "Ghi chú"; "Common.Alerts.VoteFailure.PollEnded" = "Cuộc bình chọn đã kết thúc"; "Common.Alerts.VoteFailure.Title" = "Bình chọn không thành công"; "Common.Controls.Actions.Add" = "Thêm"; @@ -31,6 +34,7 @@ Vui lòng kiểm tra kết nối mạng."; "Common.Controls.Actions.Compose" = "Viết tút"; "Common.Controls.Actions.Confirm" = "Xác nhận"; "Common.Controls.Actions.Continue" = "Tiếp tục"; +"Common.Controls.Actions.Copy" = "Chép"; "Common.Controls.Actions.CopyPhoto" = "Sao chép ảnh"; "Common.Controls.Actions.Delete" = "Xóa"; "Common.Controls.Actions.Discard" = "Bỏ qua"; @@ -56,9 +60,11 @@ Vui lòng kiểm tra kết nối mạng."; "Common.Controls.Actions.SharePost" = "Chia sẻ tút"; "Common.Controls.Actions.ShareUser" = "Chia sẻ %@"; "Common.Controls.Actions.SignIn" = "Đăng nhập"; -"Common.Controls.Actions.SignUp" = "Đăng ký"; +"Common.Controls.Actions.SignUp" = "Tạo tài khoản"; "Common.Controls.Actions.Skip" = "Bỏ qua"; "Common.Controls.Actions.TakePhoto" = "Chụp ảnh"; +"Common.Controls.Actions.TranslatePost.Title" = "Dịch từ %@"; +"Common.Controls.Actions.TranslatePost.UnknownLanguage" = "Chưa xác định"; "Common.Controls.Actions.TryAgain" = "Thử lại"; "Common.Controls.Actions.UnblockDomain" = "Bỏ chặn %@"; "Common.Controls.Friendship.Block" = "Chặn"; @@ -100,6 +106,7 @@ Vui lòng kiểm tra kết nối mạng."; "Common.Controls.Status.Actions.Menu" = "Menu"; "Common.Controls.Status.Actions.Reblog" = "Đăng lại"; "Common.Controls.Status.Actions.Reply" = "Trả lời"; +"Common.Controls.Status.Actions.ShareLinkInPost" = "Chia sẻ liên kết trong Tút"; "Common.Controls.Status.Actions.ShowGif" = "Hiển thị GIF"; "Common.Controls.Status.Actions.ShowImage" = "Hiển thị hình ảnh"; "Common.Controls.Status.Actions.ShowVideoPlayer" = "Hiện trình phát video"; @@ -107,6 +114,8 @@ Vui lòng kiểm tra kết nối mạng."; "Common.Controls.Status.Actions.Unfavorite" = "Bỏ thích"; "Common.Controls.Status.Actions.Unreblog" = "Hủy đăng lại"; "Common.Controls.Status.ContentWarning" = "Nội dung ẩn"; +"Common.Controls.Status.LinkViaUser" = "%@ bởi %@"; +"Common.Controls.Status.LoadEmbed" = "Nạp mã nhúng"; "Common.Controls.Status.MediaContentWarning" = "Nhấn để hiển thị"; "Common.Controls.Status.MetaEntity.Email" = "Email: %@"; "Common.Controls.Status.MetaEntity.Hashtag" = "Hashtag: %@"; @@ -124,6 +133,10 @@ Vui lòng kiểm tra kết nối mạng."; "Common.Controls.Status.Tag.Mention" = "Nhắc đến"; "Common.Controls.Status.Tag.Url" = "URL"; "Common.Controls.Status.TapToReveal" = "Nhấn để xem"; +"Common.Controls.Status.Translation.ShowOriginal" = "Bản gốc"; +"Common.Controls.Status.Translation.TranslatedFrom" = "Translated from %@ using %@"; +"Common.Controls.Status.Translation.UnknownLanguage" = "Không xác định"; +"Common.Controls.Status.Translation.UnknownProvider" = "Unknown"; "Common.Controls.Status.UserReblogged" = "%@ đăng lại"; "Common.Controls.Status.UserRepliedTo" = "Trả lời đến %@"; "Common.Controls.Status.Visibility.Direct" = "Chỉ người được nhắc đến có thể thấy tút."; @@ -131,9 +144,9 @@ Vui lòng kiểm tra kết nối mạng."; "Common.Controls.Status.Visibility.PrivateFromMe" = "Chỉ người theo dõi tôi có thể thấy tút này."; "Common.Controls.Status.Visibility.Unlisted" = "Ai cũng thấy tút này nhưng không hiện trên bảng tin máy chủ."; "Common.Controls.Tabs.Home" = "Bảng tin"; -"Common.Controls.Tabs.Notification" = "Thông báo"; +"Common.Controls.Tabs.Notifications" = "Thông báo"; "Common.Controls.Tabs.Profile" = "Trang hồ sơ"; -"Common.Controls.Tabs.Search" = "Tìm kiếm"; +"Common.Controls.Tabs.SearchAndExplore" = "Tìm và Khám Phá"; "Common.Controls.Timeline.Filtered" = "Bộ lọc"; "Common.Controls.Timeline.Header.BlockedWarning" = "Bạn không thể xem trang người này cho tới khi họ bỏ chặn bạn."; @@ -161,17 +174,21 @@ Họ sẽ thấy trang của bạn như thế này."; "Scene.Compose.Accessibility.CustomEmojiPicker" = "Chọn emoji"; "Scene.Compose.Accessibility.DisableContentWarning" = "Tắt nội dung ẩn"; "Scene.Compose.Accessibility.EnableContentWarning" = "Bật nội dung ẩn"; +"Scene.Compose.Accessibility.PostOptions" = "Tùy chọn đăng"; "Scene.Compose.Accessibility.PostVisibilityMenu" = "Menu hiển thị tút"; +"Scene.Compose.Accessibility.PostingAs" = "Đăng dưới dạng %@"; "Scene.Compose.Accessibility.RemovePoll" = "Xóa bình chọn"; "Scene.Compose.Attachment.AttachmentBroken" = "%@ này bị lỗi và không thể tải lên Mastodon."; -"Scene.Compose.Attachment.AttachmentTooLarge" = "Attachment too large"; -"Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "Can not regonize this media attachment"; +"Scene.Compose.Attachment.AttachmentTooLarge" = "Tập tin đính kèm quá lớn"; +"Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "Không xem được tập tin đính kèm"; +"Scene.Compose.Attachment.CompressingState" = "Đang nén..."; "Scene.Compose.Attachment.DescriptionPhoto" = "Mô tả hình ảnh cho người khiếm thị..."; "Scene.Compose.Attachment.DescriptionVideo" = "Mô tả video cho người khiếm thị..."; -"Scene.Compose.Attachment.LoadFailed" = "Load Failed"; +"Scene.Compose.Attachment.LoadFailed" = "Tải thất bại"; "Scene.Compose.Attachment.Photo" = "ảnh"; -"Scene.Compose.Attachment.UploadFailed" = "Upload Failed"; +"Scene.Compose.Attachment.ServerProcessingState" = "Máy chủ đang xử lý..."; +"Scene.Compose.Attachment.UploadFailed" = "Tải lên thất bại"; "Scene.Compose.Attachment.Video" = "video"; "Scene.Compose.AutoComplete.SpaceToAdd" = "Khoảng cách để thêm"; "Scene.Compose.ComposeAction" = "Đăng"; @@ -192,9 +209,11 @@ tải lên Mastodon."; "Scene.Compose.Poll.OptionNumber" = "Lựa chọn %ld"; "Scene.Compose.Poll.SevenDays" = "7 ngày"; "Scene.Compose.Poll.SixHours" = "6 giờ"; +"Scene.Compose.Poll.ThePollHasEmptyOption" = "Thiếu lựa chọn"; +"Scene.Compose.Poll.ThePollIsInvalid" = "Bình chọn không hợp lệ"; "Scene.Compose.Poll.ThirtyMinutes" = "30 phút"; "Scene.Compose.Poll.ThreeDays" = "3 ngày"; -"Scene.Compose.ReplyingToUser" = "trả lời %@"; +"Scene.Compose.ReplyingToUser" = "%@ viết tiếp"; "Scene.Compose.Title.NewPost" = "Viết tút"; "Scene.Compose.Title.NewReply" = "Viết trả lời"; "Scene.Compose.Visibility.Direct" = "Nhắn riêng"; @@ -223,6 +242,12 @@ tải lên Mastodon."; "Scene.Familiarfollowers.Title" = "Người theo dõi tương tự"; "Scene.Favorite.Title" = "Lượt thích"; "Scene.FavoritedBy.Title" = "Thích bởi"; +"Scene.FollowedTags.Actions.Follow" = "Theo dõi"; +"Scene.FollowedTags.Actions.Unfollow" = "Ngưng theo dõi"; +"Scene.FollowedTags.Header.Participants" = "người thảo luận"; +"Scene.FollowedTags.Header.Posts" = "tút"; +"Scene.FollowedTags.Header.PostsToday" = "tút hôm nay"; +"Scene.FollowedTags.Title" = "Hashtag Theo Dõi"; "Scene.Follower.Footer" = "Không hiển thị người theo dõi từ máy chủ khác."; "Scene.Follower.Title" = "người theo dõi"; "Scene.Following.Footer" = "Không hiển thị người bạn theo dõi từ máy chủ khác."; @@ -234,6 +259,9 @@ tải lên Mastodon."; "Scene.HomeTimeline.NavigationBarState.Published" = "Đã đăng!"; "Scene.HomeTimeline.NavigationBarState.Publishing" = "Đang đăng tút..."; "Scene.HomeTimeline.Title" = "Bảng tin"; +"Scene.Login.ServerSearchField.Placeholder" = "Nhập URL hoặc tìm máy chủ"; +"Scene.Login.Subtitle" = "Đăng nhập vào máy chủ mà bạn đã tạo tài khoản."; +"Scene.Login.Title" = "Chào mừng trở lại!"; "Scene.Notification.FollowRequest.Accept" = "Chấp nhận"; "Scene.Notification.FollowRequest.Accepted" = "Đã chấp nhận"; "Scene.Notification.FollowRequest.Reject" = "từ chối"; @@ -259,8 +287,11 @@ tải lên Mastodon."; "Scene.Profile.Dashboard.Following" = "theo dõi"; "Scene.Profile.Dashboard.Posts" = "tút"; "Scene.Profile.Fields.AddRow" = "Thêm hàng"; +"Scene.Profile.Fields.Joined" = "Đã tham gia"; "Scene.Profile.Fields.Placeholder.Content" = "Nội dung"; "Scene.Profile.Fields.Placeholder.Label" = "Nhãn"; +"Scene.Profile.Fields.Verified.Long" = "Liên kết này đã được xác minh trên %@"; +"Scene.Profile.Fields.Verified.Short" = "Đã xác minh %@"; "Scene.Profile.Header.FollowsYou" = "Đang theo dõi bạn"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Message" = "Xác nhận chặn %@"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title" = "Chặn người này"; @@ -393,13 +424,11 @@ tải lên Mastodon."; "Scene.ServerPicker.EmptyState.BadNetwork" = "Đã xảy ra lỗi. Hãy thử lại hoặc kiểm tra kết nối internet của bạn."; "Scene.ServerPicker.EmptyState.FindingServers" = "Đang tìm máy chủ hoạt động..."; "Scene.ServerPicker.EmptyState.NoResults" = "Không có kết quả"; -"Scene.ServerPicker.Input.Placeholder" = "Tìm máy chủ"; -"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Tìm máy chủ hoặc nhập URL"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "Tìm một máy chủ hoặc nhập URL"; "Scene.ServerPicker.Label.Category" = "PHÂN LOẠI"; "Scene.ServerPicker.Label.Language" = "NGÔN NGỮ"; "Scene.ServerPicker.Label.Users" = "NGƯỜI"; -"Scene.ServerPicker.Subtitle" = "Chọn một máy chủ dựa theo sở thích, tôn giáo, hoặc ý muốn của bạn."; -"Scene.ServerPicker.SubtitleExtend" = "Chọn một máy chủ dựa theo sở thích, tôn giáo, hoặc ý muốn của bạn. Mỗi máy chủ có thể được vận hành bởi một cá nhân hoặc một tổ chức."; +"Scene.ServerPicker.Subtitle" = "Chọn một máy chủ dựa theo sở thích, tôn giáo, hoặc ý muốn của bạn. Bạn vẫn có thể giao tiếp với bất cứ ai mà không phụ thuộc vào máy chủ của họ."; "Scene.ServerPicker.Title" = "Mastodon gồm nhiều máy chủ với thành viên riêng."; "Scene.ServerRules.Button.Confirm" = "Tôi đồng ý"; "Scene.ServerRules.PrivacyPolicy" = "chính sách bảo mật"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/vi.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/vi.lproj/Localizable.stringsdict index 6905b240e..4c772f014 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/vi.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/vi.lproj/Localizable.stringsdict @@ -44,6 +44,20 @@ %ld ký tự + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ còn lại + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld ký tự + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/zh-Hans.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/zh-Hans.lproj/Localizable.strings index 3883e30df..7d164f80e 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/zh-Hans.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/zh-Hans.lproj/Localizable.strings @@ -22,6 +22,9 @@ "Common.Alerts.SignOut.Message" = "您确定要退出吗?"; "Common.Alerts.SignOut.Title" = "退出"; "Common.Alerts.SignUpFailure.Title" = "注册失败"; +"Common.Alerts.TranslationFailed.Button" = "OK"; +"Common.Alerts.TranslationFailed.Message" = "Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported."; +"Common.Alerts.TranslationFailed.Title" = "Note"; "Common.Alerts.VoteFailure.PollEnded" = "投票已结束"; "Common.Alerts.VoteFailure.Title" = "投票失败"; "Common.Controls.Actions.Add" = "添加"; @@ -31,6 +34,7 @@ "Common.Controls.Actions.Compose" = "撰写"; "Common.Controls.Actions.Confirm" = "确认"; "Common.Controls.Actions.Continue" = "继续"; +"Common.Controls.Actions.Copy" = "Copy"; "Common.Controls.Actions.CopyPhoto" = "拷贝照片"; "Common.Controls.Actions.Delete" = "删除"; "Common.Controls.Actions.Discard" = "放弃"; @@ -56,9 +60,11 @@ "Common.Controls.Actions.SharePost" = "分享帖子"; "Common.Controls.Actions.ShareUser" = "分享 %@"; "Common.Controls.Actions.SignIn" = "登录"; -"Common.Controls.Actions.SignUp" = "注册"; +"Common.Controls.Actions.SignUp" = "创建账户"; "Common.Controls.Actions.Skip" = "跳过"; "Common.Controls.Actions.TakePhoto" = "拍照"; +"Common.Controls.Actions.TranslatePost.Title" = "Translate from %@"; +"Common.Controls.Actions.TranslatePost.UnknownLanguage" = "Unknown"; "Common.Controls.Actions.TryAgain" = "再试一次"; "Common.Controls.Actions.UnblockDomain" = "解除屏蔽 %@"; "Common.Controls.Friendship.Block" = "屏蔽"; @@ -68,13 +74,13 @@ "Common.Controls.Friendship.EditInfo" = "编辑"; "Common.Controls.Friendship.Follow" = "关注"; "Common.Controls.Friendship.Following" = "正在关注"; -"Common.Controls.Friendship.HideReblogs" = "Hide Reblogs"; +"Common.Controls.Friendship.HideReblogs" = "隐藏转发"; "Common.Controls.Friendship.Mute" = "静音"; "Common.Controls.Friendship.MuteUser" = "静音 %@"; "Common.Controls.Friendship.Muted" = "已静音"; "Common.Controls.Friendship.Pending" = "待确认"; "Common.Controls.Friendship.Request" = "请求"; -"Common.Controls.Friendship.ShowReblogs" = "Show Reblogs"; +"Common.Controls.Friendship.ShowReblogs" = "显示转发"; "Common.Controls.Friendship.Unblock" = "解除屏蔽"; "Common.Controls.Friendship.UnblockUser" = "解除屏蔽 %@"; "Common.Controls.Friendship.Unmute" = "取消静音"; @@ -100,6 +106,7 @@ "Common.Controls.Status.Actions.Menu" = "菜单"; "Common.Controls.Status.Actions.Reblog" = "转发"; "Common.Controls.Status.Actions.Reply" = "回复"; +"Common.Controls.Status.Actions.ShareLinkInPost" = "Share Link in Post"; "Common.Controls.Status.Actions.ShowGif" = "显示 GIF"; "Common.Controls.Status.Actions.ShowImage" = "显示图片"; "Common.Controls.Status.Actions.ShowVideoPlayer" = "显示视频播放器"; @@ -107,11 +114,13 @@ "Common.Controls.Status.Actions.Unfavorite" = "取消喜欢"; "Common.Controls.Status.Actions.Unreblog" = "取消转发"; "Common.Controls.Status.ContentWarning" = "内容警告"; +"Common.Controls.Status.LinkViaUser" = "%@ via %@"; +"Common.Controls.Status.LoadEmbed" = "Load Embed"; "Common.Controls.Status.MediaContentWarning" = "点击任意位置显示"; -"Common.Controls.Status.MetaEntity.Email" = "Email address: %@"; -"Common.Controls.Status.MetaEntity.Hashtag" = "Hashtag: %@"; -"Common.Controls.Status.MetaEntity.Mention" = "Show Profile: %@"; -"Common.Controls.Status.MetaEntity.Url" = "Link: %@"; +"Common.Controls.Status.MetaEntity.Email" = "邮箱地址:%@"; +"Common.Controls.Status.MetaEntity.Hashtag" = "话题:%@"; +"Common.Controls.Status.MetaEntity.Mention" = "显示用户资料:%@"; +"Common.Controls.Status.MetaEntity.Url" = "链接:%@"; "Common.Controls.Status.Poll.Closed" = "已关闭"; "Common.Controls.Status.Poll.Vote" = "投票"; "Common.Controls.Status.SensitiveContent" = "敏感内容"; @@ -124,6 +133,10 @@ "Common.Controls.Status.Tag.Mention" = "提及"; "Common.Controls.Status.Tag.Url" = "URL"; "Common.Controls.Status.TapToReveal" = "点击以显示"; +"Common.Controls.Status.Translation.ShowOriginal" = "Shown Original"; +"Common.Controls.Status.Translation.TranslatedFrom" = "Translated from %@ using %@"; +"Common.Controls.Status.Translation.UnknownLanguage" = "Unknown"; +"Common.Controls.Status.Translation.UnknownProvider" = "Unknown"; "Common.Controls.Status.UserReblogged" = "%@ 转发"; "Common.Controls.Status.UserRepliedTo" = "回复给 %@"; "Common.Controls.Status.Visibility.Direct" = "只有提到的用户才能看到此帖子。"; @@ -131,9 +144,9 @@ "Common.Controls.Status.Visibility.PrivateFromMe" = "只有我的关注者才能看到此帖子。"; "Common.Controls.Status.Visibility.Unlisted" = "任何人都可以看到这个帖子,但不会在公开的时间线中显示。"; "Common.Controls.Tabs.Home" = "主页"; -"Common.Controls.Tabs.Notification" = "通知"; +"Common.Controls.Tabs.Notifications" = "通知"; "Common.Controls.Tabs.Profile" = "个人资料"; -"Common.Controls.Tabs.Search" = "搜索"; +"Common.Controls.Tabs.SearchAndExplore" = "Search and Explore"; "Common.Controls.Timeline.Filtered" = "已过滤"; "Common.Controls.Timeline.Header.BlockedWarning" = "您不能查看此用户的个人资料 直到他们解除屏蔽。"; @@ -155,23 +168,27 @@ "Scene.AccountList.AddAccount" = "添加账户"; "Scene.AccountList.DismissAccountSwitcher" = "关闭账户切换页面"; "Scene.AccountList.TabBarHint" = "当前账户:%@。 双击并按住来打开账户切换页面"; -"Scene.Bookmark.Title" = "Bookmarks"; +"Scene.Bookmark.Title" = "书签"; "Scene.Compose.Accessibility.AppendAttachment" = "添加附件"; "Scene.Compose.Accessibility.AppendPoll" = "添加投票"; "Scene.Compose.Accessibility.CustomEmojiPicker" = "自定义表情选择器"; "Scene.Compose.Accessibility.DisableContentWarning" = "关闭内容警告"; "Scene.Compose.Accessibility.EnableContentWarning" = "启用内容警告"; +"Scene.Compose.Accessibility.PostOptions" = "帖子选项"; "Scene.Compose.Accessibility.PostVisibilityMenu" = "帖子可见性"; +"Scene.Compose.Accessibility.PostingAs" = "以 %@ 身份发布"; "Scene.Compose.Accessibility.RemovePoll" = "移除投票"; "Scene.Compose.Attachment.AttachmentBroken" = "%@已损坏 无法上传到 Mastodon"; -"Scene.Compose.Attachment.AttachmentTooLarge" = "Attachment too large"; -"Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "Can not regonize this media attachment"; +"Scene.Compose.Attachment.AttachmentTooLarge" = "附件太大"; +"Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "无法识别此媒体"; +"Scene.Compose.Attachment.CompressingState" = "压缩中..."; "Scene.Compose.Attachment.DescriptionPhoto" = "为视觉障碍人士添加照片的文字说明..."; "Scene.Compose.Attachment.DescriptionVideo" = "为视觉障碍人士添加视频的文字说明..."; -"Scene.Compose.Attachment.LoadFailed" = "Load Failed"; +"Scene.Compose.Attachment.LoadFailed" = "加载失败"; "Scene.Compose.Attachment.Photo" = "照片"; -"Scene.Compose.Attachment.UploadFailed" = "Upload Failed"; +"Scene.Compose.Attachment.ServerProcessingState" = "服务器正在处理..."; +"Scene.Compose.Attachment.UploadFailed" = "上传失败"; "Scene.Compose.Attachment.Video" = "视频"; "Scene.Compose.AutoComplete.SpaceToAdd" = "输入空格键入"; "Scene.Compose.ComposeAction" = "发送"; @@ -192,6 +209,8 @@ "Scene.Compose.Poll.OptionNumber" = "选项 %ld"; "Scene.Compose.Poll.SevenDays" = "7 天"; "Scene.Compose.Poll.SixHours" = "6 小时"; +"Scene.Compose.Poll.ThePollHasEmptyOption" = "投票含有空选项"; +"Scene.Compose.Poll.ThePollIsInvalid" = "投票无效"; "Scene.Compose.Poll.ThirtyMinutes" = "30 分钟"; "Scene.Compose.Poll.ThreeDays" = "3 天"; "Scene.Compose.ReplyingToUser" = "回复给 %@"; @@ -223,6 +242,12 @@ "Scene.Familiarfollowers.Title" = "你熟悉的关注者"; "Scene.Favorite.Title" = "你的喜欢"; "Scene.FavoritedBy.Title" = "收藏者"; +"Scene.FollowedTags.Actions.Follow" = "Follow"; +"Scene.FollowedTags.Actions.Unfollow" = "Unfollow"; +"Scene.FollowedTags.Header.Participants" = "participants"; +"Scene.FollowedTags.Header.Posts" = "posts"; +"Scene.FollowedTags.Header.PostsToday" = "posts today"; +"Scene.FollowedTags.Title" = "Followed Tags"; "Scene.Follower.Footer" = "不会显示来自其它服务器的关注者"; "Scene.Follower.Title" = "关注者"; "Scene.Following.Footer" = "不会显示来自其它服务器的关注"; @@ -234,6 +259,9 @@ "Scene.HomeTimeline.NavigationBarState.Published" = "已发送"; "Scene.HomeTimeline.NavigationBarState.Publishing" = "正在发送..."; "Scene.HomeTimeline.Title" = "主页"; +"Scene.Login.ServerSearchField.Placeholder" = "输入网址或搜索您的服务器"; +"Scene.Login.Subtitle" = "登入您账户所在的服务器。"; +"Scene.Login.Title" = "欢迎回来"; "Scene.Notification.FollowRequest.Accept" = "接受"; "Scene.Notification.FollowRequest.Accepted" = "已接受"; "Scene.Notification.FollowRequest.Reject" = "拒绝"; @@ -259,17 +287,20 @@ "Scene.Profile.Dashboard.Following" = "正在关注"; "Scene.Profile.Dashboard.Posts" = "帖子"; "Scene.Profile.Fields.AddRow" = "添加"; +"Scene.Profile.Fields.Joined" = "Joined"; "Scene.Profile.Fields.Placeholder.Content" = "内容"; "Scene.Profile.Fields.Placeholder.Label" = "标签"; +"Scene.Profile.Fields.Verified.Long" = "此链接的所有权已在 %@ 上检查通过"; +"Scene.Profile.Fields.Verified.Short" = "验证于 %@"; "Scene.Profile.Header.FollowsYou" = "关注了你"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Message" = "确认屏蔽 %@"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title" = "屏蔽帐户"; -"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Message" = "Confirm to hide reblogs"; -"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Title" = "Hide Reblogs"; +"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Message" = "确认隐藏转发"; +"Scene.Profile.RelationshipActionAlert.ConfirmHideReblogs.Title" = "隐藏转发"; "Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Message" = "确认静音 %@"; "Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Title" = "静音账户"; -"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Message" = "Confirm to show reblogs"; -"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Title" = "Show Reblogs"; +"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Message" = "确认显示转发"; +"Scene.Profile.RelationshipActionAlert.ConfirmShowReblogs.Title" = "显示转发"; "Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Message" = "确认取消屏蔽 %@"; "Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Title" = "解除屏蔽帐户"; "Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.Message" = "确认取消静音 %@"; @@ -393,13 +424,11 @@ "Scene.ServerPicker.EmptyState.BadNetwork" = "出了些问题。请检查你的互联网连接"; "Scene.ServerPicker.EmptyState.FindingServers" = "正在查找可用的服务器..."; "Scene.ServerPicker.EmptyState.NoResults" = "无结果"; -"Scene.ServerPicker.Input.Placeholder" = "查找或加入你自己的服务器..."; -"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "搜索服务器或输入 URL"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "搜索社区或输入 URL"; "Scene.ServerPicker.Label.Category" = "类别"; "Scene.ServerPicker.Label.Language" = "语言"; "Scene.ServerPicker.Label.Users" = "用户"; -"Scene.ServerPicker.Subtitle" = "根据你的兴趣、区域或一般目的选择一个社区。"; -"Scene.ServerPicker.SubtitleExtend" = "根据你的兴趣、区域或一般目的选择一个社区。每个社区都由完全独立的组织或个人管理。"; +"Scene.ServerPicker.Subtitle" = "根据你的地区、兴趣挑选一个服务器。无论你选择哪个服务器,你都可以跟其他服务器的任何人一起聊天。"; "Scene.ServerPicker.Title" = "挑选一个服务器, 任意服务器。"; "Scene.ServerRules.Button.Confirm" = "我同意"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/zh-Hans.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/zh-Hans.lproj/Localizable.stringsdict index 5a7af3752..362d55c4f 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/zh-Hans.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/zh-Hans.lproj/Localizable.stringsdict @@ -44,6 +44,20 @@ %ld 个字符 + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ 剩余 + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld 个字符 + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/zh-Hant.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/zh-Hant.lproj/Localizable.strings index 34e59a582..eba0703c7 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/zh-Hant.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/zh-Hant.lproj/Localizable.strings @@ -22,6 +22,9 @@ "Common.Alerts.SignOut.Message" = "您確定要登出嗎?"; "Common.Alerts.SignOut.Title" = "登出"; "Common.Alerts.SignUpFailure.Title" = "註冊失敗"; +"Common.Alerts.TranslationFailed.Button" = "OK"; +"Common.Alerts.TranslationFailed.Message" = "翻譯失敗。也許管理員未於此伺服器啟用翻譯功能,或此伺服器為未支援翻譯功能之舊版本 Mastodon。"; +"Common.Alerts.TranslationFailed.Title" = "備註"; "Common.Alerts.VoteFailure.PollEnded" = "投票已結束"; "Common.Alerts.VoteFailure.Title" = "投票失敗"; "Common.Controls.Actions.Add" = "新增"; @@ -31,6 +34,7 @@ "Common.Controls.Actions.Compose" = "撰寫"; "Common.Controls.Actions.Confirm" = "確認"; "Common.Controls.Actions.Continue" = "繼續"; +"Common.Controls.Actions.Copy" = "複製"; "Common.Controls.Actions.CopyPhoto" = "複製照片"; "Common.Controls.Actions.Delete" = "刪除"; "Common.Controls.Actions.Discard" = "捨棄"; @@ -56,9 +60,11 @@ "Common.Controls.Actions.SharePost" = "分享嘟文"; "Common.Controls.Actions.ShareUser" = "分享 %@"; "Common.Controls.Actions.SignIn" = "登入"; -"Common.Controls.Actions.SignUp" = "註冊"; +"Common.Controls.Actions.SignUp" = "新增帳號"; "Common.Controls.Actions.Skip" = "跳過"; "Common.Controls.Actions.TakePhoto" = "拍攝照片"; +"Common.Controls.Actions.TranslatePost.Title" = "翻譯自 %@"; +"Common.Controls.Actions.TranslatePost.UnknownLanguage" = "未知"; "Common.Controls.Actions.TryAgain" = "再試一次"; "Common.Controls.Actions.UnblockDomain" = "解除封鎖 %@"; "Common.Controls.Friendship.Block" = "封鎖"; @@ -100,6 +106,7 @@ "Common.Controls.Status.Actions.Menu" = "選單"; "Common.Controls.Status.Actions.Reblog" = "轉嘟"; "Common.Controls.Status.Actions.Reply" = "回覆"; +"Common.Controls.Status.Actions.ShareLinkInPost" = "於嘟文中分享鏈結"; "Common.Controls.Status.Actions.ShowGif" = "顯示 GIF"; "Common.Controls.Status.Actions.ShowImage" = "顯示圖片"; "Common.Controls.Status.Actions.ShowVideoPlayer" = "顯示影片播放器"; @@ -107,6 +114,8 @@ "Common.Controls.Status.Actions.Unfavorite" = "取消最愛"; "Common.Controls.Status.Actions.Unreblog" = "取消轉嘟"; "Common.Controls.Status.ContentWarning" = "內容警告"; +"Common.Controls.Status.LinkViaUser" = "%@ 透過 %@"; +"Common.Controls.Status.LoadEmbed" = "讀取嵌入內容"; "Common.Controls.Status.MediaContentWarning" = "輕觸任何地方以顯示"; "Common.Controls.Status.MetaEntity.Email" = "電子郵件地址:%@"; "Common.Controls.Status.MetaEntity.Hashtag" = "主題標籤: %@"; @@ -124,6 +133,10 @@ "Common.Controls.Status.Tag.Mention" = "提及"; "Common.Controls.Status.Tag.Url" = "網址"; "Common.Controls.Status.TapToReveal" = "輕觸以顯示"; +"Common.Controls.Status.Translation.ShowOriginal" = "顯示原文"; +"Common.Controls.Status.Translation.TranslatedFrom" = "Translated from %@ using %@"; +"Common.Controls.Status.Translation.UnknownLanguage" = "未知"; +"Common.Controls.Status.Translation.UnknownProvider" = "Unknown"; "Common.Controls.Status.UserReblogged" = "%@ 已轉嘟"; "Common.Controls.Status.UserRepliedTo" = "回覆給 %@"; "Common.Controls.Status.Visibility.Direct" = "只有被提及的使用者能看到此嘟文。"; @@ -131,9 +144,9 @@ "Common.Controls.Status.Visibility.PrivateFromMe" = "只有我的跟隨者能看到此嘟文。"; "Common.Controls.Status.Visibility.Unlisted" = "任何人都能看到此嘟文,但是不會顯示於公開時間軸上。"; "Common.Controls.Tabs.Home" = "首頁"; -"Common.Controls.Tabs.Notification" = "通知"; +"Common.Controls.Tabs.Notifications" = "通知"; "Common.Controls.Tabs.Profile" = "個人檔案"; -"Common.Controls.Tabs.Search" = "搜尋"; +"Common.Controls.Tabs.SearchAndExplore" = "搜尋與探索"; "Common.Controls.Timeline.Filtered" = "已過濾"; "Common.Controls.Timeline.Header.BlockedWarning" = "您無法瀏覽該使用者的個人檔案,除非他們取消封鎖您。"; "Common.Controls.Timeline.Header.BlockingWarning" = "您無法瀏覽該使用者的個人檔案,除非您取消封鎖。 @@ -157,16 +170,20 @@ "Scene.Compose.Accessibility.CustomEmojiPicker" = "自訂 emoji 選擇器"; "Scene.Compose.Accessibility.DisableContentWarning" = "停用內容警告"; "Scene.Compose.Accessibility.EnableContentWarning" = "啟用內容警告"; +"Scene.Compose.Accessibility.PostOptions" = "嘟文選項"; "Scene.Compose.Accessibility.PostVisibilityMenu" = "嘟文可見性選單"; +"Scene.Compose.Accessibility.PostingAs" = "以 %@ 發嘟"; "Scene.Compose.Accessibility.RemovePoll" = "移除投票"; "Scene.Compose.Attachment.AttachmentBroken" = "此 %@ 已損毀,並無法被上傳至 Mastodon。"; -"Scene.Compose.Attachment.AttachmentTooLarge" = "Attachment too large"; -"Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "Can not regonize this media attachment"; +"Scene.Compose.Attachment.AttachmentTooLarge" = "附加檔案大小過大"; +"Scene.Compose.Attachment.CanNotRecognizeThisMediaAttachment" = "無法識別此媒體附加檔案"; +"Scene.Compose.Attachment.CompressingState" = "正在壓縮..."; "Scene.Compose.Attachment.DescriptionPhoto" = "為視障人士提供圖片說明..."; "Scene.Compose.Attachment.DescriptionVideo" = "為視障人士提供影片說明..."; -"Scene.Compose.Attachment.LoadFailed" = "Load Failed"; +"Scene.Compose.Attachment.LoadFailed" = "讀取失敗"; "Scene.Compose.Attachment.Photo" = "照片"; -"Scene.Compose.Attachment.UploadFailed" = "Upload Failed"; +"Scene.Compose.Attachment.ServerProcessingState" = "伺服器處理中..."; +"Scene.Compose.Attachment.UploadFailed" = "上傳失敗"; "Scene.Compose.Attachment.Video" = "影片"; "Scene.Compose.AutoComplete.SpaceToAdd" = "添加的空白"; "Scene.Compose.ComposeAction" = "嘟出去"; @@ -187,6 +204,8 @@ "Scene.Compose.Poll.OptionNumber" = "選項 %ld"; "Scene.Compose.Poll.SevenDays" = "七天"; "Scene.Compose.Poll.SixHours" = "六小時"; +"Scene.Compose.Poll.ThePollHasEmptyOption" = "此投票有空白選項"; +"Scene.Compose.Poll.ThePollIsInvalid" = "此投票是無效的"; "Scene.Compose.Poll.ThirtyMinutes" = "30 分鐘"; "Scene.Compose.Poll.ThreeDays" = "三天"; "Scene.Compose.ReplyingToUser" = "正在回覆 %@"; @@ -218,6 +237,12 @@ "Scene.Familiarfollowers.Title" = "您熟悉的跟隨者"; "Scene.Favorite.Title" = "您的最愛"; "Scene.FavoritedBy.Title" = "已加入最愛"; +"Scene.FollowedTags.Actions.Follow" = "跟隨"; +"Scene.FollowedTags.Actions.Unfollow" = "取消跟隨"; +"Scene.FollowedTags.Header.Participants" = "參與者"; +"Scene.FollowedTags.Header.Posts" = "嘟文"; +"Scene.FollowedTags.Header.PostsToday" = "本日嘟文"; +"Scene.FollowedTags.Title" = "已跟隨主題標籤"; "Scene.Follower.Footer" = "來自其他伺服器的跟隨者不會被顯示。"; "Scene.Follower.Title" = "跟隨者"; "Scene.Following.Footer" = "來自其他伺服器的跟隨中不會被顯示。"; @@ -229,6 +254,9 @@ "Scene.HomeTimeline.NavigationBarState.Published" = "嘟出去!"; "Scene.HomeTimeline.NavigationBarState.Publishing" = "發表嘟文..."; "Scene.HomeTimeline.Title" = "首頁"; +"Scene.Login.ServerSearchField.Placeholder" = "請輸入 URL 或搜尋您的伺服器"; +"Scene.Login.Subtitle" = "登入您新增帳號之伺服器"; +"Scene.Login.Title" = "歡迎回來"; "Scene.Notification.FollowRequest.Accept" = "接受"; "Scene.Notification.FollowRequest.Accepted" = "已接受"; "Scene.Notification.FollowRequest.Reject" = "拒絕"; @@ -254,8 +282,11 @@ "Scene.Profile.Dashboard.Following" = "跟隨中"; "Scene.Profile.Dashboard.Posts" = "嘟文"; "Scene.Profile.Fields.AddRow" = "新增列"; +"Scene.Profile.Fields.Joined" = "加入時間"; "Scene.Profile.Fields.Placeholder.Content" = "內容"; "Scene.Profile.Fields.Placeholder.Label" = "標籤"; +"Scene.Profile.Fields.Verified.Long" = "已在 %@ 檢查此連結的擁有者權限"; +"Scene.Profile.Fields.Verified.Short" = "於 %@ 上已驗證"; "Scene.Profile.Header.FollowsYou" = "跟隨了您"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Message" = "確認將 %@ 封鎖"; "Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title" = "封鎖"; @@ -388,13 +419,11 @@ "Scene.ServerPicker.EmptyState.BadNetwork" = "讀取資料時發生錯誤。請檢查您的網路連線。"; "Scene.ServerPicker.EmptyState.FindingServers" = "尋找可用的伺服器..."; "Scene.ServerPicker.EmptyState.NoResults" = "沒有結果"; -"Scene.ServerPicker.Input.Placeholder" = "搜尋伺服器"; -"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "搜尋伺服器或輸入網址"; +"Scene.ServerPicker.Input.SearchServersOrEnterUrl" = "搜尋社群或輸入 URL 地址"; "Scene.ServerPicker.Label.Category" = "分類"; "Scene.ServerPicker.Label.Language" = "語言"; "Scene.ServerPicker.Label.Users" = "使用者"; -"Scene.ServerPicker.Subtitle" = "基於您的興趣、地區、或一般用途選定一個伺服器。"; -"Scene.ServerPicker.SubtitleExtend" = "基於您的興趣、地區、或一般用途選定一個伺服器。每個伺服器是由完全獨立的組織或個人營運。"; +"Scene.ServerPicker.Subtitle" = "基於您的興趣、地區、或一般用途選定一個伺服器。您仍會與任何伺服器中的每個人連結。"; "Scene.ServerPicker.Title" = "Mastodon 由不同伺服器的使用者組成。"; "Scene.ServerRules.Button.Confirm" = "我已閱讀並同意"; "Scene.ServerRules.PrivacyPolicy" = "隱私權政策"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/zh-Hant.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/zh-Hant.lproj/Localizable.stringsdict index c0ce0f9a2..d545fd6a4 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/zh-Hant.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/zh-Hant.lproj/Localizable.stringsdict @@ -44,6 +44,20 @@ %ld 個字 + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + 剩餘 %#@character_count@ 字 + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld 個字 + + plural.count.followed_by_and_mutual NSStringLocalizedFormatKey diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Account+FollowedTags.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Account+FollowedTags.swift new file mode 100644 index 000000000..02c79e35b --- /dev/null +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Account+FollowedTags.swift @@ -0,0 +1,73 @@ +// +// Mastodon+API+Account+FollowedTags.swift +// +// +// Created by Marcus Kida on 22.11.22. +// + +import Foundation +import Combine + +extension Mastodon.API.Account { + + static func followedTagsEndpointURL(domain: String) -> URL { + return Mastodon.API.endpointURL(domain: domain) + .appendingPathComponent("followed_tags") + } + + /// Followed Tags + /// + /// View your followed hashtags. + /// + /// - Since: 4.0.0 + /// - Version: 4.0.3 + /// # Reference + /// [Document](https://docs.joinmastodon.org/methods/followed_tags/) + /// - Parameters: + /// - session: `URLSession` + /// - domain: Mastodon instance domain. e.g. "example.com" + /// - authorization: User token + /// - Returns: `AnyPublisher` contains `[Tag]` nested in the response + public static func followedTags( + session: URLSession, + domain: String, + query: FollowedTagsQuery, + authorization: Mastodon.API.OAuth.Authorization + ) -> AnyPublisher, Error> { + let request = Mastodon.API.get( + url: followedTagsEndpointURL(domain: domain), + query: query, + authorization: authorization + ) + return session.dataTaskPublisher(for: request) + .tryMap { data, response in + let value = try Mastodon.API.decode(type: [Mastodon.Entity.Tag].self, from: data, response: response) + return Mastodon.Response.Content(value: value, response: response) + } + .eraseToAnyPublisher() + } + + public struct FollowedTagsQuery: Codable, GetQuery { + + public let limit: Int? // default 100 + + enum CodingKeys: String, CodingKey { + case limit + } + + public init( + limit: Int? + ) { + self.limit = limit + } + + var queryItems: [URLQueryItem]? { + var items: [URLQueryItem] = [] + limit.flatMap { items.append(URLQueryItem(name: "limit", value: String($0))) } + guard !items.isEmpty else { return nil } + return items + } + + } + +} diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Account+Friendship.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Account+Friendship.swift index 2c0c39b97..278e6cd27 100644 --- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Account+Friendship.swift +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Account+Friendship.swift @@ -215,6 +215,70 @@ extension Mastodon.API.Account { } +public extension Mastodon.API.Account { + + static func blocksEndpointURL(domain: String) -> URL { + return Mastodon.API.endpointURL(domain: domain).appendingPathComponent("blocks") + } + + /// Block + /// + /// Block the given account. Clients should filter statuses from this account if received (e.g. due to a boost in the Home timeline). + /// + /// - Since: 0.0.0 + /// - Version: 3.3.0 + /// # Last Update + /// 2021/4/1 + /// # Reference + /// [Document](https://docs.joinmastodon.org/methods/blocks/) + /// - Parameters: + /// - session: `URLSession` + /// - domain: Mastodon instance domain. e.g. "example.com" + /// - authorization: User token. + /// - Returns: `AnyPublisher` contains `Relationship` nested in the response + static func blocks( + session: URLSession, + domain: String, + sinceID: Mastodon.Entity.Status.ID?, + limit: Int?, + authorization: Mastodon.API.OAuth.Authorization + ) -> AnyPublisher, Error> { + let request = Mastodon.API.get( + url: blocksEndpointURL(domain: domain), + query: BlocksQuery(sinceID: sinceID, limit: limit), + authorization: authorization + ) + return session.dataTaskPublisher(for: request) + .tryMap { data, response in + let value = try Mastodon.API.decode(type: [Mastodon.Entity.Account].self, from: data, response: response) + return Mastodon.Response.Content(value: value, response: response) + } + .eraseToAnyPublisher() + } + + private struct BlocksQuery: GetQuery { + private let sinceID: Mastodon.Entity.Status.ID? + private let limit: Int? + + public init( + sinceID: Mastodon.Entity.Status.ID?, + limit: Int? + ) { + self.sinceID = sinceID + self.limit = limit + } + + var queryItems: [URLQueryItem]? { + var items: [URLQueryItem] = [] + sinceID.flatMap { items.append(URLQueryItem(name: "since_id", value: $0)) } + limit.flatMap { items.append(URLQueryItem(name: "limit", value: String($0))) } + guard !items.isEmpty else { return nil } + return items + } + } + +} + extension Mastodon.API.Account { static func blockEndpointURL(domain: String, accountID: Mastodon.Entity.Account.ID) -> URL { @@ -414,3 +478,70 @@ extension Mastodon.API.Account { } } + +extension Mastodon.API.Account { + + static func mutesEndpointURL( + domain: String + ) -> URL { + return Mastodon.API.endpointURL(domain: domain) + .appendingPathComponent("mutes") + } + + /// View all mutes + /// + /// View your mutes. See also accounts/:id/{mute,unmute}. + /// + /// - Since: 0.0.0 + /// - Version: 3.3.0 + /// # Last Update + /// 2021/4/1 + /// # Reference + /// [Document](https://docs.joinmastodon.org/methods/accounts/) + /// - Parameters: + /// - session: `URLSession` + /// - domain: Mastodon instance domain. e.g. "example.com" + /// - accountID: id for account + /// - authorization: User token. + /// - Returns: `AnyPublisher` contains `Relationship` nested in the response + public static func mutes( + session: URLSession, + domain: String, + sinceID: Mastodon.Entity.Status.ID? = nil, + limit: Int?, + authorization: Mastodon.API.OAuth.Authorization + ) -> AnyPublisher, Error> { + let request = Mastodon.API.get( + url: mutesEndpointURL(domain: domain), + query: MutesQuery(sinceID: sinceID, limit: limit), + authorization: authorization + ) + return session.dataTaskPublisher(for: request) + .tryMap { data, response in + let value = try Mastodon.API.decode(type: [Mastodon.Entity.Account].self, from: data, response: response) + return Mastodon.Response.Content(value: value, response: response) + } + .eraseToAnyPublisher() + + struct MutesQuery: GetQuery { + private let sinceID: Mastodon.Entity.Status.ID? + private let limit: Int? + + public init( + sinceID: Mastodon.Entity.Status.ID?, + limit: Int? + ) { + self.sinceID = sinceID + self.limit = limit + } + + var queryItems: [URLQueryItem]? { + var items: [URLQueryItem] = [] + sinceID.flatMap { items.append(URLQueryItem(name: "since_id", value: $0)) } + limit.flatMap { items.append(URLQueryItem(name: "limit", value: String($0))) } + guard !items.isEmpty else { return nil } + return items + } + } + } +} diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Statuses+Translate.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Statuses+Translate.swift new file mode 100644 index 000000000..c3e790b67 --- /dev/null +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Statuses+Translate.swift @@ -0,0 +1,48 @@ +// +// Mastodon+API+Statuses+Translate.swift +// +// +// Created by Marcus Kida on 02.12.2022. +// + +import Foundation +import Combine + +extension Mastodon.API.Statuses { + + private static func translateEndpointURL(domain: String, statusID: Mastodon.Entity.Status.ID) -> URL { + return Mastodon.API.endpointURL(domain: domain) + .appendingPathComponent("statuses") + .appendingPathComponent(statusID) + .appendingPathComponent("translate") + } + + /// Translate Status + /// + /// Translate a given Status. + /// + /// - Parameters: + /// - session: `URLSession` + /// - domain: Mastodon instance domain. e.g. "example.com" + /// - statusID: id for status + /// - authorization: User token. Could be nil if status is public + /// - Returns: `AnyPublisher` contains `Status` nested in the response + public static func translate( + session: URLSession, + domain: String, + statusID: Mastodon.Entity.Status.ID, + authorization: Mastodon.API.OAuth.Authorization? + ) -> AnyPublisher, Error> { + let request = Mastodon.API.post( + url: translateEndpointURL(domain: domain, statusID: statusID), + query: nil, + authorization: authorization + ) + return session.dataTaskPublisher(for: request) + .tryMap { data, response in + let value = try Mastodon.API.decode(type: Mastodon.Entity.Translation.self, from: data, response: response) + return Mastodon.Response.Content(value: value, response: response) + } + .eraseToAnyPublisher() + } +} diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Tags.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Tags.swift new file mode 100644 index 000000000..42f8b4fd3 --- /dev/null +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Tags.swift @@ -0,0 +1,117 @@ +// +// Mastodin+API+Tags.swift +// +// +// Created by Marcus Kida on 23.11.22. +// + +import Combine +import Foundation + +extension Mastodon.API.Tags { + static func tagsEndpointURL(domain: String) -> URL { + return Mastodon.API.endpointURL(domain: domain) + .appendingPathComponent("tags") + } + + /// Tags + /// + /// View information about a single tag. + /// + /// - Since: 4.0.0 + /// - Version: 4.0.3 + /// # Reference + /// [Document](https://docs.joinmastodon.org/methods/tags/) + /// - Parameters: + /// - session: `URLSession` + /// - domain: Mastodon instance domain. e.g. "example.com" + /// - authorization: User token + /// - tagId: The Hashtag + /// - Returns: `AnyPublisher` contains `Tag` nested in the response + public static func getTagInformation( + session: URLSession, + domain: String, + tagId: String, + authorization: Mastodon.API.OAuth.Authorization + ) -> AnyPublisher, Error> { + let request = Mastodon.API.get( + url: tagsEndpointURL(domain: domain).appendingPathComponent(tagId), + query: nil, + authorization: authorization + ) + return session.dataTaskPublisher(for: request) + .tryMap { data, response in + let value = try Mastodon.API.decode(type: Mastodon.Entity.Tag.self, from: data, response: response) + return Mastodon.Response.Content(value: value, response: response) + } + .eraseToAnyPublisher() + } + + /// Tags + /// + /// Follow a hashtag. + /// + /// - Since: 4.0.0 + /// - Version: 4.0.3 + /// # Reference + /// [Document](https://docs.joinmastodon.org/methods/tags/) + /// - Parameters: + /// - session: `URLSession` + /// - domain: Mastodon instance domain. e.g. "example.com" + /// - authorization: User token + /// - tagId: The Hashtag + /// - Returns: `AnyPublisher` contains `Tag` nested in the response + public static func followTag( + session: URLSession, + domain: String, + tagId: String, + authorization: Mastodon.API.OAuth.Authorization + ) -> AnyPublisher, Error> { + let request = Mastodon.API.post( + url: tagsEndpointURL(domain: domain).appendingPathComponent(tagId) + .appendingPathComponent("follow"), + query: nil, + authorization: authorization + ) + return session.dataTaskPublisher(for: request) + .tryMap { data, response in + let value = try Mastodon.API.decode(type: Mastodon.Entity.Tag.self, from: data, response: response) + return Mastodon.Response.Content(value: value, response: response) + } + .eraseToAnyPublisher() + } + + /// Tags + /// + /// Unfollow a hashtag. + /// + /// - Since: 4.0.0 + /// - Version: 4.0.3 + /// # Reference + /// [Document](https://docs.joinmastodon.org/methods/tags/) + /// - Parameters: + /// - session: `URLSession` + /// - domain: Mastodon instance domain. e.g. "example.com" + /// - authorization: User token + /// - tagId: The Hashtag + /// - Returns: `AnyPublisher` contains `Tag` nested in the response + public static func unfollowTag( + session: URLSession, + domain: String, + tagId: String, + authorization: Mastodon.API.OAuth.Authorization + ) -> AnyPublisher, Error> { + let request = Mastodon.API.post( + url: tagsEndpointURL(domain: domain).appendingPathComponent(tagId) + .appendingPathComponent("unfollow"), + query: nil, + authorization: authorization + ) + return session.dataTaskPublisher(for: request) + .tryMap { data, response in + let value = try Mastodon.API.decode(type: Mastodon.Entity.Tag.self, from: data, response: response) + return Mastodon.Response.Content(value: value, response: response) + } + .eraseToAnyPublisher() + } +} diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+V2+Instance.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+V2+Instance.swift new file mode 100644 index 000000000..e276fddba --- /dev/null +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+V2+Instance.swift @@ -0,0 +1,50 @@ +import Foundation +import Combine + +extension Mastodon.API.V2.Instance { + + private static func instanceEndpointURL(domain: String) -> URL { + return Mastodon.API.endpointV2URL(domain: domain).appendingPathComponent("instance") + } + + /// Information about the server + /// + /// - Since: 4.0.0 + /// - Version: 4.0.0 + /// # Last Update + /// 2022/12/09 + /// # Reference + /// [Document](https://docs.joinmastodon.org/methods/instance/) + /// - Parameters: + /// - session: `URLSession` + /// - domain: Mastodon instance domain. e.g. "example.com" + /// - Returns: `AnyPublisher` contains `Instance` nested in the response + public static func instance( + session: URLSession, + domain: String + ) -> AnyPublisher, Error> { + let request = Mastodon.API.get( + url: instanceEndpointURL(domain: domain), + query: nil, + authorization: nil + ) + return session.dataTaskPublisher(for: request) + .tryMap { data, response in + let value: Mastodon.Entity.V2.Instance + + do { + value = try Mastodon.API.decode(type: Mastodon.Entity.V2.Instance.self, from: data, response: response) + } catch { + if let response = response as? HTTPURLResponse, 400 ..< 500 ~= response.statusCode { + // For example, AUTHORIZED_FETCH may result in authentication errors + value = Mastodon.Entity.V2.Instance(domain: domain) + } else { + throw error + } + } + return Mastodon.Response.Content(value: value, response: response) + } + .eraseToAnyPublisher() + } + +} diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift index 43d5873d0..f85d50bd0 100644 --- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift @@ -11,7 +11,7 @@ import enum NIOHTTP1.HTTPResponseStatus extension Mastodon.API { - static let timeoutInterval: TimeInterval = 10 + static let timeoutInterval: TimeInterval = 60 static let httpHeaderDateFormatter: ISO8601DateFormatter = { var formatter = ISO8601DateFormatter() @@ -112,6 +112,7 @@ extension Mastodon.API { public enum Polls { } public enum Reblog { } public enum Statuses { } + public enum Tags {} public enum Timeline { } public enum Trends { } public enum Suggestions { } @@ -125,6 +126,7 @@ extension Mastodon.API.V2 { public enum Search { } public enum Suggestions { } public enum Media { } + public enum Instance { } } extension Mastodon.API { diff --git a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+InstanceV2.swift b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+InstanceV2.swift new file mode 100644 index 000000000..d662c24c2 --- /dev/null +++ b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+InstanceV2.swift @@ -0,0 +1,108 @@ +import Foundation + +extension Mastodon.Entity.V2 { + /// Instance + /// + /// - Since: 4.0.0 + /// - Version: 4.0.3 + /// # Last Update + /// 2022/12/09 + /// # Reference + /// [Document](https://docs.joinmastodon.org/entities/instance/) + public struct Instance: Codable { + + public let domain: String? + public let title: String + public let description: String + public let shortDescription: String? + public let email: String? + public let version: String? + public let languages: [String]? // (ISO 639 Part 1-5 language codes) + public let registrations: Mastodon.Entity.V2.Instance.Registrations? + public let approvalRequired: Bool? + public let invitesEnabled: Bool? + public let urls: Mastodon.Entity.Instance.InstanceURL? + public let statistics: Mastodon.Entity.Instance.Statistics? + + public let thumbnail: Thumbnail? + public let contactAccount: Mastodon.Entity.Account? + public let rules: [Mastodon.Entity.Instance.Rule]? + + // https://github.com/mastodon/mastodon/pull/16485 + public let configuration: Configuration? + + public init(domain: String, approvalRequired: Bool? = nil) { + self.domain = domain + self.title = domain + self.description = "" + self.shortDescription = nil + self.email = "" + self.version = nil + self.languages = nil + self.registrations = nil + self.approvalRequired = approvalRequired + self.invitesEnabled = nil + self.urls = nil + self.statistics = nil + self.thumbnail = nil + self.contactAccount = nil + self.rules = nil + self.configuration = nil + } + + enum CodingKeys: String, CodingKey { + case domain + case title + case description + case shortDescription = "short_description" + case email + case version + case languages + case registrations + case approvalRequired = "approval_required" + case invitesEnabled = "invites_enabled" + case urls + case statistics = "stats" + + case thumbnail + case contactAccount = "contact_account" + case rules + + case configuration + } + } +} + +extension Mastodon.Entity.V2.Instance { + public struct Configuration: Codable { + public let statuses: Mastodon.Entity.Instance.Configuration.Statuses? + public let mediaAttachments: Mastodon.Entity.Instance.Configuration.MediaAttachments? + public let polls: Mastodon.Entity.Instance.Configuration.Polls? + public let translation: Mastodon.Entity.V2.Instance.Configuration.Translation? + + enum CodingKeys: String, CodingKey { + case statuses + case mediaAttachments = "media_attachments" + case polls + case translation + } + } +} + +extension Mastodon.Entity.V2.Instance { + public struct Registrations: Codable { + public let enabled: Bool + } +} + +extension Mastodon.Entity.V2.Instance.Configuration { + public struct Translation: Codable { + public let enabled: Bool + } +} + +extension Mastodon.Entity.V2.Instance { + public struct Thumbnail: Codable { + public let url: String? + } +} diff --git a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Server.swift b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Server.swift index 2d1f9953f..4267810b7 100644 --- a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Server.swift +++ b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Server.swift @@ -9,7 +9,7 @@ import Foundation extension Mastodon.Entity { - public struct Server: Codable, Equatable { + public struct Server: Codable, Equatable, Hashable { public let domain: String public let version: String public let description: String diff --git a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Tag.swift b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Tag.swift index 84875359a..d40bf8915 100644 --- a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Tag.swift +++ b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Tag.swift @@ -11,9 +11,9 @@ extension Mastodon.Entity { /// Tag /// /// - Since: 0.9.0 - /// - Version: 3.3.0 + /// - Version: 4.0.0 /// # Last Update - /// 2021/1/28 + /// 2022/11/22 /// # Reference /// [Document](https://docs.joinmastodon.org/entities/tag/) public struct Tag: Hashable, Codable { @@ -23,11 +23,13 @@ extension Mastodon.Entity { public let url: String public let history: [History]? + public let following: Bool? enum CodingKeys: String, CodingKey { case name case url case history + case following } public static func == (lhs: Mastodon.Entity.Tag, rhs: Mastodon.Entity.Tag) -> Bool { @@ -39,5 +41,9 @@ extension Mastodon.Entity { hasher.combine(name) hasher.combine(url) } + + public func copy(following: Bool?) -> Self { + Tag(name: name, url: url, history: history, following: following) + } } } diff --git a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Translation.swift b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Translation.swift new file mode 100644 index 000000000..f500453f1 --- /dev/null +++ b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Translation.swift @@ -0,0 +1,22 @@ +// +// Mastodon+Entity+Translation.swift +// +// +// Created by Marcus Kida on 02.12.22. +// + +import Foundation + +extension Mastodon.Entity { + public struct Translation: Codable { + public let content: String? + public let sourceLanguage: String? + public let provider: String? + + enum CodingKeys: String, CodingKey { + case content + case sourceLanguage = "detected_source_language" + case provider + } + } +} diff --git a/MastodonSDK/Sources/MastodonSDK/Response/Mastodon+Response+Content.swift b/MastodonSDK/Sources/MastodonSDK/Response/Mastodon+Response+Content.swift index 6cf95752b..aa156ac16 100644 --- a/MastodonSDK/Sources/MastodonSDK/Response/Mastodon+Response+Content.swift +++ b/MastodonSDK/Sources/MastodonSDK/Response/Mastodon+Response+Content.swift @@ -106,6 +106,7 @@ extension Mastodon.Response { public struct Link { public let maxID: Mastodon.Entity.Status.ID? public let minID: Mastodon.Entity.Status.ID? + public let linkIDs: [String: Mastodon.Entity.Status.ID] public let offset: Int? init(link: String) { @@ -135,6 +136,33 @@ extension Mastodon.Response { let offset = link[range] return Int(offset) }() + self.linkIDs = { + var linkIDs = [String: Mastodon.Entity.Status.ID]() + let links = link.components(separatedBy: ", ") + for link in links { + guard let regex = try? NSRegularExpression(pattern: "<(.*)>; *rel=\"(.*)\"") else { return [:] } + let results = regex.matches(in: link, options: [], range: NSRange(link.startIndex.. jpeg %.2fMiB", ((#file as NSString).lastPathComponent), #line, #function, Double(imageData.count) / 1024 / 1024, Double(compressedJpegData.count) / 1024 / 1024) + imageData = compressedJpegData + } else { + os_log("%{public}s[%{public}ld], %{public}s: png %.2fMiB", ((#file as NSString).lastPathComponent), #line, #function, Double(imageData.count) / 1024 / 1024) + break + } + } else { + // B. other image + if imageData.count > maxPayloadSizeInBytes { + let targetSize = CGSize(width: image.size.width * 0.8, height: image.size.height * 0.8) + let scaledImage = image.kf.resize(to: targetSize) + guard let compressedJpegData = scaledImage.jpegData(compressionQuality: 0.8) else { + throw AttachmentError.invalidAttachmentType + } + os_log("%{public}s[%{public}ld], %{public}s: compress jpeg %.2fMiB -> jpeg %.2fMiB", ((#file as NSString).lastPathComponent), #line, #function, Double(imageData.count) / 1024 / 1024, Double(compressedJpegData.count) / 1024 / 1024) + imageData = compressedJpegData + } else { + os_log("%{public}s[%{public}ld], %{public}s: jpeg %.2fMiB", ((#file as NSString).lastPathComponent), #line, #function, Double(imageData.count) / 1024 / 1024) + break + } + } + } while (imageData.count > maxPayloadSizeInBytes) + + + return .image(imageData, imageKind: imageData.kf.imageFormat == .PNG ? .png : .jpg) + } +} + +@globalActor actor AttachmentViewModelActor { + static var shared = AttachmentViewModelActor() +} diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Attachment/AttachmentViewModel+Load.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Attachment/AttachmentViewModel+Load.swift index a259485f1..7cfd51eb5 100644 --- a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Attachment/AttachmentViewModel+Load.swift +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Attachment/AttachmentViewModel+Load.swift @@ -16,7 +16,7 @@ extension AttachmentViewModel { func load(input: Input) async throws -> Output { switch input { case .image(let image): - guard let data = image.pngData() else { + guard let data = image.normalized()?.pngData() else { throw AttachmentError.invalidAttachmentType } return .image(data, imageKind: .png) diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Attachment/AttachmentViewModel.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Attachment/AttachmentViewModel.swift index 18da157c5..3ff97769e 100644 --- a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Attachment/AttachmentViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Attachment/AttachmentViewModel.swift @@ -48,8 +48,8 @@ final public class AttachmentViewModel: NSObject, ObservableObject, Identifiable public let api: APIService public let authContext: AuthContext public let input: Input + public let sizeLimit: SizeLimit @Published var caption = "" - // @Published var sizeLimit = SizeLimit() // output @Published public private(set) var output: Output? @@ -77,11 +77,13 @@ final public class AttachmentViewModel: NSObject, ObservableObject, Identifiable api: APIService, authContext: AuthContext, input: Input, + sizeLimit: SizeLimit, delegate: AttachmentViewModelDelegate ) { self.api = api self.authContext = authContext self.input = input + self.sizeLimit = sizeLimit self.delegate = delegate super.init() // end init @@ -131,35 +133,36 @@ final public class AttachmentViewModel: NSObject, ObservableObject, Identifiable .receive(on: DispatchQueue.main) .assign(to: &$thumbnail) - defer { - let uploadTask = Task { @MainActor in - do { - var output = try await load(input: input) - - switch output { - case .video(let fileURL, let mimeType): - self.output = output - self.update(uploadState: .compressing) - let compressedFileURL = try await comporessVideo(url: fileURL) - output = .video(compressedFileURL, mimeType: mimeType) - try? FileManager.default.removeItem(at: fileURL) // remove old file - default: - break - } - - self.outputSizeInByte = output.asAttachment.sizeInByte.flatMap { Int64($0) } ?? 0 + let uploadTask = Task { @MainActor in + do { + var output = try await load(input: input) + + switch output { + case .image(let data, _): self.output = output - - self.update(uploadState: .ready) - self.delegate?.attachmentViewModel(self, uploadStateValueDidChange: self.uploadState) - } catch { - self.error = error + self.update(uploadState: .compressing) + let compressedOutput = try await compressImage(data: data, sizeLimit: sizeLimit) + output = compressedOutput + case .video(let fileURL, let mimeType): + self.output = output + self.update(uploadState: .compressing) + let compressedFileURL = try await compressVideo(url: fileURL) + output = .video(compressedFileURL, mimeType: mimeType) + try? FileManager.default.removeItem(at: fileURL) // remove old file } - } // end Task - self.uploadTask = uploadTask - Task { - await uploadTask.value + + self.outputSizeInByte = output.asAttachment.sizeInByte.flatMap { Int64($0) } ?? 0 + self.output = output + + self.update(uploadState: .ready) + self.delegate?.attachmentViewModel(self, uploadStateValueDidChange: self.uploadState) + } catch { + self.error = error } + } // end Task + self.uploadTask = uploadTask + Task { + await uploadTask.value } } @@ -262,19 +265,15 @@ extension AttachmentViewModel { } } - // not in using public struct SizeLimit { - public let image: Int - public let gif: Int - public let video: Int + public let image: Int? + public let video: Int? public init( - image: Int = 10 * 1024 * 1024, // 10 MiB - gif: Int = 40 * 1024 * 1024, // 40 MiB - video: Int = 40 * 1024 * 1024 // 40 MiB + image: Int?, + video: Int? ) { self.image = image - self.gif = gif self.video = video } } diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/AutoComplete/AutoCompleteViewController.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/AutoComplete/AutoCompleteViewController.swift index 9af1ce9bf..ccb483f35 100644 --- a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/AutoComplete/AutoCompleteViewController.swift +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/AutoComplete/AutoCompleteViewController.swift @@ -80,12 +80,7 @@ extension AutoCompleteViewController { tableView.translatesAutoresizingMaskIntoConstraints = false containerBackgroundView.addSubview(tableView) - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: containerBackgroundView.topAnchor), - tableView.leadingAnchor.constraint(equalTo: containerBackgroundView.leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: containerBackgroundView.trailingAnchor), - tableView.bottomAnchor.constraint(equalTo: containerBackgroundView.bottomAnchor), - ]) + tableView.pinToParent() tableView.delegate = self viewModel.setupDiffableDataSource(tableView: tableView) diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/AutoComplete/View/AutoCompleteTopChevronView.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/AutoComplete/View/AutoCompleteTopChevronView.swift index 6b842c9f1..98690a3ce 100644 --- a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/AutoComplete/View/AutoCompleteTopChevronView.swift +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/AutoComplete/View/AutoCompleteTopChevronView.swift @@ -65,12 +65,7 @@ extension AutoCompleteTopChevronView { shadowView.translatesAutoresizingMaskIntoConstraints = false addSubview(shadowView) - NSLayoutConstraint.activate([ - shadowView.topAnchor.constraint(equalTo: topAnchor), - shadowView.leadingAnchor.constraint(equalTo: leadingAnchor), - shadowView.trailingAnchor.constraint(equalTo: trailingAnchor), - shadowView.bottomAnchor.constraint(equalTo: bottomAnchor), - ]) + shadowView.pinToParent() shadowLayer.fillColor = topViewBackgroundColor.cgColor shadowView.layer.addSublayer(shadowLayer) diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewController.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewController.swift index b84b47385..319804fb1 100644 --- a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewController.swift +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewController.swift @@ -108,12 +108,7 @@ extension ComposeContentViewController { // setup tableView tableView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(tableView) - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: view.topAnchor), - tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + tableView.pinToParent() tableView.delegate = self viewModel.setupDataSource(tableView: tableView) @@ -440,6 +435,7 @@ extension ComposeContentViewController: PHPickerViewControllerDelegate { api: viewModel.context.apiService, authContext: viewModel.authContext, input: .pickerResult(result), + sizeLimit: viewModel.sizeLimit, delegate: viewModel ) } @@ -458,6 +454,7 @@ extension ComposeContentViewController: UIImagePickerControllerDelegate & UINavi api: viewModel.context.apiService, authContext: viewModel.authContext, input: .image(image), + sizeLimit: viewModel.sizeLimit, delegate: viewModel ) viewModel.attachmentViewModels += [attachmentViewModel] @@ -478,6 +475,7 @@ extension ComposeContentViewController: UIDocumentPickerDelegate { api: viewModel.context.apiService, authContext: viewModel.authContext, input: .url(url), + sizeLimit: viewModel.sizeLimit, delegate: viewModel ) viewModel.attachmentViewModels += [attachmentViewModel] @@ -627,10 +625,10 @@ extension ComposeContentViewController: ComposeContentViewModelDelegate { let _replacedText: String? = { var text: String switch item { - case .hashtag(let hashtag): - text = "#" + hashtag.name - case .hashtagV1(let hashtagName): - text = "#" + hashtagName + case .hashtag, .hashtagV1: + // do no fill the hashtag + // allow user delete suffix and post they want + return nil case .account(let account): text = "@" + account.acct case .emoji(let emoji): diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewModel+DataSource.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewModel+DataSource.swift index abbfe0e61..58ceeaa92 100644 --- a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewModel+DataSource.swift +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewModel+DataSource.swift @@ -47,10 +47,7 @@ extension ComposeContentViewModel { } .store(in: &disposeBag) - switch kind { - case .post: - break - case .reply(let status): + if case .reply(let status) = destination { let cell = composeReplyToTableViewCell // bind frame publisher cell.$framePublisher @@ -66,10 +63,6 @@ extension ComposeContentViewModel { guard let replyTo = status.object(in: context.managedObjectContext) else { return } cell.statusView.configure(status: replyTo) } - case .hashtag: - break - case .mention: - break } } } @@ -83,7 +76,7 @@ extension ComposeContentViewModel: UITableViewDataSource { public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { switch Section.allCases[section] { case .replyTo: - switch kind { + switch destination { case .reply: return 1 default: return 0 } diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewModel.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewModel.swift index 06d84566b..066a7ff78 100644 --- a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewModel.swift @@ -32,7 +32,7 @@ public final class ComposeContentViewModel: NSObject, ObservableObject { // input let context: AppContext - let kind: Kind + let destination: Destination weak var delegate: ComposeContentViewModelDelegate? @Published var viewLayoutFrame = ViewLayoutFrame() @@ -59,8 +59,7 @@ public final class ComposeContentViewModel: NSObject, ObservableObject { customEmojiPickerInputViewModel.configure(textInput: textView) } } - // for hashtag: "# " - // for mention: "@ " + // allow dismissing the compose view without confirmation if content == intialContent @Published public var initialContent = "" @Published public var content = "" @Published public var contentWeightedLength = 0 @@ -88,7 +87,7 @@ public final class ComposeContentViewModel: NSObject, ObservableObject { // attachment @Published public var attachmentViewModels: [AttachmentViewModel] = [] @Published public var maxMediaAttachmentLimit = 4 - // @Published public internal(set) var isMediaValid = true + @Published public internal(set) var maxImageMediaSizeLimitInByte = 10 * 1024 * 1024 // 10 MiB // poll @Published public var isPollActive = false @@ -126,15 +125,24 @@ public final class ComposeContentViewModel: NSObject, ObservableObject { @Published var isPollButtonEnabled = false @Published public private(set) var shouldDismiss = true + + // size limit + public var sizeLimit: AttachmentViewModel.SizeLimit { + AttachmentViewModel.SizeLimit( + image: maxImageMediaSizeLimitInByte, + video: nil + ) + } public init( context: AppContext, authContext: AuthContext, - kind: Kind + destination: Destination, + initialContent: String ) { self.context = context self.authContext = authContext - self.kind = kind + self.destination = destination self.visibility = { // default private when user locked var visibility: Mastodon.Entity.Status.Visibility = { @@ -144,8 +152,7 @@ public final class ComposeContentViewModel: NSObject, ObservableObject { return author.locked ? .private : .public }() // set visibility for reply post - switch kind { - case .reply(let record): + if case .reply(let record) = destination { context.managedObjectContext.performAndWait { guard let status = record.object(in: context.managedObjectContext) else { assertionFailure() @@ -165,8 +172,6 @@ public final class ComposeContentViewModel: NSObject, ObservableObject { break } } - default: - break } return visibility }() @@ -177,7 +182,8 @@ public final class ComposeContentViewModel: NSObject, ObservableObject { // end init // setup initial value - switch kind { + let initialContentWithSpace = initialContent.isEmpty ? "" : initialContent + " " + switch destination { case .reply(let record): context.managedObjectContext.performAndWait { guard let status = record.object(in: context.managedObjectContext) else { @@ -206,29 +212,15 @@ public final class ComposeContentViewModel: NSObject, ObservableObject { } let initialComposeContent = mentionAccts.joined(separator: " ") - let preInsertedContent: String? = initialComposeContent.isEmpty ? nil : initialComposeContent + " " - self.initialContent = preInsertedContent ?? "" - self.content = preInsertedContent ?? "" + let preInsertedContent = initialComposeContent.isEmpty ? "" : initialComposeContent + " " + self.initialContent = preInsertedContent + initialContentWithSpace + self.content = preInsertedContent + initialContentWithSpace } - case .hashtag(let hashtag): - let initialComposeContent = "#" + hashtag - UITextChecker.learnWord(initialComposeContent) - let preInsertedContent = initialComposeContent + " " - self.initialContent = preInsertedContent - self.content = preInsertedContent - case .mention(let record): - context.managedObjectContext.performAndWait { - guard let user = record.object(in: context.managedObjectContext) else { return } - let initialComposeContent = "@" + user.acct - UITextChecker.learnWord(initialComposeContent) - let preInsertedContent = initialComposeContent + " " - self.initialContent = preInsertedContent - self.content = preInsertedContent - } - case .post: - break + case .topLevel: + self.initialContent = initialContentWithSpace + self.content = initialContentWithSpace } - + // set limit let _configuration: Mastodon.Entity.Instance.Configuration? = { var configuration: Mastodon.Entity.Instance.Configuration? = nil @@ -252,6 +244,10 @@ public final class ComposeContentViewModel: NSObject, ObservableObject { if let maxOptions = configuration.polls?.maxOptions { maxPollOptionLimit = maxOptions } + // set photo attachment limit + if let imageSizeLimit = configuration.mediaAttachments?.imageSizeLimit { + maxImageMediaSizeLimitInByte = imageSizeLimit + } // TODO: more limit } @@ -359,7 +355,7 @@ extension ComposeContentViewModel { let isMediaUploadAllSuccess = $attachmentViewModels .map { attachmentViewModels in return Publishers.MergeMany(attachmentViewModels.map { $0.$uploadState }) - .delay(for: 0.5, scheduler: DispatchQueue.main) // convert to outputs with delay. Due to @Published emit before changes + .delay(for: 0.3, scheduler: DispatchQueue.main) // convert to outputs with delay. Due to @Published emit before changes .map { _ in attachmentViewModels.map { $0.uploadState } } } .switchToLatest() @@ -367,18 +363,20 @@ extension ComposeContentViewModel { guard outputs.allSatisfy({ $0 == .finish }) else { return false } return true } + .prepend(true) let isPollOptionsAllValid = $pollOptions .map { options in return Publishers.MergeMany(options.map { $0.$text }) - .delay(for: 0.5, scheduler: DispatchQueue.main) // convert to outputs with delay. Due to @Published emit before changes + .delay(for: 0.3, scheduler: DispatchQueue.main) // convert to outputs with delay. Due to @Published emit before changes .map { _ in options.map { $0.text } } } .switchToLatest() .map { outputs in return outputs.allSatisfy { !$0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty } } - + .prepend(true) + let isPublishBarButtonItemEnabledPrecondition1 = Publishers.CombineLatest4( isComposeContentEmpty, isComposeContentValid, @@ -394,17 +392,15 @@ extension ComposeContentViewModel { } .eraseToAnyPublisher() - let isPublishBarButtonItemEnabledPrecondition2 = Publishers.CombineLatest4( - isComposeContentEmpty, - isComposeContentValid, + let isPublishBarButtonItemEnabledPrecondition2 = Publishers.CombineLatest( $isPollActive, isPollOptionsAllValid ) - .map { isComposeContentEmpty, isComposeContentValid, isPollComposing, isPollOptionsAllValid -> Bool in - if isPollComposing { - return isComposeContentValid && !isComposeContentEmpty && isPollOptionsAllValid + .map { isPollActive, isPollOptionsAllValid -> Bool in + if isPollActive { + return isPollOptionsAllValid } else { - return isComposeContentValid && !isComposeContentEmpty + return true } } .eraseToAnyPublisher() @@ -431,11 +427,9 @@ extension ComposeContentViewModel { } extension ComposeContentViewModel { - public enum Kind { - case post - case hashtag(hashtag: String) - case mention(user: ManagedObjectRecord) - case reply(status: ManagedObjectRecord) + public enum Destination { + case topLevel + case reply(parent: ManagedObjectRecord) } public enum ScrollViewState { @@ -498,7 +492,7 @@ extension ComposeContentViewModel { let managedObjectContext = self.context.managedObjectContext var _author: ManagedObjectRecord? managedObjectContext.performAndWait { - _author = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: managedObjectContext)?.user.asRecrod + _author = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: managedObjectContext)?.user.asRecord } guard let author = _author else { throw AppError.badAuthentication @@ -518,10 +512,10 @@ extension ComposeContentViewModel { return MastodonStatusPublisher( author: author, replyTo: { - switch self.kind { - case .reply(let status): return status - default: return nil + if case .reply(let status) = destination { + return status } + return nil }(), isContentWarningComposing: isContentWarningActive, contentWarning: contentWarning, diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/EmojiPicker/CustomEmojiPickerInputView.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/EmojiPicker/CustomEmojiPickerInputView.swift index 2fa582883..237164ae2 100644 --- a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/EmojiPicker/CustomEmojiPickerInputView.swift +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/EmojiPicker/CustomEmojiPickerInputView.swift @@ -44,12 +44,7 @@ extension CustomEmojiPickerInputView { collectionView.translatesAutoresizingMaskIntoConstraints = false addSubview(collectionView) - NSLayoutConstraint.activate([ - collectionView.topAnchor.constraint(equalTo: topAnchor), - collectionView.leadingAnchor.constraint(equalTo: leadingAnchor), - collectionView.trailingAnchor.constraint(equalTo: trailingAnchor), - collectionView.bottomAnchor.constraint(equalTo: bottomAnchor), - ]) + collectionView.pinToParent() activityIndicatorView.hidesWhenStopped = true activityIndicatorView.startAnimating() diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/EmojiPicker/CustomEmojiPickerItemCollectionViewCell.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/EmojiPicker/CustomEmojiPickerItemCollectionViewCell.swift index c92e689bc..49b1a8269 100644 --- a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/EmojiPicker/CustomEmojiPickerItemCollectionViewCell.swift +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/EmojiPicker/CustomEmojiPickerItemCollectionViewCell.swift @@ -46,12 +46,7 @@ extension CustomEmojiPickerItemCollectionViewCell { private func _init() { emojiImageView.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(emojiImageView) - NSLayoutConstraint.activate([ - emojiImageView.topAnchor.constraint(equalTo: contentView.topAnchor), - emojiImageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), - emojiImageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), - emojiImageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), - ]) + emojiImageView.pinToParent() isAccessibilityElement = true accessibilityTraits = .button diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Publisher/MastodonStatusPublisher.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Publisher/MastodonStatusPublisher.swift index 93f3dd3a1..3cecbc675 100644 --- a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Publisher/MastodonStatusPublisher.swift +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Publisher/MastodonStatusPublisher.swift @@ -108,11 +108,6 @@ extension MastodonStatusPublisher: StatusPublisher { // Task: attachment - let uploadContext = AttachmentViewModel.UploadContext( - apiService: api, - authContext: authContext - ) - var attachmentIDs: [Mastodon.Entity.Attachment.ID] = [] for attachmentViewModel in attachmentViewModels { // set progress @@ -161,11 +156,6 @@ extension MastodonStatusPublisher: StatusPublisher { guard pollOptions != nil else { return nil } return self.pollExpireConfigurationOption.seconds }() - let pollMultiple: Bool? = { - guard self.isPollComposing else { return nil } - guard pollOptions != nil else { return nil } - return self.pollMultipleConfigurationOption - }() let inReplyToID: Mastodon.Entity.Status.ID? = try await api.backgroundManagedObjectContext.perform { guard let replyTo = self.replyTo?.object(in: api.backgroundManagedObjectContext) else { return nil } return replyTo.id diff --git a/MastodonSDK/Sources/MastodonUI/SwiftUI/AnimatedImage.swift b/MastodonSDK/Sources/MastodonUI/SwiftUI/AnimatedImage.swift index 9a0f65108..29dd16549 100644 --- a/MastodonSDK/Sources/MastodonUI/SwiftUI/AnimatedImage.swift +++ b/MastodonSDK/Sources/MastodonUI/SwiftUI/AnimatedImage.swift @@ -37,12 +37,7 @@ final public class FLAnimatedImageViewProxy: UIView { imageView.translatesAutoresizingMaskIntoConstraints = false addSubview(imageView) - NSLayoutConstraint.activate([ - imageView.topAnchor.constraint(equalTo: topAnchor), - imageView.leadingAnchor.constraint(equalTo: leadingAnchor), - imageView.trailingAnchor.constraint(equalTo: trailingAnchor), - imageView.bottomAnchor.constraint(equalTo: bottomAnchor), - ]) + imageView.pinToParent() } required init?(coder: NSCoder) { diff --git a/MastodonSDK/Sources/MastodonUI/SwiftUI/MetaTextViewRepresentable.swift b/MastodonSDK/Sources/MastodonUI/SwiftUI/MetaTextViewRepresentable.swift index 8796feb06..9e4d968de 100644 --- a/MastodonSDK/Sources/MastodonUI/SwiftUI/MetaTextViewRepresentable.swift +++ b/MastodonSDK/Sources/MastodonUI/SwiftUI/MetaTextViewRepresentable.swift @@ -50,6 +50,8 @@ public struct MetaTextViewRepresentable: UIViewRepresentable { .foregroundColor: Asset.Colors.brand.color, ] + metaText.paragraphStyle = NSMutableParagraphStyle() + configurationHandler(metaText) metaText.configure(content: PlaintextMetaContent(string: string)) diff --git a/MastodonSDK/Sources/MastodonUI/Vendor/MetaTextView+PasteExtensions.swift b/MastodonSDK/Sources/MastodonUI/Vendor/MetaTextView+PasteExtensions.swift index 8fe1949af..f3fa8e0e4 100644 --- a/MastodonSDK/Sources/MastodonUI/Vendor/MetaTextView+PasteExtensions.swift +++ b/MastodonSDK/Sources/MastodonUI/Vendor/MetaTextView+PasteExtensions.swift @@ -13,6 +13,8 @@ extension MetaTextView { public override func paste(_ sender: Any?) { super.paste(sender) + // fix #660 + // https://github.com/mastodon/mastodon-ios/issues/660 var nextResponder = self.next; // Force the event to bubble through ALL responders diff --git a/MastodonSDK/Sources/MastodonUI/View/Button/AvatarButton.swift b/MastodonSDK/Sources/MastodonUI/View/Button/AvatarButton.swift index 57257fd89..d970ac69c 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Button/AvatarButton.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Button/AvatarButton.swift @@ -32,12 +32,7 @@ open class AvatarButton: UIControl { avatarImageView.frame = bounds avatarImageView.translatesAutoresizingMaskIntoConstraints = false addSubview(avatarImageView) - NSLayoutConstraint.activate([ - avatarImageView.topAnchor.constraint(equalTo: topAnchor), - avatarImageView.leadingAnchor.constraint(equalTo: leadingAnchor), - avatarImageView.trailingAnchor.constraint(equalTo: trailingAnchor), - avatarImageView.bottomAnchor.constraint(equalTo: bottomAnchor), - ]) + avatarImageView.pinToParent() isAccessibilityElement = true accessibilityLabel = L10n.Common.Controls.Status.showUserProfile diff --git a/MastodonSDK/Sources/MastodonUI/View/Button/HUDButton.swift b/MastodonSDK/Sources/MastodonUI/View/Button/HUDButton.swift new file mode 100644 index 000000000..26dda32d6 --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/View/Button/HUDButton.swift @@ -0,0 +1,76 @@ +// +// HUDButton.swift +// Mastodon +// +// Created by Jed Fox on 2022-11-24. +// + +import UIKit + +public class HUDButton: UIView { + + public static let height: CGFloat = 30 + + let background: UIVisualEffectView = { + let backgroundView = UIVisualEffectView(effect: UIBlurEffect(style: .systemMaterial)) + backgroundView.layer.masksToBounds = true + backgroundView.layer.cornerRadius = HUDButton.height * 0.5 + return backgroundView + }() + + let vibrancyView = UIVisualEffectView(effect: UIVibrancyEffect(blurEffect: UIBlurEffect(style: .systemMaterial))) + + public let button: UIButton = { + let button = HighlightDimmableButton() + button.expandEdgeInsets = .constant(-10) + button.contentEdgeInsets = .constant(7) + button.imageView?.tintColor = .label + button.titleLabel?.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .bold)) + return button + }() + + public init(configure: (UIButton) -> Void) { + super.init(frame: .zero) + + configure(button) + _init() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + _init() + } + + func _init() { + translatesAutoresizingMaskIntoConstraints = false + background.translatesAutoresizingMaskIntoConstraints = false + addSubview(background) + background.pinToParent() + vibrancyView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + background.contentView.addSubview(vibrancyView) + + button.translatesAutoresizingMaskIntoConstraints = false + vibrancyView.contentView.addSubview(button) + button.pinToParent() + NSLayoutConstraint.activate([ + heightAnchor.constraint(equalToConstant: HUDButton.height).priority(.defaultHigh), + ]) + } + + public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + button.titleLabel?.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .bold)) + } + + public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + button.point(inside: button.convert(point, from: self), with: event) + } + + public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if self.point(inside: point, with: event) { + return button + } else { + return nil + } + } +} diff --git a/MastodonSDK/Sources/MastodonUI/View/Container/MediaGridContainerView.swift b/MastodonSDK/Sources/MastodonUI/View/Container/MediaGridContainerView.swift index 1b879d975..91eda9a9a 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Container/MediaGridContainerView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Container/MediaGridContainerView.swift @@ -136,12 +136,7 @@ extension MediaGridContainerView { private func layoutContentWarningOverlay() { contentWarningOverlay.translatesAutoresizingMaskIntoConstraints = false addSubview(contentWarningOverlay) - NSLayoutConstraint.activate([ - contentWarningOverlay.topAnchor.constraint(equalTo: topAnchor), - contentWarningOverlay.leadingAnchor.constraint(equalTo: leadingAnchor), - contentWarningOverlay.trailingAnchor.constraint(equalTo: trailingAnchor), - contentWarningOverlay.bottomAnchor.constraint(equalTo: bottomAnchor), - ]) + contentWarningOverlay.pinToParent() } } @@ -208,12 +203,7 @@ extension MediaGridContainerView { let containerVerticalStackView = createStackView(axis: .vertical) containerVerticalStackView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(containerVerticalStackView) - NSLayoutConstraint.activate([ - containerVerticalStackView.topAnchor.constraint(equalTo: view.topAnchor), - containerVerticalStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - containerVerticalStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - containerVerticalStackView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + containerVerticalStackView.pinToParent() let count = mediaViews.count switch count { diff --git a/MastodonSDK/Sources/MastodonUI/View/Container/TouchTransparentStackView.swift b/MastodonSDK/Sources/MastodonUI/View/Container/TouchTransparentStackView.swift new file mode 100644 index 000000000..e519c0cf7 --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/View/Container/TouchTransparentStackView.swift @@ -0,0 +1,26 @@ +// +// TouchTransparentStackView.swift +// +// +// Created by Jed Fox on 2022-12-21. +// + +import UIKit + +/// A subclass of `UIStackView` that allows touches that aren’t captured by any +/// of its subviews to pass through to views beneath this view in the Z-order. +public class TouchTransparentStackView: UIStackView { + // allow subview hit boxes to grow outside of this view’s bounds + public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + subviews.contains { $0.point(inside: $0.convert(point, from: self), with: event) } + } + + // allow taps on blank areas to pass through + public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + let view = super.hitTest(point, with: event) + if view == self { + return nil + } + return view + } +} diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/FamiliarFollowersDashboardView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/FamiliarFollowersDashboardView.swift index c15f8878a..32d9bce96 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/FamiliarFollowersDashboardView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/FamiliarFollowersDashboardView.swift @@ -45,12 +45,7 @@ extension FamiliarFollowersDashboardView { stackView.translatesAutoresizingMaskIntoConstraints = false addSubview(stackView) - NSLayoutConstraint.activate([ - stackView.topAnchor.constraint(equalTo: topAnchor), - stackView.leadingAnchor.constraint(equalTo: leadingAnchor), - stackView.trailingAnchor.constraint(equalTo: trailingAnchor), - stackView.bottomAnchor.constraint(equalTo: bottomAnchor), - ]) + stackView.pinToParent() avatarContainerView.translatesAutoresizingMaskIntoConstraints = false stackView.addArrangedSubview(avatarContainerView) diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/MediaAltTextOverlay.swift b/MastodonSDK/Sources/MastodonUI/View/Content/MediaAltTextOverlay.swift new file mode 100644 index 000000000..a625be292 --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/View/Content/MediaAltTextOverlay.swift @@ -0,0 +1,78 @@ +// +// MediaAltTextOverlay.swift +// +// +// Created by Jed Fox on 2022-12-20. +// + +import SwiftUI + +struct MediaAltTextOverlay: View { + var altDescription: String? + + @State private var showingAlt = false + @Namespace private var namespace + + var body: some View { + GeometryReader { geom in + ZStack { + if let altDescription { + if showingAlt { + HStack(alignment: .top) { + Text(altDescription) + Spacer() + Button(action: { showingAlt = false }) { + Image(systemName: "xmark.circle.fill") + .resizable() + .aspectRatio(contentMode: .fill) + .frame(width: 20, height: 20) + } + } + .padding(8) + .matchedGeometryEffect(id: "background", in: namespace, properties: .position) + .transition( + .scale(scale: 0.2, anchor: .bottomLeading) + .combined(with: .opacity) + ) + } else { + Button("ALT") { showingAlt = true } + .font(.caption.weight(.semibold)) + .padding(.horizontal, 8) + .padding(.vertical, 3) + .matchedGeometryEffect(id: "background", in: namespace, properties: .position) + .transition( + .scale(scale: 3, anchor: .trailing) + .combined(with: .opacity) + ) + } + } + } + .foregroundColor(.white) + .tint(.white) + .background(Color.black.opacity(0.85)) + .cornerRadius(4) + .overlay( + .white.opacity(0.5), + in: RoundedRectangle(cornerRadius: 4) + .inset(by: -0.5) + .stroke(lineWidth: 0.5) + ) + .animation(.spring(response: 0.3), value: showingAlt) + .frame(width: geom.size.width, height: geom.size.height, alignment: .bottomLeading) + } + .padding(.horizontal, 16) + .padding(.vertical, 8) + .onChange(of: altDescription) { _ in + showingAlt = false + } + } +} + +struct MediaAltTextOverlay_Previews: PreviewProvider { + static var previews: some View { + MediaAltTextOverlay(altDescription: "Hello, world!") + .frame(height: 300) + .background(Color.gray) + .previewLayout(.sizeThatFits) + } +} diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/MediaView+Configuration.swift b/MastodonSDK/Sources/MastodonUI/View/Content/MediaView+Configuration.swift index 438baff7e..05c8eee14 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/MediaView+Configuration.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/MediaView+Configuration.swift @@ -21,6 +21,8 @@ extension MediaView { public let info: Info public let blurhash: String? + public let index: Int + public let total: Int @Published public var isReveal = true @Published public var previewImage: UIImage? @@ -29,10 +31,14 @@ extension MediaView { public init( info: MediaView.Configuration.Info, - blurhash: String? + blurhash: String?, + index: Int, + total: Int ) { self.info = info self.blurhash = blurhash + self.index = index + self.total = total } public var aspectRadio: CGSize { @@ -101,19 +107,16 @@ extension MediaView.Configuration { public struct ImageInfo: Hashable { public let aspectRadio: CGSize public let assetURL: String? + public let altDescription: String? public init( aspectRadio: CGSize, - assetURL: String? + assetURL: String?, + altDescription: String? ) { self.aspectRadio = aspectRadio self.assetURL = assetURL - } - - public func hash(into hasher: inout Hasher) { - hasher.combine(aspectRadio.width) - hasher.combine(aspectRadio.height) - assetURL.flatMap { hasher.combine($0) } + self.altDescription = altDescription } } @@ -121,26 +124,21 @@ extension MediaView.Configuration { public let aspectRadio: CGSize public let assetURL: String? public let previewURL: String? + public let altDescription: String? public let durationMS: Int? public init( aspectRadio: CGSize, assetURL: String?, previewURL: String?, + altDescription: String?, durationMS: Int? ) { self.aspectRadio = aspectRadio self.assetURL = assetURL self.previewURL = previewURL self.durationMS = durationMS - } - - public func hash(into hasher: inout Hasher) { - hasher.combine(aspectRadio.width) - hasher.combine(aspectRadio.height) - assetURL.flatMap { hasher.combine($0) } - previewURL.flatMap { hasher.combine($0) } - durationMS.flatMap { hasher.combine($0) } + self.altDescription = altDescription } } @@ -187,41 +185,51 @@ extension MediaView { aspectRadio: attachment.size, assetURL: attachment.assetURL, previewURL: attachment.previewURL, + altDescription: attachment.altDescription, durationMS: attachment.durationMS ) } let status = status.reblog ?? status let attachments = status.attachments - let configurations = attachments.map { attachment -> MediaView.Configuration in + let configurations = attachments.enumerated().map { (idx, attachment) -> MediaView.Configuration in let configuration: MediaView.Configuration = { switch attachment.kind { case .image: let info = MediaView.Configuration.ImageInfo( aspectRadio: attachment.size, - assetURL: attachment.assetURL + assetURL: attachment.assetURL, + altDescription: attachment.altDescription ) return .init( info: .image(info: info), - blurhash: attachment.blurhash + blurhash: attachment.blurhash, + index: idx, + total: attachments.count ) case .video: let info = videoInfo(from: attachment) return .init( info: .video(info: info), - blurhash: attachment.blurhash + blurhash: attachment.blurhash, + index: idx, + total: attachments.count ) case .gifv: let info = videoInfo(from: attachment) return .init( info: .gif(info: info), - blurhash: attachment.blurhash + blurhash: attachment.blurhash, + index: idx, + total: attachments.count ) case .audio: let info = videoInfo(from: attachment) return .init( info: .video(info: info), - blurhash: attachment.blurhash + blurhash: attachment.blurhash, + index: idx, + total: attachments.count ) } // end switch }() diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/MediaView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/MediaView.swift index f4cee0922..d543e64c8 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/MediaView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/MediaView.swift @@ -10,18 +10,15 @@ import AVKit import UIKit import Combine import AlamofireImage +import SwiftUI +import MastodonLocalization +import MastodonAsset public final class MediaView: UIView { var _disposeBag = Set() public static let cornerRadius: CGFloat = 0 - public static let durationFormatter: DateComponentsFormatter = { - let formatter = DateComponentsFormatter() - formatter.zeroFormattingBehavior = .pad - formatter.allowedUnits = [.minute, .second] - return formatter - }() public static let placeholderImage = UIImage.placeholder(color: .systemGray6) public let container = TouchBlockingView() @@ -53,11 +50,20 @@ public final class MediaView: UIView { return playerViewController }() private var playerLooper: AVPlayerLooper? - private(set) lazy var playbackImageView: UIImageView = { + + private(set) lazy var playbackImageView: UIView = { + let wrapper = UIView() + let imageView = UIImageView() + imageView.translatesAutoresizingMaskIntoConstraints = false imageView.image = UIImage(systemName: "play.circle.fill") - imageView.tintColor = .white - return imageView + imageView.tintColor = Asset.Colors.Label.primary.color + wrapper.addSubview(imageView) + imageView.pinToParent(padding: .init(top: 8, left: 8, bottom: 8, right: 8)) + wrapper.backgroundColor = Asset.Theme.Mastodon.systemBackground.color.withAlphaComponent(0.8) + wrapper.applyCornerRadius(radius: 8) + + return wrapper }() private(set) lazy var indicatorBlurEffectView: UIVisualEffectView = { @@ -77,6 +83,12 @@ public final class MediaView: UIView { return label }() + let altViewController: UIHostingController = { + let vc = UIHostingController(rootView: MediaAltTextOverlay()) + vc.view.backgroundColor = .clear + return vc + }() + public override init(frame: CGRect) { super.init(frame: frame) _init() @@ -118,18 +130,18 @@ extension MediaView { case .image(let info): layoutImage() bindImage(configuration: configuration, info: info) - accessibilityLabel = "Show image" // TODO: i18n + accessibilityHint = L10n.Common.Controls.Status.Media.expandImageHint case .gif(let info): layoutGIF() bindGIF(configuration: configuration, info: info) - accessibilityLabel = "Show GIF" // TODO: i18n + accessibilityHint = L10n.Common.Controls.Status.Media.expandGifHint case .video(let info): layoutVideo() bindVideo(configuration: configuration, info: info) - accessibilityLabel = "Show video player" // TODO: i18n + accessibilityHint = L10n.Common.Controls.Status.Media.expandVideoHint } - accessibilityHint = "Tap then hold to show menu" // TODO: i18n + accessibilityTraits.insert([.button, .image]) layoutBlurhash() bindBlurhash(configuration: configuration) @@ -138,12 +150,8 @@ extension MediaView { private func layoutImage() { imageView.translatesAutoresizingMaskIntoConstraints = false container.addSubview(imageView) - NSLayoutConstraint.activate([ - imageView.topAnchor.constraint(equalTo: container.topAnchor), - imageView.leadingAnchor.constraint(equalTo: container.leadingAnchor), - imageView.trailingAnchor.constraint(equalTo: container.trailingAnchor), - imageView.bottomAnchor.constraint(equalTo: container.bottomAnchor), - ]) + imageView.pinToParent() + layoutAlt() } private func bindImage(configuration: Configuration, info: Configuration.ImageInfo) { @@ -162,21 +170,20 @@ extension MediaView { self.imageView.image = image } .store(in: &configuration.disposeBag) + + bindAlt(configuration: configuration, altDescription: info.altDescription) } - + private func layoutGIF() { // use view controller as View here playerViewController.view.translatesAutoresizingMaskIntoConstraints = false container.addSubview(playerViewController.view) - NSLayoutConstraint.activate([ - playerViewController.view.topAnchor.constraint(equalTo: container.topAnchor), - playerViewController.view.leadingAnchor.constraint(equalTo: container.leadingAnchor), - playerViewController.view.trailingAnchor.constraint(equalTo: container.trailingAnchor), - playerViewController.view.bottomAnchor.constraint(equalTo: container.bottomAnchor), - ]) + playerViewController.view.pinToParent() setupIndicatorViewHierarchy() playerIndicatorLabel.attributedText = NSAttributedString(string: "GIF") + + layoutAlt() } private func bindGIF(configuration: Configuration, info: Configuration.VideoInfo) { @@ -187,6 +194,8 @@ extension MediaView { // auto play for GIF player.play() + + bindAlt(configuration: configuration, altDescription: info.altDescription) } private func layoutVideo() { @@ -205,20 +214,30 @@ extension MediaView { private func bindVideo(configuration: Configuration, info: Configuration.VideoInfo) { let imageInfo = Configuration.ImageInfo( aspectRadio: info.aspectRadio, - assetURL: info.previewURL + assetURL: info.previewURL, + altDescription: info.altDescription ) bindImage(configuration: configuration, info: imageInfo) } + private func bindAlt(configuration: Configuration, altDescription: String?) { + if configuration.total > 1 { + accessibilityLabel = L10n.Common.Controls.Status.Media.accessibilityLabel( + altDescription ?? "", + configuration.index + 1, + configuration.total + ) + } else { + accessibilityLabel = altDescription + } + + altViewController.rootView.altDescription = altDescription + } + private func layoutBlurhash() { blurhashImageView.translatesAutoresizingMaskIntoConstraints = false container.addSubview(blurhashImageView) - NSLayoutConstraint.activate([ - blurhashImageView.topAnchor.constraint(equalTo: container.topAnchor), - blurhashImageView.leadingAnchor.constraint(equalTo: container.leadingAnchor), - blurhashImageView.trailingAnchor.constraint(equalTo: container.trailingAnchor), - blurhashImageView.bottomAnchor.constraint(equalTo: container.bottomAnchor), - ]) + blurhashImageView.pinToParent() } private func bindBlurhash(configuration: Configuration) { @@ -243,6 +262,12 @@ extension MediaView { .store(in: &_disposeBag) } + private func layoutAlt() { + altViewController.view.translatesAutoresizingMaskIntoConstraints = false + container.addSubview(altViewController.view) + altViewController.view.pinToParent() + } + public func prepareForReuse() { _disposeBag.removeAll() @@ -278,6 +303,8 @@ extension MediaView { container.removeFromSuperview() container.removeConstraints(container.constraints) + altViewController.rootView.altDescription = nil + // reset configuration configuration = nil } @@ -304,12 +331,7 @@ extension MediaView { guard container.superview == nil else { return } container.translatesAutoresizingMaskIntoConstraints = false addSubview(container) - NSLayoutConstraint.activate([ - container.topAnchor.constraint(equalTo: topAnchor), - container.leadingAnchor.constraint(equalTo: leadingAnchor), - container.trailingAnchor.constraint(equalTo: trailingAnchor), - container.bottomAnchor.constraint(equalTo: bottomAnchor), - ]) + container.pinToParent() } private func setupIndicatorViewHierarchy() { @@ -329,12 +351,7 @@ extension MediaView { if vibrancyEffectView.superview == nil { vibrancyEffectView.translatesAutoresizingMaskIntoConstraints = false blurEffectView.contentView.addSubview(vibrancyEffectView) - NSLayoutConstraint.activate([ - vibrancyEffectView.topAnchor.constraint(equalTo: blurEffectView.contentView.topAnchor), - vibrancyEffectView.leadingAnchor.constraint(equalTo: blurEffectView.contentView.leadingAnchor), - vibrancyEffectView.trailingAnchor.constraint(equalTo: blurEffectView.contentView.trailingAnchor), - vibrancyEffectView.bottomAnchor.constraint(equalTo: blurEffectView.contentView.bottomAnchor), - ]) + vibrancyEffectView.pinToParent() } if playerIndicatorLabel.superview == nil { diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/NewsView+Configuration.swift b/MastodonSDK/Sources/MastodonUI/View/Content/NewsView+Configuration.swift index 7f44232aa..8403be756 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/NewsView+Configuration.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/NewsView+Configuration.swift @@ -39,9 +39,12 @@ extension NewsView { let configuration = MediaView.Configuration( info: .image(info: .init( aspectRadio: CGSize(width: link.width, height: link.height), - assetURL: link.image + assetURL: link.image, + altDescription: nil )), - blurhash: link.blurhash + blurhash: link.blurhash, + index: 1, + total: 1 ) imageView.setup(configuration: configuration) diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/NewsView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/NewsView.swift index 0d4298035..304bcebc4 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/NewsView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/NewsView.swift @@ -79,12 +79,7 @@ extension NewsView { container.spacing = 8 container.translatesAutoresizingMaskIntoConstraints = false addSubview(container) - NSLayoutConstraint.activate([ - container.topAnchor.constraint(equalTo: topAnchor), - container.leadingAnchor.constraint(equalTo: leadingAnchor), - container.trailingAnchor.constraint(equalTo: trailingAnchor), - container.bottomAnchor.constraint(equalTo: bottomAnchor), - ]) + container.pinToParent() // textContainer: V - [ providerContainer | headlineLabel | (spacer) | footnoteLabel ] let textContainer = UIStackView() diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView+ViewModel.swift b/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView+ViewModel.swift index d7ba51e17..ed038f47f 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView+ViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView+ViewModel.swift @@ -23,9 +23,11 @@ extension NotificationView { public var objects = Set() let logger = Logger(subsystem: "NotificationView", category: "ViewModel") - + + @Published public var context: AppContext? @Published public var authContext: AuthContext? - + + @Published public var type: MastodonNotificationType? @Published public var notificationIndicatorText: MetaContent? @Published public var authorAvatarImage: UIImage? @@ -36,6 +38,7 @@ extension NotificationView { @Published public var isMyself = false @Published public var isMuting = false @Published public var isBlocking = false + @Published public var isTranslated = false @Published public var timestamp: Date? @@ -54,7 +57,10 @@ extension NotificationView.ViewModel { bindAuthor(notificationView: notificationView) bindAuthorMenu(notificationView: notificationView) bindFollowRequest(notificationView: notificationView) - + + $context + .assign(to: \.context, on: notificationView.statusView.viewModel) + .store(in: &disposeBag) $authContext .assign(to: \.authContext, on: notificationView.statusView.viewModel) .store(in: &disposeBag) @@ -100,21 +106,21 @@ extension NotificationView.ViewModel { } .store(in: &disposeBag) // timestamp - Publishers.CombineLatest( + let formattedTimestamp = Publishers.CombineLatest( $timestamp, timestampUpdatePublisher.prepend(Date()).eraseToAnyPublisher() ) - .sink { [weak self] timestamp, _ in - guard let self = self else { return } - guard let timestamp = timestamp else { - notificationView.dateLabel.configure(content: PlaintextMetaContent(string: "")) - return - } - - let text = timestamp.localizedTimeAgoSinceNow - notificationView.dateLabel.configure(content: PlaintextMetaContent(string: text)) + .map { timestamp, _ in + timestamp?.localizedTimeAgoSinceNow ?? "" } - .store(in: &disposeBag) + .removeDuplicates() + + formattedTimestamp + .sink { timestamp in + notificationView.dateLabel.configure(content: PlaintextMetaContent(string: timestamp)) + } + .store(in: &disposeBag) + // notification type indicator $notificationIndicatorText .sink { text in @@ -125,6 +131,76 @@ extension NotificationView.ViewModel { } } .store(in: &disposeBag) + + Publishers.CombineLatest4( + $authorName, + $authorUsername, + $notificationIndicatorText, + formattedTimestamp + ) + .sink { name, username, type, timestamp in + notificationView.accessibilityLabel = [ + "\(name?.string ?? "") \(type?.string ?? "")", + username.map { "@\($0)" } ?? "", + timestamp + ].joined(separator: ", ") + if !notificationView.statusView.isHidden { + notificationView.accessibilityLabel! += ", " + (notificationView.statusView.accessibilityLabel ?? "") + } + if !notificationView.quoteStatusViewContainerView.isHidden { + notificationView.accessibilityLabel! += ", " + (notificationView.quoteStatusView.accessibilityLabel ?? "") + } + } + .store(in: &disposeBag) + + Publishers.CombineLatest( + $authorAvatarImage, + $type + ) + .sink { avatarImage, type in + var actions = [UIAccessibilityCustomAction]() + + // these notifications can be directly actioned to view the profile + if type != .follow, type != .followRequest { + actions.append( + UIAccessibilityCustomAction( + name: L10n.Common.Controls.Status.showUserProfile, + image: avatarImage + ) { [weak notificationView] _ in + guard let notificationView = notificationView, let delegate = notificationView.delegate else { return false } + delegate.notificationView(notificationView, authorAvatarButtonDidPressed: notificationView.avatarButton) + return true + } + ) + } + + if type == .followRequest { + actions.append( + UIAccessibilityCustomAction( + name: L10n.Common.Controls.Actions.confirm, + image: Asset.Editing.checkmark20.image + ) { [weak notificationView] _ in + guard let notificationView = notificationView, let delegate = notificationView.delegate else { return false } + delegate.notificationView(notificationView, acceptFollowRequestButtonDidPressed: notificationView.acceptFollowRequestButton) + return true + } + ) + + actions.append( + UIAccessibilityCustomAction( + name: L10n.Common.Controls.Actions.delete, + image: Asset.Circles.forbidden20.image + ) { [weak notificationView] _ in + guard let notificationView = notificationView, let delegate = notificationView.delegate else { return false } + delegate.notificationView(notificationView, rejectFollowRequestButtonDidPressed: notificationView.rejectFollowRequestButton) + return true + } + ) + } + + notificationView.notificationActions = actions + } + .store(in: &disposeBag) } private func bindAuthorMenu(notificationView: NotificationView) { @@ -132,22 +208,48 @@ extension NotificationView.ViewModel { $authorName, $isMuting, $isBlocking, - $isMyself + Publishers.CombineLatest( + $isMyself, + $isTranslated + ) ) - .sink { authorName, isMuting, isBlocking, isMyself in + .sink { [weak self] authorName, isMuting, isBlocking, isMyselfIsTranslated in guard let name = authorName?.string else { notificationView.menuButton.menu = nil return } + let (isMyself, isTranslated) = isMyselfIsTranslated + + lazy var instanceConfigurationV2: Mastodon.Entity.V2.Instance.Configuration? = { + guard + let self = self, + let context = self.context, + let authContext = self.authContext + else { return nil } + + var configuration: Mastodon.Entity.V2.Instance.Configuration? = nil + context.managedObjectContext.performAndWait { + guard let authentication = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext) + else { return } + configuration = authentication.instance?.configurationV2 + } + return configuration + }() + let menuContext = NotificationView.AuthorMenuContext( name: name, isMuting: isMuting, isBlocking: isBlocking, isMyself: isMyself, - isBookmarking: false // no bookmark action display for notification item + isBookmarking: false, // no bookmark action display for notification item + isTranslationEnabled: instanceConfigurationV2?.translation?.enabled == true, + isTranslated: isTranslated, + statusLanguage: "" ) - notificationView.menuButton.menu = notificationView.setupAuthorMenu(menuContext: menuContext) + let (menu, actions) = notificationView.setupAuthorMenu(menuContext: menuContext) + notificationView.menuButton.menu = menu + notificationView.authorActions = actions notificationView.menuButton.showsMenuAsPrimaryAction = true notificationView.menuButton.isHidden = menuContext.isMyself @@ -207,5 +309,5 @@ extension NotificationView.ViewModel { } .store(in: &disposeBag) } - + } diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView.swift index e52422770..9196c340e 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView.swift @@ -46,7 +46,10 @@ public final class NotificationView: UIView { var _disposeBag = Set() public var disposeBag = Set() - + + var notificationActions = [UIAccessibilityCustomAction]() + var authorActions = [UIAccessibilityCustomAction]() + public private(set) lazy var viewModel: ViewModel = { let viewModel = ViewModel() viewModel.bind(notificationView: self) @@ -292,21 +295,11 @@ extension NotificationView { acceptFollowRequestButton.translatesAutoresizingMaskIntoConstraints = false acceptFollowRequestButtonShadowBackgroundContainer.addSubview(acceptFollowRequestButton) - NSLayoutConstraint.activate([ - acceptFollowRequestButton.topAnchor.constraint(equalTo: acceptFollowRequestButtonShadowBackgroundContainer.topAnchor), - acceptFollowRequestButton.leadingAnchor.constraint(equalTo: acceptFollowRequestButtonShadowBackgroundContainer.leadingAnchor), - acceptFollowRequestButton.trailingAnchor.constraint(equalTo: acceptFollowRequestButtonShadowBackgroundContainer.trailingAnchor), - acceptFollowRequestButton.bottomAnchor.constraint(equalTo: acceptFollowRequestButtonShadowBackgroundContainer.bottomAnchor), - ]) + acceptFollowRequestButton.pinToParent() rejectFollowRequestButton.translatesAutoresizingMaskIntoConstraints = false rejectFollowRequestButtonShadowBackgroundContainer.addSubview(rejectFollowRequestButton) - NSLayoutConstraint.activate([ - rejectFollowRequestButton.topAnchor.constraint(equalTo: rejectFollowRequestButtonShadowBackgroundContainer.topAnchor), - rejectFollowRequestButton.leadingAnchor.constraint(equalTo: rejectFollowRequestButtonShadowBackgroundContainer.leadingAnchor), - rejectFollowRequestButton.trailingAnchor.constraint(equalTo: rejectFollowRequestButtonShadowBackgroundContainer.trailingAnchor), - rejectFollowRequestButton.bottomAnchor.constraint(equalTo: rejectFollowRequestButtonShadowBackgroundContainer.bottomAnchor), - ]) + rejectFollowRequestButton.pinToParent() followRequestContainerView.axis = .horizontal followRequestContainerView.distribution = .fillEqually @@ -382,6 +375,30 @@ extension NotificationView { statusView.delegate = self quoteStatusView.delegate = self + + isAccessibilityElement = true + } +} + +extension NotificationView { + public override var accessibilityElements: [Any]? { + get { [] } + set {} + } + + public override var accessibilityCustomActions: [UIAccessibilityCustomAction]? { + get { + var actions = notificationActions + actions += authorActions + if !statusView.isHidden { + actions += statusView.accessibilityCustomActions ?? [] + } + if !quoteStatusViewContainerView.isHidden { + actions += quoteStatusView.accessibilityCustomActions ?? [] + } + return actions + } + set {} } } @@ -440,7 +457,7 @@ extension NotificationView: AdaptiveContainerView { extension NotificationView { public typealias AuthorMenuContext = StatusAuthorView.AuthorMenuContext - public func setupAuthorMenu(menuContext: AuthorMenuContext) -> UIMenu { + public func setupAuthorMenu(menuContext: AuthorMenuContext) -> (UIMenu, [UIAccessibilityCustomAction]) { var actions: [MastodonMenu.Action] = [] actions = [ @@ -466,15 +483,23 @@ extension NotificationView { actions: actions, delegate: self ) - - return menu + + let accessibilityActions = MastodonMenu.setupAccessibilityActions( + actions: actions, + delegate: self + ) + + return (menu, accessibilityActions) } } // MARK: - StatusViewDelegate extension NotificationView: StatusViewDelegate { - + public func statusView(_ statusView: StatusView, didTapCardWithURL url: URL) { + assertionFailure() + } + public func statusView(_ statusView: StatusView, headerDidPressed header: UIView) { // do nothing } @@ -577,6 +602,15 @@ extension NotificationView: StatusViewDelegate { assertionFailure() } + public func statusView(_ statusView: StatusView, cardControl: StatusCardControl, didTapURL url: URL) { + assertionFailure() + } + + public func statusView(_ statusView: StatusView, cardControlMenu: StatusCardControl) -> UIMenu? { + assertionFailure() + return nil + } + } // MARK: - MastodonMenuDelegate diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/PollOptionView+ViewModel.swift b/MastodonSDK/Sources/MastodonUI/View/Content/PollOptionView+ViewModel.swift index a91f57dc2..e3e34d1cb 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/PollOptionView+ViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/PollOptionView+ViewModel.swift @@ -175,7 +175,7 @@ extension PollOptionView.ViewModel { view.voteProgressStripView.setProgress(0.0, animated: false) case .reveal(let voted, let percentage, let animating): view.optionPercentageLabel.isHidden = false - view.optionPercentageLabel.text = String(Int(100 * percentage)) + "%" + view.optionPercentageLabel.text = String(Int(round(100 * percentage))) + "%" view.voteProgressStripView.isHidden = false view.voteProgressStripView.tintColor = voted ? self.primaryStripProgressViewTintColor : self.secondaryStripProgressViewTintColor view.voteProgressStripView.setProgress(CGFloat(percentage), animated: animating) diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/PollOptionView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/PollOptionView.swift index 04a032563..514657e62 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/PollOptionView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/PollOptionView.swift @@ -110,12 +110,7 @@ extension PollOptionView { voteProgressStripView.translatesAutoresizingMaskIntoConstraints = false roundedBackgroundView.addSubview(voteProgressStripView) - NSLayoutConstraint.activate([ - voteProgressStripView.topAnchor.constraint(equalTo: roundedBackgroundView.topAnchor), - voteProgressStripView.leadingAnchor.constraint(equalTo: roundedBackgroundView.leadingAnchor), - voteProgressStripView.trailingAnchor.constraint(equalTo: roundedBackgroundView.trailingAnchor), - voteProgressStripView.bottomAnchor.constraint(equalTo: roundedBackgroundView.bottomAnchor), - ]) + voteProgressStripView.pinToParent() checkmarkBackgroundView.translatesAutoresizingMaskIntoConstraints = false roundedBackgroundView.addSubview(checkmarkBackgroundView) @@ -138,12 +133,7 @@ extension PollOptionView { plusCircleImageView.translatesAutoresizingMaskIntoConstraints = false addSubview(plusCircleImageView) - NSLayoutConstraint.activate([ - plusCircleImageView.topAnchor.constraint(equalTo: checkmarkBackgroundView.topAnchor), - plusCircleImageView.leadingAnchor.constraint(equalTo: checkmarkBackgroundView.leadingAnchor), - plusCircleImageView.trailingAnchor.constraint(equalTo: checkmarkBackgroundView.trailingAnchor), - plusCircleImageView.bottomAnchor.constraint(equalTo: checkmarkBackgroundView.bottomAnchor), - ]) + plusCircleImageView.pinTo(to: checkmarkBackgroundView) optionTextField.translatesAutoresizingMaskIntoConstraints = false roundedBackgroundView.addSubview(optionTextField) diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/ProfileCardView+ViewModel.swift b/MastodonSDK/Sources/MastodonUI/View/Content/ProfileCardView+ViewModel.swift index 55d270f74..beb7680a8 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/ProfileCardView+ViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/ProfileCardView+ViewModel.swift @@ -43,7 +43,7 @@ extension ProfileCardView { @Published public var isMuting = false @Published public var isBlocking = false @Published public var isBlockedBy = false - + @Published public var groupedAccessibilityLabel = "" @Published public var familiarFollowers: Mastodon.Entity.FamiliarFollowers? @@ -173,6 +173,19 @@ extension ProfileCardView.ViewModel { } private func bindDashboard(view: ProfileCardView) { + relationshipViewModel.$isMyself + .sink { isMyself in + if isMyself { + view.statusDashboardView.postDashboardMeterView.textLabel.text = L10n.Scene.Profile.Dashboard.myPosts + view.statusDashboardView.followingDashboardMeterView.textLabel.text = L10n.Scene.Profile.Dashboard.myFollowing + view.statusDashboardView.followersDashboardMeterView.textLabel.text = L10n.Scene.Profile.Dashboard.myFollowers + } else { + view.statusDashboardView.postDashboardMeterView.textLabel.text = L10n.Scene.Profile.Dashboard.otherPosts + view.statusDashboardView.followingDashboardMeterView.textLabel.text = L10n.Scene.Profile.Dashboard.otherFollowing + view.statusDashboardView.followersDashboardMeterView.textLabel.text = L10n.Scene.Profile.Dashboard.otherFollowers + } + } + .store(in: &disposeBag) $statusesCount .receive(on: DispatchQueue.main) .sink { count in diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/ProfileCardView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/ProfileCardView.swift index b93d44b73..cb7d8f839 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/ProfileCardView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/ProfileCardView.swift @@ -137,12 +137,7 @@ extension ProfileCardView { container.spacing = 8 container.translatesAutoresizingMaskIntoConstraints = false addSubview(container) - NSLayoutConstraint.activate([ - container.topAnchor.constraint(equalTo: topAnchor), - container.leadingAnchor.constraint(equalTo: leadingAnchor), - container.trailingAnchor.constraint(equalTo: trailingAnchor), - container.bottomAnchor.constraint(equalTo: bottomAnchor), - ]) + container.pinToParent() // bannerContainer let bannerContainer = UIView() @@ -237,11 +232,8 @@ extension ProfileCardView { relationshipActionButton.translatesAutoresizingMaskIntoConstraints = false relationshipActionButtonShadowContainer.addSubview(relationshipActionButton) + relationshipActionButton.pinToParent() NSLayoutConstraint.activate([ - relationshipActionButton.topAnchor.constraint(equalTo: relationshipActionButtonShadowContainer.topAnchor), - relationshipActionButton.leadingAnchor.constraint(equalTo: relationshipActionButtonShadowContainer.leadingAnchor), - relationshipActionButton.trailingAnchor.constraint(equalTo: relationshipActionButtonShadowContainer.trailingAnchor), - relationshipActionButton.bottomAnchor.constraint(equalTo: relationshipActionButtonShadowContainer.bottomAnchor), relationshipActionButtonShadowContainer.widthAnchor.constraint(greaterThanOrEqualToConstant: ProfileCardView.friendshipActionButtonSize.width).priority(.required - 1), relationshipActionButtonShadowContainer.heightAnchor.constraint(equalToConstant: ProfileCardView.friendshipActionButtonSize.height).priority(.required - 1), ]) diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/StatusAuthorView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/StatusAuthorView.swift index 0631875c0..ef40ab7fc 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/StatusAuthorView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/StatusAuthorView.swift @@ -149,12 +149,22 @@ extension StatusAuthorView { public let isBlocking: Bool public let isMyself: Bool public let isBookmarking: Bool + + public let isTranslationEnabled: Bool + public let isTranslated: Bool + public let statusLanguage: String? } public func setupAuthorMenu(menuContext: AuthorMenuContext) -> (UIMenu, [UIAccessibilityCustomAction]) { var actions = [MastodonMenu.Action]() if !menuContext.isMyself { + if let statusLanguage = menuContext.statusLanguage, menuContext.isTranslationEnabled, !menuContext.isTranslated { + actions.append( + .translateStatus(.init(language: statusLanguage)) + ) + } + actions.append(contentsOf: [ .muteUser(.init( name: menuContext.name, diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/StatusCardControl.swift b/MastodonSDK/Sources/MastodonUI/View/Content/StatusCardControl.swift new file mode 100644 index 000000000..530ceea20 --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/View/Content/StatusCardControl.swift @@ -0,0 +1,329 @@ +// +// OpenGraphView.swift +// +// +// Created by Kyle Bashour on 11/11/22. +// + +import AlamofireImage +import Combine +import MastodonAsset +import MastodonCore +import MastodonLocalization +import CoreDataStack +import UIKit +import WebKit + +public protocol StatusCardControlDelegate: AnyObject { + func statusCardControl(_ statusCardControl: StatusCardControl, didTapURL url: URL) + func statusCardControlMenu(_ statusCardControl: StatusCardControl) -> UIMenu? +} + +public final class StatusCardControl: UIControl { + public weak var delegate: StatusCardControlDelegate? + + private var disposeBag = Set() + + private let containerStackView = UIStackView() + private let labelStackView = UIStackView() + + private let highlightView = UIView() + private let dividerView = UIView() + private let imageView = UIImageView() + private let titleLabel = UILabel() + private let linkLabel = UILabel() + private lazy var showEmbedButton: UIButton = { + var configuration = UIButton.Configuration.gray() + configuration.background.visualEffect = UIBlurEffect(style: .systemUltraThinMaterial) + configuration.baseBackgroundColor = .clear + configuration.cornerStyle = .capsule + configuration.buttonSize = .large + configuration.title = L10n.Common.Controls.Status.loadEmbed + configuration.image = UIImage(systemName: "play.fill") + configuration.imagePadding = 12 + return UIButton(configuration: configuration, primaryAction: UIAction { [weak self] _ in + self?.showWebView() + }) + }() + private var html = "" + + private static let cardContentPool = WKProcessPool() + private var webView: WKWebView? + + private var layout: Layout? + private var layoutConstraints: [NSLayoutConstraint] = [] + private var dividerConstraint: NSLayoutConstraint? + + public override var isHighlighted: Bool { + didSet { + // override UIKit behavior of highlighting subviews when cell is highlighted + if isHighlighted, + let cell = sequence(first: self, next: \.superview).first(where: { $0 is UITableViewCell }) as? UITableViewCell { + highlightView.isHidden = cell.isHighlighted + } else { + highlightView.isHidden = !isHighlighted + } + } + } + + public override init(frame: CGRect) { + super.init(frame: frame) + + apply(theme: ThemeService.shared.currentTheme.value) + + ThemeService.shared.currentTheme.sink { [weak self] theme in + self?.apply(theme: theme) + }.store(in: &disposeBag) + + clipsToBounds = true + layer.cornerCurve = .continuous + layer.cornerRadius = 10 + + maximumContentSizeCategory = .accessibilityLarge + highlightView.backgroundColor = UIColor.label.withAlphaComponent(0.1) + highlightView.isHidden = true + + titleLabel.numberOfLines = 2 + titleLabel.textColor = Asset.Colors.Label.primary.color + titleLabel.font = .preferredFont(forTextStyle: .body) + + linkLabel.numberOfLines = 1 + linkLabel.textColor = Asset.Colors.Label.secondary.color + linkLabel.font = .preferredFont(forTextStyle: .subheadline) + + imageView.tintColor = Asset.Colors.Label.secondary.color + imageView.contentMode = .scaleAspectFill + imageView.clipsToBounds = true + imageView.setContentHuggingPriority(.zero, for: .horizontal) + imageView.setContentHuggingPriority(.zero, for: .vertical) + imageView.setContentCompressionResistancePriority(.zero, for: .horizontal) + imageView.setContentCompressionResistancePriority(.zero, for: .vertical) + + labelStackView.addArrangedSubview(titleLabel) + labelStackView.addArrangedSubview(linkLabel) + labelStackView.layoutMargins = .init(top: 10, left: 10, bottom: 10, right: 10) + labelStackView.isLayoutMarginsRelativeArrangement = true + labelStackView.axis = .vertical + labelStackView.spacing = 2 + + containerStackView.addArrangedSubview(imageView) + containerStackView.addArrangedSubview(dividerView) + containerStackView.addArrangedSubview(labelStackView) + containerStackView.isUserInteractionEnabled = false + containerStackView.distribution = .fill + + addSubview(containerStackView) + addSubview(highlightView) + addSubview(showEmbedButton) + + containerStackView.translatesAutoresizingMaskIntoConstraints = false + highlightView.translatesAutoresizingMaskIntoConstraints = false + showEmbedButton.translatesAutoresizingMaskIntoConstraints = false + dividerView.translatesAutoresizingMaskIntoConstraints = false + + containerStackView.pinToParent() + highlightView.pinToParent() + NSLayoutConstraint.activate([ + showEmbedButton.centerXAnchor.constraint(equalTo: imageView.centerXAnchor), + showEmbedButton.centerYAnchor.constraint(equalTo: imageView.centerYAnchor), + ]) + + addInteraction(UIContextMenuInteraction(delegate: self)) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public func configure(card: Card) { + if let host = card.url?.host { + accessibilityLabel = "\(card.title) \(host)" + } else { + accessibilityLabel = card.title + } + + titleLabel.text = card.title + linkLabel.text = card.url?.host + imageView.contentMode = .center + + imageView.sd_setImage( + with: card.imageURL, + placeholderImage: icon(for: card.layout) + ) { [weak self] image, _, _, _ in + if image != nil { + self?.imageView.contentMode = .scaleAspectFill + } + + self?.containerStackView.setNeedsLayout() + self?.containerStackView.layoutIfNeeded() + } + + if let html = card.html, !html.isEmpty { + showEmbedButton.isHidden = false + self.html = html + } else { + webView?.removeFromSuperview() + webView = nil + showEmbedButton.isHidden = true + self.html = "" + } + + updateConstraints(for: card.layout) + } + + public override func didMoveToWindow() { + super.didMoveToWindow() + + if let window = window { + layer.borderWidth = window.screen.pixelSize + dividerConstraint?.constant = window.screen.pixelSize + } + } + + private func updateConstraints(for layout: Layout) { + guard layout != self.layout else { return } + self.layout = layout + + NSLayoutConstraint.deactivate(layoutConstraints) + dividerConstraint?.deactivate() + + let pixelSize = (window?.screen.pixelSize ?? 1) + switch layout { + case .large(let aspectRatio): + containerStackView.alignment = .fill + containerStackView.axis = .vertical + layoutConstraints = [ + imageView.widthAnchor.constraint( + equalTo: imageView.heightAnchor, + multiplier: aspectRatio + ) + // This priority is important or constraints break; + // it still renders the card correctly. + .priority(.defaultLow - 1), + // set a reasonable max height for very tall images + imageView.heightAnchor + .constraint(lessThanOrEqualToConstant: 400), + ] + dividerConstraint = dividerView.heightAnchor.constraint(equalToConstant: pixelSize).activate() + case .compact: + containerStackView.alignment = .center + containerStackView.axis = .horizontal + layoutConstraints = [ + imageView.heightAnchor.constraint(equalTo: heightAnchor), + imageView.widthAnchor.constraint(equalToConstant: 85), + heightAnchor.constraint(equalToConstant: 85).priority(.defaultLow - 1), + heightAnchor.constraint(greaterThanOrEqualToConstant: 85), + dividerView.heightAnchor.constraint(equalTo: containerStackView.heightAnchor), + ] + dividerConstraint = dividerView.widthAnchor.constraint(equalToConstant: pixelSize).activate() + } + + NSLayoutConstraint.activate(layoutConstraints) + } + + private func icon(for layout: Layout) -> UIImage? { + switch layout { + case .compact: + return UIImage(systemName: "newspaper.fill") + case .large: + let configuration = UIImage.SymbolConfiguration(pointSize: 32) + return UIImage(systemName: "photo", withConfiguration: configuration) + } + } + + private func apply(theme: Theme) { + layer.borderColor = theme.separator.cgColor + dividerView.backgroundColor = theme.separator + imageView.backgroundColor = UIColor.tertiarySystemFill + } +} + +// MARK: WKWebView delegates +extension StatusCardControl: WKNavigationDelegate, WKUIDelegate { + fileprivate func showWebView() { + let webView = setupWebView() + webView.loadHTMLString("" + html, baseURL: nil) + if webView.superview == nil { + addSubview(webView) + webView.pinTo(to: imageView) + } + } + + private func setupWebView() -> WKWebView { + if let webView { return webView } + + let config = WKWebViewConfiguration() + config.processPool = Self.cardContentPool + config.websiteDataStore = .nonPersistent() // private/incognito mode + config.suppressesIncrementalRendering = true + config.allowsInlineMediaPlayback = true + let webView = WKWebView(frame: .zero, configuration: config) + webView.uiDelegate = self + webView.navigationDelegate = self + webView.translatesAutoresizingMaskIntoConstraints = false + webView.isOpaque = false + webView.backgroundColor = .clear + self.webView = webView + return webView + } + + public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction) async -> WKNavigationActionPolicy { + let isTopLevelNavigation: Bool + if let frame = navigationAction.targetFrame { + isTopLevelNavigation = frame.isMainFrame + } else { + isTopLevelNavigation = true + } + + if isTopLevelNavigation, + // ignore form submits and such + navigationAction.navigationType == .linkActivated || navigationAction.navigationType == .other, + let url = navigationAction.request.url, + url.absoluteString != "about:blank" { + delegate?.statusCardControl(self, didTapURL: url) + return .cancel + } + return .allow + } + + public func webViewDidClose(_ webView: WKWebView) { + webView.removeFromSuperview() + self.webView = nil + } +} + +// MARK: UIContextMenuInteractionDelegate +extension StatusCardControl { + public override func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? { + return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { elements in + self.delegate?.statusCardControlMenu(self) + } + } + + public override func contextMenuInteraction(_ interaction: UIContextMenuInteraction, previewForDismissingMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? { + UITargetedPreview(view: self) + } +} + +private extension StatusCardControl { + enum Layout: Equatable { + case compact + case large(aspectRatio: CGFloat) + } +} + +private extension Card { + var layout: StatusCardControl.Layout { + var aspectRatio = CGFloat(width) / CGFloat(height) + if !aspectRatio.isFinite { + aspectRatio = 1 + } + return (abs(aspectRatio - 1) < 0.05 || image == nil) && html == nil + ? .compact + : .large(aspectRatio: aspectRatio) + } +} + +private extension UILayoutPriority { + static let zero = UILayoutPriority(rawValue: 0) +} diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+Configuration.swift b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+Configuration.swift index 353dfc097..6e950bc95 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+Configuration.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+Configuration.swift @@ -53,8 +53,20 @@ extension StatusView { configureContent(status: status) configureMedia(status: status) configurePoll(status: status) + configureCard(status: status) configureToolbar(status: status) configureFilter(status: status) + viewModel.originalStatus = status + [ + status.publisher(for: \.translatedContent), + status.reblog?.publisher(for: \.translatedContent) + ].compactMap { $0 } + .last? + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.configureTranslated(status: status) + } + .store(in: &disposeBag) } } @@ -130,6 +142,7 @@ extension StatusView { authorization: authenticationBox.userAuthorization ).singleOutput() } + .receive(on: DispatchQueue.main) .sink { completion in // do nothing } receiveValue: { [weak self] response in @@ -230,7 +243,50 @@ extension StatusView { .store(in: &disposeBag) } + func revertTranslation() { + guard let originalStatus = viewModel.originalStatus else { return } + viewModel.translatedFromLanguage = nil + viewModel.translatedUsingProvider = nil + originalStatus.reblog?.update(translatedContent: nil) + originalStatus.update(translatedContent: nil) + configure(status: originalStatus) + } + + func configureTranslated(status: Status) { + let translatedContent: Status.TranslatedContent? = { + if let translatedContent = status.reblog?.translatedContent { + return translatedContent + } + return status.translatedContent + + }() + + guard + let translatedContent = translatedContent + else { + viewModel.isCurrentlyTranslating = false + return + } + + // content + do { + let content = MastodonContent(content: translatedContent.content, emojis: status.emojis.asDictionary) + let metaContent = try MastodonMetaContent.convert(document: content) + viewModel.content = metaContent + viewModel.translatedFromLanguage = status.reblog?.language ?? status.language + viewModel.translatedUsingProvider = status.reblog?.translatedContent?.provider ?? status.translatedContent?.provider + viewModel.isCurrentlyTranslating = false + } catch { + assertionFailure(error.localizedDescription) + viewModel.content = PlaintextMetaContent(string: "") + } + } + private func configureContent(status: Status) { + guard status.translatedContent == nil else { + return configureTranslated(status: status) + } + let status = status.reblog ?? status // spoilerText @@ -253,6 +309,8 @@ extension StatusView { let content = MastodonContent(content: status.content, emojis: status.emojis.asDictionary) let metaContent = try MastodonMetaContent.convert(document: content) viewModel.content = metaContent + viewModel.translatedFromLanguage = nil + viewModel.isCurrentlyTranslating = false } catch { assertionFailure(error.localizedDescription) viewModel.content = PlaintextMetaContent(string: "") @@ -348,6 +406,17 @@ extension StatusView { .assign(to: \.isVoting, on: viewModel) .store(in: &disposeBag) } + + private func configureCard(status: Status) { + let status = status.reblog ?? status + if viewModel.mediaViewConfigurations.isEmpty { + status.publisher(for: \.card) + .assign(to: \.card, on: viewModel) + .store(in: &disposeBag) + } else { + viewModel.card = nil + } + } private func configureToolbar(status: Status) { let status = status.reblog ?? status diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift index 38d0b551e..aa9915f6d 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift @@ -17,6 +17,7 @@ import MastodonCommon import MastodonExtension import MastodonLocalization import MastodonSDK +import MastodonMeta extension StatusView { public final class ViewModel: ObservableObject { @@ -26,8 +27,10 @@ extension StatusView { let logger = Logger(subsystem: "StatusView", category: "ViewModel") + public var context: AppContext? public var authContext: AuthContext? - + public var originalStatus: Status? + // Header @Published public var header: Header = .none @@ -43,6 +46,11 @@ extension StatusView { @Published public var isMuting = false @Published public var isBlocking = false + // Translation + @Published public var isCurrentlyTranslating = false + @Published public var translatedFromLanguage: String? + @Published public var translatedUsingProvider: String? + @Published public var timestamp: Date? public var timestampFormatter: ((_ date: Date) -> String)? @Published public var timestampText = "" @@ -69,7 +77,10 @@ extension StatusView { @Published public var voteCount = 0 @Published public var expireAt: Date? @Published public var expired: Bool = false - + + // Card + @Published public var card: Card? + // Visibility @Published public var visibility: MastodonVisibility = .public @@ -134,6 +145,9 @@ extension StatusView { isContentSensitive = false isMediaSensitive = false isSensitiveToggled = false + translatedFromLanguage = nil + translatedUsingProvider = nil + isCurrentlyTranslating = false activeFilters = [] filterContext = nil @@ -146,14 +160,12 @@ extension StatusView { $isMyself ) .map { visibility, isMyself in - if isMyself { - return true - } - switch visibility { - case .public, .unlisted: + case .public, .unlisted, ._other: return true - case .private, .direct, ._other: + case .private where isMyself: + return true + case .private, .direct: return false } } @@ -185,6 +197,7 @@ extension StatusView.ViewModel { bindContent(statusView: statusView) bindMedia(statusView: statusView) bindPoll(statusView: statusView) + bindCard(statusView: statusView) bindToolbar(statusView: statusView) bindMetric(statusView: statusView) bindMenu(statusView: statusView) @@ -203,6 +216,7 @@ extension StatusView.ViewModel { statusView.headerInfoLabel.configure(content: info.header) statusView.setHeaderDisplay() case .reply(let info): + assert(Thread.isMainThread) statusView.headerIconImageView.image = UIImage(systemName: "arrowshape.turn.up.left.fill") statusView.headerInfoLabel.configure(content: info.header) statusView.setHeaderDisplay() @@ -238,12 +252,11 @@ extension StatusView.ViewModel { } .store(in: &disposeBag) // username - let usernamePublisher = $authorUsername + $authorUsername .map { text -> String in guard let text = text else { return "" } return "@\(text)" } - usernamePublisher .sink { username in let metaContent = PlaintextMetaContent(string: username) authorView.authorUsernameLabel.configure(content: metaContent) @@ -270,18 +283,6 @@ extension StatusView.ViewModel { authorView.dateLabel.configure(content: PlaintextMetaContent(string: text)) } .store(in: &disposeBag) - - // accessibility label - Publishers.CombineLatest4($authorName, usernamePublisher, $timestampText, $timestamp) - .map { name, username, timestampText, timestamp in - let formatter = DateFormatter() - formatter.dateStyle = .medium - formatter.timeStyle = .short - let longTimestamp = timestamp.map { formatter.string(from: $0) } ?? "" - return "\(name?.string ?? "") \(username), \(timestampText). \(longTimestamp)" - } - .assign(to: \.accessibilityLabel, on: authorView) - .store(in: &disposeBag) } private func bindContent(statusView: StatusView) { @@ -311,18 +312,22 @@ extension StatusView.ViewModel { } statusView.contentMetaText.paragraphStyle = paragraphStyle - if let content = content { + if let content = content, !(content.string.isEmpty && content.entities.isEmpty) { statusView.contentMetaText.configure( content: content ) statusView.contentMetaText.textView.accessibilityTraits = [.staticText] statusView.contentMetaText.textView.accessibilityElementsHidden = false + statusView.contentMetaText.textView.isHidden = false + } else { statusView.contentMetaText.reset() statusView.contentMetaText.textView.accessibilityLabel = "" + statusView.contentMetaText.textView.isHidden = true } statusView.contentMetaText.textView.alpha = isContentReveal ? 1 : 0 // keep the frame size and only display when revealing + statusView.statusCardControl.alpha = isContentReveal ? 1 : 0 statusView.setSpoilerOverlayViewHidden(isHidden: isContentReveal) @@ -416,12 +421,7 @@ extension StatusView.ViewModel { var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([.main]) snapshot.appendItems(items, toSection: .main) - if #available(iOS 15.0, *) { - statusView.pollTableViewDiffableDataSource?.applySnapshotUsingReloadData(snapshot) - } else { - // Fallback on earlier versions - statusView.pollTableViewDiffableDataSource?.apply(snapshot, animatingDifferences: false) - } + statusView.pollTableViewDiffableDataSource?.applySnapshotUsingReloadData(snapshot) statusView.pollTableViewHeightLayoutConstraint.constant = CGFloat(items.count) * PollOptionTableViewCell.height statusView.setPollDisplay() @@ -468,7 +468,7 @@ extension StatusView.ViewModel { pollCountdownDescription ) .sink { pollVoteDescription, pollCountdownDescription in - statusView.pollVoteCountLabel.text = pollVoteDescription ?? "-" + statusView.pollVoteCountLabel.text = pollVoteDescription statusView.pollCountdownLabel.text = pollCountdownDescription ?? "-" } .store(in: &disposeBag) @@ -492,6 +492,15 @@ extension StatusView.ViewModel { .assign(to: \.isEnabled, on: statusView.pollVoteButton) .store(in: &disposeBag) } + + private func bindCard(statusView: StatusView) { + $card.sink { card in + guard let card = card else { return } + statusView.statusCardControl.configure(card: card) + statusView.setStatusCardControlDisplay() + } + .store(in: &disposeBag) + } private func bindToolbar(statusView: StatusView) { $replyCount @@ -593,26 +602,52 @@ extension StatusView.ViewModel { $isBlocking, $isBookmark ) + let publishersThree = Publishers.CombineLatest( + $translatedFromLanguage, + $language + ) - Publishers.CombineLatest( + Publishers.CombineLatest3( publisherOne.eraseToAnyPublisher(), - publishersTwo.eraseToAnyPublisher() + publishersTwo.eraseToAnyPublisher(), + publishersThree.eraseToAnyPublisher() ).eraseToAnyPublisher() - .sink { tupleOne, tupleTwo in + .sink { tupleOne, tupleTwo, tupleThree in let (authorName, isMyself) = tupleOne let (isMuting, isBlocking, isBookmark) = tupleTwo - + let (translatedFromLanguage, language) = tupleThree + guard let name = authorName?.string else { statusView.authorView.menuButton.menu = nil return } + lazy var instanceConfigurationV2: Mastodon.Entity.V2.Instance.Configuration? = { + guard + let context = self.context, + let authContext = self.authContext + else { + return nil + } + + var configuration: Mastodon.Entity.V2.Instance.Configuration? = nil + context.managedObjectContext.performAndWait { + guard let authentication = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext) + else { return } + configuration = authentication.instance?.configurationV2 + } + return configuration + }() + let menuContext = StatusAuthorView.AuthorMenuContext( name: name, isMuting: isMuting, isBlocking: isBlocking, isMyself: isMyself, - isBookmarking: isBookmark + isBookmarking: isBookmark, + isTranslationEnabled: instanceConfigurationV2?.translation?.enabled == true, + isTranslated: translatedFromLanguage != nil, + statusLanguage: language ) let (menu, actions) = authorView.setupAuthorMenu(menuContext: menuContext) authorView.menuButton.menu = menu @@ -634,7 +669,7 @@ extension StatusView.ViewModel { } private func bindAccessibility(statusView: StatusView) { - let authorAccessibilityLabel = Publishers.CombineLatest3( + let shortAuthorAccessibilityLabel = Publishers.CombineLatest3( $header, $authorName, $timestampText @@ -644,19 +679,56 @@ extension StatusView.ViewModel { switch header { case .none: - break + strings.append(authorName?.string) case .reply(let info): + strings.append(authorName?.string) strings.append(info.header.string) case .repost(let info): strings.append(info.header.string) + strings.append(authorName?.string) } - strings.append(authorName?.string) strings.append(timestamp) return strings.compactMap { $0 }.joined(separator: ", ") } - + + let longTimestampFormatter = DateFormatter() + longTimestampFormatter.dateStyle = .medium + longTimestampFormatter.timeStyle = .short + let longTimestampLabel = Publishers.CombineLatest( + $timestampText, + $timestamp.map { timestamp in + if let timestamp { + return longTimestampFormatter.string(from: timestamp) + } + return "" + } + ) + .map { timestampText, longTimestamp in + "\(timestampText). \(longTimestamp)" + } + + Publishers.CombineLatest4( + $header, + $authorName, + $authorUsername, + longTimestampLabel + ) + .map { header, name, username, timestamp in + let nameAndUsername = "\(name?.string ?? "") @\(username ?? "")" + switch header { + case .none: + return "\(nameAndUsername), \(timestamp)" + case .repost(info: let info): + return "\(info.header.string) \(nameAndUsername), \(timestamp)" + case .reply(info: let info): + return "\(nameAndUsername) \(info.header.string), \(timestamp)" + } + } + .assign(to: \.accessibilityLabel, on: statusView.authorView) + .store(in: &disposeBag) + let contentAccessibilityLabel = Publishers.CombineLatest3( $isContentReveal, $spoilerContent, @@ -694,28 +766,69 @@ extension StatusView.ViewModel { statusView.spoilerOverlayView.accessibilityLabel = contentAccessibilityLabel } .store(in: &disposeBag) - - let meidaAccessibilityLabel = $mediaViewConfigurations + + let mediaAccessibilityLabel = $mediaViewConfigurations .map { configurations -> String? in let count = configurations.count return L10n.Plural.Count.media(count) } - // TODO: Toolbar + let replyLabel = $replyCount + .map { [L10n.Common.Controls.Actions.reply, L10n.Plural.Count.reply($0)] } + .map { $0.joined(separator: ", ") } + + let reblogLabel = Publishers.CombineLatest($isReblog, $reblogCount) + .map { isReblog, reblogCount in + [ + isReblog ? L10n.Common.Controls.Status.Actions.unreblog : L10n.Common.Controls.Status.Actions.reblog, + L10n.Plural.Count.reblog(reblogCount) + ] + } + .map { $0.joined(separator: ", ") } + + let favoriteLabel = Publishers.CombineLatest($isFavorite, $favoriteCount) + .map { isFavorite, favoriteCount in + [ + isFavorite ? L10n.Common.Controls.Status.Actions.unfavorite : L10n.Common.Controls.Status.Actions.favorite, + L10n.Plural.Count.favorite(favoriteCount) + ] + } + .map { $0.joined(separator: ", ") } + + Publishers.CombineLatest4(replyLabel, reblogLabel, $isReblogEnabled, favoriteLabel) + .map { replyLabel, reblogLabel, canReblog, favoriteLabel in + let toolbar = statusView.actionToolbarContainer + let replyAction = UIAccessibilityCustomAction(name: replyLabel) { _ in + statusView.actionToolbarContainer(toolbar, buttonDidPressed: toolbar.replyButton, action: .reply) + return true + } + let reblogAction = UIAccessibilityCustomAction(name: reblogLabel) { _ in + statusView.actionToolbarContainer(toolbar, buttonDidPressed: toolbar.reblogButton, action: .reblog) + return true + } + let favoriteAction = UIAccessibilityCustomAction(name: favoriteLabel) { _ in + statusView.actionToolbarContainer(toolbar, buttonDidPressed: toolbar.favoriteButton, action: .like) + return true + } + // (share, bookmark are excluded since they are already present in the “…” menu action set) + return canReblog ? [replyAction, reblogAction, favoriteAction] : [replyAction, favoriteAction] + } + .assign(to: \.toolbarActions, on: statusView) + .store(in: &disposeBag) Publishers.CombineLatest3( - authorAccessibilityLabel, + shortAuthorAccessibilityLabel, contentAccessibilityLabel, - meidaAccessibilityLabel + mediaAccessibilityLabel ) .map { author, content, media in - let group = [ - author, - content, - media - ] - - return group + var labels: [String?] = [content, media] + + if statusView.style != .notification { + labels.insert(author, at: 0) + } + + return labels .compactMap { $0 } .joined(separator: ", ") } diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView.swift index 9b42b9e4d..acc52fdea 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView.swift @@ -23,6 +23,7 @@ public protocol StatusViewDelegate: AnyObject { func statusView(_ statusView: StatusView, authorAvatarButtonDidPressed button: AvatarButton) func statusView(_ statusView: StatusView, contentSensitiveeToggleButtonDidPressed button: UIButton) func statusView(_ statusView: StatusView, metaText: MetaText, didSelectMeta meta: Meta) + func statusView(_ statusView: StatusView, didTapCardWithURL url: URL) func statusView(_ statusView: StatusView, mediaGridContainerView: MediaGridContainerView, mediaView: MediaView, didSelectMediaViewAt index: Int) func statusView(_ statusView: StatusView, pollTableView tableView: UITableView, didSelectRowAt indexPath: IndexPath) func statusView(_ statusView: StatusView, pollVoteButtonPressed button: UIButton) @@ -32,6 +33,8 @@ public protocol StatusViewDelegate: AnyObject { func statusView(_ statusView: StatusView, mediaGridContainerView: MediaGridContainerView, mediaSensitiveButtonDidPressed button: UIButton) func statusView(_ statusView: StatusView, statusMetricView: StatusMetricView, reblogButtonDidPressed button: UIButton) func statusView(_ statusView: StatusView, statusMetricView: StatusMetricView, favoriteButtonDidPressed button: UIButton) + func statusView(_ statusView: StatusView, cardControl: StatusCardControl, didTapURL url: URL) + func statusView(_ statusView: StatusView, cardControlMenu: StatusCardControl) -> UIMenu? // a11y func statusView(_ statusView: StatusView, accessibilityActivate: Void) @@ -49,7 +52,10 @@ public final class StatusView: UIView { public weak var delegate: StatusViewDelegate? public private(set) var style: Style? - + + // accessibility actions + var toolbarActions = [UIAccessibilityCustomAction]() + public private(set) lazy var viewModel: ViewModel = { let viewModel = ViewModel() viewModel.bind(statusView: self) @@ -113,6 +119,8 @@ public final class StatusView: UIView { ] return metaText }() + + public let statusCardControl = StatusCardControl() // content warning public let spoilerOverlayView = SpoilerOverlayView() @@ -176,6 +184,55 @@ public final class StatusView: UIView { indicatorView.stopAnimating() return indicatorView }() + let isTranslatingLoadingView: UIActivityIndicatorView = { + let activityIndicatorView = UIActivityIndicatorView(style: .medium) + activityIndicatorView.hidesWhenStopped = true + activityIndicatorView.stopAnimating() + return activityIndicatorView + }() + private let translatedInfoLabel: UILabel = { + let label = UILabel() + label.font = UIFontMetrics(forTextStyle: .footnote).scaledFont(for: .systemFont(ofSize: 13, weight: .regular)) + label.textColor = Asset.Colors.Label.secondary.color + label.numberOfLines = 0 + return label + }() + lazy var translatedInfoView: UIView = { + let containerView = UIView() + + let revertButton = UIButton() + revertButton.titleLabel?.font = UIFontMetrics(forTextStyle: .footnote).scaledFont(for: .systemFont(ofSize: 13, weight: .bold)) + revertButton.setTitle(L10n.Common.Controls.Status.Translation.showOriginal, for: .normal) + revertButton.setTitleColor(Asset.Colors.brand.color, for: .normal) + revertButton.addAction(UIAction { [weak self] _ in + self?.revertTranslation() + }, for: .touchUpInside) + + [containerView, translatedInfoLabel, revertButton].forEach { + $0.translatesAutoresizingMaskIntoConstraints = false + } + + [translatedInfoLabel, revertButton].forEach { + containerView.addSubview($0) + } + + translatedInfoLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) + revertButton.setContentHuggingPriority(.required, for: .horizontal) + + NSLayoutConstraint.activate([ + containerView.heightAnchor.constraint(equalToConstant: 24), + translatedInfoLabel.centerYAnchor.constraint(equalTo: containerView.centerYAnchor), + translatedInfoLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16), + translatedInfoLabel.trailingAnchor.constraint(equalTo: revertButton.leadingAnchor, constant: -16), + revertButton.topAnchor.constraint(equalTo: containerView.topAnchor), + revertButton.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -16), + revertButton.bottomAnchor.constraint(equalTo: containerView.bottomAnchor) + ]) + + containerView.isHidden = true + + return containerView + }() // toolbar let actionToolbarAdaptiveMarginContainerView = AdaptiveMarginContainerView() @@ -203,12 +260,7 @@ public final class StatusView: UIView { authorView.avatarButton.avatarImageView.cancelTask() if var snapshot = pollTableViewDiffableDataSource?.snapshot() { snapshot.deleteAllItems() - if #available(iOS 15.0, *) { - pollTableViewDiffableDataSource?.applySnapshotUsingReloadData(snapshot) - } else { - // Fallback on earlier versions - pollTableViewDiffableDataSource?.apply(snapshot, animatingDifferences: false) - } + pollTableViewDiffableDataSource?.applySnapshotUsingReloadData(snapshot) } setHeaderDisplay(isDisplay: false) @@ -217,6 +269,8 @@ public final class StatusView: UIView { setMediaDisplay(isDisplay: false) setPollDisplay(isDisplay: false) setFilterHintLabelDisplay(isDisplay: false) + setStatusCardControlDisplay(isDisplay: false) + setupTranslationIndicator() } public override init(frame: CGRect) { @@ -236,16 +290,12 @@ extension StatusView { // container containerStackView.translatesAutoresizingMaskIntoConstraints = false addSubview(containerStackView) - NSLayoutConstraint.activate([ - containerStackView.topAnchor.constraint(equalTo: topAnchor), - containerStackView.leadingAnchor.constraint(equalTo: leadingAnchor), - containerStackView.trailingAnchor.constraint(equalTo: trailingAnchor), - containerStackView.bottomAnchor.constraint(equalTo: bottomAnchor), - ]) + containerStackView.pinToParent() // header headerIconImageView.isUserInteractionEnabled = false headerInfoLabel.isUserInteractionEnabled = false + headerInfoLabel.isAccessibilityElement = false let headerTapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer headerTapGestureRecognizer.addTarget(self, action: #selector(StatusView.headerDidPressed(_:))) headerContainerView.addGestureRecognizer(headerTapGestureRecognizer) @@ -261,10 +311,14 @@ extension StatusView { // content contentMetaText.textView.delegate = self contentMetaText.textView.linkDelegate = self - + + // card + statusCardControl.addTarget(self, action: #selector(statusCardControlPressed), for: .touchUpInside) + statusCardControl.delegate = self + // media mediaGridContainerView.delegate = self - + // poll pollTableView.translatesAutoresizingMaskIntoConstraints = false pollTableViewHeightLayoutConstraint = pollTableView.heightAnchor.constraint(equalToConstant: 44.0).priority(.required - 1) @@ -299,6 +353,12 @@ extension StatusView { logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") delegate?.statusView(self, spoilerOverlayViewDidPressed: spoilerOverlayView) } + + @objc private func statusCardControlPressed(_ sender: StatusCardControl) { + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") + guard let url = viewModel.card?.url else { return } + delegate?.statusView(self, didTapCardWithURL: url) + } } @@ -374,11 +434,11 @@ extension StatusView.Style { statusView.authorAdaptiveMarginContainerView.margin = StatusView.containerLayoutMargin statusView.containerStackView.addArrangedSubview(statusView.authorAdaptiveMarginContainerView) - // content container: V - [ contentMetaText ] + // content container: V - [ contentMetaText statusCardControl ] statusView.contentContainer.axis = .vertical statusView.contentContainer.spacing = 12 statusView.contentContainer.distribution = .fill - statusView.contentContainer.alignment = .top + statusView.contentContainer.alignment = .fill statusView.contentAdaptiveMarginContainerView.contentView = statusView.contentContainer statusView.contentAdaptiveMarginContainerView.margin = StatusView.containerLayoutMargin @@ -388,16 +448,15 @@ extension StatusView.Style { // status content statusView.contentContainer.addArrangedSubview(statusView.contentMetaText.textView) - statusView.containerStackView.setCustomSpacing(16, after: statusView.contentMetaText.textView) + statusView.contentContainer.addArrangedSubview(statusView.statusCardControl) + + // translated info + statusView.containerStackView.addArrangedSubview(statusView.isTranslatingLoadingView) + statusView.containerStackView.addArrangedSubview(statusView.translatedInfoView) statusView.spoilerOverlayView.translatesAutoresizingMaskIntoConstraints = false statusView.containerStackView.addSubview(statusView.spoilerOverlayView) - NSLayoutConstraint.activate([ - statusView.contentContainer.topAnchor.constraint(equalTo: statusView.spoilerOverlayView.topAnchor), - statusView.contentContainer.leadingAnchor.constraint(equalTo: statusView.spoilerOverlayView.leadingAnchor), - statusView.contentContainer.trailingAnchor.constraint(equalTo: statusView.spoilerOverlayView.trailingAnchor), - statusView.contentContainer.bottomAnchor.constraint(equalTo: statusView.spoilerOverlayView.bottomAnchor), - ]) + statusView.contentContainer.pinTo(to: statusView.spoilerOverlayView) // media container: V - [ mediaGridContainerView ] statusView.mediaContainerView.translatesAutoresizingMaskIntoConstraints = false @@ -409,12 +468,7 @@ extension StatusView.Style { statusView.mediaGridContainerView.translatesAutoresizingMaskIntoConstraints = false statusView.mediaContainerView.addSubview(statusView.mediaGridContainerView) - NSLayoutConstraint.activate([ - statusView.mediaGridContainerView.topAnchor.constraint(equalTo: statusView.mediaContainerView.topAnchor), - statusView.mediaGridContainerView.leadingAnchor.constraint(equalTo: statusView.mediaContainerView.leadingAnchor), - statusView.mediaGridContainerView.trailingAnchor.constraint(equalTo: statusView.mediaContainerView.trailingAnchor), - statusView.mediaGridContainerView.bottomAnchor.constraint(equalTo: statusView.mediaContainerView.bottomAnchor), - ]) + statusView.mediaGridContainerView.pinToParent() // pollContainerView: V - [ pollTableView | pollStatusStackView ] statusView.pollAdaptiveMarginContainerView.contentView = statusView.pollContainerView @@ -438,7 +492,7 @@ extension StatusView.Style { statusView.pollStatusDotLabel.setContentHuggingPriority(.defaultHigh + 1, for: .horizontal) statusView.pollCountdownLabel.setContentHuggingPriority(.defaultLow, for: .horizontal) statusView.pollVoteButton.setContentHuggingPriority(.defaultHigh + 3, for: .horizontal) - + // action toolbar statusView.actionToolbarAdaptiveMarginContainerView.contentView = statusView.actionToolbarContainer statusView.actionToolbarAdaptiveMarginContainerView.margin = StatusView.containerLayoutMargin @@ -486,6 +540,7 @@ extension StatusView.Style { statusView.headerAdaptiveMarginContainerView.removeFromSuperview() statusView.authorAdaptiveMarginContainerView.removeFromSuperview() + statusView.statusCardControl.removeFromSuperview() } func notificationQuote(statusView: StatusView) { @@ -494,6 +549,7 @@ extension StatusView.Style { statusView.contentAdaptiveMarginContainerView.bottomLayoutConstraint?.constant = 16 // fix bottom margin missing issue statusView.pollAdaptiveMarginContainerView.bottomLayoutConstraint?.constant = 16 // fix bottom margin missing issue statusView.actionToolbarAdaptiveMarginContainerView.removeFromSuperview() + statusView.statusCardControl.removeFromSuperview() } func composeStatusReplica(statusView: StatusView) { @@ -539,6 +595,10 @@ extension StatusView { func setFilterHintLabelDisplay(isDisplay: Bool = true) { filterHintLabel.isHidden = !isDisplay } + + func setStatusCardControlDisplay(isDisplay: Bool = true) { + statusCardControl.isHidden = !isDisplay + } // container width public var contentMaxLayoutWidth: CGFloat { @@ -556,7 +616,11 @@ extension StatusView { extension StatusView { public override var accessibilityCustomActions: [UIAccessibilityCustomAction]? { - get { contentMetaText.textView.accessibilityCustomActions } + get { + (contentMetaText.textView.accessibilityCustomActions ?? []) + + toolbarActions + + (authorView.accessibilityCustomActions ?? []) + } set { } } } @@ -671,6 +735,52 @@ extension StatusView: MastodonMenuDelegate { } } +extension StatusView { + func setupTranslationIndicator() { + viewModel.$isCurrentlyTranslating + .receive(on: DispatchQueue.main) + .sink { [weak self] isTranslating in + switch isTranslating { + case true: + self?.isTranslatingLoadingView.startAnimating() + case false: + self?.isTranslatingLoadingView.stopAnimating() + } + } + .store(in: &disposeBag) + + Publishers.CombineLatest( + viewModel.$translatedFromLanguage, + viewModel.$translatedUsingProvider + ) + .receive(on: DispatchQueue.main) + .sink { [weak self] translatedFromLanguage, translatedUsingProvider in + guard let self = self else { return } + if let translatedFromLanguage = translatedFromLanguage { + self.translatedInfoLabel.text = L10n.Common.Controls.Status.Translation.translatedFrom( + Locale.current.localizedString(forIdentifier: translatedFromLanguage) ?? L10n.Common.Controls.Status.Translation.unknownLanguage, + translatedUsingProvider ?? L10n.Common.Controls.Status.Translation.unknownProvider + ) + self.translatedInfoView.isHidden = false + } else { + self.translatedInfoView.isHidden = true + } + } + .store(in: &disposeBag) + } +} + +// MARK: StatusCardControlDelegate +extension StatusView: StatusCardControlDelegate { + public func statusCardControl(_ statusCardControl: StatusCardControl, didTapURL url: URL) { + delegate?.statusView(self, cardControl: statusCardControl, didTapURL: url) + } + + public func statusCardControlMenu(_ statusCardControl: StatusCardControl) -> UIMenu? { + delegate?.statusView(self, cardControlMenu: statusCardControl) + } +} + #if DEBUG import SwiftUI diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/UserView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/UserView.swift index cb066abfd..901a6547f 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/UserView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/UserView.swift @@ -66,12 +66,7 @@ extension UserView { // container containerStackView.translatesAutoresizingMaskIntoConstraints = false addSubview(containerStackView) - NSLayoutConstraint.activate([ - containerStackView.topAnchor.constraint(equalTo: topAnchor), - containerStackView.leadingAnchor.constraint(equalTo: leadingAnchor), - containerStackView.trailingAnchor.constraint(equalTo: trailingAnchor), - containerStackView.bottomAnchor.constraint(equalTo: bottomAnchor) - ]) + containerStackView.pinToParent() avatarButton.translatesAutoresizingMaskIntoConstraints = false containerStackView.addArrangedSubview(avatarButton) diff --git a/MastodonSDK/Sources/MastodonUI/View/Control/ActionToolbarContainer.swift b/MastodonSDK/Sources/MastodonUI/View/Control/ActionToolbarContainer.swift index 446b4af2a..ccb59157a 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Control/ActionToolbarContainer.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Control/ActionToolbarContainer.swift @@ -9,6 +9,7 @@ import os.log import UIKit import MastodonAsset import MastodonLocalization +import MastodonExtension public protocol ActionToolbarContainerDelegate: AnyObject { func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, buttonDidPressed button: UIButton, action: ActionToolbarContainer.Action) @@ -105,7 +106,7 @@ extension ActionToolbarContainer { shareButton.setImage(ActionToolbarContainer.shareImage, for: .normal) container.axis = .horizontal - container.distribution = .fill + container.distribution = .equalSpacing replyButton.translatesAutoresizingMaskIntoConstraints = false reblogButton.translatesAutoresizingMaskIntoConstraints = false @@ -283,7 +284,7 @@ extension ActionToolbarContainer { extension ActionToolbarContainer { private static func title(from number: Int?) -> String { guard let number = number, number > 0 else { return "" } - return String(number) + return number.asAbbreviatedCountString() } } diff --git a/MastodonSDK/Sources/MastodonUI/View/Control/ProfileStatusDashboardMeterView.swift b/MastodonSDK/Sources/MastodonUI/View/Control/ProfileStatusDashboardMeterView.swift index 0c9d243c2..08f84da33 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Control/ProfileStatusDashboardMeterView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Control/ProfileStatusDashboardMeterView.swift @@ -29,7 +29,6 @@ public final class ProfileStatusDashboardMeterView: UIView { let label = UILabel() label.font = .systemFont(ofSize: 13, weight: .regular) label.textColor = Asset.Colors.Label.primary.color - label.text = L10n.Scene.Profile.Dashboard.posts label.textAlignment = .center if UIView.isZoomedMode { label.adjustsFontSizeToFitWidth = true diff --git a/MastodonSDK/Sources/MastodonUI/View/Control/ProfileStatusDashboardView.swift b/MastodonSDK/Sources/MastodonUI/View/Control/ProfileStatusDashboardView.swift index a45e8ef6a..3be447292 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Control/ProfileStatusDashboardView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Control/ProfileStatusDashboardView.swift @@ -66,10 +66,6 @@ extension ProfileStatusDashboardView { containerStackView.setCustomSpacing(spacing + 2, after: followingDashboardMeterView) containerStackView.addArrangedSubview(followersDashboardMeterView) - postDashboardMeterView.textLabel.text = L10n.Scene.Profile.Dashboard.posts - followingDashboardMeterView.textLabel.text = L10n.Scene.Profile.Dashboard.following - followersDashboardMeterView.textLabel.text = L10n.Scene.Profile.Dashboard.followers - [postDashboardMeterView, followingDashboardMeterView, followersDashboardMeterView].forEach { meterView in let tapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer tapGestureRecognizer.addTarget(self, action: #selector(ProfileStatusDashboardView.tapGestureRecognizerHandler(_:))) diff --git a/MastodonSDK/Sources/MastodonUI/View/Control/SpoilerOverlayView.swift b/MastodonSDK/Sources/MastodonUI/View/Control/SpoilerOverlayView.swift index 98139b00e..c9074f9b5 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Control/SpoilerOverlayView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Control/SpoilerOverlayView.swift @@ -48,12 +48,7 @@ extension SpoilerOverlayView { private func _init() { containerStackView.translatesAutoresizingMaskIntoConstraints = false addSubview(containerStackView) - NSLayoutConstraint.activate([ - containerStackView.topAnchor.constraint(equalTo: topAnchor), - containerStackView.leadingAnchor.constraint(equalTo: leadingAnchor), - containerStackView.trailingAnchor.constraint(equalTo: trailingAnchor), - containerStackView.bottomAnchor.constraint(equalTo: bottomAnchor), - ]) + containerStackView.pinToParent() let topPaddingView = UIView() topPaddingView.translatesAutoresizingMaskIntoConstraints = false diff --git a/MastodonSDK/Sources/MastodonUI/View/Menu/MastodonMenu.swift b/MastodonSDK/Sources/MastodonUI/View/Menu/MastodonMenu.swift index 422494328..6fd5df772 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Menu/MastodonMenu.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Menu/MastodonMenu.swift @@ -40,6 +40,7 @@ public enum MastodonMenu { extension MastodonMenu { public enum Action { + case translateStatus(TranslateStatusActionContext) case muteUser(MuteUserActionContext) case blockUser(BlockUserActionContext) case reportUser(ReportUserActionContext) @@ -126,6 +127,15 @@ extension MastodonMenu { delegate.menuAction(self) } return deleteAction + case let .translateStatus(context): + let translateAction = BuiltAction( + title: L10n.Common.Controls.Actions.TranslatePost.title(Locale.current.localizedString(forIdentifier: context.language) ?? L10n.Common.Controls.Actions.TranslatePost.unknownLanguage), + image: UIImage(systemName: "character.book.closed") + ) { [weak delegate] in + guard let delegate = delegate else { return } + delegate.menuAction(self) + } + return translateAction } // end switch } // end func build } // end enum Action @@ -225,4 +235,12 @@ extension MastodonMenu { self.showReblogs = showReblogs } } + + public struct TranslateStatusActionContext { + public let language: String + + public init(language: String) { + self.language = language + } + } } diff --git a/MastodonSDK/Sources/MastodonUI/View/TableViewCell/PollOptionTableViewCell.swift b/MastodonSDK/Sources/MastodonUI/View/TableViewCell/PollOptionTableViewCell.swift index 6ae6ea0b5..d1e1a90f4 100644 --- a/MastodonSDK/Sources/MastodonUI/View/TableViewCell/PollOptionTableViewCell.swift +++ b/MastodonSDK/Sources/MastodonUI/View/TableViewCell/PollOptionTableViewCell.swift @@ -53,12 +53,7 @@ extension PollOptionTableViewCell { pollOptionView.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(pollOptionView) - NSLayoutConstraint.activate([ - pollOptionView.topAnchor.constraint(equalTo: contentView.topAnchor), - pollOptionView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), - pollOptionView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), - pollOptionView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), - ]) + pollOptionView.pinToParent() pollOptionView.setup(style: .plain) } diff --git a/MastodonSDK/Sources/MastodonUI/View/TableViewCell/ProfileCardTableViewCell.swift b/MastodonSDK/Sources/MastodonUI/View/TableViewCell/ProfileCardTableViewCell.swift index 2d210f20c..a14028ea2 100644 --- a/MastodonSDK/Sources/MastodonUI/View/TableViewCell/ProfileCardTableViewCell.swift +++ b/MastodonSDK/Sources/MastodonUI/View/TableViewCell/ProfileCardTableViewCell.swift @@ -67,12 +67,7 @@ extension ProfileCardTableViewCell { profileCardView.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(profileCardView) - NSLayoutConstraint.activate([ - profileCardView.topAnchor.constraint(equalTo: shadowBackgroundContainer.topAnchor), - profileCardView.leadingAnchor.constraint(equalTo: shadowBackgroundContainer.leadingAnchor), - profileCardView.trailingAnchor.constraint(equalTo: shadowBackgroundContainer.trailingAnchor), - profileCardView.bottomAnchor.constraint(equalTo: shadowBackgroundContainer.bottomAnchor), - ]) + profileCardView.pinTo(to: shadowBackgroundContainer) profileCardView.delegate = self diff --git a/MastodonSDK/Sources/MastodonUI/View/TableViewCell/TimelineLoaderTableViewCell.swift b/MastodonSDK/Sources/MastodonUI/View/TableViewCell/TimelineLoaderTableViewCell.swift index 6e396dc6d..7f5b355d3 100644 --- a/MastodonSDK/Sources/MastodonUI/View/TableViewCell/TimelineLoaderTableViewCell.swift +++ b/MastodonSDK/Sources/MastodonUI/View/TableViewCell/TimelineLoaderTableViewCell.swift @@ -96,12 +96,7 @@ open class TimelineLoaderTableViewCell: UITableViewCell { stackView.translatesAutoresizingMaskIntoConstraints = false stackView.isUserInteractionEnabled = false contentView.addSubview(stackView) - NSLayoutConstraint.activate([ - stackView.topAnchor.constraint(equalTo: loadMoreButton.topAnchor), - stackView.leadingAnchor.constraint(equalTo: loadMoreButton.leadingAnchor), - stackView.trailingAnchor.constraint(equalTo: loadMoreButton.trailingAnchor), - stackView.bottomAnchor.constraint(equalTo: loadMoreButton.bottomAnchor), - ]) + stackView.pinTo(to: loadMoreButton) let leftPaddingView = UIView() leftPaddingView.translatesAutoresizingMaskIntoConstraints = false stackView.addArrangedSubview(leftPaddingView) diff --git a/MastodonSDK/Sources/MastodonUI/View/Window/TouchesVisibleWindow.swift b/MastodonSDK/Sources/MastodonUI/View/Window/TouchesVisibleWindow.swift new file mode 100644 index 000000000..8779e1ce7 --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/View/Window/TouchesVisibleWindow.swift @@ -0,0 +1,135 @@ +// +// TouchesVisibleWindow.swift +// +// +// Created by Chase Carroll on 12/5/22. +// + +#if DEBUG + +import UIKit + +/// View that represents a single touch from the user. +private final class TouchView: UIView { + + private let blurView = UIVisualEffectView(effect: nil) + + override var frame: CGRect { + didSet { + layer.cornerRadius = frame.height / 2.0 + } + } + + override init(frame: CGRect) { + super.init(frame: frame) + + let isLightMode = traitCollection.userInterfaceStyle == .light + + backgroundColor = .clear + layer.masksToBounds = true + layer.cornerCurve = .circular + layer.borderColor = isLightMode ? UIColor.gray.cgColor : UIColor.white.cgColor + layer.borderWidth = 2.0 + + let blurEffect = isLightMode ? + UIBlurEffect(style: .systemUltraThinMaterialDark) : + UIBlurEffect(style: .systemUltraThinMaterialLight) + blurView.effect = blurEffect + addSubview(blurView) + } + + @available(iOS, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + super.layoutSubviews() + blurView.frame = bounds + } + +} + + +/// `UIWindow` subclass that renders visual representations of the user's touches. +public final class TouchesVisibleWindow: UIWindow { + + public var touchesVisible = false { + didSet { + if !touchesVisible { + cleanUpAllTouches() + } + } + } + + private var touchViews: [UITouch : TouchView] = [:] + + private func newTouchView() -> TouchView { + let touchSize = 44.0 + return TouchView(frame: CGRect( + origin: .zero, + size: CGSize( + width: touchSize, + height: touchSize + ) + )) + } + + private func cleanupTouch(_ touch: UITouch) { + guard let touchView = touchViews[touch] else { + return + } + + touchView.removeFromSuperview() + touchViews.removeValue(forKey: touch) + } + + private func cleanUpAllTouches() { + for (_, touchView) in touchViews { + touchView.removeFromSuperview() + } + + touchViews.removeAll() + } + + public override func sendEvent(_ event: UIEvent) { + if touchesVisible { + let touches = event.allTouches + + guard + let touches = touches, + touches.count > 0 + else { + cleanUpAllTouches() + super.sendEvent(event) + return + } + + for touch in touches { + let touchLocation = touch.location(in: self) + switch touch.phase { + case .began: + let touchView = newTouchView() + touchView.center = touchLocation + addSubview(touchView) + touchViews[touch] = touchView + + case .moved: + if let touchView = touchViews[touch] { + touchView.center = touchLocation + } + + case .ended, .cancelled: + cleanupTouch(touch) + + default: + break + } + } + } + + super.sendEvent(event) + } +} + +#endif diff --git a/MastodonSDK/Tests/MastodonExtensionTests/IntTests.swift b/MastodonSDK/Tests/MastodonExtensionTests/IntTests.swift new file mode 100644 index 000000000..ec0d045b2 --- /dev/null +++ b/MastodonSDK/Tests/MastodonExtensionTests/IntTests.swift @@ -0,0 +1,53 @@ +// +// IntTests.swift +// +// +// Created by Marcus Kida on 28.12.22. +// + +import XCTest +@testable import MastodonSDK + +class IntFriendlyCountTests: XCTestCase { + func testFriendlyCount_for_1000() { + let input = 1_000 + let expectedOutput = "1K" + + XCTAssertEqual(expectedOutput, input.asAbbreviatedCountString()) + } + + func testFriendlyCount_for_1200() { + let input = 1_200 + let expectedOutput = "1.2K" + + XCTAssertEqual(expectedOutput, input.asAbbreviatedCountString()) + } + + func testFriendlyCount_for_50000() { + let input = 50_000 + let expectedOutput = "50K" + + XCTAssertEqual(expectedOutput, input.asAbbreviatedCountString()) + } + + func testFriendlyCount_for_70666() { + let input = 70_666 + let expectedOutput = "70.7K" + + XCTAssertEqual(expectedOutput, input.asAbbreviatedCountString()) + } + + func testFriendlyCount_for_1M() { + let input = 1_000_000 + let expectedOutput = "1M" + + XCTAssertEqual(expectedOutput, input.asAbbreviatedCountString()) + } + + func testFriendlyCount_for_1dot5M() { + let input = 1_499_000 + let expectedOutput = "1.5M" + + XCTAssertEqual(expectedOutput, input.asAbbreviatedCountString()) + } +} diff --git a/MastodonTests/MastodonTests.swift b/MastodonTests/MastodonTests.swift index ec572fd92..e26e6a3f1 100644 --- a/MastodonTests/MastodonTests.swift +++ b/MastodonTests/MastodonTests.swift @@ -43,10 +43,11 @@ extension MastodonTests { } receiveValue: { domain in expectation.fulfill() } - wait(for: [expectation], timeout: 10) + withExtendedLifetime(cancellable) { + wait(for: [expectation], timeout: 10) + } } - @available(iOS 15.0, *) func testConnectOnion() async throws { let request = URLRequest( url: URL(string: "http://a232ncr7jexk2chvubaq2v6qdizbocllqap7mnn7w7vrdutyvu32jeyd.onion/@k0gen")!, diff --git a/Podfile b/Podfile index 4df2d4d7c..5c8a0a47e 100644 --- a/Podfile +++ b/Podfile @@ -1,5 +1,7 @@ source 'https://cdn.cocoapods.org/' -platform :ios, '14.0' +platform :ios, '15.0' + +inhibit_all_warnings! target 'Mastodon' do # Comment the next line if you don't want to use dynamic frameworks @@ -12,7 +14,6 @@ target 'Mastodon' do # misc pod 'SwiftGen', '~> 6.6.2' - pod 'DateToolsSwift', '~> 5.0.0' pod 'Kanna', '~> 5.2.2' pod 'Sourcery', '~> 1.6.1' diff --git a/Podfile.lock b/Podfile.lock index c7220b00a..f0949d7ab 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,5 +1,4 @@ PODS: - - DateToolsSwift (5.0.0) - FLEX (4.4.1) - Kanna (5.2.7) - Sourcery (1.6.1): @@ -9,7 +8,6 @@ PODS: - XLPagerTabStrip (9.0.0) DEPENDENCIES: - - DateToolsSwift (~> 5.0.0) - FLEX (~> 4.4.0) - Kanna (~> 5.2.2) - Sourcery (~> 1.6.1) @@ -18,7 +16,6 @@ DEPENDENCIES: SPEC REPOS: trunk: - - DateToolsSwift - FLEX - Kanna - Sourcery @@ -26,13 +23,12 @@ SPEC REPOS: - XLPagerTabStrip SPEC CHECKSUMS: - DateToolsSwift: 4207ada6ad615d8dc076323d27037c94916dbfa6 FLEX: 7ca2c8cd3a435ff501ff6d2f2141e9bdc934eaab Kanna: 01cfbddc127f5ff0963692f285fcbc8a9d62d234 Sourcery: f3759f803bd0739f74fc92a4341eed0473ce61ac SwiftGen: 1366a7f71aeef49954ca5a63ba4bef6b0f24138c XLPagerTabStrip: 61c57fd61f611ee5f01ff1495ad6fbee8bf496c5 -PODFILE CHECKSUM: 7499a197793f73c4dcf1d16a315434baaa688873 +PODFILE CHECKSUM: 5b1dbf90a3e6fff01240ad0a2ceb2bc7f7bb4e36 COCOAPODS: 1.11.3 diff --git a/ShareActionExtension/Info.plist b/ShareActionExtension/Info.plist index 52924beed..f9993cc01 100644 --- a/ShareActionExtension/Info.plist +++ b/ShareActionExtension/Info.plist @@ -5,7 +5,7 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName - ShareActionExtension + Mastodon CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier diff --git a/ShareActionExtension/Scene/ShareViewController.swift b/ShareActionExtension/Scene/ShareViewController.swift index 7757452cd..5804f431b 100644 --- a/ShareActionExtension/Scene/ShareViewController.swift +++ b/ShareActionExtension/Scene/ShareViewController.swift @@ -21,7 +21,7 @@ final class ShareViewController: UIViewController { var disposeBag = Set() - let context = AppContext() + let context = AppContext.shared private(set) lazy var viewModel = ShareViewModel(context: context) let publishButton: UIButton = { @@ -63,6 +63,10 @@ final class ShareViewController: UIViewController { label.text = "No Available Account" // TODO: i18n return label }() + + deinit { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + } } @@ -95,19 +99,15 @@ extension ShareViewController { let composeContentViewModel = ComposeContentViewModel( context: context, authContext: authContext, - kind: .post + destination: .topLevel, + initialContent: "" ) let composeContentViewController = ComposeContentViewController() composeContentViewController.viewModel = composeContentViewModel addChild(composeContentViewController) composeContentViewController.view.translatesAutoresizingMaskIntoConstraints = false view.addSubview(composeContentViewController.view) - NSLayoutConstraint.activate([ - composeContentViewController.view.topAnchor.constraint(equalTo: view.topAnchor), - composeContentViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), - composeContentViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), - composeContentViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + composeContentViewController.view.pinToParent() composeContentViewController.didMove(toParent: self) self.composeContentViewModel = composeContentViewModel @@ -160,7 +160,7 @@ extension ShareViewController { _ = try await statusPublisher.publish(api: context.apiService, authContext: authContext) self.publishButton.setTitle(L10n.Common.Controls.Actions.done, for: .normal) - try await Task.sleep(nanoseconds: 1 * .second) + try await Task.sleep(nanoseconds: 1 * .second) self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil) @@ -281,6 +281,7 @@ extension ShareViewController { api: context.apiService, authContext: authContext, input: .itemProvider(movieProvider), + sizeLimit: .init(image: nil, video: nil), delegate: composeContentViewModel ) composeContentViewModel.attachmentViewModels.append(attachmentViewModel) @@ -290,6 +291,7 @@ extension ShareViewController { api: context.apiService, authContext: authContext, input: .itemProvider(provider), + sizeLimit: .init(image: nil, video: nil), delegate: composeContentViewModel ) } @@ -328,3 +330,7 @@ extension ShareViewController { case missingAuthentication } } + +extension AppContext { + static let shared = AppContext() +} diff --git a/update_localization.sh b/update_localization.sh index 87477bdaa..1a240c893 100755 --- a/update_localization.sh +++ b/update_localization.sh @@ -25,6 +25,7 @@ cd ${SRCROOT}/Localization/StringsConvertor sh ./scripts/build.sh # Task 3 copy strings file +cp -R ${SRCROOT}/Localization/StringsConvertor/output/main/ ${SRCROOT}/Mastodon/Resources cp -R ${SRCROOT}/Localization/StringsConvertor/output/module/ ${SRCROOT}/MastodonSDK/Sources/MastodonLocalization/Resources cp -R ${SRCROOT}/Localization/StringsConvertor/Intents/output/ ${SRCROOT}/MastodonIntent