From 3e98dd1e7e700e30746b5cb4f271cdd90ce918f2 Mon Sep 17 00:00:00 2001 From: Felitendo Date: Fri, 2 Oct 2026 10:39:46 +0200 Subject: [PATCH] feat: first version --- .gitattributes | 2 + .github/FUNDING.yml | 1 + .github/ISSUE_TEMPLATE/1-feature_request.yml | 35 + .github/ISSUE_TEMPLATE/2-bug_report.yml | 98 +++ .github/ISSUE_TEMPLATE/config.yml | 1 + .github/check-version.sh | 36 + .github/release-notes.sh | 136 ++++ .github/workflows/check.yml | 30 + .github/workflows/release.yml | 42 ++ .gitignore | 8 + CHANGELOG.md | 24 + CLAUDE.md | 102 +++ LICENSE | 674 +++++++++++++++++++ Makefile | 146 ++++ README.md | 152 +++++ doc/rdfeed.1.scd | 173 +++++ po/de.po | 564 ++++++++++++++++ po/es.po | 565 ++++++++++++++++ po/fr.po | 574 ++++++++++++++++ po/it.po | 561 +++++++++++++++ po/ja.po | 561 +++++++++++++++ po/ko.po | 547 +++++++++++++++ po/nl.po | 562 ++++++++++++++++ po/pl.po | 555 +++++++++++++++ po/pt_BR.po | 561 +++++++++++++++ po/rdfeed.pot | 531 +++++++++++++++ po/ru.po | 558 +++++++++++++++ po/tr.po | 554 +++++++++++++++ po/uk.po | 559 +++++++++++++++ po/update-pot.sh | 34 + po/zh_CN.po | 537 +++++++++++++++ res/rdfeed.svg | 68 ++ res/screenshots/menu.png | Bin 0 -> 59497 bytes res/screenshots/settings.png | Bin 0 -> 102680 bytes res/systemd/rdfeed-refresh.service | 8 + res/systemd/rdfeed-refresh.timer | 11 + src/fetch/rdfeed-fetch | 617 +++++++++++++++++ src/hook/rdfeed-hook.c | 146 ++++ src/lib/common.sh | 202 ++++++ src/lib/config.sh | 155 +++++ src/lib/launch.sh | 307 +++++++++ src/lib/menu.sh | 635 +++++++++++++++++ src/lib/workspace.sh | 389 +++++++++++ src/rdfeed | 321 +++++++++ tests/fake_rdweb.py | 212 ++++++ tests/test_cli.sh | 163 +++++ tests/test_fetch.py | 220 ++++++ tests/test_hook.sh | 88 +++ 48 files changed, 13025 insertions(+) create mode 100644 .gitattributes create mode 100644 .github/FUNDING.yml create mode 100644 .github/ISSUE_TEMPLATE/1-feature_request.yml create mode 100644 .github/ISSUE_TEMPLATE/2-bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100755 .github/check-version.sh create mode 100755 .github/release-notes.sh create mode 100644 .github/workflows/check.yml create mode 100644 .github/workflows/release.yml create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 CLAUDE.md create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 doc/rdfeed.1.scd create mode 100644 po/de.po create mode 100644 po/es.po create mode 100644 po/fr.po create mode 100644 po/it.po create mode 100644 po/ja.po create mode 100644 po/ko.po create mode 100644 po/nl.po create mode 100644 po/pl.po create mode 100644 po/pt_BR.po create mode 100644 po/rdfeed.pot create mode 100644 po/ru.po create mode 100644 po/tr.po create mode 100644 po/uk.po create mode 100755 po/update-pot.sh create mode 100644 po/zh_CN.po create mode 100644 res/rdfeed.svg create mode 100644 res/screenshots/menu.png create mode 100644 res/screenshots/settings.png create mode 100644 res/systemd/rdfeed-refresh.service create mode 100644 res/systemd/rdfeed-refresh.timer create mode 100755 src/fetch/rdfeed-fetch create mode 100644 src/hook/rdfeed-hook.c create mode 100644 src/lib/common.sh create mode 100644 src/lib/config.sh create mode 100644 src/lib/launch.sh create mode 100644 src/lib/menu.sh create mode 100644 src/lib/workspace.sh create mode 100755 src/rdfeed create mode 100755 tests/fake_rdweb.py create mode 100644 tests/test_cli.sh create mode 100644 tests/test_fetch.py create mode 100644 tests/test_hook.sh diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..2c85677 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +ko_fi: felitendo diff --git a/.github/ISSUE_TEMPLATE/1-feature_request.yml b/.github/ISSUE_TEMPLATE/1-feature_request.yml new file mode 100644 index 0000000..14ebb7d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1-feature_request.yml @@ -0,0 +1,35 @@ +name: ๐Ÿ’ก Feature request +description: An idea to make rdfeed better. +labels: +- enhancement +body: +- type: checkboxes + id: checks + attributes: + label: Before you start + options: + - label: I searched the issues and this is not requested yet. + required: true +- type: textarea + id: problem + attributes: + label: What problem would this solve? + description: What are you trying to do, and what gets in the way? + validations: + required: true +- type: textarea + id: idea + attributes: + label: What would you like? + description: How it could work. A rough idea is fine. + validations: + required: true +- type: textarea + id: alternatives + attributes: + label: Other ways you thought of +- type: textarea + id: more + attributes: + label: Anything else? + description: Mockups, screenshots, or other tools that do it well. diff --git a/.github/ISSUE_TEMPLATE/2-bug_report.yml b/.github/ISSUE_TEMPLATE/2-bug_report.yml new file mode 100644 index 0000000..68afdd9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/2-bug_report.yml @@ -0,0 +1,98 @@ +name: ๐Ÿ› Bug report +description: Something does not work as it should. +labels: +- bug +body: +- type: markdown + attributes: + value: Thanks for taking the time! The more you fill in, the faster it can be fixed. Please leave out your company's server names and your user name. +- type: checkboxes + id: checks + attributes: + label: Before you start + options: + - label: I searched the issues and this is not reported yet. + required: true + - label: I use the latest version. + required: true +- type: textarea + id: what + attributes: + label: What happened? + description: What did you do, and what went wrong? + validations: + required: true +- type: textarea + id: steps + attributes: + label: Steps to reproduce + placeholder: | + 1. + 2. + 3. +- type: textarea + id: expected + attributes: + label: What did you expect? + validations: + required: true +- type: dropdown + id: where + attributes: + label: Where does it happen? + multiple: true + options: + - Adding a workspace + - Starting an app + - The app window (drawing, keyboard, size) + - MFA prompts + - The app menu + - The menu + - Other + validations: + required: true +- type: dropdown + id: mfa + attributes: + label: Does your company use MFA? + options: + - "Yes" + - "No" + - I don't know +- type: input + id: version + attributes: + label: Version + description: The output of `rdfeed --version` and `xfreerdp3 /version`. + placeholder: rdfeed 1.0.0, FreeRDP 3.32.1 + validations: + required: true +- type: dropdown + id: install + attributes: + label: How did you install it? + options: + - AUR (Arch, CachyOS, EndeavourOS, Manjaro) + - Built from source + - Other + validations: + required: true +- type: input + id: system + attributes: + label: System + description: Distribution, desktop and its version, Wayland or X11. + placeholder: CachyOS, KDE Plasma 6.7, Wayland + validations: + required: true +- type: textarea + id: logs + attributes: + label: Log + description: For a start that goes wrong, the log from `~/.cache/rdfeed/` (one file per app). Remove server names and user names first. + render: shell +- type: textarea + id: more + attributes: + label: Anything else? + description: Screenshots or anything else that could help. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..3ba13e0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false diff --git a/.github/check-version.sh b/.github/check-version.sh new file mode 100755 index 0000000..68e69a9 --- /dev/null +++ b/.github/check-version.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +# +# Refuses a release whose tag and Makefile disagree. +# +# The version is baked into the program at install time from the Makefile, and +# the packages take theirs from the same place. But the tag is what people see +# and what the release is named after. A tag that says something else produces +# a package called 1.0.4 containing a program that reports 1.0.3, and nothing +# would have complained. +# +# Anything that is not a v-tag (a run started by hand from a branch) is not a +# release and has nothing to check. + +set -euo pipefail + +here="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" +ref="${1:-}" + +case "$ref" in + v[0-9]*) ;; + *) + echo "not a release tag (${ref:-none}), nothing to check against" + exit 0 + ;; +esac + +tag_version="${ref#v}" +make_version="$(make -s -C "$here" version)" + +if [[ $tag_version != "$make_version" ]]; then + echo "tag $ref says $tag_version, the Makefile says $make_version" >&2 + echo "Bump VERSION in the Makefile to match the tag, or retag." >&2 + exit 1 +fi + +echo "$ref matches the Makefile" diff --git a/.github/release-notes.sh b/.github/release-notes.sh new file mode 100755 index 0000000..276fd04 --- /dev/null +++ b/.github/release-notes.sh @@ -0,0 +1,136 @@ +#!/usr/bin/env bash +# +# Builds the GitHub release notes for a tag, the way big projects such as +# Immich lay them out: the hand-written entry from CHANGELOG.md (a welcome, +# the highlights), a support section, then every commit since the last +# release, sorted by kind, and a link to the full changelog. +# +# .github/release-notes.sh v1.2.0 the notes +# .github/release-notes.sh --title v1.2.0 the title (checks the entry exists) +# +# Commit authors come from the GitHub API through gh. Without it the list +# goes without them. + +set -euo pipefail +cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." + +title=0 +if [[ ${1:-} == --title ]]; then + title=1 + shift +fi +tag="${1:?usage: release-notes.sh [--title] vX.Y.Z}" + +if ! grep -qx "## $tag" CHANGELOG.md; then + echo "CHANGELOG.md has no entry for $tag. Add \"## $tag\" first." >&2 + exit 1 +fi +if (( title )); then + printf '%s\n' "$tag" + exit 0 +fi + +repo="${GITHUB_REPOSITORY:-$(git remote get-url origin | sed -E 's#^.*github\.com[:/]##; s#\.git$##')}" +name="${repo#*/}" + +# The entry: up to the next one, without the date line (GitHub shows the +# date), one heading level up, outside code blocks. +entry="$(awk -v h="## $tag" ' + $0 == h { on = 1; next } + on && /^## / { exit } + !on { next } + /^```/ { code = !code } + !code && /^_[0-9]{4}-[0-9]{2}-[0-9]{2}_$/ { next } + !code && /^###/ { sub(/^#/, "") } + { print } +' CHANGELOG.md | sed -e '/./,$!d')" + +# The commits: from the last release, or from the start. A tag that does not +# exist yet is a preview of what HEAD would become. +if git rev-parse -q --verify "refs/tags/$tag" > /dev/null; then + target="$tag" + prev="$(git describe --tags --abbrev=0 --match 'v[0-9]*' "$tag^" 2>/dev/null || true)" +else + target="$(git rev-parse HEAD)" + prev="$(git describe --tags --abbrev=0 --match 'v[0-9]*' HEAD 2>/dev/null || true)" +fi + +declare -A login=() +if command -v gh > /dev/null; then + if [[ -n $prev ]]; then + api=(api "repos/$repo/compare/$prev...$target" --jq '.commits[] | [.sha, (.author.login // "")] | @tsv') + else + api=(api --paginate "repos/$repo/commits?sha=$target&per_page=100" --jq '.[] | [.sha, (.author.login // "")] | @tsv') + fi + while IFS=$'\t' read -r sha who; do + [[ -n $who ]] && login[$sha]="$who" + done < <(gh "${api[@]}" 2>/dev/null || true) +fi + +declare -A list=() +count_maint=0 +while IFS=$'\t' read -r sha subject body; do + # Version bumps say nothing a reader needs. + [[ $subject =~ ^(chore:\ )?($name\ )?v?[0-9]+\.[0-9]+\.[0-9]+$ ]] && continue + + case "$subject" in + *!:*) kind=breaking ;; + feat:* | feat\(*) kind=feat ;; + fix:* | fix\(*) kind=fix ;; + perf:* | perf\(*) kind=enh ;; + docs:* | docs\(*) kind=docs ;; + chore* | ci:* | ci\(* | build* | refactor* | test* | style*) kind=maint ;; + # Older commits without a prefix, sorted by what they say. + *README* | *readme* | *Readme*) kind=docs ;; + Add\ * | Put\ * | Introduce\ *) kind=feat ;; + Fix\ * | Repair\ * | Recover\ * | Stop\ *) kind=fix ;; + *\ *) kind=enh ;; + *) kind=maint ;; + esac + [[ $body == *"BREAKING CHANGE"* ]] && kind=breaking + [[ $subject == "Initial commit" ]] && kind=maint + + line="* $subject" + [[ -n ${login[$sha]:-} ]] && line+=" by @${login[$sha]}" + line+=" in https://github.com/$repo/commit/$sha" + list[$kind]+="$line"$'\n' + [[ $kind == maint ]] && count_maint=$((count_maint + 1)) +done < <(git log --reverse --no-merges --format='%H%x09%s%x09%b%x1e' "${prev:+$prev..}$target" | tr '\n\036' ' \n' | sed 's/^ //') + +changes='' +for pair in "breaking:๐Ÿšจ Breaking Changes" "feat:๐Ÿš€ Features" "enh:๐ŸŒŸ Enhancements" "fix:๐Ÿ› Bug fixes" "docs:๐Ÿ“š Documentation"; do + kind="${pair%%:*}" + [[ -n ${list[$kind]:-} ]] || continue + changes+="### ${pair#*:}"$'\n'"${list[$kind]}" +done +if [[ -n ${list[maint]:-} ]]; then + changes+=$'\n'"
๐Ÿงฐ Maintenance ($count_maint)"$'\n\n'"${list[maint]}"$'\n'"
"$'\n' +fi + +if [[ -n $prev ]]; then + full="**Full Changelog**: https://github.com/$repo/compare/$prev...$tag" +else + full="**Full Changelog**: https://github.com/$repo/commits/$tag" +fi + +# A release with highlights is a big one: it gets a heading and the support +# section, as in Immich. A patch is a sentence or two and the list. +if grep -q '^## Highlights$' <<< "$entry"; then + cat <Buy me a coffee on Ko-fi + +---- + +EOF +else + printf '%s\n\n' "$entry" +fi +printf "## What's Changed\n%s\n\n%s\n" "$changes" "$full" diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 0000000..c28d3e0 --- /dev/null +++ b/.github/workflows/check.yml @@ -0,0 +1,30 @@ +name: check + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +jobs: + check: + name: tests and shellcheck + runs-on: ubuntu-latest + # Arch has FreeRDP 3, which the hook test loads. + container: archlinux:latest + steps: + - name: Install the tools + run: | + pacman -Syu --noconfirm --needed git base-devel python freerdp shared-mime-info \ + gettext scdoc shellcheck desktop-file-utils + + - uses: actions/checkout@v7 + + - run: make check + + # NTLM against the spec's test vectors, the feed, the .rdp files, the + # icons, a fake RD Web server, the hook and the command end to end. + - run: make test + + - name: Build the catalogs and the man page + run: make build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..960bd3f --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,42 @@ +name: release + +on: + push: + tags: ['v*'] + +permissions: + contents: write + +jobs: + release: + name: Test and release + runs-on: ubuntu-latest + container: archlinux:latest + steps: + - name: Install the tools + run: | + pacman -Syu --noconfirm --needed git base-devel python freerdp shared-mime-info \ + gettext scdoc github-cli + + - uses: actions/checkout@v7 + with: + # All tags, for the link to the changes since the last release. + fetch-depth: 0 + + - name: Check the tag against the Makefile + run: .github/check-version.sh "${{ github.ref_name }}" + + - name: Check CHANGELOG.md has this release + run: .github/release-notes.sh --title "${{ github.ref_name }}" + + - run: make test + + - name: Create the release + env: + GH_TOKEN: ${{ github.token }} + run: | + git config --global --add safe.directory "$PWD" + .github/release-notes.sh "${{ github.ref_name }}" > notes.md + gh release create "${{ github.ref_name }}" \ + --title "$(.github/release-notes.sh --title "${{ github.ref_name }}")" \ + --notes-file notes.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..be26d69 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# build artifacts +build/ +po/*.mo +doc/*.1 +__pycache__/ + +# packages +dist/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..18fc1ac --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,24 @@ +# Changelog + +What each release brings, newest first. The [GitHub releases](https://github.com/LoonixTools/rdfeed/releases) add every commit that went into it. + +## v1.0.0 + +_2026-10-02_ + +Welcome to the very first release of rdfeed! It brings the RemoteApps of your company to Linux: add the workspace once, and Word, Excel and the other apps show up in your app menu and open as windows of their own. + +

+ The rdfeed menu in Konsole with the Contoso Apps workspace +

+ +### Highlights + +- The apps of your company in the app menu +- Open your files in them with "Open with" +- Signs in like Windows, also with a work e-mail address +- Works with MFA on the gateway +- Sharp on scaled screens, with the right keyboard layout +- New apps show up by themselves every day +- The password stays in the keyring +- In 14 languages diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..040b0f1 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,102 @@ +# CLAUDE.md + +## Style + +- Keep everything short: replies, explanations, comments, docs. +- Use simple English: short sentences, common words. +- Avoid em dashes (โ€”). Do not just swap them for "-" either. Rewrite the sentence instead, for + example with a comma, a colon, brackets or two sentences. + +## Commits + +- English only. +- Conventional Commits prefix: `feat:`, `fix:`, `chore:`, `docs:`, `refactor:`, `ci:`, `build:`. +- Subject as short as possible. Imperative, lowercase, no trailing period. +- Body only when something genuinely cannot be inferred from the diff. +- Never add `Co-Authored-By`, "Generated with" or any other AI attribution to commits or PR descriptions. +- Do not commit or push before I have checked the changes locally and said they are fine. Then + commit and push. Several attempts at the same thing make one commit, and attempts that did not + work make none. Different things done in one session get a commit each. This also goes for + releases and tags. + +## Layout + +- `src/rdfeed` and `src/lib/*.sh`: the command and its menu, plain bash. +- `src/fetch/rdfeed-fetch`: signs in to the feed (NTLM) and downloads the apps. Python, standard + library only. +- `src/hook/rdfeed-hook.c`: goes into FreeRDP with `LD_PRELOAD`. Waits before the second gateway + sign-in so the second MFA prompt comes, and keeps GFX off. +- `res/systemd`: the daily refresh, a user timer. + +## Releases + +Releases look like the ones of big projects such as Immich. `.github/release-notes.sh ` builds +the notes: the entry from `CHANGELOG.md` (welcome and highlights), a support section, every commit +since the last release sorted by its prefix (`feat`, `fix`, `docs`, ...) with author and link, and +the full changelog link. So commit subjects end up in public: keep them clear. + +1. Read `git log ..HEAD` and pick the version: only fixes โ†’ patch, something new โ†’ minor, + something that breaks or needs the user to act โ†’ major. +2. Bump the version and add the entry at the top of `CHANGELOG.md`, in one commit (`chore: 1.4.0`). +3. Push, then push the tag: `git tag v1.4.0 && git push origin main v1.4.0`. The `release` workflow + runs the tests and creates the release. It stops when the entry is missing. +4. PKGBUILDS picks up the new release for the AUR on its own. + +A minor or major release (has `### Highlights`, gets a heading and the support section): + +```markdown +## v1.4.0 + +_2026-09-24_ + +Welcome to rdfeed `v1.4.0`! One or two sentences on what this release is about. + +

+ What the picture shows +

+ +### ๐Ÿšจ Breaking changes + +- Only if there are any: what changed, and what the user has to do. + +### Highlights + +- First highlight, a few words +- Second highlight +``` + +A picture under the welcome is optional. To show the menu or another screen of the program, use a +real screenshot of it running in Konsole, in English. Never a text copy of the screen. The same goes +for the README. The highlights stay a plain list: no heading or text per highlight, the list of +commits explains the rest. + +A patch release is just a sentence or two, for example: "A small patch. The menu no longer closes +when you press Enter." The list of commits follows on its own. + +- Write for users: what they notice, not how the code does it. Friendly and simple. +- Commands and settings they type go in backticks, buttons and labels in bold. +- To change an old release: edit its entry, commit, then + `gh release edit --title --notes "$(.github/release-notes.sh )"`. + +## README + +- New UI (a window, a screen, a menu, a setting, a notification) gets a screenshot in the README, + next to the text about it. Like for releases: a real screenshot of it running, in English. +- When a screen changes, take its screenshot again. The README never shows an old one. +- Screenshots show the demo workspace (Contoso Apps, `CONTOSO\jdoe`) from `tests/fake_rdweb.py`, + never a real company. + +## Testing + +Never test against the real setup: the workspaces, the keyring, the timer and the app menu belong +to the user's machine, and a real server counts every wrong password. + +- `make test` runs NTLM against the test vectors of MS-NLMP, the feed, the .rdp files and the + icons, whole subscriptions against `tests/fake_rdweb.py`, the hook, and the command from `add` to + `remove`. No real server, no root. +- `tests/test_cli.sh` runs in a temporary home, with fakes for `secret-tool`, `systemctl`, + `notify-send`, `kbuildsycoca6` and `xfreerdp3` on `PATH`. Do the same for anything by hand. +- `python3 tests/fake_rdweb.py [port]` serves the demo workspace (user `jdoe`, domain `CONTOSO`, + the password is in the file). Point a temporary home at it with `XDG_CONFIG_HOME`, + `XDG_DATA_HOME`, `XDG_CACHE_HOME` and `RF_LIBDIR=src/lib RF_FETCH=src/fetch/rdfeed-fetch`. +- `make check` after every change. New strings: `po/update-pot.sh`, then translate them in every `po/*.po`. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..36217a8 --- /dev/null +++ b/Makefile @@ -0,0 +1,146 @@ +# rdfeed: build and install +# +# The shell front end and rdfeed-fetch install as they are. The one thing to +# compile is rdfeed-hook.so, which goes into FreeRDP. Translations and the +# man page are optional and skipped when msgfmt or scdoc are missing. + +# Overridable so a packager can pass the version it is actually building +# (`make VERSION=$pkgver`). The literal below is the fallback for builds +# straight from a checkout, and is what a release tag has to carry. +VERSION ?= 1.0.0 + +PREFIX ?= /usr +DESTDIR ?= +BINDIR ?= $(PREFIX)/bin +DATADIR ?= $(PREFIX)/share +LIBDIR ?= $(DATADIR)/rdfeed/lib +LIBEXECDIR ?= $(PREFIX)/lib/rdfeed +LOCALEDIR ?= $(DATADIR)/locale +MANDIR ?= $(DATADIR)/man +ICONDIR ?= $(DATADIR)/icons/hicolor/scalable/apps + +# Where systemd looks for user units, asked of systemd itself for a normal +# install. A build with a prefix of its own keeps them under that prefix. +ifeq ($(PREFIX),/usr) +USERUNITDIR ?= $(shell pkg-config --variable=systemduserunitdir systemd 2>/dev/null || echo /usr/lib/systemd/user) +else +USERUNITDIR ?= $(DATADIR)/systemd/user +endif + +CC ?= cc +CFLAGS ?= -O2 -Wall -Wextra +BUILDDIR ?= build +HOOK := $(BUILDDIR)/rdfeed-hook.so + +LINGUAS := de es fr it ja ko nl pl pt_BR ru tr uk zh_CN +MOFILES := $(patsubst %,po/%.mo,$(LINGUAS)) +MANPAGE := doc/rdfeed.1 + +LIBS := $(wildcard src/lib/*.sh) + +MSGFMT := $(shell command -v msgfmt 2>/dev/null) +SCDOC := $(shell command -v scdoc 2>/dev/null) + +.PHONY: all build check test install uninstall clean version + +all: build + +build: $(HOOK) $(MOFILES) $(MANPAGE) + +# The one place the version is written down, for everything that has to agree +# with it: the release workflow checking that the tag it was handed says the +# same thing, and the PKGBUILD. +version: + @echo $(VERSION) + +$(HOOK): src/hook/rdfeed-hook.c + @mkdir -p $(BUILDDIR) + $(CC) $(CFLAGS) $(CPPFLAGS) -shared -fPIC $(LDFLAGS) -o $@ $< -ldl + +po/%.mo: po/%.po +ifdef MSGFMT + $(MSGFMT) --check --output-file=$@ $< +else + @echo "msgfmt not found, skipping $@" +endif + +$(MANPAGE): doc/rdfeed.1.scd +ifdef SCDOC + $(SCDOC) < $< > $@ +else + @echo "scdoc not found, skipping $@" +endif + +# Syntax-check every shell file and rdfeed-fetch, and run shellcheck when it +# is available. SC2034 is off: the files share their variables, and +# shellcheck looks at one file at a time. +check: + @set -e; for f in src/rdfeed $(LIBS) tests/*.sh; do \ + bash -n "$$f" && echo "ok $$f"; \ + done + @python3 -m py_compile src/fetch/rdfeed-fetch tests/*.py && echo "ok python" + @rm -rf src/fetch/__pycache__ tests/__pycache__ + @if command -v shellcheck >/dev/null 2>&1; then \ + shellcheck -x -e SC1090,SC1091,SC2034 src/rdfeed $(LIBS) tests/*.sh \ + && echo "ok shellcheck"; \ + else \ + echo "shellcheck not found, skipped"; \ + fi + +# The NTLM sign-in against the test vectors of the spec, the feed, the .rdp +# files and the icons, a whole subscription against a fake RD Web server, and +# the hook. Nothing here talks to a real server or touches the real setup. +test: $(HOOK) + python3 -m unittest discover -s tests -p 'test_*.py' -v + RDFEED_HOOK=$(HOOK) bash tests/test_hook.sh + bash tests/test_cli.sh + +install: build + # the command + install -Dm755 src/rdfeed "$(DESTDIR)$(BINDIR)/rdfeed" + install -d "$(DESTDIR)$(LIBDIR)" + install -Dm644 -t "$(DESTDIR)$(LIBDIR)" $(LIBS) + sed -i -e 's|@VERSION@|$(VERSION)|g' \ + -e 's|@LIBDIR@|$(LIBDIR)|g' \ + -e 's|@LIBEXECDIR@|$(LIBEXECDIR)|g' \ + -e 's|@LOCALEDIR@|$(LOCALEDIR)|g' \ + "$(DESTDIR)$(BINDIR)/rdfeed" \ + "$(DESTDIR)$(LIBDIR)"/*.sh + + # the helpers + install -Dm755 src/fetch/rdfeed-fetch "$(DESTDIR)$(LIBEXECDIR)/rdfeed-fetch" + install -Dm755 $(HOOK) "$(DESTDIR)$(LIBEXECDIR)/rdfeed-hook.so" + + # the daily refresh, and the icon of the workspace folders + install -Dm644 res/systemd/rdfeed-refresh.service "$(DESTDIR)$(USERUNITDIR)/rdfeed-refresh.service" + install -Dm644 res/systemd/rdfeed-refresh.timer "$(DESTDIR)$(USERUNITDIR)/rdfeed-refresh.timer" + sed -i -e 's|@BINDIR@|$(BINDIR)|g' "$(DESTDIR)$(USERUNITDIR)/rdfeed-refresh.service" + install -Dm644 res/rdfeed.svg "$(DESTDIR)$(ICONDIR)/rdfeed.svg" + + # translations + @for l in $(LINGUAS); do \ + if [ -f "po/$$l.mo" ]; then \ + install -Dm644 "po/$$l.mo" \ + "$(DESTDIR)$(LOCALEDIR)/$$l/LC_MESSAGES/rdfeed.mo"; \ + fi; \ + done + + # documentation + @if [ -f $(MANPAGE) ]; then \ + install -Dm644 $(MANPAGE) "$(DESTDIR)$(MANDIR)/man1/rdfeed.1"; \ + fi + install -Dm644 README.md "$(DESTDIR)$(DATADIR)/doc/rdfeed/README.md" + +uninstall: + rm -f "$(DESTDIR)$(BINDIR)/rdfeed" + rm -rf "$(DESTDIR)$(DATADIR)/rdfeed" + rm -rf "$(DESTDIR)$(LIBEXECDIR)" + rm -f "$(DESTDIR)$(USERUNITDIR)/rdfeed-refresh.service" + rm -f "$(DESTDIR)$(USERUNITDIR)/rdfeed-refresh.timer" + rm -f "$(DESTDIR)$(ICONDIR)/rdfeed.svg" + rm -f "$(DESTDIR)$(MANDIR)/man1/rdfeed.1" + rm -rf "$(DESTDIR)$(DATADIR)/doc/rdfeed" + @for l in $(LINGUAS); do rm -f "$(DESTDIR)$(LOCALEDIR)/$$l/LC_MESSAGES/rdfeed.mo"; done + +clean: + rm -rf $(BUILDDIR) po/*.mo $(MANPAGE) diff --git a/README.md b/README.md new file mode 100644 index 0000000..8a4d4dc --- /dev/null +++ b/README.md @@ -0,0 +1,152 @@ +

+ rdfeed +

+ +

rdfeed

+ +

The RemoteApps of your company, in the Linux app menu.

+ +

+ Word, Excel and the other Windows apps your company publishes open as windows of their own.
+ Like "Work Resources" on Windows, through FreeRDP. +

+ +
+ Install | + How to use | + Is it safe? | + Report a bug +
+ +

+ Buy me a coffee on Ko-fi +

+ +

+ The rdfeed menu in Konsole: one workspace, Contoso Apps, with 4 apps for CONTOSO\jdoe +

+ +## Install + +
+Arch, CachyOS, EndeavourOS, Manjaro + +```bash +yay -S rdfeed +``` + +
+ +
+Other distributions + +See [Build from source](#more). It needs FreeRDP 3, Python 3, bash and +`secret-tool` (libsecret). + +
+ +## How to use + +```bash +rdfeed +``` + +Press **1** and type the address your IT department gave you. The server +name or your work e-mail address often works too. Then sign in with your +Windows user name and password. + +The apps show up in your app menu, in a folder with the name of the +workspace. Click one and it opens in a window of its own. If your company uses +MFA, confirm the sign-in in your authenticator app. Many servers ask twice: a +notification tells you when the second prompt is coming. + +Files on your computer open in the apps too: right-click a `.docx` and pick +**Open with**, then Word. + +
+Settings + +

+ The settings in Konsole: share a folder, open files with the apps, size, sound, graphics, the wait for the second MFA prompt, and a daily refresh, with the selected one explained at the bottom +

+ +
+ +## Is it safe? + +Your password goes into the keyring, not into a file. FreeRDP gets it through +a pipe, so it never shows up in the list of running programs. + +The connection files from the server can ask to share every drive, camera and +USB device of your computer. rdfeed removes that. The apps only see the folder +you pick under **Share a folder**, and none by default. When you open a file in +an app, its folder is shared while that app runs. + +## More + +
+What it fixes in FreeRDP + +Starting a RemoteApp with plain `xfreerdp3` often goes wrong. rdfeed takes care of it: + +- **It hangs after the MFA prompt.** A connection broker needs a second sign-in + at the gateway. Its prompt only comes if the client waits about ten seconds, + and FreeRDP does not wait. rdfeed does. +- **Y and Z are swapped.** Under Wayland, FreeRDP sees the wrong keyboard layout. + rdfeed asks the desktop. +- **The apps are blurry or tiny** with a scaled desktop. rdfeed has the server + draw them at your scale. +- **Signing in to the feed fails.** It needs NTLM, which `curl` no longer + always has. rdfeed signs in by itself. + +
+ +
+Known problems + +- **A window stays partly empty after you minimize or resize it.** This is a + bug in FreeRDP. Under **Settings**, **Graphics** on classic paints every + window right, but some servers end the session with it. +- **A second app of the same workspace takes over the first one's connection.** + Its windows move into the new one, nothing closes. + +
+ +
+How it works + +| | | +|---|---| +| `rdfeed` | The menu and the commands. | +| `rdfeed-fetch` | Signs in to the feed and downloads the apps and their icons. | +| `rdfeed-hook.so` | Goes into FreeRDP: the wait for the second MFA prompt, and the classic graphics if you pick them. | +| `rdfeed-refresh.timer` | Loads the apps again once a day, so new ones show up. | + +The feed is the one Windows subscribes to under *RemoteApp and Desktop +Connections* (`https://server/RDWeb/Feed/webfeed.aspx`). Azure Virtual Desktop +and Windows 365 sign in another way and are not supported. All details: +`man rdfeed`. + +
+ +
+Build from source + +```bash +make +make test +sudo make install +``` + +Needs a C compiler, and for the translations and the man page gettext and +scdoc. To run it: bash, Python 3, FreeRDP 3 (`xfreerdp3` or `xfreerdp`) and +`secret-tool`. + +
+ +## Credits + +- [FreeRDP](https://www.freerdp.com/), which does the actual work. +- [MS-NLMP](https://learn.microsoft.com/openspecs/windows_protocols/ms-nlmp/), for NTLM and its test vectors. + +GPL-3.0-or-later. diff --git a/doc/rdfeed.1.scd b/doc/rdfeed.1.scd new file mode 100644 index 0000000..4d4315a --- /dev/null +++ b/doc/rdfeed.1.scd @@ -0,0 +1,173 @@ +rdfeed(1) + +# NAME + +rdfeed - the RemoteApps of your company in the Linux app menu + +# SYNOPSIS + +*rdfeed* [_command_] + +# DESCRIPTION + +Companies publish their Windows apps (Word, Excel, their own tools) as an RD +Web Access feed. Windows subscribes to it under "RemoteApp and Desktop +Connections" and puts the apps in the start menu, in a folder with the name of +the workspace. rdfeed does the same on Linux: each app gets an entry in the +app menu and opens as a window of its own, not as a whole Windows desktop. +The apps run on the company's servers, through FreeRDP. + +Run without a command it shows an interactive menu. Everything it can be told +is reachable from there; the files behind it do not need to be edited by +hand. + +# COMMANDS + +*add* [_address_] [*--user* _name_] + Add a workspace. The address is the one of the feed, as Windows takes it + (*https://server/RDWeb/Feed/webfeed.aspx*), or just the server name, or + the RD Web Access page, or a work e-mail address. For an e-mail address + the feed is looked up in the TXT record *\_msradc.*_domain_, as Windows + does it. The user name can be _DOMAIN\\name_, _name@domain_, or just the + name, which takes the server's own domain. + + Without a terminal, the user name and the password are read from stdin, + one per line. + +*refresh* [_workspace_] + Load the apps of every workspace again, or of the one given. Apps that + were added show up in the app menu, apps that were taken away disappear. + +*list* + The workspaces with their apps, each as _workspace/app_, the name *run* + takes. + +*run* _app_ [_file_] + Start an app. _app_ is enough when only one workspace has an app of that + name, otherwise _workspace/app_. This is what the app menu entries run. + + With a file, the app opens it. The folder of the file is shared as a + drive for that session, and the app is started with the path on it, + _\\\\tsclient\\folder\\file_, the way Windows does it. If the server + does not let the app take a file this way, it opens empty, and the file + is on that drive. + +*remove* _workspace_ + Remove a workspace: its app menu entries, its folder, the downloaded + files, the saved password. + +*-h*, *--help* + Show the help. + +*-V*, *--version* + Show the version. + +# SETTINGS + +*Share a folder* + Off, Documents, Downloads or the home folder. The apps see it as a drive + and can open and save files there. Off by default: then they only see the + files on the server. + +*Open files with the apps* + On by default. The app menu entries name the file types of each app, so + file managers offer it under "Open with", for example Word for .docx + files. The types come from the feed when the administrator entered them, + as Windows takes them. Most feeds have none, and then rdfeed knows the + usual ones: Word, Excel, PowerPoint, OneNote, Outlook, Visio, Access, + Project, Publisher, PDF readers, AutoCAD and DWG TrueView, Notepad. Your + default apps stay as they are. + +*Size* + How big the apps are drawn on the server. Automatic takes the scale your + desktop gives X11 programs that scale themselves (KDE Plasma's Xwayland + scale, or Xft.dpi), so the apps come out sharp and the right size. + +*Sound* + Play the sound of the apps on this computer. + +*Graphics* + GFX (the default) or classic. See *FREERDP PROBLEMS*. + +*Wait for the second MFA prompt* + Automatic (the default), always or never. See *FREERDP PROBLEMS*. + +*Refresh the apps every day* + A systemd user timer (*rdfeed-refresh.timer*) that loads the apps again + once a day, as Windows does. It is switched on with the first workspace. + When the password stops working it says so in a notification. + +# FREERDP PROBLEMS + +rdfeed starts each app with *xfreerdp3* (*xfreerdp* on Fedora) in RemoteApp +mode, with *rdfeed-hook.so* loaded into it. The hook works around two +problems that leave FreeRDP hanging or windows half painted: + +*The second MFA prompt* + Most companies run a connection broker, which sends the client on to the + server that hosts the session. FreeRDP then opens a second tunnel through + the RD Gateway at once. With MFA on the gateway (the Azure MFA extension + of NPS), that second sign-in needs a second prompt in the authenticator + app. When it comes less than about ten seconds after the first one was + confirmed, no prompt is sent and the gateway waits forever. So the hook + waits 12 seconds before a new gateway connection, and a notification says + a second prompt is coming. Automatic waits only when the first sign-in + took long enough for somebody to confirm a prompt. + +*Windows that stay empty* + In RemoteApp mode with the graphics pipeline (GFX), FreeRDP may not + repaint a window after it was minimized or resized. FreeRDP 3 has no + switch to turn GFX off. With *Graphics* on classic, the hook keeps it off + and every window paints right. But some servers end the session right + after signing in without GFX, so GFX stays the default. Not fixed yet. + +# HOW SAFE IS THIS + +The password goes into the keyring (the Secret Service, as GNOME Keyring or +KWallet provide it), never into a file. FreeRDP gets it on stdin, never on its +command line, so it shows in no process list. + +The .rdp files from the server can ask to share every drive, camera and USB +device of this computer. rdfeed removes those lines. Only the folder picked +under *Share a folder* is shared, and the folder of a file you open in an app, +for as long as that session runs. + +The server's certificate is trusted the first time and checked after that +(*/cert:tofu*). The feed itself is fetched over HTTPS with the system's +certificates. + +# FILES + +_~/.config/rdfeed/config_ + The settings. + +_~/.config/rdfeed/workspaces/_ + One file per workspace: the address, the name, the user. + +_~/.local/share/rdfeed/_ + The apps of each workspace: the .rdp files and the icons. + +_~/.local/share/applications/rdfeed-\*.desktop_ + The app menu entries, with a folder per workspace in + _~/.config/menus/applications-merged/_. + +_~/.cache/rdfeed/_ + The log of the last start of each app, for when something goes wrong. + +# LIMITS + +Only feeds that sign in with Windows (NTLM) authentication, which is what RD +Web Access uses. Azure Virtual Desktop and Windows 365 sign in with Microsoft +Entra and are not supported. + +Starting a second app of the same workspace opens a second connection, which +takes over the session of the first: its windows move into the new one. +Nothing closes. + +# SEE ALSO + +*xfreerdp3*(1), *secret-tool*(1) + +# AUTHORS + +Felitendo. Source and bug reports: https://github.com/LoonixTools/rdfeed diff --git a/po/de.po b/po/de.po new file mode 100644 index 0000000..f7fb8ec --- /dev/null +++ b/po/de.po @@ -0,0 +1,564 @@ +# German translation for rdfeed. +# Copyright (C) 2026 Felitendo +# This file is distributed under the same license as rdfeed. +# +msgid "" +msgstr "" +"Project-Id-Version: rdfeed 1.0.0\n" +"Report-Msgid-Bugs-To: https://github.com/LoonixTools/rdfeed/issues\n" +"POT-Creation-Date: 2026-10-02 10:29+0200\n" +"PO-Revision-Date: 2026-10-02 09:30+0200\n" +"Last-Translator: Felitendo\n" +"Language-Team: German\n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/rdfeed:79 src/rdfeed:103 +#, sh-printf-format +msgid "Could not write to %s." +msgstr "Konnte nicht nach %s schreiben." + +#: src/rdfeed:82 +#, sh-printf-format +msgid "Signing in to %sโ€ฆ" +msgstr "Anmeldung bei %sโ€ฆ" + +#: src/rdfeed:100 +msgid "" +"The password could not be saved in the keyring, so each app asks for it." +msgstr "" +"Das Passwort konnte nicht im Schlรผsselbund gespeichert werden, darum fragt " +"jede App danach." + +#: src/rdfeed:111 +#, sh-printf-format +msgid "1 app from %s is now in your app menu." +msgstr "1 App von %s ist jetzt in deinem Anwendungsmenรผ." + +#: src/rdfeed:113 +#, sh-printf-format +msgid "%d apps from %s are now in your app menu." +msgstr "%d Apps von %s sind jetzt in deinem Anwendungsmenรผ." + +#: src/rdfeed:129 +msgid "" +"Your IT department has the address. A work e-mail address often works too." +msgstr "" +"Die Adresse bekommst du von deiner IT-Abteilung. Oft klappt auch deine " +"Firmen-E-Mail-Adresse." + +#: src/rdfeed:130 +msgid "Feed address, server or e-mail:" +msgstr "Feed-Adresse, Server oder E-Mail:" + +#: src/rdfeed:137 +#, sh-printf-format +msgid "No feed is registered for %s. Ask your IT department for the address." +msgstr "" +"Fรผr %s ist kein Feed hinterlegt. Frag deine IT-Abteilung nach der Adresse." + +#: src/rdfeed:142 src/rdfeed:156 +msgid "User name (DOMAIN\\name or name@domain):" +msgstr "Benutzername (DOMร„NE\\name oder name@domรคne):" + +#: src/rdfeed:146 src/rdfeed:159 +msgid "Password:" +msgstr "Passwort:" + +#: src/rdfeed:178 src/rdfeed:236 +#, sh-printf-format +msgid "No workspace yet. Add one with `%s add`." +msgstr "Noch kein Arbeitsbereich. Fรผge einen mit `%s add` hinzu." + +#: src/rdfeed:187 src/lib/launch.sh:220 +#, sh-printf-format +msgid "No password saved for %s. Sign in again in rdfeed." +msgstr "Fรผr %s ist kein Passwort gespeichert. Melde dich in rdfeed neu an." + +#: src/rdfeed:191 src/lib/launch.sh:149 +#, sh-printf-format +msgid "Password for %s:" +msgstr "Passwort fรผr %s:" + +#: src/rdfeed:195 +#, sh-printf-format +msgid "Refreshing %sโ€ฆ" +msgstr "%s wird aktualisiertโ€ฆ" + +#: src/rdfeed:205 +msgid "The password no longer works. Sign in again in rdfeed." +msgstr "Das Passwort funktioniert nicht mehr. Melde dich in rdfeed neu an." + +#: src/rdfeed:214 +#, sh-printf-format +msgid "%s: %d apps" +msgstr "%s: %d Apps" + +#: src/rdfeed:222 +#, sh-printf-format +msgid "Which workspace? See `%s list` for the names." +msgstr "Welcher Arbeitsbereich? Die Namen zeigt `%s list`." + +#: src/rdfeed:229 +#, sh-printf-format +msgid "%s and its apps are removed." +msgstr "%s und seine Apps sind entfernt." + +#: src/rdfeed:253 +#, sh-printf-format +msgid "Which app? See `%s list`." +msgstr "Welche App? Siehe `%s list`." + +#: src/rdfeed:268 src/lib/launch.sh:199 +#, sh-printf-format +msgid "There is no app %s. See `%s list`." +msgstr "Es gibt keine App %s. Siehe `%s list`." + +#: src/rdfeed:270 +#, sh-printf-format +msgid "Several workspaces have %s. Write it as WORKSPACE/APP, see `%s list`." +msgstr "" +"Mehrere Arbeitsbereiche haben %s. Schreib es als ARBEITSBEREICH/APP, siehe " +"`%s list`." + +#: src/rdfeed:278 +msgid "Usage: rdfeed [command]" +msgstr "Aufruf: rdfeed [Befehl]" + +#: src/rdfeed:280 +msgid "Commands:" +msgstr "Befehle:" + +#: src/rdfeed:281 +msgid "Add the workspace of your company" +msgstr "Den Arbeitsbereich deiner Firma hinzufรผgen" + +#: src/rdfeed:282 +msgid "Load the apps again" +msgstr "Die Apps neu laden" + +#: src/rdfeed:283 +msgid "List the workspaces and their apps" +msgstr "Arbeitsbereiche und ihre Apps auflisten" + +#: src/rdfeed:284 +msgid "Start an app, or open a file with it" +msgstr "Eine App starten oder eine Datei damit รถffnen" + +#: src/rdfeed:285 +msgid "Remove a workspace and its apps" +msgstr "Einen Arbeitsbereich mit seinen Apps entfernen" + +#: src/rdfeed:286 +msgid "Show this help" +msgstr "Diese Hilfe anzeigen" + +#: src/rdfeed:287 +msgid "Show the version" +msgstr "Die Version anzeigen" + +#: src/rdfeed:289 +msgid "Without a command an interactive menu is shown." +msgstr "Ohne Befehl wird ein interaktives Menรผ angezeigt." + +#: src/rdfeed:313 +#, sh-printf-format +msgid "Unknown command: %s" +msgstr "Unbekannter Befehl: %s" + +#: src/lib/common.sh:174 src/lib/menu.sh:251 +msgid "never" +msgstr "nie" + +#: src/lib/common.sh:184 +msgid "just now" +msgstr "gerade eben" + +#: src/lib/common.sh:186 +msgid "1 minute ago" +msgstr "vor 1 Minute" + +#: src/lib/common.sh:189 +#, sh-printf-format +msgid "%d minutes ago" +msgstr "vor %d Minuten" + +#: src/lib/common.sh:191 +msgid "1 hour ago" +msgstr "vor 1 Stunde" + +#: src/lib/common.sh:194 +#, sh-printf-format +msgid "%d hours ago" +msgstr "vor %d Stunden" + +#: src/lib/common.sh:196 +msgid "1 day ago" +msgstr "vor 1 Tag" + +#: src/lib/common.sh:199 +#, sh-printf-format +msgid "%d days ago" +msgstr "vor %d Tagen" + +#: src/lib/launch.sh:169 +msgid "" +"Your password has expired. Change it on Windows or on the RD Web page, then " +"sign in again in rdfeed." +msgstr "" +"Dein Passwort ist abgelaufen. ร„ndere es unter Windows oder auf der RD-Web-" +"Seite und melde dich dann in rdfeed neu an." + +#: src/lib/launch.sh:171 +msgid "Your account is locked. Ask your IT department." +msgstr "Dein Konto ist gesperrt. Frag deine IT-Abteilung." + +#: src/lib/launch.sh:173 +msgid "The server did not accept the password. Sign in again in rdfeed." +msgstr "" +"Der Server hat das Passwort nicht angenommen. Melde dich in rdfeed neu an." + +#: src/lib/launch.sh:175 +msgid "Could not reach the server. Check the internet connection." +msgstr "Der Server ist nicht erreichbar. Prรผf die Internetverbindung." + +#: src/lib/launch.sh:178 +#, sh-printf-format +msgid "FreeRDP stopped: %s" +msgstr "FreeRDP hat aufgegeben: %s" + +#: src/lib/launch.sh:205 src/lib/launch.sh:206 +#, sh-printf-format +msgid "File not found: %s" +msgstr "Datei nicht gefunden: %s" + +#: src/lib/launch.sh:212 src/lib/launch.sh:213 +msgid "" +"FreeRDP 3 is missing. Install it to start the apps (the package is called " +"freerdp)." +msgstr "" +"FreeRDP 3 fehlt. Installiere es, um die Apps zu starten (das Paket heiรŸt " +"freerdp)." + +#: src/lib/launch.sh:277 +#, sh-printf-format +msgid "Opening %s in %s" +msgstr "%s wird in %s geรถffnet" + +#: src/lib/launch.sh:278 +#, sh-printf-format +msgid "If it does not open by itself, open it in the app from the drive %s." +msgstr "" +"Falls sie sich nicht von selbst รถffnet, รถffne sie in der App vom Laufwerk %s." + +#: src/lib/launch.sh:280 +#, sh-printf-format +msgid "Starting %s" +msgstr "%s wird gestartet" + +#: src/lib/launch.sh:281 +msgid "" +"If your company uses MFA, confirm the sign-in in your authenticator app." +msgstr "" +"Wenn deine Firma MFA nutzt, bestรคtige die Anmeldung in deiner Authenticator-" +"App." + +#: src/lib/launch.sh:290 +msgid "A second sign-in is coming" +msgstr "Gleich kommt eine zweite Anmeldung" + +#: src/lib/launch.sh:291 +#, sh-printf-format +msgid "" +"Confirm the second prompt in your authenticator app too. It comes in about " +"%d seconds." +msgstr "" +"Bestรคtige auch die zweite Anfrage in deiner Authenticator-App. Sie kommt in " +"etwa %d Sekunden." + +#: src/lib/launch.sh:301 +#, sh-printf-format +msgid "%s could not start" +msgstr "%s konnte nicht starten" + +#: src/lib/launch.sh:303 +#, sh-printf-format +msgid "Log: %s" +msgstr "Protokoll: %s" + +#. TRANSLATORS: the letter after "[" is the key for yes. y works too. +#: src/lib/menu.sh:93 +msgid "[y/N]" +msgstr "[j/N]" + +#: src/lib/menu.sh:163 +msgid "1 app" +msgstr "1 App" + +#: src/lib/menu.sh:165 +#, sh-printf-format +msgid "%d apps" +msgstr "%d Apps" + +#: src/lib/menu.sh:190 +msgid "No workspace yet. Add the one of your company with [1]." +msgstr "Noch kein Arbeitsbereich. Fรผge den deiner Firma mit [1] hinzu." + +#: src/lib/menu.sh:233 +msgid "off" +msgstr "aus" + +#: src/lib/menu.sh:234 +msgid "home folder" +msgstr "Persรถnlicher Ordner" + +#: src/lib/menu.sh:237 +msgid "Documents" +msgstr "Dokumente" + +#: src/lib/menu.sh:238 +msgid "Downloads" +msgstr "Downloads" + +#: src/lib/menu.sh:243 +msgid "not found" +msgstr "nicht gefunden" + +#: src/lib/menu.sh:246 +#, sh-printf-format +msgid "automatic (%d %%)" +msgstr "automatisch (%d %%)" + +#: src/lib/menu.sh:248 +msgid "classic" +msgstr "klassisch" + +#: src/lib/menu.sh:249 +msgid "GFX" +msgstr "GFX" + +#: src/lib/menu.sh:250 +msgid "always" +msgstr "immer" + +#: src/lib/menu.sh:252 +msgid "automatic" +msgstr "automatisch" + +#: src/lib/menu.sh:261 +msgid "" +"The apps only see the files on the server. Pick a folder to open and save " +"files of this computer in them." +msgstr "" +"Die Apps sehen nur die Dateien auf dem Server. Wรคhl einen Ordner, damit sie " +"Dateien dieses Computers รถffnen und speichern kรถnnen." + +#: src/lib/menu.sh:262 +msgid "" +"The apps can open and save files in this folder. It shows up as a drive in " +"them." +msgstr "" +"Die Apps kรถnnen in diesem Ordner Dateien รถffnen und speichern. Er erscheint " +"in ihnen als Laufwerk." + +#: src/lib/menu.sh:263 +msgid "" +"Offers the apps under \"Open with\" for their file types, for example Word " +"for .docx. The folder of the file is shared with the app while it is open." +msgstr "" +"Bietet die Apps unter โ€žร–ffnen mitโ€œ fรผr ihre Dateitypen an, zum Beispiel Word " +"fรผr .docx. Der Ordner der Datei wird freigegeben, solange sie offen ist." + +#: src/lib/menu.sh:264 +msgid "" +"How big the apps are drawn. Automatic follows the scale of your desktop, so " +"they are sharp and the right size." +msgstr "" +"Wie groรŸ die Apps gezeichnet werden. Automatisch folgt der Skalierung deines " +"Desktops, so sind sie scharf und richtig groรŸ." + +#: src/lib/menu.sh:265 +msgid "Plays the sound of the apps here, for calls and videos." +msgstr "Spielt den Ton der Apps hier ab, fรผr Anrufe und Videos." + +#: src/lib/menu.sh:266 +msgid "" +"Paints every window right. But some servers end the session right after " +"signing in, then go back to GFX." +msgstr "Zeichnet jedes Fenster richtig. Manche Server beenden die Sitzung aber direkt nach der Anmeldung, dann zurรผck auf GFX." + +#: src/lib/menu.sh:267 +msgid "" +"Works with every server. FreeRDP may not repaint a window after it was " +"minimized or resized." +msgstr "Geht mit jedem Server. FreeRDP zeichnet ein Fenster nach dem Minimieren oder GrรถรŸe-ร„ndern eventuell nicht neu." + +#: src/lib/menu.sh:268 +msgid "" +"Always waits before the second sign-in. For when the second prompt does not " +"come with automatic." +msgstr "" +"Wartet immer vor der zweiten Anmeldung. Fรผr den Fall, dass mit automatisch " +"keine zweite Anfrage kommt." + +#: src/lib/menu.sh:269 +msgid "Never waits. Right when your company has no MFA." +msgstr "Wartet nie. Richtig, wenn deine Firma kein MFA nutzt." + +#: src/lib/menu.sh:270 +msgid "" +"Many servers with MFA need a second sign-in, and its prompt only comes after " +"a short wait. Automatic waits when the first sign-in took a while." +msgstr "" +"Viele Server mit MFA brauchen eine zweite Anmeldung, und deren Anfrage kommt " +"erst nach kurzem Warten. Automatisch wartet, wenn die erste Anmeldung etwas " +"gedauert hat." + +#: src/lib/menu.sh:273 +msgid "" +"Loads the list of apps again once a day, so new apps show up by themselves." +msgstr "" +"Lรคdt die Liste der Apps einmal am Tag neu, so tauchen neue Apps von selbst " +"auf." + +#: src/lib/menu.sh:275 src/lib/menu.sh:330 +msgid "Needs a systemd user session." +msgstr "Braucht eine systemd-Benutzersitzung." + +#: src/lib/menu.sh:322 src/lib/menu.sh:325 +msgid "Could not save the setting." +msgstr "Einstellung konnte nicht gespeichert werden." + +#: src/lib/menu.sh:371 src/lib/menu.sh:609 +msgid "Settings" +msgstr "Einstellungen" + +#: src/lib/menu.sh:372 +msgid "โ†‘โ†“ select โ†โ†’ or Space: change q: back" +msgstr "โ†‘โ†“ auswรคhlen โ†โ†’ oder Leertaste: รคndern q: zurรผck" + +#: src/lib/menu.sh:373 +msgid "ON" +msgstr "AN" + +#: src/lib/menu.sh:374 +msgid "OFF" +msgstr "AUS" + +#: src/lib/menu.sh:456 src/lib/menu.sh:608 +msgid "Workspaces" +msgstr "Arbeitsbereiche" + +#. TRANSLATORS: keep the letters, they are the keys. +#: src/lib/menu.sh:458 +msgid "" +"โ†‘โ†“ select r: refresh s: sign in again d: remove a: add q: back" +msgstr "" +"โ†‘โ†“ auswรคhlen r: aktualisieren s: neu anmelden d: entfernen a: " +"hinzufรผgen q: zurรผck" + +#: src/lib/menu.sh:459 +msgid "No workspace yet. Press a to add one." +msgstr "Noch kein Arbeitsbereich. Drรผck a, um einen hinzuzufรผgen." + +#: src/lib/menu.sh:484 +#, sh-printf-format +msgid "refreshed %s" +msgstr "aktualisiert %s" + +#: src/lib/menu.sh:512 +#, sh-printf-format +msgid "Remove \"%s\" and its apps?" +msgstr "โ€ž%sโ€œ und seine Apps entfernen?" + +#: src/lib/menu.sh:536 src/lib/menu.sh:607 +msgid "Start an app" +msgstr "Eine App starten" + +#: src/lib/menu.sh:537 +msgid "โ†‘โ†“ select Enter: start q: back" +msgstr "โ†‘โ†“ auswรคhlen Enter: starten q: zurรผck" + +#: src/lib/menu.sh:538 +msgid "No apps yet. Add a workspace first." +msgstr "Noch keine Apps. Fรผge zuerst einen Arbeitsbereich hinzu." + +#: src/lib/menu.sh:579 +#, sh-printf-format +msgid "%s is starting. It opens in a window of its own." +msgstr "%s startet. Es รถffnet sich in einem eigenen Fenster." + +#: src/lib/menu.sh:606 +msgid "Add a workspace" +msgstr "Arbeitsbereich hinzufรผgen" + +#: src/lib/menu.sh:610 +msgid "Quit" +msgstr "Beenden" + +#: src/lib/workspace.sh:163 +msgid "The server did not accept the user name or password." +msgstr "Der Server hat Benutzername oder Passwort nicht angenommen." + +#: src/lib/workspace.sh:164 +#, sh-printf-format +msgid "Could not reach the server: %s" +msgstr "Server nicht erreichbar: %s" + +#: src/lib/workspace.sh:165 +#, sh-printf-format +msgid "There is no RD Web feed at %s." +msgstr "Unter %s gibt es keinen RD-Web-Feed." + +#: src/lib/workspace.sh:166 +#, sh-printf-format +msgid "Could not load the apps: %s" +msgstr "Die Apps konnten nicht geladen werden: %s" + +#: src/lib/workspace.sh:282 +#, sh-printf-format +msgid "Remote desktop of %s" +msgstr "Remotedesktop von %s" + +#: src/lib/workspace.sh:284 +#, sh-printf-format +msgid "%s from %s" +msgstr "%s von %s" + +#: src/lib/menu.sh +msgid "Apps" +msgstr "Apps" + +#: src/lib/menu.sh +msgid "Share a folder" +msgstr "Ordner freigeben" + +#: src/lib/menu.sh +msgid "Open files with the apps" +msgstr "Dateien mit den Apps รถffnen" + +#: src/lib/menu.sh +msgid "Size" +msgstr "GrรถรŸe" + +#: src/lib/menu.sh +msgid "Sound" +msgstr "Ton" + +#: src/lib/menu.sh +msgid "Graphics" +msgstr "Grafik" + +#: src/lib/menu.sh +msgid "Signing in" +msgstr "Anmeldung" + +#: src/lib/menu.sh +msgid "Wait for the second MFA prompt" +msgstr "Auf zweite MFA-Anfrage warten" + +#: src/lib/menu.sh +msgid "Refresh the apps every day" +msgstr "Apps tรคglich aktualisieren" diff --git a/po/es.po b/po/es.po new file mode 100644 index 0000000..86bbbd2 --- /dev/null +++ b/po/es.po @@ -0,0 +1,565 @@ +# Spanish translation for rdfeed. +# Copyright (C) 2026 Felitendo +# This file is distributed under the same license as rdfeed. +# +msgid "" +msgstr "" +"Project-Id-Version: rdfeed 1.0.0\n" +"Report-Msgid-Bugs-To: https://github.com/LoonixTools/rdfeed/issues\n" +"POT-Creation-Date: 2026-10-02 10:29+0200\n" +"PO-Revision-Date: 2026-10-02 09:30+0200\n" +"Last-Translator: Felitendo\n" +"Language-Team: Spanish\n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/rdfeed:79 src/rdfeed:103 +#, sh-printf-format +msgid "Could not write to %s." +msgstr "No se pudo escribir en %s." + +#: src/rdfeed:82 +#, sh-printf-format +msgid "Signing in to %sโ€ฆ" +msgstr "Iniciando sesiรณn en %sโ€ฆ" + +#: src/rdfeed:100 +msgid "" +"The password could not be saved in the keyring, so each app asks for it." +msgstr "" +"No se pudo guardar la contraseรฑa en el llavero, asรญ que cada app la pedirรก." + +#: src/rdfeed:111 +#, sh-printf-format +msgid "1 app from %s is now in your app menu." +msgstr "1 app de %s ya estรก en tu menรบ de aplicaciones." + +#: src/rdfeed:113 +#, sh-printf-format +msgid "%d apps from %s are now in your app menu." +msgstr "%d apps de %s ya estรกn en tu menรบ de aplicaciones." + +#: src/rdfeed:129 +msgid "" +"Your IT department has the address. A work e-mail address often works too." +msgstr "" +"Tu departamento de TI tiene la direcciรณn. A menudo tambiรฉn sirve tu correo " +"del trabajo." + +#: src/rdfeed:130 +msgid "Feed address, server or e-mail:" +msgstr "Direcciรณn del feed, servidor o correo:" + +#: src/rdfeed:137 +#, sh-printf-format +msgid "No feed is registered for %s. Ask your IT department for the address." +msgstr "" +"No hay ningรบn feed registrado para %s. Pide la direcciรณn a tu departamento " +"de TI." + +#: src/rdfeed:142 src/rdfeed:156 +msgid "User name (DOMAIN\\name or name@domain):" +msgstr "Nombre de usuario (DOMINIO\\nombre o nombre@dominio):" + +#: src/rdfeed:146 src/rdfeed:159 +msgid "Password:" +msgstr "Contraseรฑa:" + +#: src/rdfeed:178 src/rdfeed:236 +#, sh-printf-format +msgid "No workspace yet. Add one with `%s add`." +msgstr "Todavรญa no hay ningรบn espacio de trabajo. Aรฑade uno con `%s add`." + +#: src/rdfeed:187 src/lib/launch.sh:220 +#, sh-printf-format +msgid "No password saved for %s. Sign in again in rdfeed." +msgstr "" +"No hay ninguna contraseรฑa guardada para %s. Vuelve a iniciar sesiรณn en " +"rdfeed." + +#: src/rdfeed:191 src/lib/launch.sh:149 +#, sh-printf-format +msgid "Password for %s:" +msgstr "Contraseรฑa de %s:" + +#: src/rdfeed:195 +#, sh-printf-format +msgid "Refreshing %sโ€ฆ" +msgstr "Actualizando %sโ€ฆ" + +#: src/rdfeed:205 +msgid "The password no longer works. Sign in again in rdfeed." +msgstr "La contraseรฑa ya no funciona. Vuelve a iniciar sesiรณn en rdfeed." + +#: src/rdfeed:214 +#, sh-printf-format +msgid "%s: %d apps" +msgstr "%s: %d apps" + +#: src/rdfeed:222 +#, sh-printf-format +msgid "Which workspace? See `%s list` for the names." +msgstr "ยฟQuรฉ espacio de trabajo? Los nombres estรกn en `%s list`." + +#: src/rdfeed:229 +#, sh-printf-format +msgid "%s and its apps are removed." +msgstr "%s y sus apps se han quitado." + +#: src/rdfeed:253 +#, sh-printf-format +msgid "Which app? See `%s list`." +msgstr "ยฟQuรฉ app? Mira `%s list`." + +#: src/rdfeed:268 src/lib/launch.sh:199 +#, sh-printf-format +msgid "There is no app %s. See `%s list`." +msgstr "No existe la app %s. Mira `%s list`." + +#: src/rdfeed:270 +#, sh-printf-format +msgid "Several workspaces have %s. Write it as WORKSPACE/APP, see `%s list`." +msgstr "" +"Varios espacios de trabajo tienen %s. Escrรญbelo como ESPACIO/APP, mira `%s " +"list`." + +#: src/rdfeed:278 +msgid "Usage: rdfeed [command]" +msgstr "Uso: rdfeed [comando]" + +#: src/rdfeed:280 +msgid "Commands:" +msgstr "Comandos:" + +#: src/rdfeed:281 +msgid "Add the workspace of your company" +msgstr "Aรฑadir el espacio de trabajo de tu empresa" + +#: src/rdfeed:282 +msgid "Load the apps again" +msgstr "Volver a cargar las apps" + +#: src/rdfeed:283 +msgid "List the workspaces and their apps" +msgstr "Listar los espacios de trabajo y sus apps" + +#: src/rdfeed:284 +msgid "Start an app, or open a file with it" +msgstr "Abrir una app, o un archivo con ella" + +#: src/rdfeed:285 +msgid "Remove a workspace and its apps" +msgstr "Quitar un espacio de trabajo y sus apps" + +#: src/rdfeed:286 +msgid "Show this help" +msgstr "Mostrar esta ayuda" + +#: src/rdfeed:287 +msgid "Show the version" +msgstr "Mostrar la versiรณn" + +#: src/rdfeed:289 +msgid "Without a command an interactive menu is shown." +msgstr "Sin comando se muestra un menรบ interactivo." + +#: src/rdfeed:313 +#, sh-printf-format +msgid "Unknown command: %s" +msgstr "Comando desconocido: %s" + +#: src/lib/common.sh:174 src/lib/menu.sh:251 +msgid "never" +msgstr "nunca" + +#: src/lib/common.sh:184 +msgid "just now" +msgstr "ahora mismo" + +#: src/lib/common.sh:186 +msgid "1 minute ago" +msgstr "hace 1 minuto" + +#: src/lib/common.sh:189 +#, sh-printf-format +msgid "%d minutes ago" +msgstr "hace %d minutos" + +#: src/lib/common.sh:191 +msgid "1 hour ago" +msgstr "hace 1 hora" + +#: src/lib/common.sh:194 +#, sh-printf-format +msgid "%d hours ago" +msgstr "hace %d horas" + +#: src/lib/common.sh:196 +msgid "1 day ago" +msgstr "hace 1 dรญa" + +#: src/lib/common.sh:199 +#, sh-printf-format +msgid "%d days ago" +msgstr "hace %d dรญas" + +#: src/lib/launch.sh:169 +msgid "" +"Your password has expired. Change it on Windows or on the RD Web page, then " +"sign in again in rdfeed." +msgstr "" +"Tu contraseรฑa ha caducado. Cรกmbiala en Windows o en la pรกgina de RD Web y " +"luego vuelve a iniciar sesiรณn en rdfeed." + +#: src/lib/launch.sh:171 +msgid "Your account is locked. Ask your IT department." +msgstr "Tu cuenta estรก bloqueada. Pregunta a tu departamento de TI." + +#: src/lib/launch.sh:173 +msgid "The server did not accept the password. Sign in again in rdfeed." +msgstr "" +"El servidor no aceptรณ la contraseรฑa. Vuelve a iniciar sesiรณn en rdfeed." + +#: src/lib/launch.sh:175 +msgid "Could not reach the server. Check the internet connection." +msgstr "No se pudo conectar con el servidor. Comprueba la conexiรณn a internet." + +#: src/lib/launch.sh:178 +#, sh-printf-format +msgid "FreeRDP stopped: %s" +msgstr "FreeRDP se detuvo: %s" + +#: src/lib/launch.sh:205 src/lib/launch.sh:206 +#, sh-printf-format +msgid "File not found: %s" +msgstr "No se encontrรณ el archivo: %s" + +#: src/lib/launch.sh:212 src/lib/launch.sh:213 +msgid "" +"FreeRDP 3 is missing. Install it to start the apps (the package is called " +"freerdp)." +msgstr "" +"Falta FreeRDP 3. Instรกlalo para abrir las apps (el paquete se llama freerdp)." + +#: src/lib/launch.sh:277 +#, sh-printf-format +msgid "Opening %s in %s" +msgstr "Abriendo %s en %s" + +#: src/lib/launch.sh:278 +#, sh-printf-format +msgid "If it does not open by itself, open it in the app from the drive %s." +msgstr "Si no se abre solo, รกbrelo en la app desde la unidad %s." + +#: src/lib/launch.sh:280 +#, sh-printf-format +msgid "Starting %s" +msgstr "Abriendo %s" + +#: src/lib/launch.sh:281 +msgid "" +"If your company uses MFA, confirm the sign-in in your authenticator app." +msgstr "" +"Si tu empresa usa MFA, confirma el inicio de sesiรณn en tu app de " +"autenticaciรณn." + +#: src/lib/launch.sh:290 +msgid "A second sign-in is coming" +msgstr "Viene un segundo inicio de sesiรณn" + +#: src/lib/launch.sh:291 +#, sh-printf-format +msgid "" +"Confirm the second prompt in your authenticator app too. It comes in about " +"%d seconds." +msgstr "" +"Confirma tambiรฉn la segunda solicitud en tu app de autenticaciรณn. Llega en " +"unos %d segundos." + +#: src/lib/launch.sh:301 +#, sh-printf-format +msgid "%s could not start" +msgstr "%s no se pudo abrir" + +#: src/lib/launch.sh:303 +#, sh-printf-format +msgid "Log: %s" +msgstr "Registro: %s" + +#. TRANSLATORS: the letter after "[" is the key for yes. y works too. +#: src/lib/menu.sh:93 +msgid "[y/N]" +msgstr "[s/N]" + +#: src/lib/menu.sh:163 +msgid "1 app" +msgstr "1 app" + +#: src/lib/menu.sh:165 +#, sh-printf-format +msgid "%d apps" +msgstr "%d apps" + +#: src/lib/menu.sh:190 +msgid "No workspace yet. Add the one of your company with [1]." +msgstr "" +"Todavรญa no hay ningรบn espacio de trabajo. Aรฑade el de tu empresa con [1]." + +#: src/lib/menu.sh:233 +msgid "off" +msgstr "desactivado" + +#: src/lib/menu.sh:234 +msgid "home folder" +msgstr "carpeta personal" + +#: src/lib/menu.sh:237 +msgid "Documents" +msgstr "Documentos" + +#: src/lib/menu.sh:238 +msgid "Downloads" +msgstr "Descargas" + +#: src/lib/menu.sh:243 +msgid "not found" +msgstr "no encontrada" + +#: src/lib/menu.sh:246 +#, sh-printf-format +msgid "automatic (%d %%)" +msgstr "automรกtico (%d %%)" + +#: src/lib/menu.sh:248 +msgid "classic" +msgstr "clรกsico" + +#: src/lib/menu.sh:249 +msgid "GFX" +msgstr "GFX" + +#: src/lib/menu.sh:250 +msgid "always" +msgstr "siempre" + +#: src/lib/menu.sh:252 +msgid "automatic" +msgstr "automรกtico" + +#: src/lib/menu.sh:261 +msgid "" +"The apps only see the files on the server. Pick a folder to open and save " +"files of this computer in them." +msgstr "" +"Las apps solo ven los archivos del servidor. Elige una carpeta para que " +"puedan abrir y guardar archivos de este equipo." + +#: src/lib/menu.sh:262 +msgid "" +"The apps can open and save files in this folder. It shows up as a drive in " +"them." +msgstr "" +"Las apps pueden abrir y guardar archivos en esta carpeta. Aparece en ellas " +"como una unidad." + +#: src/lib/menu.sh:263 +msgid "" +"Offers the apps under \"Open with\" for their file types, for example Word " +"for .docx. The folder of the file is shared with the app while it is open." +msgstr "" +"Ofrece las apps en ยซAbrir conยป para sus tipos de archivo, por ejemplo Word " +"para .docx. La carpeta del archivo se comparte mientras estรก abierto." + +#: src/lib/menu.sh:264 +msgid "" +"How big the apps are drawn. Automatic follows the scale of your desktop, so " +"they are sharp and the right size." +msgstr "" +"Quรฉ tamaรฑo tienen las apps. Automรกtico sigue la escala de tu escritorio, asรญ " +"se ven nรญtidas y del tamaรฑo correcto." + +#: src/lib/menu.sh:265 +msgid "Plays the sound of the apps here, for calls and videos." +msgstr "Reproduce aquรญ el sonido de las apps, para llamadas y vรญdeos." + +#: src/lib/menu.sh:266 +msgid "" +"Paints every window right. But some servers end the session right after " +"signing in, then go back to GFX." +msgstr "Dibuja bien cada ventana. Pero algunos servidores cierran la sesiรณn justo despuรฉs de entrar; entonces vuelve a GFX." + +#: src/lib/menu.sh:267 +msgid "" +"Works with every server. FreeRDP may not repaint a window after it was " +"minimized or resized." +msgstr "Funciona con cualquier servidor. Puede que FreeRDP no vuelva a dibujar una ventana tras minimizarla o cambiar su tamaรฑo." + +#: src/lib/menu.sh:268 +msgid "" +"Always waits before the second sign-in. For when the second prompt does not " +"come with automatic." +msgstr "" +"Espera siempre antes del segundo inicio de sesiรณn. Para cuando con " +"automรกtico no llega la segunda solicitud." + +#: src/lib/menu.sh:269 +msgid "Never waits. Right when your company has no MFA." +msgstr "Nunca espera. Lo adecuado si tu empresa no usa MFA." + +#: src/lib/menu.sh:270 +msgid "" +"Many servers with MFA need a second sign-in, and its prompt only comes after " +"a short wait. Automatic waits when the first sign-in took a while." +msgstr "" +"Muchos servidores con MFA piden un segundo inicio de sesiรณn, y su solicitud " +"solo llega tras una breve espera. Automรกtico espera si el primero tardรณ un " +"poco." + +#: src/lib/menu.sh:273 +msgid "" +"Loads the list of apps again once a day, so new apps show up by themselves." +msgstr "" +"Vuelve a cargar la lista de apps una vez al dรญa, asรญ las nuevas aparecen " +"solas." + +#: src/lib/menu.sh:275 src/lib/menu.sh:330 +msgid "Needs a systemd user session." +msgstr "Necesita una sesiรณn de usuario de systemd." + +#: src/lib/menu.sh:322 src/lib/menu.sh:325 +msgid "Could not save the setting." +msgstr "No se pudo guardar el ajuste." + +#: src/lib/menu.sh:371 src/lib/menu.sh:609 +msgid "Settings" +msgstr "Ajustes" + +#: src/lib/menu.sh:372 +msgid "โ†‘โ†“ select โ†โ†’ or Space: change q: back" +msgstr "โ†‘โ†“ elegir โ†โ†’ o Espacio: cambiar q: volver" + +#: src/lib/menu.sh:373 +msgid "ON" +msgstr "ACTIVADO" + +#: src/lib/menu.sh:374 +msgid "OFF" +msgstr "DESACTIVADO" + +#: src/lib/menu.sh:456 src/lib/menu.sh:608 +msgid "Workspaces" +msgstr "Espacios de trabajo" + +#. TRANSLATORS: keep the letters, they are the keys. +#: src/lib/menu.sh:458 +msgid "" +"โ†‘โ†“ select r: refresh s: sign in again d: remove a: add q: back" +msgstr "" +"โ†‘โ†“ elegir r: actualizar s: volver a entrar d: quitar a: aรฑadir q: " +"volver" + +#: src/lib/menu.sh:459 +msgid "No workspace yet. Press a to add one." +msgstr "Todavรญa no hay ningรบn espacio de trabajo. Pulsa a para aรฑadir uno." + +#: src/lib/menu.sh:484 +#, sh-printf-format +msgid "refreshed %s" +msgstr "actualizado %s" + +#: src/lib/menu.sh:512 +#, sh-printf-format +msgid "Remove \"%s\" and its apps?" +msgstr "ยฟQuitar ยซ%sยป y sus apps?" + +#: src/lib/menu.sh:536 src/lib/menu.sh:607 +msgid "Start an app" +msgstr "Abrir una app" + +#: src/lib/menu.sh:537 +msgid "โ†‘โ†“ select Enter: start q: back" +msgstr "โ†‘โ†“ elegir Intro: abrir q: volver" + +#: src/lib/menu.sh:538 +msgid "No apps yet. Add a workspace first." +msgstr "Todavรญa no hay apps. Aรฑade primero un espacio de trabajo." + +#: src/lib/menu.sh:579 +#, sh-printf-format +msgid "%s is starting. It opens in a window of its own." +msgstr "%s se estรก abriendo. Se abre en una ventana propia." + +#: src/lib/menu.sh:606 +msgid "Add a workspace" +msgstr "Aรฑadir un espacio de trabajo" + +#: src/lib/menu.sh:610 +msgid "Quit" +msgstr "Salir" + +#: src/lib/workspace.sh:163 +msgid "The server did not accept the user name or password." +msgstr "El servidor no aceptรณ el nombre de usuario o la contraseรฑa." + +#: src/lib/workspace.sh:164 +#, sh-printf-format +msgid "Could not reach the server: %s" +msgstr "No se pudo conectar con el servidor: %s" + +#: src/lib/workspace.sh:165 +#, sh-printf-format +msgid "There is no RD Web feed at %s." +msgstr "No hay ningรบn feed de RD Web en %s." + +#: src/lib/workspace.sh:166 +#, sh-printf-format +msgid "Could not load the apps: %s" +msgstr "No se pudieron cargar las apps: %s" + +#: src/lib/workspace.sh:282 +#, sh-printf-format +msgid "Remote desktop of %s" +msgstr "Escritorio remoto de %s" + +#: src/lib/workspace.sh:284 +#, sh-printf-format +msgid "%s from %s" +msgstr "%s de %s" + +#: src/lib/menu.sh +msgid "Apps" +msgstr "Apps" + +#: src/lib/menu.sh +msgid "Share a folder" +msgstr "Compartir una carpeta" + +#: src/lib/menu.sh +msgid "Open files with the apps" +msgstr "Abrir archivos con las apps" + +#: src/lib/menu.sh +msgid "Size" +msgstr "Tamaรฑo" + +#: src/lib/menu.sh +msgid "Sound" +msgstr "Sonido" + +#: src/lib/menu.sh +msgid "Graphics" +msgstr "Grรกficos" + +#: src/lib/menu.sh +msgid "Signing in" +msgstr "Inicio de sesiรณn" + +#: src/lib/menu.sh +msgid "Wait for the second MFA prompt" +msgstr "Esperar la segunda solicitud de MFA" + +#: src/lib/menu.sh +msgid "Refresh the apps every day" +msgstr "Actualizar las apps cada dรญa" diff --git a/po/fr.po b/po/fr.po new file mode 100644 index 0000000..6038159 --- /dev/null +++ b/po/fr.po @@ -0,0 +1,574 @@ +# French translation for rdfeed. +# Copyright (C) 2026 Felitendo +# This file is distributed under the same license as rdfeed. +# +msgid "" +msgstr "" +"Project-Id-Version: rdfeed 1.0.0\n" +"Report-Msgid-Bugs-To: https://github.com/LoonixTools/rdfeed/issues\n" +"POT-Creation-Date: 2026-10-02 10:29+0200\n" +"PO-Revision-Date: 2026-10-02 09:30+0200\n" +"Last-Translator: Felitendo\n" +"Language-Team: French\n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/rdfeed:79 src/rdfeed:103 +#, sh-printf-format +msgid "Could not write to %s." +msgstr "Impossible d'รฉcrire dans %s." + +#: src/rdfeed:82 +#, sh-printf-format +msgid "Signing in to %sโ€ฆ" +msgstr "Connexion ร  %sโ€ฆ" + +#: src/rdfeed:100 +msgid "" +"The password could not be saved in the keyring, so each app asks for it." +msgstr "" +"Le mot de passe n'a pas pu รชtre enregistrรฉ dans le trousseau, chaque " +"application le demandera donc." + +#: src/rdfeed:111 +#, sh-printf-format +msgid "1 app from %s is now in your app menu." +msgstr "1 application de %s est maintenant dans votre menu des applications." + +#: src/rdfeed:113 +#, sh-printf-format +msgid "%d apps from %s are now in your app menu." +msgstr "" +"%d applications de %s sont maintenant dans votre menu des applications." + +#: src/rdfeed:129 +msgid "" +"Your IT department has the address. A work e-mail address often works too." +msgstr "" +"Votre service informatique connaรฎt l'adresse. Souvent, votre adresse e-mail " +"professionnelle fonctionne aussi." + +#: src/rdfeed:130 +msgid "Feed address, server or e-mail:" +msgstr "Adresse du flux, serveur ou e-mail :" + +#: src/rdfeed:137 +#, sh-printf-format +msgid "No feed is registered for %s. Ask your IT department for the address." +msgstr "" +"Aucun flux n'est enregistrรฉ pour %s. Demandez l'adresse ร  votre service " +"informatique." + +#: src/rdfeed:142 src/rdfeed:156 +msgid "User name (DOMAIN\\name or name@domain):" +msgstr "Nom d'utilisateur (DOMAINE\\nom ou nom@domaine) :" + +#: src/rdfeed:146 src/rdfeed:159 +msgid "Password:" +msgstr "Mot de passe :" + +#: src/rdfeed:178 src/rdfeed:236 +#, sh-printf-format +msgid "No workspace yet. Add one with `%s add`." +msgstr "Aucun espace de travail pour l'instant. Ajoutez-en un avec `%s add`." + +#: src/rdfeed:187 src/lib/launch.sh:220 +#, sh-printf-format +msgid "No password saved for %s. Sign in again in rdfeed." +msgstr "Aucun mot de passe enregistrรฉ pour %s. Reconnectez-vous dans rdfeed." + +#: src/rdfeed:191 src/lib/launch.sh:149 +#, sh-printf-format +msgid "Password for %s:" +msgstr "Mot de passe pour %s :" + +#: src/rdfeed:195 +#, sh-printf-format +msgid "Refreshing %sโ€ฆ" +msgstr "Actualisation de %sโ€ฆ" + +#: src/rdfeed:205 +msgid "The password no longer works. Sign in again in rdfeed." +msgstr "Le mot de passe ne fonctionne plus. Reconnectez-vous dans rdfeed." + +#: src/rdfeed:214 +#, sh-printf-format +msgid "%s: %d apps" +msgstr "%s : %d applications" + +#: src/rdfeed:222 +#, sh-printf-format +msgid "Which workspace? See `%s list` for the names." +msgstr "Quel espace de travail ? Les noms sont dans `%s list`." + +#: src/rdfeed:229 +#, sh-printf-format +msgid "%s and its apps are removed." +msgstr "%s et ses applications ont รฉtรฉ supprimรฉs." + +#: src/rdfeed:253 +#, sh-printf-format +msgid "Which app? See `%s list`." +msgstr "Quelle application ? Voir `%s list`." + +#: src/rdfeed:268 src/lib/launch.sh:199 +#, sh-printf-format +msgid "There is no app %s. See `%s list`." +msgstr "L'application %s n'existe pas. Voir `%s list`." + +#: src/rdfeed:270 +#, sh-printf-format +msgid "Several workspaces have %s. Write it as WORKSPACE/APP, see `%s list`." +msgstr "" +"Plusieurs espaces de travail ont %s. ร‰crivez-le sous la forme ESPACE/" +"APPLICATION, voir `%s list`." + +#: src/rdfeed:278 +msgid "Usage: rdfeed [command]" +msgstr "Utilisationย : rdfeed [commande]" + +#: src/rdfeed:280 +msgid "Commands:" +msgstr "Commandesย :" + +#: src/rdfeed:281 +msgid "Add the workspace of your company" +msgstr "Ajouter l'espace de travail de votre entreprise" + +#: src/rdfeed:282 +msgid "Load the apps again" +msgstr "Recharger les applications" + +#: src/rdfeed:283 +msgid "List the workspaces and their apps" +msgstr "Lister les espaces de travail et leurs applications" + +#: src/rdfeed:284 +msgid "Start an app, or open a file with it" +msgstr "Lancer une application, ou ouvrir un fichier avec" + +#: src/rdfeed:285 +msgid "Remove a workspace and its apps" +msgstr "Supprimer un espace de travail et ses applications" + +#: src/rdfeed:286 +msgid "Show this help" +msgstr "Afficher cette aide" + +#: src/rdfeed:287 +msgid "Show the version" +msgstr "Afficher la version" + +#: src/rdfeed:289 +msgid "Without a command an interactive menu is shown." +msgstr "Sans commande, un menu interactif s'affiche." + +#: src/rdfeed:313 +#, sh-printf-format +msgid "Unknown command: %s" +msgstr "Commande inconnueย : %s" + +#: src/lib/common.sh:174 src/lib/menu.sh:251 +msgid "never" +msgstr "jamais" + +#: src/lib/common.sh:184 +msgid "just now" +msgstr "ร  l'instant" + +#: src/lib/common.sh:186 +msgid "1 minute ago" +msgstr "il y a 1 minute" + +#: src/lib/common.sh:189 +#, sh-printf-format +msgid "%d minutes ago" +msgstr "il y a %d minutes" + +#: src/lib/common.sh:191 +msgid "1 hour ago" +msgstr "il y a 1 heure" + +#: src/lib/common.sh:194 +#, sh-printf-format +msgid "%d hours ago" +msgstr "il y a %d heures" + +#: src/lib/common.sh:196 +msgid "1 day ago" +msgstr "il y a 1 jour" + +#: src/lib/common.sh:199 +#, sh-printf-format +msgid "%d days ago" +msgstr "il y a %d jours" + +#: src/lib/launch.sh:169 +msgid "" +"Your password has expired. Change it on Windows or on the RD Web page, then " +"sign in again in rdfeed." +msgstr "" +"Votre mot de passe a expirรฉ. Changez-le sous Windows ou sur la page RD Web, " +"puis reconnectez-vous dans rdfeed." + +#: src/lib/launch.sh:171 +msgid "Your account is locked. Ask your IT department." +msgstr "" +"Votre compte est verrouillรฉ. Adressez-vous ร  votre service informatique." + +#: src/lib/launch.sh:173 +msgid "The server did not accept the password. Sign in again in rdfeed." +msgstr "" +"Le serveur n'a pas acceptรฉ le mot de passe. Reconnectez-vous dans rdfeed." + +#: src/lib/launch.sh:175 +msgid "Could not reach the server. Check the internet connection." +msgstr "Impossible de joindre le serveur. Vรฉrifiez la connexion internet." + +#: src/lib/launch.sh:178 +#, sh-printf-format +msgid "FreeRDP stopped: %s" +msgstr "FreeRDP s'est arrรชtรฉ : %s" + +#: src/lib/launch.sh:205 src/lib/launch.sh:206 +#, sh-printf-format +msgid "File not found: %s" +msgstr "Fichier introuvable : %s" + +#: src/lib/launch.sh:212 src/lib/launch.sh:213 +msgid "" +"FreeRDP 3 is missing. Install it to start the apps (the package is called " +"freerdp)." +msgstr "" +"FreeRDP 3 est absent. Installez-le pour lancer les applications (le paquet " +"s'appelle freerdp)." + +#: src/lib/launch.sh:277 +#, sh-printf-format +msgid "Opening %s in %s" +msgstr "Ouverture de %s dans %s" + +#: src/lib/launch.sh:278 +#, sh-printf-format +msgid "If it does not open by itself, open it in the app from the drive %s." +msgstr "" +"S'il ne s'ouvre pas tout seul, ouvrez-le dans l'application depuis le " +"lecteur %s." + +#: src/lib/launch.sh:280 +#, sh-printf-format +msgid "Starting %s" +msgstr "Lancement de %s" + +#: src/lib/launch.sh:281 +msgid "" +"If your company uses MFA, confirm the sign-in in your authenticator app." +msgstr "" +"Si votre entreprise utilise la MFA, confirmez la connexion dans votre " +"application d'authentification." + +#: src/lib/launch.sh:290 +msgid "A second sign-in is coming" +msgstr "Une deuxiรจme connexion arrive" + +#: src/lib/launch.sh:291 +#, sh-printf-format +msgid "" +"Confirm the second prompt in your authenticator app too. It comes in about " +"%d seconds." +msgstr "" +"Confirmez aussi la deuxiรจme demande dans votre application " +"d'authentification. Elle arrive dans environ %d secondes." + +#: src/lib/launch.sh:301 +#, sh-printf-format +msgid "%s could not start" +msgstr "%s n'a pas pu dรฉmarrer" + +#: src/lib/launch.sh:303 +#, sh-printf-format +msgid "Log: %s" +msgstr "Journal : %s" + +#. TRANSLATORS: the letter after "[" is the key for yes. y works too. +#: src/lib/menu.sh:93 +msgid "[y/N]" +msgstr "[o/N]" + +#: src/lib/menu.sh:163 +msgid "1 app" +msgstr "1 application" + +#: src/lib/menu.sh:165 +#, sh-printf-format +msgid "%d apps" +msgstr "%d applications" + +#: src/lib/menu.sh:190 +msgid "No workspace yet. Add the one of your company with [1]." +msgstr "" +"Aucun espace de travail pour l'instant. Ajoutez celui de votre entreprise " +"avec [1]." + +#: src/lib/menu.sh:233 +msgid "off" +msgstr "dรฉsactivรฉ" + +#: src/lib/menu.sh:234 +msgid "home folder" +msgstr "dossier personnel" + +#: src/lib/menu.sh:237 +msgid "Documents" +msgstr "Documents" + +#: src/lib/menu.sh:238 +msgid "Downloads" +msgstr "Tรฉlรฉchargements" + +#: src/lib/menu.sh:243 +msgid "not found" +msgstr "introuvable" + +#: src/lib/menu.sh:246 +#, sh-printf-format +msgid "automatic (%d %%)" +msgstr "automatique (%d %%)" + +#: src/lib/menu.sh:248 +msgid "classic" +msgstr "classique" + +#: src/lib/menu.sh:249 +msgid "GFX" +msgstr "GFX" + +#: src/lib/menu.sh:250 +msgid "always" +msgstr "toujours" + +#: src/lib/menu.sh:252 +msgid "automatic" +msgstr "automatique" + +#: src/lib/menu.sh:261 +msgid "" +"The apps only see the files on the server. Pick a folder to open and save " +"files of this computer in them." +msgstr "" +"Les applications ne voient que les fichiers du serveur. Choisissez un " +"dossier pour qu'elles puissent ouvrir et enregistrer des fichiers de cet " +"ordinateur." + +#: src/lib/menu.sh:262 +msgid "" +"The apps can open and save files in this folder. It shows up as a drive in " +"them." +msgstr "" +"Les applications peuvent ouvrir et enregistrer des fichiers dans ce dossier. " +"Il y apparaรฎt comme un lecteur." + +#: src/lib/menu.sh:263 +msgid "" +"Offers the apps under \"Open with\" for their file types, for example Word " +"for .docx. The folder of the file is shared with the app while it is open." +msgstr "" +"Propose les applications dans ยซ Ouvrir avec ยป pour leurs types de fichiers, " +"par exemple Word pour .docx. Le dossier du fichier est partagรฉ tant qu'il " +"est ouvert." + +#: src/lib/menu.sh:264 +msgid "" +"How big the apps are drawn. Automatic follows the scale of your desktop, so " +"they are sharp and the right size." +msgstr "" +"La taille ร  laquelle les applications sont dessinรฉes. Automatique suit " +"l'รฉchelle de votre bureau : elles sont nettes et ร  la bonne taille." + +#: src/lib/menu.sh:265 +msgid "Plays the sound of the apps here, for calls and videos." +msgstr "Joue ici le son des applications, pour les appels et les vidรฉos." + +#: src/lib/menu.sh:266 +msgid "" +"Paints every window right. But some servers end the session right after " +"signing in, then go back to GFX." +msgstr "Dessine correctement chaque fenรชtre. Mais certains serveurs ferment la session juste aprรจs la connexion : revenez alors ร  GFX." + +#: src/lib/menu.sh:267 +msgid "" +"Works with every server. FreeRDP may not repaint a window after it was " +"minimized or resized." +msgstr "Fonctionne avec tous les serveurs. FreeRDP peut ne pas redessiner une fenรชtre rรฉduite ou redimensionnรฉe." + +#: src/lib/menu.sh:268 +msgid "" +"Always waits before the second sign-in. For when the second prompt does not " +"come with automatic." +msgstr "" +"Attend toujours avant la deuxiรจme connexion. Si la deuxiรจme demande n'arrive " +"pas en automatique." + +#: src/lib/menu.sh:269 +msgid "Never waits. Right when your company has no MFA." +msgstr "" +"N'attend jamais. Le bon choix si votre entreprise n'utilise pas la MFA." + +#: src/lib/menu.sh:270 +msgid "" +"Many servers with MFA need a second sign-in, and its prompt only comes after " +"a short wait. Automatic waits when the first sign-in took a while." +msgstr "" +"Beaucoup de serveurs avec MFA demandent une deuxiรจme connexion, dont la " +"demande n'arrive qu'aprรจs une courte attente. Automatique attend si la " +"premiรจre a pris du temps." + +#: src/lib/menu.sh:273 +msgid "" +"Loads the list of apps again once a day, so new apps show up by themselves." +msgstr "" +"Recharge la liste des applications une fois par jour : les nouvelles " +"apparaissent d'elles-mรชmes." + +#: src/lib/menu.sh:275 src/lib/menu.sh:330 +msgid "Needs a systemd user session." +msgstr "Nรฉcessite une session utilisateur systemd." + +#: src/lib/menu.sh:322 src/lib/menu.sh:325 +msgid "Could not save the setting." +msgstr "Impossible d'enregistrer le rรฉglage." + +#: src/lib/menu.sh:371 src/lib/menu.sh:609 +msgid "Settings" +msgstr "Rรฉglages" + +#: src/lib/menu.sh:372 +msgid "โ†‘โ†“ select โ†โ†’ or Space: change q: back" +msgstr "โ†‘โ†“ choisir โ†โ†’ ou Espaceย : changer qย : retour" + +#: src/lib/menu.sh:373 +msgid "ON" +msgstr "ACTIVร‰" + +#: src/lib/menu.sh:374 +msgid "OFF" +msgstr "Dร‰SACTIVร‰" + +#: src/lib/menu.sh:456 src/lib/menu.sh:608 +msgid "Workspaces" +msgstr "Espaces de travail" + +#. TRANSLATORS: keep the letters, they are the keys. +#: src/lib/menu.sh:458 +msgid "" +"โ†‘โ†“ select r: refresh s: sign in again d: remove a: add q: back" +msgstr "" +"โ†‘โ†“ choisir r : actualiser s : se reconnecter d : supprimer a : " +"ajouter q : retour" + +#: src/lib/menu.sh:459 +msgid "No workspace yet. Press a to add one." +msgstr "" +"Aucun espace de travail pour l'instant. Appuyez sur a pour en ajouter un." + +#: src/lib/menu.sh:484 +#, sh-printf-format +msgid "refreshed %s" +msgstr "actualisรฉ %s" + +#: src/lib/menu.sh:512 +#, sh-printf-format +msgid "Remove \"%s\" and its apps?" +msgstr "Supprimer ยซ %s ยป et ses applications ?" + +#: src/lib/menu.sh:536 src/lib/menu.sh:607 +msgid "Start an app" +msgstr "Lancer une application" + +#: src/lib/menu.sh:537 +msgid "โ†‘โ†“ select Enter: start q: back" +msgstr "โ†‘โ†“ choisir Entrรฉe : lancer q : retour" + +#: src/lib/menu.sh:538 +msgid "No apps yet. Add a workspace first." +msgstr "Pas encore d'applications. Ajoutez d'abord un espace de travail." + +#: src/lib/menu.sh:579 +#, sh-printf-format +msgid "%s is starting. It opens in a window of its own." +msgstr "%s dรฉmarre. L'application s'ouvre dans sa propre fenรชtre." + +#: src/lib/menu.sh:606 +msgid "Add a workspace" +msgstr "Ajouter un espace de travail" + +#: src/lib/menu.sh:610 +msgid "Quit" +msgstr "Quitter" + +#: src/lib/workspace.sh:163 +msgid "The server did not accept the user name or password." +msgstr "Le serveur n'a pas acceptรฉ le nom d'utilisateur ou le mot de passe." + +#: src/lib/workspace.sh:164 +#, sh-printf-format +msgid "Could not reach the server: %s" +msgstr "Impossible de joindre le serveur : %s" + +#: src/lib/workspace.sh:165 +#, sh-printf-format +msgid "There is no RD Web feed at %s." +msgstr "Il n'y a pas de flux RD Web ร  l'adresse %s." + +#: src/lib/workspace.sh:166 +#, sh-printf-format +msgid "Could not load the apps: %s" +msgstr "Impossible de charger les applications : %s" + +#: src/lib/workspace.sh:282 +#, sh-printf-format +msgid "Remote desktop of %s" +msgstr "Bureau ร  distance de %s" + +#: src/lib/workspace.sh:284 +#, sh-printf-format +msgid "%s from %s" +msgstr "%s de %s" + +#: src/lib/menu.sh +msgid "Apps" +msgstr "Applications" + +#: src/lib/menu.sh +msgid "Share a folder" +msgstr "Partager un dossier" + +#: src/lib/menu.sh +msgid "Open files with the apps" +msgstr "Ouvrir des fichiers avec les applications" + +#: src/lib/menu.sh +msgid "Size" +msgstr "Taille" + +#: src/lib/menu.sh +msgid "Sound" +msgstr "Son" + +#: src/lib/menu.sh +msgid "Graphics" +msgstr "Graphismes" + +#: src/lib/menu.sh +msgid "Signing in" +msgstr "Connexion" + +#: src/lib/menu.sh +msgid "Wait for the second MFA prompt" +msgstr "Attendre la deuxiรจme demande MFA" + +#: src/lib/menu.sh +msgid "Refresh the apps every day" +msgstr "Actualiser les applications chaque jour" diff --git a/po/it.po b/po/it.po new file mode 100644 index 0000000..59cfcf4 --- /dev/null +++ b/po/it.po @@ -0,0 +1,561 @@ +# Italian translation for rdfeed. +# Copyright (C) 2026 Felitendo +# This file is distributed under the same license as rdfeed. +# +msgid "" +msgstr "" +"Project-Id-Version: rdfeed 1.0.0\n" +"Report-Msgid-Bugs-To: https://github.com/LoonixTools/rdfeed/issues\n" +"POT-Creation-Date: 2026-10-02 10:29+0200\n" +"PO-Revision-Date: 2026-10-02 09:30+0200\n" +"Last-Translator: Felitendo\n" +"Language-Team: Italian\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/rdfeed:79 src/rdfeed:103 +#, sh-printf-format +msgid "Could not write to %s." +msgstr "Impossibile scrivere in %s." + +#: src/rdfeed:82 +#, sh-printf-format +msgid "Signing in to %sโ€ฆ" +msgstr "Accesso a %sโ€ฆ" + +#: src/rdfeed:100 +msgid "" +"The password could not be saved in the keyring, so each app asks for it." +msgstr "" +"Non รจ stato possibile salvare la password nel portachiavi, quindi ogni app " +"la chiederร ." + +#: src/rdfeed:111 +#, sh-printf-format +msgid "1 app from %s is now in your app menu." +msgstr "1 app di %s รจ ora nel menu delle applicazioni." + +#: src/rdfeed:113 +#, sh-printf-format +msgid "%d apps from %s are now in your app menu." +msgstr "%d app di %s sono ora nel menu delle applicazioni." + +#: src/rdfeed:129 +msgid "" +"Your IT department has the address. A work e-mail address often works too." +msgstr "" +"L'indirizzo lo ha il tuo reparto IT. Spesso funziona anche l'e-mail di " +"lavoro." + +#: src/rdfeed:130 +msgid "Feed address, server or e-mail:" +msgstr "Indirizzo del feed, server o e-mail:" + +#: src/rdfeed:137 +#, sh-printf-format +msgid "No feed is registered for %s. Ask your IT department for the address." +msgstr "Nessun feed registrato per %s. Chiedi l'indirizzo al tuo reparto IT." + +#: src/rdfeed:142 src/rdfeed:156 +msgid "User name (DOMAIN\\name or name@domain):" +msgstr "Nome utente (DOMINIO\\nome o nome@dominio):" + +#: src/rdfeed:146 src/rdfeed:159 +msgid "Password:" +msgstr "Password:" + +#: src/rdfeed:178 src/rdfeed:236 +#, sh-printf-format +msgid "No workspace yet. Add one with `%s add`." +msgstr "Nessuno spazio di lavoro ancora. Aggiungine uno con `%s add`." + +#: src/rdfeed:187 src/lib/launch.sh:220 +#, sh-printf-format +msgid "No password saved for %s. Sign in again in rdfeed." +msgstr "Nessuna password salvata per %s. Accedi di nuovo in rdfeed." + +#: src/rdfeed:191 src/lib/launch.sh:149 +#, sh-printf-format +msgid "Password for %s:" +msgstr "Password per %s:" + +#: src/rdfeed:195 +#, sh-printf-format +msgid "Refreshing %sโ€ฆ" +msgstr "Aggiornamento di %sโ€ฆ" + +#: src/rdfeed:205 +msgid "The password no longer works. Sign in again in rdfeed." +msgstr "La password non funziona piรน. Accedi di nuovo in rdfeed." + +#: src/rdfeed:214 +#, sh-printf-format +msgid "%s: %d apps" +msgstr "%s: %d app" + +#: src/rdfeed:222 +#, sh-printf-format +msgid "Which workspace? See `%s list` for the names." +msgstr "Quale spazio di lavoro? I nomi sono in `%s list`." + +#: src/rdfeed:229 +#, sh-printf-format +msgid "%s and its apps are removed." +msgstr "%s e le sue app sono stati rimossi." + +#: src/rdfeed:253 +#, sh-printf-format +msgid "Which app? See `%s list`." +msgstr "Quale app? Vedi `%s list`." + +#: src/rdfeed:268 src/lib/launch.sh:199 +#, sh-printf-format +msgid "There is no app %s. See `%s list`." +msgstr "Non esiste l'app %s. Vedi `%s list`." + +#: src/rdfeed:270 +#, sh-printf-format +msgid "Several workspaces have %s. Write it as WORKSPACE/APP, see `%s list`." +msgstr "" +"Piรน spazi di lavoro hanno %s. Scrivilo come SPAZIO/APP, vedi `%s list`." + +#: src/rdfeed:278 +msgid "Usage: rdfeed [command]" +msgstr "Uso: rdfeed [comando]" + +#: src/rdfeed:280 +msgid "Commands:" +msgstr "Comandi:" + +#: src/rdfeed:281 +msgid "Add the workspace of your company" +msgstr "Aggiungi lo spazio di lavoro della tua azienda" + +#: src/rdfeed:282 +msgid "Load the apps again" +msgstr "Ricarica le app" + +#: src/rdfeed:283 +msgid "List the workspaces and their apps" +msgstr "Elenca gli spazi di lavoro e le loro app" + +#: src/rdfeed:284 +msgid "Start an app, or open a file with it" +msgstr "Avvia un'app, o apri un file con essa" + +#: src/rdfeed:285 +msgid "Remove a workspace and its apps" +msgstr "Rimuovi uno spazio di lavoro e le sue app" + +#: src/rdfeed:286 +msgid "Show this help" +msgstr "Mostra questo aiuto" + +#: src/rdfeed:287 +msgid "Show the version" +msgstr "Mostra la versione" + +#: src/rdfeed:289 +msgid "Without a command an interactive menu is shown." +msgstr "Senza comando viene mostrato un menu interattivo." + +#: src/rdfeed:313 +#, sh-printf-format +msgid "Unknown command: %s" +msgstr "Comando sconosciuto: %s" + +#: src/lib/common.sh:174 src/lib/menu.sh:251 +msgid "never" +msgstr "mai" + +#: src/lib/common.sh:184 +msgid "just now" +msgstr "proprio ora" + +#: src/lib/common.sh:186 +msgid "1 minute ago" +msgstr "1 minuto fa" + +#: src/lib/common.sh:189 +#, sh-printf-format +msgid "%d minutes ago" +msgstr "%d minuti fa" + +#: src/lib/common.sh:191 +msgid "1 hour ago" +msgstr "1 ora fa" + +#: src/lib/common.sh:194 +#, sh-printf-format +msgid "%d hours ago" +msgstr "%d ore fa" + +#: src/lib/common.sh:196 +msgid "1 day ago" +msgstr "1 giorno fa" + +#: src/lib/common.sh:199 +#, sh-printf-format +msgid "%d days ago" +msgstr "%d giorni fa" + +#: src/lib/launch.sh:169 +msgid "" +"Your password has expired. Change it on Windows or on the RD Web page, then " +"sign in again in rdfeed." +msgstr "" +"La password รจ scaduta. Cambiala in Windows o sulla pagina RD Web, poi accedi " +"di nuovo in rdfeed." + +#: src/lib/launch.sh:171 +msgid "Your account is locked. Ask your IT department." +msgstr "Il tuo account รจ bloccato. Chiedi al reparto IT." + +#: src/lib/launch.sh:173 +msgid "The server did not accept the password. Sign in again in rdfeed." +msgstr "Il server non ha accettato la password. Accedi di nuovo in rdfeed." + +#: src/lib/launch.sh:175 +msgid "Could not reach the server. Check the internet connection." +msgstr "" +"Impossibile raggiungere il server. Controlla la connessione a internet." + +#: src/lib/launch.sh:178 +#, sh-printf-format +msgid "FreeRDP stopped: %s" +msgstr "FreeRDP si รจ fermato: %s" + +#: src/lib/launch.sh:205 src/lib/launch.sh:206 +#, sh-printf-format +msgid "File not found: %s" +msgstr "File non trovato: %s" + +#: src/lib/launch.sh:212 src/lib/launch.sh:213 +msgid "" +"FreeRDP 3 is missing. Install it to start the apps (the package is called " +"freerdp)." +msgstr "" +"Manca FreeRDP 3. Installalo per avviare le app (il pacchetto si chiama " +"freerdp)." + +#: src/lib/launch.sh:277 +#, sh-printf-format +msgid "Opening %s in %s" +msgstr "Apertura di %s in %s" + +#: src/lib/launch.sh:278 +#, sh-printf-format +msgid "If it does not open by itself, open it in the app from the drive %s." +msgstr "Se non si apre da solo, aprilo nell'app dall'unitร  %s." + +#: src/lib/launch.sh:280 +#, sh-printf-format +msgid "Starting %s" +msgstr "Avvio di %s" + +#: src/lib/launch.sh:281 +msgid "" +"If your company uses MFA, confirm the sign-in in your authenticator app." +msgstr "" +"Se la tua azienda usa l'MFA, conferma l'accesso nell'app di autenticazione." + +#: src/lib/launch.sh:290 +msgid "A second sign-in is coming" +msgstr "Sta arrivando un secondo accesso" + +#: src/lib/launch.sh:291 +#, sh-printf-format +msgid "" +"Confirm the second prompt in your authenticator app too. It comes in about " +"%d seconds." +msgstr "" +"Conferma anche la seconda richiesta nell'app di autenticazione. Arriva tra " +"circa %d secondi." + +#: src/lib/launch.sh:301 +#, sh-printf-format +msgid "%s could not start" +msgstr "Impossibile avviare %s" + +#: src/lib/launch.sh:303 +#, sh-printf-format +msgid "Log: %s" +msgstr "Log: %s" + +#. TRANSLATORS: the letter after "[" is the key for yes. y works too. +#: src/lib/menu.sh:93 +msgid "[y/N]" +msgstr "[s/N]" + +#: src/lib/menu.sh:163 +msgid "1 app" +msgstr "1 app" + +#: src/lib/menu.sh:165 +#, sh-printf-format +msgid "%d apps" +msgstr "%d app" + +#: src/lib/menu.sh:190 +msgid "No workspace yet. Add the one of your company with [1]." +msgstr "" +"Nessuno spazio di lavoro ancora. Aggiungi quello della tua azienda con [1]." + +#: src/lib/menu.sh:233 +msgid "off" +msgstr "disattivo" + +#: src/lib/menu.sh:234 +msgid "home folder" +msgstr "cartella home" + +#: src/lib/menu.sh:237 +msgid "Documents" +msgstr "Documenti" + +#: src/lib/menu.sh:238 +msgid "Downloads" +msgstr "Download" + +#: src/lib/menu.sh:243 +msgid "not found" +msgstr "non trovata" + +#: src/lib/menu.sh:246 +#, sh-printf-format +msgid "automatic (%d %%)" +msgstr "automatica (%d %%)" + +#: src/lib/menu.sh:248 +msgid "classic" +msgstr "classico" + +#: src/lib/menu.sh:249 +msgid "GFX" +msgstr "GFX" + +#: src/lib/menu.sh:250 +msgid "always" +msgstr "sempre" + +#: src/lib/menu.sh:252 +msgid "automatic" +msgstr "automatico" + +#: src/lib/menu.sh:261 +msgid "" +"The apps only see the files on the server. Pick a folder to open and save " +"files of this computer in them." +msgstr "" +"Le app vedono solo i file sul server. Scegli una cartella perchรฉ possano " +"aprire e salvare file di questo computer." + +#: src/lib/menu.sh:262 +msgid "" +"The apps can open and save files in this folder. It shows up as a drive in " +"them." +msgstr "" +"Le app possono aprire e salvare file in questa cartella. Compare in esse " +"come un'unitร ." + +#: src/lib/menu.sh:263 +msgid "" +"Offers the apps under \"Open with\" for their file types, for example Word " +"for .docx. The folder of the file is shared with the app while it is open." +msgstr "" +"Offre le app in ยซApri conยป per i loro tipi di file, per esempio Word " +"per .docx. La cartella del file viene condivisa finchรฉ รจ aperto." + +#: src/lib/menu.sh:264 +msgid "" +"How big the apps are drawn. Automatic follows the scale of your desktop, so " +"they are sharp and the right size." +msgstr "" +"Quanto grandi vengono disegnate le app. Automatica segue la scala del " +"desktop, cosรฌ sono nitide e della giusta dimensione." + +#: src/lib/menu.sh:265 +msgid "Plays the sound of the apps here, for calls and videos." +msgstr "Riproduce qui l'audio delle app, per chiamate e video." + +#: src/lib/menu.sh:266 +msgid "" +"Paints every window right. But some servers end the session right after " +"signing in, then go back to GFX." +msgstr "Disegna bene ogni finestra. Ma alcuni server chiudono la sessione subito dopo l'accesso: allora torna a GFX." + +#: src/lib/menu.sh:267 +msgid "" +"Works with every server. FreeRDP may not repaint a window after it was " +"minimized or resized." +msgstr "Funziona con ogni server. FreeRDP potrebbe non ridisegnare una finestra dopo averla ridotta o ridimensionata." + +#: src/lib/menu.sh:268 +msgid "" +"Always waits before the second sign-in. For when the second prompt does not " +"come with automatic." +msgstr "" +"Attende sempre prima del secondo accesso. Per quando con automatico la " +"seconda richiesta non arriva." + +#: src/lib/menu.sh:269 +msgid "Never waits. Right when your company has no MFA." +msgstr "Non attende mai. Giusto se la tua azienda non usa l'MFA." + +#: src/lib/menu.sh:270 +msgid "" +"Many servers with MFA need a second sign-in, and its prompt only comes after " +"a short wait. Automatic waits when the first sign-in took a while." +msgstr "" +"Molti server con MFA chiedono un secondo accesso, e la sua richiesta arriva " +"solo dopo una breve attesa. Automatico attende se il primo accesso ha " +"richiesto un po'." + +#: src/lib/menu.sh:273 +msgid "" +"Loads the list of apps again once a day, so new apps show up by themselves." +msgstr "" +"Ricarica l'elenco delle app una volta al giorno, cosรฌ quelle nuove compaiono " +"da sole." + +#: src/lib/menu.sh:275 src/lib/menu.sh:330 +msgid "Needs a systemd user session." +msgstr "Serve una sessione utente di systemd." + +#: src/lib/menu.sh:322 src/lib/menu.sh:325 +msgid "Could not save the setting." +msgstr "Impossibile salvare l'impostazione." + +#: src/lib/menu.sh:371 src/lib/menu.sh:609 +msgid "Settings" +msgstr "Impostazioni" + +#: src/lib/menu.sh:372 +msgid "โ†‘โ†“ select โ†โ†’ or Space: change q: back" +msgstr "โ†‘โ†“ scegli โ†โ†’ o Spazio: cambia q: indietro" + +#: src/lib/menu.sh:373 +msgid "ON" +msgstr "ATTIVO" + +#: src/lib/menu.sh:374 +msgid "OFF" +msgstr "DISATTIVO" + +#: src/lib/menu.sh:456 src/lib/menu.sh:608 +msgid "Workspaces" +msgstr "Spazi di lavoro" + +#. TRANSLATORS: keep the letters, they are the keys. +#: src/lib/menu.sh:458 +msgid "" +"โ†‘โ†“ select r: refresh s: sign in again d: remove a: add q: back" +msgstr "" +"โ†‘โ†“ scegli r: aggiorna s: accedi di nuovo d: rimuovi a: aggiungi q: " +"indietro" + +#: src/lib/menu.sh:459 +msgid "No workspace yet. Press a to add one." +msgstr "Nessuno spazio di lavoro ancora. Premi a per aggiungerne uno." + +#: src/lib/menu.sh:484 +#, sh-printf-format +msgid "refreshed %s" +msgstr "aggiornato %s" + +#: src/lib/menu.sh:512 +#, sh-printf-format +msgid "Remove \"%s\" and its apps?" +msgstr "Rimuovere ยซ%sยป e le sue app?" + +#: src/lib/menu.sh:536 src/lib/menu.sh:607 +msgid "Start an app" +msgstr "Avvia un'app" + +#: src/lib/menu.sh:537 +msgid "โ†‘โ†“ select Enter: start q: back" +msgstr "โ†‘โ†“ scegli Invio: avvia q: indietro" + +#: src/lib/menu.sh:538 +msgid "No apps yet. Add a workspace first." +msgstr "Nessuna app ancora. Aggiungi prima uno spazio di lavoro." + +#: src/lib/menu.sh:579 +#, sh-printf-format +msgid "%s is starting. It opens in a window of its own." +msgstr "%s si sta avviando. Si apre in una finestra propria." + +#: src/lib/menu.sh:606 +msgid "Add a workspace" +msgstr "Aggiungi uno spazio di lavoro" + +#: src/lib/menu.sh:610 +msgid "Quit" +msgstr "Esci" + +#: src/lib/workspace.sh:163 +msgid "The server did not accept the user name or password." +msgstr "Il server non ha accettato il nome utente o la password." + +#: src/lib/workspace.sh:164 +#, sh-printf-format +msgid "Could not reach the server: %s" +msgstr "Impossibile raggiungere il server: %s" + +#: src/lib/workspace.sh:165 +#, sh-printf-format +msgid "There is no RD Web feed at %s." +msgstr "Non c'รจ nessun feed RD Web in %s." + +#: src/lib/workspace.sh:166 +#, sh-printf-format +msgid "Could not load the apps: %s" +msgstr "Impossibile caricare le app: %s" + +#: src/lib/workspace.sh:282 +#, sh-printf-format +msgid "Remote desktop of %s" +msgstr "Desktop remoto di %s" + +#: src/lib/workspace.sh:284 +#, sh-printf-format +msgid "%s from %s" +msgstr "%s di %s" + +#: src/lib/menu.sh +msgid "Apps" +msgstr "App" + +#: src/lib/menu.sh +msgid "Share a folder" +msgstr "Condividi una cartella" + +#: src/lib/menu.sh +msgid "Open files with the apps" +msgstr "Apri i file con le app" + +#: src/lib/menu.sh +msgid "Size" +msgstr "Dimensione" + +#: src/lib/menu.sh +msgid "Sound" +msgstr "Audio" + +#: src/lib/menu.sh +msgid "Graphics" +msgstr "Grafica" + +#: src/lib/menu.sh +msgid "Signing in" +msgstr "Accesso" + +#: src/lib/menu.sh +msgid "Wait for the second MFA prompt" +msgstr "Attendi la seconda richiesta MFA" + +#: src/lib/menu.sh +msgid "Refresh the apps every day" +msgstr "Aggiorna le app ogni giorno" diff --git a/po/ja.po b/po/ja.po new file mode 100644 index 0000000..8890285 --- /dev/null +++ b/po/ja.po @@ -0,0 +1,561 @@ +# Japanese translation for rdfeed. +# Copyright (C) 2026 Felitendo +# This file is distributed under the same license as rdfeed. +# +msgid "" +msgstr "" +"Project-Id-Version: rdfeed 1.0.0\n" +"Report-Msgid-Bugs-To: https://github.com/LoonixTools/rdfeed/issues\n" +"POT-Creation-Date: 2026-10-02 10:29+0200\n" +"PO-Revision-Date: 2026-10-02 09:30+0200\n" +"Last-Translator: Felitendo\n" +"Language-Team: Japanese\n" +"Language: ja\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/rdfeed:79 src/rdfeed:103 +#, sh-printf-format +msgid "Could not write to %s." +msgstr "%s ใซๆ›ธใ่พผใ‚ใพใ›ใ‚“ใงใ—ใŸใ€‚" + +#: src/rdfeed:82 +#, sh-printf-format +msgid "Signing in to %sโ€ฆ" +msgstr "%s ใซใ‚ตใ‚คใƒณใ‚คใƒณใ—ใฆใ„ใพใ™โ€ฆ" + +#: src/rdfeed:100 +msgid "" +"The password could not be saved in the keyring, so each app asks for it." +msgstr "" +"ใƒ‘ใ‚นใƒฏใƒผใƒ‰ใ‚’ใ‚ญใƒผใƒชใƒณใ‚ฐใซไฟๅญ˜ใงใใชใ‹ใฃใŸใŸใ‚ใ€ใ‚ขใƒ—ใƒชใ”ใจใซๅ…ฅๅŠ›ใ‚’ๆฑ‚ใ‚ใพใ™ใ€‚" + +#: src/rdfeed:111 +#, sh-printf-format +msgid "1 app from %s is now in your app menu." +msgstr "%s ใฎใ‚ขใƒ—ใƒช 1 ๅ€‹ใŒใ‚ขใƒ—ใƒชใƒกใƒ‹ใƒฅใƒผใซ่ฟฝๅŠ ใ•ใ‚Œใพใ—ใŸใ€‚" + +#: src/rdfeed:113 +#, sh-printf-format +msgid "%d apps from %s are now in your app menu." +msgstr "%d ๅ€‹ใฎใ‚ขใƒ—ใƒช๏ผˆ%s๏ผ‰ใŒใ‚ขใƒ—ใƒชใƒกใƒ‹ใƒฅใƒผใซ่ฟฝๅŠ ใ•ใ‚Œใพใ—ใŸใ€‚" + +#: src/rdfeed:129 +msgid "" +"Your IT department has the address. A work e-mail address often works too." +msgstr "" +"ใ‚ขใƒ‰ใƒฌใ‚นใฏ IT ้ƒจ้–€ใŒ็Ÿฅใฃใฆใ„ใพใ™ใ€‚ไป•ไบ‹็”จใฎใƒกใƒผใƒซใ‚ขใƒ‰ใƒฌใ‚นใงใ‚‚ไฝฟใˆใ‚‹ใ“ใจใŒใ‚ˆใ" +"ใ‚ใ‚Šใพใ™ใ€‚" + +#: src/rdfeed:130 +msgid "Feed address, server or e-mail:" +msgstr "ใƒ•ใ‚ฃใƒผใƒ‰ใฎใ‚ขใƒ‰ใƒฌใ‚นใ€ใ‚ตใƒผใƒใƒผใ€ใพใŸใฏใƒกใƒผใƒซ:" + +#: src/rdfeed:137 +#, sh-printf-format +msgid "No feed is registered for %s. Ask your IT department for the address." +msgstr "" +"%s ใซใฏใƒ•ใ‚ฃใƒผใƒ‰ใŒ็™ป้Œฒใ•ใ‚Œใฆใ„ใพใ›ใ‚“ใ€‚IT ้ƒจ้–€ใซใ‚ขใƒ‰ใƒฌใ‚นใ‚’็ขบ่ชใ—ใฆใใ ใ•ใ„ใ€‚" + +#: src/rdfeed:142 src/rdfeed:156 +msgid "User name (DOMAIN\\name or name@domain):" +msgstr "ใƒฆใƒผใ‚ถใƒผๅ๏ผˆDOMAIN\\name ใพใŸใฏ name@domain๏ผ‰:" + +#: src/rdfeed:146 src/rdfeed:159 +msgid "Password:" +msgstr "ใƒ‘ใ‚นใƒฏใƒผใƒ‰:" + +#: src/rdfeed:178 src/rdfeed:236 +#, sh-printf-format +msgid "No workspace yet. Add one with `%s add`." +msgstr "ใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใฏใพใ ใ‚ใ‚Šใพใ›ใ‚“ใ€‚`%s add` ใง่ฟฝๅŠ ใ—ใฆใใ ใ•ใ„ใ€‚" + +#: src/rdfeed:187 src/lib/launch.sh:220 +#, sh-printf-format +msgid "No password saved for %s. Sign in again in rdfeed." +msgstr "" +"%s ใฎใƒ‘ใ‚นใƒฏใƒผใƒ‰ใŒไฟๅญ˜ใ•ใ‚Œใฆใ„ใพใ›ใ‚“ใ€‚rdfeed ใงใ‚‚ใ†ไธ€ๅบฆใ‚ตใ‚คใƒณใ‚คใƒณใ—ใฆใใ ใ•" +"ใ„ใ€‚" + +#: src/rdfeed:191 src/lib/launch.sh:149 +#, sh-printf-format +msgid "Password for %s:" +msgstr "%s ใฎใƒ‘ใ‚นใƒฏใƒผใƒ‰:" + +#: src/rdfeed:195 +#, sh-printf-format +msgid "Refreshing %sโ€ฆ" +msgstr "%s ใ‚’ๆ›ดๆ–ฐใ—ใฆใ„ใพใ™โ€ฆ" + +#: src/rdfeed:205 +msgid "The password no longer works. Sign in again in rdfeed." +msgstr "" +"ใƒ‘ใ‚นใƒฏใƒผใƒ‰ใŒไฝฟใˆใชใใชใ‚Šใพใ—ใŸใ€‚rdfeed ใงใ‚‚ใ†ไธ€ๅบฆใ‚ตใ‚คใƒณใ‚คใƒณใ—ใฆใใ ใ•ใ„ใ€‚" + +#: src/rdfeed:214 +#, sh-printf-format +msgid "%s: %d apps" +msgstr "%s: ใ‚ขใƒ—ใƒช %d ๅ€‹" + +#: src/rdfeed:222 +#, sh-printf-format +msgid "Which workspace? See `%s list` for the names." +msgstr "ใฉใฎใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใงใ™ใ‹๏ผŸๅๅ‰ใฏ `%s list` ใง็ขบ่ชใงใใพใ™ใ€‚" + +#: src/rdfeed:229 +#, sh-printf-format +msgid "%s and its apps are removed." +msgstr "%s ใจใใฎใ‚ขใƒ—ใƒชใ‚’ๅ‰Š้™คใ—ใพใ—ใŸใ€‚" + +#: src/rdfeed:253 +#, sh-printf-format +msgid "Which app? See `%s list`." +msgstr "ใฉใฎใ‚ขใƒ—ใƒชใงใ™ใ‹๏ผŸ`%s list` ใ‚’ๅ‚็…งใ—ใฆใใ ใ•ใ„ใ€‚" + +#: src/rdfeed:268 src/lib/launch.sh:199 +#, sh-printf-format +msgid "There is no app %s. See `%s list`." +msgstr "ใ‚ขใƒ—ใƒช %s ใฏใ‚ใ‚Šใพใ›ใ‚“ใ€‚`%s list` ใ‚’ๅ‚็…งใ—ใฆใใ ใ•ใ„ใ€‚" + +#: src/rdfeed:270 +#, sh-printf-format +msgid "Several workspaces have %s. Write it as WORKSPACE/APP, see `%s list`." +msgstr "" +"่ค‡ๆ•ฐใฎใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใซ %s ใŒใ‚ใ‚Šใพใ™ใ€‚ใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚น/ใ‚ขใƒ—ใƒช ใฎๅฝขใงๆŒ‡ๅฎšใ—ใฆใ" +"ใ ใ•ใ„๏ผˆ`%s list` ใ‚’ๅ‚็…ง๏ผ‰ใ€‚" + +#: src/rdfeed:278 +msgid "Usage: rdfeed [command]" +msgstr "ไฝฟใ„ๆ–น: rdfeed [ใ‚ณใƒžใƒณใƒ‰]" + +#: src/rdfeed:280 +msgid "Commands:" +msgstr "ใ‚ณใƒžใƒณใƒ‰:" + +#: src/rdfeed:281 +msgid "Add the workspace of your company" +msgstr "ไผš็คพใฎใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใ‚’่ฟฝๅŠ ใ™ใ‚‹" + +#: src/rdfeed:282 +msgid "Load the apps again" +msgstr "ใ‚ขใƒ—ใƒชใ‚’่ชญใฟ่พผใฟ็›ดใ™" + +#: src/rdfeed:283 +msgid "List the workspaces and their apps" +msgstr "ใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใจใใฎใ‚ขใƒ—ใƒชใ‚’ไธ€่ฆง่กจ็คบใ™ใ‚‹" + +#: src/rdfeed:284 +msgid "Start an app, or open a file with it" +msgstr "ใ‚ขใƒ—ใƒชใ‚’่ตทๅ‹•ใ™ใ‚‹ใ€ใพใŸใฏใƒ•ใ‚กใ‚คใƒซใ‚’้–‹ใ" + +#: src/rdfeed:285 +msgid "Remove a workspace and its apps" +msgstr "ใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใจใใฎใ‚ขใƒ—ใƒชใ‚’ๅ‰Š้™คใ™ใ‚‹" + +#: src/rdfeed:286 +msgid "Show this help" +msgstr "ใ“ใฎใƒ˜ใƒซใƒ—ใ‚’่กจ็คบ" + +#: src/rdfeed:287 +msgid "Show the version" +msgstr "ใƒใƒผใ‚ธใƒงใƒณใ‚’่กจ็คบ" + +#: src/rdfeed:289 +msgid "Without a command an interactive menu is shown." +msgstr "ใ‚ณใƒžใƒณใƒ‰ใชใ—ใง่ตทๅ‹•ใ™ใ‚‹ใจใ€ๅฏพ่ฉฑๅผใƒกใƒ‹ใƒฅใƒผใŒ่กจ็คบใ•ใ‚Œใพใ™ใ€‚" + +#: src/rdfeed:313 +#, sh-printf-format +msgid "Unknown command: %s" +msgstr "ไธๆ˜Žใชใ‚ณใƒžใƒณใƒ‰: %s" + +#: src/lib/common.sh:174 src/lib/menu.sh:251 +msgid "never" +msgstr "ใชใ—" + +#: src/lib/common.sh:184 +msgid "just now" +msgstr "ใŸใฃใŸไปŠ" + +#: src/lib/common.sh:186 +msgid "1 minute ago" +msgstr "1 ๅˆ†ๅ‰" + +#: src/lib/common.sh:189 +#, sh-printf-format +msgid "%d minutes ago" +msgstr "%d ๅˆ†ๅ‰" + +#: src/lib/common.sh:191 +msgid "1 hour ago" +msgstr "1 ๆ™‚้–“ๅ‰" + +#: src/lib/common.sh:194 +#, sh-printf-format +msgid "%d hours ago" +msgstr "%d ๆ™‚้–“ๅ‰" + +#: src/lib/common.sh:196 +msgid "1 day ago" +msgstr "1 ๆ—ฅๅ‰" + +#: src/lib/common.sh:199 +#, sh-printf-format +msgid "%d days ago" +msgstr "%d ๆ—ฅๅ‰" + +#: src/lib/launch.sh:169 +msgid "" +"Your password has expired. Change it on Windows or on the RD Web page, then " +"sign in again in rdfeed." +msgstr "" +"ใƒ‘ใ‚นใƒฏใƒผใƒ‰ใฎๆœ‰ๅŠนๆœŸ้™ใŒๅˆ‡ใ‚Œใฆใ„ใพใ™ใ€‚Windows ใ‹ RD Web ใฎใƒšใƒผใ‚ธใงๅค‰ๆ›ดใ—ใฆใ‹" +"ใ‚‰ใ€rdfeed ใงใ‚‚ใ†ไธ€ๅบฆใ‚ตใ‚คใƒณใ‚คใƒณใ—ใฆใใ ใ•ใ„ใ€‚" + +#: src/lib/launch.sh:171 +msgid "Your account is locked. Ask your IT department." +msgstr "ใ‚ขใ‚ซใ‚ฆใƒณใƒˆใŒใƒญใƒƒใ‚ฏใ•ใ‚Œใฆใ„ใพใ™ใ€‚IT ้ƒจ้–€ใซๅ•ใ„ๅˆใ‚ใ›ใฆใใ ใ•ใ„ใ€‚" + +#: src/lib/launch.sh:173 +msgid "The server did not accept the password. Sign in again in rdfeed." +msgstr "" +"ใ‚ตใƒผใƒใƒผใŒใƒ‘ใ‚นใƒฏใƒผใƒ‰ใ‚’ๅ—ใ‘ไป˜ใ‘ใพใ›ใ‚“ใงใ—ใŸใ€‚rdfeed ใงใ‚‚ใ†ไธ€ๅบฆใ‚ตใ‚คใƒณใ‚คใƒณใ—ใฆใ" +"ใ ใ•ใ„ใ€‚" + +#: src/lib/launch.sh:175 +msgid "Could not reach the server. Check the internet connection." +msgstr "ใ‚ตใƒผใƒใƒผใซๆŽฅ็ถšใงใใพใ›ใ‚“ใงใ—ใŸใ€‚ใ‚คใƒณใ‚ฟใƒผใƒใƒƒใƒˆๆŽฅ็ถšใ‚’็ขบ่ชใ—ใฆใใ ใ•ใ„ใ€‚" + +#: src/lib/launch.sh:178 +#, sh-printf-format +msgid "FreeRDP stopped: %s" +msgstr "FreeRDP ใŒๅœๆญขใ—ใพใ—ใŸ: %s" + +#: src/lib/launch.sh:205 src/lib/launch.sh:206 +#, sh-printf-format +msgid "File not found: %s" +msgstr "ใƒ•ใ‚กใ‚คใƒซใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“: %s" + +#: src/lib/launch.sh:212 src/lib/launch.sh:213 +msgid "" +"FreeRDP 3 is missing. Install it to start the apps (the package is called " +"freerdp)." +msgstr "" +"FreeRDP 3 ใŒใ‚ใ‚Šใพใ›ใ‚“ใ€‚ใ‚ขใƒ—ใƒชใ‚’่ตทๅ‹•ใ™ใ‚‹ใซใฏใ‚คใƒณใ‚นใƒˆใƒผใƒซใ—ใฆใใ ใ•ใ„๏ผˆใƒ‘ใƒƒ" +"ใ‚ฑใƒผใ‚ธๅใฏ freerdp๏ผ‰ใ€‚" + +#: src/lib/launch.sh:277 +#, sh-printf-format +msgid "Opening %s in %s" +msgstr "%s ใ‚’ %s ใง้–‹ใ„ใฆใ„ใพใ™" + +#: src/lib/launch.sh:278 +#, sh-printf-format +msgid "If it does not open by itself, open it in the app from the drive %s." +msgstr "่‡ชๅ‹•ใง้–‹ใ‹ใชใ„ๅ ดๅˆใฏใ€ใ‚ขใƒ—ใƒชใงใƒ‰ใƒฉใ‚คใƒ– %s ใ‹ใ‚‰้–‹ใ„ใฆใใ ใ•ใ„ใ€‚" + +#: src/lib/launch.sh:280 +#, sh-printf-format +msgid "Starting %s" +msgstr "%s ใ‚’่ตทๅ‹•ใ—ใฆใ„ใพใ™" + +#: src/lib/launch.sh:281 +msgid "" +"If your company uses MFA, confirm the sign-in in your authenticator app." +msgstr "" +"ไผš็คพใŒ MFA ใ‚’ไฝฟใฃใฆใ„ใ‚‹ๅ ดๅˆใฏใ€่ช่จผใ‚ขใƒ—ใƒชใงใ‚ตใ‚คใƒณใ‚คใƒณใ‚’ๆ‰ฟ่ชใ—ใฆใใ ใ•ใ„ใ€‚" + +#: src/lib/launch.sh:290 +msgid "A second sign-in is coming" +msgstr "2 ๅ›ž็›ฎใฎใ‚ตใ‚คใƒณใ‚คใƒณใŒๆฅใพใ™" + +#: src/lib/launch.sh:291 +#, sh-printf-format +msgid "" +"Confirm the second prompt in your authenticator app too. It comes in about " +"%d seconds." +msgstr "่ช่จผใ‚ขใƒ—ใƒชใง 2 ๅ›ž็›ฎใฎ็ขบ่ชใ‚‚ๆ‰ฟ่ชใ—ใฆใใ ใ•ใ„ใ€‚็ด„ %d ็ง’ๅพŒใซๅฑŠใใพใ™ใ€‚" + +#: src/lib/launch.sh:301 +#, sh-printf-format +msgid "%s could not start" +msgstr "%s ใ‚’่ตทๅ‹•ใงใใพใ›ใ‚“ใงใ—ใŸ" + +#: src/lib/launch.sh:303 +#, sh-printf-format +msgid "Log: %s" +msgstr "ใƒญใ‚ฐ: %s" + +#. TRANSLATORS: the letter after "[" is the key for yes. y works too. +#: src/lib/menu.sh:93 +msgid "[y/N]" +msgstr "[y/N]" + +#: src/lib/menu.sh:163 +msgid "1 app" +msgstr "ใ‚ขใƒ—ใƒช 1 ๅ€‹" + +#: src/lib/menu.sh:165 +#, sh-printf-format +msgid "%d apps" +msgstr "ใ‚ขใƒ—ใƒช %d ๅ€‹" + +#: src/lib/menu.sh:190 +msgid "No workspace yet. Add the one of your company with [1]." +msgstr "" +"ใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใฏใพใ ใ‚ใ‚Šใพใ›ใ‚“ใ€‚[1] ใงไผš็คพใฎใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใ‚’่ฟฝๅŠ ใ—ใฆใใ ใ•" +"ใ„ใ€‚" + +#: src/lib/menu.sh:233 +msgid "off" +msgstr "ใ‚ชใƒ•" + +#: src/lib/menu.sh:234 +msgid "home folder" +msgstr "ใƒ›ใƒผใƒ ใƒ•ใ‚ฉใƒซใƒ€ใƒผ" + +#: src/lib/menu.sh:237 +msgid "Documents" +msgstr "ใƒ‰ใ‚ญใƒฅใƒกใƒณใƒˆ" + +#: src/lib/menu.sh:238 +msgid "Downloads" +msgstr "ใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰" + +#: src/lib/menu.sh:243 +msgid "not found" +msgstr "่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“" + +#: src/lib/menu.sh:246 +#, sh-printf-format +msgid "automatic (%d %%)" +msgstr "่‡ชๅ‹• (%d %%)" + +#: src/lib/menu.sh:248 +msgid "classic" +msgstr "ใ‚ฏใƒฉใ‚ทใƒƒใ‚ฏ" + +#: src/lib/menu.sh:249 +msgid "GFX" +msgstr "GFX" + +#: src/lib/menu.sh:250 +msgid "always" +msgstr "ๅธธใซ" + +#: src/lib/menu.sh:252 +msgid "automatic" +msgstr "่‡ชๅ‹•" + +#: src/lib/menu.sh:261 +msgid "" +"The apps only see the files on the server. Pick a folder to open and save " +"files of this computer in them." +msgstr "" +"ใ‚ขใƒ—ใƒชใฏใ‚ตใƒผใƒใƒผไธŠใฎใƒ•ใ‚กใ‚คใƒซใ—ใ‹่ฆ‹ใˆใพใ›ใ‚“ใ€‚ใƒ•ใ‚ฉใƒซใƒ€ใƒผใ‚’้ธใถใจใ€ใ“ใฎใ‚ณใƒณ" +"ใƒ”ใƒฅใƒผใ‚ฟใƒผใฎใƒ•ใ‚กใ‚คใƒซใ‚’้–‹ใ„ใŸใ‚Šไฟๅญ˜ใ—ใŸใ‚Šใงใใพใ™ใ€‚" + +#: src/lib/menu.sh:262 +msgid "" +"The apps can open and save files in this folder. It shows up as a drive in " +"them." +msgstr "" +"ใ‚ขใƒ—ใƒชใฏใ“ใฎใƒ•ใ‚ฉใƒซใƒ€ใƒผใฎใƒ•ใ‚กใ‚คใƒซใ‚’้–‹ใ„ใŸใ‚Šไฟๅญ˜ใ—ใŸใ‚Šใงใใพใ™ใ€‚ใ‚ขใƒ—ใƒชๅ†…ใงใฏใƒ‰" +"ใƒฉใ‚คใƒ–ใจใ—ใฆ่กจ็คบใ•ใ‚Œใพใ™ใ€‚" + +#: src/lib/menu.sh:263 +msgid "" +"Offers the apps under \"Open with\" for their file types, for example Word " +"for .docx. The folder of the file is shared with the app while it is open." +msgstr "" +"ใƒ•ใ‚กใ‚คใƒซใฎ็จฎ้กžใ”ใจใซใ€Œใ‚ขใƒ—ใƒชใ‚ฑใƒผใ‚ทใƒงใƒณใง้–‹ใใ€ใซใ‚ขใƒ—ใƒชใ‚’่กจ็คบใ—ใพใ™" +"๏ผˆไพ‹: .docx ใชใ‚‰ Word๏ผ‰ใ€‚ใƒ•ใ‚กใ‚คใƒซใ‚’้–‹ใ„ใฆใ„ใ‚‹้–“ใ€ใใฎใƒ•ใ‚ฉใƒซใƒ€ใƒผใŒๅ…ฑๆœ‰ใ•ใ‚Œใพ" +"ใ™ใ€‚" + +#: src/lib/menu.sh:264 +msgid "" +"How big the apps are drawn. Automatic follows the scale of your desktop, so " +"they are sharp and the right size." +msgstr "" +"ใ‚ขใƒ—ใƒชใ‚’ๆใๅคงใใ•ใงใ™ใ€‚่‡ชๅ‹•ใงใฏใƒ‡ใ‚นใ‚ฏใƒˆใƒƒใƒ—ใฎๆ‹กๅคง็އใซๅˆใ‚ใ›ใ‚‹ใฎใงใ€ใใฃใใ‚Š" +"ใจ้ฉๅˆ‡ใชๅคงใใ•ใซใชใ‚Šใพใ™ใ€‚" + +#: src/lib/menu.sh:265 +msgid "Plays the sound of the apps here, for calls and videos." +msgstr "ใ‚ขใƒ—ใƒชใฎ้Ÿณใ‚’ใ“ใฎใ‚ณใƒณใƒ”ใƒฅใƒผใ‚ฟใƒผใงๅ†็”Ÿใ—ใพใ™ใ€‚้€š่ฉฑใ‚„ๅ‹•็”ปใซใ€‚" + +#: src/lib/menu.sh:266 +msgid "" +"Paints every window right. But some servers end the session right after " +"signing in, then go back to GFX." +msgstr "ใ™ในใฆใฎใ‚ฆใ‚ฃใƒณใƒ‰ใ‚ฆใ‚’ๆญฃใ—ใๆใใพใ™ใ€‚ใŸใ ใ—ใ‚ตใ‚คใƒณใ‚คใƒณ็›ดๅพŒใซใ‚ปใƒƒใ‚ทใƒงใƒณใ‚’ๅˆ‡ใ‚‹ใ‚ตใƒผใƒใƒผใ‚‚ใ‚ใ‚Šใ€ใใฎๅ ดๅˆใฏ GFX ใซๆˆปใ—ใฆใใ ใ•ใ„ใ€‚" + +#: src/lib/menu.sh:267 +msgid "" +"Works with every server. FreeRDP may not repaint a window after it was " +"minimized or resized." +msgstr "ใฉใฎใ‚ตใƒผใƒใƒผใงใ‚‚ๅ‹•ใใพใ™ใ€‚ๆœ€ๅฐๅŒ–ใ‚„ใ‚ตใ‚คใ‚บๅค‰ๆ›ดใฎๅพŒใ€FreeRDP ใŒใ‚ฆใ‚ฃใƒณใƒ‰ใ‚ฆใ‚’ๆใ็›ดใ•ใชใ„ใ“ใจใŒใ‚ใ‚Šใพใ™ใ€‚" + +#: src/lib/menu.sh:268 +msgid "" +"Always waits before the second sign-in. For when the second prompt does not " +"come with automatic." +msgstr "" +"2 ๅ›ž็›ฎใฎใ‚ตใ‚คใƒณใ‚คใƒณใฎๅ‰ใซๅฟ…ใšๅพ…ใกใพใ™ใ€‚่‡ชๅ‹•ใง 2 ๅ›ž็›ฎใฎ็ขบ่ชใŒๆฅใชใ„ใจใใซใ€‚" + +#: src/lib/menu.sh:269 +msgid "Never waits. Right when your company has no MFA." +msgstr "ๅพ…ใกใพใ›ใ‚“ใ€‚ไผš็คพใŒ MFA ใ‚’ไฝฟใฃใฆใ„ใชใ„ใจใๅ‘ใ‘ใงใ™ใ€‚" + +#: src/lib/menu.sh:270 +msgid "" +"Many servers with MFA need a second sign-in, and its prompt only comes after " +"a short wait. Automatic waits when the first sign-in took a while." +msgstr "" +"MFA ใ‚’ไฝฟใ†ๅคšใใฎใ‚ตใƒผใƒใƒผใงใฏ 2 ๅ›ž็›ฎใฎใ‚ตใ‚คใƒณใ‚คใƒณใŒๅฟ…่ฆใงใ€ใใฎ็ขบ่ชใฏๅฐ‘ใ—ๅพ…ใŸใช" +"ใ„ใจๅฑŠใใพใ›ใ‚“ใ€‚่‡ชๅ‹•ใงใฏใ€1 ๅ›ž็›ฎใซๆ™‚้–“ใŒใ‹ใ‹ใฃใŸใจใใซๅพ…ใกใพใ™ใ€‚" + +#: src/lib/menu.sh:273 +msgid "" +"Loads the list of apps again once a day, so new apps show up by themselves." +msgstr "" +"1 ๆ—ฅ 1 ๅ›žใ‚ขใƒ—ใƒชใฎไธ€่ฆงใ‚’่ชญใฟ่พผใฟ็›ดใ™ใฎใงใ€ๆ–ฐใ—ใ„ใ‚ขใƒ—ใƒชใŒ่‡ชๅ‹•ใง็พใ‚Œใพใ™ใ€‚" + +#: src/lib/menu.sh:275 src/lib/menu.sh:330 +msgid "Needs a systemd user session." +msgstr "systemd ใฎใƒฆใƒผใ‚ถใƒผใ‚ปใƒƒใ‚ทใƒงใƒณใŒๅฟ…่ฆใงใ™ใ€‚" + +#: src/lib/menu.sh:322 src/lib/menu.sh:325 +msgid "Could not save the setting." +msgstr "่จญๅฎšใ‚’ไฟๅญ˜ใงใใพใ›ใ‚“ใงใ—ใŸใ€‚" + +#: src/lib/menu.sh:371 src/lib/menu.sh:609 +msgid "Settings" +msgstr "่จญๅฎš" + +#: src/lib/menu.sh:372 +msgid "โ†‘โ†“ select โ†โ†’ or Space: change q: back" +msgstr "โ†‘โ†“ ้ธๆŠž โ†โ†’ ใพใŸใฏใ‚นใƒšใƒผใ‚น: ๅค‰ๆ›ด q: ๆˆปใ‚‹" + +#: src/lib/menu.sh:373 +msgid "ON" +msgstr "ใ‚ชใƒณ" + +#: src/lib/menu.sh:374 +msgid "OFF" +msgstr "ใ‚ชใƒ•" + +#: src/lib/menu.sh:456 src/lib/menu.sh:608 +msgid "Workspaces" +msgstr "ใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚น" + +#. TRANSLATORS: keep the letters, they are the keys. +#: src/lib/menu.sh:458 +msgid "" +"โ†‘โ†“ select r: refresh s: sign in again d: remove a: add q: back" +msgstr "โ†‘โ†“ ้ธๆŠž r: ๆ›ดๆ–ฐ s: ๅ†ใ‚ตใ‚คใƒณใ‚คใƒณ d: ๅ‰Š้™ค a: ่ฟฝๅŠ  q: ๆˆปใ‚‹" + +#: src/lib/menu.sh:459 +msgid "No workspace yet. Press a to add one." +msgstr "ใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใฏใพใ ใ‚ใ‚Šใพใ›ใ‚“ใ€‚a ใง่ฟฝๅŠ ใงใใพใ™ใ€‚" + +#: src/lib/menu.sh:484 +#, sh-printf-format +msgid "refreshed %s" +msgstr "%sใซๆ›ดๆ–ฐ" + +#: src/lib/menu.sh:512 +#, sh-printf-format +msgid "Remove \"%s\" and its apps?" +msgstr "ใ€Œ%sใ€ใจใใฎใ‚ขใƒ—ใƒชใ‚’ๅ‰Š้™คใ—ใพใ™ใ‹๏ผŸ" + +#: src/lib/menu.sh:536 src/lib/menu.sh:607 +msgid "Start an app" +msgstr "ใ‚ขใƒ—ใƒชใ‚’่ตทๅ‹•ใ™ใ‚‹" + +#: src/lib/menu.sh:537 +msgid "โ†‘โ†“ select Enter: start q: back" +msgstr "โ†‘โ†“ ้ธๆŠž Enter: ่ตทๅ‹• q: ๆˆปใ‚‹" + +#: src/lib/menu.sh:538 +msgid "No apps yet. Add a workspace first." +msgstr "ใ‚ขใƒ—ใƒชใฏใพใ ใ‚ใ‚Šใพใ›ใ‚“ใ€‚ๅ…ˆใซใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใ‚’่ฟฝๅŠ ใ—ใฆใใ ใ•ใ„ใ€‚" + +#: src/lib/menu.sh:579 +#, sh-printf-format +msgid "%s is starting. It opens in a window of its own." +msgstr "%s ใ‚’่ตทๅ‹•ใ—ใฆใ„ใพใ™ใ€‚ๅฐ‚็”จใฎใ‚ฆใ‚ฃใƒณใƒ‰ใ‚ฆใง้–‹ใใพใ™ใ€‚" + +#: src/lib/menu.sh:606 +msgid "Add a workspace" +msgstr "ใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใ‚’่ฟฝๅŠ " + +#: src/lib/menu.sh:610 +msgid "Quit" +msgstr "็ต‚ไบ†" + +#: src/lib/workspace.sh:163 +msgid "The server did not accept the user name or password." +msgstr "ใ‚ตใƒผใƒใƒผใŒใƒฆใƒผใ‚ถใƒผๅใพใŸใฏใƒ‘ใ‚นใƒฏใƒผใƒ‰ใ‚’ๅ—ใ‘ไป˜ใ‘ใพใ›ใ‚“ใงใ—ใŸใ€‚" + +#: src/lib/workspace.sh:164 +#, sh-printf-format +msgid "Could not reach the server: %s" +msgstr "ใ‚ตใƒผใƒใƒผใซๆŽฅ็ถšใงใใพใ›ใ‚“ใงใ—ใŸ: %s" + +#: src/lib/workspace.sh:165 +#, sh-printf-format +msgid "There is no RD Web feed at %s." +msgstr "%s ใซ RD Web ใฎใƒ•ใ‚ฃใƒผใƒ‰ใฏใ‚ใ‚Šใพใ›ใ‚“ใ€‚" + +#: src/lib/workspace.sh:166 +#, sh-printf-format +msgid "Could not load the apps: %s" +msgstr "ใ‚ขใƒ—ใƒชใ‚’่ชญใฟ่พผใ‚ใพใ›ใ‚“ใงใ—ใŸ: %s" + +#: src/lib/workspace.sh:282 +#, sh-printf-format +msgid "Remote desktop of %s" +msgstr "%s ใฎใƒชใƒขใƒผใƒˆใƒ‡ใ‚นใ‚ฏใƒˆใƒƒใƒ—" + +#: src/lib/workspace.sh:284 +#, sh-printf-format +msgid "%s from %s" +msgstr "%s๏ผˆ%s๏ผ‰" + +#: src/lib/menu.sh +msgid "Apps" +msgstr "ใ‚ขใƒ—ใƒช" + +#: src/lib/menu.sh +msgid "Share a folder" +msgstr "ใƒ•ใ‚ฉใƒซใƒ€ใƒผใ‚’ๅ…ฑๆœ‰" + +#: src/lib/menu.sh +msgid "Open files with the apps" +msgstr "ใƒ•ใ‚กใ‚คใƒซใ‚’ใ‚ขใƒ—ใƒชใง้–‹ใ" + +#: src/lib/menu.sh +msgid "Size" +msgstr "ใ‚ตใ‚คใ‚บ" + +#: src/lib/menu.sh +msgid "Sound" +msgstr "้Ÿณๅฃฐ" + +#: src/lib/menu.sh +msgid "Graphics" +msgstr "ใ‚ฐใƒฉใƒ•ใ‚ฃใƒƒใ‚ฏ" + +#: src/lib/menu.sh +msgid "Signing in" +msgstr "ใ‚ตใ‚คใƒณใ‚คใƒณ" + +#: src/lib/menu.sh +msgid "Wait for the second MFA prompt" +msgstr "2 ๅ›ž็›ฎใฎ MFA ็ขบ่ชใ‚’ๅพ…ใค" + +#: src/lib/menu.sh +msgid "Refresh the apps every day" +msgstr "ใ‚ขใƒ—ใƒชใ‚’ๆฏŽๆ—ฅๆ›ดๆ–ฐ" diff --git a/po/ko.po b/po/ko.po new file mode 100644 index 0000000..3f73148 --- /dev/null +++ b/po/ko.po @@ -0,0 +1,547 @@ +# Korean translation for rdfeed. +# Copyright (C) 2026 Felitendo +# This file is distributed under the same license as rdfeed. +# +msgid "" +msgstr "" +"Project-Id-Version: rdfeed 1.0.0\n" +"Report-Msgid-Bugs-To: https://github.com/LoonixTools/rdfeed/issues\n" +"POT-Creation-Date: 2026-10-02 10:29+0200\n" +"PO-Revision-Date: 2026-10-02 09:30+0200\n" +"Last-Translator: Felitendo\n" +"Language-Team: Korean\n" +"Language: ko\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/rdfeed:79 src/rdfeed:103 +#, sh-printf-format +msgid "Could not write to %s." +msgstr "%s์— ์“ฐ์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค." + +#: src/rdfeed:82 +#, sh-printf-format +msgid "Signing in to %sโ€ฆ" +msgstr "%s์— ๋กœ๊ทธ์ธํ•˜๋Š” ์ค‘โ€ฆ" + +#: src/rdfeed:100 +msgid "" +"The password could not be saved in the keyring, so each app asks for it." +msgstr "๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ํ‚ค๋ง์— ์ €์žฅํ•˜์ง€ ๋ชปํ•ด ์•ฑ๋งˆ๋‹ค ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ฌป์Šต๋‹ˆ๋‹ค." + +#: src/rdfeed:111 +#, sh-printf-format +msgid "1 app from %s is now in your app menu." +msgstr "%s์˜ ์•ฑ 1๊ฐœ๊ฐ€ ์•ฑ ๋ฉ”๋‰ด์— ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + +#: src/rdfeed:113 +#, sh-printf-format +msgid "%d apps from %s are now in your app menu." +msgstr "์•ฑ %d๊ฐœ(%s)๊ฐ€ ์•ฑ ๋ฉ”๋‰ด์— ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + +#: src/rdfeed:129 +msgid "" +"Your IT department has the address. A work e-mail address often works too." +msgstr "" +"์ฃผ์†Œ๋Š” IT ๋ถ€์„œ์—์„œ ์•Œ๋ ค ์ค๋‹ˆ๋‹ค. ํšŒ์‚ฌ ์ด๋ฉ”์ผ ์ฃผ์†Œ๋กœ๋„ ๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค." + +#: src/rdfeed:130 +msgid "Feed address, server or e-mail:" +msgstr "ํ”ผ๋“œ ์ฃผ์†Œ, ์„œ๋ฒ„ ๋˜๋Š” ์ด๋ฉ”์ผ:" + +#: src/rdfeed:137 +#, sh-printf-format +msgid "No feed is registered for %s. Ask your IT department for the address." +msgstr "%s์— ๋“ฑ๋ก๋œ ํ”ผ๋“œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. IT ๋ถ€์„œ์— ์ฃผ์†Œ๋ฅผ ๋ฌผ์–ด๋ณด์„ธ์š”." + +#: src/rdfeed:142 src/rdfeed:156 +msgid "User name (DOMAIN\\name or name@domain):" +msgstr "์‚ฌ์šฉ์ž ์ด๋ฆ„ (DOMAIN\\name ๋˜๋Š” name@domain):" + +#: src/rdfeed:146 src/rdfeed:159 +msgid "Password:" +msgstr "๋น„๋ฐ€๋ฒˆํ˜ธ:" + +#: src/rdfeed:178 src/rdfeed:236 +#, sh-printf-format +msgid "No workspace yet. Add one with `%s add`." +msgstr "์•„์ง ์ž‘์—… ๊ณต๊ฐ„์ด ์—†์Šต๋‹ˆ๋‹ค. `%s add`๋กœ ์ถ”๊ฐ€ํ•˜์„ธ์š”." + +#: src/rdfeed:187 src/lib/launch.sh:220 +#, sh-printf-format +msgid "No password saved for %s. Sign in again in rdfeed." +msgstr "%s์˜ ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ €์žฅ๋˜์–ด ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. rdfeed์—์„œ ๋‹ค์‹œ ๋กœ๊ทธ์ธํ•˜์„ธ์š”." + +#: src/rdfeed:191 src/lib/launch.sh:149 +#, sh-printf-format +msgid "Password for %s:" +msgstr "%s ๋น„๋ฐ€๋ฒˆํ˜ธ:" + +#: src/rdfeed:195 +#, sh-printf-format +msgid "Refreshing %sโ€ฆ" +msgstr "%s ์ƒˆ๋กœ ๊ณ ์น˜๋Š” ์ค‘โ€ฆ" + +#: src/rdfeed:205 +msgid "The password no longer works. Sign in again in rdfeed." +msgstr "๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๋” ์ด์ƒ ๋งž์ง€ ์•Š์Šต๋‹ˆ๋‹ค. rdfeed์—์„œ ๋‹ค์‹œ ๋กœ๊ทธ์ธํ•˜์„ธ์š”." + +#: src/rdfeed:214 +#, sh-printf-format +msgid "%s: %d apps" +msgstr "%s: ์•ฑ %d๊ฐœ" + +#: src/rdfeed:222 +#, sh-printf-format +msgid "Which workspace? See `%s list` for the names." +msgstr "์–ด๋А ์ž‘์—… ๊ณต๊ฐ„์ธ๊ฐ€์š”? ์ด๋ฆ„์€ `%s list`์—์„œ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." + +#: src/rdfeed:229 +#, sh-printf-format +msgid "%s and its apps are removed." +msgstr "%s์™€(๊ณผ) ํ•ด๋‹น ์•ฑ์„ ์‚ญ์ œํ–ˆ์Šต๋‹ˆ๋‹ค." + +#: src/rdfeed:253 +#, sh-printf-format +msgid "Which app? See `%s list`." +msgstr "์–ด๋А ์•ฑ์ธ๊ฐ€์š”? `%s list`๋ฅผ ๋ณด์„ธ์š”." + +#: src/rdfeed:268 src/lib/launch.sh:199 +#, sh-printf-format +msgid "There is no app %s. See `%s list`." +msgstr "%s ์•ฑ์ด ์—†์Šต๋‹ˆ๋‹ค. `%s list`๋ฅผ ๋ณด์„ธ์š”." + +#: src/rdfeed:270 +#, sh-printf-format +msgid "Several workspaces have %s. Write it as WORKSPACE/APP, see `%s list`." +msgstr "" +"์—ฌ๋Ÿฌ ์ž‘์—… ๊ณต๊ฐ„์— %s์ด(๊ฐ€) ์žˆ์Šต๋‹ˆ๋‹ค. ์ž‘์—…๊ณต๊ฐ„/์•ฑ ํ˜•์‹์œผ๋กœ ์“ฐ์„ธ์š”. `%s list`๋ฅผ " +"๋ณด์„ธ์š”." + +#: src/rdfeed:278 +msgid "Usage: rdfeed [command]" +msgstr "์‚ฌ์šฉ๋ฒ•: rdfeed [๋ช…๋ น]" + +#: src/rdfeed:280 +msgid "Commands:" +msgstr "๋ช…๋ น:" + +#: src/rdfeed:281 +msgid "Add the workspace of your company" +msgstr "ํšŒ์‚ฌ์˜ ์ž‘์—… ๊ณต๊ฐ„ ์ถ”๊ฐ€" + +#: src/rdfeed:282 +msgid "Load the apps again" +msgstr "์•ฑ ๋‹ค์‹œ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ" + +#: src/rdfeed:283 +msgid "List the workspaces and their apps" +msgstr "์ž‘์—… ๊ณต๊ฐ„๊ณผ ์•ฑ ๋ชฉ๋ก ๋ณด๊ธฐ" + +#: src/rdfeed:284 +msgid "Start an app, or open a file with it" +msgstr "์•ฑ ์‹œ์ž‘ ๋˜๋Š” ํŒŒ์ผ ์—ด๊ธฐ" + +#: src/rdfeed:285 +msgid "Remove a workspace and its apps" +msgstr "์ž‘์—… ๊ณต๊ฐ„๊ณผ ์•ฑ ์‚ญ์ œ" + +#: src/rdfeed:286 +msgid "Show this help" +msgstr "์ด ๋„์›€๋ง ๋ณด๊ธฐ" + +#: src/rdfeed:287 +msgid "Show the version" +msgstr "๋ฒ„์ „ ๋ณด๊ธฐ" + +#: src/rdfeed:289 +msgid "Without a command an interactive menu is shown." +msgstr "๋ช…๋ น ์—†์ด ์‹คํ–‰ํ•˜๋ฉด ๋Œ€ํ™”ํ˜• ๋ฉ”๋‰ด๊ฐ€ ๋‚˜์˜ต๋‹ˆ๋‹ค." + +#: src/rdfeed:313 +#, sh-printf-format +msgid "Unknown command: %s" +msgstr "์•Œ ์ˆ˜ ์—†๋Š” ๋ช…๋ น: %s" + +#: src/lib/common.sh:174 src/lib/menu.sh:251 +msgid "never" +msgstr "์—†์Œ" + +#: src/lib/common.sh:184 +msgid "just now" +msgstr "๋ฐฉ๊ธˆ" + +#: src/lib/common.sh:186 +msgid "1 minute ago" +msgstr "1๋ถ„ ์ „" + +#: src/lib/common.sh:189 +#, sh-printf-format +msgid "%d minutes ago" +msgstr "%d๋ถ„ ์ „" + +#: src/lib/common.sh:191 +msgid "1 hour ago" +msgstr "1์‹œ๊ฐ„ ์ „" + +#: src/lib/common.sh:194 +#, sh-printf-format +msgid "%d hours ago" +msgstr "%d์‹œ๊ฐ„ ์ „" + +#: src/lib/common.sh:196 +msgid "1 day ago" +msgstr "1์ผ ์ „" + +#: src/lib/common.sh:199 +#, sh-printf-format +msgid "%d days ago" +msgstr "%d์ผ ์ „" + +#: src/lib/launch.sh:169 +msgid "" +"Your password has expired. Change it on Windows or on the RD Web page, then " +"sign in again in rdfeed." +msgstr "" +"๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๋งŒ๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. Windows๋‚˜ RD Web ํŽ˜์ด์ง€์—์„œ ๋ฐ”๊พผ ๋‹ค์Œ rdfeed์—์„œ " +"๋‹ค์‹œ ๋กœ๊ทธ์ธํ•˜์„ธ์š”." + +#: src/lib/launch.sh:171 +msgid "Your account is locked. Ask your IT department." +msgstr "๊ณ„์ •์ด ์ž ๊ฒผ์Šต๋‹ˆ๋‹ค. IT ๋ถ€์„œ์— ๋ฌธ์˜ํ•˜์„ธ์š”." + +#: src/lib/launch.sh:173 +msgid "The server did not accept the password. Sign in again in rdfeed." +msgstr "์„œ๋ฒ„๊ฐ€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ฐ›์•„๋“ค์ด์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. rdfeed์—์„œ ๋‹ค์‹œ ๋กœ๊ทธ์ธํ•˜์„ธ์š”." + +#: src/lib/launch.sh:175 +msgid "Could not reach the server. Check the internet connection." +msgstr "์„œ๋ฒ„์— ์—ฐ๊ฒฐํ•˜์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค. ์ธํ„ฐ๋„ท ์—ฐ๊ฒฐ์„ ํ™•์ธํ•˜์„ธ์š”." + +#: src/lib/launch.sh:178 +#, sh-printf-format +msgid "FreeRDP stopped: %s" +msgstr "FreeRDP๊ฐ€ ๋ฉˆ์ท„์Šต๋‹ˆ๋‹ค: %s" + +#: src/lib/launch.sh:205 src/lib/launch.sh:206 +#, sh-printf-format +msgid "File not found: %s" +msgstr "ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค: %s" + +#: src/lib/launch.sh:212 src/lib/launch.sh:213 +msgid "" +"FreeRDP 3 is missing. Install it to start the apps (the package is called " +"freerdp)." +msgstr "" +"FreeRDP 3์ด ์—†์Šต๋‹ˆ๋‹ค. ์•ฑ์„ ์‹œ์ž‘ํ•˜๋ ค๋ฉด ์„ค์น˜ํ•˜์„ธ์š”(ํŒจํ‚ค์ง€ ์ด๋ฆ„์€ freerdp)." + +#: src/lib/launch.sh:277 +#, sh-printf-format +msgid "Opening %s in %s" +msgstr "%s ํŒŒ์ผ์„ %s(์œผ)๋กœ ์—ฌ๋Š” ์ค‘" + +#: src/lib/launch.sh:278 +#, sh-printf-format +msgid "If it does not open by itself, open it in the app from the drive %s." +msgstr "์ €์ ˆ๋กœ ์—ด๋ฆฌ์ง€ ์•Š์œผ๋ฉด ์•ฑ์—์„œ %s ๋“œ๋ผ์ด๋ธŒ์˜ ํŒŒ์ผ์„ ์—ฌ์„ธ์š”." + +#: src/lib/launch.sh:280 +#, sh-printf-format +msgid "Starting %s" +msgstr "%s ์‹œ์ž‘ํ•˜๋Š” ์ค‘" + +#: src/lib/launch.sh:281 +msgid "" +"If your company uses MFA, confirm the sign-in in your authenticator app." +msgstr "ํšŒ์‚ฌ์—์„œ MFA๋ฅผ ์“ด๋‹ค๋ฉด ์ธ์ฆ ์•ฑ์—์„œ ๋กœ๊ทธ์ธ์„ ์Šน์ธํ•˜์„ธ์š”." + +#: src/lib/launch.sh:290 +msgid "A second sign-in is coming" +msgstr "๋‘ ๋ฒˆ์งธ ๋กœ๊ทธ์ธ์ด ์˜ต๋‹ˆ๋‹ค" + +#: src/lib/launch.sh:291 +#, sh-printf-format +msgid "" +"Confirm the second prompt in your authenticator app too. It comes in about " +"%d seconds." +msgstr "์ธ์ฆ ์•ฑ์—์„œ ๋‘ ๋ฒˆ์งธ ์š”์ฒญ๋„ ์Šน์ธํ•˜์„ธ์š”. ์•ฝ %d์ดˆ ๋’ค์— ์˜ต๋‹ˆ๋‹ค." + +#: src/lib/launch.sh:301 +#, sh-printf-format +msgid "%s could not start" +msgstr "%s์„(๋ฅผ) ์‹œ์ž‘ํ•˜์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค" + +#: src/lib/launch.sh:303 +#, sh-printf-format +msgid "Log: %s" +msgstr "๋กœ๊ทธ: %s" + +#. TRANSLATORS: the letter after "[" is the key for yes. y works too. +#: src/lib/menu.sh:93 +msgid "[y/N]" +msgstr "[y/N]" + +#: src/lib/menu.sh:163 +msgid "1 app" +msgstr "์•ฑ 1๊ฐœ" + +#: src/lib/menu.sh:165 +#, sh-printf-format +msgid "%d apps" +msgstr "์•ฑ %d๊ฐœ" + +#: src/lib/menu.sh:190 +msgid "No workspace yet. Add the one of your company with [1]." +msgstr "์•„์ง ์ž‘์—… ๊ณต๊ฐ„์ด ์—†์Šต๋‹ˆ๋‹ค. [1]๋กœ ํšŒ์‚ฌ์˜ ์ž‘์—… ๊ณต๊ฐ„์„ ์ถ”๊ฐ€ํ•˜์„ธ์š”." + +#: src/lib/menu.sh:233 +msgid "off" +msgstr "๊บผ์ง" + +#: src/lib/menu.sh:234 +msgid "home folder" +msgstr "ํ™ˆ ํด๋”" + +#: src/lib/menu.sh:237 +msgid "Documents" +msgstr "๋ฌธ์„œ" + +#: src/lib/menu.sh:238 +msgid "Downloads" +msgstr "๋‹ค์šด๋กœ๋“œ" + +#: src/lib/menu.sh:243 +msgid "not found" +msgstr "์ฐพ์„ ์ˆ˜ ์—†์Œ" + +#: src/lib/menu.sh:246 +#, sh-printf-format +msgid "automatic (%d %%)" +msgstr "์ž๋™ (%d%%)" + +#: src/lib/menu.sh:248 +msgid "classic" +msgstr "ํด๋ž˜์‹" + +#: src/lib/menu.sh:249 +msgid "GFX" +msgstr "GFX" + +#: src/lib/menu.sh:250 +msgid "always" +msgstr "ํ•ญ์ƒ" + +#: src/lib/menu.sh:252 +msgid "automatic" +msgstr "์ž๋™" + +#: src/lib/menu.sh:261 +msgid "" +"The apps only see the files on the server. Pick a folder to open and save " +"files of this computer in them." +msgstr "" +"์•ฑ์€ ์„œ๋ฒ„์˜ ํŒŒ์ผ๋งŒ ๋ด…๋‹ˆ๋‹ค. ํด๋”๋ฅผ ๊ณ ๋ฅด๋ฉด ์ด ์ปดํ“จํ„ฐ์˜ ํŒŒ์ผ์„ ์—ด๊ณ  ์ €์žฅํ•  ์ˆ˜ ์žˆ" +"์Šต๋‹ˆ๋‹ค." + +#: src/lib/menu.sh:262 +msgid "" +"The apps can open and save files in this folder. It shows up as a drive in " +"them." +msgstr "" +"์•ฑ์ด ์ด ํด๋”์˜ ํŒŒ์ผ์„ ์—ด๊ณ  ์ €์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•ฑ์—์„œ๋Š” ๋“œ๋ผ์ด๋ธŒ๋กœ ๋ณด์ž…๋‹ˆ๋‹ค." + +#: src/lib/menu.sh:263 +msgid "" +"Offers the apps under \"Open with\" for their file types, for example Word " +"for .docx. The folder of the file is shared with the app while it is open." +msgstr "" +"ํŒŒ์ผ ํ˜•์‹์— ๋งž๊ฒŒ \"์—ฐ๊ฒฐ ํ”„๋กœ๊ทธ๋žจ\"์— ์•ฑ์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค(์˜ˆ: .docx๋Š” Word). ํŒŒ์ผ" +"์ด ์—ด๋ ค ์žˆ๋Š” ๋™์•ˆ ํ•ด๋‹น ํด๋”๊ฐ€ ๊ณต์œ ๋ฉ๋‹ˆ๋‹ค." + +#: src/lib/menu.sh:264 +msgid "" +"How big the apps are drawn. Automatic follows the scale of your desktop, so " +"they are sharp and the right size." +msgstr "" +"์•ฑ์„ ๊ทธ๋ฆฌ๋Š” ํฌ๊ธฐ์ž…๋‹ˆ๋‹ค. ์ž๋™์€ ๋ฐ์Šคํฌํ†ฑ ๋ฐฐ์œจ์„ ๋”ฐ๋ฅด๋ฏ€๋กœ ์„ ๋ช…ํ•˜๊ณ  ์•Œ๋งž์€ ํฌ๊ธฐ" +"๋กœ ๋ณด์ž…๋‹ˆ๋‹ค." + +#: src/lib/menu.sh:265 +msgid "Plays the sound of the apps here, for calls and videos." +msgstr "์•ฑ์˜ ์†Œ๋ฆฌ๋ฅผ ์ด ์ปดํ“จํ„ฐ์—์„œ ์žฌ์ƒํ•ฉ๋‹ˆ๋‹ค. ํ†ตํ™”์™€ ๋™์˜์ƒ์— ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค." + +#: src/lib/menu.sh:266 +msgid "" +"Paints every window right. But some servers end the session right after " +"signing in, then go back to GFX." +msgstr "๋ชจ๋“  ์ฐฝ์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๊ทธ๋ฆฝ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋กœ๊ทธ์ธ ์งํ›„ ์„ธ์…˜์„ ๋Š๋Š” ์„œ๋ฒ„๋„ ์žˆ์œผ๋‹ˆ ๊ทธ๋•Œ๋Š” GFX๋กœ ๋Œ์•„๊ฐ€์„ธ์š”." + +#: src/lib/menu.sh:267 +msgid "" +"Works with every server. FreeRDP may not repaint a window after it was " +"minimized or resized." +msgstr "๋ชจ๋“  ์„œ๋ฒ„์—์„œ ๋ฉ๋‹ˆ๋‹ค. ์ตœ์†Œํ™”ํ•˜๊ฑฐ๋‚˜ ํฌ๊ธฐ๋ฅผ ๋ฐ”๊พผ ์ฐฝ์„ FreeRDP๊ฐ€ ๋‹ค์‹œ ๊ทธ๋ฆฌ์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." + +#: src/lib/menu.sh:268 +msgid "" +"Always waits before the second sign-in. For when the second prompt does not " +"come with automatic." +msgstr "" +"๋‘ ๋ฒˆ์งธ ๋กœ๊ทธ์ธ ์ „์— ํ•ญ์ƒ ๊ธฐ๋‹ค๋ฆฝ๋‹ˆ๋‹ค. ์ž๋™์ผ ๋•Œ ๋‘ ๋ฒˆ์งธ ์š”์ฒญ์ด ์˜ค์ง€ ์•Š์œผ๋ฉด ์“ฐ" +"์„ธ์š”." + +#: src/lib/menu.sh:269 +msgid "Never waits. Right when your company has no MFA." +msgstr "๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํšŒ์‚ฌ์—์„œ MFA๋ฅผ ์“ฐ์ง€ ์•Š์„ ๋•Œ ์•Œ๋งž์Šต๋‹ˆ๋‹ค." + +#: src/lib/menu.sh:270 +msgid "" +"Many servers with MFA need a second sign-in, and its prompt only comes after " +"a short wait. Automatic waits when the first sign-in took a while." +msgstr "" +"MFA๋ฅผ ์“ฐ๋Š” ๋งŽ์€ ์„œ๋ฒ„๋Š” ๋‘ ๋ฒˆ์งธ ๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•˜๊ณ , ๊ทธ ์š”์ฒญ์€ ์ž ์‹œ ๊ธฐ๋‹ค๋ฆฐ ๋’ค์—" +"์•ผ ์˜ต๋‹ˆ๋‹ค. ์ž๋™์€ ์ฒซ ๋กœ๊ทธ์ธ์ด ์˜ค๋ž˜ ๊ฑธ๋ ธ์„ ๋•Œ ๊ธฐ๋‹ค๋ฆฝ๋‹ˆ๋‹ค." + +#: src/lib/menu.sh:273 +msgid "" +"Loads the list of apps again once a day, so new apps show up by themselves." +msgstr "ํ•˜๋ฃจ์— ํ•œ ๋ฒˆ ์•ฑ ๋ชฉ๋ก์„ ๋‹ค์‹œ ๋ถˆ๋Ÿฌ์˜ค๋ฏ€๋กœ ์ƒˆ ์•ฑ์ด ์ €์ ˆ๋กœ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค." + +#: src/lib/menu.sh:275 src/lib/menu.sh:330 +msgid "Needs a systemd user session." +msgstr "systemd ์‚ฌ์šฉ์ž ์„ธ์…˜์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค." + +#: src/lib/menu.sh:322 src/lib/menu.sh:325 +msgid "Could not save the setting." +msgstr "์„ค์ •์„ ์ €์žฅํ•˜์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค." + +#: src/lib/menu.sh:371 src/lib/menu.sh:609 +msgid "Settings" +msgstr "์„ค์ •" + +#: src/lib/menu.sh:372 +msgid "โ†‘โ†“ select โ†โ†’ or Space: change q: back" +msgstr "โ†‘โ†“ ์„ ํƒ โ†โ†’ ๋˜๋Š” ์ŠคํŽ˜์ด์Šค: ๋ณ€๊ฒฝ q: ๋’ค๋กœ" + +#: src/lib/menu.sh:373 +msgid "ON" +msgstr "์ผœ์ง" + +#: src/lib/menu.sh:374 +msgid "OFF" +msgstr "๊บผ์ง" + +#: src/lib/menu.sh:456 src/lib/menu.sh:608 +msgid "Workspaces" +msgstr "์ž‘์—… ๊ณต๊ฐ„" + +#. TRANSLATORS: keep the letters, they are the keys. +#: src/lib/menu.sh:458 +msgid "" +"โ†‘โ†“ select r: refresh s: sign in again d: remove a: add q: back" +msgstr "โ†‘โ†“ ์„ ํƒ r: ์ƒˆ๋กœ ๊ณ ์นจ s: ๋‹ค์‹œ ๋กœ๊ทธ์ธ d: ์‚ญ์ œ a: ์ถ”๊ฐ€ q: ๋’ค๋กœ" + +#: src/lib/menu.sh:459 +msgid "No workspace yet. Press a to add one." +msgstr "์•„์ง ์ž‘์—… ๊ณต๊ฐ„์ด ์—†์Šต๋‹ˆ๋‹ค. a๋ฅผ ๋ˆŒ๋Ÿฌ ์ถ”๊ฐ€ํ•˜์„ธ์š”." + +#: src/lib/menu.sh:484 +#, sh-printf-format +msgid "refreshed %s" +msgstr "%s ์ƒˆ๋กœ ๊ณ ์นจ" + +#: src/lib/menu.sh:512 +#, sh-printf-format +msgid "Remove \"%s\" and its apps?" +msgstr "\"%s\"์™€(๊ณผ) ํ•ด๋‹น ์•ฑ์„ ์‚ญ์ œํ• ๊นŒ์š”?" + +#: src/lib/menu.sh:536 src/lib/menu.sh:607 +msgid "Start an app" +msgstr "์•ฑ ์‹œ์ž‘" + +#: src/lib/menu.sh:537 +msgid "โ†‘โ†“ select Enter: start q: back" +msgstr "โ†‘โ†“ ์„ ํƒ Enter: ์‹œ์ž‘ q: ๋’ค๋กœ" + +#: src/lib/menu.sh:538 +msgid "No apps yet. Add a workspace first." +msgstr "์•„์ง ์•ฑ์ด ์—†์Šต๋‹ˆ๋‹ค. ๋จผ์ € ์ž‘์—… ๊ณต๊ฐ„์„ ์ถ”๊ฐ€ํ•˜์„ธ์š”." + +#: src/lib/menu.sh:579 +#, sh-printf-format +msgid "%s is starting. It opens in a window of its own." +msgstr "%s์„(๋ฅผ) ์‹œ์ž‘ํ•˜๋Š” ์ค‘์ž…๋‹ˆ๋‹ค. ๋ณ„๋„ ์ฐฝ์œผ๋กœ ์—ด๋ฆฝ๋‹ˆ๋‹ค." + +#: src/lib/menu.sh:606 +msgid "Add a workspace" +msgstr "์ž‘์—… ๊ณต๊ฐ„ ์ถ”๊ฐ€" + +#: src/lib/menu.sh:610 +msgid "Quit" +msgstr "๋๋‚ด๊ธฐ" + +#: src/lib/workspace.sh:163 +msgid "The server did not accept the user name or password." +msgstr "์„œ๋ฒ„๊ฐ€ ์‚ฌ์šฉ์ž ์ด๋ฆ„์ด๋‚˜ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ฐ›์•„๋“ค์ด์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค." + +#: src/lib/workspace.sh:164 +#, sh-printf-format +msgid "Could not reach the server: %s" +msgstr "์„œ๋ฒ„์— ์—ฐ๊ฒฐํ•˜์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค: %s" + +#: src/lib/workspace.sh:165 +#, sh-printf-format +msgid "There is no RD Web feed at %s." +msgstr "%s์— RD Web ํ”ผ๋“œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค." + +#: src/lib/workspace.sh:166 +#, sh-printf-format +msgid "Could not load the apps: %s" +msgstr "์•ฑ์„ ๋ถˆ๋Ÿฌ์˜ค์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค: %s" + +#: src/lib/workspace.sh:282 +#, sh-printf-format +msgid "Remote desktop of %s" +msgstr "%s ์›๊ฒฉ ๋ฐ์Šคํฌํ†ฑ" + +#: src/lib/workspace.sh:284 +#, sh-printf-format +msgid "%s from %s" +msgstr "%s (%s)" + +#: src/lib/menu.sh +msgid "Apps" +msgstr "์•ฑ" + +#: src/lib/menu.sh +msgid "Share a folder" +msgstr "ํด๋” ๊ณต์œ " + +#: src/lib/menu.sh +msgid "Open files with the apps" +msgstr "์•ฑ์œผ๋กœ ํŒŒ์ผ ์—ด๊ธฐ" + +#: src/lib/menu.sh +msgid "Size" +msgstr "ํฌ๊ธฐ" + +#: src/lib/menu.sh +msgid "Sound" +msgstr "์†Œ๋ฆฌ" + +#: src/lib/menu.sh +msgid "Graphics" +msgstr "๊ทธ๋ž˜ํ”ฝ" + +#: src/lib/menu.sh +msgid "Signing in" +msgstr "๋กœ๊ทธ์ธ" + +#: src/lib/menu.sh +msgid "Wait for the second MFA prompt" +msgstr "๋‘ ๋ฒˆ์งธ MFA ์š”์ฒญ ๊ธฐ๋‹ค๋ฆฌ๊ธฐ" + +#: src/lib/menu.sh +msgid "Refresh the apps every day" +msgstr "๋งค์ผ ์•ฑ ์ƒˆ๋กœ ๊ณ ์นจ" diff --git a/po/nl.po b/po/nl.po new file mode 100644 index 0000000..8762785 --- /dev/null +++ b/po/nl.po @@ -0,0 +1,562 @@ +# Dutch translation for rdfeed. +# Copyright (C) 2026 Felitendo +# This file is distributed under the same license as rdfeed. +# +msgid "" +msgstr "" +"Project-Id-Version: rdfeed 1.0.0\n" +"Report-Msgid-Bugs-To: https://github.com/LoonixTools/rdfeed/issues\n" +"POT-Creation-Date: 2026-10-02 10:29+0200\n" +"PO-Revision-Date: 2026-10-02 09:30+0200\n" +"Last-Translator: Felitendo\n" +"Language-Team: Dutch\n" +"Language: nl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/rdfeed:79 src/rdfeed:103 +#, sh-printf-format +msgid "Could not write to %s." +msgstr "Kon niet schrijven naar %s." + +#: src/rdfeed:82 +#, sh-printf-format +msgid "Signing in to %sโ€ฆ" +msgstr "Aanmelden bij %sโ€ฆ" + +#: src/rdfeed:100 +msgid "" +"The password could not be saved in the keyring, so each app asks for it." +msgstr "" +"Het wachtwoord kon niet in de sleutelbos worden opgeslagen, dus elke app " +"vraagt erom." + +#: src/rdfeed:111 +#, sh-printf-format +msgid "1 app from %s is now in your app menu." +msgstr "1 app van %s staat nu in je app-menu." + +#: src/rdfeed:113 +#, sh-printf-format +msgid "%d apps from %s are now in your app menu." +msgstr "%d apps van %s staan nu in je app-menu." + +#: src/rdfeed:129 +msgid "" +"Your IT department has the address. A work e-mail address often works too." +msgstr "Je IT-afdeling heeft het adres. Vaak werkt je werk-e-mailadres ook." + +#: src/rdfeed:130 +msgid "Feed address, server or e-mail:" +msgstr "Feedadres, server of e-mail:" + +#: src/rdfeed:137 +#, sh-printf-format +msgid "No feed is registered for %s. Ask your IT department for the address." +msgstr "" +"Er is geen feed geregistreerd voor %s. Vraag je IT-afdeling om het adres." + +#: src/rdfeed:142 src/rdfeed:156 +msgid "User name (DOMAIN\\name or name@domain):" +msgstr "Gebruikersnaam (DOMEIN\\naam of naam@domein):" + +#: src/rdfeed:146 src/rdfeed:159 +msgid "Password:" +msgstr "Wachtwoord:" + +#: src/rdfeed:178 src/rdfeed:236 +#, sh-printf-format +msgid "No workspace yet. Add one with `%s add`." +msgstr "Nog geen werkruimte. Voeg er een toe met `%s add`." + +#: src/rdfeed:187 src/lib/launch.sh:220 +#, sh-printf-format +msgid "No password saved for %s. Sign in again in rdfeed." +msgstr "Geen wachtwoord opgeslagen voor %s. Meld je opnieuw aan in rdfeed." + +#: src/rdfeed:191 src/lib/launch.sh:149 +#, sh-printf-format +msgid "Password for %s:" +msgstr "Wachtwoord voor %s:" + +#: src/rdfeed:195 +#, sh-printf-format +msgid "Refreshing %sโ€ฆ" +msgstr "%s vernieuwenโ€ฆ" + +#: src/rdfeed:205 +msgid "The password no longer works. Sign in again in rdfeed." +msgstr "Het wachtwoord werkt niet meer. Meld je opnieuw aan in rdfeed." + +#: src/rdfeed:214 +#, sh-printf-format +msgid "%s: %d apps" +msgstr "%s: %d apps" + +#: src/rdfeed:222 +#, sh-printf-format +msgid "Which workspace? See `%s list` for the names." +msgstr "Welke werkruimte? De namen staan in `%s list`." + +#: src/rdfeed:229 +#, sh-printf-format +msgid "%s and its apps are removed." +msgstr "%s en de apps ervan zijn verwijderd." + +#: src/rdfeed:253 +#, sh-printf-format +msgid "Which app? See `%s list`." +msgstr "Welke app? Zie `%s list`." + +#: src/rdfeed:268 src/lib/launch.sh:199 +#, sh-printf-format +msgid "There is no app %s. See `%s list`." +msgstr "Er is geen app %s. Zie `%s list`." + +#: src/rdfeed:270 +#, sh-printf-format +msgid "Several workspaces have %s. Write it as WORKSPACE/APP, see `%s list`." +msgstr "" +"Meerdere werkruimtes hebben %s. Schrijf het als WERKRUIMTE/APP, zie `%s " +"list`." + +#: src/rdfeed:278 +msgid "Usage: rdfeed [command]" +msgstr "Gebruik: rdfeed [opdracht]" + +#: src/rdfeed:280 +msgid "Commands:" +msgstr "Opdrachten:" + +#: src/rdfeed:281 +msgid "Add the workspace of your company" +msgstr "De werkruimte van je bedrijf toevoegen" + +#: src/rdfeed:282 +msgid "Load the apps again" +msgstr "De apps opnieuw laden" + +#: src/rdfeed:283 +msgid "List the workspaces and their apps" +msgstr "Werkruimtes en hun apps tonen" + +#: src/rdfeed:284 +msgid "Start an app, or open a file with it" +msgstr "Een app starten, of er een bestand mee openen" + +#: src/rdfeed:285 +msgid "Remove a workspace and its apps" +msgstr "Een werkruimte en de apps ervan verwijderen" + +#: src/rdfeed:286 +msgid "Show this help" +msgstr "Deze hulp tonen" + +#: src/rdfeed:287 +msgid "Show the version" +msgstr "De versie tonen" + +#: src/rdfeed:289 +msgid "Without a command an interactive menu is shown." +msgstr "Zonder opdracht verschijnt een interactief menu." + +#: src/rdfeed:313 +#, sh-printf-format +msgid "Unknown command: %s" +msgstr "Onbekende opdracht: %s" + +#: src/lib/common.sh:174 src/lib/menu.sh:251 +msgid "never" +msgstr "nooit" + +#: src/lib/common.sh:184 +msgid "just now" +msgstr "zojuist" + +#: src/lib/common.sh:186 +msgid "1 minute ago" +msgstr "1 minuut geleden" + +#: src/lib/common.sh:189 +#, sh-printf-format +msgid "%d minutes ago" +msgstr "%d minuten geleden" + +#: src/lib/common.sh:191 +msgid "1 hour ago" +msgstr "1 uur geleden" + +#: src/lib/common.sh:194 +#, sh-printf-format +msgid "%d hours ago" +msgstr "%d uur geleden" + +#: src/lib/common.sh:196 +msgid "1 day ago" +msgstr "1 dag geleden" + +#: src/lib/common.sh:199 +#, sh-printf-format +msgid "%d days ago" +msgstr "%d dagen geleden" + +#: src/lib/launch.sh:169 +msgid "" +"Your password has expired. Change it on Windows or on the RD Web page, then " +"sign in again in rdfeed." +msgstr "" +"Je wachtwoord is verlopen. Wijzig het in Windows of op de RD Web-pagina en " +"meld je daarna opnieuw aan in rdfeed." + +#: src/lib/launch.sh:171 +msgid "Your account is locked. Ask your IT department." +msgstr "Je account is geblokkeerd. Vraag het je IT-afdeling." + +#: src/lib/launch.sh:173 +msgid "The server did not accept the password. Sign in again in rdfeed." +msgstr "" +"De server heeft het wachtwoord niet geaccepteerd. Meld je opnieuw aan in " +"rdfeed." + +#: src/lib/launch.sh:175 +msgid "Could not reach the server. Check the internet connection." +msgstr "Kon de server niet bereiken. Controleer de internetverbinding." + +#: src/lib/launch.sh:178 +#, sh-printf-format +msgid "FreeRDP stopped: %s" +msgstr "FreeRDP is gestopt: %s" + +#: src/lib/launch.sh:205 src/lib/launch.sh:206 +#, sh-printf-format +msgid "File not found: %s" +msgstr "Bestand niet gevonden: %s" + +#: src/lib/launch.sh:212 src/lib/launch.sh:213 +msgid "" +"FreeRDP 3 is missing. Install it to start the apps (the package is called " +"freerdp)." +msgstr "" +"FreeRDP 3 ontbreekt. Installeer het om de apps te starten (het pakket heet " +"freerdp)." + +#: src/lib/launch.sh:277 +#, sh-printf-format +msgid "Opening %s in %s" +msgstr "%s wordt geopend in %s" + +#: src/lib/launch.sh:278 +#, sh-printf-format +msgid "If it does not open by itself, open it in the app from the drive %s." +msgstr "" +"Gaat het niet vanzelf open, open het dan in de app vanaf het station %s." + +#: src/lib/launch.sh:280 +#, sh-printf-format +msgid "Starting %s" +msgstr "%s wordt gestart" + +#: src/lib/launch.sh:281 +msgid "" +"If your company uses MFA, confirm the sign-in in your authenticator app." +msgstr "" +"Gebruikt je bedrijf MFA, bevestig de aanmelding dan in je authenticator-app." + +#: src/lib/launch.sh:290 +msgid "A second sign-in is coming" +msgstr "Er komt een tweede aanmelding" + +#: src/lib/launch.sh:291 +#, sh-printf-format +msgid "" +"Confirm the second prompt in your authenticator app too. It comes in about " +"%d seconds." +msgstr "" +"Bevestig ook het tweede verzoek in je authenticator-app. Het komt over " +"ongeveer %d seconden." + +#: src/lib/launch.sh:301 +#, sh-printf-format +msgid "%s could not start" +msgstr "%s kon niet starten" + +#: src/lib/launch.sh:303 +#, sh-printf-format +msgid "Log: %s" +msgstr "Logboek: %s" + +#. TRANSLATORS: the letter after "[" is the key for yes. y works too. +#: src/lib/menu.sh:93 +msgid "[y/N]" +msgstr "[j/N]" + +#: src/lib/menu.sh:163 +msgid "1 app" +msgstr "1 app" + +#: src/lib/menu.sh:165 +#, sh-printf-format +msgid "%d apps" +msgstr "%d apps" + +#: src/lib/menu.sh:190 +msgid "No workspace yet. Add the one of your company with [1]." +msgstr "Nog geen werkruimte. Voeg die van je bedrijf toe met [1]." + +#: src/lib/menu.sh:233 +msgid "off" +msgstr "uit" + +#: src/lib/menu.sh:234 +msgid "home folder" +msgstr "thuismap" + +#: src/lib/menu.sh:237 +msgid "Documents" +msgstr "Documenten" + +#: src/lib/menu.sh:238 +msgid "Downloads" +msgstr "Downloads" + +#: src/lib/menu.sh:243 +msgid "not found" +msgstr "niet gevonden" + +#: src/lib/menu.sh:246 +#, sh-printf-format +msgid "automatic (%d %%)" +msgstr "automatisch (%d %%)" + +#: src/lib/menu.sh:248 +msgid "classic" +msgstr "klassiek" + +#: src/lib/menu.sh:249 +msgid "GFX" +msgstr "GFX" + +#: src/lib/menu.sh:250 +msgid "always" +msgstr "altijd" + +#: src/lib/menu.sh:252 +msgid "automatic" +msgstr "automatisch" + +#: src/lib/menu.sh:261 +msgid "" +"The apps only see the files on the server. Pick a folder to open and save " +"files of this computer in them." +msgstr "" +"De apps zien alleen de bestanden op de server. Kies een map, dan kunnen ze " +"bestanden van deze computer openen en opslaan." + +#: src/lib/menu.sh:262 +msgid "" +"The apps can open and save files in this folder. It shows up as a drive in " +"them." +msgstr "" +"De apps kunnen in deze map bestanden openen en opslaan. Hij verschijnt " +"daarin als station." + +#: src/lib/menu.sh:263 +msgid "" +"Offers the apps under \"Open with\" for their file types, for example Word " +"for .docx. The folder of the file is shared with the app while it is open." +msgstr "" +"Biedt de apps aan onder \"Openen met\" voor hun bestandstypen, bijvoorbeeld " +"Word voor .docx. De map van het bestand wordt gedeeld zolang het open is." + +#: src/lib/menu.sh:264 +msgid "" +"How big the apps are drawn. Automatic follows the scale of your desktop, so " +"they are sharp and the right size." +msgstr "" +"Hoe groot de apps worden getekend. Automatisch volgt de schaal van je " +"bureaublad, zo zijn ze scherp en goed van formaat." + +#: src/lib/menu.sh:265 +msgid "Plays the sound of the apps here, for calls and videos." +msgstr "Speelt het geluid van de apps hier af, voor gesprekken en video's." + +#: src/lib/menu.sh:266 +msgid "" +"Paints every window right. But some servers end the session right after " +"signing in, then go back to GFX." +msgstr "Tekent elk venster goed. Maar sommige servers beรซindigen de sessie direct na het aanmelden; ga dan terug naar GFX." + +#: src/lib/menu.sh:267 +msgid "" +"Works with every server. FreeRDP may not repaint a window after it was " +"minimized or resized." +msgstr "Werkt met elke server. FreeRDP tekent een venster na minimaliseren of van formaat wijzigen misschien niet opnieuw." + +#: src/lib/menu.sh:268 +msgid "" +"Always waits before the second sign-in. For when the second prompt does not " +"come with automatic." +msgstr "" +"Wacht altijd voor de tweede aanmelding. Voor als het tweede verzoek met " +"automatisch niet komt." + +#: src/lib/menu.sh:269 +msgid "Never waits. Right when your company has no MFA." +msgstr "Wacht nooit. Goed als je bedrijf geen MFA gebruikt." + +#: src/lib/menu.sh:270 +msgid "" +"Many servers with MFA need a second sign-in, and its prompt only comes after " +"a short wait. Automatic waits when the first sign-in took a while." +msgstr "" +"Veel servers met MFA vragen een tweede aanmelding, en het verzoek daarvoor " +"komt pas na even wachten. Automatisch wacht als de eerste aanmelding even " +"duurde." + +#: src/lib/menu.sh:273 +msgid "" +"Loads the list of apps again once a day, so new apps show up by themselves." +msgstr "" +"Laadt de lijst met apps รฉรฉn keer per dag opnieuw, zo verschijnen nieuwe apps " +"vanzelf." + +#: src/lib/menu.sh:275 src/lib/menu.sh:330 +msgid "Needs a systemd user session." +msgstr "Vereist een systemd-gebruikerssessie." + +#: src/lib/menu.sh:322 src/lib/menu.sh:325 +msgid "Could not save the setting." +msgstr "Kon de instelling niet opslaan." + +#: src/lib/menu.sh:371 src/lib/menu.sh:609 +msgid "Settings" +msgstr "Instellingen" + +#: src/lib/menu.sh:372 +msgid "โ†‘โ†“ select โ†โ†’ or Space: change q: back" +msgstr "โ†‘โ†“ kiezen โ†โ†’ of Spatie: wijzigen q: terug" + +#: src/lib/menu.sh:373 +msgid "ON" +msgstr "AAN" + +#: src/lib/menu.sh:374 +msgid "OFF" +msgstr "UIT" + +#: src/lib/menu.sh:456 src/lib/menu.sh:608 +msgid "Workspaces" +msgstr "Werkruimtes" + +#. TRANSLATORS: keep the letters, they are the keys. +#: src/lib/menu.sh:458 +msgid "" +"โ†‘โ†“ select r: refresh s: sign in again d: remove a: add q: back" +msgstr "" +"โ†‘โ†“ kiezen r: vernieuwen s: opnieuw aanmelden d: verwijderen a: " +"toevoegen q: terug" + +#: src/lib/menu.sh:459 +msgid "No workspace yet. Press a to add one." +msgstr "Nog geen werkruimte. Druk op a om er een toe te voegen." + +#: src/lib/menu.sh:484 +#, sh-printf-format +msgid "refreshed %s" +msgstr "vernieuwd %s" + +#: src/lib/menu.sh:512 +#, sh-printf-format +msgid "Remove \"%s\" and its apps?" +msgstr "\"%s\" en de apps ervan verwijderen?" + +#: src/lib/menu.sh:536 src/lib/menu.sh:607 +msgid "Start an app" +msgstr "Een app starten" + +#: src/lib/menu.sh:537 +msgid "โ†‘โ†“ select Enter: start q: back" +msgstr "โ†‘โ†“ kiezen Enter: starten q: terug" + +#: src/lib/menu.sh:538 +msgid "No apps yet. Add a workspace first." +msgstr "Nog geen apps. Voeg eerst een werkruimte toe." + +#: src/lib/menu.sh:579 +#, sh-printf-format +msgid "%s is starting. It opens in a window of its own." +msgstr "%s wordt gestart. Het opent in een eigen venster." + +#: src/lib/menu.sh:606 +msgid "Add a workspace" +msgstr "Werkruimte toevoegen" + +#: src/lib/menu.sh:610 +msgid "Quit" +msgstr "Afsluiten" + +#: src/lib/workspace.sh:163 +msgid "The server did not accept the user name or password." +msgstr "De server heeft de gebruikersnaam of het wachtwoord niet geaccepteerd." + +#: src/lib/workspace.sh:164 +#, sh-printf-format +msgid "Could not reach the server: %s" +msgstr "Kon de server niet bereiken: %s" + +#: src/lib/workspace.sh:165 +#, sh-printf-format +msgid "There is no RD Web feed at %s." +msgstr "Er is geen RD Web-feed op %s." + +#: src/lib/workspace.sh:166 +#, sh-printf-format +msgid "Could not load the apps: %s" +msgstr "Kon de apps niet laden: %s" + +#: src/lib/workspace.sh:282 +#, sh-printf-format +msgid "Remote desktop of %s" +msgstr "Extern bureaublad van %s" + +#: src/lib/workspace.sh:284 +#, sh-printf-format +msgid "%s from %s" +msgstr "%s van %s" + +#: src/lib/menu.sh +msgid "Apps" +msgstr "Apps" + +#: src/lib/menu.sh +msgid "Share a folder" +msgstr "Map delen" + +#: src/lib/menu.sh +msgid "Open files with the apps" +msgstr "Bestanden openen met de apps" + +#: src/lib/menu.sh +msgid "Size" +msgstr "Grootte" + +#: src/lib/menu.sh +msgid "Sound" +msgstr "Geluid" + +#: src/lib/menu.sh +msgid "Graphics" +msgstr "Weergave" + +#: src/lib/menu.sh +msgid "Signing in" +msgstr "Aanmelden" + +#: src/lib/menu.sh +msgid "Wait for the second MFA prompt" +msgstr "Wachten op tweede MFA-verzoek" + +#: src/lib/menu.sh +msgid "Refresh the apps every day" +msgstr "Apps elke dag vernieuwen" diff --git a/po/pl.po b/po/pl.po new file mode 100644 index 0000000..965f2d3 --- /dev/null +++ b/po/pl.po @@ -0,0 +1,555 @@ +# Polish translation for rdfeed. +# Copyright (C) 2026 Felitendo +# This file is distributed under the same license as rdfeed. +# +msgid "" +msgstr "" +"Project-Id-Version: rdfeed 1.0.0\n" +"Report-Msgid-Bugs-To: https://github.com/LoonixTools/rdfeed/issues\n" +"POT-Creation-Date: 2026-10-02 10:29+0200\n" +"PO-Revision-Date: 2026-10-02 09:30+0200\n" +"Last-Translator: Felitendo\n" +"Language-Team: Polish\n" +"Language: pl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/rdfeed:79 src/rdfeed:103 +#, sh-printf-format +msgid "Could not write to %s." +msgstr "Nie udaล‚o siฤ™ zapisaฤ‡ w %s." + +#: src/rdfeed:82 +#, sh-printf-format +msgid "Signing in to %sโ€ฆ" +msgstr "Logowanie do %sโ€ฆ" + +#: src/rdfeed:100 +msgid "" +"The password could not be saved in the keyring, so each app asks for it." +msgstr "" +"Nie udaล‚o siฤ™ zapisaฤ‡ hasล‚a w pฤ™ku kluczy, wiฤ™c kaลผda aplikacja o nie zapyta." + +#: src/rdfeed:111 +#, sh-printf-format +msgid "1 app from %s is now in your app menu." +msgstr "1 aplikacja z %s jest teraz w menu aplikacji." + +#: src/rdfeed:113 +#, sh-printf-format +msgid "%d apps from %s are now in your app menu." +msgstr "Dodano aplikacje (%d) z %s do menu aplikacji." + +#: src/rdfeed:129 +msgid "" +"Your IT department has the address. A work e-mail address often works too." +msgstr "Adres ma twรณj dziaล‚ IT. Czฤ™sto dziaล‚a teลผ sล‚uลผbowy adres e-mail." + +#: src/rdfeed:130 +msgid "Feed address, server or e-mail:" +msgstr "Adres kanaล‚u, serwer lub e-mail:" + +#: src/rdfeed:137 +#, sh-printf-format +msgid "No feed is registered for %s. Ask your IT department for the address." +msgstr "Dla %s nie zarejestrowano kanaล‚u. Zapytaj dziaล‚ IT o adres." + +#: src/rdfeed:142 src/rdfeed:156 +msgid "User name (DOMAIN\\name or name@domain):" +msgstr "Nazwa uลผytkownika (DOMENA\\nazwa lub nazwa@domena):" + +#: src/rdfeed:146 src/rdfeed:159 +msgid "Password:" +msgstr "Hasล‚o:" + +#: src/rdfeed:178 src/rdfeed:236 +#, sh-printf-format +msgid "No workspace yet. Add one with `%s add`." +msgstr "Nie ma jeszcze obszaru roboczego. Dodaj go poleceniem `%s add`." + +#: src/rdfeed:187 src/lib/launch.sh:220 +#, sh-printf-format +msgid "No password saved for %s. Sign in again in rdfeed." +msgstr "Brak zapisanego hasล‚a dla %s. Zaloguj siฤ™ ponownie w rdfeed." + +#: src/rdfeed:191 src/lib/launch.sh:149 +#, sh-printf-format +msgid "Password for %s:" +msgstr "Hasล‚o dla %s:" + +#: src/rdfeed:195 +#, sh-printf-format +msgid "Refreshing %sโ€ฆ" +msgstr "Odล›wieลผanie %sโ€ฆ" + +#: src/rdfeed:205 +msgid "The password no longer works. Sign in again in rdfeed." +msgstr "Hasล‚o juลผ nie dziaล‚a. Zaloguj siฤ™ ponownie w rdfeed." + +#: src/rdfeed:214 +#, sh-printf-format +msgid "%s: %d apps" +msgstr "%s: aplikacje: %d" + +#: src/rdfeed:222 +#, sh-printf-format +msgid "Which workspace? See `%s list` for the names." +msgstr "Ktรณry obszar roboczy? Nazwy pokazuje `%s list`." + +#: src/rdfeed:229 +#, sh-printf-format +msgid "%s and its apps are removed." +msgstr "Usuniฤ™to %s i jego aplikacje." + +#: src/rdfeed:253 +#, sh-printf-format +msgid "Which app? See `%s list`." +msgstr "Ktรณra aplikacja? Zobacz `%s list`." + +#: src/rdfeed:268 src/lib/launch.sh:199 +#, sh-printf-format +msgid "There is no app %s. See `%s list`." +msgstr "Nie ma aplikacji %s. Zobacz `%s list`." + +#: src/rdfeed:270 +#, sh-printf-format +msgid "Several workspaces have %s. Write it as WORKSPACE/APP, see `%s list`." +msgstr "" +"Kilka obszarรณw roboczych ma %s. Wpisz to jako OBSZAR/APLIKACJA, zobacz `%s " +"list`." + +#: src/rdfeed:278 +msgid "Usage: rdfeed [command]" +msgstr "Uลผycie: rdfeed [polecenie]" + +#: src/rdfeed:280 +msgid "Commands:" +msgstr "Polecenia:" + +#: src/rdfeed:281 +msgid "Add the workspace of your company" +msgstr "Dodaj obszar roboczy swojej firmy" + +#: src/rdfeed:282 +msgid "Load the apps again" +msgstr "Wczytaj aplikacje ponownie" + +#: src/rdfeed:283 +msgid "List the workspaces and their apps" +msgstr "Wyล›wietl obszary robocze i ich aplikacje" + +#: src/rdfeed:284 +msgid "Start an app, or open a file with it" +msgstr "Uruchom aplikacjฤ™ lub otwรณrz niฤ… plik" + +#: src/rdfeed:285 +msgid "Remove a workspace and its apps" +msgstr "Usuล„ obszar roboczy i jego aplikacje" + +#: src/rdfeed:286 +msgid "Show this help" +msgstr "Pokaลผ tฤ™ pomoc" + +#: src/rdfeed:287 +msgid "Show the version" +msgstr "Pokaลผ wersjฤ™" + +#: src/rdfeed:289 +msgid "Without a command an interactive menu is shown." +msgstr "Bez polecenia wyล›wietla siฤ™ interaktywne menu." + +#: src/rdfeed:313 +#, sh-printf-format +msgid "Unknown command: %s" +msgstr "Nieznane polecenie: %s" + +#: src/lib/common.sh:174 src/lib/menu.sh:251 +msgid "never" +msgstr "nigdy" + +#: src/lib/common.sh:184 +msgid "just now" +msgstr "przed chwilฤ…" + +#: src/lib/common.sh:186 +msgid "1 minute ago" +msgstr "1 minutฤ™ temu" + +#: src/lib/common.sh:189 +#, sh-printf-format +msgid "%d minutes ago" +msgstr "%d min temu" + +#: src/lib/common.sh:191 +msgid "1 hour ago" +msgstr "1 godzinฤ™ temu" + +#: src/lib/common.sh:194 +#, sh-printf-format +msgid "%d hours ago" +msgstr "%d godz. temu" + +#: src/lib/common.sh:196 +msgid "1 day ago" +msgstr "1 dzieล„ temu" + +#: src/lib/common.sh:199 +#, sh-printf-format +msgid "%d days ago" +msgstr "%d dni temu" + +#: src/lib/launch.sh:169 +msgid "" +"Your password has expired. Change it on Windows or on the RD Web page, then " +"sign in again in rdfeed." +msgstr "" +"Hasล‚o wygasล‚o. Zmieล„ je w Windows lub na stronie RD Web, a potem zaloguj siฤ™ " +"ponownie w rdfeed." + +#: src/lib/launch.sh:171 +msgid "Your account is locked. Ask your IT department." +msgstr "Konto jest zablokowane. Zapytaj dziaล‚ IT." + +#: src/lib/launch.sh:173 +msgid "The server did not accept the password. Sign in again in rdfeed." +msgstr "Serwer nie przyjฤ…ล‚ hasล‚a. Zaloguj siฤ™ ponownie w rdfeed." + +#: src/lib/launch.sh:175 +msgid "Could not reach the server. Check the internet connection." +msgstr "Nie udaล‚o siฤ™ poล‚ฤ…czyฤ‡ z serwerem. Sprawdลบ poล‚ฤ…czenie z internetem." + +#: src/lib/launch.sh:178 +#, sh-printf-format +msgid "FreeRDP stopped: %s" +msgstr "FreeRDP zatrzymaล‚ siฤ™: %s" + +#: src/lib/launch.sh:205 src/lib/launch.sh:206 +#, sh-printf-format +msgid "File not found: %s" +msgstr "Nie znaleziono pliku: %s" + +#: src/lib/launch.sh:212 src/lib/launch.sh:213 +msgid "" +"FreeRDP 3 is missing. Install it to start the apps (the package is called " +"freerdp)." +msgstr "" +"Brakuje FreeRDP 3. Zainstaluj go, aby uruchamiaฤ‡ aplikacje (pakiet nazywa " +"siฤ™ freerdp)." + +#: src/lib/launch.sh:277 +#, sh-printf-format +msgid "Opening %s in %s" +msgstr "Otwieranie %s w %s" + +#: src/lib/launch.sh:278 +#, sh-printf-format +msgid "If it does not open by itself, open it in the app from the drive %s." +msgstr "Jeล›li nie otworzy siฤ™ sam, otwรณrz go w aplikacji z dysku %s." + +#: src/lib/launch.sh:280 +#, sh-printf-format +msgid "Starting %s" +msgstr "Uruchamianie: %s" + +#: src/lib/launch.sh:281 +msgid "" +"If your company uses MFA, confirm the sign-in in your authenticator app." +msgstr "" +"Jeล›li firma uลผywa MFA, potwierdลบ logowanie w aplikacji uwierzytelniajฤ…cej." + +#: src/lib/launch.sh:290 +msgid "A second sign-in is coming" +msgstr "Nadchodzi drugie logowanie" + +#: src/lib/launch.sh:291 +#, sh-printf-format +msgid "" +"Confirm the second prompt in your authenticator app too. It comes in about " +"%d seconds." +msgstr "" +"Potwierdลบ teลผ drugie ลผฤ…danie w aplikacji uwierzytelniajฤ…cej. Przyjdzie za " +"okoล‚o %d s." + +#: src/lib/launch.sh:301 +#, sh-printf-format +msgid "%s could not start" +msgstr "Nie udaล‚o siฤ™ uruchomiฤ‡: %s" + +#: src/lib/launch.sh:303 +#, sh-printf-format +msgid "Log: %s" +msgstr "Dziennik: %s" + +#. TRANSLATORS: the letter after "[" is the key for yes. y works too. +#: src/lib/menu.sh:93 +msgid "[y/N]" +msgstr "[t/N]" + +#: src/lib/menu.sh:163 +msgid "1 app" +msgstr "1 aplikacja" + +#: src/lib/menu.sh:165 +#, sh-printf-format +msgid "%d apps" +msgstr "aplikacje: %d" + +#: src/lib/menu.sh:190 +msgid "No workspace yet. Add the one of your company with [1]." +msgstr "Nie ma jeszcze obszaru roboczego. Dodaj obszar swojej firmy przez [1]." + +#: src/lib/menu.sh:233 +msgid "off" +msgstr "wyล‚." + +#: src/lib/menu.sh:234 +msgid "home folder" +msgstr "folder domowy" + +#: src/lib/menu.sh:237 +msgid "Documents" +msgstr "Dokumenty" + +#: src/lib/menu.sh:238 +msgid "Downloads" +msgstr "Pobrane" + +#: src/lib/menu.sh:243 +msgid "not found" +msgstr "nie znaleziono" + +#: src/lib/menu.sh:246 +#, sh-printf-format +msgid "automatic (%d %%)" +msgstr "automatyczny (%d %%)" + +#: src/lib/menu.sh:248 +msgid "classic" +msgstr "klasyczny" + +#: src/lib/menu.sh:249 +msgid "GFX" +msgstr "GFX" + +#: src/lib/menu.sh:250 +msgid "always" +msgstr "zawsze" + +#: src/lib/menu.sh:252 +msgid "automatic" +msgstr "automatycznie" + +#: src/lib/menu.sh:261 +msgid "" +"The apps only see the files on the server. Pick a folder to open and save " +"files of this computer in them." +msgstr "" +"Aplikacje widzฤ… tylko pliki na serwerze. Wybierz folder, aby mogล‚y otwieraฤ‡ " +"i zapisywaฤ‡ pliki z tego komputera." + +#: src/lib/menu.sh:262 +msgid "" +"The apps can open and save files in this folder. It shows up as a drive in " +"them." +msgstr "" +"Aplikacje mogฤ… otwieraฤ‡ i zapisywaฤ‡ pliki w tym folderze. Widzฤ… go jako dysk." + +#: src/lib/menu.sh:263 +msgid "" +"Offers the apps under \"Open with\" for their file types, for example Word " +"for .docx. The folder of the file is shared with the app while it is open." +msgstr "" +"Pokazuje aplikacje w โ€žOtwรณrz za pomocฤ…โ€ dla ich typรณw plikรณw, na przykล‚ad " +"Word dla .docx. Folder pliku jest udostฤ™pniany, dopรณki plik jest otwarty." + +#: src/lib/menu.sh:264 +msgid "" +"How big the apps are drawn. Automatic follows the scale of your desktop, so " +"they are sharp and the right size." +msgstr "" +"Jak duลผe sฤ… rysowane aplikacje. Automatyczny podฤ…ลผa za skalฤ… pulpitu, wiฤ™c " +"sฤ… ostre i majฤ… wล‚aล›ciwy rozmiar." + +#: src/lib/menu.sh:265 +msgid "Plays the sound of the apps here, for calls and videos." +msgstr "Odtwarza tutaj dลบwiฤ™k aplikacji, do rozmรณw i filmรณw." + +#: src/lib/menu.sh:266 +msgid "" +"Paints every window right. But some servers end the session right after " +"signing in, then go back to GFX." +msgstr "Rysuje kaลผde okno poprawnie. Ale niektรณre serwery koล„czฤ… sesjฤ™ zaraz po zalogowaniu, wtedy wrรณฤ‡ do GFX." + +#: src/lib/menu.sh:267 +msgid "" +"Works with every server. FreeRDP may not repaint a window after it was " +"minimized or resized." +msgstr "Dziaล‚a z kaลผdym serwerem. FreeRDP moลผe nie odrysowaฤ‡ okna po zminimalizowaniu lub zmianie rozmiaru." + +#: src/lib/menu.sh:268 +msgid "" +"Always waits before the second sign-in. For when the second prompt does not " +"come with automatic." +msgstr "" +"Zawsze czeka przed drugim logowaniem. Gdy przy ustawieniu automatycznym " +"drugie ลผฤ…danie nie przychodzi." + +#: src/lib/menu.sh:269 +msgid "Never waits. Right when your company has no MFA." +msgstr "Nigdy nie czeka. Wล‚aล›ciwe, gdy firma nie uลผywa MFA." + +#: src/lib/menu.sh:270 +msgid "" +"Many servers with MFA need a second sign-in, and its prompt only comes after " +"a short wait. Automatic waits when the first sign-in took a while." +msgstr "" +"Wiele serwerรณw z MFA wymaga drugiego logowania, a jego ลผฤ…danie przychodzi " +"dopiero po chwili. Automatycznie czeka, gdy pierwsze logowanie trochฤ™ trwaล‚o." + +#: src/lib/menu.sh:273 +msgid "" +"Loads the list of apps again once a day, so new apps show up by themselves." +msgstr "" +"Wczytuje listฤ™ aplikacji raz dziennie, wiฤ™c nowe aplikacje pojawiajฤ… siฤ™ " +"same." + +#: src/lib/menu.sh:275 src/lib/menu.sh:330 +msgid "Needs a systemd user session." +msgstr "Wymaga sesji uลผytkownika systemd." + +#: src/lib/menu.sh:322 src/lib/menu.sh:325 +msgid "Could not save the setting." +msgstr "Nie udaล‚o siฤ™ zapisaฤ‡ ustawienia." + +#: src/lib/menu.sh:371 src/lib/menu.sh:609 +msgid "Settings" +msgstr "Ustawienia" + +#: src/lib/menu.sh:372 +msgid "โ†‘โ†“ select โ†โ†’ or Space: change q: back" +msgstr "โ†‘โ†“ wybierz โ†โ†’ lub Spacja: zmieล„ q: wstecz" + +#: src/lib/menu.sh:373 +msgid "ON" +msgstr "Wล." + +#: src/lib/menu.sh:374 +msgid "OFF" +msgstr "WYล." + +#: src/lib/menu.sh:456 src/lib/menu.sh:608 +msgid "Workspaces" +msgstr "Obszary robocze" + +#. TRANSLATORS: keep the letters, they are the keys. +#: src/lib/menu.sh:458 +msgid "" +"โ†‘โ†“ select r: refresh s: sign in again d: remove a: add q: back" +msgstr "" +"โ†‘โ†“ wybierz r: odล›wieลผ s: zaloguj ponownie d: usuล„ a: dodaj q: " +"wstecz" + +#: src/lib/menu.sh:459 +msgid "No workspace yet. Press a to add one." +msgstr "Nie ma jeszcze obszaru roboczego. Naciล›nij a, aby dodaฤ‡." + +#: src/lib/menu.sh:484 +#, sh-printf-format +msgid "refreshed %s" +msgstr "odล›wieลผono %s" + +#: src/lib/menu.sh:512 +#, sh-printf-format +msgid "Remove \"%s\" and its apps?" +msgstr "Usunฤ…ฤ‡ โ€ž%sโ€ i jego aplikacje?" + +#: src/lib/menu.sh:536 src/lib/menu.sh:607 +msgid "Start an app" +msgstr "Uruchom aplikacjฤ™" + +#: src/lib/menu.sh:537 +msgid "โ†‘โ†“ select Enter: start q: back" +msgstr "โ†‘โ†“ wybierz Enter: uruchom q: wstecz" + +#: src/lib/menu.sh:538 +msgid "No apps yet. Add a workspace first." +msgstr "Nie ma jeszcze aplikacji. Najpierw dodaj obszar roboczy." + +#: src/lib/menu.sh:579 +#, sh-printf-format +msgid "%s is starting. It opens in a window of its own." +msgstr "Uruchamianie: %s. Otworzy siฤ™ we wล‚asnym oknie." + +#: src/lib/menu.sh:606 +msgid "Add a workspace" +msgstr "Dodaj obszar roboczy" + +#: src/lib/menu.sh:610 +msgid "Quit" +msgstr "Zakoล„cz" + +#: src/lib/workspace.sh:163 +msgid "The server did not accept the user name or password." +msgstr "Serwer nie przyjฤ…ล‚ nazwy uลผytkownika lub hasล‚a." + +#: src/lib/workspace.sh:164 +#, sh-printf-format +msgid "Could not reach the server: %s" +msgstr "Nie udaล‚o siฤ™ poล‚ฤ…czyฤ‡ z serwerem: %s" + +#: src/lib/workspace.sh:165 +#, sh-printf-format +msgid "There is no RD Web feed at %s." +msgstr "Pod adresem %s nie ma kanaล‚u RD Web." + +#: src/lib/workspace.sh:166 +#, sh-printf-format +msgid "Could not load the apps: %s" +msgstr "Nie udaล‚o siฤ™ wczytaฤ‡ aplikacji: %s" + +#: src/lib/workspace.sh:282 +#, sh-printf-format +msgid "Remote desktop of %s" +msgstr "Pulpit zdalny %s" + +#: src/lib/workspace.sh:284 +#, sh-printf-format +msgid "%s from %s" +msgstr "%s z %s" + +#: src/lib/menu.sh +msgid "Apps" +msgstr "Aplikacje" + +#: src/lib/menu.sh +msgid "Share a folder" +msgstr "Udostฤ™pnij folder" + +#: src/lib/menu.sh +msgid "Open files with the apps" +msgstr "Otwieraj pliki w aplikacjach" + +#: src/lib/menu.sh +msgid "Size" +msgstr "Rozmiar" + +#: src/lib/menu.sh +msgid "Sound" +msgstr "Dลบwiฤ™k" + +#: src/lib/menu.sh +msgid "Graphics" +msgstr "Grafika" + +#: src/lib/menu.sh +msgid "Signing in" +msgstr "Logowanie" + +#: src/lib/menu.sh +msgid "Wait for the second MFA prompt" +msgstr "Czekaj na drugie ลผฤ…danie MFA" + +#: src/lib/menu.sh +msgid "Refresh the apps every day" +msgstr "Odล›wieลผaj aplikacje codziennie" diff --git a/po/pt_BR.po b/po/pt_BR.po new file mode 100644 index 0000000..f974dfa --- /dev/null +++ b/po/pt_BR.po @@ -0,0 +1,561 @@ +# Brazilian Portuguese translation for rdfeed. +# Copyright (C) 2026 Felitendo +# This file is distributed under the same license as rdfeed. +# +msgid "" +msgstr "" +"Project-Id-Version: rdfeed 1.0.0\n" +"Report-Msgid-Bugs-To: https://github.com/LoonixTools/rdfeed/issues\n" +"POT-Creation-Date: 2026-10-02 10:29+0200\n" +"PO-Revision-Date: 2026-10-02 09:30+0200\n" +"Last-Translator: Felitendo\n" +"Language-Team: Brazilian Portuguese\n" +"Language: pt_BR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/rdfeed:79 src/rdfeed:103 +#, sh-printf-format +msgid "Could not write to %s." +msgstr "Nรฃo foi possรญvel gravar em %s." + +#: src/rdfeed:82 +#, sh-printf-format +msgid "Signing in to %sโ€ฆ" +msgstr "Entrando em %sโ€ฆ" + +#: src/rdfeed:100 +msgid "" +"The password could not be saved in the keyring, so each app asks for it." +msgstr "" +"Nรฃo foi possรญvel salvar a senha no chaveiro, entรฃo cada app vai pedi-la." + +#: src/rdfeed:111 +#, sh-printf-format +msgid "1 app from %s is now in your app menu." +msgstr "1 app de %s agora estรก no seu menu de aplicativos." + +#: src/rdfeed:113 +#, sh-printf-format +msgid "%d apps from %s are now in your app menu." +msgstr "%d apps de %s agora estรฃo no seu menu de aplicativos." + +#: src/rdfeed:129 +msgid "" +"Your IT department has the address. A work e-mail address often works too." +msgstr "" +"O seu departamento de TI tem o endereรงo. Muitas vezes o e-mail do trabalho " +"tambรฉm funciona." + +#: src/rdfeed:130 +msgid "Feed address, server or e-mail:" +msgstr "Endereรงo do feed, servidor ou e-mail:" + +#: src/rdfeed:137 +#, sh-printf-format +msgid "No feed is registered for %s. Ask your IT department for the address." +msgstr "" +"Nenhum feed estรก registrado para %s. Peรงa o endereรงo ao seu departamento de " +"TI." + +#: src/rdfeed:142 src/rdfeed:156 +msgid "User name (DOMAIN\\name or name@domain):" +msgstr "Nome de usuรกrio (DOMรNIO\\nome ou nome@domรญnio):" + +#: src/rdfeed:146 src/rdfeed:159 +msgid "Password:" +msgstr "Senha:" + +#: src/rdfeed:178 src/rdfeed:236 +#, sh-printf-format +msgid "No workspace yet. Add one with `%s add`." +msgstr "Ainda nรฃo hรก espaรงo de trabalho. Adicione um com `%s add`." + +#: src/rdfeed:187 src/lib/launch.sh:220 +#, sh-printf-format +msgid "No password saved for %s. Sign in again in rdfeed." +msgstr "Nenhuma senha salva para %s. Entre de novo no rdfeed." + +#: src/rdfeed:191 src/lib/launch.sh:149 +#, sh-printf-format +msgid "Password for %s:" +msgstr "Senha de %s:" + +#: src/rdfeed:195 +#, sh-printf-format +msgid "Refreshing %sโ€ฆ" +msgstr "Atualizando %sโ€ฆ" + +#: src/rdfeed:205 +msgid "The password no longer works. Sign in again in rdfeed." +msgstr "A senha nรฃo funciona mais. Entre de novo no rdfeed." + +#: src/rdfeed:214 +#, sh-printf-format +msgid "%s: %d apps" +msgstr "%s: %d apps" + +#: src/rdfeed:222 +#, sh-printf-format +msgid "Which workspace? See `%s list` for the names." +msgstr "Qual espaรงo de trabalho? Os nomes estรฃo em `%s list`." + +#: src/rdfeed:229 +#, sh-printf-format +msgid "%s and its apps are removed." +msgstr "%s e os seus apps foram removidos." + +#: src/rdfeed:253 +#, sh-printf-format +msgid "Which app? See `%s list`." +msgstr "Qual app? Veja `%s list`." + +#: src/rdfeed:268 src/lib/launch.sh:199 +#, sh-printf-format +msgid "There is no app %s. See `%s list`." +msgstr "Nรฃo existe o app %s. Veja `%s list`." + +#: src/rdfeed:270 +#, sh-printf-format +msgid "Several workspaces have %s. Write it as WORKSPACE/APP, see `%s list`." +msgstr "" +"Vรกrios espaรงos de trabalho tรชm %s. Escreva como ESPAร‡O/APP, veja `%s list`." + +#: src/rdfeed:278 +msgid "Usage: rdfeed [command]" +msgstr "Uso: rdfeed [comando]" + +#: src/rdfeed:280 +msgid "Commands:" +msgstr "Comandos:" + +#: src/rdfeed:281 +msgid "Add the workspace of your company" +msgstr "Adicionar o espaรงo de trabalho da sua empresa" + +#: src/rdfeed:282 +msgid "Load the apps again" +msgstr "Carregar os apps de novo" + +#: src/rdfeed:283 +msgid "List the workspaces and their apps" +msgstr "Listar os espaรงos de trabalho e seus apps" + +#: src/rdfeed:284 +msgid "Start an app, or open a file with it" +msgstr "Abrir um app, ou um arquivo com ele" + +#: src/rdfeed:285 +msgid "Remove a workspace and its apps" +msgstr "Remover um espaรงo de trabalho e seus apps" + +#: src/rdfeed:286 +msgid "Show this help" +msgstr "Mostrar esta ajuda" + +#: src/rdfeed:287 +msgid "Show the version" +msgstr "Mostrar a versรฃo" + +#: src/rdfeed:289 +msgid "Without a command an interactive menu is shown." +msgstr "Sem comando, um menu interativo รฉ exibido." + +#: src/rdfeed:313 +#, sh-printf-format +msgid "Unknown command: %s" +msgstr "Comando desconhecido: %s" + +#: src/lib/common.sh:174 src/lib/menu.sh:251 +msgid "never" +msgstr "nunca" + +#: src/lib/common.sh:184 +msgid "just now" +msgstr "agora mesmo" + +#: src/lib/common.sh:186 +msgid "1 minute ago" +msgstr "hรก 1 minuto" + +#: src/lib/common.sh:189 +#, sh-printf-format +msgid "%d minutes ago" +msgstr "hรก %d minutos" + +#: src/lib/common.sh:191 +msgid "1 hour ago" +msgstr "hรก 1 hora" + +#: src/lib/common.sh:194 +#, sh-printf-format +msgid "%d hours ago" +msgstr "hรก %d horas" + +#: src/lib/common.sh:196 +msgid "1 day ago" +msgstr "hรก 1 dia" + +#: src/lib/common.sh:199 +#, sh-printf-format +msgid "%d days ago" +msgstr "hรก %d dias" + +#: src/lib/launch.sh:169 +msgid "" +"Your password has expired. Change it on Windows or on the RD Web page, then " +"sign in again in rdfeed." +msgstr "" +"A sua senha expirou. Mude-a no Windows ou na pรกgina do RD Web e depois entre " +"de novo no rdfeed." + +#: src/lib/launch.sh:171 +msgid "Your account is locked. Ask your IT department." +msgstr "A sua conta estรก bloqueada. Fale com o seu departamento de TI." + +#: src/lib/launch.sh:173 +msgid "The server did not accept the password. Sign in again in rdfeed." +msgstr "O servidor nรฃo aceitou a senha. Entre de novo no rdfeed." + +#: src/lib/launch.sh:175 +msgid "Could not reach the server. Check the internet connection." +msgstr "" +"Nรฃo foi possรญvel alcanรงar o servidor. Verifique a conexรฃo com a internet." + +#: src/lib/launch.sh:178 +#, sh-printf-format +msgid "FreeRDP stopped: %s" +msgstr "O FreeRDP parou: %s" + +#: src/lib/launch.sh:205 src/lib/launch.sh:206 +#, sh-printf-format +msgid "File not found: %s" +msgstr "Arquivo nรฃo encontrado: %s" + +#: src/lib/launch.sh:212 src/lib/launch.sh:213 +msgid "" +"FreeRDP 3 is missing. Install it to start the apps (the package is called " +"freerdp)." +msgstr "" +"O FreeRDP 3 nรฃo estรก instalado. Instale-o para abrir os apps (o pacote se " +"chama freerdp)." + +#: src/lib/launch.sh:277 +#, sh-printf-format +msgid "Opening %s in %s" +msgstr "Abrindo %s no %s" + +#: src/lib/launch.sh:278 +#, sh-printf-format +msgid "If it does not open by itself, open it in the app from the drive %s." +msgstr "Se nรฃo abrir sozinho, abra-o no app pela unidade %s." + +#: src/lib/launch.sh:280 +#, sh-printf-format +msgid "Starting %s" +msgstr "Abrindo %s" + +#: src/lib/launch.sh:281 +msgid "" +"If your company uses MFA, confirm the sign-in in your authenticator app." +msgstr "Se a sua empresa usa MFA, confirme a entrada no seu app autenticador." + +#: src/lib/launch.sh:290 +msgid "A second sign-in is coming" +msgstr "Vem uma segunda entrada" + +#: src/lib/launch.sh:291 +#, sh-printf-format +msgid "" +"Confirm the second prompt in your authenticator app too. It comes in about " +"%d seconds." +msgstr "" +"Confirme tambรฉm o segundo pedido no seu app autenticador. Ele chega em cerca " +"de %d segundos." + +#: src/lib/launch.sh:301 +#, sh-printf-format +msgid "%s could not start" +msgstr "Nรฃo foi possรญvel abrir %s" + +#: src/lib/launch.sh:303 +#, sh-printf-format +msgid "Log: %s" +msgstr "Log: %s" + +#. TRANSLATORS: the letter after "[" is the key for yes. y works too. +#: src/lib/menu.sh:93 +msgid "[y/N]" +msgstr "[s/N]" + +#: src/lib/menu.sh:163 +msgid "1 app" +msgstr "1 app" + +#: src/lib/menu.sh:165 +#, sh-printf-format +msgid "%d apps" +msgstr "%d apps" + +#: src/lib/menu.sh:190 +msgid "No workspace yet. Add the one of your company with [1]." +msgstr "Ainda nรฃo hรก espaรงo de trabalho. Adicione o da sua empresa com [1]." + +#: src/lib/menu.sh:233 +msgid "off" +msgstr "desativado" + +#: src/lib/menu.sh:234 +msgid "home folder" +msgstr "pasta pessoal" + +#: src/lib/menu.sh:237 +msgid "Documents" +msgstr "Documentos" + +#: src/lib/menu.sh:238 +msgid "Downloads" +msgstr "Downloads" + +#: src/lib/menu.sh:243 +msgid "not found" +msgstr "nรฃo encontrada" + +#: src/lib/menu.sh:246 +#, sh-printf-format +msgid "automatic (%d %%)" +msgstr "automรกtico (%d %%)" + +#: src/lib/menu.sh:248 +msgid "classic" +msgstr "clรกssico" + +#: src/lib/menu.sh:249 +msgid "GFX" +msgstr "GFX" + +#: src/lib/menu.sh:250 +msgid "always" +msgstr "sempre" + +#: src/lib/menu.sh:252 +msgid "automatic" +msgstr "automรกtico" + +#: src/lib/menu.sh:261 +msgid "" +"The apps only see the files on the server. Pick a folder to open and save " +"files of this computer in them." +msgstr "" +"Os apps sรณ veem os arquivos do servidor. Escolha uma pasta para que possam " +"abrir e salvar arquivos deste computador." + +#: src/lib/menu.sh:262 +msgid "" +"The apps can open and save files in this folder. It shows up as a drive in " +"them." +msgstr "" +"Os apps podem abrir e salvar arquivos nesta pasta. Ela aparece neles como " +"uma unidade." + +#: src/lib/menu.sh:263 +msgid "" +"Offers the apps under \"Open with\" for their file types, for example Word " +"for .docx. The folder of the file is shared with the app while it is open." +msgstr "" +"Oferece os apps em \"Abrir com\" para seus tipos de arquivo, por exemplo o " +"Word para .docx. A pasta do arquivo fica compartilhada enquanto ele estรก " +"aberto." + +#: src/lib/menu.sh:264 +msgid "" +"How big the apps are drawn. Automatic follows the scale of your desktop, so " +"they are sharp and the right size." +msgstr "" +"O tamanho em que os apps sรฃo desenhados. Automรกtico segue a escala da sua " +"รกrea de trabalho, assim ficam nรญtidos e do tamanho certo." + +#: src/lib/menu.sh:265 +msgid "Plays the sound of the apps here, for calls and videos." +msgstr "Toca aqui o som dos apps, para chamadas e vรญdeos." + +#: src/lib/menu.sh:266 +msgid "" +"Paints every window right. But some servers end the session right after " +"signing in, then go back to GFX." +msgstr "Desenha cada janela direito. Mas alguns servidores encerram a sessรฃo logo apรณs a entrada; nesse caso, volte ao GFX." + +#: src/lib/menu.sh:267 +msgid "" +"Works with every server. FreeRDP may not repaint a window after it was " +"minimized or resized." +msgstr "Funciona com qualquer servidor. O FreeRDP pode nรฃo redesenhar uma janela depois de minimizada ou redimensionada." + +#: src/lib/menu.sh:268 +msgid "" +"Always waits before the second sign-in. For when the second prompt does not " +"come with automatic." +msgstr "" +"Sempre espera antes da segunda entrada. Para quando, no automรกtico, o " +"segundo pedido nรฃo chega." + +#: src/lib/menu.sh:269 +msgid "Never waits. Right when your company has no MFA." +msgstr "Nunca espera. Certo quando a sua empresa nรฃo usa MFA." + +#: src/lib/menu.sh:270 +msgid "" +"Many servers with MFA need a second sign-in, and its prompt only comes after " +"a short wait. Automatic waits when the first sign-in took a while." +msgstr "" +"Muitos servidores com MFA pedem uma segunda entrada, e o pedido sรณ chega " +"depois de uma breve espera. Automรกtico espera quando a primeira entrada " +"demorou." + +#: src/lib/menu.sh:273 +msgid "" +"Loads the list of apps again once a day, so new apps show up by themselves." +msgstr "" +"Carrega a lista de apps de novo uma vez por dia, assim os novos aparecem " +"sozinhos." + +#: src/lib/menu.sh:275 src/lib/menu.sh:330 +msgid "Needs a systemd user session." +msgstr "Precisa de uma sessรฃo de usuรกrio do systemd." + +#: src/lib/menu.sh:322 src/lib/menu.sh:325 +msgid "Could not save the setting." +msgstr "Nรฃo foi possรญvel salvar a configuraรงรฃo." + +#: src/lib/menu.sh:371 src/lib/menu.sh:609 +msgid "Settings" +msgstr "Configuraรงรตes" + +#: src/lib/menu.sh:372 +msgid "โ†‘โ†“ select โ†โ†’ or Space: change q: back" +msgstr "โ†‘โ†“ escolher โ†โ†’ ou Espaรงo: mudar q: voltar" + +#: src/lib/menu.sh:373 +msgid "ON" +msgstr "ATIVADO" + +#: src/lib/menu.sh:374 +msgid "OFF" +msgstr "DESATIVADO" + +#: src/lib/menu.sh:456 src/lib/menu.sh:608 +msgid "Workspaces" +msgstr "Espaรงos de trabalho" + +#. TRANSLATORS: keep the letters, they are the keys. +#: src/lib/menu.sh:458 +msgid "" +"โ†‘โ†“ select r: refresh s: sign in again d: remove a: add q: back" +msgstr "" +"โ†‘โ†“ escolher r: atualizar s: entrar de novo d: remover a: adicionar " +"q: voltar" + +#: src/lib/menu.sh:459 +msgid "No workspace yet. Press a to add one." +msgstr "Ainda nรฃo hรก espaรงo de trabalho. Aperte a para adicionar um." + +#: src/lib/menu.sh:484 +#, sh-printf-format +msgid "refreshed %s" +msgstr "atualizado %s" + +#: src/lib/menu.sh:512 +#, sh-printf-format +msgid "Remove \"%s\" and its apps?" +msgstr "Remover \"%s\" e seus apps?" + +#: src/lib/menu.sh:536 src/lib/menu.sh:607 +msgid "Start an app" +msgstr "Abrir um app" + +#: src/lib/menu.sh:537 +msgid "โ†‘โ†“ select Enter: start q: back" +msgstr "โ†‘โ†“ escolher Enter: abrir q: voltar" + +#: src/lib/menu.sh:538 +msgid "No apps yet. Add a workspace first." +msgstr "Ainda nรฃo hรก apps. Adicione primeiro um espaรงo de trabalho." + +#: src/lib/menu.sh:579 +#, sh-printf-format +msgid "%s is starting. It opens in a window of its own." +msgstr "%s estรก abrindo. Ele abre numa janela prรณpria." + +#: src/lib/menu.sh:606 +msgid "Add a workspace" +msgstr "Adicionar um espaรงo de trabalho" + +#: src/lib/menu.sh:610 +msgid "Quit" +msgstr "Sair" + +#: src/lib/workspace.sh:163 +msgid "The server did not accept the user name or password." +msgstr "O servidor nรฃo aceitou o nome de usuรกrio ou a senha." + +#: src/lib/workspace.sh:164 +#, sh-printf-format +msgid "Could not reach the server: %s" +msgstr "Nรฃo foi possรญvel alcanรงar o servidor: %s" + +#: src/lib/workspace.sh:165 +#, sh-printf-format +msgid "There is no RD Web feed at %s." +msgstr "Nรฃo hรก feed do RD Web em %s." + +#: src/lib/workspace.sh:166 +#, sh-printf-format +msgid "Could not load the apps: %s" +msgstr "Nรฃo foi possรญvel carregar os apps: %s" + +#: src/lib/workspace.sh:282 +#, sh-printf-format +msgid "Remote desktop of %s" +msgstr "รrea de trabalho remota de %s" + +#: src/lib/workspace.sh:284 +#, sh-printf-format +msgid "%s from %s" +msgstr "%s de %s" + +#: src/lib/menu.sh +msgid "Apps" +msgstr "Apps" + +#: src/lib/menu.sh +msgid "Share a folder" +msgstr "Compartilhar uma pasta" + +#: src/lib/menu.sh +msgid "Open files with the apps" +msgstr "Abrir arquivos com os apps" + +#: src/lib/menu.sh +msgid "Size" +msgstr "Tamanho" + +#: src/lib/menu.sh +msgid "Sound" +msgstr "Som" + +#: src/lib/menu.sh +msgid "Graphics" +msgstr "Grรกficos" + +#: src/lib/menu.sh +msgid "Signing in" +msgstr "Acesso" + +#: src/lib/menu.sh +msgid "Wait for the second MFA prompt" +msgstr "Esperar o segundo pedido de MFA" + +#: src/lib/menu.sh +msgid "Refresh the apps every day" +msgstr "Atualizar os apps todo dia" diff --git a/po/rdfeed.pot b/po/rdfeed.pot new file mode 100644 index 0000000..4642cf9 --- /dev/null +++ b/po/rdfeed.pot @@ -0,0 +1,531 @@ +# Translation template for rdfeed. +# Copyright (C) 2026 Felitendo +# This file is distributed under the same license as rdfeed. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: rdfeed 1.0.0\n" +"Report-Msgid-Bugs-To: https://github.com/LoonixTools/rdfeed/issues\n" +"POT-Creation-Date: 2026-10-02 10:29+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/rdfeed:79 src/rdfeed:103 +#, sh-printf-format +msgid "Could not write to %s." +msgstr "" + +#: src/rdfeed:82 +#, sh-printf-format +msgid "Signing in to %sโ€ฆ" +msgstr "" + +#: src/rdfeed:100 +msgid "" +"The password could not be saved in the keyring, so each app asks for it." +msgstr "" + +#: src/rdfeed:111 +#, sh-printf-format +msgid "1 app from %s is now in your app menu." +msgstr "" + +#: src/rdfeed:113 +#, sh-printf-format +msgid "%d apps from %s are now in your app menu." +msgstr "" + +#: src/rdfeed:129 +msgid "" +"Your IT department has the address. A work e-mail address often works too." +msgstr "" + +#: src/rdfeed:130 +msgid "Feed address, server or e-mail:" +msgstr "" + +#: src/rdfeed:137 +#, sh-printf-format +msgid "No feed is registered for %s. Ask your IT department for the address." +msgstr "" + +#: src/rdfeed:142 src/rdfeed:156 +msgid "User name (DOMAIN\\name or name@domain):" +msgstr "" + +#: src/rdfeed:146 src/rdfeed:159 +msgid "Password:" +msgstr "" + +#: src/rdfeed:178 src/rdfeed:236 +#, sh-printf-format +msgid "No workspace yet. Add one with `%s add`." +msgstr "" + +#: src/rdfeed:187 src/lib/launch.sh:220 +#, sh-printf-format +msgid "No password saved for %s. Sign in again in rdfeed." +msgstr "" + +#: src/rdfeed:191 src/lib/launch.sh:149 +#, sh-printf-format +msgid "Password for %s:" +msgstr "" + +#: src/rdfeed:195 +#, sh-printf-format +msgid "Refreshing %sโ€ฆ" +msgstr "" + +#: src/rdfeed:205 +msgid "The password no longer works. Sign in again in rdfeed." +msgstr "" + +#: src/rdfeed:214 +#, sh-printf-format +msgid "%s: %d apps" +msgstr "" + +#: src/rdfeed:222 +#, sh-printf-format +msgid "Which workspace? See `%s list` for the names." +msgstr "" + +#: src/rdfeed:229 +#, sh-printf-format +msgid "%s and its apps are removed." +msgstr "" + +#: src/rdfeed:253 +#, sh-printf-format +msgid "Which app? See `%s list`." +msgstr "" + +#: src/rdfeed:268 src/lib/launch.sh:199 +#, sh-printf-format +msgid "There is no app %s. See `%s list`." +msgstr "" + +#: src/rdfeed:270 +#, sh-printf-format +msgid "Several workspaces have %s. Write it as WORKSPACE/APP, see `%s list`." +msgstr "" + +#: src/rdfeed:278 +msgid "Usage: rdfeed [command]" +msgstr "" + +#: src/rdfeed:280 +msgid "Commands:" +msgstr "" + +#: src/rdfeed:281 +msgid "Add the workspace of your company" +msgstr "" + +#: src/rdfeed:282 +msgid "Load the apps again" +msgstr "" + +#: src/rdfeed:283 +msgid "List the workspaces and their apps" +msgstr "" + +#: src/rdfeed:284 +msgid "Start an app, or open a file with it" +msgstr "" + +#: src/rdfeed:285 +msgid "Remove a workspace and its apps" +msgstr "" + +#: src/rdfeed:286 +msgid "Show this help" +msgstr "" + +#: src/rdfeed:287 +msgid "Show the version" +msgstr "" + +#: src/rdfeed:289 +msgid "Without a command an interactive menu is shown." +msgstr "" + +#: src/rdfeed:313 +#, sh-printf-format +msgid "Unknown command: %s" +msgstr "" + +#: src/lib/common.sh:174 src/lib/menu.sh:251 +msgid "never" +msgstr "" + +#: src/lib/common.sh:184 +msgid "just now" +msgstr "" + +#: src/lib/common.sh:186 +msgid "1 minute ago" +msgstr "" + +#: src/lib/common.sh:189 +#, sh-printf-format +msgid "%d minutes ago" +msgstr "" + +#: src/lib/common.sh:191 +msgid "1 hour ago" +msgstr "" + +#: src/lib/common.sh:194 +#, sh-printf-format +msgid "%d hours ago" +msgstr "" + +#: src/lib/common.sh:196 +msgid "1 day ago" +msgstr "" + +#: src/lib/common.sh:199 +#, sh-printf-format +msgid "%d days ago" +msgstr "" + +#: src/lib/launch.sh:169 +msgid "" +"Your password has expired. Change it on Windows or on the RD Web page, then " +"sign in again in rdfeed." +msgstr "" + +#: src/lib/launch.sh:171 +msgid "Your account is locked. Ask your IT department." +msgstr "" + +#: src/lib/launch.sh:173 +msgid "The server did not accept the password. Sign in again in rdfeed." +msgstr "" + +#: src/lib/launch.sh:175 +msgid "Could not reach the server. Check the internet connection." +msgstr "" + +#: src/lib/launch.sh:178 +#, sh-printf-format +msgid "FreeRDP stopped: %s" +msgstr "" + +#: src/lib/launch.sh:205 src/lib/launch.sh:206 +#, sh-printf-format +msgid "File not found: %s" +msgstr "" + +#: src/lib/launch.sh:212 src/lib/launch.sh:213 +msgid "" +"FreeRDP 3 is missing. Install it to start the apps (the package is called " +"freerdp)." +msgstr "" + +#: src/lib/launch.sh:277 +#, sh-printf-format +msgid "Opening %s in %s" +msgstr "" + +#: src/lib/launch.sh:278 +#, sh-printf-format +msgid "If it does not open by itself, open it in the app from the drive %s." +msgstr "" + +#: src/lib/launch.sh:280 +#, sh-printf-format +msgid "Starting %s" +msgstr "" + +#: src/lib/launch.sh:281 +msgid "" +"If your company uses MFA, confirm the sign-in in your authenticator app." +msgstr "" + +#: src/lib/launch.sh:290 +msgid "A second sign-in is coming" +msgstr "" + +#: src/lib/launch.sh:291 +#, sh-printf-format +msgid "" +"Confirm the second prompt in your authenticator app too. It comes in about " +"%d seconds." +msgstr "" + +#: src/lib/launch.sh:301 +#, sh-printf-format +msgid "%s could not start" +msgstr "" + +#: src/lib/launch.sh:303 +#, sh-printf-format +msgid "Log: %s" +msgstr "" + +#. TRANSLATORS: the letter after "[" is the key for yes. y works too. +#: src/lib/menu.sh:93 +msgid "[y/N]" +msgstr "" + +#: src/lib/menu.sh:163 +msgid "1 app" +msgstr "" + +#: src/lib/menu.sh:165 +#, sh-printf-format +msgid "%d apps" +msgstr "" + +#: src/lib/menu.sh:190 +msgid "No workspace yet. Add the one of your company with [1]." +msgstr "" + +#: src/lib/menu.sh:233 +msgid "off" +msgstr "" + +#: src/lib/menu.sh:234 +msgid "home folder" +msgstr "" + +#: src/lib/menu.sh:237 +msgid "Documents" +msgstr "" + +#: src/lib/menu.sh:238 +msgid "Downloads" +msgstr "" + +#: src/lib/menu.sh:243 +msgid "not found" +msgstr "" + +#: src/lib/menu.sh:246 +#, sh-printf-format +msgid "automatic (%d %%)" +msgstr "" + +#: src/lib/menu.sh:248 +msgid "classic" +msgstr "" + +#: src/lib/menu.sh:249 +msgid "GFX" +msgstr "" + +#: src/lib/menu.sh:250 +msgid "always" +msgstr "" + +#: src/lib/menu.sh:252 +msgid "automatic" +msgstr "" + +#: src/lib/menu.sh:261 +msgid "" +"The apps only see the files on the server. Pick a folder to open and save " +"files of this computer in them." +msgstr "" + +#: src/lib/menu.sh:262 +msgid "" +"The apps can open and save files in this folder. It shows up as a drive in " +"them." +msgstr "" + +#: src/lib/menu.sh:263 +msgid "" +"Offers the apps under \"Open with\" for their file types, for example Word " +"for .docx. The folder of the file is shared with the app while it is open." +msgstr "" + +#: src/lib/menu.sh:264 +msgid "" +"How big the apps are drawn. Automatic follows the scale of your desktop, so " +"they are sharp and the right size." +msgstr "" + +#: src/lib/menu.sh:265 +msgid "Plays the sound of the apps here, for calls and videos." +msgstr "" + +#: src/lib/menu.sh:266 +msgid "" +"Paints every window right. But some servers end the session right after " +"signing in, then go back to GFX." +msgstr "" + +#: src/lib/menu.sh:267 +msgid "" +"Works with every server. FreeRDP may not repaint a window after it was " +"minimized or resized." +msgstr "" + +#: src/lib/menu.sh:268 +msgid "" +"Always waits before the second sign-in. For when the second prompt does not " +"come with automatic." +msgstr "" + +#: src/lib/menu.sh:269 +msgid "Never waits. Right when your company has no MFA." +msgstr "" + +#: src/lib/menu.sh:270 +msgid "" +"Many servers with MFA need a second sign-in, and its prompt only comes after " +"a short wait. Automatic waits when the first sign-in took a while." +msgstr "" + +#: src/lib/menu.sh:273 +msgid "" +"Loads the list of apps again once a day, so new apps show up by themselves." +msgstr "" + +#: src/lib/menu.sh:275 src/lib/menu.sh:330 +msgid "Needs a systemd user session." +msgstr "" + +#: src/lib/menu.sh:322 src/lib/menu.sh:325 +msgid "Could not save the setting." +msgstr "" + +#: src/lib/menu.sh:371 src/lib/menu.sh:609 +msgid "Settings" +msgstr "" + +#: src/lib/menu.sh:372 +msgid "โ†‘โ†“ select โ†โ†’ or Space: change q: back" +msgstr "" + +#: src/lib/menu.sh:373 +msgid "ON" +msgstr "" + +#: src/lib/menu.sh:374 +msgid "OFF" +msgstr "" + +#: src/lib/menu.sh:456 src/lib/menu.sh:608 +msgid "Workspaces" +msgstr "" + +#. TRANSLATORS: keep the letters, they are the keys. +#: src/lib/menu.sh:458 +msgid "" +"โ†‘โ†“ select r: refresh s: sign in again d: remove a: add q: back" +msgstr "" + +#: src/lib/menu.sh:459 +msgid "No workspace yet. Press a to add one." +msgstr "" + +#: src/lib/menu.sh:484 +#, sh-printf-format +msgid "refreshed %s" +msgstr "" + +#: src/lib/menu.sh:512 +#, sh-printf-format +msgid "Remove \"%s\" and its apps?" +msgstr "" + +#: src/lib/menu.sh:536 src/lib/menu.sh:607 +msgid "Start an app" +msgstr "" + +#: src/lib/menu.sh:537 +msgid "โ†‘โ†“ select Enter: start q: back" +msgstr "" + +#: src/lib/menu.sh:538 +msgid "No apps yet. Add a workspace first." +msgstr "" + +#: src/lib/menu.sh:579 +#, sh-printf-format +msgid "%s is starting. It opens in a window of its own." +msgstr "" + +#: src/lib/menu.sh:606 +msgid "Add a workspace" +msgstr "" + +#: src/lib/menu.sh:610 +msgid "Quit" +msgstr "" + +#: src/lib/workspace.sh:163 +msgid "The server did not accept the user name or password." +msgstr "" + +#: src/lib/workspace.sh:164 +#, sh-printf-format +msgid "Could not reach the server: %s" +msgstr "" + +#: src/lib/workspace.sh:165 +#, sh-printf-format +msgid "There is no RD Web feed at %s." +msgstr "" + +#: src/lib/workspace.sh:166 +#, sh-printf-format +msgid "Could not load the apps: %s" +msgstr "" + +#: src/lib/workspace.sh:282 +#, sh-printf-format +msgid "Remote desktop of %s" +msgstr "" + +#: src/lib/workspace.sh:284 +#, sh-printf-format +msgid "%s from %s" +msgstr "" + +#: src/lib/menu.sh +msgid "Apps" +msgstr "" + +#: src/lib/menu.sh +msgid "Share a folder" +msgstr "" + +#: src/lib/menu.sh +msgid "Open files with the apps" +msgstr "" + +#: src/lib/menu.sh +msgid "Size" +msgstr "" + +#: src/lib/menu.sh +msgid "Sound" +msgstr "" + +#: src/lib/menu.sh +msgid "Graphics" +msgstr "" + +#: src/lib/menu.sh +msgid "Signing in" +msgstr "" + +#: src/lib/menu.sh +msgid "Wait for the second MFA prompt" +msgstr "" + +#: src/lib/menu.sh +msgid "Refresh the apps every day" +msgstr "" diff --git a/po/ru.po b/po/ru.po new file mode 100644 index 0000000..4cdadcf --- /dev/null +++ b/po/ru.po @@ -0,0 +1,558 @@ +# Russian translation for rdfeed. +# Copyright (C) 2026 Felitendo +# This file is distributed under the same license as rdfeed. +# +msgid "" +msgstr "" +"Project-Id-Version: rdfeed 1.0.0\n" +"Report-Msgid-Bugs-To: https://github.com/LoonixTools/rdfeed/issues\n" +"POT-Creation-Date: 2026-10-02 10:29+0200\n" +"PO-Revision-Date: 2026-10-02 09:30+0200\n" +"Last-Translator: Felitendo\n" +"Language-Team: Russian\n" +"Language: ru\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/rdfeed:79 src/rdfeed:103 +#, sh-printf-format +msgid "Could not write to %s." +msgstr "ะะต ัƒะดะฐะปะพััŒ ะทะฐะฟะธัะฐั‚ัŒ ะฒ %s." + +#: src/rdfeed:82 +#, sh-printf-format +msgid "Signing in to %sโ€ฆ" +msgstr "ะ’ั…ะพะด ะฝะฐ %sโ€ฆ" + +#: src/rdfeed:100 +msgid "" +"The password could not be saved in the keyring, so each app asks for it." +msgstr "" +"ะะต ัƒะดะฐะปะพััŒ ัะพั…ั€ะฐะฝะธั‚ัŒ ะฟะฐั€ะพะปัŒ ะฒ ัะฒัะทะบะต ะบะปัŽั‡ะตะน, ะฟะพัั‚ะพะผัƒ ะบะฐะถะดะพะต ะฟั€ะธะปะพะถะตะฝะธะต ะฑัƒะดะตั‚ " +"ะตะณะพ ะทะฐะฟั€ะฐัˆะธะฒะฐั‚ัŒ." + +#: src/rdfeed:111 +#, sh-printf-format +msgid "1 app from %s is now in your app menu." +msgstr "1 ะฟั€ะธะปะพะถะตะฝะธะต ะธะท %s ั‚ะตะฟะตั€ัŒ ะฒ ะผะตะฝัŽ ะฟั€ะธะปะพะถะตะฝะธะน." + +#: src/rdfeed:113 +#, sh-printf-format +msgid "%d apps from %s are now in your app menu." +msgstr "ะŸั€ะธะปะพะถะตะฝะธะน: %d (ะธะท %s). ะขะตะฟะตั€ัŒ ะพะฝะธ ะฒ ะผะตะฝัŽ ะฟั€ะธะปะพะถะตะฝะธะน." + +#: src/rdfeed:129 +msgid "" +"Your IT department has the address. A work e-mail address often works too." +msgstr "" +"ะะดั€ะตั ะตัั‚ัŒ ัƒ ะฒะฐัˆะตะณะพ ะ˜ะข-ะพั‚ะดะตะปะฐ. ะงะฐัั‚ะพ ะฟะพะดั…ะพะดะธั‚ ะธ ั€ะฐะฑะพั‡ะธะน ะฐะดั€ะตั ัะปะตะบั‚ั€ะพะฝะฝะพะน " +"ะฟะพั‡ั‚ั‹." + +#: src/rdfeed:130 +msgid "Feed address, server or e-mail:" +msgstr "ะะดั€ะตั ั„ะธะดะฐ, ัะตั€ะฒะตั€ ะธะปะธ e-mail:" + +#: src/rdfeed:137 +#, sh-printf-format +msgid "No feed is registered for %s. Ask your IT department for the address." +msgstr "ะ”ะปั %s ะฝะต ะทะฐั€ะตะณะธัั‚ั€ะธั€ะพะฒะฐะฝ ั„ะธะด. ะกะฟั€ะพัะธั‚ะต ะฐะดั€ะตั ัƒ ะ˜ะข-ะพั‚ะดะตะปะฐ." + +#: src/rdfeed:142 src/rdfeed:156 +msgid "User name (DOMAIN\\name or name@domain):" +msgstr "ะ˜ะผั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั (ะ”ะžะœะ•ะ\\ะธะผั ะธะปะธ ะธะผั@ะดะพะผะตะฝ):" + +#: src/rdfeed:146 src/rdfeed:159 +msgid "Password:" +msgstr "ะŸะฐั€ะพะปัŒ:" + +#: src/rdfeed:178 src/rdfeed:236 +#, sh-printf-format +msgid "No workspace yet. Add one with `%s add`." +msgstr "ะ ะฐะฑะพั‡ะธั… ะพะฑะปะฐัั‚ะตะน ะฟะพะบะฐ ะฝะตั‚. ะ”ะพะฑะฐะฒัŒั‚ะต ะตั‘ ะบะพะผะฐะฝะดะพะน `%s add`." + +#: src/rdfeed:187 src/lib/launch.sh:220 +#, sh-printf-format +msgid "No password saved for %s. Sign in again in rdfeed." +msgstr "ะ”ะปั %s ะฝะต ัะพั…ั€ะฐะฝั‘ะฝ ะฟะฐั€ะพะปัŒ. ะ’ะพะนะดะธั‚ะต ัะฝะพะฒะฐ ะฒ rdfeed." + +#: src/rdfeed:191 src/lib/launch.sh:149 +#, sh-printf-format +msgid "Password for %s:" +msgstr "ะŸะฐั€ะพะปัŒ ะดะปั %s:" + +#: src/rdfeed:195 +#, sh-printf-format +msgid "Refreshing %sโ€ฆ" +msgstr "ะžะฑะฝะพะฒะปะตะฝะธะต %sโ€ฆ" + +#: src/rdfeed:205 +msgid "The password no longer works. Sign in again in rdfeed." +msgstr "ะŸะฐั€ะพะปัŒ ะฑะพะปัŒัˆะต ะฝะต ะฟะพะดั…ะพะดะธั‚. ะ’ะพะนะดะธั‚ะต ัะฝะพะฒะฐ ะฒ rdfeed." + +#: src/rdfeed:214 +#, sh-printf-format +msgid "%s: %d apps" +msgstr "%s: ะฟั€ะธะปะพะถะตะฝะธะน: %d" + +#: src/rdfeed:222 +#, sh-printf-format +msgid "Which workspace? See `%s list` for the names." +msgstr "ะšะฐะบะฐั ั€ะฐะฑะพั‡ะฐั ะพะฑะปะฐัั‚ัŒ? ะะฐะทะฒะฐะฝะธั ะฟะพะบะฐะทั‹ะฒะฐะตั‚ `%s list`." + +#: src/rdfeed:229 +#, sh-printf-format +msgid "%s and its apps are removed." +msgstr "ะฃะดะฐะปะตะฝะพ: %s ะฒะผะตัั‚ะต ั ะฟั€ะธะปะพะถะตะฝะธัะผะธ." + +#: src/rdfeed:253 +#, sh-printf-format +msgid "Which app? See `%s list`." +msgstr "ะšะฐะบะพะต ะฟั€ะธะปะพะถะตะฝะธะต? ะกะผะพั‚ั€ะธั‚ะต `%s list`." + +#: src/rdfeed:268 src/lib/launch.sh:199 +#, sh-printf-format +msgid "There is no app %s. See `%s list`." +msgstr "ะŸั€ะธะปะพะถะตะฝะธั %s ะฝะตั‚. ะกะผะพั‚ั€ะธั‚ะต `%s list`." + +#: src/rdfeed:270 +#, sh-printf-format +msgid "Several workspaces have %s. Write it as WORKSPACE/APP, see `%s list`." +msgstr "" +"%s ะตัั‚ัŒ ะฒ ะฝะตัะบะพะปัŒะบะธั… ั€ะฐะฑะพั‡ะธั… ะพะฑะปะฐัั‚ัั…. ะฃะบะฐะถะธั‚ะต ะฒ ะฒะธะดะต ะžะ‘ะ›ะะกะขะฌ/ะŸะ ะ˜ะ›ะžะ–ะ•ะะ˜ะ•, " +"ัะผะพั‚ั€ะธั‚ะต `%s list`." + +#: src/rdfeed:278 +msgid "Usage: rdfeed [command]" +msgstr "ะ˜ัะฟะพะปัŒะทะพะฒะฐะฝะธะต: rdfeed [ะบะพะผะฐะฝะดะฐ]" + +#: src/rdfeed:280 +msgid "Commands:" +msgstr "ะšะพะผะฐะฝะดั‹:" + +#: src/rdfeed:281 +msgid "Add the workspace of your company" +msgstr "ะ”ะพะฑะฐะฒะธั‚ัŒ ั€ะฐะฑะพั‡ัƒัŽ ะพะฑะปะฐัั‚ัŒ ะฒะฐัˆะตะน ะบะพะผะฟะฐะฝะธะธ" + +#: src/rdfeed:282 +msgid "Load the apps again" +msgstr "ะ—ะฐะณั€ัƒะทะธั‚ัŒ ะฟั€ะธะปะพะถะตะฝะธั ะทะฐะฝะพะฒะพ" + +#: src/rdfeed:283 +msgid "List the workspaces and their apps" +msgstr "ะŸะพะบะฐะทะฐั‚ัŒ ั€ะฐะฑะพั‡ะธะต ะพะฑะปะฐัั‚ะธ ะธ ะธั… ะฟั€ะธะปะพะถะตะฝะธั" + +#: src/rdfeed:284 +msgid "Start an app, or open a file with it" +msgstr "ะ—ะฐะฟัƒัั‚ะธั‚ัŒ ะฟั€ะธะปะพะถะตะฝะธะต ะธะปะธ ะพั‚ะบั€ั‹ั‚ัŒ ะฒ ะฝั‘ะผ ั„ะฐะนะป" + +#: src/rdfeed:285 +msgid "Remove a workspace and its apps" +msgstr "ะฃะดะฐะปะธั‚ัŒ ั€ะฐะฑะพั‡ัƒัŽ ะพะฑะปะฐัั‚ัŒ ะธ ะตั‘ ะฟั€ะธะปะพะถะตะฝะธั" + +#: src/rdfeed:286 +msgid "Show this help" +msgstr "ะŸะพะบะฐะทะฐั‚ัŒ ัั‚ัƒ ัะฟั€ะฐะฒะบัƒ" + +#: src/rdfeed:287 +msgid "Show the version" +msgstr "ะŸะพะบะฐะทะฐั‚ัŒ ะฒะตั€ัะธัŽ" + +#: src/rdfeed:289 +msgid "Without a command an interactive menu is shown." +msgstr "ะ‘ะตะท ะบะพะผะฐะฝะดั‹ ะพั‚ะบั€ั‹ะฒะฐะตั‚ัั ะธะฝั‚ะตั€ะฐะบั‚ะธะฒะฝะพะต ะผะตะฝัŽ." + +#: src/rdfeed:313 +#, sh-printf-format +msgid "Unknown command: %s" +msgstr "ะะตะธะทะฒะตัั‚ะฝะฐั ะบะพะผะฐะฝะดะฐ: %s" + +#: src/lib/common.sh:174 src/lib/menu.sh:251 +msgid "never" +msgstr "ะฝะธะบะพะณะดะฐ" + +#: src/lib/common.sh:184 +msgid "just now" +msgstr "ั‚ะพะปัŒะบะพ ั‡ั‚ะพ" + +#: src/lib/common.sh:186 +msgid "1 minute ago" +msgstr "1 ะผะธะฝัƒั‚ัƒ ะฝะฐะทะฐะด" + +#: src/lib/common.sh:189 +#, sh-printf-format +msgid "%d minutes ago" +msgstr "%d ะผะธะฝ ะฝะฐะทะฐะด" + +#: src/lib/common.sh:191 +msgid "1 hour ago" +msgstr "1 ั‡ะฐั ะฝะฐะทะฐะด" + +#: src/lib/common.sh:194 +#, sh-printf-format +msgid "%d hours ago" +msgstr "%d ั‡ ะฝะฐะทะฐะด" + +#: src/lib/common.sh:196 +msgid "1 day ago" +msgstr "1 ะดะตะฝัŒ ะฝะฐะทะฐะด" + +#: src/lib/common.sh:199 +#, sh-printf-format +msgid "%d days ago" +msgstr "%d ะดะฝ. ะฝะฐะทะฐะด" + +#: src/lib/launch.sh:169 +msgid "" +"Your password has expired. Change it on Windows or on the RD Web page, then " +"sign in again in rdfeed." +msgstr "" +"ะกั€ะพะบ ะดะตะนัั‚ะฒะธั ะฟะฐั€ะพะปั ะธัั‚ั‘ะบ. ะกะผะตะฝะธั‚ะต ะตะณะพ ะฒ Windows ะธะปะธ ะฝะฐ ัั‚ั€ะฐะฝะธั†ะต RD Web, " +"ะทะฐั‚ะตะผ ัะฝะพะฒะฐ ะฒะพะนะดะธั‚ะต ะฒ rdfeed." + +#: src/lib/launch.sh:171 +msgid "Your account is locked. Ask your IT department." +msgstr "ะฃั‡ั‘ั‚ะฝะฐั ะทะฐะฟะธััŒ ะทะฐะฑะปะพะบะธั€ะพะฒะฐะฝะฐ. ะžะฑั€ะฐั‚ะธั‚ะตััŒ ะฒ ะ˜ะข-ะพั‚ะดะตะป." + +#: src/lib/launch.sh:173 +msgid "The server did not accept the password. Sign in again in rdfeed." +msgstr "ะกะตั€ะฒะตั€ ะฝะต ะฟั€ะธะฝัะป ะฟะฐั€ะพะปัŒ. ะ’ะพะนะดะธั‚ะต ัะฝะพะฒะฐ ะฒ rdfeed." + +#: src/lib/launch.sh:175 +msgid "Could not reach the server. Check the internet connection." +msgstr "ะะต ัƒะดะฐะปะพััŒ ัะฒัะทะฐั‚ัŒัั ั ัะตั€ะฒะตั€ะพะผ. ะŸั€ะพะฒะตั€ัŒั‚ะต ะฟะพะดะบะปัŽั‡ะตะฝะธะต ะบ ะธะฝั‚ะตั€ะฝะตั‚ัƒ." + +#: src/lib/launch.sh:178 +#, sh-printf-format +msgid "FreeRDP stopped: %s" +msgstr "FreeRDP ะพัั‚ะฐะฝะพะฒะธะปัั: %s" + +#: src/lib/launch.sh:205 src/lib/launch.sh:206 +#, sh-printf-format +msgid "File not found: %s" +msgstr "ะคะฐะนะป ะฝะต ะฝะฐะนะดะตะฝ: %s" + +#: src/lib/launch.sh:212 src/lib/launch.sh:213 +msgid "" +"FreeRDP 3 is missing. Install it to start the apps (the package is called " +"freerdp)." +msgstr "" +"ะะตั‚ FreeRDP 3. ะฃัั‚ะฐะฝะพะฒะธั‚ะต ะตะณะพ, ั‡ั‚ะพะฑั‹ ะทะฐะฟัƒัะบะฐั‚ัŒ ะฟั€ะธะปะพะถะตะฝะธั (ะฟะฐะบะตั‚ ะฝะฐะทั‹ะฒะฐะตั‚ัั " +"freerdp)." + +#: src/lib/launch.sh:277 +#, sh-printf-format +msgid "Opening %s in %s" +msgstr "ะžั‚ะบั€ั‹ั‚ะธะต %s ะฒ %s" + +#: src/lib/launch.sh:278 +#, sh-printf-format +msgid "If it does not open by itself, open it in the app from the drive %s." +msgstr "ะ•ัะปะธ ั„ะฐะนะป ะฝะต ะพั‚ะบั€ะพะตั‚ัั ัะฐะผ, ะพั‚ะบั€ะพะนั‚ะต ะตะณะพ ะฒ ะฟั€ะธะปะพะถะตะฝะธะธ ั ะดะธัะบะฐ %s." + +#: src/lib/launch.sh:280 +#, sh-printf-format +msgid "Starting %s" +msgstr "ะ—ะฐะฟัƒัะบ: %s" + +#: src/lib/launch.sh:281 +msgid "" +"If your company uses MFA, confirm the sign-in in your authenticator app." +msgstr "" +"ะ•ัะปะธ ะฒะฐัˆะฐ ะบะพะผะฟะฐะฝะธั ะธัะฟะพะปัŒะทัƒะตั‚ MFA, ะฟะพะดั‚ะฒะตั€ะดะธั‚ะต ะฒั…ะพะด ะฒ ะฟั€ะธะปะพะถะตะฝะธะธ-" +"ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั‚ะพั€ะต." + +#: src/lib/launch.sh:290 +msgid "A second sign-in is coming" +msgstr "ะ‘ัƒะดะตั‚ ะฒั‚ะพั€ะพะน ะฒั…ะพะด" + +#: src/lib/launch.sh:291 +#, sh-printf-format +msgid "" +"Confirm the second prompt in your authenticator app too. It comes in about " +"%d seconds." +msgstr "" +"ะŸะพะดั‚ะฒะตั€ะดะธั‚ะต ะธ ะฒั‚ะพั€ะพะน ะทะฐะฟั€ะพั ะฒ ะฟั€ะธะปะพะถะตะฝะธะธ-ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั‚ะพั€ะต. ะžะฝ ะฟั€ะธะดั‘ั‚ ะฟั€ะธะผะตั€ะฝะพ " +"ั‡ะตั€ะตะท %d ั." + +#: src/lib/launch.sh:301 +#, sh-printf-format +msgid "%s could not start" +msgstr "ะะต ัƒะดะฐะปะพััŒ ะทะฐะฟัƒัั‚ะธั‚ัŒ: %s" + +#: src/lib/launch.sh:303 +#, sh-printf-format +msgid "Log: %s" +msgstr "ะ–ัƒั€ะฝะฐะป: %s" + +#. TRANSLATORS: the letter after "[" is the key for yes. y works too. +#: src/lib/menu.sh:93 +msgid "[y/N]" +msgstr "[ะด/ะ]" + +#: src/lib/menu.sh:163 +msgid "1 app" +msgstr "1 ะฟั€ะธะปะพะถะตะฝะธะต" + +#: src/lib/menu.sh:165 +#, sh-printf-format +msgid "%d apps" +msgstr "ะฟั€ะธะปะพะถะตะฝะธะน: %d" + +#: src/lib/menu.sh:190 +msgid "No workspace yet. Add the one of your company with [1]." +msgstr "ะ ะฐะฑะพั‡ะธั… ะพะฑะปะฐัั‚ะตะน ะฟะพะบะฐ ะฝะตั‚. ะ”ะพะฑะฐะฒัŒั‚ะต ะพะฑะปะฐัั‚ัŒ ะฒะฐัˆะตะน ะบะพะผะฟะฐะฝะธะธ ั‡ะตั€ะตะท [1]." + +#: src/lib/menu.sh:233 +msgid "off" +msgstr "ะฒั‹ะบะป." + +#: src/lib/menu.sh:234 +msgid "home folder" +msgstr "ะดะพะผะฐัˆะฝัั ะฟะฐะฟะบะฐ" + +#: src/lib/menu.sh:237 +msgid "Documents" +msgstr "ะ”ะพะบัƒะผะตะฝั‚ั‹" + +#: src/lib/menu.sh:238 +msgid "Downloads" +msgstr "ะ—ะฐะณั€ัƒะทะบะธ" + +#: src/lib/menu.sh:243 +msgid "not found" +msgstr "ะฝะต ะฝะฐะนะดะตะฝะฐ" + +#: src/lib/menu.sh:246 +#, sh-printf-format +msgid "automatic (%d %%)" +msgstr "ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ (%d %%)" + +#: src/lib/menu.sh:248 +msgid "classic" +msgstr "ะบะปะฐััะธั‡ะตัะบะธะน" + +#: src/lib/menu.sh:249 +msgid "GFX" +msgstr "GFX" + +#: src/lib/menu.sh:250 +msgid "always" +msgstr "ะฒัะตะณะดะฐ" + +#: src/lib/menu.sh:252 +msgid "automatic" +msgstr "ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ" + +#: src/lib/menu.sh:261 +msgid "" +"The apps only see the files on the server. Pick a folder to open and save " +"files of this computer in them." +msgstr "" +"ะŸั€ะธะปะพะถะตะฝะธั ะฒะธะดัั‚ ั‚ะพะปัŒะบะพ ั„ะฐะนะปั‹ ะฝะฐ ัะตั€ะฒะตั€ะต. ะ’ั‹ะฑะตั€ะธั‚ะต ะฟะฐะฟะบัƒ, ั‡ั‚ะพะฑั‹ ะพะฝะธ ะผะพะณะปะธ " +"ะพั‚ะบั€ั‹ะฒะฐั‚ัŒ ะธ ัะพั…ั€ะฐะฝัั‚ัŒ ั„ะฐะนะปั‹ ัั‚ะพะณะพ ะบะพะผะฟัŒัŽั‚ะตั€ะฐ." + +#: src/lib/menu.sh:262 +msgid "" +"The apps can open and save files in this folder. It shows up as a drive in " +"them." +msgstr "" +"ะŸั€ะธะปะพะถะตะฝะธั ะผะพะณัƒั‚ ะพั‚ะบั€ั‹ะฒะฐั‚ัŒ ะธ ัะพั…ั€ะฐะฝัั‚ัŒ ั„ะฐะนะปั‹ ะฒ ัั‚ะพะน ะฟะฐะฟะบะต. ะ’ ะฝะธั… ะพะฝะฐ ะฒะธะดะฝะฐ " +"ะบะฐะบ ะดะธัะบ." + +#: src/lib/menu.sh:263 +msgid "" +"Offers the apps under \"Open with\" for their file types, for example Word " +"for .docx. The folder of the file is shared with the app while it is open." +msgstr "" +"ะŸั€ะตะดะปะฐะณะฐะตั‚ ะฟั€ะธะปะพะถะตะฝะธั ะฒ ยซะžั‚ะบั€ั‹ั‚ัŒ ั ะฟะพะผะพั‰ัŒัŽยป ะดะปั ะธั… ั‚ะธะฟะพะฒ ั„ะฐะนะปะพะฒ, ะฝะฐะฟั€ะธะผะตั€ " +"Word ะดะปั .docx. ะŸะฐะฟะบะฐ ั„ะฐะนะปะฐ ะพั‚ะบั€ั‹ั‚ะฐ ะดะปั ัะตั€ะฒะตั€ะฐ, ะฟะพะบะฐ ั„ะฐะนะป ะพั‚ะบั€ั‹ั‚." + +#: src/lib/menu.sh:264 +msgid "" +"How big the apps are drawn. Automatic follows the scale of your desktop, so " +"they are sharp and the right size." +msgstr "" +"ะะฐัะบะพะปัŒะบะพ ะบั€ัƒะฟะฝะพ ั€ะธััƒัŽั‚ัั ะฟั€ะธะปะพะถะตะฝะธั. ยซะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธยป ัะปะตะดัƒะตั‚ ะผะฐััˆั‚ะฐะฑัƒ " +"ั€ะฐะฑะพั‡ะตะณะพ ัั‚ะพะปะฐ, ั‚ะฐะบ ะพะฝะธ ั‡ั‘ั‚ะบะธะต ะธ ะฝัƒะถะฝะพะณะพ ั€ะฐะทะผะตั€ะฐ." + +#: src/lib/menu.sh:265 +msgid "Plays the sound of the apps here, for calls and videos." +msgstr "ะ’ะพัะฟั€ะพะธะทะฒะพะดะธั‚ ะทะฒัƒะบ ะฟั€ะธะปะพะถะตะฝะธะน ะทะดะตััŒ, ะดะปั ะทะฒะพะฝะบะพะฒ ะธ ะฒะธะดะตะพ." + +#: src/lib/menu.sh:266 +msgid "" +"Paints every window right. But some servers end the session right after " +"signing in, then go back to GFX." +msgstr "ะŸั€ะฐะฒะธะปัŒะฝะพ ั€ะธััƒะตั‚ ะบะฐะถะดะพะต ะพะบะฝะพ. ะะพ ะฝะตะบะพั‚ะพั€ั‹ะต ัะตั€ะฒะตั€ั‹ ะพะฑั€ั‹ะฒะฐัŽั‚ ัะตะฐะฝั ัั€ะฐะทัƒ ะฟะพัะปะต ะฒั…ะพะดะฐ, ั‚ะพะณะดะฐ ะฒะตั€ะฝะธั‚ะตััŒ ะบ GFX." + +#: src/lib/menu.sh:267 +msgid "" +"Works with every server. FreeRDP may not repaint a window after it was " +"minimized or resized." +msgstr "ะ ะฐะฑะพั‚ะฐะตั‚ ั ะปัŽะฑั‹ะผ ัะตั€ะฒะตั€ะพะผ. FreeRDP ะผะพะถะตั‚ ะฝะต ะฟะตั€ะตั€ะธัะพะฒะฐั‚ัŒ ะพะบะฝะพ ะฟะพัะปะต ัะฒะพั€ะฐั‡ะธะฒะฐะฝะธั ะธะปะธ ะธะทะผะตะฝะตะฝะธั ั€ะฐะทะผะตั€ะฐ." + +#: src/lib/menu.sh:268 +msgid "" +"Always waits before the second sign-in. For when the second prompt does not " +"come with automatic." +msgstr "" +"ะ’ัะตะณะดะฐ ะถะดั‘ั‚ ะฟะตั€ะตะด ะฒั‚ะพั€ั‹ะผ ะฒั…ะพะดะพะผ. ะ•ัะปะธ ั ยซะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธยป ะฒั‚ะพั€ะพะน ะทะฐะฟั€ะพั ะฝะต " +"ะฟั€ะธั…ะพะดะธั‚." + +#: src/lib/menu.sh:269 +msgid "Never waits. Right when your company has no MFA." +msgstr "ะะธะบะพะณะดะฐ ะฝะต ะถะดั‘ั‚. ะŸะพะดั…ะพะดะธั‚, ะตัะปะธ ะฒะฐัˆะฐ ะบะพะผะฟะฐะฝะธั ะฝะต ะธัะฟะพะปัŒะทัƒะตั‚ MFA." + +#: src/lib/menu.sh:270 +msgid "" +"Many servers with MFA need a second sign-in, and its prompt only comes after " +"a short wait. Automatic waits when the first sign-in took a while." +msgstr "" +"ะœะฝะพะณะธะผ ัะตั€ะฒะตั€ะฐะผ ั MFA ะฝัƒะถะตะฝ ะฒั‚ะพั€ะพะน ะฒั…ะพะด, ะธ ะตะณะพ ะทะฐะฟั€ะพั ะฟั€ะธั…ะพะดะธั‚ ั‚ะพะปัŒะบะพ ะฟะพัะปะต " +"ะบะพั€ะพั‚ะบะพะน ะฟะฐัƒะทั‹. ยซะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธยป ะถะดั‘ั‚, ะตัะปะธ ะฟะตั€ะฒั‹ะน ะฒั…ะพะด ะทะฐะฝัะป ะฒั€ะตะผั." + +#: src/lib/menu.sh:273 +msgid "" +"Loads the list of apps again once a day, so new apps show up by themselves." +msgstr "" +"ะ ะฐะท ะฒ ะดะตะฝัŒ ะทะฐะฝะพะฒะพ ะทะฐะณั€ัƒะถะฐะตั‚ ัะฟะธัะพะบ ะฟั€ะธะปะพะถะตะฝะธะน, ั‚ะฐะบ ะฝะพะฒั‹ะต ะฟะพัะฒะปััŽั‚ัั ัะฐะผะธ." + +#: src/lib/menu.sh:275 src/lib/menu.sh:330 +msgid "Needs a systemd user session." +msgstr "ะัƒะถะตะฝ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะน ัะตะฐะฝั systemd." + +#: src/lib/menu.sh:322 src/lib/menu.sh:325 +msgid "Could not save the setting." +msgstr "ะะต ัƒะดะฐะปะพััŒ ัะพั…ั€ะฐะฝะธั‚ัŒ ะฝะฐัั‚ั€ะพะนะบัƒ." + +#: src/lib/menu.sh:371 src/lib/menu.sh:609 +msgid "Settings" +msgstr "ะะฐัั‚ั€ะพะนะบะธ" + +#: src/lib/menu.sh:372 +msgid "โ†‘โ†“ select โ†โ†’ or Space: change q: back" +msgstr "โ†‘โ†“ ะฒั‹ะฑะพั€ โ†โ†’ ะธะปะธ ะŸั€ะพะฑะตะป: ะธะทะผะตะฝะธั‚ัŒ q: ะฝะฐะทะฐะด" + +#: src/lib/menu.sh:373 +msgid "ON" +msgstr "ะ’ะšะ›" + +#: src/lib/menu.sh:374 +msgid "OFF" +msgstr "ะ’ะซะšะ›" + +#: src/lib/menu.sh:456 src/lib/menu.sh:608 +msgid "Workspaces" +msgstr "ะ ะฐะฑะพั‡ะธะต ะพะฑะปะฐัั‚ะธ" + +#. TRANSLATORS: keep the letters, they are the keys. +#: src/lib/menu.sh:458 +msgid "" +"โ†‘โ†“ select r: refresh s: sign in again d: remove a: add q: back" +msgstr "" +"โ†‘โ†“ ะฒั‹ะฑะพั€ r: ะพะฑะฝะพะฒะธั‚ัŒ s: ะฒะพะนั‚ะธ ัะฝะพะฒะฐ d: ัƒะดะฐะปะธั‚ัŒ a: ะดะพะฑะฐะฒะธั‚ัŒ q: ะฝะฐะทะฐะด" + +#: src/lib/menu.sh:459 +msgid "No workspace yet. Press a to add one." +msgstr "ะ ะฐะฑะพั‡ะธั… ะพะฑะปะฐัั‚ะตะน ะฟะพะบะฐ ะฝะตั‚. ะะฐะถะผะธั‚ะต a, ั‡ั‚ะพะฑั‹ ะดะพะฑะฐะฒะธั‚ัŒ." + +#: src/lib/menu.sh:484 +#, sh-printf-format +msgid "refreshed %s" +msgstr "ะพะฑะฝะพะฒะปะตะฝะพ %s" + +#: src/lib/menu.sh:512 +#, sh-printf-format +msgid "Remove \"%s\" and its apps?" +msgstr "ะฃะดะฐะปะธั‚ัŒ ยซ%sยป ะฒะผะตัั‚ะต ั ะฟั€ะธะปะพะถะตะฝะธัะผะธ?" + +#: src/lib/menu.sh:536 src/lib/menu.sh:607 +msgid "Start an app" +msgstr "ะ—ะฐะฟัƒัั‚ะธั‚ัŒ ะฟั€ะธะปะพะถะตะฝะธะต" + +#: src/lib/menu.sh:537 +msgid "โ†‘โ†“ select Enter: start q: back" +msgstr "โ†‘โ†“ ะฒั‹ะฑะพั€ Enter: ะทะฐะฟัƒัั‚ะธั‚ัŒ q: ะฝะฐะทะฐะด" + +#: src/lib/menu.sh:538 +msgid "No apps yet. Add a workspace first." +msgstr "ะŸั€ะธะปะพะถะตะฝะธะน ะฟะพะบะฐ ะฝะตั‚. ะกะฝะฐั‡ะฐะปะฐ ะดะพะฑะฐะฒัŒั‚ะต ั€ะฐะฑะพั‡ัƒัŽ ะพะฑะปะฐัั‚ัŒ." + +#: src/lib/menu.sh:579 +#, sh-printf-format +msgid "%s is starting. It opens in a window of its own." +msgstr "ะ—ะฐะฟัƒัะบ: %s. ะžั‚ะบั€ะพะตั‚ัั ะฒ ะพั‚ะดะตะปัŒะฝะพะผ ะพะบะฝะต." + +#: src/lib/menu.sh:606 +msgid "Add a workspace" +msgstr "ะ”ะพะฑะฐะฒะธั‚ัŒ ั€ะฐะฑะพั‡ัƒัŽ ะพะฑะปะฐัั‚ัŒ" + +#: src/lib/menu.sh:610 +msgid "Quit" +msgstr "ะ’ั‹ั…ะพะด" + +#: src/lib/workspace.sh:163 +msgid "The server did not accept the user name or password." +msgstr "ะกะตั€ะฒะตั€ ะฝะต ะฟั€ะธะฝัะป ะธะผั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะธะปะธ ะฟะฐั€ะพะปัŒ." + +#: src/lib/workspace.sh:164 +#, sh-printf-format +msgid "Could not reach the server: %s" +msgstr "ะะต ัƒะดะฐะปะพััŒ ัะฒัะทะฐั‚ัŒัั ั ัะตั€ะฒะตั€ะพะผ: %s" + +#: src/lib/workspace.sh:165 +#, sh-printf-format +msgid "There is no RD Web feed at %s." +msgstr "ะŸะพ ะฐะดั€ะตััƒ %s ะฝะตั‚ ั„ะธะดะฐ RD Web." + +#: src/lib/workspace.sh:166 +#, sh-printf-format +msgid "Could not load the apps: %s" +msgstr "ะะต ัƒะดะฐะปะพััŒ ะทะฐะณั€ัƒะทะธั‚ัŒ ะฟั€ะธะปะพะถะตะฝะธั: %s" + +#: src/lib/workspace.sh:282 +#, sh-printf-format +msgid "Remote desktop of %s" +msgstr "ะฃะดะฐะปั‘ะฝะฝั‹ะน ั€ะฐะฑะพั‡ะธะน ัั‚ะพะป %s" + +#: src/lib/workspace.sh:284 +#, sh-printf-format +msgid "%s from %s" +msgstr "%s ะธะท %s" + +#: src/lib/menu.sh +msgid "Apps" +msgstr "ะŸั€ะธะปะพะถะตะฝะธั" + +#: src/lib/menu.sh +msgid "Share a folder" +msgstr "ะžะฑั‰ะฐั ะฟะฐะฟะบะฐ" + +#: src/lib/menu.sh +msgid "Open files with the apps" +msgstr "ะžั‚ะบั€ั‹ะฒะฐั‚ัŒ ั„ะฐะนะปั‹ ะฒ ะฟั€ะธะปะพะถะตะฝะธัั…" + +#: src/lib/menu.sh +msgid "Size" +msgstr "ะ ะฐะทะผะตั€" + +#: src/lib/menu.sh +msgid "Sound" +msgstr "ะ—ะฒัƒะบ" + +#: src/lib/menu.sh +msgid "Graphics" +msgstr "ะ“ั€ะฐั„ะธะบะฐ" + +#: src/lib/menu.sh +msgid "Signing in" +msgstr "ะ’ั…ะพะด" + +#: src/lib/menu.sh +msgid "Wait for the second MFA prompt" +msgstr "ะ–ะดะฐั‚ัŒ ะฒั‚ะพั€ะพะน ะทะฐะฟั€ะพั MFA" + +#: src/lib/menu.sh +msgid "Refresh the apps every day" +msgstr "ะžะฑะฝะพะฒะปัั‚ัŒ ะฟั€ะธะปะพะถะตะฝะธั ะบะฐะถะดั‹ะน ะดะตะฝัŒ" diff --git a/po/tr.po b/po/tr.po new file mode 100644 index 0000000..641ba55 --- /dev/null +++ b/po/tr.po @@ -0,0 +1,554 @@ +# Turkish translation for rdfeed. +# Copyright (C) 2026 Felitendo +# This file is distributed under the same license as rdfeed. +# +msgid "" +msgstr "" +"Project-Id-Version: rdfeed 1.0.0\n" +"Report-Msgid-Bugs-To: https://github.com/LoonixTools/rdfeed/issues\n" +"POT-Creation-Date: 2026-10-02 10:29+0200\n" +"PO-Revision-Date: 2026-10-02 09:30+0200\n" +"Last-Translator: Felitendo\n" +"Language-Team: Turkish\n" +"Language: tr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/rdfeed:79 src/rdfeed:103 +#, sh-printf-format +msgid "Could not write to %s." +msgstr "%s konumuna yazฤฑlamadฤฑ." + +#: src/rdfeed:82 +#, sh-printf-format +msgid "Signing in to %sโ€ฆ" +msgstr "%s รผzerinde oturum aรงฤฑlฤฑyorโ€ฆ" + +#: src/rdfeed:100 +msgid "" +"The password could not be saved in the keyring, so each app asks for it." +msgstr "" +"Parola anahtarlฤฑฤŸa kaydedilemedi, bu yรผzden her uygulama parolayฤฑ soracak." + +#: src/rdfeed:111 +#, sh-printf-format +msgid "1 app from %s is now in your app menu." +msgstr "%s kaynaฤŸฤฑndan 1 uygulama artฤฑk uygulama menรผnรผzde." + +#: src/rdfeed:113 +#, sh-printf-format +msgid "%d apps from %s are now in your app menu." +msgstr "%d uygulama (%s) artฤฑk uygulama menรผnรผzde." + +#: src/rdfeed:129 +msgid "" +"Your IT department has the address. A work e-mail address often works too." +msgstr "Adres BT bรถlรผmรผnรผzde. ร‡oฤŸu zaman iลŸ e-posta adresiniz de iลŸe yarar." + +#: src/rdfeed:130 +msgid "Feed address, server or e-mail:" +msgstr "AkฤฑลŸ adresi, sunucu ya da e-posta:" + +#: src/rdfeed:137 +#, sh-printf-format +msgid "No feed is registered for %s. Ask your IT department for the address." +msgstr "%s iรงin kayฤฑtlฤฑ bir akฤฑลŸ yok. Adresi BT bรถlรผmรผnรผze sorun." + +#: src/rdfeed:142 src/rdfeed:156 +msgid "User name (DOMAIN\\name or name@domain):" +msgstr "Kullanฤฑcฤฑ adฤฑ (ALAN\\ad ya da ad@alan):" + +#: src/rdfeed:146 src/rdfeed:159 +msgid "Password:" +msgstr "Parola:" + +#: src/rdfeed:178 src/rdfeed:236 +#, sh-printf-format +msgid "No workspace yet. Add one with `%s add`." +msgstr "Henรผz รงalฤฑลŸma alanฤฑ yok. `%s add` ile bir tane ekleyin." + +#: src/rdfeed:187 src/lib/launch.sh:220 +#, sh-printf-format +msgid "No password saved for %s. Sign in again in rdfeed." +msgstr "%s iรงin kayฤฑtlฤฑ parola yok. rdfeed iรงinde yeniden oturum aรงฤฑn." + +#: src/rdfeed:191 src/lib/launch.sh:149 +#, sh-printf-format +msgid "Password for %s:" +msgstr "%s parolasฤฑ:" + +#: src/rdfeed:195 +#, sh-printf-format +msgid "Refreshing %sโ€ฆ" +msgstr "%s yenileniyorโ€ฆ" + +#: src/rdfeed:205 +msgid "The password no longer works. Sign in again in rdfeed." +msgstr "Parola artฤฑk รงalฤฑลŸmฤฑyor. rdfeed iรงinde yeniden oturum aรงฤฑn." + +#: src/rdfeed:214 +#, sh-printf-format +msgid "%s: %d apps" +msgstr "%s: %d uygulama" + +#: src/rdfeed:222 +#, sh-printf-format +msgid "Which workspace? See `%s list` for the names." +msgstr "Hangi รงalฤฑลŸma alanฤฑ? Adlarฤฑ `%s list` gรถsterir." + +#: src/rdfeed:229 +#, sh-printf-format +msgid "%s and its apps are removed." +msgstr "%s ve uygulamalarฤฑ kaldฤฑrฤฑldฤฑ." + +#: src/rdfeed:253 +#, sh-printf-format +msgid "Which app? See `%s list`." +msgstr "Hangi uygulama? `%s list` komutuna bakฤฑn." + +#: src/rdfeed:268 src/lib/launch.sh:199 +#, sh-printf-format +msgid "There is no app %s. See `%s list`." +msgstr "%s diye bir uygulama yok. `%s list` komutuna bakฤฑn." + +#: src/rdfeed:270 +#, sh-printf-format +msgid "Several workspaces have %s. Write it as WORKSPACE/APP, see `%s list`." +msgstr "" +"%s birden รงok รงalฤฑลŸma alanฤฑnda var. ร‡ALIลžMA_ALANI/UYGULAMA biรงiminde yazฤฑn, " +"`%s list` komutuna bakฤฑn." + +#: src/rdfeed:278 +msgid "Usage: rdfeed [command]" +msgstr "Kullanฤฑm: rdfeed [komut]" + +#: src/rdfeed:280 +msgid "Commands:" +msgstr "Komutlar:" + +#: src/rdfeed:281 +msgid "Add the workspace of your company" +msgstr "ลžirketinizin รงalฤฑลŸma alanฤฑnฤฑ ekle" + +#: src/rdfeed:282 +msgid "Load the apps again" +msgstr "Uygulamalarฤฑ yeniden yรผkle" + +#: src/rdfeed:283 +msgid "List the workspaces and their apps" +msgstr "ร‡alฤฑลŸma alanlarฤฑnฤฑ ve uygulamalarฤฑnฤฑ listele" + +#: src/rdfeed:284 +msgid "Start an app, or open a file with it" +msgstr "Bir uygulama baลŸlat ya da onunla bir dosya aรง" + +#: src/rdfeed:285 +msgid "Remove a workspace and its apps" +msgstr "Bir รงalฤฑลŸma alanฤฑnฤฑ ve uygulamalarฤฑnฤฑ kaldฤฑr" + +#: src/rdfeed:286 +msgid "Show this help" +msgstr "Bu yardฤฑmฤฑ gรถster" + +#: src/rdfeed:287 +msgid "Show the version" +msgstr "Sรผrรผmรผ gรถster" + +#: src/rdfeed:289 +msgid "Without a command an interactive menu is shown." +msgstr "Komut verilmezse etkileลŸimli bir menรผ gรถsterilir." + +#: src/rdfeed:313 +#, sh-printf-format +msgid "Unknown command: %s" +msgstr "Bilinmeyen komut: %s" + +#: src/lib/common.sh:174 src/lib/menu.sh:251 +msgid "never" +msgstr "hiรงbir zaman" + +#: src/lib/common.sh:184 +msgid "just now" +msgstr "az รถnce" + +#: src/lib/common.sh:186 +msgid "1 minute ago" +msgstr "1 dakika รถnce" + +#: src/lib/common.sh:189 +#, sh-printf-format +msgid "%d minutes ago" +msgstr "%d dakika รถnce" + +#: src/lib/common.sh:191 +msgid "1 hour ago" +msgstr "1 saat รถnce" + +#: src/lib/common.sh:194 +#, sh-printf-format +msgid "%d hours ago" +msgstr "%d saat รถnce" + +#: src/lib/common.sh:196 +msgid "1 day ago" +msgstr "1 gรผn รถnce" + +#: src/lib/common.sh:199 +#, sh-printf-format +msgid "%d days ago" +msgstr "%d gรผn รถnce" + +#: src/lib/launch.sh:169 +msgid "" +"Your password has expired. Change it on Windows or on the RD Web page, then " +"sign in again in rdfeed." +msgstr "" +"Parolanฤฑzฤฑn sรผresi doldu. Windows'ta ya da RD Web sayfasฤฑnda deฤŸiลŸtirin, " +"sonra rdfeed iรงinde yeniden oturum aรงฤฑn." + +#: src/lib/launch.sh:171 +msgid "Your account is locked. Ask your IT department." +msgstr "Hesabฤฑnฤฑz kilitli. BT bรถlรผmรผnรผze sorun." + +#: src/lib/launch.sh:173 +msgid "The server did not accept the password. Sign in again in rdfeed." +msgstr "Sunucu parolayฤฑ kabul etmedi. rdfeed iรงinde yeniden oturum aรงฤฑn." + +#: src/lib/launch.sh:175 +msgid "Could not reach the server. Check the internet connection." +msgstr "Sunucuya ulaลŸฤฑlamadฤฑ. ฤฐnternet baฤŸlantฤฑsฤฑnฤฑ kontrol edin." + +#: src/lib/launch.sh:178 +#, sh-printf-format +msgid "FreeRDP stopped: %s" +msgstr "FreeRDP durdu: %s" + +#: src/lib/launch.sh:205 src/lib/launch.sh:206 +#, sh-printf-format +msgid "File not found: %s" +msgstr "Dosya bulunamadฤฑ: %s" + +#: src/lib/launch.sh:212 src/lib/launch.sh:213 +msgid "" +"FreeRDP 3 is missing. Install it to start the apps (the package is called " +"freerdp)." +msgstr "" +"FreeRDP 3 eksik. Uygulamalarฤฑ baลŸlatmak iรงin kurun (paketin adฤฑ freerdp)." + +#: src/lib/launch.sh:277 +#, sh-printf-format +msgid "Opening %s in %s" +msgstr "%s, %s ile aรงฤฑlฤฑyor" + +#: src/lib/launch.sh:278 +#, sh-printf-format +msgid "If it does not open by itself, open it in the app from the drive %s." +msgstr "KendiliฤŸinden aรงฤฑlmazsa uygulamada %s sรผrรผcรผsรผnden aรงฤฑn." + +#: src/lib/launch.sh:280 +#, sh-printf-format +msgid "Starting %s" +msgstr "%s baลŸlatฤฑlฤฑyor" + +#: src/lib/launch.sh:281 +msgid "" +"If your company uses MFA, confirm the sign-in in your authenticator app." +msgstr "" +"ลžirketiniz MFA kullanฤฑyorsa doฤŸrulama uygulamanฤฑzda oturum aรงmayฤฑ onaylayฤฑn." + +#: src/lib/launch.sh:290 +msgid "A second sign-in is coming" +msgstr "ฤฐkinci bir oturum aรงma geliyor" + +#: src/lib/launch.sh:291 +#, sh-printf-format +msgid "" +"Confirm the second prompt in your authenticator app too. It comes in about " +"%d seconds." +msgstr "" +"ฤฐkinci isteฤŸi de doฤŸrulama uygulamanฤฑzda onaylayฤฑn. YaklaลŸฤฑk %d saniye " +"iรงinde gelir." + +#: src/lib/launch.sh:301 +#, sh-printf-format +msgid "%s could not start" +msgstr "%s baลŸlatฤฑlamadฤฑ" + +#: src/lib/launch.sh:303 +#, sh-printf-format +msgid "Log: %s" +msgstr "Gรผnlรผk: %s" + +#. TRANSLATORS: the letter after "[" is the key for yes. y works too. +#: src/lib/menu.sh:93 +msgid "[y/N]" +msgstr "[e/H]" + +#: src/lib/menu.sh:163 +msgid "1 app" +msgstr "1 uygulama" + +#: src/lib/menu.sh:165 +#, sh-printf-format +msgid "%d apps" +msgstr "%d uygulama" + +#: src/lib/menu.sh:190 +msgid "No workspace yet. Add the one of your company with [1]." +msgstr "Henรผz รงalฤฑลŸma alanฤฑ yok. ลžirketinizinkini [1] ile ekleyin." + +#: src/lib/menu.sh:233 +msgid "off" +msgstr "kapalฤฑ" + +#: src/lib/menu.sh:234 +msgid "home folder" +msgstr "ev klasรถrรผ" + +#: src/lib/menu.sh:237 +msgid "Documents" +msgstr "Belgeler" + +#: src/lib/menu.sh:238 +msgid "Downloads" +msgstr "ฤฐndirilenler" + +#: src/lib/menu.sh:243 +msgid "not found" +msgstr "bulunamadฤฑ" + +#: src/lib/menu.sh:246 +#, sh-printf-format +msgid "automatic (%d %%)" +msgstr "otomatik (%%%d)" + +#: src/lib/menu.sh:248 +msgid "classic" +msgstr "klasik" + +#: src/lib/menu.sh:249 +msgid "GFX" +msgstr "GFX" + +#: src/lib/menu.sh:250 +msgid "always" +msgstr "her zaman" + +#: src/lib/menu.sh:252 +msgid "automatic" +msgstr "otomatik" + +#: src/lib/menu.sh:261 +msgid "" +"The apps only see the files on the server. Pick a folder to open and save " +"files of this computer in them." +msgstr "" +"Uygulamalar yalnฤฑzca sunucudaki dosyalarฤฑ gรถrรผr. Bu bilgisayarฤฑn dosyalarฤฑnฤฑ " +"aรงฤฑp kaydedebilmeleri iรงin bir klasรถr seรงin." + +#: src/lib/menu.sh:262 +msgid "" +"The apps can open and save files in this folder. It shows up as a drive in " +"them." +msgstr "" +"Uygulamalar bu klasรถrde dosya aรงฤฑp kaydedebilir. Klasรถr onlarda bir sรผrรผcรผ " +"olarak gรถrรผnรผr." + +#: src/lib/menu.sh:263 +msgid "" +"Offers the apps under \"Open with\" for their file types, for example Word " +"for .docx. The folder of the file is shared with the app while it is open." +msgstr "" +"Uygulamalarฤฑ dosya tรผrleri iรงin \"Birlikte aรง\" menรผsรผnde sunar, " +"รถrneฤŸin .docx iรงin Word. Dosya aรงฤฑk olduฤŸu sรผrece klasรถrรผ paylaลŸฤฑlฤฑr." + +#: src/lib/menu.sh:264 +msgid "" +"How big the apps are drawn. Automatic follows the scale of your desktop, so " +"they are sharp and the right size." +msgstr "" +"Uygulamalarฤฑn ne kadar bรผyรผk รงizileceฤŸi. Otomatik, masaรผstรผnรผzรผn รถlรงeฤŸini " +"izler; bรถylece keskin ve doฤŸru boyutta olurlar." + +#: src/lib/menu.sh:265 +msgid "Plays the sound of the apps here, for calls and videos." +msgstr "Uygulamalarฤฑn sesini burada รงalar; aramalar ve videolar iรงin." + +#: src/lib/menu.sh:266 +msgid "" +"Paints every window right. But some servers end the session right after " +"signing in, then go back to GFX." +msgstr "Her pencereyi doฤŸru รงizer. Ama bazฤฑ sunucular oturum aรงar aรงmaz oturumu kapatฤฑr, o zaman GFX'e geri dรถnรผn." + +#: src/lib/menu.sh:267 +msgid "" +"Works with every server. FreeRDP may not repaint a window after it was " +"minimized or resized." +msgstr "Her sunucuyla รงalฤฑลŸฤฑr. FreeRDP kรผรงรผltรผlen ya da boyutu deฤŸiลŸen bir pencereyi yeniden รงizmeyebilir." + +#: src/lib/menu.sh:268 +msgid "" +"Always waits before the second sign-in. For when the second prompt does not " +"come with automatic." +msgstr "" +"ฤฐkinci oturum aรงmadan รถnce hep bekler. Otomatikte ikinci istek gelmiyorsa " +"kullanฤฑn." + +#: src/lib/menu.sh:269 +msgid "Never waits. Right when your company has no MFA." +msgstr "Hiรง beklemez. ลžirketiniz MFA kullanmฤฑyorsa doฤŸru seรงim." + +#: src/lib/menu.sh:270 +msgid "" +"Many servers with MFA need a second sign-in, and its prompt only comes after " +"a short wait. Automatic waits when the first sign-in took a while." +msgstr "" +"MFA kullanan birรงok sunucu ikinci bir oturum aรงma ister ve isteฤŸi ancak kฤฑsa " +"bir beklemeden sonra gelir. Otomatik, ilk oturum aรงma biraz sรผrdรผyse bekler." + +#: src/lib/menu.sh:273 +msgid "" +"Loads the list of apps again once a day, so new apps show up by themselves." +msgstr "" +"Uygulama listesini gรผnde bir kez yeniden yรผkler; bรถylece yeni uygulamalar " +"kendiliฤŸinden gรถrรผnรผr." + +#: src/lib/menu.sh:275 src/lib/menu.sh:330 +msgid "Needs a systemd user session." +msgstr "Bir systemd kullanฤฑcฤฑ oturumu gerekir." + +#: src/lib/menu.sh:322 src/lib/menu.sh:325 +msgid "Could not save the setting." +msgstr "Ayar kaydedilemedi." + +#: src/lib/menu.sh:371 src/lib/menu.sh:609 +msgid "Settings" +msgstr "Ayarlar" + +#: src/lib/menu.sh:372 +msgid "โ†‘โ†“ select โ†โ†’ or Space: change q: back" +msgstr "โ†‘โ†“ seรง โ†โ†’ ya da BoลŸluk: deฤŸiลŸtir q: geri" + +#: src/lib/menu.sh:373 +msgid "ON" +msgstr "Aร‡IK" + +#: src/lib/menu.sh:374 +msgid "OFF" +msgstr "KAPALI" + +#: src/lib/menu.sh:456 src/lib/menu.sh:608 +msgid "Workspaces" +msgstr "ร‡alฤฑลŸma alanlarฤฑ" + +#. TRANSLATORS: keep the letters, they are the keys. +#: src/lib/menu.sh:458 +msgid "" +"โ†‘โ†“ select r: refresh s: sign in again d: remove a: add q: back" +msgstr "" +"โ†‘โ†“ seรง r: yenile s: yeniden oturum aรง d: kaldฤฑr a: ekle q: geri" + +#: src/lib/menu.sh:459 +msgid "No workspace yet. Press a to add one." +msgstr "Henรผz รงalฤฑลŸma alanฤฑ yok. Eklemek iรงin a tuลŸuna basฤฑn." + +#: src/lib/menu.sh:484 +#, sh-printf-format +msgid "refreshed %s" +msgstr "%s yenilendi" + +#: src/lib/menu.sh:512 +#, sh-printf-format +msgid "Remove \"%s\" and its apps?" +msgstr "\"%s\" ve uygulamalarฤฑ kaldฤฑrฤฑlsฤฑn mฤฑ?" + +#: src/lib/menu.sh:536 src/lib/menu.sh:607 +msgid "Start an app" +msgstr "Bir uygulama baลŸlat" + +#: src/lib/menu.sh:537 +msgid "โ†‘โ†“ select Enter: start q: back" +msgstr "โ†‘โ†“ seรง Enter: baลŸlat q: geri" + +#: src/lib/menu.sh:538 +msgid "No apps yet. Add a workspace first." +msgstr "Henรผz uygulama yok. ร–nce bir รงalฤฑลŸma alanฤฑ ekleyin." + +#: src/lib/menu.sh:579 +#, sh-printf-format +msgid "%s is starting. It opens in a window of its own." +msgstr "%s baลŸlatฤฑlฤฑyor. Kendi penceresinde aรงฤฑlฤฑr." + +#: src/lib/menu.sh:606 +msgid "Add a workspace" +msgstr "ร‡alฤฑลŸma alanฤฑ ekle" + +#: src/lib/menu.sh:610 +msgid "Quit" +msgstr "ร‡ฤฑk" + +#: src/lib/workspace.sh:163 +msgid "The server did not accept the user name or password." +msgstr "Sunucu kullanฤฑcฤฑ adฤฑnฤฑ ya da parolayฤฑ kabul etmedi." + +#: src/lib/workspace.sh:164 +#, sh-printf-format +msgid "Could not reach the server: %s" +msgstr "Sunucuya ulaลŸฤฑlamadฤฑ: %s" + +#: src/lib/workspace.sh:165 +#, sh-printf-format +msgid "There is no RD Web feed at %s." +msgstr "%s adresinde RD Web akฤฑลŸฤฑ yok." + +#: src/lib/workspace.sh:166 +#, sh-printf-format +msgid "Could not load the apps: %s" +msgstr "Uygulamalar yรผklenemedi: %s" + +#: src/lib/workspace.sh:282 +#, sh-printf-format +msgid "Remote desktop of %s" +msgstr "%s uzak masaรผstรผ" + +#: src/lib/workspace.sh:284 +#, sh-printf-format +msgid "%s from %s" +msgstr "%s (%s)" + +#: src/lib/menu.sh +msgid "Apps" +msgstr "Uygulamalar" + +#: src/lib/menu.sh +msgid "Share a folder" +msgstr "Klasรถr paylaลŸ" + +#: src/lib/menu.sh +msgid "Open files with the apps" +msgstr "Dosyalarฤฑ uygulamalarla aรง" + +#: src/lib/menu.sh +msgid "Size" +msgstr "Boyut" + +#: src/lib/menu.sh +msgid "Sound" +msgstr "Ses" + +#: src/lib/menu.sh +msgid "Graphics" +msgstr "Grafik" + +#: src/lib/menu.sh +msgid "Signing in" +msgstr "Oturum aรงma" + +#: src/lib/menu.sh +msgid "Wait for the second MFA prompt" +msgstr "ฤฐkinci MFA isteฤŸini bekle" + +#: src/lib/menu.sh +msgid "Refresh the apps every day" +msgstr "Uygulamalarฤฑ her gรผn yenile" diff --git a/po/uk.po b/po/uk.po new file mode 100644 index 0000000..28e2ab2 --- /dev/null +++ b/po/uk.po @@ -0,0 +1,559 @@ +# Ukrainian translation for rdfeed. +# Copyright (C) 2026 Felitendo +# This file is distributed under the same license as rdfeed. +# +msgid "" +msgstr "" +"Project-Id-Version: rdfeed 1.0.0\n" +"Report-Msgid-Bugs-To: https://github.com/LoonixTools/rdfeed/issues\n" +"POT-Creation-Date: 2026-10-02 10:29+0200\n" +"PO-Revision-Date: 2026-10-02 09:30+0200\n" +"Last-Translator: Felitendo\n" +"Language-Team: Ukrainian\n" +"Language: uk\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/rdfeed:79 src/rdfeed:103 +#, sh-printf-format +msgid "Could not write to %s." +msgstr "ะะต ะฒะดะฐะปะพัั ะทะฐะฟะธัะฐั‚ะธ ะฒ %s." + +#: src/rdfeed:82 +#, sh-printf-format +msgid "Signing in to %sโ€ฆ" +msgstr "ะ’ั…ั–ะด ะฝะฐ %sโ€ฆ" + +#: src/rdfeed:100 +msgid "" +"The password could not be saved in the keyring, so each app asks for it." +msgstr "" +"ะะต ะฒะดะฐะปะพัั ะทะฑะตั€ะตะณั‚ะธ ะฟะฐั€ะพะปัŒ ัƒ ะฒ'ัะทั†ั– ะบะปัŽั‡ั–ะฒ, ั‚ะพะถ ะบะพะถะตะฝ ะทะฐัั‚ะพััƒะฝะพะบ ะฟะธั‚ะฐั‚ะธะผะต " +"ะนะพะณะพ." + +#: src/rdfeed:111 +#, sh-printf-format +msgid "1 app from %s is now in your app menu." +msgstr "1 ะทะฐัั‚ะพััƒะฝะพะบ ั–ะท %s ั‚ะตะฟะตั€ ัƒ ะผะตะฝัŽ ะทะฐัั‚ะพััƒะฝะบั–ะฒ." + +#: src/rdfeed:113 +#, sh-printf-format +msgid "%d apps from %s are now in your app menu." +msgstr "ะ—ะฐัั‚ะพััƒะฝะบั–ะฒ: %d (ั–ะท %s). ะขะตะฟะตั€ ะฒะพะฝะธ ะฒ ะผะตะฝัŽ ะทะฐัั‚ะพััƒะฝะบั–ะฒ." + +#: src/rdfeed:129 +msgid "" +"Your IT department has the address. A work e-mail address often works too." +msgstr "" +"ะะดั€ะตััƒ ะผะฐั” ะฒะฐัˆ ะ†ะข-ะฒั–ะดะดั–ะป. ะงะฐัั‚ะพ ะฟั–ะดั…ะพะดะธั‚ัŒ ั– ั€ะพะฑะพั‡ะฐ ะฐะดั€ะตัะฐ ะตะปะตะบั‚ั€ะพะฝะฝะพั— ะฟะพัˆั‚ะธ." + +#: src/rdfeed:130 +msgid "Feed address, server or e-mail:" +msgstr "ะะดั€ะตัะฐ ั„ั–ะดะฐ, ัะตั€ะฒะตั€ ะฐะฑะพ e-mail:" + +#: src/rdfeed:137 +#, sh-printf-format +msgid "No feed is registered for %s. Ask your IT department for the address." +msgstr "ะ”ะปั %s ะฝะต ะทะฐั€ะตั”ัั‚ั€ะพะฒะฐะฝะพ ั„ั–ะด. ะ—ะฐะฟะธั‚ะฐะนั‚ะต ะฐะดั€ะตััƒ ะฒ ะ†ะข-ะฒั–ะดะดั–ะปั–." + +#: src/rdfeed:142 src/rdfeed:156 +msgid "User name (DOMAIN\\name or name@domain):" +msgstr "ะ†ะผ'ั ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ (ะ”ะžะœะ•ะ\\ั–ะผ'ั ะฐะฑะพ ั–ะผ'ั@ะดะพะผะตะฝ):" + +#: src/rdfeed:146 src/rdfeed:159 +msgid "Password:" +msgstr "ะŸะฐั€ะพะปัŒ:" + +#: src/rdfeed:178 src/rdfeed:236 +#, sh-printf-format +msgid "No workspace yet. Add one with `%s add`." +msgstr "ะ ะพะฑะพั‡ะธั… ะฟั€ะพัั‚ะพั€ั–ะฒ ั‰ะต ะฝะตะผะฐั”. ะ”ะพะดะฐะนั‚ะต ะนะพะณะพ ะบะพะผะฐะฝะดะพัŽ `%s add`." + +#: src/rdfeed:187 src/lib/launch.sh:220 +#, sh-printf-format +msgid "No password saved for %s. Sign in again in rdfeed." +msgstr "ะ”ะปั %s ะฝะต ะทะฑะตั€ะตะถะตะฝะพ ะฟะฐั€ะพะปัŒ. ะฃะฒั–ะนะดั–ั‚ัŒ ะทะฝะพะฒัƒ ะฒ rdfeed." + +#: src/rdfeed:191 src/lib/launch.sh:149 +#, sh-printf-format +msgid "Password for %s:" +msgstr "ะŸะฐั€ะพะปัŒ ะดะปั %s:" + +#: src/rdfeed:195 +#, sh-printf-format +msgid "Refreshing %sโ€ฆ" +msgstr "ะžะฝะพะฒะปะตะฝะฝั %sโ€ฆ" + +#: src/rdfeed:205 +msgid "The password no longer works. Sign in again in rdfeed." +msgstr "ะŸะฐั€ะพะปัŒ ะฑั–ะปัŒัˆะต ะฝะต ะฟั–ะดั…ะพะดะธั‚ัŒ. ะฃะฒั–ะนะดั–ั‚ัŒ ะทะฝะพะฒัƒ ะฒ rdfeed." + +#: src/rdfeed:214 +#, sh-printf-format +msgid "%s: %d apps" +msgstr "%s: ะทะฐัั‚ะพััƒะฝะบั–ะฒ: %d" + +#: src/rdfeed:222 +#, sh-printf-format +msgid "Which workspace? See `%s list` for the names." +msgstr "ะฏะบะธะน ั€ะพะฑะพั‡ะธะน ะฟั€ะพัั‚ั–ั€? ะะฐะทะฒะธ ะฟะพะบะฐะทัƒั” `%s list`." + +#: src/rdfeed:229 +#, sh-printf-format +msgid "%s and its apps are removed." +msgstr "ะ’ะธะดะฐะปะตะฝะพ: %s ั€ะฐะทะพะผ ั–ะท ะทะฐัั‚ะพััƒะฝะบะฐะผะธ." + +#: src/rdfeed:253 +#, sh-printf-format +msgid "Which app? See `%s list`." +msgstr "ะฏะบะธะน ะทะฐัั‚ะพััƒะฝะพะบ? ะ”ะธะฒั–ั‚ัŒัั `%s list`." + +#: src/rdfeed:268 src/lib/launch.sh:199 +#, sh-printf-format +msgid "There is no app %s. See `%s list`." +msgstr "ะ—ะฐัั‚ะพััƒะฝะบัƒ %s ะฝะตะผะฐั”. ะ”ะธะฒั–ั‚ัŒัั `%s list`." + +#: src/rdfeed:270 +#, sh-printf-format +msgid "Several workspaces have %s. Write it as WORKSPACE/APP, see `%s list`." +msgstr "" +"%s ั” ะฒ ะบั–ะปัŒะบะพั… ั€ะพะฑะพั‡ะธั… ะฟั€ะพัั‚ะพั€ะฐั…. ะ’ะบะฐะถั–ั‚ัŒ ัƒ ะฒะธะณะปัะดั– ะŸะ ะžะกะขะ†ะ /ะ—ะะกะขะžะกะฃะะžะš, " +"ะดะธะฒั–ั‚ัŒัั `%s list`." + +#: src/rdfeed:278 +msgid "Usage: rdfeed [command]" +msgstr "ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั: rdfeed [ะบะพะผะฐะฝะดะฐ]" + +#: src/rdfeed:280 +msgid "Commands:" +msgstr "ะšะพะผะฐะฝะดะธ:" + +#: src/rdfeed:281 +msgid "Add the workspace of your company" +msgstr "ะ”ะพะดะฐั‚ะธ ั€ะพะฑะพั‡ะธะน ะฟั€ะพัั‚ั–ั€ ะฒะฐัˆะพั— ะบะพะผะฟะฐะฝั–ั—" + +#: src/rdfeed:282 +msgid "Load the apps again" +msgstr "ะ—ะฐะฒะฐะฝั‚ะฐะถะธั‚ะธ ะทะฐัั‚ะพััƒะฝะบะธ ะทะฝะพะฒัƒ" + +#: src/rdfeed:283 +msgid "List the workspaces and their apps" +msgstr "ะŸะพะบะฐะทะฐั‚ะธ ั€ะพะฑะพั‡ั– ะฟั€ะพัั‚ะพั€ะธ ั‚ะฐ ั—ั…ะฝั– ะทะฐัั‚ะพััƒะฝะบะธ" + +#: src/rdfeed:284 +msgid "Start an app, or open a file with it" +msgstr "ะ—ะฐะฟัƒัั‚ะธั‚ะธ ะทะฐัั‚ะพััƒะฝะพะบ ะฐะฑะพ ะฒั–ะดะบั€ะธั‚ะธ ะฒ ะฝัŒะพะผัƒ ั„ะฐะนะป" + +#: src/rdfeed:285 +msgid "Remove a workspace and its apps" +msgstr "ะ’ะธะดะฐะปะธั‚ะธ ั€ะพะฑะพั‡ะธะน ะฟั€ะพัั‚ั–ั€ ั– ะนะพะณะพ ะทะฐัั‚ะพััƒะฝะบะธ" + +#: src/rdfeed:286 +msgid "Show this help" +msgstr "ะŸะพะบะฐะทะฐั‚ะธ ั†ัŽ ะดะพะฒั–ะดะบัƒ" + +#: src/rdfeed:287 +msgid "Show the version" +msgstr "ะŸะพะบะฐะทะฐั‚ะธ ะฒะตั€ัั–ัŽ" + +#: src/rdfeed:289 +msgid "Without a command an interactive menu is shown." +msgstr "ะ‘ะตะท ะบะพะผะฐะฝะดะธ ะฒั–ะดะบั€ะธะฒะฐั”ั‚ัŒัั ั–ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝะต ะผะตะฝัŽ." + +#: src/rdfeed:313 +#, sh-printf-format +msgid "Unknown command: %s" +msgstr "ะะตะฒั–ะดะพะผะฐ ะบะพะผะฐะฝะดะฐ: %s" + +#: src/lib/common.sh:174 src/lib/menu.sh:251 +msgid "never" +msgstr "ะฝั–ะบะพะปะธ" + +#: src/lib/common.sh:184 +msgid "just now" +msgstr "ั‰ะพะนะฝะพ" + +#: src/lib/common.sh:186 +msgid "1 minute ago" +msgstr "1 ั…ะฒะธะปะธะฝัƒ ั‚ะพะผัƒ" + +#: src/lib/common.sh:189 +#, sh-printf-format +msgid "%d minutes ago" +msgstr "%d ั…ะฒ ั‚ะพะผัƒ" + +#: src/lib/common.sh:191 +msgid "1 hour ago" +msgstr "1 ะณะพะดะธะฝัƒ ั‚ะพะผัƒ" + +#: src/lib/common.sh:194 +#, sh-printf-format +msgid "%d hours ago" +msgstr "%d ะณะพะด ั‚ะพะผัƒ" + +#: src/lib/common.sh:196 +msgid "1 day ago" +msgstr "1 ะดะตะฝัŒ ั‚ะพะผัƒ" + +#: src/lib/common.sh:199 +#, sh-printf-format +msgid "%d days ago" +msgstr "%d ะดะฝ. ั‚ะพะผัƒ" + +#: src/lib/launch.sh:169 +msgid "" +"Your password has expired. Change it on Windows or on the RD Web page, then " +"sign in again in rdfeed." +msgstr "" +"ะขะตั€ะผั–ะฝ ะดั–ั— ะฟะฐั€ะพะปั ะผะธะฝัƒะฒ. ะ—ะผั–ะฝั–ั‚ัŒ ะนะพะณะพ ัƒ Windows ะฐะฑะพ ะฝะฐ ัั‚ะพั€ั–ะฝั†ั– RD Web, " +"ะฟะพั‚ั–ะผ ะทะฝะพะฒัƒ ัƒะฒั–ะนะดั–ั‚ัŒ ัƒ rdfeed." + +#: src/lib/launch.sh:171 +msgid "Your account is locked. Ask your IT department." +msgstr "ะžะฑะปั–ะบะพะฒะธะน ะทะฐะฟะธั ะทะฐะฑะปะพะบะพะฒะฐะฝะพ. ะ—ะฒะตั€ะฝั–ั‚ัŒัั ะดะพ ะ†ะข-ะฒั–ะดะดั–ะปัƒ." + +#: src/lib/launch.sh:173 +msgid "The server did not accept the password. Sign in again in rdfeed." +msgstr "ะกะตั€ะฒะตั€ ะฝะต ะฟั€ะธะนะฝัะฒ ะฟะฐั€ะพะปัŒ. ะฃะฒั–ะนะดั–ั‚ัŒ ะทะฝะพะฒัƒ ะฒ rdfeed." + +#: src/lib/launch.sh:175 +msgid "Could not reach the server. Check the internet connection." +msgstr "ะะต ะฒะดะฐะปะพัั ะทะฒ'ัะทะฐั‚ะธัั ั–ะท ัะตั€ะฒะตั€ะพะผ. ะŸะตั€ะตะฒั–ั€ั‚ะต ะฟั–ะดะบะปัŽั‡ะตะฝะฝั ะดะพ ั–ะฝั‚ะตั€ะฝะตั‚ัƒ." + +#: src/lib/launch.sh:178 +#, sh-printf-format +msgid "FreeRDP stopped: %s" +msgstr "FreeRDP ะทัƒะฟะธะฝะธะฒัั: %s" + +#: src/lib/launch.sh:205 src/lib/launch.sh:206 +#, sh-printf-format +msgid "File not found: %s" +msgstr "ะคะฐะนะป ะฝะต ะทะฝะฐะนะดะตะฝะพ: %s" + +#: src/lib/launch.sh:212 src/lib/launch.sh:213 +msgid "" +"FreeRDP 3 is missing. Install it to start the apps (the package is called " +"freerdp)." +msgstr "" +"ะะตะผะฐั” FreeRDP 3. ะฃัั‚ะฐะฝะพะฒั–ั‚ัŒ ะนะพะณะพ, ั‰ะพะฑ ะทะฐะฟัƒัะบะฐั‚ะธ ะทะฐัั‚ะพััƒะฝะบะธ (ะฟะฐะบะตั‚ " +"ะฝะฐะทะธะฒะฐั”ั‚ัŒัั freerdp)." + +#: src/lib/launch.sh:277 +#, sh-printf-format +msgid "Opening %s in %s" +msgstr "ะ’ั–ะดะบั€ะธั‚ั‚ั %s ัƒ %s" + +#: src/lib/launch.sh:278 +#, sh-printf-format +msgid "If it does not open by itself, open it in the app from the drive %s." +msgstr "ะฏะบั‰ะพ ั„ะฐะนะป ะฝะต ะฒั–ะดะบั€ะธั”ั‚ัŒัั ัะฐะผ, ะฒั–ะดะบั€ะธะนั‚ะต ะนะพะณะพ ะฒ ะทะฐัั‚ะพััƒะฝะบัƒ ะท ะดะธัะบะฐ %s." + +#: src/lib/launch.sh:280 +#, sh-printf-format +msgid "Starting %s" +msgstr "ะ—ะฐะฟัƒัะบ: %s" + +#: src/lib/launch.sh:281 +msgid "" +"If your company uses MFA, confirm the sign-in in your authenticator app." +msgstr "" +"ะฏะบั‰ะพ ะฒะฐัˆะฐ ะบะพะผะฟะฐะฝั–ั ะฒะธะบะพั€ะธัั‚ะพะฒัƒั” MFA, ะฟั–ะดั‚ะฒะตั€ะดัŒั‚ะต ะฒั…ั–ะด ัƒ ะทะฐัั‚ะพััƒะฝะบัƒ " +"ะฐะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ั—." + +#: src/lib/launch.sh:290 +msgid "A second sign-in is coming" +msgstr "ะ‘ัƒะดะต ะดั€ัƒะณะธะน ะฒั…ั–ะด" + +#: src/lib/launch.sh:291 +#, sh-printf-format +msgid "" +"Confirm the second prompt in your authenticator app too. It comes in about " +"%d seconds." +msgstr "" +"ะŸั–ะดั‚ะฒะตั€ะดัŒั‚ะต ั– ะดั€ัƒะณะธะน ะทะฐะฟะธั‚ ัƒ ะทะฐัั‚ะพััƒะฝะบัƒ ะฐะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ั—. ะ’ั–ะฝ ะฝะฐะดั–ะนะดะต " +"ะฟั€ะธะฑะปะธะทะฝะพ ะทะฐ %d ั." + +#: src/lib/launch.sh:301 +#, sh-printf-format +msgid "%s could not start" +msgstr "ะะต ะฒะดะฐะปะพัั ะทะฐะฟัƒัั‚ะธั‚ะธ: %s" + +#: src/lib/launch.sh:303 +#, sh-printf-format +msgid "Log: %s" +msgstr "ะ–ัƒั€ะฝะฐะป: %s" + +#. TRANSLATORS: the letter after "[" is the key for yes. y works too. +#: src/lib/menu.sh:93 +msgid "[y/N]" +msgstr "[ั‚/ะ]" + +#: src/lib/menu.sh:163 +msgid "1 app" +msgstr "1 ะทะฐัั‚ะพััƒะฝะพะบ" + +#: src/lib/menu.sh:165 +#, sh-printf-format +msgid "%d apps" +msgstr "ะทะฐัั‚ะพััƒะฝะบั–ะฒ: %d" + +#: src/lib/menu.sh:190 +msgid "No workspace yet. Add the one of your company with [1]." +msgstr "ะ ะพะฑะพั‡ะธั… ะฟั€ะพัั‚ะพั€ั–ะฒ ั‰ะต ะฝะตะผะฐั”. ะ”ะพะดะฐะนั‚ะต ะฟั€ะพัั‚ั–ั€ ะฒะฐัˆะพั— ะบะพะผะฟะฐะฝั–ั— ั‡ะตั€ะตะท [1]." + +#: src/lib/menu.sh:233 +msgid "off" +msgstr "ะฒะธะผะบ." + +#: src/lib/menu.sh:234 +msgid "home folder" +msgstr "ะดะพะผะฐัˆะฝั ั‚ะตะบะฐ" + +#: src/lib/menu.sh:237 +msgid "Documents" +msgstr "ะ”ะพะบัƒะผะตะฝั‚ะธ" + +#: src/lib/menu.sh:238 +msgid "Downloads" +msgstr "ะ—ะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั" + +#: src/lib/menu.sh:243 +msgid "not found" +msgstr "ะฝะต ะทะฝะฐะนะดะตะฝะพ" + +#: src/lib/menu.sh:246 +#, sh-printf-format +msgid "automatic (%d %%)" +msgstr "ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ (%d %%)" + +#: src/lib/menu.sh:248 +msgid "classic" +msgstr "ะบะปะฐัะธั‡ะฝะธะน" + +#: src/lib/menu.sh:249 +msgid "GFX" +msgstr "GFX" + +#: src/lib/menu.sh:250 +msgid "always" +msgstr "ะทะฐะฒะถะดะธ" + +#: src/lib/menu.sh:252 +msgid "automatic" +msgstr "ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ" + +#: src/lib/menu.sh:261 +msgid "" +"The apps only see the files on the server. Pick a folder to open and save " +"files of this computer in them." +msgstr "" +"ะ—ะฐัั‚ะพััƒะฝะบะธ ะฑะฐั‡ะฐั‚ัŒ ะปะธัˆะต ั„ะฐะนะปะธ ะฝะฐ ัะตั€ะฒะตั€ั–. ะ’ะธะฑะตั€ั–ั‚ัŒ ั‚ะตะบัƒ, ั‰ะพะฑ ะฒะพะฝะธ ะผะพะณะปะธ " +"ะฒั–ะดะบั€ะธะฒะฐั‚ะธ ะน ะทะฑะตั€ั–ะณะฐั‚ะธ ั„ะฐะนะปะธ ั†ัŒะพะณะพ ะบะพะผะฟ'ัŽั‚ะตั€ะฐ." + +#: src/lib/menu.sh:262 +msgid "" +"The apps can open and save files in this folder. It shows up as a drive in " +"them." +msgstr "" +"ะ—ะฐัั‚ะพััƒะฝะบะธ ะผะพะถัƒั‚ัŒ ะฒั–ะดะบั€ะธะฒะฐั‚ะธ ะน ะทะฑะตั€ั–ะณะฐั‚ะธ ั„ะฐะนะปะธ ะฒ ั†ั–ะน ั‚ะตั†ั–. ะฃ ะฝะธั… ะฒะพะฝะฐ ะฒะธะดะฝะฐ " +"ัะบ ะดะธัะบ." + +#: src/lib/menu.sh:263 +msgid "" +"Offers the apps under \"Open with\" for their file types, for example Word " +"for .docx. The folder of the file is shared with the app while it is open." +msgstr "" +"ะŸั€ะพะฟะพะฝัƒั” ะทะฐัั‚ะพััƒะฝะบะธ ะฒ ยซะ’ั–ะดะบั€ะธั‚ะธ ะทะฐ ะดะพะฟะพะผะพะณะพัŽยป ะดะปั ั—ั…ะฝั–ั… ั‚ะธะฟั–ะฒ ั„ะฐะนะปั–ะฒ, " +"ะฝะฐะฟั€ะธะบะปะฐะด Word ะดะปั .docx. ะขะตะบะฐ ั„ะฐะนะปัƒ ะฒั–ะดะบั€ะธั‚ะฐ ะดะปั ัะตั€ะฒะตั€ะฐ, ะดะพะบะธ ั„ะฐะนะป " +"ะฒั–ะดะบั€ะธั‚ะธะน." + +#: src/lib/menu.sh:264 +msgid "" +"How big the apps are drawn. Automatic follows the scale of your desktop, so " +"they are sharp and the right size." +msgstr "" +"ะะฐัะบั–ะปัŒะบะธ ะฒะตะปะธะบะธะผะธ ะผะฐะปัŽัŽั‚ัŒัั ะทะฐัั‚ะพััƒะฝะบะธ. ยซะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพยป ัั‚ะตะถะธั‚ัŒ ะทะฐ ะผะฐััˆั‚ะฐะฑะพะผ " +"ัั‚ั–ะปัŒะฝะธั†ั–, ั‚ะพะถ ะฒะพะฝะธ ั‡ั–ั‚ะบั– ะน ะฟะพั‚ั€ั–ะฑะฝะพะณะพ ั€ะพะทะผั–ั€ัƒ." + +#: src/lib/menu.sh:265 +msgid "Plays the sound of the apps here, for calls and videos." +msgstr "ะ’ั–ะดั‚ะฒะพั€ัŽั” ะทะฒัƒะบ ะทะฐัั‚ะพััƒะฝะบั–ะฒ ั‚ัƒั‚, ะดะปั ะดะทะฒั–ะฝะบั–ะฒ ั– ะฒั–ะดะตะพ." + +#: src/lib/menu.sh:266 +msgid "" +"Paints every window right. But some servers end the session right after " +"signing in, then go back to GFX." +msgstr "ะŸั€ะฐะฒะธะปัŒะฝะพ ะผะฐะปัŽั” ะบะพะถะฝะต ะฒั–ะบะฝะพ. ะะปะต ะดะตัะบั– ัะตั€ะฒะตั€ะธ ะพะฑั€ะธะฒะฐัŽั‚ัŒ ัะตะฐะฝั ะพะดั€ะฐะทัƒ ะฟั–ัะปั ะฒั…ะพะดัƒ, ั‚ะพะดั– ะฟะพะฒะตั€ะฝั–ั‚ัŒัั ะดะพ GFX." + +#: src/lib/menu.sh:267 +msgid "" +"Works with every server. FreeRDP may not repaint a window after it was " +"minimized or resized." +msgstr "ะŸั€ะฐั†ัŽั” ะท ะฑัƒะดัŒ-ัะบะธะผ ัะตั€ะฒะตั€ะพะผ. FreeRDP ะผะพะถะต ะฝะต ะฟะตั€ะตะผะฐะปัŽะฒะฐั‚ะธ ะฒั–ะบะฝะพ ะฟั–ัะปั ะทะณะพั€ั‚ะฐะฝะฝั ั‡ะธ ะทะผั–ะฝะธ ั€ะพะทะผั–ั€ัƒ." + +#: src/lib/menu.sh:268 +msgid "" +"Always waits before the second sign-in. For when the second prompt does not " +"come with automatic." +msgstr "" +"ะ—ะฐะฒะถะดะธ ั‡ะตะบะฐั” ะฟะตั€ะตะด ะดั€ัƒะณะธะผ ะฒั…ะพะดะพะผ. ะฏะบั‰ะพ ะท ยซะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพยป ะดั€ัƒะณะธะน ะทะฐะฟะธั‚ ะฝะต " +"ะฝะฐะดั…ะพะดะธั‚ัŒ." + +#: src/lib/menu.sh:269 +msgid "Never waits. Right when your company has no MFA." +msgstr "ะั–ะบะพะปะธ ะฝะต ั‡ะตะบะฐั”. ะŸั–ะดั…ะพะดะธั‚ัŒ, ัะบั‰ะพ ะฒะฐัˆะฐ ะบะพะผะฟะฐะฝั–ั ะฝะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒั” MFA." + +#: src/lib/menu.sh:270 +msgid "" +"Many servers with MFA need a second sign-in, and its prompt only comes after " +"a short wait. Automatic waits when the first sign-in took a while." +msgstr "" +"ะ‘ะฐะณะฐั‚ัŒะพะผ ัะตั€ะฒะตั€ะฐะผ ะท MFA ะฟะพั‚ั€ั–ะฑะตะฝ ะดั€ัƒะณะธะน ะฒั…ั–ะด, ั– ะนะพะณะพ ะทะฐะฟะธั‚ ะฝะฐะดั…ะพะดะธั‚ัŒ ะปะธัˆะต " +"ะฟั–ัะปั ะบะพั€ะพั‚ะบะพั— ะฟะฐัƒะทะธ. ยซะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพยป ั‡ะตะบะฐั”, ัะบั‰ะพ ะฟะตั€ัˆะธะน ะฒั…ั–ะด ั‚ั€ะธะฒะฐะฒ ัะบะธะนััŒ " +"ั‡ะฐั." + +#: src/lib/menu.sh:273 +msgid "" +"Loads the list of apps again once a day, so new apps show up by themselves." +msgstr "" +"ะ ะฐะท ะฝะฐ ะดะตะฝัŒ ะทะฐะฝะพะฒะพ ะทะฐะฒะฐะฝั‚ะฐะถัƒั” ัะฟะธัะพะบ ะทะฐัั‚ะพััƒะฝะบั–ะฒ, ั‚ะพะถ ะฝะพะฒั– ะท'ัะฒะปััŽั‚ัŒัั ัะฐะผั–." + +#: src/lib/menu.sh:275 src/lib/menu.sh:330 +msgid "Needs a systemd user session." +msgstr "ะŸะพั‚ั€ั–ะฑะตะฝ ะบะพั€ะธัั‚ัƒะฒะฐั†ัŒะบะธะน ัะตะฐะฝั systemd." + +#: src/lib/menu.sh:322 src/lib/menu.sh:325 +msgid "Could not save the setting." +msgstr "ะะต ะฒะดะฐะปะพัั ะทะฑะตั€ะตะณั‚ะธ ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั." + +#: src/lib/menu.sh:371 src/lib/menu.sh:609 +msgid "Settings" +msgstr "ะะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั" + +#: src/lib/menu.sh:372 +msgid "โ†‘โ†“ select โ†โ†’ or Space: change q: back" +msgstr "โ†‘โ†“ ะฒะธะฑั–ั€ โ†โ†’ ะฐะฑะพ ะŸั€ะพะฑั–ะป: ะทะผั–ะฝะธั‚ะธ q: ะฝะฐะทะฐะด" + +#: src/lib/menu.sh:373 +msgid "ON" +msgstr "ะฃะ’ะ†ะœะš" + +#: src/lib/menu.sh:374 +msgid "OFF" +msgstr "ะ’ะ˜ะœะš" + +#: src/lib/menu.sh:456 src/lib/menu.sh:608 +msgid "Workspaces" +msgstr "ะ ะพะฑะพั‡ั– ะฟั€ะพัั‚ะพั€ะธ" + +#. TRANSLATORS: keep the letters, they are the keys. +#: src/lib/menu.sh:458 +msgid "" +"โ†‘โ†“ select r: refresh s: sign in again d: remove a: add q: back" +msgstr "" +"โ†‘โ†“ ะฒะธะฑั–ั€ r: ะพะฝะพะฒะธั‚ะธ s: ัƒะฒั–ะนั‚ะธ ะทะฝะพะฒัƒ d: ะฒะธะดะฐะปะธั‚ะธ a: ะดะพะดะฐั‚ะธ q: ะฝะฐะทะฐะด" + +#: src/lib/menu.sh:459 +msgid "No workspace yet. Press a to add one." +msgstr "ะ ะพะฑะพั‡ะธั… ะฟั€ะพัั‚ะพั€ั–ะฒ ั‰ะต ะฝะตะผะฐั”. ะะฐั‚ะธัะฝั–ั‚ัŒ a, ั‰ะพะฑ ะดะพะดะฐั‚ะธ." + +#: src/lib/menu.sh:484 +#, sh-printf-format +msgid "refreshed %s" +msgstr "ะพะฝะพะฒะปะตะฝะพ %s" + +#: src/lib/menu.sh:512 +#, sh-printf-format +msgid "Remove \"%s\" and its apps?" +msgstr "ะ’ะธะดะฐะปะธั‚ะธ ยซ%sยป ั€ะฐะทะพะผ ั–ะท ะทะฐัั‚ะพััƒะฝะบะฐะผะธ?" + +#: src/lib/menu.sh:536 src/lib/menu.sh:607 +msgid "Start an app" +msgstr "ะ—ะฐะฟัƒัั‚ะธั‚ะธ ะทะฐัั‚ะพััƒะฝะพะบ" + +#: src/lib/menu.sh:537 +msgid "โ†‘โ†“ select Enter: start q: back" +msgstr "โ†‘โ†“ ะฒะธะฑั–ั€ Enter: ะทะฐะฟัƒัั‚ะธั‚ะธ q: ะฝะฐะทะฐะด" + +#: src/lib/menu.sh:538 +msgid "No apps yet. Add a workspace first." +msgstr "ะ—ะฐัั‚ะพััƒะฝะบั–ะฒ ั‰ะต ะฝะตะผะฐั”. ะกะฟะตั€ัˆัƒ ะดะพะดะฐะนั‚ะต ั€ะพะฑะพั‡ะธะน ะฟั€ะพัั‚ั–ั€." + +#: src/lib/menu.sh:579 +#, sh-printf-format +msgid "%s is starting. It opens in a window of its own." +msgstr "ะ—ะฐะฟัƒัะบ: %s. ะ’ั–ะดะบั€ะธั”ั‚ัŒัั ะฒ ะพะบั€ะตะผะพะผัƒ ะฒั–ะบะฝั–." + +#: src/lib/menu.sh:606 +msgid "Add a workspace" +msgstr "ะ”ะพะดะฐั‚ะธ ั€ะพะฑะพั‡ะธะน ะฟั€ะพัั‚ั–ั€" + +#: src/lib/menu.sh:610 +msgid "Quit" +msgstr "ะ’ะธะนั‚ะธ" + +#: src/lib/workspace.sh:163 +msgid "The server did not accept the user name or password." +msgstr "ะกะตั€ะฒะตั€ ะฝะต ะฟั€ะธะนะฝัะฒ ั–ะผ'ั ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ ะฐะฑะพ ะฟะฐั€ะพะปัŒ." + +#: src/lib/workspace.sh:164 +#, sh-printf-format +msgid "Could not reach the server: %s" +msgstr "ะะต ะฒะดะฐะปะพัั ะทะฒ'ัะทะฐั‚ะธัั ั–ะท ัะตั€ะฒะตั€ะพะผ: %s" + +#: src/lib/workspace.sh:165 +#, sh-printf-format +msgid "There is no RD Web feed at %s." +msgstr "ะ—ะฐ ะฐะดั€ะตัะพัŽ %s ะฝะตะผะฐั” ั„ั–ะดะฐ RD Web." + +#: src/lib/workspace.sh:166 +#, sh-printf-format +msgid "Could not load the apps: %s" +msgstr "ะะต ะฒะดะฐะปะพัั ะทะฐะฒะฐะฝั‚ะฐะถะธั‚ะธ ะทะฐัั‚ะพััƒะฝะบะธ: %s" + +#: src/lib/workspace.sh:282 +#, sh-printf-format +msgid "Remote desktop of %s" +msgstr "ะ’ั–ะดะดะฐะปะตะฝะฐ ัั‚ั–ะปัŒะฝะธั†ั %s" + +#: src/lib/workspace.sh:284 +#, sh-printf-format +msgid "%s from %s" +msgstr "%s ั–ะท %s" + +#: src/lib/menu.sh +msgid "Apps" +msgstr "ะ—ะฐัั‚ะพััƒะฝะบะธ" + +#: src/lib/menu.sh +msgid "Share a folder" +msgstr "ะกะฟั–ะปัŒะฝะฐ ั‚ะตะบะฐ" + +#: src/lib/menu.sh +msgid "Open files with the apps" +msgstr "ะ’ั–ะดะบั€ะธะฒะฐั‚ะธ ั„ะฐะนะปะธ ะฒ ะทะฐัั‚ะพััƒะฝะบะฐั…" + +#: src/lib/menu.sh +msgid "Size" +msgstr "ะ ะพะทะผั–ั€" + +#: src/lib/menu.sh +msgid "Sound" +msgstr "ะ—ะฒัƒะบ" + +#: src/lib/menu.sh +msgid "Graphics" +msgstr "ะ“ั€ะฐั„ั–ะบะฐ" + +#: src/lib/menu.sh +msgid "Signing in" +msgstr "ะ’ั…ั–ะด" + +#: src/lib/menu.sh +msgid "Wait for the second MFA prompt" +msgstr "ะงะตะบะฐั‚ะธ ะฝะฐ ะดั€ัƒะณะธะน ะทะฐะฟะธั‚ MFA" + +#: src/lib/menu.sh +msgid "Refresh the apps every day" +msgstr "ะžะฝะพะฒะปัŽะฒะฐั‚ะธ ะทะฐัั‚ะพััƒะฝะบะธ ั‰ะพะดะฝั" diff --git a/po/update-pot.sh b/po/update-pot.sh new file mode 100755 index 0000000..fccd1b1 --- /dev/null +++ b/po/update-pot.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# +# Collects every translatable string into po/rdfeed.pot and brings the +# translations up to date with it. +# +# The settings labels and headings live in an array in menu.sh and reach +# gettext at run time, so xgettext cannot see them; they are pulled out here +# first. + +set -euo pipefail + +cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." +tmp="$(mktemp -d)" +trap 'rm -rf -- "$tmp"' EXIT + +version="$(make -s version)" + +# The labels, as calls xgettext understands, pointing back at menu.sh. +awk -F'|' '/^\t"(user|entries|timer)\|/ { sub(/"$/, "", $5); print "rf_msg \"" $5 "\"" } + /^\t"group\|/ { sub(/"$/, "", $2); print "rf_msg \"" $2 "\"" }' src/lib/menu.sh > "$tmp/settings.sh" + +xgettext --from-code=UTF-8 --add-comments=TRANSLATORS --package-name=rdfeed --package-version="$version" \ + --msgid-bugs-address=https://github.com/LoonixTools/rdfeed/issues \ + -L Shell -k --keyword=rf_msg --keyword=rf_msg_into:2 --keyword=rf_msg_in:2 \ + -o po/rdfeed.pot src/rdfeed src/lib/*.sh "$tmp/settings.sh" +sed -i "s|$tmp/settings.sh:[0-9]*|src/lib/menu.sh|g" po/rdfeed.pot +sed -i -e '1i # Translation template for rdfeed.\n# Copyright (C) 2026 Felitendo\n# This file is distributed under the same license as rdfeed.' -e '1,4d' \ + po/rdfeed.pot + +for po in po/*.po; do + [[ -e $po ]] || continue + msgmerge --quiet --update --backup=none --no-fuzzy-matching "$po" po/rdfeed.pot +done +echo "po/rdfeed.pot: $(grep -c '^msgid' po/rdfeed.pot) strings" diff --git a/po/zh_CN.po b/po/zh_CN.po new file mode 100644 index 0000000..bd762c4 --- /dev/null +++ b/po/zh_CN.po @@ -0,0 +1,537 @@ +# Simplified Chinese translation for rdfeed. +# Copyright (C) 2026 Felitendo +# This file is distributed under the same license as rdfeed. +# +msgid "" +msgstr "" +"Project-Id-Version: rdfeed 1.0.0\n" +"Report-Msgid-Bugs-To: https://github.com/LoonixTools/rdfeed/issues\n" +"POT-Creation-Date: 2026-10-02 10:29+0200\n" +"PO-Revision-Date: 2026-10-02 09:30+0200\n" +"Last-Translator: Felitendo\n" +"Language-Team: Simplified Chinese\n" +"Language: zh_CN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/rdfeed:79 src/rdfeed:103 +#, sh-printf-format +msgid "Could not write to %s." +msgstr "ๆ— ๆณ•ๅ†™ๅ…ฅ %sใ€‚" + +#: src/rdfeed:82 +#, sh-printf-format +msgid "Signing in to %sโ€ฆ" +msgstr "ๆญฃๅœจ็™ปๅฝ• %sโ€ฆ" + +#: src/rdfeed:100 +msgid "" +"The password could not be saved in the keyring, so each app asks for it." +msgstr "ๆ— ๆณ•ๆŠŠๅฏ†็ ไฟๅญ˜ๅˆฐๅฏ†้’ฅ็Žฏ๏ผŒๅ› ๆญคๆฏไธชๅบ”็”จ้ƒฝไผš่ฏข้—ฎๅฏ†็ ใ€‚" + +#: src/rdfeed:111 +#, sh-printf-format +msgid "1 app from %s is now in your app menu." +msgstr "ๆฅ่‡ช %s ็š„ 1 ไธชๅบ”็”จๅทฒๅŠ ๅ…ฅๅบ”็”จ่œๅ•ใ€‚" + +#: src/rdfeed:113 +#, sh-printf-format +msgid "%d apps from %s are now in your app menu." +msgstr "%d ไธชๅบ”็”จ๏ผˆๆฅ่‡ช %s๏ผ‰ๅทฒๅŠ ๅ…ฅๅบ”็”จ่œๅ•ใ€‚" + +#: src/rdfeed:129 +msgid "" +"Your IT department has the address. A work e-mail address often works too." +msgstr "ๅœฐๅ€ๅฏไปฅๅ‘ไฝ ็š„ IT ้ƒจ้—จ็ดขๅ–ใ€‚ๅพˆๅคšๆ—ถๅ€™ๅทฅไฝœ้‚ฎ็ฎฑๅœฐๅ€ไนŸๅฏไปฅใ€‚" + +#: src/rdfeed:130 +msgid "Feed address, server or e-mail:" +msgstr "่ฎข้˜…ๆบๅœฐๅ€ใ€ๆœๅŠกๅ™จๆˆ–้‚ฎ็ฎฑ๏ผš" + +#: src/rdfeed:137 +#, sh-printf-format +msgid "No feed is registered for %s. Ask your IT department for the address." +msgstr "%s ๆฒกๆœ‰็™ป่ฎฐ่ฎข้˜…ๆบใ€‚่ฏทๅ‘ IT ้ƒจ้—จ่ฏข้—ฎๅœฐๅ€ใ€‚" + +#: src/rdfeed:142 src/rdfeed:156 +msgid "User name (DOMAIN\\name or name@domain):" +msgstr "็”จๆˆทๅ๏ผˆๅŸŸ\\็”จๆˆทๅ ๆˆ– ็”จๆˆทๅ@ๅŸŸ๏ผ‰๏ผš" + +#: src/rdfeed:146 src/rdfeed:159 +msgid "Password:" +msgstr "ๅฏ†็ ๏ผš" + +#: src/rdfeed:178 src/rdfeed:236 +#, sh-printf-format +msgid "No workspace yet. Add one with `%s add`." +msgstr "่ฟ˜ๆฒกๆœ‰ๅทฅไฝœๅŒบใ€‚็”จ `%s add` ๆทปๅŠ ไธ€ไธชใ€‚" + +#: src/rdfeed:187 src/lib/launch.sh:220 +#, sh-printf-format +msgid "No password saved for %s. Sign in again in rdfeed." +msgstr "ๆฒกๆœ‰ไฟๅญ˜ %s ็š„ๅฏ†็ ใ€‚่ฏทๅœจ rdfeed ไธญ้‡ๆ–ฐ็™ปๅฝ•ใ€‚" + +#: src/rdfeed:191 src/lib/launch.sh:149 +#, sh-printf-format +msgid "Password for %s:" +msgstr "%s ็š„ๅฏ†็ ๏ผš" + +#: src/rdfeed:195 +#, sh-printf-format +msgid "Refreshing %sโ€ฆ" +msgstr "ๆญฃๅœจๅˆทๆ–ฐ %sโ€ฆ" + +#: src/rdfeed:205 +msgid "The password no longer works. Sign in again in rdfeed." +msgstr "ๅฏ†็ ๅทฒๅคฑๆ•ˆใ€‚่ฏทๅœจ rdfeed ไธญ้‡ๆ–ฐ็™ปๅฝ•ใ€‚" + +#: src/rdfeed:214 +#, sh-printf-format +msgid "%s: %d apps" +msgstr "%s๏ผš%d ไธชๅบ”็”จ" + +#: src/rdfeed:222 +#, sh-printf-format +msgid "Which workspace? See `%s list` for the names." +msgstr "ๅ“ชไธชๅทฅไฝœๅŒบ๏ผŸๅ็งฐ่ง `%s list`ใ€‚" + +#: src/rdfeed:229 +#, sh-printf-format +msgid "%s and its apps are removed." +msgstr "ๅทฒ็งป้™ค %s ๅŠๅ…ถๅบ”็”จใ€‚" + +#: src/rdfeed:253 +#, sh-printf-format +msgid "Which app? See `%s list`." +msgstr "ๅ“ชไธชๅบ”็”จ๏ผŸ่ง `%s list`ใ€‚" + +#: src/rdfeed:268 src/lib/launch.sh:199 +#, sh-printf-format +msgid "There is no app %s. See `%s list`." +msgstr "ๆฒกๆœ‰ๅบ”็”จ %sใ€‚่ง `%s list`ใ€‚" + +#: src/rdfeed:270 +#, sh-printf-format +msgid "Several workspaces have %s. Write it as WORKSPACE/APP, see `%s list`." +msgstr "ๅคšไธชๅทฅไฝœๅŒบ้ƒฝๆœ‰ %sใ€‚่ฏทๅ†™ๆˆ ๅทฅไฝœๅŒบ/ๅบ”็”จ๏ผŒ่ง `%s list`ใ€‚" + +#: src/rdfeed:278 +msgid "Usage: rdfeed [command]" +msgstr "็”จๆณ•๏ผšrdfeed [ๅ‘ฝไปค]" + +#: src/rdfeed:280 +msgid "Commands:" +msgstr "ๅ‘ฝไปค๏ผš" + +#: src/rdfeed:281 +msgid "Add the workspace of your company" +msgstr "ๆทปๅŠ ไฝ ๅ…ฌๅธ็š„ๅทฅไฝœๅŒบ" + +#: src/rdfeed:282 +msgid "Load the apps again" +msgstr "้‡ๆ–ฐๅŠ ่ฝฝๅบ”็”จ" + +#: src/rdfeed:283 +msgid "List the workspaces and their apps" +msgstr "ๅˆ—ๅ‡บๅทฅไฝœๅŒบๅŠๅ…ถๅบ”็”จ" + +#: src/rdfeed:284 +msgid "Start an app, or open a file with it" +msgstr "ๅฏๅŠจๅบ”็”จ๏ผŒๆˆ–็”จๅฎƒๆ‰“ๅผ€ๆ–‡ไปถ" + +#: src/rdfeed:285 +msgid "Remove a workspace and its apps" +msgstr "็งป้™คๅทฅไฝœๅŒบๅŠๅ…ถๅบ”็”จ" + +#: src/rdfeed:286 +msgid "Show this help" +msgstr "ๆ˜พ็คบๆญคๅธฎๅŠฉ" + +#: src/rdfeed:287 +msgid "Show the version" +msgstr "ๆ˜พ็คบ็‰ˆๆœฌ" + +#: src/rdfeed:289 +msgid "Without a command an interactive menu is shown." +msgstr "ไธๅธฆๅ‘ฝไปคๆ—ถๆ˜พ็คบไบคไบ’ๅผ่œๅ•ใ€‚" + +#: src/rdfeed:313 +#, sh-printf-format +msgid "Unknown command: %s" +msgstr "ๆœช็Ÿฅๅ‘ฝไปค๏ผš%s" + +#: src/lib/common.sh:174 src/lib/menu.sh:251 +msgid "never" +msgstr "ไปŽๆœช" + +#: src/lib/common.sh:184 +msgid "just now" +msgstr "ๅˆšๅˆš" + +#: src/lib/common.sh:186 +msgid "1 minute ago" +msgstr "1 ๅˆ†้’Ÿๅ‰" + +#: src/lib/common.sh:189 +#, sh-printf-format +msgid "%d minutes ago" +msgstr "%d ๅˆ†้’Ÿๅ‰" + +#: src/lib/common.sh:191 +msgid "1 hour ago" +msgstr "1 ๅฐๆ—ถๅ‰" + +#: src/lib/common.sh:194 +#, sh-printf-format +msgid "%d hours ago" +msgstr "%d ๅฐๆ—ถๅ‰" + +#: src/lib/common.sh:196 +msgid "1 day ago" +msgstr "1 ๅคฉๅ‰" + +#: src/lib/common.sh:199 +#, sh-printf-format +msgid "%d days ago" +msgstr "%d ๅคฉๅ‰" + +#: src/lib/launch.sh:169 +msgid "" +"Your password has expired. Change it on Windows or on the RD Web page, then " +"sign in again in rdfeed." +msgstr "" +"ไฝ ็š„ๅฏ†็ ๅทฒ่ฟ‡ๆœŸใ€‚่ฏทๅœจ Windows ๆˆ– RD Web ้กต้ขไธŠไฟฎๆ”น๏ผŒ็„ถๅŽๅœจ rdfeed ไธญ้‡ๆ–ฐ็™ปๅฝ•ใ€‚" + +#: src/lib/launch.sh:171 +msgid "Your account is locked. Ask your IT department." +msgstr "ไฝ ็š„่ดฆๆˆทๅทฒ่ขซ้”ๅฎšใ€‚่ฏท่”็ณป IT ้ƒจ้—จใ€‚" + +#: src/lib/launch.sh:173 +msgid "The server did not accept the password. Sign in again in rdfeed." +msgstr "ๆœๅŠกๅ™จๆฒกๆœ‰ๆŽฅๅ—ๅฏ†็ ใ€‚่ฏทๅœจ rdfeed ไธญ้‡ๆ–ฐ็™ปๅฝ•ใ€‚" + +#: src/lib/launch.sh:175 +msgid "Could not reach the server. Check the internet connection." +msgstr "ๆ— ๆณ•่ฟžๆŽฅๆœๅŠกๅ™จใ€‚่ฏทๆฃ€ๆŸฅ็ฝ‘็ปœ่ฟžๆŽฅใ€‚" + +#: src/lib/launch.sh:178 +#, sh-printf-format +msgid "FreeRDP stopped: %s" +msgstr "FreeRDP ๅทฒๅœๆญข๏ผš%s" + +#: src/lib/launch.sh:205 src/lib/launch.sh:206 +#, sh-printf-format +msgid "File not found: %s" +msgstr "ๆ‰พไธๅˆฐๆ–‡ไปถ๏ผš%s" + +#: src/lib/launch.sh:212 src/lib/launch.sh:213 +msgid "" +"FreeRDP 3 is missing. Install it to start the apps (the package is called " +"freerdp)." +msgstr "็ผบๅฐ‘ FreeRDP 3ใ€‚่ฏทๅฎ‰่ฃ…ๅฎƒไปฅๅฏๅŠจๅบ”็”จ๏ผˆ่ฝฏไปถๅŒ…ๅไธบ freerdp๏ผ‰ใ€‚" + +#: src/lib/launch.sh:277 +#, sh-printf-format +msgid "Opening %s in %s" +msgstr "ๆญฃๅœจๆ‰“ๅผ€ %s๏ผˆไฝฟ็”จ %s๏ผ‰" + +#: src/lib/launch.sh:278 +#, sh-printf-format +msgid "If it does not open by itself, open it in the app from the drive %s." +msgstr "ๅฆ‚ๆžœๆฒกๆœ‰่‡ชๅŠจๆ‰“ๅผ€๏ผŒ่ฏทๅœจๅบ”็”จไธญไปŽ้ฉฑๅŠจๅ™จ %s ๆ‰“ๅผ€ๅฎƒใ€‚" + +#: src/lib/launch.sh:280 +#, sh-printf-format +msgid "Starting %s" +msgstr "ๆญฃๅœจๅฏๅŠจ %s" + +#: src/lib/launch.sh:281 +msgid "" +"If your company uses MFA, confirm the sign-in in your authenticator app." +msgstr "ๅฆ‚ๆžœไฝ ็š„ๅ…ฌๅธไฝฟ็”จ MFA๏ผŒ่ฏทๅœจ่บซไปฝ้ชŒ่ฏๅ™จๅบ”็”จไธญ็กฎ่ฎค็™ปๅฝ•ใ€‚" + +#: src/lib/launch.sh:290 +msgid "A second sign-in is coming" +msgstr "ๅณๅฐ†่ฟ›่กŒ็ฌฌไบŒๆฌก็™ปๅฝ•" + +#: src/lib/launch.sh:291 +#, sh-printf-format +msgid "" +"Confirm the second prompt in your authenticator app too. It comes in about " +"%d seconds." +msgstr "ไนŸ่ฏทๅœจ่บซไปฝ้ชŒ่ฏๅ™จๅบ”็”จไธญ็กฎ่ฎค็ฌฌไบŒๆฌก่ฏทๆฑ‚ใ€‚ๅฎƒๅคง็บฆไผšๅœจ %d ็ง’ๅŽๅˆฐ่พพใ€‚" + +#: src/lib/launch.sh:301 +#, sh-printf-format +msgid "%s could not start" +msgstr "ๆ— ๆณ•ๅฏๅŠจ %s" + +#: src/lib/launch.sh:303 +#, sh-printf-format +msgid "Log: %s" +msgstr "ๆ—ฅๅฟ—๏ผš%s" + +#. TRANSLATORS: the letter after "[" is the key for yes. y works too. +#: src/lib/menu.sh:93 +msgid "[y/N]" +msgstr "[y/N]" + +#: src/lib/menu.sh:163 +msgid "1 app" +msgstr "1 ไธชๅบ”็”จ" + +#: src/lib/menu.sh:165 +#, sh-printf-format +msgid "%d apps" +msgstr "%d ไธชๅบ”็”จ" + +#: src/lib/menu.sh:190 +msgid "No workspace yet. Add the one of your company with [1]." +msgstr "่ฟ˜ๆฒกๆœ‰ๅทฅไฝœๅŒบใ€‚ๆŒ‰ [1] ๆทปๅŠ ไฝ ๅ…ฌๅธ็š„ๅทฅไฝœๅŒบใ€‚" + +#: src/lib/menu.sh:233 +msgid "off" +msgstr "ๅ…ณ" + +#: src/lib/menu.sh:234 +msgid "home folder" +msgstr "ไธปๆ–‡ไปถๅคน" + +#: src/lib/menu.sh:237 +msgid "Documents" +msgstr "ๆ–‡ๆกฃ" + +#: src/lib/menu.sh:238 +msgid "Downloads" +msgstr "ไธ‹่ฝฝ" + +#: src/lib/menu.sh:243 +msgid "not found" +msgstr "ๆœชๆ‰พๅˆฐ" + +#: src/lib/menu.sh:246 +#, sh-printf-format +msgid "automatic (%d %%)" +msgstr "่‡ชๅŠจ๏ผˆ%d%%๏ผ‰" + +#: src/lib/menu.sh:248 +msgid "classic" +msgstr "็ปๅ…ธ" + +#: src/lib/menu.sh:249 +msgid "GFX" +msgstr "GFX" + +#: src/lib/menu.sh:250 +msgid "always" +msgstr "ๆ€ปๆ˜ฏ" + +#: src/lib/menu.sh:252 +msgid "automatic" +msgstr "่‡ชๅŠจ" + +#: src/lib/menu.sh:261 +msgid "" +"The apps only see the files on the server. Pick a folder to open and save " +"files of this computer in them." +msgstr "" +"ๅบ”็”จๅช่ƒฝ็œ‹ๅˆฐๆœๅŠกๅ™จไธŠ็š„ๆ–‡ไปถใ€‚้€‰ไธ€ไธชๆ–‡ไปถๅคน๏ผŒๅฎƒไปฌๅฐฑ่ƒฝๆ‰“ๅผ€ๅ’Œไฟๅญ˜่ฟ™ๅฐ็”ต่„‘ไธŠ็š„ๆ–‡" +"ไปถใ€‚" + +#: src/lib/menu.sh:262 +msgid "" +"The apps can open and save files in this folder. It shows up as a drive in " +"them." +msgstr "ๅบ”็”จๅฏไปฅๅœจ่ฟ™ไธชๆ–‡ไปถๅคน้‡Œๆ‰“ๅผ€ๅ’Œไฟๅญ˜ๆ–‡ไปถใ€‚ๅฎƒๅœจๅบ”็”จไธญๆ˜พ็คบไธบไธ€ไธช้ฉฑๅŠจๅ™จใ€‚" + +#: src/lib/menu.sh:263 +msgid "" +"Offers the apps under \"Open with\" for their file types, for example Word " +"for .docx. The folder of the file is shared with the app while it is open." +msgstr "" +"ๅœจโ€œๆ‰“ๅผ€ๆ–นๅผโ€ไธญๆŒ‰ๆ–‡ไปถ็ฑปๅž‹ๆไพ›่ฟ™ไบ›ๅบ”็”จ๏ผŒไพ‹ๅฆ‚็”จ Word ๆ‰“ๅผ€ .docxใ€‚ๆ–‡ไปถๆ‰“ๅผ€ๆœŸ้—ด๏ผŒ" +"ๅฎƒๆ‰€ๅœจ็š„ๆ–‡ไปถๅคนไผš่ขซๅ…ฑไบซใ€‚" + +#: src/lib/menu.sh:264 +msgid "" +"How big the apps are drawn. Automatic follows the scale of your desktop, so " +"they are sharp and the right size." +msgstr "ๅบ”็”จ็ป˜ๅˆถ็š„ๅคงๅฐใ€‚โ€œ่‡ชๅŠจโ€ไผš่ทŸ้šๆกŒ้ข็š„็ผฉๆ”พ๏ผŒ่ฟ™ๆ ทๅบ”็”จๆธ…ๆ™ฐไธ”ๅคงๅฐๅˆ้€‚ใ€‚" + +#: src/lib/menu.sh:265 +msgid "Plays the sound of the apps here, for calls and videos." +msgstr "ๅœจ่ฟ™ๅฐ็”ต่„‘ไธŠๆ’ญๆ”พๅบ”็”จ็š„ๅฃฐ้Ÿณ๏ผŒ็”จไบŽ้€š่ฏๅ’Œ่ง†้ข‘ใ€‚" + +#: src/lib/menu.sh:266 +msgid "" +"Paints every window right. But some servers end the session right after " +"signing in, then go back to GFX." +msgstr "่ƒฝๆญฃ็กฎ็ป˜ๅˆถๆฏไธช็ช—ๅฃใ€‚ไฝ†ๆœ‰ไบ›ๆœๅŠกๅ™จไผšๅœจ็™ปๅฝ•ๅŽ็ซ‹ๅณ็ป“ๆŸไผš่ฏ๏ผŒ่ฟ™ๆ—ถ่ฏทๆ”นๅ›ž GFXใ€‚" + +#: src/lib/menu.sh:267 +msgid "" +"Works with every server. FreeRDP may not repaint a window after it was " +"minimized or resized." +msgstr "้€‚็”จไบŽๆ‰€ๆœ‰ๆœๅŠกๅ™จใ€‚็ช—ๅฃๆœ€ๅฐๅŒ–ๆˆ–่ฐƒๆ•ดๅคงๅฐๅŽ๏ผŒFreeRDP ๅฏ่ƒฝไธไผš้‡็ป˜ใ€‚" + +#: src/lib/menu.sh:268 +msgid "" +"Always waits before the second sign-in. For when the second prompt does not " +"come with automatic." +msgstr "็ฌฌไบŒๆฌก็™ปๅฝ•ๅ‰ๆ€ปๆ˜ฏ็ญ‰ๅพ…ใ€‚้€‚็”จไบŽ้€‰โ€œ่‡ชๅŠจโ€ๆ—ถ็ฌฌไบŒๆฌก่ฏทๆฑ‚ๆฒกๆœ‰ๅ‡บ็Žฐ็š„ๆƒ…ๅ†ตใ€‚" + +#: src/lib/menu.sh:269 +msgid "Never waits. Right when your company has no MFA." +msgstr "ไปŽไธ็ญ‰ๅพ…ใ€‚้€‚ๅˆๅ…ฌๅธไธไฝฟ็”จ MFA ็š„ๆƒ…ๅ†ตใ€‚" + +#: src/lib/menu.sh:270 +msgid "" +"Many servers with MFA need a second sign-in, and its prompt only comes after " +"a short wait. Automatic waits when the first sign-in took a while." +msgstr "" +"่ฎธๅคšไฝฟ็”จ MFA ็š„ๆœๅŠกๅ™จ้œ€่ฆ็ฌฌไบŒๆฌก็™ปๅฝ•๏ผŒ่€Œๅฎƒ็š„่ฏทๆฑ‚่ฆ็จ็ญ‰ไธ€ไธ‹ๆ‰ไผšๅ‡บ็Žฐใ€‚โ€œ่‡ชๅŠจโ€ไผšๅœจ" +"็ฌฌไธ€ๆฌก็™ปๅฝ•่€—ๆ—ถ่พƒ้•ฟๆ—ถ็ญ‰ๅพ…ใ€‚" + +#: src/lib/menu.sh:273 +msgid "" +"Loads the list of apps again once a day, so new apps show up by themselves." +msgstr "ๆฏๅคฉ้‡ๆ–ฐๅŠ ่ฝฝไธ€ๆฌกๅบ”็”จๅˆ—่กจ๏ผŒๆ–ฐๅบ”็”จไผš่‡ชๅŠจๅ‡บ็Žฐใ€‚" + +#: src/lib/menu.sh:275 src/lib/menu.sh:330 +msgid "Needs a systemd user session." +msgstr "้œ€่ฆ systemd ็”จๆˆทไผš่ฏใ€‚" + +#: src/lib/menu.sh:322 src/lib/menu.sh:325 +msgid "Could not save the setting." +msgstr "ๆ— ๆณ•ไฟๅญ˜่ฎพ็ฝฎใ€‚" + +#: src/lib/menu.sh:371 src/lib/menu.sh:609 +msgid "Settings" +msgstr "่ฎพ็ฝฎ" + +#: src/lib/menu.sh:372 +msgid "โ†‘โ†“ select โ†โ†’ or Space: change q: back" +msgstr "โ†‘โ†“ ้€‰ๆ‹ฉ โ†โ†’ ๆˆ–็ฉบๆ ผ๏ผšๆ›ดๆ”น q๏ผš่ฟ”ๅ›ž" + +#: src/lib/menu.sh:373 +msgid "ON" +msgstr "ๅผ€ๅฏ" + +#: src/lib/menu.sh:374 +msgid "OFF" +msgstr "ๅ…ณ้—ญ" + +#: src/lib/menu.sh:456 src/lib/menu.sh:608 +msgid "Workspaces" +msgstr "ๅทฅไฝœๅŒบ" + +#. TRANSLATORS: keep the letters, they are the keys. +#: src/lib/menu.sh:458 +msgid "" +"โ†‘โ†“ select r: refresh s: sign in again d: remove a: add q: back" +msgstr "โ†‘โ†“ ้€‰ๆ‹ฉ r๏ผšๅˆทๆ–ฐ s๏ผš้‡ๆ–ฐ็™ปๅฝ• d๏ผš็งป้™ค a๏ผšๆทปๅŠ  q๏ผš่ฟ”ๅ›ž" + +#: src/lib/menu.sh:459 +msgid "No workspace yet. Press a to add one." +msgstr "่ฟ˜ๆฒกๆœ‰ๅทฅไฝœๅŒบใ€‚ๆŒ‰ a ๆทปๅŠ ไธ€ไธชใ€‚" + +#: src/lib/menu.sh:484 +#, sh-printf-format +msgid "refreshed %s" +msgstr "%sๅˆทๆ–ฐ" + +#: src/lib/menu.sh:512 +#, sh-printf-format +msgid "Remove \"%s\" and its apps?" +msgstr "็งป้™คโ€œ%sโ€ๅŠๅ…ถๅบ”็”จ๏ผŸ" + +#: src/lib/menu.sh:536 src/lib/menu.sh:607 +msgid "Start an app" +msgstr "ๅฏๅŠจๅบ”็”จ" + +#: src/lib/menu.sh:537 +msgid "โ†‘โ†“ select Enter: start q: back" +msgstr "โ†‘โ†“ ้€‰ๆ‹ฉ Enter๏ผšๅฏๅŠจ q๏ผš่ฟ”ๅ›ž" + +#: src/lib/menu.sh:538 +msgid "No apps yet. Add a workspace first." +msgstr "่ฟ˜ๆฒกๆœ‰ๅบ”็”จใ€‚่ฏทๅ…ˆๆทปๅŠ ๅทฅไฝœๅŒบใ€‚" + +#: src/lib/menu.sh:579 +#, sh-printf-format +msgid "%s is starting. It opens in a window of its own." +msgstr "ๆญฃๅœจๅฏๅŠจ %sใ€‚ๅฎƒไผšๅœจๅ•็‹ฌ็š„็ช—ๅฃไธญๆ‰“ๅผ€ใ€‚" + +#: src/lib/menu.sh:606 +msgid "Add a workspace" +msgstr "ๆทปๅŠ ๅทฅไฝœๅŒบ" + +#: src/lib/menu.sh:610 +msgid "Quit" +msgstr "้€€ๅ‡บ" + +#: src/lib/workspace.sh:163 +msgid "The server did not accept the user name or password." +msgstr "ๆœๅŠกๅ™จๆฒกๆœ‰ๆŽฅๅ—็”จๆˆทๅๆˆ–ๅฏ†็ ใ€‚" + +#: src/lib/workspace.sh:164 +#, sh-printf-format +msgid "Could not reach the server: %s" +msgstr "ๆ— ๆณ•่ฟžๆŽฅๆœๅŠกๅ™จ๏ผš%s" + +#: src/lib/workspace.sh:165 +#, sh-printf-format +msgid "There is no RD Web feed at %s." +msgstr "%s ๆฒกๆœ‰ RD Web ่ฎข้˜…ๆบใ€‚" + +#: src/lib/workspace.sh:166 +#, sh-printf-format +msgid "Could not load the apps: %s" +msgstr "ๆ— ๆณ•ๅŠ ่ฝฝๅบ”็”จ๏ผš%s" + +#: src/lib/workspace.sh:282 +#, sh-printf-format +msgid "Remote desktop of %s" +msgstr "%s ็š„่ฟœ็จ‹ๆกŒ้ข" + +#: src/lib/workspace.sh:284 +#, sh-printf-format +msgid "%s from %s" +msgstr "%s๏ผˆๆฅ่‡ช %s๏ผ‰" + +#: src/lib/menu.sh +msgid "Apps" +msgstr "ๅบ”็”จ" + +#: src/lib/menu.sh +msgid "Share a folder" +msgstr "ๅ…ฑไบซๆ–‡ไปถๅคน" + +#: src/lib/menu.sh +msgid "Open files with the apps" +msgstr "็”จ่ฟ™ไบ›ๅบ”็”จๆ‰“ๅผ€ๆ–‡ไปถ" + +#: src/lib/menu.sh +msgid "Size" +msgstr "ๅคงๅฐ" + +#: src/lib/menu.sh +msgid "Sound" +msgstr "ๅฃฐ้Ÿณ" + +#: src/lib/menu.sh +msgid "Graphics" +msgstr "ๅ›พๅฝข" + +#: src/lib/menu.sh +msgid "Signing in" +msgstr "็™ปๅฝ•" + +#: src/lib/menu.sh +msgid "Wait for the second MFA prompt" +msgstr "็ญ‰ๅพ…็ฌฌไบŒๆฌก MFA ่ฏทๆฑ‚" + +#: src/lib/menu.sh +msgid "Refresh the apps every day" +msgstr "ๆฏๅคฉๅˆทๆ–ฐๅบ”็”จ" diff --git a/res/rdfeed.svg b/res/rdfeed.svg new file mode 100644 index 0000000..2b1d9d4 --- /dev/null +++ b/res/rdfeed.svg @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/screenshots/menu.png b/res/screenshots/menu.png new file mode 100644 index 0000000000000000000000000000000000000000..21077f8269404a77581d5dfbf168b6f03160a1a5 GIT binary patch literal 59497 zcmb@tcQl;e_Xe6o1Q8`cMD*UGgy=0wf@smA6VYQBy$wSUy(P*-9liH5dhbRHQN~2? zox$kbiO=`9)?N4SYb~tx@}9HLZfEcPJQMm+Ri5DS%g6Wb-6K#`kkhz#56k4;|8!cTIcN=?ipu3ih3%~;NNL%YYm+-y+_cJGROA8Bgx+T9jIwhdJ zGZ0`QozP(I3Co!@wq-R_x(kzG#Hp!6yumRp0S51is*?b!FmyC zCw*xMy=Vzjs~%%yV^!mAv0db@qt}`gejA>7$jW|Lr})4VWI`_mFYIisrwQrQd2=DFKV^@mN4_^xYdSq->*OflKr+ z#Z~_k_)OOH`Tejkw4=BO4c@P#cY5oT>c$2u zAFZ#ijnxx{QH-s+DsJ$$0k%Cdi7$i&ZBBksOJzAj8{SL*Rb`$4t^eGT9)=0g7D} zVe_L?^Qt|-*r(Ivf2YZ2{GHJK9)^}MIDx&~?S4Hnx!?Dxx!@=8lH@zAZsgy(o4=~x zSxG_BBFUFOhZ>yU8@NaCH{T|f@(?Eebz|9PxZAs)Rr3p@=1KO4+*;`7h0_NEw=w>j z{lr36j!ghhYNe@pmHodn#Y8E?9?FWI#uD7bV=t5X8w|5Gd`kz+lm4fs7(gshGgrZ$>|ZCx z-Q}PC7yNoUlQ#HK&Mc>|V_gw)C1QotA!fEezlz)`^(SpM1IJQA=vUeaZtlclp;%Xo zNN;zHu)c|fy-Xic)d6pFckjGVp_i_3qc6@#7hGtbIj8*Rhc*h4sOCOD#swtFY2i&S z_a^RUF>JOHT%7xe!m7SY(vP^?Lj*FASZ zbq@5<&o+#4LCH`u5Av@U!n_3g{s6=3Oa5- zM!wetzu1h!h||+gZY7?2t&sOB1UVibrGN_21nSb@!qp&vDYd)EO6Z3L(B|9-cd&i% zp>YJYEbYisjj=;j5fML$R9!_cV^sB`g}-n_mC!nFsj%{p@fp$n^L_<+mT^^IT_49l zHC-bX($5r`HL1HUFlb4odbfJYt~?j502hZNyyt)Cy6Ji?e_mTG6!01&kiRENJ)IbtmJb9p+!9ise4`k#0|_ss_!qxIET0+qC+Nd<;j zzSig4sN0lO+KeYfIddRYb0XxmDo6x|da6S;g5f6< zodi!%<`A}4rysL*aZyf5W>p8trfmsoa^Yk?j<0*bJ}3#0c*PTo24`yo-D$LoQ9Dn{ z=qU-zh+j(C<@*G-|5kYH^I)xkJPj5*{mw&Gi{yg1!w?RQC0u5rah76lDta=QJxRj; z51U3d^Gfc!paJ|;3>N=tlvs{WqL}!KQVSwqimh8{p~(f|>%Q-apo2u!&BHnZRdVGt zK1$H_2?A!Lc^k{Uqx0R=`^=5*9D*88lgfM`uM%56#Y=s%`jmtxEfOj*=8x_yn-f$^ z2}tEG0C2|hwP|xJ?40E!p2bIV#QE=BB^b%go@#B~w-h?H+V7TyNifX(3vciAh8xYtu{N>I5xF#fR09dWi>Wa0ycvw)GO2B%8+49I^= z1&a>DmZrVro-%O1Ug=O{JI_fbeF=y8u%KUm&?il<%Qi^6TdVnJ6c)7bBc4*Y!5sJc zfOCnwo*-T!yk-ybT#o8e7-kcTA1t~jh>?oz3!}ol`Y|I0wgETs!&MI8l8%+Gh>n*o zg^oVe7}tLRk)}BHEt)~NV{-R6-=Cdrq!`X^ww4TcJRLW7E4j?UemX^yt-@!w_LU&vA9+CM~$;5b0G3#|H;V|0T7-xbkt}<@`UVNh(w!@u}3p-bWC24*;SBKbF~6V45;b>MsQOb7PkhI zS!o+^R|U2sW?8#D5!Gu)fHYObVj)sn@K^(lW!H^0Ss;6~dpcpY@8WF{H?X5?z)r)^ofjq28Js-(7hvWA zY*FqUH!6k|-|RfL(P;5Svzdpc$oc_KWcj;zh~DFi()N>snBSR zxe=D)V(7u^aU<6=1Old#kXn>dYmag6uu7Fi8va?;sw2Z~^_}a!YNHS4$oo+mzNC~y z$Vp~N!)?nX=z=36@w{LfVS`5dPhP`t4ELY$JLX`oVj~(uZ1)*RT>p7UV75#Hp&Q5Q zGH!6WODk~7>5pq`1Ar1VZ-};RFwvVr?}$%%eKdZd4#=9$R)$e8lwWkBRQ$U_=AAya zaCz^%0V05n+(_PJVH+?UJTC~|1bY_zF?;skC6Uebbb^AYGMs^TDH{hbg^p58DxaoR zGytVcDK<3hyqa9MlevQZv|BJtWx#ygG)lvrl=3t3yd*gEF=YDn(au$!pc|OO%`Hi< zhi9luHT=smZ%we)L-H*=ja9Ul&hMv=dS2M#>(;Yvr^$s4vYvIXmV4G4tmV#+)Oa|69 z@3e{1w<2wH2@K?%XV&Ef55Q`shFtjPVMj&q(oRe+hHfJT<~Z|HFgx)`u|?kLpDC2% zk)cdS4>PYJ92SFD_1aEFtkug9URnuDov$^~;~%=hMEbja=cnC|n}v$-gQtPNdN)GZ7gdzJiS#ZJWZRA0m1RSozMc z(#rA~+IVKW)B@`sM#6h6Y{LZkM{a(()&s7#%Hh+VYYnNEU#1o-UmtSP%8^puc`)n) zsr+9HlJ5Fbl4qqB25r-VVAmwtGrLk(A|Q23Da0NbXgXzk5oVbNu?O}7p)=y=uUeF^ zQb0NlT!H|{FS&`Hxsi!h!RMIBf`k1r(wX?6D=`{L3r&2ExvcZu?(VAHNH&=^3$t*B zsY;Wy^IachO5`xC#?J7h&TqtO#$(&k2nG!`=I5EM;d5t2Z&y_S|s3qAc99X5b%${st!5L%<_<{SGWUrCG#d*pvYOS@Sz{= z>NCb%qZsQ$)NZtmSuV)q#M8!DA)}s}e{l4O{(7t8_33^1fuqWOOXRO@7(w}z#Ln{4 z45Wz#vcy?9ZjY>&`&PuQBhvbB&uES?aWRaE(OZuTJ7La<}qnsJLDilmPIq7kK|&N*296T_QK zJ*eG&4g&dR9Fxz6wTItnHAehUIwZrs%%0u`|8kpqw^Rtfd5F1l4-*WOCtthmcw#*w zqV}@T^F(xGzRd8m)8DAaD9kWDQwJ-NCXXv*>vD6-U8de?{9Hv^S~V5L`&feDX{tC7krqkJ^FMAFzu-N3*W zZTVl<_48CqOY2R1JVe`lcg)jWPp`YwtT#&hW;hwx^c9awZF+hM9&bN`#CT;bz_(E@ z&dw^Y5oLIWkYlGz&h%^2s`##=+YqWmir6gm0ywUm+TpY7cun^b3cUISK98Sr*`K_I zqIc&_>OrS_a{?1JsV^xh*_-}ddol{iSs57_>AAVNXMH~yb3)}Zv#mzbAcR!n9{ahG zl#`T}xl(p{^(X+q_b-W<^hv{5CV&&zXLY!$j}|*{YcWBvnbjN8`c(=WyOrrIekb7u zTa2VN%M;RwzEQ|d@34}(i&R`2&Ne7lQE6#`vci&X^qxcz+|^ zB?L;k^7c&5xABza_OiNCjfjM*d~De6nijm*%CEinJ=&;6WEaI*R-Xfdtu3_62784o zgsMyZS5?`i{tUn&M3)v6?1@?Tl^0RRcBMWid%n0Ff6~(I1rVofJ<%Fh*|{FzNPK3J zIA??L8#oKvZMPC#TMO8)N?p51v7E|rh8Upt=1oEmbhE(!LLSr0;*-foQBi-?*LUkz zq@A8%e~R#hjqQ39iH#67mZq-?*P(^%_~={sVEn9(6XdRx>T21k_IU_e=TEXgqgMRz zupTXFe$d%KR|V6ZuQAQD6&Doj{xkUBFkF70&AJDJoyLR%RpWrGfi;42$%!V~(f}ZY zK&$}$eZgv-wdq*%qwZ)?Mn*fETiDMr4GM8U2q_tv>0`7eMZ+)aD1gc|dkRROv|aTeFeR#c8~;&*cYG^^C{NWJ;`3T$9>b%w_plg_!cO^5ktbN#jhVFlG;M+|825ZCw)*TPAx|rquvBqKL-;^&14qRQ2 z0EF`#we@tD0d!Nj56yEg5uUWI+?ryuX>H4I3X5!4`YN(CbUi+JC(3Zlh<-B2r55v2 zPMY3+cV`W!=ye>>gTd!!#z|A0mg|hOO8lTULvRiBGisn~2%j;v@<$)+EX;rVEy%_# zEp-n9L{8I=v+VVfntqcoiByRko1UgqQNdPyy87Ip^u%0me>{+eCyeLL);~ewrr7=u zU--2GPW3%)+*(rCJIZKyMoOociuY1RXO`e%ex^3u;$p}-lK0X!xxbup&V7VnNHm@FSw*uw1lfgd5~nG zBOaLR`G6hwNT!^1tQ?NqM6~ZgJt0Jn5wU1W12o4HEghABxmR8_ z1{!Du3rE>a;_cS=zXpHlQ6t6FU3_5ZHKh>@-7C-n-`VAxy8fox@$e<9)DjnK64UQL zOgY`%PDcbn1AgG^V`$rRBk-4E&-ZdNxOks;e*H$BU+%|_q)4@h*iDybpmuRundfik zd}DdMZ|*0Gm^B=4A;$G2HB)>h6+9POKo(1Z_*lh!O06wEt5rgVJP_5ixD-b;lZ8c2 zlG~|~dtb;?D^WO1FefL6&u8XM{b^d`i904_aSl$x!mFnC?=OA_T>*Wsl@djq8_JA4 zJ?sB^_S&J~!C;Q~cn<>&fFZa73becYv)0?GS_M%t^n^tQn|KwCB`tC8_|;*C4ZrGC z3pwW{L)YGYSp#{lVL$h{8bmxc@YeEU&Mqxg{rXk4@7jHy3+s|7<^j7k{iGo+t}xpB zsZ8cQ))w2^QVruzlPPY8U7>vDeV;wo5bLY^Ew|CAm2ow=eU{EE%Fj{_uDcv$tE=}v zCFbaqrSaKqSvcLC9sQxdPi)Gcrv@4E-2>ZE^w^4_ewc^5e0SNMc^4L@h9@thfW$s+ z?~mpF0lhuGjpZL{E2`vE)6|T`WbKb@Ji2uA=S4sf>=%2TLh~+J-iyveeaXO95~(fv z9}eeTuv-Aw51uD@jY>b}H58Oy^+!3$*oN`Mx)25WXzYrM&DZRS9)iiANtPPdB%{iNZb_qnlOMyU7%n5R&uHNC9X4{~1XtG(T7wRQ=> z@oGA_l)P>8K^<3(^yBliWVcv9uJisfRSUh{g;*}3nvvLu?Mr2%sr!VYB6GF%0C3}u zzhrLewoIj+&o^DsdWWPCnVE{^UW-X2aJ$=gB-Oo-L`{MV>9!M-8!KU0%I8T6`+(a| zzn%(;dO|CyEC~3^Wc7rWMUy9)!FH+yqGJLwilP?d*347=!46TKG)`p>hkWpn^{MQo zQhgUq4pQOBL;(soDNxc^yPBB={mP(C4iYu9Y4P@I578l3pb= zj^==3xdUI4v3Aa@3NYKClIF9A>4|_QN7wbXQ>9M*%;f!l*dbfMzs;cNLGZp$V(Vvm zV~#>W67HtApeOEof0>tiBIBJ$C|FNUPWVp8&rx%refZ5a&L2Yhlvq}^vE`5o*WjW; z^To_}sP^jDt^%0_%ubNMJ3|eI7Jl$acMCC;N{=HRR)%`-wvQeiA z0Rhw@tMlshsZ>m7FwtcKV|JGREk)oic$24v&gb&@hdeEa;iL8O_LNS`;YnG4d4{a{ zV#hlQF+kdZKXFp4!?&g@R1u2YzMMOBKW8~$zRrE$*mLKQ)%6-l zbVGjfN$O&QWcTnQ<#E~iYW=#&==%GxUoCuBW>mW4LV~@{^^?0^h7Uy=jq8O;Z2w(H z%3b^qsq6AR*YGOf(>CC3%ZA#WFlhl$u^~LIxz-*$h-#2@`&(}^TMNAHXW(MzqH zEVKtqESgkVMlR5}AWMYcTtYsd&^rjuBAv1yrn12U-6X6EWpm9IzcaflZBOktk+sng z6cQi8!j#M>3MVQ}$5{|CyX2r{pS^D&i=JwmN>BJFM2kV&H4If08K_XvCj*6hu+=Vfg3Cw^VPx$KI_r9^(&d7V|hSYj0e>h>(=wJ z=9K{FTSR+yFlCj~l3dMSpQ+u8D7iNd(te(*FThhq?sF`)bKbFgM-3;GfAAWwP!ytq zW1k+WZVQ_@ulp35%IV&s z$XnA%Ag}Zql#z?MYRQFy!jKwQ*Op%Ym?+$v-b1P2siVJc+gu#}4n$w=Nrl>OuY_6; zT`45C0O#F2;GEl2b~J8H8>un;Q*jLR`idN*x95@KzZ@2=%rA~NE4|O1MB)}7kzVzX zu&P?i8r9m*u6(=P`(fG{+(S=@9!DTbQa2Ifys zSKEaDbskA|TKrDlw3m)*=pbEMV}6v4 z@qNS}ipNk7l+CU{Kn#j115>BTQ;jWy6EHPp-1gDtGJte3%7*L9c9gjK{P=~VB@>iU7GUjgNvjH(d}2~ znJgaTr%!$q$T(?)Ph^}Q?P9XE6AG>Z3V{h4$`R1?^ zM(5CZP*2tO(Jz@HBPkgY2b;J&SY^u(rZtB_H@33GqEw z*OUI$WmXqzIHP>jpF{Ntfr*(|zRz!c3H0P>yh`a_zK@{FSPWq;wEbhb;CI8(wzW;E z<#D|6+vpVQ3s&=6F}J-x*KM7&5^q613=b;_0@?MeE{`|4%-%OMZquWUI+!u~Cu4H~DCmUAMiz1rlIuqtZgrV%N8$ zrn&DzScHAGwY6Vn3a9(}5>U&*g&uY3P>W=GDnYno2`B}(UJz3$CEr|M!26yuY7I_I zPHt7~&oaQs2a6?>`%UID4(No= z$=rIpAq>>4pKIjR-z^%ol(?RzDWB6)YHT2<^dT(PY3|iL?4cw*Qvj8Oo8ddiCY{UX zU%e@M+@|M7b|)o;V~#n}ZzC|(w;t{A3(CbvM;SH-FF&`PY4%i3v2B!|Rpe;J7ue9= zY-3J!ww9=SXP|1+9Exlyx7JR%p(nIfxUZWyDlZwAAf6nder4)k@$tI~o?#p}Cqg!313ESNbM#<@@N8Ta?QAocnF^)p+X_o!P!jaXuR)SrU{L6n=ZN zDDP^xnOZxaOCNJ@+hzd;X{O;V>xFOk^X(4JafuQ9g3mSR%o(1J`Fs8D7}IF~D-^5i zTAL#VbN}f-Hi+Ne({fK)Oeak#C)qP&37-l2d#}QvKe}%voF}0_mZe{b=UV+!i7}UJ zjVtSJQg(|Z{r7k8`ipuvN%pBoWM-bKs6_TVSYl;oFxTeiuX5qpe8+CDWf#5|sUq4_ zF8hC6fJo8vo>(=BEVFoJRVt~{2ZYNkyg~SaxZPEDTE%Y!ZHQ9Njk)})(-A$RW~VE$ zBl*0c_&utBCZ;ae%tX&u;W`693bV%Y)J*F4W^65bhmB^CfyQFq<*nV!T(yzJYCHVk zTAZ%Q&E>bMv(Z0l?JzEjD#%-%b)O%9ZKY!JAfBShr#<2=d=5?Hir+}1fyvrob)MV`q&3)cn{7;B+_Lb63`MGLx)Eq4nAn4 zH;?TPg4I}0rGa&{Y4;1L!b{5OJ46b5j>9risd1D{TS@nkroKk~=Kx=$Vi>z1h`7sM`yzn*n|*8jEQ+&0HOncW8W z{pTLK3kUo2?r(b{DY8~!_O2s!I5?Sg9x2?Hh;-Nb>TJ2%qK?(0MFLw6tT%#TcX^_Y}w{BV@Xm>rFbV?^lS=( zUiRGYC22N$q4{l8NmyLR#3v_3z$wsM)b|_A&y0_liV;}J66i=sA+7*wMeiX&`l$-? zXKYj)!Xa)WFX)-5H2uiPo^LU|lk(jlr+9AM4;m|+uyuJZtDH{~@2VGcN&Lm2t@~e2{ho*lbCg=uk>VzK~a^q0Q0l=IsKK`7pmp& z+1c0tZiTq?U04D2R)fE|b&MVBRb!)UmY#M5^;7dEo4mk(&K-wG!X0A(tXBlJ^BOSp zyowM$dh&0OFnm?>`8PwV?s7shP;}FE@Sgri9bD^*bhGQ;o2zOheNMJ@CPW2iR)P$a)c$={JL^<+wYjR3onNj{ z+K7HBvK+y`vZ`Y|;ltaTb3cSD;;@s64wqi0UN)Feu`I1QTWN~B((ICOR!2sGH|-v1 zv0G{j^yyG4QE?HwoFHPTi4kcH_&(u#JzSd%n)$tbbif#0aHikvRWiH~%#OHS z6C)Btrmt@-%G2r`exKtM7MPCS_c;Y>_enpoe=|omne^Fky0~^O;Xc-*jVIB>A)=R4 z&OG@CSC{)fI#vRKjDe@I61N@Ul|H>uelHFD%M}MDg`L-2$g~$WDzZd-0CRaqu49pi zs(Bq#{C3x6f+<@P%AT;roRP9-Nu^r50ITlCs`+ufXJxH7`j{b2(y;lb`O$O|_s>B} zG74`NCen({GG`8^ zy%Gx?H8>qRSa~x(NQC;c1{2%&Brj>?7IiI+8=n0cP-CY`5^MCpSHyQeFz2 zx43jE9!)wufW&v_d$SFSn2O(p@&)KI<#CrsNoF46&x9?};Em5^GAp`mM_oQiwa~Rx8|JCL)St<+B1&B)c3ryqj7+p|W(}-1 z8Z6(Z%bZD!pz%SslS4s|suI%EPrtRH5*(`priP-YKnq-#ImxvA1k>j<+>}5>^`VmU z(>SYV6RNMVRbHsu1?&LsTks%lV09>7*&BeErgEZ%o#X076l06a`9B->@lS_ zy8T`?x6)Qxv1*t z`MMBgQEY;nd-Apm*W9OFFMo$Rc{`Y#dJ!#CF_uxajL)jc;D*8#4_%XGKcC}q8+g~Z zWIg8nyzcpHyHtFtwi!TjCu+aNbOu-Yj*FD3C5cwF5kx5+0ds4C4`2TBA1+;XDK1!R zb~W(uEXi>A^inN|bg9g~ZQ~I2Fohxs!--=lEw2%lbKJln!Hu*f(sA=--`P$)2y7e` zYp5{5I5ASl-pxQ(GA*@;d!)YRXowGn1xUbW=;QuMO~D9Rf`3lM)t?Z1X33gSM3mQNB0Ze zO^b2+U#Ai6obV=Y=kD*+?v!9bHu$<@;KRf2s|XArE0R~!twX$~7!tnyFqszFGk8Rz z=SFRu#6xgG;IzcnBb`F3uONQjYk(H5YTBM6u^Dp9z3zYT{Y78u@bCYS04A0f6lQ1b zxo1x2{kZoJCa(iyHlur=s^>OG7Aen-zFguoLVQlsooay384JOKa_S{>UmRic$k>ID zb@;cGJ{aVJ7K^C+c?u?#4O$kF&V9clO~@!b`p6^DrGviwl{rw{dqp^tORK(ha6q-m z+gOZQ6#gzI=>XLfONw0;v5ho{x;WfEAJEvD@1F&n|2NTBur&*M5fpj;ptO4Y;`xKX zUz|hAYVj`cJh_A}*#Wr;3Uogoq;m_mf$j>h^QP+6f2X0wK5H$F!jrn4iQivD?B5D0Na*ieVMKKmccrl>51SS3$xslgv-6`C^IEVUr1 zih!yZ|G+z3aBu6ttyE#=0U*(mGnc#Hp9Uck(RYYVpbJxnw*~)@z>id;o=!u-@f|+( zgQ{?SnmVw~%!_imQx_ul>Em;MzOu*}` zu7JyN(RL5ND_s5ZgQ$wyaw`}^afl=NY(iQZZDTb9lRx}$=SY2wDAm1Yp1wV$W_$(q4 z&(8mvda`hfNl`jCPxRNEgmaP-94hUdOPKHFxkGU=d`5q0G{6^m?v|9e z7tG1?LH}bai~26#d+u(XKeXhlehp)WDI3rY@7a_p1b%}GPmNAiE(l9jk zVosuGDXKKQod*_wM>~j#^0R#}3z-L8^t+`+VyG%K92Jw@`CW*vcrmknHf6_^S zCc^0MWg*>||Gg5pPT45Ra^?N?^W`FJWY@~-%M=Cq6nHfP^Ohs77@IVPm6bljI%#1w zwWu!@pXOj6{NJ1Cpgh_RZ+jx7{At4VK;6!+IJvZ^r)*0&4>?$--;hvDuT)h3ap5>5 z0Pr6!w#jyOKBPWwG`(uL36|rsDqByPBsD3erFB`zq$$p~*lj8&**pE;!F`-L#^C60 zcDINdLIc>f4QN)iwiQWQ!rpQf;zXB)OREFcT3o4I#83F2`Ff9k*v-$NGwghk7G^W` zV}^l<`Z1KhH>pVh@#@MMBGBkn=^L@s|2s9D9Oh%OFV+WiJ4fs$z*t%gS*Q2AF7OI? z2fYkW>QVo*6zA46aa*1=8=%mUu zG_&p`iI#r|cF0L$`5*02LB0(g+6+Wm!KQ}ZR24T<1sPerKvb%P0>aAxOQOQ1Bzu*I z2JS{r+|n?VssDk%yYX7rc2ZE}s&AvW9jIAFgDPg~`GJ_JAQ(g9G1f0iJ0AP_Y!g{l zhQUv`F`1rSr|WgH$82F?(IV*T>gu%6CcQb=+^D4$cRAzv z4_S$#m1;jyQ6OxWXeb|@O-NK=I==mJy0D}9@grS)eEe9E&d9wYCRSA~_%|#Z593M& zN5_hPn9TWI%wvjz8 zeufxgi5tcJj&11eCT+HSG|l}_?c!H`W~em-pb|zfKQbEvx~7)vh1!+kAB;YB7;NuV z>sfc%N(*TJ$KmqGQO(iW3DB2yzgZ}FJvoL-T}zA2s6p!kWMFvMJA_9#RX&O)wx{QP z9zp*TX69}_&oO3HO&M6Ejidhx8-FEF3Zy_;q_D=u&HWQIiRu&3F=AeimQusCS=`vz z-!(Kf8Iy&838(AcY_#5IPoe5|v{D(^nJe`>$_B5H{{8WjEd4@<7SH6TF?>06Xzlo} zI0#_@bsW`qSxdC)+XtVP1CSL)&))A(2({h_PzZdDo7?c;_??Am1uR{K6vN`;;{HvB zS9Yqdx^*-)v*O|*@N{V`tX=U2_w6<012x?KN7$HI=_>*P0;iSf*JK7X7z}MJN6Bgv z1C5qy`dN?*pJR}faF?>bHMS7a=eac6GO9RJ7lVoXD`*2?LQ;~xUis$?z%0yYYkT`2 z-+|9<@WnZ1(o6?Ysn-JE9x!5FU;2((@(!#SmS2Mha|QH(|3g zhkq4x5OCQe`lYLz-wAsV0VRjYMfixVwb1Y^2H`PBgZ6Ra=%n9z7`d*JIjvLI_WOSi zUm4+PWSbWZdz%gyie7Ds%@>87 zs~Z(&7<-XZ2)Za>wD5?4Du2~&ZziWBUH;Ub)P&u?!ic!}AK);UEM!9Cxf43xGcLVC zBLy}@?@Ogi_CZ?Cu+Oj7_aU^mEA!bf!AQ0qg#@RP+y3+ZE91uQ2O9F%6{5#gkdc zRZV25n)6orN1f3>2k_oB9j*Lz?osrtzs)8o8%bgobKiS>Wv9ua_A``(CoUL#$cbq3 z3@mv_8gxftr<2=<=Ri#rb5)Z>Sj{Xre5+A6e8MU=5 z?XMr{HJKMkzA!>Myk6|Uw;0HYT`kCi{=l@*{TNJqAQA(XLkCki@t?m6JNA1i{Mk=A zMOb>W%%rSjj;N1CX|Gw5958+Ek$K58qc{6nVi>$qP3*x>)+qfa&lxlTcCU^ z4_74cM2j^ozH7N~Og*Y7WRySg6*VH3oT_)u38LP9D2d&FiJkVemv{2jLfZ5sI~Uh) zt3N3sw1xLx@Ox=t3`>lT1n~@HkiQzfcJ5nf=QF!_)9ju}U!4p42YoSe>XiIQ6?1O4C^Zzn7R%y;9AuPX9XR89E)lL(N?}f1!PG z_TV2Pi_S*0@p!JMy02!2vkaWCLs?s|CdyVZNO2ybJEOQ-i$%5WqkY|t==t91jWjeC zk$Yg?slvkcs_)F<;dmbG+7`(jQeNo@sxh?aP{>i%U(xXXp6aKCTf`q9-p-6EU-YJ7 zRRbfm7vHtqN&)uT1X%op*9RsydKe1DpZmBEN~(nBkM(xxJ32voO=d8luJNhqSAJ1&l2Y;2JN}hE)pdsdN0RD?UYRfCTMed4H9mQLeboEf?7iap zu#x#}!^tqtCp)85!kl>DwxtBZCo++Z^J zTKmCFMddAl9qn<-j+DdON1}U?(hG9|P7)yWKS1b_F6<#e%s+~Ppb%dLgqFS}s)X4QuxMvouAu29v9OjERaw8fVG z+6{qPMb!YooNXL5`6M?QkMNu&JFmxLT4QIxbbps=F zEQLlrh)P%6w|kp!6(%O+L2aEj8^4=^f^sy zuKarPl1xExqof+dc3pQorsy;*vff8~du(;S5?ilfGM~;>XZ>To!PP+ji`ONZ8iky{ zm3}8?k6sv4jLj)-()u2&%gG#_Q(j9P!k{q8(W0jXWWe~_nBW;}DR&IM7aF!EX4?BJDdt7!lR>halo`(*JJ2Ru{;zNReTmm#;^-onIo7KTs&?F%F0ME)HN6Mnu?(AiS^TG@BzINHcY z`YbfRZc`fb*#fibptCTq1C@?}x`$dXxZ5pEUP*5o>o=};li#vUnfR9c^|-D{_;&9|hU(yLSHNSE+Hi871biN0lOQ5W4o;oYuQXV^PAj++qAvMCuQLTmN-oFqj9B0aT#!}H zPg^%%TAzab?Fcrc-qLREkDR|v;4;bJ`U6o-%EoHbDOR2_n=Ikvh88efT@y);N-y4{ zk0Wk`4nxZb2p!vB*^>J$TK8bwa@T z3Py*WC;HotkzXQM&SKKV^kn6}J>2Tl|M@ptNVl43w za{Hc!1ICe|d79{#mOWV)J*lMwpK;xuNKWEi?6?PK5_8}Ab0GA_Fz3Nd>uYd7zR7&O zZS5S?zVXD;r}VA&;xFa8g9j8s_QgS7%ObVb{m;%fveb?#f|dmx-uhySvWAEkA~Ip& zVdfa_DxYt=j9#;4Bm#Uldcyb7je=mfYY+ z;q_n*|9WjS=e12u0HGCgt2TzibxPy(fGx9EhMoM_1TUwIKs6~oXgv)h&8(4>2$zf+ zDe?JfG^9De;xUho8bmRs4h848^G6wZToTtB&S1R#1KH(D18!xg#vG%vc|AAGt#?4X zNPzGrsi1e3uFT)gj~uT>Ao4#!Q+BX2XguhQ zoG0R6=2m!YYWT1%2D#4)O!c)~U%GCs)zP78iC&98gxSrPMs=pT|1G7b58*lWifC}# z=79Pxfc5IVYIf$D4cZlo}dEh&#WXG^d)SD$2U&ut5 zrVHhszaOD~wBUQfY4Xzw3X*1hP=C-dVr_IrJ&j|StYhR^FWPKSYo~em-4=+-4%eoW z3Cw!{CI?IP=w%AL$Q)p9qiZ_2^?MYGf$nX7bIOR)q}}Umr7b;dXs}*>@l-#=sjihq z(na{A$D&u)iZE_{X-R>)Rb^ALVGt253+PmP>lW_@Li{ZB+fCg_mi&F!j!+0TpguG?Qo6ZGh#tKUy+bfq=9ScaY$yWTdRT2F6`S#V~*eKyuC?@l2I z_*Jaes2VM@r-EzJUG?nb&(uW0fsE|>_312Qqx)_`i9zj_@SDGE5MhmqDdK zeJE6F2eS6;i*yTUxLX|-y1xjMRDY;ON=Y&mdOA6@@5WQ7d=P~xaeY*QTBoz1T2WZz zY0AxKYTnO&u;HOpF*OXKML@)*?t_8Z?FsZ&y>}~}P@J?km0$Haw{hmu_RnzE0d%U^ zYD$G!P3?n_<4ahS8)%C?PJs=Pa^&Z{n_TTq}(2FjFt?UPrWv-I+N*R(r+0Z z({|1NU;w(^cBhG>1lFkPdh*8jIL%z=TZVk6_t6ho^=on9fDqv1WRvtgtT65 z*>XfI@Om#(n#6OK)fskv9X&F~%6J&eEAp#ZU-HI0jqqd7xyR8^?Zkb@lk_jhjhv8%}Yjt|rf)B`GJ1 zzQge~Tl)2sapX_%s}%Apx9Ws1VjjOTPa2ihhqn&W^N5y*HSV`QouXi&s=sMiA#I1< zI|`hNV~2dDfl=4i22@M^jI!PArzxXJr=}+R_iql$X|iY}y!xmkYpsQEFs(Mdz=Xie zhWpL=^6C%hUNimwaRKCMA?{Wqlc8nhk*zoA07=jfJ>G1r$UsaTxc^AE8;fB6_LxU1 z#2d`8fC?RYqtg(n6oNQU+}j<%M@f5klYO93ANY=#J}<@3?etGzKYsiMWI7)@)w) zG*4`geIDVvkbZST6x|mV$|-QX#3Om@_jrWd*jsVF^FhUf1KxLKOg%G;(_gk0JdY&f zLHqSe)b-{4DhF>*EUo1{sAV7vzv|jNb*rr_lpD?%sKxG^1_zH|G`><&JAXpW7Lm}8 znIdgW<9A$8B>7B#GBo#Js!MbZ_3?%Ew4r!_)^Xe)H2z(`Ic=L(OA}ftLCYfh^F~| zt(KU+j{gO3N=CRd3e*q*ZWc(aE`W1U}EQZQk~lBP+63+xJ1>_4<* z9&0v0C5D6E$YFUoPL@cK&7`WhG~y5qU>aQk{V#ie)nsLH50Uh8zt1lI;`3nZ1_i8N z65X7%7D4YH+@4_2UpR($vRmDI65Zd?^!`(bUPS$SrU#aGas*pw5?1oIK{jy->Al*TDV;D4jRPBR)o-l-hqkv2 ziff7fMR5r>IKf?my9c+yNs!hf9m6)Uwh`PF?BtUr71wn`@l66tBGBMJ%n5eosDS=F8=I5(_Vv^IB8wFaTkSzc7 zrxxytaJ$o!;HjVEU-x@O_kt{EePno9L*L!?d-Vt;+k7RSfB|7Ogcu^L?hTH*SDY*t z)T9@epA#XaGMw>`{lkVhd}Auq*RbIRXUcDlf#{_`$nOm%T8d_A>>m7ZI88y+nv(MH zvOBg)`-f#d!&&EUh2Hg*#YO;{+jfCITIeQlm7(o^q~g1oSRQg8-D4}aa=(Vt$-4kJ z)EEZltda?+R9%gHvTHn~NDo6MkrduO`K=Y5IbDZ3u5DPqv$;<;O>~+}BeeK5A^%pKQgox!GJGUg>*Dp$WJ#5}^l=+O`Mwqvjj3gaFR?Jvi# zKyb@zDr^e!RV4DNi?b9+CMj=WArSy81?|Ac0*(iED>qxjzjQISPydwK*~%%c$&aP+g{7uM~RP-{N!b_(61jU{`#OTFmTO zE?qRzdsR)qWfLa`k9M#P=W`!faX@NQ%T=abCmW=!J-!s=>cTTw(fShHCjRzRxAA%Q zlVdeP{AS$dlvc|5crbJzfxTt@@-7CCa{o`8nJP`Qg_pGUm--0dTkVW9sE(ZO2o-AA zGYXVFfvJ8(GWq@UCab{$wlCY#UcyA8lvk-23pj+rJi)f@f^uTkV1__p0}LpzSF|Ob z5jl$uN-oAm5WmZDf=oQqEQt6jm`K30p_dY>#1N?x=w@ZH!0|KRBeS&v6YHeooM0v+BXb@?12V( zdq`%?!%wi)0Lcc$vOE8S%d&SG7yiqIdb_>N9KlksO#aK$;CK991Aj?MFm;?| zi{-YU%UTZk^G>qfjA;a7`Ap3iafwkmSsbenN^8M^Z!mA*&ooS%b>fJ}vhdGF{xcmW zPtf}4TzKtfLM~T6RFY$keEP5Fs}0F7S9=iOUDtoQwywVI6QX5$1#not(3@l0sn5+X z03+`XN`!_oqS*NP_iv=$vBD9*Ss8%{7LiU8VsCJ1De6zxJ+K-m>~@-cY*4-JNy1u? zv4ZD1vw(4_-r_rb*AR=zppDLlG{#RK0u7wWz`ke0>_7p1XtH3a_IGVb@<0q(04Ek} zqJ~ohE&;bmL<{?)URE68;e3a(!CM`sBF(uTS1dP`0}dJeg< zF_ns=irt2n7LS5i>}?-aC!I70XUP`&HDA|#kUo-!-G(RGmEEGT>gT@N68a<-S|T(u z;h2zTNwl|J()Jlkq$Gxx(^DAwfM}Y55a9GU$?ykUK+1G)Qjy5 z>M0|?rm$!oDHlr(BVkjYxN7)<*(aps__I+O+mAW~9KT>j775_3kRm^p+mAzRHL}=G zAThrvpE3f`2uGaX5MmD*)8zMRom^IYsObBB(!MP!0$I$F+t0opNSPS`MdfNC*woB2 zz!G_DOt-5t5GwW$`Pv6Mvp3}{Xln(uD(L;x2$WxOO9Z>fixlKXwZjoxRbVGj??`?n zoQ1G-Uk4!I-(j!T!E6Y*j+3r^`6fxg%jQ_N8*2494tTsUT*!D9gn5Z}DJW52bTd`A zYyg@rnw2J(cqw8pQ86MFSnem6)eJ^l`SfA{=l|UaZJ@Sq-gJA?d{y{num1g;E91aW zTfLv&0ey9O>PV`cM!zB8B#cxdd%j}wSu^$EcEO&V$rwXH@j%#JphJDNM{MvT4<^Wo zFzaNaqo+>-2ydDm^DM+>!^IPPO88R&-F^d((F$#TC0`+7aBWUf>|g}h5r(=OFS>N> zmRV+)L@hvEA$}VO8A}c3@ohB>hxU9bPU0D$;r*aWl0etHpc?}#Qj=`a6oztjgwj;} zSCR91J#FEsB88bX{}}R!oeLA)?rRu$M8UE-ZLb1kd=BZCtDp_3T?T;p! zk_>J0df4a2CuS*xVJ9U|Ka81pR8muUC+L1N-o%7;pBg%K_D>=ggbIcZ<*DincZ6;7I9>c$4FC zT)M_q58`n_w_68~hPd52Z@5EvA4TBB)@smPojx+=pzD~hIOI0I70IbVPrmy1k7W~} zBYxX?+hichExyv$C4397 zYHGCG32p(sH821c#D=yfTr_Jmfozh8-U>C~2}NT!7xCOMh@7_t3r2@rZ}!UVTzq4{ z775_7uGstB=`TS)XxH8MJ3u+Z9W#(gKNlM8@)}R_tVCqVGi7GqgG4XSl8M0hbM5It z-HDVV9cj4m7TA*kHIQ!y_rFY8`jDn-jMCxVBgWm|Oq@Rt1GU5m3wuJN#Ec6I2z3YeV_ebzEte*SA-X)kW4Wn#?JfSWTMjAEFWp8@2k02Fr zOs0C&F7ZD3X>uDxUqrt^*n`&$wGLMz0XY^Yp7)f{UE8Lu z_er+jto(wrZr8FX*i9AY9^al=gA{FBpQBdMM8=3$c4j}>s3=DmMFCV$xnZkN78UU_ z(jRACNK7##FGs?bZh^TQFJFrxI4FhAI=uJ@Sn)}e;i=QiP|jh`z*1KbC--Jnz2cOh zfPGF;W=2ogtC$V9$nhRPfAR68=@RvRQN5kA{b6ivHwdDewP(25l^f5bTp}N}+~+B4 zW2Hb{+urT{!LaYDG8pfAhS`S|sOAGG(p1S+0J%)Hc+-o6?$<(aY{e#nsy~e^kupq5 zqfMbiiSQmN46Le79_Q<_L%)oK1dVlPXE?iF)^#D@s0f+S-QLqu`(s@K^GJGV=u>b3M8VFYt?*~H=kbHV{7|d8q4a=jvwSt zVBa!5K$MG$@O-ZLBHyjcLW|R}5Pa7){Kd>%{PbItF`ssU{f!-A$52H}sZkqf6o7O{ zGK4g{C!LQct0a_*+-!!{ognZ`eM@rXh6WuU%!kiHKFSCXwmO7aZfCfd44t%k%v5zl zo6Vi*EKVLGa%>w-q7n*2bOL$jQ!Jm?DrYcmFgXW&6iKexI1;uaO|K=yIMUZyM}B0G zppo#?gkq{kd1Zy2G)u-MpbyO}8?wVu#@SnL7CKX?BzYb4nKC}rf^c z{=oGMz9``{2^c&2wIqH}lAgubwP$?UvEvyVg({_N9Ek#^!DVh*ja$#h?(p2_ld^94 zR?N6`uz4N&jfrj^U*`Tg88#CiqI9mzY2AL6jrm-5EXti^o~BSX^8+B57#H*HBj$Ph z24|G=davibPq2z$7u_`+LNb<7lykE0C0-#kic8(sao>4)F6?v3AIbM#al9(8-|08u zN$-FX--O3C+Rd{@_YF&*?MSMV7S3(g+S3V|yPm`yO*wBm_vv$38SKfxUF4nh-9BDd z{8pg+cv_$&E4IFih_9EX_w~`5S%oUr*$?-J#=Y%2ZWG=-4Cm#8OwA)L^c2tj`CBUvLYCc3I6-Q#c29># zLuz&bNP$~R7#)B8nj2^VLCZk$!26pS6zrlbVxA&-hLYtyo!fH#5bKPz1>&(7m_m{-#+U3 z`Fm7it*>+`Oskyp^Sq@nB_ z9EL4~CmE})W0=+mjN;wKU#!?pdW(d^&koUsVZO%`VN*&Vb=*q^Oq{F??BOkArO-d*6Bv*MA*9|0Cc!X#)btgL)hka=c5R-&R^ghnS(vF-bH$>MMVUDdNi42uoJapGcn5ehvR$TUd zhLy+wDiRduXf-_Rb@lGxv4WmoMkXt6+`|QMrf8$YF{;suFBFfL|^KcWLNP@})o_SA;a~rhezxgc39&LVtSANyd-= zRL83Q@+BsVu)khsVzx)UiKdT%+kEbfS-WFQ7ETAE)n_YchGfg*5 zSv0CR9Z=Tm5dhk|-~8UN3|Jimu=oL>Y?#A1V(?D4CEty2-?0OU{rqgnxFsSEgh7FeFrY6=3wH%YM;wL*sM^P{B z=J~O|DR;{JwynRXmwjIxO$^$=3w5UthFDA< zC|(oM4Sz{2aS_?rz;I$|;BF=*%eMxZT4Bo&A#^!r#Z$ItpAFiwWLds<222>Ox$n3e z_k<5Z3rSU!&+{`NOc8T$n~usS%yrM9HUSe0fTpHL1(bgj+Qj}cG8bc1)g)JoaD&k7=evnv3VCKzi+qmqW5H9y`Mk7JOy2xG@l+k{?PKUy)uHzJ3JCj*KT1^#I{egKv?2JtkfdxDf4cXxuOB;my&s*6SF2a{|Y?Ea? zu=IAnQcxO`b|f)=D$fmv-up}IY^pUqhpi@xlV0TgmQw$hy~)Gsb%na|{bvZPp21z@ z!XbzzM?eowuOkx{(WQaxvfzj)9l12iF@qx2gjy0(9ACH+VdD_z@8 zG9#jFLL(Eu~)})ih$(tt$w1Ayu3VQBPBhS-doa%X$ zEuSzXf+kCHwNq2^p?^%$h@2;)&+Ggpq$7~=`U?KrI!A~%Z{Fnp_yOiq|0t_$Uj;y3 z1Ax&(L3w#O^wYzW?`HVl);go^(g0=WG{9v=Np?uK;QgC?y4aXQ%$aJVJyc%zD>Hs! zsC&rnzf5Ta^ZxAq@4zf(vzk|;qobS1EMHvIGB2)}J2#X*Ra2gbihlp{{`_BdJAWZS zDuuf`W$x{zQqj@*qdpvg`T@|N28V_?NysBV9U6!%EPFg zZG^~#n|8sxqRo1?l>NM(vH-BXE1!PI($y$qY~YrPdNwC5{=6IMJ+B`qjayL zh=hyz{8KEYMYL@0L$)+IcdKr8<&r+#p-U9U2X%91KsfT}9>*wQ6V-z+?&`**%H0W)Ub-Un)0~n z%hUK=V$&% zzu9kPl`Cr0ma%W%Qo%~}s&v?i{WG5@qjK&nJD$w0K!X6@$77NCtr#eygWhYlF z#l0f2uo9&g=F{?vA6d-j8>KCh1sNn(LSz^2Z^N)~facotB28A_dmSIs6AOJvEv=Uw z@fViPeW(ORH2wEA{kyp8^V{Ok(zPeQ>9VJI=@t89O$NY$C?T?0A{EspsW4Sch3kch zj@Z45!|FxJLj74L|7KsN`K^lkDC1-(pCziy!9GELasRi;N{?^TOdblopBLO*zG9t! z`D-C|jO*TPo$w%kDzg%X8$Z=4mYnFKcth@V!WbkNe2~I0{86LbCTTj^F>Vums-U68 z*i3IL6WgDTGYnonVKWDXzKJxwg%7f@nE8-fK|(LrA_Pm{Q`4Y|GU{{=kvA+vkDBdt zPDMj&!D`O&GjQ#ly1`exHOAW{~9mn zw~C(6NSe>rsrANjf0M|4i1Ef%yRyh+th8TC-FE%)U}4TzEr-!M`ehbd++YrJ;9#%d zy+_kHbnsWfH?r{?Q3I!6=-!=r51vGrs|dK}8qCSO8Em5Lrl{LPeN-n!d7Lx1&6p;R z2SNkogspbRKi0K?v3Wf(qTlz$@uD<=QrS?vQ-r!7li_CMwu->kpC55W($WWX zjn(IbmCT(8{vRN74Gh)WaXI*8pAB#->2b)zbJ{i+xSxzc3_2W7#j7IHW1NO#S?T0@{<0l7&RQMkF=x9&r<@=p2(LNePZw%NQddx{rRkco5 zI%Q&gypeCKZ2WO)?i~3zXK|{&m6@nAZylNt)yn!dN@GFp4JoSXyzsxj@C}yUabzJ} ziOxd;)-@irMC+Iepat34U`3=C6Oyp8C>ObG$l8A+jcls0e??$24 zjkawO8l{NBKlbi&BLO+gHAljMq;kYaE}#nou$5n3Bc25i`y!&pRYhXw;gaj|!Y$ zPV>FD9#y8DvJt4~&3JNhVj|(W%%3fOdX%V*-3zNMn?{6jX5i)Dq$u#NId1w=TA*gt zqy5`PC!mcOk=H5!#sv{T`88O}eE#7p$BAxXs|v`PPtxjErAW{{(OVG{22q?Dgn-!4wtS@XYPy1*MfmH#Kc{vRy0|9cI8@wr)r<|*fwxdgkO zwo2LH_Gq9~ccpp(tL zh2tPagrY>i0F0qp`+Ji+7=*CC>zj?kBnV5w4n7?s&9Ne1{oRo?pEK*3cT0J!ofif4W0UBI9%Ve zb-q59ZZ6!b!@6m$?%b=lp(L4GwbEKaLE*EpvBb#8$n?|{d++}Fny#s->C4jmymUZ7 zfNBH|(D=aq(7V(SaVd$3Fzlg$p{Z~h6?yp)$DP9u^t80uIU5^vtM^t7O$}#FQN~gY;r2C%rScVdX(&C=n70fE18<^i7wQ^ zeoaosWz=j5^!JAv7#@yjYHHdC*e_S4404wX3rkB=n?2%TU%!&c$;nw*Smg4XyU;Q- z>roIUz0(Ds+uN(Dsfga5tW33Vlacv(;xgtA!=wHB^(z#IS%H#9il(){`p1t5wv&}D zr}>42^T#biBjBh94sO-OmF9%_G&yLXi*DcF>1(TfrqulON!7lPWRz*@x@zzQ1qA^qw$RSro^X{{Q-?R>r>2B1 z3iAEo1@?sa4EA0#awH@qS>3v@+tZ!_%<1`gj$X4rQ_D3Xw8E_~Y-i|?f5(b;FqvehD_a#Quy%OH4>ov9x4Ryn!KS zW>#FR@!(c`HRfSyZ@7jgPlaqo}CJMhab8_I+z?ba^>_ zw&If%!E8lQ`H78%tz}-yzS$dOglvj6FR9y;HB)=cw{ImLZ%<7-{ZGX|e#EJuAQwna zV$_k0l$W1gm=Likx7j|~?W=I}@TjpXEicbWN;2^-uc-+KgTbz?mLDq$8Pm8`6>XWB z+1c3QprD}-_HU)=j&1lV=6V;t0c2`P85x=BnFR^!W9ui~PzeN?(9Wt@bT)4GyqST4 zTBoCrfV|0QBgh)*4RSs(bNp=98oP(s*}3xy(%0M5rNe zY$tLh2%mvFWEtlFc~-%aJ#zw3@n5rAFH!ZJo}Y2`lne~Wiq;w$8|}kJ$H(JKm{WT6 zMg6}osYTK)98!7j4opyzipDOnPk_kvz zIvyVU(E&Vu2o^Tx2C@F3SmfFs&Zjv$LrKb-ZoEiuRaG&CY&yw0v2gHZ-Q3)syx3Ve zsT`1y*tpmV!v&Up8?Yr= zKW&UfHzfxeP7*$2z9sdcWI6~}?S>mpA(puGlxkkAMB(Dcq`?JSO_?=3cA9~}d zuYE2cAzwTT2o6Nyf`ip`KFIHlX6+5cv!SAA=jT%;BsnJoZ5>tr;^FbQzgZyXIpd*E zPIQnV7V@jNHwXiBb6{pBQONbxOP*uymHlv(jw0>V zHFN9VC0c50m3=nOQe9KG(}V&`m^?p^SNh{eZgH`+!ciBtTfVc9=asa>ax6JE0fF4i z%m)F#tDB2mhHG_I7N)d{QHJ-mDgH>bAnejeT+y% zhRGq2pqo|$gps6`%BF3(x0E7@9WY^ zN*smRRUL=}0|S*jwcpj%)=rX>kO-ODWfrbB^(qrw-*4_7$^zW_?9elW>gwwJwij=O zvTpmnZ67l9_0zHbirOCuOnQyry1Kd+kGpW0R&p&-F|oU@k!u2OUO}`q0>Zt!I~bvQ z-OS8)Z4iMWIew6^cknSGA@Cp6)WCe7?^eH4aYp68H}@$`s|k3ZNC?_UaJSH*C9G~VjKI(2W)P{eAYq-;NtZ%<5oQVSU5y+K0R zS{oZ2lvUuJ?(6LI!@$I(W17#X-1%lgvU-F}*0%k}Enop08Wc zLWbvo3=O0#v@xhHH~6iWH)NTC>tRl!q5@ubosZkO_}l_)V6kp74mRa{VF7_2Op@i8 z+&pkpjQ?fliP(c-AG1oF;L3YP;7yqgJyCO7!;%+jT^ea+Oah z!U`LmfRb#Xxz@fLF0(;op0YwqhU9s3Fa0F{oh?@&419cZ8iOL1s;X^lUOa^&6d43I z%YcW|nvgE{;WBB|nb2+7API^LT-|6Xy? zsHE?Qjo$%`;x6oOt}l1K9_C62dD^T$r`ih%)mrY|@rbMP@$nDz_ouCTA29-}`K&m_ z*;_-Bdg~Qg6O+Q}@p0*Hv2IUDsEO<#xSr&^Jm|!vB!wndi0NSb^71V@|CDJ4+;@xo zd_3|Xr)Z5}QCmgko!y<-)&QccOT|kfRXZp99VC{}2D+nOQ)IFzB;rwpEhnRA=;x=m zqGyfGjh1bC^WILiYOD>uWWg9z{^J%lHsk~Z1l~GsBsY+_L0#S518c(X>psMHYEZ3= zkIf|mrg3sYLPGI<`k(YPAKzH7)_$8pA$uab%gxK9iR^aa<>mc$bGp>uo;9!j8L7p4 z9YPPJG^+YUOuBjZa0LIWe<>u-Bb44oyFJsVh)f(7*hvM2i?{8fhvMX9<`y-33}P`& zOZ?ETit~P6FinVy6YZv;AY^raeZJ+{L5-yVsD&hLWsxX=e0r*Ci^dhh7auW{1N<9A z*Fm|re29lpt;Q;M2-;My9ImEaxsNdsv#q%b zMv>x3ZKfEc31onnn3}Hgs3g^C+cTGxS#>q>mr^ViHa2R050CO3e&@}6>YxML*N2J* zoHwyS^*7E!fKj=#=deP$Kri~Qvr3i+0^*->dEOwK6f7parUYVR)2R)LweuNo@U-1; zWuMC;I7}<>lhu#1gXx1QPwgz&bj)OCy$uKCJuFc@7WcavMWMpGJ6Ax!xVZ&y4+|Z% zn5q>uh=Yw?L;4&p{7OYdQ(;JkFa=9&YSc|mq^u^+Xz%^f%j_fbUA=5m##u*4Fe~lO zlXXG(Vnw;Mw47QF2}!qKG~6BXcSVjL84}1#S)Y(!9OAr5OKB??k!d&uFU?LiCpA5t zP=Q~38zHv0sPosam`FiVt7&xoC!6cr>vLyK4UG$j#>U2U|AsF+vRvq0E;S7efqD9O z%V&>WLp7E86RcLZuG?xgEic||=^9y3b3X(^lWUzxX*7wCpT{4VjFDRo|E#d8|(s4oA9;48m;u-nwpyq(DZypB!xhg6|Fb; z%((ubcI?oLwu?cAC;qFE4O_QHu34^H(=nS-@n1t@dB4q(h5u~e@Bf*0I7vwyJRDvl znA8uUUCRyj0pTaCe!OlE9M@zsv`jV^i65Y$o1BmFv6BVm@_&jmUrQSX6u4df&A94FX!QETb0Awm7)8Maj@EoZ~3 z&+(#@b4*X-4Zpd03(CfZ!Dma;N7r;eNS$`?0M#@WRLsWGL3{MVd@2JUDT8`x zr6Kwf9bMbkfnY)-pJ{UwnW^W!+NNPlWF+JL{e!u+AFwg5jEVU*Ctzr&@2lE`T*xZo z3kgq+P)wl|hz_IX;Q`;+5K(ke40r%cYx7Edexu&YILv60@n!q)u!~Nj)hl`~d_clO ziod9i&JfiIWG+TOIwt(&8>I9>p0gldd+U1}H1t-TiS&K9^CcJEkR^$tQ8&yd%~fz` zApC?FxKCLxFrj-jlNcx8=ENrcaHCQPVa^YcHS|haT+I{r-VR1d6Ed2F$mXhsMh8_Qfri*bL{@w*0eH#RnoZRyz9D%Nn^PXwRXSk2QD z-}*K;6C5vB1?16y_IOC_m^8ICtKFb;@$mclGpkvIczlA`l0I<)Za!s#K_>g<3r7b%C4U;-PkTcRf4Boka?f5YMyQfX4;1 z5XTA~Kfj;0jOfb*xq;Y0M@v6#O^t}AGlU`sBEQ5+F|MPpSX*0foramDCt?>V{;aPv zzd>2E_FUr~(l>m^G^kIatX`V03uS6bQ(ax_Nsd7WWIFHOiF=Mv#2kh|xiQEwWDXyO z`x=n5F{M+7(|C;g`=cEaU`zO zKD<7;w2=>A6AyZuF=Aj=vL4fzLOdWWEH2nT$7W;*Mn)hz06OH=*pHzM(?mp=CQxv2 zm`7aeMr(c?yr(H`0k0RubV*iHK>INQA6;f<>zJU%MEKD{b?dw14O4CBI* zd$SOUSWrfM&{5k4318 zSqjhS;bricH8}1=NZ?wLWqK(Sg8y%eK~9|dP^S0#6c@o}JAlTjPx6f-`@OfOxwY+< zSAV{)z4J62IYbu@)X4JFc|LJJY8OF2IQdSBtxUS_6ll((iu8SVt5 zRkC3Vn~-;e#2Xu)IS?xwJNsVoyTja=)R6N#6tRaKcQ>A&l2Vd1jA;5qpjbrA=MgqG zHXaPFPklb&ePbVBz1oGImP)SEzpT<1v7cpCg5+=xpw8#Yrj;vdzR{2(~rgof^88cQPuGGr9&xK1M< z_2v^_s~Q{c#OKmT>$KQkQcG1-T6;JjZ=1nRPQ_G*T8oRLwtBPNt0sjYq_(ya?QHK( zj0X~Ifjb^!yQY_?!F$ZQ*-5+oQHHi8d_D13fWX$g2locV6tWvle4dYLf;x{2Mos*c zUacJwpp4-DHZs~_^ZT}iDwZ}%$daDBI~+5-eCC?K9o)UEn}gSo6}j9PCb&g0gLfp# z<&B+rI3C_QBKauQwHp^Esm?}ENm)1qi{|@jh=Y6{5>RZQe)svb zzJ7;!X{adaVRl3}R~MxyWJL}u2lZ+3SO2ZO&H&p=(2!@$b)fQyC)^s{^z_CiEV5oq z(|4r&xy=-QUS11rWrO$r#Ym&3;&!#2ZhmH1xssr2MNCdB3ybZs<$(nm3eHDs>oF1$68<%mf6Up2$?IzC6N&5Y^CMx7C?#yrWr`#DGvoEFwN{%IZ_)2`9{V^Ngo=|j zsgECL1$ND+mRY`b&wQEZ&bzV7_VFdcz~1m}qSctCI6j)M+Oj&0$P`K;T^Sml&QCJH zK;eE54y*(VM5m%^U-#ZH#8)=EOfB>nP4z`CCnGII?*6(#MI}`JHWCd*aYGnR4h!B!M--1-njmT0OQ=}zXmxX3Gg*+#>a5S-yXAdr}sJ}lgqoCKa z;1|P5q#WO`JVH{TlGE^!Y4TT!3%2G$Dk>^nNr$1iHjA}i%K=B}l@*=s-4g|8=Xy2W zkE5-X4K5#4F&TARQX*;W?2Q(xjj=#@zi^>TEghu-lmZa9i02B*Q|mEnaw>~sfN~Pg zX1ZR?SQ&v5ZrHw{#Z$~K(s&^xHCCu=_b{9x%pOGgyQM`f&OOKn*O}>AyHu889#DoH z7@S>M$uh`H`#BHQ`fP#XnuSa#b4hf$*~fNvmmxGs#CHPPs?KJwvVJW>u zamAj!re@XClApWE58Qr2I3b8Ef*Z_WQ69KrhF+nqE=+M&agIJDAh|@sMAy4S0B3tY zHm}*UKJbUE=8h0>SDN{K;20(&m4;K?cS$V#A4EE>p_PMO)ny2zl2mw z)kkY{9|!pBPYE*5w!Un@qG;l6Xfg;?9R60 zSkPU&T?KLi2TDECT3YxzZJys&2<)AF%*;}Ny6eI2h?=ZAqb+szF0^{~gq(C^T$%4a zvU_|`AdITPgsTq;c+mjlw-vD!Bm6uHM!%?ag8wx|Se5N5@nWM9zni}ff4#NQ7Dh{y zQSp^bzAc9a+Ewh=7S+;tSTgu&FpDi-D+? zoRlei5k}#P{8lXiSOs0P6Z-=Pa4zBp4urnIyQ>3sPbzJ6?uJwpQ(g|;50h0f)PXVq zoAgCdgQSyz->Xxm4Gd?em%E%kg>vW%E?d=AhtZ4`yZP_1!Mr6zI~Ow6Oi?q43;p#r z)6&*<>mmDckJ-*5mQBHYHEgGI099Ug>LfpsxuVeIV=k;ed@KYIaGTc6kJN z`+Ht}Z5uU14KC44+6})IR@YEOo2ksh;c)-~= z{deMLcY7%nn#>V?F+yK&jUJnt_}ttY%mR>P!+t3z6{UY->Fx|7N%^r}(H0JKw=5Mb zMf=XPCCA;sz;AS4xQBtf=uR+D|3gRp7B_mkn+8r4Z(DlP%@4gAujl6{ zZ5eySQ|4v##Bhmuw0CIjU&A`lqZq`AyAQrG;-5U332DR#Wb++RB5)v^D3nZEyw`;GRss|ya%R~2w_QR^a zM>N7Gj!SRJBEOssqowb>5bC;#2vF43P1MA9Y@75vsL(GCKAG`j_K&8}x`oaoP`cgq z8&2TOD+xj+*Jx%w-Tr95w&S#ei%eXzxQBD8_U!g&MW6l>^?mFhEUvmTq4%|Lf)g)u zug5-n13%jA-`Jt`ymTd^#BbxalK99bkl0UmY)BDeDjyqF8(*3TjkZ5r7ZJYgI5)Cq zAydHywp{jEUM~chzb<(4tx8qYX7n>Oetr?rua9lM|K+;&4(TO)3*J=icq!fiiyF6i!QBi|~(sd3VDeZ+H(ZAcO; z!pU;Z!v+Hr*qOmA<^^$lQDJ_K=({eU0MnpzAOZN9ooS(Ko%4sj9fl$t)MZ0dj^K@D_3ndSFp0<)TRm3^V!+&XXM>?LI+wNjD_ z{gpfk&Uk$3?X{O>n>3h6W3C|8Gd;%Zuq}7qR>U=0Mrg8?tg935b@%{+*(izvu-kDc z&bL7E=$o2De)wRiMN|)MJg zhsZKEIncnjs4En#0NQ=hC=l{nA_yflL52MrnKS1yf z*RZ+3FFx93r4cv%>UMMwyy?xRlK8_ndG>ZPLM z5D@rrDaZ_^s;YW%b5l4w`|5Zo#eh4HHlsmS??Sbsggm zK~)AjVj;crng@XF95Gwqud9SD95x=fiuN0NDk`ejprBsS)enD~0t$rfof@kQ5=Ml( zGu3~;MkFW;>_fykB%z@LI9OQDdgc=uK8}wG-}yp8J(V;S23uHIW`)LSH+yFQC?86J zq(M$jEwJZdW0}s|H9-iR01o36u{k+W)6$asdAgj_+1W|AD)NpTz;k8qVPQXiPAG9n zFDRg5l+%(0*q)!Qe)uel!u6A~vc|f*dmJb%W(2i-|2rp<-hA~OQEh7W;1NVCagpXG zBp^t;a@V$c*ZR8Z@!8Z=YIb(^v*86#nUcJ%EpwfVqY{8&0+nmQWDcRw;*(5We0==e zz(7|3ZJOjoC7%RV&gFR?!l?@nhkqihrJ!E>B5vJtPqLi_7tH=R_ev3HQK^(`ouK zRW2JV>jVJP%LCj7ECM2VU}F3Ot7&U!;FW2#MQH12m*mw109d=z8*%ZiBxXHH0Nb9P zSdalYs-F!@KS?XVi|z<=f(#3g=sg*0_RTltB)@pNnd;jLW!U8zKFS$ng!+ZHP0E`2C z$qHj)KnmAs*tz8C@H-DL=m!76otpWd&w)eW zavK7@UpC;k4>n|gQ-AQG(OROKIDrYFAQz|~9u`6F;n6HJ^u@&?&t+9e8~|6;v6Mk(lV*YnAM zRTndY4j%_Vj5kO~1%Nioi$x9 z2;J7|vrL!TpU#FcXrtJ)g!CVHIbb(+#b~wP3g%tuHkOMw%Wlttg|&zSAUjh+#37)} zKRRk3TMgucVd3EgKChc{P2O8@{M_Cb1O1BmJ!KpA_E$3`FCBH}CMLUA15zY_kf8`I z-(0uxOQP1&VsSy^x8wk`i7c-R*51B8J9)xM7E`EP?s<3w-u2(xJ-am0Yk|dQI<=ve zD|W~jm>6cy4^EDpJNOxZ8yp4S(QK=zEwO;~P+VMbPL2p>iU0V;)m45@jsc(BA!RtF z7_?Z_qSHP|HynpCH}Z%(rs9;G&*ep6MMiE)JR+Tth|X<>9zbLYZQBIF*G^8Kjf}d3 zi4LVHk<|%*AK!bKj%Dl>6Ob$WybRDJw0yz-p#DMCK@Q2u#bs(Fjr$&tlZ*4*qsq4Z z?o8|@oR+vAebarcT<2YjH;;2fb&W%4Ums5{9D1>MtD&Kx*%6TBYG{iKzqm%g76F_i zA7Y@|uBNQnYY1!}{&aZ`>87P$?vU@6M1WdNU?2=VBZDdWIGK(9M z^5|drAp!|foT!Xk5?G6PatAn@_+a*!pOAIKelC-cTUAR-i}tb;8Xy@IRGzT_)RZl( z_W;|VzEMhN0omJx z^A10g>Jv4g?k5;^rTmPS!b!rdp`r2aFUqaxCji3nb7Soay;uX)=12mC;)!T>us$*O z_bX`4H#N8P^@YBRQ>9Q;R1l;jlwJR<=Fd^zQ7#I~na=NA#oZItw>O&?^SXDBL`Ap{ zKUE8h&+LpfIDkkF*Tvac1?e+m?h=RS#6?;d0|kZW<<(_&!>{C#H5rGoA-2iE2TPgt z`5-|<(kU@kit*DPU@y$3;dPDoo6FXyac=hS^o5@k_(8<9^xV)oGK>&t z%gSk}hj3MBV~xq`$Eg|vbOVs1R3V|}zXl9BU~R z@VXhgRVJ34{@N_QJ^?I?)t+4ra>LJnQCddcW3BAmVyt^By9Ki=C6;htq+XLi{!zTbA9>KoO!!XMyU zE-#ON%b(R(YK^;(?RiOSL|{~)^p(d`+pC9)jQDwfK?iOxYhq#|9Tk7b2&InLJ#@E; za*-LKDo+&566Ib*JT|-$1m4W2s=Ur=`MAZiBHAT9zYr6VrEbHZMGG3`x(~$OkVQTHCox2J*2D@!|~G5Gi^TT8+K zO}M@sP$s!rCoGB~_B<~#(&kxDTO|%6r0uqwmQ|?$g=ww}7D0zvE^+sM{xJ7x=H^-E)Y(kN3UqoX%I8}?EaW@d`v41Mja zvRZzWuU`ChJMq9--|+#6EDk4?)e=RVmD!%33?AapzS*kKo95Ew4q-oVGMlc6-q|sG z^9tn`GBtYo6Dk4O;#(A+Gc1j(^WC)MWQ#pByOWg;nDwV?LQ=t^9GJRj7C`}yUI2|bVxR?4E!KVGsK zoSu{is8dc(uFYLjn*Wv{R`R&M40pFTD)Or)0?ZGbjtb9gwh{`?w_JYj`(@AB4lFku zDZ_4z$;Cy*_J0@Vk+On6^1Co}NAC655t>k3dOt4Z&Ao5sWecgg0}Uyed{FTa<(Y17eOgk# zR#g+H4rU3NhR`>p-O~ed57&Z6a$Tcl~^@P zXEM=)z{sp*EFm6~jlh_MNcM~o9W5yN>dI;fJH=dhw&EvK^z*G|qtNA0J!GBW2F-w1 zzcimt2Wo16QOqad8zBh3VS&eO#U$^=BYDr;9GjYcAw1)~8SIK4L`?p>g&g+@~xp#e$udAqLyh-l$^8T?e#Aka%oOByP+vIV8XZodZ{ zkaN;JhKG}$M>lRIT-wbjK??a5_ZY(2a%7T05Nnn56ikWNIz#T7YJ)rq< z{zz^t*{Fud+9S)vhk?EBhf`S`y}_XQ~xC`+vwh9_N+2|9}!JM3?T zxIZT*9_sAOo~ubb-PZio)YSCh>^S@U6+=SOq90eb>HX|erx+rp#Wv*ip7mY#zvc`D ze<0$X&L07O5d~BO;Y?-~8UJbVDT!-iaZzYUR(551S8|+dd?y6uhX^XiC`nG+pd<99 zi+z!Z7Tt+q?(b~<^TsS}ts*(C5}pWGS65}vKgF0~uH5qSDcR-Lww4QX+C}Oq#u!7# zNMUc4SuSk@H~!m1fscs>@?uYrUu)+bLEMj-)T~9`V?34O;(SZJw6gMfr~Wk$N9TQ0 zM8ebY;vj-LQgkpnkKXp!@RivP9V15+f)en^Pughi-k;O6UzJsHrAuoldRQF$eJpg^ zl$rSMMcm?%%XfzZcQ#UOO0KRX=Z%+Rg%G^|aXEUQgFG&M(FbuTtp>dtX;PBy#StF9 z(MGlJ`vJcFI?2M_`3?+^($Z2+f#eATizT;!`^IX)1CLPaIP;VckhbDeflE;k>M73N z9$#b)$ZV?mbLPf7h=MlFKhmIS32klIAi~W1{F|Wpz{$&vaHv$!yoe>>m0(Y?Vyp>d zjN)}r@d#;4To}&Zw{I98Q24NOEGmlDnFAobWp#z`UofkRK79C)MPC;<`5lsuESfc~ z#=&SRDwrxSUI|-uoSCoetVjet4ouvt0sw83`9c&ed#Q}82jkKgKzaoq@iJ9MOJMTd@-YE<{gHeYhBZ_2@)$ravFN8TZs%-YQf)5sC-hH}H$?G* zg_=pdZk^69E}ese0c}#Y3LI`{7waUG@-a!VvF}aCe;cM@ArMBdU#}RbsjA9oYLW%B zC@B#Xc#DJ*Yueb@jfl<;wmMbR4<{xi8IR>gudHlzrUE98=hE^rJq@p)`_D^!kVB3s@ck7MU=y>?}ece$HnyuP`@K)Bgx;q|DlM)brDlTR_ zxdOre>uXm{=$9|3Szh*b_Hw3b#OPB#Jmf(^AwNMh;#}R-e_dEucmw2NtySqnNs^Dl zjx#$uQRl)F6A!KaEfA%YOWU+asjs^jl&tl$%F4{-S~r}&1`ty*`Oi5AQ?r9oKqP@) z(QicksaU0w1Y2^zII_Wx=0Tswr}69LjgfRrT5pB^HFZ}$svni>a@e2 zjGKrz%`0TKhdRE=Mzm7NsY~B=0S{qKZ1khRBtZN*&1@IecZ-1%L&@o>OS{ z0V_8ztODBvsb?xaDraTT|LE;;SFrbbO zbkx2JUcmJLQQ;w!JdC>nbNSIF%Er#cv4Vm^>rjid;AOyfrGh0ZCZ>FOvxvU|cvc)y zS0+mtSeeneg_PUXId}w-N(YF1x zk&;MB86Ol t)?C9-sOZ>S7LAQ=n77A9+UWvXhW@b^;vW`!O_)%ZBH+fpjNM!s5@cC(rxtez_ zXFD{Q4i5HtiJ@>|=Y{&K^9~rFY^@*er-j|su15?+@_5L|Qmehau1y1!OwVX&u@@If zguLN$?Tg1fbtzq`PjyX?GbM4!$lB>s|Hn{ECdJl5Cluvi9hxiF<3sQw08F@U@RC$w z&x7AR#P^;A6F$??BI+P9%F}}vmOr0^i?*o*fjR`^w%upz~9AUAWnCdi2aaO=@00|^|PG;zpc_NxX z|Gs0UN(b53*Y98%M37!{yk4QB@*$LC<_rmyl{C*6djQIg<>bW4Kf?}`56{cb9}|7d z_H^mlQctrr|I5E&Ga*MV@a|+-e`z(klh(U0aPIBfom}ltk{-Te=jU%W#HsQEuJGml z-EN~bFPQ|BA$0?nnexCI$@^Wsyvt;Zg2!s*CV1jM${5Oub{2mv65vmc81BZGm)UgK zhwc1gMl=47#R1|zA+T^`^M%2KZ-&zoo)7NPw$PsI@hZ|5E}aT?cBhJmm#CajImciT zaLZP5IR#F2AOqap{IV6{y?bwFdH&nkA1rQSAC@Ei@p0&N784oLcYoL=?pMn%RADg{i6Q#RxtG|F8_2#f02;N~Q{liay99J1QxgLxD9~ zePHKY&I$G_<(e3Lv__spnq+$oe=NC6XfpThW8kjK`06$_Ggp}JT32Ki9XCsOiU_p) z&!1nk;aOhy`1oJZe^Z}d-DvtMnY0yf{w3UI!D@CC2H}nn{paNOuhh}$2qCREV$OM_ z(87$39lE-^)tdzH`*Jbaa$x|P+yAo zX+n_Q#zrtRfM1843p$nU#6NA@9QT?P1ZPB4!oT}%EGurau;>z>!z8cU|)))@#SZaUlD34-u4TUkQcTqwSvOF-F;%egg<`NUjhW#tG^z`rOws{;G3B?qQ(J)q%rEFR&kI_FQ52yV(M;jyHJIW zmxssFXUjP3!&d&ZzOL>^wjtS-jSW$zd?wq?BRuQ6&G#TCFr$Tz`yT52qyVF$r0rM&)8=59w~W=@mt8fu`i*)N2-Lmbn-Z z{0F}8?F&=45bu0Q#XwI_B(ZQyKe=&eY54-d-)FZ4q4Q+xg)yaSE9T$Nw9k-wY(y=a zx<;~28>AD;O~+;4dO~3EpT;aybkgV#@GXjX#eLB1_bdlSxScvg$qgy0gpepnp9wTX zy(fLbI2g?E@};kYgyb7*?~PdIVfNQ>E}SAwgSvv#e%ydYkU|&+;+Q<4*mepNg65P; z%F3OUANF^F4yAaQgtJAac0KTUevXc+CAw!r>(Q`ez)-EQ{MsrBYgk)bw~F7gUH;o_ zpDYoCi@^LfAhzlAKu@!GN?t*M`xY-Vii^w9$-&^Ls*uFb&BEagxba&BBd!8Jr0tP% zDb%2_K-A&jxAV?MXcd@#ROu&U@zh0{0nCz+LgTmbn-X%8M!Zsg?dw{R<1}ENbZf=GX({KO!RU9!q!k%4ju=RhlBTSRo zSa~`t*B{DFjF!tcH~;$i^V`GEZXzvFe7;fdQOnwm+yBv@2mC-bUioK}l{HOh`TyAj z#QNOFu05L^6(tIoKk@6f$CFEp_NE*2?;@tEb0fZwY;T16ZM%?U`bGRy4!^2?gVXI`#I^7*7oYVCyO zbb$c@6b{@zUEh*u7m1BV#y8H~Atba&6-C38Mcx#)UNgxS?XgHf6_Xu^<5ozgNknWO3#hrS7 z4EmI<^?77{XGPS?%4%i1HlVk!H>a{N9At11*;^Q(CtgfU)$K&SXsyyR9=0=EQY7uae+V$B8cNd`k&jEVr#V5-&#Q$4u4?2uBz!rGAtyGjWH1XTDARe6TGK#JM8y(!3pcA(}NzZ`FOY?RU|BW%k9GPvUV4+MB6P!~TMBcnwBqy8F8_py4FH z*47NIizOyRRbPt^oq?0h`~S$E`cu^X{MQPEYlXOnU~XN&jXog&f}i zJJM6An?#g=W;K9|@*$M}JG-0tn3>QNWvVJObN`C={##HYR$s?LDj5I5vhTT%oQpV$ zxWz&?4=c}GP)jr;HC1;mc|XiuI)U3lKf;EV$^5&qe{{ynB(Uqhi)-KzDe5%B1|~cx z8;j+3F{WgDYSNv4`2NNlRdlp#rZ?mf3JOoCj8ZJ^;AO0~bBLRFwet+v)k?(1e_Lx4hhfFOnG@5_UD8|M^?Xcb22xw>1tdr(1Ka?MxCCA?W3f6Vr3evuGX>q^K*NG zLA<0lXC!MA(5M+35ZrpO7)E~l*uz7VULfvF_H!I4R3ykFM+gQGG5yv9(gFGg#%;Q~ zmZ^qaBQ4&(pOhRb#Zal@A0T6MbHivQ_$Ua`(a80w1WeFXt$NIv3UkyaF>})%JH6zCosC^ z<(Y2$XFHhrg)5jkpcWn zE7kcUgPtmwvYtE+hEzJAFojjw2ZsCHuX#WEJ@Qkh&dAKF{Nd-9=^!GyI~tv*WDF2i z2;B~;X_2jzBSF!OZ(;#@*w09sWvf>{Gtg5ChTWbjHKulV=XPup5~oEJ4}I8H*s46h zTyWf*uKnSXu)4BRvP>;X5U+hKD6#Ws7(=|HJ#*GwbdBN&K#HouJ}m-}xF%OCbyF;P z7-?E}?kj+*cu5W=N6;f)($EZMrFaws(xClY2?(ef+^G;ox!@=_WzZ3o=jJ1mC^K_2 zp410km4Lzwi1&K3>8OI~{eLbnpN3%3W1+ndZq5JMa1a>Jp&=|u5O73#vxtKK3R-u_ zn;=cXXQli7-ovf5uoRvy*%zY4XZy1~T-P8rvGhK6Sea;g6=+)+yJ7KRi3L!kJ~h+U zg~TwbNF*g`*q&^hD=4V<4*2@`9Jo_$WdytrkQ>dZt_cx8J$bApilUQwPv@acXpSe& zGDz50g$=Z{c%SV|eE@~3Qjs7YuNW9SDDB+-4bxJ#WP+EBbl+nb*N7-VFsY=p)F7f* z@5-Lb;O*4npiP)F{o6YD)s?K92(q2Mt>qLM2aeoh>iAY+S>*ranb zpNOVv{(F(M&n{Fwn{r8Lu(0@V+}!w8{0%Nz#KWuBa-gZ}W5d4+-OX4L6w2K|Ge!mwIQLXe8EtLthZpz52IntsFm^%O z&t!W$r>ZKp4SQ#fQi?(u%U3vsIUPe$QAv7>#qSHT=#-a|)~YJUsP-?Z-SPZsqC>mw zGjp^jXz5Dl`&n&)!PT1RH?t(PmU03X|ZDD!txbDn*vPj&C5RG8RG4ClKn#y&pAJ1IlZ@e0Q?5+ zAta&9^yXhrw-GpCkczVOL7{iHG8PXM)TOAB>W+=ZtGF7`cn?@RV7QeGb3qHpg6ltD-; zQ8PQB43WQYW#D=g8sj#JicH0Ddd3vCMt^onh#BGQ*L8Sk#mFNr<~PYphJV_2l2_sW% zej+Ev3l;?{zcYbkHP8>x8%F>DGUsZ;QC&et=Yc=mlZ*1G z`Ow}2i26Lp$;&Tj&NhbM-Zown)HrUw&RVX2|INbR9Kb7Ab`$b=U#8zsMq{O-xD}VR z(OrhRT@SHrK<`H8ty&-FcnZwUl0=M1ZF*;mfUPR-xp?crbBX9Plq)w=VvtHFO9cjI zy)a>q&u4)|^lDA%4kLe9ety2OzGG|>_4*e4zsEl~@^rTHfIu3W3ebBWvvnLFA1|XX zUA9x0qV&8qZ(wd06X#3QTi$@5&)>W(V4{`{2w;u)0@J168tuP+1Y-g8>=r|9zDytz3$idj>x&REYS}S+MbDs@_VA&^S_;=kyR&O2dn$$LTI!&qR5_e=WQ+{&|hNZ#}NAJhFg9iGAhHKKY<& z)go|MAui_{Kx!(DrSTG(?|E(;tonF;)8r$X%aOmIpA3+wdQQ}ziTp=zFbW=ujG|2O z6cc0Qpp#X+si+<=I(l?unlA&RR&YsI&v-h9>7=mn_B1N>75;LC#V;+7%gLx^ZL9EG z-3jBiRoQ zm;o(|3X56cj_{O)(RTq%wBq7mT^ThEd}`%K{NPuEn-`v-g`r`%Jd+(T8hW|eZI0>1 zu+hg_=G{8#YikS_zMy*f(Jd%+JkmS3v>g(D7a~>3|JRwgGCW3H-%bBUOqk> zg`w%`;7UF|KL6k5zjN|RgM)&CId57igwxW}oD!?!-khGD${8C!H&|c!()t&z{I#eE znCfr6P@RrLx#kh(k z%O}nAH@D}EO$W(EMKVW+XOt{Z7I$fV(Fd>succu*NexxyFLaUS9Wb0lNKD^5EuN<$YW%VI)uq2rg<1e0;)k zcgFCFg~f<6aH=?Ix^g5ll(>qPP*ec~Gtg&&*F6ev`%z~m+6HvIKAL`48fl`C6Gf?N ze|n{;Ay55J9B+~KE3m;)<8<)?KuDdX`8BSZThKZi?7XxqJx37CgL#zy5u zTzs6QiDJICu9+~9rURk|!t-Q;z4y&QMbeY@Z4|l}JOL)ACKL2*Jahp-%2_C!%wXgk za{YFfbQh5X0?{|^5lj(k5zXW@fTEhM^L@7pe6gNKF9KFTG%fJpulyhUF=7yZz04+^ zb{Gur2$MB1zys^ zN=OBW7KhdD&ovH^$TX!Ng_;?p)0Gif{UM}c3|Q9@Dz zg#Ck(khFXzktNHc{p?8pS2K-?#q=iwGOfPL)UfNRdWx zz;1shw|~|87hKf>xoGW>un*B%jv4>85K1F(&2F5Wu8qX}2 z;(0p`b{f#j#@E(zy}&ysO?ZmR%SbHHpLyTd%}-2hD+E&i_IfdvFuQf$EGMrp3y1+8 zHBb`yZ6MMrYL})^Q4I~!n}cPxIcTik&UC5&2qD7rj$j3Ju)kYP5i3}0n!cD=WP03; z6;6EA_%qTINi73Z<`8IM}=&P#M6PAvSzlMPdCw53M}O1;`I3=skGwK<1Mv13InfVA(_TaCXiNq&p-N3zIcl zL5=0NlIBbgA3iF1ySu%e;pKH$T>7D-*BSoJes|_WS65zkd46b3Z7l*zZpKh3#pjdX z;9tuJXd?&3;-RXaKjRfjFKPT8j1W?g&ts+F0GMi;UA(O(F){J#O}5ra+fdJxn3tdM zZ6cR1{T`W^n^W5W#Og&eFN;=nHw_UmY8VooE8V8KfV`nT&J)pfe*T$B{Kj9!R^Q>> zU43b}-LJxM-@Ox)yL$_%#gB=OmVBlNKne;fDj$G~->=R9%K})9<>50RwjxN`RG`%; zbno`I47#tGTbO4=0H$YQ|Hs&7{bjH@i#GxYa`jqY`?wz<2^lLyyQ8*50$n{z)+bkf zozx`c5%#EhSb@%e&EIPdtiw|rBtz^XTmP3e!%gsao*Vc+r18&cd20u;wi?lbd4^9Z z7)(?9g8`&I;>syst1&r$dUkde&dIN(puqpZ@!mv*$LT37Di%xn>C-T?5epriUpe)| zAq-3z5Z4=V5RuZ(bL8WjU2aM$<0vU&UFYRN5l1QTk`Lja2mqo??<7Md<8ixh8wBxP zvki!2Sr^}~Y)w;(^Mt+zDrTk=3*QLIh(5a2*P9LA!#WhMC)hbpOG||l>b&{EtYPT= z^k4iv%L~|lXsLXCeF2uChoWQ{UU*h%7;;Ou9gw@yewa3TcxERU;wy`bf3UN&K>b^Z z2B5Hs2>e1ZhcWHm*?&_-z4ypp1T}rGK@doU4#%CL+_PO?lWh1Ug?_4~oOz}k;yu`6 zo@<39TU@%{U;0z{1={{+pUt!#vxc0_t-Z_v_haJxjF4K2)H}kt@KcH-lxoG#p9L(8 zAVa24n7{ErW=+nn4=#TqUjBYqNmZhtsaqNt1qd88O zsh^u8DIUHy?$QO+qSXxo=O|J>_pb?bkYLZh#562?GDK5vkda+oG%3dRS2uDwA#dC+ zISd4Psl-uGxOjXAs_PZLmw-Iy(zPfhlA5{^BWg% zM5^20r!2NBFsjtW*45W@NO>&m&D!pHpE4TTC@1R*AO5Cz`K-SA5y~T;mq#iU|5LoZ zRRAimMlW8pj?7<=X$^V*_6Nph3k_yVi->}Z%z=U*F~ZIu#`%<)NuzIS+H;e28-8~T zk=K(Skq;ypecL7~j@XidGyO8foMTVFkrJdR2K>5rd|U65lUdzhQx_ zPYKJogr~tZVDo$d7_ntPQaw@p82p(!I6(FpjWsD_NH7Ai#m>P_ndRTEYd31EC@X81qW0{D|0H6>%CfU6JFO!@Bgn2)dDQ6Ioh?Gqwq4E%1owsihY z0YmZf58QY0!T8_5zYHe&aFDBUMAelX_(Cn&kuh8V^;=wPr?;6j7j2=?*9T6hYivTd zo)k^3Qdr@%!A9*Bbq~BJ#$Q)td?_~QnNl*LrkBO#4%}v`GAG|UIGD+ZDJa;xb@vpW zCeHS2&Uw%bRj&27}Dr=rQrVY zHbza5&FS@NDD?Ure6(&2IiY4AgP~M+iY6vkV0p_G_SSmq$llocQwy_p{jem zwKxV-C%wJ+(MWRJ)_AQHnWb{LIr}_;%h%&<)q@CBKHK+C;&Vu*YbdBY3OcwwuV-P;nXh#iK40o#sl2`7 z1TFt54SINE%CsQPb!`7^-<8{S|7sr76qb*EcM#r)%UreawFpqr&NpS14q+IT7L(63 z-7hjC(P}5cfZCJAEbqm1K1lJDN75=>65ubxsFyJfTWPf~FPG-iZ?tbPTRg7M?{tn@ zh}i}%dm1nE4#MoVo5^n$=P|hFZRO$QcgG&SM#)Zw!)DCL7I7LaarLn! ze}4O&3AV|`i}8d=E&G$!yHTF0`m56}C7k7fl@3}9#0_E-FA4A2>@Nkgpt00n%S|-i zH3gEdI9>jq=n-l9)T`#I6~R*9_9?egJ-TcNxDFzI?a#+BUFaihc@Q!1J9j zNO=HAc@^3VOR{LbN133b~p&j?|(Ffxsy}D$5Oy*NQH7`W7>O zX!0JeFx@WcT$|`JK6}Y>tGXE9d)mfU3^_kH5f3L#x|Ss5Bmp;Q4*-UITZ$57J<+E5 zvy#Rer!CvHI3qt|k9DXosG2Q(I(EB~1xdoyglU~efSS(XCay=3DNjHFJ=Al^K~vye z8pACqY>`^Lc}z(Ij|(cum*{pjxtPX1!|G%bb2R*k#UE6g_BdicWrHMl+``4KU$()Q zsR*=ettLtgK24Mv%UAqF1}*Vx?Y6x)Yt@mo7$faUb?rUiA*gP<2=yI-C6h7No(y+| zYf6lOy2PYlmXTw498?eZf(t{B%C=&{J^1;r4_X$JUG|c$Q?!Og2KaHvTuzEDxARk; zCiG8mTzp*dB``xb9k08wd$-+RyN9M#w_DNWF-4M+DsXpwc96!m7kjNx@3;~n-U8x} zgzk6jm42Tf2e(}w{ny^)w{-khJEig$#%`dYKOcjaT6W`0(5%#WsnxRYwk5wvO@3j? z<9N__;Nbypv24Tp0s5^y#qMt8?kF0x)ZPBb!=f!qVLipe9S*&_F^v?w_!A2E*)r9- zbB3SnyS-9(pEOuJ6>w?Zt@NPXnXY8vzlAUFNw>`3aC@*jP1)_1!z92OrYte3dlX2U z@~tBIMJw%QJ7m2+ z>)?My?rM2d-T-hklDBuiJ&wZfhUkx7H@wN8fFODT?yKnv4c3eBZ?%pm#U0I{s5$3_ zM}0xv_0fUvv;{g$<2>S^6-Qda-AXq3?G2n3@)&Y;G0+lcl#D&+sJFiYkqXmNlL(aV zYq-dV`aTn!r9^jp@oFAnaZe6tb8N9!^0 za(t$mhW}zkJ`0!n5c+#%|Myb3n&VPr7N~GI^+5>drtxYIv#_p&nuPCcTZ_hw8lV4g zzh=pP2Yz_a#xjF1XtSNwGUw((>rwl%Z4kI}v}!n3W{jgB;qjEy9J`zv$@YMS*=MY2`|L4dr_0&kI7h;PNrppxXnb-At0Nl@A|-f|IKZjkg&>rzuPw zjio`;Wvzwx=d9K>A~C?Z%7(aFpQr>coEN-Meyofk7K-JTfu?)l*n~va01imnNVr;v zBP*!bgE$g`B0h-Y6VD}d#PQMF!-t3?@=rM8=brauT>pRi`R}e19j{s2n6YPZt~!Sec<2M3#R^v)BZoUwF%Rr zH!xqV!Y7!yhHl6CbgW3PBg}H8GeUw3{LHiX7d##Zj6Za(>t=JXlWC%33c|G=*CyAS zgIVgZ&i1AW!pI@X&mV)6wV%*6thJu)PPa(!S97a-%!)U!-`%da6ev0PY6)ED9dxFJ zOI>QZuSQ$K4;Fi$AlhXZxMV6-*I*g!R?-;chS=`vd zN3(A29pOshKA^k)z65H~Lr$Z9{^Q<*0*54W_w8o%mS`pQ=lufbGY1=b;r>~m8fo2O zJ29>%xPH@nFbe_^>pw$b6~DUEYV=~sCl-u--n;9AslD07+beQZhdX%3V)c45x_CHQ zUoWCn+A)Z3@FlQ7_qfhr@jcy!`FD`HFuJbS&ZD2tdT32FrW8I$42)w>@a&+(7u|om zG^W5w@WIUSfIsMegA&Am3Bdr5=Xf@`PD2d-=Xp`aJAwlKfwb}X|1sX0=3SX`Qs!)+ z;Jp|H7Yg2H^&`%!Nka^cJ?UHh4_a@)|5=G$cd-y({QrA^{$JyJ>o@KhxIxqFz-{eG znZTu#G}uPGTYU=7D~ojgB7~jE=L1hQRx=^dU)y}qO;mfb8a7ei3n!7a*XUqLfpfVarV*`JGjtTpgVEA4t#>- zZvM-I$qGA&MFUGRjpr!&r{pp};!OV7GiS%C?W;HypCtyOBgBS=v|v7>%D|$I%~jY2 z!)hBZ8$jHW@w>5`fy@9O%=E;GsX>&Sd+thAC!glO6>&zjZK|@==~Y9f%EoA~dEQPu z8{*nj#oLQhoQE@z@~swrJT2MX1hwV_Oz=$^3|3PYOw%?`*Q<6o_Bnw&yN?ijtR2Up z!osq9>AcgX6v@q&(U+i>pp}4luZ*wurEN@LUFPFkHl`s!I+=UEUp~I_7&pCOK9(3G3@*$rOi*MOXYROqyJ4CaM-I+X#+4l3bnctcpJ^sCJp{61 z&B2&x;Pb)WVFJ};(DzfR6rcl|UUv($^G=Vx z1b6f_kX40bU7El~%bn;oI?hcb+F-wn2ij z5R^%?-h-zjxrS)*TC@hH3mZ!Z*)&`(&h8Me+zGkvYxk?w^h+CZBJe%Z?&$A6`8O4f zo4obLw+bPk7*Xz8``F5tv%aX+Lqm28-H_#-;%~Gw!v1@bkut}Rt3Sps-elZH zRV?$WL~bVFJN?RgcH_q2Mz^C#gm@&z+W4jJg5rc>Qyb}5$s>jm(6z-brD@{Pv(O4X zYw}dCW5ij`Gi4o zv@5S8T`t57VMMOIEjQgrj89aZQ!j@t8uJxIP$J$IC1+-zdW@2UliWk)T}N=H_Rz%F z9Na*_9hPp*g(J=Y%Whn?awWzaB_*44$FqhWAkUlBo zyj{_iv%Ywa*f2xzK@N$VS%4mvrT) zH@t=r8BK&yQ_y)2X@|^X<$uCSs5kb;@n))`pGC9ja<{gtvov7oU*eVk2)R`&nM!iTo85 zvC4o>JuQh*<#RbyXiNqL$~6+W(exM^b`q9OqtoYPh)1exq{*iRq5s|forUBMX|Tdf z_vyPclAJ`@c!R^wF7i8l`Onu^Ct}Yjb0$y7F*4U4f$QR{m@wt*RWNnbSG1_L(4n3IvOtQ5gtZS=Z-UPr7>5GsyKI_aZ7E35HL3SN*&piaw^Q&4CiIu zoEqO^!0pGzH&qK8-8M^~n}hZ?#xwYFL) z?y`jXoDdW5oer$SIVD)FyjYHZjQe9Se1+jOvu92)|45MaEHp~*s?4-t&J0}oXWGmN zGgO1=qtlTZntcc<1m@>@^x37!-d{&7OrAq; ztXJM=a*r$IHFtL(>drwo?24G07;DMSE7Uc^xVf^ppt*j`4g7Ai0i(om&NiRk*q1*1 zVe#LcaZ(IPc~2C1?5{y?=ww8CGu#&z&uQ8W>SnYow)jhJD#caXS@#(_-Bk0>LeG;& zos3MLw80o1p^0XfSDCz-Hcm&tJoFq2ZiA^oNzs6ax+=oa;@K@5k=g` z7XQ3t*U40|@%BrUO>4Cv@Z#m=BH86daJ4dslXN>Ak5|^ttn(Fm-QT9?LzHRJN}{8S zI^q!TDYhD}upOf3*X&-fK^qe=KKCdc4FLA9h>-HMyi~sMF05dsWHd?@n#bk9R!hN*swP7nL_7 z9e=eklvbE%KeeUw88MUyLzNXsc`PaTFGl4fZ~QQbX{7&*=c^H%?Kla5&qgAf8I6FS zzb|0Da)2IJUS^(hQq+7x3md1&bh@CgeiKipA+M)*lXC&TzY5_PzmjXP=#lKcG3vQg z9>|!sX558S23DDQt5iDd&!LPtPfSZ&OR9C$m6wMZb8)|f!P*qoc9**l@25aes?Fre zP91_VQrvYL;qT$p0m{l^0)E5Z4-+YHBW>FyB`n}=5ZE`~I!|~kyp>$U8_(Cc8U)YT zvK)T9)i3%h1J+S)3Ux^&O0!|&8`ucVfalg%SG)Itx#xaSwmNq*%2n)gzE{s;K7Skf ziP`;p5~$aB-$xk3!Al!8<)$9)hcADa%{AOy6z)xxGu-wInP@PYnzAGZWvpCEtAx{T z+{*1~8y=R6I2~3OVwj)R?aubtE%tRtM!jKQyk1tJhKuEP zj7|SUozrnUL9)jl1OjhzJC9#_Rp4uol<4v^!kH>_AYU&z{#I4e+6AVsqmeM3{`LS3 zz5ZOcMmkK-YngPr%8V`ZYW1AUysoUxinh;YT*b)zvdKW{<_yG*>`rU!_N!j}M$0Nh z%UfEXc1b!^dgd`ASiQlU!NZ$@^a|gYw$3e##G#?$G7)%V+KSHV=_zs#lBjMW9f8cW zd%Z^ODeBtwNzi`1wF|qs?nAvROme~NVPfdcOz51;%liQc@r9`HnPjYp#ie$Pzv`aDQ6J`39-J!vB#o+Lp z>eNq{8Srj={kY;eCo{%jtNrqHuIG9C{^l-jb8{8t@nFh$Iw_RONHZNJ>@8=eP;|}V zQ_ALkDYhyk>?WPg&uq$_tlKU%cOgI7KH_poIdOc0KSwyb{-G^@sxFYS(Vmc{S1r-y za7x{ck%j*=(uDdIO`Bl7w7Ijywf2VV#X@VU_|$jkvK--NBH_sT@Vjp$pTck@OD2Us zg%?{%cI*h^9fy6gLbQU~v`Srqe7ig~Y|~EvRYABsTm9zJ%Of;mH?ymHz1qp+i8nT| zmoTW7|L%_phu_N?u|Fb%2U@?iDdB3lnpkO0XF-4dV7ZZVO~162;1Uq)FbF2cr*}^$ zJts?`^`NHFJ1K9gSowsVp{Kc+uE#5VKFP24-pOXmjef1`)VLgBYq*tcw?wHYYjljY z*Xbph>dEGI1}4CMFeIAk7~EO(OxGQrCGvJOC?=&%#+qlfbIKB_ZSBtvwkZ)GX8s?Y zU3*xPS+}2=?=)7OtMom%h z(nT=55h^GQYRwmrDN^2E1;Z&7L^4xUQpY*xb4L}KMlYitL z2lF6o@}zkvw#Q&2{_@=;=}39&K+-&FI=8X+6xu0lJ4&K&TIEr<}yEj zz-A};UD&6o82IIaD}DQceSXD%-Sc;|bL-l=t;jI6hP^*YEOa8$kZH40$*$SqT&icK z3Pcpw;2!56sRj2B6h8z?(&~Xr0IQ(hGWBY$Ra5fv(7DFvO+;6@EFpUO&$R7*OY^C8 zc;h*1Ag&jyX!fKjgI~ zEM8Z6tBa38IG&!usjIphJbU8{!FBWU7uAL~1%Xr#6mYM!F9kMu?`v;t*-)SWl6jOD zg2^MMb0;$tn#FWCTVTP8g_D;|knpmvVx6U7l5a&>6zlMc`|8aMDUwX-^XQLVd^QES~AOZn#aOrc8sGQq1m=4H?tJ zvJe_H4q%FBqzRoaO?!DC3dhlNY}35qmY1?yxo9xF0`5isqTow9FbRoy;F8OR4LaFX zyIMBm8a~R@e6KK>O!Cp(FMjiy;CL4h+!5RHm+GFvQXJm2q3reB)Mr<%pb+Emf!Opb zw*ATRhW1|m96D$_Q3hIN%3#{l%7}%UoJ}koP{wVe>qUzzC}`vtgA4i_4lm5lLhZ>@ zdtF*bfV=6mZ9qn_<55@~nzb6TFh=72c7?lwqYY-<)p)D-p(aA53ujJcn9ae?z~nDiw%YZYboQlCARrVqry1M+Gm_ zPH_pxwkrvnVNp9qa3c%bA-*-fzx>pT_8A=Fmt(Lq>%gM9B!U7v|En(NpHZ7A((6QB ztVm&TRCvbG{35i3weKfL()OkBSmf|BX-za7!#STRFi*N2^%&7(=q_N@$yGLuI4}8? zZ(=c)k$ngF!>-9ux4X*4FALktPnhkUjc3sHZ3&0M9OIPnIN{h5HQ_4cC;)<}gZA7k zQ9i@sgLtgbf!HHV?dh(2y|A5we#%xOm)JKDjbYI@-#7E*-a*oovC*C2QGHUDLvC8U zfp%e)*r>>6(fTgP)t+xRo|2uxR{}^TkRQ<3BmdPwZ|!o)k3^lgd8U8;p*8L z4;v?2##``pafJ~*YKneXcEJ`uj;(_AE@foV^G6gGKL~?OhUI`P2BdMqdQmXlHq-2x zjty3fLVrq;p)#d50~ZS*up_;16#LzhlEzZj&YiF=YR@bB>Nfa*WZNCN_?x0=hrEI_L5z+ew?#c%5BVv?5$j#|RcXVIFfRsrE?j zL|1W3YU9Ln&&6P=;Q*FYzjrTG@& zn3jS|tpcrz>k@`$&lCB@Hsj`TB11DTY)C0q;=m#w-#prL?sLOu&vNy!Zv|7po?Fjt zYF6z4i}k8b4w`fLnP z;gx8V;JmT&cBF11XTP5DC+hyWNJ^JhVVE2SIJ_}oY6ncyWTgKNQZf{r%6Z^ox#n~_ zgx0_Ry)*%1=2RAD86yt1#EOIE^a6fzMWOre9QIC|Tp2gwzGH_~cDI;W>L8h{_Qq-DAQZ5|l#0{OGo6wd7GMbU5YvwQZDgf zC5?CzRHETO;-WJB=1x&^=Qzpg1F&$*J@dEx!J`BM)5~{+v)z)59=IefbuoY?DDUVC zL5Q0=2^YZ#_h+qBaI{BJ8*^0BOb7cJOyDdCUZ}~gTJs;awYu@5WkXtnTY9%NAv&nG z(;ZUNJ?z*I>3F+&O8h)XQILBY)iNct+{zqNey1 z7xlSc#dD%E2XkQ6;KATy&qpa`V;aiNhhvvrgNHv0j78N??HRva?V@Xqv>TU{wp`>X z_a$`ZkNBvA7AHw~!MguH=*0Di$qus`2B!z5?q!?O5K@i?|3l^jXCPCj%vSl1? zb97{0M&y*tgn(D8mD+7$E03Q+}PkLvv>=y`d$j={qUkva4?E z$s-T^Ed#}re%>*Xq|~=W_}J}rn3s^o4M&0seIjvc*g($xNzL#YO18;eMLV0jPNuVf z1N>3IYEcy6$NCT}=G`d;k_IOh;(EZvx3%dJLrB+4&gfLQops|-pyR9tFk=Us?OuNJ zJbCn8*cRXPJYONNzax#NNZ&$(*faxgCOIICuY;}|Xb;o*MTjVkrWY*oU#r6%m1>e&xW@Q7j4vT)m$Ju`9+D~#*3ij{G86N6PxxCqq zX1jD?I`zs0HPjv_X|rYI){p{FRKYqPkU6)u@uEJeZ~R+0W{F+;HV7Xo7ByQ#LB{#A76Yt-6On|qj}J<2$hRy(HE3xBG# z=1Np^`GL6m<*eqVOehci{cvem%hWF0R&qLSu@pnMbW()%Uot^U@EKI7+@o zfNbS8`;`c^PCJ)#bAV50row?f5-EFDy5i8%%P+s%YQ04BLiFv&4 z@`Wj$t<3Ul^Hk8JzKwyyk@~?)Ukn{qCe@)%)8KKJ)Cz2*7RV~B3{lR2nKSk`|L6cD z{qj1DK1e`XjCzRJ55C{SP(eeto9K(BoHZ}4B575C?num_Ie2(JJe)E&7%CC5V^8V8 z6NmN~O7~~|Q0tPhR<&#MY(&7?HmCN_yZGd(EA6doB4LS56{=`F-Ww!`@8t?}mzc@W z12CDBJ8GOPO0Sw$PNt79aK@WVS1L91McGIu2rUGdF-)oKvo`lj+-L-7`E9z*VwaaIa!~dkQqS@Xx>`1i=8fPSJ z2vBIec@+;iM4TYmwqGvt>CUCjp({g+*8SWp-eMeSb{*3gmPeUG-5;Nv{tF#FeFSz& zj!ITXPaElEnucG)E0=Nj{{qD#u>E@vRC5Seq3td1$Ff})k^l%V8BF*r|<{yeTgM6pNcnvBDMZA zdnNwIe%PMWzqEoUlc-*CM&`sY;e~>Y6Y^5ZQAJ@z{i+tr9`-U~EwuLeBZniXtN?ud zjB);5IrfS}O^WZf;7Q7|SK{hSvSVmn_d2qnzPfm}(#cseJ|IF#5%??KH3|X~XTo{| zIuHWoPLjY#?IH5;Y(Qqb!5qGdq(3*`CW3=2fjZ98f~RIyfH+?;!yLEgmvfdwfjMxT zQ7KU|tvjO~52E)5QLFAeS4LG|97vu$ zkr0}XD9v|PVy^dblunl=!hE*YRRM;FmG*E5V8iOS>-0~=)m&Uzd3oWwW`C=mPpA4J z7=-wJrZ;_{HGn(C7uM3P!6WN5_lLO^NtzzR zy%Eq)_UID0sfSR!v2IhW1CsQRvz(7y?Ft*X@T0~KR_pK`DGRLEkYxc0wBt$0c;h0l=-XlJ=cYrDXag`e{9JBIwUu+S-xM8nn#hqLAEUABm* zBA9H+!!vk0QtqxuN=OLqIN9%p%wgF3<-j;85MGouSVBPHqytBtK}{X;Q&`vEEt)o& zeUlC5WU=-zDC5$dRA(4OYGbgiiQWIVl#93DA}AY91npVo#yY0HlM+t4GHUr}#r0|HMmCo3Goe%Iox{@XZI+ z3~PpU)eF}}_FE5}Bs`#K^iezs)YEh1 zaswf~+_0VodJ<(PiE;sS0@&imlBToVB8Xdyoa)8X8E_(#4EoG_tH;~BKQi~$`3E|g z`>lZS5zXZJC%ow)z0zn$iL%=${-8TYK+QSFJRC4N(3A9cLHD4I==X>5_X`cVsvW1< zo)4K+<}8)k#xb~SXpVrs#${edRt-0Ukwb)Jh{7?8#; zTUlugLaA(6b>mT&h#&`K_|%M`@EVyBs|6PROPL=?o?TP zPLyZ&eT00qBVY}gQc-94$hA@fTLg`M$AP&}MP^iP!#YX)Nl)juIX9W)yCm@vY*3y_ z>v1#3MZYs8(`Y?!92Ak$mNXqGForr{VNKHxPPh*O)C4lDy~%+|yEDy*3F}TLV zbbGmXP7fYncmDbJFp(9T3~0o3mezH)wX?J`1>5RAc>P*ntI_$<|EK-k18zaSkEjC# zK>I5zOBo2HestvS%7r76QM@ZkU5=4fVNZf-`s{P8ojq8-%9&emKciA>?a z1L_CzQWBc(M!PMjDHL6acRu@OAk`rf^RFKteF=PnADSK*`*Vg5Ug@BBz+qF~c3aLr zZM0EgLq-%ruhw5os6P=#QeCH z-9#Y^o+KJgAnyNPe|l+_@nrRInq&S>krVthMqn)bOQ-^;yZGOEtzfEB`6MKi4*y#O z{$t_RG0cV7U*C6>{(ge~%qvdgfwZ3Y;nly@>OFe4F7P!4<7qj;-^J(DzWGZ2AiNpw zAnHzq{$HCsR_VJB@O38@f%G>!PfE2^;giRV|4ov^+kWLn_EmK8r}ORJmZ#C*nPaEr zrYL8eMaw^rEClKZ-7Gi@p#D7;InU9t_L|(7EXUbKE}O!|DfJ$PzkO=h^%Qd8zZ^7V z^a&w_cZP*xl>gRq8?W#+tH=?}*!l^a? zx31g|gUg)nHZn`R^&{c5pW{!3;}h*lauAnKzVc@B{5P_dhhFxDLcWvodILSr!ROK0 zAcb$k;-i;AG;ux3X8*QQ`?iL2>?{APNhdTIM~ROveyShsv4T*k z#7}x8IYDZ&{77@a>?ba`P-Pz7-$P7SDxu|6YWxD1*lUmf=}nNTGbZN-EcjSaQ%pz# ziD~i}A0qqL668fCq~Cwry;(M8z`JCmjme20T4q^rir{aNkG;9EU5-+DUMPh9_XMhzYvz|MgR>L6yuy?S~>k5e@#E0Bj;N*`2n|o4vq;y zu=_FaiMo7Gf?}s211-+);kE7(=NXMYmD;Z}oEeOhlDL^dMZa1YMM}#mbxI09+Nyqa4t) zE&9l1!DRQbM{Ory=`v$qzU5xlP%Jd-zq^+6qASqFFxjvmpf8o5ZoY|LB0i86*;McoA_EB|4xMJe>IN>{QeeO%kS6m4 zn3Q>o+HqMh#C=RC5U&nwJC6offkS2N=FPv8CdOxPzd>TqN!=pHzEUgWva|ax3;I@# zJtZs#M^nA_8L9XZ2qK%z{A(VQk}rO2;VP2img8bO&3)PGNY_x+V}VG1kUt_At|Z9d z%j8PB!`ZI#-eEmWNSd$0r-VbxrcW1O7tXP@*gaxPMPxCdRY9Y{~t zfcFR#6wz{YV@Pa-a5V*j2}^v>e6M{Spl9HYmQV()e-@IeE3NzD=tcLWM9ewC4_}Xm zPHJt3kugIAuYBWRnr=YJrrO4-)2XrpzrWH% z{;};=b7#JOO0@pfm-dPNAqYZd#Ob`Rhc)`|O`HWLPh-s5Y%{%>_jI0b2i@$>A$iap z_a$uo@%apOuj}P<@I=ymvR_5AUkgqz@52b+ful(o0*zu*@Q3!)2 zI@0Y$fl1(Y!Co^k2O-}c^kc7PG#F!vk3o()W{{}1uGep-XY-wf^0BX>R;L2Th`Gs* zx%Q6Trl5v6HvPX|PcQt7KT83Hef24RmaqTV;DUu|e?KNpRq2YWcb!ii7iT<#in#E( z063bkdy*!U>x3ZJ!hlUu;4Ol0Z$(sA-;+lq6r{T>ulV;Nv4qL0cCnD(wzuc;kIyXI zUQ57Tnvqp|O+g-b-C~iDxg5#WKv5Z8FI*Y3r-TuFpGZ~(qF=ysryTmnIWySjIHTCv zFpu(>{#u|`Cu@N}+M6~X9+85Nr{O%u&*F#ryV*tLNcB^Q!Fv*HR+4eg;t1AXfNd#m zINg9}uleSM9w`cqW1WXYfyhVv#Al9S|$XWy%rU*|WKI zFS)LP;@1NFfNh?3C$Z|~Pn z@FXjL@ppualXuMU$K+!u%y;k24GO?YoiIgJQ0tD-9*w27bzwSH1l0hR zm33c?=J$9&gsH@ZZ^^3Vy)q(nGA-&!?3OJp6|+}}4wA8-R36bTocz7MQ-bo}&*B&Q z8I{sGyytCCxW($rmG`r= zonIivI8Fp#_=H8ph=h3tL^9b8M^-YVmz#GXfwgsUp(1~6d56j92Z7=hm*f!V z+#Ui>;k?MsIo>V84{M>^PnvWUp@5Y(KoIA&p%7eHiaeb9sDF{us0T#u34wv3lD~-7 zZ4tI}w)3_FD~Q(lq0u?lYd!}Iw}=P^s*EqLf3KlaLDgZ^+%m$c8V7%$L7>HUz`fji z4tWwj4jJI$86qk-4W1cGMfg6c#4;h^>3z;6fy6d>tR!6LBOYf-kmZ?9LN(bPSvTY1 z)6{htv~|(d6ckd)i*EAv?+$3uSYrJ{h3dHU>vi;wfh|ZyHqK19L#zgFZ26!LaCZt= z2CxM;x=_PETL=zCPqP$p<@-V04IF8^|7=Rn>+#erhzIN_FN1QQ$TBmVpv(`p1idy| zg2MmqAcxFXa)@=#W|7D;;{4gM;z}n1+5W<`{KP_!$EeN;qdIpqtZOJj3K2w0Ld*0a zQ)0LINjd^fvd%<3h+b~{O#^3Y1teV8Y-=^weo_OsBkvgOW>@O5*daO~l=-_0A$-mB znZS&&!EVk0yiy~+=m)hyGs_T!(Pb)P-7VH>w9UoVRpK0UvrOSX8}yAKNn0VX0@>fe zh~{!+g>if}DGsa!#|gnM?u1~CYTZj}vnofVfT4kvE@&pEbd%11?P?FV_$PWiMLT=GaVBrhn%PWN}S{%4yOCasxgLe9v7AeuGA%ikK>@CC+w*PGo zP5!xWdoR&JVs{u%P)>3bhX{oB7i-k9gy<~%;P>v^_rgngQ2_}H>;@`!5l-#5(c>0! zmfR&PjBwi}iNPl6Na@knj_?tqva-{$j7Cn!VA4D}B54=%9# z=#A)gRw(1r|E{_3iO?HlITQ!Xr$MY2jv*&$B)tA&cN?Wl_iuSlQQtblgZ*9M$%%)29D) z@SX5!&G$JsuJVXIgRwL-H2gY|0LuQF@$(FbzENJrF*jr&hFz=x*LC-V%L%c2>r&0n z)Q#+$ve?*#9l`B*7U&By0*sr<{+Gf+0(4P6>j?1-4NmqCJG(L1mFJ0SEhX$`(eq-RVn2TWJw@Hw*_ome^*s4eN7Qm> zZCWvoD1Hq37NKW3#-11j&d%o^g=F|JWuz`(Asqa36ko!rs5qe>PX&{#vJaXdJJ)%- zwOz25*2b2_o}`d);qw~|&X+7~jkRz%^l-n$%dwcH_!$`~>B)l8zxoATT3cJ&$JaLm zxzOTe+Ej7zGp?M|_qcWgYYF4T#}K!vOE3kz4xg$EFs^4G3WkAVYre|M4ClUlecSG0 zOFMyCw`LlyRcEI$J=@+QhFh0sQ-j}55fQ#G=zjQqDYL~(cw%yLoeVns_vPc_r(IoL z$!Tf1Ovcf5xzyIyUN@a?x=JiA_|)0XH=0LSSd7a_ER!qwtcI%alKQ=C3FJD^s!={tOH3js+SyFbhHf|8}k1SwDN&ZoZ-87 zsm40`UiJEQ`vk|&guE;4hBNUapxon^;Bi$?m6FtuhCk65MwIjId~B~fC9t7Dxt?;v zG8j7mu&*Xa!&EG`I-G&!7^ca^! zdn0(i_sH*f?_6$P?4hE&ML2N{vlh}TU@~3h48lyHJ1IT9$C5mXU$I(1T%3k-ikF)T`!~Re+Gt8ob0v0rf=>Hu$YaG&ajp9{xRrkB~ z*Xl>yXv=xcQ4sy~)EF}8Wggbn`Xt#2?F>h2>f}M*)w~kZoE6u|ISd&pM_ELlIP2&tM)`t%_(P`<;6txhgsS)byv;E|vzCMDjf5*QUoR*e` z87AdOS{o*oP+fgjQRd*B&ebEc$k!E=X_Tc}JQy15jMo7tTO+n4sAjY=-5^NCZFiyW0)L;B5Hnx8&Y{^-YXby+Y zPpx~uPblf;B4z6d^3HJ)ualDnq8Wx_+{d2M&X@?G!7)lPbj$5|jAUbYf=c!45wgVX zpp_t>XwK797j+$D*0`skO9|4YhBvckuyg#WsR^db%D)v zsRd(`(9Soy#)*2J_rmuOxy{WYRyw)*b!B7az^C39p}Py!Jst5M2L_Z#1irLACnD-g zDn+bb9IZ5S1;)Bc)8oha`#%zN-A&mzz_#A-F9Xk=M1l^t5smNR=vbmdy~6|i1#i$z zVa_66eEJXg9juxP;eaD&)P=y|N8i_57syHunjTZ=FOHnxSvGw&BHi9$U}DuL8h|nR zc%EzfJ<0p+Lfme3e1j_~Hzw$E@}XgG9cDI`69xhYpkl=zc<+!Q0}dAH>gGMG(}gYJ z&O5UcX<}-TLK;zF&ch;S&K~Gx6t}KJrXh7 z0$zw>LP7#vwdHU;Sy`L1=jrwq7;r^}s4fR3C99_xwD%GLPkZ0u%rR{Yi%v~&aB`}Y zwOD+byU!Od=Um%vre4@{oALAyEug1SSZT!yZSMHC=>=8jg*Yw9;! z<%`oRryO%raV_pxK6`hn=3g&SR9u`>QlbfMS$euP)f6K|Kc*yt5K~aCwoJhti+*E{ z!oDsjI0I_C8sHg@ZS}eS`4~rxZSmqv{HO8V)tB9wCjLU5wN8&!PhEzjGQ+068J^+z z@$MKcR)Pni%f~|*bae}k31*g-{h6Y@xAT?$;nXZ1C@7clsY{KAk7xpO($jZsQ@J5U zbJrpFiC^D_xa)RfZ|{WQP{lJSCim5~demv*u26VPG_$Ur>Dwi37`bz6A24cUyKj+L z>f`XZ#Ou^k@sX7~8#%oEp)-&h>dqOsC$n>H(F|F1T^U4GycclTuRqxp%DTGpd{?sy zdDmc_IOlv@J60a!h2v1233kOKken41^>j`M4}UfThrq~%oOS#9`es)hop8T52sS93 zJo=1>+oNOT(BR!_o!ERa#;R_lHae8`R8p=nz-@YSGB`OHH5xA`LyY(G=a#U&Bv-UNFDsjrl?``YtpxtYN&S+d9M}%p1 z>}#lq7uQH&PQMP{Lx2uaJf`gxiCM#b($UZl^HYo2693#;7g)T4hgof$R?c|T!gbZK z2bDR?w0`jw^v&7WwZ1%cad8|!o$58F^3-+RUtmAoL~)hxcvpi)Q<^%iOvd?EnS>3- zZx{C?0JV2_X5(it7Hl<$*eHIi>QybykL0W@&i>@`SYY>gYpWv~LXf`HS1x_ex8{rU zy|*IVex}v#tVZz@_gy?Toy_E|b2`6pmRpAFBU^JeP-ybsc0TVD1 zr;F>z#E`ypIX$gHRu8X5Nk^C=;Cqu!sB-+a)2a=d?HFdie&Q8jdY`wPoA<`o<<{qP z@>0;`xCFHM4BXrIl-4PfXNs;3pWk*J`^;9$;xt_EH~orD!4bcjp5S0ROB;#BezuldxJG9L3i<&*y z1QN&~kk*ur_?ZN$P^#!?6B~L*4`4JtciaE9Jf|Ozc=WWt`p|;kujQ zbDN#7lcmuUdA;L>9sENtl`hKc)5`XItBv7Mejzjc7Jyi%&cc9BR(gX_OYYX{ik&w*nV+WB_T^Q&=ooHf%PJ-o+->XZfA$6Z zU^rn`;j!>>WbZ47Ebl5M>z!@Q z6;zhi55EeEiX0p817jyK1MM6f!Zbf8_qU_y6nFO#W|fMJTIGClBM#(Rx4P)!^kXOE zv#ps8%9Tg+yVkF=?Rs`OVdu*P>`yx?kAMjji?N=Wzi`mi)kWFRDI+bNYdhQg4vB$W zgX=@%e&k~&DRMT*QF?F2)d$+2Q_izamA*SwT`2>-eR#Z_nqb^8^FW^xdZj#NzzDCX zp*HjioK_F_4y9##$YC0 zjMz~p{HFADtMq9`Ac83`hvD``U!z=Y7x=@!R)!<@t-*V=ZxNHCM+-YuTfX94ba}?QUQ_f0kI&HhqtOad8@)W#1=~al05_GA-_oDJS2}4eP zy4Z=6Yh7C>(^7%l-`(cG z;mX13j^Vb`_41Uy7ixt?W!hV+cbZySMPYpw11Wio{x>Tveo0!zQ*!jQRgM$ZF2D=4 zEL=%--u@+@2ZFK0MPfFbtDF~Sx}&XM9+#9H5ZKM~Wg zl}Dy&S2^>xB=B%5Tk;}b{I&s!-R>o1q>Q#?$3k?EYcUJ(FeMF%_toV`Q`emw8;^lUp89?jOL%8+O*tebizI7yPgD8DgexHc#qkyDeS zxxGWWHzBKX;P6Hg%?jn$ugSiuHCD}G4i-6Fxcy>KkzSo|hGUm>0JSH<=4GfTQoP=S-%vW zW9KZduSXx}Nv}yj3)^Fy85(r`1R+MSFFgNo^*E=eVQh7wQXu$E7Os;m4zu z?q0XFz{7h!f_CgiY8jj(oYs3zB`i4*(@by|C?u#)IPwM5DxGYDFi#u} z>e?7ZX%XRc4Pa6PZrtAOx0kzHYBV<&rYg*J+$Yw*FUPI-{-*`tGZ9@*OM9v%_(K9V z_WF(v@D$-lXRGw*{SOoI>PNPBwjS+Xqcfb`O-@b}Wm<$PjDzYPiT)%c+I=UccN8 zUo%s)v-qr6R##X3^4Ng}A9A$^>hP&fz7ffObn!DgrXOaW(|z95obj+uBXT{(}Gbhk)zAi{7E$B%kYZ{k``*zH_?0 zgM$vWpKSP&7C!c8`ckHJVsLaocc&W^T@Pl8S&YG9&*aYgsD>n>J}oqYj66=Y8rJg2 zw9Fv@q_yn^Bw)H>YDy<4Q|2F!KPvKg4xWZl2E5K&cE`;}^t%<@RC*Ic^V@E!ULLN* zBu2QD8;MTv3fceuq7ZQV>G4%R0Fr6DA?dngf29Zx{EBkwFf$K~JqsIuiz$&FhZna~ z-p02i`Qmp=qwAh0FMnWG2;O%uqT_cWhri!~st0}L4{>j1J2*&Hzu2QrU6)c=?(>y7RzBGU_1DOQR*?eCdO3<7%GgDwD;r4B_aB3 zpYMQVGKj02>tt(Q1BjA%GgU4T)(!r>VM^IrNs++?%+XS>(vW}|!=HM(SPrK#Y-IUm z;%lVfdHgDfdv#nwGL~Ci_u^@mZ)1tW)>jYfp&(wHDHh^6@Y@jPWW&gZcg{1ffD}Of z)qRnNx{gk{g@QKU+u)xIx7QQgQzeF+UT6DS39jHX>l1n$DsH7AL9aZ$^D!*VZ}{A! z5yU_5J8;CczY#-chz+=O8dS;+8q}kXS}wj}d{*tUCu4&-FQGV@0|Ci;a`MajybTRy z68g0@!YZF@Y+eIdUQ9EKf(3H4Lqb;VzMIQqvmZCbt1}zMs-n!vMvj%3lp?yBKKmZL zqa-Y!gz8ZRVs^?63p#I{hTFbabnh>eHsSv)_{iAD5QiIoC*Xf^No*Wnx9FkL!jJzl z@L}}B0}>y(XsQpGovNAc4y5#Utw+HHxV~327CY@LbKm?gxEWq@y}e&tydQBAh{k9P z=0>T^es{6})|e7l8fqm|*3*cbZodg-0I7eF=iYUx{Nzz>>pruI^XZOzW$?AdaQQK} zR}h64PI_wgo=JUCm0qUbk@+cY0dU1>+*eZ8D@uqntdSdSoln>O`XV59n(a; ze&m#-)9ukVpx@H@QDcGY;imql9LhQ$jn44cji*7)%a)gprWidfc^r}EvX1nq{% zXIgAx*xog2hM6e22ib&lPi}9MUapRtBY`lWzVi$frEG{N50ud0G^_=kWQL9)uG=Fx zdqb#vbLMi0TWY0111?=_$%VNUs%&7Z62P$3N@r~K#h)jRc10|zx9lC4)hn^j2O=@C zeo%QI#yR#g2G(#MV!!DiOQN*4p%A+e{qXyGmcKQ(kMKr0p@3S9?R@^8m=`(=SwN#A zuiSaROL{0U5zeZ$Hl)?*ptdP)Z=h6 z)p1hUI@GF9KDGnCLUrh~xNF~8Xm5<vzUu88P^TR!Wd8nC zk|;rBi~UZ{SXYb^Hyx>OmP<8>Djevry zHvD<=^Ww0Uk;l)#l%4Y&c;5}l2plCoZWDNZ?RnVtu%)~whC-tS3%4vtI%urcM(5?v zVV#XoqYzVTqRoQP9w{)(;1SY-<73K4R(?*EQ@QU%1W=cqNkbpma&TpuF2)q<#>W*p zDz?)=-e@%=Fk~n`yFPeSq0}k5KFQoXw}|G4e296{2qG&vx!9N3Vn+5$cf$3)TX@`Ap9VfyvWV7O?-#c6MudlE~s2_!bDl+K6Lfqq=DjrW^A>a(ndX zJLwD~V?(r^#;kNk$(jpbi^KGfSQ}xA7X=yYXdVwet_e>f` z7w+BD(JqLt1m=Lf#xJ33xB#NA0kTY(Pw-1QPJ!P_VAP(({$0C~d+}Gu1}4ooTM%?Yt?FeY*rgDEQT|Ld2qiC2W^PW0D?x z)5ITnPr6#SW3H>~zX}MDItgIf4rYOi`SMU*q?6IEMRP*;*|Hl3|4|i2nrypV?h81W zAi**jmd7Bd*Cjz2-^4}*)8XYJUHExQU=9uj>1iV$l3X3Be z!=w?jDrn#yZ1&9*rHKDHIoz=_hu2}_rg;ZVR@53ef(Smmk_Zi01__{i*qdutR|WuS z&J)=C4%P+Nfcf?Tcm7eESpjg_ugPD1M^!6Ko=y|@o1!|4O#WmL4NP=>gnF$(s<^)P&V z<#zWY9iUo>msAV|lNoIfg*idvsTwmbA=MG5D<{}YadP7TZT{fj5Qf=JQrA%=Px*+g zEf*gmpzYH{c_B@BcHZi8RVf%C)j`VzHUvD3z)}o%y_LkGx;po7$G}+4N21FcUE6i# z8|KRvHr>Jq2{h|u=P?i_?7~o2zj-m>eKmjGsojQYG{e&^-`jtL34|C1b$A^?u>U(R zNU6mP;PC6g!ZNe*X#88cejj2ZUN8z6|`U38P^TgSj4IIL+l| zxbwSxpkON9*QV&Kok>d8smI!I$CeoPh<_%LZ+t?X(hd;k9Rp68=!3w!^7=62@=Q~L@otQ9M!B?_Z@FCtY{Vmbd8r7Cl)V)~oD+vmC=TAgbFF#e zt0z6>#fN38nC;ip1qc^ld_%qw2@2e{O&8D<;=Y5YGlz|8(5?Asb13YeW(7Ci+V)Cn ziX$(op?rtLX|a=tn0|m&1rSr9(>(eo>!{~ zkI7^9I2|>QrQrk;OI7+OY_+5nf~V_T!0TF zAS#Cy+rhv_{6;bS;@_LK!!9WPN8}M{Y-|$Tds>n5WE}6ATh7A3@WU@DJ)oEDG{GIe z-PCOP`-$>QXJOpH1vSbArk$g+qN0=GRY53WiezJkn23sbRVT!O zg@@?#^#C(3UE8I96UAFAK}--+9fJ@u_=!b5nZ$VN^~-#Ki`eV&0su^6)(?6-fQ+n$ zSNvaxG5l%gh^aaV0`xc)4!1vU(i)97nU10+eie#a+l7i(wf=bN{nhXP8JnUBK{Y&v z;2=>O#|@);$Z@KbirHIb_sp~xBuVcC?(S<765o?(A4II6;oTi zwzLg_lP|w+Tje_33eXaO;jcZ@Q~zq_a3$5l3RV0+(DL8BPRM$S>=)mYBj(<9U9w#| zGBQI%oy@vkr~Ff*;KnP=)kGm$vsy>PWOq6f_{{0`G3$Owqj?&XhsgBQ@O1pU-yDVt zt3)Zh*KJ%IzlpP6#j^v)%^)>c&qX{kxeQ+4g4-$DRR52^{WUuBK=j*=pvKPhNem27 z2Lza`Z^>5Kn$l);+UpKPthjc7Y_|p=ZO-eJ|2KZ9^nJY*bvXn^X+2@)(OLV{f9}b# zOqv7So{8-@NVYhVJ>ueL?@-c$kEf!HOkPMLzic#loM}(AypK;y2%!~vRdrPkfTzYM zB&=9#7#cE!U{m~|a}GK(`hmO4TJ%FS`XPExLkfrm%q70({z77GgtNVbRF%$|ix z(Wjvgx)^vZejWf>og50YejgXBl93AZt?bVoVVI$iuhNfnb@=?Dp%7lD#T zRz@cHIU$i;Pqg$T)uQSm^%^0ex?_zdK>p4yqA1Yd8f)Y$#o77oKyiwh`;*FJsctX) zR;Rh2)kyupvLB;j#wv2QNGWH%mTz?4Vd4BGa1f{Q53-0_kyS89-I$>e`tFt}Uw!6XmKHzL~T9>2__T5@9 zu}DNcKU+UM|5i|-s)a3g^(kEPz(~~m&HY^&pSgL@FQ<`eOB@y9kaqt^VN&jG=rnr? za#EV*_8?FcIXip8)fItDEw)F)SfObge*Z^*fd&E54hdieVYkY%`1j8+FwFPooYt;v zh!g-=1bH@=TV;;mD=aHY+MjRi1(?*;Xaw{R^4)uIe<(XsZ&fV|r61&$E~TXWHNmYS_`%*Pjp+nZW1}3J9gY0`I^^;{<^#Eeo6L~>p-F7V0-ae z)8ZxkW;I_t0R*daU!4_`SegC!@UhtfVKT$+qtD4YKND2M=>WRts3II)qkIz#z32-0 z0TKz|`X()&&$VSjOK2i6Ux9AHiDx^TpMG^ER9IRHlq3OJ)jR2Ngcss09 zniL-&?<&k;`TgVFSgs=RAL!AOB<^B!JU_-7ywGae^f@WXLl<9CZrh=+Z+Dliw|5ZA zGP0zFB7r&la-dq(P=gNOTP8*1>^;x-88$M27f8vT`0X1liO7d9A*bj7=?k<9%ewB* zGBh*@jsc)^_|l6{HYY#vI?R`DE~`!e)Qr>5M^!-~ny;9@-RV)s#=`aWHIT#88eeZG zuSFO)zx6%5zxC4a;m-K6$SJaZb-KsOs^eRB=bM1CfCtdoNhmtQ=M~RT%Px29I27_; zxWXY<-K84kU8l;5an{`AXOgIubu;xrC@ou12Z?c}j4IQf!~*G^Ok&GShsH=UVVMkw z8aVD|r|q^0;B5vpFx~d&ukli6`3t#s34JVrf97Id=cKjv)CVcb+e$ydpw z&yS)y5Dfd}#Mw*@lzpc+kd1UvjUka+=gS#RUj~HRu6?Z{Lwzf6K_Cehc|$|P*q2+P z$gRS%GB{tL)cokTGCzs$fd)JlN{Je>G1|~+X6C6c@+E>1sMfTwFl56c*5YKT zl9!k9cW|UVo-H-G?g{;2DF9xn+0bH0L~+5}No^A&V8grJYQ-FxBhS=D@luSIv+bq` zziAh3>xfaJpFdr*Uzaz;6@>+&PL^Z5r+@P_PIKX^UyKkIB34kd`?WvbcHkf z%+pDQV=h?{lK(EHo*pgx8q!Ve83lccp-d8Jynj7&woj zyjuS?3c8LFI}2ie9Ef;Rvdd!GIrnyr$xfQ%|%WXMl{r3;53Xc7|%UwN*g zrFyQDIBx!?VF(V0H;yeYvvhTJma%RA?NsfgsaSMu5J|t|A_C>i*jb>kezi8R7-d~e4 z;}i$llnR*H{8vatPPGiH%&6;GcBr)h~i)Q;bOmLbaLIt}BgI5lHH{fJ@*Af3rTBJQtS^oIJaq(!fHQ(06xXF9n=$j^3X zvPlJgpXEe=wZ1E-eDTygypHR+zr8b>2*5q@P-bBM5@6KE1rLn9_@OlBKD>Q^U9Xb8 z(^F^ws5!ZtZGZ100DhlAQdL>_K%(A7NYr8PVe?Lw*FMU-Ruk#pSrL@xpHbfiKOwW+ z{V0Uqcb9w6%39uh)ksPp_F?1=FVR_*B4<)!R=GM4QxAp+#vlDcx|oOKY&iI&!fj_# zD~hE109(ELdmf;~nJ65};1SNNLXQG{K$8?*)oHtfRBw+piTlnngZFKt>sCqUaRg^;i3O7gpji>z*sWe$ zlBG<%Ma5?JisWsv_~8vmWSSfcT_&5F0E9FL;Ko?kyEO&g7%Z)*Y8bMw_4J!B-aU4_ zI0O?uU3!u{FHgWEw}iFn{;x znzQACR!f)Ds7mVWAVw?!(558N03^HH!gW(4A6NqCQqVGs0ajT&~&w#iJZR1Ls&%qv^UgJ3o-!_hk9S+jvjsgRcf+;0UnnhZCnKuRC?} zl`}pQYf6B>ZXX3~2FOnS2nf-n6m4`%PMb}$z*_zAdVj7kVcSsD{!lfd+4#_R9`4hG zY)|7hZ45F0+~RfCcU>%|ImJc9wM7){`a_iTx6s-4j0Uu2jq%hdLKBtiz^w?3)9CNLy4K9P5qx9qnh_36xTXp#IV`fR%m#56rW*4z#mL!!RK1JNb#$z`wdV}%+^;TH?FBv*XT&uR5!eqx^&FW1T9%PU;RR8 zm+3KZww{DHW7h$`qmsEG(?%msN!8KVRW@<>@$oE$rFR8OgJpds4*S zGT61?oq55=O=_x6-Ov1z|2%bNv|Itf^b z9us&}jHeb$i9=UZ~KbAAv9lmM1R)F0(^D@}s%bD2-_(;XVqz4mQizT}9D z5xbNM12-RXG+fZmgfS4AY`Vx`+32Mx*V{LwZ zYmQOp^ntJYgxKh`Mw#CqM*k2x+bmp|zu*AzV&PMmq9?DRuk?86%KX4BYwU6vT<;A< zB`2H0+*optlu#W^;RZ#Mi+v-NMaA6ZoHF(+CB+X0%iUg`CL*8>@30vBA4KzIx+VaI z%*>shnvrYpot357>^yV6PZ8r3#o)kdJnttlv`vdQrwou3%=edjUn-83+UR&}CF&Rs z^Njv>)6v$HvycgPK1?y~dSdS58{*B2;q|JSSXG=%%()=lX=n!dJpiZJ%fFYOcp!^2 zTQ*7^OT=cXTqjBxV|`d-<@^1HWt-~x1MvL?&Z2e(@rU;X%;lfQRkCuuRRKm~_ z??&&SB6_6^A!$ClnQwv5^h{{v4x?r=i8;F;9WHr&Sn5WGc-`md2~>&%R7Z1q<{A5~ z3^H=hH8>G_oL!=>4e}Eg*Pd5kFQ4CCW@#3$q}AFavO@LO_v#ka*rzdw3>Ylax!-|w zkwZ;>c6@ha(D9V%?FM3_&&fu<($PueT8TAwKF838-!~1&%k2sEM%Q`y7U-r~0dPE~gIVp<=3HuM)`-)#(h4x#i=(s~_&MO75Y(#fTlb;R~Q&xI}d z!NJ3mZ|}#5=KN~TdLFSzbG9MV2D03O3_QAWw?UuYaky0Opn*@Cv(LS|ELjE7U(i@R zWoZLMvfBijn5NUJ%Ft+hy3GdUqXZkU5_fay2zA(N_xvASq?u~wx#rfsftwzy1#au` zQiP_dv#WLz^5Z8~w%{0YVPB=NC5{XsUcj}`jaG!vNU_gM2QQ-FWZd5!-Mi~b|IoE& zzzp|~A$)t`V*EcXfa~M6;gPRzSDT$Ii#?b}DsViYI5fYNh4@Hn}{RhfyV| zhw>hl_QUg{;%n7rDFYG^^A?hdXMVbB^qlz6ygR;DB=)IlwJ<+R!yMo9&1iwcQ4qlU z8+xtQwOzNcU{IIX)!zv<4j(iHqesWg=hDARbRzszPx{1>W$I z_VB1Zzk6zrMG+Tr^ut}|2s7K^=`dra%||3TmWum=-?^Wk>Zus!W&7gX$(Du|9SUg# zqjLI}?MHu59<4g5%nx7cH>mD^tg+!^WHKpXUv=D{KL$+y{fi&s^T#bxQ~aQ)<=v18 z0jSB83K}5GX9tjCQ^MrRJSTfEfdSOsV10MkBMu;Ar-}!7MxBO@tX5WHL-$WE>6jKC z`?IwXy9+I{gx^0<^z`qKe5HHkdBV(4F%rS=c)@krF1_^1q|@Hx_b1d`y8|%=+w2V- zPGNGV`Kwdz2G;EN?I}8u`V!!vUbrgt$D+@Y z%S}xu>Gi-on`%ffg@k)+SgjTd% z10&!WA%um;UgC z1Xz!n6%RRrA8`ESRP6}-4H++VVw$P*^IPeAbXk;7dT^EG-6qwem(;WCNcOZo*4U$K z&01GwLpeihShzX)52XxRLw!Ozm#N0_UKfK@ndU~w^`KxI?Src9We5#F^SRi?@3(@# zrbDr#lc{vf-#PnN5Uf4fCVQa(h_QaBMdK`-AG-R7N^&x7^AUhG6`&kNb${;1C+gzfvvXSQ*D`LI>;aQ|1kFzPH{9}v`KIY?i$?P z-Q9viaCdhI?(VL^3GNO-7YhV;g1fu(CZGLY)%yot)mCMz*v;(pbl=$s?Qscu+q>xkD17kYcZCk6f;!w31INsYE&sn{eKMB)K7-Z77zsTXY z$#8TNIb)}e%Vf7x?Bv)Qj8f!dbthMJ=ymejBU7`ljDrHM+>b95rIr^uq;$+^nGVDmR6_RZ=c0B7p<+D^u`ffDV^zKT7c4As9nEqiOp^umPA+`_dUb%Ak>2# z$7^Gf=CMTJclFw;oA-`<>nX6sgMflJbHMeD)D>YZ%vZeah1(mswoq2#G*u7}$G@XpulE$@8A?Tg!042Vc~d30r+g zht(}4-^jx=Vid&*4qoy$=fjGsZhpIup0^IM%I|Y>~q}vg;y-Joq(idkjM8%9=O!%TQGO5*=L&L=?zh3AF7x z0OSzvQuTAYrCN@SUx=YQ`?%8gk_Rs++oKhP!J zhzIed<83GRO_5Dfe}(Rcqtvd8@D5}$mv%|bL_A^XP#y(NVxEEZd8uN-jHp)WO3RI{ zKDo7Q@$I(q#CJ0(UmX$z2$l|vPf<_^c%^eMqE>vK#_JgTPJ_+|%gxPESp2d-Uk|Sb z`vHE~hAD^L8hYLNVY$i{yeJR3r9mtskqu%8s^YFbxlz>KfY%_TuE~w%VtSZo>S5kb zHMSMYGbW9^`3KVWsQsW&PWoNZ{kcXpfYe0_8i|q8wavLyk_x?qk1k158HU6mD3~Yq zvH$q``WkwH{z#XKr3@?B3LG&v^29d#YMCnXwlHmbRe$1DMU{ zPggA$;K}uZQYU^9l?+YzTv#1BTy#lnfrrfNSFYWyd39@kb&+#n7LBF2Kgq4srmF6G z%+PMP_dl#~KP)rL=;*@xoMS{E2M|f+7^N*hk4xBplNAer$yAS#Ur8ZU!5F0Xvpt4B+erc>Ob-?$)mzN!@ad~3DVJ+A} zhg<3EoCDt?UWW`+GaSQtPVMF|?MF173g)3Bx9Y{pd?IxJ-1;*Jq8{GJF$7c3M^o;r z|L=0$9}f={jZ;QnDF;oy-7H`$xQtK*7Ix%o>*P9@3KpV3vxSLA74$YbdQMK(_`%SB zj^noCDqsSehv!2O$|bo@6I6L>#oP4pzC0*lwwhbCtiYVZ_i>se=5ikL&KBiOwGWm6 zg90zL8FW&CcLj>pL9A#je(kllb+AR>^*ofJB2Y}(+7i&wDqp0TWQ>DO`owQ9nB#HF z)%pN1tV#L+nyLbktRybkA&U05H4Y8M_npz~u^0;*-VrVuOa_w%To>a)1+;6;=A}+Q ze00~sbHvzUuv|4Y<&rJjxea8=S6s5mvaZq5zQ86G0m7FguQ%shD_-&rS@3;-UZ3YQ zD-!UK;XnP5oUf1A58H_*hXjJ>yH+mPe)dUZlyINB@+$hIvD1cRWmla&0Fbsu z$MV5Ac6YErStBsHb5AtO*i%pFJFw_^0FW;Xkuy+|_1ka-gSvH-OEo|DGCU}?5PD{n z>+qyNKA0AU4s=H83-3LHBqLrsBa*@0ooH9$c0DZB_RS&-Gkl-yHMYrNbBNXm{&6K7 z|C4#M^%`;W%GX4cwKNxGqY}m~GV;3Kdj+&5ta&b*5wu~3A$246ix0NurJosoFV>H% zRWaEsI|iv^F*2ciK~!8Y(0t!OPOK0mBHn^v`n~HkBUnjOlL$!GsTW3|k;SwV92Oi9 zr-zc&W-r{hdH_b{Z_0h9E?6rjL*bOhqd5*3Y3&jKanp{D4v=|TPzIIOsr2F4%@|hsUineMS3zWaj19vfXL^5Lt-a520vK_69kPq z<=R6kWv(Grsf?iGO;Nmz&#}U^|l?#lhBT+v@G=&>_}d~2&JYc z7m3n`IQ__k{n%mtTsf%386AN6@t1pZUOY#MRZ~(Df!uyOt=hTQqzKk1-luR#T}z~~ zG!gL5q_iQ6@hnYdk15i{F21%Tg1OK^MjE#G&3HqAeyW(r+DnEx&q16PyIFcDhR@*| z^@ST{XY3X1DcKBYSD2Mu!ihwG9yAD-fZi@T>p1e%9saO`IC4PodYj~knakppGS+=4 z%|?aJDrsYrO|6&fyFtcQ#mT^-j-kz`clvmkI(ycS5^?YjCyCJ-cJDQK*-mkk`CApI z(UM9!mr+PtiRea=+!$kbj1Yq}a;91UJHb*u7J^8e8tTi&xtHV=LJOghA&gU&9c`Zd zfbkc5vk-enL5TIe?&s*0uUj84X91jp9+mq&kt?D1v4g7qJKCQ?DH36Z4Q-|Br{#2fkPJo8=FyzltPhPrN|?IaZq z{2gmUpuZm5DUer~F=h*$(Zh0WlbD-}=JE*w3lp6l^aSqhFr|9u9Vm5K;-ie*5i^RKnjyiWpS zL7ir2{5L#?9!)Srm0%s}>&O9@C2nWB%!c>SHyp8-NM|29jAk5plamiSe^uKN0-8~c z#CiB2mX6YE?7Q-$w2IG(clwtC8p7JAOD_2r2q3}q5N}zfaNQAS_xZfL$0!#0)MdMN z&69#joU2&grxmy=i-du3{Pyj}`L(!FhHjNgwx~7dq^Ai$^CX*PpHbH%JBVfU)Y#rt z(NFwk)?tFcuZF=qma3iDdL zuH%Q0ZB?yFSpx8URn%3>H0yFbcQxlP_QS!qQkbldGTnaf7@#A}xcFYy>#^TBm&B=+ z7%2&*MQ+X}tcS=+9^MwOH=I0qGnOI~I_V+0HGPA=;XLLs*k#9o9phx7wE~EiuML*7 zhjmd=QTOv;jDkIPdtSZ_daXxC(hCrkrG0ETGWg$aoDOGLW@+8s%T(1=My?pT3QeW1 z9SwRS9oPL{0R-XwP2CKfY6s)wVKkGJRX$M5 za15I)C~EkaPd(g0X%JXjYE1MbLrO@1YFLHOA8YGi!Ht$$>0t}cUf28^NT#c%^m8_p zb|6w{$orPlUI?sc2uieHb&*D(DH?nl`{kEK1s~G4rze3@sbt36FMAoma4HPXc0fJt zI3eJxz6JoZ)GdnX%tA!3nR+k6IbN?29y_!1d|nVWbHqLzD|xl>9{_w&2zwsoz`pud z4U^X#m*X3luj)KPG@-o&f6bfUcRbESqjlY_S0eacG@~7RhIq^>z8rkCF6cf|{QaWe zK%f7pV(Q&i3u7ed+)r96dRz8y=$8_9uwPClGXtI#g?c=kL1%|kV$nq+CP))lD?1lo z^Dy(ws&nLwe6w<3uj}$a5S>iW3R74F!jcg`DlW5mTvX~CQ)tc#AlZi487L%PUI@9B zjEW^Igw0w7X_{6rxR4@s_tu9v@>_!sAfCHah zM{srsp#0y7#Ldrt4i5+ShtrFw=&V-Yfk@@BFe5d;6I}+-!z7!JXE5p5c0Pzdymb1i zeOyng_1zt-SHW9rKd0^OkG9RLXxYtxm@08&Xx;PbNvZ4c!aH+we_qeF1Dk;Gw5Wwg zA=jW4(5=GJs^#Y$VVsGypFsLu>BVsap9c>#jO~>dp+DyelR%DqUZHR&+(^SRoN0BU zP|e8$`>^IfSpu{cx7%=pW&A=gzgpi&@iw1tww*2oTMi)7x?LLx3Pm+cVz<@G-^L_f zs{5v9x0iZ?08RYtZ?ac${CP8w4u4y*o&RCHL?CZY`ab*tgyp_U>?vPiTho^pxjgzH zPdps5=hxz`_r4|jH4i7%KBvHP;Vsu|xhl5|_~l+&UCq$!KsVxAjIaaT$4a+UE z*`@^Foyw?FSoSOy6I%D&9#L!~Y>iJqKXlR6ChvS8|FY@)<>{_mipTzI$39XqoJE7H zkuWK07|ybW0z=Jh1&k@!;5MQL0aU#z&*|jj0u`DyMH6D#u)IEI!?$mA?IUc#WFXVe$1+k9uXDd0>k&{HQ~5wx+&?>Vif^s64xy%5%<-s@ui8Zj*UbJ#Y#`u`HN0srhg3 zJ=s(g2&+P$Ok-*XTfe=RwhI!*!XaNb3A3u67h5@m4F~rvCnFn>qp>0VFypbRHZ1wh z$CI4)Vz;P7p;{nJ9AL&B^?QK{E`$=7dLv~9eb)F~`UNi#ZMzhFeIUjG-8}s1GPNtu zbLsTu>gBKqC{`r-YXeeMsJ+zX?4R@Kf)2I8mdF#IpFg<7j$IA*6FgcMn3sj%!&;y= zSchsJ4pGT=P_)`pJM>hSVagW~X_6lD0$B``Hr=hGLztRzQIQ0x5DO(x$}LM=kYP;o zdaFkP{$b!KtqDm7=?9iwR1Pa`bI{;8k|ufZScR-hr=pz&20il5V00{Q+7_u~fpy|1MtP=YYPRLEmoHoLS+qaGTNq`!0RMjpH>_@V{Q=j)hy#{bDHq0~*T-8}jV?4yZ zJ;sNsa@L?}5UW)~3w^c$!&}a~)%_HU;Y0F(+Ht$W9=5aVSp<`*F8jQU*i}6!2+2NHdwpK;JryI51B0*c>F&f1Gf|{b{PcB6eaHQwQhr_t6{*X9%hB&>K@3wTPdA@D z?fAQ0ZWl!`NZLfTC<&h?((0!UK*NTS?b&Jx&KIgxpRoR2hbBU&j~JVf0^UoI?AyE( z>geP&Tc~BvHy}HgRzrhNJ|571EAZOqW_b7@TtFY9VxS5=#mvd~s9^~s0|uzD#DHpN zRI8z&fXp8{bpD+wrk%;cb!4jTMTFIK13ObzDQ@zRT5O^JMf%$-;Tu^i7Z-ur&&5_N zw|a-ziSGOSFCjhJdNB>Wp@{fuRxEwltI-8TMc)Ii{vgGJ0Z+A-vp8(6q5!9*yu15N zh^5&PxP!_hb(J8?l|bCedD+{tcWc^6&9=ng^zB$jZBTvQKzHUL?5*j%7zGW1?I#Tk@S+gWYmnTw}v7LYB0_3bF);)DEt*G+I`g*;J93VEdCIOFp6=Ye;VSp=FOnexnsN@ zlGU-HO^Q`2qwhEt7`pEHyUrJ`A5%Fu=mE_67}X;YHCs;!=%8?F26>zN84TKehWcEE=!`xTJ7ltL-WWvM^y; z@SNL~$R(=aPk%W-vy|2|M6MOGzmyYSBmLPULugb@-+qnK*nmV{9DvjX^fxAV7*Kv4 zN&iLSl!TRZh0-mbm#b_vTj=;U+|l#MVFC4r08^>?^)yRU8rmo!VP4lug#XF9=u2YU zUqt9QR&vdyBGpzwsBdg{@I3ngY}g$9s=b+oF!s^9m$ym)z!Uyu*WhB43}F4a%~-Fb zttS5SMdHOr(TVQaJIJGkxf}zA1geO{cm^pEJfRMcD-5rAG}J#g^R z)~T~Mk1oRCWiXv1A?}ADlHEd!Vz|EiC!xP@U^$;I(%@Hd+lAC&&sf^u%-#ktXN3jV z$@m3~LHN+%59xo?w}tZ?v|^WTDMS9@pci%;%7@L`TAUfCb@u=L#ki&rx$P#a4Xc9A zNCW(7i8Y(seb*g{zs*yjEs_@o-zg(meTIkXJ0aTa6aAY9Ye97_&)YoCyZK?j-u_&Ozlf=pTOMxwEU^4zLqtSHjuI|uYg>he zjxGiChg8(nWh^w)AIZ!9cOy!Y0HxLWU`h;7v@-y$C+Z3zoWlGLC4g8!B_SiDGo8>c z#9{G|O_vn%KJuxC3MsJjOo|GV^zdkXR8do*HsA$ZwgCOk-H<;iY=psoAGQx4jGESn zF3zu00&F#ApiU=~lamv&6IZbaFNT6iKm6~b?=7?Jb#t7LXQXZ*=jS z3!PHO0Vc7Nl4pX(e@~edLJ;0KfJG7T4k`MF*Xiooskd-|CB%#-$86qN{ebydet;amk>hj z)?VSSOjfj{mO)0S&FMGV0iWn$&AsEMd{YPm)yRkOo0!HD{vG z^5kmjvm!C^M}o2wV?=(LItd+O7eF*{ld)I$?J;eZ-s&8F7~3O!2490@wmjFR`N%h9 zJElw9@~$#rNhkL+U}E#vBA7ym*iaL_{k53DaYK`^=u(Q(tE5Mhp1zncYgadOtV8ai zT10$9(Ah@69o4E-r@$rgeIz!_xW#LHFUPaU7%`npHJOYUJH5&^{`hP7wwo&7(4Tbv z*W4%lnTKU7wjI?e&FmGGu(Uc_==q>m)U{b^jCPxJ{LP7UlRSXMi4gI! z^(~B?bh`McWBq?v_HnLo1NJn;^;i1g#>P^sT@yv7d!R{&_H?ch{MK*JHIgqd7pZLe z5!+E68q96*36kQ)GpVQXhpGxvKXcHDU1m{+k4W*yAB#mVxn;!f45Y115i{x1bs(ih zbfRZ$ebv9ucM~VX)8U)0(8VL@kQF?Im9GBMOjz^~ELim#LD&kIw94Z9S|#cTs~T&+ z>fflOSEr0~_&wt#CQA{MX8w4IFSktBILnHZN8PfigHE+PHzj05$t3i52Ft1CFafg` zF&BhVvv2YYfi{Ka3%QiYB7fQQF_VR$o$D6oP8RZgCs=`DOR@IhZFS1`9YiUKZzV=1bNHPx+qt0preS!lIR|%2####Hg5; z87}o|Z=s-n1F)eW(@I3?mxmT(XWZ|i25Z=QaaoF~k|$NMg`PJ~O6qka-0e={DckaG zQgxou@rO4IGPB=HL@lXF-8Q2W_@2KG<2MT)BG{!oj-XKKU7tzU>`uJq7~f)1v1R_Y z1bp6GW(WoHJk@b6X$rqyAd)4d(>&Cl#g7pb6q#P3l<5Y-H0Zn4SOiqO<#yh^i=m#F z`r6&#=!f@-2ubo2IP_WPovIGP$kX>zN1fT#`iZgA zK5m2HnHSHcLs4fGspb%<(_&c8QF_-BD%@WtZJ-v@n09alFOgUW?Qy`7w(jj#R_8M~ z53KxH}AOTM67Qg}-g z)xtg@i>hFrU3mH=Ok9rEYbZ%(FuT6YxT?^vJ-SGQus|Sz%gfqP$GB+~Fih}Kqi?4x zGCyTYJKLTurROmJzBBOZ)`2q-voEDd_jZxn2WF+Rk=f&y!-2x8%Rodv<=-zz>X1;J zD*2WpxD8uM3`Of00*ZinZqu0g3E!xfm9ZZlPvQJ8waVk)=JU8H?G+B znTa<@pRTHYpH_ReHR16Xy^Xwjoi&|o?&v#%U#RV5$M4VZ5~o$RM3U1q4*sqUH%LPS zu^c}a&@g80^z`R0l=d@DUSb0a#DXfYY+S_30)yjqnBXI&F!~sq66=Cu{n&^^4*k8E zLZki4%ivE3nK@+`@>m?9h}GY;G(dE5I96ymkSg#r{|v!qAn+uDvLQ8;36Xn7%}cqM zb-@X>V7O!^BB4!Z<{gfpkEaR57Ai{g!wD#iJwc6Y*y`RQQyB;QcwL8Ye(hC){pL>t z0#cQie)cXAT1_U#IM%2sz0bdXOTW~3h$>a{gHpD|pmSRwbBdLu3Q|6)e|#c5@cDB7{}o5s8*2-UzZeZhLz7&F`TP21X?rmZ_zx zH|U|v=FpmRAK18^T&0ZD#8n6+Sa^S`CUDy{2s7&+<3t$?Y829g0^@Rc(=dvL2)?hMgA~QxWX? zC%IJ9IU>|Dh*QUmTfH+u&~Q`Ah+KXHc_K&Hk)x-&vfjJ%-N16{_B~h)I6MKE+k@po zF|_IObYdHs3=-Rg@$bi{VGIzBiTQ0)yU~7Lya~+X-fDkGJs^k#0aG^&dj0(YufjQ1 zFIaRwf-|0&yMV$S)W>e?G8%&>L`)*PIx*=-7Lp$4k(9OJt`@xW`$iq5|LcD+F!|L> z>AW2O2J2)Up^oH*XU~z;tC|evCYLkA_U|IeU-?KKk}MQi(uo|SC1F6#PZ6_7(9%h6 z;9cbP-)G>F(ECQ#Lr(JDkIcgUou<+?zaXUk88zUl!m#Ll|HU5Zp>Kx&#&F#{+&blL3q?5|AmvJIFW&xe=in+;#i9H-|#33BmDnzu?nJQ zkia|QU28w@XXi9pwFa|XVNi7aIVX?rJFnt@@9gp=&(OWpfSJhglzf3=?A@4J+A;Dz zZAm}1Q!Mc=FnTuT-+x#fd$4VkQ<*Bq0E zgu;FVapq)vYWUpM?;q1^hxpCMQVS@`zkE(OD@YOz6w(h^Y&m#O=-k%aHnl}%_1?=^ z=ZhBdW!u=N?B?%WtJ^oT-7Lxf`}mT2qJViC!e^2q8iXG_zhPS?;V#a5j9Oe!f6vXU z130QoX}4;klK1e^LhgnkGloj^rm$k44t@bfDX!tLM#>3^-GaX&JW)U95MLY_oIYUKI5-#oYEb(@jmH|M%zWuhi?ciux$%)JoLXXRIVfg1b=~3=oR8>wO1hpZ2an#uvi-rd3Ftq7)G;Is}>=M8LkjjjL9 z@jwjpJ`w1nQscA&iQX;eADRxhRc($xcKmFP(b7ml`c{`dHTFZsM0{<~;e%KAE`nCq z>J?o`ERix^#FXFy;m6--ZlZ|c$#6IQ07!a;JnK|>x|Nc8xXronA+_cE#rB>Y&zX@u6;S@`ri6Vb&vYha~pJ zUau$?3HZQ*x_9G;#Lg?ch@)Zdq5{tZ)?3BB8EH$idm8@>9wJb{*nw*q;*2RyZep!^ zI>gSv%Y1S2ChKKMkvTIMt>JV?W3TBa*Y8i^AS>efddMtQd)ngsjvzGFkbTg`2eoR@ zGDuE`+OUP>>>Y3Rnu7uAUT;p*yQ(Fn7MyZ@Q%hHshC(Z4L9dl6f+OXBBQW+cuy-Y9 zyc|t3pL=b?v1*(ChF`={I8n_;El%7RwT9!2p?i0Wn8T_7-(;RNz~A>;tR9npR5KWx zsN0JIQPr|@!ZOBfXjn^Ry@{7rOpi`vQ>Rf>-171W+-^9>nE7EEF-`4W)0Tp-gIEseC z_t^VD3&l&1$!(_>Lh%-mW+UaOV3CMXnW*VQJ;t3XN$<^bq83*{y{@y5ku|K8rUzFcjfi zYiqegLq26-%3{6QMy&+FK#RlHx-+-K@jcV=LM`e?AL7)#bqPXm@Zqflla-4t(8bMo zwz;|ae&gRs0EllJNTy3OW$tIIZe&yTTc$ap-c;Y&ZCC!_j(KHeQu+NC7Y=Dmd?NX= z(su4&bKIYqX6EO&jJ@?`K^L6P_t1dZB^mX~ZmKtnwFa6RtsZ$gA)dO% zXTPaS=7x{@39^6IN7CzL`7YA#qy%7YbN+VU-rbj6K`Yf784)3T*;1RZd6Y{pCrggn z+t)X_xVUIXO%Jm729ulLagP9XffvqN2Ctfmcu7cnE(3WZ6JYFmcfYsQ^JR32x7hZ& zI|KO#LQPIhxv8W)Nx45i-vwg!cCfLr6*TTFFB?4gVEl%RC7iDAK5=5nYuRlPw781M zzeaqUzZU>G>Fez^q@|eyj8qJh9rT5EJHnBXQLvcEn3$9y2fz%~OZ)8Z+d5|-Qi4Cie}tfdRH8cOE&WJi9Qu~2YqLu-i-k;0wB14 zoU7Ifg}B-?08~*}Y@V!5wo6gz(`G+*906OxZqd2Mqg7#GRM559?Svn-l<_jIC*D0Rs*;?RXMuzT@8d?!Wfkf=!_hgz{`=001 zH;xqUEN-Xbr!02wN9PL7Mp(etwLc1bdUA3xJooL`Y=7TeV)p?Km{2vwJI2Pwl8gQQ z;bmz|Mj=@OzOQRu%%{)6aGdT#Jo10dr*_cFgwx2UKI2HgVd);DL$jG50OT&#yFZ!6 z<5s2Jq8OFXMeHBT_gFg9w+0}_Nu3Wc1@jy}|->V$PFj`rv-k-iVcEA2%o#-8?x%cBw4j2&h zMGr;VO}hZ_71pdG%=H!*@Elihh*SRH8-VxHzuhoQ?1$B8{F7SjxD}W=O-;8b0}v3w z_m`v9zkV%*UDh!_&ocPXD=PNQCI#Fw5PFkhI!9?}jF}>jzis?}(@WzrA_ro3`_T#( zeo<+`(d8f2UA$7{EcK)wNkEQC7Ux>eni4D9Ib3P>|CzF{PUG0t!Op(dh z?;dO7ae1l`k7>uo6SP_%DA9hYLGlH3@HJ*<1jt*>aIhDEk^IQ3q9CrR377#OM3a<} z>3afnE**pv5V~GF=*SCQw0cdlwmfbp3H!XU=p^cIr@vp>0P_GUP|jx7`9iFetf-C- ze%D$p>chjs8tZWwbTljt(R<)1AOmje#JL_-A9SFMe|PFX|DqtJ0E*b5+2(zy?)K8F z)8OQiG~%J}&T#n{92`tZPcJSf_mNy~ELBQ4L*Kj6DD>I)a>c-S={!qvem|2#nM^KY zZ=l-kI1U9P=Bk?Rm3<7Esivqhkct;0=2^skb{nG6_fLqs6!}wj_ZkyIYAr!(B&Pmg zcc|3<#1i3F%rjUcmB1)9ln@{i9m8Wa56DpNK zPu$E8gQ#4sQdmoiM_M{UEGf9QYGDJ(VQ}9>#n||>VOl`_ualJ0>lH31QFlZ_cI`u3 z5Gnc4EjURC_RCK6kqK)_QLAQuq3=@}U_vtLIg!mnp@>hQJcl9hGLtxzmf{b{(xJ#g z%zkDOO|F-#SC8oRI^WEetKVQX8xI>kd`Ut}mHcS*JQRQVaQ-2aezQW1wV$ze@r1bQ zq4Tp70h!7^Mi1(IFSjcJtpn=h+VGX2eY}Bb3nO=j-J8!!OFYG!m8{wB+eVgVeZRV& zVcMSD+958^ugOWOJ-8eW_p74276k0RsOa`y%TSkVXAO$VU^WlrtO2w2ma_amRXU6f z0Nyakn&OUK0F@X%;^@AGDvtnQvybAv)bU`}rO!};X4DJSA; zAuEd(l$aaIAlqkrM2p@lEve7km%)kJbp2Bo87~u7`93U9tfuGOpl{uwecN}H5w!J& z@;1SR&1QXH!>{+$Y0&foj}3Nr$X!ERK7`GKDJ!_-mxmFj`1)PnDu2Z5a25Jq)T%OXyp4dh)BFaV)sS$DguH{e&wa?;GBv~n|lO!7%nnb&C$ z9uUs@3j4g$AzP7%!-Lvfjy}N36+hgZSs%x%tgU+8ULTeWhtVp34BU`F6d>LGEStOw z{tkkO;WIE_<*jzxJHTIEwF#QTY~PIZ8#OvX(+s6&-V1JpxtL2?e&;%wZZCYG3nG$- zr&~fnlbvj6#`rKV2ZvY_{A_%C?!XEWl_a7_C;|x@gJ>+gFfwQ+_ zYy!9QMV{Z-D|=z??)foX=t>puLm2{=F5Z<%5L! zQ(y@FYo(LXK?+!`NX88 zr0d$d9#RY)>cw)|A>T)+vp5};h^K<`a|3e9)_P9Q1P$oerV|#`lR$n?a>|uy3s_}b zw{Eqj)duoPqN1YUtS|-P2+_^=9q)CDNQgN_eprh5ffY6^d{ne_VMo3l`zznpz0Dpsc(+;O5xUZ~9peJRO(W zzdqc|6E}p4i4pPf;oHXIVlrqSPZI1e>$eeWaXLI{xQ)%6+bHl7HP)jgm^#^8%!37= z6sT2ehXcMJVPI@e&{y6b@y`I6=q|$3X&LY#7c13k%?Qr#6_$cCOUhB)7`xhIG8@OM z(5ThUs#x^(=zDa*VskW(y#dY%!KybMPYUZ?K8c5cfHm(6e;P?H?_AST6o!ed2dipZ zw&zIhQH_TfJu#}0*FaEUVWA4(sa_jur{afWoo@Hx)5==&$X%oayk+}N0s?$=U-;Ht z`&2VyZ{fNVXUx@AQ=!#R>Hf@|AYkSM+0UZqy^K|_+tP-VBwE1G_Ti6 z5XLuX&Q-(=KoMK^T8 zVJ)uJTBTkqn+Bzs8c9h>aelpr9?#;LA4N|Az0@;w{ZL+R-dc5Ai|@(@h!DMirdkw@ zP{0AD-t=d0id-h?rM&;xFcbly9%Y8W%QZ`dUN^zp-rH-pR5ZI4CRGlgf{i&o&LY@C zCsSb(tt~G@8;_vL`%h9tIRsC{T$*2ao$J|)+Ix9>WVq_Y9*z2(@9^7wyv2?d@jA_b z|2L>>)cXuK*89vU;m2ET?U-AI%hUA4%VBTu7xx--2sl~@ssi82 zNCR@nic2@~hK-z^U2Sf1-uOFc%L3rTBk;d%_jMbNMR5r>+#j^rJ>PUb@J~E-jYfk8 zy|;@gz?p}_%$L>tiupbyWh&^6o+ucU(H1n9cCLrp0KT(H>Xu#uswhj&rmKr53 zmcx}Vbpx|9q>7-rPwQRj7MlAJ9u*ds2$x;b8vR@CZ3(l^dt*bXPqz6;zyG?k7gdXN zx>m0EeK&_*AFcrE+eC1@tB)c7Q3Ema@zerur3TaeL}X@ zzb3+mmL0fN0I8wvy!JW4%issl0kUaeq3=9Va7vH>Z~l2by#F6$`~UU<=HO{Qd@Ry9 za_xq%CLk>kk47;w!I4ErLknB3vso# z3pm0~`h&PEyxnb7YQ?9pONkO|`F*{)nWv6u&L{ARr-OG8-Fd~v^2tybojomgITbN#_uaQIMl8j9^#%06CRzL zI*=O=UTq|?p#rRA|NyHM+f#ME!!2BoOiqlvcUv={vWn~Ci$Q8TP z!Th(}kMGyKI~gb#j&;1t&Lj>6V#7nv9_XG%&RCjbm*?~d5By{5ztCNJJ8*V@G_gBc z23yxP*8R#Rqt0~<80XVAWSzcw8v%lTJd_i*`HYQP&vhoRb)3Brnx7C>G3dqesJv=F z@OS+|j(2Y|e=4jLO-MPePdt0HY z(dG;zpUoeQKAJ*8MkeOz=u_{-!8~D>-^tgK1CosdT#XP0V->`;wei^PmV&USOZrY+ zH-OSyRNl4}u6agD7fNaJMnHU>`0|dSrK(eCU z=3FMB_j`NW0e~XFrYID|-QCkfEf}<0{-BKcU0wX^ZD1GMw>JC7&g=WNMx9gbFwJD_ z?2vzKKvniYgkJE=0=Wf`B971c0oVQyv9H(4Oz@OX^P%D(nMaQS9F`#m|0&51le zKYzPDDcR*C6e7N`g@r}6jVb|el`+T4h3!IYNKb|tM3!IWPw+if8ttuY`)G%7Eb?8? z`We*r!Ilkq=RPG)XBLG&0o{bMz+#FBmPFYi5#ai15)t==xvHwD2|;8+`eK z(`2_y{vj-WL#x>?{Qa!LZ%3*kSL?)@h&b*-@B)F8v(OK8J|r<-zHDAi7|llP5U)^< zXX0eW@zQhc-{o>Tps-iyh2FQc=uTX;*sRp(l7EPzv~rSRv9vm>vELA;{nCQe7b14! ziLX3pb!17VniD@USS-%v+R^Cdd>s)~rBe@C>rhKcN7L6*Yjklb!{9of1qCL+(8WLj ziFLDW=CtR%>;c9N=jNZ7WPwy?q(;`iPP7wpcuJ?vv;X1v+bp&8YA$! z?ynx{g%|js@O*V$4{Rx;MA9ceRLlvWaj-j{zRVZ%<|Y6foMXM%AVuzQ-t zbsx4tEV{~nkcyUwW{LJ;m;S=C_DjFi{qbTwunAXC1WwC>l|Ju-+?#X1+O zdBcZG6GKjy^IrzYMS!(!pYN+ecmGnORsYAdNfB|AP()nvBLf|V0R;y|CP3|2lE&$f zA6B7{7t5g66}6J3J(ep#IGRG=6P`%naJem|2^HLB=Sf8pjwZH3KoF=O+{^&<0)C93 zk6tWd>KXnqeR?k#e@pm!<{Qgau3k{s0zS*emf+K2-y;kFz&OCsTD8Xv1>R_OJ)*(G zC(F&X<{KZvzo3XC$(h!R7Pj(>Yhbh4o<@Ah;rE8bq}T4>9EfbYZEn`zVXDooeNPZ8 z+J=b7OA1P|=X+MFVC)t6e9y&Xc;w8vgN4uk6?~7G zWrqs@aWm{btExuxJ>BLP7NT)+wYoo5axhs;iH2({53vOL2c%;|joGi?MLu0c=Dun3 zmjJ0&)DHp8haIpRO@>K+1fW~^deg_%+^(&kNkik}Ha2v$rjerGuxnpT?+CWnTKtn> zP|vS&J42Ic)IR}5lE=6q`{R!@h{MyV(HL%RZu7NvPZ!IhUkEd;9O!%3$Q-6q`EW`JN|+IJ z>$f^=L0YSohe?S0rr*jTKer1!3oS{BiHj2p2GEh!tSl^?Ju%y4OzOhwaR(_^Fe}M6 zTZ_P7r7=c~KI-E5Ts-{@|4_WiB1445GUU$1HM562|?*DK~lOql$HkRZt3olZV-^}2I)>W-6aju zE!}LIc{u01?>93yb2WGTKzFS5tS9~%7t`NbGyl8M>r8G7mPP7-g4391Rcg%5=IL>D zMFD$T`Ba$%VIOMVdxIPd4U8~|=}!#oa8gBD)KH~Rer`JaowSm$h>(z;vr&E;(=ynQ zkPuS2;jMH>+m<$t>_`lGkEr8g_{1X;^;^%5nwi;IPTodKe0B9B9NRIUrrJ_z(p`W3 zt%*^Agud%29dE*jHxX-go*>Lq6Jn>fTLEi3NJWr`=&u95T-KZlkz@C#d@Y=gGxWZ==T}8=x2GxFkcW*D3?u()D zMp~R3@98fNQYb<1j|b`VGx2`U ztgujg9BEH|$7#1;IJtS#A#NlRz!4+XS*+Czi7up&hNxN(Q&9wrFFzzalCovJbTk0a zDO*tqm(d%>+mF?etW37y=eukQWA}P*4(>ZGb~DThB05s z6n$|q4Y@xkI$ARgH%d;9M<*8*k&1qXtzWm)aL3=*(^Kf3+fJ-wMeHCQ1PWL2 z)wvS2YO+5BeQZgnN+HIV6AVf7ZTCpi@O#>;w+iA*d$(0s31fQsa*8c)9elGG#wldb zOwOK8a%UV#+(x1BYSX_`JSm=0*@GS4XER<(PnVK8XmahSXwB?53fuRA$O%Usv@NIhCB5GD%EOhdLpK=#t z8q1*26uP()4mz7WPH=F8Rc{Rn5&UQ2ZECD9mNgQu1`vNEj`#kU7+H7Hmh33+Sq-g0 zF@Y$Dr|Vs%82QhB@r~)>6!M7k$sLx(^{+%->6PHZtFYENzs(`qlzfy02X5eNz^4x)G@dM3Sq=6+1Wf3#zR(izsk}XkAksA74 zn;N-1u+piZ>ytPBqSt|vV1jVmxSwg}`HXNvjSO=FYmTj9E zo+piTrFmK#5y~Xs$i>CS!Lq(xchAe0YMYSd4#9o{L@`b#Q%#UE#L*nKrfc+1rAbAon^;81l08%**!R#t|I#8Dv)Mw}!Az zpeK|Wp%N)4i+J$Z3|d&uc6A9i#X5JIHzQxo;UHkKVJ);jyBrFl){s6t)o0H0rNhhXDAElPDa^5gp7$By4-bLN7yF zs`l$MMfz3R+O3`MH&p8f><%S+!t=2QjBF_@A(w5MMp*Su_695c zgo?2esd;lTMJm7kRZ%Zfx8I##V&?BKcyw+zN#5K&JYlD=+$?pXZkw1qpq7KK%Ps_e zAL{u>68X=fPfx34V*58N_qqLb@mLj8s57a!|0)mGakCy};a5i+51h|mpKYxYKkTTG zLO3rij441EMnKHkniZ2+yF+uPph7OSKQ#^_;_K)nS0ZeBt(Y*+5Ubic5=u(AnKBdS zFhWr=Q0yI4NxBJ^S+dHK8W}s-$B#>ll9yBx{InSS>>6s8=57ryV?uLq90m zG~Tplem z^zzPn#*K#7R#M84G92XxJ2fdP));FwWBndWDr#O}KYEA{q2bKwhUmxcWu-7hA&M|d zLvSiL%o`&t4-Oh!&+>y+I!luW)$oV=(TQ@JQOy>X#$yz?;(2>KbcjsWZvk%0!U7!* zcC+V#CS|ZegN_DAN1E@u?ON0dM(!INPS8el8s4G)(XERjjh`Q%{rxV3PH{CUegtEG zZ*GRX`P(41I*1Sjq29>iW-MvVuHm3b&~Ja=q95iRE3XElH{>pgNh^KceGw-ic7kuU zt*@L@VTPm6L_tX_zz+TDUBaoY#3j_aK#tZmivGdsftZ?RHp%Yd4t%A z+cP68mUeaB=-`SX8oK0;Id$Ctx6ufARoA1J&Y%ZEjfEWA5QCMD!L3FR z33tRT#QQ$sdMVsU6tBKKnA{UVeYqFo=Hb({>Y@9x%f5RCs{Lbc5A$;6^nt7Zv=|Ya zIYo*hw*F)f#ox45^fQA`6ST2Vd$712Brly)FUdbJqp>#w#*UCQ z`>@vQtohTfK&dhlgT6*Sq;6t9bb#~|*>@Ks)+p-V7Ca&+SYMIeZ-gt@5%N~@!OM5v zvyHcVzA3t6G7D-XZV$b1U-kOM(3&Gwp?TTe#v!D`tC-@ z%MUQsjs@)YEk~-NCCjdRba$8c(7fC>95GaauR3aFZO|>eD^CQ`J{n|{Wa9wWmR6j! zr6yglV39kl9B~XnaOB`&%IyH|DfcWvsVmI?1=~L?1#Y@5htGXiJQ;ISWQLf zyWswm1z;b(lt{|6MY#dNN>XwqNL?+VpOWe!D?>~52M2;ti_Qp8=Xcq=+TDK_?OK-- z&>346k$u&$TP0s!^S?1%5bUAyLqX2<{#RY@NBsWuCzWT%mxXfYLt)8h3Hl-kji}L*hzGXd8bQe2mSe;>+@LwmdLV&{!ZYnRqBE zvuabr`DY_UHQ*c;s@Huh^4F{yy)g2JF@H;?m(PK|3)3iGkb6<*mmkEhV@L(Q#eDOL zRQy3qdQPKTT#fo(G_xu@TTJKRD7k{MxrIaPYLMo`4K5esl}6t^Zg{JYQ?r)aT(z@% zk!=05rz_70xB~*LqjIfg(VG_j>nT2#~J(|V>m*IW=ie9T}VSDy=c%y$u zwbt$xy$)o&04Kj8Z=J`zW1|1zcl7(UTx&s80gz%hl+K195`w^-zL!1Gy!uvFKRc%q zfG9)BTsgfrbMX;$OS7bOIA2}FZSm3ZX$W73V_A5%AAm-5JmcZy5afr`tcJTMuYqR{ zTPFz;L&cIiRO|W#+Q2s+N8k z&-qr@(8G?|>-<G8T$qfU2g2)9DD1ZdJ!YjjDpy$_Utc&5gEp<=bO(q-(E#X`mN_1-8k1%;Re zG>}R^}O|SoeNd7wDt)EXYCc40$HwBW4@Q;Vc-rwOxH0IkD99!Syo6MIe?{vEf zX!?U&Ma0Tm&~Avfed%sVBgVvGwZI8T=HH`K3?4c_gmg5YV9q<=l_X^>akA-e;$1rA z;>DgKWpiFlJT~^;@b&}E%X&DbOmPE-^Zk8-FPCGVyRbv0tLG-I5nFxQ_6tlE3rLQP ziGYUkWh5?(F%psIIV#vK!dTVA0qn_6)1#=kyf->WjJ#t|`;6CVA0Kib(pzbDz^&Qj zA(CCktF5QEI^Oy)N_>~c;};rj0HMd}i69IEC~|gA4x!0NIyiB|0%ag2->7HKH00QR zsMQe>=$9%S`W2qKVbw`LfUD#q&qUB6mBCL4&}B}?8`cjWJ~T%PtF=MCj`E!$fk0#$ zYVC~#$Qx`eP&NfqdmE0($V1A z4cOi0u_tV+^2_S=Ic#=y2^(&2^qCi!7{23x!d?<`eygft#^tf!lA={>fk(&@lT5bq z6naUo-Qv$;G7?NhhT$8|s{k*X%qp=o!wEIfY7PXA5)O#%YAh!D!@E;eho_--1Om3q zx=C-A2gQ}Pa!aPnq77`llAN>|?>8=HDjoN*8Y`$ieE0+o zZ7tV5vfq=FNwI~&P!K|RJqG~x@81)@H8pXA_9Jn#{pwL(x{IF}#{SzK0C5i$WX;?e zyK_COu-kl%YULlTm2S_&&9mav*fgQzYJe4G8<`Kk*r3M;NH$YcSym1L4l7!1x06>T zpcFT|S@RKyxGG&IC~#S<_BmUQ@6Hm_!EVDqozdJi;-b>u*g_#j_#^d;_4T!r$=LZy zt3_^HSsXg!K$MAecT1h^w1d5+giH2Zptc3{*Kef zYfY1hiM&eCms(rXXEN3QX@Q=T=d~u-Z+=%?3pPFH+>e8^U5u4GF z!Kn8_+hf(W<)TOS7;NjA5zuH;{l8jUcLD^`aLp$JbvdkcadlNM5gbXSm;osC_k`OE zD;%~u*Yu_)TzgOHX*$e&nr`+8d6>8?nZ53A_EejV-&id+k;77f4SP%&+;UhFkbL(e zKW+`DY^)8mrYG9K-VK&ReDfsFtF+B_)oNE-Ze0EOi!E2y!9Nz1~T!9M|Ga`lB z*R+|S?IEAty&w?UyRe{XOOqdL0&{&DSA4b7iEKnjcyPeS97H8dQ~W{C7r}hG_1db)cV3Jw(4r%!w!yRiECVW!4*)OG1F~)MB9A*gBeHLYv3oP+ zv0o~o%a3Wf^!UEBiFD4e;w&sHBIt^L;B#6*XTp=%_u+gSSx!@7Tq{--Y9<s zg1X2-9h_9f=RQsAalNVX%c8 z?N&b|WJg5H$PC!UOo}r+utPTAEAOPPe&J(b2B`mI6@31*%pAixJ^J|d4Kz`}k|H1=todFa3n5GFo+jvcda8KJ zLDydbq zM0$L?O||9~7IVzFqkl(yfY zmGycLKkvZJRkVkd%bSJe>k-5gtvmO-iD4o|Xivj!QNJ_q|7ZasDycE7p@^YM1)U*` z?yAsA2zEmx_0Eoh!0;ESfcLQvyvF0jjX+sxQ5=FXUZSmnUM8b`D5|9l%8wsEG8^;M z2+Dy*$d=`iLKH*F!peeooe2*tUNP8rx3yRNftM@~UnQnq|{XCfe(KOU(D7Nc(BoO53pQhp!J>-6)y> z^ll+-4thA;z}NrBKv4h9WxfoK#CN%WZD?~k)6Lq1l-TY7Oxa$b^XEQ)ohL(0<%@ zAdyca%q$4G)qE5($SDbmMP*%keW`l2=9nha87pg*FnNM3a0j21w0&!#w<&4(1!GO$ zU~Dd~iC3=Sm&s^a>=Aqv{`;==*xoS3b2#s7L@e6Gs5g7l639m0Oj3$cUXhqeasQZc zZ$2&p`G^>?D~v|HMKrPM^i>sKmq8T2uYZ>?lXaF_kboMvd8mBAHJp`5h6^#r} zP*+`V8N}0=ei+X*1>|Xrx+I73G!ixjmUx62V?_bo=pu-n+PE9JDAj~$G6yy+w8jeB zD^?3LP|BoInYJzPPfOlHcgVwand#iO!g2MVi9EMXJ=&oG*cY2^q}Si9($o3&``nD~ zp(K{vLpIe!{vm@lZWC?`=qmvRE1r3p?yfFFA4Ze(wp>zo1jwkJ%vEw{GT*$gx1mBWI^ z{S@%(&y<_2%MyFMW0^&#l#U6DzpP{DeO##K+x0ii7BQfIXdo8wdO^KT8GL2*SzA%D zx+WwzJh&~ZQ_SYT%?aKc?8T8p{zeEY=1Jpogy?2}(}PJg{(?asSeU)XG8!PucTM(m zF0*yYKeN`!|2&kNg?{met@y;+>jW|6G>y~s_457_U3pI1pJr=;CzsdV{-U$ImZG1( zd=7bD{NN5ep_B$``ox@HVxa6!?8J&g6WLAweI|U43p9Y7-DK40^Zc*Yo9ofBKhqzg zT9+zwX81QZw$|>h&s1yrG~gr+X!7-0VuKBkiFnEOma9yw@7Tv+LU-o&Z8bL(6L{oQ zTP6KyUd!imOc1|E@i+|F&OkpISQbo~3I%ciC4zj@z;3J8B^O$Ch9R8p)vvIi?cX97 zh`n!ctE#KJ;rDk6(qlI1=)eu{kqBr>xnAqEWiS%NfC4>YoWwpb>XhZSo0x2i*Td>i z4b2rKktX&&*@1TOF*`|yfMSTutSy*+qK~uLW8*KA=5SMJ%JubglUN2VNVv}ZSvmQA z6sp$r173E0MFq;^z4sjeo5?gSE-3p85k;)XIW}#C+`A`9nEH0 zS-=|cu79+ZJk|?H0MJ$kgLi88_w>>F;QyA+FY(v+xW<2?V`EbyQp}adV1AXf{ZWT= ztigSh2m=c%SgvM%pXvksuFU9W?glu>k?kg@^Q-y)jvG|@?ns9?Q*Ga5COTi~KB}E` zock@m9euQuRagH50D8Tq(>_a%4Pr@z(?*3yiAkD=!P9epjKbqmfXQai1RFBA+&3um zNGE&^k%Bv&`)YUB2|6pHOK-ZmdqQm3$Fq{mhKF(l9!_M(=f3Uy2^XH3oLG`j$`3a>53hhF=T3VVA#&GB%*1QN zf{D{u3N4%TJz4VLMRi?b_J#It{55Xc@Q|>*IU=xk{QmtrICX*G8p$(DgYy-C6s6+N zC!hm@&{LFMUU^^YH;OxY`*@+V<<1h1>#7VSnOy6TO5WcFSd7>Fmsl#B!f871NV$dWoG9CTlKO8i-w1jdNX@ zviBkLm;fL`dXXqPWdC7L2zPE@^6vPq-xk9n^%*M#;;mm%Z;Pu|rhDYJU774ifrZiJ zs5>&uTvP?412;jH=SwwgZ)_^vU#*CqkC8U1l?(*itqedIwTg;59-GuE0UdvzaQjiH z0@y}Yt4H}0;>DKPcwz5oJNBK5it>XmhJW19uWq*j;+wa{k>=s5Kw$kU1pPi7C^VbD zk+bZNF3lKXjCu|U>j@T?$2$-Rc@viT;fnYiyYNZ_ZFBo}hR2o4K3XrApgNbs;{O|& zqx3f5I=X%M=K{*?KMY0?qd)d8HgDF`{>E9}_EjT7i9n)R6n}{LD&U)_x6#`WcB^eb3Vcjimd~a9S zvdrSBE}=f>ZeD_|mZ@Krkn{tu%DHa-8wO~A9-uUb|Jv0z-Lg@=W`ne9~o zFYU8#zFG)AGSxt)(Qz^HCQraGcyI^``GG)9N}rO9XWN{K(mdCB%8#44Ap5rj3Q#2R z;%LQ!QGco36tEE@9%V#Mok+EjvF#vS^=WK}na9MZ<)CA9bcAKAhe?5 z%xOP;LyT6%Qbn7BFnQ@eZ|NKnIQ=QHriS(I{Gc8nEb9J2LG3S*fmirs_I=blZVGix zYB+qbDFFsUdNee&yn{gSL3P^#c3Ga1 zaNO(;9b6BQDOEI6AtReW-F1D5^{+B;f%u8ly8D|I#Aq^NaW_%^fQwD9{X;tvI8qWI zCJ_3h9g!v2?F-(>yoJe0Fw&tDq8dmd@?`i$0Uj@iv(PDUob%$;d zb7%CSaPQ4q&MhvoKjvjDody?e1}EYevvFJgY%?!nJ7J>U*aW$6xc+lHAjDsgf*1h; zBIxMI7Ma0GQc5W3PK^eS zcT6&}fxtE%_(PHA>n%GsX;i)g7oD$@nflhU?@QY z1!-VNV>{Bwho+bX8_EVf>gk%VPv5{50RpgV5&{g9j{k&wq(V(~ai$6Z>g2Cz>Ndvjb309R4h&}fbt8o;Efu)BVJSH64)3%XsuP3&}S zNv9m&6qm1E`Njh4Z#jU{cL@HI7cPwwAO zj?!;lTVbyN0pNFxgVR&Hkay)e^=~Kgr8~<(#hUHf2DLjYLb0&k8mORLEc0odVeuKG~HX#kvEU+8%L66)Fq_???@99($6 zKTv3|h-#-#=LYC;m}0C)5-nksfPN1i;R;`bhK2B4R#UT;2_m4 z;WpOs$_3E*;NA zgd{!c>bAHIUx|M+H$0BMCOg0zRI0j#EC44-jk81W38q$#gsVs+js}yNd3Zq)mOHA7 zvXW!ipXq6Kmjx?f5fR7M`10ySqm71^5&|GU30(B*<#W?K<(%#8co7K(xT{>E*)ZAx zW==`j-wO9cxH44^Dd{aM${nKeont)H_Iy$Gr*C`uP7WOOdGCG;%>rUeK~VvPkT9al zAI8=^|J(3L|Dlzw!|4V!5aVh}RbOd(c<|Qoo%q*Cb0;M2L=e3MhSvC|WuWU9A3jNB z3Yl%tdl~o!-%Ca8dZ@8lL8t|&rsw#VmMg4&C0ai%_Y@@oSUYt4sayW`p^Czl26aM6 zRkQ(EiIOiL7u2ma^vY`6_{QJNvhn^iWS1$Ov8xolP{L(pdO+b|7kqJEBd(J>4$w&V zbcbPA0Yg)njTkFYpxYt7yWvwxC z+p{hRZN0GVIZulEk&xh+ER|1^&@37B6;H3WzP|myE+;=9Hfej*V7p9f?bL(vz7Z2x zg$qlI0}v>fmbe{vF(7{`6&KP?Jw$LY>FH>M3>4pMT5*JihJp*g5Asx3*SaNR6oxA! zW#C@Fer>b4t5j24J7})PqN$+~G?>5;X3wfK%HNk~l!xZJbsPWVhc0yKS09jeKf#;_ zY{0*UvH4NoE=jkHty9wgHkBfgfI`(;O;^A);mg-2dboQtUZJ{1nVG}=RaN5@jAY2G zY0ct9v!s-W3~n&98x^*%NLxrDnVFftAC!Bv?~y}sNV4Q{Y#c-X$S}* zeUS7E>4d4b)-8;e_j}%Z=B_#%Yu>HpMi3ZvcB-*H8~E6i6dI0)Y7zgSuxfjAjg&;Q z>saqStf$Zlt>gq#jGrUV*2{ESBk%4!ghfR&^YWCYlTzGsdU5LP)`qduks!dSPBaLg zR%X4iBq(f{8=kcS0lZrIlQ9VyPcasu4v}58Tw}G;wTpFda!zsfa|EL`e`4`ah05ZI zsNB59zqfoS_j?VHZJOgM~S134wV?AG!2H*C#5cHlsc18mg;sL(`_zhAJM`C$k**Dr6@Wie(HQo5s(GP${zK$}z?GXy~ zjsoI!FHtvOerg+JfCK|w`Rdb}A|xavkOPMIAw#NvN@{#y03w#|{w&!Jjl8xpJ>8z2 z^#Y2)$8`JayCGZ_w_oYo%Ro8+vL?d7!CAlK$`HglJm&y7cW*_|t?%`joVB}r`6#sK+eJJXgORljdqmi9byTy`fcdkJe{5zEAdKmzj_hMeq&eF@{fJUokUzJ;1 z>L_)BuopOGcF{m_A`Id9)+eG?Ld?6=X4S$?vha9(=srF%kz-B})$4NcTneXc3f1q= zNn{ktT5Fqrv%#%XNLUJWGju-=nEOMfjN@{1$q3Dgn@M|u6h)L1{5a0``88sy$r z-utc|bE4Vw#X${B5tH@>8@ozej=MD*n_C0BQ^mRYb;|Ub4TYoRPpQHS0vMfMLy_C{ zl3&Gx?{+$)avK^{;dGx_jpLMj%*3PBfk?%mp>Hb4gAm_F1!flJu0xwbNn*{Pl`^(p zzWgZBXjW!sF&V5WG*#CzP>dxI@X}no3%|jpReg7MYPSilX-q~2!-=i!_|g(dP!v)0 z6W>Qbz}F@zCH1BL6B4_vRD~+Gb`$G>*{}8MZE`LywNjRu&yrKG*G)5n8~$z%#!GlT zkS3w>30bfgf0>yaXcr3F7)j%K@5@0=Ek9kRMuUsHgGPWs%lC`VZu4_UDzD?`(k*|K zwb!4dPwnhB02hBc^>F`wG-P)d&-1WBmVV8>K?oa5`*ilZ3lWf^3y&*I7zPZ$v4HrworA#;3%- zw{E?Nzgux%j#ZP8Fk@1yvq-wz5EF>W%~gB9iBxChstoA=JghjtGYv#}UA>eL7srX) zr=j8$2hX?Rb|hgHU^E>m9pE-I=IfLokH3xv7D^h;YJE2Ab!{P`It{8XRl}EE?nfP3 z^|JInG?5%`Z*IDlB%rJ<3+{iaNv6YU^t$gUL}1|+JK;T_3JqJv)M+F&8BLGdFKN8l zA08fw22?};X7=Fum#2hiqGvTox76bCn7FvqA_-weUsd6SaP;Ni7*T}uv4l|IU|&f9 z+Mbo2eSor=KW;bKj1C`_D60dvroMh~`}R^oQ85-QR7I~00BH*fP_B=R9`3Hq$O9|7 zyo`m#BtdTZ-A+!_ri_rEdhp|e^`q$EI@HFI{qN2}9I*V0OPUuiwA4z7*t@C!TqrA! zZC`!91<*M%Dk^d1a`jC%8YgwLpRaEM17WWx#E5q?F<;jmFk?9KEi zv6yVi5_=WiMudh>&hn`TAdnosqj33iZaO|cQ9Szbn*<+k*fB$TPNI!NnuAn2o20he z>MONMi|amim*4NGvC=c1*>?0o9@OpZs*KhHBkcYj>v4IU7dkjPH}9uCjTR_kcSXhv z=-1e}k~zsY?HUy>l|Ul#5zgu2&Bf``EwT6$Mur#==s&OoV2TNok#M^#cEL+i`1PT= zY9-xIsid&DYuKOgUJ7;A;_6=vmR}aX3;fIPwx4M1tDV{^eo4&9_!$X6m7hQ}tz!nI zY+C%&Fe0NM`zgrLRl($P?w|=9hP_=Za;@Wirr{{fI>64lfU7i;#ic8fFD9EXJB#Dk zSXNr9M8i$u-++#RQ)t`nC*(}mRzyv`JDr*yPY)zjb{l`?8y$+iZgA+WuHI;d2M6QK z1#X+z$o(#Ca=*PX{Mz?(iPJ^d?$gr~;%XxY#)a8MEe|WvbVc?j7;XY5wQA$qo&r5r zQWd6!`T3l9L~CF4vY~RwX_Y!JW1$e2Y7j|i^v9{Gd)yZUNnawGYnZ4oXEhFz#f;iv%E(4?!ntmROqy~y4*+dEF*E6RYF>ZcxqgFo&@}8Cj0HH)VOFQ z!O4r5fI%CtCJ~~btew9~5O#Lv*zq0V9!!bf`-PbH3*N<=I?L9Rb@fIeEq(r6Hc+ae z#LUB@m|Y;-T41D`RngdR-FS9qKR;O&PA>4FTqgmt#2*q2pCB#Ew>f5coqiD|HPW7A zkK>BH2LQN>KUdhwma>kKzplo+rZs$L?+s_@Yeh{!o|33r6Ytr%asS|8+~dvTLk{rK zn%^0v!;^RkSzDK@dq*oi3xRq5iQ#hpUg{NHyu2M8{co3^chp%N6~-ru_#6XiJrr-K z=PJC?C?-0RyTt{?|M?E9DW(w~2H!U8Ha>?KONz3asM;`P`Z+pfwl3$)`jP9?A~e+K zhV-m^U0(BZIqjQsIX|?3C)9j;=h@)jT5l3V#Tb*KDV!A@RaxD*0Lhj67aSIooBd{* zVR<>ezDCO9i}!9hMU85pjED5`@^*P})cXW+m_34aR{ScUj?^FJWrJwvX&9WLn|g9! za@#G;D*ka9u5}2P?7Q+#P+d-DEE-O5dy`1O@1ZJ_!9`PTOx_P5b2hzqPty$rTz3WW z*-kp&hc7zb2W(ocXb9N=%RJZ7(fa<;SvKkcYYh)KX_fuHBa#q}2#X?1Pfs5dIY!Ud zSt$+Dpt-jH@wwn+z|tnbWZ0*vuz0U*Z8c305*0PG&eXNn-SNrP%xb(=5H#bKmrYl! zy;H^`ovHZVJDf^Q6VEW?c04~)Mvsd_@T1-5IpNCq2Pt@4xV_p={jlTLs`k-Z0_+r` zKqhh&@{$Od*L#&H!f2)J$B(|G>TTBO6)??gY?W$MISu0tTRnAJeD4Qs4FX#rf$lU& z;6WZZDM`H_sxu@cCBx`rVGs{M4qcu%r{uZt^=War!(0yW{c+Iv>|`NAJ&Kr$qDC<( zzJxK7E_Fz7@Z@IP&F!rr;Bpic6td%y-}7ADf4ksQR8-s;w)T{yV-D`Ub(x`;_6@+< zbY6DX(e}_QxI2au_|D^u=cu*T*GF}IPwZ8$Z2K&12$9FRFjMS~Xo0}QWN5mi4D8~d zMw=x59@RxI2&YE^Gq_JWrKC>ALeo>kBb{Bo^!Ya-zF__{IYUmFlD zcRX+1N{-KAHQC>~6^~VZ@iY$_T6mm5&#`Q{l;2T6SRl@iGoC4XgA>-jJTEVOP3>C_ zT&z7V&%G8#K_AIJXwx8OO_FM--)1BKR@iKNmjAY9|82AC3)=CyF_K={)ZZ|4IEJ{& za%90DOZEZA`->vOH;gr&#FuT#g4rC_IC#Q5Z|1xn6LTc|w+)B3g50~->%}8her$7? zgt+(}L9|{2vcZm@r@ zZ<~nXoYuIS-}!TycHBK2@@G@iq$b7nvGJLia>-_;A1W^A{vY?520BQ?YXc*Yrd;Sr z8cEV^gC)wEJ>AKK1i(0QT+?G9(4FZ=Eb3Jrp=n3#ERc0DtE<3~aRoDkUcb@beG z^?q#8CDA)ByW5%jP0QwbY#Z+*%M_aQI=*O}H9Jxl8XjfBz`v|Z%@2E!;R_=R77s@) z5!z#luUY~^f145244U`c*Q@v3RfiS@@aHuLVOvgb>batXifSYfzQkM4)_wZW~NxbO6QjY<&^#?r^7G2Uw-$% z%wkCwq(b_1ZERu~?(*RB;bmnw_e$dqLQv;et&JXJ|K_h>t8aw4>R)8lU+ zs+Rgc%@|LcAGjK%Sh2VID=2$`Mq=QZej4N(wt`GjL|o#}{ILK1g+DL-Ukd5}{@Qi) zzpW=IYtH{$;Q@d0f9pEABE;zYpSQSs`@hvIBwldcjQ_nPO6&hAwFzGS_+KFnJjwrg z`P;-Z>;KULfL-tZpKtZ_@-SD6xZaA4DIx@FmmlC#{!bRzUQ>_L@Ep)X-IGQQc4ZF!{sS zl3&7u`^h39R+a2CeQgn>wn*on2%~+#F64-* zZ&~EI)@38Ng@&M2qgLW<^;t#3Z}xxMWS*{d0g6vQ7h2_|BfL7NlC$ZM(mWZrkpoP3@2JH0IpkDaDpEe}r22SDUYU2{ zoHtE1cZp?f@q$e40~YvbwhNdO+LbWD{i`TJi(4mqJ_Ghx?p<{KU(g zNJ~^0m2X2!bL8huG>4w+qY)5mj#IXJgMkNzOy7EtzRPRWA2F2q-vn_R~zDJJ#&IkT$bZi#h;_0`o78m~#wS&QGN$tnC7Ji5j|XWhB;TGvWAZ`==y zmhopzd|ri*H!Cdf)9w6s_)aJM{c z))IUgjSdyayRAN)Z7&)`qhsUf*@cqM_9RI%i)*J!wwVwZn9prP7)(LJeR7_8vrWH z23`%1%-GdkilWBOxq4)wGEdqlayvQy^|IytcPjW^gqWb5{OMeWE8n&O#T*rXIVN~` zIrg`RY#8|mWs#J2Iq9PF)H_wWMl=+%7l@UnHHd7QKZO#k6jzao?Vy!V!_563>ZVG< zaIcEZtKyO170f>jklxxsLy)_sC+L0p?a3K&r+43NdzlaBZl8mA*H<&fqqp4{Qr{DO zJIJ10OsR;2f7?Ah^}$D?@ThFi`R|&E5{l3dC*sH0a~&RPA6s1g$i%F8{Tcgoc%gb4 z@FG^2i@P@je;eP%8eLt*RI$EozTFVh-}^dV*WTsBMRlIO{4`;L@0X8NgCt9G6Z-b2 zoWHrf;5Y$-)G)fnpdBvNXzXFR0E)&fp<^7;Pr51W7~yujT?EYfT_-|X&+eeS?XI9* z_BVU<3;ujQxoHn0r3=x#2?#{CIwuKoFZAOZ-!bV31vpdBy7+PR}43p+M#Oo@wloy?eL1 zx7NHcdW;AqCne5*ler?^vXuEorT(6UGL6}c&$2kQ-Ptg6!@Tx&5ZyAEoTlGse4z~F z@<>h;>X@|SQS}r_5U7GW71Gpnh9Opz{*o|oAe{bKEB8L@$Kgcmh4YK>pXIF@>n>>* zN!xeT@RlEVF-vxUw-jCq)mlM6Zj5L<62w3k#Q?^-Te+3(YV9xtr2UI>QxPnaI z^KegEUvh+zG4g9kWkusDhOq^>BepiBc)T_WVqw5P99jCk85J1zE`FMh`T|}i+JY<< z?|tzp3>49pD~8Z5oOXSV+mQM69TPO#ipP&xWvmm70lW{CM~ z)YNMA#o%Zo+)s9etAXlaQ~J33HQAB+Uj^ebdY(0xLd}m%vE8i`nv~#7u!5<{KE2R4 zmJsv^Y~quJRZSv%+pM0Uq`3@Nq0otjWN!O=Uqt_HSj;zEiR2x(Dysob{>d+ z)xO_K$`%K>9^bseoKbDYQI>6>5VH9>n+{Vq&5gSE{gA_EHXk6l8% z?IJ*>`)G>DN>f{#C-cf)S1vzv^w#k;QH!e@tQDq2U#bxrWY?&mjbMMqVqzL0RPVRS zRUG{UMrx(pCv4ul-u%6H?^CL8xR;kv-zvxTz57Gqp66dW>k7TXvr_o48f=YH4m?+G zl@{`rMXKLzaU~JwEStzsyAD=L^=%u}0`;N~_%F+lmYR6?KUj`6=s2PWVzb4+;l50V zq0stm6j(CMhE})l>L^l&d|rgehEv$`?(%_1Yh9%t*`zM8LFDpl4kY@%Zc<`&hPY2o zDJ0Z#+sjg)IMRe@k}b-;dwPuaQqOil$3wN`B98fs3KJSM+)$gZSKxa)KDJ}g`$>HD z;}Vz=sR@7eo|Eg>!j908C_@xWTrMH4K0>GY;3>RV__yn-TrkwX5w5Ll$5?1SMd<1 zoj~sn6VXCWh+u^?{V@b!cnhgGUXoW834Ncs8;QwH8@Mhfe4s-`eJNJn5Sm+;F zsnqY>MqvL}+lL~^w7=cP_lNy>gTnt)HtoF4IQ_rR1-z@A98xS?WWQm|j}5~e0M=bz zI#teD82s=$2DTNgeAyI==g*(pZI2M{9PXg@qU-JuMfC;4gItAZo`{I?nO=l%a36Jb zGrPdi+w=Z@d~EEb@0OIL3*mtt5HZp~91no=h*3`PcCs)4sUD~+)yS)^R(vv2zI+L# znEBIS>-Sb$CWC+dER&p-OnAv{Pr3-CLo-0`6q=g(D4Rk-WH?t+l7ijE&+|)uoW1i< z%t52!pHpatQ70EI?izI`G-j-5F@lHmXfEc9;gcD1E zEd1*2k^bQn4su2SRDuZh>KNliMOkZ|@>CvY?Om&7qPakBKy(1X^@-iqJ{MnI5u@5! zMF>1{0ua2Inp-E#(;sXe`3D3@n0*dCzr2uebi6>HA;`+g;=T?s*&o)ZZ;9{R*r+0x zG+b(NJ>68%>8~AEC^rK4{kzyJfJRo?_r3*A@UiD1luUP9_}1bY8Y!9(k2w=2i#k<9 z<9#Vppte0bt`bRCZORoCx%R#;e>@iKVuU%aUv`u9_4UndxC%`t)|L(Z-KSF{mp)%7 z?R7G!>1B?znGSX1XZy6i)mjh6B?sm?^;5AYz+&4A@ z(rk1~U;Fg%6iZRJz;tUHn`4~3Ltlfghn_zL-TKkb!bAdl8Y$3UU97S#v0WOqqH`hF zFz}-G%rgk8O>UMF>@p23FmQ6H+Q`G*e9_cQRrkT(W|)@1R&Deh3zDK>N|8n*A}TvP z$nbn<1m-pYh0&Fj%*pAgh@b8cEv{%-c=^klhNE#;4V;a(XIq8LkTtgvbS`TG0YNp{ zdjW8Amoh#}oaxkWZ`!1|C%WxLJmQ-QI;xQM2;$~@F3ClK4+849PHu}}Zjf&R>n|?? z0C+X9(5S|3%1ks{zSL@P{<3UO>9r zphLR51?g^3q`SLIKmjGCyTO<4R!ZQN?vU<|ee*wipHFAb>^U=Mf78)%oQEf_`&w)L z)+->x96G9oxw=TZ)U9uAm6o1?t(B~eO`4zk-fm@nMTMO>f>&@*(6hmzUtjo~Ik*{Y zJAMet&~}mNo1EW`!Z)L537m+2Wb)_6008W1TP&tF2USn!1ca{xcEgM80)AI@nKI$30 zWcmXBRaHEUh{!%q3H|WU&=Q+}z0sjwEtyRBYwVnFHPH!Jlk3j7S}$5#eZB5k2ngb8 zOmlq+x3+dArL{d$l|5b%7$%-Xp=ThncZd>bPF;I?;^L9!BJ78S34oV@PH|-=C9y|; z@9)Wb(;PU{ipEBrkqi;mpCu*IH%jvIHM$17J9Y{nducv0G8TBAuOQ_m1WB_Cc&i6i zV6};fljSdNzsKP341L9|$7Q5vC@Dm|A-cglKyU{%_WYv>`g%lf1Uzczt&y()z~1=B zL*~{@T)fLS(ga#yX=PQ4Dwfr`J}vqf@WP;tNvqO;bV`PkV`2)a@y8D_u>Pdy;gJb? z7QB8FOX{@Jg(Tz#!Q+$wzL}Rm?WE(S(>@#qN@eWXn>u_EyD#hHC8+3;XRk8|T9>xyuqN1bE`OG2;gMxY!fllVxIO1(LRic4Q zKvA)@BIpona66q8WjI&HG&uZQLYSy*(XzUq%~ zG+GbRL`U#aB;R?z#k#<OH41r?!U}7>DF6@hK2s?*uP!6tw3LU1aD*`*D7T8_j6p zMhCwA-CYfvPH@YbuLND6u!=l3PQrL%z++W~^ivpmOTq6X3EqZ>!~>=f$}C9bZEwdL zZhDQJLYZB6;9^vFJiuV*w3PLdZo=wgA5(~k*D}E_byHOxm-UU9@m(7=1iybnF$APt z>6+TD5^iG0s6Nl^Ut#CF8=z)^Lx^9O*@AcagQ}{Ursif6QCNV2g3@~o^ql}bYJHnd zfY@?@+a3acTYcH&U0PBGGZ3VPpKNIIptpm9((krwB%=mUM8QCyshW_KR2san^l(5P zn{Z*lLH&~L-NJiVnPEAC!JYseoi-yA)dVJc%zX}71@jI3(5Cs;00=m4EIk}~<1?oQ zh2 z5LP91#}|iy;1F@*e|^pBk^{FfmSyPoPYS;q;tmOOkh*v=ET9S`HgzV^&k|(1zbuO@ zipSgBM2xfJ<>*mEOG-X^-q&k!Q@pregvrH#h->Na+Z)7Ld;`KR-|?JKu;*_pO+uFz z>2RH`zW2m0ei^YFEX^?~_=V5$qxK_IZD}xn=YzA(%?qE3Z-6&=5`D3YazM zhN+4$$WryeF;L7O#xvrdS)pp44Y#%VRnOUaserJ$x|{D!v5)T^G}GQxE%D;daBuJH z6SzzakHzJi&RRHA53e(q);{$oK2(YA;Hl z(PD4(48Hdgsnq+asmWqgafyHMO7IPPWat*|kZI29@(+nwY$|CjCL@;ndtcMq*|SVA zZG79Cx&4;h>1iyEbxB=!fOwx}6Y0k>btVp8pAjr0F^j3r;Bb-GZjcRCc z10I1feuQTTE)$SZH1K$=i-`kKZgO&RC!0td7eO6!LL;xQw^-pNG82T7=uGqzIY{V= zn2p7XodI!aW7Pi!kMH8#5fB~8i3bir{16QbJsG60i%E+aO`2^k?Z_iq?>B#{ANgL5 z%g`dwqS70MkOfE~Bs0(n1|c0UcS?K=BqJk9oSvS>DSd-{F>1R|(2vUV5w&Kk!FKBJ zSk9{HOnbX+Pa-@9@+?9{84;ZLi_{7{rmpvcA(l!&8JbjJVNBA6Wc``={OCN)WIkBC7VFX!_WbD7(}nq+RH+oj*d}lQ(Tnz4G{%h00mUDMns$ zbLgf-rl@%DRw1nnp(*GZXQr#q@m>Q;z?iV}#U5XyTe&(&-~|vB@h6N@C_+a9pI&;QZ}Z4 z>(%&NJNepm;B9rsi;N#qy?tKFCb^kxl{sZ|TG@VIuCApq+!c!9x_bDpFl$qJr|IvE^(p&X)6X`@z2F zyHhl=-ly_%k$&;F&tUuVgBlrJ@Y{zKo>UIlCgSV883G#6?%6%88-^Ps2E5P4y9489 zUU8{(>}lyNw@*9c;4m0fTf`A3r)p(Q12-u|U+#G7gXN z7}Z5hsQ~rY(GxIU)CY`@Vw`8IOj;3iDK#rXJTB|NmT+v0_7z(tVF2NnBx zulJ(ws95}g(hN?1XF8Z~7?Pp}fr)>IJN$2#YnLxNOn1!?0ixHhk~xNXm^lIuW8-+O z_5?^4vWnOMcH^PMAo|x|Onz{!{=j(%IPLOWCUe+JEfZaGbX5o+5b!_FS=4jrlu{Ei z_ZiJhV;&qF)Xj2yEa~s)2xziY+4MYuc5vddjHD{|aN=L8$;uX8$O&4pH-_wTsvI2obLtRdCrTgf(?490{(vIs&1GiX>Sh;M?_9mbWr9#Nk`*Oc{Sw zIBz$Hyet(Len$ru0+aS_3_LM%c*M_MWBh4jEjGANsczV(*(9W(PRI$7sHp=f1&(my z87d2`ip9?BW9|#45z1@w5$sh|@fkv0T|ITK3jId$w)+dc9Tr19cRh3rjQkU?{Ig4e zc%*|C9GsPp^!jZ6ivx!yXd$U@aes6^Co9J|46bIt8QUcCaChNnG!O-*qYEYINLXlC z+-q!V_+bLkR>*hiA3tdKpRq^5H`>rpDn=2p$6aDDjglK=>=47lKiNNwA?@CynW|`Y zY@d00*ag`fuvcc4(+Kh%zj#n)ea^PkbZL|g3-hV-<0DhXxT(5*8A2$@XzI6eu!BBt zyfU(9n}cXb!BGp zb?or&=Nzs7RvH5wM$IDEs@&Wmo4!XNjjLkPzz zW($}i(hLC`5StvP!u$SMMz-6+!M$JKcl^w;XiH1x+@KPAjH$LaVyoX*NcxeN8LR`Y zEVMUq9?@(P;Pt23QNyOD38~yfX6hA6K9)>9iy*RNl2QNME^FrW{0+Y(V^7hopUL3C z&H+hvK)0FboM*Zl=eP8Yc~;bhV(-?p=vEs`vpS3lI*tXBe3$m_b{#h=k+&qxLYO584eTs^M&z!a`no&wV=&rl)1i zL!=$CRYft+J36vqyEz*|`aG=^SEKoD%~JXorH`;g+PMIVtYIrF-%E-mbriWv2xI(U zEg&YAgpxkPn^t#?q!2cMHL!8;nejE<#>G zWr_=@(9-eUf{oK|Q?{mN5Ds=U7;ri~JUr!}z&q$bEJMaS`=Yw7YVVdQOo_o&FBG51 zu8G7*$hXOh8_MWeD zyLq6-xcXF3#ULfoX12T;^Y`}h;#MWX#g7Y-ebT$I6OJFf66~3{>s`N>zo3EEmMWlZ zj`?XLqYfrurU+&K`7z=MJ`1MW1nC zkavZnnHnaMbOuChxhmU!&YxAiGMnBZ&~XM+N!O6mopicIyjE-xwI;PohD^#5gb=Ag z(MZu;J|~b7Hz$P75`Hd1ztyQT{&AsU-fh@u8menV$piTku_hIEA#!&d|2=fU15D~Y zjXv+yCVX{J63{#VX45Nvh2Qv^vB#@fmTTW%`vKjT zVwJF?_SsB!f<3j(2$_K!`r!JQh+yEa;qOx-d{U=xvkhXGZM6;N^~Z0~?*z|r?bL4H%;t8>K@hMBeR=}c8UvPAW|sdonTR7Al4}Q{ zvv2bf(k2kyAa<#-6$c>3MpR1+Y5#_^4Zi$u^4eT^g?-Y4FwBPhVZbj+jLq;XPsOhB zjobY`j>11+Xx?aEy1sA_{BU1&&LU58I6p4dQb<%&35G&S-$(Bgu(!3oBLoD#xu5px zcb1OF_<-VEP{A2f?~kJrq4?+T1sg}WxTI;UI^D0X30?={UaJTWy39sO5D3w4)hyYs z+L*CYZ|Sf-KlQzJxqf9dJt}Fr$oI`zeNqCG_5*PuEwv@V)-!5!jA6+@>-lQm=IQbI z`5Zi4&(jmGNWz-mB(T-9e}6cv+zOiBF^VsIn(RVj)`&=e3(3wsdnU4N?Fn{Sf< zM+T)v4TJplZ~-m^@V z$zqn2dL#+mlQ=V`fs^Z)PkQ+^CB-JIgM&LFcWNV^0W|~(cagfl|9nW}brqHPXUShj ztEn$jdwBg$@-9lV?za0U<|oQl(b@kRW%2&NqsyGuqQKCG$Ej^{9VXc4fw(}R4zzA6 zF(Gsg+cFRXNQCT{g2B#_Oy70K+sp3y8V;QXpKqTQTjX$)aHn2l!Q9Vw6MoHhCj|@) z#C;kOx$C^-hBuIPh>*c14OlmqWkV6WNuqp|WiA5bNvG8wx%vmQYaS@4{alkqyNsuz zB_a~iM_m30MY_)KF9-16z1Pc)yourNGI8x|J1UFMT+v4!&Hm9Q#%a;*6m!4U*6X~z zU0o^DdnmBm#*&|JL829l_U?{g)WSnyxiMf(iiY`4DK#af^vCNzg-Wmx4{zbyPx5C7 z;05zOx)}pLF8*ixl$p|;jdkj8(H6xCHfC_AbcQy zKo?wlPL8QMA1xpDJP!gwuuF%wu4ctSt@NiN){dcwn8K7O7KPGj0bN8B|QuVe_W`rn$9jB{44biaz_h5G16nII%;p?%WC59X5Y<@q-}y!yh^ZWE+ui zK#b1cTfF_o0&fnjG;OcC>b4*tAYZ8L%~5!6LQJ%FWh^J{A5-RmXpgu=VZcyWQw(z(ding_Dy06fplPO}Gu9foQCF4zqK8HjDMaLe(_B=hz!Byby&H0ivmb?qt+EVV z!3pwbpTQ@_`2oX8($?lj%X}7Ajh#fti>(jN#ZG9VEjhl_2ab=0`T75jO^6xKw9J1= zKMJ$8oPBG4*L9p@zkIhM9qkQCvTsrxN35J%LmcHq<)_2%S;wkiPfq2iz(yoz5O=W8 zVd_Gmp{1QfkB+47Nu#BM_nkeTUGd5`IAUe0wI-ySk+{u`&uL{l4s4K0tSu^%y?+4a z^j$=P9gYLSGgifcnZ39F_VzvlE#Rgl^p>OI)d9WW(n{~MXyy?FW1X@jMwNd{$V`{( z{0V`6;}i2a*N718c$;wHFspD!Y|`cLX1LEU@pk$zftONP+^Fk5b~pAGJ4tXm*n>d) zQ!e&((;fvFeXx#r2=O{)(Ra|u-K8YAQ)lOh@GRKE#L3MR5wZIE8D;;~C$h%WK~lrr z{S z-Cs1Uv<=^)5l5wxWc2#2KZnE$YdR)BFgo)$%UHT?v_rlTM8)`Xi!+)WImtbJ+!*B6 zbc^X?$>5G;8|*AbT=!dD$L2dk94^y@_}@Q%VAmA9UDk<|v$by&e}`;J?CLRGb?Cs` zM8NVZ#emZHNK1Bwjmd?5tjl}N2$r&Y>2cV!aP_H$M|e%l?o>wgyf_ZYdx8b!O1;sxH!7SD6qUn^n(yDO#9Hy&4BK4GcfKN3-3;~g_} zn!ndea~>>?<6+IE|U|I_?caO;2rW8eiMd(mP0V<0Rwum8EJ^yAq|_~nKwctbylj!!<& z&#wl8T_Syns22YU$q_t>`vXacy=zb$z^?pC$`IP>tR(U zpu_6&OE{idGSwLjhQEX)7ffgm#l@lE+ACAgNb~nsPx$?3L#+lPX#Z2p1Qx;l?br7+ zMAQ1VHbXGCyELW@2GpBrmFpMJ#3k^(I7r0*`Zb<$iz z0aR-y)-|l@RA0L}P0!2LOII{TaUUeS*K=Uq$(>(az?9rhtwIs^F8uF=?M)4eK+n8I zV!$rEX>n}?CZpBQ{F$EbCV0+CI?wEMdnR}F={l25@w{6Mn79epmrVGvpH{f4z(>&E z0vMwljGXW7G&KGtGQxK&P?^N8fQXOcOT8*anR~|?Q&^nFn1zGuJVu*`ntydA==36Q|tJ zVnn2SnyeI)g*0*bz7tpSKf|pc?$eP=J8hZeZm`0jsm(@FF{Y);Is9(z-?oZnXK>SI`+7ru++0_0Z3vZ@U6^j*`6vxP4 z9k&y#z50@q^L=@{_L4J=qIo;eS?(EY)CWCcU~R}%tGRm~x7$GK2H%1Y~Gl_xlo5p49hWu zw}*d@VOlXwH?Dez*3SytJ%aW#EdFUdqYlZ4Y9)I4e$bI@$ zw$7@Xw|~0FIXV!8bb8(loEydGGp-!)>{(ANE?`F?uEF|O^OPl&>BZ^ZAUV%3EL708 zE~;~{v*Ckk-Fo?$n3&Xn4kCq`YEj%c;1lxJNoTys}-`_X1nSu{LsJw9mUd zcGe=jUM*FBO&_gokUNPODDGr{YJmFg83Ft0O+N?%&8cUe2d19 zM;Azl(91<0_nEj;WZz!2V706ENEK&T8;JSrymECD;|E;c?)16K7o9 z9gvTY0TgE7`&4OuYpVoEvE*fEixRMCQ$5}jwtoR}g8Sz^;-K{%T+5A&8XAj(c_y7E z^T@kykq7@KNvsQu<0g>MjopwYm9UluIXm#63>_|d%JklQBpYZ^O;Z1*w!6JW>G{O* z4-v6v{BZG_2fxW$L{Zo0w*2RlF=h^(eO)dxGO^tu;ckbg9YFV;n8VRxfCuOOtnOUi z&q}Rf`kZ+nE$%MeX-ZKJt<2ib)ksZO8dbt_e*RRcv^N#cnVg)yVG0{>&;vBS%*7U+ zPi|?JFXN)3Chi}OTQBSE1A!j8oj1Vhe{Pdu4p$`)4lGhqwrd&E_}xW^Glf||v+&cs z6?1n_nf__LLgV=ujICZpnnVOy*86FfAmDx=E9+1dWu0yWBywOv`Pax~&?I@{bh-{} zzt(E>sD9^w$4Sbr_qfna_p3CpO^VYaBP>D~q<)$A{n|_KveSHO8z>7P5 zd6}yIM9ib>rUAfh@p~Y6;6yT5W$1ftaScjz%@3`zg9R9KK#qCHz{RjIxRWf2P$do7Qt@TzC^ z4Qo4Hcs$uxp{#w4Xmj8TxI6d1!+wf_)tkGyx|VKxA2e>Q1NiW3OJku-9P;w6Y*=vg z+nzD7v7?GrQmWbbW~IJtkylvi;adMTzCW`yt5~#4621&dd3YoF`c37odsTWfEc8C7 zobA=o*{0G7(BkAa_b`<b_u;<$%{tnl5H8F1LW+ucy{zoXqpk!=fevc4_k5n+y1iaqIfU;5(~ zYC!_aJr$l#&k(WCY&q&uj{W(to@Bs({mkYf>v6BJ#$zM zZLOYlwTX+H$Ho_YK6EKVt^pA^hrma>)v*mAU*9KS9AmJJ2Svhb$6xN>ibqxIk4Ivh z{Q)4q{XdYkQmyjjMjdC3vYZ7RCpgDgVdQvJBL*E{M=8Q%{7X3EKJf7?kDVpbQ2=sW**@Nl9vkVEyr>^q2(FP<)BI4({`gP6_^wz7CSgFJE5ZqYe0! zijPKjV8)}Sj0!I?qF|AqK-b3r)hsvo>}iiIMKWDhbK-k-PmU63o$~V}FFiqo$@5^V-JT$6S{JMHfD!^VRJmQ);;$y^NEXQhm|1RT~(}umD8=? zDH7os8%*%{Q0-+c>%X!coWlKJYdgMFq1z~4Af=RKuqC2_gSPzE(BM5Y@EXdC``kqkA*c3S zeSGwrx!g#hn4`Rum+)qxy}ybh7Rl*9dls^|(t5d!6MuXvZBqMRAHTgVbRsbbB}e}4)i7fiHxEMQma z&_`rs ztD=xHHU^o2nzTmoA}DIZp;{OPxZ>JPK*FKYZfQzpveZ~6;|z=p2c{C!Xrl;jpk1b+JFI>wl6f3BZ zeL;~XNv&Xw5T6bsZctDLO93y-nSr+GN>iL1y7nA3$%>i?@Ae)F5G!$siGSenIk@tO z&hsN$ILKBFFG?AFUKu(d!QMmP3Af>%hra)#R{zMXqo0k3y+JX5){jteu}$f4%@bfP z<5Tm-q0kUS%XJfR7~}u?=o{&~qdMdoiFMFYkR<6k5LQ-{BuYgS>>v{AFFU52C;nAh zt-{s|Fu?4Na!gyGO_XASF>`#ez_%$lr>Iu^?}Ud99J0>0ZmoBJ{BNyd38gqP+8^iI zlXxEXM!Ddzk1%%Un$)c_^gUtJk_KVNypVN4ehikdL!@G9(a_nhyrdTA5 z_BSKW+BeC&gxvW3@vqKcWu+PZ__eL23-A92OR7#=@rnqeBiqX2luER z*iC}=HlYOFDH4tEw2S(~BXP7_P2k)8)|#3!lTZRAdL~6a!DG4YGKyi<6Lh2R#pO@a zBum%+wVF|X_kTS%;_QkqP$NN&)FAiyQD=^!^t(!Esz*>H5dGU(K7a0Uyy#FLnh(*GqO%SPp!nK5Yp!-_ zbm;_&;oSq_S2ILz@0@4+mjjyi4(6*RQuc%DDW^@0=Eql}gluoPu3bj$ViU-tL2D#f zq<%^NRH(pK)^a3lZQm}CFg7t!qIo0aeOaP+<{SjO{9A0UD@#h$O8Hs@ zmxW_fw-8XUO9X^H;dmu~?K9Lr%oeT$|G|ynHNE9(PW4jyt}{#iK1+@XhR~DDMb&_q z_9q`~ZcwS?W4s^v@!FB64Yfnv?UpJA6R8s8$b?E1TBssH$B$UHa@Jd=T8P;ZHvA16 zFECQMx=T&#nKSqKv40M)$GPpb3g}VPW)k^A<`;{hJVtxO7p;=Aw{w30(^iV4g*Xo&Hlwn|#j0OSevO z>DIGJ0v5HNjHnzvDP61ELpvI!lLo{a%XYg=T70IB(`^Z9rKT@(Fy-=$zlFL7E*#WF z+&;t2{lQ#iMAsBF77{HSB6Jf}D%(@f%FH~)v=U(fgs%lNqOOh~Ze2xK8E6ir#Zo(9SLm_vIrPIgeahak8_p6X>C^7$8zRUQtGc+jeC*q9zZq`-0?cV88d%dp z=0gjOX3b4%-8IP6F9<0M5kB<2Za<&~Z)BN3B@I{_`5sShd}Aw}2u2JFq+>-`rX(r5F|Gj}c!z!p^#|Uf=8d6s~JEZR=Fa#V}~7HOBKs zQj9VMGkS)hH>|Bpp!oc?rGNncM$aoc*5)^184A|)Lx4j?XKOofkWo6lg;%H?8?i~u z&fX;XLca#oB+yfJiVq!3vc+L=nqK&CTOqcZ9hod7hbOU zjimiFmXVOS&mJ@<$#AtohcofKzj^+Og@uCNvYO(}2(DeTtY|3Im<}So_cD=BEXH*C zIeQe5QUsn>CmKvgI~@`{GdsOG&L7>Qr|pMoSGkCbN&OA4%;t)3I=;7!TDMBX94qW^ zho6aWkCSk9HTy@ouyYI@50Qs;yyDVkAzT}@H?MF5_{@|tOi{j7BLMIA&{V0K76wL+ zz4&t~KjLA&Z@(Gv(;-z=U%HT{wA~4}A{sansi-Xp=ct_$uF!Ib?fR|l>BOUAGhQ7Z zJANDw9cG_Jt5|I#qb$pd5DB9vf?=ZNEt6mjzp7w1)upCMaQFy(|F55c<%$>lvd^+~ zOuk1Y5^OEO(eO7_TTV6t=K2*UtqG7#8{_EX zebUoK=S-e&2X?~Of5|cUT!yh9z1HnsaZl*jUL~rfp*8prj3Q&YBr~P;KDVDVx*;+i zc4N#e8OiCaT-qzYnK&|P4>>d#B(t?fu#{hi-_gBR;a%2)Ha{omHSAB z|CblQ*WSm?7dyJ~*2*l+W+p((@Ns$+1B2BCAe@(c-xN|2dDygA8)c!tvL7ZCvRk+I@~6^n1X=S7h1bqj`K^jWzL=A|@b}ns#Z~}xDeL}>@g(bE^sy`n=Vf>}^w0hEqO-=X zxnu~1Kt(vKrjz}${xVD4=~&EsQcE8%iXyt0evXk+fw1|j z6*_?S7p1L;>G{!2-{{P{j(ceNyC=qTF!KK4Zd#c+u57>lojM$lAjj5HP(}N9B?e#G zlo?!jKLmze&WS&*%KT>O5s@A-q{it-nooL#am^wgR(P(>N|(>f`8bOIhJLPSC<`)& zhyC5|jnh5Fp}|kmEWn?z!h9XGUtv1?a!tvYG??IW|G;$CcWkxo_j_X4q0h_#PLmv7 zRsg>Ge6okeArO7jQ>Nq-n5j?}1QuKYvL1Y?N)!wY;aMrQ5~x#EkUwzK^}3@g7R z8LDsC;m}-X(QdGY6*gI-)Ig1i$?#;CyyAo!tB;|A9IKp%*5FR zrLLwb{h0}(En;y0=M_gNof_(+`tQ$*N=r%C^qy{=ryomO2jc$?J^BAkB>8{4Ch&gl zeSV@I9?++BCkZKO@5lO^PGXT6Nl8grb@RMbW`g_q#c2ki*49-A-j;{3d3r7`^#Um= zIbcvfJ3kj{C_ux&NE#SGMZ?7WG7_i!lqzjr5I$)I2~>cD^j(^oc_m?YN#RF{kN*lD zK9JU;si|3JN~{HsjEqTDRdr%%iPgZs!0Zd%dvZZe$0c_+x1o-X4kG$iv$tvV92{zg zu(&uZVzBPYJzHZ0bh#|-&Q5Btkt+d{`kB6~UTcsxqksMJdq_ygw$JKzjKp+0-&6PufTRBfn1NAE-qG>c7yG2`L$(q~n>}$4`LA3V znVB`-3f0R!p||q$^W!aWshuxAJs|`E2~JitK&nmHPaizunkJS3VwamrDv^Wc$+`V; zLm2|GxjV8-N(mbZDRQ7#2Yu~*q4MxIP|;RcXr<2Qj}%<`wDoTK{rmSNmxQTlj$plG z@RzS&hr#lPQbB~O4aDhaoj1*g0PgW-24D>Vpp1))YheH8#yc|@1bq|RtrOPQ&4r;yfY2r5d>At z%*^QNc(v^oo4BRrPKr8txoBx6GC^KK_-5S<1Q1Dcxu{iypPrE3=u<%Ldk#>MWm(h^ zW7Q_#Wck*& zgb4?CYSOn?A`dIr=QrH*jkH+J2nF=#`a#PA-UsPXW(e~lsaFHP2fqS+Nobl6CrCov ze7s94F-b^kBunJ7C(0zEjb0&nz`gz67UVG!zR!MVm-j|-mLS4F)Mnyf<p)%}^&0v@R#0HEq;n?^= zlu7dxUo0rt#2G$Xy6bBP(K$F!a5R7CG6%Eew;7K7T8Z^(lAt8J^7| zxyHKtAopEo@0ni+5I}T~q8c?cm??els&%QUKd(q7e1@==mzO&=LOt>XAHp(3{0&P= zO92-3m^p~oDE+cq5XGIsyxAnt1xQN&D2YD%-)ub=jH22TjUt@kUG7G2O#l&KFmgNT zWID~hglEEo$Tl3NO>~d0IJxgtv(ITe>oPcM#KqA@99=!T;m1b0R)98Xwdc(QbIQFR z8#-9wJRlm4J04O(B3wE-4{7@5f~K00na{3={{l;OwWS=A^Lz>$|00#$vK= zY~)LXP*w(03aZZu%x=uqt7UxU*-=nZ`U-T|dQ5Zkyi!sFE2|V;B$Ju9ajZa41_Zl0 zGDOKwWwrAlF_<@!PnL>9&fM78$Peu@E-ft78pXHW+!^9mbx=&zQIVIQ6oLEwoB3(6 zjY8JvNe9xbrSFt%%k%OJtVu5}u?OmHV3R@H3S`eOs`Ii%Q6#GZn)>>>FsMNA5fItV zy~LagSEtgFkLv!?OgHI#&g!zO58ZszQ}>U1 ztrjjCGYyAx2x8Ad5ak?#1UeTM&|keQ`;us&w+VN9Z|zDA&^OfK zL*Iu9Ai7^7h9jcVQG4#gw4^^;PU#MQr|txv2YS5abiTznyyd;==I^z(GYt2HG_V0lGrlgcfY) z8pazAX6s82nw__lXH^1^2L2SPJeFYNN)NXdzoV8%!n^EFlTp-L{;+vXH<~8g)tJHW z+w_@`Lv33tWayOX#8p$X0cgQHj+Nem_Urn{G33AN+fAzkU2ndm^Sd^^Yo?>4lUld*dSMrYxa@*TwsStvE6R5tZ^|MNGOCcwr5s-&-!vm zxJ@?}TbsT7(WqJF$U{lEOLkkgv{6|nivQZP&4)`rC;cek{F{#t`&{_MkRE$Z%J zJmQP}u^$pB_r&+9mn)&s3E|^TM$N9f46!8KlXYl)&DCao+lL-l+iwoOqoMRm%rv-? zk~d%RnbB=&C2plRB8(DThWh;db$jVb(J3&pAMXv!0?w^Q?i>f!b7RUal^!xl&imP; zM=cFDFy%vcL&J)=QNgu?Z<~!|5$2e z@CO%=uybYN2tpRMiq&-D6*4e34Txw;VMQ!Y-BDn9`GRA6V&oS+apBeXTM|k_%GK?# z<4>lhSA3Q|5i9%+V!(;mS-%ceMzzg<%8;CL}bT3WL{6<<5iGWsSUM7ot0S2z zI+Cy7N14QNTb$4l3x7&g?y7#<-`_jfrwZ=z;RTwfux)t-#M96t8V>U~vi?&Gtu{nvg?M4mY4vLZC z8b~wS$*Ci-Ddu7OknbVuob0D&qSPVPV9c9n(MgP2$rSREt+kZ95ine=t94h4);>r~ zrw6?L*AEaPfA2$~j8EeXEqURX6ijRVvF{TumLCv=Mgaz40Cw6wKA4|%YIefsF(!)t zQIRP1jEanxQFvXDpZynNn@ZN=>%DIg_iMm3u>A$G!fZL$&ei`S>fy$rb$%JzD`$Ilk!6#g7 zrt?gi0k@jLhq3Io$x1CF7Q?2Ol%pAMn%(wUrqlL9Blng&gY#6orPT`EZ2y50kwDcD zhYt+j4B6*@NO=1y4{*w)vt|0;4eCG7*o4O5o#AJaw%jOwexp@VwvrQ_!Za?pFWaJr zHCtyDf52gJV!n@ps-LM9_^VNqhHS{xU~B=EK-PZb)_9^RaP@*3BJp7>#U4O#| zHkv%+?^?X-nT)E%DFjgkQzq3?xy_mk5w3FubI;!NiNAGey|Tq3 zf=h@0a=O+6#fnjha9&jXcFD0Z7m9ICc3&55Xumssvh#*jp_G7m>htJBstA)dRMzkA zAL9b$Wo&hTRa_l;3_EzB#|Ow}%~x^`>x8(!K;`hhvHLcWToJM6(#bo~tGq2+%HA4i zCRm#aIbJ(!T!cC%NuxO!LHqNtj_uB!$zvpVku68le;j(pD988{<@wzmGb$CX-F;NA zq+aWbOArf29-OVWnSy)A$)ddV3EchTFJH#(-gGt}K1d&;(C}TZNED}1a$A8JZ8|N> zjAYV2do_0O@8qi_iyH;jLD-T{?h@yK>*1mSXiVqU`DxP1baNo!`^s&1Rs(Fa?rK4s zw5+D@W2^EVO=!yWNXDxjPsx{%w}O^u)=xlra~b)l`T05>#(Kku<96oZrS5gKFy=EwGu=G z@5pKA>*K;KjbBnb%=!ClBrVsQWcVS96dop1#qagJ&iCj+x6oKgYy!GThu#PvoSg&m zQ1Nj9A>aP$IlB9DFP}{Tm{z2XZX)6vZ0Cj!{cbB32^mt(hz+Z1El1vsb_6v#YwU3n zkMAR6PYhZ>eP-Xill^ob_8gQm^AdBLhWPw6uil%f)$%7S5w}?2{~zqVWm8;T)U})7 z?u5qO-GW1cySoMm!QI{6-8DD_cXthL!QF$qG`u_aQ%}`<>imLJb-wU{hJxM3m$dHra8ln7H5QDO0yX(mWIpHg{x4wA1Ln%1 z>+XO6_p?>0xY%^_Wl&V#v{G$>*X`)AMZ=bA9$hgSphcEXSFd)uQJ^gLo#0b$hsUr8>Y@C2@{kI@!V7VP zq1vyIu*aOf_fI${;NE&kKF>j{84NgOQD|`NSs0PK`#l4rncKl%Tpm}ZIMfJ@TK!H@ zbNyfGm3r;+k%SnXf!hg!V{Sbyn$DVPcXXs%?nIaB&SsnP!?yf+ejk}gdQOi!NgQDM z_bSubO@L$GO6YpiTKhndXc?}CN@v4`?`3|3X%vtJ%#Tm`v9?E4uhZo2^mH$ibrSz9 zpe3)G)QJ_Y)|zEa1B!hLLOcNYOr-?YK_jJqh%KV_i0)5UHIPk!439WUnbZ@vLs(HR zwq{M^QETjm_bnP!ck@h-aU8kr92_0MPbvf!TX!*St7ruZRb<5u5_nr z4PlO~h3=)`_qFvl=S&33=anFMOb!P)NOBe!fGnd!F75{VJDW>kkXLN()x*qPkk!G6 z!rOh3q1BaH2p6PP zso=9ewIZGOL0LCGd+rD85*!HN@DUUwU*FfM_aqC}g?Td;AdFbf+&HI4GwSEZz>`A* zpP~!if{USOLi$81xp_%;haD-28L|Xqyd(DO!|pdVXux^a4Lpov5`k?`Ea~Aa?|rkw zUSwDjDebt~wqnNaS!VA0!Ef9;lhx0h-yC(xD7l$nBD-JDE}jL3J`8Oaji;V!4mh*K ze9L>T76MD55b`pNX7i1Azs?C^+kXPiT86+`3&!#e2<~?3tF;=G&Mr078any?Jpvx~ zy;`$XnwrGDwN?9f(CB@SKcopbB)V&W1yhzEQFg#hNlYVR5lsTdRCpY1Onz(Y>eXgc z**l%3OtZx-Ue?PnKwc9q|GdSj`TMA{l+<$3s-E6$wmkpy-SN)_wPc`IE9gsUP!V(JGu#hwf70amJ&6amlMq{m0@X465PMTxeFY(b#GYS)ORvT=78SXUb?bE5L>Ruw* zfo1c;yDp1W9{r^#F~30(V;bZXtv1gstVWYURcCWSp+PRS-QhzdK6pM9`gV8WVfXEg z#)4d|mx`S$df0vLEaJf*$f3z3V?7_LZjaY>=$*(fxtSnZwUkw~T2DNAYTo_yVZHrE zJ)6(h=kZT~$MWrtR&n%k$-~VQYs(lbrYKbuXgqCi=W3r9WczY|_Hzh^I>f&Ly1?!e zgErQ+mt<;>-$J0FP@Qck3{dXvT|3cb;5W)ppD6oJc6j=oV?9niDT8l3KaC zQa^kMGu2=$4&WA-Ozky#Y`DRQ7-*?8zUNpEtt}exeM>o+A38=e#Q)EklrI+L($1{AD>dt+ zGSN+jCBUsk(kr*S5W2to;%ek=B4+&!9w-g@3S$gE#W!Q$xMMkb;rpQDjdP6zA;t2F zV$3rH7xi($a(=p-TKgkwR$5$MFviu7YW;d~a_g4Q&(pw?a+1CL6!vq9S-(zom~P-I zL=Jx+p#7kVc0Z!^XK`H!JY(##o{8#Q=TNPnkc;#>5@Q#Itcyja9=QdY<~}?U`#kV@ zPjXWi*ny10-Ch95NMrZ3E0@(832;G>UGYM$WdE&jfO&+|G+0QaB=LxxZ|+AOZB=C` z5@Lpu091eOd_J8zvV~U^@sL9lgmC-c{q_UJ7JB_o_&v5#R=z-~mSa8YtTT19{Hbn| zA>p8{ArvM;&&^uXx`D*KW!7(@35_9Gv{>~KUh*3qUa-{QY#s{sWTO>3A}vh=dFE$l za}TXfT)Cw`Sqt%}x{0?tNiC*=HadZeJPM@kA~dQMUXsa3Nh#$&Uys-C-^0#M#36{tVKb{K@&_(@0By%?wk6!-M~iri^J%es96a(cW}jm~sgc8w z=!mVyhlluShr@KJ0Z2^SzSm22w^o-sZU5Ae!-ThR!!dzW6X>b@n+#SS>dK@z;Q>m! z?%iF>vZeDu+C;=Rl->Y|RMR=sprQm3uC8P5yQ(dof^78uD9*{7BJrM;Mpd+KZg=J0 zO-CY-RZEG!*>3&eWI3!L7N`mh8{`d_jZ}$+HnJ=t+P;f{5X0&hhH+sJ;1kNuFtQNw zE_rhwOwim^;ZTdtdoK+|oMi|9w0fN3MMH!YN~Yj!us5F0FG}V~4wx#>QFz;+?ZNR3 zEhqGN#;<&sXZ-RlFRT>VFCV9NduVi)>a*{J$$2+^J-n9qhZ~>$w-PlrVHyz&sxlqpnb%8rq7wf1Nx|sgE$u?C-p4IX0=+4O(^=x)l6T z{Pj76Skiwx1EjLvp2B+DSM-I?@UPvp#adwf(+o67-`#+GEqnxW2sdXGliK0?^#BgS z+b2|BN66<|9L|`%SZMyAAOqF!F9g*rewZ#(x=|gLj4l9_GyL~4ooZb9(*-K*(#a4+ z6}jCS0^PYkn%!jb*hN*hT~&vzEE4_{BH-XRPW#i=I}SF;H-aVveYE%EN?Rj{AmzCX z!6-g)Ws{3J!>{fv=V^drf>E1bC;ZINjV8TaAiT`894x%zQ0M4G!mR2Qwr>_dM^aCy zZ>lj9Cq-%~2P_*U`ll`bK#mT#)qBG0H(27M!dV43)3i9hAg=hf1CsAh zyrjO&K=pIZ=@1lDEOIdEq!Nv97J%oX5L!6pWI!}CP305ori6_k_)N9Z<80^SR_%E5 zX7s^uC-yM-@`;W5+UCPRX2i@GvUHzhzZ(zONq_**XN#4mM3JLLBfvK=xt)3Ex zE#Ym#dFO1`cI0Wj*|u9e?XlA*$~q{0hm{B97~M_-N)A7kl>rUb<03{E!mtJ>#HG8*mt+Oh5792viGuk>KMgU)tn8+OUmrjjAzEfbd3I{l$y`@>hh&R8z;uNPIybJ!Inn z>0(t;e(44!^dF{L{Q_;$<=E92c&cqblA3_{UB$e$-?|$YP(Fg1cLkhM6!Ho_E^||fz0gkV4kLHKyOi|qc@S*q_-Cs659M^AIHb$pme* ziBy&xgXaPt6)=*=dCn6|#`j)`eQ4B6Nej`!#lDVEKyRVx1j3-qqqz&yKcxqf{UC51 z980dVdWulysS32%Untkm*goNUU5s?_qlr9LlNW18d8{JZ;#{hdy|RAB*{r@MJJ*QM19Nb3Sp{Jm z;~QhZ=r1wd;tW-oA&fjRkfcq#v9ULzNN$4%rHaP<1DsIKoq8TsC8EiqQbi?L_1ofp zOrqKfnyX~u9#a@>nOJ(Oz%;A|?N~$oi@XW0G-`x1GN-x(( zf#!3$VXQv{FXWwvCPr_&YB(_sO@M3oJ>^;k8nbA*29(2O=4b0gYnlUpYBYruV%Cdk zPk=rYa(g9=6f{jV$1hWDFMPArkm2|cz+%@YmHwzyG#9*ozSbx+mHsBooxK_(xVJvZ zESo^D8nqZM2#zxmieLi;1wyFbaxN}WFvr}G4buN1?%cAKA|}y5j{PPTg~;$x zRA|n$Qb}Z8XzIgo&lg8dQMJukC?%0MitXq^nHGd=pK3_*4?9T^{z0^}K5L#)(Ej@o z2%(-hu=zu`q;G;XPK#>4?0msPcYdyP@2?}R#j=@s_qcBw+kHhx z;j6(w+=ys)n^g+b4P&6lpsWN`KMz8zcdQra9gy^7%R8lR}x?8<*@Zoxo+ZUR8 zeAKWwlG(s4H?mT0p`5vmP7lj4Uvo)QnmqfGWrhdf_ z3_95vc&dD?Nc(~Y!DNgQxjA^OPw#YU$YFIUT1|BX1n%_wCH<#6)QcN~sQTO=W zXd{P#y#z&Bstii4o7w%ggqJa&bT`E-jNa{G#BP)EPXaxfTYixn9n0h*7HiN(?vroX;UdG+RFz@((9KSqt8I6 z$X0y_tJ*wz48H-;lvsd|Js1Ui-OpKt+;Po^T*Cz9XkxWA5P={C8M!$!p1-a)DZfsF znPkG+dK@L?ZN5eC>S&KduPF-A;;XLi16I@{9@~!mr+YUMyw9c8&L2uuqd!t9@9gZF z4AdCX&6Zt89sY7Gi8*l@nb4;VM3}Z{e6EvgGuw%D^5Ga*(GTJn4&}5*&Ns#HSTIpMSkLMF_62yn$HP9u zvk84Kfn8sGmo0X$1$lvC1#lKU)J>U}Oq! zkGsXgG3>$!s3EZ&(-UsAW&a24pRmCQ<~SV#CLb?Ys#151-I{1)2A*rMddXrFujUYy z2nwZ1+_b2WKL&;-OzR^3&>aR`CyhpznT`i$8y=Adj1Z>~o*SIH^oC8pQ8(OQtQk{A z+;o{)730Ep+<&8wAsisX$pk%SEg5K_T3Kk96Qv%88>^salc`jKz68NsFer=DJc=1y zL4qU55>(ILC-^SR>=hzl?*DFx976G%qIh+Lt@=a4oHz<&X*v>G13AKzQ@jC4)LNEm#boGy-;b5lvouVR>9J^*=divKlo0IJC@P z=~|6`$x4=1=+ldWm=%Q*iSK2)kf6nCzDY=EBn$$r#3|gMGOLM!c)D0Og)Xp*Kgw*! zU%0!U2$cG@m9M8?RksLrYQE)rOxL`T`IV83MJ~%3!9mMdq!j8;13|*lKSPjJ;c5jB zTkA~``Q<%#zAlbUnHFdq7J=V1A+t*b(dTh%u9skx91w#H5&ie4Wj`kv?b2 zfrT`M`xGAZg_Pjoe6@P1_o;UMnBDY_$g}xpEa{gtU2)#pTjAB21CS9OhgF{CB3V_d zO%yAG&esj>)=8V|A{qCA;Zt_MRLKh3&GwQ*Y5k5&hQ$g4178W>e6lhFf4CqNOz|*N zKTowJf4D#l?<5*%D&;KdoXjn{)mvtVgJVb#i5a9Y*jz>sL=RUsTrlNaBj@6hf>(y- z8YZ&nE^2nwQHz{od|&Uh?2sMz#7g0(vx*xF0RdT^Rh8dnl^$YAUil^RyIOgr_nMmJ zK2NC@yZi-i&hG>eAsR=}XAGNCOPnR!yc{JYwp)4&g01A9-Ca7`xi0<^ZL5CbMa=q##9;?LNVJNhEbNHvX>7GdI3F`pH}mES>CziEaVzN<<(g2|KuhFL*V{dF(KCrG)bXq^ROvrH261XfB zXbp=;Je;cFc9B_7t5DaQN{zGF;&l%1I*LfBvvkehAC3^I_yzz5ByE?9e{tBXiEhfM zu5~%{=`H=Cf?!t6qQCTF35B264^!Snd0;m&jc5`&E3ia7JUD3&aiu@~IrnhB-dS=; zP>!17x<&xK>qKK_d2q5;Vs}A~+#MS}USBc(DY` zjxV|W4(xUny0O6*upavR;Bp~98U}1Yx^*|W0kAQ!3867}c<#7K4RA0oeZrI9@GO35 zek1JJ?0ms@zscUjs!SdGHs-gLZB^T}d9mTGvGH_f3%SGb+XZFfG&*|0n62!7cL4>O znKp$?u)iMDOQ-v3T2Jz>2F#N*q7kz0R78&kX+x0(OJkE66AyzZy+@RKVEiXhOu7z^qH(ha%`8 zBGf&HW-(+Y%o3Ri3l>xkr!mM2~p_n8MQ1bDMG!ObOUdB2q-51Q4u&<8Ak_>(oUfp6L=FTp`UkjOcAW69Q;wj3KQ=;XLYi_Ai8VO zWmkd+v65p)7r|Rkr1^b}hmh+vhBy@89A**yj%GQV!g=m)Tt;va0cnD4i~x?n#%z%v zy57l&?BO1mr##WvMcc*nPHKY=t1CEXHvX9Ng-Vr|Dv1Q*NNI~#H_i2uTZ(wbhZv`1 zilU&d#f3Iv=RP$57`6Mo6HmpVvh0xAo>WWc2m*#T^_po@{zw zeT%+->6$j_>t8zey%>J+?7Dwj^StxK{k|rk*t?0Nt3Ev? zY`qK4&dm)S=qp4fJOXPHMFw~W*9QedRM^-;~`arHpTl6@^E#z>H2Av9F2|L+l7+Z4{?Sc&WP#vE`sh$CH4AOuHKLzfvjV%?d z$xK~s5^iZrej3_X3ncp^-XipS@Xj=62szTy{mt)WDiG7FTW4hd6lW@QhXNXC+X*kE zQ-br;b5TQXv!tPH_;>zG3{a?WEuq9RYRK1jI|d22eYHDeF|v?QB*VGZ z&@hm12nsO-#li%4N?Oy%^|u>>6h2}04;hDcm>?7cA->ohxujO}yNtgl1}K6m-!uzH zqaz3E&Hnw;(60Zq+X-|W!Vl2Ujeoe|Zmr0XJ;nC$o>J1#^Y9eH{+S1IKMT^X76iF- z?cgC&zgsa2s};XJjo1F7vGW2`AmVpL?8H>XZ;Z2jts<|T>eG`3CCNr4drkt<=+Pw~ zSAtNXzmHvKlt$oY8e}nG*f!9&q{xU}^SMe>HqW_vY`rOCg&dBmC41xLDKmhRu6l*B z{{6vz8f4(DDdLjkVgL%TmFeU5M6wA6zw;QDo<|6$eITMvO@sJD z)%vxVGpAOQQ4xWlQ|U|DJms#K|tMyjx(wQ9;e?Iv$QSg0_blNAEa#l}!R+k>?}IPr0lZ zu4eUpdTA0ht8)<66=|_<4Hqvdv;B4)ObkJ6NWj1T(`!40i7jdvCGpI4!$&4kycVsq zsv@=9D*(sAvCZYUz|t#ZY_1G#unrO1X<~FVkxB~iQ&8~wYGssXlsigZu{GACR11TYSU5;qZR+?r^#F2a( zfb8ub9k&U1{5j%bqTeR206p=jPBa#A5h3?}2TRX@AB#bR)nZ=K9EjW=*~AC#fDpsS zLH_RKZtCz$-)NdJ&?J5yXis z^nNXKSGbP2y}Xv5P(VyI>h1CHws0YWe(YS6t@CiNRE>j%r#Sz1=||~$LNTsh8E4z2 zcR3;h0-T`gyv`>y3#n?%MimBI1d-Zf0nS$4IvtuK<+d9PEA?6{XRw3WxvMY{yj6%5 zrb6eIO~{P`;tW#%e`sFPVdIs@t-TZqqdu(69To{244=0ftD;Te=JRwBJaPhtQ2 zAovZ^Kt4kxSuNI?O$l*|8bIk-NgAB=B;>Or`qhkeqpit$MG`KuVavpK-4j~GnZY(6Kxv%B+s^9>AbDZ4{zlnVshr9$6!b-HRWWp$GPjt_>JwoF}-t(H4Zf~-42RABr7 z;APO8b$?>imIThxMS81E9ipj zxu)F=G&fzym&j`DYSPWa$?T<<{zG6+29HkO=kwBXixRnj!AgABR3~r=TC#+E^Yiph z=F7+#Gq_T34yR7-KV0xUTzy@HTo>|P>eA!0M&++s+XSXCaIzjoRZ1GYb$yuf=O;2a z6N}|CkJ$To{xPtCY%;~tcYFCe3{2-=GF1Y!v{Jce06%W*%6Pc(x{O%6$&3^zPg9PL zB37#Mp-8=H%FGjD1VmjqtXEV4@X!A3F}uU_Z`{q(wH;d9mBLz&Aj_rlBJp47#~&f5 z0o=ttV2laAPN~qSC|;fYtfYj!_qWv%@U@N#yARy*3~-*I>--Gam{KvF9097Dg_a@k zw)Sy}sJ?2oz6cNoQV51b?L*c_gUse4lRsFC)&= zQzlT*2V|`2QEhn1(cJu*F{UUN23+8Wk_&0&bh?L=nI#ZCu{cFQRX^LTK#c7p`}}fy z!uU(OLA6%q?KnD<3sF?K+sC^|u3|9(&=?mh_2>a-BJm*8C>9FU2;g&@_0pi~l&z^y zEu)i&A&6HHdY5I=ZPENj1LFG85JkYF9Bet+PXH$V_yqVwx&~Q{4uat^zyB_cr#rY( zzGA;-J@mbi`}#tf!-BwJKC2mxy58=bIE0Kp*1r~-kj`U}ns!Yd0tn#bXRA%)IB|{N z0hM;P)sY11?(NlfZ>6#CrN3gbksJoiAE2Y8bc4Ycbl=v&`&n(yr*wpTPH{=VL<#YQ zZ=TTr;tNR7sx*p_UEHXAND_!Sy8k*rK=hUnW(f6n9>*Xlg;B?BbJ#N!1O}qtIen9= z(SQ6DkBI{wzEa6_YEFDXb1~VpE0Z8^7L*D1-MfHI<1HSS*PsaXzEjpHw6ZVybOERP z%k>lj0wUz|tfAGj`#TD{);QCHO0HUXrBSe0%koq^X? zaA9!~;Ox^Ya=p-}J3*iB^t?;>AtmMEJ+4tXk8pY`^-KlM5mafG*Bc!hn>EeNda}h~ zhyxM|u+{+kK-(B#wA`N^j8y(9UWp+3_NMBivnl9ABO4h4L(1zmO=7;<6pgToD)^{~ zLcp(SmZ{FDGn&O?EBSop)aomwGRPhRDS8SRdq%6)+r6V2|V_)DFS zppzHMpRM3Uj6*ej1wYUJ^yEr^pJ+aj^Jf|zz5X}C<$kKJ)nXSPF=MmdHY)T!=c_VB zV0qqZP3}*o+5YfX%`iD>UN!}Y&%rdK>IgFNsNQcMlu>1QzY=;QE>mtSR}CX6;Uoyl zG9-r411cHvk25(`b0$Wlf zw-2V1X-uB?=L@?IQNWrW9SCPG#koFQsAvQ?*{srqq7pAUKHK^}2EJbI#@3nN{OHuk z3fwI30^G34#1&wMM5->5{!ob)uxIfuFg>=Lt!}}Q9ZtZ8^@h9 zH4Ez7e0$6JE4@s)I~uFm)U zBPSHA{4f1_b)VNKQ*d@g!8x=DgyEXORBpBL$nSLaLWyRZI$(7QFcBFdQQO;dnrxWn zj^_7=E_}EG9J1s8oM?elE}*BQrQ5QNPS0_G@brCmD7wymj_Ry?4HmQi--+YO%h>1< zH$aM?rzcG5`ZvwTj0VP;tH z%LDO<-$yWIxiBX7WQd`V`#St~-7lbM%eQ6(ZGRG~3V> z4k;{mwQ)eitvImJ&tBu}d&j{1Y*YJpy;&jl_eCM07Q;TZdLG(nUJ$R6Bbjqm zAu5T#5Z)daIXRvDQCev0bQ!+M-O-_;rR!ysFCJJG5M9D&pq zt2gtExRuwQS4739naoq?gg0V+yq`;5i9~(_dmP2#wn+3FiB&2VOi;FKwoOMCXyg-< zHbpAAW_LRc+P)XCVf-?dP?{c!A{AwrHhsQ5{&hI9`2_e$Oue)lEy_rL>*^>0WsD{G z`ObfLqm#bV<8HoaiAB;*7s**vq*D8?6F416)B)To48C|2w``pzGuju_zf0bFEdWi6 zZ9DLG-0*+B0H?Rqf~nQIi!UPWc>mY!GRmG4@e%w|VS0e&9+Jv58vj#b?|PpeI)8BQ z7MXxWai!U^0@IV<6wZj>Jt(2Ak6ji5o{*5k&d-Cy(SgW5v4&#DpaV!($*5@@EZ?4Xj#Lgt7$(BgA09Zal0$t-F9xUn&Gc; zX+G?-pL&{JT1EvQ7*6(o>1{YN@ErWF6m{!z`>Yz3UsH9O`+lu4VIr0nL%b>P2wJMr zp$1&H{+|K^(ECHj0i~ey5awg554A|77%?&Ji0-i;Hz%k6t83L)J;1KhcQ=U$?_W8-u}?!8D!NUKW@?EzSpjLs%g;Xcjkli9-8QH(*rRM}`8^_XxxJ-ghIFt6Z}aJM zC&v%Mo}0dQn;MxZF!LxqmdI7lY~xwUw{7Im+b2lM=PmpqKxAln;EuWdmA!WDhQB{G zrAaC<8;Je-H}&%zQQW(&9}V)*{~UMShk%DajG@=N+q?H&fMW1nw1$BaYrESh2C{@0 zTof*+wm|#)A+s-S8aJkXdUkTrQ{)CM(RMELR0i#6Hm}TjP#ayG>)FaL(m9m-ulrl? zukUX%oy7_Pe=DuR57wM2o1X4{y27#$mZzZ_XE#(Nk!_=a<17K81*eo^@mn4s zA=kehA1<8W-l~iskV9KOs+VaOeOvy-uj=*$)LjRBIDeS4=!Nq+#1OX6ZYc*8Xy8WQ z{=q7H`^2=ZAB)PEEw0!cl*_9A4!6hj2Cu0(grY}m^N;V*BpS%y23nd5H<_E3Yk5`} zNg%wCA_N{Hlfxh88}_89L+d-;u?hW79!sXjW>^ZA$nL^Kz&@pG6WK)V_;ouqCHOAf zzZeZ28Wc9(Gn1MZ0HTbtjEq%>(re}{$5kV~BG5!eIW)C4}M zM>=SKH2OU%GM~T_3R(cdFXWvRok2>{oZlUgo(QdzlIB%K?DalUyrF!QHDq1!SG*2{MLph2 zb-AafLnY!bRg0n(9qRl7_i(zTfQS}9ex!#{%S}BZ^MYXRHETjf$CKj3rqqZ+fXPMKceXC_}zdI%3&_2uhG`6Q0564uL zWM}Cj7|cuxc;3%@`T+dUGh)ho%Oxi5!HnJ6X3cB3+E_{00tA*%Gp4%pUx83;Z59SB z&@Zze#irKsG7tvbAcc8GzsA>B((1||N$GEIcoawaFXGQasiV#Y z^hq3*aV)J(vhI}_i!2#p>%jkOE+aXkK?-i z1ihhv7`ttNUhd)e5|B>}xNBAzr5)^dcWwcn>BqsDyh2et!4 zav;cLR>?OE+LMuklG|G$&OPS5Q4b=o!)0TBn|eR{yyx#acgcK7gio!WZED)9~c zZ0>V+wM#t@*Uu&c`hFzCkiUl$#n&H-oCr*gSmv&@yXIEIq5pUBa|B=0n)=fZBiUAO z_#R%i-UEFPz(LQecAKFuTg7zehhAr0sCUk_o32NH+>NxgE!0(kk><1!WvD629M(ha6Kn3d~-I^g&BLq%%~SMI}>H7cHL$<8tk_F0yskn z!#F#p8J1@A^*r?sF_{tF2luu}(PY?Y_Xv!QEz8pkmov9?6`1{v_sUCP(M64jH^gnR zEuk8ydRu-hu;iuVJ@aEw==nf{J-2{~w8uQ$SYBF7^JX4g~r^5TEta6no*a{svN_P;Ti&MMouI`0s?yE z&}e}GmN-!!=zA`OaX)_=KYzsDTGN>H_l!1e0Cs_s?eVd(MT_3lx!HybyJ)PByB?a< zsSwhtV)`{^@l2fJHhdu@i|0i&c=?wN>@!eqN3BF1ZDGt+u_r?exDD1XDA=ZMc%zv5 zfO`*5FN-v!NouO)r;KzXFoWHh0V-rEoSuX;u@$03$}mMBl{F|G+ri&YLF=8fK z(R*i#o#}twPu5R9&jU~7Mdw_-9KpjW7g$v2$<_uSGOh*ll+xne6lq&K46j#4mUv&k z%?Z+>^wR-9vigs|0j$iqx`;5gZU zY#EYxIw7!!YqQ89rNR(HC{S!El?W4%?|+C^`}=N&SxY(cCeIExL^J}6_fj!2G|Qb? zrO9f&Ks;kOyTlr33iE@H+Gz#5NCFr@8`Qtm+hc)p{)r2~MqgPm`kWS{!r zwJ#pvz~4wuea^uv-WOoJSoh0^bJGp$pCuD%;EnTjT3mnxLmAmg|53K!>NneQKR%vx z))gB1fljM5b~erPuPuCpR3TiIUm0tijuO;WV(@?J{1kPgAh13OBL!zh?L>dar5u1h z+g*=2{T64WJVi3#9AqH^L${KlYbhKJ_TUI4%}NzEfun=r656Pm4RFjuhJ-GA4S!aA zRSxD;K*V={2^^2~NlW~Brh(^;_El8#b8sfhN>CfM|GXb?XxwOXS4rKc3pc}M|IzUJ zw(h#$>D$44`q>JOVLdUujEq}#b#Op+!a$uyMNErg3Wix$aQhtgMI?=Me$KpK;%}hO zmAV$H>K4;mhBV|769Mz%(_Nroe{F4swD8MJOe_SLxlNgJw8K^5!Tg^{(|frwfbQqd ziPfQ_6@hj*vcN+BwFcQJ-Fr(BPVE07p^3_NP4Djy{J$hL2p6G;2=nIX2h)w%Octnj zeoa}}?yW%IPZszW7q74U6rZTTNC6Mg3Q8>QPWoN50p8`w^if4X4&fem!pQ<0yI}qn z?`6t0ErO_m`N7SOd!OSy#novtBg(l1w^A$#!n+R0TZ`PR(> z=|NmdGWW&tgo1NhEoI$yQE79b{*?!>$}*e()Tg=Vn~r~_4biM^$@oOb=lB1<2{c3d0pk%!9IjM+a`@km0fLlZEsOd8yb65%xn^ke z=YL)RzS6j+f&ObL{rCD9AyvqK2l{`nM>zobA^-oc{eS)=LDxI4`s21UFfedLO-Co) z-R*c7^WDTm3Ro6@x><8<3kvAq)=9az9-g|Ul>y;FS{YzbwEDhx@w;8-g)v5#gqxk~ z$VLHjh|G^4KhSkjn6T+or|nbUFV@?sQHcc^j4c1%B{amqokYa@%kkn3sY5;lT z`sQT8;bfi`Kqu4gR7;~$1p(R?(Dtt`hvUj2Sg6oQg+a!X%;t5b0`A^?1@wmfF&}^=X$omKhGqPNNxVP5o7%PX=Y`Y{c9!)?A zC;;g|?nN2!HtQ&w#F26N-D;4C1$9rBDyUmR0Aj-a{h8C#RzIrA`x}_*G9piDcj#Z+ z7MN7&AFBTfv@X8?J(>Y93^~JeA*_P`Mno+Zsu%&_H76^uHVr|dR$BBRACiHX%O8DO zkqDMQ+7SS?!|=S{z`EJ_R2|%4xhfB6eYX|e$YS4om9A^jc7+Ecup{}q0gXn+!ND|K z3xs+7u#dZf64Bm`(q!765XALxG$Zn3DudE=GE3>o^X%FA?vE=lnSD_|BnO)f=^fjp zxW-CfC->%zB|)*Y6@zcyoUkv!(WC!hyFoS1?5kT5ec9HbY^wbI)8G98I>^)j;K zzZZSb>$wjeX&-jXG^bQHrpsor?Tf>xv5&MwVP8c7Ah<=Yc^f1SEVQZ zXo6n=+UnTZYO{<}Op%nmupoiF*&~=w0_YAH(?$MayWI@ga-*kf?-XD_RXoz>mIvl)DQ`9e!Xsl- zc^fUq!LX=%eNQ()h79LKCzONP70XhYibORvfS`Rbx3DO`$FUVE>IA9_$1>S%^pq?F11eq*;bZB%xCfO@k+py$~OaD z*q6w|*XwHv@VzxMG3t5A?LMG008uRk5gL8ADs!_4=g?<;-uoLfOm+_&0LS*mc)NudtoPL*i_^@l{0p!_9OO)m4<<$}6-?+2uym}jj)ajaK z*LdGn|=8Ki-Kys30Lh{|01K*op9*ceO>i|9?5lDm_ z4S+`6mxp&`L6;yGkBa+8d)ZS8jtKh;3^g7Aa$BO~w;et}m2o%hgDM+^66De-ppXWd zrBs_h`iO3~&w8q+u`eVnQd~Huz9M>3>TnQ~bkoHjrhjoY06XQ=-)gzc&urEw9Omn7 z$#xFdbfU-K@4%>napV%QE zK^Tfvx6vZ%?@BV+O*dG5GDo1|FjT|+x^Q&1NTv?qiyheMDuEI&;-ZQiP-UsQE(4sT z&I?+{Fenq^b4U$*Z=$U>yY!}ly`3%=Gb5V+ZDMmVF{EPv;C zB%b{G*fiIiiS>{G2ZtH3PbcbkdP)JUm^<5+1V~tv5x{Q)5R?)<2(4EdQkLs~j03Vh zNBK{H42?l-AOqn3C~|$Tf(Zl(Mr3~Q9|60yY$mT7-eN}4A?AS+9ZN+PG^fEJAgW$!+KVfjs4=Zb)g)) zdg*F^$>8+QNl)AtgQLyvZU((NRrM-uO08u|0_K8(LXnsI6;oCsqr#uQY*j^nSBlO8 z?2UjX1Dn%`Vt9C&_GQViLRMVSc*-%@5sdnk&tgwfj<4*Kik!oZ!@kdt;#Xiq$Rj6z z2nvZvugxiW=BIcWX_(-d>)-!QOs0gG{Zk;#H-mI=9nz5yiLW$%QNXYlc^6n45p5WRX`=!%d1UMD$G~K9Q;9563ZejqshNj@_5qYb}T?X*D z6WPeRDbC{cmIgpi7vqj$J>G|B&CUVOqaCd$WydqW+JC3tg#7Dv4B{@3`$dR&APKl6gGFE{CoEvfpOhbz&$@c zB!}M_wkfpT~v+0f2xiiI((DZ>bUSTh9!sFz5tM6 z$4)pc5zg@o8tr$fPA0UQeLCgG;0;WbbNA~DuHwl<)&54~^Qj}6>#4@R%K?E*9{E1X ztqwiAks9X!LODSM7S$q*GbG0mhpF@sF<(;5qGK5rg=m`JDQGqCE2PPFq0|Yfh^+Bz z*e`T?_F{#l`!}FP+?a;8dJWEwiVBFE2b>Fl^{4*UmDu|_Jd4*|`ekpt-gGqYYv6Tf zU^Bub<`luk?2m~A3Rc#yfC8pV#u{~$alaEeTV$!B)#=e_wNWSL;7Dvi0l8u5>Vq=r zxb3<*%7yqzdQ-4dZI9aZoB>eM6qRBP!#q7b-nal~gE_Xg_w#K$piNfzM>OSw>BKaY zMIb|x2P3=Zyzl?ch(Le)N1d53oAWThz`!u+d%OY;RZ(7#SHpBcp~!^ggB>(Mhe+sA z0s!PhMPVPHD3t9qyVw6Qx=+};OKKr~TV93GEDArb+%qO!8f6Gnd+1yvy;jyE-6%9#iM(rQ)@ zK_)KVNdFfh(=R)((`-R`0p2C{;BVF)t=%1V7CxT5d3|zx79!n6Jr|mmqSpUE-F9j{n(F#%`b|^}!L~0~46QmUc zK_np|&Xaz}@0|b6b)El?EB{E|&-)(Fec$i>x=Zrc9;PAcd5L25ha6JWxlg0#9^7r( zHk}}o#%)nK{);~|q*5YtLDAYRXl}H*Kkr#Cfc4f-`@tB(LD9}vuWR9nGMAbxV#t9LDT(LfZPku>FU7ArJ&b48c=R$SH*9D!uj(F z{P1M%jhtQ8!JIrF9I)Ze-%Sq4UR47JIRm8>_A)b*3XMyBtAN|iMyg7$Ww+YaVg|l# zs3zE^T~_}3J<_P#Kjsu8Vw$i$8{T5uz; zH2A@TQVHA?tcBXcs&J_(KmI#nab&YYeFPzv?PdDorn@3D`pM7c5a%T9?S01OoL}Nf z5mmkDAfp)N1jy>8-EvKUm&hKyO2$Om9oWT6;?925wYs{s%Jkz9fI;&LfNHuAt_;wx zCta0_QXy+jY>cjV@A>dkcfb5;%X_OpDoL4E0O$B63rv3MjR=Km0Mto+|C>2Q_t~be zn<3b8g=jyrx+y;E^X6p?w&%lN|N9?V$?<7vma=z+vC2oJg0C5PIc#;S4F6LKq|}wR zZxHo9XTCumvn3Q*<_(slY}Yw${s~Z2s)so#-}fP-Of{Q3%de|sd zp1?u3zIXxx5_o_^>0tgpS*K(gKYm;Ptnxy-%@^m~VOI2hVl&ldeUw5p>xJBjtt*8G z_x>udDJocZ=#5?-as~M~`OyC1h80n27U1?7P7k%4HQAqh@8#9^*?3JL9B_0(X+lU) z_^zXVZ!bP-MK}1}2a;-G`6JxX?u0H(g`WBxJdcP9FHC#%v%^lWcLR(B*z}sCG z9E1TcBZMkRIaUYYl2jimNh8vqriUx3Tc-s$`He;WO3TKqgA?s-1vnHg8Azio&?tT><}p@zt`x4- zKL?}a4CDp91GFvHxf({VUvB1-3=y9NZ}>G4%-C3|;Hh8M7ph!p_U)2A*Q~Tn=D-5@ z!Tq`Ka<7sAWh>(*_P%xm#{X&5y1->qmO`spDXw&D@gX#Gbhzk7$uM#*iX6HE0_~h) zM4oM`S#kkntZtb}CD>5<%Zyh2s}z3Ca>NJoqS~{O4=YC<=(tOToe`RjdPxAXS7^k6 zRK3S@f3BPd5BC#R9(^6n7RF$DmX8W(BR)`pY1qTp8qXkIw~m^XLpcz+B2}H!C7GE| zY7m3=CPEX9*Xg@kE@tYBIYYSou|zKFU?f~(tCCd!M1oth^4eD#yFXm2c=JYgS$?0x zuDSN#tJp|U+E%MZ1BYEu0ItV#sCtJZC<(P6`DIpAzsdynv{|bb9&6hW*BW^AF;P6* zvrN=jy)7eDc)_M2#zSQ{y;MB*&mOp&*5S98yt$Pl>4{pd#nZ1dGIF9i8Xsp$-{`t^#K+aG-V@wK?mv%>%N_=Z>Z|Y-M8WFh2Ew8k*-s3!vkLU0p^VBOOMcs zLlv(gL>m9e1$fKN_K`URx(kQR9ft0i7|6$pSLcodfXY+=e6zh5Hc1~lDAcC^vKrTd zNlCpuk?GG$82eTy5Q~)g&Z^JAnLAz(Uo%^EZoCP)TAu-I(!nJlcKOrRy`UBF8Gu#| z_I}<4;u!rpEY94}A``dmw_Xh^KA<^m5|uD_b9aGp@Qev59@Rg*lzl^Slg4J+Sw;V& zidLns0#1Gbd3^@VJU(76lKA zM88VFIN)yVtM+?kGz)+(z{)z>{sJh)(_SrJZt<4*T0-cHfu1{T_cwnWWtXB+T&MVSUhhy6XlZ>57S<{(kr2eyGlwu0P@@jr8e%GK}w! zYYyCeTyZ7Gyn0XkOUnppF=!9cEWv)^~V_Xsi1<>+1_4KmqNbOKSoQohWITn(OJU=cqCXD)yEAt!jv`|5D! zIrA&lC=doOF{mf|HE)$Wbi~*qYdtFV?pztN-75OrXF|^A19v#2; zRoAwnvY4l1yUd;q0w|1cf(-90x@WKkpH@xApyYxnSn53W=E@vVBk+Oa zF0I{{{94c&SW1Kz06hEY`5~~BTW%wc`@MKs95&HY^0w9_tbA)mS&xmJf~FI`r``oH zf@7j7K-?O%j0NB$jkbJmdinK`{yXj`z}x&!h6jMEcswe3mI9QHJC?z|>~`Qk{d%Y! z!5kxSvB>iB8P+{}*7ykFa|JqL0{?P%KOA){9oAlE{p)>#5s?}`5I z#4q}Z*(L`p;(pBzOdAJ}3q04x6lp)ap|sqUYeRbeNocTpkpj1CqhKC~U;TE1T6GJm z1ez}x>k0xu2w^?X-WLs2$c2<8s2cBAGYTNGv2&@GR5F9cDnU7h8YUd6z~hAode;rnW_N9M*Zt<_ z|FqzC+)duIO}I($`sd`OnCFrbh8AwDhQfu)+tt~3r)p1!{2v9bZh1Ec$xHnxjY;<1 zdd{yB?)S2?p2E+cKR5bjpUK-OXMuIL=GY#6{Bm)i29Xupp=OA@XqIBEm3`Ob;qs? zEM>k#w52vZ6Y$mg;dt2LZ-1<}cXt#X0{lPg3B-^ksY{uwBz@Sy*;tpuhY!O1^9f=6 zdm15ov~PbM(-Axp$IxB^3HbsBJQu3}m>3qDk7RZ1(WHNOECAhg_xvNk*ytL$B_{?s z9Jl{c2(e0BY6y#~lq$?gJ7R%WKXIF#`Zcn#{!71Q^U_1G!|jKLRIvtbw9}qW#++U%zZbyW(^s-2P!7aOZ`b0O4nQb#LQG#+1eC#+=@HXp`}#x116GEu_Nl}$Sys&uWpdRJQ#u8LTD;dJN4k+CZm&(5Fvwx}^<`a`c> zzt`e(cEOUzYrCM|vkkUO?4o(Mn=>om#oaTk5i^>Dh zk?!u(hmnxEpW@pWl3#fh4D5f?_}p%j!h>rZaL9+ZO{3p6i5HKWgeTvcDkmFD9s4e` z?pZ^U$oWmWJ=hOXg_SZE?nZ6AkRYgZH%BMxv)Vh;c^SuQKG}t*^VcU~)N8>rU=$iP z_vYB7a!BWFWA-cAmTbREbg7NSN-7wi#t4n1JHyMBnS&+|<`D{M(#kuNo>=yuMViN& zC7~Z6wwwsAVunvrznoZ|*IF$UL(p^$sV320*N=b(sPccz*K3NEz)pin*mnNI}Z z#j`OU4(C6e3lC3MhTlsXj>z4ex19U_2U@FmY`th(bl2`&znafmB$-dv=6Avm?s$2~ z>_S8B+lL?JNe*|l9}+f|pF4?@-roN4M_XIl<$q?5Gc=P|vHKejCMs-r8&DUt8V05_ zfU-l@Oqc*K%504i8U81b4Y6wgOUrjRynQ{K71Q4$G6sXCpT}HLk-eaPBy04}14}$~ z3wJg)D%(2ze1lj9`Z{EP%5R0j6y>$Qw<{NGKG^S=nyWiP7gj-mN?VGRL9a$^>g5x1 z2jut5ZdbhX!ZG$s;j@M17f*tYk>Gm~a^xK4YIU=Bzki<+nudPPR`0Tol0rGtBmag&GgS}Awmk##T(9Pik78QiyuC6G*smY= z2?9?6&aw`N-KK{M0en2JHCz8P)^IucQdZv2GIS%v>VVV|o=t^`4MJ(anG9qQFp?Ed z9T3mwgYVNF{I@F`wKw)}&ykWjbl5WUlni0d@F>^h+|VN?u}#nFZ^$+-f3y9EpAuK& zzdk>A{Qn%U?ff(ILSy&lovQCDD=YVxOTEg-aQTk-pZIj?x9@&Rb2FSs*Z=+7Bfvtl zFwbN$>;7g-{#BQirKl&&HzX_Wyl$P*dg56TPU-Lb1L@_Tj{fsDSqCd+xm`iN?y&EF zn1@ieU?{x3Zdpv&(eYdyv+rOU&;*5h%%e61vcGvy)o7W>dawC8P zj*L^>DR%%Nv8?`nmX)!P;|l!~p>j>0{@kB;v;OmgqSXm9eiKst`z&)|jnhWG|1_6* z_@`se22TA+H1GEFQ=`G;U38UQa-#xtV?Y4zKRwmH`XeC8YPk>@tC7UZ89a#oSH&VQ zG-sJYx1U$_-}A(6=}_D4##eJjo(N=H&9atM{@ba!XBV?GG95Dyopw&#q1IY=ZGY;2 z8UK9o2KHw7a_@sy+`-t7x(K@~zIBZ0|M@vJ|J`+xz!@yIK$PfHKFJ)H<=!MAW=FrVu(+lnMx-Uk$V9S>`7UdXV zOBgHvc|mR_ryB34Tn@Z;e==jVjB=}?`)KUQ{ri=E8Lilu=Kt)3oU+h8LpELY+%~3B z0wUAy^m661Pu%3ie`iVn#knn*(EXif|3uB7ihY^=dFS1Xf9FPM$K}WP$rinqX$^Yq z&2694eLnI=&+n@za}~jv2|8R=%uT4*GW>K<>fPRycjR?WSB7z_8_aixk2=lrmU{AA zuPa`8WS}Rbc=^tWXL5TFt2t($j8NQfn3{5@+hJZun%1vjOBteQt%5)NvV~&_nCj{+ z3r#aAN-@2Y@!3vRacJQh;BWX%=}DVE?>c+q=*l|$cGJO>#@V8{#uD0E$&czb4*5{s zI$AC*D~PQfK7nvROW`BpjeKL^xws8c6@xAros)8mvrFe@{q`PzC+v@b+Xy`&f!~ONx>PFTINpGtzmT zZme#y2a2wfmJrZBoqK4trg&^}@oj*XTcw~e`edOOlj5HzXqk7H5LCXmPA*#amF<~M z?^RE7tYAApr*4iuHNm4R?P^RjBb7@PhNx-fJ>Xr&f4QQ^XgBn)I8T z5Wm`MQ}izf*8XYUB%vYff7%{BTnPP?6l~6pZ?R&gx`nh|S0a6Bu|T|-jM<^1g+!Q1 z8g_omQbQGnm?`rx6cbRXXU8ep_9oJy%;CvYdj0%oRyH;o*7xE9Jh}of1~G;n43o@q)NB9d><=VI3ub^f&P6ks|$%sW?wf*5is% zglc3!{Fr16-wt^yHR~j6pjFj;p)O3-djELs@OSk1IV5k?)18v`ooz%fPii%guyuYp z_7+xXkLBY9twv6Y5JI2MitV=|0f|ZkpA9B3MfARQyXrW4b3^ zD`nCn-oGt?V2d16D!OV~bDL&=?!G;tHc|AR@T_kegrIb{b|q!x09HxzjNLuEFK6O7 zY-ig)nd)Hso|#5~zHlBN3W$g914Hn^;``~Rx0444t-q1t&La?9L#K|v?OIH3q@S$!;K7_} z3%smoT+GO1b0I8caw8>G(2?M&A{ofw`L+XM)+3O}16`6uaX}P*>6kFhf7qsN5_Zev zVMn_PK}sCuGbAA$rh1jeCx}cmFi0exemgM>33qPRY?_TrQ7N0x_FUev3lDP*TheKg z5EozPKJ$8KUEC@x7<^ZxqoWnqf|!wa>jlsqkXjGzy4@KCW|8em*6%AVDYVjGtv@B^NdFytQHa@GB&?N7I=VV;qGq3? zsynGcs{gDLGh(Xml}B^^HPyUOQjXVuEmn18>##NgO+30b>pbdU4zI&{!A&Xs`GI-B z(wl0gG7G>1wHEbi2}Y8R44&sfW1-x5s0zu{y4%ofwb(^xJDGlxqe8er&nNz*b>fkx zguumZWfE!tD{J%;bRjEVsLu8NZ3umbD7F0U4rv<`v@hu5P*1D86n&Ss*bxaTuSrUj z@S(~0;XthC>fipXiG=`ZFtbanEO2f3()hGI>&U!a>t>(CB+#icg zLX`$0y^yD7`rQlydg6aTJ{RPn=}9DbT75&=-;Gece|w~`^@ucMWhPidT|>iF1#xcC zn6cE+MAV2}p8b>L8sQUh_b@i@j3vk{EznDdrm0IeR zpQ%@hXAFWGXWQd{E~a~D(ED{*>7lRj@`wWddNE_eZ@`?5NH3JUyEWMRjGe3VOteOX zuzPi7b_@VgV64&w>UDw+V`)w;x1*`eURpYms}DvkPT|OakvKOuhfC6OE!)0JDfiFY z<627!VM4&U>}Z!&Vnhz;y=2OcBh7C^t<;De(~@|0ff|Ck%7j9B5z;*{kBLQL!?c)LO`wR+Ej@`eA^5VTa9622!z2uk^vIoR*rYn2r{le(QKVx4#;=8hu1d%n?NbxS9eYI0AikJHTxCrlX8FgRwLR zV3V#!ihnbVJ4LdF2D_-=C;$X@uSz7Z<)wn_ichDOc$KkXWs%2| zM{Bqe=EhW9>v&UrQ1J2yg0M1!AP71(Yb;M|tTsokXsxz3`YvyD6t+=Mb}xwI{C~hO zOq?B3x?k7iyVwUY&?0J5%iL zXI!qsDwA!RV~B;+Elw?QS)9oE31Z~Z+pJ|?PrG#k&lwD>(|-Z zNa5ojc{3f!9ZV1^)2NsSpz6kawyx#!Qt^QEoJg)9i7#64*S2hMAUvs=Z6l#i?oCX4#G)K3O74X zcUR8{#x`qcP|greg9de%iKDhu)$nU#(>?wg-1P^x}0-cOCA^Vvny#gX4u3 zuAimi98CEbg;IjQ$p+{?`}!>jfh$hKdXSB7*k<1S8~ow472STc3gK|kX04dKG>H>&Jm;&5Y6}Ki6H4f zKq=_*{YNf&v}K}_Zi$7$;~~Gx8T5;N6%9r+CTobHPW78z>;I9?GSW|Q$3!Tr5oO;E z;B8E;Z|N{k5a0JnR8JEZ+~Xf2b0Vb8j16@cCZohOcjtEEfKyC6W?M^1RDGXQU3ril*boX5KC85{G|!k<)fLETbsn=s}X)w4zn-N zlT2inaKZ~G$zz%QK!a8_Uu0*LcR1ywk^`qnBLR5*F<4gY&|;oGtjMBX$v1#EaEC*V zSf_@s3>qqPWi|V6@GSB~5s*|SuK?PdXefEVj^&#^ACD%@h*%Lt@OnP%I+#P02TA9~ zi~rp2=MZ#0(%?eGPLU9)x^Npi5LP=@Af~6^a@f*Dd0QRy`95ug6_=WDU(nS-v?O&6wpjh`?oDHbm=#$S?%qpA+c38*4sT^A47J>)U+IFD2e?C(eU zUw$gu4u!g^idYXht!896Hen({J}R)=U%J|QVE*%C{cS`-z&Otf-@9DJhaJbb^zUOi zZ)w7fHWqxyVfui#h4v3P9Z}&TgA!zDuVNrsOhi;j4*V-+$xNMy^y$0pjE!hh0TC>G zoU@!3TJrF{0tK(c0g3jpRv{L6R7{P>+p;NN47iwlxzUd4kJx|MdY|STHhh9ey=3;* zk_BP-PP>h?$r62{$h~bWPOo12C)y&MKo7wNfs(d2IpI}C_Z^4zB_}|`iy3}Jm#j9p zO~)gD)KTIMmE+v6uUx^IwS+8;<6$IrNwcFZi97+qif*?MG2#M=B@}0K?mnn2^m5x6 zT>TfS$dl;E+1UAGdx>idL>jMy&iSR9T}<9*L(EA{7|PIz9myMNm%3SkMaQE>%Q6A| zd5>)vQbw-i-5+nWZTEK6sriEM-wRynD~s*Cjq^eJRNp60$uBsYjWPRj=KIG(Y$QkL zaf08UwMEKpFy*m=s!k@;A-QK*i3d4Kohu|?D9#Mt6U`HjIxeJ^6}wk&jD&Qs;6p4M z_~4*qSDgzx|CVvb^#qlyzH*6{We(R=cZbxT)14|O%0F_lT4+(RA}a6rd>UMQ0_KpV z&gL#S24xnyqIuS#K6#SH4IJeQy&98Z^@}_MP~9cRZIEB%%}6^UrbPZz%#u192Dpxa zE>HWkYydNYq7P6FzH2u5)odZGc^{8$?@TMie}+qx>oOJu)=Qp_-bQvOHgr|@hwGPeXnbNFG7XA7h#ZSrzvZFK?f^O?SZw;t=)fOf!Xy92VvJL_gtlQK)NP7|Y_snl;kx zS1z8jafDN0U|`Uh>Z_J!2ex^bs(&w-&13eZDj&WuAV5x89R4vTtWEir#`})6y+>sO zQa68;Nud8sG*k#=YY+N9d%C}9DMXbWUwHmKe^NEel@dkn{(y1Qy?B}bVG=(z;4zK9 zPUetpX)oM4JmULQG!N!L{_(F9)2M(vEXN^_p=QE{4hMGCQ#A0KYsScJBl4~bd$x0IEx`^*{eFI^B6J~!$l#0Mk~|@ z?u6`D&SHT~6IQ1DZMZ8b%mRt4MD_mR+az`9V-g%9NJTi9Vr&~i_rTmu#u9`$!)hL& z)7*+jkMX8bjQ@1GRhm97+IMWs4!$rM|Ma2@naI4&s*(D3+Umjs^d_Ix@{k~SX*GR4 zr@McuPTihoiI6DG23~;JD$}qP)Sn2UEl)Tv4ld>Z^Fcfo&!q zPVzPxz_uV)aOEA6JyAgZeZY)-0fKNv6Nqf#xRV`1_q!KShh0{r@Bo$OV!z~Xmo;IC z$3>6)@*T8%^ixK6U*{S zOCXV6J^IR${eFhRn(Ivd+2~|<%i(NK6(vGXpC2|7Xw!{|T~QhF_SMr`=wOAPWVgXm z`sy%7`?rgB52!@+BTLMu;F@eiHd(-{oSYLEP`?`qx#Q#UC3Nyf9R6;60YgAwu>0K0 zUE`~~3u#y{5zyG9SE!w2n=D$`T@$nVFc&n+B+ZHEX_|wiIFYb*4*b38yhX>pWGc=g zDc+udNpNK8CrC9N@bApI(Id=0GC)feco40(RmVbZaq_&F*4!)$`_XhtqAAUQzY@}u z1+hm_#GkSDm`PhLpgF&?DX!>fx-Gs}gED%X7#``>4tH?16M*%dd2B7)Fu16;kQ}PX zo}gH`yNwM#tu$Ax`xJCb_t{bjRb@vO1QXYyveH+&$=%d`1J%0?siH4+Z@i)DyRMP{b#$~3MVLh3?c`R+{&ZcSe zg{W*ksGm(dZO^8&wQTr0P>JI%$t6%QCU zncpp?3WQ~uybw)aN4ssdk>wg}C^&}dZtH^Updfo5o}3haW+_jL0zL5%Y}$lG-#FC@ z9)K?3YPqTEP?2qsvLKV5PVq0|jZOOh7^|cwcW0)EZ&d~<(hG)uK|U5-=-MmHD)q(vGrg8{YJRphD!i;(kG@fVYm>?j8hjb@!?-2ptk(G9j+iw+<6K zbW8#6-#X3L-anM=>7H|AoS8G6!kb*YXIY`0nuEg8oZ`O}BE}2?Y{;e8Hl*}{1Pi2S zvm#RTWe$nj0Zct59IS$YnbE_xpCFng8vZpF4H_-AQzz~h{LNxnmy3CYJuX04|q4VuisbNB-Tc;;e*#zeZ0#u1PdX+f}!d;o{6qPcq z5ng%uDbtcOl%KrC+zE*sDK_F8Qk45S0qF4dV%#K$=Yeuen^rNbMzrRxZ@;(F8KM|d z2^sEols5zRRKkn^S&A?4N2O1HnY2Z>P2%wF-V&$WSBm%10-;@$liZ@009VqN3CW_? zBH(Crs4K$6DWJuYVM+E%+NqtxvtSl)Md_^lx;25~tkEV6Cl%H^YGFK0t;xtz`VHQq zzik>GdP>5wIrEk7R%C>8M%#r?;DVFykPH|a>?I zs4h#Z@r^a~WE#@_fDX#Rig!MN9+ENzj=IXcgN5=uZ&$ck zg+4J18DQ5^5HJ}Ms7X(XhvKa{g{A@0I=iR%$u5!QTJ5pJka3c*0ACy2Lvunrqhc`O1cIL`C2s#`c0&( zHykH?yw1~)?tu|#Y@(?xg0qogsDtQ|qo~+dnDRR$q@O^70Py{jE zZpup?w+q6J?(9LsFj_QX{H*SpCDPb(9^rS zyw&MwCcS{`f-xElZ>b0>#omye!@Bryx7~1 z$1F6%Wf?lj_}Ynr4Y|O?S_dR-f0I+!xp*10`a^%54&MMg({Yc_{hFGg!(FOz#L(=g zYaon7P(i;X`K=#Gd$2I}D@ClmI|zz&jzd%DnFYl}C)(|jGr@nQr2(dIZ-w%HTaek- z)Ufm3dU^x)6fdNgPew4PLdwLcBlLH=_4rf2g6xnm| zzngDG}_hQ$QidRT}xI-pBDZ9Ncl+gI;IspsF$v5!7FAb+} zFr5P4jkJfs_T1!!l-cPxB69h^2hUveF%%>ky6uUuhr`+`YI8toMd!?P{%T*pq@yqm zCHhqFEg0bIlt4Qe&W=^g*O-)NY=3GxGBsWm2J>atdX;l5XXa7?Y2?Q^rIc;gH2o9b z-jP-GrO%LJl*0?-w-ZtTyU5Gxl@W3#Gc_$k-wD&c(6R8D5&;Zpc&a=a-rqy4OSU8txi`#1s-6^K(oPZZD6!+s= zGOL~%UQ_htM^^(+bS}UDHi&U;{HZdLN|TsigK2$aYEGTzxvJ7bzkJnz<1ijLkbnTr z;M5j)*jQ|*vQxd1dXbY7N#(DJn2b~I@)AOkXDl`kd+D0N$wpyg>ND{jy_!>1hX0}m zZfXv!aCE8QVLK#pJF}WX{-Lv?W16CQF2sZEjE82J255~IGEUAAiV0jwCL&zWo>Af% zuNeSPKg#!p-xx5`iW}oBHTvk*-Q_rjv0T6RMI0{T$CeYgL+!}0CXS^f1oz0@+R zigT!v-pyJiY!A06#!rq1Xb|YOrtgOzeqs5439K;a#8t* z6`wNL9H1k-@DWgEx!UNo3)g zO7Mn}j72Z5Q6*XOub0dNZ1k(3Q?(Ms>s$B5AEru;wC-~ZT&HvlqH+|~r Z`g2&r52wOq>m{tNT>9r?@%cMZ{|j;HRj>d6 literal 0 HcmV?d00001 diff --git a/res/systemd/rdfeed-refresh.service b/res/systemd/rdfeed-refresh.service new file mode 100644 index 0000000..2d9ae06 --- /dev/null +++ b/res/systemd/rdfeed-refresh.service @@ -0,0 +1,8 @@ +[Unit] +Description=Load the apps of the rdfeed workspaces again +Documentation=man:rdfeed(1) +After=network-online.target + +[Service] +Type=oneshot +ExecStart=@BINDIR@/rdfeed refresh --quiet diff --git a/res/systemd/rdfeed-refresh.timer b/res/systemd/rdfeed-refresh.timer new file mode 100644 index 0000000..45f4a3e --- /dev/null +++ b/res/systemd/rdfeed-refresh.timer @@ -0,0 +1,11 @@ +[Unit] +Description=Load the apps of the rdfeed workspaces again every day +Documentation=man:rdfeed(1) + +[Timer] +OnCalendar=daily +RandomizedDelaySec=1h +Persistent=true + +[Install] +WantedBy=timers.target diff --git a/src/fetch/rdfeed-fetch b/src/fetch/rdfeed-fetch new file mode 100755 index 0000000..957632b --- /dev/null +++ b/src/fetch/rdfeed-fetch @@ -0,0 +1,617 @@ +#!/usr/bin/env python3 +# +# rdfeed-fetch: sign in to an RD Web Access feed and download its apps +# +# The feed is the one Windows subscribes to under "RemoteApp and Desktop +# Connections". Signing in takes NTLM, which curl no longer always has, so it +# is done here with nothing but the Python standard library. +# +# rdfeed-fetch --url URL --user USER [--domain DOMAIN] --out DIR +# +# The password comes on stdin. DIR gets feed.xml, apps.tsv (app, type, title, +# file types), rdp/.rdp and icons/.png. Progress goes to stdout, one +# tab-separated line each: +# +# workspace the name the company gave the feed +# domain the Windows domain the server signed us in to +# app +# skip <title> <reason> +# +# Exit codes: 0 done, 10 user name or password wrong, 11 server not reached, +# 12 no feed at that address, 2 bad arguments. +# +# Copyright (C) 2026 Felitendo +# SPDX-License-Identifier: GPL-3.0-or-later + +import argparse +import base64 +import hmac +import html.parser +import http.client +import os +import re +import socket +import ssl +import struct +import sys +import time +import unicodedata +import urllib.parse +import xml.etree.ElementTree as ET +import zlib + +EXIT_AUTH = 10 +EXIT_NETWORK = 11 +EXIT_NOFEED = 12 + +# WebFeedLogin.aspx hands back a forms ticket in its body, and the Windows +# client returns it as a cookie. The cookie name is not documented and differs +# between versions, so it goes out under every name in use. IIS reads its own. +FEED_AUTH_COOKIES = ("TSWAFeedAuthCookie", ".ASPXAUTH", "TSWAFeedAuth") + +PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n" + +# The file types of the usual apps, for feeds that list none. Windows only +# offers "Open with" for the types the administrator entered, and most never +# enter any. Matched as whole words against the program, the title and the +# alias of an app. +KNOWN_TYPES = [ + (("winword", "word"), "docx docm doc dotx dotm dot rtf odt"), + (("excel",), "xlsx xlsm xlsb xls xltx xltm csv ods"), + (("powerpnt", "powerpoint"), "pptx pptm ppt potx potm ppsx pps odp"), + (("onenote",), "one"), + (("outlook",), "msg"), + (("visio",), "vsdx vsd"), + (("msaccess", "access"), "accdb mdb"), + (("winproj", "project"), "mpp"), + (("mspub", "publisher"), "pub"), + (("acrobat", "acrord32", "acrord", "pdf24", "foxit"), "pdf"), + (("trueview", "dwgviewr", "autocad", "acad"), "dwg dxf"), + (("notepad",), "txt log"), +] + + +class FeedError(Exception): + code = EXIT_NOFEED + + +class AuthError(FeedError): + code = EXIT_AUTH + + +# ---------------------------------------------------------------- NTLMv2 (MS-NLMP) + +def md4(data): + """hashlib has no MD4 any more on OpenSSL 3, and NTLM needs it.""" + def rol(v, s): + v &= 0xFFFFFFFF + return ((v << s) | (v >> (32 - s))) & 0xFFFFFFFF + + msg = bytearray(data) + b"\x80" + while len(msg) % 64 != 56: + msg.append(0) + msg += struct.pack("<Q", (8 * len(data)) & 0xFFFFFFFFFFFFFFFF) + a, b, c, d = 0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476 + for off in range(0, len(msg), 64): + x = struct.unpack("<16I", msg[off:off + 64]) + aa, bb, cc, dd = a, b, c, d + for i in (0, 4, 8, 12): + a = rol(a + ((b & c) | (~b & d)) + x[i], 3) + d = rol(d + ((a & b) | (~a & c)) + x[i + 1], 7) + c = rol(c + ((d & a) | (~d & b)) + x[i + 2], 11) + b = rol(b + ((c & d) | (~c & a)) + x[i + 3], 19) + for i in (0, 1, 2, 3): + a = rol(a + ((b & c) | (b & d) | (c & d)) + x[i] + 0x5A827999, 3) + d = rol(d + ((a & b) | (a & c) | (b & c)) + x[i + 4] + 0x5A827999, 5) + c = rol(c + ((d & a) | (d & b) | (a & b)) + x[i + 8] + 0x5A827999, 9) + b = rol(b + ((c & d) | (c & a) | (d & a)) + x[i + 12] + 0x5A827999, 13) + for i in (0, 2, 1, 3): + a = rol(a + (b ^ c ^ d) + x[i] + 0x6ED9EBA1, 3) + d = rol(d + (a ^ b ^ c) + x[i + 8] + 0x6ED9EBA1, 9) + c = rol(c + (d ^ a ^ b) + x[i + 4] + 0x6ED9EBA1, 11) + b = rol(b + (c ^ d ^ a) + x[i + 12] + 0x6ED9EBA1, 15) + a, b = (a + aa) & 0xFFFFFFFF, (b + bb) & 0xFFFFFFFF + c, d = (c + cc) & 0xFFFFFFFF, (d + dd) & 0xFFFFFFFF + return struct.pack("<4I", a, b, c, d) + + +# UNICODE | REQUEST_TARGET | NTLM | ALWAYS_SIGN | EXTENDED_SESSIONSECURITY | +# TARGET_INFO | VERSION | 128 | 56 +NTLM_FLAGS = (0x00000001 | 0x00000004 | 0x00000200 | 0x00008000 | 0x00080000 + | 0x00800000 | 0x02000000 | 0x20000000 | 0x80000000) +NTLM_VERSION = bytes([10, 0, 0x61, 0x4A, 0, 0, 0, 15]) + + +def ntlm_negotiate(): + return (b"NTLMSSP\x00" + struct.pack("<II", 1, NTLM_FLAGS | 0x00000002) + + struct.pack("<HHI", 0, 0, 40) * 2 + NTLM_VERSION) + + +def ntlm_av_pairs(info): + pairs, i = {}, 0 + while i + 4 <= len(info): + av_id, length = struct.unpack("<HH", info[i:i + 4]) + i += 4 + if av_id == 0: + break + pairs[av_id] = info[i:i + length] + i += length + return pairs + + +def ntowfv2(user, domain, password): + return hmac.new(md4(password.encode("utf-16-le")), + (user.upper() + domain).encode("utf-16-le"), "md5").digest() + + +def ntlmv2_response(key, server_challenge, client_challenge, timestamp, target_info): + temp = (b"\x01\x01" + b"\x00" * 6 + timestamp + client_challenge + b"\x00" * 4 + + target_info + b"\x00" * 4) + return hmac.new(key, server_challenge + temp, "md5").digest() + temp + + +def ntlm_authenticate(challenge, user, domain, password): + """The AUTHENTICATE message. domain None takes the server's domain.""" + if challenge[:8] != b"NTLMSSP\x00" or struct.unpack("<I", challenge[8:12])[0] != 2: + raise FeedError("invalid NTLM challenge") + server_flags = struct.unpack("<I", challenge[20:24])[0] + server_challenge = challenge[24:32] + info_len, _, info_off = struct.unpack("<HHI", challenge[40:48]) + target_info = challenge[info_off:info_off + info_len] + av = ntlm_av_pairs(target_info) + if domain is None: + domain = av.get(2, b"").decode("utf-16-le") + + key = ntowfv2(user, domain, password) + client_challenge = os.urandom(8) + timestamp = av.get(7) or struct.pack("<Q", int((time.time() + 11644473600) * 10_000_000)) + nt_response = ntlmv2_response(key, server_challenge, client_challenge, timestamp, target_info) + # With a server timestamp the LM response is left empty (MS-NLMP 3.1.5.1.2). + if 7 in av: + lm_response = b"\x00" * 24 + else: + lm_response = hmac.new(key, server_challenge + client_challenge, "md5").digest() + client_challenge + + workstation = socket.gethostname().split(".")[0].upper() + fields = [lm_response, nt_response, domain.encode("utf-16-le"), user.encode("utf-16-le"), + workstation.encode("utf-16-le"), b""] + offset, secbufs, payload = 72, b"", b"" + for field in fields: + secbufs += struct.pack("<HHI", len(field), len(field), offset) + payload += field + offset += len(field) + message = (b"NTLMSSP\x00" + struct.pack("<I", 3) + secbufs + + struct.pack("<I", server_flags & NTLM_FLAGS) + NTLM_VERSION + payload) + return message, domain + + +# ---------------------------------------------------------------- HTTP + +class HiddenInputs(html.parser.HTMLParser): + def __init__(self): + super().__init__() + self.fields, self.action = {}, None + + def handle_starttag(self, tag, attrs): + a = dict(attrs) + if tag == "form" and self.action is None: + self.action = a.get("action") + elif tag == "input" and (a.get("type") or "").lower() == "hidden" and a.get("name"): + self.fields[a["name"]] = a.get("value") or "" + + +class Session: + """One keep-alive connection with cookies. NTLM signs in the connection, + not the request, so the handshake has to stay on one socket.""" + + def __init__(self, url, user, domain, password, timeout=30): + parts = urllib.parse.urlsplit(url) + self.scheme = parts.scheme + self.host = parts.hostname + self.port = parts.port + self.user, self.domain, self.password = user, domain, password + self.detected_domain = domain + self.timeout = timeout + self.cookies = {"TSWAFeatureCheckCookie": "true"} + self.conn = None + self.forms_tried = False + + def close(self): + if self.conn: + self.conn.close() + self.conn = None + + def _connect(self): + if self.scheme == "http": + return http.client.HTTPConnection(self.host, self.port, timeout=self.timeout) + return http.client.HTTPSConnection(self.host, self.port, timeout=self.timeout, + context=ssl.create_default_context()) + + def path(self, url, base="/"): + parts = urllib.parse.urlsplit(urllib.parse.urljoin(f"{self.scheme}://{self.host}{base}", url)) + if parts.hostname and parts.hostname.lower() != self.host.lower(): + raise FeedError(f"redirected to another server: {parts.hostname}") + return urllib.parse.urlunsplit(("", "", parts.path or "/", parts.query, "")) + + def send(self, method, path, body=None, headers=None): + hdrs = {"User-Agent": "TSWorkspace/2.0", "Accept": "*/*", "Connection": "keep-alive"} + if self.cookies: + hdrs["Cookie"] = "; ".join(f"{k}={v}" for k, v in self.cookies.items()) + hdrs.update(headers or {}) + for attempt in range(2): + if self.conn is None: + self.conn = self._connect() + try: + self.conn.request(method, path, body=body, headers=hdrs) + resp = self.conn.getresponse() + data = resp.read() + break + except (http.client.HTTPException, OSError): + self.close() + if attempt: + raise + for name, value in resp.getheaders(): + if name.lower() == "set-cookie": + cname, _, rest = value.partition("=") + cvalue = rest.split(";", 1)[0] + if cvalue and "01-jan-1970" not in value.lower(): + self.cookies[cname.strip()] = cvalue + else: + self.cookies.pop(cname.strip(), None) + if (resp.getheader("Connection") or "").lower() == "close": + self.close() + return resp, data + + def ntlm(self, method, path, body, headers): + auth = {"Authorization": "NTLM " + base64.b64encode(ntlm_negotiate()).decode()} + resp, data = self.send(method, path, body, {**(headers or {}), **auth}) + challenge = next((v[5:] for k, v in resp.getheaders() + if k.lower() == "www-authenticate" and v.startswith("NTLM ")), None) + if resp.status != 401 or not challenge: + return resp, data + message, self.detected_domain = ntlm_authenticate( + base64.b64decode(challenge), self.user, self.domain, self.password) + auth = {"Authorization": "NTLM " + base64.b64encode(message).decode()} + return self.send(method, path, body, {**(headers or {}), **auth}) + + def fetch(self, url, method="GET", body=None, headers=None): + path = original = self.path(url) + token_retry = True + for _ in range(10): + resp, data = self.send(method, path, body, headers) + if resp.status == 401: + offered = " ".join(v.lower() for k, v in resp.getheaders() + if k.lower() == "www-authenticate") + if "ntlm" in offered: + resp, data = self.ntlm(method, path, body, headers) + if resp.status == 401: + raise AuthError("the server did not accept the user name or password") + content_type = (resp.getheader("Content-Type") or "").lower() + if resp.status == 200 and content_type.startswith("application/x-msts-webfeed-login"): + token = data.decode("ascii", "replace").strip() + for name in FEED_AUTH_COOKIES: + self.cookies[name] = token + if path != original and token_retry: + path, token_retry = original, False + continue + location = resp.getheader("Location") + if resp.status in (301, 302, 303, 307, 308) and location: + path = self.path(location, path) + if resp.status not in (307, 308): + method, body, headers = "GET", None, None + continue + return resp, data, path + raise FeedError("too many redirects") + + def forms_login(self, login_path): + """Some servers keep the .rdp files behind the RD Web form login.""" + resp, data, path = self.fetch(login_path) + parser = HiddenInputs() + parser.feed(data.decode("utf-8", "replace")) + fields = dict(parser.fields) + user = self.user if "@" in self.user else f"{self.detected_domain}\\{self.user}" + fields.update({"DomainUserName": user, "UserPass": self.password, + "MachineType": "private", "isUtf8": "1"}) + fields.setdefault("flags", "0") + action = self.path(parser.action or path, path) + resp, data, path = self.fetch(action, "POST", urllib.parse.urlencode(fields), + {"Content-Type": "application/x-www-form-urlencoded"}) + if "login.aspx" in path.lower(): + raise AuthError("the RD Web form login failed") + + def download(self, url): + resp, data, path = self.fetch(url) + if "login.aspx" in path.lower() and not self.forms_tried: + self.forms_tried = True + self.forms_login(path) + resp, data, path = self.fetch(url) + if resp.status != 200: + raise FeedError(f"HTTP {resp.status}") + return data + + +# ---------------------------------------------------------------- The feed + +def local_tag(el): + return el.tag.rsplit("}", 1)[-1] + + +def make_slug(text, taken): + # รค โ†’ a, รฉ โ†’ e: the marks go, the letters stay. + text = "".join(c for c in unicodedata.normalize("NFKD", text) if not unicodedata.combining(c)) + base = re.sub(r"[^a-z0-9]+", "-", text.lower()).strip("-")[:40].strip("-") or "app" + slug, n = base, 2 + while slug in taken: + slug, n = f"{base}-{n}", n + 1 + taken.add(slug) + return slug + + +def common_suffix(names): + """The end all aliases share, like "-RemoteApps-CmsRdsh", from a dash on.""" + if len(names) < 2: + return "" + suffix = os.path.commonprefix([n[::-1] for n in names])[::-1] + cut = suffix.find("-") + suffix = suffix[cut:] if cut >= 0 else "" + return suffix if all(len(n) > len(suffix) for n in names) else "" + + +def parse_feed(data): + """The workspace name and its apps. Raises FeedError if this is no feed.""" + try: + root = ET.fromstring(data) + except ET.ParseError: + raise FeedError("the server sent no feed") + if local_tag(root) != "ResourceCollection": + raise FeedError("the server sent no feed") + name = "" + apps = [] + for el in root.iter(): + tag = local_tag(el) + if tag == "Publisher" and not name: + name = (el.get("Name") or "").strip() + if tag != "Resource": + continue + rtype = el.get("Type") or "RemoteApp" + if rtype not in ("RemoteApp", "Desktop"): + continue + title = (el.get("Title") or "").strip() + alias = el.get("Alias") or title or el.get("ID") or "app" + icons, rdp_url, types = [], None, [] + for sub in el.iter(): + stag = local_tag(sub) + if stag == "FileExtension" and sub.get("Name"): + ext = sub.get("Name").strip().lstrip(".").lower() + if re.fullmatch(r"[a-z0-9_+-]+", ext) and ext not in types: + types.append(ext) + elif stag.startswith("Icon") and sub.get("FileURL"): + dims = re.findall(r"\d+", sub.get("Dimensions") or stag) or ["0"] + icons.append({"url": sub.get("FileURL"), "type": (sub.get("FileType") or "").lower(), + "size": int(dims[0])}) + elif stag == "ResourceFile" and sub.get("URL") and rdp_url is None: + if (sub.get("FileExtension") or ".rdp").lower() == ".rdp": + rdp_url = sub.get("URL") + if rdp_url: + apps.append({"alias": alias, "title": title or alias, "type": rtype, + "rdp": rdp_url, "icons": icons, "types": types}) + suffix = common_suffix([a["alias"] for a in apps]) + taken = set() + for app in apps: + app["slug"] = make_slug(app["alias"][:len(app["alias"]) - len(suffix)], taken) + return name, apps + + +def known_types(*names): + """The file types of a well-known app, from its program, title or alias.""" + text = " ".join(names).lower() + for keys, types in KNOWN_TYPES: + if any(re.search(rf"(^|[^a-z0-9]){re.escape(k)}($|[^a-z0-9])", text) for k in keys): + return types.split() + return [] + + +# ---------------------------------------------------------------- .rdp files + +def decode_rdp(data): + if data.startswith(b"\xff\xfe") or data.startswith(b"\xfe\xff"): + return data.decode("utf-16") + if b"\x00" in data[:16]: + return data.decode("utf-16-le") + try: + return data.decode("utf-8-sig") + except UnicodeDecodeError: + return data.decode("latin-1") + + +def rdp_set(text, name, kind, value): + pattern = re.compile(rf"^{re.escape(name)}:[isb]:[^\r\n]*", re.IGNORECASE | re.MULTILINE) + line = f"{name}:{kind}:{value}" + if pattern.search(text): + return pattern.sub(line, text) + return text.rstrip("\r\n") + "\r\n" + line + "\r\n" + + +def rdp_del(text, name): + return re.sub(rf"^{re.escape(name)}:[isb]:[^\r\n]*\r?\n?", "", text, flags=re.IGNORECASE | re.MULTILINE) + + +def sanitize_rdp(text): + # One sign-in for the gateway and the session, and never a prompt: started + # from the app menu, there is no terminal to ask in. + text = rdp_set(text, "promptcredentialonce", "i", "1") + text = rdp_set(text, "prompt for credentials on client", "i", "0") + # Drive letters mean nothing here, and FreeRDP reads "*" as every mount, + # camera or USB device of this computer. Sharing a folder is a setting. + for name in ("drivestoredirect", "devicestoredirect", "camerastoredirect", "usbdevicestoredirect"): + text = rdp_del(text, name) + return text + + +# ---------------------------------------------------------------- Icons + +def png_encode(width, height, rows): + """RGBA rows, top down, as a PNG.""" + def chunk(kind, payload): + return (struct.pack(">I", len(payload)) + kind + payload + + struct.pack(">I", zlib.crc32(kind + payload) & 0xFFFFFFFF)) + raw = b"".join(b"\x00" + bytes(row) for row in rows) + return (PNG_SIGNATURE + chunk(b"IHDR", struct.pack(">IIBBBBB", width, height, 8, 6, 0, 0, 0)) + + chunk(b"IDAT", zlib.compress(raw, 9)) + chunk(b"IEND", b"")) + + +def dib_to_png(dib): + """One bitmap frame of an .ico (BITMAPINFOHEADER, 1 to 32 bits) as a PNG.""" + header, width, height2, _, bpp, compression = struct.unpack("<IiiHHI", dib[:20]) + height = abs(height2) // 2 + if width <= 0 or height <= 0 or compression not in (0, 3) or bpp not in (1, 4, 8, 24, 32): + return None + off = header + (12 if compression == 3 else 0) + palette = [] + if bpp <= 8: + count = struct.unpack("<I", dib[32:36])[0] or (1 << bpp) + for i in range(count): + b, g, r = dib[off + 4 * i:off + 4 * i + 3] + palette.append((r, g, b)) + off += 4 * count + stride = ((width * bpp + 31) // 32) * 4 + pixels = dib[off:off + stride * height] + mask_stride = ((width + 31) // 32) * 4 + mask = dib[off + stride * height:off + stride * height + mask_stride * height] + alpha = bpp == 32 and any(pixels[i] for i in range(3, len(pixels), 4)) + + rows = [] + for y in range(height): + src = height - 1 - y + row = pixels[src * stride:(src + 1) * stride] + mrow = mask[src * mask_stride:(src + 1) * mask_stride] + out = bytearray() + for x in range(width): + if bpp == 32: + b, g, r, a = row[4 * x:4 * x + 4] + if not alpha: + a = 255 + elif bpp == 24: + b, g, r = row[3 * x:3 * x + 3] + a = 255 + else: + bit = x * bpp + index = (row[bit // 8] >> (8 - bpp - bit % 8)) & ((1 << bpp) - 1) + r, g, b = palette[index] if index < len(palette) else (0, 0, 0) + a = 255 + if not alpha and x // 8 < len(mrow) and (mrow[x // 8] >> (7 - x % 8)) & 1: + a = 0 + out += bytes((r, g, b, a)) + rows.append(out) + return png_encode(width, height, rows) + + +def ico_to_png(data): + """The biggest frame of an .ico, as a PNG. None if there is none to read.""" + if data[:4] != b"\x00\x00\x01\x00": + return None + count = struct.unpack("<H", data[4:6])[0] + entries = [] + for i in range(count): + entry = data[6 + 16 * i:22 + 16 * i] + if len(entry) < 16: + break + w, h, _, _, _, bpp, size, offset = struct.unpack("<BBBBHHII", entry) + entries.append(((w or 256) * (h or 256), bpp, size, offset)) + for _, _, size, offset in sorted(entries, reverse=True): + frame = data[offset:offset + size] + if frame[:8] == PNG_SIGNATURE: + return frame + try: + png = dib_to_png(frame) + except (struct.error, ValueError, IndexError): + png = None + if png: + return png + return None + + +def save_icon(session, app, dest): + candidates = [i for i in app["icons"] if i["type"] == "ico"] + candidates += sorted((i for i in app["icons"] if i["type"] != "ico"), + key=lambda i: i["size"], reverse=True) + for icon in candidates: + try: + data = session.download(icon["url"]) + except (FeedError, OSError, http.client.HTTPException): + continue + png = data if data[:8] == PNG_SIGNATURE else ico_to_png(data) + if png: + with open(dest, "wb") as f: + f.write(png) + return True + return False + + +# ---------------------------------------------------------------- Main + +def say(*fields): + print("\t".join(str(f).replace("\t", " ").replace("\n", " ") for f in fields), flush=True) + + +def run(args, password): + session = Session(args.url, args.user, args.domain, password, args.timeout) + resp, data, _ = session.fetch(args.url) + if resp.status != 200: + raise FeedError(f"HTTP {resp.status}") + name, apps = parse_feed(data) + + os.makedirs(os.path.join(args.out, "rdp"), exist_ok=True) + os.makedirs(os.path.join(args.out, "icons"), exist_ok=True) + with open(os.path.join(args.out, "feed.xml"), "wb") as f: + f.write(data) + say("workspace", name or session.host) + say("domain", session.detected_domain or "") + + done = [] + for app in apps: + try: + text = decode_rdp(session.download(app["rdp"])) + if "full address:s:" not in text.lower(): + raise FeedError("not an RDP file") + with open(os.path.join(args.out, "rdp", app["slug"] + ".rdp"), "w") as f: + f.write(sanitize_rdp(text)) + if not app["types"] and app["type"] == "RemoteApp": + program = re.search(r"^remoteapplicationprogram:s:(.*)$", text, re.IGNORECASE | re.MULTILINE) + app["types"] = known_types(program.group(1) if program else "", app["title"], app["alias"]) + save_icon(session, app, os.path.join(args.out, "icons", app["slug"] + ".png")) + except (FeedError, OSError, http.client.HTTPException) as e: + say("skip", app["title"], e) + continue + done.append(app) + say("app", app["slug"], app["type"], app["title"]) + + with open(os.path.join(args.out, "apps.tsv"), "w") as f: + for app in done: + f.write("\t".join((app["slug"], app["type"], app["title"].replace("\t", " "), + " ".join(app["types"]))) + "\n") + + +def main(argv): + parser = argparse.ArgumentParser(prog="rdfeed-fetch", description="Download the apps of an RD Web Access feed.") + parser.add_argument("--url", required=True) + parser.add_argument("--user", required=True) + parser.add_argument("--domain", help="Windows domain; without it the server's own is used") + parser.add_argument("--out", required=True, help="directory for the downloaded files") + parser.add_argument("--timeout", type=float, default=30) + args = parser.parse_args(argv) + + password = sys.stdin.readline().rstrip("\n") + try: + run(args, password) + except FeedError as e: + print(f"rdfeed-fetch: {e}", file=sys.stderr) + return e.code + except (OSError, http.client.HTTPException) as e: + print(f"rdfeed-fetch: {e}", file=sys.stderr) + return EXIT_NETWORK + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:])) diff --git a/src/hook/rdfeed-hook.c b/src/hook/rdfeed-hook.c new file mode 100644 index 0000000..fb9ea44 --- /dev/null +++ b/src/hook/rdfeed-hook.c @@ -0,0 +1,146 @@ +/* + * rdfeed-hook: works around two FreeRDP problems, loaded with LD_PRELOAD. + * + * 1. The second MFA prompt. A connection broker sends the client on to the + * server that hosts the session, so FreeRDP opens a second tunnel through + * the RD Gateway right away. With MFA on the gateway (the Azure MFA NPS + * extension), that second sign-in needs a second prompt. When it comes + * less than about 10 seconds after the first one was confirmed, no prompt + * is sent and the gateway waits forever. Windows does not run into this. + * So a new connection to port 443 waits a little first. + * + * RDFEED_GW_WAIT auto (the default), always or never. auto waits only + * when the first sign-in took long enough for somebody to + * confirm a prompt. + * RDFEED_GW_DELAY how long to wait, in seconds (12). + * + * Before waiting it prints "[rdfeed] gateway-delay <seconds>" to stderr, + * so rdfeed can tell the user a second prompt is coming. + * + * 2. The graphics pipeline. FreeRDP 3 always turns GFX on, there is no switch + * to turn it off, and in RemoteApp mode xfreerdp does not repaint a window + * after it was minimized or resized. With RDFEED_NO_GFX set, every attempt + * to turn it on is turned into off, and the classic drawing is used. + * + * It also makes stdout line buffered, so FreeRDP's log reaches rdfeed as it + * happens. + * + * Copyright (C) 2026 Felitendo + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#define _GNU_SOURCE +#include <dlfcn.h> +#include <netinet/in.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <time.h> +#include <unistd.h> + +/* A pause this long between two connections means a new tunnel, not the + * second channel of the same one or an IPv6 to IPv4 fallback. */ +#define NEW_TUNNEL_SECONDS 2.0 +/* A first sign-in that took this long had a person confirming a prompt. */ +#define MFA_SECONDS 2.5 + +static int (*real_connect)(int, const struct sockaddr *, socklen_t); +static int (*real_set_bool)(void *, int, int); + +static double first_connect = -1; +static double last_connect = -1; + +static double now(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (double)ts.tv_sec + (double)ts.tv_nsec / 1e9; +} + +__attribute__((constructor)) static void init(void) +{ + setvbuf(stdout, NULL, _IOLBF, 0); +} + +/* RTLD_NEXT only finds libfreerdp3 when it was loaded globally, as it is in + * xfreerdp. Otherwise ask the library itself. */ +static void *freerdp_sym(const char *name) +{ + void *sym = dlsym(RTLD_NEXT, name); + if (!sym) { + void *lib = dlopen("libfreerdp3.so.3", RTLD_LAZY | RTLD_NOLOAD); + if (lib) + sym = dlsym(lib, name); + } + return sym; +} + +static long gfx_key(void) +{ + static long key = -2; + if (key == -2) { + key = -1; + if (getenv("RDFEED_NO_GFX")) { + ssize_t (*by_name)(const char *) = + (ssize_t (*)(const char *))freerdp_sym("freerdp_settings_get_key_for_name"); + if (by_name) + key = (long)by_name("FreeRDP_SupportGraphicsPipeline"); + } + } + return key; +} + +int freerdp_settings_set_bool(void *settings, int id, int val) +{ + if (!real_set_bool) + real_set_bool = (int (*)(void *, int, int))freerdp_sym("freerdp_settings_set_bool"); + if (!real_set_bool) + return 0; + if (val && id == gfx_key()) + val = 0; + return real_set_bool(settings, id, val); +} + +static int should_wait(double t) +{ + const char *mode = getenv("RDFEED_GW_WAIT"); + + if (last_connect < 0 || t - last_connect < NEW_TUNNEL_SECONDS) + return 0; + if (mode && strcmp(mode, "never") == 0) + return 0; + if (mode && strcmp(mode, "always") == 0) + return 1; + return t - first_connect >= MFA_SECONDS; +} + +int connect(int fd, const struct sockaddr *addr, socklen_t len) +{ + if (!real_connect) + real_connect = (int (*)(int, const struct sockaddr *, socklen_t))dlsym(RTLD_NEXT, "connect"); + + int port = -1; + if (addr && addr->sa_family == AF_INET) + port = ntohs(((const struct sockaddr_in *)addr)->sin_port); + else if (addr && addr->sa_family == AF_INET6) + port = ntohs(((const struct sockaddr_in6 *)addr)->sin6_port); + + if (port == 443) { + double t = now(); + if (first_connect < 0) + first_connect = t; + if (should_wait(t)) { + const char *env = getenv("RDFEED_GW_DELAY"); + unsigned left = env ? (unsigned)atoi(env) : 12; + fprintf(stderr, "[rdfeed] gateway-delay %u\n", left); + fflush(stderr); + while (left) + left = sleep(left); + t = now(); + } + last_connect = t; + } + return real_connect(fd, addr, len); +} diff --git a/src/lib/common.sh b/src/lib/common.sh new file mode 100644 index 0000000..7bc3ad2 --- /dev/null +++ b/src/lib/common.sh @@ -0,0 +1,202 @@ +# shellcheck shell=bash +# +# Paths, translations and output helpers. +# +# Everything rdfeed keeps is the user's own: a settings file and one file per +# workspace under ~/.config/rdfeed, the downloaded apps under +# ~/.local/share/rdfeed, and the passwords in the keyring. Nothing needs root. + +RF_VERSION="@VERSION@" +RF_NAME="rdfeed" +RF_PRETTY="rdfeed" + +RF_LIBDIR="${RF_LIBDIR:-@LIBDIR@}" +RF_LIBEXECDIR="${RF_LIBEXECDIR:-@LIBEXECDIR@}" +RF_LOCALEDIR="${RF_LOCALEDIR:-@LOCALEDIR@}" + +RF_FETCH="${RF_FETCH:-$RF_LIBEXECDIR/rdfeed-fetch}" +RF_HOOK="${RF_HOOK:-$RF_LIBEXECDIR/rdfeed-hook.so}" + +RF_XDG_CONFIG="${XDG_CONFIG_HOME:-$HOME/.config}" +RF_XDG_DATA="${XDG_DATA_HOME:-$HOME/.local/share}" +RF_XDG_CACHE="${XDG_CACHE_HOME:-$HOME/.cache}" + +RF_CONFDIR="${RF_XDG_CONFIG}/${RF_NAME}" +RF_CONFIG="${RF_CONFDIR}/config" +RF_WSDIR="${RF_CONFDIR}/workspaces" +RF_DATADIR="${RF_XDG_DATA}/${RF_NAME}" +RF_CACHEDIR="${RF_XDG_CACHE}/${RF_NAME}" +# For what only lives while an app runs: the .rdp copy that names a file. +RF_RUNDIR="${XDG_RUNTIME_DIR:-$RF_CACHEDIR}/${RF_NAME}" + +# Where the app menu looks: the entries, the folder for each workspace (a +# merged menu) and the folder's name and icon. +RF_APPDIR="${RF_XDG_DATA}/applications" +RF_MENUDIR="${RF_XDG_CONFIG}/menus/applications-merged" +RF_DIRDIR="${RF_XDG_DATA}/desktop-directories" + +RF_UNIT_TIMER="rdfeed-refresh.timer" + +# --------------------------------------------------------------------------- +# Translations +# --------------------------------------------------------------------------- + +export TEXTDOMAIN="rdfeed" +export TEXTDOMAINDIR="${RF_LOCALEDIR}" + +rf_ui_locale() { + local l="${RF_UI_LOCALE:-}" + + if [[ -z $l ]]; then + l="${LC_ALL:-}" + [[ -z $l ]] && l="${LC_MESSAGES:-}" + [[ -z $l ]] && l="${LANG:-}" + fi + + # systemd writes /etc/locale.conf and most distributions use it; Debian and + # Ubuntu keep the same LANG= line in /etc/default/locale instead. + if [[ -z $l ]]; then + local f + for f in /etc/locale.conf /etc/default/locale; do + [[ -r $f ]] || continue + l="$(sed -n 's/^LANG=//p' "$f" | tr -d '"' | head -n1)" + [[ -n $l ]] && break + done + fi + + printf '%s\n' "${l:-C}" +} + +# Every gettext lookup is a fork and the settings screen redraws a screenful of +# labels per keypress, so results are memoized. +declare -A RF_MSG_CACHE=() + +RF_MSG_RESULT='' + +# rf_msg_into <locale> <msgid> +# Plain lookup with the result in RF_MSG_RESULT and no printf formatting, for +# callers that would otherwise pay a fork per label per frame. +rf_msg_into() { + local locale="$1" msgid="$2" cachekey + cachekey="${locale}"$'\x1f'"${msgid}" + + if [[ -n ${RF_MSG_CACHE[$cachekey]+set} ]]; then + RF_MSG_RESULT="${RF_MSG_CACHE[$cachekey]}" + return 0 + fi + + RF_MSG_RESULT="$(LC_ALL="$locale" LANGUAGE="${locale%%.*}" gettext -- "$msgid" 2>/dev/null)" + [[ -n $RF_MSG_RESULT ]] || RF_MSG_RESULT="$msgid" + RF_MSG_CACHE[$cachekey]="$RF_MSG_RESULT" + return 0 +} + +# rf_msg_in <locale> <msgid> [printf args...] +rf_msg_in() { + local locale="$1" msgid="$2" + shift 2 + + rf_msg_into "$locale" "$msgid" + + # With no arguments the message is plain text, not a format string. Feeding + # it to printf anyway would turn a literal percent sign in a translation + # into an invalid conversion. + if (( $# == 0 )); then + printf '%s' "$RF_MSG_RESULT" + return + fi + + # shellcheck disable=SC2059 # the format string is the translated message + printf -- "$RF_MSG_RESULT" "$@" +} + +RF_LOCALE_CACHED='' + +# rf_msg <msgid> [printf args...] +rf_msg() { + [[ -n $RF_LOCALE_CACHED ]] || RF_LOCALE_CACHED="$(rf_ui_locale)" + rf_msg_in "$RF_LOCALE_CACHED" "$@" +} + +# --------------------------------------------------------------------------- +# Output +# --------------------------------------------------------------------------- + +# Decided once, while stdout is still whatever the process was started with: +# testing -t 1 at the point of use is wrong for anything called through $(...), +# which sees a pipe and would conclude nobody is watching. +RF_INTERACTIVE='' +[[ -t 1 ]] && RF_INTERACTIVE=1 + +if [[ -n $RF_INTERACTIVE && -z ${NO_COLOR:-} ]]; then + RF_C_RESET=$'\033[0m' + RF_C_BOLD=$'\033[1m' + RF_C_DIM=$'\033[2m' + RF_C_BLUE=$'\033[38;2;52;153;255m' + RF_C_GREEN=$'\033[32m' + RF_C_YELLOW=$'\033[33m' + RF_C_RED=$'\033[31m' +else + RF_C_RESET='' RF_C_BOLD='' RF_C_DIM='' RF_C_BLUE='' + RF_C_GREEN='' RF_C_YELLOW='' RF_C_RED='' +fi + +# What rf_ok, rf_bad and rf_note printed. The menu redraws straight after an +# action, which wipes the screen, so it shows these again under the new frame +# rather than holding everything up for a key press. +RF_UI_NOTICES=() + +rf_say() { printf '%s\n' "$*"; } +rf_head() { printf '\n%s%s%s\n\n' "$RF_C_BOLD$RF_C_BLUE" "$*" "$RF_C_RESET"; } +rf_ok() { RF_UI_NOTICES+=("$RF_C_GREENโœ”$RF_C_RESET $*"); printf '%sโœ”%s %s\n' "$RF_C_GREEN" "$RF_C_RESET" "$*"; } +rf_bad() { RF_UI_NOTICES+=("$RF_C_REDโœ˜$RF_C_RESET $*"); printf '%sโœ˜%s %s\n' "$RF_C_RED" "$RF_C_RESET" "$*" >&2; } +rf_note() { RF_UI_NOTICES+=("$RF_C_DIMโ€ข$RF_C_RESET $*"); printf '%sโ€ข%s %s\n' "$RF_C_DIM" "$RF_C_RESET" "$*"; } + +rf_have() { command -v "$1" > /dev/null 2>&1; } + +# rf_notify <summary> <body> [icon] +# A desktop notification, for what happens after the menu or the app menu +# started something and nobody watches a terminal. +rf_notify() { + rf_have notify-send || return 0 + notify-send -a "$RF_PRETTY" -i "${3:-rdfeed}" -- "$1" "$2" > /dev/null 2>&1 || true +} + +# Human-readable "x minutes ago" for a unix timestamp. 0 or empty yields the +# translated "never". +# +# Written with [[ ]] and a variable for each number on purpose: xgettext reads +# the < of an (( )) as a redirection and loses every string after it. +rf_time_ago() { + local ts="$1" now delta n + + if [[ ! $ts =~ ^[0-9]+$ || $ts -eq 0 ]]; then + rf_msg "never" + printf '\n' + return + fi + + now="$(date +%s)" + delta=$(( now - ts )) + [[ $delta -lt 0 ]] && delta=0 + + if [[ $delta -lt 60 ]]; then + rf_msg "just now" + elif [[ $delta -lt 120 ]]; then + rf_msg "1 minute ago" + elif [[ $delta -lt 3600 ]]; then + n=$(( delta / 60 )) + rf_msg "%d minutes ago" "$n" + elif [[ $delta -lt 7200 ]]; then + rf_msg "1 hour ago" + elif [[ $delta -lt 86400 ]]; then + n=$(( delta / 3600 )) + rf_msg "%d hours ago" "$n" + elif [[ $delta -lt 172800 ]]; then + rf_msg "1 day ago" + else + n=$(( delta / 86400 )) + rf_msg "%d days ago" "$n" + fi + printf '\n' +} diff --git a/src/lib/config.sh b/src/lib/config.sh new file mode 100644 index 0000000..3aee70f --- /dev/null +++ b/src/lib/config.sh @@ -0,0 +1,155 @@ +# shellcheck shell=bash +# +# Reading and writing the settings files. +# +# ~/.config/rdfeed/config holds how the apps run (a shared folder, the size, +# the graphics), and ~/.config/rdfeed/workspaces/<id> where a workspace comes +# from and who signs in to it. Neither is meant to be edited by hand: every +# option is in the menu. +# +# Both are parsed rather than sourced. One "Key=Value" per line, '#' comments. + +declare -A RF_KV_CACHE=() + +# _rf_kv_lookup <file> <Key> [default] +# Result in RF_KV_VALUE. Assigning rather than printing matters on the +# settings screen, which reads every key on every frame: a command +# substitution there is a fork, and forks are the whole cost of a redraw. +RF_KV_VALUE='' + +_rf_kv_lookup() { + local file="$1" key="$2" default="${3:-}" val='' line content + + RF_KV_VALUE="$default" + [[ -r $file ]] || return 0 + + if [[ -n ${RF_KV_CACHE[$file]+set} ]]; then + content="${RF_KV_CACHE[$file]}" + else + content="$(< "$file")" + RF_KV_CACHE[$file]="$content" + fi + + while IFS= read -r line; do + [[ $line == *"$key"* ]] || continue + [[ $line =~ ^[[:space:]]*"$key"[[:space:]]*=(.*)$ ]] || continue + val="${BASH_REMATCH[1]}" + done <<< "$content" + + val="${val#"${val%%[![:space:]]*}"}" + val="${val%"${val##*[![:space:]]}"}" + + [[ -n $val ]] && RF_KV_VALUE="$val" + return 0 +} + +rf_is_true() { + case "${1,,}" in + yes|y|true|1|on|enabled) return 0 ;; + *) return 1 ;; + esac +} + +# rf_kv_set <file> <Key> <Value> <header line> +rf_kv_set() { + local file="$1" key="$2" value="$3" header="$4" tmp + + if [[ ! -e $file ]]; then + mkdir -p "$(dirname "$file")" || return 1 + { + printf '# %s\n' "$header" + printf '#\n' + # shellcheck disable=SC2016 # the backticks are text + printf '# Written by `%s`. Nothing here needs editing by hand:\n' "$RF_NAME" + printf '# every option is in the menu.\n' + } > "$file" || return 1 + fi + [[ -w $file ]] || return 1 + + tmp="$(mktemp "${file}.XXXXXX")" || return 1 + chmod --reference="$file" "$tmp" 2>/dev/null || chmod 0644 "$tmp" + + if grep -qE "^[[:space:]]*#?[[:space:]]*${key}[[:space:]]*=" "$file"; then + awk -v key="$key" -v value="$value" ' + !done && $0 ~ "^[[:space:]]*#?[[:space:]]*" key "[[:space:]]*=" { + print key "=" value; done = 1; next + } + # drop any further occurrences so the file cannot grow duplicates + $0 ~ "^[[:space:]]*" key "[[:space:]]*=" { next } + { print } + ' "$file" > "$tmp" || { rm -f "$tmp"; return 1; } + else + cat "$file" > "$tmp" || { rm -f "$tmp"; return 1; } + printf '%s=%s\n' "$key" "$value" >> "$tmp" + fi + + mv -f "$tmp" "$file" + unset 'RF_KV_CACHE[$file]' +} + +# --------------------------------------------------------------------------- +# The settings +# --------------------------------------------------------------------------- + +rf_config_get() { + _rf_kv_lookup "$RF_CONFIG" "$@" + printf '%s\n' "$RF_KV_VALUE" +} + +rf_config_set() { + rf_kv_set "$RF_CONFIG" "$1" "$2" "$RF_PRETTY" +} + +# rf_config_load +# Everything starting an app needs, resolved once. +rf_config_load() { + RF_KV_CACHE=() + _rf_kv_lookup "$RF_CONFIG" Share off; CFG_SHARE="$RF_KV_VALUE" + _rf_kv_lookup "$RF_CONFIG" Scale auto; CFG_SCALE="$RF_KV_VALUE" + _rf_kv_lookup "$RF_CONFIG" Graphics gfx; CFG_GRAPHICS="$RF_KV_VALUE" + _rf_kv_lookup "$RF_CONFIG" MfaWait auto; CFG_MFAWAIT="$RF_KV_VALUE" + _rf_kv_lookup "$RF_CONFIG" Sound yes; CFG_SOUND=no; rf_is_true "$RF_KV_VALUE" && CFG_SOUND=yes + return 0 +} + +# --------------------------------------------------------------------------- +# Workspaces +# --------------------------------------------------------------------------- + +rf_ws_file() { printf '%s/%s\n' "$RF_WSDIR" "$1"; } + +# rf_ws_ids +# The workspaces, sorted, in RF_WS_IDS. +RF_WS_IDS=() + +rf_ws_ids() { + local f + RF_WS_IDS=() + [[ -d $RF_WSDIR ]] || return 0 + for f in "$RF_WSDIR"/*; do + [[ -f $f ]] || continue + RF_WS_IDS+=("${f##*/}") + done +} + +# rf_ws_load <id> +# Sets WS_URL, WS_NAME, WS_USER, WS_DOMAIN, WS_REFRESHED and WS_ACCOUNT, the +# name the way Windows writes it (DOMAIN\user, or user@domain as typed). +rf_ws_load() { + local file + file="$(rf_ws_file "$1")" + _rf_kv_lookup "$file" Url; WS_URL="$RF_KV_VALUE" + _rf_kv_lookup "$file" Name "$1"; WS_NAME="$RF_KV_VALUE" + _rf_kv_lookup "$file" User; WS_USER="$RF_KV_VALUE" + _rf_kv_lookup "$file" Domain; WS_DOMAIN="$RF_KV_VALUE" + _rf_kv_lookup "$file" Refreshed 0; WS_REFRESHED="$RF_KV_VALUE" + if [[ -n $WS_DOMAIN && $WS_USER != *@* ]]; then + WS_ACCOUNT="$WS_DOMAIN\\$WS_USER" + else + WS_ACCOUNT="$WS_USER" + fi +} + +rf_ws_set() { + rf_kv_set "$(rf_ws_file "$1")" "$2" "$3" "$RF_PRETTY workspace" +} diff --git a/src/lib/launch.sh b/src/lib/launch.sh new file mode 100644 index 0000000..2ab94bd --- /dev/null +++ b/src/lib/launch.sh @@ -0,0 +1,307 @@ +# shellcheck shell=bash +# +# Starting an app. +# +# Each app runs in xfreerdp in RemoteApp mode: only its own windows show, not +# a whole Windows desktop. The arguments, the password among them, go to +# FreeRDP on stdin (/args-from:stdin), never on its command line. +# +# A file from this computer (an "Open with", or `rdfeed run APP FILE`) gets +# there the way Windows does it: its folder is shared as a drive, and the app +# is started with the path on that drive, \\tsclient\<folder>\<file>. +# +# rdfeed-hook.so goes into FreeRDP with LD_PRELOAD. It waits before the second +# gateway sign-in so a second MFA prompt is sent, and keeps the graphics +# pipeline off, which leaves windows unpainted in RemoteApp mode. See +# src/hook/rdfeed-hook.c. + +# How long the hook waits before the second sign-in, in seconds. +RF_GW_DELAY=12 + +# rf_freerdp +# The FreeRDP 3 client that can show RemoteApps (the X11 one), in RF_FREERDP. +# Arch and Debian call it xfreerdp3, Fedora xfreerdp. +RF_FREERDP='' + +rf_freerdp() { + RF_FREERDP='' + if rf_have xfreerdp3; then + RF_FREERDP=xfreerdp3 + elif rf_have xfreerdp && [[ $(xfreerdp /version 2>/dev/null) == *" 3."* ]]; then + RF_FREERDP=xfreerdp + fi + [[ -n $RF_FREERDP ]] +} + +# rf_kbd_layout +# The Windows id of the keyboard layout in use. Under Wayland, X11 programs +# often see "us" whatever the real layout is, so it is asked of the desktop. +rf_kbd_layout() { + local layout='' + if [[ -r $RF_XDG_CONFIG/kxkbrc ]]; then + layout="$(sed -n 's/^LayoutList=//p' "$RF_XDG_CONFIG/kxkbrc" | head -n1)" + fi + if [[ -z $layout && ${XDG_CURRENT_DESKTOP:-} == *GNOME* ]] && rf_have gsettings; then + layout="$(gsettings get org.gnome.desktop.input-sources sources 2>/dev/null \ + | sed -n "s/^[^']*'xkb', '\([a-z]*\).*/\1/p")" + fi + if [[ -z $layout ]] && rf_have localectl; then + layout="$(localectl status 2>/dev/null | sed -n 's/.*X11 Layout: *//p')" + fi + [[ -z $layout ]] && layout="${XKB_DEFAULT_LAYOUT:-}" + layout="${layout%%,*}" + case "${layout,,}" in + de|at) printf '0x00000407\n' ;; + ch) printf '0x00000807\n' ;; + us) printf '0x00000409\n' ;; + gb|uk) printf '0x00000809\n' ;; + fr) printf '0x0000040C\n' ;; + be) printf '0x0000080C\n' ;; + es) printf '0x0000040A\n' ;; + it) printf '0x00000410\n' ;; + nl) printf '0x00000413\n' ;; + pl) printf '0x00000415\n' ;; + pt) printf '0x00000816\n' ;; + br) printf '0x00000416\n' ;; + ru) printf '0x00000419\n' ;; + ua) printf '0x00000422\n' ;; + tr) printf '0x0000041F\n' ;; + jp) printf '0x00000411\n' ;; + kr) printf '0x00000412\n' ;; + se) printf '0x0000041D\n' ;; + no) printf '0x00000414\n' ;; + dk) printf '0x00000406\n' ;; + fi) printf '0x0000040B\n' ;; + cz) printf '0x00000405\n' ;; + sk) printf '0x0000041B\n' ;; + hu) printf '0x0000040E\n' ;; + *) return 1 ;; + esac +} + +# rf_auto_scale +# How much the desktop scales X11 programs that scale themselves, in percent, +# in RF_AUTO_SCALE. The apps are then drawn that much bigger on the server, so +# they come out sharp and the right size. +RF_AUTO_SCALE=100 + +rf_auto_scale() { + local s dpi + RF_AUTO_SCALE=100 + s="$(awk -F= '/^\[/ { section = $0 } section == "[Xwayland]" && $1 == "Scale" { print $2 }' \ + "$RF_XDG_CONFIG/kwinrc" 2>/dev/null | tail -n1)" + if [[ $s =~ ^[0-9]+(\.[0-9]+)?$ ]]; then + RF_AUTO_SCALE="$(awk -v s="$s" 'BEGIN { printf "%d", s * 100 + 0.5 }')" + elif rf_have xrdb; then + dpi="$(xrdb -query 2>/dev/null | awk '/^Xft\.dpi:/ { print $2 }')" + [[ $dpi =~ ^[0-9]+$ ]] && RF_AUTO_SCALE=$(( (dpi * 100 + 48) / 96 )) + fi + [[ $RF_AUTO_SCALE -lt 100 ]] && RF_AUTO_SCALE=100 + [[ $RF_AUTO_SCALE -gt 500 ]] && RF_AUTO_SCALE=500 + return 0 +} + +# rf_share_dir +# The folder the apps may see, from the Share setting. Nothing for off. +rf_share_dir() { + local dir='' + case "$CFG_SHARE" in + home) dir="$HOME" ;; + documents) rf_have xdg-user-dir && dir="$(xdg-user-dir DOCUMENTS)" ;; + downloads) rf_have xdg-user-dir && dir="$(xdg-user-dir DOWNLOAD)" ;; + esac + # xdg-user-dir answers with the home folder for a folder that is not set + # up. Sharing all of it then would be a surprise. + [[ $CFG_SHARE != home && ${dir%/} == "${HOME%/}" ]] && return 1 + [[ -n $dir && -d $dir ]] || return 1 + printf '%s\n' "$dir" +} + +# _rf_drive_name <dir> +# A name for a shared folder, as the app shows it: the folder's own name, +# without what Windows does not allow in one. +_rf_drive_name() { + local name="${1%/}" + name="${name##*/}" + name="${name//[\\\/:*?\"<>|,;]/_}" + printf '%s\n' "${name:-Linux}" +} + +# _rf_drive_path <dir> +# The folder as FreeRDP can take it. /drive splits at commas, so a folder with +# one in its path is shared through a link that has none. +_rf_drive_path() { + local dir="$1" link + if [[ $dir != *,* ]]; then + printf '%s\n' "$dir" + return + fi + link="$RF_RUNDIR/links/$(_rf_drive_name "$dir")" + link="${link//,/_}" + mkdir -p "${link%/*}" && ln -sfn "$dir" "$link" && printf '%s\n' "$link" +} + +# rf_ask_password <account> +# For when the keyring has none: in the terminal if there is one, else in a +# small window. +rf_ask_password() { + local prompt password + prompt="$(rf_msg "Password for %s:" "$1")" + if [[ -t 0 ]]; then + IFS= read -rsp " $prompt " password || return 1 + printf '\n' >&2 + elif rf_have kdialog; then + password="$(kdialog --title "$RF_PRETTY" --password "$prompt" 2>/dev/null)" || return 1 + elif rf_have zenity; then + password="$(zenity --password --title "$prompt" 2>/dev/null)" || return 1 + else + return 1 + fi + [[ -n $password ]] || return 1 + printf '%s' "$password" +} + +# rf_launch_error <log> +# Why FreeRDP gave up, in words, from what it logged. +rf_launch_error() { + local log="$1" last + if grep -qE 'ERRCONNECT_(PASSWORD_EXPIRED|PASSWORD_MUST_CHANGE|PASSWORD_CERTAINLY_EXPIRED)' "$log"; then + rf_msg "Your password has expired. Change it on Windows or on the RD Web page, then sign in again in rdfeed." + elif grep -qE 'ERRCONNECT_ACCOUNT_LOCKED_OUT' "$log"; then + rf_msg "Your account is locked. Ask your IT department." + elif grep -qE 'ERRCONNECT_(LOGON_FAILURE|WRONG_PASSWORD|AUTHENTICATION_FAILED)|STATUS_LOGON_FAILURE' "$log"; then + rf_msg "The server did not accept the password. Sign in again in rdfeed." + elif grep -qE 'ERRCONNECT_(DNS_NAME_NOT_FOUND|DNS_ERROR|CONNECT_FAILED|CONNECT_TRANSPORT_FAILED)' "$log"; then + rf_msg "Could not reach the server. Check the internet connection." + else + last="$(grep '\[ERROR\]' "$log" | tail -n1 | sed 's/^.*\] - \[[^]]*\]: *//')" + rf_msg "FreeRDP stopped: %s" "${last:-?}" + fi +} + +# rf_launch <id> <app> [file] +rf_launch() { + local id="$1" slug="$2" file="${3:-}" i title='' type='' icon rdp password log rcfile rc line kbd share + local drive='' remote='' temprdp='' dir name rel + local -a args env + + rf_ws_load "$id" + rf_ws_apps_load "$id" + for i in "${!RF_APP_SLUGS[@]}"; do + if [[ ${RF_APP_SLUGS[i]} == "$slug" ]]; then + title="${RF_APP_TITLES[i]}" type="${RF_APP_TYPES[i]}" + fi + done + rdp="$RF_DATADIR/$id/rdp/$slug.rdp" + icon="$RF_DATADIR/$id/icons/$slug.png" + [[ -f $icon ]] || icon=rdfeed + if [[ -z $title || ! -f $rdp ]]; then + rf_bad "$(rf_msg "There is no app %s. See \`%s list\`." "$id/$slug" "$RF_NAME")" + return 1 + fi + + if [[ -n $file ]]; then + file="$(realpath -e -- "$file" 2>/dev/null)" && [[ -f $file ]] || { + rf_bad "$(rf_msg "File not found: %s" "${3:-}")" + rf_notify "$title" "$(rf_msg "File not found: %s" "${3:-}")" dialog-error + return 1 + } + fi + + if ! rf_freerdp; then + rf_bad "$(rf_msg "FreeRDP 3 is missing. Install it to start the apps (the package is called freerdp).")" + rf_notify "$title" "$(rf_msg "FreeRDP 3 is missing. Install it to start the apps (the package is called freerdp).")" dialog-error + return 1 + fi + + password="$(rf_secret_get "$id" "$WS_ACCOUNT")" || password='' + if [[ -z $password ]]; then + password="$(rf_ask_password "$WS_ACCOUNT")" || { + rf_notify "$title" "$(rf_msg "No password saved for %s. Sign in again in rdfeed." "$WS_NAME")" dialog-error + return 1 + } + fi + + rf_config_load + args=("$rdp" "/u:$WS_USER") + [[ -n $WS_DOMAIN && $WS_USER != *@* ]] && args+=("/d:$WS_DOMAIN") + args+=("/p:$password" /cert:tofu +clipboard) + kbd="$(rf_kbd_layout)" && args+=("/kbd:layout:$kbd") + [[ $CFG_SOUND == yes ]] && args+=(/sound) + if [[ $CFG_SCALE == auto ]]; then + rf_auto_scale + else + RF_AUTO_SCALE="$CFG_SCALE" + fi + [[ $RF_AUTO_SCALE -gt 100 ]] && args+=("/scale-desktop:$RF_AUTO_SCALE") + if share="$(rf_share_dir)"; then + name="$(_rf_drive_name "$share")" + args+=("/drive:$name,$(_rf_drive_path "$share")") + # A file in the shared folder needs no drive of its own. + if [[ -n $file && $file == "${share%/}"/* ]]; then + drive="$name" + rel="${file#"${share%/}"/}" + remote="\\\\tsclient\\$name\\${rel//\//\\}" + fi + fi + if [[ -n $file && -z $remote ]]; then + dir="${file%/*}" + drive="$(_rf_drive_name "$dir")" + args+=("/drive:$drive,$(_rf_drive_path "${dir:-/}")") + remote="\\\\tsclient\\$drive\\${file##*/}" + fi + if [[ -n $remote ]]; then + # The app gets the file as its command line, in a copy of the .rdp. + mkdir -p "$RF_RUNDIR" + temprdp="$(mktemp "$RF_RUNDIR/$id--$slug.XXXXXX.rdp")" + RF_CMDLINE="\"$remote\"" awk ' + BEGIN { line = "remoteapplicationcmdline:s:" ENVIRON["RF_CMDLINE"] } + tolower($0) ~ /^remoteapplicationcmdline:s:/ { if (!done) print line "\r"; done = 1; next } + { print } + END { if (!done) print line "\r" } + ' "$rdp" > "$temprdp" + args[0]="$temprdp" + fi + [[ $type == Desktop ]] && args+=(/dynamic-resolution) + + env=(WLOG_LEVEL="${WLOG_LEVEL:-INFO}" RDFEED_GW_WAIT="$CFG_MFAWAIT" RDFEED_GW_DELAY="$RF_GW_DELAY") + [[ $CFG_GRAPHICS == gfx ]] || env+=(RDFEED_NO_GFX=1) + [[ -f $RF_HOOK ]] && env+=(LD_PRELOAD="$RF_HOOK${LD_PRELOAD:+:$LD_PRELOAD}") + + mkdir -p "$RF_CACHEDIR" + log="$RF_CACHEDIR/$id--$slug.log" + rcfile="$(mktemp "$RF_CACHEDIR/.exit.XXXXXX")" + : > "$log" + + if [[ -n $file ]]; then + rf_notify "$(rf_msg "Opening %s in %s" "${file##*/}" "$title")" \ + "$(rf_msg "If it does not open by itself, open it in the app from the drive %s." "$drive")" "$icon" + else + rf_notify "$(rf_msg "Starting %s" "$title")" \ + "$(rf_msg "If your company uses MFA, confirm the sign-in in your authenticator app.")" "$icon" + fi + + { + printf '%s\n' "${args[@]}" | env "${env[@]}" "$RF_FREERDP" /args-from:stdin 2>&1 + printf '%s\n' "${PIPESTATUS[1]}" > "$rcfile" + } | while IFS= read -r line; do + printf '%s\n' "$line" >> "$log" + if [[ $line == "[rdfeed] gateway-delay"* ]]; then + rf_notify "$(rf_msg "A second sign-in is coming")" \ + "$(rf_msg "Confirm the second prompt in your authenticator app too. It comes in about %d seconds." "$RF_GW_DELAY")" "$icon" + fi + done + rc="$(< "$rcfile")" + rm -f "$rcfile" + [[ -n $temprdp ]] && rm -f "$temprdp" + + # Below 128 the session ended the normal way: closed, logged off, or taken + # over by the next app of the same workspace. + if [[ $rc =~ ^[0-9]+$ ]] && (( rc >= 128 )) && ! grep -q ERRCONNECT_CONNECT_CANCELLED "$log"; then + rf_notify "$(rf_msg "%s could not start" "$title")" "$(rf_launch_error "$log")" dialog-error + rf_bad "$(rf_launch_error "$log")" + rf_note "$(rf_msg "Log: %s" "$log")" + return 1 + fi + return 0 +} diff --git a/src/lib/menu.sh b/src/lib/menu.sh new file mode 100644 index 0000000..9c10a10 --- /dev/null +++ b/src/lib/menu.sh @@ -0,0 +1,635 @@ +# shellcheck shell=bash +# +# The interactive front end. +# +# This is the whole configuration interface. There are files behind it, but no +# part of the program ever asks anybody to open one: the workspaces, the apps, +# a settings list. + +# Terminal mode. +# +# bash flips the terminal into non-canonical mode for each `read -sn1` and back +# out again in between. That gap matters: in canonical mode DEL is the ERASE +# character, so the line discipline eats it instead of delivering it, and a +# backspace typed while the interface was between reads simply vanishes. +# Holding non-canonical mode for the whole interface removes the gap. +RF_TERM_SAVED='' + +rf_ui_term_raw() { + rf_have stty || return 0 + [[ -t 0 ]] || return 0 + [[ -n $RF_TERM_SAVED ]] && return 0 + + RF_TERM_SAVED="$(stty -g 2>/dev/null)" || { RF_TERM_SAVED=''; return 0; } + stty -icanon -echo min 1 time 0 2>/dev/null || true +} + +rf_ui_term_restore() { + [[ -n $RF_TERM_SAVED ]] || return 0 + stty "$RF_TERM_SAVED" 2>/dev/null || true + RF_TERM_SAVED='' +} + +# Runs an action with the terminal handed back to normal line mode, so what it +# prompts for (a user name, a password) behaves the way a program expects. +rf_ui_cooked() { + rf_ui_term_restore + "$@" + local rc=$? + rf_ui_term_raw + return $rc +} + +# rf_read_key +# One keypress, resolved to a symbolic name. Arrow keys arrive as ESC [ A, so +# the tail of the sequence is consumed here rather than being mistaken for +# three separate presses. +rf_read_key() { + local k rest + + IFS= read -rsn1 k || return 1 + + case "$k" in + $'\e') + if IFS= read -rsn2 -t 0.05 rest; then + case "$rest" in + '[A') printf 'up\n' ;; + '[B') printf 'down\n' ;; + '[C') printf 'right\n' ;; + '[D') printf 'left\n' ;; + *) printf 'escape\n' ;; + esac + else + printf 'escape\n' + fi + ;; + ''|$'\r') printf 'enter\n' ;; + $'\x7f'|$'\b') printf 'backspace\n' ;; + ' ') printf 'space\n' ;; + *) printf '%s\n' "$k" ;; + esac +} + +# _rf_ui_take_notices +# The messages the last action left, as lines for under a frame, in +# RF_UI_NOTICE_TEXT. Each is shown once. +RF_UI_NOTICE_TEXT='' + +_rf_ui_take_notices() { + local n + RF_UI_NOTICE_TEXT='' + (( ${#RF_UI_NOTICES[@]} )) || return 0 + RF_UI_NOTICE_TEXT=$'\n' + for n in "${RF_UI_NOTICES[@]}"; do + RF_UI_NOTICE_TEXT+=" $n"$'\n' + done + RF_UI_NOTICES=() +} + +# rf_ui_confirm <question> +rf_ui_confirm() { + local key hint yes + # TRANSLATORS: the letter after "[" is the key for yes. y works too. + hint="$(rf_msg "[y/N]")" + yes="${hint:1:1}" + printf '\n %s %s ' "$1" "$hint" + key="$(rf_read_key)" || return 1 + printf '%s\n' "$key" + [[ ${key,,} == y || ${key,,} == "${yes,,}" ]] +} + +# _rf_width <text> +# The columns the text takes, into RF_WIDTH. ${#s} counts characters, but +# Chinese, Japanese and Korean ones take two columns each. +RF_WIDTH=0 + +_rf_width() { + local wide="${1//[^ใ€€-ใ€ฟใ€-ใƒฟใ€-ไถฟไธ€-้ฟฟ๊ฐ€-ํžฏ่ฑˆ-๏ซฟ๏ผ€-๏ฝ ]/}" + RF_WIDTH=$(( ${#1} + ${#wide} )) +} + +# _rf_pad <text> <columns> +# The text, padded with spaces to the columns, into RF_PADDED. printf's %-28s +# pads by bytes, so a label containing "รผ" would come out one column short. +RF_PADDED='' + +_rf_pad() { + local pad + _rf_width "$1" + pad=$(( $2 - RF_WIDTH )) + (( pad < 1 )) && pad=1 + printf -v RF_PADDED '%s%*s' "$1" "$pad" '' +} + +# _rf_wrap <width> <text> +# Word wrap, into RF_WRAP_LINES. +RF_WRAP_LINES=() + +_rf_wrap() { + local width="$1" word line='' used=0 c i + local -a words + RF_WRAP_LINES=() + read -r -a words <<< "$2" + for word in "${words[@]}"; do + _rf_width "$word" + if (( used > 0 && used + 1 + RF_WIDTH > width )); then + RF_WRAP_LINES+=("$line") + line='' used=0 + fi + (( used > 0 )) && line+=' ' used=$(( used + 1 )) + if (( used + RF_WIDTH <= width )); then + line+="$word" used=$(( used + RF_WIDTH )) + continue + fi + # Longer than a line: Chinese and Japanese have no spaces, so break + # between any two characters. + for (( i = 0; i < ${#word}; i++ )); do + c="${word:i:1}" + _rf_width "$c" + if (( used + RF_WIDTH > width )); then + RF_WRAP_LINES+=("$line") + line='' used=0 + fi + line+="$c" used=$(( used + RF_WIDTH )) + done + done + [[ -n $line ]] && RF_WRAP_LINES+=("$line") + return 0 +} + +# _rf_apps_label <count> +_rf_apps_label() { + if [[ $1 == 1 ]]; then + rf_msg "1 app" + else + rf_msg "%d apps" "$1" + fi +} + +# rf_start_detached <id> <app> +# Starts an app on its own, so it keeps running when the menu closes. +rf_start_detached() { + if rf_have setsid; then + setsid -f "$RF_SELF" run "$1/$2" > /dev/null 2>&1 < /dev/null + else + nohup "$RF_SELF" run "$1/$2" > /dev/null 2>&1 < /dev/null & + fi +} + +# --------------------------------------------------------------------------- +# Status +# --------------------------------------------------------------------------- + +# rf_ui_status +# The workspaces, for the head of the menu. +rf_ui_status() { + local id width=0 apps + + rf_ws_ids + if (( ${#RF_WS_IDS[@]} == 0 )); then + printf ' %s\n' "$(rf_msg "No workspace yet. Add the one of your company with [1].")" + return 0 + fi + for id in "${RF_WS_IDS[@]}"; do + rf_ws_load "$id" + _rf_width "$WS_NAME" + (( RF_WIDTH > width )) && width=$RF_WIDTH + done + for id in "${RF_WS_IDS[@]}"; do + rf_ws_load "$id" + rf_ws_apps_load "$id" + _rf_pad "$WS_NAME" $(( width + 3 )) + apps="$(_rf_apps_label "${#RF_APP_SLUGS[@]}")" + printf ' %s%-10s %s%s%s\n' "$RF_PADDED" "$apps" "$RF_C_DIM" "$WS_ACCOUNT" "$RF_C_RESET" + done +} + +# --------------------------------------------------------------------------- +# Settings +# --------------------------------------------------------------------------- +# Format: scope|Key|type|default|label-msgid|choices +# scope user (the settings file), entries (the settings file, and the app +# menu entries are written again), timer (the daily refresh, a +# systemd user timer), or group for a heading, with only the label +# after it +# type bool, or choice (steps through the choices) +RF_SETTINGS=( + "group|Apps" + "user|Share|choice|off|Share a folder|off,documents,downloads,home" + "entries|OpenWith|bool|yes|Open files with the apps" + "user|Scale|choice|auto|Size|auto,100,125,150,175,200" + "user|Sound|bool|yes|Sound" + "user|Graphics|choice|gfx|Graphics|gfx,classic" + "group|Signing in" + "user|MfaWait|choice|auto|Wait for the second MFA prompt|auto,always,never" + "timer|AutoRefresh|bool|yes|Refresh the apps every day" +) + +# rf_value_label <Key> <value> +# How a setting's value reads in the menu. +rf_value_label() { + local dir + case "$1:$2" in + Share:off) rf_msg "off" ;; + Share:home) rf_msg "home folder" ;; + Share:documents|Share:downloads) + case "$2" in + documents) rf_msg "Documents" ;; + *) rf_msg "Downloads" ;; + esac + if dir="$(CFG_SHARE="$2" rf_share_dir)"; then + printf ' %s(%s)%s' "$RF_C_DIM" "${dir/#"$HOME"/\~}" "$RF_C_RESET" + else + printf ' %s(%s)%s' "$RF_C_YELLOW" "$(rf_msg "not found")" "$RF_C_RESET" + fi + ;; + Scale:auto) rf_auto_scale; rf_msg "automatic (%d %%)" "$RF_AUTO_SCALE" ;; + Scale:*) printf '%s %%' "$2" ;; + Graphics:classic) rf_msg "classic" ;; + Graphics:*) rf_msg "GFX" ;; + MfaWait:always) rf_msg "always" ;; + MfaWait:never) rf_msg "never" ;; + MfaWait:*) rf_msg "automatic" ;; + *) printf '%s' "$2" ;; + esac +} + +# rf_setting_help <Key> <value> +# What a setting does, shown under the list for the selected one. +rf_setting_help() { + case "$1:$2" in + Share:off) rf_msg "The apps only see the files on the server. Pick a folder to open and save files of this computer in them." ;; + Share:*) rf_msg "The apps can open and save files in this folder. It shows up as a drive in them." ;; + OpenWith:*) rf_msg "Offers the apps under \"Open with\" for their file types, for example Word for .docx. The folder of the file is shared with the app while it is open." ;; + Scale:*) rf_msg "How big the apps are drawn. Automatic follows the scale of your desktop, so they are sharp and the right size." ;; + Sound:*) rf_msg "Plays the sound of the apps here, for calls and videos." ;; + Graphics:classic) rf_msg "Paints every window right. But some servers end the session right after signing in, then go back to GFX." ;; + Graphics:*) rf_msg "Works with every server. FreeRDP may not repaint a window after it was minimized or resized." ;; + MfaWait:always) rf_msg "Always waits before the second sign-in. For when the second prompt does not come with automatic." ;; + MfaWait:never) rf_msg "Never waits. Right when your company has no MFA." ;; + MfaWait:*) rf_msg "Many servers with MFA need a second sign-in, and its prompt only comes after a short wait. Automatic waits when the first sign-in took a while." ;; + AutoRefresh:*) + if rf_timer_available; then + rf_msg "Loads the list of apps again once a day, so new apps show up by themselves." + else + rf_msg "Needs a systemd user session." + fi + ;; + esac +} + +# _rf_setting_value <scope> <Key> <default> +# The current value, in RF_SETTING_VALUE. +RF_SETTING_VALUE='' + +_rf_setting_value() { + case "$1" in + user|entries) _rf_kv_lookup "$RF_CONFIG" "$2" "$3"; RF_SETTING_VALUE="$RF_KV_VALUE" ;; + timer) if rf_timer_enabled; then RF_SETTING_VALUE=yes; else RF_SETTING_VALUE=no; fi ;; + esac +} + +# _rf_step <current> <step> <choice>... +# The choice <step> (1 or -1) away from the current one, round at the ends. +_rf_step() { + local current="$1" step="$2" i + shift 2 + local -a all=("$@") + for i in "${!all[@]}"; do + if [[ ${all[i]} == "$current" ]]; then + printf '%s\n' "${all[(i + step + ${#all[@]}) % ${#all[@]}]}" + return + fi + done + printf '%s\n' "${all[0]}" +} + +# _rf_setting_change <scope> <Key> <type> <current> <choices> <step> +_rf_setting_change() { + local scope="$1" key="$2" type="$3" current="$4" options="$5" step="$6" next + local -a choices + + case "$type" in + bool) if rf_is_true "$current"; then next=no; else next=yes; fi ;; + choice) + IFS=',' read -r -a choices <<< "$options" + next="$(_rf_step "$current" "$step" "${choices[@]}")" + ;; + esac + + case "$scope" in + user) + rf_config_set "$key" "$next" || rf_bad "$(rf_msg "Could not save the setting.")" + ;; + entries) + rf_config_set "$key" "$next" || rf_bad "$(rf_msg "Could not save the setting.")" + rf_ws_entries_all + ;; + timer) + rf_config_set "$key" "$next" + rf_timer_set "$next" || rf_bad "$(rf_msg "Needs a systemd user session.")" + ;; + esac +} + +# rf_ui_settings +# A cursor list in groups, with what the selected setting does under it. The +# frame is assembled in memory and written once, and everything constant is +# resolved before the loop. +rf_ui_settings() { + local -a scopes=() keys=() types=() defaults=() labels=() widths=() choices=() values=() rows=() + local spec scope key type default label choice locale i j frame row pad dirty=1 cursor=0 shown + local width=0 wrap cols + + locale="$(rf_ui_locale)" + for spec in "${RF_SETTINGS[@]}"; do + IFS='|' read -r scope key type default label choice <<< "$spec" + # A heading has its label where the key would be. + [[ $scope == group ]] && label="$key" key='' + scopes+=("$scope"); keys+=("$key"); types+=("$type"); defaults+=("$default") + choices+=("$choice") + rf_msg_into "$locale" "$label" + labels+=("$RF_MSG_RESULT") + _rf_width "$RF_MSG_RESULT" + widths+=("$RF_WIDTH") + if [[ $scope != group ]]; then + rows+=($(( ${#keys[@]} - 1 ))) + (( RF_WIDTH > width )) && width=$RF_WIDTH + fi + done + local count=${#rows[@]} + + cols="$(tput cols 2>/dev/null)" || cols=80 + [[ $cols =~ ^[0-9]+$ ]] || cols=80 + wrap=$(( cols - 4 )) + (( wrap > 72 )) && wrap=72 + local rule + printf -v rule '%*s' "$wrap" '' + rule="${rule// /โ”€}" + + local title hint l_on l_off + rf_msg_into "$locale" "Settings"; title="$RF_MSG_RESULT" + rf_msg_into "$locale" "โ†‘โ†“ select โ†โ†’ or Space: change q: back"; hint="$RF_MSG_RESULT" + rf_msg_into "$locale" "ON"; l_on="$RF_MSG_RESULT" + rf_msg_into "$locale" "OFF"; l_off="$RF_MSG_RESULT" + + local clearseq + clearseq="$(clear 2>/dev/null)" || clearseq=$'\033[H\033[2J' + + while true; do + if (( dirty )); then + RF_KV_CACHE=() + for i in "${rows[@]}"; do + _rf_setting_value "${scopes[i]}" "${keys[i]}" "${defaults[i]}" + values[i]="$RF_SETTING_VALUE" + done + dirty=0 + fi + + frame="$clearseq"$'\n'"${RF_C_BOLD}${RF_C_BLUE} ${title}${RF_C_RESET}"$'\n' + + local selected=${rows[cursor]} marker before after + for i in "${!keys[@]}"; do + if [[ ${scopes[i]} == group ]]; then + frame+=$'\n'" ${RF_C_BOLD}${labels[i]}${RF_C_RESET}"$'\n' + continue + fi + if [[ ${types[i]} == bool ]]; then + if rf_is_true "${values[i]}"; then + shown="${RF_C_GREEN}${l_on}${RF_C_RESET}" + else + shown="${RF_C_DIM}${l_off}${RF_C_RESET}" + fi + else + shown="$(rf_value_label "${keys[i]}" "${values[i]}")" + fi + # Arrows on the selected choice: left and right step through it. + before=' ' after='' + if (( i == selected )) && [[ ${types[i]} != bool ]]; then + before="${RF_C_BLUE}โ—‚${RF_C_RESET} " after=" ${RF_C_BLUE}โ–ธ${RF_C_RESET}" + fi + pad=$(( width + 2 - widths[i] )) + if (( i == selected )); then marker="${RF_C_BLUE}โ–ธ${RF_C_RESET} "; else marker=' '; fi + printf -v row ' %s%s%*s%s%s%s' "$marker" "${labels[i]}" "$pad" '' "$before" "$shown$RF_C_RESET" "$after" + frame+="$row"$'\n' + done + + # What the selected setting does, in a box of fixed height so the + # screen does not jump while moving through the list. + _rf_wrap "$wrap" "$(rf_setting_help "${keys[selected]}" "${values[selected]}")" + frame+=$'\n'" ${RF_C_DIM}${rule}${RF_C_RESET}"$'\n' + for j in 0 1 2; do + frame+=" ${RF_WRAP_LINES[j]:-}"$'\n' + done + frame+=$'\n'" ${RF_C_DIM}${hint}${RF_C_RESET}"$'\n' + _rf_ui_take_notices + frame+="$RF_UI_NOTICE_TEXT" + printf '%s' "$frame" + + key="$(rf_read_key)" || return 0 + + case "$key" in + up|k) cursor=$(( (cursor - 1 + count) % count )) ;; + down|j) cursor=$(( (cursor + 1) % count )) ;; + space|right|l|left|h) + local step=1 + [[ $key == left || $key == h ]] && step=-1 + _rf_setting_change "${scopes[selected]}" "${keys[selected]}" "${types[selected]}" "${values[selected]}" "${choices[selected]}" "$step" + dirty=1 + ;; + q|Q|escape) return 0 ;; + *) ;; + esac + done +} + +# --------------------------------------------------------------------------- +# Workspaces +# --------------------------------------------------------------------------- + +rf_ui_workspaces() { + local key frame row i cursor=0 count locale width + local -a ids=() + + locale="$(rf_ui_locale)" + local title hint empty + rf_msg_into "$locale" "Workspaces"; title="$RF_MSG_RESULT" + # TRANSLATORS: keep the letters, they are the keys. + rf_msg_into "$locale" "โ†‘โ†“ select r: refresh s: sign in again d: remove a: add q: back"; hint="$RF_MSG_RESULT" + rf_msg_into "$locale" "No workspace yet. Press a to add one."; empty="$RF_MSG_RESULT" + + local clearseq + clearseq="$(clear 2>/dev/null)" || clearseq=$'\033[H\033[2J' + + while true; do + rf_ws_ids + ids=("${RF_WS_IDS[@]}") + count=${#ids[@]} + (( cursor >= count )) && cursor=$(( count > 0 ? count - 1 : 0 )) + + width=0 + for i in "${!ids[@]}"; do + rf_ws_load "${ids[i]}" + _rf_width "$WS_NAME" + (( RF_WIDTH > width )) && width=$RF_WIDTH + done + + frame="$clearseq"$'\n'"${RF_C_BOLD}${RF_C_BLUE} ${title}${RF_C_RESET}"$'\n\n' + (( count == 0 )) && frame+=" ${RF_C_DIM}${empty}${RF_C_RESET}"$'\n' + local marker apps when + for i in "${!ids[@]}"; do + rf_ws_load "${ids[i]}" + rf_ws_apps_load "${ids[i]}" + apps="$(_rf_apps_label "${#RF_APP_SLUGS[@]}")" + when="$(rf_msg "refreshed %s" "$(rf_time_ago "$WS_REFRESHED")")" + _rf_pad "$WS_NAME" $(( width + 3 )) + if (( i == cursor )); then marker="${RF_C_BLUE}โ–ธ${RF_C_RESET} "; else marker=' '; fi + printf -v row ' %s%s%-10s %s %s%s%s' "$marker" "$RF_PADDED" "$apps" "$WS_ACCOUNT" "$RF_C_DIM" "$when" "$RF_C_RESET" + frame+="$row"$'\n' + done + frame+=$'\n'" ${RF_C_DIM}${hint}${RF_C_RESET}"$'\n' + _rf_ui_take_notices + frame+="$RF_UI_NOTICE_TEXT" + printf '%s' "$frame" + + key="$(rf_read_key)" || return 0 + case "$key" in + up|k) (( count )) && cursor=$(( (cursor - 1 + count) % count )) ;; + down|j) (( count )) && cursor=$(( (cursor + 1) % count )) ;; + r|R) + (( count )) || continue + printf '\n' + rf_ui_cooked rf_do_refresh "${ids[cursor]}" + ;; + s|S) + (( count )) || continue + printf '\n' + rf_ui_cooked rf_do_signin_again "${ids[cursor]}" + ;; + d|D) + (( count )) || continue + rf_ws_load "${ids[cursor]}" + if rf_ui_confirm "$(rf_msg "Remove \"%s\" and its apps?" "$WS_NAME")"; then + rf_do_remove "${ids[cursor]}" > /dev/null + fi + ;; + a|A) + printf '\n' + rf_ui_cooked rf_do_add + ;; + q|Q|escape) return 0 ;; + *) ;; + esac + done +} + +# --------------------------------------------------------------------------- +# Apps +# --------------------------------------------------------------------------- + +rf_ui_apps() { + local key frame row i id cursor=0 count locale + local -a ws=() slugs=() titles=() + + locale="$(rf_ui_locale)" + local title hint empty + rf_msg_into "$locale" "Start an app"; title="$RF_MSG_RESULT" + rf_msg_into "$locale" "โ†‘โ†“ select Enter: start q: back"; hint="$RF_MSG_RESULT" + rf_msg_into "$locale" "No apps yet. Add a workspace first."; empty="$RF_MSG_RESULT" + + rf_ws_ids + for id in "${RF_WS_IDS[@]}"; do + rf_ws_apps_load "$id" + for i in "${!RF_APP_SLUGS[@]}"; do + ws+=("$id") slugs+=("${RF_APP_SLUGS[i]}") titles+=("${RF_APP_TITLES[i]}") + done + done + count=${#slugs[@]} + + local clearseq + clearseq="$(clear 2>/dev/null)" || clearseq=$'\033[H\033[2J' + + while true; do + frame="$clearseq"$'\n'"${RF_C_BOLD}${RF_C_BLUE} ${title}${RF_C_RESET}"$'\n' + (( count == 0 )) && frame+=$'\n'" ${RF_C_DIM}${empty}${RF_C_RESET}"$'\n' + local marker last='' + for i in "${!slugs[@]}"; do + # A heading for each workspace. + if [[ ${ws[i]} != "$last" ]]; then + rf_ws_load "${ws[i]}" + frame+=$'\n'" ${RF_C_BOLD}${WS_NAME}${RF_C_RESET}"$'\n' + last="${ws[i]}" + fi + if (( i == cursor )); then marker="${RF_C_BLUE}โ–ธ${RF_C_RESET} "; else marker=' '; fi + printf -v row ' %s%s' "$marker" "${titles[i]}" + frame+="$row"$'\n' + done + frame+=$'\n'" ${RF_C_DIM}${hint}${RF_C_RESET}"$'\n' + _rf_ui_take_notices + frame+="$RF_UI_NOTICE_TEXT" + printf '%s' "$frame" + + key="$(rf_read_key)" || return 0 + case "$key" in + up|k) (( count )) && cursor=$(( (cursor - 1 + count) % count )) ;; + down|j) (( count )) && cursor=$(( (cursor + 1) % count )) ;; + enter|space) + (( count )) || continue + rf_start_detached "${ws[cursor]}" "${slugs[cursor]}" + rf_ok "$(rf_msg "%s is starting. It opens in a window of its own." "${titles[cursor]}")" > /dev/null + ;; + q|Q|escape) return 0 ;; + *) ;; + esac + done +} + +# --------------------------------------------------------------------------- +# The menu +# --------------------------------------------------------------------------- + +rf_ui_menu() { + local choice + + if [[ ! -t 0 ]]; then + rf_do_help + return 1 + fi + rf_ui_term_raw + trap 'rf_ui_term_restore' EXIT INT TERM + + while true; do + clear 2>/dev/null || true + rf_head " $RF_PRETTY" + rf_ui_status + printf '\n' + printf ' [1] %s\n' "$(rf_msg "Add a workspace")" + printf ' [2] %s\n' "$(rf_msg "Start an app")" + printf ' [3] %s\n' "$(rf_msg "Workspaces")" + printf ' [4] %s\n' "$(rf_msg "Settings")" + printf ' [q] %s\n' "$(rf_msg "Quit")" + _rf_ui_take_notices + printf '%s' "$RF_UI_NOTICE_TEXT" + printf '\n > ' + + choice="$(rf_read_key)" || { + printf '\n'; rf_ui_term_restore; trap - EXIT INT TERM; return 0 + } + case "$choice" in + enter|space|up|down|left|right|escape) choice='' ;; + esac + printf '%s\n' "$choice" + + case "$choice" in + 1) printf '\n'; rf_ui_cooked rf_do_add ;; + 2) rf_ui_apps ;; + 3) rf_ui_workspaces ;; + 4) rf_ui_settings ;; + q|Q) rf_ui_term_restore; trap - EXIT INT TERM; return 0 ;; + # Anything else (Enter, arrow keys, stray characters) just + # redraws. Escape is deliberately not a quit key, so a mistyped + # arrow key cannot close the menu. + *) ;; + esac + done +} diff --git a/src/lib/workspace.sh b/src/lib/workspace.sh new file mode 100644 index 0000000..178516e --- /dev/null +++ b/src/lib/workspace.sh @@ -0,0 +1,389 @@ +# shellcheck shell=bash +# +# Workspaces: the feeds rdfeed is subscribed to, and the app menu entries they +# turn into. +# +# A workspace has an id made from its server name. Its settings are in +# ~/.config/rdfeed/workspaces/<id>, its apps in ~/.local/share/rdfeed/<id> +# (apps.tsv, rdp/<app>.rdp, icons/<app>.png), its password in the keyring. +# Each app gets ~/.local/share/applications/rdfeed-<id>--<app>.desktop, and +# the workspace a folder of its own in the app menu. With "Open files with +# the apps" on, the entries also name the file types, so file managers offer +# the apps under "Open with". + +# --------------------------------------------------------------------------- +# The keyring +# --------------------------------------------------------------------------- + +rf_secret_get() { + rf_have secret-tool || return 1 + secret-tool lookup service rdfeed workspace "$1" user "$2" 2>/dev/null +} + +# rf_secret_set <id> <account> <label> <password> +# secret-tool keeps all of stdin, a trailing newline too, so printf and not +# a here-string. printf is a builtin: the password shows in no process list. +rf_secret_set() { + rf_have secret-tool || return 1 + printf '%s' "$4" | secret-tool store --label="rdfeed: $3" service rdfeed workspace "$1" user "$2" \ + > /dev/null 2>&1 +} + +rf_secret_clear() { + rf_have secret-tool || return 0 + secret-tool clear service rdfeed workspace "$1" > /dev/null 2>&1 || true +} + +# --------------------------------------------------------------------------- +# Finding the feed +# --------------------------------------------------------------------------- + +# rf_discover <domain> +# Windows finds the feed for a work e-mail address in a TXT record of the +# domain, _msradc.<domain>. Whichever DNS tool is around asks for it. +rf_discover() { + local name="_msradc.$1" txt='' + rf_have resolvectl && txt="$(resolvectl query --legend=no -t TXT "$name" 2>/dev/null)" + [[ -z $txt ]] && rf_have dig && txt="$(dig +short TXT "$name" 2>/dev/null)" + [[ -z $txt ]] && rf_have host && txt="$(host -t TXT "$name" 2>/dev/null)" + [[ -z $txt ]] && rf_have drill && txt="$(drill TXT "$name" 2>/dev/null)" + [[ $txt =~ (https?://[^\"[:space:]]+) ]] || return 1 + printf '%s\n' "${BASH_REMATCH[1]}" +} + +# rf_feed_url <what was typed> +# A server name, an address or a work e-mail address, as the address of the +# feed, in RF_URL. 1: nothing typed, 2: no feed found for the e-mail address. +RF_URL='' + +rf_feed_url() { + local in="$1" scheme rest host path + in="${in#"${in%%[![:space:]]*}"}" + in="${in%"${in##*[![:space:]]}"}" + RF_URL='' + [[ -n $in ]] || return 1 + + if [[ $in != */* && $in == *@* ]]; then + RF_URL="$(rf_discover "${in#*@}")" || return 2 + return 0 + fi + + [[ $in == *://* ]] || in="https://$in" + scheme="${in%%://*}" + rest="${in#*://}" + host="${rest%%/*}" + path="${rest#"$host"}" + # The feed sits at the same place on every server. Whatever part of it + # was typed, the RD Web page included, leads there. + case "${path,,}" in + ''|/|/rdweb|/rdweb/|/rdweb/feed|/rdweb/feed/|/rdweb/pages*) path=/RDWeb/Feed/webfeed.aspx ;; + */webfeed) path+=.aspx ;; + esac + RF_URL="${scheme,,}://$host$path" +} + +# rf_ws_id_for <url> +# The workspace that has this address, or a new id made from the server name. +rf_ws_id_for() { + local url="$1" id base n=2 host + rf_ws_ids + for id in "${RF_WS_IDS[@]}"; do + _rf_kv_lookup "$(rf_ws_file "$id")" Url + [[ ${RF_KV_VALUE,,} == "${url,,}" ]] && { printf '%s\n' "$id"; return; } + done + host="${url#*://}" + host="${host%%/*}" + host="${host%%:*}" + base="${host,,}" + base="${base//[^a-z0-9]/-}" + while [[ $base == *--* ]]; do base="${base//--/-}"; done + base="${base#-}" + base="${base%-}" + [[ -n $base ]] || base=workspace + id="$base" + while [[ -e $(rf_ws_file "$id") ]]; do + id="$base-$n" + n=$(( n + 1 )) + done + printf '%s\n' "$id" +} + +# rf_split_user <typed> +# DOMAIN\name, name@domain or just a name, into RF_USER and RF_DOMAIN. The +# domain is "-" when the server's own should be taken, and empty for +# name@domain, which carries its domain. +rf_split_user() { + local in="$1" + if [[ $in == *\\* ]]; then + RF_DOMAIN="${in%%\\*}" + RF_USER="${in#*\\}" + elif [[ $in == *@* ]]; then + RF_DOMAIN='' + RF_USER="$in" + else + RF_DOMAIN='-' + RF_USER="$in" + fi +} + +# --------------------------------------------------------------------------- +# Downloading +# --------------------------------------------------------------------------- + +# rf_fetch <url> <user> <domain or -> <dir> <password> [quiet] +# Runs rdfeed-fetch into <dir>, printing each app as it arrives unless quiet. +# Leaves RF_FETCH_NAME, RF_FETCH_DOMAIN, RF_FETCH_COUNT and, when it failed, +# RF_FETCH_ERROR (the reason, already translated). +rf_fetch() { + local url="$1" user="$2" domain="$3" out="$4" password="$5" quiet="${6:-}" + local kind a b c rc=1 detail + local -a args=(--url "$url" --user "$user" --out "$out") + [[ $domain != - ]] && args+=(--domain "$domain") + + RF_FETCH_NAME='' RF_FETCH_DOMAIN='' RF_FETCH_COUNT=0 RF_FETCH_ERROR='' + while IFS=$'\t' read -r kind a b c; do + case "$kind" in + workspace) RF_FETCH_NAME="$a" ;; + domain) RF_FETCH_DOMAIN="$a" ;; + app) + RF_FETCH_COUNT=$(( RF_FETCH_COUNT + 1 )) + [[ -z $quiet ]] && printf ' %sโœ”%s %s\n' "$RF_C_GREEN" "$RF_C_RESET" "$c" + ;; + skip) + [[ -z $quiet ]] && printf ' %sโœ˜%s %s %s(%s)%s\n' "$RF_C_RED" "$RF_C_RESET" "$a" "$RF_C_DIM" "$b" "$RF_C_RESET" + ;; + exit) rc="$a" ;; + esac + done < <("$RF_FETCH" "${args[@]}" <<< "$password" 2> "$out/.error"; printf 'exit\t%s\n' "$?") + + (( rc == 0 )) && { rm -f "$out/.error"; return 0; } + detail="$(tail -n1 "$out/.error" 2>/dev/null)" + detail="${detail#rdfeed-fetch: }" + case "$rc" in + 10) RF_FETCH_ERROR="$(rf_msg "The server did not accept the user name or password.")" ;; + 11) RF_FETCH_ERROR="$(rf_msg "Could not reach the server: %s" "$detail")" ;; + 12) RF_FETCH_ERROR="$(rf_msg "There is no RD Web feed at %s." "$url")" ;; + *) RF_FETCH_ERROR="$(rf_msg "Could not load the apps: %s" "${detail:-$rc}")" ;; + esac + return "$rc" +} + +# rf_stage_dir +# A fresh directory next to the workspaces, so moving it in place is a rename. +rf_stage_dir() { + mkdir -p "$RF_DATADIR" && mktemp -d "$RF_DATADIR/.new.XXXXXX" +} + +# rf_ws_install <id> <stage dir> +rf_ws_install() { + local dir="$RF_DATADIR/$1" + rm -f "$2/.error" + rm -rf "$dir.old" + [[ -d $dir ]] && mv "$dir" "$dir.old" + mv "$2" "$dir" || { [[ -d $dir.old ]] && mv "$dir.old" "$dir"; return 1; } + rm -rf "$dir.old" + rf_ws_entries "$1" +} + +# rf_ws_apps_load <id> +# The apps of a workspace in RF_APP_SLUGS, RF_APP_TYPES, RF_APP_TITLES and +# RF_APP_EXTS (the file types it opens, as "docx doc rtf"). +RF_APP_SLUGS=() RF_APP_TYPES=() RF_APP_TITLES=() RF_APP_EXTS=() + +rf_ws_apps_load() { + local slug type title exts file="$RF_DATADIR/$1/apps.tsv" + RF_APP_SLUGS=() RF_APP_TYPES=() RF_APP_TITLES=() RF_APP_EXTS=() + [[ -r $file ]] || return 0 + while IFS=$'\t' read -r slug type title exts; do + [[ -n $slug ]] || continue + RF_APP_SLUGS+=("$slug") RF_APP_TYPES+=("$type") RF_APP_TITLES+=("$title") RF_APP_EXTS+=("$exts") + done < "$file" +} + +# --------------------------------------------------------------------------- +# The app menu +# --------------------------------------------------------------------------- + +_rf_desktop_value() { + local v="${1//\\/\\\\}" + v="${v//$'\n'/ }" + printf '%s' "${v//$'\r'/}" +} + +# The command, as the Exec line of an entry wants it. +_rf_exec_path() { + if [[ $RF_SELF =~ ^[A-Za-z0-9_./+-]+$ ]]; then + printf '%s' "$RF_SELF" + else + local p="${RF_SELF//\\/\\\\\\\\}" + p="${p//\"/\\\\\"}" + p="${p//\$/\\\\\$}" + p="${p//\`/\\\\\`}" + printf '"%s"' "$p" + fi +} + +# _rf_mime_types <ext>... +# The MIME types of the file extensions, as a MimeType= value, from the +# shared-mime-info database every desktop uses. +_rf_mime_types() { + local f + local -a globs=() + for f in "$RF_XDG_DATA/mime/globs2" /usr/local/share/mime/globs2 /usr/share/mime/globs2; do + [[ -r $f ]] && globs+=("$f") + done + (( ${#globs[@]} )) || return 0 + awk -F: -v want=" $* " ' + BEGIN { want = tolower(want) } + /^#/ { next } + { + glob = tolower($3) + if (glob !~ /^\*\.[a-z0-9_+-]+$/) next + if (index(want, " " substr(glob, 3) " ") && !seen[$2]++) out = out $2 ";" + } + END { printf "%s", out } + ' "${globs[@]}" +} + +# rf_menu_refresh +# KDE reads the app menu from a cache, which needs a nudge. GNOME and the rest +# watch the folders themselves. +rf_menu_refresh() { + if rf_have kbuildsycoca6; then + kbuildsycoca6 > /dev/null 2>&1 || true + elif rf_have kbuildsycoca5; then + kbuildsycoca5 > /dev/null 2>&1 || true + fi + rf_have update-desktop-database && update-desktop-database "$RF_APPDIR" > /dev/null 2>&1 + return 0 +} + +# rf_ws_entries <id> +# An entry for each app, a folder for the workspace, and nothing left of apps +# the feed no longer has. +rf_ws_entries() { + local id="$1" i slug title icon comment file keywords mime openwith=no args + local -A keep=() + + rf_ws_load "$id" + rf_ws_apps_load "$id" + mkdir -p "$RF_APPDIR" "$RF_MENUDIR" "$RF_DIRDIR" || return 1 + _rf_kv_lookup "$RF_CONFIG" OpenWith yes + rf_is_true "$RF_KV_VALUE" && openwith=yes + + keywords="$(_rf_desktop_value "$WS_NAME")" + keywords="${keywords//;/\\;}" + for i in "${!RF_APP_SLUGS[@]}"; do + slug="${RF_APP_SLUGS[i]}" + title="${RF_APP_TITLES[i]}" + icon="$RF_DATADIR/$id/icons/$slug.png" + [[ -f $icon ]] || icon=rdfeed + if [[ ${RF_APP_TYPES[i]} == Desktop ]]; then + comment="$(rf_msg "Remote desktop of %s" "$WS_NAME")" + else + comment="$(rf_msg "%s from %s" "$title" "$WS_NAME")" + fi + mime='' args='' + if [[ $openwith == yes && -n ${RF_APP_EXTS[i]} ]]; then + # shellcheck disable=SC2086 # one extension per argument + mime="$(_rf_mime_types ${RF_APP_EXTS[i]})" + [[ -n $mime ]] && args=' %f' + fi + file="$RF_APPDIR/rdfeed-$id--$slug.desktop" + keep[$file]=1 + cat > "$file" <<- EOF + [Desktop Entry] + Type=Application + Name=$(_rf_desktop_value "$title") + GenericName=$(_rf_desktop_value "$WS_NAME") + Comment=$(_rf_desktop_value "$comment") + Exec=$(_rf_exec_path) run $id/$slug$args + Icon=$icon + Terminal=false + StartupNotify=false + Categories=X-rdfeed-$id; + Keywords=RemoteApp;RDP;$keywords; + EOF + [[ -n $mime ]] && printf 'MimeType=%s\n' "$mime" >> "$file" + done + + for file in "$RF_APPDIR"/rdfeed-"$id"--*.desktop; do + [[ -e $file && -z ${keep[$file]:-} ]] && rm -f "$file" + done + + cat > "$RF_MENUDIR/rdfeed-$id.menu" <<- EOF + <!DOCTYPE Menu PUBLIC "-//freedesktop//DTD Menu 1.0//EN" + "http://www.freedesktop.org/standards/menu-spec/menu-1.0.dtd"> + <!-- Written by rdfeed: the folder of the workspace $id. --> + <Menu> + <Name>Applications</Name> + <Menu> + <Name>rdfeed-$id</Name> + <Directory>rdfeed-$id.directory</Directory> + <Include> + <Category>X-rdfeed-$id</Category> + </Include> + </Menu> + </Menu> + EOF + + cat > "$RF_DIRDIR/rdfeed-$id.directory" <<- EOF + [Desktop Entry] + Type=Directory + Name=$(_rf_desktop_value "$WS_NAME") + Icon=rdfeed + EOF + + rf_menu_refresh +} + +# rf_ws_entries_all +# The entries of every workspace again, after a setting that shows in them. +rf_ws_entries_all() { + local id + rf_ws_ids + for id in "${RF_WS_IDS[@]}"; do + rf_ws_entries "$id" + done +} + +# rf_ws_purge <id> +# Everything of a workspace: entries, folder, apps, settings, password, logs. +rf_ws_purge() { + local id="$1" file + for file in "$RF_APPDIR"/rdfeed-"$id"--*.desktop; do + [[ -e $file ]] && rm -f "$file" + done + rm -f "$RF_MENUDIR/rdfeed-$id.menu" "$RF_DIRDIR/rdfeed-$id.directory" + rm -rf "${RF_DATADIR:?}/$id" + rm -f "$(rf_ws_file "$id")" + for file in "$RF_CACHEDIR/$id"--*.log; do + [[ -e $file ]] && rm -f "$file" + done + rf_secret_clear "$id" + rf_menu_refresh +} + +# --------------------------------------------------------------------------- +# Refreshing every day +# --------------------------------------------------------------------------- + +rf_timer_available() { + rf_have systemctl || return 1 + systemctl --user cat "$RF_UNIT_TIMER" > /dev/null 2>&1 +} + +rf_timer_enabled() { + rf_have systemctl || return 1 + [[ $(systemctl --user is-enabled "$RF_UNIT_TIMER" 2>/dev/null) == enabled ]] +} + +# rf_timer_set yes|no +rf_timer_set() { + rf_timer_available || return 1 + if [[ $1 == yes ]]; then + systemctl --user enable --now "$RF_UNIT_TIMER" > /dev/null 2>&1 + else + systemctl --user disable --now "$RF_UNIT_TIMER" > /dev/null 2>&1 + fi +} diff --git a/src/rdfeed b/src/rdfeed new file mode 100755 index 0000000..cdfc474 --- /dev/null +++ b/src/rdfeed @@ -0,0 +1,321 @@ +#!/usr/bin/env bash +# +# rdfeed: the RemoteApps of your company in the Linux app menu +# +# Companies publish their Windows apps (Word, Excel, their own tools) as an RD +# Web Access feed. Windows subscribes to it under "RemoteApp and Desktop +# Connections" and puts the apps in the start menu. rdfeed does the same on +# Linux: each app gets an entry in the app menu and opens as a window of its +# own, through FreeRDP. +# +# This is the front end: the menu and the commands. Signing in to the feed and +# downloading the apps is rdfeed-fetch's job, and rdfeed-hook.so goes into +# FreeRDP to work around two of its problems (see launch.sh). +# +# Copyright (C) 2026 Felitendo +# SPDX-License-Identifier: GPL-3.0-or-later + +set -uo pipefail + +RF_SELF="$(readlink -f "${BASH_SOURCE[0]}")" +RF_LIBDIR="${RF_LIBDIR:-@LIBDIR@}" + +for _mod in common config workspace launch menu; do + # shellcheck source=/dev/null + if ! source "$RF_LIBDIR/$_mod.sh"; then + printf 'rdfeed: cannot load %s/%s.sh\n' "$RF_LIBDIR" "$_mod" >&2 + exit 14 + fi +done +unset _mod + +# --------------------------------------------------------------------------- +# Commands +# --------------------------------------------------------------------------- + +# rf_prompt <question> [default] +# A line typed in, in RF_ANSWER. Without a terminal it is read from stdin, so +# scripts can pipe the answers in. +RF_ANSWER='' + +rf_prompt() { + RF_ANSWER='' + if [[ -t 0 ]]; then + IFS= read -rep " $1 " -i "${2:-}" RF_ANSWER || return 1 + else + IFS= read -r RF_ANSWER || return 1 + fi + return 0 +} + +# rf_prompt_password <question> +rf_prompt_password() { + RF_ANSWER='' + if [[ -t 0 ]]; then + IFS= read -rsp " $1 " RF_ANSWER || return 1 + printf '\n' + else + IFS= read -r RF_ANSWER || return 1 + fi + [[ -n $RF_ANSWER ]] +} + +# rf_signin <id or ""> <url> <user as typed> <password> +# Signs in, downloads the apps and puts them in the app menu. With an empty +# id a new workspace is made, or the one that already has this address. +rf_signin() { + local id="$1" url="$2" typed="$3" password="$4" stage old_account first=0 domain + + [[ -n $id ]] || id="$(rf_ws_id_for "$url")" + rf_ws_ids + (( ${#RF_WS_IDS[@]} == 0 )) && first=1 + old_account='' + if [[ -e $(rf_ws_file "$id") ]]; then + rf_ws_load "$id" + old_account="$WS_ACCOUNT" + fi + + rf_split_user "$typed" + stage="$(rf_stage_dir)" || { rf_bad "$(rf_msg "Could not write to %s." "$RF_DATADIR")"; return 1; } + + local host="${url#*://}" + rf_say " $(rf_msg "Signing in to %sโ€ฆ" "${host%%/*}")" + if ! rf_fetch "$url" "$RF_USER" "$RF_DOMAIN" "$stage" "$password"; then + rm -rf "$stage" + rf_bad "$RF_FETCH_ERROR" + return 1 + fi + + domain="$RF_DOMAIN" + [[ $domain == - ]] && domain="$RF_FETCH_DOMAIN" + rf_ws_set "$id" Url "$url" + rf_ws_set "$id" Name "$RF_FETCH_NAME" + rf_ws_set "$id" User "$RF_USER" + rf_ws_set "$id" Domain "$domain" + rf_ws_set "$id" Refreshed "$(date +%s)" + rf_ws_load "$id" + + [[ -n $old_account && $old_account != "$WS_ACCOUNT" ]] && rf_secret_clear "$id" + if ! rf_secret_set "$id" "$WS_ACCOUNT" "$WS_NAME" "$password"; then + rf_note "$(rf_msg "The password could not be saved in the keyring, so each app asks for it.")" + fi + + rf_ws_install "$id" "$stage" || { rf_bad "$(rf_msg "Could not write to %s." "$RF_DATADIR")"; return 1; } + + # New apps should show up by themselves, as on Windows. + if (( first )) && [[ $(rf_config_get AutoRefresh yes) == yes ]]; then + rf_timer_set yes || true + fi + + if (( RF_FETCH_COUNT == 1 )); then + rf_ok "$(rf_msg "1 app from %s is now in your app menu." "$WS_NAME")" + else + rf_ok "$(rf_msg "%d apps from %s are now in your app menu." "$RF_FETCH_COUNT" "$WS_NAME")" + fi +} + +rf_do_add() { + local input='' user='' url rc + + while (( $# )); do + case "$1" in + --user) user="${2:-}"; shift ;; + *) input="$1" ;; + esac + shift + done + + if [[ -z $input ]]; then + rf_say " $(rf_msg "Your IT department has the address. A work e-mail address often works too.")" + rf_prompt "$(rf_msg "Feed address, server or e-mail:")" || return 1 + input="$RF_ANSWER" + fi + rf_feed_url "$input" + rc=$? + case "$rc" in + 1) return 1 ;; + 2) rf_bad "$(rf_msg "No feed is registered for %s. Ask your IT department for the address." "$input")"; return 1 ;; + esac + url="$RF_URL" + + if [[ -z $user ]]; then + rf_prompt "$(rf_msg "User name (DOMAIN\\name or name@domain):")" || return 1 + user="$RF_ANSWER" + fi + [[ -n $user ]] || return 1 + rf_prompt_password "$(rf_msg "Password:")" || return 1 + + rf_signin '' "$url" "$user" "$RF_ANSWER" +} + +# rf_do_signin_again <id> +# Another password or another user for a workspace. +rf_do_signin_again() { + local id="$1" + rf_ws_load "$id" + rf_prompt "$(rf_msg "User name (DOMAIN\\name or name@domain):")" "$WS_ACCOUNT" || return 1 + [[ -n $RF_ANSWER ]] || return 1 + local user="$RF_ANSWER" + rf_prompt_password "$(rf_msg "Password:")" || return 1 + rf_signin "$id" "$WS_URL" "$user" "$RF_ANSWER" +} + +# rf_do_refresh [id] [--quiet] +# The quiet one is the daily timer's: no questions, and a notification only +# when somebody has to do something. +rf_do_refresh() { + local quiet='' only='' id password stage rc failed=0 + while (( $# )); do + case "$1" in + --quiet) quiet=1 ;; + *) only="$1" ;; + esac + shift + done + + rf_ws_ids + if (( ${#RF_WS_IDS[@]} == 0 )); then + [[ -z $quiet ]] && rf_note "$(rf_msg "No workspace yet. Add one with \`%s add\`." "$RF_NAME")" + return 0 + fi + for id in "${RF_WS_IDS[@]}"; do + [[ -n $only && $id != "$only" ]] && continue + rf_ws_load "$id" + password="$(rf_secret_get "$id" "$WS_ACCOUNT")" || password='' + if [[ -z $password ]]; then + if [[ -n $quiet || ! -t 0 ]]; then + [[ -n $quiet ]] && rf_notify "$WS_NAME" "$(rf_msg "No password saved for %s. Sign in again in rdfeed." "$WS_NAME")" dialog-warning + failed=1 + continue + fi + rf_prompt_password "$(rf_msg "Password for %s:" "$WS_ACCOUNT")" || { failed=1; continue; } + password="$RF_ANSWER" + fi + + [[ -z $quiet ]] && rf_say " $(rf_msg "Refreshing %sโ€ฆ" "$WS_NAME")" + stage="$(rf_stage_dir)" || return 1 + rf_fetch "$WS_URL" "$WS_USER" "${WS_DOMAIN:-}" "$stage" "$password" "$quiet" + rc=$? + if (( rc != 0 )); then + rm -rf "$stage" + failed=1 + if [[ -n $quiet ]]; then + # A server that is not reachable now will be tomorrow. A + # password that no longer works needs the user. + (( rc == 10 )) && rf_notify "$WS_NAME" "$(rf_msg "The password no longer works. Sign in again in rdfeed.")" dialog-warning + else + rf_bad "$RF_FETCH_ERROR" + fi + continue + fi + [[ -n $RF_FETCH_NAME ]] && rf_ws_set "$id" Name "$RF_FETCH_NAME" + rf_ws_set "$id" Refreshed "$(date +%s)" + rf_ws_install "$id" "$stage" || { failed=1; continue; } + [[ -z $quiet ]] && rf_ok "$(rf_msg "%s: %d apps" "$WS_NAME" "$RF_FETCH_COUNT")" + done + return "$failed" +} + +rf_do_remove() { + local id="${1:-}" + if [[ -z $id || ! -e $(rf_ws_file "$id") ]]; then + rf_bad "$(rf_msg "Which workspace? See \`%s list\` for the names." "$RF_NAME")" + return 1 + fi + rf_ws_load "$id" + rf_ws_purge "$id" + rf_ws_ids + (( ${#RF_WS_IDS[@]} == 0 )) && { rf_timer_set no || true; } + rf_ok "$(rf_msg "%s and its apps are removed." "$WS_NAME")" +} + +rf_do_list() { + local id i + rf_ws_ids + if (( ${#RF_WS_IDS[@]} == 0 )); then + rf_note "$(rf_msg "No workspace yet. Add one with \`%s add\`." "$RF_NAME")" + return 0 + fi + for id in "${RF_WS_IDS[@]}"; do + rf_ws_load "$id" + rf_ws_apps_load "$id" + printf '%s%s%s %s(%s, %s)%s\n' "$RF_C_BOLD" "$WS_NAME" "$RF_C_RESET" "$RF_C_DIM" "$id" "$WS_ACCOUNT" "$RF_C_RESET" + for i in "${!RF_APP_SLUGS[@]}"; do + printf ' %-36s %s\n' "$id/${RF_APP_SLUGS[i]}" "${RF_APP_TITLES[i]}" + done + done +} + +# rf_do_run <app or workspace/app> [file] +rf_do_run() { + local spec="${1:-}" file="${2:-}" id slug found=() i + if [[ -z $spec ]]; then + rf_bad "$(rf_msg "Which app? See \`%s list\`." "$RF_NAME")" + return 1 + fi + if [[ $spec == */* ]]; then + rf_launch "${spec%%/*}" "${spec#*/}" "$file" + return + fi + rf_ws_ids + for id in "${RF_WS_IDS[@]}"; do + rf_ws_apps_load "$id" + for i in "${!RF_APP_SLUGS[@]}"; do + [[ ${RF_APP_SLUGS[i]} == "$spec" ]] && found+=("$id") + done + done + case "${#found[@]}" in + 0) rf_bad "$(rf_msg "There is no app %s. See \`%s list\`." "$spec" "$RF_NAME")"; return 1 ;; + 1) slug="$spec"; rf_launch "${found[0]}" "$slug" "$file" ;; + *) rf_bad "$(rf_msg "Several workspaces have %s. Write it as WORKSPACE/APP, see \`%s list\`." "$spec" "$RF_NAME")"; return 1 ;; + esac +} + +rf_do_help() { + cat <<- EOF + $RF_PRETTY $RF_VERSION + + $(rf_msg "Usage: rdfeed [command]") + + $(rf_msg "Commands:") + add [ADDRESS] $(rf_msg "Add the workspace of your company") + refresh [NAME] $(rf_msg "Load the apps again") + list $(rf_msg "List the workspaces and their apps") + run APP [FILE] $(rf_msg "Start an app, or open a file with it") + remove NAME $(rf_msg "Remove a workspace and its apps") + -h, --help $(rf_msg "Show this help") + -V, --version $(rf_msg "Show the version") + + $(rf_msg "Without a command an interactive menu is shown.") + EOF +} + +# --------------------------------------------------------------------------- +# Dispatch +# --------------------------------------------------------------------------- + +main() { + local cmd="${1:-}" + [[ $# -gt 0 ]] && shift + + case "$cmd" in + add|subscribe) rf_do_add "$@" ;; + refresh|sync) rf_do_refresh "$@" ;; + list|ls) rf_do_list ;; + run|start) rf_do_run "$@" ;; + remove|delete) rf_do_remove "$@" ;; + + -h|--help|help) rf_do_help ;; + -V|--version) printf '%s %s\n' "$RF_NAME" "$RF_VERSION" ;; + + '') rf_ui_menu ;; + *) + rf_bad "$(rf_msg "Unknown command: %s" "$cmd")" + printf '\n' + rf_do_help + exit 1 + ;; + esac +} + +main "$@" diff --git a/tests/fake_rdweb.py b/tests/fake_rdweb.py new file mode 100755 index 0000000..560555a --- /dev/null +++ b/tests/fake_rdweb.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python3 +# +# A fake RD Web Access server for the tests: the feed behind the NTLM login, +# the forms ticket it hands out, .rdp files and icons. It checks the NTLMv2 +# answer for real, against one user and password. +# +# python3 tests/fake_rdweb.py [port] serve until Ctrl+C +# +# Copyright (C) 2026 Felitendo +# SPDX-License-Identifier: GPL-3.0-or-later + +import base64 +import hmac +import http.server +import os +import socketserver +import struct +import sys +import threading +import urllib.parse + +import importlib.machinery # noqa: E402 +import importlib.util # noqa: E402 + +# rdfeed-fetch has no .py ending, so it is loaded by path. +_loader = importlib.machinery.SourceFileLoader( + "rdfeed_fetch", os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "src", "fetch", "rdfeed-fetch")) +_spec = importlib.util.spec_from_loader("rdfeed_fetch", _loader) +fetch = importlib.util.module_from_spec(_spec) +_loader.exec_module(fetch) + +DOMAIN = "CONTOSO" +USER = "jdoe" +PASSWORD = "Correct horse 1" +TOKEN = "5EC2E7F00DBA11ADE0F7EED5ACC0FFEE" + +APPS = [ + ("winword-RemoteApps-CmsRdsh", "Word", "RemoteApp"), + ("excel-RemoteApps-CmsRdsh", "Excel", "RemoteApp"), + ("powerpnt-RemoteApps-CmsRdsh", "PowerPoint", "RemoteApp"), + ("desktop-RemoteApps-CmsRdsh", "Contoso Desktop", "Desktop"), +] + + +def extensions(alias): + """Excel comes with its file types, as an administrator would enter them.""" + if not alias.startswith("excel"): + return "<FileExtensions />" + return ('<FileExtensions><FileExtension Name=".xlsx" PrimaryHandler="True" />' + '<FileExtension Name=".CSV" PrimaryHandler="False" /></FileExtensions>') + + +def feed_xml(): + resources = "" + for alias, title, kind in APPS: + resources += f""" + <Resource ID="{alias}" Alias="{alias}" Title="{title}" LastUpdated="2026-10-01T08:00:00Z" Type="{kind}" ShowByDefault="True"> + <Icons> + <IconRaw FileType="Ico" FileURL="/RDWeb/Pages/rdp/{alias}.ico" /> + <Icon32 Dimensions="32x32" FileType="Png" FileURL="/RDWeb/Pages/rdp/{alias}.png" /> + </Icons> + {extensions(alias)} + <Folders><Folder Name="/" /></Folders> + <HostingTerminalServers> + <HostingTerminalServer> + <ResourceFile FileExtension=".rdp" URL="/RDWeb/Pages/rdp/cpub-{alias}.rdp" /> + <TerminalServerRef Ref="RDSH01.contoso.test" /> + </HostingTerminalServer> + </HostingTerminalServers> + </Resource>""" + return f"""<?xml version="1.0" encoding="utf-8"?> +<ResourceCollection PubDate="2026-10-01T08:00:00Z" SchemaVersion="2.1" xmlns="http://schemas.microsoft.com/ts/2007/05/tswf"> + <Publisher LastUpdated="2026-10-01T08:00:00Z" Name="Contoso Apps" ID="RDSH01.contoso.test" Description=""> + <Resources>{resources} + </Resources> + </Publisher> +</ResourceCollection> +""".encode() + + +def rdp_file(alias, title, kind): + lines = [ + "full address:s:RDBROKER.contoso.test", + "gatewayhostname:s:gateway.contoso.test", + "gatewayusagemethod:i:1", + "promptcredentialonce:i:0", + "prompt for credentials on client:i:1", + "drivestoredirect:s:*", + "devicestoredirect:s:*", + "camerastoredirect:s:*", + "loadbalanceinfo:s:tsv://MS Terminal Services Plugin.1.RemoteApps", + ] + if kind == "RemoteApp": + lines += ["remoteapplicationmode:i:1", f"remoteapplicationprogram:s:||{alias.split('-')[0]}", + f"remoteapplicationname:s:{title}"] + return ("\r\n".join(lines) + "\r\n").encode("utf-16") + + +def ico_32bpp(size=48, rgba=(30, 110, 220, 255)): + """An .ico with one 32-bit bitmap frame, the way Windows writes them.""" + r, g, b, a = rgba + header = struct.pack("<IiiHHIIiiII", 40, size, size * 2, 1, 32, 0, 0, 0, 0, 0, 0) + pixels = bytes((b, g, r, a)) * (size * size) + mask = b"\x00" * (((size + 31) // 32) * 4 * size) + dib = header + pixels + mask + return (struct.pack("<HHH", 0, 1, 1) + + struct.pack("<BBBBHHII", size, size, 0, 0, 1, 32, len(dib), 22) + dib) + + +def ntlm_challenge(): + def av(kind, value): + data = value.encode("utf-16-le") + return struct.pack("<HH", kind, len(data)) + data + info = (av(2, DOMAIN) + av(1, "RDWEB01") + av(4, "contoso.test") + av(3, "rdweb01.contoso.test") + + struct.pack("<HH", 7, 8) + struct.pack("<Q", 133000000000000000) + struct.pack("<HH", 0, 0)) + server_challenge = os.urandom(8) + target = DOMAIN.encode("utf-16-le") + head = 56 + message = (b"NTLMSSP\x00" + struct.pack("<I", 2) + struct.pack("<HHI", len(target), len(target), head) + + struct.pack("<I", 0xE2898215) + server_challenge + b"\x00" * 8 + + struct.pack("<HHI", len(info), len(info), head + len(target)) + fetch.NTLM_VERSION + + target + info) + return message, server_challenge + + +def ntlm_check(message, server_challenge): + """True if the AUTHENTICATE message proves the password.""" + def field(i): + length, _, offset = struct.unpack("<HHI", message[12 + 8 * i:20 + 8 * i]) + return message[offset:offset + length] + nt, domain, user = field(1), field(2).decode("utf-16-le"), field(3).decode("utf-16-le") + if user.lower() != USER or domain.upper() != DOMAIN or len(nt) < 32: + return False + key = fetch.ntowfv2(user, domain, PASSWORD) + proof = hmac.new(key, server_challenge + nt[16:], "md5").digest() + return hmac.compare_digest(proof, nt[:16]) + + +class Handler(http.server.BaseHTTPRequestHandler): + protocol_version = "HTTP/1.1" + + def log_message(self, *args): + pass + + def reply(self, status, body=b"", headers=None): + self.send_response(status) + for k, v in (headers or {}).items(): + if isinstance(v, list): + for one in v: + self.send_header(k, one) + else: + self.send_header(k, v) + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + + def signed_in(self): + cookies = self.headers.get("Cookie", "") + return f"TSWAFeedAuthCookie={TOKEN}" in cookies + + def do_GET(self): + path = urllib.parse.urlsplit(self.path).path + lower = path.lower() + if lower == "/rdweb/feed/webfeed.aspx": + if self.signed_in(): + return self.reply(200, feed_xml(), {"Content-Type": "application/x-msts-radc+xml; charset=utf-8"}) + return self.reply(302, b"", {"Location": "/RDWeb/FeedLogin/WebFeedLogin.aspx"}) + if lower == "/rdweb/feedlogin/webfeedlogin.aspx": + auth = self.headers.get("Authorization", "") + if auth.startswith("NTLM "): + message = base64.b64decode(auth[5:]) + kind = struct.unpack("<I", message[8:12])[0] + if kind == 1: + challenge, self.server_challenge = ntlm_challenge() + return self.reply(401, b"", {"WWW-Authenticate": "NTLM " + base64.b64encode(challenge).decode()}) + if kind == 3 and ntlm_check(message, getattr(self, "server_challenge", b"")): + return self.reply(200, TOKEN.encode(), {"Content-Type": "application/x-msts-webfeed-login; charset=utf-8"}) + return self.reply(401, b"denied", {"WWW-Authenticate": ["NTLM", "Negotiate"], "Content-Type": "text/html"}) + if lower.startswith("/rdweb/pages/rdp/"): + if not self.signed_in(): + return self.reply(302, b"", {"Location": "/RDWeb/Pages/en-US/login.aspx?ReturnUrl=" + path}) + name = path.rsplit("/", 1)[1] + for alias, title, kind in APPS: + if name == f"cpub-{alias}.rdp": + return self.reply(200, rdp_file(alias, title, kind), {"Content-Type": "application/x-rdp"}) + if name == f"{alias}.ico": + return self.reply(200, ico_32bpp(), {"Content-Type": "image/x-icon"}) + if name == f"{alias}.png": + return self.reply(404) + return self.reply(404, b"<html>not here</html>", {"Content-Type": "text/html"}) + + +class Server(socketserver.ThreadingMixIn, http.server.HTTPServer): + daemon_threads = True + allow_reuse_address = True + + +def serve(port=0): + """Starts the server in a thread. Returns it; its URL base is server.base.""" + server = Server(("127.0.0.1", port), Handler) + server.base = f"http://127.0.0.1:{server.server_address[1]}" + threading.Thread(target=server.serve_forever, daemon=True).start() + return server + + +if __name__ == "__main__": + srv = serve(int(sys.argv[1]) if len(sys.argv) > 1 else 0) + print(srv.base + "/RDWeb/Feed/webfeed.aspx", flush=True) + try: + threading.Event().wait() + except KeyboardInterrupt: + pass diff --git a/tests/test_cli.sh b/tests/test_cli.sh new file mode 100644 index 0000000..bd645eb --- /dev/null +++ b/tests/test_cli.sh @@ -0,0 +1,163 @@ +#!/usr/bin/env bash +# +# The rdfeed command from start to end, against tests/fake_rdweb.py: add a +# workspace, list, refresh, start an app, remove it again. +# +# Everything happens in a temporary home. secret-tool, systemctl, notify-send, +# kbuildsycoca6 and xfreerdp3 are fakes on PATH, so the real keyring, timers, +# notifications and app menu stay untouched. +# +# Copyright (C) 2026 Felitendo +# SPDX-License-Identifier: GPL-3.0-or-later + +set -uo pipefail +root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" +tmp="$(mktemp -d)" +server='' +trap '[[ -n $server ]] && kill "$server" 2>/dev/null; rm -rf -- "$tmp"' EXIT +failed=0 + +export HOME="$tmp/home" XDG_CONFIG_HOME="$tmp/config" XDG_DATA_HOME="$tmp/data" XDG_CACHE_HOME="$tmp/cache" +export RF_LIBDIR="$root/src/lib" RF_FETCH="$root/src/fetch/rdfeed-fetch" RF_LOCALEDIR="$tmp/locale" +RF_HOOK="$(readlink -f "${RDFEED_HOOK:-$root/build/rdfeed-hook.so}")" +export RF_HOOK +export LANG=C LC_ALL=C NO_COLOR=1 XDG_CURRENT_DESKTOP=test XDG_RUNTIME_DIR="$tmp/run" +mkdir -p "$HOME" "$tmp/bin" + +# The fakes. Each writes what it was asked into $tmp, as a log or a file. +cat > "$tmp/bin/secret-tool" <<- 'EOF' +#!/usr/bin/env bash +store="${0%/bin/*}/secrets"; mkdir -p "$store" +cmd="$1"; shift; ws='' +while (( $# )); do [[ $1 == workspace ]] && ws="$2"; shift; done +case "$cmd" in + store) cat > "$store/$ws" ;; + lookup) [[ -f $store/$ws ]] && cat "$store/$ws" ;; + clear) rm -f "$store/$ws" ;; +esac +EOF +cat > "$tmp/bin/xfreerdp3" <<- 'EOF' +#!/usr/bin/env bash +out="${0%/bin/*}" +cat > "$out/freerdp-args" +cp "$(head -n1 "$out/freerdp-args")" "$out/freerdp-rdp" +env > "$out/freerdp-env" +echo "[INFO] connected" +echo "[rdfeed] gateway-delay 12" >&2 +exit "${FAKE_FREERDP_EXIT:-0}" +EOF +for fake in systemctl notify-send kbuildsycoca6 update-desktop-database; do + # shellcheck disable=SC2016 # the script expands when it runs, not here + printf '#!/usr/bin/env bash\necho "%s $*" >> "${0%%/bin/*}/calls"\n[[ $1 == --user && $2 == cat ]] && exit 1\nexit 0\n' "$fake" > "$tmp/bin/$fake" +done +chmod +x "$tmp/bin"/* +export PATH="$tmp/bin:$PATH" + +rdfeed() { bash "$root/src/rdfeed" "$@"; } + +check() { + if eval "$2"; then + printf 'ok %s\n' "$1" + else + printf 'FAIL %s\n' "$1" + failed=1 + fi +} + +# The URL helpers, on their own. +url_helpers() ( + source "$RF_LIBDIR/common.sh"; source "$RF_LIBDIR/config.sh"; source "$RF_LIBDIR/workspace.sh" + for pair in "rdweb.contoso.com|https://rdweb.contoso.com/RDWeb/Feed/webfeed.aspx" \ + "office.contoso.com/rdweb/feed/webfeed|https://office.contoso.com/rdweb/feed/webfeed.aspx" \ + "https://x.test/RDWeb/Pages/en-US/Default.aspx|https://x.test/RDWeb/Feed/webfeed.aspx" \ + "HTTPS://x.test:8443/RDWeb/|https://x.test:8443/RDWeb/Feed/webfeed.aspx" \ + "https://x.test/custom/feed.aspx|https://x.test/custom/feed.aspx"; do + rf_feed_url "${pair%%|*}" + [[ $RF_URL == "${pair#*|}" ]] || { echo " $RF_URL != ${pair#*|}"; exit 1; } + done + rf_split_user 'CONTOSO\jdoe'; [[ $RF_USER == jdoe && $RF_DOMAIN == CONTOSO ]] || exit 1 + rf_split_user 'jdoe@contoso.com'; [[ $RF_USER == jdoe@contoso.com && -z $RF_DOMAIN ]] || exit 1 + rf_split_user 'jdoe'; [[ $RF_USER == jdoe && $RF_DOMAIN == - ]] || exit 1 + [[ $(rf_ws_id_for "https://Office.Contoso.COM/RDWeb/Feed/webfeed.aspx") == office-contoso-com ]] || exit 1 +) +if url_helpers; then check "addresses, user names and ids" true; else check "addresses, user names and ids" false; fi + +python3 "$root/tests/fake_rdweb.py" > "$tmp/url" & +server=$! +for _ in $(seq 50); do [[ -s $tmp/url ]] && break; sleep 0.1; done +url="$(< "$tmp/url")" +id=127-0-0-1 +password="$(python3 -c 'import sys; sys.path.insert(0, sys.argv[1]); import fake_rdweb; print(fake_rdweb.PASSWORD)' "$root/tests")" + +out="$(printf 'CONTOSO\\jdoe\nwrong\n' | rdfeed add "$url" 2>&1)" +rc=$? +check "a wrong password is refused" "[[ \$rc -ne 0 ]] && grep -q 'did not accept' <<< \"\$out\"" +check "and leaves nothing behind" "[[ ! -e $XDG_CONFIG_HOME/rdfeed/workspaces/$id ]]" + +out="$(printf 'jdoe\n%s\n' "$password" | rdfeed add "${url%/RDWeb/*}" 2>&1)" +check "add signs in and loads the apps" "grep -q '4 apps from Contoso Apps are now in your app menu' <<< \"\$out\"" +ws="$XDG_CONFIG_HOME/rdfeed/workspaces/$id" +check "the workspace remembers the server's domain" "grep -qx 'Domain=CONTOSO' '$ws' && grep -qx 'User=jdoe' '$ws'" +check "the password is in the keyring, as typed" "[[ \$(< '$tmp/secrets/$id') == \"\$password\" ]]" +entry="$XDG_DATA_HOME/applications/rdfeed-$id--powerpnt.desktop" +check "each app has a menu entry" "[[ \$(ls '$XDG_DATA_HOME/applications' | wc -l) -eq 4 ]]" +check "which starts it" "grep -q 'Exec=$root/src/rdfeed run $id/powerpnt' '$entry'" +check "with its own icon" "grep -qx 'Icon=$XDG_DATA_HOME/rdfeed/$id/icons/powerpnt.png' '$entry'" +word="$XDG_DATA_HOME/applications/rdfeed-$id--winword.desktop" +if [[ -r /usr/share/mime/globs2 ]]; then + check "Word is offered for .docx files" "grep -q '^MimeType=.*application/vnd.openxmlformats-officedocument.wordprocessingml.document;' '$word' && grep -q 'run $id/winword %f\$' '$word'" + check "Excel for the types of the feed" "grep -qx 'MimeType=.*text/csv;.*' '$XDG_DATA_HOME/applications/rdfeed-$id--excel.desktop' || grep -q 'MimeType=.*csv' '$XDG_DATA_HOME/applications/rdfeed-$id--excel.desktop'" +fi +check "the desktop for none" "! grep -q MimeType '$XDG_DATA_HOME/applications/rdfeed-$id--desktop.desktop'" +if command -v desktop-file-validate > /dev/null; then + check "the entries are valid" "desktop-file-validate --no-hints '$XDG_DATA_HOME'/applications/*.desktop" +fi +check "the workspace has a folder" "grep -q '<Category>X-rdfeed-$id</Category>' '$XDG_CONFIG_HOME/menus/applications-merged/rdfeed-$id.menu' && grep -qx 'Name=Contoso Apps' '$XDG_DATA_HOME/desktop-directories/rdfeed-$id.directory'" + +out="$(rdfeed list 2>&1)" +check "list shows the apps" "grep -q '$id/powerpnt *PowerPoint' <<< \"\$out\"" + +out="$(rdfeed refresh < /dev/null 2>&1)" +check "refresh takes the password from the keyring" "grep -q 'Contoso Apps: 4 apps' <<< \"\$out\"" + +rdfeed run powerpnt > /dev/null 2>&1 +check "run hands FreeRDP the .rdp file first" "[[ \$(head -n1 '$tmp/freerdp-args') == '$XDG_DATA_HOME/rdfeed/$id/rdp/powerpnt.rdp' ]]" +check "with the user, domain and password" "grep -qx '/u:jdoe' '$tmp/freerdp-args' && grep -qx '/d:CONTOSO' '$tmp/freerdp-args' && grep -qxF \"/p:\$password\" '$tmp/freerdp-args'" +check "and the hook, with GFX left on" "grep -qx 'LD_PRELOAD=$RF_HOOK' '$tmp/freerdp-env' && ! grep -q 'RDFEED_NO_GFX' '$tmp/freerdp-env'" +check "the log has what FreeRDP said" "grep -q 'gateway-delay 12' '$XDG_CACHE_HOME/rdfeed/$id--powerpnt.log'" +check "a notification says a second prompt comes" "grep -q 'notify-send .*A second sign-in is coming' '$tmp/calls'" + +mkdir -p "$HOME/Downloads" "$HOME/Documents/Reports" +: > "$HOME/Downloads/Report, final.docx" +rdfeed run "$id/winword" "$HOME/Downloads/Report, final.docx" > /dev/null 2>&1 +check "a file opens through its shared folder" "grep -qxF 'remoteapplicationcmdline:s:\"\\\\tsclient\\Downloads\\Report, final.docx\"'\$'\\r' '$tmp/freerdp-rdp' && grep -qx '/drive:Downloads,$HOME/Downloads' '$tmp/freerdp-args'" +check "and its copy of the .rdp is gone after" "[[ -z \$(ls -A '$XDG_RUNTIME_DIR/rdfeed' 2>/dev/null) ]]" +check "and a notification names it" "grep -q 'notify-send .*Opening Report, final.docx in Word' '$tmp/calls'" + +rdfeed run "$id/winword" "$HOME/nothing.docx" > /dev/null 2>&1 +rc=$? +check "a missing file is refused" "[[ \$rc -ne 0 ]]" + +# shellcheck disable=SC2016 # user-dirs.dirs wants $HOME as it is +printf 'XDG_DOCUMENTS_DIR="$HOME/Documents"\n' > "$XDG_CONFIG_HOME/user-dirs.dirs" +printf 'Share=documents\nGraphics=classic\nSound=no\nOpenWith=no\n' > "$XDG_CONFIG_HOME/rdfeed/config" +: > "$HOME/Documents/Reports/q3.xlsx" +rdfeed run "$id/excel" "$HOME/Documents/Reports/q3.xlsx" > /dev/null 2>&1 +check "a file in the shared folder needs no drive of its own" "grep -qF 'tsclient\\Documents\\Reports\\q3.xlsx' '$tmp/freerdp-rdp' && [[ \$(grep -c '^/drive:' '$tmp/freerdp-args') -eq 1 ]]" +check "the settings reach FreeRDP" "grep -qx 'RDFEED_NO_GFX=1' '$tmp/freerdp-env' && ! grep -qx '/sound' '$tmp/freerdp-args'" + +rdfeed refresh < /dev/null > /dev/null 2>&1 +check "with Open with off the entries name no file types" "! grep -q MimeType '$word' && grep -q 'run $id/winword\$' '$word'" + +FAKE_FREERDP_EXIT=131 rdfeed run "$id/powerpnt" > /dev/null 2>&1 +rc=$? +check "a failed start says so" "[[ \$rc -ne 0 ]] && grep -q 'notify-send .*PowerPoint could not start' '$tmp/calls'" + +rdfeed run nothing > /dev/null 2>&1 +rc=$? +check "an unknown app is refused" "[[ \$rc -ne 0 ]]" + +rdfeed remove "$id" > /dev/null 2>&1 +check "remove takes everything away" "[[ ! -e '$ws' && ! -e '$tmp/secrets/$id' && ! -d '$XDG_DATA_HOME/rdfeed/$id' ]] && ! ls '$XDG_DATA_HOME'/applications/rdfeed-* > /dev/null 2>&1" + +exit "$failed" diff --git a/tests/test_fetch.py b/tests/test_fetch.py new file mode 100644 index 0000000..e56a248 --- /dev/null +++ b/tests/test_fetch.py @@ -0,0 +1,220 @@ +#!/usr/bin/env python3 +# +# rdfeed-fetch: NTLM against the test vectors of MS-NLMP, the feed, the .rdp +# files, the icons, and whole subscriptions against tests/fake_rdweb.py. +# +# Copyright (C) 2026 Felitendo +# SPDX-License-Identifier: GPL-3.0-or-later + +import hmac +import os +import struct +import subprocess +import sys +import tempfile +import unittest +import zlib + +HERE = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, HERE) +import fake_rdweb # noqa: E402 + +fetch = fake_rdweb.fetch +FETCH = os.path.join(HERE, "..", "src", "fetch", "rdfeed-fetch") + + +def png_info(png): + """Width, height and the RGBA bytes of a PNG written by png_encode.""" + assert png[:8] == fetch.PNG_SIGNATURE + width, height = struct.unpack(">II", png[16:24]) + pos, idat = 8, b"" + while pos < len(png): + length = struct.unpack(">I", png[pos:pos + 4])[0] + kind = png[pos + 4:pos + 8] + if kind == b"IDAT": + idat += png[pos + 8:pos + 8 + length] + pos += 12 + length + raw = zlib.decompress(idat) + return width, height, raw + + +class Ntlm(unittest.TestCase): + def test_md4(self): + self.assertEqual(fetch.md4(b"").hex(), "31d6cfe0d16ae931b73c59d7e0c089c0") + self.assertEqual(fetch.md4(b"abc").hex(), "a448017aaf21d8525fc10ae87aa6729d") + self.assertEqual(fetch.md4(b"1234567890" * 8).hex(), "e33b4ddc9c38f2199c3e7b164fcc0536") + + # MS-NLMP 4.2.4: User, Domain, Password, server challenge 0123456789abcdef, + # client challenge aa..aa, time 0, target info Domain and Server. + def test_ntlmv2_vectors(self): + key = fetch.ntowfv2("User", "Domain", "Password") + self.assertEqual(key.hex(), "0c868a403bfd7a93a3001ef22ef02e3f") + server, client = bytes.fromhex("0123456789abcdef"), b"\xaa" * 8 + lm = hmac.new(key, server + client, "md5").digest() + client + self.assertEqual(lm.hex(), "86c35097ac9cec102554764a57cccc19aaaaaaaaaaaaaaaa") + info = (struct.pack("<HH", 2, 12) + "Domain".encode("utf-16-le") + + struct.pack("<HH", 1, 12) + "Server".encode("utf-16-le") + b"\x00" * 4) + nt = fetch.ntlmv2_response(key, server, client, b"\x00" * 8, info) + self.assertEqual(nt[:16].hex(), "68cd0ab851e51c96aabc927bebef6a1c") + + def test_domain_from_server(self): + challenge, server_challenge = fake_rdweb.ntlm_challenge() + message, domain = fetch.ntlm_authenticate(challenge, "jdoe", None, fake_rdweb.PASSWORD) + self.assertEqual(domain, "CONTOSO") + self.assertTrue(fake_rdweb.ntlm_check(message, server_challenge)) + message, _ = fetch.ntlm_authenticate(challenge, "jdoe", None, "wrong") + self.assertFalse(fake_rdweb.ntlm_check(message, server_challenge)) + + +class Feed(unittest.TestCase): + def test_parse(self): + name, apps = fetch.parse_feed(fake_rdweb.feed_xml()) + self.assertEqual(name, "Contoso Apps") + self.assertEqual([a["slug"] for a in apps], ["winword", "excel", "powerpnt", "desktop"]) + self.assertEqual([a["type"] for a in apps], ["RemoteApp"] * 3 + ["Desktop"]) + self.assertEqual(apps[0]["rdp"], "/RDWeb/Pages/rdp/cpub-winword-RemoteApps-CmsRdsh.rdp") + self.assertEqual(apps[1]["types"], ["xlsx", "csv"]) + self.assertEqual(apps[0]["types"], []) + + def test_known_types(self): + self.assertEqual(fetch.known_types("||winword", "Word", "winword-x")[:2], ["docx", "docm"]) + self.assertEqual(fetch.known_types("||powerpoint", "PowerPoint", ""), "pptx pptm ppt potx potm ppsx pps odp".split()) + self.assertEqual(fetch.known_types("||trueview", "DWG TrueView", "trueview-x"), ["dwg", "dxf"]) + self.assertEqual(fetch.known_types("||pdf24", "PDF24", ""), ["pdf"]) + self.assertEqual(fetch.known_types("||wordpad", "WordPad", "wordpad-x"), []) + self.assertEqual(fetch.known_types("||pwchange", "Passwort รคndern", "pwchange-x"), []) + + def test_not_a_feed(self): + for data in (b"<html><body>login</body></html>", b"", b"<RDWAPage/>"): + with self.assertRaises(fetch.FeedError): + fetch.parse_feed(data) + + def test_slugs(self): + taken = set() + self.assertEqual(fetch.make_slug("Passwort รคndern", taken), "passwort-andern") + self.assertEqual(fetch.make_slug("Passwort รคndern", taken), "passwort-andern-2") + self.assertEqual(fetch.make_slug("่ฎก็ฎ—ๅ™จ", taken), "app") + self.assertEqual(fetch.make_slug("!!!", taken), "app-2") + self.assertEqual(fetch.common_suffix(["a-X-Y", "bb-X-Y"]), "-X-Y") + self.assertEqual(fetch.common_suffix(["word", "sword"]), "") + self.assertEqual(fetch.common_suffix(["only-one"]), "") + + +class Rdp(unittest.TestCase): + def test_sanitize(self): + text = fetch.decode_rdp(fake_rdweb.rdp_file("winword-x", "Word", "RemoteApp")) + out = fetch.sanitize_rdp(text) + self.assertIn("promptcredentialonce:i:1\r\n", out) + self.assertIn("prompt for credentials on client:i:0\r\n", out) + for gone in ("drivestoredirect", "devicestoredirect", "camerastoredirect"): + self.assertNotIn(gone, out) + self.assertIn("remoteapplicationprogram:s:||winword\r\n", out) + + def test_encodings(self): + line = "full address:s:host\r\n" + for data in (line.encode("utf-16"), line.encode("utf-16-le"), line.encode("utf-8-sig"), line.encode()): + self.assertEqual(fetch.decode_rdp(data), line) + + def test_append(self): + self.assertEqual(fetch.rdp_set("a:s:b\r\n", "promptcredentialonce", "i", "1"), + "a:s:b\r\npromptcredentialonce:i:1\r\n") + + +class Icons(unittest.TestCase): + def test_32bpp(self): + width, height, raw = png_info(fetch.ico_to_png(fake_rdweb.ico_32bpp(48, (30, 110, 220, 128)))) + self.assertEqual((width, height), (48, 48)) + self.assertEqual(raw[1:5], bytes((30, 110, 220, 128))) + + def test_24bpp_with_mask(self): + size = 16 + header = struct.pack("<IiiHHIIiiII", 40, size, size * 2, 1, 24, 0, 0, 0, 0, 0, 0) + stride = ((size * 24 + 31) // 32) * 4 + pixels = (bytes((0, 0, 255)) * size + b"\x00" * (stride - size * 3)) * size + # The top row of the image (the last one in the file) is transparent. + mask = b"\x00\x00\x00\x00" * (size - 1) + b"\xff\xff\x00\x00" + dib = header + pixels + mask + ico = (struct.pack("<HHH", 0, 1, 1) + + struct.pack("<BBBBHHII", size, size, 0, 0, 1, 24, len(dib), 22) + dib) + width, height, raw = png_info(fetch.ico_to_png(ico)) + self.assertEqual((width, height), (16, 16)) + self.assertEqual(raw[1:5], bytes((255, 0, 0, 0))) + row = 1 + width * 4 + self.assertEqual(raw[row + 1:row + 5], bytes((255, 0, 0, 255))) + + def test_png_frame_and_biggest(self): + small = fake_rdweb.ico_32bpp(16) + png = fetch.png_encode(2, 1, [bytes((1, 2, 3, 4, 5, 6, 7, 8))]) + # Two entries: a 16 px bitmap and a 256 px PNG (width 0 means 256). + dib = small[22:] + ico = (struct.pack("<HHH", 0, 1, 2) + + struct.pack("<BBBBHHII", 16, 16, 0, 0, 1, 32, len(dib), 38) + + struct.pack("<BBBBHHII", 0, 0, 0, 0, 1, 32, len(png), 38 + len(dib)) + dib + png) + self.assertEqual(fetch.ico_to_png(ico), png) + + def test_garbage(self): + self.assertIsNone(fetch.ico_to_png(b"not an icon")) + self.assertIsNone(fetch.ico_to_png(b"\x00\x00\x01\x00\x01\x00" + b"\x00" * 16)) + + +class Subscribe(unittest.TestCase): + """rdfeed-fetch against the fake server, as rdfeed runs it.""" + + @classmethod + def setUpClass(cls): + cls.server = fake_rdweb.serve() + cls.url = cls.server.base + "/RDWeb/Feed/webfeed.aspx" + + @classmethod + def tearDownClass(cls): + cls.server.shutdown() + + def run_fetch(self, password, url=None, user="jdoe", domain=None): + out = tempfile.mkdtemp() + args = [sys.executable, FETCH, "--url", url or self.url, "--user", user, "--out", out, "--timeout", "5"] + if domain is not None: + args += ["--domain", domain] + proc = subprocess.run(args, input=password + "\n", capture_output=True, text=True, timeout=60) + return proc, out + + def test_ok(self): + proc, out = self.run_fetch(fake_rdweb.PASSWORD) + self.assertEqual(proc.returncode, 0, proc.stderr) + lines = [line.split("\t") for line in proc.stdout.splitlines()] + self.assertIn(["workspace", "Contoso Apps"], lines) + self.assertIn(["domain", "CONTOSO"], lines) + self.assertIn(["app", "powerpnt", "RemoteApp", "PowerPoint"], lines) + self.assertIn(["app", "desktop", "Desktop", "Contoso Desktop"], lines) + with open(os.path.join(out, "apps.tsv")) as f: + rows = [line.split("\t") for line in f.read().splitlines()] + self.assertEqual(len(rows), 4) + types = {row[0]: row[3] for row in rows} + self.assertTrue(types["winword"].startswith("docx docm doc ")) + self.assertEqual(types["excel"], "xlsx csv") + self.assertEqual(types["desktop"], "") + with open(os.path.join(out, "rdp", "winword.rdp")) as f: + rdp = f.read() + self.assertIn("promptcredentialonce:i:1", rdp) + self.assertNotIn("drivestoredirect", rdp) + with open(os.path.join(out, "icons", "excel.png"), "rb") as f: + self.assertEqual(png_info(f.read())[:2], (48, 48)) + + def test_domain_given(self): + proc, _ = self.run_fetch(fake_rdweb.PASSWORD, domain="contoso") + self.assertEqual(proc.returncode, 0, proc.stderr) + + def test_wrong_password(self): + proc, _ = self.run_fetch("nope") + self.assertEqual(proc.returncode, fetch.EXIT_AUTH) + + def test_no_feed(self): + proc, _ = self.run_fetch(fake_rdweb.PASSWORD, url=self.server.base + "/somewhere/else") + self.assertEqual(proc.returncode, fetch.EXIT_NOFEED) + + def test_unreachable(self): + proc, _ = self.run_fetch(fake_rdweb.PASSWORD, url="http://127.0.0.1:9/RDWeb/Feed/webfeed.aspx") + self.assertEqual(proc.returncode, fetch.EXIT_NETWORK) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_hook.sh b/tests/test_hook.sh new file mode 100644 index 0000000..a70ea52 --- /dev/null +++ b/tests/test_hook.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash +# +# rdfeed-hook.so: the wait before a new gateway tunnel, and GFX kept off. +# Connections go to port 443 of this computer, nothing has to answer there. +# +# Copyright (C) 2026 Felitendo +# SPDX-License-Identifier: GPL-3.0-or-later + +set -uo pipefail +cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." || exit 1 + +hook="$(readlink -f "${RDFEED_HOOK:-build/rdfeed-hook.so}")" +[[ -f $hook ]] || { echo "no hook at $hook, run make first" >&2; exit 1; } +failed=0 + +check() { + if [[ $2 == "$3" ]]; then + printf 'ok %s\n' "$1" + else + printf 'FAIL %s: got "%s", want "%s"\n' "$1" "$2" "$3" + failed=1 + fi +} + +# timings <wait mode> <pause> +# One connection, the pause, then two right after each other. Prints how long +# each one waited, in whole seconds, and whether the hook said so. +timings() { + LD_PRELOAD="$hook" RDFEED_GW_DELAY=1 RDFEED_GW_WAIT="$1" python3 - "$2" 2>&1 <<- 'EOF' + import socket, sys, time + def attempt(): + start = time.monotonic() + s = socket.socket() + try: + s.connect(("127.0.0.1", 443)) + except OSError: + pass + s.close() + return round(time.monotonic() - start) + first = attempt() + time.sleep(float(sys.argv[1])) + second = attempt() + third = attempt() + print(first, second, third) + EOF +} + +check "auto waits after a long first sign-in" "$(timings auto 3 | tr '\n' ' ')" "[rdfeed] gateway-delay 1 0 1 0 " +check "auto does not wait after a quick one" "$(timings auto 2.1)" "0 0 0" +check "always waits" "$(timings always 2.1 | tail -n1)" "0 1 0" +check "never waits not" "$(timings never 3)" "0 0 0" +check "the same tunnel does not wait" "$(timings always 0.5)" "0 0 0" + +# GFX: what FreeRDP's own command line parser makes of it, with and without. +gfx() { + env "$@" python3 - <<- 'EOF' + import ctypes + try: + core = ctypes.CDLL("libfreerdp3.so.3", mode=ctypes.RTLD_GLOBAL) + client = ctypes.CDLL("libfreerdp-client3.so.3", mode=ctypes.RTLD_GLOBAL) + except OSError: + print("skip") + raise SystemExit + core.freerdp_settings_new.restype = ctypes.c_void_p + core.freerdp_settings_new.argtypes = [ctypes.c_uint32] + core.freerdp_settings_get_key_for_name.restype = ctypes.c_ssize_t + core.freerdp_settings_get_key_for_name.argtypes = [ctypes.c_char_p] + core.freerdp_settings_get_bool.argtypes = [ctypes.c_void_p, ctypes.c_size_t] + client.freerdp_client_settings_parse_command_line.argtypes = [ + ctypes.c_void_p, ctypes.c_int, ctypes.POINTER(ctypes.c_char_p), ctypes.c_int] + settings = core.freerdp_settings_new(0) + argv = [b"xfreerdp3", b"/v:host.invalid", b"/gfx"] + client.freerdp_client_settings_parse_command_line( + settings, len(argv), (ctypes.c_char_p * len(argv))(*argv), 0) + key = core.freerdp_settings_get_key_for_name(b"FreeRDP_SupportGraphicsPipeline") + print("on" if core.freerdp_settings_get_bool(settings, key) else "off") + EOF +} + +with="$(gfx LD_PRELOAD="$hook" RDFEED_NO_GFX=1 WLOG_LEVEL=OFF)" +if [[ $with == skip ]]; then + echo "skip GFX: FreeRDP 3 is not installed" +else + check "GFX stays off with RDFEED_NO_GFX" "$with" "off" + check "GFX is left alone without it" "$(gfx LD_PRELOAD="$hook" WLOG_LEVEL=OFF)" "on" +fi + +exit "$failed"