pax_global_header00006660000000000000000000000064137647403050014523gustar00rootroot0000000000000052 comment=47cb743637271bfae6ff477b1258160995f9d1a0 giara-0.3/000077500000000000000000000000001376474030500124505ustar00rootroot00000000000000giara-0.3/.gitlab-ci.yml000066400000000000000000000011341376474030500151030ustar00rootroot00000000000000include: 'https://gitlab.gnome.org/GNOME/citemplates/raw/master/flatpak/flatpak_ci_initiative.yml' variables: BUNDLE: "giara-dev.flatpak" test: image: python:3.8 script: - pip install pyflakes - pyflakes giara flatpak: image: 'registry.gitlab.gnome.org/gnome/gnome-runtime-images/gnome:3.38' variables: MANIFEST_PATH: "dist/flatpak/org.gabmus.giara.json" MESON_ARGS: "-Dprofile=development" FLATPAK_MODULE: "giara" RUNTIME_REPO: "https://flathub.org/repo/flathub.flatpakrepo" APP_ID: "org.gabmus.giara" extends: .flatpak giara-0.3/LICENSE000066400000000000000000001045141376474030500134620ustar00rootroot00000000000000 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 . giara-0.3/README.md000066400000000000000000000020611376474030500137260ustar00rootroot00000000000000# Giara *Giara is a reddit app*, built with Python, GTK and Handy. Created with mobile Linux in mind. # Dependencies - Python >= 3.7 - pillow - praw - requests - mistune - beautifulsoup - libhandy >= 1.0.0 # Downloading the latest development snapshot (`x86_64` only) - Head over to [the *Jobs* page of the repository](https://gitlab.gnome.org/World/giara/-/jobs) - find the latest job with name *flatpak* - press the download button on the right - extract the zip you just downloaded (typically called `Flatpak_artifacts.zip`) and open a terminal in the resulting directory - install the snapshot with `flatpak --user install giara-dev.flatpak` # Running from source This application targets Flatpak, therefore that's the recommended way to run the app. To run the app from source using Flatpak: - install and open GNOME Builder - select the *Clone Repository...* button - paste in the url of this repository, then select *Clone Project* - press the *Run* button indicated by a play icon (like this one `▶`) in the headerbar or press `Ctrl+F5` on your keyboard giara-0.3/bin/000077500000000000000000000000001376474030500132205ustar00rootroot00000000000000giara-0.3/bin/giara.in000077500000000000000000000053361376474030500146450ustar00rootroot00000000000000#!@PYTHON@ # @projectname@ # # Copyright (C) 2019 @authorfullname@ <@authoremail@> # # 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 . import os import sys import signal import gettext import locale sys.path.insert(1, '@pythondir@') # compositing causes issues for webkit in some website with nvidia drivers os.environ['WEBKIT_DISABLE_COMPOSITING_MODE'] = '1' VERSION = '@VERSION@' pkgdatadir = '@pkgdatadir@' localedir = '@localedir@' builddir = os.environ.get('MESON_BUILD_ROOT') if builddir: pkgdatadir = os.path.join(builddir, 'data') os.environ['GSETTINGS_SCHEMA_DIR'] = pkgdatadir sys.dont_write_bytecode = True sys.path.insert(1, os.environ['MESON_SOURCE_ROOT']) signal.signal(signal.SIGINT, signal.SIG_DFL) # Why both locale and gettext? # gettext works for the python part # but not for the glade/xml files # they need locale # don't ask me, it's effin weird # I copied this from uberwriter try: locale.bindtextdomain('@projectname@', localedir) locale.textdomain('@projectname@') except AttributeError as e: # Python built without gettext support doesn't have bindtextdomain() # and textdomain() print("Couldn't bind the gettext translation domain. Some translations" " won't work. Error: \n{}".format(e)) gettext.textdomain('@projectname@') gettext.bindtextdomain('@projectname@', localedir) if __name__ == '__main__': # os.environ['G_MESSAGES_DEBUG'] = '0' import gi gi.require_version('Gtk', '3.0') gi.require_version('WebKit2', '4.0') gi.require_version('GtkSource', '4') gi.require_version('Gdk', '3.0') gi.require_version('Handy', '1') from gi.repository import Gio, GObject, GtkSource # line below and necessary imports above required to accept GtkSourceView # from glade file GObject.type_register(GtkSource.View) resource = Gio.Resource.load(os.path.join(pkgdatadir, '@app_id@.gresource')) resource._register() from @projectname@ import constants constants.APP_ID = '@app_id@' constants.APP_NAME = '@prettyname@' constants.RESOURCE_PREFIX = '/@app_id_aspath@' constants.PROFILE = '@profile@' from @projectname@ import __main__ __main__.main() giara-0.3/bin/meson.build000066400000000000000000000017051376474030500153650ustar00rootroot00000000000000conf = configuration_data() conf.set('VERSION', meson.project_version()) conf.set('localedir', join_paths(prefix, get_option('localedir'))) conf.set('pkgdatadir', pkgdatadir) conf.set('PYTHON', py_installation.path()) conf.set('pythondir', join_paths(prefix, pythondir)) conf.set('localedir', join_paths(prefix, localedir)) conf.set('projectname', meson.project_name()) conf.set('authorfullname', authorfullname) conf.set('authoremail', authoremail) # if profile == 'development' # conf.set('app_id', app_id+'.dev') # else # conf.set('app_id', app_id) # endif conf.set('app_id', app_id) conf.set('app_id_aspath', app_id_aspath) conf.set('prettyname', prettyname) conf.set('profile', profile) configure_file( input: meson.project_name() + '.in', output: meson.project_name(), configuration: conf, install_dir: get_option('bindir') ) myprogram = join_paths(meson.build_root(), 'bin', meson.project_name()) run_target('run', command: [myprogram] ) giara-0.3/build.sh000077500000000000000000000001721376474030500141060ustar00rootroot00000000000000#!/bin/sh pwd rm -rf build mkdir build cd build meson .. meson configure -Dprefix=$PWD/build/testdir ninja ninja install giara-0.3/build_dev.sh000077500000000000000000000002221376474030500147400ustar00rootroot00000000000000#!/bin/sh pwd rm -rf build mkdir build cd build meson .. meson configure -Dprefix=$PWD/build/testdir -Dprofile="development" ninja ninja install giara-0.3/data/000077500000000000000000000000001376474030500133615ustar00rootroot00000000000000giara-0.3/data/icons/000077500000000000000000000000001376474030500144745ustar00rootroot00000000000000giara-0.3/data/icons/arrow1-up-symbolic.svg000066400000000000000000000040631376474030500206740ustar00rootroot00000000000000 image/svg+xml giara-0.3/data/icons/best-symbolic.svg000066400000000000000000000012151376474030500177700ustar00rootroot00000000000000giara-0.3/data/icons/controversial-symbolic.svg000066400000000000000000000055401376474030500217320ustar00rootroot00000000000000 image/svg+xml giara-0.3/data/icons/eye-not-looking-symbolic.svg000066400000000000000000000010731376474030500220550ustar00rootroot00000000000000 giara-0.3/data/icons/hot-symbolic.svg000066400000000000000000000022121376474030500176230ustar00rootroot00000000000000giara-0.3/data/icons/new-symbolic.svg000066400000000000000000000005431376474030500176270ustar00rootroot00000000000000 giara-0.3/data/icons/open-link-symbolic.svg000066400000000000000000000040621376474030500207320ustar00rootroot00000000000000 giara-0.3/data/icons/open-media-symbolic.svg000066400000000000000000000057601376474030500210620ustar00rootroot00000000000000 giara-0.3/data/icons/org.gabmus.giara-symbolic.svg000066400000000000000000000020421376474030500221600ustar00rootroot00000000000000 giara-0.3/data/icons/org.gabmus.giara.dev.svg000066400000000000000000000136771376474030500211360ustar00rootroot00000000000000 giara-0.3/data/icons/org.gabmus.giara.svg000066400000000000000000000045511376474030500203500ustar00rootroot00000000000000 giara-0.3/data/icons/rising-symbolic.svg000066400000000000000000000044711376474030500203350ustar00rootroot00000000000000 image/svg+xml giara-0.3/data/icons/video-icon.svg000066400000000000000000000066301376474030500172560ustar00rootroot00000000000000 image/svg+xml giara-0.3/data/meson.build000066400000000000000000000077611376474030500155360ustar00rootroot00000000000000desktop_conf = configuration_data() desktop_conf.set('bindir', join_paths(prefix, bindir)) desktop_conf.set('prettyname', prettyname) # .desktop comment now hardcoded for better i18n support #desktop_conf.set('description', description) if profile == 'development' desktop_conf.set('appid', app_id+'.dev') else desktop_conf.set('appid', app_id) endif desktop_conf.set('projectname', meson.project_name()) desktop_file = configure_file( input: app_id + '.desktop.in', output: app_id + '.desktop.i18n.in', configuration: desktop_conf ) i18n.merge_file( input: desktop_file, output: app_id + '.desktop', po_dir: '../po', type: 'desktop', install: true, install_dir: join_paths(datadir, 'applications') ) dbus_conf = configuration_data() dbus_conf.set('bindir', join_paths(prefix, bindir)) dbus_conf.set('appid', app_id) dbus_conf.set('projectname', meson.project_name()) configure_file( input: app_id + '.service.in', output: app_id + '.service', configuration: dbus_conf, install_dir: join_paths(datadir, 'dbus-1/services') ) gschema_conf = configuration_data() gschema_conf.set('apppath', app_id_aspath) gschema_conf.set('appid', app_id) gschema_conf.set('projectname', meson.project_name()) configure_file( input: app_id + '.gschema.xml.in', output: app_id + '.gschema.xml', configuration: gschema_conf, install_dir: join_paths(datadir, 'glib-2.0/schemas') ) icondir = join_paths(datadir, 'icons/hicolor') if profile == 'development' install_data( 'icons/'+app_id+'.dev.svg', install_dir: join_paths(icondir, 'scalable/apps') ) else install_data( 'icons/'+app_id+'.svg', install_dir: join_paths(icondir, 'scalable/apps') ) endif install_data( 'icons/'+app_id+'-symbolic.svg', install_dir: join_paths(icondir, 'symbolic/apps') ) subdir('ui') service_desktop_conf = configuration_data() service_desktop_conf.set('prettyname', prettyname) service_desktop_conf.set('projname', meson.project_name()) service_desktop_conf.set('app_id', app_id) # gresource_conf = configuration_data() # gresource_conf.set('app_id_in_aspath', app_id_in_aspath) # gresource_conf.set('app_id_aspath', app_id_aspath) # gresource_conf.set('app_id', app_id) # gresource_conf.set('app_id_in', app_id_in) app_resources = gnome.compile_resources( app_id, app_id + '.gresource.xml', # configure_file( # alt config # input: app_id_in + '.gresource.xml.in', # output: app_id + '.gresource.xml', # configuration: gresource_conf # ), gresource_bundle: true, dependencies: [ configure_file( input: 'ui/aboutdialog.ui.in', output: 'aboutdialog.ui', configuration: glade_conf ), configure_file( input: app_id + '.service.desktop.in', output: app_id + '.service.desktop', configuration: service_desktop_conf ) ], install: true, install_dir: pkgdatadir ) app_settings = gnome.compile_schemas() #appdata_conf = configuration_data() #appdata_conf.set('authorfullname', authorfullname) #appdata_conf.set('gitrepo', gitrepo) #appdata_conf.set('website', website) #appdata_conf.set('authoremail', authoremail) #appdata_conf.set('prettyname', prettyname) #appdata_conf.set('appid', app_id) #appdata_conf.set('prettylicense', prettylicense) # #configure_file( # input: appdata_file, # output: app_id + '.appdata.xml.', # configuration: appdata_conf, # install: true, # install_dir: join_paths(datadir, 'metainfo') #) ascli_exe = find_program('appstreamcli', required: false) if ascli_exe.found() test( 'validate metainfo file', ascli_exe, args: [ 'validate', #'--no-net', #'--pedantic', 'data/' + app_id + '.appdata.xml' ] ) endif i18n.merge_file( input: app_id + '.appdata.xml.in', output: app_id + '.appdata.xml', po_dir: '../po', install: true, install_dir: join_paths(datadir, 'metainfo') ) giara-0.3/data/org.gabmus.giara.appdata.xml.in000066400000000000000000000115271376474030500212550ustar00rootroot00000000000000 org.gabmus.giara Giara Gabriele Musco An app for Reddit CC0-1.0 GPL-3.0+

An app for Reddit.

Browse Reddit from your Linux desktop or smartphone.

org.gabmus.giara.desktop https://gitlab.gnome.org/World/giara/raw/website/website/screenshots/mainwindow.png https://gitlab.gnome.org/World/giara/raw/website/website/screenshots/scrot01.png https://gitlab.gnome.org/World/giara/raw/website/website/screenshots/scrot02.png https://gitlab.gnome.org/World/giara/raw/website/website/screenshots/scrot03.png https://gitlab.gnome.org/World/giara/raw/website/website/screenshots/scrot04.png https://gitlab.gnome.org/World/giara/raw/website/website/screenshots/scrot05.png https://gitlab.gnome.org/World/giara/raw/website/website/screenshots/scrot06.png https://gitlab.gnome.org/World/giara/raw/website/website/screenshots/scrot07.png https://giara.gabmus.org https://gitlab.gnome.org/World/giara/issues gabmus@disroot.org
  • Opening a post from a comment jumps to that comment
  • Post creation and editing are now asynchronous
  • Replaced quit button in welcome view with a more standard close button
  • Fixed icon rendering problems in KDE
  • Comment replies can now be collapsed
  • Comments don't always load in full, hidden comments can be loaded if the user wants them
  • You can now open Twitter links in Nitter and YouTube links in Invidious
  • Limited picture preview size
  • Added all available post sorting options
  • New UI for choosing post sorting
  • Implemented blockquote rendering
  • Implemented superscript rendering
  • Improved Markdown rendering
  • Improved Inbox view
  • Added A LOT of new languages! Thanks a lot to the community for the contributions!
  • Improved markdown rendering
  • Dark mode implemented
  • Added option to disable images in post previews
  • Added option to set a maximum size for images
  • Added option to clear cache
  • Added Brazilian Portuguese translation
  • New post or comment window now remembers its size
  • Subreddits can now be searched when creating new posts
  • Initial support for multireddits
  • Added Croatian translation
  • Added notifications for unread inbox items
  • Made comment border lines colorful
  • Authentication is now handled by your default browser
  • Improvements for flatpak packaging
  • First release
moderate workstation mobile
giara-0.3/data/org.gabmus.giara.desktop.in000066400000000000000000000013101376474030500205020ustar00rootroot00000000000000[Desktop Entry] # Translators: Do NOT translate or transliterate this text (this is a variable that will be replaced with the app name)! Name=@prettyname@ Comment=An app for Reddit Exec=@bindir@/@projectname@ %u # Translators: Do NOT translate or transliterate this text (this is an icon file name)! Icon=@appid@ Terminal=false Type=Application StartupNotify=true Categories=Network # Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! Keywords=reddit; MimeType=x-scheme-handler/orggabmusgiaradesktop; # Translators: Do NOT translate or transliterate this text (these are enum types)! X-Purism-FormFactor=Workstation;Mobile; giara-0.3/data/org.gabmus.giara.gresource.xml000066400000000000000000000050101376474030500212220ustar00rootroot00000000000000 org.gabmus.giara.service.desktop ui/gtk_style.css ui/login_page_style.css ui/main_ui.ui ui/post_preview.ui ui/post_body.ui ui/comment_box.ui ui/headerbar.ui ui/post_details_headerbar.ui ui/new_post_window.ui ui/login_view.ui ui/subreddit_view_headerbar.ui ui/subreddit_heading.ui ui/redditor_heading.ui ui/headerbar_with_back_and_squeezer.ui ui/choice_picker_popover_content.ui ui/subreddit_more_actions.ui ui/inbox_item_view.ui ui/menu.ui ui/new_post_menu.ui ui/shortcutsWindow.ui aboutdialog.ui icons/org.gabmus.giara-symbolic.svg icons/org.gabmus.giara.svg icons/org.gabmus.giara.dev.svg icons/best-symbolic.svg icons/hot-symbolic.svg icons/new-symbolic.svg icons/open-link-symbolic.svg icons/open-media-symbolic.svg icons/video-icon.svg icons/eye-not-looking-symbolic.svg icons/arrow1-up-symbolic.svg icons/rising-symbolic.svg icons/controversial-symbolic.svg giara-0.3/data/org.gabmus.giara.gschema.xml.in000066400000000000000000000004251376474030500212450ustar00rootroot00000000000000 giara-0.3/data/org.gabmus.giara.service.desktop.in000066400000000000000000000006141376474030500221470ustar00rootroot00000000000000[Desktop Entry] # Translators: Do NOT translate or transliterate this text (this is a variable that will be replaced with the app name)! Name=@prettyname@ # TODO: We could include the full path here but Meson requires # newer GLib to generate files for GResource TryExec=@projname@ Exec=@projname@ --gapplication-service Type=Application NoDisplay=true Name[en_US]=@app_id@.service.desktop giara-0.3/data/org.gabmus.giara.service.in000066400000000000000000000001201376474030500204670ustar00rootroot00000000000000[D-BUS Service] Name=@appid@ Exec=@bindir@/@projectname@ --gapplication-service giara-0.3/data/ui/000077500000000000000000000000001376474030500137765ustar00rootroot00000000000000giara-0.3/data/ui/aboutdialog.ui.in000066400000000000000000000051021376474030500172320ustar00rootroot00000000000000 False dialog @prettyname@ @VERSION@ @authorfullname@, et al. This application is not affiliated with or endorsed by Reddit, Inc. @PACKAGE_URL@ @CONTRIBUTORS@ @TRANSLATORS@ @DESIGNERS@ @APPID@ gpl-3-0 False vertical 2 False end False False 0 giara-0.3/data/ui/choice_picker_popover_content.ui000066400000000000000000000032551376474030500224350ustar00rootroot00000000000000 True False 12 12 12 12 vertical 6 True True edit-find-symbolic False False False True 0 True True never True True 1 giara-0.3/data/ui/comment_box.ui000066400000000000000000000400721376474030500166520ustar00rootroot00000000000000 True False 6 6 6 vertical 6 True False 6 vertical 6 True False 6 False True audio-input-microphone-symbolic False True 0 False True avatar-default-symbolic False True 1 True False start label True word-char False True 2 35 35 True True True Collapse replies True False go-down-symbolic False True end 3 False True 0 True False start 6 False True 1 True False vertical False True 2 True False 6 True False True expand True True True Upvote True False go-up-symbolic False True 1 True True True Downvote True False go-down-symbolic False True 2 False True 0 True False Ups label False True 1 True False True expand True True True Reply True False mail-reply-sender-symbolic False True 1 True True True Save True False document-save-symbolic False True 2 True True True Share True False emblem-shared-symbolic False True 3 True True True Edit True False document-edit-symbolic True True 4 True True True Delete True False user-trash-symbolic True True 5 False True end 4 False True 3 False True 0 True False True True False vertical False True 1 giara-0.3/data/ui/gtk_style.css000066400000000000000000000034761376474030500165270ustar00rootroot00000000000000.nested, .nested-0, .nested-1, .nested-2, .nested-3, .nested-4, .nested-5, .nested-6, .nested-7, .nested-8, .nested-9, .markdown-quote { border-left: 2px solid @theme_fg_color; padding-left: 4px; } .nested-0 { border-color: #3584e4; } .nested-1 { border-color: #33d17a; } .nested-2 { border-color: #f6d32d; } .nested-3 { border-color: #ff7800; } .nested-4 { border-color: #e01b24; } .nested-5 { border-color: #9141ac; } .nested-6 { border-color: #986a44; } .nested-7 { border-color: @theme_fg_color; } .comment_author, .blue { color: #1c71d8; } .red { color: #ed333b; } .green { color: #33d17a; } .op_comment { color: #ff7800; } .card { border: 1px solid @borders; color: @theme_fg_color; box-shadow: 0 1px 2px rgba(0,0,0,0.07); background-color: @theme_base_color; } .card, .card row, .card > *, .card > * > * { border-radius: 5px; } .welcome-title { font-weight: 300; font-size: 20pt; letter-spacing: 0.1rem; } .login-button { font-size: 18pt; margin-top: 12px; padding: 12px 24px; } .app-notification { border-radius: 5px 5px 0 0; } .separator-hr { background-color: @theme_fg_color; } .subtitle { opacity: .55; text-shadow: none; font-size: smaller; } .non-highlighted-listbox-row:hover { background: @theme_base_color; } .padding12 { padding: 12px; } .flair { font-size: .8rem; border-radius: .4rem; padding: .5em; background-color: #ff7800; /* Will be overwritten */ color: white; /* Will be overwritten */ } .inbox-count-badge { padding: 2px; font-size: .6em; border-radius: .6em; background-color: #ed333b; color: black; margin-top: 3px; } .collapse-icon { transition: all .3s ease-in-out; } .replies-closed { -gtk-icon-transform: rotate(90deg); } giara-0.3/data/ui/headerbar.ui000066400000000000000000000352321376474030500162570ustar00rootroot00000000000000 False False False True False 12 12 12 12 vertical 6 True False 6 True False vertical False True 0 True False vertical True False start username False True 0 True False start karma False True 1 True True 1 False True 0 True False vertical True Inbox True True True none True True 0 Subreddits True True True none True True 1 Multireddits True True True none True True 2 Profile True True True none True True 3 Saved True True True none True True 4 Log Out True True True none True True 5 False True 1 True False True True False True True True False True New post new_post_popover True False list-add-symbolic True False True True False True Profile profile_popover True False avatar-default-symbolic -1 False True Messages in your inbox end start count right end 5 True 1 1 True True True Refresh True False view-refresh-symbolic 2 True False vertical True True False True Menu menu_popover True False open-menu-symbolic False True 0 end 3 True True True Search True False system-search-symbolic end 4 giara-0.3/data/ui/headerbar_with_back_and_squeezer.ui000066400000000000000000000030261376474030500230330ustar00rootroot00000000000000 True False True True False True 2 True True True Back True False go-previous-symbolic giara-0.3/data/ui/inbox_item_view.ui000066400000000000000000000132261376474030500175300ustar00rootroot00000000000000 True False 6 6 6 6 vertical 6 True False True False vertical False True 0 True False 6 vertical 3 True False start author end False True 0 True True 1 True False end datetime right True word-char False True end 2 False True 0 True False start 6 False True 1 True False vertical False True 2 True False vertical False True 4 True False 6 False True 5 giara-0.3/data/ui/login_page_style.css000066400000000000000000000012261376474030500200350ustar00rootroot00000000000000.oauth2-authorize { background: none; padding-left: 0; } .icon { display: none; } .access, .oauth2-authorize, .footer, .footer-parent { float: none !important; position: static !important; width: unset !important;; } .content { height: auto; } .access::after, .access::before { display: none !important; } #eu-cookie-policy p { font-size: 9px; } #eu-cookie-policy .cookie-infobar::before { display: none; } #eu-cookie-policy .cookie-infobar { margin: 0; padding-left: 10px; padding-right: 90px; } .infobar-toaster-container { bottom: 0; left: 0; right: 0; } #header { display: none; } giara-0.3/data/ui/login_view.ui000066400000000000000000000213471376474030500165060ustar00rootroot00000000000000 False center center True False True False vertical 12 34 34 True True True end center 6 6 6 6 True False center center window-close-symbolic False True 0 True False 12 12 12 12 crossfade True False 12 12 12 12 vertical 6 True False center vertical 6 True False Welcome to Giara True True 0 Login True True True center False True 1 True True 1 page0 page0 True False center vertical 12 True False Waiting for authentication… center True False True 0 64 64 True False center True False True 1 page1 page1 1 True True 1 giara-0.3/data/ui/main_ui.ui000066400000000000000000000105161376474030500157610ustar00rootroot00000000000000 True False True True True False True True vertical -1 True False end True False center True False 0 none True False 20 True False notification True word-char False True 0 True True True none True False Close window-close-symbolic False True 1 True -1 giara-0.3/data/ui/menu.ui000066400000000000000000000013061376474030500153010ustar00rootroot00000000000000
Preferences app.settings Keyboard Shortcuts app.shortcuts About Giara app.about
giara-0.3/data/ui/meson.build000066400000000000000000000022461376474030500161440ustar00rootroot00000000000000LIBEXEC_DIR = join_paths(get_option('prefix'), get_option('libexecdir')) EXTENSION_DIR = join_paths(get_option('prefix'), get_option('libdir'), meson.project_name()) glade_conf = configuration_data() glade_conf.set('PACKAGE_URL', website) glade_conf.set('DATA_DIR', pkgdatadir) glade_conf.set('EXTENSION_DIR', EXTENSION_DIR) glade_conf.set('LOCALE_DIR', join_paths(get_option('prefix'), get_option('datadir'), 'locale')) glade_conf.set('PYTHON_DIR', pythondir) glade_conf.set('PYTHON_EXEC_DIR', join_paths(get_option('prefix'), py_installation.get_path('stdlib'))) glade_conf.set('PYTHON', py_installation.path()) if profile == 'development' glade_conf.set('APPID', app_id+'.dev') else glade_conf.set('APPID', app_id) endif glade_conf.set('libexecdir', LIBEXEC_DIR) glade_conf.set('VERSION', app_version) glade_conf.set('CONTRIBUTORS', contributors) glade_conf.set('DESIGNERS', designers) glade_conf.set('TRANSLATORS', translators) glade_conf.set('authorfullname', authorfullname) glade_conf.set('prettyname', prettyname) # this has been moved to /data/meson.build #configure_file( # input: 'aboutdialog.ui.in', # output: 'aboutdialog.ui', # configuration: glade_conf #) giara-0.3/data/ui/new_post_menu.ui000066400000000000000000000012561376474030500172230ustar00rootroot00000000000000
Text newpost.text Link newpost.link Media newpost.media
giara-0.3/data/ui/new_post_window.ui000066400000000000000000000141501376474030500175630ustar00rootroot00000000000000 image/png image/gif image/jpeg video/mpeg True False New post Cancel True True True Send True True True end 1 True False 6 6 6 6 vertical 6 True False vertical 6 False True 0 True True 300 Post title… True False True 1 True False vertical True True never in True True word-char 2 2 True True 4 4 True True True True True True 0 True True 2 True True Link… url False True 3 True True True True False Select media… center end False True 4 giara-0.3/data/ui/post_body.ui000066400000000000000000000507271376474030500163520ustar00rootroot00000000000000 True False 6 6 6 6 vertical 6 True False True False vertical False True 0 True False 6 vertical 3 True False start subreddit end False True 0 True False start op end False True 1 True False start 6 False True 2 True True 1 True False end datetime right True word-char False True end 2 False True 0 True False 12 True False center vertical 6 True True True Upvote center center True False center go-up-symbolic False False 0 True False Ups ups True True 1 True True True Downvote center center True False center go-down-symbolic False False 2 False True 0 True False center vertical 6 True False 6 False True Pinned view-pin-symbolic False True 0 True False start title fill True word-char True True 1 False True 0 True False start 6 False True 1 False True 2 False True 1 True False vertical False True 4 True False vertical False True 5 True False 6 True False expand True True True Reply True False mail-reply-sender-symbolic False True 0 True True True Open media True False open-media-symbolic True True 1 True True True Open link True False open-link-symbolic True True 2 True True True Save True False document-save-symbolic False True 3 True True True Share True False emblem-shared-symbolic False True 4 True True True Edit True False document-edit-symbolic True True 5 True True True Delete True False user-trash-symbolic True True 6 False True end 4 False True 6 giara-0.3/data/ui/post_details_headerbar.ui000066400000000000000000000031131376474030500210220ustar00rootroot00000000000000 True False True True True True Back True False go-previous-symbolic True True True Refresh True False view-refresh-symbolic 1 giara-0.3/data/ui/post_preview.ui000066400000000000000000000457671376474030500171060ustar00rootroot00000000000000 True False 6 6 6 6 vertical 6 True False True False vertical False True 0 True False 6 vertical 3 True False start subreddit end False True 0 True False start op end False True 1 True True 1 True False end datetime right True word-char False True end 2 False True 0 True False 12 True False center vertical 6 True True True Upvote center center True False center go-up-symbolic False False 0 True False Ups ups True True 1 True True True Downvote center center True False center go-down-symbolic False False 2 False True 0 True False center vertical 6 True False 6 False True Pinned view-pin-symbolic False True 0 True False start title fill True word-char True True 1 False True 0 True False start 6 False True 1 False True 2 False True 1 True False vertical False True 4 True False 6 False True Comments 6 True False media-view-subtitles-symbolic False True 2 True False ... False True 3 False True 3 True False expand True True True Open media True False open-media-symbolic True True 0 True True True Open link True False open-link-symbolic True True 1 True True True Save True False document-save-symbolic False True 2 True True True Share True False emblem-shared-symbolic False True 3 True True True Delete True False user-trash-symbolic True True 4 False True end 4 False True 5 giara-0.3/data/ui/redditor_heading.ui000066400000000000000000000125251376474030500176350ustar00rootroot00000000000000 True False 6 6 6 6 vertical 6 True False vertical False True 0 True False vertical 6 True False name center True word-char False True 0 True True 1 Follow True True True center False True 2 True False center 6 6 True False end Karma 0 0 True False start karma fill True word-char 1 0 True False end Cake day 0 1 True False start cake_day fill 1 1 False True 3 giara-0.3/data/ui/shortcutsWindow.ui000066400000000000000000000036121376474030500175650ustar00rootroot00000000000000 1 1 shortcuts 15 1 General 1 <ctrl>question Open Keyboard Shortcuts 1 F10 Open Menu 1 <ctrl>comma Open Preferences 1 <ctrl>Q Quit giara-0.3/data/ui/subreddit_heading.ui000066400000000000000000000140451376474030500200050ustar00rootroot00000000000000 True False 6 6 6 6 vertical 6 True False vertical False True 0 True False vertical 6 True False title center True word-char False True 0 True False display_name center True word-char False True 1 True True 1 Join True True True center False True 2 True False center 6 6 True False end Members 0 0 True False start num_members fill True word-char 1 0 True False end Since 0 1 True False start date fill 1 1 False True 3 giara-0.3/data/ui/subreddit_more_actions.ui000066400000000000000000000261211376474030500210660ustar00rootroot00000000000000 True False 6 6 6 6 0 0 none True False 6 6 6 6 slide-left-right True False vertical start Add to multireddit True True True True True 0 main main True False vertical 6 True False 6 True True True Back True False go-previous-symbolic False True 0 True False Add to multireddit center True True 1 False True 0 True False False True 1 True False vertical True True 2 Create multireddit True True True False True 3 add_to_multi add_to_multi 1 True False vertical 6 True False 6 True True True Back True False go-previous-symbolic False True 0 True False Create multireddit center True True 1 False True 0 True False False True 1 True True 50 Multireddit name… False True 2 Create True False True True False True 3 page0 page0 2 giara-0.3/data/ui/subreddit_view_headerbar.ui000066400000000000000000000054611376474030500213570ustar00rootroot00000000000000 True False True True False True 2 True True True Back True False go-previous-symbolic True True True Refresh True False view-refresh-symbolic 1 True True True Search True False system-search-symbolic end 4 giara-0.3/dist/000077500000000000000000000000001376474030500134135ustar00rootroot00000000000000giara-0.3/dist/flatpak/000077500000000000000000000000001376474030500150355ustar00rootroot00000000000000giara-0.3/dist/flatpak/org.gabmus.giara.json000066400000000000000000000202631376474030500210610ustar00rootroot00000000000000{ "app-id": "org.gabmus.giara", "command": "giara", "runtime": "org.gnome.Platform", "runtime-version": "3.38", "sdk": "org.gnome.Sdk", "finish-args": [ "--device=dri", "--share=ipc", "--socket=fallback-x11", "--socket=wayland", "--share=network" ], "add-extensions": { "org.freedesktop.Platform.ffmpeg-full": { "directory": "lib/ffmpeg", "version": "20.08", "add-ld-path": ".", "no-autodownload": true, "autodelete": false } }, "cleanup-commands": [ "mkdir -p ${FLATPAK_DEST}/lib/ffmpeg" ], "modules": [ { "name": "libhandy", "buildsystem": "meson", "config-opts": [ "-Dglade_catalog=disabled", "-Dtests=false", "-Dexamples=false" ], "sources": [ { "type": "git", "url": "https://gitlab.gnome.org/GNOME/libhandy", "tag": "1.0.2" } ] }, { "name": "python-setuptools-scm", "buildsystem": "simple", "build-commands": [ "python3 setup.py build", "python3 setup.py egg_info", "python3 setup.py install --prefix=/app --root=/" ], "sources": [ { "type": "git", "url": "https://github.com/pypa/setuptools_scm", "tag": "v4.1.2" } ] }, { "name": "python-dateutil", "buildsystem": "simple", "build-commands": [ "python3 setup.py install --prefix=/app --root=/ --optimize=1" ], "sources": [ { "type": "git", "url": "https://github.com/dateutil/dateutil", "tag": "2.8.1", "commit": "fc9b1625ebc729f01e449879b6b140abd12ae621" } ] }, { "name": "python-idna", "buildsystem": "simple", "build-commands": [ "python3 setup.py install --prefix=/app --root=/" ], "sources": [ { "type": "git", "url": "https://github.com/kjd/idna", "tag": "v2.10" } ] }, { "name": "python-certifi", "buildsystem": "simple", "build-commands": [ "python3 setup.py install --prefix=/app --root=/" ], "sources": [ { "type": "git", "url": "https://github.com/certifi/python-certifi", "tag": "2020.06.20" } ] }, { "name": "python-chardet", "buildsystem": "simple", "build-commands": [ "python3 setup.py install --prefix=/app --root=/" ], "sources": [ { "type": "git", "url": "https://github.com/chardet/chardet", "tag": "3.0.4", "commit": "9b8c5c2fb118d76c6beeab9affd01c332732a530" } ] }, { "name": "python-urllib3", "buildsystem": "simple", "build-commands": [ "python3 setup.py install --prefix=/app --root=/" ], "sources": [ { "type": "git", "url": "https://github.com/urllib3/urllib3", "tag": "1.25.10" } ] }, { "name": "python-requests", "buildsystem": "simple", "build-commands": [ "python3 setup.py install --prefix=/app --root=/" ], "sources": [ { "type": "archive", "url": "https://github.com/psf/requests/archive/v2.24.0.tar.gz", "sha256": "904e9a2bd22e98e444f5b23f80fa3fb8dda5d1e81d0f87d9ee56af2e15f73862" } ] }, { "name": "python-markupsafe", "buildsystem": "simple", "build-commands": [ "python3 setup.py install --prefix=/app --root=/" ], "sources": [ { "type": "git", "url": "https://github.com/pallets/markupsafe", "tag": "1.1.1" } ] }, { "name": "python-jinja2", "buildsystem": "simple", "build-commands": [ "python3 setup.py install --prefix=/app --root=/" ], "sources": [ { "type": "git", "url": "https://github.com/pallets/jinja", "tag": "2.11.2" } ] }, { "name": "python-mistune", "buildsystem": "simple", "build-commands": [ "python3 setup.py install --prefix=/app --root=/" ], "sources": [ { "type": "git", "url": "https://github.com/lepture/mistune", "tag": "v0.8.4" } ] }, { "name": "python-pillow", "buildsystem": "simple", "build-commands": [ "python3 setup.py install --prefix=/app --root=/" ], "sources": [ { "type": "git", "url": "https://github.com/python-pillow/Pillow", "tag": "7.2.0" } ] }, { "name": "python-websocket-client", "buildsystem": "simple", "build-commands": [ "python3 setup.py install --prefix=/app --root=/" ], "sources": [ { "type": "git", "url": "https://github.com/websocket-client/websocket-client", "tag": "v0.57.0" } ] }, { "name": "python-prawcore", "buildsystem": "simple", "build-commands": [ "python3 setup.py install --prefix=/app --root=/" ], "sources": [ { "type": "git", "url": "https://github.com/praw-dev/prawcore", "tag": "v1.5.0" } ] }, { "name": "python-praw", "buildsystem": "simple", "build-commands": [ "python3 setup.py install --prefix=/app --root=/" ], "sources": [ { "type": "git", "url": "https://github.com/praw-dev/praw", "tag": "v7.1.0" } ] }, { "name": "python-beautifulsoup", "buildsystem": "simple", "build-commands": [ "python3 setup.py install --prefix=/app --root=/" ], "sources": [ { "type": "archive", "url": "https://files.pythonhosted.org/packages/c6/62/8a2bef01214eeaa5a4489eca7104e152968729512ee33cb5fbbc37a896b7/beautifulsoup4-4.9.1.tar.gz", "sha256": "73cc4d115b96f79c7d77c1c7f7a0a8d4c57860d1041df407dd1aae7f07a77fd7" } ] }, { "name": "giara", "buildsystem": "meson", "config-opts": [ "--buildtype=release" ], "sources": [ { "type": "git", "url": "https://gitlab.gnome.org/World/giara", "tag": "0.3" } ] } ] } giara-0.3/giara/000077500000000000000000000000001376474030500135335ustar00rootroot00000000000000giara-0.3/giara/__main__.py000066400000000000000000000204311376474030500156250ustar00rootroot00000000000000# __main__.py # # Copyright (C) 2019 GabMus # # 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 . from giara.constants import RESOURCE_PREFIX, APP_ID import sys # import argparse # from gettext import gettext as _ # from os.path import isfile from gi.repository import Gtk, Gdk, Gio, Handy, GLib from giara.confManager import ConfManager from giara.app_window import AppWindow from giara.settings_window import SettingsWindow from giara.auth import ( get_preauthorized_client, get_unauthorized_client, get_authorized_client, get_auth_link ) from prawcore.exceptions import ResponseException class GApplication(Gtk.Application): def __init__(self, **kwargs): super().__init__( application_id=APP_ID, flags=Gio.ApplicationFlags.HANDLES_OPEN, **kwargs ) GLib.set_application_name('Giara') GLib.set_prgname('org.gabmus.giara') self.confman = ConfManager() self.confman.application = self self._unauth_reddit = get_unauthorized_client() self.reddit = None self.login_window = None self.window = None self.connect('open', self.open) def open(self, app, files, *args): target = files[0].get_uri() print(target) get_authorized_client( reddit=self._unauth_reddit, code=target.split('=')[-1] ) self.continue_activate(self._unauth_reddit) def do_startup(self): Gtk.Application.do_startup(self) Handy.init() actions = [ { 'name': 'settings', 'func': self.show_settings_window, 'accel': 'comma' }, { 'name': 'shortcuts', 'func': self.show_shortcuts_window, 'accel': 'question' }, { 'name': 'about', 'func': self.show_about_dialog }, { 'name': 'quit', 'func': self.on_destroy_window, 'accel': 'q' }, { 'name': 'goinbox', 'func': self.go_inbox, } ] for a in actions: c_action = Gio.SimpleAction.new(a['name'], None) c_action.connect('activate', a['func']) self.add_action(c_action) if 'accel' in a.keys(): self.set_accels_for_action( f'app.{a["name"]}', [a['accel']] ) def quit(self, *args, **kwargs): if self.confman.notifman is not None: self.confman.notifman.stop() super().quit(*args, **kwargs) def go_inbox(self, *args): self.window.main_ui.deck.left_stack.on_go_inbox() def show_about_dialog(self, *args): about_builder = Gtk.Builder.new_from_resource( f'{RESOURCE_PREFIX}/aboutdialog.ui' ) dialog = about_builder.get_object('aboutdialog') dialog.set_modal(True) dialog.set_transient_for(self.window) dialog.present() def on_destroy_window(self, *args): if self.window: self.window.on_destroy() self.quit() def show_shortcuts_window(self, *args): shortcuts_win = Gtk.Builder.new_from_resource( f'{RESOURCE_PREFIX}/ui/shortcutsWindow.ui' ).get_object('shortcuts-win') shortcuts_win.props.section_name = 'shortcuts' shortcuts_win.set_transient_for(self.window) shortcuts_win.present() def show_settings_window(self, *args): settings_win = SettingsWindow() settings_win.set_transient_for(self.window) settings_win.set_modal(True) settings_win.present() def create_login_win(self, login_url: str): builder = Gtk.Builder.new_from_resource( f'{RESOURCE_PREFIX}/ui/login_view.ui' ) self.login_window = builder.get_object('login_window') self.login_window.resize(350, 500) def on_login_win_destroy(*args): if self.reddit is None: self.quit() sys.exit(0) self.login_window.connect( 'destroy', on_login_win_destroy ) builder.get_object('quit_btn').connect( 'clicked', lambda *args: self.login_window.destroy() ) def on_login_clicked(*args): builder.get_object('stack').set_visible_child( builder.get_object('waiting_box') ) Gio.AppInfo.launch_default_for_uri( login_url ) builder.get_object('login_btn').connect( 'clicked', on_login_clicked ) def get_reddit_client(self, refresh_token=None): if refresh_token is None: refresh_token = self.confman.conf['refresh_token'] if refresh_token != '': try: return self.continue_activate( get_preauthorized_client(refresh_token) ) except ResponseException: return self.get_reddit_client('') except ValueError as e: if 'scope' in repr(e).lower(): return self.get_reddit_client('') else: self.confman.conf['refresh_token'] = '' # self._unauth_reddit = get_unauthorized_client() # moved up self.create_login_win(get_auth_link(self._unauth_reddit)) self.add_window(self.login_window) self.login_window.present() self.login_window.show_all() def do_activate(self): stylecontext = Gtk.StyleContext() provider = Gtk.CssProvider() provider.load_from_resource( f'{RESOURCE_PREFIX}/ui/gtk_style.css' ) stylecontext.add_provider_for_screen( Gdk.Screen.get_default(), provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION ) self.get_reddit_client() def continue_activate(self, reddit=None): if reddit is None: self.reddit = self.confman.reddit else: self.reddit = reddit self.confman.reddit = reddit if self.login_window is not None: self.login_window.destroy() self.window = AppWindow() self.confman.window = self.window self.window.connect('destroy', self.on_destroy_window) self.add_window(self.window) self.window.present() self.window.show_all() if hasattr(self, 'args'): if self.args: pass self.window.do_startup() def do_command_line(self, args): """ GTK.Application command line handler called if Gio.ApplicationFlags.HANDLES_COMMAND_LINE is set. must call the self.do_activate() to get the application up and running. """ # call the default commandline handler Gtk.Application.do_command_line(self, args) # make a command line parser # #parser = argparse.ArgumentParser() # #parser.add_argument( # # 'argurl', # # metavar=_('url'), # # type=str, # # nargs='?', # # help=_('opml file local url or rss remote url to import') # #) # parse the command line stored in args, # but skip the first element (the filename) # #self.args = parser.parse_args(args.get_arguments()[1:]) # call the main program do_activate() to start up the app self.do_activate() return 0 def main(): application = GApplication() try: ret = application.run(sys.argv) except SystemExit as e: ret = e.code sys.exit(ret) if __name__ == '__main__': main() giara-0.3/giara/accel_manager.py000066400000000000000000000016611376474030500166520ustar00rootroot00000000000000from gi.repository import Gtk def add_accelerators(window, shortcuts_l: list): accel_group = Gtk.AccelGroup() window.add_accel_group(accel_group) for s in shortcuts_l: __add_accelerator(accel_group, s['combo'], s['cb']) def __add_accelerator(accel_group, shortcut, callback): if shortcut: key, mod = Gtk.accelerator_parse(shortcut) accel_group.connect( key, mod, Gtk.AccelFlags.VISIBLE, callback ) def add_mouse_button_accel(widget, function): '''Adds an accelerator for mouse btn press for widget to function. NOTE: this returns the Gtk.Gesture, you need to keep this around or it won't work. Assign it to some random variable and don't let it go out of scope''' gesture = Gtk.GestureMultiPress.new(widget) gesture.set_button(0) gesture.set_propagation_phase(Gtk.PropagationPhase.CAPTURE) gesture.connect('pressed', function) return gesture giara-0.3/giara/app_window.py000066400000000000000000000047661376474030500162710ustar00rootroot00000000000000from giara.constants import APP_ID, APP_NAME, PROFILE from gi.repository import Gtk, Handy from giara.confManager import ConfManager from giara.main_ui import MainUI from giara.accel_manager import add_accelerators class AppWindow(Handy.ApplicationWindow): def __init__(self, **kwargs): super().__init__(**kwargs) self.confman = ConfManager() self.reddit = self.confman.reddit self.set_title(APP_NAME) self.set_icon_name(APP_ID+'.dev' if PROFILE == 'development' else '') self.main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.main_ui = MainUI() self.main_box.add(self.main_ui) self.main_ui.set_hexpand(True) self.main_ui.set_vexpand(True) self.add(self.main_box) def toggle_popover(*args): self.main_ui.deck.left_stack.get_headerbar().menu_btn.clicked() # if popover.is_visible(): # popover.popdown() # else: # popover.popup() add_accelerators( self, [ { 'combo': 'F10', 'cb': toggle_popover } ] ) # Why this -52? # because every time a new value is saved, for some reason # it's the actual value +52 out of nowhere # this makes the window ACTUALLY preserve its old size self.resize( self.confman.conf['windowsize']['width']-52, self.confman.conf['windowsize']['height']-52 ) self.size_allocation = self.get_allocation() self.connect('size-allocate', self.update_size_allocation) def on_dark_mode_changed(*args): Gtk.Settings.get_default().set_property( 'gtk-application-prefer-dark-theme', self.confman.conf['dark_mode'] ) self.confman.connect('dark_mode_changed', on_dark_mode_changed) on_dark_mode_changed() def emit_destroy(self, *args): self.emit('destroy') def on_destroy(self, *args): self.confman.conf['windowsize'] = { 'width': self.size_allocation.width, 'height': self.size_allocation.height } self.hide() self.confman.save_conf() def update_size_allocation(self, *args): self.size_allocation = self.get_allocation() def do_startup(self): pass # self.main_ui.source_buffer.set_language( # self.main_ui.source_lang_markdown # ) giara-0.3/giara/auth.py000066400000000000000000000034241376474030500150510ustar00rootroot00000000000000from gettext import gettext as _ import praw from datetime import datetime from giara.confManager import ConfManager, SCOPES def get_authorized_client(retry=False, reddit=None, code=''): if reddit is None: reddit = get_unauthorized_client() confman = ConfManager() refresh_token = '' if not retry: refresh_token = confman.conf['refresh_token'] if refresh_token != '': try: return get_preauthorized_client(refresh_token) except Exception: print( _('Error getting client with refresh token, retrying…') ) return get_authorized_client(True) try: refresh_token = reddit.auth.authorize(code) confman.conf['refresh_token'] = refresh_token confman.save_conf() except Exception: if not retry: print( _('Error authorizing Reddit client, retrying…') ) return get_authorized_client(retry=True) else: print( _('Error authorizing Reddit client after retry, quitting…') ) import sys sys.exit(1) return reddit def get_auth_link(reddit): return reddit.auth.url( SCOPES, f'giara-t{datetime.now().timestamp()}', 'permanent' ) USER_AGENT = 'giara by /u/gabmus' CLIENT_ID = '5rQWiP4kMWi7CA' def get_unauthorized_client(): return praw.Reddit( client_id=CLIENT_ID, client_secret=None, redirect_uri='orggabmusgiaradesktop://authcallback', user_agent=USER_AGENT ) def get_preauthorized_client(refresh_token): return praw.Reddit( client_id=CLIENT_ID, client_secret=None, refresh_token=refresh_token, user_agent=USER_AGENT ) giara-0.3/giara/choice_picker.py000066400000000000000000000044171376474030500167020ustar00rootroot00000000000000from gettext import gettext as _ from gi.repository import Gtk, GObject from giara.constants import RESOURCE_PREFIX class ChoicePickerPopover(Gtk.Popover): def __init__(self, relative_to, listbox, **kwargs): super().__init__(**kwargs) self.builder = Gtk.Builder.new_from_resource( f'{RESOURCE_PREFIX}/ui/choice_picker_popover_content.ui' ) self.search_entry = self.builder.get_object('search_entry') self.relative_to = relative_to self.set_relative_to(self.relative_to) self.set_modal(True) self.set_size_request(280, 400) self.sw = self.builder.get_object('scrolled_win') self.listbox = listbox self.listbox.get_style_context().remove_class('card') self.listbox.set_filter_func(self.filter_func, None, False) self.sw.add(self.listbox) self.search_entry.connect('changed', self.on_search_entry_changed) self.main_container = self.builder.get_object('main_container') self.add(self.main_container) self.listbox.connect('row-selected', lambda *args: self.popdown()) def filter_func(self, row, data, notify_destroy): term = self.search_entry.get_text().strip().lower() if not term: return True return term in row.get_key().lower() def on_search_entry_changed(self, *args): self.listbox.invalidate_filter() def popup(self, *args): super().popup() self.main_container.show_all() class ChoicePickerButton(Gtk.Button): __gsignals__ = { 'changed': ( GObject.SignalFlags.RUN_LAST, None, (Gtk.ListBoxRow,) ) } def __init__(self, listbox, default_title=_('None'), **kwargs): super().__init__(**kwargs) self.set_label(default_title) self.listbox = listbox self.popover = ChoicePickerPopover(self, self.listbox) self.set_hexpand(True) self.connect('clicked', lambda *args: self.popover.popup()) self.listbox.connect('row-selected', self.on_item_selected) def on_item_selected(self, listbox, row): self.emit('changed', row) self.set_label(row.title if row is not None else _('None')) def get_choice(self): return self.listbox.get_selected_row() giara-0.3/giara/common_collection_listbox_row.py000066400000000000000000000031131376474030500222410ustar00rootroot00000000000000from gi.repository import Gtk, Pango from giara.simple_avatar import SimpleAvatar class CommonCollectionListboxRow(Gtk.ListBoxRow): def __init__(self, name, title, get_icon_func, avatar_name=None, **kwargs): super().__init__(**kwargs) self.main_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) self.main_box.set_margin_start(12) self.main_box.set_margin_end(12) self.main_box.set_margin_top(12) self.main_box.set_margin_bottom(12) self.name = name self.title = title self.get_icon_func = get_icon_func self.avatar = SimpleAvatar( 42, avatar_name or title, self.get_icon_func ) self.label = Gtk.Label( title or name ) self.desc_label = Gtk.Label(name) self.labels_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) for lbl in (self.label, self.desc_label): self.labels_box.add(lbl) lbl.set_line_wrap(True) lbl.set_line_wrap_mode(Pango.WrapMode.WORD_CHAR) lbl.set_vexpand(True) lbl.set_hexpand(True) lbl.set_halign(Gtk.Align.START) lbl.set_justify(Gtk.Justification.FILL) lbl.set_margin_start(12) self.desc_label.get_style_context().add_class('subtitle') self.main_box.add(self.avatar) self.avatar.set_vexpand(False) self.avatar.set_hexpand(False) self.main_box.add(self.labels_box) self.add(self.main_box) def get_key(self): return self.name + ' ' + self.title giara-0.3/giara/common_post_box.py000066400000000000000000000351411376474030500173160ustar00rootroot00000000000000from gettext import gettext as _ from giara.path_utils import is_image, is_media from giara.download_manager import download_img, download_video from gi.repository import Gtk, GLib, Gdk, Gio from praw.models import Comment, Submission, Message from threading import Thread from giara.new_post_window import EditWindow from giara.picture_view import PictureView from giara.flair_label import FlairLabel from giara.time_utils import humanize_utc_timestamp from giara.confManager import ConfManager from giara.simple_avatar import SimpleAvatar class InteractiveEntityBox(Gtk.Bin): def __init__(self, entity, builder, **kwargs): super().__init__(**kwargs) self.entity = entity self.builder = builder self.save_btn = self.builder.get_object('save_btn') self.save_btn.connect('clicked', self.on_save_clicked) self.upvotes_label = self.builder.get_object('upvotes_label') self.upvote_btn = self.builder.get_object('upvote_btn') self.downvote_btn = self.builder.get_object('downvote_btn') self.color_up_down_btns() self.upvote_btn.connect('clicked', self.on_upvote_btn_clicked) self.downvote_btn.connect('clicked', self.on_downvote_btn_clicked) self.color_saved_btn() confman = ConfManager() self.me = confman.reddit_user_me self.delete_btn = self.builder.get_object('delete_btn') self.share_btn = self.builder.get_object('share_btn') if hasattr(self.entity, 'permalink'): self.share_btn.set_visible(True) self.share_btn.set_no_show_all(False) self.share_btn.connect('clicked', self.copy_link) else: self.share_btn.set_visible(False) self.share_btn.set_no_show_all(True) self.main_box = self.builder.get_object('main_box') self.add(self.main_box) self.edit_btn = self.builder.get_object('edit_btn') self.show_hide_edit_delete() self.author_flairs_container = self.builder.get_object( 'author_flairs_container' ) if ( self.author_flairs_container is not None and self.entity.author_flair_text is not None ): self.author_flairs_container.add( FlairLabel( self.entity.author_flair_text, self.entity.author_flair_background_color, self.entity.author_flair_text_color ) ) def show_hide_edit_delete(self): def af(): if self.edit_btn: show_edit = ( self.entity.author is not None and self.entity.author.fullname == self.me.fullname and ( isinstance(self.entity, Comment) or ( isinstance(self.entity, Submission) and self.entity.is_self ) ) ) GLib.idle_add(edit_cb, show_edit) if ( hasattr(self.entity, 'delete') and hasattr(self.entity, 'author') and self.entity.author == self.me ): GLib.idle_add(delete_cb) def edit_cb(show_edit): self.edit_btn.set_visible(show_edit) self.edit_btn.set_no_show_all(not show_edit) if show_edit: self.edit_btn.connect('clicked', self.on_edit_clicked) def delete_cb(): self.delete_btn.set_visible(True) self.delete_btn.set_no_show_all(False) self.delete_btn.connect('clicked', self.on_delete_clicked) Thread(target=af).start() def on_delete_clicked(self, *args): confirm_dialog = Gtk.MessageDialog( flags=( Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT ), message_type=Gtk.MessageType.QUESTION, buttons=Gtk.ButtonsType.YES_NO, text=_('Are you sure you want to delete this item?') ) confirm_dialog.set_transient_for(self.get_toplevel()) res = confirm_dialog.run() confirm_dialog.close() if res == Gtk.ResponseType.YES: self.entity.delete() if hasattr(self, 'refresh_func'): self.refresh_func(wait_for_comments_update=True) def on_edit_clicked(self, *args): win = EditWindow( self.entity, lambda *args: ( self.refresh_func(wait_for_comments_update=True) if hasattr(self, 'refresh_func') else None ) ) win.set_transient_for(self.get_toplevel()) win.present() win.show_all() def copy_link(self, *args): clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) clipboard.set_text( 'https://reddit.com' + ( '/' if self.entity.permalink[0] != '/' else '' ) + self.entity.permalink, -1 ) clipboard.store() self.get_toplevel().main_ui.show_notification( _('Link copied to clipboard') ) def on_save_clicked(self, *args): def af(): if self.entity.saved: self.entity.unsave() else: self.entity.save() self.entity._fetch() def cb(): self.color_saved_btn() self.save_btn.set_sensitive(True) GLib.idle_add(cb) self.save_btn.set_sensitive(False) Thread(target=af).start() def color_saved_btn(self): if hasattr(self.entity, 'saved'): self.save_btn.set_visible(True) self.save_btn.set_no_show_all(False) if self.entity.saved: self.save_btn.get_style_context().add_class('blue') else: self.save_btn.get_style_context().remove_class('blue') else: self.save_btn.set_visible(False) self.save_btn.set_no_show_all(True) def on_upvote_btn_clicked(self, *args): def af(): if self.entity.likes: self.entity.clear_vote() else: self.entity.upvote() self.entity._fetch() def cb(): self.color_up_down_btns() self.downvote_btn.set_sensitive(True) self.upvote_btn.set_sensitive(True) GLib.idle_add(cb) self.upvote_btn.set_sensitive(False) self.downvote_btn.set_sensitive(False) Thread(target=af).start() def on_downvote_btn_clicked(self, *args): def af(): if self.entity.likes or self.entity.likes is None: self.entity.downvote() else: self.entity.clear_vote() self.entity._fetch() def cb(): self.color_up_down_btns() self.downvote_btn.set_sensitive(True) self.upvote_btn.set_sensitive(True) GLib.idle_add(cb) self.upvote_btn.set_sensitive(False) self.downvote_btn.set_sensitive(False) Thread(target=af).start() def color_up_down_btns(self): # also update ups label self.upvotes_label.set_text(str(self.entity.score)) upvote_style_context = self.upvote_btn.get_style_context() downvote_style_context = self.downvote_btn.get_style_context() if self.entity.likes is None: # None = no interaction upvote_style_context.remove_class('blue') downvote_style_context.remove_class('red') elif self.entity.likes: # True = upvote upvote_style_context.add_class('blue') downvote_style_context.remove_class('red') else: # False = downvote upvote_style_context.remove_class('blue') downvote_style_context.add_class('red') class CommonPostBox(InteractiveEntityBox): def __init__(self, post, builder, **kwargs): super().__init__(post, builder, **kwargs) self.post = post self.confman = ConfManager() self.title_label = self.builder.get_object('title_label') self.pinned_icon = self.builder.get_object('pinned_icon') if isinstance(self.post, Submission): self.title_label.set_text(self.post.title) if self.post.stickied: self.pinned_icon.set_visible(True) self.pinned_icon.set_no_show_all(False) elif isinstance(self.post, Comment): self.title_label.set_text(_('Comment: ')+self.post.body[:50]+'...') else: self.title_label.set_text(self.post.body) self.datetime_label = self.builder.get_object('datetime_label') self.datetime_label.set_text( humanize_utc_timestamp(self.post.created_utc) ) self.subreddit_label = self.builder.get_object('subreddit_label') if isinstance(self.post, Message): self.subreddit_label.set_text(_('Message')) else: self.subreddit_label.set_text(self.post.subreddit_name_prefixed) self.op_label = self.builder.get_object('op_label') self.op_label.set_text( f'u/{self.post.author.name}' if self.post.author is not None else _('Author unknown') ) self.avatar = SimpleAvatar( 42, self.post.subreddit.display_name if self.post.subreddit is not None else self.post.author.name, self.get_subreddit_icon ) self.builder.get_object('avatar_container').add(self.avatar) self.open_media_btn = self.builder.get_object('open_media_btn') can_open_media = isinstance(self.post, Submission) and ( self.post.is_video or self.post.is_reddit_media_domain or is_media(self.post.url) ) self.is_video = ( isinstance(self.post, Submission) and (self.post.is_video or 'https://v.redd.it' in self.post.url) ) self.open_media_btn.set_visible(can_open_media) self.open_media_btn.set_no_show_all(not can_open_media) self.open_media_btn.connect( 'clicked', lambda *args: self.open_media() ) self.flairs_container = self.builder.get_object('flairs_container') if isinstance(self.post, Submission): type_flair = None if self.post.is_self: type_flair = FlairLabel.new_type_text() elif self.is_video: type_flair = FlairLabel.new_type_video() elif self.post.is_reddit_media_domain: type_flair = FlairLabel.new_type_image() else: type_flair = FlairLabel.new_type_link() self.flairs_container.add(type_flair) if self.post.link_flair_text: self.flairs_container.add(FlairLabel.new_from_post(self.post)) self.image = None self.image_container = self.builder.get_object('image_container') self.set_post_image() self.open_link_btn = self.builder.get_object('open_link_btn') if hasattr(self.post, 'url'): self.open_link_btn.set_visible(True) self.open_link_btn.set_no_show_all(False) if 'http://' in self.post.url or 'https://' in self.post.url: self.open_link_btn.connect( 'clicked', self.open_link ) else: reddit = self.confman.reddit self.open_link_btn.connect( 'clicked', lambda *args: self.get_toplevel().main_ui.deck.show_post( reddit.submission(url=( 'https://reddit.com' + ('/' if self.post.url[0] != '/' else '') + self.post.url )) ) ) else: self.open_link_btn.set_visible(False) self.open_link_btn.set_no_show_all(True) def open_link(self, *args): url = self.post.url print(url) if self.confman.conf['twitter2nitter']: for pfx in ('twitter.com', 'www.twitter.com', 'mobile.twitter.com'): if f'://{pfx}' in url: url = url.replace(f'://{pfx}', '://nitter.net') break if self.confman.conf['youtube2invidious']: inv = self.confman.conf['invidious_instance'] for pfx in ('youtube.com', 'www.youtube.com'): if f'://{pfx}' in url: url = url.replace(f'://{pfx}', f'://{inv}') break print(url) Gio.AppInfo.launch_default_for_uri(url) def get_subreddit_icon(self): if ( self.post.subreddit is not None and is_image(self.post.subreddit.icon_img) ): return download_img(self.post.subreddit.icon_img) def set_post_image(self): def af(): post_img = self.get_post_image_filename() if post_img is not None: GLib.idle_add(cb, post_img) def cb(post_img): self.image = PictureView(post_img, self.is_video) self.image_container.add(self.image) self.image_container.show_all() self.image.set_vexpand(False) Thread(target=af).start() def open_media(self): def af(): path = None if self.is_video: path = download_video( self.post.media['reddit_video']['fallback_url'] if self.post.media else self.post.url+'/DASH_1080.mp4' ) else: path = download_img(self.post.url) if path is not None: Gio.AppInfo.launch_default_for_uri( GLib.filename_to_uri(path) ) Thread(target=af).start() def get_post_image_filename(self): if isinstance(self.post, Comment): return None image = 'No image' try: image = self.post.url if not is_image(image): if not hasattr(self.post, 'preview'): return None image = max([ p for p in self.post.preview['images'][0]['resolutions'] if p['width'] <= 400 ], key=lambda prev: prev['width'])['url'] if is_image(image): image_path = download_img(image) return image_path except Exception: print(f'Error creating pixbuf for post image `{image}`') return None giara-0.3/giara/confManager.py000066400000000000000000000130501376474030500163240ustar00rootroot00000000000000# from gettext import gettext as _ from giara.constants import APP_ID from pathlib import Path from os.path import isfile from os import environ as Env from os import makedirs import json from gi.repository import GObject from giara.singleton import Singleton from giara.notification_manager import NotifManager SCOPES = [ 'identity', 'history', 'mysubreddits', 'read', 'save', 'report', 'submit', 'subscribe', 'vote', 'account', 'edit', 'livemanage', 'flair', 'privatemessages' ] class ConfManagerSignaler(GObject.Object): __gsignals__ = { 'dark_mode_changed': ( GObject.SignalFlags.RUN_FIRST, None, (str,) ), 'on_show_thumbnails_in_preview_changed': ( GObject.SignalFlags.RUN_FIRST, None, (str,) ), 'notif_count_change': ( GObject.SignalFlags.RUN_FIRST, None, (str,) ) } class ConfManager(metaclass=Singleton): BASE_SCHEMA = { 'windowsize': { 'width': 350, 'height': 650 }, 'newentity_windowsize': { 'width': 340, 'height': 500 }, 'dark_mode': False, 'default_front_page_view': 'best', 'refresh_token': '', 'max_picture_width': 0, 'show_thumbnails_in_preview': True, 'twitter2nitter': False, 'youtube2invidious': False, 'invidious_instance': 'invidious.site' } def __init__(self): self.window = None self.__reddit = None self.__application = None self.notifman = None self.signaler = ConfManagerSignaler() self.emit = self.signaler.emit self.connect = self.signaler.connect # check if inside flatpak sandbox self.is_flatpak = ( 'XDG_RUNTIME_DIR' in Env.keys() and isfile(f'{Env["XDG_RUNTIME_DIR"]}/flatpak-info') ) if self.is_flatpak: self.path = Path( f'{Env.get("XDG_CONFIG_HOME")}/{APP_ID}.json' ) self.cache_path = Path( f'{Env.get("XDG_CACHE_HOME")}/{APP_ID}' ) else: self.path = Path( f'{Env.get("HOME")}/.config/{APP_ID}.json' ) self.cache_path = Path( f'{Env.get("HOME")}/.cache/{APP_ID}' ) self.thumbs_cache_path = self.cache_path.joinpath('thumbnails') self.conf = None if self.path.is_file(): try: with open(str(self.path)) as fd: self.conf = json.loads(fd.read()) # verify that the file has all of the schema keys for k in ConfManager.BASE_SCHEMA: if k not in self.conf.keys(): if isinstance( ConfManager.BASE_SCHEMA[k], (list, dict) ): self.conf[k] = ConfManager.BASE_SCHEMA[k].copy() else: self.conf[k] = ConfManager.BASE_SCHEMA[k] except Exception: self.conf = ConfManager.BASE_SCHEMA.copy() self.save_conf() else: self.conf = ConfManager.BASE_SCHEMA.copy() self.save_conf() if self.conf['default_front_page_view'] not in ( 'best', 'hot', 'new', 'top', 'rising', 'controversial' ): self.conf['default_front_page_view'] = 'best' self.save_conf() for p in (self.cache_path, self.thumbs_cache_path): if not p.is_dir(): makedirs(str(p)) def attempt_create_notifman(self): if ( self.notifman is None and self.__reddit is not None and self.__application is not None ): self.notifman = NotifManager( self.__reddit, self.__application, self.emit ) @property def reddit(self): if self.__reddit is None: raise AttributeError( 'Attempting to access uninitialized reddit client instance' ) else: return self.__reddit self.attempt_create_notifman() @reddit.setter def reddit(self, reddit): if self.__reddit is None: if reddit.auth.scopes() != set(SCOPES): raise ValueError( "Current scopes don't match with the needed scopes" ) else: self.__reddit = reddit self.reddit_user_me = self.__reddit.user.me() self.attempt_create_notifman() else: raise AttributeError( 'Attempting to re-assing read only reddit client instance' ) @property def application(self): if self.__application is None: raise AttributeError( 'Attempting to access uninitialized application instance' ) else: return self.__application self.attempt_create_notifman() @application.setter def application(self, application): if self.__application is None: self.__application = application self.attempt_create_notifman() else: raise AttributeError( 'Attempting to re-assing read only application instance' ) def save_conf(self, *args): with open(str(self.path), 'w') as fd: fd.write(json.dumps(self.conf)) giara-0.3/giara/constants.py000066400000000000000000000002441376474030500161210ustar00rootroot00000000000000# these variables are set from within bin/@appname@.py.in APP_ID = 'org.gabmus.giara' PROFILE = 'default' APP_NAME = 'Giara' RESOURCE_PREFIX = '/org/gabmus/giara' giara-0.3/giara/download_manager.py000066400000000000000000000034211376474030500174060ustar00rootroot00000000000000import requests from giara.confManager import ConfManager from giara.path_utils import get_file_extension, sha256sum from os.path import isfile from os import remove from subprocess import Popen confman = ConfManager() GET_HEADERS = { 'User-Agent': 'giara/1.0', 'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate' } TIMEOUT = 30 def __error_out(link: str, code: int): raise requests.HTTPError( f'response code {code} for url `{link}`' ) def download_img(link: str, force_extension: str = None) -> str: extension = force_extension or get_file_extension(link) dest = f'{confman.cache_path}/{sha256sum(link)}.{extension}' if not isfile(dest): res = requests.get(link, headers=GET_HEADERS, timeout=TIMEOUT) if 200 <= res.status_code <= 299: with open(dest, 'wb') as fd: for chunk in res.iter_content(1024): fd.write(chunk) else: # __error_out(link, res.status_code) return None return dest def download_video(link: str) -> str: dest = f'{confman.cache_path}/{sha256sum(link)}_video_audio.mkv' if not isfile(dest): extension = get_file_extension(link) or 'mp4' video = download_img(link, extension) audio = download_img(link[:link.rfind('/')]+'/DASH_audio.mp4', 'mp4') if audio is None: return video command = ' '.join([ 'ffmpeg', '-y', '-i', f'"{video}"', '-i', f'"{audio}"', '-c', 'copy', '-map', '0:v:0', '-map', '1:a:0', '-shortest', f'"{dest}"' ]) p = Popen( command, shell=True ) p.wait() remove(video) remove(audio) return dest giara-0.3/giara/flair_label.py000066400000000000000000000044001376474030500163370ustar00rootroot00000000000000from gettext import gettext as _ from gi.repository import Gtk, Pango from praw.models import Comment, Submission, Message class FlairLabel(Gtk.Label): @classmethod def new_from_type(cls, item): if isinstance(item, Comment): return cls( _('Comment'), '#3584e4', 'light' ) elif isinstance(item, Submission): return cls( _('Post'), '#3584e4', 'light' ) elif isinstance(item, Message): return cls( _('Message'), '#3584e4', 'light' ) else: raise TypeError( 'Cannot create flair for unknown type '+type(item) ) @classmethod def new_from_post(cls, post): return cls( post.link_flair_text, post.link_flair_background_color, post.link_flair_text_color ) @classmethod def new_type_image(cls): return cls( _('Image'), '#33d17a', 'dark' ) @classmethod def new_type_video(cls): return cls( _('Video'), '#f6d32d', 'dark' ) @classmethod def new_type_text(cls): return cls( _('Text'), '#986a44', 'light' ) @classmethod def new_type_link(cls): return cls( _('Link'), '#3584e4', 'light' ) def __init__(self, text, bg, fg, *args, **kwargs): super().__init__(text, *args, **kwargs) self.set_hexpand(False) self.set_halign(Gtk.Align.START) self.set_line_wrap(True) self.set_line_wrap_mode(Pango.WrapMode.WORD_CHAR) style_context = self.get_style_context() style_context.add_class('flair') if fg and bg: flair_name = (fg+bg).replace('#', '0x') css = f'''.flair-{flair_name} {{ background-color: {bg}; color: {"white" if fg == "light" else "black"}; }}''' css_provider = Gtk.CssProvider() css_provider.load_from_data(css.encode()) style_context.add_provider_for_screen( self.get_screen(), css_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER ) style_context.add_class(f'flair-{flair_name}') giara-0.3/giara/front_page_headerbar.py000066400000000000000000000150261376474030500202320ustar00rootroot00000000000000from giara.constants import RESOURCE_PREFIX from gettext import gettext as _, ngettext from gi.repository import Gtk, Gio from giara.new_post_window import NewPostWindow from giara.download_manager import download_img from giara.squeezing_viewswitcher_headerbar import \ SqueezingViewSwitcherHeaderbar from giara.simple_avatar import SimpleAvatar class FrontPageHeaderbar(SqueezingViewSwitcherHeaderbar): def __init__(self, front_page_stack, reddit, **kwargs): super().__init__( Gtk.Builder.new_from_resource( f'{RESOURCE_PREFIX}/ui/headerbar.ui' ), front_page_stack, view_switcher=False, sort_menu=True, sorting_methods={ 'best': { 'name': _('Best'), 'icon': 'best-symbolic' }, 'hot': { 'name': _('Hot'), 'icon': 'hot-symbolic' }, 'new': { 'name': _('New'), 'icon': 'new-symbolic' }, 'top': { 'name': _('Top'), 'icon': 'arrow1-up-symbolic' }, 'rising': { 'name': _('Rising'), 'icon': 'rising-symbolic' }, 'controversial': { 'name': _('Controversial'), 'icon': 'controversial-symbolic' } }, default_method='from_conf', **kwargs ) # self.reddit = reddit # TODO: remove this self.builder.connect_signals(self) self.menu_btn = self.builder.get_object( 'menu_btn' ) self.menu_popover = self.builder.get_object('menu_popover') self.menu_builder = Gtk.Builder.new_from_resource( f'{RESOURCE_PREFIX}/ui/menu.ui' ) self.menu = self.menu_builder.get_object('generalMenu') self.menu_popover.bind_model(self.menu) self.new_post_action_group = Gio.SimpleActionGroup() for a in ('link', 'text', 'media'): c_action = Gio.SimpleAction.new(a, None) c_action.connect( 'activate', self.on_new_clicked ) self.new_post_action_group.add_action(c_action) self.insert_action_group('newpost', self.new_post_action_group) self.new_btn = self.builder.get_object('new_btn') self.new_post_popover = self.builder.get_object('new_post_popover') self.new_post_menu_builder = Gtk.Builder.new_from_resource( f'{RESOURCE_PREFIX}/ui/new_post_menu.ui' ) self.new_post_menu = self.new_post_menu_builder.get_object( 'newPostMenu' ) self.new_post_popover.bind_model(self.new_post_menu) self.profile_btn = self.builder.get_object('profile_btn') self.profile_popover = self.builder.get_object('profile_popover') self.username_label = self.builder.get_object('username_label') self.karma_label = self.builder.get_object('karma_label') self.user = self.reddit.user.me() self.username_label.set_text(f'u/{self.user.name}') self.karma_label.set_text(ngettext( '{0} Karma', '{0} Karma', self.user.total_karma ).format(self.user.total_karma)) self.avatar_container = self.builder.get_object('avatar_container') self.avatar = SimpleAvatar( 42, self.user.name, lambda *args: download_img(self.user.icon_img) ) self.avatar_container.add(self.avatar) self.avatar_container.show_all() self.profile_btn.connect( 'clicked', lambda *args: self.profile_popover.popup() ) self.go_subreddits_btn = self.builder.get_object('go_subreddits_btn') self.go_subreddits_btn.connect( 'clicked', lambda *args: self.profile_popover.popdown() ) self.go_saved_btn = self.builder.get_object('go_saved_btn') self.go_saved_btn.connect( 'clicked', lambda *args: self.profile_popover.popdown() ) self.go_profile_btn = self.builder.get_object('go_profile_btn') self.go_profile_btn.connect( 'clicked', lambda *args: self.profile_popover.popdown() ) self.go_inbox_btn = self.builder.get_object('go_inbox_btn') self.go_inbox_btn.connect( 'clicked', lambda *args: self.profile_popover.popdown() ) self.go_logout_btn = self.builder.get_object('go_logout_btn') self.go_logout_btn.connect( 'clicked', self.on_logout ) self.go_multireddits_btn = self.builder.get_object( 'go_multireddits_btn' ) self.go_multireddits_btn.connect( 'clicked', lambda *args: self.profile_popover.popdown() ) self.inbox_badge = self.builder.get_object('inbox_count_badge_label') self.confman.connect( 'notif_count_change', self.on_inbox_count_change ) self.refresh_btn = self.builder.get_object('refresh_btn') self.search_btn = self.builder.get_object('search_btn') def on_logout(self, *args): self.profile_popover.popdown() dialog = Gtk.MessageDialog( self.get_toplevel(), Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO, _('Do you want to log out? This will close the application.') ) res = dialog.run() dialog.close() if res == Gtk.ResponseType.YES: self.confman.conf['refresh_token'] = '' self.confman.save_conf() self.get_toplevel().destroy() def on_inbox_count_change(self, caller, count): if int(count) <= 0: self.inbox_badge.set_text('') self.inbox_badge.set_visible(False) self.inbox_badge.set_no_show_all(True) else: self.inbox_badge.set_text(count) self.inbox_badge.set_visible(True) self.inbox_badge.set_no_show_all(False) self.inbox_badge.show() def on_menu_btn_clicked(self, *args): self.menu_popover.popup() def on_new_clicked(self, action, param): self.new_post_popover.popdown() np_win = NewPostWindow(self.reddit, action.get_name()) np_win.set_transient_for(self.get_toplevel()) np_win.present() giara-0.3/giara/giara_clamp.py000066400000000000000000000005511376474030500163450ustar00rootroot00000000000000from gi.repository import Handy class GiaraClamp(Handy.Clamp): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.set_maximum_size(1200) self.set_tightening_threshold(1000) self.set_margin_start(12) self.set_margin_end(12) self.set_margin_top(12) self.set_margin_bottom(12) giara-0.3/giara/image_utils.py000066400000000000000000000044121376474030500164100ustar00rootroot00000000000000from giara.confManager import ConfManager from PIL import Image, ImageFilter from pathlib import Path from gi.repository import GdkPixbuf from subprocess import Popen from giara.path_utils import sha256sum confman = ConfManager() def make_thumb(path, width: int, height: int = 1000) -> str: if not path: return None if not isinstance(path, Path): path = Path(path) dest = confman.thumbs_cache_path.joinpath(f'{width}x{height}_{path.name}') if dest.is_file(): return str(dest) try: with Image.open(path) as thumb: thumb = Image.open(path) thumb.thumbnail((width, height), Image.ANTIALIAS) thumb.save(dest, 'PNG') return str(dest) except IOError: print(f'Error creating thumbnail for image `{path}`') return None avatar_cache = dict() def set_avatar_func(icon: str, size: int) -> GdkPixbuf.Pixbuf: if icon is None: return None key = f'{icon}__{size}' if key in avatar_cache.keys(): return avatar_cache[key] pixbuf = None try: pixbuf = GdkPixbuf.Pixbuf.new_from_file( make_thumb(icon, size, size) ) except Exception: print(f'Error creating pixbuf for icon `{icon}`') avatar_cache[key] = pixbuf return pixbuf def make_video_thumb(path) -> str: if not path: return None if not isinstance(path, Path): path = Path(path) dest = confman.thumbs_cache_path.joinpath( f'{sha256sum(str(path))}_upload_thumb.png' ) command = ' '.join([ 'ffmpeg', '-y', '-i', f'"{path}"', '-ss', '00:00:01.000', '-vframes', '1', f'"{dest}"' ]) p = Popen( command, shell=True ) p.wait() return str(dest) def blur_image(path) -> str: if not path: return None if not isinstance(path, Path): path = Path(path) dest = confman.thumbs_cache_path.joinpath( f'{path.stem}_blur.png' ) try: with Image.open(str(path)) as img: blurred = img.filter(ImageFilter.GaussianBlur(60)) blurred.save(dest, 'PNG') blurred.close() return dest except IOError: print(f'Error creating blur for image `{path}`') return None giara-0.3/giara/inbox_view.py000066400000000000000000000115051376474030500162600ustar00rootroot00000000000000from giara.constants import RESOURCE_PREFIX from gettext import gettext as _ from gi.repository import Gtk, GLib from giara.simple_avatar import SimpleAvatar from giara.path_utils import is_image from giara.download_manager import download_img from giara.time_utils import humanize_utc_timestamp from giara.flair_label import FlairLabel from giara.post_preview import PostPreviewListbox from giara.giara_clamp import GiaraClamp from giara.single_post_stream_headerbar import SinglePostStreamHeaderbar from giara.markdown_view import MarkdownView from giara.sections_stack import SectionScrolledWindow from praw.models import Comment, Submission from threading import Thread class InboxItemView(Gtk.Bin): def __init__(self, item, **kwargs): super().__init__(**kwargs) self.item = item self.builder = Gtk.Builder.new_from_resource( f'{RESOURCE_PREFIX}/ui/inbox_item_view.ui' ) self.main_box = self.builder.get_object('main_box') self.markdown_container = self.builder.get_object('markdown_container') self.markdown_view = MarkdownView(self.item.body) self.markdown_container.add(self.markdown_view) self.avatar_container = self.builder.get_object('avatar_container') self.avatar = SimpleAvatar( 42, self.item.author.name, self.get_author_img ) self.avatar_container.add(self.avatar) self.author_label = self.builder.get_object('author_label') self.author_label.set_text( 'u/' + self.item.author.name if self.item.author is not None else _('Author unknown') ) def set_post_title_async(): if isinstance(self.item, Comment): title = self.item.submission.title GLib.idle_add(set_post_title_cb, title) def set_post_title_cb(title): self.author_label.set_text( self.author_label.get_text() + '\n' + _('Comment in "{0}"').format(title) ) Thread(target=set_post_title_async).start() self.datetime_label = self.builder.get_object('datetime_label') self.datetime_label.set_text( humanize_utc_timestamp(self.item.created_utc) ) self.flairs_container = self.builder.get_object('flairs_container') self.flairs_container.add( FlairLabel.new_from_type(self.item) ) self.new_flair = None if self.item.new: self.new_flair = FlairLabel(_('New'), '#33d17a', 'dark') self.flairs_container.add(self.new_flair) else: self.mark_read() self.add(self.main_box) def get_author_img(self): if ( self.item.author is not None and is_image(self.item.author.icon_img) ): download_img(self.item.author.icon_img) def mark_read(self): def af(): self.item.mark_read() Thread(target=af).start() for w in ( self.author_label, self.flairs_container, self.markdown_view, self.avatar_container ): w.get_style_context().add_class('dim-label') if self.new_flair is not None: self.flairs_container.remove(self.new_flair) self.new_flair = None class InboxListboxRow(Gtk.ListBoxRow): def __init__(self, item, **kwargs): super().__init__(**kwargs) self.item = item self.post = None if isinstance(item, Comment) or isinstance(item, Submission): self.post = self.item self.view = InboxItemView(self.item) self.add(self.view) self.mark_read = self.view.mark_read class InboxListbox(PostPreviewListbox): def __init__(self, post_gen_func, show_post_func, load_now=True, **kwargs): super().__init__(post_gen_func, show_post_func, load_now, **kwargs) def _on_post_preview_row_loaded(self, target): row = InboxListboxRow(target) self.add(row) row.show_all() def on_row_activate(self, lb, row): row.mark_read() super().on_row_activate(lb, row) class InboxListView(Gtk.Box): def __init__(self, post_gen_func, show_post_func, load_now=True, **kwargs): super().__init__(orientation=Gtk.Orientation.VERTICAL, **kwargs) self.listbox = InboxListbox(post_gen_func, show_post_func, load_now) self.sw = SectionScrolledWindow(self.listbox) self.clamp = GiaraClamp() self.clamp.add(self.listbox) self.sw.add(self.clamp) self.headerbar = SinglePostStreamHeaderbar(_('Inbox')) self.headerbar.refresh_btn.connect( 'clicked', lambda *args: self.listbox.refresh() ) self.add(self.headerbar) self.headerbar.set_vexpand(False) self.add(self.sw) self.sw.set_vexpand(True) giara-0.3/giara/left_stack.py000066400000000000000000000255371376474030500162400ustar00rootroot00000000000000from gettext import gettext as _ from gi.repository import Gtk from giara.sections_stack import SectionsStack from giara.front_page_headerbar import FrontPageHeaderbar from giara.single_post_stream_view import SinglePostStreamView from giara.subreddits_list_view import SubredditsListView from giara.subreddit_view import SubredditView from giara.search_view import SearchView from giara.user_view import UserView from giara.confManager import ConfManager from giara.subreddit_search_view import SubredditSearchView from giara.accel_manager import add_mouse_button_accel from praw.models import Redditor from giara.multireddit_list_view import MultiredditsListView from giara.multireddit_view import MultiredditView from giara.inbox_view import InboxListView class LeftStack(Gtk.Stack): def __init__(self, show_post_func, **kwargs): super().__init__(**kwargs) self.confman = ConfManager() self.reddit = self.confman.reddit self.user_me = self.reddit.user.me() self.set_transition_type(Gtk.StackTransitionType.CROSSFADE) self.show_post_func = show_post_func # Child 1: Front page stack and respective headerbar self.front_page_view = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.front_page_stack = SectionsStack([ { 'name': 'posts', 'title': _('Posts'), 'gen': getattr( self.reddit.front, self.confman.conf['default_front_page_view'] ) } ], show_post_func, load_now=False) self.front_page_headerbar = FrontPageHeaderbar( self.front_page_stack, self.reddit ) self.front_page_view.headerbar = self.front_page_headerbar self.front_page_view.add(self.front_page_headerbar) self.front_page_headerbar.set_vexpand(False) self.front_page_headerbar.set_hexpand(True) self.front_page_headerbar.refresh_btn.connect( 'clicked', self.front_page_stack.refresh ) self.front_page_view.add(self.front_page_stack) self.front_page_stack.set_hexpand(True) self.front_page_stack.set_vexpand(True) self.front_page_headerbar.connect( 'squeeze', lambda caller, squeezed: self.front_page_bottom_bar.set_reveal( squeezed ) ) self.add_titled( self.front_page_view, 'front_page', _('Front page') ) # Child 2: Saved items stack (forcedly a stack to preserve structure) self.saved_view = SinglePostStreamView( self.user_me.saved, 'saved', _('Saved posts'), show_post_func, sort_menu=False ) self.add_titled( self.saved_view, 'saved_view', _('Saved posts') ) self.front_page_view.headerbar.go_saved_btn.connect( 'clicked', lambda *args: self.set_visible_child(self.saved_view) ) # Child 3: Profile stack (forcedly a stack to preserve structure) self.profile_view = SinglePostStreamView( self.user_me.new, 'profile', _('Profile'), show_post_func, load_now=False, sort_menu=False ) self.add_titled( self.profile_view, 'profile_view', _('Profile') ) def on_go_profile(*args): self.set_visible_child(self.profile_view) self.profile_view.section_stack.on_visible_child_change() self.front_page_view.headerbar.go_profile_btn.connect( 'clicked', on_go_profile ) # Child 4: Subreddits list view self.subreddits_list_view = SubredditsListView( lambda *args, **kwargs: self.reddit.user.subreddits( *args, **kwargs ), self.show_subreddit_func, sort=True ) self.add_titled( self.subreddits_list_view, 'subreddits', _('Subreddits') ) self.front_page_view.headerbar.go_subreddits_btn.connect( 'clicked', lambda *args: self.set_visible_child(self.subreddits_list_view) ) # Child 5: Single subreddit view, initialized in show_subreddit_func self.single_subreddit_view = None # Child 6: Single subreddit search view # closely related to 5, created along 5 self.single_subreddit_search_view = None # Child 7: Search view self.search_view = SearchView( show_post_func, lambda *args, **kwargs: self.show_subreddit_func( *args, **kwargs, prev_view=self.search_view ) ) self.add_titled( self.search_view, 'search', _('Search') ) self.front_page_view.headerbar.search_btn.connect( 'clicked', lambda *args: self.set_visible_child(self.search_view) ) # Child 8: Inbox view self.inbox_view = InboxListView( self.reddit.inbox.all, show_post_func, load_now=False ) self.add_titled( self.inbox_view, 'inbox_view', _('Inbox') ) self.front_page_view.headerbar.go_inbox_btn.connect( 'clicked', self.on_go_inbox ) # Child 9: Multireddits list view self.multireddits_list_view = MultiredditsListView( self.show_multireddit_func ) self.add_titled( self.multireddits_list_view, 'multireddits', _('Multireddits') ) self.front_page_view.headerbar.go_multireddits_btn.connect( 'clicked', lambda *args: self.set_visible_child(self.multireddits_list_view) ) # Child 10: Single multireddit view, # initialized in show_multireddit_func self.single_multireddit_view = None for view in ( self.saved_view, self.profile_view, self.subreddits_list_view, self.search_view, self.inbox_view, self.multireddits_list_view ): def go_back(*args): self.set_visible_child(self.front_page_view) def on_mouse_event(gesture, n_press, x, y): if gesture.get_current_button() == 8: # Mouse back btn go_back() view.headerbar.back_btn.connect('clicked', go_back) # the mouse accelerator needs to be kept around, thus this useless # assignment view.mouse_btn_accel = add_mouse_button_accel( view, on_mouse_event ) view.children_mouse_btn_accel = [add_mouse_button_accel( child, on_mouse_event ) for child in view.get_children()] def on_go_inbox(self, *args): self.set_visible_child(self.inbox_view) self.inbox_view.listbox.refresh() def get_headerbar(self): return self.get_visible_child().headerbar def show_multireddit_func(self, multi): if self.single_multireddit_view is not None: self.remove(self.single_multireddit_view) self.single_multireddit_view = MultiredditView( multi, self.show_post_func ) self.add_titled( self.single_multireddit_view, multi.display_name, multi.display_name ) def back_to_prev(*args): self.set_visible_child(self.multireddits_list_view) def on_mouse_event(gesture, n_press, x, y): if gesture.get_current_button() == 8: # Mouse back btn back_to_prev() self.single_multireddit_view.headerbar.back_btn.connect( 'clicked', back_to_prev ) self.single_multireddit_view.mouse_btn_accel = add_mouse_button_accel( self.single_multireddit_view, on_mouse_event ) self.single_multireddit_view.show_all() self.set_visible_child(self.single_multireddit_view) def show_subreddit_func(self, sub, prev_view=None): if prev_view is None: prev_view = self.subreddits_list_view for view in ( self.single_subreddit_view, self.single_subreddit_search_view ): if view is not None: self.remove(view) if isinstance(sub, Redditor) or '/user/' in sub.url: redditor = sub if not isinstance(sub, Redditor): redditor = self.reddit.redditor( sub.display_name_prefixed.replace('u/', '') ) self.single_subreddit_view = UserView( redditor, self.show_post_func ) else: self.single_subreddit_view = SubredditView( sub, self.show_post_func ) self.single_subreddit_search_view = SubredditSearchView( sub, self.show_post_func ) def back_to_sub(*args): self.set_visible_child(self.single_subreddit_view) def on_mouse_event(gesture, n_press, x, y): if gesture.get_current_button() == 8: # Mouse back btn back_to_sub() self.single_subreddit_search_view.headerbar.back_btn.connect( 'clicked', back_to_sub ) self.single_subreddit_search_view.mouse_btn_accel = \ add_mouse_button_accel( self.single_subreddit_search_view, on_mouse_event ) def show_search(): self.single_subreddit_search_view.show_all() self.set_visible_child( self.single_subreddit_search_view ) self.single_subreddit_view.headerbar.search_btn.connect( 'clicked', lambda *args: show_search() ) self.add_titled( self.single_subreddit_search_view, f'search_{sub.display_name}', _('Searching in {0}').format(sub.display_name_prefixed) ) self.add_titled( self.single_subreddit_view, sub.display_name, sub.display_name_prefixed ) def back_to_prev(*args): self.set_visible_child(prev_view) def on_mouse_event(gesture, n_press, x, y): if gesture.get_current_button() == 8: # Mouse back btn back_to_prev() self.single_subreddit_view.headerbar.back_btn.connect( 'clicked', back_to_prev ) self.single_subreddit_view.mouse_btn_accel = add_mouse_button_accel( self.single_subreddit_view, on_mouse_event ) self.single_subreddit_view.show_all() self.set_visible_child(self.single_subreddit_view) giara-0.3/giara/main_deck.py000066400000000000000000000043451376474030500160250ustar00rootroot00000000000000from gi.repository import Gtk, Handy, GLib from giara.left_stack import LeftStack from giara.post_details_view import PostDetailsView from giara.confManager import ConfManager from giara.accel_manager import add_mouse_button_accel from praw.models import Comment, Message from threading import Thread class MainDeck(Handy.Deck): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.confman = ConfManager() self.reddit = self.confman.reddit self.left_stack = LeftStack(self.show_post) self.post_view_container = Gtk.Box( orientation=Gtk.Orientation.VERTICAL ) self.add(self.left_stack) self.add(self.post_view_container) self.left_stack.set_size_request(300, 100) self.set_can_swipe_back(True) self.set_can_swipe_forward(False) self._loading = False def show_post(self, post): if self._loading: return self._loading = True if isinstance(post, Message): return for child in self.post_view_container.get_children(): self.post_view_container.remove(child) def on_mouse_event(gesture, n_press, x, y): if gesture.get_current_button() == 8: # Mouse back btn self.go_back() def af(post): target_comment = None if isinstance(post, Comment): target_comment = post post = post.submission post._fetch() post.comments GLib.idle_add(cb, post, target_comment) def cb(post, target_comment): details_view = PostDetailsView( post, self.go_back, target_comment=target_comment ) details_view.mouse_btn_accel = add_mouse_button_accel( details_view, on_mouse_event ) self.post_view_container.add(details_view) details_view.set_vexpand(True) details_view.set_hexpand(True) self.set_visible_child(self.post_view_container) details_view.show_all() self._loading = False Thread(target=af, args=(post,)).start() def go_back(self): self.set_visible_child(self.left_stack) giara-0.3/giara/main_ui.py000066400000000000000000000024721376474030500155330ustar00rootroot00000000000000from giara.constants import RESOURCE_PREFIX from gi.repository import Gtk, GLib # , Gdk, GtkSource, GObject # from gettext import gettext as _ from giara.confManager import ConfManager from giara.main_deck import MainDeck class MainUI(Gtk.Bin): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.confman = ConfManager() self.reddit = self.confman.reddit self.builder = Gtk.Builder.new_from_resource( f'{RESOURCE_PREFIX}/ui/main_ui.ui' ) self.ui_box = self.builder.get_object('ui_box') self.notif_revealer = self.builder.get_object('revealer') self.notif_label = self.builder.get_object('notif_label') self.notif_close_btn = self.builder.get_object('notif_close_btn') self.notif_close_btn.connect('clicked', self.hide_notif) self.deck = MainDeck() self.ui_box.add(self.deck) self.deck.set_vexpand(True) self.add(self.builder.get_object('overlay1')) self.show_all() def show_notification(self, text=None): if text is not None: self.notif_label.set_text(text) self.notif_revealer.set_reveal_child(True) GLib.timeout_add_seconds(5, self.hide_notif) def hide_notif(self, *args): self.notif_revealer.set_reveal_child(False) giara-0.3/giara/markdown_view.py000066400000000000000000000117711376474030500167700ustar00rootroot00000000000000from gettext import gettext as _ from gi.repository import Gtk, Pango from bs4 import BeautifulSoup import mistune import re # Note: I've used the \uffff character quite a bit here. This character doesn't # represent anything, and it's not supposed to be found inside normal text. # I used it as a custom delimiter for special markdown elements that cannot # be represented using just pango markup. HEADER_LEVELS = [ 'xx-large', 'x-large', 'large', 'medium', 'small', 'x-small', 'xx-small' ] SUPERSCRIPT_REGEX = re.compile(r'\^\(.+\)|\^\S+') class PangoRenderer(mistune.Renderer): def __init__(self, *args, **kwargs): super().__init__(*args, escape=True, **kwargs) def text(self, text): return mistune.escape( text.replace( '\n', '\ufafa' ).replace( '\ufafa\ufafa', '\n\n' ).replace( '\ufafa', ' ' ) ) def link(self, link, title, text): return super().link(link, None, text) def image(self, src, alt='', title=None): return self.link( src, None, _('Image: ')+alt if isinstance(alt, str) else _('[Image]') ) def emphasis(self, text): return f'{text}' def double_emphasis(self, text): return f'{text}' def codespan(self, text): return ( '{0}' ).format(mistune.escape(text.rstrip(), smart_amp=False)) def block_code(self, code, lang=None): return f'\n{mistune.escape(code)}\n' def newline(self): return '\n' def list(self, body, ordered=True): return '\n'.join([ li.replace('\u2022', f'{i+1}.') for i, li in enumerate(body.split('\n')) ]) if ordered else body def list_item(self, text): # u2022 is a bullet sign return f'\u2022 {text}\n' def paragraph(self, text): return f'{text}\n\n' def strikethrough(self, text): return f'{text}' def linebreak(self): return '\n' def hrule(self): return '\n\uffff\uffff##HR##\uffff\uffff\n' def block_quote(self, text): # return mistune.escape('> '+text) return f'\n\uffff\uffff##BLOCKQUOTE##\n{text}\uffff\uffff' def table(self, header, body): return ( f'\n{header}\n{body}\n' ) def table_row(self, content): return f'{content}\n' def table_cell(self, content, **flags): return content + '\t\t\t' def header(self, text, level, raw=None): if level > len(HEADER_LEVELS): level = 0 return '\n{1}\n'.format( HEADER_LEVELS[level-1], text ) class MarkdownView(Gtk.Box): def __init__(self, markdown='', **kwargs): super().__init__(orientation=Gtk.Orientation.VERTICAL, **kwargs) self.markdown = '' self.pango_strings = [] self.parser = mistune.Markdown(renderer=PangoRenderer()) self.set_text(markdown) def empty(self): for child in self.get_children(): self.remove(child) def apply_superscript(self, txt): if '^' in txt: for match in SUPERSCRIPT_REGEX.findall(txt): txt = txt.replace(match, f'''{ match.replace("^", "").replace("(", "").replace(")", "") }''') return txt def append_label(self, pstr, is_quote=False): pstr = str(BeautifulSoup( pstr, 'html.parser' )) pstr = self.apply_superscript(pstr) label = Gtk.Label() label.set_use_markup(True) label.set_markup(pstr) label.set_selectable(True) label.set_line_wrap(True) label.set_line_wrap_mode(Pango.WrapMode.WORD_CHAR) label.set_justify(Gtk.Justification.FILL) if is_quote: label.get_style_context().add_class('markdown-quote') self.add(label) label.set_vexpand(False) label.set_hexpand(True) label.set_halign(Gtk.Align.START) def append_quote(self, pstr): return self.append_label(pstr, is_quote=True) def append_hr(self): separator = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL) separator.get_style_context().add_class('separator-hr') self.add(separator) separator.set_vexpand(False) separator.set_hexpand(True) def set_text(self, markdown): self.empty() self.markdown = markdown if markdown == '': return self.pango_strings = self.parser(self.markdown).split('\uffff\uffff') for pstr in self.pango_strings: if pstr == '##HR##': self.append_hr() elif '##BLOCKQUOTE##' in pstr: self.append_quote( pstr.replace('##BLOCKQUOTE##', '').strip() ) else: self.append_label(pstr.strip()) self.show_all() giara-0.3/giara/multireddit_list_view.py000066400000000000000000000073521376474030500205270ustar00rootroot00000000000000from gettext import gettext as _ from gi.repository import Gtk, GLib from giara.confManager import ConfManager from giara.download_manager import download_img from giara.path_utils import is_image from threading import Thread from giara.common_collection_listbox_row import CommonCollectionListboxRow from giara.giara_clamp import GiaraClamp from giara.single_post_stream_headerbar import SinglePostStreamHeaderbar class MultiredditsListboxRow(CommonCollectionListboxRow): def __init__(self, multi, **kwargs): self.multi = multi super().__init__( '', self.multi.display_name, self.get_icon, **kwargs ) def get_icon(self, *args): if is_image(self.multi.icon_url): return download_img(self.multi.icon_url) def get_key(self): return self.multi.display_name.lower() class MultiredditsListbox(Gtk.ListBox): def __init__(self, show_multi_func=None, **kwargs): super().__init__(**kwargs) self.confman = ConfManager() self.reddit = self.confman.reddit self.show_multi_func = show_multi_func self.get_style_context().add_class('card') self.multis = list() # self.front_row = None self.set_sort_func(self.sort_func, None, False) self.set_header_func(self.separator_header_func) self.populate() self.set_selection_mode(Gtk.SelectionMode.NONE) self.connect('row-activated', self.on_row_activate) def on_row_activate(self, lb, row): if row.multi is not None and self.show_multi_func is not None: self.show_multi_func(row.multi) def sort_func(self, row1, row2, data, notify_destroy): # if row1 == self.front_row: # return False # if row2 == self.front_row: # return True return row1.get_key().lower() > row2.get_key().lower() def separator_header_func(self, row, prev_row): if ( prev_row is not None and row.get_header() is None ): row.set_header( Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL) ) def empty(self): while True: row = self.get_row_at_index(0) if row: self.remove(row) else: break def populate(self): self.empty() # self.front_row = CommonCollectionListboxRow( # '', # _('Front page'), # lambda *args: None # ) # self.add(self.front_row) # self.select_row(self.front_row) def af(): self.multis = self.reddit.user.multireddits() for multi in self.multis: multi._fetch() GLib.idle_add(cb, multi) def cb(multi): self.add(MultiredditsListboxRow(multi)) self.show_all() Thread(target=af).start() class MultiredditsListView(Gtk.Box): def __init__(self, show_multi_func, **kwargs): super().__init__(orientation=Gtk.Orientation.VERTICAL, **kwargs) self.show_multi_func = show_multi_func self.sw = Gtk.ScrolledWindow() self.sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) self.listbox = MultiredditsListbox(self.show_multi_func) self.clamp = GiaraClamp() self.clamp.add(self.listbox) self.sw.add(self.clamp) self.headerbar = SinglePostStreamHeaderbar(_('Multireddits')) self.headerbar.refresh_btn.connect( 'clicked', lambda *args: self.listbox.populate() ) self.add(self.headerbar) self.headerbar.set_vexpand(False) self.headerbar.set_hexpand(True) self.add(self.sw) self.sw.set_vexpand(True) giara-0.3/giara/multireddit_view.py000066400000000000000000000061241376474030500174700ustar00rootroot00000000000000from gettext import gettext as _ from gi.repository import Gtk, GLib from giara.single_post_stream_view import SinglePostStreamView from giara.subreddits_list_view import SubredditsListboxRow from threading import Thread class MultiredditEditPopover(Gtk.Popover): def __init__(self, multi, relative_to, **kwargs): super().__init__(**kwargs) self.multi = multi self.relative_to = relative_to self.set_modal(True) self.set_relative_to(self.relative_to) self.set_size_request(300, 400) self.sw = Gtk.ScrolledWindow() self.sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) self.sw.get_style_context().add_class('card') self.sw.set_margin_top(6) self.sw.set_margin_bottom(6) self.sw.set_margin_start(6) self.sw.set_margin_end(6) self.sw.show_all() self.subs_lbox = Gtk.ListBox() self.subs_lbox.set_selection_mode(Gtk.SelectionMode.NONE) self.sw.add(self.subs_lbox) self.add(self.sw) self.populate() def empty(self): while True: row = self.subs_lbox.get_row_at_index(0) if row: self.subs_lbox.remove(row) else: break def populate(self): self.empty() def af(): self.multi._fetch() for sub in self.multi.subreddits: sub._fetch() GLib.idle_add(cb, sub) def cb(sub): row = SubredditsListboxRow(sub) def on_remove_sub(*args): Thread(target=lambda *args: self.multi.remove(sub)).start() self.subs_lbox.remove(row) row.delete_btn = Gtk.Button.new_from_icon_name( 'user-trash-symbolic', Gtk.IconSize.BUTTON ) row.delete_btn.get_style_context().add_class('destructive-action') row.delete_btn.get_style_context().remove_class('image-button') row.delete_btn.set_tooltip_text(_('Remove from multireddit')) row.delete_btn.connect( 'clicked', on_remove_sub ) row.main_box.pack_end(row.delete_btn, False, False, 6) row.delete_btn.set_valign(Gtk.Align.CENTER) self.subs_lbox.add(row) self.sw.show_all() Thread(target=af).start() class MultiredditView(SinglePostStreamView): def __init__(self, multi, show_post_func, source=None): self.multi = multi super().__init__( self.multi.hot, self.multi.display_name, self.multi.display_name, show_post_func, source=self.multi ) self.edit_btn = Gtk.MenuButton() self.edit_btn.add(Gtk.Image.new_from_icon_name( 'document-edit-symbolic', Gtk.IconSize.BUTTON )) self.edit_btn.set_tooltip_text(_('Edit multireddit')) self.edit_popover = MultiredditEditPopover(self.multi, self.edit_btn) self.edit_btn.set_popover(self.edit_popover) self.headerbar.headerbar.pack_end(self.edit_btn) self.headerbar.show_all() giara-0.3/giara/new_post_window.py000066400000000000000000000356731376474030500173500ustar00rootroot00000000000000from giara.constants import RESOURCE_PREFIX from gettext import gettext as _ from gi.repository import Gtk, Gdk, GtkSource, GObject, GLib from os.path import isfile from giara.path_utils import is_image, is_video from giara.image_utils import make_video_thumb from giara.subreddits_list_view import SubredditsListbox from giara.choice_picker import ChoicePickerButton from giara.flair_label import FlairLabel from giara.confManager import ConfManager from praw.models import Submission from threading import Thread class CommonNewEntityWindow(Gtk.Window): __gsignals__ = { 'file-chosen': ( GObject.SignalFlags.RUN_LAST, None, (str,) ) } def __init__(self, widgets_to_hide, widgets_to_show, **kwargs): super().__init__(**kwargs) self.set_modal(True) self.set_type_hint(Gdk.WindowTypeHint.DIALOG) # self.set_size_request(340, 500) self.confman = ConfManager() # Why this -52? # because every time a new value is saved, for some reason # it's the actual value +52 out of nowhere # this makes the window ACTUALLY preserve its old size self.resize( self.confman.conf['newentity_windowsize']['width']-52, self.confman.conf['newentity_windowsize']['height']-52 ) print(self.confman.conf['newentity_windowsize']) self.size_allocation = self.get_allocation() def update_size_allocation(*args): self.size_allocation = self.get_allocation() self.connect('size-allocate', update_size_allocation) self.builder = Gtk.Builder.new_from_resource( f'{RESOURCE_PREFIX}/ui/new_post_window.ui' ) self.title_entry = self.builder.get_object('title_entry') self.link_entry = self.builder.get_object('link_entry') self.text_source_view_container = self.builder.get_object( 'text_source_view_container' ) self.choice_picker_container = self.builder.get_object( 'choice_picker_container' ) self.headerbar = self.builder.get_object('headerbar') self.set_titlebar(self.headerbar) self.add(self.builder.get_object('inner_box')) self.widgets_to_hide = widgets_to_hide self.widgets_to_show = widgets_to_show for w in self.widgets_to_hide: w = self.builder.get_object(w) w.set_visible(False) w.set_no_show_all(True) for w in self.widgets_to_show: w = self.builder.get_object(w) w.set_visible(True) w.set_no_show_all(False) self.source_buffer = None if 'text_source_view_container' in self.widgets_to_show: source_style_scheme_manager = \ GtkSource.StyleSchemeManager.get_default() source_lang_manager = GtkSource.LanguageManager.get_default() source_lang_md = source_lang_manager.get_language('markdown') self.source_buffer = GtkSource.Buffer() color_scheme = 'oblivion' self.source_buffer.set_style_scheme( source_style_scheme_manager.get_scheme(color_scheme) ) self.source_buffer.set_language(source_lang_md) source_view = self.builder.get_object('source_view') source_view.set_buffer(self.source_buffer) self.source_buffer.connect('changed', self.on_input) self.file_chooser_btn = self.builder.get_object('file_chooser_btn') self.file_chooser_label = self.builder.get_object('file_chooser_label') self.file_chooser_dialog = None if 'file_chooser_btn' in self.widgets_to_show: def on_file_chooser_deployed(*args): self.file_chooser_dialog = Gtk.FileChooserNative.new( _('Choose an image or video to upload'), self, Gtk.FileChooserAction.OPEN, None, None ) self.file_chooser_dialog.set_filter(self.builder.get_object( 'filefilter1' )) self.file_chooser_dialog.run() filename = self.file_chooser_dialog.get_filename() if filename: self.file_chooser_label.set_text( filename.split('/')[-1] ) self.emit('file-chosen', '') self.file_chooser_btn.connect('clicked', on_file_chooser_deployed) self.cancel_btn = self.builder.get_object('cancel_btn') self.cancel_btn.connect('clicked', lambda *args: self.destroy()) self.send_btn = self.builder.get_object('send_btn') self.send_btn.set_sensitive(False) self.send_btn.connect('clicked', self.on_send) for entry in ( self.title_entry, self.link_entry ): entry.connect('changed', self.on_input) self.connect( 'file-chosen', self.on_input ) self.accel_group = Gtk.AccelGroup() self.accel_group.connect( *Gtk.accelerator_parse('Escape'), Gtk.AccelFlags.VISIBLE, lambda *args: self.close() ) self.add_accel_group(self.accel_group) def destroy(self, *args, **kwargs): self.on_destroy() super().destroy(*args, **kwargs) def on_destroy(self, *args): self.confman.conf['newentity_windowsize'] = { 'width': self.size_allocation.width, 'height': self.size_allocation.height } self.hide() self.confman.save_conf() def on_send(self, *args): raise NotImplementedError('on_send not implemented') def on_input(self, *args): raise NotImplementedError('on_input not implemented') def get_sourcebuffer_text(self): return self.source_buffer.get_text( self.source_buffer.get_start_iter(), self.source_buffer.get_end_iter(), True ).strip() if self.source_buffer is not None else '' class SubredditChoiceListbox(SubredditsListbox): def __init__(self, *args, **kwargs): super().__init__(*args, load_now=True, sort=True, **kwargs) self.set_selection_mode(Gtk.SelectionMode.SINGLE) self.set_filter_func(self.subs_only_filter_func, None, False) def subs_only_filter_func(self, row, data, notify_destroy): return 'u/' not in row.subreddit.display_name_prefixed class FlairChoiceListboxRow(Gtk.ListBoxRow): def __init__(self, text, bg, fg, flair_id, *args, **kwargs): super().__init__(*args, **kwargs) self.flair_id = flair_id fl = FlairLabel(text, bg, fg) fl.set_halign(Gtk.Align.CENTER) fl.set_margin_top(12) fl.set_margin_bottom(12) fl.set_margin_start(12) fl.set_margin_end(12) self.add(fl) self.title = text def get_key(self): return self.title.lower() class FlairChoiceListbox(Gtk.ListBox): def __init__(self, subreddit, *args, **kwargs): super().__init__(*args, **kwargs) self.set_selection_mode(Gtk.SelectionMode.SINGLE) self.set_header_func(self.separator_header_func) self.set_sort_func(self.flair_sort_func, None, False) self.subreddit = subreddit self.populate() def flair_sort_func(self, row1, row2, data, notify_destroy): if row1.flair_id is None: return False if row2.flair_id is None: return True return row1.title.lower() > row2.title.lower() def populate(self): nonerow = Gtk.ListBoxRow() nonerow.get_key = lambda *args: _('None').lower() nonelbl = Gtk.Label(_('None')) nonelbl.set_margin_top(12) nonelbl.set_margin_bottom(12) nonelbl.set_margin_start(12) nonelbl.set_margin_end(12) nonerow.add(nonelbl) nonerow.title = _('None') nonerow.flair_id = None self.add(nonerow) for lt in self.subreddit.flair.link_templates: self.add(FlairChoiceListboxRow( lt['text'], lt['background_color'], lt['text_color'], lt['id'] )) def separator_header_func(self, row, prev_row): if ( prev_row is not None and row.get_header() is None ): row.set_header( Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL) ) class NewPostWindow(CommonNewEntityWindow): def __init__(self, reddit, post_type, callback=None, **kwargs): hide = [] show = [] self.post_type = post_type if self.post_type == 'text': hide = ['link_entry', 'file_chooser_btn'] show = ['text_source_view_container'] elif self.post_type == 'link': hide = ['file_chooser_btn', 'text_source_view_container'] show = ['link_entry'] else: # media hide = ['text_source_view_container', 'link_entry'] show = ['file_chooser_btn'] show.append('choice_picker_container') super().__init__(hide, show, **kwargs) self.callback = callback assert ( self.post_type in ('text', 'link', 'media') ), 'Post type can only be text, link or media' self.subreddits_picker_listbox = SubredditChoiceListbox( reddit.user.subreddits ) self.picker = ChoicePickerButton( self.subreddits_picker_listbox, _('Select a subreddit…') ) self.selected_subreddit = None self.flair_picker = None self.picker.connect('changed', self.on_picker_changed) self.choice_picker_container.add(self.picker) self.show_all() def on_send(self, *args): self.set_sensitive(False) subreddit = self.get_selected_subreddit() title = self.title_entry.get_text().strip() def af(): if self.post_type in ('text', 'link'): subreddit.submit( title=title, selftext=( self.get_sourcebuffer_text() if self.post_type == 'text' else None ), url=( self.link_entry.get_text().strip() if self.post_type == 'link' else None ), flair_id=self.get_selected_flair_id() ) else: media = self.get_selected_media() if is_image(media): subreddit.submit_image( title=title, image_path=media ) elif is_video(media): subreddit.submit_video( title=title, video_path=media, thumbnail_path=make_video_thumb(media) ) GLib.idle_add(cb) def cb(): # TODO: verify that post has been sent before closing, else show error if self.callback is not None: self.callback() self.destroy() Thread(target=af).start() def on_picker_changed(self, *args): n_sub = self.get_selected_subreddit() if n_sub != self.selected_subreddit and n_sub is not None: if self.flair_picker is not None: self.choice_picker_container.remove(self.flair_picker) self.selected_subreddit = n_sub try: flair_picker_listbox = FlairChoiceListbox( self.selected_subreddit ) self.flair_picker = ChoicePickerButton( flair_picker_listbox, default_title=_('Select a flair…') ) self.choice_picker_container.add(self.flair_picker) self.flair_picker.show_all() except Exception: print( 'Error getting flairs for subreddit', f'`{self.selected_subreddit}`' ) self.on_input() def on_input(self, *args): self.send_btn.set_sensitive( self.get_selected_subreddit() is not None and len(self.title_entry.get_text().strip()) > 0 and ( self.post_type != 'text' or len(self.get_sourcebuffer_text()) > 0 ) and ( self.post_type != 'link' or len(self.link_entry.get_text().strip()) > 0 ) and ( self.post_type != 'media' or (self.validate_selected_media()) ) ) def get_selected_media(self): return ( self.file_chooser_dialog.get_filename() if self.file_chooser_dialog is not None else None ) def validate_selected_media(self): media = self.get_selected_media() return media is not None and isfile(media) and ( is_image(media) or is_video(media) ) def get_selected_subreddit(self): choice = self.picker.get_choice() return choice.subreddit if choice is not None else None def get_selected_flair_id(self): choice = self.flair_picker.get_choice() return ( choice.flair_id if self.flair_picker is not None else None ) if choice is not None else None class NewCommentWindow(CommonNewEntityWindow): def __init__(self, parent, callback=None, **kwargs): super().__init__( [ 'link_entry', 'file_chooser_btn', 'choice_picker_container', 'title_entry' ], ['text_source_view_container'], **kwargs ) self.parent = parent self.callback = callback self.headerbar.set_title(_('New comment')) def on_send(self, *args): self.set_sensitive(False) def af(): self.parent.reply(body=self.get_sourcebuffer_text()) GLib.idle_add(cb) def cb(): if self.callback is not None: self.callback() self.destroy() Thread(target=af).start() def on_input(self, *args): self.send_btn.set_sensitive( len(self.get_sourcebuffer_text()) > 0 ) class EditWindow(NewCommentWindow): def __init__(self, entity, callback=None, **kwargs): super().__init__( entity, callback, **kwargs ) self.headerbar.set_title(_('Editing')) self.send_btn.set_label(_('Edit')) self.source_buffer.set_text( entity.selftext if isinstance(entity, Submission) else entity.body ) def on_send(self, *args): # NOTE: parent == entity self.set_sensitive(False) def af(): self.parent.edit(body=self.get_sourcebuffer_text()) GLib.idle_add(cb) def cb(): if self.callback is not None: self.callback() self.destroy() Thread(target=af).start() giara-0.3/giara/notification_manager.py000066400000000000000000000033771376474030500202770ustar00rootroot00000000000000from gettext import ngettext from gi.repository import Gio, GLib from threading import Thread from time import sleep class NotifManager: def __init__(self, reddit, application, emit_func): self.reddit = reddit self.app = application self.old_len = -1 self.__stop = False self.emit = emit_func self.thread = Thread(target=self.check_inbox, daemon=True) self.thread.start() def stop(self): self.__stop = True def __del__(self): self.__stop = True def check_inbox(self): while not self.__stop: inbox = list(self.reddit.inbox.unread(limit=None)) len_inbox = len(inbox) if len_inbox != self.old_len: if len_inbox > 0: title = ngettext( '{0} new message', '{0} new messages', len_inbox ).format(len_inbox) body = '\n\n'.join([m.body for m in inbox[:3]]) if len_inbox > 3: body += '\n\n' + ngettext( '{0} more', '{0} more', len_inbox-3 ).format( len_inbox-3 ) GLib.idle_add(self.send_notif, title, body) GLib.idle_add(self.emit, 'notif_count_change', str(len_inbox)) self.old_len = len_inbox sleep(10) def send_notif(self, title, body): notif = Gio.Notification.new(title) notif.set_body(body) notif.set_default_action('app.goinbox') notif.set_icon( Gio.ThemedIcon.new( 'org.gabmus.giara-symbolic' ) ) self.app.send_notification('new_inbox_items', notif) giara-0.3/giara/path_utils.py000066400000000000000000000016271376474030500162670ustar00rootroot00000000000000from urllib.parse import urlparse from os.path import basename, splitext from hashlib import sha256 def get_url_filename(link: str) -> str: return basename(urlparse(link).path) IMAGE_EXTENSIONS = [ 'jpg', 'jpeg', 'png', 'bmp', 'tiff', 'svg', 'gif' ] VIDEO_EXTENSIONS = [ 'mp4', 'webm' ] def get_file_extension(path: str) -> str: return splitext(path)[-1].strip('.').split('?')[0].lower() def __has_extensions(path: str, extensions: list) -> bool: if path is None: return False return get_file_extension(path) in extensions def is_image(path: str) -> bool: return __has_extensions(path, IMAGE_EXTENSIONS) def is_video(path: str) -> bool: return __has_extensions(path, VIDEO_EXTENSIONS) def is_media(path: str) -> bool: return __has_extensions(path, IMAGE_EXTENSIONS+VIDEO_EXTENSIONS) def sha256sum(txt: str) -> str: return sha256(txt.encode()).hexdigest() giara-0.3/giara/picture_view.py000066400000000000000000000053041376474030500166140ustar00rootroot00000000000000from giara.constants import RESOURCE_PREFIX from gi.repository import Gtk, GdkPixbuf, Gdk from giara.confManager import ConfManager import cairo class PictureView(Gtk.DrawingArea): def __init__(self, path, is_video=False, *args, **kwargs): super().__init__(*args, **kwargs) self.confman = ConfManager() self.is_video = is_video self.path = path self.video_icon_pixbuf = None self.video_icon_surface = None if self.is_video: self.video_icon_pixbuf = ( GdkPixbuf.Pixbuf.new_from_resource( f'{RESOURCE_PREFIX}/icons/video-icon.svg' ) ) self.video_icon_surface = Gdk.cairo_surface_create_from_pixbuf( self.video_icon_pixbuf, 1, None ) self.pixbuf = GdkPixbuf.Pixbuf.new_from_file(self.path) self.img_surface = Gdk.cairo_surface_create_from_pixbuf( self.pixbuf, 1, None ) self.old_scaled_surface = None self.old_sf = -1 def get_useful_height(self, width): aw = width pw = self.pixbuf.get_width() ph = self.pixbuf.get_height() return aw/pw * ph def get_mscale_factor(self, width): return width / self.pixbuf.get_width() def do_draw(self, context): width = self.get_allocated_width() x_pos = 0 if self.confman.conf['max_picture_width'] > 0: if width > self.confman.conf['max_picture_width']: width = self.confman.conf['max_picture_width'] x_pos = (self.get_allocated_width()//2)-(width//2) height = self.get_useful_height(width) sf = self.get_mscale_factor(width) if sf != self.old_sf: self.old_sf = sf wsf = self.get_scale_factor() self.old_scaled_surface = context.get_target().create_similar( cairo.Content.COLOR_ALPHA, int(wsf*width), int(wsf*height) ) self.old_scaled_surface.set_device_scale(wsf, wsf) scaled_ctx = cairo.Context(self.old_scaled_surface) scaled_ctx.scale(sf, sf) scaled_ctx.set_source_surface(self.img_surface, x_pos, 0) scaled_ctx.paint() context.set_source_surface(self.old_scaled_surface, x_pos, 0) context.paint() self.set_size_request(-1, height) if self.is_video: self.old_scale_factor = 1 context.set_source_surface( self.video_icon_surface, x_pos+(width//2)-(self.video_icon_pixbuf.get_width()//2), (height//2)-(self.video_icon_pixbuf.get_height()//2) ) context.paint() giara-0.3/giara/post_details_view.py000066400000000000000000000333051376474030500176350ustar00rootroot00000000000000from giara.constants import RESOURCE_PREFIX from gettext import gettext as _ from gi.repository import Gtk, Handy, GLib, GObject from giara.common_post_box import CommonPostBox, InteractiveEntityBox from giara.new_post_window import NewCommentWindow from giara.markdown_view import MarkdownView from giara.confManager import ConfManager from threading import Thread from time import sleep from giara.giara_clamp import GiaraClamp from praw.models import MoreComments, Comment def create_comment_widget(comment, refresh_func, me, level=0, visible_dict=dict()): return ( MoreCommentsBox(comment, refresh_func, me, level, visible_dict) if isinstance(comment, MoreComments) else CommentBox(comment, refresh_func, me, level, visible_dict) ) class MoreCommentsBox(Gtk.Box): def __init__(self, mc, refresh_func, me, level=0, visible_dict=dict(), **kwargs): super().__init__(orientation=Gtk.Orientation.VERTICAL, **kwargs) self.mc = mc self.level = level self.refresh_func = refresh_func self.me = me self.visible_dict = visible_dict self.load_btn = Gtk.Button.new_with_label(_('Load more comments')) self.add(self.load_btn) self.load_btn_removed = False self.load_btn.connect('clicked', self.on_load_more) def on_load_more(self, *args): self.load_btn.set_sensitive(False) def af(): comments = self.mc.comments() for comment in comments: if isinstance(comment, Comment): comment.body GLib.idle_add(cb, comment) GLib.idle_add(final_cb) def cb(comment): if not self.load_btn_removed: self.remove(self.load_btn) self.load_btn_removed = True cbox = create_comment_widget( comment, self.refresh_func, self.me, level=self.level, visible_dict=self.visible_dict ) self.add(cbox) cbox.show_all() def final_cb(): self.show_all() Thread(target=af).start() class CommentBox(InteractiveEntityBox): def __init__(self, comment, refresh_func, me, level=0, visible_dict=dict(), **kwargs): super().__init__( comment, Gtk.Builder.new_from_resource( f'{RESOURCE_PREFIX}/ui/comment_box.ui' ), **kwargs ) self.me = me self.refresh_func = refresh_func self.comment = comment self.level = level self.visible_dict = visible_dict self.reply_btn = self.builder.get_object('reply_btn') self.reply_btn.connect( 'clicked', self.on_reply_clicked ) self.author_label = self.builder.get_object('author_label') self.op_icon = self.builder.get_object('op_icon') self.me_icon = self.builder.get_object('me_icon') self.comment_label = self.builder.get_object('comment_label') self.replies_container = self.builder.get_object('replies_container') self.comment_container = self.builder.get_object('comment_container') self.body_view = MarkdownView(self.comment.body) self.comment_container.add(self.body_view) self.body_view.set_vexpand(False) self.body_view.set_hexpand(True) author_name = _('Author unknown') if hasattr(self.comment, 'author') and self.comment.author is not None: author_name = f'u/{self.comment.author.name}' self.author_label.set_text(author_name) if self.level > 0: self.builder.get_object( 'main_box' ).get_style_context().add_class(f'nested-{(self.level - 1) % 8}') author_label_style_context = self.author_label.get_style_context() if self.comment.is_submitter: author_label_style_context.add_class('op_comment') self.op_icon.set_visible(True) self.op_icon.set_no_show_all(False) else: author_label_style_context.add_class('comment_author') self.op_icon.set_visible(False) self.op_icon.set_no_show_all(True) if self.comment.author == self.me: for sc in ('op_comment', 'comment_author'): author_label_style_context.remove_class(sc) author_label_style_context.add_class('green') self.me_icon.set_visible(True) self.me_icon.set_no_show_all(False) else: self.me_icon.set_visible(False) self.me_icon.set_no_show_all(True) self.collapse_replies_btn = self.builder.get_object( 'collapse_replies_btn' ) self.collapse_replies_btn_icon = self.builder.get_object( 'collapse_replies_btn_icon' ) self.collapse_icon_style_context = \ self.collapse_replies_btn_icon.get_style_context() self.replies_revealer = self.builder.get_object('replies_revealer') self.replies_visible = True def toggle_replies(*args): self.replies_visible = not self.replies_visible self.replies_revealer.set_reveal_child(self.replies_visible) if self.replies_visible: self.collapse_icon_style_context.remove_class('replies-closed') else: self.collapse_icon_style_context.add_class('replies-closed') if ( self.comment.id in self.visible_dict.keys() and not self.visible_dict[self.comment.id] ): toggle_replies() self.collapse_replies_btn.connect('clicked', toggle_replies) self.populate_replies() # GLib.idle_add(self.populate_replies) def populate_replies(self): for reply in self.comment.replies: n_reply = create_comment_widget( reply, self.refresh_func, self.me, self.level+1, visible_dict=self.visible_dict ) self.replies_container.pack_start( n_reply, False, False, 0 ) n_reply.show_all() def on_reply_clicked(self, *args): win = NewCommentWindow( self.comment, lambda *args: self.refresh_func(wait_for_comments_update=True) ) win.set_transient_for(self.get_toplevel()) win.present() win.show_all() def get_comments(self, res=set()): res.add(self) for c in self.replies_container.get_children(): c.get_comments(res) return res class MultiCommentsBox(Gtk.Box): __gsignals__ = { 'comments_loaded': ( GObject.SignalFlags.RUN_LAST, None, (str,) ) } def __init__(self, comments, refresh_func, me, level=0, visible_dict=dict(), **kwargs): super().__init__(orientation=Gtk.Orientation.VERTICAL, **kwargs) self.me = me self.level = level self.refresh_func = refresh_func self.comments = comments self.populated = False self.visible_dict = visible_dict # self.populate() def populate(self, *args): self.populated = False def af(): # self.comments.replace_more(limit=None) for comment in self.comments: if isinstance(comment, Comment): comment.body GLib.idle_add(cb, comment) GLib.idle_add(final_cb) def cb(comment): cbox = create_comment_widget( comment, self.refresh_func, self.me, level=self.level, visible_dict=self.visible_dict ) self.pack_start(cbox, False, False, 6) cbox.show_all() def final_cb(): self.show_all() self.populated = True self.emit('comments_loaded', '') Thread(target=af).start() def get_comments(self, res=set()): for c in self.get_children(): c.get_comments(res) return list(res) def get_visible_dict(self): res = dict() for cw in self.get_comments(): res[cw.comment.id] = cw.replies_visible return res class PostBody(CommonPostBox): def __init__(self, post, refresh_func, **kwargs): super().__init__( post, Gtk.Builder.new_from_resource( f'{RESOURCE_PREFIX}/ui/post_body.ui' ), **kwargs ) self.body_view = MarkdownView(self.post.selftext) self.body_container = self.builder.get_object('body_container') self.body_container.add(self.body_view) self.body_view.set_vexpand(False) self.body_view.set_hexpand(True) self.refresh_func = refresh_func self.reply_btn = self.builder.get_object('reply_btn') self.reply_btn.connect( 'clicked', self.on_reply_clicked ) def on_reply_clicked(self, *args): win = NewCommentWindow( self.post, lambda *args: self.refresh_func(wait_for_comments_update=True) ) win.set_transient_for(self.get_toplevel()) win.present() win.show_all() class PostDetailsHeaderbar(Handy.WindowHandle): def __init__(self, post, back_func, refresh_func, **kwargs): super().__init__(**kwargs) self.post = post self.builder = Gtk.Builder.new_from_resource( f'{RESOURCE_PREFIX}/ui/post_details_headerbar.ui' ) self.headerbar = self.builder.get_object('headerbar') self.headerbar.set_title(self.post.title) self.back_btn = self.builder.get_object('back_btn') self.back_btn.connect('clicked', lambda *args: back_func()) self.refresh_btn = self.builder.get_object('refresh_btn') self.refresh_btn.connect( 'clicked', lambda *args: refresh_func(reload_post=True) ) self.add(self.headerbar) class PostDetailsView(Gtk.ScrolledWindow): def __init__(self, post, back_func, target_comment=None, **kwargs): super().__init__(**kwargs) self.confman = ConfManager() self.target_comment = target_comment self.reddit = self.confman.reddit self.post = post self.main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.inner_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.headerbar = PostDetailsHeaderbar( self.post, back_func, self.refresh ) self.main_box.add(self.headerbar) self.headerbar.set_vexpand(False) self.headerbar.set_hexpand(True) self.sw = Gtk.ScrolledWindow() self.sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) self.clamp = GiaraClamp() self.inner_box.get_style_context().add_class('card') self.clamp.add(self.inner_box) self.sw.add(self.clamp) self.main_box.add(self.sw) self.sw.set_vexpand(True) self.add(self.main_box) self.multi_comments_box = None self.refresh(reload_post=False) def populate_inner_box(self): for child in self.inner_box.get_children(): self.inner_box.remove(child) self.inner_box.add(self.post_body) self.post_body.set_vexpand(True) separator = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL) self.inner_box.add(separator) separator.set_vexpand(False) self.inner_box.add(self.multi_comments_box) self.inner_box.set_vexpand(False) self.show_all() self.jump_to_target() def jump_to_target(self, *args): if self.target_comment is None: return def af(): while not self.multi_comments_box.populated: sleep(1) GLib.idle_add(cb) def cb(): comments_widgets = self.multi_comments_box.get_comments() comment_w = None for c in comments_widgets: if self.target_comment.id == c.comment.id: comment_w = c break if comment_w is not None: adj = self.sw.get_vadjustment() adj.set_value( min(comment_w.get_allocation().y, adj.get_upper()) ) self.sw.set_vadjustment(adj) Thread(target=af).start() def refresh(self, reload_post=True, wait_for_comments_update=False): self.headerbar.refresh_btn.set_sensitive(False) def af(callback): if reload_post: if wait_for_comments_update: tries = 0 cl = self.post.comments.list() self.post = self.reddit.submission(self.post.id) while ( tries < 10 and len(cl) == len(self.post.comments.list()) ): tries += 1 sleep(1) self.post = self.reddit.submission(self.post.id) else: self.post = self.reddit.submission(self.post.id) GLib.idle_add(callback) def cb(): self.post_body = PostBody(self.post, self.refresh) visible_dict = ( self.multi_comments_box.get_visible_dict() if self.multi_comments_box is not None else dict() ) self.multi_comments_box = MultiCommentsBox( self.post.comments, self.refresh, self.reddit.user.me(), visible_dict=visible_dict ) self.multi_comments_box.populate() self.populate_inner_box() self.headerbar.refresh_btn.set_sensitive(True) Thread(target=af, args=(cb,)).start() giara-0.3/giara/post_preview.py000066400000000000000000000110461376474030500166350ustar00rootroot00000000000000from giara.constants import RESOURCE_PREFIX from giara.common_post_box import CommonPostBox from giara.confManager import ConfManager from gi.repository import Gtk, GLib from threading import Thread from praw.models import Submission class PostPreview(CommonPostBox): def __init__(self, post, **kwargs): self.confman = ConfManager() super().__init__( post, Gtk.Builder.new_from_resource( f'{RESOURCE_PREFIX}/ui/post_preview.ui' ), **kwargs ) self.comments_box = self.builder.get_object('comments_box') self.comments_label = self.builder.get_object('comments_label') GLib.idle_add(self.count_comments) def hide_show_image(*args): img_cont = self.builder.get_object('image_container') if self.confman.conf['show_thumbnails_in_preview']: img_cont.set_visible(True) img_cont.set_no_show_all(False) if len(img_cont.get_children()) <= 0: self.set_post_image() else: img_cont.set_visible(False) img_cont.set_no_show_all(True) self.confman.connect( 'on_show_thumbnails_in_preview_changed', hide_show_image ) def set_post_image(self): if self.confman.conf['show_thumbnails_in_preview']: super().set_post_image() def count_comments(self): def af(): if isinstance(self.post, Submission): num_comments = str(len(self.post.comments)) GLib.idle_add(cb, num_comments) def cb(num_comments): self.comments_box.set_visible(True) self.comments_box.set_no_show_all(False) self.comments_label.set_text(num_comments) self.comments_box.show_all() Thread(target=af).start() class PostPreviewListboxRow(Gtk.ListBoxRow): def __init__(self, post, **kwargs): super().__init__(**kwargs) self.post = post def lazy(): self.post_preview = PostPreview(post) self.add(self.post_preview) self.show_all() GLib.idle_add(lazy, priority=GLib.PRIORITY_LOW) class PostPreviewListbox(Gtk.ListBox): def __init__(self, post_gen_func, show_post_func, load_now=True, **kwargs): super().__init__(**kwargs) self.get_style_context().add_class('card') self.set_selection_mode(Gtk.SelectionMode.NONE) self.post_gen_func = post_gen_func self.show_post_func = show_post_func self.post_gen = None self.initialized = False self.loading = False if load_now: self.refresh() self.connect('row-activated', self.on_row_activate) self.set_header_func(self.separator_header_func) self.stop_loading = False def set_gen_func(self, n_gen_func): self.post_gen_func = n_gen_func def separator_header_func(self, row, prev_row): if ( prev_row is not None and row.get_header() is None ): row.set_header( Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL) ) def empty(self, *args): while True: row = self.get_row_at_index(0) if row: self.remove(row) else: break def refresh(self, *args): self.initialized = True self.empty() self.post_gen = self.post_gen_func(limit=None) self.load_more() def _on_post_preview_row_loaded(self, target): row = PostPreviewListboxRow(target) self.add(row) def _async_create_post_preview_row(self, gen, num): for n in range(num): if self.stop_loading: break try: target = next(gen) GLib.idle_add( self._on_post_preview_row_loaded, target, priority=GLib.PRIORITY_LOW ) except StopIteration: # TODO: add child indicating post stream end break self.loading = False def load_more(self, num=10): if not self.stop_loading and self.loading: return self.loading = True t = Thread( target=self._async_create_post_preview_row, args=(self.post_gen, num) ) self.stop_loading = False t.start() def on_row_activate(self, lb, row): if hasattr(row, 'post') and row.post is not None: self.show_post_func(row.post) giara-0.3/giara/search_view.py000066400000000000000000000077461376474030500164220ustar00rootroot00000000000000from giara.constants import RESOURCE_PREFIX from gettext import gettext as _ from gi.repository import Gtk, Handy from giara.squeezing_viewswitcher_headerbar import \ SqueezingViewSwitcherHeaderbar from giara.sections_stack import SectionsStack from giara.confManager import ConfManager class SearchViewHeaderbar(SqueezingViewSwitcherHeaderbar): def __init__(self, stack, **kwargs): super().__init__( Gtk.Builder.new_from_resource( f'{RESOURCE_PREFIX}/ui/' 'headerbar_with_back_and_squeezer.ui' ), stack, **kwargs ) self.back_btn = self.builder.get_object('back_btn') class CommonSearchView(Gtk.Box): def __init__(self, headerbar, **kwargs): super().__init__(orientation=Gtk.Orientation.VERTICAL, **kwargs) self.confman = ConfManager() self.reddit = self.confman.reddit self.headerbar = headerbar self.searchbar = Handy.SearchBar() self.search_entry = Gtk.SearchEntry() self.search_entry.connect('activate', self.on_search_activate) self.searchbar.add(self.search_entry) self.searchbar.connect_entry(self.search_entry) self.add(self.headerbar) self.headerbar.set_vexpand(False) self.headerbar.set_hexpand(True) self.add(self.searchbar) self.searchbar.set_vexpand(False) self.searchbar.set_hexpand(True) self.searchbar.set_search_mode(True) def on_search_activate(self, *args): raise NotImplementedError() class SearchView(CommonSearchView): def __init__(self, show_post_func, show_subreddit_func, **kwargs): self.show_post_func = show_post_func self.show_subreddit_func = show_subreddit_func self.sections_stack = SectionsStack( [ { 'name': 'subreddits', 'title': _('Subreddits'), 'gen': None, 'type': 'subs', 'load_now': False, 'icon': 'org.gabmus.giara-symbolic' }, { 'name': 'users', 'title': _('Users'), 'gen': None, 'type': 'subs', 'load_now': False, 'icon': 'avatar-default-symbolic' }, # { # 'name': 'posts', # 'title': _('Posts'), # 'gen': None, # 'icon': 'text-editor-symbolic' # }, ], self.show_post_func, show_subreddit_func=self.show_subreddit_func, load_now=False ) super().__init__( SearchViewHeaderbar(self.sections_stack), **kwargs ) self.bottom_bar = Handy.ViewSwitcherBar() self.bottom_bar.set_stack(self.sections_stack) self.headerbar.connect( 'squeeze', lambda caller, squeezed: self.bottom_bar.set_reveal(squeezed) ) self.add(self.sections_stack) self.sections_stack.set_vexpand(True) self.add(self.bottom_bar) self.bottom_bar.set_vexpand(False) def on_search_activate(self, *args): term = self.search_entry.get_text() subs_view = self.sections_stack.get_child_by_name( 'subreddits' ).get_child().get_child().get_child() subs_view.set_gen_func( lambda *args, **kwargs: self.reddit.subreddits.search( term, *args, **kwargs ) ) subs_view.initialized = False users_view = self.sections_stack.get_child_by_name( 'users' ).get_child().get_child().get_child() users_view.set_gen_func( lambda *args, **kwargs: self.reddit.redditors.search( term, *args, **kwargs ) ) users_view.initialized = False self.sections_stack.on_visible_child_change() giara-0.3/giara/sections_stack.py000066400000000000000000000123531376474030500171250ustar00rootroot00000000000000from gi.repository import Gtk from giara.post_preview import PostPreviewListbox from giara.markdown_view import MarkdownView from giara.subreddits_list_view import SubredditsListbox from giara.confManager import ConfManager from giara.giara_clamp import GiaraClamp class SectionScrolledWindow(Gtk.ScrolledWindow): def __init__(self, post_preview_lbox, **kwargs): super().__init__(**kwargs) self.post_preview_lbox = post_preview_lbox self.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) self.connect('edge_reached', self.on_edge_reached) def on_edge_reached(self, sw, pos): if len(self.post_preview_lbox.get_children()) < 10: return if pos == Gtk.PositionType.BOTTOM: self.post_preview_lbox.load_more() class SectionsStack(Gtk.Stack): def __init__(self, sections: list, show_post_func, show_subreddit_func=None, load_now=True, source=None, **kwargs): super().__init__(**kwargs) self.confman = ConfManager() self.reddit = self.confman.reddit self.source = self.reddit.front if source is None else source self.sections = sections self.show_post_func = show_post_func self.show_subreddit_func = show_subreddit_func self.set_transition_type(Gtk.StackTransitionType.CROSSFADE) for section in self.sections: if ( 'type' not in section.keys() or section['type'] == 'post_preview' ): self.add_post_preview(section) elif section['type'] == 'text': self.add_text(section) elif section['type'] == 'links': self.add_links(section) elif section['type'] == 'subs': self.add_subreddit_listbox(section) else: raise ValueError( 'SectionsStack: unknown section type `{section["type"]}`' ) self.show_all() self.connect('notify::visible-child', self.on_visible_child_change) if load_now: self.on_visible_child_change() def on_visible_child_change(self, *args): child = self.get_visible_child().get_child().get_child().get_child() gen_func = None if isinstance(child, SubredditsListbox): gen_func = child.subreddits_gen_func elif isinstance(child, PostPreviewListbox): gen_func = child.post_gen_func else: return if not child.initialized and gen_func is not None: child.refresh() def __make_clamp(self): clamp = GiaraClamp() return clamp def add_text(self, section): clamp = self.__make_clamp() markdown_view = MarkdownView(section['text']) markdown_view.get_style_context().add_class('card') markdown_view.get_style_context().add_class('padding12') clamp.add(markdown_view) sw = Gtk.ScrolledWindow() sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) sw.add(clamp) self.add_titled( sw, section['name'], section['title'] ) self.set_icon(section, sw) def add_links(self, section): clamp = self.__make_clamp() markdown_view = MarkdownView( '\n\n'.join( [f'[{lnk.label}]({lnk.url})' for lnk in section['links']]) ) clamp.add(markdown_view) sw = Gtk.ScrolledWindow() sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) sw.add(clamp) self.add_titled( sw, section['name'], section['title'] ) self.set_icon(section, sw) def add_subreddit_listbox(self, section): assert self.show_subreddit_func is not None, \ 'Show subreddit function cannot be None if you want to add a ' \ 'subreddit listbox' clamp = self.__make_clamp() subreddit_listbox = SubredditsListbox( section['gen'], self.show_subreddit_func, load_now=( 'load_now' not in section.keys() or section['load_now'] ) ) clamp.add(subreddit_listbox) sw = Gtk.ScrolledWindow() sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) sw.add(clamp) self.add_titled( sw, section['name'], section['title'] ) self.set_icon(section, sw) def add_post_preview(self, section): post_preview_clamp = self.__make_clamp() post_preview_lbox = PostPreviewListbox( section['gen'], self.show_post_func, load_now=False ) post_preview_clamp.add(post_preview_lbox) sw = SectionScrolledWindow(post_preview_lbox) sw.add(post_preview_clamp) self.add_titled( sw, section['name'], section['title'] ) self.set_icon(section, sw) def set_icon(self, section, widget): if 'icon' in section.keys() and section['icon']: self.child_set_property(widget, 'icon-name', section['icon']) def refresh(self, *args): self.get_visible_child().post_preview_lbox.refresh() giara-0.3/giara/settings_window.py000066400000000000000000000361011376474030500173350ustar00rootroot00000000000000# from os import remove, listdir # from os.path import isfile, abspath, join from gettext import gettext as _ from gi.repository import Gtk, Handy from giara.confManager import ConfManager from os.path import isfile, abspath, join from os import remove, listdir class PreferencesButtonRow(Handy.ActionRow): """ A preferences row with a title and a button title: the title shown button_label: a label to show inside the button onclick: the function that will be called when the button is pressed button_style_class: the style class of the button. Common options: `suggested-action`, `destructive-action` signal: an optional signal to let ConfManager emit when the button is pressed """ def __init__(self, title, button_label, onclick, button_style_class=None, signal=None, *args, **kwargs): super().__init__(*args, **kwargs) self.title = title self.button_label = button_label self.confman = ConfManager() self.set_title(self.title) self.signal = signal self.onclick = onclick self.button = Gtk.Button() self.button.set_label(self.button_label) self.button.set_valign(Gtk.Align.CENTER) if button_style_class: self.button.get_style_context().add_class(button_style_class) self.button.connect('clicked', self.on_button_clicked) self.add(self.button) # You need to press the actual button # Avoids accidental presses # self.set_activatable_widget(self.button) def on_button_clicked(self, button): self.onclick(self.confman) if self.signal: self.confman.emit(self.signal, '') self.confman.save_conf() class PreferencesEntryRow(Handy.ActionRow): """ A preferences row with a title and a button title: the title shown conf_key: the key of the configuration dictionary/json in ConfManager onchange: an optional function that will be called when the entry changes subtitle: an optional subtitle to be shown signal: an optional signal to let ConfManager emit when the entry changes """ def __init__(self, title, conf_key, onchange=None, subtitle=None, signal=None, *args, **kwargs): super().__init__(*args, **kwargs) self.title = title self.conf_key = conf_key self.subtitle = subtitle if self.subtitle is not None: self.set_subtitle(self.subtitle) self.confman = ConfManager() self.set_title(self.title) self.signal = signal self.onchange = onchange self.entry = Gtk.Entry() self.entry.set_text(self.confman.conf[self.conf_key]) self.entry.connect('changed', self.on_entry_changed) self.add(self.entry) def on_entry_changed(self, *args): self.confman.conf[self.conf_key] = self.entry.get_text().strip() if self.onchange is not None: self.onchange(self.confman) if self.signal: self.confman.emit(self.signal, '') self.confman.save_conf() class PreferencesFileChooserRow(Handy.ActionRow): """ A preferences row with a title and a file chooser button title: the title shown file_chooser_title: the title of the file chooser dialog conf_key: the key of the configuration dictionary/json in ConfManager signal: an optional signal to let ConfManager emit when the value changes """ def __init__(self, title, conf_key, signal=None, file_chooser_title=None, subtitle=None, *args, **kwargs): super().__init__(*args, **kwargs) self.title = title if subtitle: self.subtitle = subtitle self.set_subtitle(self.subtitle) self.confman = ConfManager() self.set_title(self.title) self.signal = signal self.conf_key = conf_key self.file_chooser_btn = Gtk.FileChooserButton.new( file_chooser_title or _('Choose a folder'), Gtk.FileChooserAction.SELECT_FOLDER ) self.file_chooser_btn.set_current_folder_uri( 'file://'+self.confman.conf[self.conf_key] ) self.file_chooser_btn.connect('file-set', self.on_file_set) self.add(self.file_chooser_btn) def on_file_set(self, *args): self.confman.conf[self.conf_key] = self.file_chooser_btn.get_filename() if self.signal: self.confman.emit(self.signal, '') self.confman.save_conf() class PreferencesSpinButtonRow(Handy.ActionRow): """ A preferences row with a title and a spin button title: the title shown min_v: minimum num value max_v: maximum num value conf_key: the key of the configuration dictionary/json in ConfManager signal: an optional signal to let ConfManager emit when the value changes """ def __init__(self, title, min_v, max_v, conf_key, signal=None, subtitle=None, *args, **kwargs): super().__init__(*args, **kwargs) self.title = title if subtitle: self.subtitle = subtitle self.set_subtitle(self.subtitle) self.confman = ConfManager() self.set_title(self.title) self.signal = signal self.conf_key = conf_key self.adjustment = Gtk.Adjustment( self.confman.conf[self.conf_key], # initial value min_v, # minimum value max_v, # maximum value 1, # step increment 7, # page increment (page up, page down? large steps anyway) 0 ) self.spin_button = Gtk.SpinButton() self.spin_button.set_adjustment(self.adjustment) self.spin_button.set_valign(Gtk.Align.CENTER) self.spin_button.connect('value-changed', self.on_value_changed) self.add(self.spin_button) # You need to interact with the actual spin button # Avoids accidental presses # self.set_activatable_widget(self.button) def on_value_changed(self, *args): self.confman.conf[self.conf_key] = self.spin_button.get_value_as_int() if self.signal: self.confman.emit(self.signal, self.confman.conf[self.conf_key]) self.confman.save_conf() class PreferencesComboBoxRow(Handy.ActionRow): """ A preferences row with a title and a combo box title: the title shown values: a list of acceptable values values_names: a list of user facing names for the values provided above conf_key: the key of the configuration dictionary/json in ConfManager signal: an optional signal to let ConfManager emit when the value changes """ def __init__(self, title, values, values_names, conf_key, signal=None, subtitle=None, *args, **kwargs): super().__init__(*args, **kwargs) self.title = title if subtitle: self.subtitle = subtitle self.set_subtitle(self.subtitle) self.confman = ConfManager() self.set_title(self.title) self.signal = signal self.conf_key = conf_key self.list_store = Gtk.ListStore(str, str) for name, value in zip(values_names, values): self.list_store.append([value, name]) self.combo_box = Gtk.ComboBox.new_with_model(self.list_store) self.cell_renderer = Gtk.CellRendererText() self.combo_box.pack_start(self.cell_renderer, True) self.combo_box.add_attribute(self.cell_renderer, "text", 1) self.combo_box.set_id_column(0) self.combo_box.set_active_id(self.confman.conf[self.conf_key]) self.add(self.combo_box) self.combo_box.connect('changed', self.on_value_changed) def on_value_changed(self, *args): store_iter = self.combo_box.get_active_iter() if store_iter is not None: self.confman.conf[self.conf_key] = \ self.combo_box.get_model()[store_iter][0] if self.signal: self.confman.emit(self.signal, '') self.confman.save_conf() class PreferencesToggleRow(Handy.ActionRow): """ A preferences row with a title and a toggle title: the title shown conf_key: the key of the configuration dictionary/json in ConfManager signal: an optional signal to let ConfManager emit when the configuration is set """ def __init__(self, title, conf_key, signal=None, subtitle=None, *args, **kwargs): super().__init__(*args, **kwargs) self.title = title if subtitle: self.subtitle = subtitle self.set_subtitle(self.subtitle) self.confman = ConfManager() self.set_title(self.title) self.conf_key = conf_key self.signal = signal self.toggle = Gtk.Switch() self.toggle.set_valign(Gtk.Align.CENTER) if self.conf_key == 'selection_mode': self.toggle.set_active( self.confman.conf[self.conf_key] == 'double' ) else: self.toggle.set_active(self.confman.conf[self.conf_key]) self.toggle.connect('state-set', self.on_toggle_state_set) self.add(self.toggle) self.set_activatable_widget(self.toggle) def on_toggle_state_set(self, toggle, state): self.confman.conf[self.conf_key] = state self.confman.save_conf() if self.signal: self.confman.emit(self.signal, '') class GeneralPreferencesPage(Handy.PreferencesPage): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.set_title(_('General')) self.set_icon_name('preferences-other-symbolic') self.general_preferences_group = Handy.PreferencesGroup() self.general_preferences_group.set_title(_('General Settings')) toggle_settings = [ ] for s in toggle_settings: row = PreferencesToggleRow( s['title'], s['conf_key'], s['signal'], s['subtitle'] if 'subtitle' in s.keys() else None ) self.general_preferences_group.add(row) # self.general_preferences_group.add( # PreferencesFileChooserRow( # _('Notes folder'), # 'notes_dir', # signal='notes_dir_changed', # file_chooser_title=_('Choose a notes folder') # ) # ) self.general_preferences_group.add( PreferencesComboBoxRow( _('Default view'), ['best', 'hot', 'new', 'top', 'rising', 'controversial'], [ _('Best'), _('Hot'), _('New'), _('Top'), _('Rising'), _('Controversial') ], 'default_front_page_view', signal=None ) ) self.add(self.general_preferences_group) self.cache_preferences_group = Handy.PreferencesGroup() self.cache_preferences_group.set_title(_('Cache')) def clear_cache(confman): for p in [confman.cache_path, confman.thumbs_cache_path]: files = [ abspath(join(p, f)) for f in listdir(p) ] for f in files: if isfile(f): remove(f) self.cache_preferences_group.add( PreferencesButtonRow( _('Clear cache'), _('Clear'), clear_cache, 'destructive-action', signal=None ) ) self.add(self.cache_preferences_group) self.show_all() class ViewPreferencesPage(Handy.PreferencesPage): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.set_title(_('View')) self.set_icon_name('applications-graphics-symbolic') self.view_preferences_group = Handy.PreferencesGroup() self.view_preferences_group.set_title(_('View Settings')) toggle_settings = [ { 'title': _('Dark mode'), 'conf_key': 'dark_mode', 'signal': 'dark_mode_changed' }, { 'title': _('Show thumbnails in post previews'), 'conf_key': 'show_thumbnails_in_preview', 'signal': 'on_show_thumbnails_in_preview_changed' } ] for s in toggle_settings: row = PreferencesToggleRow(s['title'], s['conf_key'], s['signal']) self.view_preferences_group.add(row) self.add(self.view_preferences_group) # self.view_preferences_group.add( # PreferencesComboBoxRow( # _('Editor color scheme'), # color_schemes, # [ # c.capitalize() if c != 'default' else _('Default') # for c in color_schemes # ], # 'editor_color_scheme', # 'editor_color_scheme_changed' # ) # ) self.view_preferences_group.add( PreferencesSpinButtonRow( _('Max thumbnail width'), 0, 1000, 'max_picture_width', subtitle=_('Use 0 to remove the limit') ) ) self.show_all() class PrivacyPreferencesPage(Handy.PreferencesPage): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.set_title(_('Privacy')) self.set_icon_name('eye-not-looking-symbolic') self.link_replacement_preferences_group = Handy.PreferencesGroup() self.link_replacement_preferences_group.set_title(_('Link replacement')) toggle_settings = [ { 'title': _('Replace Twitter links with Nitter'), 'conf_key': 'twitter2nitter', 'signal': None }, { 'title': _('Replace YouTube links with Invidious'), 'conf_key': 'youtube2invidious', 'signal': None } ] for s in toggle_settings: row = PreferencesToggleRow(s['title'], s['conf_key'], s['signal']) self.link_replacement_preferences_group.add(row) self.link_replacement_preferences_group.add( PreferencesEntryRow( title=_('Invidious instance'), conf_key='invidious_instance', subtitle=_('Naked domain name only, no https://'), ) ) self.add(self.link_replacement_preferences_group) self.show_all() class SettingsWindow(Handy.PreferencesWindow): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.confman = ConfManager() self.pages = [ GeneralPreferencesPage(), ViewPreferencesPage(), PrivacyPreferencesPage() ] for p in self.pages: self.add(p) self.set_default_size(630, 700) # self.get_titlebar().set_show_close_button(True) self.accel_group = Gtk.AccelGroup() self.accel_group.connect( *Gtk.accelerator_parse('Escape'), Gtk.AccelFlags.VISIBLE, lambda *args: self.close() ) self.add_accel_group(self.accel_group) giara-0.3/giara/simple_avatar.py000066400000000000000000000012711376474030500167350ustar00rootroot00000000000000from gi.repository import Gtk, Handy, GLib from giara.image_utils import set_avatar_func from threading import Thread class SimpleAvatar(Gtk.Bin): def __init__(self, size: int, title: str, get_image_func): super().__init__() self.avatar = Handy.Avatar.new(size, title, True) self.add(self.avatar) self.get_image_func = get_image_func self.load_avatar() def load_avatar(self): def af(): icon = self.get_image_func() GLib.idle_add(cb, icon) def cb(icon): self.avatar.set_image_load_func( lambda size: set_avatar_func(icon, size) ) Thread(target=af).start() giara-0.3/giara/single_post_stream_headerbar.py000066400000000000000000000027351376474030500220120ustar00rootroot00000000000000from giara.constants import RESOURCE_PREFIX from gettext import gettext as _ from gi.repository import Gtk from giara.squeezing_viewswitcher_headerbar import \ SqueezingViewSwitcherHeaderbar class SinglePostStreamHeaderbar(SqueezingViewSwitcherHeaderbar): def __init__(self, title, stack=None, **kwargs): super().__init__( Gtk.Builder.new_from_resource( f'{RESOURCE_PREFIX}/ui/post_details_headerbar.ui' ), stack, view_switcher=False, sort_menu=stack is not None, sorting_methods={ 'hot': { 'name': _('Hot'), 'icon': 'hot-symbolic' }, 'new': { 'name': _('New'), 'icon': 'new-symbolic' }, 'top': { 'name': _('Top'), 'icon': 'arrow1-up-symbolic' }, 'rising': { 'name': _('Rising'), 'icon': 'rising-symbolic' }, 'controversial': { 'name': _('Controversial'), 'icon': 'controversial-symbolic' } }, **kwargs ) self.title = title self.headerbar.set_title(self.title) self.back_btn = self.builder.get_object('back_btn') self.refresh_btn = self.builder.get_object('refresh_btn') giara-0.3/giara/single_post_stream_view.py000066400000000000000000000027661376474030500210530ustar00rootroot00000000000000from gi.repository import Gtk from giara.sections_stack import SectionsStack from giara.single_post_stream_headerbar import SinglePostStreamHeaderbar class SinglePostStreamView(Gtk.Box): def __init__(self, generator, name, title, show_post_func, load_now=True, source=None, sort_menu=True, **kwargs): super().__init__(orientation=Gtk.Orientation.VERTICAL, **kwargs) self.generator = generator self.name = name self.title = title # it's a stack, but I'm just gonna use one child of it. # I mostly care about the whole structure for this particular case self.section_stack = SectionsStack( [{ 'name': 'posts', 'title': self.title, 'gen': self.generator }], show_post_func, load_now=load_now, source=source ) self.headerbar = SinglePostStreamHeaderbar( self.title, self.section_stack if sort_menu else None ) self.add(self.headerbar) self.headerbar.set_vexpand(False) self.headerbar.set_hexpand(True) self.add(self.section_stack) self.section_stack.set_vexpand(True) self.headerbar.refresh_btn.connect( 'clicked', self.refresh ) def refresh(self, *args): # self.headerbar.refresh_btn.set_sensitive(False) self.section_stack.refresh() # self.headerbar.refresh_btn.set_sensitive(True) giara-0.3/giara/singleton.py000066400000000000000000000003301376474030500161030ustar00rootroot00000000000000class Singleton(type): instance = None def __call__(cls, *args, **kwargs): if not cls.instance: cls.instance = super(Singleton, cls).__call__(*args, **kwargs) return cls.instance giara-0.3/giara/sort_menu_btn.py000066400000000000000000000061031376474030500167630ustar00rootroot00000000000000from gettext import gettext as _ from gi.repository import Gtk, Gio, GLib class SortingMenuButton(Gtk.MenuButton): def __init__(self, target_stack, methods, default_method=None, **kwargs): super().__init__(**kwargs) self.methods = methods if default_method is None: default_method = list(self.methods.keys())[0] self.target_stack = target_stack self.main_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) self.icon = Gtk.Image.new_from_icon_name( self.methods[default_method]['icon'], Gtk.IconSize.BUTTON ) self.main_box.set_spacing(6) self.main_box.add(self.icon) self.main_box.add(Gtk.Image.new_from_icon_name( 'pan-down-symbolic', Gtk.IconSize.BUTTON )) self.add(self.main_box) self.create_action(self.methods, default_method) self.popover = Gtk.Popover.new_from_model( self, self.methods_to_menu_model(self.methods) ) self.set_popover(self.popover) self.old_sorting = None self.set_sorting(default_method) def methods_to_menu_model(self, methods: dict): items = '\n'.join([ f''' {methods[m]['name']} sort.change {m} ''' for m in methods.keys() ]) return Gtk.Builder.new_from_string( f'''
{items}
''', -1 ).get_object('sort-menu') def create_action(self, methods: dict, default_method: str) -> None: self.sort_action_group = Gio.SimpleActionGroup() sm_action = Gio.SimpleAction.new_stateful( 'change', GLib.VariantType.new('s'), GLib.Variant('s', default_method) ) sm_action.connect('activate', self.on_sort_action_activated) self.sort_action_group.add_action(sm_action) self.insert_action_group('sort', self.sort_action_group) def on_sort_action_activated(self, action: Gio.SimpleAction, target: GLib.Variant, *args): self.popover.popdown() action.change_state(target) n_sorting = str(target).strip("'") if n_sorting == self.old_sorting: return self.set_sorting(n_sorting) def set_sorting(self, n_sorting): self.old_sorting = n_sorting self.icon.set_from_icon_name( self.methods[n_sorting]['icon'], Gtk.IconSize.BUTTON ) self.set_tooltip_text(_('Sort by: {0}').format( self.methods[n_sorting]['name'] )) self.target_stack.get_child_by_name( 'posts' ).post_preview_lbox.set_gen_func( getattr(self.target_stack.source, n_sorting) ) self.target_stack.refresh() self.target_stack.on_visible_child_change() giara-0.3/giara/squeezing_viewswitcher_headerbar.py000066400000000000000000000033231376474030500227200ustar00rootroot00000000000000from gi.repository import Gtk, Handy, GObject from giara.confManager import ConfManager from giara.sort_menu_btn import SortingMenuButton class SqueezingViewSwitcherHeaderbar(Handy.WindowHandle): __gsignals__ = { 'squeeze': ( GObject.SignalFlags.RUN_FIRST, None, (bool,) ) } def __init__(self, builder, stack, view_switcher=True, sort_menu=False, sorting_methods=dict(), default_method=None, **kwargs): super().__init__(**kwargs) self.confman = ConfManager() self.reddit = self.confman.reddit self.builder = builder self.stack = stack self.headerbar = self.builder.get_object('headerbar') if sort_menu: if default_method == 'from_conf': default_method = self.confman.conf['default_front_page_view'] self.sort_btn = SortingMenuButton( self.stack, sorting_methods, default_method ) self.headerbar.pack_end(self.sort_btn) if view_switcher: self.squeezer = self.builder.get_object('squeezer') self.view_switcher = Handy.ViewSwitcher() self.view_switcher.set_policy(Handy.ViewSwitcherPolicy.AUTO) self.squeezer.add(self.view_switcher) self.squeezer.add(Gtk.Label()) self.squeezer.connect( 'notify::visible-child', lambda *args: self.emit( 'squeeze', self.squeezer.get_visible_child() != self.view_switcher ) ) self.view_switcher.set_valign(Gtk.Align.FILL) self.view_switcher.set_stack(self.stack) self.add(self.headerbar) giara-0.3/giara/subreddit_search_view.py000066400000000000000000000035161376474030500204560ustar00rootroot00000000000000from giara.constants import RESOURCE_PREFIX from gettext import gettext as _ from gi.repository import Gtk from giara.search_view import CommonSearchView from giara.sections_stack import SectionsStack class SubredditSearchView(CommonSearchView): def __init__(self, subreddit, show_post_func, **kwargs): self.headerbar_builder = Gtk.Builder.new_from_resource( f'{RESOURCE_PREFIX}/ui/post_details_headerbar.ui' ) super().__init__( self.headerbar_builder.get_object('headerbar'), **kwargs ) self.subreddit = subreddit self.show_post_func = show_post_func self.headerbar.back_btn = self.headerbar_builder.get_object( 'back_btn' ) self.headerbar.refresh_btn = self.headerbar_builder.get_object( 'refresh_btn' ) title = _('Searching in {0}').format( self.subreddit.display_name_prefixed ) self.headerbar.set_title(title) self.sections_stack = SectionsStack( [{ 'name': f'search_{self.subreddit.display_name}', 'title': title, 'gen': None, 'load_now': False }], self.show_post_func, load_now=False ) self.add(self.sections_stack) self.sections_stack.set_vexpand(True) def on_search_activate(self, *args): term = self.search_entry.get_text() lbox = self.sections_stack.get_children()[0].post_preview_lbox lbox.stop_loading = True while lbox.loading: while Gtk.events_pending(): Gtk.main_iteration() lbox.set_gen_func( lambda *args, **kwargs: self.subreddit.search( term, *args, **kwargs ) ) self.sections_stack.refresh() giara-0.3/giara/subreddit_view.py000066400000000000000000000311631376474030500171300ustar00rootroot00000000000000from giara.constants import RESOURCE_PREFIX from gettext import gettext as _ from giara.path_utils import is_image from giara.download_manager import download_img from gi.repository import Gtk, Handy, GLib from giara.sections_stack import SectionsStack from giara.squeezing_viewswitcher_headerbar import \ SqueezingViewSwitcherHeaderbar from threading import Thread from giara.time_utils import humanize_utc_timestamp from giara.simple_avatar import SimpleAvatar from giara.multireddit_list_view import MultiredditsListbox from giara.confManager import ConfManager class MultiPickerListbox(MultiredditsListbox): def __init__(self, sub, *args, **kwargs): super().__init__(*args, **kwargs) self.sub = sub self.set_filter_func(self.filter_func, None, False) self.term = '' self.set_selection_mode(Gtk.SelectionMode.SINGLE) self.connect('row-activated', self.on_row_activate) self.working = False def add(self, row, *args, **kwargs): row.in_sub = False super().add(row, *args, **kwargs) def af(): row.in_sub = self.sub in row.multi.subreddits GLib.idle_add(cb) def cb(): if row.in_sub: icon = Gtk.Image.new_from_icon_name( 'emblem-ok-symbolic', Gtk.IconSize.BUTTON ) icon.set_tooltip_text(_( 'This subreddit is already part of this multireddit' )) row.main_box.pack_end(icon, False, False, 6) row.show_all() Thread(target=af).start() def on_row_activate(self, lb, row): if self.working or row.in_sub: return self.working = True self.set_sensitive(False) def af(): row.multi.add(self.sub) GLib.idle_add(cb) def cb(): self.working = False self.populate() self.set_sensitive(True) Thread(target=af).start() def set_term(self, term): self.term = term.strip() self.invalidate_filter() def filter_func(self, row, data, notify_destroy): return not self.term or self.term.lower() in row.get_key().lower() class SubredditViewMorePopover(Gtk.Popover): def __init__(self, sub, relative_to, **kwargs): super().__init__(**kwargs) self.confman = ConfManager() self.reddit = self.confman.reddit self.sub = sub self.relative_to = relative_to self.set_modal(True) self.set_relative_to(self.relative_to) self.set_size_request(300, 400) self.builder = Gtk.Builder.new_from_resource( f'{RESOURCE_PREFIX}/ui/subreddit_more_actions.ui' ) self.stack = self.builder.get_object('stack') self.stack_container = self.builder.get_object('stack_container') self.add(self.stack_container) self.add_to_multi_box = self.builder.get_object('add_to_multi_box') self.add_to_multi_back_btn = self.builder.get_object( 'add_to_multi_back_btn' ) self.actions_buttons_box = self.builder.get_object( 'actions_buttons_box' ) self.add_to_multi_btn = self.builder.get_object('add_to_multi_btn') self.multi_picker_container = self.builder.get_object( 'multi_picker_container' ) self.add_to_multi_btn.connect( 'clicked', lambda *args: self.stack.set_visible_child(self.add_to_multi_box) ) self.add_to_multi_back_btn.connect( 'clicked', lambda *args: self.stack.set_visible_child( self.actions_buttons_box ) ) self.picker_builder = Gtk.Builder.new_from_resource( f'{RESOURCE_PREFIX}/ui/choice_picker_popover_content.ui' ) self.picker_main_container = self.picker_builder.get_object( 'main_container' ) self.multi_picker_container.add(self.picker_main_container) self.picker_main_container.set_vexpand(True) self.picker_main_container.set_hexpand(True) self.picker_main_container.set_margin_top(0) self.picker_main_container.set_margin_bottom(0) self.picker_main_container.set_margin_start(0) self.picker_main_container.set_margin_end(0) self.picker_sw = self.picker_builder.get_object('scrolled_win') self.picker_lbox = MultiPickerListbox(self.sub) self.picker_sw.add(self.picker_lbox) self.picker_search_entry = self.picker_builder.get_object( 'search_entry' ) self.picker_search_entry.connect( 'changed', lambda *args: self.picker_lbox.set_term( self.picker_search_entry.get_text() ) ) self.new_multi_box = self.builder.get_object('new_multi_box') self.new_multi_name_entry = self.builder.get_object( 'new_multi_name_entry' ) self.new_multi_create_btn = self.builder.get_object( 'new_multi_create_btn' ) self.new_multi_btn = self.builder.get_object('new_multi_btn') self.new_multi_btn.connect( 'clicked', lambda *args: self.stack.set_visible_child( self.new_multi_box ) ) self.new_multi_create_btn.connect('clicked', self.create_multi) self.new_multi_name_entry.connect( 'changed', self.on_new_multi_name_entry_changed ) self.new_multi_back_btn = self.builder.get_object( 'new_multi_back_btn' ) self.new_multi_back_btn.connect( 'clicked', lambda *args: self.stack.set_visible_child( self.add_to_multi_box ) ) def get_new_multi_name(self, *args): return self.new_multi_name_entry.get_text().strip() def on_new_multi_name_entry_changed(self, *args): self.new_multi_create_btn.set_sensitive( not not self.get_new_multi_name() ) def create_multi(self, *args): name = self.get_new_multi_name() if not name: return def af(): self.reddit.multireddit.create( display_name=name, subreddits=[self.sub] ) GLib.idle_add(cb) def cb(): self.stack.set_visible_child(self.add_to_multi_box) self.new_multi_name_entry.set_text('') self.picker_lbox.populate() Thread(target=af).start() class SubredditViewHeaderbar(SqueezingViewSwitcherHeaderbar): def __init__(self, stack, sub, **kwargs): super().__init__( Gtk.Builder.new_from_resource( f'{RESOURCE_PREFIX}/ui/subreddit_view_headerbar.ui' ), stack, view_switcher=True, sort_menu=True, sorting_methods={ 'hot': { 'name': _('Hot'), 'icon': 'hot-symbolic' }, 'new': { 'name': _('New'), 'icon': 'new-symbolic' }, 'top': { 'name': _('Top'), 'icon': 'arrow1-up-symbolic' }, 'rising': { 'name': _('Rising'), 'icon': 'rising-symbolic' }, 'controversial': { 'name': _('Controversial'), 'icon': 'controversial-symbolic' } }, **kwargs ) self.sub = sub self.refresh_btn = self.builder.get_object('refresh_btn') self.back_btn = self.builder.get_object('back_btn') self.search_btn = self.builder.get_object('search_btn') self.more_btn = Gtk.MenuButton() self.more_btn.add(Gtk.Image.new_from_icon_name( 'view-more-symbolic', Gtk.IconSize.BUTTON )) self.more_popover = SubredditViewMorePopover(self.sub, self.more_btn) self.more_btn.set_popover(self.more_popover) self.more_btn.set_tooltip_text(_('More actions')) self.headerbar.pack_end(self.more_btn) class SubredditHeading(Gtk.Bin): def __init__(self, sub, refresh_func, **kwargs): super().__init__(**kwargs) self.sub = sub self.refresh_func = refresh_func self.builder = Gtk.Builder.new_from_resource( f'{RESOURCE_PREFIX}/ui/subreddit_heading.ui' ) self.main_box = self.builder.get_object('main_box') self.avatar_container = self.builder.get_object('avatar_container') self.title_label = self.builder.get_object('title_label') self.title_label.set_text(self.sub.title) self.display_name_label = self.builder.get_object('display_name_label') self.display_name_label.set_text(self.sub.display_name_prefixed) self.join_btn = self.builder.get_object('join_btn') self.join_btn.connect('clicked', self.join_or_leave) self.refresh_join_btn() self.members_label = self.builder.get_object('members_label') self.members_label.set_text(str(self.sub.subscribers)) self.since_label = self.builder.get_object('since_label') self.since_label.set_text(str( humanize_utc_timestamp(self.sub.created_utc) )) self.avatar = SimpleAvatar( 64, self.sub.display_name, self.get_subreddit_icon ) self.avatar_container.add(self.avatar) self.avatar.set_hexpand(False) self.avatar.set_vexpand(False) self.add(self.main_box) def refresh_join_btn(self, *args): join_style_context = self.join_btn.get_style_context() for c in ('suggested-action', 'destructive-action'): join_style_context.remove_class(c) if self.sub.user_is_subscriber: join_style_context.add_class('destructive-action') self.join_btn.set_label(_('Leave')) else: join_style_context.add_class('suggested-action') self.join_btn.set_label(_('Join')) def join_or_leave(self, *args): self.join_btn.set_sensitive(False) def af(): if self.sub.user_is_subscriber: self.sub.unsubscribe() else: self.sub.subscribe() GLib.idle_add(cb) def cb(): self.refresh_func() Thread(target=af).start() def get_subreddit_icon(self): if is_image(self.sub.icon_img): return download_img(self.sub.icon_img) class SubredditView(Gtk.Box): def __init__(self, sub, show_post_func, **kwargs): super().__init__(orientation=Gtk.Orientation.VERTICAL, **kwargs) self.sub = sub self.show_post_func = show_post_func self.sections_stack = SectionsStack([ { 'name': 'posts', 'title': self.sub.display_name, 'gen': self.sub.hot, 'icon': 'hot-symbolic' }, { 'name': 'description', 'title': _('Description'), 'type': 'text', 'text': self.sub.description, 'icon': 'preferences-system-details-symbolic' } # TODO: add links section to this stack ], self.show_post_func, source=self.sub) self.headerbar = SubredditViewHeaderbar(self.sections_stack, sub) self.headerbar.refresh_btn.connect('clicked', self.refresh) self.bottom_bar = Handy.ViewSwitcherBar() self.bottom_bar.set_stack(self.sections_stack) self.headerbar.connect( 'squeeze', lambda caller, squeezed: self.bottom_bar.set_reveal(squeezed) ) self.add_heading() self.add(self.headerbar) self.headerbar.set_vexpand(False) self.headerbar.set_hexpand(True) self.add(self.sections_stack) self.sections_stack.set_vexpand(True) self.add(self.bottom_bar) self.bottom_bar.set_vexpand(False) self.bottom_bar.set_hexpand(True) def add_heading(self): heading = SubredditHeading(self.sub, self.refresh) heading_lbox_row = Gtk.ListBoxRow() heading_lbox_row.add(heading) heading_lbox_row.get_style_context().add_class( 'non-highlighted-listbox-row' ) self.sections_stack.get_child_by_name( 'posts' ).post_preview_lbox.insert(heading_lbox_row, 0) heading_lbox_row.show_all() def refresh(self, *args): def af(): self.sub._fetch() GLib.idle_add(cb) def cb(): self.sections_stack.refresh() self.add_heading() Thread(target=af).start() giara-0.3/giara/subreddits_list_view.py000066400000000000000000000112611376474030500203430ustar00rootroot00000000000000from gettext import gettext as _ from gi.repository import Gtk, GLib from giara.path_utils import is_image from giara.download_manager import download_img from giara.single_post_stream_headerbar import SinglePostStreamHeaderbar from threading import Thread from praw.models import Redditor from giara.giara_clamp import GiaraClamp from giara.common_collection_listbox_row import CommonCollectionListboxRow class SubredditsListboxRow(CommonCollectionListboxRow): def __init__(self, subreddit, **kwargs): self.subreddit = subreddit if isinstance(subreddit, Redditor): self.subreddit.display_name = self.subreddit.name self.subreddit.display_name_prefixed = 'u/'+self.subreddit.name self.subreddit.title = self.subreddit.name super().__init__( self.subreddit.display_name_prefixed, self.subreddit.title, self.get_subreddit_icon, avatar_name=self.subreddit.display_name, **kwargs ) def get_subreddit_icon(self): if is_image(self.subreddit.icon_img): return download_img(self.subreddit.icon_img) class SubredditsListbox(Gtk.ListBox): def __init__(self, subreddits_gen_func, show_sub_func=None, load_now=True, sort=False, **kwargs): super().__init__(**kwargs) self.sort = sort self.get_style_context().add_class('card') self.subreddits_gen_func = subreddits_gen_func self.show_sub_func = show_sub_func self.subreddits = [] self.connect('row-activated', self.on_row_activate) self.initialized = load_now self.refresh_thread = None if load_now: self.refresh() self.set_selection_mode(Gtk.SelectionMode.NONE) self.set_header_func(self.separator_header_func) def set_gen_func(self, gen_func): self.subreddits_gen_func = gen_func def separator_header_func(self, row, prev_row): if ( prev_row is not None and row.get_header() is None ): row.set_header( Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL) ) def empty(self): while True: row = self.get_row_at_index(0) if row: self.remove(row) else: break def refresh(self): self.empty() if self.refresh_thread is not None: self.refresh_thread_stop = True self.refresh_thread.join() self.refresh_thread_stop = False def af(): self.subreddits = list(self.subreddits_gen_func(limit=None)) if self.sort: self.subreddits.sort(key=lambda sub: sub.display_name.lower()) for sub in self.subreddits: if self.refresh_thread_stop: return try: sub.display_name # fetch the object? except Exception: pass GLib.idle_add(cb, sub) GLib.idle_add(final_cb) def cb(sub): self.add(SubredditsListboxRow(sub)) def final_cb(): self.initialized = True self.show_all() self.refresh_thread = Thread(target=af) self.refresh_thread.start() def add(self, *args, **kwargs): super().add(*args, **kwargs) self.show_all() def populate(self): for sub in self.subreddits: self.add( SubredditsListboxRow(sub) ) self.show_all() self.initialized = True def on_row_activate(self, lb, row): if self.show_sub_func is not None: self.show_sub_func(row.subreddit) class SubredditsListView(Gtk.Box): def __init__(self, subreddits_gen_func, show_sub_func, sort=False, **kwargs): super().__init__(orientation=Gtk.Orientation.VERTICAL, **kwargs) self.subreddits_gen_func = subreddits_gen_func self.show_sub_func = show_sub_func self.sw = Gtk.ScrolledWindow() self.sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) self.listbox = SubredditsListbox( self.subreddits_gen_func, self.show_sub_func, sort=sort ) self.clamp = GiaraClamp() self.clamp.add(self.listbox) self.sw.add(self.clamp) self.headerbar = SinglePostStreamHeaderbar(_('Subreddits')) self.headerbar.refresh_btn.connect( 'clicked', lambda *args: self.listbox.refresh() ) self.add(self.headerbar) self.headerbar.set_vexpand(False) self.headerbar.set_hexpand(True) self.add(self.sw) self.sw.set_vexpand(True) giara-0.3/giara/time_utils.py000066400000000000000000000017101376474030500162620ustar00rootroot00000000000000from gi.repository import GLib from datetime import datetime from dateutil import tz def utc_datetime_to_now(in_time: datetime) -> datetime: return in_time.astimezone(tz.tzlocal()) def utc_timestamp_to_now(timestamp: str) -> datetime: return utc_datetime_to_now( datetime.fromtimestamp(timestamp, tz.tzutc()) ) def humanize(dt: datetime) -> str: tzl = tz.tzlocal() timezone_seconds = tzl.utcoffset(dt).seconds timezone_str = '{0}{1}:{2}'.format( '+' if timezone_seconds >= 0 else '', format(int(timezone_seconds/3600), '02'), format(int( (timezone_seconds - (int(timezone_seconds/3600)*3600))/60 ), '02') ) return GLib.DateTime( GLib.TimeZone(timezone_str), dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second ).to_local().format('%-e %b %Y\n%X') def humanize_utc_timestamp(timestamp: str) -> str: return humanize(utc_timestamp_to_now(timestamp)) giara-0.3/giara/user_heading.py000066400000000000000000000054301376474030500165440ustar00rootroot00000000000000from giara.constants import RESOURCE_PREFIX from gettext import gettext as _ from gi.repository import Gtk, GLib from giara.path_utils import is_image from giara.download_manager import download_img from threading import Thread from giara.confManager import ConfManager from giara.time_utils import humanize_utc_timestamp from giara.simple_avatar import SimpleAvatar class UserHeading(Gtk.Bin): def __init__(self, user, refresh_func, **kwargs): super().__init__(**kwargs) self.confman = ConfManager() self.reddit = self.confman.reddit self.user = user self.refresh_func = refresh_func self.builder = Gtk.Builder.new_from_resource( f'{RESOURCE_PREFIX}/ui/redditor_heading.ui' ) self.main_box = self.builder.get_object('main_box') self.avatar_container = self.builder.get_object('avatar_container') self.name_label = self.builder.get_object('name_label') self.name_label.set_text(self.user.name) self.follow_btn = self.builder.get_object('follow_btn') self.follow_btn.connect('clicked', self.follow_or_unfollow) self.refresh_follow_btn() self.karma_label = self.builder.get_object('karma_label') self.karma_label.set_text(str(self.user.total_karma)) self.cakeday_label = self.builder.get_object('cakeday_label') self.cakeday_label.set_text(str( humanize_utc_timestamp(self.user.created_utc) )) self.avatar = SimpleAvatar( 64, self.user.name, self.get_user_icon ) self.avatar_container.add(self.avatar) self.avatar.set_hexpand(False) self.avatar.set_vexpand(False) self.add(self.main_box) def refresh_follow_btn(self, *args): sub = self.reddit.subreddit('u_'+self.user.name) follow_style_context = self.follow_btn.get_style_context() for c in ('suggested-action', 'destructive-action'): follow_style_context.remove_class(c) if sub.user_is_subscriber: follow_style_context.add_class('destructive-action') self.follow_btn.set_label(_('Unfollow')) else: follow_style_context.add_class('suggested-action') self.follow_btn.set_label(_('Follow')) def follow_or_unfollow(self, *args): self.follow_btn.set_sensitive(False) def af(): sub = self.reddit.subreddit('u_'+self.user.name) if sub.user_is_subscriber: sub.unsubscribe() else: sub.subscribe() GLib.idle_add(cb) def cb(): self.refresh_func() Thread(target=af).start() def get_user_icon(self): if is_image(self.user.icon_img): return download_img(self.user.icon_img) giara-0.3/giara/user_view.py000066400000000000000000000015771376474030500161270ustar00rootroot00000000000000from gi.repository import Gtk from giara.single_post_stream_view import SinglePostStreamView from giara.user_heading import UserHeading class UserView(SinglePostStreamView): def __init__(self, user, show_post_func): self.user = user super().__init__( self.user.new, self.user.name, 'u/'+self.user.name, show_post_func ) self.add_heading() def add_heading(self, *args): heading = UserHeading(self.user, self.refresh) heading_lbox_row = Gtk.ListBoxRow() heading_lbox_row.add(heading) heading_lbox_row.get_style_context().add_class( 'non-highlighted-listbox-row' ) self.section_stack.get_children()[0].post_preview_lbox.insert( heading_lbox_row, 0 ) self.show_all() def refresh(self, *args): super().refresh(*args) self.add_heading() giara-0.3/meson.build000066400000000000000000000057631376474030500146250ustar00rootroot00000000000000project('giara', version: '0.3', meson_version: '>= 0.50.0', license: 'GPL3' ) profile = get_option('profile') description = 'An app for Reddit' prettyname = 'Giara' prettylicense = 'GPL-3.0+' author = 'gabmus' authorfullname = 'Gabriele Musco' authoremail = 'gabmus@disroot.org' domain = 'org' # app_id built as: domain.author.project_name app_id = '.'.join([ domain, author, meson.project_name() ]) app_id_in = app_id app_id_aspath = '/'.join([ domain, author, meson.project_name() ]) app_id_in_aspath = app_id_aspath app_version = meson.project_version() if profile == 'development' # app_id += '.dev' # app_id_aspath += 'dev' app_version += '-' + run_command( 'git', 'rev-parse', '--short', 'HEAD' ).stdout().strip() prettyname += ' (Development)' endif gitrepo = 'https://gitlab.gnome.org/World/'+meson.project_name() website = 'https://'+meson.project_name()+'.'+author+'.org' # NOTE: if you contributed, feel free to add your name and email address here contributors = '\n'.join([ 'Gabriele Musco (GabMus) <gabmus@disroot.org>' ]) translators = '\n'.join([ 'Spanish: Daniel Mustieles <daniel.mustieles@gmail.com>', 'Croatian: Milo Ivir <mail@milotype.de>', 'Indonesian: Andika Triwidada <andika@gmail.com>', 'Indonesian: Kukuh Syafaat <kukuhsyafaat@gnome.org>', 'Italian: Gabriele Musco (GabMus) <gabmus@disroot.org>', 'Brazilian Portoguese: Gustavo Peredo <gustavomperedo@protonmail.com>', 'Slovenian: Matej Urbančič <mateju@svn.gnome.org>', 'Russian: Alexander Postol <grief.north@gmail.com>', 'Ukrainian: Yuri Chornoivan <yurchor@ukr.net>', 'Czech: Marek Černocký <marek@manet.cz>', 'Swedish: Anders Jonsson <anders.jonsson@norsjovallen.se>', 'French: Thibault Martin <thibaultamartin@gnome.org>', 'German: Harry Haller <random.error-code@web.de>' ]) designers = '\n'.join([ 'Icon: @martinpfirth', 'Icon: Gabriele Musco (GabMus) <gabmus@disroot.org>', 'UI: Gabriele Musco (GabMus) <gabmus@disroot.org>' ]) i18n = import('i18n') python = import('python') py_installation = python.find_installation('python3') if not py_installation.found() error('No valid python3 binary found') endif dependency('glib-2.0') dependency('gobject-introspection-1.0', version: '>=1.35.9') dependency('gtk+-3.0', version :'>=3.24') gnome = import('gnome') prefix = get_option('prefix') # should be /usr bindir = get_option('bindir') # should be bin datadir = get_option('datadir') # should be /usr/share pkgdatadir = join_paths(prefix, datadir, meson.project_name()) # pythondir = join_paths(prefix, py_installation.get_path('purelib')) pythondir = py_installation.get_install_dir() localedir = join_paths(prefix, get_option('localedir')) install_subdir( meson.project_name(), install_dir: py_installation.get_install_dir() ) subdir('data') subdir('bin') subdir('po') meson.add_install_script('meson_post_install.py') giara-0.3/meson_options.txt000066400000000000000000000002021376474030500160770ustar00rootroot00000000000000option ( 'profile', type: 'combo', choices: [ 'default', 'development' ], value: 'default' ) giara-0.3/meson_post_install.py000066400000000000000000000007201376474030500167350ustar00rootroot00000000000000#!/usr/bin/env python3 from os import environ, path from subprocess import call prefix = environ.get('MESON_INSTALL_PREFIX', '/usr/local') datadir = path.join(prefix, 'share') destdir = environ.get('DESTDIR', '') if not destdir: print('Updating icon cache...') call(['gtk-update-icon-cache', '-qtf', path.join(datadir, 'icons', 'hicolor')]) print("Installing new Schemas") call(['glib-compile-schemas', path.join(datadir, 'glib-2.0/schemas')]) giara-0.3/misc/000077500000000000000000000000001376474030500134035ustar00rootroot00000000000000giara-0.3/misc/org.gabmus.giara.Source.svg000066400000000000000000005430201376474030500205150ustar00rootroot00000000000000 Adwaita Icon Template image/svg+xml GNOME Design Team Adwaita Icon Template Hicolor Symbolic battery is full and there is no a/c connected. giara-0.3/new_release.sh000077500000000000000000000030341376474030500153000ustar00rootroot00000000000000#!/bin/bash if [ -z "$2" ]; then echo "Usage: $0 VERSION_NUMBER \"CHANGELOG_LINE1;CHANGELOG_LINE2;...\"" exit fi AUTHOR='gabmus' PROJECT_NAME=$(grep "project('" meson.build | sed "s/project('//;s/',//") n_version="$1" changelog="$2" sed -i "s/ version: '.*',/ version: '$n_version',/" meson.build MANIFEST_PATH="dist/flatpak/org.$AUTHOR.$PROJECT_NAME.json" TARGET_MODULE="$PROJECT_NAME" python3 -c " import json manifest = None with open('$MANIFEST_PATH') as fd: manifest = json.loads(fd.read()) for i, module in enumerate(manifest['modules']): if module['name'] == '$TARGET_MODULE': manifest['modules'][i]['sources'][0]['tag'] = '$n_version' break with open('$MANIFEST_PATH', 'w') as fd: fd.write(json.dumps(manifest, indent=4, sort_keys=False)) " RELEASE_TIME=$(date +%s) release_text=$(python3 -c " def mprint(t): print(t, end='\\\\n') mprint(' ') mprint(' ') mprint('
    ') for line in '$changelog'.split(';'): mprint('
  • '+line.strip()+'
  • ') mprint('
') mprint('
') mprint('
') ") APPDATA_PATH="data/org.$AUTHOR.$PROJECT_NAME.appdata.xml.in" target_line=$(sed -n "/~ ~" $APPDATA_PATH giara-0.3/po/000077500000000000000000000000001376474030500130665ustar00rootroot00000000000000giara-0.3/po/LINGUAS000066400000000000000000000001251376474030500141110ustar00rootroot00000000000000# Please keep this list alphabetically sorted cs de es fr hr id it pt_BR sl ru sv uk giara-0.3/po/POTFILES.in000066400000000000000000000026221376474030500146450ustar00rootroot00000000000000data/org.gabmus.giara.appdata.xml.in data/org.gabmus.giara.desktop.in data/org.gabmus.giara.service.desktop.in data/ui/comment_box.ui data/ui/headerbar.ui data/ui/new_post_window.ui data/ui/headerbar.ui data/ui/post_details_headerbar.ui data/ui/headerbar_with_back_and_squeezer.ui data/ui/login_view.ui data/ui/main_ui.ui data/ui/menu.ui data/ui/new_post_menu.ui data/ui/new_post_window.ui data/ui/post_body.ui data/ui/comment_box.ui data/ui/post_body.ui data/ui/post_preview.ui data/ui/post_details_headerbar.ui data/ui/post_preview.ui data/ui/redditor_heading.ui data/ui/shortcutsWindow.ui data/ui/subreddit_heading.ui data/ui/subreddit_more_actions.ui data/ui/subreddit_view_headerbar.ui giara/auth.py giara/choice_picker.py giara/common_post_box.py giara/flair_label.py giara/front_page_headerbar.py giara/settings_window.py giara/inbox_view.py giara/front_page_headerbar.py giara/inbox_view.py giara/left_stack.py giara/inbox_view.py giara/post_details_view.py giara/left_stack.py giara/multireddit_list_view.py giara/markdown_view.py giara/multireddit_view.py giara/new_post_window.py giara/notification_manager.py giara/post_details_view.py giara/search_view.py giara/settings_window.py giara/single_post_stream_headerbar.py giara/subreddit_view.py giara/sort_menu_btn.py giara/subreddit_search_view.py giara/left_stack.py giara/subreddits_list_view.py giara/left_stack.py giara/subreddit_view.py giara/user_heading.py giara-0.3/po/README.md000066400000000000000000000014621376474030500143500ustar00rootroot00000000000000# How to create and update a translation First, run the script `update_potfiles.sh` like this, where `LANGUAGE` is the language code that you want to add or update (`it`: italian, `fr`: french, `es`: spanish...): ```bash cd po ./update_potfiles.sh LANGUAGE ``` It will ask for an email, provide yours if you want and it will be used to credit you. It's historically been used to report issues in the translation for a specific language, but nowadays with issue systems and easier bug reporting than ever it's not really necessary. Finally edit the `.po` file that was just created with the language code you used. You can use a normal text editor or a simpler tool like **lokalize** or **poedit** (you can probably find both in your distribution's repositories). If you need any help, feel free to open an issue. giara-0.3/po/cs.po000066400000000000000000000324011376474030500140330ustar00rootroot00000000000000# Czech translation for giara. # Copyright (C) 2020 giara's COPYRIGHT HOLDER # This file is distributed under the same license as the giara package. # Marek Černocký , 2020. # msgid "" msgstr "" "Project-Id-Version: giara\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/giara/issues\n" "POT-Creation-Date: 2020-11-09 17:34+0000\n" "PO-Revision-Date: 2020-11-09 18:48+0100\n" "Last-Translator: Marek Černocký \n" "Language-Team: Czech \n" "Language: cs\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" #: data/org.gabmus.giara.appdata.xml.in:4 msgid "Giara" msgstr "Giara" #: data/org.gabmus.giara.appdata.xml.in:5 msgid "Gabriele Musco" msgstr "Gabriele Musco" #: data/org.gabmus.giara.appdata.xml.in:6 data/org.gabmus.giara.desktop.in:5 msgid "An app for Reddit" msgstr "Aplikace pro Reddit" #: data/org.gabmus.giara.appdata.xml.in:15 msgid "An app for Reddit." msgstr "Aplikace pro Reddit." #: data/org.gabmus.giara.appdata.xml.in:16 msgid "Browse Reddit from your Linux desktop or smartphone." msgstr "Procházejte si Reddit ze svého linuxového počítače nebo telefonu." #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: data/org.gabmus.giara.desktop.in:14 msgid "reddit;" msgstr "reddit;" #: data/ui/comment_box.ui:77 msgid "Collapse replies" msgstr "Sbalit odpovědi" #: data/ui/comment_box.ui:157 data/ui/post_body.ui:141 #: data/ui/post_preview.ui:122 msgid "Upvote" msgstr "Hlasovat pro" #: data/ui/comment_box.ui:177 data/ui/post_body.ui:177 #: data/ui/post_preview.ui:158 msgid "Downvote" msgstr "Hlasovat proti" #: data/ui/comment_box.ui:206 data/ui/post_body.ui:163 #: data/ui/post_preview.ui:144 msgid "Ups" msgstr "Hlasů" #: data/ui/comment_box.ui:226 data/ui/post_body.ui:341 msgid "Reply" msgstr "Odpovědět" #: data/ui/comment_box.ui:246 data/ui/post_body.ui:401 #: data/ui/post_preview.ui:384 msgid "Save" msgstr "Uložit" #: data/ui/comment_box.ui:266 data/ui/post_body.ui:421 #: data/ui/post_preview.ui:404 msgid "Share" msgstr "Sdílet" #: data/ui/comment_box.ui:286 data/ui/post_body.ui:441 #: giara/new_post_window.py:419 msgid "Edit" msgstr "Upravit" #: data/ui/comment_box.ui:306 data/ui/post_body.ui:461 #: data/ui/post_preview.ui:424 msgid "Delete" msgstr "Smazat" #: data/ui/headerbar.ui:103 giara/inbox_view.py:134 giara/left_stack.py:159 msgid "Inbox" msgstr "Doručená pošta" #: data/ui/headerbar.ui:120 giara/left_stack.py:119 giara/search_view.py:57 #: giara/subreddits_list_view.py:136 msgid "Subreddits" msgstr "Subreddity" #: data/ui/headerbar.ui:137 giara/left_stack.py:174 #: giara/multireddit_list_view.py:111 msgid "Multireddits" msgstr "Multireddity" #: data/ui/headerbar.ui:154 data/ui/headerbar.ui:254 giara/left_stack.py:89 #: giara/left_stack.py:97 msgid "Profile" msgstr "Profil" #: data/ui/headerbar.ui:171 msgid "Saved" msgstr "Uložené" #: data/ui/headerbar.ui:188 msgid "Log Out" msgstr "Odhlásit" #: data/ui/headerbar.ui:233 data/ui/new_post_window.ui:17 msgid "New post" msgstr "Nový příspěvek" #: data/ui/headerbar.ui:272 msgid "Messages in your inbox" msgstr "Zprávy ve vaší doručené poště" #: data/ui/headerbar.ui:298 data/ui/post_details_headerbar.ui:29 #: data/ui/subreddit_view_headerbar.ui:43 msgid "Refresh" msgstr "Aktualizovat" #: data/ui/headerbar.ui:322 msgid "Menu" msgstr "Nabídka" #: data/ui/headerbar.ui:349 data/ui/subreddit_view_headerbar.ui:67 #: giara/left_stack.py:143 msgid "Search" msgstr "Hledat" #: data/ui/new_post_window.ui:20 msgid "Cancel" msgstr "Zrušit" #: data/ui/new_post_window.ui:28 msgid "Send" msgstr "Odeslat" #: data/ui/new_post_window.ui:72 msgid "Post title…" msgstr "Název příspěvku…" #: data/ui/new_post_window.ui:127 msgid "Link…" msgstr "odkaz…" #: data/ui/new_post_window.ui:145 msgid "Select media…" msgstr "Vybrat multimédia…" #: data/ui/post_details_headerbar.ui:14 #: data/ui/headerbar_with_back_and_squeezer.ui:28 #: data/ui/subreddit_more_actions.ui:74 data/ui/subreddit_more_actions.ui:171 #: data/ui/subreddit_view_headerbar.ui:28 msgid "Back" msgstr "Zpět" #: data/ui/login_view.ui:83 msgid "Welcome to Giara" msgstr "Vítejte v aplikaci Giara." #: data/ui/login_view.ui:96 msgid "Login" msgstr "Přihlásit se" #: data/ui/login_view.ui:136 msgid "Waiting for authentication…" msgstr "Čeká se na ověření…" #: data/ui/main_ui.ui:70 msgid "Close" msgstr "Zavřít" #: data/ui/menu.ui:6 msgid "Preferences" msgstr "Předvolby" #: data/ui/menu.ui:10 msgid "Keyboard Shortcuts" msgstr "Klávesové zkratky" #: data/ui/menu.ui:14 msgid "About Giara" msgstr "O aplikaci Giara" #: data/ui/new_post_menu.ui:6 giara/flair_label.py:49 msgid "Text" msgstr "Text" #: data/ui/new_post_menu.ui:10 giara/flair_label.py:55 msgid "Link" msgstr "Odkaz" #: data/ui/new_post_menu.ui:14 msgid "Media" msgstr "Multimédia" #: data/ui/post_body.ui:218 data/ui/post_preview.ui:199 msgid "Pinned" msgstr "Připíchnuto" #: data/ui/post_body.ui:361 data/ui/post_preview.ui:344 msgid "Open media" msgstr "Otevřít multimédia" #: data/ui/post_body.ui:381 data/ui/post_preview.ui:364 msgid "Open link" msgstr "Otevřít odkaz" #: data/ui/post_preview.ui:301 msgid "Comments" msgstr "Komentáře" #: data/ui/redditor_heading.ui:59 giara/user_heading.py:57 msgid "Follow" msgstr "Sledovat" #: data/ui/redditor_heading.ui:83 msgid "Karma" msgstr "Karma" #: data/ui/redditor_heading.ui:113 msgid "Cake day" msgstr "Výročí" #: data/ui/shortcutsWindow.ui:13 giara/settings_window.py:256 msgid "General" msgstr "Obecné" #: data/ui/shortcutsWindow.ui:18 msgid "Open Keyboard Shortcuts" msgstr "Otevřít klávesové zkratky" #: data/ui/shortcutsWindow.ui:25 msgid "Open Menu" msgstr "Otevřít nabídku" #: data/ui/shortcutsWindow.ui:32 msgid "Open Preferences" msgstr "Otevřít předvolby" #: data/ui/shortcutsWindow.ui:39 msgid "Quit" msgstr "Ukončit" #: data/ui/subreddit_heading.ui:77 giara/subreddit_view.py:281 msgid "Join" msgstr "Přidat se" #: data/ui/subreddit_heading.ui:101 msgid "Members" msgstr "Členů" #: data/ui/subreddit_heading.ui:131 msgid "Since" msgstr "Vznikl" #: data/ui/subreddit_more_actions.ui:32 data/ui/subreddit_more_actions.ui:93 msgid "Add to multireddit" msgstr "Přidat do multiredditu" #: data/ui/subreddit_more_actions.ui:137 data/ui/subreddit_more_actions.ui:190 msgid "Create multireddit" msgstr "Vytvořit multireddit" #: data/ui/subreddit_more_actions.ui:222 msgid "Multireddit name…" msgstr "Název multiredditu…" #: data/ui/subreddit_more_actions.ui:232 msgid "Create" msgstr "Vytvořit" #: giara/auth.py:20 msgid "Error getting client with refresh token, retrying…" msgstr "" "Chyby při získávání klienta pomocí občerstveného tiketu, zkouší se znovu…" #: giara/auth.py:30 msgid "Error authorizing Reddit client, retrying…" msgstr "Chyba při ověřovaní klienta Redditu, zkouší se znovu…" #: giara/auth.py:35 msgid "Error authorizing Reddit client after retry, quitting…" msgstr "Chyba při opakovaném ověřovaní klienta Redditu, končí se…" #: giara/choice_picker.py:51 giara/choice_picker.py:63 #: giara/new_post_window.py:212 giara/new_post_window.py:213 #: giara/new_post_window.py:219 msgid "None" msgstr "Nic" #: giara/common_post_box.py:108 msgid "Are you sure you want to delete this item?" msgstr "Opravdu chcete smazat tuto položku?" #: giara/common_post_box.py:141 msgid "Link copied to clipboard" msgstr "Odkaz byl zkopírován do schránky" #: giara/common_post_box.py:244 msgid "Comment: " msgstr "Komentář:" #: giara/common_post_box.py:253 giara/flair_label.py:19 msgid "Message" msgstr "Zpráva" #: giara/common_post_box.py:259 giara/inbox_view.py:41 #: giara/post_details_view.py:97 msgid "Author unknown" msgstr "Neznámý autor" #: giara/flair_label.py:11 msgid "Comment" msgstr "Komentář" #: giara/flair_label.py:15 msgid "Post" msgstr "Příspěvek" #: giara/flair_label.py:37 msgid "Image" msgstr "Obrázek" #: giara/flair_label.py:43 msgid "Video" msgstr "Video" #: giara/front_page_headerbar.py:22 giara/settings_window.py:284 msgid "Best" msgstr "Nejlepší" #: giara/front_page_headerbar.py:26 giara/settings_window.py:284 #: giara/single_post_stream_headerbar.py:19 giara/subreddit_view.py:202 msgid "Hot" msgstr "Žhavé" #: giara/front_page_headerbar.py:30 giara/settings_window.py:284 #: giara/inbox_view.py:68 giara/single_post_stream_headerbar.py:23 #: giara/subreddit_view.py:206 msgid "New" msgstr "Nové" #: giara/front_page_headerbar.py:34 giara/settings_window.py:284 #: giara/single_post_stream_headerbar.py:27 giara/subreddit_view.py:210 msgid "Top" msgstr "Špička" #: giara/front_page_headerbar.py:38 giara/settings_window.py:284 #: giara/single_post_stream_headerbar.py:31 giara/subreddit_view.py:214 msgid "Rising" msgstr "Rostoucí" #: giara/front_page_headerbar.py:42 giara/settings_window.py:285 #: giara/single_post_stream_headerbar.py:35 giara/subreddit_view.py:218 msgid "Controversial" msgstr "Sporné" #: giara/front_page_headerbar.py:88 #, python-brace-format msgid "{0} Karma" msgid_plural "{0} Karma" msgstr[0] "{0} karma" msgstr[1] "{0} karmy" msgstr[2] "{0} karem" #: giara/front_page_headerbar.py:153 msgid "Do you want to log out? This will close the application." msgstr "Chcete se odhlásit? Odhlášením se zavře aplikace." #: giara/settings_window.py:107 msgid "Choose a folder" msgstr "Výběr složky" #: giara/settings_window.py:260 msgid "General Settings" msgstr "Obecná nastavení" #: giara/settings_window.py:281 msgid "Default view" msgstr "Výchozí zobrazení" #: giara/settings_window.py:294 msgid "Cache" msgstr "Mezipaměť" #: giara/settings_window.py:307 msgid "Clear cache" msgstr "Mazání mezipaměti" #: giara/settings_window.py:307 msgid "Clear" msgstr "Vymazat" #: giara/settings_window.py:318 msgid "View" msgstr "Zobrazení" #: giara/settings_window.py:322 msgid "View Settings" msgstr "Nastavení zobrazení" #: giara/settings_window.py:325 msgid "Dark mode" msgstr "Tmavý režim" #: giara/settings_window.py:330 msgid "Show thumbnails in post previews" msgstr "Zobrazovat miniatury v náhledech příspěvků" #: giara/settings_window.py:354 msgid "Max thumbnail width" msgstr "Maximální šířka miniatury" #: giara/settings_window.py:358 msgid "Use 0 to remove the limit" msgstr "Použitím hodnoty 0 omezení zrušíte." #: giara/settings_window.py:368 msgid "Privacy" msgstr "Soukromí" #: giara/settings_window.py:372 msgid "Link replacement" msgstr "Náhrada odkazu" #: giara/settings_window.py:375 msgid "Replace Twitter links with Nitter" msgstr "Nahrazovat odkazy na Twitter pomocí Nitter" #: giara/settings_window.py:380 msgid "Replace YouTube links with Invidious" msgstr "Nahrazovat odkazy na YouTube pomocí Invidious" #: giara/settings_window.py:390 msgid "Invidious instance" msgstr "Instance Invidious" #: giara/settings_window.py:392 msgid "Naked domain name only, no https://" msgstr "Jen holý název domény, ne https://" #: giara/inbox_view.py:52 #, python-brace-format msgid "Comment in \"{0}\"" msgstr "Komentář v „{0}“" #: giara/left_stack.py:33 msgid "Posts" msgstr "Příspěvky" #: giara/left_stack.py:63 msgid "Front page" msgstr "Hlavní stránka" #: giara/left_stack.py:70 giara/left_stack.py:77 msgid "Saved posts" msgstr "Uložené příspěvky" #: giara/left_stack.py:299 giara/subreddit_search_view.py:26 #, python-brace-format msgid "Searching in {0}" msgstr "Hledá se v {0}" #: giara/post_details_view.py:32 msgid "Load more comments" msgstr "Načíst další komentáře" #: giara/markdown_view.py:42 msgid "Image: " msgstr "Obrázek:" #: giara/markdown_view.py:42 msgid "[Image]" msgstr "[Obrázek]" #: giara/multireddit_view.py:59 msgid "Remove from multireddit" msgstr "Odebrat z multiredditu" #: giara/multireddit_view.py:85 msgid "Edit multireddit" msgstr "Upravit multireddit" #: giara/new_post_window.py:100 msgid "Choose an image or video to upload" msgstr "Výběr obrázku nebo videa pro nahrání" #: giara/new_post_window.py:262 msgid "Select a subreddit…" msgstr "Vybrat subreddit…" #: giara/new_post_window.py:324 msgid "Select a flair…" msgstr "Vybrat nálepku" #: giara/new_post_window.py:389 msgid "New comment" msgstr "Nový komentář" #: giara/new_post_window.py:418 msgid "Editing" msgstr "Úpravy" #: giara/notification_manager.py:31 #, python-brace-format msgid "{0} new message" msgid_plural "{0} new messages" msgstr[0] "{0} nová zpráva" msgstr[1] "{0} nové zprávy" msgstr[2] "{0} nových zpráv" #: giara/notification_manager.py:36 #, python-brace-format msgid "{0} more" msgid_plural "{0} more" msgstr[0] "{0} další" msgstr[1] "{0} další" msgstr[2] "{0} dalších" #: giara/search_view.py:65 msgid "Users" msgstr "Uživatelé" #: giara/subreddit_view.py:40 msgid "This subreddit is already part of this multireddit" msgstr "Tento subreddit je již součástí tohoto multiredditu" #: giara/subreddit_view.py:235 msgid "More actions" msgstr "Další činnosti" #: giara/subreddit_view.py:278 msgid "Leave" msgstr "Opustit" #: giara/subreddit_view.py:317 msgid "Description" msgstr "Popis" #: giara/sort_menu_btn.py:73 #, python-brace-format msgid "Sort by: {0}" msgstr "Řadit podle: {0}" #: giara/user_heading.py:54 msgid "Unfollow" msgstr "Zrušit sledování" giara-0.3/po/de.po000066400000000000000000000446711376474030500140320ustar00rootroot00000000000000# German translation for giara. # Copyright (C) 2020 Giara's COPYRIGHT HOLDER # This file is distributed under the same license as the giara package. # Harry Haller , 2020. # msgid "" msgstr "" "Project-Id-Version: giara master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/giara/issues\n" "POT-Creation-Date: 2020-11-16 00:26+0000\n" "PO-Revision-Date: 2020-11-16 00:26+0000\n" "Last-Translator: Harry Haller \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" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: ../giara/common_post_box.py:108 msgid "Are you sure you want to delete this item?" msgstr "Bist du sicher, dass du dieses Element löschen willst?" #: ../giara/common_post_box.py:141 msgid "Link copied to clipboard" msgstr "Link in die Zwischenablage kopiert" #: ../giara/common_post_box.py:244 msgid "Comment: " msgstr "Kommentar: " #: ../giara/common_post_box.py:253 ../giara/flair_label.py:19 msgid "Message" msgstr "Nachricht" #: ../giara/common_post_box.py:259 ../giara/post_details_view.py:97 #: ../giara/inbox_view.py:41 msgid "Author unknown" msgstr "Autor unbekannt" #: ../giara/new_post_window.py:100 msgid "Choose an image or video to upload" msgstr "Wähle ein Bild oder Video zum Hochladen" #: ../giara/new_post_window.py:212 ../giara/new_post_window.py:213 #: ../giara/new_post_window.py:219 ../giara/choice_picker.py:51 #: ../giara/choice_picker.py:63 msgid "None" msgstr "Keines" #: ../giara/new_post_window.py:262 msgid "Select a subreddit…" msgstr "Wähle ein Subreddit…" #: ../giara/new_post_window.py:324 msgid "Select a flair…" msgstr "Wähle ein Flair…" #: ../giara/new_post_window.py:389 msgid "New comment" msgstr "Neuer Kommentar" #: ../giara/new_post_window.py:418 msgid "Editing" msgstr "Bearbeitung" #: ../giara/new_post_window.py:419 msgid "Edit" msgstr "Bearbeiten" #: ../giara/auth.py:20 msgid "Error getting client with refresh token, retrying…" msgstr "Fehler beim Erhalt des Clients mit Aktualisierungs-Token, erneuter Versuch…" #: ../giara/auth.py:30 msgid "Error authorizing Reddit client, retrying…" msgstr "Fehler bei Authentifizierung des Reddit-Clients, erneuter Versuch…" #: ../giara/auth.py:35 msgid "Error authorizing Reddit client after retry, quitting…" msgstr "Fehler bei erneutem Authentifizierungsversuch des Reddit-Clients, Abbruch…" #: ../giara/subreddit_search_view.py:26 ../giara/left_stack.py:299 #, python-brace-format msgid "Searching in {0}" msgstr "Suche in {0}" #: ../giara/markdown_view.py:42 msgid "Image: " msgstr "Bild: " #: ../giara/markdown_view.py:42 msgid "[Image]" msgstr "[Bild]" #: ../giara/multireddit_view.py:59 msgid "Remove from multireddit" msgstr "Entferne von Multireddit" #: ../giara/multireddit_view.py:85 msgid "Edit multireddit" msgstr "Bearbeite Multireddit" #: ../giara/notification_manager.py:31 #, python-brace-format msgid "{0} new message" msgid_plural "{0} new messages" msgstr[0] "{0} neue Nachricht" msgstr[1] "{0} neue Nachrichten" #: ../giara/notification_manager.py:36 #, python-brace-format msgid "{0} more" msgid_plural "{0} more" msgstr[0] "{0} weitere" msgstr[1] "{0} weitere" #: ../giara/post_details_view.py:32 msgid "Load more comments" msgstr "Lade weitere Kommentare" #: ../giara/left_stack.py:33 msgid "Posts" msgstr "Beiträge" #: ../giara/left_stack.py:63 msgid "Front page" msgstr "Startseite" #: ../giara/left_stack.py:70 ../giara/left_stack.py:77 msgid "Saved posts" msgstr "Gespeicherte Beiträge" #: ../giara/left_stack.py:89 ../giara/left_stack.py:97 msgid "Profile" msgstr "Profil" #: ../giara/left_stack.py:119 ../giara/search_view.py:57 #: ../giara/subreddits_list_view.py:136 msgid "Subreddits" msgstr "Subreddits" #: ../giara/left_stack.py:143 msgid "Search" msgstr "Suche" #: ../giara/left_stack.py:159 ../giara/inbox_view.py:134 msgid "Inbox" msgstr "Nachrichten" #: ../giara/left_stack.py:174 ../giara/multireddit_list_view.py:111 msgid "Multireddits" msgstr "Multireddits" #: ../giara/single_post_stream_headerbar.py:19 ../giara/settings_window.py:284 #: ../giara/front_page_headerbar.py:26 ../giara/subreddit_view.py:202 msgid "Hot" msgstr "Heiß" #: ../giara/single_post_stream_headerbar.py:23 ../giara/inbox_view.py:68 #: ../giara/settings_window.py:284 ../giara/front_page_headerbar.py:30 #: ../giara/subreddit_view.py:206 msgid "New" msgstr "Neu" #: ../giara/single_post_stream_headerbar.py:27 ../giara/settings_window.py:284 #: ../giara/front_page_headerbar.py:34 ../giara/subreddit_view.py:210 msgid "Top" msgstr "Top" #: ../giara/single_post_stream_headerbar.py:31 ../giara/settings_window.py:284 #: ../giara/front_page_headerbar.py:38 ../giara/subreddit_view.py:214 msgid "Rising" msgstr "Aufsteigend" #: ../giara/single_post_stream_headerbar.py:35 ../giara/settings_window.py:285 #: ../giara/front_page_headerbar.py:42 ../giara/subreddit_view.py:218 msgid "Controversial" msgstr "Kontrovers" #: ../giara/inbox_view.py:52 #, python-brace-format msgid "Comment in \"{0}\"" msgstr "Kommentar in \"{0}\"" #: ../giara/user_heading.py:54 msgid "Unfollow" msgstr "Entfolgen" #: ../giara/user_heading.py:57 msgid "Follow" msgstr "Folgen" #: ../giara/search_view.py:65 msgid "Users" msgstr "Nutzer" #: ../giara/settings_window.py:107 msgid "Choose a folder" msgstr "Wähle einen Ordner" #: ../giara/settings_window.py:256 msgid "General" msgstr "Allgemeines" #: ../giara/settings_window.py:260 msgid "General Settings" msgstr "Allgemeine Einstellungen" #: ../giara/settings_window.py:281 msgid "Default view" msgstr "Standardansicht" #: ../giara/settings_window.py:284 ../giara/front_page_headerbar.py:22 msgid "Best" msgstr "Beste" #: ../giara/settings_window.py:294 msgid "Cache" msgstr "Zwischenspeicher" #: ../giara/settings_window.py:307 msgid "Clear cache" msgstr "Bereinige Cache" #: ../giara/settings_window.py:307 msgid "Clear" msgstr "Leeren" #: ../giara/settings_window.py:318 msgid "View" msgstr "Ansicht" #: ../giara/settings_window.py:322 msgid "View Settings" msgstr "Ansichtseinstellungen" #: ../giara/settings_window.py:325 msgid "Dark mode" msgstr "Dunkler Modus" #: ../giara/settings_window.py:330 msgid "Show thumbnails in post previews" msgstr "Zeige Miniatur in Beitragsvorschau" #: ../giara/settings_window.py:354 msgid "Max thumbnail width" msgstr "Maximale Miniaturbreite" #: ../giara/settings_window.py:358 msgid "Use 0 to remove the limit" msgstr "Wähle 0 zum Entfernen des Limits" #: ../giara/settings_window.py:368 msgid "Privacy" msgstr "Privatsphäre" #: ../giara/settings_window.py:372 msgid "Link replacement" msgstr "Link-Ersetzung" #: ../giara/settings_window.py:375 msgid "Replace Twitter links with Nitter" msgstr "Ersetze Twitter-Links mit Nitter" #: ../giara/settings_window.py:380 msgid "Replace YouTube links with Invidious" msgstr "Ersetze YouTube-Links mit Invidious" #: ../giara/settings_window.py:390 msgid "Invidious instance" msgstr "Invidious-Instanz" #: ../giara/settings_window.py:392 msgid "Naked domain name only, no https://" msgstr "Bloßer Domainname, ohne https://" #: ../giara/front_page_headerbar.py:88 #, python-brace-format msgid "{0} Karma" msgid_plural "{0} Karma" msgstr[0] "{0} Karma" msgstr[1] "{0} Karma" #: ../giara/front_page_headerbar.py:153 msgid "Do you want to log out? This will close the application." msgstr "Möchtest du dich abmelden? Dies wird die Anwendung schließen." #: ../giara/sort_menu_btn.py:73 #, python-brace-format msgid "Sort by: {0}" msgstr "Sortieren nach: {0}" #: ../giara/flair_label.py:11 msgid "Comment" msgstr "Kommentar" #: ../giara/flair_label.py:15 msgid "Post" msgstr "Beitrag" #: ../giara/flair_label.py:37 msgid "Image" msgstr "Bild" #: ../giara/flair_label.py:43 msgid "Video" msgstr "Video" #: ../giara/flair_label.py:49 msgid "Text" msgstr "Text" #: ../giara/flair_label.py:55 msgid "Link" msgstr "Link" #: ../giara/subreddit_view.py:40 msgid "This subreddit is already part of this multireddit" msgstr "Dieses Subreddit ist bereits Teil dieses Multireddits" #: ../giara/subreddit_view.py:235 msgid "More actions" msgstr "Weitere Aktionen" #: ../giara/subreddit_view.py:278 msgid "Leave" msgstr "Verlassen" #: ../giara/subreddit_view.py:281 msgid "Join" msgstr "Beitreten" #: ../giara/subreddit_view.py:317 msgid "Description" msgstr "Beschreibung" #: ../data/ui/subreddit_more_actions.ui:32 #: ../data/ui/subreddit_more_actions.ui:93 msgid "Add to multireddit" msgstr "Hinzufügen zu Multireddit" #: ../data/ui/subreddit_more_actions.ui:74 #: ../data/ui/subreddit_more_actions.ui:171 #: ../data/ui/headerbar_with_back_and_squeezer.ui:28 #: ../data/ui/post_details_headerbar.ui:14 #: ../data/ui/subreddit_view_headerbar.ui:28 msgid "Back" msgstr "Zurück" #: ../data/ui/subreddit_more_actions.ui:137 #: ../data/ui/subreddit_more_actions.ui:190 msgid "Create multireddit" msgstr "Erstelle Multireddit" #: ../data/ui/subreddit_more_actions.ui:222 msgid "Multireddit name…" msgstr "Name des Multireddit…" #: ../data/ui/subreddit_more_actions.ui:232 msgid "Create" msgstr "Erstellen" #: ../data/ui/post_body.ui:141 ../data/ui/comment_box.ui:157 #: ../data/ui/post_preview.ui:122 msgid "Upvote" msgstr "Positiv bewerten" #: ../data/ui/post_body.ui:163 ../data/ui/comment_box.ui:206 #: ../data/ui/post_preview.ui:144 msgid "Ups" msgstr "Bewertung" #: ../data/ui/post_body.ui:177 ../data/ui/comment_box.ui:177 #: ../data/ui/post_preview.ui:158 msgid "Downvote" msgstr "Negativ bewerten" #: ../data/ui/post_body.ui:218 ../data/ui/post_preview.ui:199 msgid "Pinned" msgstr "Angeheftet" #: ../data/ui/post_body.ui:341 ../data/ui/comment_box.ui:226 msgid "Reply" msgstr "Antworten" #: ../data/ui/post_body.ui:361 ../data/ui/post_preview.ui:344 msgid "Open media" msgstr "Öffne Medium" #: ../data/ui/post_body.ui:381 ../data/ui/post_preview.ui:364 msgid "Open link" msgstr "Öffne Link" #: ../data/ui/post_body.ui:401 ../data/ui/comment_box.ui:246 #: ../data/ui/post_preview.ui:384 msgid "Save" msgstr "Speichern" #: ../data/ui/post_body.ui:421 ../data/ui/comment_box.ui:266 #: ../data/ui/post_preview.ui:404 msgid "Share" msgstr "Teilen" #: ../data/ui/post_body.ui:461 ../data/ui/comment_box.ui:306 #: ../data/ui/post_preview.ui:424 msgid "Delete" msgstr "Löschen" #: ../data/ui/new_post_window.ui:17 ../data/ui/headerbar.ui:233 msgid "New post" msgstr "Neuer Beitrag" #: ../data/ui/new_post_window.ui:20 msgid "Cancel" msgstr "Abbrechen" #: ../data/ui/new_post_window.ui:28 msgid "Send" msgstr "Senden" #: ../data/ui/new_post_window.ui:72 msgid "Post title…" msgstr "Beitragstitel…" #: ../data/ui/new_post_window.ui:127 msgid "Link…" msgstr "Link…" #: ../data/ui/new_post_window.ui:145 msgid "Select media…" msgstr "Wähle Medium…" #: ../data/ui/redditor_heading.ui:83 msgid "Karma" msgstr "Karma" #: ../data/ui/redditor_heading.ui:113 msgid "Cake day" msgstr "Kuchentag" #: ../data/ui/shortcutsWindow.ui:18 msgid "Open Keyboard Shortcuts" msgstr "Öffne Tastenkombinationen" #: ../data/ui/shortcutsWindow.ui:25 msgid "Open Menu" msgstr "Öffne Menü" #: ../data/ui/shortcutsWindow.ui:32 msgid "Open Preferences" msgstr "Öffne Einstellungen" #: ../data/ui/shortcutsWindow.ui:39 msgid "Quit" msgstr "Beenden" #: ../data/ui/comment_box.ui:77 msgid "Collapse replies" msgstr "Antworten einklappen" #: ../data/ui/post_details_headerbar.ui:29 ../data/ui/headerbar.ui:298 #: ../data/ui/subreddit_view_headerbar.ui:43 msgid "Refresh" msgstr "Auffrischen" #: ../data/ui/new_post_menu.ui:14 msgid "Media" msgstr "Media" #: ../data/ui/post_preview.ui:301 msgid "Comments" msgstr "Kommentare" #: ../data/ui/menu.ui:6 msgid "Preferences" msgstr "Einstellungen" #: ../data/ui/menu.ui:10 msgid "Keyboard Shortcuts" msgstr "Tastenkombinationen" #: ../data/ui/menu.ui:14 msgid "About Giara" msgstr "Über Giara" #: ../data/ui/headerbar.ui:171 msgid "Saved" msgstr "Gespeichertes" #: ../data/ui/headerbar.ui:188 msgid "Log Out" msgstr "Abmelden" #: ../data/ui/headerbar.ui:272 msgid "Messages in your inbox" msgstr "Nachrichten in deinem Posteingang" #: ../data/ui/headerbar.ui:322 msgid "Menu" msgstr "Menü" #: ../data/ui/login_view.ui:83 msgid "Welcome to Giara" msgstr "Willkommen bei Giara" #: ../data/ui/login_view.ui:96 msgid "Login" msgstr "Anmelden" #: ../data/ui/login_view.ui:136 msgid "Waiting for authentication…" msgstr "Warte auf Authentifizierung…" #: ../data/ui/main_ui.ui:70 msgid "Close" msgstr "Schließen" #: ../data/ui/subreddit_heading.ui:101 msgid "Members" msgstr "Mitglieder" #: ../data/ui/subreddit_heading.ui:131 msgid "Since" msgstr "Seit" #: ../data/org.gabmus.giara.service.desktop.in:4 #: ../data/org.gabmus.giara.desktop.in:4 msgid "@prettyname@" msgstr "@prettyname@" #: ../data/org.gabmus.giara.desktop.in:5 msgid "An app for Reddit" msgstr "Eine Anwendung für Reddit" #: ../data/org.gabmus.giara.desktop.in:8 msgid "@appid@" msgstr "@appid@" #: ../data/org.gabmus.giara.desktop.in:14 msgid "reddit;" msgstr "reddit;" #: ../data/org.gabmus.giara.appdata.xml.in:4 msgid "Giara" msgstr "Giara" #: ../data/org.gabmus.giara.appdata.xml.in:5 msgid "Gabriele Musco" msgstr "Gabriele Musco" #: ../data/org.gabmus.giara.appdata.xml.in:15 msgid "An app for Reddit." msgstr "Eine Anwendung für Reddit." #: ../data/org.gabmus.giara.appdata.xml.in:16 msgid "Browse Reddit from your Linux desktop or smartphone." msgstr "Browse Reddit auf deinem Linux-Computer oder Smartphone." #: ../data/org.gabmus.giara.appdata.xml.in:38 msgid "Opening a post from a comment jumps to that comment" msgstr "Einen Beitrag von den Kommentaren öffnen, lässt dich zu diesem Kommentar springen" #: ../data/org.gabmus.giara.appdata.xml.in:39 msgid "Post creation and editing are now asynchronous" msgstr "Beitragserstellung und -bearbeitung sind jetzt asyn­chron" #: ../data/org.gabmus.giara.appdata.xml.in:40 msgid "Replaced quit button in welcome view with a more standard close button" msgstr "Beenden-Schaltfläche in Willkommensansicht durch standardgemäßere Schließen-Schaltfläche ersetzt" #: ../data/org.gabmus.giara.appdata.xml.in:41 msgid "Fixed icon rendering problems in KDE" msgstr "Probleme beim Rendern von Icons in KDE behoben" #: ../data/org.gabmus.giara.appdata.xml.in:42 msgid "Comment replies can now be collapsed" msgstr "Antworten auf Kommentare können jetzt eingeklappt werden" #: ../data/org.gabmus.giara.appdata.xml.in:43 msgid "" "Comments don't always load in full, hidden comments can be loaded if the " "user wants them" msgstr "" "Kommentare laden nicht immer vollständig, versteckte Kommentare können " "geladen werden, wenn der Nutzer es möchte" #: ../data/org.gabmus.giara.appdata.xml.in:44 msgid "You can now open Twitter links in Nitter and YouTube links in Invidious" msgstr "Du kannst jetzt Twitter-Links mit Nitter und YouTube-Links mit Invidious öffnen" #: ../data/org.gabmus.giara.appdata.xml.in:45 msgid "Limited picture preview size" msgstr "Größe der Bildvorschau eingeschränkt" #: ../data/org.gabmus.giara.appdata.xml.in:46 msgid "Added all available post sorting options" msgstr "Alle verfügbaren Sortierungsoptionen für Beiträge hinzugefügt" #: ../data/org.gabmus.giara.appdata.xml.in:47 msgid "New UI for choosing post sorting" msgstr "Neue Benutzeroberfläche für die Wahl der Beitragssortierung" #: ../data/org.gabmus.giara.appdata.xml.in:48 msgid "Implemented blockquote rendering" msgstr "Rendern von Blockquotes implementiert" #: ../data/org.gabmus.giara.appdata.xml.in:49 msgid "Implemented superscript rendering" msgstr "Rendern von Superscript implementiert" #: ../data/org.gabmus.giara.appdata.xml.in:50 msgid "Improved Markdown rendering" msgstr "Rendern von Markdown verbessert" #: ../data/org.gabmus.giara.appdata.xml.in:51 msgid "Improved Inbox view" msgstr "Ansicht des Postfachs verbessert" #: ../data/org.gabmus.giara.appdata.xml.in:52 msgid "Added Ukranian translation" msgstr "Ukrainische Übersetzung hinzugefügt" #: ../data/org.gabmus.giara.appdata.xml.in:53 msgid "Added Indonesian translation" msgstr "Indonesische Übersetzung hinzugefügt" #: ../data/org.gabmus.giara.appdata.xml.in:54 msgid "Added Spanish translation" msgstr "Spanische Übersetzung hinzugefügt" #: ../data/org.gabmus.giara.appdata.xml.in:55 msgid "Added Russian Translation" msgstr "Russische Übersetzung hinzugefügt" #: ../data/org.gabmus.giara.appdata.xml.in:56 msgid "Added Slovenian translation" msgstr "Slovenische Übersetzung hinzugefügt" #: ../data/org.gabmus.giara.appdata.xml.in:63 msgid "Improved markdown rendering" msgstr "Rendern von Markdown verbessert" #: ../data/org.gabmus.giara.appdata.xml.in:64 msgid "Dark mode implemented" msgstr "Dunkler Modus implementiert" #: ../data/org.gabmus.giara.appdata.xml.in:65 msgid "Added option to disable images in post previews" msgstr "Option hinzugefügt, Bilder in der Beitragsvorschau zu deaktivieren" #: ../data/org.gabmus.giara.appdata.xml.in:66 msgid "Added option to set a maximum size for images" msgstr "Option hinzugefügt, eine Maximalgröße von Bildern festlegen zu können" #: ../data/org.gabmus.giara.appdata.xml.in:67 msgid "Added option to clear cache" msgstr "Option hinzugefügt, den Cache zu bereinigen" #: ../data/org.gabmus.giara.appdata.xml.in:68 msgid "Added Brazilian Portuguese translation" msgstr "Übersetzung für brasilianisches Portugiesisch hinzugefügt" #: ../data/org.gabmus.giara.appdata.xml.in:69 msgid "New post or comment window now remembers its size" msgstr "Neues Beitrags- oder Kommentarfenster merkt sich jetzt seine Größe" #: ../data/org.gabmus.giara.appdata.xml.in:70 msgid "Subreddits can now be searched when creating new posts" msgstr "Subreddits können jetzt durchsucht werden, wenn ein neuer Beitrag erstellt wird" #: ../data/org.gabmus.giara.appdata.xml.in:71 msgid "Initial support for multireddits" msgstr "Grundlegende Unterstützung für Multireddits" #: ../data/org.gabmus.giara.appdata.xml.in:72 msgid "Added Croatian translation" msgstr "Kroatische Übersetzung hinzugefügt" #: ../data/org.gabmus.giara.appdata.xml.in:73 msgid "Added notifications for unread inbox items" msgstr "Benachrichtigungen für ungelesene Eingänge im Postfach hinzugefügt" #: ../data/org.gabmus.giara.appdata.xml.in:74 msgid "Made comment border lines colorful" msgstr "Linien am Kommentarrand eingefärbt" #: ../data/org.gabmus.giara.appdata.xml.in:75 msgid "Authentication is now handled by your default browser" msgstr "Authentifizierung wird jetzt von deinem Standardbrowser übernommen" #: ../data/org.gabmus.giara.appdata.xml.in:82 msgid "Improvements for flatpak packaging" msgstr "Verbesserung der Flatpak-Pa­ke­tie­rung" #: ../data/org.gabmus.giara.appdata.xml.in:89 msgid "First release" msgstr "Erste Veröffentlichung" giara-0.3/po/es.po000066400000000000000000000305321376474030500140400ustar00rootroot00000000000000# Spanish translation for giara. # Copyright (C) 2020 giara's COPYRIGHT HOLDER # This file is distributed under the same license as the giara package. # FIRST AUTHOR , YEAR. # Daniel Mustieles , 2020. # msgid "" msgstr "" "Project-Id-Version: giara master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/giara/issues\n" "POT-Creation-Date: 2020-11-05 07:51+0000\n" "PO-Revision-Date: 2020-11-05 10:10+0100\n" "Last-Translator: Daniel Mustieles \n" "Language-Team: Spanish - Spain \n" "Language: es_ES\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Gtranslator 3.38.0\n" #: data/org.gabmus.giara.appdata.xml.in:4 msgid "Giara" msgstr "Giara" #: data/org.gabmus.giara.appdata.xml.in:5 msgid "Gabriele Musco" msgstr "Gabriele Musco" #: data/org.gabmus.giara.appdata.xml.in:6 data/org.gabmus.giara.desktop.in:5 msgid "A GTK app for Reddit" msgstr "Una aplicación GTK para Reddit" #: data/org.gabmus.giara.appdata.xml.in:15 msgid "Giara is a GTK app for Reddit" msgstr "Giara es una aplicación GTK para Reddit" #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: data/org.gabmus.giara.desktop.in:14 msgid "reddit;" msgstr "reddit;" #: data/ui/comment_box.ui:77 msgid "Collapse replies" msgstr "Contraer respuestas" #: data/ui/comment_box.ui:157 data/ui/post_body.ui:141 #: data/ui/post_preview.ui:122 msgid "Upvote" msgstr "Voto positivo" #: data/ui/comment_box.ui:177 data/ui/post_body.ui:177 #: data/ui/post_preview.ui:158 msgid "Downvote" msgstr "Voto negativo" #: data/ui/comment_box.ui:206 data/ui/post_body.ui:163 #: data/ui/post_preview.ui:144 msgid "Ups" msgstr "Subidas" #: data/ui/comment_box.ui:226 data/ui/post_body.ui:341 msgid "Reply" msgstr "Responder" #: data/ui/comment_box.ui:246 data/ui/post_body.ui:401 #: data/ui/post_preview.ui:384 msgid "Save" msgstr "Guardar" #: data/ui/comment_box.ui:266 data/ui/post_body.ui:421 #: data/ui/post_preview.ui:404 msgid "Share" msgstr "Compartir" #: data/ui/comment_box.ui:286 data/ui/post_body.ui:441 #: giara/new_post_window.py:419 msgid "Edit" msgstr "Editar" #: data/ui/comment_box.ui:306 data/ui/post_body.ui:461 #: data/ui/post_preview.ui:424 msgid "Delete" msgstr "Eliminar" #: data/ui/headerbar.ui:103 giara/inbox_view.py:120 giara/left_stack.py:192 msgid "Inbox" msgstr "Bandeja de entrada" #: data/ui/headerbar.ui:120 giara/left_stack.py:152 giara/search_view.py:57 #: giara/subreddits_list_view.py:136 msgid "Subreddits" msgstr "Subreddits" #: data/ui/headerbar.ui:137 giara/left_stack.py:207 #: giara/multireddit_list_view.py:111 msgid "Multireddits" msgstr "Multireddits" #: data/ui/headerbar.ui:154 data/ui/headerbar.ui:257 giara/left_stack.py:123 #: giara/left_stack.py:130 msgid "Profile" msgstr "Perfil" #: data/ui/headerbar.ui:171 msgid "Saved" msgstr "Guardado" #: data/ui/headerbar.ui:188 msgid "Log Out" msgstr "Cerrar la sesión" #: data/ui/headerbar.ui:233 data/ui/new_post_window.ui:17 msgid "New post" msgstr "Nueva publicación" #: data/ui/headerbar.ui:272 msgid "Messages in your inbox" msgstr "Mensajes en su bandeja de entrada" #: data/ui/headerbar.ui:298 data/ui/post_details_headerbar.ui:29 #: data/ui/subreddit_view_headerbar.ui:43 msgid "Refresh" msgstr "Actualizar" #: data/ui/headerbar.ui:316 data/ui/subreddit_view_headerbar.ui:67 #: giara/left_stack.py:176 msgid "Search" msgstr "Buscar" #: data/ui/headerbar.ui:341 msgid "Menu" msgstr "Menú" #: data/ui/headerbar_with_back_and_squeezer.ui:28 #: data/ui/post_details_headerbar.ui:14 data/ui/subreddit_more_actions.ui:74 #: data/ui/subreddit_more_actions.ui:171 data/ui/subreddit_view_headerbar.ui:28 msgid "Back" msgstr "Atrás" #: data/ui/login_view.ui:83 msgid "Welcome to Giara" msgstr "Bienvenido a Giara" #: data/ui/login_view.ui:96 msgid "Login" msgstr "Iniciar sesión" #: data/ui/login_view.ui:136 msgid "Waiting for authentication…" msgstr "Esperando la autenticación…" #: data/ui/main_ui.ui:70 msgid "Close" msgstr "Cerrar" #: data/ui/menu.ui:6 msgid "Preferences" msgstr "Preferencias" #: data/ui/menu.ui:10 msgid "Keyboard Shortcuts" msgstr "Atajo del teclado" #: data/ui/menu.ui:14 msgid "About Giara" msgstr "Acerca de Giara" #: data/ui/new_post_menu.ui:6 giara/flair_label.py:49 msgid "Text" msgstr "Texto" #: data/ui/new_post_menu.ui:10 giara/flair_label.py:55 msgid "Link" msgstr "Enlace" #: data/ui/new_post_menu.ui:14 msgid "Media" msgstr "Multimedia" #: data/ui/new_post_window.ui:20 msgid "Cancel" msgstr "Cancelar" #: data/ui/new_post_window.ui:28 msgid "Send" msgstr "Enviar" #: data/ui/new_post_window.ui:72 msgid "Post title…" msgstr "Título de la publicación…" #: data/ui/new_post_window.ui:127 msgid "Link…" msgstr "Enlace…" #: data/ui/new_post_window.ui:145 msgid "Select media…" msgstr "Seleccionar medio…" #: data/ui/post_body.ui:218 data/ui/post_preview.ui:199 msgid "Pinned" msgstr "Fijado" #: data/ui/post_body.ui:361 data/ui/post_preview.ui:344 msgid "Open media" msgstr "Abrir medio" #: data/ui/post_body.ui:381 data/ui/post_preview.ui:364 msgid "Open link" msgstr "Abrir enlace" #: data/ui/post_preview.ui:301 msgid "Comments" msgstr "Comentarios" #: data/ui/redditor_heading.ui:59 giara/user_heading.py:57 msgid "Follow" msgstr "Seguir" #: data/ui/redditor_heading.ui:83 msgid "Karma" msgstr "Karma" #: data/ui/redditor_heading.ui:113 msgid "Cake day" msgstr "Aniversario (cake day)" #: data/ui/shortcutsWindow.ui:13 giara/settings_window.py:256 msgid "General" msgstr "General" #: data/ui/shortcutsWindow.ui:18 msgid "Open Keyboard Shortcuts" msgstr "Abrir atajos del teclado" #: data/ui/shortcutsWindow.ui:25 msgid "Open Menu" msgstr "Abrir menú" #: data/ui/shortcutsWindow.ui:32 msgid "Open Preferences" msgstr "Abrir preferencias" #: data/ui/shortcutsWindow.ui:39 msgid "Quit" msgstr "Salir" #: data/ui/subreddit_heading.ui:77 giara/subreddit_view.py:257 msgid "Join" msgstr "Unirse" #: data/ui/subreddit_heading.ui:101 msgid "Members" msgstr "Miembros" #: data/ui/subreddit_heading.ui:131 msgid "Since" msgstr "Desde" #: data/ui/subreddit_more_actions.ui:32 data/ui/subreddit_more_actions.ui:93 msgid "Add to multireddit" msgstr "Añadir a multireddit" #: data/ui/subreddit_more_actions.ui:137 data/ui/subreddit_more_actions.ui:190 msgid "Create multireddit" msgstr "Crear multireddit" #: data/ui/subreddit_more_actions.ui:222 msgid "Multireddit name…" msgstr "Nombre del multireddit…" #: data/ui/subreddit_more_actions.ui:232 msgid "Create" msgstr "Crear" #: giara/auth.py:20 msgid "Error getting client with refresh token, retrying…" msgstr "Error al obtener el cliente con el testigo actualizado, reintentando…" #: giara/auth.py:30 msgid "Error authorizing Reddit client, retrying…" msgstr "Error al autorizar el cliente de Reddit, reintentando…" #: giara/auth.py:35 msgid "Error authorizing Reddit client after retry, quitting…" msgstr "" "Error al autorizar el cliente de Reddit después de reintentar, saliendo…" #: giara/choice_picker.py:51 giara/choice_picker.py:63 #: giara/new_post_window.py:212 giara/new_post_window.py:213 #: giara/new_post_window.py:219 msgid "None" msgstr "Ninguno" #: giara/common_post_box.py:108 msgid "Are you sure you want to delete this item?" msgstr "¿Está seguro de querer eliminar este elemento?" #: giara/common_post_box.py:141 msgid "Link copied to clipboard" msgstr "Enlace copiado al portapapeles" #: giara/common_post_box.py:244 msgid "Comment: " msgstr "Comentario: " #: giara/common_post_box.py:253 giara/flair_label.py:19 msgid "Message" msgstr "Mensaje" #: giara/common_post_box.py:259 giara/inbox_view.py:41 #: giara/post_details_view.py:97 msgid "Author unknown" msgstr "Autor desconocido" #: giara/flair_label.py:11 msgid "Comment" msgstr "Comentar" #: giara/flair_label.py:15 msgid "Post" msgstr "Publicar" #: giara/flair_label.py:37 msgid "Image" msgstr "Imagen" #: giara/flair_label.py:43 msgid "Video" msgstr "Vídeo" #: giara/front_page_headerbar.py:60 #, python-brace-format msgid "{0} Karma" msgstr "Karma {0}" #: giara/front_page_headerbar.py:124 msgid "Do you want to log out? This will close the application." msgstr "¿Quiere cerrar la sesión? Esto cerrará la aplicación." #: giara/inbox_view.py:54 giara/left_stack.py:44 giara/settings_window.py:284 msgid "New" msgstr "Nuevo" #: giara/left_stack.py:32 giara/settings_window.py:284 msgid "Best" msgstr "Mejor" #: giara/left_stack.py:38 giara/settings_window.py:284 msgid "Hot" msgstr "Caliente" #: giara/left_stack.py:50 giara/settings_window.py:284 msgid "Top" msgstr "Superior" #: giara/left_stack.py:56 giara/settings_window.py:284 msgid "Rising" msgstr "Creciente" #: giara/left_stack.py:62 giara/settings_window.py:285 msgid "Controversial" msgstr "Polémico" #: giara/left_stack.py:98 msgid "Front page" msgstr "Página principal" #: giara/left_stack.py:105 giara/left_stack.py:111 msgid "Saved posts" msgstr "Publicaciones guardadas" #: giara/left_stack.py:329 giara/subreddit_search_view.py:26 #, python-brace-format msgid "Searching in {0}" msgstr "Buscando en {0}" #: giara/markdown_view.py:31 msgid "Image: " msgstr "Imagen:" #: giara/markdown_view.py:31 msgid "[Image]" msgstr "[Imagen]" #: giara/multireddit_view.py:59 msgid "Remove from multireddit" msgstr "Quitar de multireddit" #: giara/multireddit_view.py:84 msgid "Edit multireddit" msgstr "Editar multireddit" #: giara/new_post_window.py:100 msgid "Choose an image or video to upload" msgstr "Elegir una imagen o vídeo para subir" #: giara/new_post_window.py:262 msgid "Select a subreddit…" msgstr "Seleccionar un subreddit…" #: giara/new_post_window.py:324 msgid "Select a flair…" msgstr "Seleccionar «flair»…" #: giara/new_post_window.py:389 msgid "New comment" msgstr "Nuevo comentario" #: giara/new_post_window.py:418 msgid "Editing" msgstr "Editando" #: giara/notification_manager.py:7 msgid "1 new message" msgstr "1 mensaje nuevo" #: giara/notification_manager.py:8 #, python-brace-format msgid "{0} new messages" msgstr "{0} mensajes nuevos" #: giara/notification_manager.py:40 #, python-brace-format msgid "{0} more" msgstr "{0} más" #: giara/post_details_view.py:32 msgid "Load more comments" msgstr "Cargar más comentarios" #: giara/search_view.py:65 msgid "Users" msgstr "Usuarios" #: giara/settings_window.py:107 msgid "Choose a folder" msgstr "Elegir una carpeta" #: giara/settings_window.py:260 msgid "General Settings" msgstr "Configuración general" #: giara/settings_window.py:281 msgid "Default view" msgstr "Vista predeterminada" #: giara/settings_window.py:294 msgid "Cache" msgstr "Caché" #: giara/settings_window.py:307 msgid "Clear cache" msgstr "Limpiar la caché" #: giara/settings_window.py:307 msgid "Clear" msgstr "Limpiar" #: giara/settings_window.py:318 msgid "View" msgstr "Ver" #: giara/settings_window.py:322 msgid "View Settings" msgstr "Configuración de la vista" #: giara/settings_window.py:325 msgid "Dark mode" msgstr "Modo oscuro" #: giara/settings_window.py:330 msgid "Show thumbnails in post previews" msgstr "Mostrar miniaturas en la vista previa de las publicaciones" #: giara/settings_window.py:354 msgid "Max thumbnail width" msgstr "Anchura máxima de las publicaciones" #: giara/settings_window.py:358 msgid "Use 0 to remove the limit" msgstr "Use 0 para quitar el límite" #: giara/settings_window.py:368 msgid "Privacy" msgstr "Privacidad" #: giara/settings_window.py:372 msgid "Link replacement" msgstr "Reemplazo de enlaes" #: giara/settings_window.py:375 msgid "Replace Twitter links with Nitter" msgstr "Reemplazar enlaces de Twitter con Nitter" #: giara/settings_window.py:380 msgid "Replace YouTube links with Invidious" msgstr "Reemplazar enlaces de Youtube con Invidious" #: giara/settings_window.py:390 msgid "Invidious instance" msgstr "Instancia de Invidious" #: giara/settings_window.py:392 msgid "Naked domain name only, no https://" msgstr "Nombre del dominio a secas, sin https://" #: giara/subreddit_view.py:40 msgid "This subreddit is already part of this multireddit" msgstr "Este subreddit ya es parte de este multireddit" #: giara/subreddit_view.py:211 msgid "More actions" msgstr "Más acciones" #: giara/subreddit_view.py:254 msgid "Leave" msgstr "Salir" #: giara/subreddit_view.py:293 msgid "Description" msgstr "Descripción" #: giara/user_heading.py:54 msgid "Unfollow" msgstr "Dejar de seguir" giara-0.3/po/fr.po000066400000000000000000000333051376474030500140410ustar00rootroot00000000000000# French translation for giara. # Copyright (C) 2020 giara's COPYRIGHT HOLDER # This file is distributed under the same license as the giara package. # Note : upvote et downvote ont été traduit selon l’usage du subreddit /r/france, Reddit ne disposant pas de traduction officielle pour ces termes # Thibault Martin , 2020. # msgid "" msgstr "" "Project-Id-Version: giara master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/giara/issues\n" "POT-Creation-Date: 2020-11-16 08:27+0000\n" "PO-Revision-Date: 2020-11-16 19:25+0100\n" "Last-Translator: Thibault Martin \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" "Plural-Forms: nplurals=2; plural=(n > 1);\n" "X-Generator: Gtranslator 3.38.0\n" #: data/org.gabmus.giara.appdata.xml.in:4 msgid "Giara" msgstr "Giara" #: data/org.gabmus.giara.appdata.xml.in:5 msgid "Gabriele Musco" msgstr "Gabriele Musco" #: data/org.gabmus.giara.appdata.xml.in:6 data/org.gabmus.giara.desktop.in:5 msgid "An app for Reddit" msgstr "Une application pour Reddit" #: data/org.gabmus.giara.appdata.xml.in:15 msgid "An app for Reddit." msgstr "Une application pour Reddit." #: data/org.gabmus.giara.appdata.xml.in:16 msgid "Browse Reddit from your Linux desktop or smartphone." msgstr "Naviguez sur Reddit depuis votre ordinateur ou téléphone Linux." #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: data/org.gabmus.giara.desktop.in:14 msgid "reddit;" msgstr "reddit;" #: data/ui/comment_box.ui:77 msgid "Collapse replies" msgstr "Replier les réponses" # Hautvote est une traduction habituelle dans /r/france. Reddit ne dispose pas de traduction usuelle #: data/ui/comment_box.ui:157 data/ui/post_body.ui:141 #: data/ui/post_preview.ui:122 msgid "Upvote" msgstr "Hautvoter" # Basvote est une traduction habituelle dans /r/france. Reddit ne dispose pas de traduction usuelle #: data/ui/comment_box.ui:177 data/ui/post_body.ui:177 #: data/ui/post_preview.ui:158 msgid "Downvote" msgstr "Basvoter" #: data/ui/comment_box.ui:206 data/ui/post_body.ui:163 #: data/ui/post_preview.ui:144 msgid "Ups" msgstr "Hautvotes" #: data/ui/comment_box.ui:226 data/ui/post_body.ui:341 msgid "Reply" msgstr "Répondre" #: data/ui/comment_box.ui:246 data/ui/post_body.ui:401 #: data/ui/post_preview.ui:384 msgid "Save" msgstr "Enregistrer" #: data/ui/comment_box.ui:266 data/ui/post_body.ui:421 #: data/ui/post_preview.ui:404 msgid "Share" msgstr "Partager" #: data/ui/comment_box.ui:286 data/ui/post_body.ui:441 #: giara/new_post_window.py:419 msgid "Edit" msgstr "Modifier" #: data/ui/comment_box.ui:306 data/ui/post_body.ui:461 #: data/ui/post_preview.ui:424 msgid "Delete" msgstr "Supprimer" #: data/ui/headerbar.ui:103 giara/inbox_view.py:134 giara/left_stack.py:159 msgid "Inbox" msgstr "Boîte de réception" #: data/ui/headerbar.ui:120 giara/left_stack.py:119 giara/search_view.py:57 #: giara/subreddits_list_view.py:136 msgid "Subreddits" msgstr "Subreddits" #: data/ui/headerbar.ui:137 giara/left_stack.py:174 #: giara/multireddit_list_view.py:111 msgid "Multireddits" msgstr "Multireddits" #: data/ui/headerbar.ui:154 data/ui/headerbar.ui:254 giara/left_stack.py:89 #: giara/left_stack.py:97 msgid "Profile" msgstr "Profil" #: data/ui/headerbar.ui:171 msgid "Saved" msgstr "Enregistré" #: data/ui/headerbar.ui:188 msgid "Log Out" msgstr "Se déconnecter" #: data/ui/headerbar.ui:233 data/ui/new_post_window.ui:17 msgid "New post" msgstr "Nouvelle publication" #: data/ui/headerbar.ui:272 msgid "Messages in your inbox" msgstr "Messages dans votre boîte de réception" #: data/ui/headerbar.ui:298 data/ui/post_details_headerbar.ui:29 #: data/ui/subreddit_view_headerbar.ui:43 msgid "Refresh" msgstr "Rafraîchir" #: data/ui/headerbar.ui:322 msgid "Menu" msgstr "Menu" #: data/ui/headerbar.ui:349 data/ui/subreddit_view_headerbar.ui:67 #: giara/left_stack.py:143 msgid "Search" msgstr "Rechercher" #: data/ui/new_post_window.ui:20 msgid "Cancel" msgstr "Annuler" #: data/ui/new_post_window.ui:28 msgid "Send" msgstr "Envoyer" #: data/ui/new_post_window.ui:72 msgid "Post title…" msgstr "Titre de la publication…" #: data/ui/new_post_window.ui:127 msgid "Link…" msgstr "Lien…" #: data/ui/new_post_window.ui:145 msgid "Select media…" msgstr "Sélectionner un média…" #: data/ui/post_details_headerbar.ui:14 #: data/ui/headerbar_with_back_and_squeezer.ui:28 #: data/ui/subreddit_more_actions.ui:74 data/ui/subreddit_more_actions.ui:171 #: data/ui/subreddit_view_headerbar.ui:28 msgid "Back" msgstr "Retour" #: data/ui/login_view.ui:83 msgid "Welcome to Giara" msgstr "Bienvenue dans Giara" #: data/ui/login_view.ui:96 msgid "Login" msgstr "Authentification" #: data/ui/login_view.ui:136 msgid "Waiting for authentication…" msgstr "En attente de l’authentification…" #: data/ui/main_ui.ui:70 msgid "Close" msgstr "Fermer" #: data/ui/menu.ui:6 msgid "Preferences" msgstr "Préférences" #: data/ui/menu.ui:10 msgid "Keyboard Shortcuts" msgstr "Raccourcis clavier" #: data/ui/menu.ui:14 msgid "About Giara" msgstr "À propos de Giara" #: data/ui/new_post_menu.ui:6 giara/flair_label.py:49 msgid "Text" msgstr "Texte" #: data/ui/new_post_menu.ui:10 giara/flair_label.py:55 msgid "Link" msgstr "Lien" #: data/ui/new_post_menu.ui:14 msgid "Media" msgstr "Média" #: data/ui/post_body.ui:218 data/ui/post_preview.ui:199 msgid "Pinned" msgstr "Épinglé" #: data/ui/post_body.ui:361 data/ui/post_preview.ui:344 msgid "Open media" msgstr "Ouvrir le média" #: data/ui/post_body.ui:381 data/ui/post_preview.ui:364 msgid "Open link" msgstr "Ouvrir le lien" #: data/ui/post_preview.ui:301 msgid "Comments" msgstr "Commentaires" #: data/ui/redditor_heading.ui:59 giara/user_heading.py:57 msgid "Follow" msgstr "Suivre" #: data/ui/redditor_heading.ui:83 msgid "Karma" msgstr "Karma" #: data/ui/redditor_heading.ui:113 msgid "Cake day" msgstr "Jour du gâteau" #: data/ui/shortcutsWindow.ui:13 giara/settings_window.py:256 msgid "General" msgstr "Général" #: data/ui/shortcutsWindow.ui:18 msgid "Open Keyboard Shortcuts" msgstr "Ouvrir les raccourcis clavier" #: data/ui/shortcutsWindow.ui:25 msgid "Open Menu" msgstr "Ouvrir le menu" #: data/ui/shortcutsWindow.ui:32 msgid "Open Preferences" msgstr "Ouvrir les préférences" #: data/ui/shortcutsWindow.ui:39 msgid "Quit" msgstr "Quitter" #: data/ui/subreddit_heading.ui:77 giara/subreddit_view.py:281 msgid "Join" msgstr "Joindre" #: data/ui/subreddit_heading.ui:101 msgid "Members" msgstr "Membres" #: data/ui/subreddit_heading.ui:131 msgid "Since" msgstr "Depuis" #: data/ui/subreddit_more_actions.ui:32 data/ui/subreddit_more_actions.ui:93 msgid "Add to multireddit" msgstr "Ajouter au multireddit" #: data/ui/subreddit_more_actions.ui:137 data/ui/subreddit_more_actions.ui:190 msgid "Create multireddit" msgstr "Créer un multireddit" #: data/ui/subreddit_more_actions.ui:222 msgid "Multireddit name…" msgstr "Nom du multireddit…" #: data/ui/subreddit_more_actions.ui:232 msgid "Create" msgstr "Créer" #: giara/auth.py:20 msgid "Error getting client with refresh token, retrying…" msgstr "" "Erreur lors de l’obtention du client à partir du jeton de renouvellement " "(refresh token), nouvel essai…" #: giara/auth.py:30 msgid "Error authorizing Reddit client, retrying…" msgstr "Erreur lors de l’habilitation du client Reddit, nouvel essai…" #: giara/auth.py:35 msgid "Error authorizing Reddit client after retry, quitting…" msgstr "" "Erreur lors de l’habilitation du client après un nouvel essai, fermeture…" #: giara/choice_picker.py:51 giara/choice_picker.py:63 #: giara/new_post_window.py:212 giara/new_post_window.py:213 #: giara/new_post_window.py:219 msgid "None" msgstr "Aucun" #: giara/common_post_box.py:108 msgid "Are you sure you want to delete this item?" msgstr "Êtes vous sûr de vouloir supprimer cet élément ?" #: giara/common_post_box.py:141 msgid "Link copied to clipboard" msgstr "Lien copié dans le presse-papier" #: giara/common_post_box.py:244 msgid "Comment: " msgstr "Commentaire :" #: giara/common_post_box.py:253 giara/flair_label.py:19 msgid "Message" msgstr "Message" #: giara/common_post_box.py:259 giara/inbox_view.py:41 #: giara/post_details_view.py:97 msgid "Author unknown" msgstr "Auteur inconnu" #: giara/flair_label.py:11 msgid "Comment" msgstr "Commentaire" #: giara/flair_label.py:15 msgid "Post" msgstr "Publication" #: giara/flair_label.py:37 msgid "Image" msgstr "Image" #: giara/flair_label.py:43 msgid "Video" msgstr "Vidéo" #: giara/front_page_headerbar.py:22 giara/settings_window.py:284 msgid "Best" msgstr "Meilleur" #: giara/front_page_headerbar.py:26 giara/settings_window.py:284 #: giara/single_post_stream_headerbar.py:19 giara/subreddit_view.py:202 msgid "Hot" msgstr "Ardent" #: giara/front_page_headerbar.py:30 giara/settings_window.py:284 #: giara/inbox_view.py:68 giara/single_post_stream_headerbar.py:23 #: giara/subreddit_view.py:206 msgid "New" msgstr "Nouveau" #: giara/front_page_headerbar.py:34 giara/settings_window.py:284 #: giara/single_post_stream_headerbar.py:27 giara/subreddit_view.py:210 msgid "Top" msgstr "Top" #: giara/front_page_headerbar.py:38 giara/settings_window.py:284 #: giara/single_post_stream_headerbar.py:31 giara/subreddit_view.py:214 msgid "Rising" msgstr "Tendance" #: giara/front_page_headerbar.py:42 giara/settings_window.py:285 #: giara/single_post_stream_headerbar.py:35 giara/subreddit_view.py:218 msgid "Controversial" msgstr "Controversé" #: giara/front_page_headerbar.py:88 #, python-brace-format msgid "{0} Karma" msgid_plural "{0} Karma" msgstr[0] "{0} karma" msgstr[1] "{0} karma" #: giara/front_page_headerbar.py:153 msgid "Do you want to log out? This will close the application." msgstr "Voulez-vous vous déconnecter ? Ceci fermera l’application." #: giara/settings_window.py:107 msgid "Choose a folder" msgstr "Choisir un répertoire" #: giara/settings_window.py:260 msgid "General Settings" msgstr "Paramètres généraux" #: giara/settings_window.py:281 msgid "Default view" msgstr "Vue par défaut" #: giara/settings_window.py:294 msgid "Cache" msgstr "Cache" #: giara/settings_window.py:307 msgid "Clear cache" msgstr "Effacer le cache" #: giara/settings_window.py:307 msgid "Clear" msgstr "Effacer" #: giara/settings_window.py:318 msgid "View" msgstr "Vue" #: giara/settings_window.py:322 msgid "View Settings" msgstr "Vue des paramètres" #: giara/settings_window.py:325 msgid "Dark mode" msgstr "Mode sombre" #: giara/settings_window.py:330 msgid "Show thumbnails in post previews" msgstr "Afficher les miniatures dans les aperçus des publications" #: giara/settings_window.py:354 msgid "Max thumbnail width" msgstr "Largeur maximum de la miniature" #: giara/settings_window.py:358 msgid "Use 0 to remove the limit" msgstr "Indiquez 0 pour supprimer la limite" #: giara/settings_window.py:368 msgid "Privacy" msgstr "Vie privée" #: giara/settings_window.py:372 msgid "Link replacement" msgstr "Remplacement de lien" #: giara/settings_window.py:375 msgid "Replace Twitter links with Nitter" msgstr "Remplacer les liens Twitter avec des liens Nitter" #: giara/settings_window.py:380 msgid "Replace YouTube links with Invidious" msgstr "Remplacer les liens YouTube avec des liens Invidious" #: giara/settings_window.py:390 msgid "Invidious instance" msgstr "Instance Invidious" #: giara/settings_window.py:392 msgid "Naked domain name only, no https://" msgstr "Nom de domaine seulement, sans https://" #: giara/inbox_view.py:52 #, python-brace-format msgid "Comment in \"{0}\"" msgstr "Commentaires pour « {0} »" #: giara/left_stack.py:33 msgid "Posts" msgstr "Publications" #: giara/left_stack.py:63 msgid "Front page" msgstr "Page d’accueil" #: giara/left_stack.py:70 giara/left_stack.py:77 msgid "Saved posts" msgstr "Publications sauvegardées" #: giara/left_stack.py:299 giara/subreddit_search_view.py:26 #, python-brace-format msgid "Searching in {0}" msgstr "Recherche dans {0}" #: giara/post_details_view.py:32 msgid "Load more comments" msgstr "Charger plus de commentaires" #: giara/markdown_view.py:42 msgid "Image: " msgstr "Image :" #: giara/markdown_view.py:42 msgid "[Image]" msgstr "[Image]" #: giara/multireddit_view.py:59 msgid "Remove from multireddit" msgstr "Supprimer du multireddit" #: giara/multireddit_view.py:85 msgid "Edit multireddit" msgstr "Modifier le multireddit" #: giara/new_post_window.py:100 msgid "Choose an image or video to upload" msgstr "Choisir une image ou une vidéo à envoyer" #: giara/new_post_window.py:262 msgid "Select a subreddit…" msgstr "Choisir un subreddit…" #: giara/new_post_window.py:324 msgid "Select a flair…" msgstr "Choisir un badge…" #: giara/new_post_window.py:389 msgid "New comment" msgstr "Nouveau commentaire" #: giara/new_post_window.py:418 msgid "Editing" msgstr "Modification" #: giara/notification_manager.py:31 #, python-brace-format msgid "{0} new message" msgid_plural "{0} new messages" msgstr[0] "{0} nouveau message" msgstr[1] "{0} nouveaux messages" #: giara/notification_manager.py:36 #, python-brace-format msgid "{0} more" msgid_plural "{0} more" msgstr[0] "{0} de plus" msgstr[1] "{0} de plus" #: giara/search_view.py:65 msgid "Users" msgstr "Utilisateurs" #: giara/subreddit_view.py:40 msgid "This subreddit is already part of this multireddit" msgstr "Ce subreddit fait déjà partie de ce multireddit" #: giara/subreddit_view.py:235 msgid "More actions" msgstr "Plus d’actions" #: giara/subreddit_view.py:278 msgid "Leave" msgstr "Partir" #: giara/subreddit_view.py:317 msgid "Description" msgstr "Description" #: giara/sort_menu_btn.py:73 #, python-brace-format msgid "Sort by: {0}" msgstr "Trier par : {0}" #: giara/user_heading.py:54 msgid "Unfollow" msgstr "Ne plus suivre" giara-0.3/po/hr.po000066400000000000000000000315541376474030500140470ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: Giara\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-10-31 10:07+0100\n" "PO-Revision-Date: \n" "Last-Translator: Milo Ivir \n" "Language-Team: \n" "Language: hr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 2.4.1\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n" #: ../giara/choice_picker.py:51 ../giara/choice_picker.py:63 #: ../giara/new_post_window.py:212 ../giara/new_post_window.py:213 #: ../giara/new_post_window.py:219 msgid "None" msgstr "Bez" #: ../giara/user_heading.py:54 msgid "Unfollow" msgstr "Prestani pratiti" #: ../giara/user_heading.py:57 msgid "Follow" msgstr "Prati" #: ../giara/multireddit_view.py:59 msgid "Remove from multireddit" msgstr "" #: ../giara/multireddit_view.py:84 msgid "Edit multireddit" msgstr "" #: ../giara/subreddits_list_view.py:133 ../giara/left_stack.py:134 #: ../giara/search_view.py:57 msgid "Subreddits" msgstr "Podforumi" #: ../giara/inbox_view.py:41 ../giara/post_details_view.py:97 #: ../giara/common_post_box.py:259 msgid "Author unknown" msgstr "Autor nepoznat" #: ../giara/inbox_view.py:54 ../giara/left_stack.py:44 #: ../giara/settings_window.py:283 msgid "New" msgstr "Novi" #: ../giara/inbox_view.py:120 ../giara/left_stack.py:174 msgid "Inbox" msgstr "Ulazni sandučić" #: ../giara/post_details_view.py:32 msgid "Load more comments" msgstr "" #: ../giara/notification_manager.py:7 msgid "1 new message" msgstr "" #: ../giara/notification_manager.py:8 #, python-brace-format msgid "{0} new messages" msgstr "" #: ../giara/notification_manager.py:40 #, python-brace-format msgid "{0} more" msgstr "" #: ../giara/common_post_box.py:108 msgid "Are you sure you want to delete this item?" msgstr "Stvarno želiš izbrisati ovu stavku?" #: ../giara/common_post_box.py:141 msgid "Link copied to clipboard" msgstr "Poveznica kopirana u međuspremnik" #: ../giara/common_post_box.py:244 msgid "Comment: " msgstr "Komentar: " #: ../giara/common_post_box.py:253 ../giara/flair_label.py:19 msgid "Message" msgstr "Poruka" #: ../giara/new_post_window.py:100 msgid "Choose an image or video to upload" msgstr "Odaberi datoteku ili video za prijenos" #: ../giara/new_post_window.py:262 msgid "Select a subreddit..." msgstr "Odaberi podforum …" #: ../giara/new_post_window.py:324 msgid "Select a flair..." msgstr "Odaberi oznaku …" #: ../giara/new_post_window.py:389 msgid "New comment" msgstr "Novi komentar" #: ../giara/new_post_window.py:418 msgid "Editing" msgstr "Uređivanje" #: ../giara/new_post_window.py:419 msgid "Edit" msgstr "Uredi" #: ../giara/markdown_view.py:26 msgid "Image: " msgstr "Slika: " #: ../giara/markdown_view.py:26 msgid "[Image]" msgstr "[Slika]" #: ../giara/subreddit_search_view.py:26 ../giara/left_stack.py:311 #, python-brace-format msgid "Searching in {0}" msgstr "Traži se u {0}" #: ../giara/front_page_headerbar.py:60 #, python-brace-format msgid "{0} Karma" msgstr "{0} karma" #: ../giara/front_page_headerbar.py:124 msgid "Do you want to log out? This will close the application." msgstr "Želiš se odjaviti? To će zatvoriti program." #: ../giara/left_stack.py:32 ../giara/settings_window.py:283 msgid "Best" msgstr "Najbolji" #: ../giara/left_stack.py:38 ../giara/settings_window.py:283 msgid "Hot" msgstr "Vrući" #: ../giara/left_stack.py:80 msgid "Front page" msgstr "Naslovna stranica" #: ../giara/left_stack.py:87 ../giara/left_stack.py:93 msgid "Saved posts" msgstr "Spremljeni članci" #: ../giara/left_stack.py:105 ../giara/left_stack.py:112 msgid "Profile" msgstr "Profil" #: ../giara/left_stack.py:158 msgid "Search" msgstr "Traži" #: ../giara/left_stack.py:189 ../giara/multireddit_list_view.py:111 msgid "Multireddits" msgstr "Grupirani podforumi" #: ../giara/settings_window.py:107 msgid "Choose a folder" msgstr "Odaberi mapu" #: ../giara/settings_window.py:256 msgid "General" msgstr "Opće" #: ../giara/settings_window.py:260 msgid "General Settings" msgstr "Opće postavke" #: ../giara/settings_window.py:281 msgid "Default view" msgstr "Standardni prikaz" #: ../giara/settings_window.py:291 msgid "Cache" msgstr "Predmemorija" #: ../giara/settings_window.py:304 msgid "Clear cache" msgstr "Izbriši predmemoriju" #: ../giara/settings_window.py:304 msgid "Clear" msgstr "Izbriši" #: ../giara/settings_window.py:315 msgid "View" msgstr "Prikaz" #: ../giara/settings_window.py:319 msgid "View Settings" msgstr "Postavke prikaza" #: ../giara/settings_window.py:322 msgid "Dark mode" msgstr "Tamni modus" #: ../giara/settings_window.py:327 msgid "Show thumnails in post previews" msgstr "Prikaži minijature u pretprikazima članaka" #: ../giara/settings_window.py:351 msgid "Max thumbnail width" msgstr "Maks. širina minijature" #: ../giara/settings_window.py:355 msgid "Use 0 to remove the limit" msgstr "Postavi na 0 za uklanjanje ograničenja" #: ../giara/settings_window.py:365 msgid "Privacy" msgstr "" #: ../giara/settings_window.py:369 msgid "Link replacement" msgstr "" #: ../giara/settings_window.py:372 msgid "Replace Twitter links with Nitter" msgstr "" #: ../giara/settings_window.py:377 msgid "Replace YouTube links with Invidious" msgstr "" #: ../giara/settings_window.py:387 msgid "Invidious instance" msgstr "" #: ../giara/settings_window.py:389 msgid "Naked domain name only, no https://" msgstr "" #: ../giara/search_view.py:65 msgid "Users" msgstr "Korisnici" #: ../giara/auth.py:20 msgid "Error getting client with refresh token, retrying..." msgstr "" "Greška pri dohvaćanju klijenta s tokenom aktualiziranja. Pokušaj se ponavlja " "…" #: ../giara/auth.py:30 msgid "Error authorizing reddit client, retrying..." msgstr "Greška pri autorizaciji reddit klijenta. Pokušaj se ponavlja …" #: ../giara/auth.py:35 msgid "Error authorizing reddit client after retry, quitting..." msgstr "" "Greška pri autorizaciji reddit klijenta nakon ponovnog pokušaja. Prekida se …" #: ../giara/flair_label.py:11 msgid "Comment" msgstr "" #: ../giara/flair_label.py:15 msgid "Post" msgstr "" #: ../giara/flair_label.py:37 msgid "Image" msgstr "Slika" #: ../giara/flair_label.py:43 msgid "Video" msgstr "Video" #: ../giara/flair_label.py:49 msgid "Text" msgstr "Tekst" #: ../giara/flair_label.py:55 msgid "Link" msgstr "Poveznica" #: ../giara/subreddit_view.py:40 msgid "This subreddit is already part of this multireddit" msgstr "" #: ../giara/subreddit_view.py:211 msgid "More actions" msgstr "" #: ../giara/subreddit_view.py:254 msgid "Leave" msgstr "Napusti" #: ../giara/subreddit_view.py:257 msgid "Join" msgstr "Pridruži se" #: ../giara/subreddit_view.py:293 msgid "Description" msgstr "Opis" #: ../data/ui/main_ui.ui:70 msgid "Close" msgstr "Zatvori" #: ../data/ui/subreddit_heading.ui:101 msgid "Members" msgstr "Članovi" #: ../data/ui/subreddit_heading.ui:131 msgid "Since" msgstr "Od" #: ../data/ui/menu.ui:6 msgid "Preferences" msgstr "Postavke" #: ../data/ui/menu.ui:10 msgid "Keyboard Shortcuts" msgstr "Tipkovni prečaci" #: ../data/ui/menu.ui:14 msgid "About Giara" msgstr "O programu Giara" #: ../data/ui/post_body.ui:141 ../data/ui/comment_box.ui:157 #: ../data/ui/post_preview.ui:122 msgid "Upvote" msgstr "Glasaj za" #: ../data/ui/post_body.ui:163 ../data/ui/comment_box.ui:206 #: ../data/ui/post_preview.ui:144 msgid "Ups" msgstr "Glasanja" #: ../data/ui/post_body.ui:177 ../data/ui/comment_box.ui:177 #: ../data/ui/post_preview.ui:158 msgid "Downvote" msgstr "Glasaj protiv" #: ../data/ui/post_body.ui:218 ../data/ui/post_preview.ui:199 msgid "Pinned" msgstr "Prikvačeno" #: ../data/ui/post_body.ui:341 ../data/ui/comment_box.ui:226 msgid "Reply" msgstr "Odgovori" #: ../data/ui/post_body.ui:361 ../data/ui/post_preview.ui:344 msgid "Open media" msgstr "Otvori medij" #: ../data/ui/post_body.ui:381 ../data/ui/post_preview.ui:364 msgid "Open link" msgstr "Otvori poveznicu" #: ../data/ui/post_body.ui:401 ../data/ui/comment_box.ui:246 #: ../data/ui/post_preview.ui:384 msgid "Save" msgstr "Spremi" #: ../data/ui/post_body.ui:421 ../data/ui/comment_box.ui:266 #: ../data/ui/post_preview.ui:404 msgid "Share" msgstr "Podijeli" #: ../data/ui/post_body.ui:461 ../data/ui/comment_box.ui:306 #: ../data/ui/post_preview.ui:424 msgid "Delete" msgstr "Izbriši" #: ../data/ui/shortcutsWindow.ui:18 msgid "Open Keyboard Shortcuts" msgstr "Otvori tipkovne prečace" #: ../data/ui/shortcutsWindow.ui:25 msgid "Open Menu" msgstr "Otvori izbornik" #: ../data/ui/shortcutsWindow.ui:32 msgid "Open Preferences" msgstr "Otvori postavke" #: ../data/ui/shortcutsWindow.ui:39 msgid "Quit" msgstr "Zatvori program" #: ../data/ui/subreddit_more_actions.ui:32 #: ../data/ui/subreddit_more_actions.ui:93 msgid "Add to multireddit" msgstr "" #: ../data/ui/subreddit_more_actions.ui:74 #: ../data/ui/subreddit_more_actions.ui:171 #: ../data/ui/post_details_headerbar.ui:14 #: ../data/ui/headerbar_with_back_and_squeezer.ui:28 #: ../data/ui/subreddit_view_headerbar.ui:28 msgid "Back" msgstr "Natrag" #: ../data/ui/subreddit_more_actions.ui:137 #: ../data/ui/subreddit_more_actions.ui:190 msgid "Create multireddit" msgstr "" #: ../data/ui/subreddit_more_actions.ui:222 msgid "Multireddit name..." msgstr "" #: ../data/ui/subreddit_more_actions.ui:232 msgid "Create" msgstr "" #: ../data/ui/comment_box.ui:77 msgid "Collapse replies" msgstr "" #: ../data/ui/post_preview.ui:301 msgid "Comments" msgstr "Komentari" #: ../data/ui/redditor_heading.ui:83 msgid "Karma" msgstr "Karma" #: ../data/ui/redditor_heading.ui:113 msgid "Cake day" msgstr "Godišnjica prijave" #: ../data/ui/new_post_menu.ui:14 msgid "Media" msgstr "Mediji" #: ../data/ui/login_view.ui:83 msgid "Welcome to Giara" msgstr "Dobro došao, dobro došla u Giara" #: ../data/ui/login_view.ui:96 msgid "Login" msgstr "Prijava" #: ../data/ui/login_view.ui:136 msgid "Waiting for authentication..." msgstr "" #: ../data/ui/headerbar.ui:171 msgid "Saved" msgstr "Spremljeno" #: ../data/ui/headerbar.ui:188 msgid "Logout" msgstr "Odjava" #: ../data/ui/headerbar.ui:233 ../data/ui/new_post_window.ui:17 msgid "New post" msgstr "Novi članak" #: ../data/ui/headerbar.ui:272 msgid "Messages in your inbox" msgstr "" #: ../data/ui/headerbar.ui:298 ../data/ui/post_details_headerbar.ui:29 #: ../data/ui/subreddit_view_headerbar.ui:43 msgid "Refresh" msgstr "Aktualiziraj" #: ../data/ui/headerbar.ui:341 msgid "Menu" msgstr "Izbornik" #: ../data/ui/new_post_window.ui:20 msgid "Cancel" msgstr "Prekini" #: ../data/ui/new_post_window.ui:28 msgid "Send" msgstr "Pošalji" #: ../data/ui/new_post_window.ui:72 msgid "Post title..." msgstr "Naslov članka …" #: ../data/ui/new_post_window.ui:127 msgid "Link..." msgstr "Poveznica …" #: ../data/ui/new_post_window.ui:145 msgid "Select media..." msgstr "Odaberi medij …" #: ../data/org.gabmus.giara.desktop.in:3 #: ../data/org.gabmus.giara.service.desktop.in:3 msgid "@prettyname@" msgstr "@prettyname@" #: ../data/org.gabmus.giara.desktop.in:4 msgid "A GTK app for Reddit" msgstr "GTK program za Reddit" #: ../data/org.gabmus.giara.desktop.in:11 msgid "reddit;" msgstr "reddit;" #: ../data/org.gabmus.giara.appdata.xml.in:4 msgid "Giara" msgstr "Giara" #: ../data/org.gabmus.giara.appdata.xml.in:5 msgid "Gabriele Musco" msgstr "Gabriele Musco" #: ../data/org.gabmus.giara.appdata.xml.in:15 msgid "Giara is a GTK app for Reddit" msgstr "Giara je GTK program za Reddit" #: ../data/org.gabmus.giara.appdata.xml.in:37 msgid "Improved markdown rendering" msgstr "" #: ../data/org.gabmus.giara.appdata.xml.in:38 msgid "Dark mode implemented" msgstr "" #: ../data/org.gabmus.giara.appdata.xml.in:39 msgid "Added option to disable images in post previews" msgstr "" #: ../data/org.gabmus.giara.appdata.xml.in:40 msgid "Added option to set a maximum size for images" msgstr "" #: ../data/org.gabmus.giara.appdata.xml.in:41 msgid "Added option to clear cache" msgstr "" #: ../data/org.gabmus.giara.appdata.xml.in:42 msgid "Added Brazilian Portuguese translation" msgstr "" #: ../data/org.gabmus.giara.appdata.xml.in:43 msgid "New post or comment window now remembers its size" msgstr "" #: ../data/org.gabmus.giara.appdata.xml.in:44 msgid "Subreddits can now be searched when creating new posts" msgstr "" #: ../data/org.gabmus.giara.appdata.xml.in:45 msgid "Initial support for multireddits" msgstr "" #: ../data/org.gabmus.giara.appdata.xml.in:46 msgid "Added Croatian translation" msgstr "" #: ../data/org.gabmus.giara.appdata.xml.in:47 msgid "Added notifications for unread inbox items" msgstr "" #: ../data/org.gabmus.giara.appdata.xml.in:48 msgid "Made comment border lines colorful" msgstr "" #: ../data/org.gabmus.giara.appdata.xml.in:49 msgid "Authentication is now handled by your default browser" msgstr "" #: ../data/org.gabmus.giara.appdata.xml.in:56 msgid "Improvements for flatpak packaging" msgstr "Poboljšanja za flatpak paketiranje" #: ../data/org.gabmus.giara.appdata.xml.in:63 msgid "First release" msgstr "Prvo izdanje" #~ msgid "Welcome" #~ msgstr "Dobro došao, dobro došla" giara-0.3/po/id.po000066400000000000000000000306521376474030500140300ustar00rootroot00000000000000# Indonesian translation for giara. # Copyright (C) 2020 giara's COPYRIGHT HOLDER # This file is distributed under the same license as the giara package. # Andika Triwidada , 2020. # msgid "" msgstr "" "Project-Id-Version: giara master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/giara/issues\n" "POT-Creation-Date: 2020-11-09 10:57+0000\n" "PO-Revision-Date: 2020-11-12 09:50+0700\n" "Last-Translator: Kukuh Syafaat \n" "Language-Team: Indonesian \n" "Language: id\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 2.4.2\n" "Plural-Forms: nplurals=2; plural= n!=1;\n" #: data/org.gabmus.giara.appdata.xml.in:4 msgid "Giara" msgstr "Giara" #: data/org.gabmus.giara.appdata.xml.in:5 msgid "Gabriele Musco" msgstr "Gabriele Musco" #: data/org.gabmus.giara.appdata.xml.in:6 data/org.gabmus.giara.desktop.in:5 msgid "An app for Reddit" msgstr "Aplikasi GTK untuk Reddit" #: data/org.gabmus.giara.appdata.xml.in:15 msgid "An app for Reddit." msgstr "Aplikasi GTK untuk Reddit." #: data/org.gabmus.giara.appdata.xml.in:16 msgid "Browse Reddit from your Linux desktop or smartphone." msgstr "Telusuri Reddit dari destop atau ponsel cerdas Linux Anda." #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: data/org.gabmus.giara.desktop.in:14 msgid "reddit;" msgstr "reddit;" #: data/ui/comment_box.ui:77 msgid "Collapse replies" msgstr "Kuncupkan balasan" #: data/ui/comment_box.ui:157 data/ui/post_preview.ui:122 msgid "Upvote" msgstr "Suara positif" #: data/ui/comment_box.ui:177 data/ui/post_preview.ui:158 msgid "Downvote" msgstr "Suara negatif" #: data/ui/comment_box.ui:206 data/ui/post_preview.ui:144 msgid "Ups" msgstr "Ups" #: data/ui/comment_box.ui:226 msgid "Reply" msgstr "Balas" #: data/ui/comment_box.ui:246 data/ui/post_preview.ui:384 msgid "Save" msgstr "Simpan" #: data/ui/comment_box.ui:266 data/ui/post_preview.ui:404 msgid "Share" msgstr "Bagikan" #: data/ui/comment_box.ui:286 giara/new_post_window.py:419 msgid "Edit" msgstr "Sunting" #: data/ui/comment_box.ui:306 data/ui/post_preview.ui:424 msgid "Delete" msgstr "Hapus" #: data/ui/headerbar.ui:103 giara/inbox_view.py:134 giara/left_stack.py:159 msgid "Inbox" msgstr "Kotak Masuk" #: data/ui/headerbar.ui:120 giara/left_stack.py:119 giara/search_view.py:57 msgid "Subreddits" msgstr "Subreddit" #: data/ui/headerbar.ui:137 giara/left_stack.py:174 msgid "Multireddits" msgstr "Multireddit" #: data/ui/headerbar.ui:154 data/ui/headerbar.ui:254 giara/left_stack.py:89 #: giara/left_stack.py:97 msgid "Profile" msgstr "Profil" #: data/ui/headerbar.ui:171 msgid "Saved" msgstr "Disimpan" #: data/ui/headerbar.ui:188 msgid "Log Out" msgstr "Log Keluar" #: data/ui/headerbar.ui:233 data/ui/new_post_window.ui:17 msgid "New post" msgstr "Kiriman baru" #: data/ui/headerbar.ui:272 msgid "Messages in your inbox" msgstr "Pesan di kotak masuk Anda" #: data/ui/headerbar.ui:298 data/ui/post_details_headerbar.ui:29 #: data/ui/subreddit_view_headerbar.ui:43 msgid "Refresh" msgstr "Segarkan" #: data/ui/headerbar.ui:322 msgid "Menu" msgstr "Menu" #: data/ui/headerbar.ui:349 data/ui/subreddit_view_headerbar.ui:67 #: giara/left_stack.py:143 msgid "Search" msgstr "Cari" #: data/ui/headerbar_with_back_and_squeezer.ui:28 #: data/ui/post_details_headerbar.ui:14 data/ui/subreddit_more_actions.ui:74 #: data/ui/subreddit_more_actions.ui:171 data/ui/subreddit_view_headerbar.ui:28 msgid "Back" msgstr "Mundur" #: data/ui/login_view.ui:83 msgid "Welcome to Giara" msgstr "Selamat Datang di Giara" #: data/ui/login_view.ui:96 msgid "Login" msgstr "Log Masuk" #: data/ui/login_view.ui:136 msgid "Waiting for authentication…" msgstr "Menunggu otentikasi…" #: data/ui/main_ui.ui:70 msgid "Close" msgstr "Tutup" #: data/ui/menu.ui:6 msgid "Preferences" msgstr "Preferensi" #: data/ui/menu.ui:10 msgid "Keyboard Shortcuts" msgstr "Pintasan Papan Tik" #: data/ui/menu.ui:14 msgid "About Giara" msgstr "Tentang Giara" #: data/ui/new_post_menu.ui:6 giara/flair_label.py:49 msgid "Text" msgstr "Teks" #: data/ui/new_post_menu.ui:10 giara/flair_label.py:55 msgid "Link" msgstr "Taut" #: data/ui/new_post_menu.ui:14 msgid "Media" msgstr "Media" #: data/ui/new_post_window.ui:20 msgid "Cancel" msgstr "Batal" #: data/ui/new_post_window.ui:28 msgid "Send" msgstr "Kirim" #: data/ui/new_post_window.ui:72 msgid "Post title…" msgstr "Judul kiriman…" #: data/ui/new_post_window.ui:127 msgid "Link…" msgstr "Tautan…" #: data/ui/new_post_window.ui:145 msgid "Select media…" msgstr "Pilih media…" #: data/ui/post_preview.ui:199 msgid "Pinned" msgstr "Dipatok" #: data/ui/post_preview.ui:301 msgid "Comments" msgstr "Komentar" #: data/ui/post_preview.ui:344 msgid "Open media" msgstr "Buka media" #: data/ui/post_preview.ui:364 msgid "Open link" msgstr "Buka tautan" #: data/ui/redditor_heading.ui:59 giara/user_heading.py:57 msgid "Follow" msgstr "Ikuti" #: data/ui/redditor_heading.ui:83 msgid "Karma" msgstr "Karma" #: data/ui/redditor_heading.ui:113 msgid "Cake day" msgstr "Hari kue" #: data/ui/shortcutsWindow.ui:13 giara/settings_window.py:256 msgid "General" msgstr "Umum" #: data/ui/shortcutsWindow.ui:18 msgid "Open Keyboard Shortcuts" msgstr "Buka Pintasan Papan Tik" #: data/ui/shortcutsWindow.ui:25 msgid "Open Menu" msgstr "Buka Menu" #: data/ui/shortcutsWindow.ui:32 msgid "Open Preferences" msgstr "Buka Preferensi" #: data/ui/shortcutsWindow.ui:39 msgid "Quit" msgstr "Keluar" #: data/ui/subreddit_heading.ui:77 giara/subreddit_view.py:281 msgid "Join" msgstr "Gabung" #: data/ui/subreddit_heading.ui:101 msgid "Members" msgstr "Anggota" #: data/ui/subreddit_heading.ui:131 msgid "Since" msgstr "Sejak" #: data/ui/subreddit_more_actions.ui:32 data/ui/subreddit_more_actions.ui:93 msgid "Add to multireddit" msgstr "Tambahkan ke multireddit" #: data/ui/subreddit_more_actions.ui:137 data/ui/subreddit_more_actions.ui:190 msgid "Create multireddit" msgstr "Membuat multireddit" #: data/ui/subreddit_more_actions.ui:222 msgid "Multireddit name…" msgstr "Nama multireddit…" #: data/ui/subreddit_more_actions.ui:232 msgid "Create" msgstr "Buat" #: giara/auth.py:20 msgid "Error getting client with refresh token, retrying…" msgstr "Galat saat mendapatkan klien dengan token penyegaran, coba lagi…" #: giara/auth.py:30 msgid "Error authorizing Reddit client, retrying…" msgstr "Galat saat mengotorisasi klien Reddit, mencoba kembali…" #: giara/auth.py:35 msgid "Error authorizing Reddit client after retry, quitting…" msgstr "Galat saat mengotorisasi klien Reddit setelah mencoba lagi, keluar…" #: giara/common_post_box.py:108 msgid "Are you sure you want to delete this item?" msgstr "Anda yakin akan menghapus butir ini?" #: giara/common_post_box.py:141 msgid "Link copied to clipboard" msgstr "Tautan disalin ke papan klip" #: giara/common_post_box.py:244 msgid "Comment: " msgstr "Komentar: " #: giara/common_post_box.py:253 giara/flair_label.py:19 msgid "Message" msgstr "Pesan" #: giara/common_post_box.py:259 giara/inbox_view.py:41 #: giara/post_details_view.py:97 msgid "Author unknown" msgstr "Penulis tidak diketahui" #: giara/flair_label.py:11 msgid "Comment" msgstr "Komentar" #: giara/flair_label.py:15 msgid "Post" msgstr "Pos" #: giara/flair_label.py:37 msgid "Image" msgstr "Citra" #: giara/flair_label.py:43 msgid "Video" msgstr "Video" #: giara/front_page_headerbar.py:22 giara/settings_window.py:284 msgid "Best" msgstr "Terbaik" #: giara/front_page_headerbar.py:26 giara/settings_window.py:284 #: giara/subreddit_view.py:202 msgid "Hot" msgstr "Panas" #: giara/front_page_headerbar.py:30 giara/inbox_view.py:68 #: giara/settings_window.py:284 giara/subreddit_view.py:206 msgid "New" msgstr "Baru" #: giara/front_page_headerbar.py:34 giara/settings_window.py:284 #: giara/subreddit_view.py:210 msgid "Top" msgstr "Puncak" #: giara/front_page_headerbar.py:38 giara/settings_window.py:284 #: giara/subreddit_view.py:214 msgid "Rising" msgstr "Meningkat" #: giara/front_page_headerbar.py:42 giara/settings_window.py:285 #: giara/subreddit_view.py:218 msgid "Controversial" msgstr "Kontroversial" #: giara/front_page_headerbar.py:88 #, python-brace-format msgid "{0} Karma" msgid_plural "{0} Karma" msgstr[0] "{0} Karma" msgstr[1] "{0} Karma" #: giara/front_page_headerbar.py:153 msgid "Do you want to log out? This will close the application." msgstr "Apakah Anda ingin log keluar? Ini akan menutup aplikasi." #: giara/inbox_view.py:52 #, python-brace-format msgid "Comment in \"{0}\"" msgstr "Komentar di \"{0}\"" #: giara/left_stack.py:33 msgid "Posts" msgstr "Pos" #: giara/left_stack.py:63 msgid "Front page" msgstr "Halaman depan" #: giara/left_stack.py:70 giara/left_stack.py:77 msgid "Saved posts" msgstr "Kiriman tersimpan" #: giara/left_stack.py:299 #, python-brace-format msgid "Searching in {0}" msgstr "Mencari di {0}" #: giara/markdown_view.py:42 msgid "Image: " msgstr "Citra: " #: giara/markdown_view.py:42 msgid "[Image]" msgstr "[Citra]" #: giara/multireddit_view.py:59 msgid "Remove from multireddit" msgstr "Hapus dari multireddit" #: giara/multireddit_view.py:85 msgid "Edit multireddit" msgstr "Sunting multireddit" #: giara/new_post_window.py:100 msgid "Choose an image or video to upload" msgstr "Pilih citra atau video untuk diunggah" #: giara/new_post_window.py:212 giara/new_post_window.py:213 #: giara/new_post_window.py:219 msgid "None" msgstr "Nihil" #: giara/new_post_window.py:262 msgid "Select a subreddit…" msgstr "Pilih subreddit…" #: giara/new_post_window.py:324 msgid "Select a flair…" msgstr "Pilih bakat…" #: giara/new_post_window.py:389 msgid "New comment" msgstr "Komentar baru" #: giara/new_post_window.py:418 msgid "Editing" msgstr "Menyunting" #: giara/notification_manager.py:31 #, python-brace-format msgid "{0} new message" msgid_plural "{0} new messages" msgstr[0] "{0} pesan baru" msgstr[1] "{0} pesan baru" #: giara/notification_manager.py:36 #, python-brace-format msgid "{0} more" msgid_plural "{0} more" msgstr[0] "{0} lebih" msgstr[1] "{0} lebih" #: giara/post_details_view.py:32 msgid "Load more comments" msgstr "Muat lebih banyak komentar" #: giara/search_view.py:65 msgid "Users" msgstr "Pengguna" #: giara/settings_window.py:107 msgid "Choose a folder" msgstr "Pilih folder" #: giara/settings_window.py:260 msgid "General Settings" msgstr "Setelan Umum" #: giara/settings_window.py:281 msgid "Default view" msgstr "Tilikan baku" #: giara/settings_window.py:294 msgid "Cache" msgstr "Singgahan" #: giara/settings_window.py:307 msgid "Clear cache" msgstr "Hapus singgahan" #: giara/settings_window.py:307 msgid "Clear" msgstr "Bersihkan" #: giara/settings_window.py:318 msgid "View" msgstr "Tampilan" #: giara/settings_window.py:322 msgid "View Settings" msgstr "Pengaturan Tilikan" #: giara/settings_window.py:325 msgid "Dark mode" msgstr "Mode gelap" #: giara/settings_window.py:330 msgid "Show thumbnails in post previews" msgstr "Memperlihatkan gambar mini di pratinjau kiriman" #: giara/settings_window.py:354 msgid "Max thumbnail width" msgstr "Lebar gambar mini maks" #: giara/settings_window.py:358 msgid "Use 0 to remove the limit" msgstr "Gunakan 0 untuk menghapus batas" #: giara/settings_window.py:368 msgid "Privacy" msgstr "Privasi" #: giara/settings_window.py:372 msgid "Link replacement" msgstr "Penggantian tautan" #: giara/settings_window.py:375 msgid "Replace Twitter links with Nitter" msgstr "Ganti tautan Twitter dengan Nitter" #: giara/settings_window.py:380 msgid "Replace YouTube links with Invidious" msgstr "Mengganti tautan YouTube dengan Invidious" #: giara/settings_window.py:390 msgid "Invidious instance" msgstr "Instansi invidious" #: giara/settings_window.py:392 msgid "Naked domain name only, no https://" msgstr "Nama domain telanjang saja, tidak ada https://" #: giara/sort_menu_btn.py:75 #, python-brace-format msgid "Sort by: {0}" msgstr "Urutkan menurut: {0}" #: giara/subreddit_view.py:40 msgid "This subreddit is already part of this multireddit" msgstr "Subreddit ini sudah menjadi bagian dari multireddit ini" #: giara/subreddit_view.py:235 msgid "More actions" msgstr "Tindakan lainnya" #: giara/subreddit_view.py:278 msgid "Leave" msgstr "Tinggalkan" #: giara/subreddit_view.py:317 msgid "Description" msgstr "Keterangan" #: giara/user_heading.py:54 msgid "Unfollow" msgstr "Berhenti mengikuti" #~ msgid "Giara is a GTK app for Reddit" #~ msgstr "Giara adalah aplikasi GTK untuk Reddit" #~ msgid "1 new message" #~ msgstr "1 pesan baru" giara-0.3/po/it.po000066400000000000000000000461411376474030500140500ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-11-09 12:28+0100\n" "PO-Revision-Date: \n" "Last-Translator: Gabriele Musco \n" "Language-Team: \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 2.4.1\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: ../giara/choice_picker.py:51 ../giara/choice_picker.py:63 #: ../giara/new_post_window.py:212 ../giara/new_post_window.py:213 #: ../giara/new_post_window.py:219 msgid "None" msgstr "Nessuno" #: ../giara/user_heading.py:54 msgid "Unfollow" msgstr "Smetti di seguire" #: ../giara/user_heading.py:57 msgid "Follow" msgstr "Segui" #: ../giara/multireddit_view.py:59 msgid "Remove from multireddit" msgstr "Rimuovi dal multireddit" #: ../giara/multireddit_view.py:85 msgid "Edit multireddit" msgstr "Modifica multireddit" #: ../giara/subreddits_list_view.py:136 ../giara/left_stack.py:119 #: ../giara/search_view.py:57 msgid "Subreddits" msgstr "Subreddit" #: ../giara/inbox_view.py:41 ../giara/post_details_view.py:97 #: ../giara/common_post_box.py:259 msgid "Author unknown" msgstr "Autore sconosciuto" #: ../giara/inbox_view.py:52 #, python-brace-format msgid "Comment in \"{0}\"" msgstr "Commento in \"{0}\"" #: ../giara/inbox_view.py:68 ../giara/front_page_headerbar.py:30 #: ../giara/settings_window.py:284 ../giara/single_post_stream_headerbar.py:23 #: ../giara/subreddit_view.py:206 msgid "New" msgstr "Nuovi" #: ../giara/inbox_view.py:134 ../giara/left_stack.py:159 msgid "Inbox" msgstr "In entrata" #: ../giara/post_details_view.py:32 msgid "Load more comments" msgstr "Carica altri commenti" #: ../giara/notification_manager.py:31 #, python-brace-format msgid "{0} new message" msgid_plural "{0} new messages" msgstr[0] "{0} nuovo messaggio" msgstr[1] "{0} nuovi messaggi" #: ../giara/notification_manager.py:36 #, python-brace-format msgid "{0} more" msgid_plural "{0} more" msgstr[0] "{0} altro" msgstr[1] "Altri {0}" #: ../giara/common_post_box.py:108 msgid "Are you sure you want to delete this item?" msgstr "Sei sicuro di voler cancellare questo elemento?" #: ../giara/common_post_box.py:141 msgid "Link copied to clipboard" msgstr "Link copiato negli appunti" #: ../giara/common_post_box.py:244 msgid "Comment: " msgstr "Commento: " #: ../giara/common_post_box.py:253 ../giara/flair_label.py:19 msgid "Message" msgstr "Messaggio" #: ../giara/new_post_window.py:100 msgid "Choose an image or video to upload" msgstr "Scegli un immagine o un video da caricare" #: ../giara/new_post_window.py:262 msgid "Select a subreddit…" msgstr "Seleziona un subreddit…" #: ../giara/new_post_window.py:324 msgid "Select a flair…" msgstr "Seleziona un flair…" #: ../giara/new_post_window.py:389 msgid "New comment" msgstr "Nuovo commento" #: ../giara/new_post_window.py:418 msgid "Editing" msgstr "In modifica" #: ../giara/new_post_window.py:419 msgid "Edit" msgstr "Modifica" #: ../giara/markdown_view.py:42 msgid "Image: " msgstr "Immagine: " #: ../giara/markdown_view.py:42 msgid "[Image]" msgstr "[Immagine]" #: ../giara/subreddit_search_view.py:26 ../giara/left_stack.py:299 #, python-brace-format msgid "Searching in {0}" msgstr "Ricerca in {0}" #: ../giara/front_page_headerbar.py:22 ../giara/settings_window.py:284 msgid "Best" msgstr "Migliori" #: ../giara/front_page_headerbar.py:26 ../giara/settings_window.py:284 #: ../giara/single_post_stream_headerbar.py:19 ../giara/subreddit_view.py:202 msgid "Hot" msgstr "Caldi" #: ../giara/front_page_headerbar.py:34 ../giara/settings_window.py:284 #: ../giara/single_post_stream_headerbar.py:27 ../giara/subreddit_view.py:210 msgid "Top" msgstr "Migliori" #: ../giara/front_page_headerbar.py:38 ../giara/settings_window.py:284 #: ../giara/single_post_stream_headerbar.py:31 ../giara/subreddit_view.py:214 msgid "Rising" msgstr "In salita" #: ../giara/front_page_headerbar.py:42 ../giara/settings_window.py:285 #: ../giara/single_post_stream_headerbar.py:35 ../giara/subreddit_view.py:218 msgid "Controversial" msgstr "Controversi" #: ../giara/front_page_headerbar.py:88 #, python-brace-format msgid "{0} Karma" msgid_plural "{0} Karma" msgstr[0] "{0} Karma" msgstr[1] "{0} Karma" #: ../giara/front_page_headerbar.py:153 msgid "Do you want to log out? This will close the application." msgstr "Vuoi effettuare il log out? L'applicazione verrà chiusa." #: ../giara/left_stack.py:33 msgid "Posts" msgstr "Post" #: ../giara/left_stack.py:63 msgid "Front page" msgstr "Pagina frontale" #: ../giara/left_stack.py:70 ../giara/left_stack.py:77 msgid "Saved posts" msgstr "Post salvati" #: ../giara/left_stack.py:89 ../giara/left_stack.py:97 msgid "Profile" msgstr "Profilo" #: ../giara/left_stack.py:143 msgid "Search" msgstr "Cerca" #: ../giara/left_stack.py:174 ../giara/multireddit_list_view.py:111 msgid "Multireddits" msgstr "Multireddits" #: ../giara/sort_menu_btn.py:75 #, python-brace-format msgid "Sort by: {0}" msgstr "Ordinato per: {0}" #: ../giara/settings_window.py:107 msgid "Choose a folder" msgstr "Scegli una cartella" #: ../giara/settings_window.py:256 msgid "General" msgstr "Generali" #: ../giara/settings_window.py:260 msgid "General Settings" msgstr "Impostazioni Generali" #: ../giara/settings_window.py:281 msgid "Default view" msgstr "Vista predefinita" #: ../giara/settings_window.py:294 msgid "Cache" msgstr "Cache" #: ../giara/settings_window.py:307 msgid "Clear cache" msgstr "Pulisci la cache" #: ../giara/settings_window.py:307 msgid "Clear" msgstr "Pulisci" #: ../giara/settings_window.py:318 msgid "View" msgstr "Vista" #: ../giara/settings_window.py:322 msgid "View Settings" msgstr "Impostazioni Vista" #: ../giara/settings_window.py:325 msgid "Dark mode" msgstr "Modalità scura" #: ../giara/settings_window.py:330 msgid "Show thumbnails in post previews" msgstr "Mostra miniature nelle anteprime dei post" #: ../giara/settings_window.py:354 msgid "Max thumbnail width" msgstr "Massima larghezza della miniatura" #: ../giara/settings_window.py:358 msgid "Use 0 to remove the limit" msgstr "Usa 0 per rimuovere il limite" #: ../giara/settings_window.py:368 msgid "Privacy" msgstr "Privacy" #: ../giara/settings_window.py:372 msgid "Link replacement" msgstr "Rimpiazzo dei link" #: ../giara/settings_window.py:375 msgid "Replace Twitter links with Nitter" msgstr "Rimpiazza i link di Twitter con Nitter" #: ../giara/settings_window.py:380 msgid "Replace YouTube links with Invidious" msgstr "Rimpiazza i link di YouTube con Invidious" #: ../giara/settings_window.py:390 msgid "Invidious instance" msgstr "Istanza di Invidious" #: ../giara/settings_window.py:392 msgid "Naked domain name only, no https://" msgstr "Solo dominio nudo, niente https://" #: ../giara/search_view.py:65 msgid "Users" msgstr "Utenti" #: ../giara/auth.py:20 msgid "Error getting client with refresh token, retrying…" msgstr "Errore nell'ottenere il client con il token di refresh, riprovo…" #: ../giara/auth.py:30 msgid "Error authorizing Reddit client, retrying…" msgstr "Errore nell'autorizzare il client reddit, riprovo…" #: ../giara/auth.py:35 msgid "Error authorizing Reddit client after retry, quitting…" msgstr "Errore nell'autorizzare il client reddit dopo aver riprovato, esco…" #: ../giara/flair_label.py:11 msgid "Comment" msgstr "Commento" #: ../giara/flair_label.py:15 msgid "Post" msgstr "Post" #: ../giara/flair_label.py:37 msgid "Image" msgstr "Immagine" #: ../giara/flair_label.py:43 msgid "Video" msgstr "Video" #: ../giara/flair_label.py:49 msgid "Text" msgstr "Testo" #: ../giara/flair_label.py:55 msgid "Link" msgstr "Link" #: ../giara/subreddit_view.py:40 msgid "This subreddit is already part of this multireddit" msgstr "Questo subreddit è già parte di questo multireddit" #: ../giara/subreddit_view.py:235 msgid "More actions" msgstr "Più azioni" #: ../giara/subreddit_view.py:278 msgid "Leave" msgstr "Lascia" #: ../giara/subreddit_view.py:281 msgid "Join" msgstr "Unisciti" #: ../giara/subreddit_view.py:317 msgid "Description" msgstr "Descrizione" #: ../data/ui/main_ui.ui:70 msgid "Close" msgstr "Chiudi" #: ../data/ui/subreddit_heading.ui:101 msgid "Members" msgstr "Membri" #: ../data/ui/subreddit_heading.ui:131 msgid "Since" msgstr "Dal" #: ../data/ui/menu.ui:6 msgid "Preferences" msgstr "Preferenze" #: ../data/ui/menu.ui:10 msgid "Keyboard Shortcuts" msgstr "Scorciatoie da tastiera" #: ../data/ui/menu.ui:14 msgid "About Giara" msgstr "A propostito di Giara" #: ../data/ui/post_body.ui:141 ../data/ui/comment_box.ui:157 #: ../data/ui/post_preview.ui:122 msgid "Upvote" msgstr "" #: ../data/ui/post_body.ui:163 ../data/ui/comment_box.ui:206 #: ../data/ui/post_preview.ui:144 msgid "Ups" msgstr "Voti" #: ../data/ui/post_body.ui:177 ../data/ui/comment_box.ui:177 #: ../data/ui/post_preview.ui:158 msgid "Downvote" msgstr "" #: ../data/ui/post_body.ui:218 ../data/ui/post_preview.ui:199 msgid "Pinned" msgstr "Fissato in cima" #: ../data/ui/post_body.ui:341 ../data/ui/comment_box.ui:226 msgid "Reply" msgstr "Rispondi" #: ../data/ui/post_body.ui:361 ../data/ui/post_preview.ui:344 msgid "Open media" msgstr "Apri media" #: ../data/ui/post_body.ui:381 ../data/ui/post_preview.ui:364 msgid "Open link" msgstr "Apri link" #: ../data/ui/post_body.ui:401 ../data/ui/comment_box.ui:246 #: ../data/ui/post_preview.ui:384 msgid "Save" msgstr "Salva" #: ../data/ui/post_body.ui:421 ../data/ui/comment_box.ui:266 #: ../data/ui/post_preview.ui:404 msgid "Share" msgstr "Condividi" #: ../data/ui/post_body.ui:461 ../data/ui/comment_box.ui:306 #: ../data/ui/post_preview.ui:424 msgid "Delete" msgstr "Elimina" #: ../data/ui/shortcutsWindow.ui:18 msgid "Open Keyboard Shortcuts" msgstr "Apri Scorciatoie da tastiera" #: ../data/ui/shortcutsWindow.ui:25 msgid "Open Menu" msgstr "Apri Menu" #: ../data/ui/shortcutsWindow.ui:32 msgid "Open Preferences" msgstr "Apri Preferenze" #: ../data/ui/shortcutsWindow.ui:39 msgid "Quit" msgstr "Esci" #: ../data/ui/subreddit_more_actions.ui:32 #: ../data/ui/subreddit_more_actions.ui:93 msgid "Add to multireddit" msgstr "Aggiungi al multireddit" #: ../data/ui/subreddit_more_actions.ui:74 #: ../data/ui/subreddit_more_actions.ui:171 #: ../data/ui/post_details_headerbar.ui:14 #: ../data/ui/headerbar_with_back_and_squeezer.ui:28 #: ../data/ui/subreddit_view_headerbar.ui:28 msgid "Back" msgstr "Indietro" #: ../data/ui/subreddit_more_actions.ui:137 #: ../data/ui/subreddit_more_actions.ui:190 msgid "Create multireddit" msgstr "Crea multireddit" #: ../data/ui/subreddit_more_actions.ui:222 msgid "Multireddit name…" msgstr "Nome del multireddit…" #: ../data/ui/subreddit_more_actions.ui:232 msgid "Create" msgstr "Crea" #: ../data/ui/comment_box.ui:77 msgid "Collapse replies" msgstr "Collassa risposte" #: ../data/ui/post_preview.ui:301 msgid "Comments" msgstr "Commenti" #: ../data/ui/redditor_heading.ui:83 msgid "Karma" msgstr "Karma" #: ../data/ui/redditor_heading.ui:113 msgid "Cake day" msgstr "Giorno della torta" #: ../data/ui/new_post_menu.ui:14 msgid "Media" msgstr "Media" #: ../data/ui/login_view.ui:83 msgid "Welcome to Giara" msgstr "Benvenuti su Giara" #: ../data/ui/login_view.ui:96 msgid "Login" msgstr "Entra" #: ../data/ui/login_view.ui:136 msgid "Waiting for authentication…" msgstr "In attesa dell'autenticazione…" #: ../data/ui/headerbar.ui:171 msgid "Saved" msgstr "Salvati" #: ../data/ui/headerbar.ui:188 msgid "Log Out" msgstr "Log Out" #: ../data/ui/headerbar.ui:233 ../data/ui/new_post_window.ui:17 msgid "New post" msgstr "Nuovo post" #: ../data/ui/headerbar.ui:272 msgid "Messages in your inbox" msgstr "Messaggi nel tuo inbox" #: ../data/ui/headerbar.ui:298 ../data/ui/post_details_headerbar.ui:29 #: ../data/ui/subreddit_view_headerbar.ui:43 msgid "Refresh" msgstr "Aggiorna" #: ../data/ui/headerbar.ui:322 msgid "Menu" msgstr "Menu" #: ../data/ui/new_post_window.ui:20 msgid "Cancel" msgstr "Annulla" #: ../data/ui/new_post_window.ui:28 msgid "Send" msgstr "Invia" #: ../data/ui/new_post_window.ui:72 msgid "Post title…" msgstr "Titolo post…" #: ../data/ui/new_post_window.ui:127 msgid "Link…" msgstr "Link…" #: ../data/ui/new_post_window.ui:145 msgid "Select media…" msgstr "Seleziona media…" #: ../data/org.gabmus.giara.desktop.in:4 #: ../data/org.gabmus.giara.service.desktop.in:4 msgid "@prettyname@" msgstr "" #: ../data/org.gabmus.giara.desktop.in:5 msgid "An app for Reddit" msgstr "Un'app per Reddit" #: ../data/org.gabmus.giara.desktop.in:14 msgid "reddit;" msgstr "" #: ../data/org.gabmus.giara.appdata.xml.in:4 msgid "Giara" msgstr "" #: ../data/org.gabmus.giara.appdata.xml.in:5 msgid "Gabriele Musco" msgstr "" #: ../data/org.gabmus.giara.appdata.xml.in:15 msgid "An app for Reddit." msgstr "Un'app per Reddit." #: ../data/org.gabmus.giara.appdata.xml.in:16 msgid "Browse Reddit from your Linux desktop or smartphone." msgstr "Naviga Reddit dal tuo desktop o smartphone Linux." #: ../data/org.gabmus.giara.appdata.xml.in:38 msgid "Opening a post from a comment jumps to that comment" msgstr "Aprire un post da un commento salta direttamente a quel commento" #: ../data/org.gabmus.giara.appdata.xml.in:39 msgid "Post creation and editing are now asynchronous" msgstr "La creazione e la modifica dei post è ora asincrona" #: ../data/org.gabmus.giara.appdata.xml.in:40 msgid "Replaced quit button in welcome view with a more standard close button" msgstr "" "Sostituito il bottone esci nella vista di benvenuto con un tasto chiudi più " "standard" #: ../data/org.gabmus.giara.appdata.xml.in:41 msgid "Fixed icon rendering problems in KDE" msgstr "Corretti problemi di rendering delle icone in KDE" #: ../data/org.gabmus.giara.appdata.xml.in:42 msgid "Comment replies can now be collapsed" msgstr "Le risposte dei commenti possono ora essere collassate" #: ../data/org.gabmus.giara.appdata.xml.in:43 msgid "" "Comments don't always load in full, hidden comments can be loaded if the " "user wants them" msgstr "" "I commenti non caricano sempre interamente, i commenti nascosti possono " "essere caricati se l'utente li richiede" #: ../data/org.gabmus.giara.appdata.xml.in:44 msgid "You can now open Twitter links in Nitter and YouTube links in Invidious" msgstr "" "Puoi ora aprire i link di Twitter su Nitter e quelli di YouTube su Invidious" #: ../data/org.gabmus.giara.appdata.xml.in:45 msgid "Limited picture preview size" msgstr "Limitata la dimensione delle anteprime delle immagini" #: ../data/org.gabmus.giara.appdata.xml.in:46 msgid "Added all available post sorting options" msgstr "Aggiunte tutte le opzioni di ordinamento disponibili" #: ../data/org.gabmus.giara.appdata.xml.in:47 msgid "New UI for choosing post sorting" msgstr "Nuova UI per scegliere l'ordinamento dei post" #: ../data/org.gabmus.giara.appdata.xml.in:48 msgid "Implemented blockquote rendering" msgstr "Implementato il rendering dei blocchi citazione" #: ../data/org.gabmus.giara.appdata.xml.in:49 msgid "Implemented superscript rendering" msgstr "Implementato il rendering del testo ad apice" #: ../data/org.gabmus.giara.appdata.xml.in:50 msgid "Improved Markdown rendering" msgstr "Migliorato il rendering del Markdown" #: ../data/org.gabmus.giara.appdata.xml.in:51 msgid "Improved Inbox view" msgstr "Migliorata la vista Inbox" #: ../data/org.gabmus.giara.appdata.xml.in:52 msgid "Added Ukranian translation" msgstr "Aggiunta traduzione in ucraino" #: ../data/org.gabmus.giara.appdata.xml.in:53 msgid "Added Indonesian translation" msgstr "Aggiunta traduzione in indonesiano" #: ../data/org.gabmus.giara.appdata.xml.in:54 msgid "Added Spanish translation" msgstr "Aggiunta traduzione in spagnolo" #: ../data/org.gabmus.giara.appdata.xml.in:55 msgid "Added Russian Translation" msgstr "Aggiunta traduzione in russo" #: ../data/org.gabmus.giara.appdata.xml.in:56 msgid "Added Slovenian translation" msgstr "Aggiunta traduzione in sloveno" #: ../data/org.gabmus.giara.appdata.xml.in:63 msgid "Improved markdown rendering" msgstr "Migliorato il rendering markdown" #: ../data/org.gabmus.giara.appdata.xml.in:64 msgid "Dark mode implemented" msgstr "Implementata la modalità scura" #: ../data/org.gabmus.giara.appdata.xml.in:65 msgid "Added option to disable images in post previews" msgstr "Aggiunta opzione per disabilitare le immagini nelle anteprime dei post" #: ../data/org.gabmus.giara.appdata.xml.in:66 msgid "Added option to set a maximum size for images" msgstr "Aggiunta opzione per impostare la dimensione massima per le immagini" #: ../data/org.gabmus.giara.appdata.xml.in:67 msgid "Added option to clear cache" msgstr "Aggiunta opzione per pulire la cache" #: ../data/org.gabmus.giara.appdata.xml.in:68 msgid "Added Brazilian Portuguese translation" msgstr "Aggiunta traduzione in portoghese brasiliano" #: ../data/org.gabmus.giara.appdata.xml.in:69 msgid "New post or comment window now remembers its size" msgstr "La finestra per i nuovi post o commenti ora ricorda la sua dimensioe" #: ../data/org.gabmus.giara.appdata.xml.in:70 msgid "Subreddits can now be searched when creating new posts" msgstr "" "I subreddit possono ora essere cercati quando vengono creati nuovi post" #: ../data/org.gabmus.giara.appdata.xml.in:71 msgid "Initial support for multireddits" msgstr "Supporto iniziale per i multireddit" #: ../data/org.gabmus.giara.appdata.xml.in:72 msgid "Added Croatian translation" msgstr "Aggiunta traduzione in croato" #: ../data/org.gabmus.giara.appdata.xml.in:73 msgid "Added notifications for unread inbox items" msgstr "Aggiunte notifiche per gli elementi dell'inbox non letti" #: ../data/org.gabmus.giara.appdata.xml.in:74 msgid "Made comment border lines colorful" msgstr "Le linee di contorno dei commenti sono state rese colorate" #: ../data/org.gabmus.giara.appdata.xml.in:75 msgid "Authentication is now handled by your default browser" msgstr "L'autenticazione è ora gestita dal browser predefinito" #: ../data/org.gabmus.giara.appdata.xml.in:82 msgid "Improvements for flatpak packaging" msgstr "Miglioramenti per il pacchetto flatpak" #: ../data/org.gabmus.giara.appdata.xml.in:89 msgid "First release" msgstr "Primo rilascio" #~ msgid "A GTK app for Reddit" #~ msgstr "Un'app GTK per Reddit" #~ msgid "Giara is a GTK app for Reddit" #~ msgstr "Giara è un'app GTK per Reddit" #~ msgid "1 new message" #~ msgstr "1 nuovo messaggio" #, python-brace-format #~ msgid "{0} new messages" #~ msgstr "{0} nuovi messaggi" #~ msgid "Select a subreddit..." #~ msgstr "Seleziona un subreddit..." #~ msgid "Select a flair..." #~ msgstr "Seleziona un flair..." #~ msgid "Show thumnails in post previews" #~ msgstr "Mostra miniature nelle anteprime dei post" #~ msgid "Error getting client with refresh token, retrying..." #~ msgstr "Errore nell'ottenere il client con il token di refresh, riprovo..." #~ msgid "Error authorizing reddit client, retrying..." #~ msgstr "Errore nell'autorizzare il client reddit, riprovo..." #~ msgid "Error authorizing reddit client after retry, quitting..." #~ msgstr "" #~ "Errore nell'autorizzare il client reddit dopo aver riprovato, esco..." #~ msgid "Multireddit name..." #~ msgstr "Nome del multireddit..." #~ msgid "Waiting for authentication..." #~ msgstr "In attesa dell'autenticazione..." #~ msgid "Logout" #~ msgstr "Logout" #~ msgid "Post title..." #~ msgstr "Titolo post..." #~ msgid "Link..." #~ msgstr "Link..." #~ msgid "Select media..." #~ msgstr "Seleziona media..." #~ msgid "Welcome" #~ msgstr "Benvenuti" giara-0.3/po/meson.build000066400000000000000000000001271376474030500152300ustar00rootroot00000000000000message('Update translations') i18n.gettext(meson.project_name(), preset: 'glib' ) giara-0.3/po/pt_BR.po000066400000000000000000000311241376474030500144350ustar00rootroot00000000000000# Portuguese translations for giara package # Traduções em português brasileiro para o pacote giara. # Copyright (C) 2020 THE giara'S COPYRIGHT HOLDER # This file is distributed under the same license as the giara package. # Gustavo Peredo , 2020. # Enrico Nicoletto , 2020. # msgid "" msgstr "" "Project-Id-Version: giara 0.1.1\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/giara/issues\n" "POT-Creation-Date: 2020-11-09 10:57+0000\n" "PO-Revision-Date: 2020-11-09 12:02-0300\n" "Last-Translator: Enrico Nicoletto \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" "Plural-Forms: nplurals=2; plural=(n > 1);\n" "X-Generator: Poedit 2.4.1\n" #: data/org.gabmus.giara.appdata.xml.in:4 msgid "Giara" msgstr "Giara" #: data/org.gabmus.giara.appdata.xml.in:5 msgid "Gabriele Musco" msgstr "Gabriele Musco" #: data/org.gabmus.giara.appdata.xml.in:6 data/org.gabmus.giara.desktop.in:5 msgid "An app for Reddit" msgstr "Um aplicativo para o Reddit" #: data/org.gabmus.giara.appdata.xml.in:15 msgid "An app for Reddit." msgstr "Um aplicativo para o Reddit." #: data/org.gabmus.giara.appdata.xml.in:16 msgid "Browse Reddit from your Linux desktop or smartphone." msgstr "Navegue pelo Reddit a partir da sua área de trabalho Linux ou smartphone." #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: data/org.gabmus.giara.desktop.in:14 msgid "reddit;" msgstr "reddit;" #: data/ui/comment_box.ui:77 msgid "Collapse replies" msgstr "Encolher respostas" #: data/ui/comment_box.ui:157 data/ui/post_preview.ui:122 msgid "Upvote" msgstr "Curtir" #: data/ui/comment_box.ui:177 data/ui/post_preview.ui:158 msgid "Downvote" msgstr "Não curtir" #: data/ui/comment_box.ui:206 data/ui/post_preview.ui:144 msgid "Ups" msgstr "Ups" #: data/ui/comment_box.ui:226 msgid "Reply" msgstr "Responder" #: data/ui/comment_box.ui:246 data/ui/post_preview.ui:384 msgid "Save" msgstr "Salvar" #: data/ui/comment_box.ui:266 data/ui/post_preview.ui:404 msgid "Share" msgstr "Compartilhar" #: data/ui/comment_box.ui:286 giara/new_post_window.py:419 msgid "Edit" msgstr "Editar" #: data/ui/comment_box.ui:306 data/ui/post_preview.ui:424 msgid "Delete" msgstr "Deletar" #: data/ui/headerbar.ui:103 giara/inbox_view.py:134 giara/left_stack.py:159 msgid "Inbox" msgstr "Inbox" #: data/ui/headerbar.ui:120 giara/left_stack.py:119 giara/search_view.py:57 msgid "Subreddits" msgstr "Subreddits" #: data/ui/headerbar.ui:137 giara/left_stack.py:174 msgid "Multireddits" msgstr "Multireddits" #: data/ui/headerbar.ui:154 data/ui/headerbar.ui:254 giara/left_stack.py:89 #: giara/left_stack.py:97 msgid "Profile" msgstr "Perfil" #: data/ui/headerbar.ui:171 msgid "Saved" msgstr "Salvo" #: data/ui/headerbar.ui:188 msgid "Log Out" msgstr "Encerrar sessão" #: data/ui/headerbar.ui:233 data/ui/new_post_window.ui:17 msgid "New post" msgstr "Nova postagem" #: data/ui/headerbar.ui:272 msgid "Messages in your inbox" msgstr "Mensagens na sua caixa de entrada" #: data/ui/headerbar.ui:298 data/ui/post_details_headerbar.ui:29 #: data/ui/subreddit_view_headerbar.ui:43 msgid "Refresh" msgstr "Recarregar" #: data/ui/headerbar.ui:322 msgid "Menu" msgstr "Menu" #: data/ui/headerbar.ui:349 data/ui/subreddit_view_headerbar.ui:67 #: giara/left_stack.py:143 msgid "Search" msgstr "Pesquisar" #: data/ui/headerbar_with_back_and_squeezer.ui:28 #: data/ui/post_details_headerbar.ui:14 data/ui/subreddit_more_actions.ui:74 #: data/ui/subreddit_more_actions.ui:171 data/ui/subreddit_view_headerbar.ui:28 msgid "Back" msgstr "Voltar" #: data/ui/login_view.ui:83 msgid "Welcome to Giara" msgstr "Bem vindo ao Giara" #: data/ui/login_view.ui:96 msgid "Login" msgstr "Entrar" #: data/ui/login_view.ui:136 msgid "Waiting for authentication…" msgstr "Aguardando autenticação…" #: data/ui/main_ui.ui:70 msgid "Close" msgstr "Fechar" #: data/ui/menu.ui:6 msgid "Preferences" msgstr "Preferências" #: data/ui/menu.ui:10 msgid "Keyboard Shortcuts" msgstr "Atalhos de teclado" #: data/ui/menu.ui:14 msgid "About Giara" msgstr "Sobre o Giara" #: data/ui/new_post_menu.ui:6 giara/flair_label.py:49 msgid "Text" msgstr "Texto" #: data/ui/new_post_menu.ui:10 giara/flair_label.py:55 msgid "Link" msgstr "Linque" #: data/ui/new_post_menu.ui:14 msgid "Media" msgstr "Media" #: data/ui/new_post_window.ui:20 msgid "Cancel" msgstr "Cancelar" #: data/ui/new_post_window.ui:28 msgid "Send" msgstr "Enviar" #: data/ui/new_post_window.ui:72 msgid "Post title…" msgstr "Título da postagem…" #: data/ui/new_post_window.ui:127 #| msgid "Link" msgid "Link…" msgstr "Link…" #: data/ui/new_post_window.ui:145 msgid "Select media…" msgstr "Selecionar mídia…" #: data/ui/post_preview.ui:199 msgid "Pinned" msgstr "Fixado" #: data/ui/post_preview.ui:301 msgid "Comments" msgstr "Comentários" #: data/ui/post_preview.ui:344 msgid "Open media" msgstr "Abrir media" #: data/ui/post_preview.ui:364 msgid "Open link" msgstr "Abrir linque" #: data/ui/redditor_heading.ui:59 giara/user_heading.py:57 msgid "Follow" msgstr "Seguir" #: data/ui/redditor_heading.ui:83 msgid "Karma" msgstr "Karma" #: data/ui/redditor_heading.ui:113 msgid "Cake day" msgstr "Dia do bolo" #: data/ui/shortcutsWindow.ui:13 giara/settings_window.py:256 msgid "General" msgstr "Geral" #: data/ui/shortcutsWindow.ui:18 msgid "Open Keyboard Shortcuts" msgstr "Abrir atalhos de teclado" #: data/ui/shortcutsWindow.ui:25 msgid "Open Menu" msgstr "Abrir menu" #: data/ui/shortcutsWindow.ui:32 msgid "Open Preferences" msgstr "Abrir Preferências" #: data/ui/shortcutsWindow.ui:39 msgid "Quit" msgstr "Sair" #: data/ui/subreddit_heading.ui:77 giara/subreddit_view.py:281 msgid "Join" msgstr "Entrar" #: data/ui/subreddit_heading.ui:101 msgid "Members" msgstr "Membros" #: data/ui/subreddit_heading.ui:131 msgid "Since" msgstr "Desde" #: data/ui/subreddit_more_actions.ui:32 data/ui/subreddit_more_actions.ui:93 msgid "Add to multireddit" msgstr "Adicionar a multireddit" #: data/ui/subreddit_more_actions.ui:137 data/ui/subreddit_more_actions.ui:190 msgid "Create multireddit" msgstr "Criar multireddit" #: data/ui/subreddit_more_actions.ui:222 msgid "Multireddit name…" msgstr "Nome do Multireddit…" #: data/ui/subreddit_more_actions.ui:232 msgid "Create" msgstr "Criar" #: giara/auth.py:20 msgid "Error getting client with refresh token, retrying…" msgstr "Erro ao obter cliente com token de atualização, tentando novamente…" #: giara/auth.py:30 msgid "Error authorizing Reddit client, retrying…" msgstr "Erro ao autorizar cliente Reddit, tentando novamente…" #: giara/auth.py:35 msgid "Error authorizing Reddit client after retry, quitting…" msgstr "Erro ao autorizar cliente Reddit após nova tentativa, encerrando…" #: giara/common_post_box.py:108 msgid "Are you sure you want to delete this item?" msgstr "Você tem certeza que deseja deletar esse item?" #: giara/common_post_box.py:141 msgid "Link copied to clipboard" msgstr "Linque copiado para a área de transferência" #: giara/common_post_box.py:244 msgid "Comment: " msgstr "Comentário: " #: giara/common_post_box.py:253 giara/flair_label.py:19 msgid "Message" msgstr "Mensagem" #: giara/common_post_box.py:259 giara/inbox_view.py:41 #: giara/post_details_view.py:97 msgid "Author unknown" msgstr "Autor desconhecido" #: giara/flair_label.py:11 msgid "Comment" msgstr "Comentar" #: giara/flair_label.py:15 msgid "Post" msgstr "Postagem" #: giara/flair_label.py:37 msgid "Image" msgstr "Imagem" #: giara/flair_label.py:43 msgid "Video" msgstr "Vídeo" #: giara/front_page_headerbar.py:22 giara/settings_window.py:284 msgid "Best" msgstr "Melhor" #: giara/front_page_headerbar.py:26 giara/settings_window.py:284 #: giara/subreddit_view.py:202 msgid "Hot" msgstr "Quente" #: giara/front_page_headerbar.py:30 giara/inbox_view.py:68 #: giara/settings_window.py:284 giara/subreddit_view.py:206 msgid "New" msgstr "Novo" #: giara/front_page_headerbar.py:34 giara/settings_window.py:284 #: giara/subreddit_view.py:210 msgid "Top" msgstr "Principal" #: giara/front_page_headerbar.py:38 giara/settings_window.py:284 #: giara/subreddit_view.py:214 msgid "Rising" msgstr "Em ascenção" #: giara/front_page_headerbar.py:42 giara/settings_window.py:285 #: giara/subreddit_view.py:218 msgid "Controversial" msgstr "Controverso" #: giara/front_page_headerbar.py:88 #, python-brace-format msgid "{0} Karma" msgid_plural "{0} Karma" msgstr[0] "{0} carma" msgstr[1] "{0} carmas" #: giara/front_page_headerbar.py:153 msgid "Do you want to log out? This will close the application." msgstr "Você deseja encerrar a sessão? Isso fechará o aplicativo." #: giara/inbox_view.py:52 #, python-brace-format msgid "Comment in \"{0}\"" msgstr "Comentário em \"{0}\"" #: giara/left_stack.py:33 msgid "Posts" msgstr "Postagens" #: giara/left_stack.py:63 msgid "Front page" msgstr "Página frontal" #: giara/left_stack.py:70 giara/left_stack.py:77 msgid "Saved posts" msgstr "Postagens salvas" #: giara/left_stack.py:299 #, python-brace-format msgid "Searching in {0}" msgstr "Pesquisando em {0}" #: giara/markdown_view.py:42 msgid "Image: " msgstr "Imagem: " #: giara/markdown_view.py:42 msgid "[Image]" msgstr "[Imagem]" #: giara/multireddit_view.py:59 msgid "Remove from multireddit" msgstr "Remover do multireddit" #: giara/multireddit_view.py:85 msgid "Edit multireddit" msgstr "Editar multireddit" #: giara/new_post_window.py:100 msgid "Choose an image or video to upload" msgstr "Escolha uma imagem ou um vídeo para upload" #: giara/new_post_window.py:212 giara/new_post_window.py:213 #: giara/new_post_window.py:219 msgid "None" msgstr "Nenhum" #: giara/new_post_window.py:262 msgid "Select a subreddit…" msgstr "Escolha um subreddit…" #: giara/new_post_window.py:324 msgid "Select a flair…" msgstr "Selecione um estilo…" #: giara/new_post_window.py:389 msgid "New comment" msgstr "Novo comentário" #: giara/new_post_window.py:418 msgid "Editing" msgstr "Editando" #: giara/notification_manager.py:31 #, python-brace-format msgid "{0} new message" msgid_plural "{0} new messages" msgstr[0] "{0} nova mensagem" msgstr[1] "{0} novas mensagens" #: giara/notification_manager.py:36 #, python-brace-format msgid "{0} more" msgid_plural "{0} more" msgstr[0] "{0} mais" msgstr[1] "{0} mais" #: giara/post_details_view.py:32 msgid "Load more comments" msgstr "Carregar mais comentários" #: giara/search_view.py:65 msgid "Users" msgstr "Usuários" #: giara/settings_window.py:107 msgid "Choose a folder" msgstr "Escolha uma pasta" #: giara/settings_window.py:260 msgid "General Settings" msgstr "Configurações Gerais" #: giara/settings_window.py:281 msgid "Default view" msgstr "Vizualização padrão" #: giara/settings_window.py:294 msgid "Cache" msgstr "Cache" #: giara/settings_window.py:307 msgid "Clear cache" msgstr "Limpar cache" #: giara/settings_window.py:307 msgid "Clear" msgstr "Limpar" #: giara/settings_window.py:318 msgid "View" msgstr "Visualizar" #: giara/settings_window.py:322 msgid "View Settings" msgstr "Configurações de visualização" #: giara/settings_window.py:325 msgid "Dark mode" msgstr "Modo escuro" #: giara/settings_window.py:330 msgid "Show thumbnails in post previews" msgstr "Mostrar miniaturas em visualizações de postagens" #: giara/settings_window.py:354 msgid "Max thumbnail width" msgstr "Largura de miniatura máxima" #: giara/settings_window.py:358 msgid "Use 0 to remove the limit" msgstr "Use 0 para remover o limite" #: giara/settings_window.py:368 msgid "Privacy" msgstr "Privacidade" #: giara/settings_window.py:372 msgid "Link replacement" msgstr "Substituição de link" #: giara/settings_window.py:375 msgid "Replace Twitter links with Nitter" msgstr "Substituir links do Twitter para Nitter" #: giara/settings_window.py:380 msgid "Replace YouTube links with Invidious" msgstr "Substituir links do YouTube para Invidious" #: giara/settings_window.py:390 msgid "Invidious instance" msgstr "Instância do Invidious" #: giara/settings_window.py:392 msgid "Naked domain name only, no https://" msgstr "Apenas nome de domínio raiz, sem https://" #: giara/sort_menu_btn.py:75 #, python-brace-format msgid "Sort by: {0}" msgstr "Ordenar por: {0}" #: giara/subreddit_view.py:40 msgid "This subreddit is already part of this multireddit" msgstr "Este subreddit já faz parte desta multireddit" #: giara/subreddit_view.py:235 msgid "More actions" msgstr "Mais ações" #: giara/subreddit_view.py:278 msgid "Leave" msgstr "Sair" #: giara/subreddit_view.py:317 msgid "Description" msgstr "Descrição" #: giara/user_heading.py:54 msgid "Unfollow" msgstr "Deixar de seguir"giara-0.3/po/ru.po000066400000000000000000000404621376474030500140620ustar00rootroot00000000000000# Russian translations for giara package. # Copyright (C) 2020 THE giara'S COPYRIGHT HOLDER # This file is distributed under the same license as the giara package. # Alexander Postol , 2020. # msgid "" msgstr "" "Project-Id-Version: giara 0.2\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-11-08 11:07+0300\n" "PO-Revision-Date: 2020-11-08 11:07+0300\n" "Last-Translator: Alexander Postol \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" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" #: ../giara/auth.py:20 msgid "Error getting client with refresh token, retrying…" msgstr "Ошибка при получении клиента с обновленным токеном, повторная попытка…" #: ../giara/auth.py:30 msgid "Error authorizing Reddit client, retrying…" msgstr "Ошибка авторизации клиента Reddit, повторная попытка…" #: ../giara/auth.py:35 msgid "Error authorizing Reddit client after retry, quitting…" msgstr "Ошибка авторизации клиента Reddit после повторной попытки, выход…" #: ../giara/choice_picker.py:51 ../giara/choice_picker.py:63 #: ../giara/new_post_window.py:212 ../giara/new_post_window.py:213 #: ../giara/new_post_window.py:219 msgid "None" msgstr "Отсутствует" #: ../giara/common_post_box.py:108 msgid "Are you sure you want to delete this item?" msgstr "Вы действительно хотите удалить?" #: ../giara/common_post_box.py:141 msgid "Link copied to clipboard" msgstr "Ссылка скопирована в буфер" #: ../giara/common_post_box.py:244 msgid "Comment: " msgstr "Комментировать:" #: ../giara/common_post_box.py:253 ../giara/flair_label.py:19 msgid "Message" msgstr "Сообщение" #: ../giara/common_post_box.py:259 ../giara/inbox_view.py:41 #: ../giara/post_details_view.py:97 msgid "Author unknown" msgstr "Автор неизвестен" #: ../giara/flair_label.py:11 msgid "Comment" msgstr "Комментарий" #: ../giara/flair_label.py:15 msgid "Post" msgstr "Пост" #: ../giara/flair_label.py:37 msgid "Image" msgstr "Изображение" #: ../giara/flair_label.py:43 msgid "Video" msgstr "Видео" #: ../giara/flair_label.py:49 msgid "Text" msgstr "Текст" #: ../giara/flair_label.py:55 msgid "Link" msgstr "Ссылка" #: ../giara/front_page_headerbar.py:60 #, python-brace-format msgid "{0} Karma" msgstr "{0} Карма" #: ../giara/front_page_headerbar.py:124 msgid "Do you want to log out? This will close the application." msgstr "" #: ../giara/inbox_view.py:54 ../giara/left_stack.py:44 #: ../giara/settings_window.py:284 msgid "New" msgstr "Новые" #: ../giara/inbox_view.py:120 ../giara/left_stack.py:192 msgid "Inbox" msgstr "Входящие" #: ../giara/left_stack.py:32 ../giara/settings_window.py:284 msgid "Best" msgstr "Лучшие" #: ../giara/left_stack.py:38 ../giara/settings_window.py:284 msgid "Hot" msgstr "Горячие" #: ../giara/left_stack.py:50 ../giara/settings_window.py:284 msgid "Top" msgstr "В топе" #: ../giara/left_stack.py:56 ../giara/settings_window.py:284 msgid "Rising" msgstr "Набирающие популярность" #: ../giara/left_stack.py:62 ../giara/settings_window.py:285 msgid "Controversial" msgstr "Обсуждаемые" #: ../giara/left_stack.py:98 msgid "Front page" msgstr "На главной странице" #: ../giara/left_stack.py:105 ../giara/left_stack.py:111 msgid "Saved posts" msgstr "Сохраненные посты" #: ../giara/left_stack.py:123 ../giara/left_stack.py:130 msgid "Profile" msgstr "Профиль" #: ../giara/left_stack.py:152 ../giara/search_view.py:57 #: ../giara/subreddits_list_view.py:136 msgid "Subreddits" msgstr "Подреддиты" #: ../giara/left_stack.py:176 msgid "Search" msgstr "Поиск" #: ../giara/left_stack.py:207 ../giara/multireddit_list_view.py:111 msgid "Multireddits" msgstr "Мультиреддиты" #: ../giara/left_stack.py:329 ../giara/subreddit_search_view.py:26 #, python-brace-format msgid "Searching in {0}" msgstr "Поиск в {0}" #: ../giara/markdown_view.py:31 msgid "Image: " msgstr "Изображение: " #: ../giara/markdown_view.py:31 msgid "[Image]" msgstr "[Изображение]" #: ../giara/multireddit_view.py:59 msgid "Remove from multireddit" msgstr "Удалить из мультиреддита" #: ../giara/multireddit_view.py:84 msgid "Edit multireddit" msgstr "Редактировать мультиреддит" #: ../giara/new_post_window.py:100 msgid "Choose an image or video to upload" msgstr "Выбирете изображение или видео для загрузки" #: ../giara/new_post_window.py:262 msgid "Select a subreddit…" msgstr "Выбрать подреддит..." #: ../giara/new_post_window.py:324 msgid "Select a flair…" msgstr "" #: ../giara/new_post_window.py:389 msgid "New comment" msgstr "Новый коментарий" #: ../giara/new_post_window.py:418 msgid "Editing" msgstr "Редактирование" #: ../giara/new_post_window.py:419 msgid "Edit" msgstr "Редактировать" #: ../giara/notification_manager.py:7 msgid "1 new message" msgstr "1 новое сообщение" #: ../giara/notification_manager.py:8 #, python-brace-format msgid "{0} new messages" msgstr "{0} новых сообщений" #: ../giara/notification_manager.py:40 #, python-brace-format msgid "{0} more" msgstr "{0} больше" #: ../giara/post_details_view.py:32 msgid "Load more comments" msgstr "Загрузить больше комментариев" #: ../giara/search_view.py:65 msgid "Users" msgstr "Пользователи" #: ../giara/settings_window.py:107 msgid "Choose a folder" msgstr "Выбрать папку" #: ../giara/settings_window.py:256 msgid "General" msgstr "Общий" #: ../giara/settings_window.py:260 msgid "General Settings" msgstr "Общие настройки" #: ../giara/settings_window.py:281 msgid "Default view" msgstr "Стандартный просмотр" #: ../giara/settings_window.py:294 msgid "Cache" msgstr "Кэш" #: ../giara/settings_window.py:307 msgid "Clear cache" msgstr "Очистить кэш" #: ../giara/settings_window.py:307 msgid "Clear" msgstr "Очистить" #: ../giara/settings_window.py:318 msgid "View" msgstr "Просмотр" #: ../giara/settings_window.py:322 msgid "View Settings" msgstr "Настройки просмотра" #: ../giara/settings_window.py:325 msgid "Dark mode" msgstr "Темный режим" #: ../giara/settings_window.py:330 msgid "Show thumbnails in post previews" msgstr "Показывать миниатюры в превью постов" #: ../giara/settings_window.py:354 msgid "Max thumbnail width" msgstr "Максимальная ширина миниатюры" #: ../giara/settings_window.py:358 msgid "Use 0 to remove the limit" msgstr "Используйте 0, чтобы убрать лимит" #: ../giara/settings_window.py:368 msgid "Privacy" msgstr "Конфиденциальность" #: ../giara/settings_window.py:372 msgid "Link replacement" msgstr "Замена ссылки" #: ../giara/settings_window.py:375 msgid "Replace Twitter links with Nitter" msgstr "Заменить ссылки Twitter на Nitter" #: ../giara/settings_window.py:380 msgid "Replace YouTube links with Invidious" msgstr "Заменить ссылки Youtube на Invidious" #: ../giara/settings_window.py:390 msgid "Invidious instance" msgstr "Пример Invidious" #: ../giara/settings_window.py:392 msgid "Naked domain name only, no https://" msgstr "Только имя домена, без https://" #: ../giara/subreddit_view.py:40 msgid "This subreddit is already part of this multireddit" msgstr "Этот субреддит уже является частью этого мультиреддита" #: ../giara/subreddit_view.py:211 msgid "More actions" msgstr "Больше действий" #: ../giara/subreddit_view.py:254 msgid "Leave" msgstr "Покинуть" #: ../giara/subreddit_view.py:257 msgid "Join" msgstr "Присоедениться" #: ../giara/subreddit_view.py:293 msgid "Description" msgstr "Описание" #: ../giara/user_heading.py:54 msgid "Unfollow" msgstr "Отписаться" #: ../giara/user_heading.py:57 msgid "Follow" msgstr "Подписаться" #: ../data/ui/comment_box.ui:77 msgid "Collapse replies" msgstr "Свернуть ответы" #: ../data/ui/comment_box.ui:157 ../data/ui/post_body.ui:141 #: ../data/ui/post_preview.ui:122 msgid "Upvote" msgstr "Проголосовать за" #: ../data/ui/comment_box.ui:177 ../data/ui/post_body.ui:177 #: ../data/ui/post_preview.ui:158 msgid "Downvote" msgstr "Проголосовать против" #: ../data/ui/comment_box.ui:206 ../data/ui/post_body.ui:163 #: ../data/ui/post_preview.ui:144 msgid "Ups" msgstr "Поднять" #: ../data/ui/comment_box.ui:226 ../data/ui/post_body.ui:341 msgid "Reply" msgstr "Ответить" #: ../data/ui/comment_box.ui:246 ../data/ui/post_body.ui:401 #: ../data/ui/post_preview.ui:384 msgid "Save" msgstr "Сохранить" #: ../data/ui/comment_box.ui:266 ../data/ui/post_body.ui:421 #: ../data/ui/post_preview.ui:404 msgid "Share" msgstr "Поделиться" #: ../data/ui/comment_box.ui:306 ../data/ui/post_body.ui:461 #: ../data/ui/post_preview.ui:424 msgid "Delete" msgstr "Удалить" #: ../data/ui/headerbar.ui:171 msgid "Saved" msgstr "Сохранен" #: ../data/ui/headerbar.ui:188 msgid "Log Out" msgstr "Выйти" #: ../data/ui/headerbar.ui:233 ../data/ui/new_post_window.ui:17 msgid "New post" msgstr "Новый пост" #: ../data/ui/headerbar.ui:272 msgid "Messages in your inbox" msgstr "Сообщения во входящих" #: ../data/ui/headerbar.ui:298 ../data/ui/post_details_headerbar.ui:29 #: ../data/ui/subreddit_view_headerbar.ui:43 msgid "Refresh" msgstr "Обновить" #: ../data/ui/headerbar.ui:341 msgid "Menu" msgstr "Меню" #: ../data/ui/headerbar_with_back_and_squeezer.ui:28 #: ../data/ui/post_details_headerbar.ui:14 #: ../data/ui/subreddit_more_actions.ui:74 #: ../data/ui/subreddit_more_actions.ui:171 #: ../data/ui/subreddit_view_headerbar.ui:28 msgid "Back" msgstr "Назад" #: ../data/ui/login_view.ui:83 msgid "Welcome to Giara" msgstr "Добро пожаловать в Giara" #: ../data/ui/login_view.ui:96 msgid "Login" msgstr "Войти" #: ../data/ui/login_view.ui:136 msgid "Waiting for authentication…" msgstr "Ждем аутентификации..." #: ../data/ui/main_ui.ui:70 msgid "Close" msgstr "Закрыть" #: ../data/ui/menu.ui:6 msgid "Preferences" msgstr "Параметры" #: ../data/ui/menu.ui:10 msgid "Keyboard Shortcuts" msgstr "Горячие клавиши" #: ../data/ui/menu.ui:14 msgid "About Giara" msgstr "О Giara" #: ../data/ui/new_post_menu.ui:14 msgid "Media" msgstr "Медиа" #: ../data/ui/new_post_window.ui:20 msgid "Cancel" msgstr "Отмена" #: ../data/ui/new_post_window.ui:28 msgid "Send" msgstr "Отправить" #: ../data/ui/new_post_window.ui:72 msgid "Post title…" msgstr "Заголовок поста..." #: ../data/ui/new_post_window.ui:127 msgid "Link…" msgstr "Ссылка..." #: ../data/ui/new_post_window.ui:145 msgid "Select media…" msgstr "Выбрать медиа..." #: ../data/ui/post_body.ui:218 ../data/ui/post_preview.ui:199 msgid "Pinned" msgstr "Закреплен" #: ../data/ui/post_body.ui:361 ../data/ui/post_preview.ui:344 msgid "Open media" msgstr "Открыть медиа" #: ../data/ui/post_body.ui:381 ../data/ui/post_preview.ui:364 msgid "Open link" msgstr "Открыть ссылку" #: ../data/ui/post_preview.ui:301 msgid "Comments" msgstr "Комментарии" #: ../data/ui/redditor_heading.ui:83 msgid "Karma" msgstr "Карма" #: ../data/ui/redditor_heading.ui:113 msgid "Cake day" msgstr "День рождения" #: ../data/ui/shortcutsWindow.ui:18 msgid "Open Keyboard Shortcuts" msgstr "Открыть горячие клавиши" #: ../data/ui/shortcutsWindow.ui:25 msgid "Open Menu" msgstr "Открыть меню" #: ../data/ui/shortcutsWindow.ui:32 msgid "Open Preferences" msgstr "Открыть параметры" #: ../data/ui/shortcutsWindow.ui:39 msgid "Quit" msgstr "Выйти" #: ../data/ui/subreddit_heading.ui:101 msgid "Members" msgstr "Подписчики" #: ../data/ui/subreddit_heading.ui:131 msgid "Since" msgstr "Со времени" #: ../data/ui/subreddit_more_actions.ui:32 #: ../data/ui/subreddit_more_actions.ui:93 msgid "Add to multireddit" msgstr "Добавить в мультиреддит" #: ../data/ui/subreddit_more_actions.ui:137 #: ../data/ui/subreddit_more_actions.ui:190 msgid "Create multireddit" msgstr "Создать мультиреддит" #: ../data/ui/subreddit_more_actions.ui:222 msgid "Multireddit name…" msgstr "Название мультиреддита..." #: ../data/ui/subreddit_more_actions.ui:232 msgid "Create" msgstr "Создать" #: ../data/org.gabmus.giara.desktop.in:4 #: ../data/org.gabmus.giara.service.desktop.in:4 msgid "@prettyname@" msgstr "@красивоеимя@" #: ../data/org.gabmus.giara.desktop.in:5 msgid "A GTK app for Reddit" msgstr "GTK клиент Reddit-а" #: ../data/org.gabmus.giara.desktop.in:14 msgid "reddit;" msgstr "reddit;" #: ../data/org.gabmus.giara.appdata.xml.in:4 msgid "Giara" msgstr "Giara" #: ../data/org.gabmus.giara.appdata.xml.in:5 msgid "Gabriele Musco" msgstr "Gabriele Musco" #: ../data/org.gabmus.giara.appdata.xml.in:15 msgid "Giara is a GTK app for Reddit" msgstr "Giara GTK клиент Reddit-а" #: ../data/org.gabmus.giara.appdata.xml.in:37 msgid "Improved markdown rendering" msgstr "Улучшенный markdown рендеринг" #: ../data/org.gabmus.giara.appdata.xml.in:38 msgid "Dark mode implemented" msgstr "Реализован темный режим" #: ../data/org.gabmus.giara.appdata.xml.in:39 msgid "Added option to disable images in post previews" msgstr "Добавлена опция отключения изображения в превью постов" #: ../data/org.gabmus.giara.appdata.xml.in:40 msgid "Added option to set a maximum size for images" msgstr "Добавлена возможность установить максимальный размер изображения" #: ../data/org.gabmus.giara.appdata.xml.in:41 msgid "Added option to clear cache" msgstr "Добавлена возможность очистки кэша" #: ../data/org.gabmus.giara.appdata.xml.in:42 msgid "Added Brazilian Portuguese translation" msgstr "Добавлен перевод на Бразильсий" #: ../data/org.gabmus.giara.appdata.xml.in:43 msgid "New post or comment window now remembers its size" msgstr "Новое окно поста или комментария теперь запоминает свой размер" #: ../data/org.gabmus.giara.appdata.xml.in:44 msgid "Subreddits can now be searched when creating new posts" msgstr "Субреддиты теперь можно искать при создании новых постов" #: ../data/org.gabmus.giara.appdata.xml.in:45 msgid "Initial support for multireddits" msgstr "Первоаначальная поддержка для мультиреддитов" #: ../data/org.gabmus.giara.appdata.xml.in:46 msgid "Added Croatian translation" msgstr "Добавлен перевод на Хорватский" #: ../data/org.gabmus.giara.appdata.xml.in:47 msgid "Added notifications for unread inbox items" msgstr "Добавлены уведомления о непрочитанных сообщениях" #: ../data/org.gabmus.giara.appdata.xml.in:48 msgid "Made comment border lines colorful" msgstr "Границы комментариев теперь разноцветные" #: ../data/org.gabmus.giara.appdata.xml.in:49 msgid "Authentication is now handled by your default browser" msgstr "Аутентификация теперь проходит в вашем браузере по умолчанию" #: ../data/org.gabmus.giara.appdata.xml.in:56 msgid "Improvements for flatpak packaging" msgstr "Улучшения для упаковки в flatpak" #: ../data/org.gabmus.giara.appdata.xml.in:63 msgid "First release" msgstr "Первый релиз" giara-0.3/po/sl.po000066400000000000000000000275521376474030500140570ustar00rootroot00000000000000# Slovenian translation for giara. # Copyright (C) 2020 giara's COPYRIGHT HOLDER # This file is distributed under the same license as the giara package. # Matej Urbančič , 2020. # msgid "" msgstr "" "Project-Id-Version: giara master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/giara/issues\n" "POT-Creation-Date: 2020-11-08 16:59+0000\n" "PO-Revision-Date: 2020-11-08 21:10+0100\n" "Last-Translator: Matej Urbančič \n" "Language-Team: Slovenian GNOME Translation Team \n" "Language: sl_SI\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=(n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || n" "%100==4 ? 3 : 0);\n" "X-Poedit-SourceCharset: utf-8\n" "X-Generator: Poedit 2.4.1\n" #: data/org.gabmus.giara.appdata.xml.in:4 msgid "Giara" msgstr "Giara" #: data/org.gabmus.giara.appdata.xml.in:5 msgid "Gabriele Musco" msgstr "Gabriele Musco" #: data/org.gabmus.giara.appdata.xml.in:6 data/org.gabmus.giara.desktop.in:5 #, fuzzy msgid "A GTK app for Reddit" msgstr "GTK" #: data/org.gabmus.giara.appdata.xml.in:15 msgid "Giara is a GTK app for Reddit" msgstr "" #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: data/org.gabmus.giara.desktop.in:14 msgid "reddit;" msgstr "reddit;" #: data/ui/comment_box.ui:77 #, fuzzy msgid "Collapse replies" msgstr "_Zloži" #: data/ui/comment_box.ui:157 data/ui/post_preview.ui:122 msgid "Upvote" msgstr "" #: data/ui/comment_box.ui:177 data/ui/post_preview.ui:158 msgid "Downvote" msgstr "" #: data/ui/comment_box.ui:206 data/ui/post_preview.ui:144 #, fuzzy msgid "Ups" msgstr "UPS" #: data/ui/comment_box.ui:226 msgid "Reply" msgstr "Odgovori" #: data/ui/comment_box.ui:246 data/ui/post_preview.ui:384 msgid "Save" msgstr "Shrani" #: data/ui/comment_box.ui:266 data/ui/post_preview.ui:404 msgid "Share" msgstr "Objavi" #: data/ui/comment_box.ui:286 giara/new_post_window.py:419 msgid "Edit" msgstr "Uredi" #: data/ui/comment_box.ui:306 data/ui/post_preview.ui:424 msgid "Delete" msgstr "Izbriši" #: data/ui/headerbar.ui:103 giara/inbox_view.py:134 giara/left_stack.py:192 msgid "Inbox" msgstr "Dohodni predal" #: data/ui/headerbar.ui:120 giara/left_stack.py:152 giara/search_view.py:57 msgid "Subreddits" msgstr "" #: data/ui/headerbar.ui:137 giara/left_stack.py:207 msgid "Multireddits" msgstr "" #: data/ui/headerbar.ui:154 data/ui/headerbar.ui:257 giara/left_stack.py:123 #: giara/left_stack.py:130 msgid "Profile" msgstr "Profil" #: data/ui/headerbar.ui:171 msgid "Saved" msgstr "Shranjeno" #: data/ui/headerbar.ui:188 msgid "Log Out" msgstr "Odjava" #: data/ui/headerbar.ui:233 data/ui/new_post_window.ui:17 msgid "New post" msgstr "Nova objava" #: data/ui/headerbar.ui:272 #, fuzzy msgid "Messages in your inbox" msgstr "Obvesti o novih sporočilih le v mapi Prejete pošte." #: data/ui/headerbar.ui:298 data/ui/post_details_headerbar.ui:29 #: data/ui/subreddit_view_headerbar.ui:43 msgid "Refresh" msgstr "Osveži" #: data/ui/headerbar.ui:316 data/ui/subreddit_view_headerbar.ui:67 #: giara/left_stack.py:176 msgid "Search" msgstr "Poišči" #: data/ui/headerbar.ui:341 msgid "Menu" msgstr "Meni" #: data/ui/headerbar_with_back_and_squeezer.ui:28 #: data/ui/post_details_headerbar.ui:14 data/ui/subreddit_more_actions.ui:74 #: data/ui/subreddit_more_actions.ui:171 data/ui/subreddit_view_headerbar.ui:28 msgid "Back" msgstr "Hrbtna stran" #: data/ui/login_view.ui:83 #, fuzzy msgid "Welcome to Giara" msgstr "Giara" #: data/ui/login_view.ui:96 msgid "Login" msgstr "Prijava" #: data/ui/login_view.ui:136 msgid "Waiting for authentication…" msgstr "Čakanje na overitev ..." #: data/ui/main_ui.ui:70 msgid "Close" msgstr "Zapri" #: data/ui/menu.ui:6 msgid "Preferences" msgstr "Možnosti" #: data/ui/menu.ui:10 msgid "Keyboard Shortcuts" msgstr "Tipkovne bližnjice" #: data/ui/menu.ui:14 msgid "About Giara" msgstr "O programu" #: data/ui/new_post_menu.ui:6 giara/flair_label.py:49 msgid "Text" msgstr "Besedilo" #: data/ui/new_post_menu.ui:10 giara/flair_label.py:55 msgid "Link" msgstr "Povezava" #: data/ui/new_post_menu.ui:14 msgid "Media" msgstr "Večpredstavna datoteka" #: data/ui/new_post_window.ui:20 msgid "Cancel" msgstr "Prekliči" #: data/ui/new_post_window.ui:28 msgid "Send" msgstr "Pošlji" #: data/ui/new_post_window.ui:72 #, fuzzy msgid "Post title…" msgstr "pošlji" #: data/ui/new_post_window.ui:127 msgid "Link…" msgstr "Povezava …" #: data/ui/new_post_window.ui:145 msgid "Select media…" msgstr "Izbor predstavne vsebine …" #: data/ui/post_preview.ui:199 msgid "Pinned" msgstr "Pripeto" #: data/ui/post_preview.ui:301 msgid "Comments" msgstr "Opombe" #: data/ui/post_preview.ui:344 #, fuzzy msgid "Open media" msgstr "Izbor predstavne vsebine" #: data/ui/post_preview.ui:364 msgid "Open link" msgstr "Odpri povezavo" #: data/ui/redditor_heading.ui:59 giara/user_heading.py:57 msgid "Follow" msgstr "Sledi" #: data/ui/redditor_heading.ui:83 msgid "Karma" msgstr "Najmanjša ocena karme za objavljanje mnenj" #: data/ui/redditor_heading.ui:113 #, fuzzy msgid "Cake day" msgstr "Roza torta" #: data/ui/shortcutsWindow.ui:13 giara/settings_window.py:256 msgid "General" msgstr "Splošno" #: data/ui/shortcutsWindow.ui:18 #, fuzzy msgid "Open Keyboard Shortcuts" msgstr "_Tipkovne bližnjice" #: data/ui/shortcutsWindow.ui:25 msgid "Open Menu" msgstr "Odpri meni" #: data/ui/shortcutsWindow.ui:32 #, fuzzy msgid "Open Preferences" msgstr "Odpre nastavitve" #: data/ui/shortcutsWindow.ui:39 msgid "Quit" msgstr "Končaj" #: data/ui/subreddit_heading.ui:77 giara/subreddit_view.py:257 msgid "Join" msgstr "Spoji" #: data/ui/subreddit_heading.ui:101 msgid "Members" msgstr "Člani" #: data/ui/subreddit_heading.ui:131 msgid "Since" msgstr "Od" #: data/ui/subreddit_more_actions.ui:32 data/ui/subreddit_more_actions.ui:93 #, fuzzy msgid "Add to multireddit" msgstr "_Dodaj …" #: data/ui/subreddit_more_actions.ui:137 data/ui/subreddit_more_actions.ui:190 #, fuzzy msgid "Create multireddit" msgstr "_Ustvari" #: data/ui/subreddit_more_actions.ui:222 #, fuzzy msgid "Multireddit name…" msgstr "" #: data/ui/subreddit_more_actions.ui:232 msgid "Create" msgstr "Ustvari" #: giara/auth.py:20 msgid "Error getting client with refresh token, retrying…" msgstr "" #: giara/auth.py:30 msgid "Error authorizing Reddit client, retrying…" msgstr "" #: giara/auth.py:35 msgid "Error authorizing Reddit client after retry, quitting…" msgstr "" #: giara/common_post_box.py:108 #, fuzzy msgid "Are you sure you want to delete this item?" msgstr "Ali ste prepričani, da želite izbrisati ta račun?" #: giara/common_post_box.py:141 #, fuzzy msgid "Link copied to clipboard" msgstr "Kopirano v odložišče" #: giara/common_post_box.py:244 msgid "Comment: " msgstr "Opomba:" #: giara/common_post_box.py:253 giara/flair_label.py:19 msgid "Message" msgstr "Sporočilo" #: giara/common_post_box.py:259 giara/inbox_view.py:41 #: giara/post_details_view.py:97 msgid "Author unknown" msgstr "Neznan avtor" #: giara/flair_label.py:11 msgid "Comment" msgstr "Opomba" #: giara/flair_label.py:15 msgid "Post" msgstr "Objavi" #: giara/flair_label.py:37 msgid "Image" msgstr "Slika" #: giara/flair_label.py:43 msgid "Video" msgstr "Posnetek" #: giara/front_page_headerbar.py:61 #, fuzzy, python-brace-format msgid "{0} Karma" msgid_plural "{0} Karma" msgstr[0] "Podpora Karma" msgstr[1] "" msgstr[2] "" msgstr[3] "" #: giara/front_page_headerbar.py:126 msgid "Do you want to log out? This will close the application." msgstr "" #: giara/inbox_view.py:52 #, fuzzy, python-brace-format msgid "Comment in \"{0}\"" msgstr "Opomba" #: giara/inbox_view.py:68 giara/left_stack.py:44 giara/settings_window.py:284 msgid "New" msgstr "Novo" #: giara/left_stack.py:32 giara/settings_window.py:284 msgid "Best" msgstr "Najboljše" #: giara/left_stack.py:38 giara/settings_window.py:284 #, fuzzy msgid "Hot" msgstr "Vroči stiki" #: giara/left_stack.py:50 giara/settings_window.py:284 msgid "Top" msgstr "Vrhnji" #: giara/left_stack.py:56 giara/settings_window.py:284 msgid "Rising" msgstr "" #: giara/left_stack.py:62 giara/settings_window.py:285 msgid "Controversial" msgstr "" #: giara/left_stack.py:98 #, fuzzy msgid "Front page" msgstr "Spredaj" #: giara/left_stack.py:105 giara/left_stack.py:111 msgid "Saved posts" msgstr "Shranjene objave" #: giara/left_stack.py:329 #, fuzzy, python-brace-format msgid "Searching in {0}" msgstr "Poteka iskanje" #: giara/markdown_view.py:31 msgid "Image: " msgstr "Slika:" #: giara/markdown_view.py:31 msgid "[Image]" msgstr "[Slika]" #: giara/multireddit_view.py:59 #, fuzzy msgid "Remove from multireddit" msgstr "Odstrani iz knjižnice" #: giara/multireddit_view.py:84 #, fuzzy msgid "Edit multireddit" msgstr "&&Uredi" #: giara/new_post_window.py:100 #, fuzzy msgid "Choose an image or video to upload" msgstr "Izbor slike" #: giara/new_post_window.py:212 giara/new_post_window.py:213 #: giara/new_post_window.py:219 msgid "None" msgstr "Brez" #: giara/new_post_window.py:262 #, fuzzy msgid "Select a subreddit…" msgstr "Izb_eri ..." #: giara/new_post_window.py:324 #, fuzzy msgid "Select a flair…" msgstr "Izb_eri ..." #: giara/new_post_window.py:389 msgid "New comment" msgstr "Nova opomba" #: giara/new_post_window.py:418 msgid "Editing" msgstr "Urejanje" #: giara/notification_manager.py:31 #, fuzzy, python-brace-format msgid "{0} new message" msgid_plural "{0} new messages" msgstr[0] "Novo _sporočilo" msgstr[1] "" msgstr[2] "" msgstr[3] "" #: giara/notification_manager.py:36 #, fuzzy, python-brace-format msgid "{0} more" msgid_plural "{0} more" msgstr[0] "Več …" msgstr[1] "" msgstr[2] "" msgstr[3] "" #: giara/post_details_view.py:32 #, fuzzy msgid "Load more comments" msgstr "Naloži več" #: giara/search_view.py:65 msgid "Users" msgstr "Uporabniki" #: giara/settings_window.py:107 msgid "Choose a folder" msgstr "Izbor mape" #: giara/settings_window.py:260 #, fuzzy msgid "General Settings" msgstr "Splošne nastavitve" #: giara/settings_window.py:281 msgid "Default view" msgstr "Privzeti pogled" #: giara/settings_window.py:294 msgid "Cache" msgstr "Predpomnilnik" #: giara/settings_window.py:307 msgid "Clear cache" msgstr "Počisti predpomnilnik" #: giara/settings_window.py:307 msgid "Clear" msgstr "Počisti" #: giara/settings_window.py:318 msgid "View" msgstr "Pogled" #: giara/settings_window.py:322 #, fuzzy msgid "View Settings" msgstr "Nastavitve ogledovanja virov" #: giara/settings_window.py:325 msgid "Dark mode" msgstr "Temna tema" #: giara/settings_window.py:330 #, fuzzy msgid "Show thumbnails in post previews" msgstr "Kdaj naj bodo prikazane sličice predstavnih datotek" #: giara/settings_window.py:354 #, fuzzy msgid "Max thumbnail width" msgstr "Največja dovoljena širina predstavne vsebine" #: giara/settings_window.py:358 #, fuzzy msgid "Use 0 to remove the limit" msgstr "spodnja meja: spodnja limita integrala, privzeto določena pri 0." #: giara/settings_window.py:368 msgid "Privacy" msgstr "Zasebnost" #: giara/settings_window.py:372 #, fuzzy msgid "Link replacement" msgstr "Zamenjava" #: giara/settings_window.py:375 #, fuzzy msgid "Replace Twitter links with Nitter" msgstr "Prepoznaj povezave v besedilu in jih zamenjaj." #: giara/settings_window.py:380 #, fuzzy msgid "Replace YouTube links with Invidious" msgstr "Prepoznaj povezave v besedilu in jih zamenjaj." #: giara/settings_window.py:390 #, fuzzy msgid "Invidious instance" msgstr "Primerek" #: giara/settings_window.py:392 msgid "Naked domain name only, no https://" msgstr "" #: giara/subreddit_view.py:40 msgid "This subreddit is already part of this multireddit" msgstr "" #: giara/subreddit_view.py:211 msgid "More actions" msgstr "Več dejanj" #: giara/subreddit_view.py:254 msgid "Leave" msgstr "Zapusti" #: giara/subreddit_view.py:293 msgid "Description" msgstr "Opis" #: giara/user_heading.py:54 msgid "Unfollow" msgstr "Prekliči sledenje" giara-0.3/po/sv.po000066400000000000000000000320011376474030500140520ustar00rootroot00000000000000# Swedish translation for giara. # Copyright © 2020 giara's COPYRIGHT HOLDER # This file is distributed under the same license as the giara package. # Anders Jonsson , 2020. # msgid "" msgstr "" "Project-Id-Version: giara master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/giara/issues\n" "POT-Creation-Date: 2020-11-09 17:34+0000\n" "PO-Revision-Date: 2020-11-10 18:45+0100\n" "Language-Team: Swedish \n" "Language: sv\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "Last-Translator: Anders Jonsson \n" "X-Generator: Poedit 2.4.1\n" #: data/org.gabmus.giara.appdata.xml.in:4 msgid "Giara" msgstr "Giara" #: data/org.gabmus.giara.appdata.xml.in:5 msgid "Gabriele Musco" msgstr "Gabriele Musco" #: data/org.gabmus.giara.appdata.xml.in:6 data/org.gabmus.giara.desktop.in:5 msgid "An app for Reddit" msgstr "Ett program för Reddit" #: data/org.gabmus.giara.appdata.xml.in:15 msgid "An app for Reddit." msgstr "Ett program för Reddit." #: data/org.gabmus.giara.appdata.xml.in:16 msgid "Browse Reddit from your Linux desktop or smartphone." msgstr "Surfa på Reddit från ditt Linuxskrivbord eller din smartmobil." #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: data/org.gabmus.giara.desktop.in:14 msgid "reddit;" msgstr "reddit;" #: data/ui/comment_box.ui:77 msgid "Collapse replies" msgstr "Fäll ihop svar" #: data/ui/comment_box.ui:157 data/ui/post_body.ui:141 #: data/ui/post_preview.ui:122 msgid "Upvote" msgstr "Rösta upp" #: data/ui/comment_box.ui:177 data/ui/post_body.ui:177 #: data/ui/post_preview.ui:158 msgid "Downvote" msgstr "Rösta ner" #: data/ui/comment_box.ui:206 data/ui/post_body.ui:163 #: data/ui/post_preview.ui:144 msgid "Ups" msgstr "Uppröster" #: data/ui/comment_box.ui:226 data/ui/post_body.ui:341 msgid "Reply" msgstr "Svara" #: data/ui/comment_box.ui:246 data/ui/post_body.ui:401 #: data/ui/post_preview.ui:384 msgid "Save" msgstr "Spara" #: data/ui/comment_box.ui:266 data/ui/post_body.ui:421 #: data/ui/post_preview.ui:404 msgid "Share" msgstr "Dela" #: data/ui/comment_box.ui:286 data/ui/post_body.ui:441 #: giara/new_post_window.py:419 msgid "Edit" msgstr "Redigera" #: data/ui/comment_box.ui:306 data/ui/post_body.ui:461 #: data/ui/post_preview.ui:424 msgid "Delete" msgstr "Ta bort" #: data/ui/headerbar.ui:103 giara/inbox_view.py:134 giara/left_stack.py:159 msgid "Inbox" msgstr "Inkorg" #: data/ui/headerbar.ui:120 giara/left_stack.py:119 giara/search_view.py:57 #: giara/subreddits_list_view.py:136 msgid "Subreddits" msgstr "Subredditar" #: data/ui/headerbar.ui:137 giara/left_stack.py:174 #: giara/multireddit_list_view.py:111 msgid "Multireddits" msgstr "Multiredditar" #: data/ui/headerbar.ui:154 data/ui/headerbar.ui:254 giara/left_stack.py:89 #: giara/left_stack.py:97 msgid "Profile" msgstr "Profil" #: data/ui/headerbar.ui:171 msgid "Saved" msgstr "Sparat" #: data/ui/headerbar.ui:188 msgid "Log Out" msgstr "Logga ut" #: data/ui/headerbar.ui:233 data/ui/new_post_window.ui:17 msgid "New post" msgstr "Nytt inlägg" #: data/ui/headerbar.ui:272 msgid "Messages in your inbox" msgstr "Meddelanden i din inkorg" #: data/ui/headerbar.ui:298 data/ui/post_details_headerbar.ui:29 #: data/ui/subreddit_view_headerbar.ui:43 msgid "Refresh" msgstr "Uppdatera" #: data/ui/headerbar.ui:322 msgid "Menu" msgstr "Meny" #: data/ui/headerbar.ui:349 data/ui/subreddit_view_headerbar.ui:67 #: giara/left_stack.py:143 msgid "Search" msgstr "Sök" #: data/ui/new_post_window.ui:20 msgid "Cancel" msgstr "Avbryt" #: data/ui/new_post_window.ui:28 msgid "Send" msgstr "Skicka" #: data/ui/new_post_window.ui:72 msgid "Post title…" msgstr "Titel för inlägg…" #: data/ui/new_post_window.ui:127 msgid "Link…" msgstr "Länk…" #: data/ui/new_post_window.ui:145 msgid "Select media…" msgstr "Välj media…" #: data/ui/post_details_headerbar.ui:14 #: data/ui/headerbar_with_back_and_squeezer.ui:28 #: data/ui/subreddit_more_actions.ui:74 data/ui/subreddit_more_actions.ui:171 #: data/ui/subreddit_view_headerbar.ui:28 msgid "Back" msgstr "Bakåt" #: data/ui/login_view.ui:83 msgid "Welcome to Giara" msgstr "Välkommen till Giara" #: data/ui/login_view.ui:96 msgid "Login" msgstr "Logga in" #: data/ui/login_view.ui:136 msgid "Waiting for authentication…" msgstr "Väntar på autentisering…" #: data/ui/main_ui.ui:70 msgid "Close" msgstr "Stäng" #: data/ui/menu.ui:6 msgid "Preferences" msgstr "Inställningar" #: data/ui/menu.ui:10 msgid "Keyboard Shortcuts" msgstr "Tangentbordsgenvägar" #: data/ui/menu.ui:14 msgid "About Giara" msgstr "Om Giara" #: data/ui/new_post_menu.ui:6 giara/flair_label.py:49 msgid "Text" msgstr "Text" #: data/ui/new_post_menu.ui:10 giara/flair_label.py:55 msgid "Link" msgstr "Länk" #: data/ui/new_post_menu.ui:14 msgid "Media" msgstr "Media" #: data/ui/post_body.ui:218 data/ui/post_preview.ui:199 msgid "Pinned" msgstr "Nålat" #: data/ui/post_body.ui:361 data/ui/post_preview.ui:344 msgid "Open media" msgstr "Öppna media" #: data/ui/post_body.ui:381 data/ui/post_preview.ui:364 msgid "Open link" msgstr "Öppna länk" #: data/ui/post_preview.ui:301 msgid "Comments" msgstr "Kommentarer" #: data/ui/redditor_heading.ui:59 giara/user_heading.py:57 msgid "Follow" msgstr "Följ" #: data/ui/redditor_heading.ui:83 msgid "Karma" msgstr "Karma" #: data/ui/redditor_heading.ui:113 msgid "Cake day" msgstr "Tårtdag" #: data/ui/shortcutsWindow.ui:13 giara/settings_window.py:256 msgid "General" msgstr "Allmänt" #: data/ui/shortcutsWindow.ui:18 msgid "Open Keyboard Shortcuts" msgstr "Öppna tangentbordsgenvägar" #: data/ui/shortcutsWindow.ui:25 msgid "Open Menu" msgstr "Öppna meny" #: data/ui/shortcutsWindow.ui:32 msgid "Open Preferences" msgstr "Öppna inställningar" #: data/ui/shortcutsWindow.ui:39 msgid "Quit" msgstr "Avsluta" #: data/ui/subreddit_heading.ui:77 giara/subreddit_view.py:281 msgid "Join" msgstr "Gå med" #: data/ui/subreddit_heading.ui:101 msgid "Members" msgstr "Medlemmar" #: data/ui/subreddit_heading.ui:131 msgid "Since" msgstr "Sedan" #: data/ui/subreddit_more_actions.ui:32 data/ui/subreddit_more_actions.ui:93 msgid "Add to multireddit" msgstr "Lägg till i multireddit" #: data/ui/subreddit_more_actions.ui:137 data/ui/subreddit_more_actions.ui:190 msgid "Create multireddit" msgstr "Skapa multireddit" #: data/ui/subreddit_more_actions.ui:222 msgid "Multireddit name…" msgstr "Namn på multireddit…" #: data/ui/subreddit_more_actions.ui:232 msgid "Create" msgstr "Skapa" #: giara/auth.py:20 msgid "Error getting client with refresh token, retrying…" msgstr "Fel vid erhållande av klient med uppdateringstoken, försöker igen…" #: giara/auth.py:30 msgid "Error authorizing Reddit client, retrying…" msgstr "Fel vid autentisering av Reddit-klient, försöker igen…" #: giara/auth.py:35 msgid "Error authorizing Reddit client after retry, quitting…" msgstr "Fel vid autentisering av Reddit-klient efter omförsök, avslutar…" #: giara/choice_picker.py:51 giara/choice_picker.py:63 #: giara/new_post_window.py:212 giara/new_post_window.py:213 #: giara/new_post_window.py:219 msgid "None" msgstr "Ingen" #: giara/common_post_box.py:108 msgid "Are you sure you want to delete this item?" msgstr "Är du säker på att du vill ta bort detta objekt?" #: giara/common_post_box.py:141 msgid "Link copied to clipboard" msgstr "Kopierade länk till urklipp" #: giara/common_post_box.py:244 msgid "Comment: " msgstr "Kommentar: " #: giara/common_post_box.py:253 giara/flair_label.py:19 msgid "Message" msgstr "Meddelande" #: giara/common_post_box.py:259 giara/inbox_view.py:41 #: giara/post_details_view.py:97 msgid "Author unknown" msgstr "Författare okänd" #: giara/flair_label.py:11 msgid "Comment" msgstr "Kommentar" #: giara/flair_label.py:15 msgid "Post" msgstr "Inlägg" #: giara/flair_label.py:37 msgid "Image" msgstr "Bild" #: giara/flair_label.py:43 msgid "Video" msgstr "Video" #: giara/front_page_headerbar.py:22 giara/settings_window.py:284 msgid "Best" msgstr "Bästa" #: giara/front_page_headerbar.py:26 giara/settings_window.py:284 #: giara/single_post_stream_headerbar.py:19 giara/subreddit_view.py:202 msgid "Hot" msgstr "Heta" #: giara/front_page_headerbar.py:30 giara/settings_window.py:284 #: giara/inbox_view.py:68 giara/single_post_stream_headerbar.py:23 #: giara/subreddit_view.py:206 msgid "New" msgstr "Nya" #: giara/front_page_headerbar.py:34 giara/settings_window.py:284 #: giara/single_post_stream_headerbar.py:27 giara/subreddit_view.py:210 msgid "Top" msgstr "Högsta poäng" #: giara/front_page_headerbar.py:38 giara/settings_window.py:284 #: giara/single_post_stream_headerbar.py:31 giara/subreddit_view.py:214 msgid "Rising" msgstr "Stigande" #: giara/front_page_headerbar.py:42 giara/settings_window.py:285 #: giara/single_post_stream_headerbar.py:35 giara/subreddit_view.py:218 msgid "Controversial" msgstr "Kontroversiella" #: giara/front_page_headerbar.py:88 #, python-brace-format msgid "{0} Karma" msgid_plural "{0} Karma" msgstr[0] "{0} karma" msgstr[1] "{0} karma" #: giara/front_page_headerbar.py:153 msgid "Do you want to log out? This will close the application." msgstr "Vill du logga ut? Detta kommer stänga programmet." #: giara/settings_window.py:107 msgid "Choose a folder" msgstr "Välj en mapp" #: giara/settings_window.py:260 msgid "General Settings" msgstr "Allmänna inställningar" #: giara/settings_window.py:281 msgid "Default view" msgstr "Standardvy" #: giara/settings_window.py:294 msgid "Cache" msgstr "Cache" #: giara/settings_window.py:307 msgid "Clear cache" msgstr "Töm cache" #: giara/settings_window.py:307 msgid "Clear" msgstr "Töm" #: giara/settings_window.py:318 msgid "View" msgstr "Visa" #: giara/settings_window.py:322 msgid "View Settings" msgstr "Visningsinställningar" #: giara/settings_window.py:325 msgid "Dark mode" msgstr "Mörkt läge" #: giara/settings_window.py:330 msgid "Show thumbnails in post previews" msgstr "Visa miniatyrbilder i förhandsvisning av inlägg" #: giara/settings_window.py:354 msgid "Max thumbnail width" msgstr "Maximal bredd för miniatyrbilder" #: giara/settings_window.py:358 msgid "Use 0 to remove the limit" msgstr "Använd 0 för att ta bort gränsen" #: giara/settings_window.py:368 msgid "Privacy" msgstr "Sekretess" #: giara/settings_window.py:372 msgid "Link replacement" msgstr "Länkersättning" #: giara/settings_window.py:375 msgid "Replace Twitter links with Nitter" msgstr "Ersätt Twitter-länkar med Nitter" #: giara/settings_window.py:380 msgid "Replace YouTube links with Invidious" msgstr "Ersätt YouTube-länkar med Invidious" #: giara/settings_window.py:390 msgid "Invidious instance" msgstr "Invidious-instans" #: giara/settings_window.py:392 msgid "Naked domain name only, no https://" msgstr "Endast naket domännamn, inget https://" #: giara/inbox_view.py:52 #, python-brace-format msgid "Comment in \"{0}\"" msgstr "Kommentar i ”{0}”" #: giara/left_stack.py:33 msgid "Posts" msgstr "Inlägg" #: giara/left_stack.py:63 msgid "Front page" msgstr "Förstasida" #: giara/left_stack.py:70 giara/left_stack.py:77 msgid "Saved posts" msgstr "Sparade inlägg" #: giara/left_stack.py:299 giara/subreddit_search_view.py:26 #, python-brace-format msgid "Searching in {0}" msgstr "Söker i {0}" #: giara/post_details_view.py:32 msgid "Load more comments" msgstr "Läs in fler kommentarer" #: giara/markdown_view.py:42 msgid "Image: " msgstr "Bild: " #: giara/markdown_view.py:42 msgid "[Image]" msgstr "[Bild]" #: giara/multireddit_view.py:59 msgid "Remove from multireddit" msgstr "Ta bort från multireddit" #: giara/multireddit_view.py:85 msgid "Edit multireddit" msgstr "Redigera multireddit" #: giara/new_post_window.py:100 msgid "Choose an image or video to upload" msgstr "Välj en bild eller video att skicka" #: giara/new_post_window.py:262 msgid "Select a subreddit…" msgstr "Välj en subreddit…" #: giara/new_post_window.py:324 msgid "Select a flair…" msgstr "Välj en stil…" #: giara/new_post_window.py:389 msgid "New comment" msgstr "Ny kommentar" #: giara/new_post_window.py:418 msgid "Editing" msgstr "Redigerar" #: giara/notification_manager.py:31 #, python-brace-format msgid "{0} new message" msgid_plural "{0} new messages" msgstr[0] "{0} nytt meddelande" msgstr[1] "{0} nya meddelanden" #: giara/notification_manager.py:36 #, python-brace-format msgid "{0} more" msgid_plural "{0} more" msgstr[0] "{0} till" msgstr[1] "{0} till" #: giara/search_view.py:65 msgid "Users" msgstr "Användare" #: giara/subreddit_view.py:40 msgid "This subreddit is already part of this multireddit" msgstr "Denna subreddit är redan en del av denna multireddit" #: giara/subreddit_view.py:235 msgid "More actions" msgstr "Fler åtgärder" #: giara/subreddit_view.py:278 msgid "Leave" msgstr "Lämna" #: giara/subreddit_view.py:317 msgid "Description" msgstr "Beskrivning" #: giara/sort_menu_btn.py:73 #, python-brace-format msgid "Sort by: {0}" msgstr "Sortera efter: {0}" #: giara/user_heading.py:54 msgid "Unfollow" msgstr "Sluta följa" giara-0.3/po/uk.po000066400000000000000000000354441376474030500140570ustar00rootroot00000000000000# Ukrainian translation for giara. # Copyright (C) 2020 giara's COPYRIGHT HOLDER # This file is distributed under the same license as the giara package. # # Yuri Chornoivan , 2020. msgid "" msgstr "" "Project-Id-Version: giara master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/giara/issues\n" "POT-Creation-Date: 2020-11-09 10:57+0000\n" "PO-Revision-Date: 2020-11-09 13:56+0200\n" "Last-Translator: Yuri Chornoivan \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" "Plural-Forms: nplurals=4; plural=n==1 ? 3 : n%10==1 && n%100!=11 ? 0 : n" "%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" "X-Generator: Lokalize 20.11.70\n" #: data/org.gabmus.giara.appdata.xml.in:4 msgid "Giara" msgstr "Giara" #: data/org.gabmus.giara.appdata.xml.in:5 msgid "Gabriele Musco" msgstr "Gabriele Musco" #: data/org.gabmus.giara.appdata.xml.in:6 data/org.gabmus.giara.desktop.in:5 #| msgid "A GTK app for Reddit" msgid "An app for Reddit" msgstr "Програма для Reddit" #: data/org.gabmus.giara.appdata.xml.in:15 #| msgid "A GTK app for Reddit" msgid "An app for Reddit." msgstr "Програма для Reddit." #: data/org.gabmus.giara.appdata.xml.in:16 msgid "Browse Reddit from your Linux desktop or smartphone." msgstr "Навігація Reddit з вашого робочого комп'ютера із Linux або смартфона." #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: data/org.gabmus.giara.desktop.in:14 msgid "reddit;" msgstr "reddit;реддіт;" #: data/ui/comment_box.ui:77 msgid "Collapse replies" msgstr "Згорнути відповіді" #: data/ui/comment_box.ui:157 data/ui/post_preview.ui:122 msgid "Upvote" msgstr "Проголосувати за" #: data/ui/comment_box.ui:177 data/ui/post_preview.ui:158 msgid "Downvote" msgstr "Проголосувати проти" #: data/ui/comment_box.ui:206 data/ui/post_preview.ui:144 msgid "Ups" msgstr "Апи" #: data/ui/comment_box.ui:226 msgid "Reply" msgstr "Відповісти" #: data/ui/comment_box.ui:246 data/ui/post_preview.ui:384 msgid "Save" msgstr "Зберегти" #: data/ui/comment_box.ui:266 data/ui/post_preview.ui:404 msgid "Share" msgstr "Оприлюднити" #: data/ui/comment_box.ui:286 giara/new_post_window.py:419 msgid "Edit" msgstr "Змінити" #: data/ui/comment_box.ui:306 data/ui/post_preview.ui:424 msgid "Delete" msgstr "Вилучити" #: data/ui/headerbar.ui:103 giara/inbox_view.py:134 giara/left_stack.py:159 msgid "Inbox" msgstr "Вхідні" #: data/ui/headerbar.ui:120 giara/left_stack.py:119 giara/search_view.py:57 msgid "Subreddits" msgstr "Сабреддіти" #: data/ui/headerbar.ui:137 giara/left_stack.py:174 msgid "Multireddits" msgstr "Мультиреддіти" #: data/ui/headerbar.ui:154 data/ui/headerbar.ui:254 giara/left_stack.py:89 #: giara/left_stack.py:97 msgid "Profile" msgstr "Профіль" #: data/ui/headerbar.ui:171 msgid "Saved" msgstr "Збережено" #: data/ui/headerbar.ui:188 msgid "Log Out" msgstr "Вийти" #: data/ui/headerbar.ui:233 data/ui/new_post_window.ui:17 msgid "New post" msgstr "Новий допис" #: data/ui/headerbar.ui:272 msgid "Messages in your inbox" msgstr "Повідомлення у вашій теці «вхідні»" #: data/ui/headerbar.ui:298 data/ui/post_details_headerbar.ui:29 #: data/ui/subreddit_view_headerbar.ui:43 msgid "Refresh" msgstr "Оновити" #: data/ui/headerbar.ui:322 msgid "Menu" msgstr "Меню" #: data/ui/headerbar.ui:349 data/ui/subreddit_view_headerbar.ui:67 #: giara/left_stack.py:143 msgid "Search" msgstr "Шукати" #: data/ui/headerbar_with_back_and_squeezer.ui:28 #: data/ui/post_details_headerbar.ui:14 data/ui/subreddit_more_actions.ui:74 #: data/ui/subreddit_more_actions.ui:171 data/ui/subreddit_view_headerbar.ui:28 msgid "Back" msgstr "Назад" #: data/ui/login_view.ui:83 msgid "Welcome to Giara" msgstr "Вітаємо у Giara" #: data/ui/login_view.ui:96 msgid "Login" msgstr "Увійти" #: data/ui/login_view.ui:136 msgid "Waiting for authentication…" msgstr "Очікування на завершення розпізнавання…" #: data/ui/main_ui.ui:70 msgid "Close" msgstr "Закрити" #: data/ui/menu.ui:6 msgid "Preferences" msgstr "Параметри" #: data/ui/menu.ui:10 msgid "Keyboard Shortcuts" msgstr "Клавіатурні скорочення" #: data/ui/menu.ui:14 msgid "About Giara" msgstr "Про Giara" #: data/ui/new_post_menu.ui:6 giara/flair_label.py:49 msgid "Text" msgstr "Текст" #: data/ui/new_post_menu.ui:10 giara/flair_label.py:55 msgid "Link" msgstr "Посилання" #: data/ui/new_post_menu.ui:14 msgid "Media" msgstr "Мультимедіа" #: data/ui/new_post_window.ui:20 msgid "Cancel" msgstr "Скасувати" #: data/ui/new_post_window.ui:28 msgid "Send" msgstr "Надіслати" #: data/ui/new_post_window.ui:72 msgid "Post title…" msgstr "Заголовок допису…" #: data/ui/new_post_window.ui:127 msgid "Link…" msgstr "Посилання…" #: data/ui/new_post_window.ui:145 msgid "Select media…" msgstr "Виберіть мультимедійні дані…" #: data/ui/post_preview.ui:199 msgid "Pinned" msgstr "Пришпилене" #: data/ui/post_preview.ui:301 msgid "Comments" msgstr "Коментарі" #: data/ui/post_preview.ui:344 msgid "Open media" msgstr "Відкрити мультимедійні дані" #: data/ui/post_preview.ui:364 msgid "Open link" msgstr "Відкрити посилання" #: data/ui/redditor_heading.ui:59 giara/user_heading.py:57 msgid "Follow" msgstr "Перейти" #: data/ui/redditor_heading.ui:83 msgid "Karma" msgstr "Карма" #: data/ui/redditor_heading.ui:113 msgid "Cake day" msgstr "День тортів" #: data/ui/shortcutsWindow.ui:13 giara/settings_window.py:256 msgid "General" msgstr "Загальне" #: data/ui/shortcutsWindow.ui:18 msgid "Open Keyboard Shortcuts" msgstr "Відкрити вікно клавіатурних скорочень" #: data/ui/shortcutsWindow.ui:25 msgid "Open Menu" msgstr "Відкрити меню" #: data/ui/shortcutsWindow.ui:32 msgid "Open Preferences" msgstr "Відкрити налаштування" #: data/ui/shortcutsWindow.ui:39 msgid "Quit" msgstr "Вийти" #: data/ui/subreddit_heading.ui:77 giara/subreddit_view.py:281 msgid "Join" msgstr "Приєднатися" #: data/ui/subreddit_heading.ui:101 msgid "Members" msgstr "Учасники" #: data/ui/subreddit_heading.ui:131 msgid "Since" msgstr "З" #: data/ui/subreddit_more_actions.ui:32 data/ui/subreddit_more_actions.ui:93 msgid "Add to multireddit" msgstr "Додати до multireddit" #: data/ui/subreddit_more_actions.ui:137 data/ui/subreddit_more_actions.ui:190 msgid "Create multireddit" msgstr "Створити multireddit" #: data/ui/subreddit_more_actions.ui:222 msgid "Multireddit name…" msgstr "Назва multireddit…" #: data/ui/subreddit_more_actions.ui:232 msgid "Create" msgstr "Створити" #: giara/auth.py:20 msgid "Error getting client with refresh token, retrying…" msgstr "" "Помилка під час спроби отримання жетона оновлення для клієнта, повторюємо " "спробу…" #: giara/auth.py:30 msgid "Error authorizing Reddit client, retrying…" msgstr "Помилка уповноваження клієнта Reddit, повторюємо спробу…" #: giara/auth.py:35 msgid "Error authorizing Reddit client after retry, quitting…" msgstr "Помилка уповноваження клієнта Reddit після повторної спроби, виходимо…" #: giara/common_post_box.py:108 msgid "Are you sure you want to delete this item?" msgstr "Ви впевнені, що хочете вилучити цей %1 запис?" #: giara/common_post_box.py:141 msgid "Link copied to clipboard" msgstr "Посилання скопійовано до буфера обміну" #: giara/common_post_box.py:244 msgid "Comment: " msgstr "Коментар: " #: giara/common_post_box.py:253 giara/flair_label.py:19 msgid "Message" msgstr "Повідомлення" #: giara/common_post_box.py:259 giara/inbox_view.py:41 #: giara/post_details_view.py:97 msgid "Author unknown" msgstr "Невідомий автор" #: giara/flair_label.py:11 msgid "Comment" msgstr "Коментар" #: giara/flair_label.py:15 msgid "Post" msgstr "Допис" #: giara/flair_label.py:37 msgid "Image" msgstr "Зображення" #: giara/flair_label.py:43 msgid "Video" msgstr "Відео" #: giara/front_page_headerbar.py:22 giara/settings_window.py:284 msgid "Best" msgstr "Найкращі" #: giara/front_page_headerbar.py:26 giara/settings_window.py:284 #: giara/subreddit_view.py:202 msgid "Hot" msgstr "Гарячі" #: giara/front_page_headerbar.py:30 giara/inbox_view.py:68 #: giara/settings_window.py:284 giara/subreddit_view.py:206 msgid "New" msgstr "Нові" #: giara/front_page_headerbar.py:34 giara/settings_window.py:284 #: giara/subreddit_view.py:210 msgid "Top" msgstr "Найкращі" #: giara/front_page_headerbar.py:38 giara/settings_window.py:284 #: giara/subreddit_view.py:214 msgid "Rising" msgstr "У тренді" #: giara/front_page_headerbar.py:42 giara/settings_window.py:285 #: giara/subreddit_view.py:218 msgid "Controversial" msgstr "Дискусійні" #: giara/front_page_headerbar.py:88 #, python-brace-format msgid "{0} Karma" msgid_plural "{0} Karma" msgstr[0] "Карма {0}" msgstr[1] "Карма {0}" msgstr[2] "Карма {0}" msgstr[3] "Карма {0}" #: giara/front_page_headerbar.py:153 msgid "Do you want to log out? This will close the application." msgstr "" "Хочете вийти з облікового запису? У результаті роботу програми буде " "завершено." #: giara/inbox_view.py:52 #, python-brace-format msgid "Comment in \"{0}\"" msgstr "Коментар у «{0}»" #: giara/left_stack.py:33 msgid "Posts" msgstr "Дописи" #: giara/left_stack.py:63 msgid "Front page" msgstr "Початкова сторінка" #: giara/left_stack.py:70 giara/left_stack.py:77 msgid "Saved posts" msgstr "Збережені дописи" #: giara/left_stack.py:299 #, python-brace-format msgid "Searching in {0}" msgstr "Шукаємо у {0}" #: giara/markdown_view.py:42 msgid "Image: " msgstr "Зображення: " #: giara/markdown_view.py:42 msgid "[Image]" msgstr "[Зображення]" #: giara/multireddit_view.py:59 msgid "Remove from multireddit" msgstr "Вилучити з multireddit" #: giara/multireddit_view.py:85 msgid "Edit multireddit" msgstr "Редагувати мультиреддіт" #: giara/new_post_window.py:100 msgid "Choose an image or video to upload" msgstr "Вибрати зображення і відео для вивантаження" #: giara/new_post_window.py:212 giara/new_post_window.py:213 #: giara/new_post_window.py:219 msgid "None" msgstr "Немає" #: giara/new_post_window.py:262 msgid "Select a subreddit…" msgstr "Виберіть сабреддіт…" #: giara/new_post_window.py:324 msgid "Select a flair…" msgstr "Виберіть відбиток…" #: giara/new_post_window.py:389 msgid "New comment" msgstr "Новий коментар" #: giara/new_post_window.py:418 msgid "Editing" msgstr "Редагування" #: giara/notification_manager.py:31 #, python-brace-format msgid "{0} new message" msgid_plural "{0} new messages" msgstr[0] "{0} нове повідомлення" msgstr[1] "{0} нових повідомлення" msgstr[2] "{0} нових повідомлень" msgstr[3] "{0} нове повідомлення" #: giara/notification_manager.py:36 #, python-brace-format msgid "{0} more" msgid_plural "{0} more" msgstr[0] "Ще {0}" msgstr[1] "Ще {0}" msgstr[2] "Ще {0}" msgstr[3] "Ще {0}" #: giara/post_details_view.py:32 msgid "Load more comments" msgstr "Завантажити ще коментарі" #: giara/search_view.py:65 msgid "Users" msgstr "Користувачі" #: giara/settings_window.py:107 msgid "Choose a folder" msgstr "Виберіть теку" #: giara/settings_window.py:260 msgid "General Settings" msgstr "Загальні параметри" #: giara/settings_window.py:281 msgid "Default view" msgstr "Типовий перегляд" #: giara/settings_window.py:294 msgid "Cache" msgstr "Кеш" #: giara/settings_window.py:307 msgid "Clear cache" msgstr "Спорожнити кеш" #: giara/settings_window.py:307 msgid "Clear" msgstr "Очистити" #: giara/settings_window.py:318 msgid "View" msgstr "Вигляд" #: giara/settings_window.py:322 msgid "View Settings" msgstr "Параметри вигляду" #: giara/settings_window.py:325 msgid "Dark mode" msgstr "Темний режим" #: giara/settings_window.py:330 msgid "Show thumbnails in post previews" msgstr "Показувати мініатюри у переглядах дописів" #: giara/settings_window.py:354 msgid "Max thumbnail width" msgstr "Максимальна ширина мініатюри" #: giara/settings_window.py:358 msgid "Use 0 to remove the limit" msgstr "Скористайтеся значенням 0, щоб прибрати обмеження" #: giara/settings_window.py:368 msgid "Privacy" msgstr "Конфіденційність" #: giara/settings_window.py:372 msgid "Link replacement" msgstr "Заміна посилання" #: giara/settings_window.py:375 msgid "Replace Twitter links with Nitter" msgstr "Замінити посилання Twitter на Nitter" #: giara/settings_window.py:380 msgid "Replace YouTube links with Invidious" msgstr "Замінити посилання YouTube на Invidious" #: giara/settings_window.py:390 msgid "Invidious instance" msgstr "Екземпляр Invidious" #: giara/settings_window.py:392 msgid "Naked domain name only, no https://" msgstr "Лише назва домену, без https://" #: giara/sort_menu_btn.py:75 #, python-brace-format msgid "Sort by: {0}" msgstr "Критерій впорядкування: {0}" #: giara/subreddit_view.py:40 msgid "This subreddit is already part of this multireddit" msgstr "Цей сабреддіт вже є частиною мультиреддіта" #: giara/subreddit_view.py:235 msgid "More actions" msgstr "Додаткові дії" #: giara/subreddit_view.py:278 msgid "Leave" msgstr "Вийти" #: giara/subreddit_view.py:317 msgid "Description" msgstr "Опис" #: giara/user_heading.py:54 msgid "Unfollow" msgstr "Зняти слідкування" #~ msgid "Giara is a GTK app for Reddit" #~ msgstr "Giara — програма на основі GTK для Reddit" #~ msgid "1 new message" #~ msgstr "1 нове повідомлення" giara-0.3/po/update_potfiles.sh000077500000000000000000000026351376474030500166220ustar00rootroot00000000000000#!/bin/bash APPNAME="giara" if [ -z $1 ]; then echo "Usage: $0 lang" exit fi lang="$1" rm *.pot version=$(fgrep -m 1 "version: " ../meson.build | grep -v "meson" | grep -o "'.*'" | sed "s/'//g") find ../$APPNAME -iname "*.py" | xargs xgettext --package-name=$APPNAME --package-version=$version --from-code=UTF-8 --output=$APPNAME-python.pot find ../data/ui -iname "*.glade" -or -iname "*.xml" -or -iname "*.ui" | xargs xgettext --package-name=$APPNAME --package-version=$version --from-code=UTF-8 --output=$APPNAME-glade.pot -L Glade find ../data/ -iname "*.desktop.in" | xargs xgettext --package-name=$APPNAME --package-version=$version --from-code=UTF-8 --output=$APPNAME-desktop.pot -L Desktop find ../data/ -iname "*.appdata.xml.in" | xargs xgettext --no-wrap --package-name=$APPNAME --package-version=$version --from-code=UTF-8 --output=$APPNAME-appdata.pot msgcat --use-first $APPNAME-python.pot $APPNAME-glade.pot $APPNAME-desktop.pot $APPNAME-appdata.pot > $APPNAME.pot sed 's/#: //g;s/:[0-9]*//g;s/\.\.\///g' <(fgrep "#: " $APPNAME.pot) | sort | uniq | sed 's/ /\n/g' | uniq > POTFILES.in [ -f "${lang}.po" ] && mv "${lang}.po" "${lang}.po.old" msginit --locale=$lang --input $APPNAME.pot if [ -f "${lang}.po.old" ]; then mv "${lang}.po" "${lang}.po.new" msgmerge -N "${lang}.po.old" "${lang}.po.new" > ${lang}.po rm "${lang}.po.old" "${lang}.po.new" fi sed -i 's/ASCII/UTF-8/' "${lang}.po" rm *.pot giara-0.3/run.sh000077500000000000000000000001041376474030500136060ustar00rootroot00000000000000#!/bin/sh ninja -C build ninja -C build install ninja -C build run