dino-0.3.0/0000755000000000000000000000000014202022370011131 5ustar rootrootdino-0.3.0/.github/0000755000000000000000000000000014202022370012471 5ustar rootrootdino-0.3.0/.github/workflows/0000755000000000000000000000000014202022370014526 5ustar rootrootdino-0.3.0/.github/workflows/build.yml0000644000000000000000000000116714202022370016355 0ustar rootrootname: Build on: [pull_request, push] jobs: build: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 - run: sudo apt-get update - run: sudo apt-get install -y build-essential gettext cmake valac libgee-0.8-dev libsqlite3-dev libgtk-3-dev libnotify-dev libgpgme-dev libsoup2.4-dev libgcrypt20-dev libqrencode-dev libgspell-1-dev libnice-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libsrtp2-dev libwebrtc-audio-processing-dev - run: ./configure --with-tests --with-libsignal-in-tree - run: make - run: build/xmpp-vala-test - run: build/signal-protocol-vala-test dino-0.3.0/.gitignore0000644000000000000000000000010414202022370013114 0ustar rootroot*.o build/ Makefile .vscode/ *.iml .idea .sqlite3 gschemas.compiled dino-0.3.0/.gitmodules0000644000000000000000000000025314202022370013306 0ustar rootroot[submodule "libsignal-protocol-c"] path = plugins/signal-protocol/libsignal-protocol-c url = https://github.com/WhisperSystems/libsignal-protocol-c.git branch = v2.3.3 dino-0.3.0/CMakeLists.txt0000644000000000000000000002226414202022370013677 0ustar rootrootcmake_minimum_required(VERSION 3.3) list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake) include(ComputeVersion) if (NOT VERSION_FOUND) project(Dino LANGUAGES C CXX) elseif (VERSION_IS_RELEASE) project(Dino VERSION ${VERSION_FULL} LANGUAGES C CXX) else () project(Dino LANGUAGES C CXX) set(PROJECT_VERSION ${VERSION_FULL}) endif () # Prepare Plugins set(DEFAULT_PLUGINS omemo;openpgp;http-files;ice;rtp) foreach (plugin ${DEFAULT_PLUGINS}) if ("$CACHE{DINO_PLUGIN_ENABLED_${plugin}}" STREQUAL "") if (NOT DEFINED DINO_PLUGIN_ENABLED_${plugin}}) set(DINO_PLUGIN_ENABLED_${plugin} "yes" CACHE BOOL "Enable plugin ${plugin}") else () set(DINO_PLUGIN_ENABLED_${plugin} "${DINO_PLUGIN_ENABLED_${plugin}}" CACHE BOOL "Enable plugin ${plugin}" FORCE) endif () if (DINO_PLUGIN_ENABLED_${plugin}) message(STATUS "Enabled plugin: ${plugin}") else () message(STATUS "Disabled plugin: ${plugin}") endif () endif () endforeach (plugin) if (DISABLED_PLUGINS) foreach(plugin ${DISABLED_PLUGINS}) set(DINO_PLUGIN_ENABLED_${plugin} "no" CACHE BOOL "Enable plugin ${plugin}" FORCE) message(STATUS "Disabled plugin: ${plugin}") endforeach(plugin) endif (DISABLED_PLUGINS) if (ENABLED_PLUGINS) foreach(plugin ${ENABLED_PLUGINS}) set(DINO_PLUGIN_ENABLED_${plugin} "yes" CACHE BOOL "Enable plugin ${plugin}" FORCE) message(STATUS "Enabled plugin: ${plugin}") endforeach(plugin) endif (ENABLED_PLUGINS) set(PLUGINS "") get_cmake_property(all_variables VARIABLES) foreach (variable_name ${all_variables}) if (variable_name MATCHES "^DINO_PLUGIN_ENABLED_(.+)$" AND ${variable_name}) list(APPEND PLUGINS ${CMAKE_MATCH_1}) endif() endforeach () list(SORT PLUGINS) string(REPLACE ";" ", " PLUGINS_TEXT "${PLUGINS}") message(STATUS "Configuring Dino ${PROJECT_VERSION} with plugins: ${PLUGINS_TEXT}") # Prepare instal paths macro(set_path what val desc) if (NOT ${what}) unset(${what} CACHE) set(${what} ${val}) endif () if (NOT "${${what}}" STREQUAL "${_${what}_SET}") message(STATUS "${desc}: ${${what}}") set(_${what}_SET ${${what}} CACHE INTERNAL ${desc}) endif() endmacro(set_path) string(REGEX REPLACE "^liblib" "lib" LIBDIR_NAME "lib${LIB_SUFFIX}") set_path(CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}" "Installation directory for architecture-independent files") set_path(EXEC_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}" "Installation directory for architecture-dependent files") set_path(SHARE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}/share" "Installation directory for read-only architecture-independent data") set_path(BIN_INSTALL_DIR "${EXEC_INSTALL_PREFIX}/bin" "Installation directory for user executables") set_path(DATA_INSTALL_DIR "${SHARE_INSTALL_PREFIX}/dino" "Installation directory for dino-specific data") set_path(APPDATA_FILE_INSTALL_DIR "${SHARE_INSTALL_PREFIX}/metainfo" "Installation directory for .appdata.xml files") set_path(DESKTOP_FILE_INSTALL_DIR "${SHARE_INSTALL_PREFIX}/applications" "Installation directory for .desktop files") set_path(SERVICE_FILE_INSTALL_DIR "${SHARE_INSTALL_PREFIX}/dbus-1/services" "Installation directory for .service files") set_path(ICON_INSTALL_DIR "${SHARE_INSTALL_PREFIX}/icons" "Installation directory for icons") set_path(INCLUDE_INSTALL_DIR "${EXEC_INSTALL_PREFIX}/include" "Installation directory for C header files") set_path(LIB_INSTALL_DIR "${EXEC_INSTALL_PREFIX}/${LIBDIR_NAME}" "Installation directory for object code libraries") set_path(LOCALE_INSTALL_DIR "${SHARE_INSTALL_PREFIX}/locale" "Installation directory for locale files") set_path(PLUGIN_INSTALL_DIR "${LIB_INSTALL_DIR}/dino/plugins" "Installation directory for dino plugin object code files") set_path(VAPI_INSTALL_DIR "${SHARE_INSTALL_PREFIX}/vala/vapi" "Installation directory for Vala API files") set(TARGET_INSTALL LIBRARY DESTINATION ${LIB_INSTALL_DIR} RUNTIME DESTINATION ${BIN_INSTALL_DIR} PUBLIC_HEADER DESTINATION ${INCLUDE_INSTALL_DIR} ARCHIVE DESTINATION ${LIB_INSTALL_DIR}) set(PLUGIN_INSTALL LIBRARY DESTINATION ${PLUGIN_INSTALL_DIR} RUNTIME DESTINATION ${PLUGIN_INSTALL_DIR}) include(CheckCCompilerFlag) include(CheckCSourceCompiles) macro(AddCFlagIfSupported list flag) string(REGEX REPLACE "[^a-z^A-Z^_^0-9]+" "_" flag_name ${flag}) check_c_compiler_flag(${flag} COMPILER_SUPPORTS${flag_name}) if (${COMPILER_SUPPORTS${flag_name}}) set(${list} "${${list}} ${flag}") endif () endmacro() if ("Ninja" STREQUAL ${CMAKE_GENERATOR}) AddCFlagIfSupported(CMAKE_C_FLAGS -fdiagnostics-color) endif () # Flags for all C files AddCFlagIfSupported(CMAKE_C_FLAGS -Wall) AddCFlagIfSupported(CMAKE_C_FLAGS -Wextra) AddCFlagIfSupported(CMAKE_C_FLAGS -Werror=format-security) AddCFlagIfSupported(CMAKE_C_FLAGS -Wno-duplicate-decl-specifier) AddCFlagIfSupported(CMAKE_C_FLAGS -fno-omit-frame-pointer) if (NOT VALA_WARN) set(VALA_WARN "conversion") endif () set(VALA_WARN "${VALA_WARN}" CACHE STRING "Which warnings to show when invoking C compiler on Vala compiler output") set_property(CACHE VALA_WARN PROPERTY STRINGS "all;unused;qualifier;conversion;deprecated;format;none") # Vala generates some unused stuff if (NOT ("all" IN_LIST VALA_WARN OR "unused" IN_LIST VALA_WARN)) AddCFlagIfSupported(VALA_CFLAGS -Wno-unused-but-set-variable) AddCFlagIfSupported(VALA_CFLAGS -Wno-unused-function) AddCFlagIfSupported(VALA_CFLAGS -Wno-unused-label) AddCFlagIfSupported(VALA_CFLAGS -Wno-unused-parameter) AddCFlagIfSupported(VALA_CFLAGS -Wno-unused-value) AddCFlagIfSupported(VALA_CFLAGS -Wno-unused-variable) endif () if (NOT ("all" IN_LIST VALA_WARN OR "qualifier" IN_LIST VALA_WARN)) AddCFlagIfSupported(VALA_CFLAGS -Wno-discarded-qualifiers) AddCFlagIfSupported(VALA_CFLAGS -Wno-discarded-array-qualifiers) AddCFlagIfSupported(VALA_CFLAGS -Wno-incompatible-pointer-types-discards-qualifiers) endif () if (NOT ("all" IN_LIST VALA_WARN OR "deprecated" IN_LIST VALA_WARN)) AddCFlagIfSupported(VALA_CFLAGS -Wno-deprecated-declarations) endif () if (NOT ("all" IN_LIST VALA_WARN OR "format" IN_LIST VALA_WARN)) AddCFlagIfSupported(VALA_CFLAGS -Wno-missing-braces) endif () if (NOT ("all" IN_LIST VALA_WARN OR "conversion" IN_LIST VALA_WARN)) AddCFlagIfSupported(VALA_CFLAGS -Wno-int-conversion) AddCFlagIfSupported(VALA_CFLAGS -Wno-pointer-sign) AddCFlagIfSupported(VALA_CFLAGS -Wno-incompatible-pointer-types) endif () try_compile(__WITHOUT_FILE_OFFSET_BITS_64 ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/cmake/LargeFileOffsets.c COMPILE_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS}) if (NOT __WITHOUT_FILE_OFFSET_BITS_64) try_compile(__WITH_FILE_OFFSET_BITS_64 ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/cmake/LargeFileOffsets.c COMPILE_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS} -D_FILE_OFFSET_BITS=64) if (__WITH_FILE_OFFSET_BITS_64) AddCFlagIfSupported(CMAKE_C_FLAGS -D_FILE_OFFSET_BITS=64) message(STATUS "Enabled large file support using _FILE_OFFSET_BITS=64") else (__WITH_FILE_OFFSET_BITS_64) message(STATUS "Large file support not available") endif (__WITH_FILE_OFFSET_BITS_64) unset(__WITH_FILE_OFFSET_BITS_64) endif (NOT __WITHOUT_FILE_OFFSET_BITS_64) unset(__WITHOUT_FILE_OFFSET_BITS_64) if ($ENV{USE_CCACHE}) # Configure CCache if available find_program(CCACHE_BIN ccache) mark_as_advanced(CCACHE_BIN) if (CCACHE_BIN) message(STATUS "Using ccache") set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ${CCACHE_BIN}) set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ${CCACHE_BIN}) else (CCACHE_BIN) message(STATUS "USE_CCACHE was set but ccache was not found") endif (CCACHE_BIN) endif ($ENV{USE_CCACHE}) if (NOT NO_DEBUG) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g") set(CMAKE_VALA_FLAGS "${CMAKE_VALA_FLAGS} -g") endif (NOT NO_DEBUG) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) set(GTK3_GLOBAL_VERSION 3.22) set(GLib_GLOBAL_VERSION 2.38) set(ICU_GLOBAL_VERSION 57) if (NOT VALA_EXECUTABLE) unset(VALA_EXECUTABLE CACHE) endif () find_package(Vala 0.34 REQUIRED) if (VALA_VERSION VERSION_GREATER "0.34.90" AND VALA_VERSION VERSION_LESS "0.36.1" OR # Due to a bug on 0.36.0 (and pre-releases), we need to disable FAST_VAPI VALA_VERSION VERSION_EQUAL "0.44.10" OR VALA_VERSION VERSION_EQUAL "0.46.4" OR VALA_VERSION VERSION_EQUAL "0.47.1" OR # See Dino issue #646 VALA_VERSION VERSION_EQUAL "0.40.21" OR VALA_VERSION VERSION_EQUAL "0.46.8" OR VALA_VERSION VERSION_EQUAL "0.48.4") # See Dino issue #816 set(DISABLE_FAST_VAPI yes) endif () include(${VALA_USE_FILE}) include(MultiFind) include(GlibCompileResourcesSupport) set(CMAKE_VALA_FLAGS "${CMAKE_VALA_FLAGS} --target-glib=${GLib_GLOBAL_VERSION}") add_subdirectory(qlite) add_subdirectory(xmpp-vala) add_subdirectory(libdino) add_subdirectory(main) add_subdirectory(crypto-vala) add_subdirectory(plugins) # uninstall target configure_file("${CMAKE_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in" "${CMAKE_BINARY_DIR}/cmake_uninstall.cmake" IMMEDIATE @ONLY) add_custom_target(uninstall COMMAND ${CMAKE_COMMAND} -P ${CMAKE_BINARY_DIR}/cmake_uninstall.cmake COMMENT "Uninstall the project...") dino-0.3.0/LICENSE0000644000000000000000000010451514202022370012144 0ustar rootroot 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 . dino-0.3.0/README.md0000644000000000000000000000343314202022370012413 0ustar rootroot![Dino](https://dino.im/img/readme_header.svg) ======= ![screenshots](https://dino.im/img/screenshot-main.png) Installation ------------ Have a look at the [prebuilt packages](https://github.com/dino/dino/wiki/Distribution-Packages). Build ----- Make sure to install all [dependencies](https://github.com/dino/dino/wiki/Build#dependencies). ./configure make build/dino Resources --------- - Check out the [Dino website](https://dino.im). - Join our XMPP channel at `chat@dino.im`. - The [wiki](https://github.com/dino/dino/wiki) provides additional information. Contribute ---------- - Pull requests are welcome. [These](https://github.com/dino/dino/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) might be good first issues. Please discuss bigger changes in our channel first. - Look at [how to debug](https://github.com/dino/dino/wiki/Debugging) Dino before you report a bug. - Help [translating](https://github.com/dino/dino/wiki/Translations) Dino into your language. - Make a [donation](https://dino.im/#donate). License ------- Dino - Modern Jabber/XMPP Client using GTK+/Vala Copyright (C) 2016-2022 Dino contributors 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 . dino-0.3.0/VERSION0000644000000000000000000000001614202022370012176 0ustar rootrootRELEASE 0.3.0 dino-0.3.0/cmake/0000755000000000000000000000000014202022370012211 5ustar rootrootdino-0.3.0/cmake/BuildTargetScript.cmake0000644000000000000000000000444314202022370016613 0ustar rootroot# This file is used to be invoked at build time. It generates the needed # resource XML file. # Input variables that need to provided when invoking this script: # GXML_OUTPUT The output file path where to save the XML file. # GXML_COMPRESS_ALL Sets all COMPRESS flags in all resources in resource # list. # GXML_NO_COMPRESS_ALL Removes all COMPRESS flags in all resources in # resource list. # GXML_STRIPBLANKS_ALL Sets all STRIPBLANKS flags in all resources in # resource list. # GXML_NO_STRIPBLANKS_ALL Removes all STRIPBLANKS flags in all resources in # resource list. # GXML_TOPIXDATA_ALL Sets all TOPIXDATA flags i nall resources in resource # list. # GXML_NO_TOPIXDATA_ALL Removes all TOPIXDATA flags in all resources in # resource list. # GXML_PREFIX Overrides the resource prefix that is prepended to # each relative name in registered resources. # GXML_RESOURCES The list of resource files. Whether absolute or # relative path is equal. # Include the GENERATE_GXML() function. include(${CMAKE_CURRENT_LIST_DIR}/GenerateGXML.cmake) # Set flags to actual invocation flags. if(GXML_COMPRESS_ALL) set(GXML_COMPRESS_ALL COMPRESS_ALL) endif() if(GXML_NO_COMPRESS_ALL) set(GXML_NO_COMPRESS_ALL NO_COMPRESS_ALL) endif() if(GXML_STRIPBLANKS_ALL) set(GXML_STRIPBLANKS_ALL STRIPBLANKS_ALL) endif() if(GXML_NO_STRIPBLANKS_ALL) set(GXML_NO_STRIPBLANKS_ALL NO_STRIPBLANKS_ALL) endif() if(GXML_TOPIXDATA_ALL) set(GXML_TOPIXDATA_ALL TOPIXDATA_ALL) endif() if(GXML_NO_TOPIXDATA_ALL) set(GXML_NO_TOPIXDATA_ALL NO_TOPIXDATA_ALL) endif() # Replace " " with ";" to import the list over the command line. Otherwise # CMake would interprete the passed resources as a whole string. string(REPLACE " " ";" GXML_RESOURCES ${GXML_RESOURCES}) # Invoke the gresource XML generation function. generate_gxml(${GXML_OUTPUT} ${GXML_COMPRESS_ALL} ${GXML_NO_COMPRESS_ALL} ${GXML_STRIPBLANKS_ALL} ${GXML_NO_STRIPBLANKS_ALL} ${GXML_TOPIXDATA_ALL} ${GXML_NO_TOPIXDATA_ALL} PREFIX ${GXML_PREFIX} RESOURCES ${GXML_RESOURCES}) dino-0.3.0/cmake/CompileGResources.cmake0000644000000000000000000002404414202022370016611 0ustar rootrootinclude(CMakeParseArguments) # Path to this file. set(GCR_CMAKE_MACRO_DIR ${CMAKE_CURRENT_LIST_DIR}) # Compiles a gresource resource file from given resource files. Automatically # creates the XML controlling file. # The type of resource to generate (header, c-file or bundle) is automatically # determined from TARGET file ending, if no TYPE is explicitly specified. # The output file is stored in the provided variable "output". # "xml_out" contains the variable where to output the XML path. Can be used to # create custom targets or doing postprocessing. # If you want to use preprocessing, you need to manually check the existence # of the tools you use. This function doesn't check this for you, it just # generates the XML file. glib-compile-resources will then throw a # warning/error. function(COMPILE_GRESOURCES output xml_out) # Available options: # COMPRESS_ALL, NO_COMPRESS_ALL Overrides the COMPRESS flag in all # registered resources. # STRIPBLANKS_ALL, NO_STRIPBLANKS_ALL Overrides the STRIPBLANKS flag in all # registered resources. # TOPIXDATA_ALL, NO_TOPIXDATA_ALL Overrides the TOPIXDATA flag in all # registered resources. set(CG_OPTIONS COMPRESS_ALL NO_COMPRESS_ALL STRIPBLANKS_ALL NO_STRIPBLANKS_ALL TOPIXDATA_ALL NO_TOPIXDATA_ALL) # Available one value options: # TYPE Type of resource to create. Valid options are: # EMBED_C: A C-file that can be compiled with your project. # EMBED_H: A header that can be included into your project. # BUNDLE: Generates a resource bundle file that can be loaded # at runtime. # AUTO: Determine from target file ending. Need to specify # target argument. # PREFIX Overrides the resource prefix that is prepended to each # relative file name in registered resources. # SOURCE_DIR Overrides the resources base directory to search for resources. # Normally this is set to the source directory with that CMake # was invoked (CMAKE_SOURCE_DIR). # TARGET Overrides the name of the output file/-s. Normally the output # names from glib-compile-resources tool is taken. set(CG_ONEVALUEARGS TYPE PREFIX SOURCE_DIR TARGET) # Available multi-value options: # RESOURCES The list of resource files. Whether absolute or relative path is # equal, absolute paths are stripped down to relative ones. If the # absolute path is not inside the given base directory SOURCE_DIR # or CMAKE_SOURCE_DIR (if SOURCE_DIR is not overriden), this # function aborts. # OPTIONS Extra command line options passed to glib-compile-resources. set(CG_MULTIVALUEARGS RESOURCES OPTIONS) # Parse the arguments. cmake_parse_arguments(CG_ARG "${CG_OPTIONS}" "${CG_ONEVALUEARGS}" "${CG_MULTIVALUEARGS}" "${ARGN}") # Variable to store the double-quote (") string. Since escaping # double-quotes in strings is not possible we need a helper variable that # does this job for us. set(Q \") # Check invocation validity with the _UNPARSED_ARGUMENTS variable. # If other not recognized parameters were passed, throw error. if (CG_ARG_UNPARSED_ARGUMENTS) set(CG_WARNMSG "Invocation of COMPILE_GRESOURCES with unrecognized") set(CG_WARNMSG "${CG_WARNMSG} parameters. Parameters are:") set(CG_WARNMSG "${CG_WARNMSG} ${CG_ARG_UNPARSED_ARGUMENTS}.") message(WARNING ${CG_WARNMSG}) endif() # Check invocation validity depending on generation mode (EMBED_C, EMBED_H # or BUNDLE). if ("${CG_ARG_TYPE}" STREQUAL "EMBED_C") # EMBED_C mode, output compilable C-file. set(CG_GENERATE_COMMAND_LINE "--generate-source") set(CG_TARGET_FILE_ENDING "c") elseif ("${CG_ARG_TYPE}" STREQUAL "EMBED_H") # EMBED_H mode, output includable header file. set(CG_GENERATE_COMMAND_LINE "--generate-header") set(CG_TARGET_FILE_ENDING "h") elseif ("${CG_ARG_TYPE}" STREQUAL "BUNDLE") # BUNDLE mode, output resource bundle. Don't do anything since # glib-compile-resources outputs a bundle when not specifying # something else. set(CG_TARGET_FILE_ENDING "gresource") else() # Everything else is AUTO mode, determine from target file ending. if (CG_ARG_TARGET) set(CG_GENERATE_COMMAND_LINE "--generate") else() set(CG_ERRMSG "AUTO mode given, but no target specified. Can't") set(CG_ERRMSG "${CG_ERRMSG} determine output type. In function") set(CG_ERRMSG "${CG_ERRMSG} COMPILE_GRESOURCES.") message(FATAL_ERROR ${CG_ERRMSG}) endif() endif() # Check flag validity. if (CG_ARG_COMPRESS_ALL AND CG_ARG_NO_COMPRESS_ALL) set(CG_ERRMSG "COMPRESS_ALL and NO_COMPRESS_ALL simultaneously set. In") set(CG_ERRMSG "${CG_ERRMSG} function COMPILE_GRESOURCES.") message(FATAL_ERROR ${CG_ERRMSG}) endif() if (CG_ARG_STRIPBLANKS_ALL AND CG_ARG_NO_STRIPBLANKS_ALL) set(CG_ERRMSG "STRIPBLANKS_ALL and NO_STRIPBLANKS_ALL simultaneously") set(CG_ERRMSG "${CG_ERRMSG} set. In function COMPILE_GRESOURCES.") message(FATAL_ERROR ${CG_ERRMSG}) endif() if (CG_ARG_TOPIXDATA_ALL AND CG_ARG_NO_TOPIXDATA_ALL) set(CG_ERRMSG "TOPIXDATA_ALL and NO_TOPIXDATA_ALL simultaneously set.") set(CG_ERRMSG "${CG_ERRMSG} In function COMPILE_GRESOURCES.") message(FATAL_ERROR ${CG_ERRMSG}) endif() # Check if there are any resources. if (NOT CG_ARG_RESOURCES) set(CG_ERRMSG "No resource files to process. In function") set(CG_ERRMSG "${CG_ERRMSG} COMPILE_GRESOURCES.") message(FATAL_ERROR ${CG_ERRMSG}) endif() # Extract all dependencies for targets from resource list. foreach(res ${CG_ARG_RESOURCES}) if (NOT(("${res}" STREQUAL "COMPRESS") OR ("${res}" STREQUAL "STRIPBLANKS") OR ("${res}" STREQUAL "TOPIXDATA"))) add_custom_command( OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/resources/${res}" COMMAND ${CMAKE_COMMAND} -E copy "${CG_ARG_SOURCE_DIR}/${res}" "${CMAKE_CURRENT_BINARY_DIR}/resources/${res}" MAIN_DEPENDENCY "${CG_ARG_SOURCE_DIR}/${res}") list(APPEND CG_RESOURCES_DEPENDENCIES "${CMAKE_CURRENT_BINARY_DIR}/resources/${res}") endif() endforeach() # Construct .gresource.xml path. set(CG_XML_FILE_PATH "${CMAKE_CURRENT_BINARY_DIR}/resources/.gresource.xml") # Generate gresources XML target. list(APPEND CG_CMAKE_SCRIPT_ARGS "-D") list(APPEND CG_CMAKE_SCRIPT_ARGS "GXML_OUTPUT=${Q}${CG_XML_FILE_PATH}${Q}") if(CG_ARG_COMPRESS_ALL) list(APPEND CG_CMAKE_SCRIPT_ARGS "-D") list(APPEND CG_CMAKE_SCRIPT_ARGS "GXML_COMPRESS_ALL") endif() if(CG_ARG_NO_COMPRESS_ALL) list(APPEND CG_CMAKE_SCRIPT_ARGS "-D") list(APPEND CG_CMAKE_SCRIPT_ARGS "GXML_NO_COMPRESS_ALL") endif() if(CG_ARG_STRPIBLANKS_ALL) list(APPEND CG_CMAKE_SCRIPT_ARGS "-D") list(APPEND CG_CMAKE_SCRIPT_ARGS "GXML_STRIPBLANKS_ALL") endif() if(CG_ARG_NO_STRIPBLANKS_ALL) list(APPEND CG_CMAKE_SCRIPT_ARGS "-D") list(APPEND CG_CMAKE_SCRIPT_ARGS "GXML_NO_STRIPBLANKS_ALL") endif() if(CG_ARG_TOPIXDATA_ALL) list(APPEND CG_CMAKE_SCRIPT_ARGS "-D") list(APPEND CG_CMAKE_SCRIPT_ARGS "GXML_TOPIXDATA_ALL") endif() if(CG_ARG_NO_TOPIXDATA_ALL) list(APPEND CG_CMAKE_SCRIPT_ARGS "-D") list(APPEND CG_CMAKE_SCRIPT_ARGS "GXML_NO_TOPIXDATA_ALL") endif() list(APPEND CG_CMAKE_SCRIPT_ARGS "-D") list(APPEND CG_CMAKE_SCRIPT_ARGS "GXML_PREFIX=${Q}${CG_ARG_PREFIX}${Q}") list(APPEND CG_CMAKE_SCRIPT_ARGS "-D") list(APPEND CG_CMAKE_SCRIPT_ARGS "GXML_RESOURCES=${Q}${CG_ARG_RESOURCES}${Q}") list(APPEND CG_CMAKE_SCRIPT_ARGS "-P") list(APPEND CG_CMAKE_SCRIPT_ARGS "${Q}${GCR_CMAKE_MACRO_DIR}/BuildTargetScript.cmake${Q}") get_filename_component(CG_XML_FILE_PATH_ONLY_NAME "${CG_XML_FILE_PATH}" NAME) set(CG_XML_CUSTOM_COMMAND_COMMENT "Creating gresources XML file (${CG_XML_FILE_PATH_ONLY_NAME})") add_custom_command(OUTPUT ${CG_XML_FILE_PATH} COMMAND ${CMAKE_COMMAND} ARGS ${CG_CMAKE_SCRIPT_ARGS} DEPENDS ${CG_RESOURCES_DEPENDENCIES} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMENT ${CG_XML_CUSTOM_COMMAND_COMMENT}) # Create target manually if not set (to make sure glib-compile-resources # doesn't change behaviour with it's naming standards). if (NOT CG_ARG_TARGET) set(CG_ARG_TARGET "${CMAKE_CURRENT_BINARY_DIR}/resources") set(CG_ARG_TARGET "${CG_ARG_TARGET}.${CG_TARGET_FILE_ENDING}") endif() # Create source directory automatically if not set. if (NOT CG_ARG_SOURCE_DIR) set(CG_ARG_SOURCE_DIR "${CMAKE_SOURCE_DIR}") endif() # Add compilation target for resources. add_custom_command(OUTPUT ${CG_ARG_TARGET} COMMAND ${GLIB_COMPILE_RESOURCES_EXECUTABLE} ARGS ${OPTIONS} "--target=${Q}${CG_ARG_TARGET}${Q}" "--sourcedir=${Q}${CG_ARG_SOURCE_DIR}${Q}" ${CG_GENERATE_COMMAND_LINE} ${CG_XML_FILE_PATH} MAIN_DEPENDENCY ${CG_XML_FILE_PATH} DEPENDS ${CG_RESOURCES_DEPENDENCIES} WORKING_DIRECTORY ${CMAKE_BUILD_DIR}) # Set output and XML_OUT to parent scope. set(${xml_out} ${CG_XML_FILE_PATH} PARENT_SCOPE) set(${output} ${CG_ARG_TARGET} PARENT_SCOPE) endfunction() dino-0.3.0/cmake/ComputeVersion.cmake0000644000000000000000000000753714202022370016211 0ustar rootrootinclude(CMakeParseArguments) function(_compute_version_from_file) set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/VERSION) if (NOT EXISTS ${CMAKE_SOURCE_DIR}/VERSION) set(VERSION_FOUND 0 PARENT_SCOPE) return() endif () file(STRINGS ${CMAKE_SOURCE_DIR}/VERSION VERSION_FILE) string(REPLACE " " ";" VERSION_FILE "${VERSION_FILE}") cmake_parse_arguments(VERSION_FILE "" "RELEASE;PRERELEASE" "" ${VERSION_FILE}) if (DEFINED VERSION_FILE_RELEASE) string(STRIP "${VERSION_FILE_RELEASE}" VERSION_FILE_RELEASE) set(VERSION_IS_RELEASE 1 PARENT_SCOPE) set(VERSION_FULL "${VERSION_FILE_RELEASE}" PARENT_SCOPE) set(VERSION_FOUND 1 PARENT_SCOPE) elseif (DEFINED VERSION_FILE_PRERELEASE) string(STRIP "${VERSION_FILE_PRERELEASE}" VERSION_FILE_PRERELEASE) set(VERSION_IS_RELEASE 0 PARENT_SCOPE) set(VERSION_FULL "${VERSION_FILE_PRERELEASE}" PARENT_SCOPE) set(VERSION_FOUND 1 PARENT_SCOPE) else () set(VERSION_FOUND 0 PARENT_SCOPE) endif () endfunction(_compute_version_from_file) function(_compute_version_from_git) set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/.git) if (NOT GIT_EXECUTABLE) find_package(Git QUIET) if (NOT GIT_FOUND) return() endif () endif (NOT GIT_EXECUTABLE) # Git tag execute_process( COMMAND "${GIT_EXECUTABLE}" describe --tags --abbrev=0 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" RESULT_VARIABLE git_result OUTPUT_VARIABLE git_tag ERROR_VARIABLE git_error OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_STRIP_TRAILING_WHITESPACE ) if (NOT git_result EQUAL 0) return() endif (NOT git_result EQUAL 0) if (git_tag MATCHES "^v?([0-9]+[.]?[0-9]*[.]?[0-9]*)(-[.0-9A-Za-z-]+)?([+][.0-9A-Za-z-]+)?$") set(VERSION_LAST_RELEASE "${CMAKE_MATCH_1}") else () return() endif () # Git describe execute_process( COMMAND "${GIT_EXECUTABLE}" describe --tags WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" RESULT_VARIABLE git_result OUTPUT_VARIABLE git_describe ERROR_VARIABLE git_error OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_STRIP_TRAILING_WHITESPACE ) if (NOT git_result EQUAL 0) return() endif (NOT git_result EQUAL 0) if ("${git_tag}" STREQUAL "${git_describe}") set(VERSION_IS_RELEASE 1) else () set(VERSION_IS_RELEASE 0) if (git_describe MATCHES "-([0-9]+)-g([0-9a-f]+)$") set(VERSION_TAG_OFFSET "${CMAKE_MATCH_1}") set(VERSION_COMMIT_HASH "${CMAKE_MATCH_2}") endif () execute_process( COMMAND "${GIT_EXECUTABLE}" show --format=%cd --date=format:%Y%m%d -s WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" RESULT_VARIABLE git_result OUTPUT_VARIABLE git_time ERROR_VARIABLE git_error OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_STRIP_TRAILING_WHITESPACE ) if (NOT git_result EQUAL 0) return() endif (NOT git_result EQUAL 0) set(VERSION_COMMIT_DATE "${git_time}") endif () if (NOT VERSION_IS_RELEASE) set(VERSION_SUFFIX "~git${VERSION_TAG_OFFSET}.${VERSION_COMMIT_DATE}.${VERSION_COMMIT_HASH}") else (NOT VERSION_IS_RELEASE) set(VERSION_SUFFIX "") endif (NOT VERSION_IS_RELEASE) set(VERSION_IS_RELEASE ${VERSION_IS_RELEASE} PARENT_SCOPE) set(VERSION_FULL "${VERSION_LAST_RELEASE}${VERSION_SUFFIX}" PARENT_SCOPE) set(VERSION_FOUND 1 PARENT_SCOPE) endfunction(_compute_version_from_git) _compute_version_from_file() if (NOT VERSION_FOUND) _compute_version_from_git() endif (NOT VERSION_FOUND)dino-0.3.0/cmake/FindATK.cmake0000644000000000000000000000267014202022370014440 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(ATK PKG_CONFIG_NAME atk LIB_NAMES atk-1.0 INCLUDE_NAMES atk/atk.h INCLUDE_DIR_SUFFIXES atk-1.0 atk-1.0/include DEPENDS GObject ) if(ATK_FOUND AND NOT ATK_VERSION) find_file(ATK_VERSION_HEADER "atk/atkversion.h" HINTS ${ATK_INCLUDE_DIRS}) mark_as_advanced(ATK_VERSION_HEADER) if(ATK_VERSION_HEADER) file(STRINGS "${ATK_VERSION_HEADER}" ATK_MAJOR_VERSION REGEX "^#define ATK_MAJOR_VERSION +\\(?([0-9]+)\\)?$") string(REGEX REPLACE "^#define ATK_MAJOR_VERSION \\(?([0-9]+)\\)?$" "\\1" ATK_MAJOR_VERSION "${ATK_MAJOR_VERSION}") file(STRINGS "${ATK_VERSION_HEADER}" ATK_MINOR_VERSION REGEX "^#define ATK_MINOR_VERSION +\\(?([0-9]+)\\)?$") string(REGEX REPLACE "^#define ATK_MINOR_VERSION \\(?([0-9]+)\\)?$" "\\1" ATK_MINOR_VERSION "${ATK_MINOR_VERSION}") file(STRINGS "${ATK_VERSION_HEADER}" ATK_MICRO_VERSION REGEX "^#define ATK_MICRO_VERSION +\\(?([0-9]+)\\)?$") string(REGEX REPLACE "^#define ATK_MICRO_VERSION \\(?([0-9]+)\\)?$" "\\1" ATK_MICRO_VERSION "${ATK_MICRO_VERSION}") set(ATK_VERSION "${ATK_MAJOR_VERSION}.${ATK_MINOR_VERSION}.${ATK_MICRO_VERSION}") unset(ATK_MAJOR_VERSION) unset(ATK_MINOR_VERSION) unset(ATK_MICRO_VERSION) endif() endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(ATK REQUIRED_VARS ATK_LIBRARY VERSION_VAR ATK_VERSION)dino-0.3.0/cmake/FindCairo.cmake0000644000000000000000000000274314202022370015057 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(Cairo PKG_CONFIG_NAME cairo LIB_NAMES cairo INCLUDE_NAMES cairo.h INCLUDE_DIR_SUFFIXES cairo cairo/include ) if(Cairo_FOUND AND NOT Cairo_VERSION) find_file(Cairo_VERSION_HEADER "cairo-version.h" HINTS ${Cairo_INCLUDE_DIRS}) mark_as_advanced(Cairo_VERSION_HEADER) if(Cairo_VERSION_HEADER) file(STRINGS "${Cairo_VERSION_HEADER}" Cairo_MAJOR_VERSION REGEX "^#define CAIRO_VERSION_MAJOR +\\(?([0-9]+)\\)?$") string(REGEX REPLACE "^#define CAIRO_VERSION_MAJOR \\(?([0-9]+)\\)?$" "\\1" Cairo_MAJOR_VERSION "${Cairo_MAJOR_VERSION}") file(STRINGS "${Cairo_VERSION_HEADER}" Cairo_MINOR_VERSION REGEX "^#define CAIRO_VERSION_MINOR +\\(?([0-9]+)\\)?$") string(REGEX REPLACE "^#define CAIRO_VERSION_MINOR \\(?([0-9]+)\\)?$" "\\1" Cairo_MINOR_VERSION "${Cairo_MINOR_VERSION}") file(STRINGS "${Cairo_VERSION_HEADER}" Cairo_MICRO_VERSION REGEX "^#define CAIRO_VERSION_MICRO +\\(?([0-9]+)\\)?$") string(REGEX REPLACE "^#define CAIRO_VERSION_MICRO \\(?([0-9]+)\\)?$" "\\1" Cairo_MICRO_VERSION "${Cairo_MICRO_VERSION}") set(Cairo_VERSION "${Cairo_MAJOR_VERSION}.${Cairo_MINOR_VERSION}.${Cairo_MICRO_VERSION}") unset(Cairo_MAJOR_VERSION) unset(Cairo_MINOR_VERSION) unset(Cairo_MICRO_VERSION) endif() endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Cairo REQUIRED_VARS Cairo_LIBRARY VERSION_VAR Cairo_VERSION)dino-0.3.0/cmake/FindCanberra.cmake0000644000000000000000000000042314202022370015530 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(Canberra PKG_CONFIG_NAME libcanberra LIB_NAMES canberra INCLUDE_NAMES canberra.h ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Canberra REQUIRED_VARS Canberra_LIBRARY) dino-0.3.0/cmake/FindGCrypt.cmake0000644000000000000000000000046614202022370015232 0ustar rootrootinclude(PkgConfigWithFallbackOnConfigScript) find_pkg_config_with_fallback_on_config_script(GCrypt PKG_CONFIG_NAME libgcrypt CONFIG_SCRIPT_NAME libgcrypt ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(GCrypt REQUIRED_VARS GCrypt_LIBRARY VERSION_VAR GCrypt_VERSION) dino-0.3.0/cmake/FindGDK3.cmake0000644000000000000000000000333414202022370014507 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(GDK3 PKG_CONFIG_NAME gdk-3.0 LIB_NAMES gdk-3 INCLUDE_NAMES gdk/gdk.h INCLUDE_DIR_SUFFIXES gtk-3.0 gtk-3.0/include gtk+-3.0 gtk+-3.0/include DEPENDS Pango Cairo GDKPixbuf2 ) if(GDK3_FOUND AND NOT GDK3_VERSION) find_file(GDK3_VERSION_HEADER "gdk/gdkversionmacros.h" HINTS ${GDK3_INCLUDE_DIRS}) mark_as_advanced(GDK3_VERSION_HEADER) if(GDK3_VERSION_HEADER) file(STRINGS "${GDK3_VERSION_HEADER}" GDK3_MAJOR_VERSION REGEX "^#define GDK_MAJOR_VERSION +\\(?([0-9]+)\\)?$") string(REGEX REPLACE "^#define GDK_MAJOR_VERSION \\(?([0-9]+)\\)?$" "\\1" GDK3_MAJOR_VERSION "${GDK3_MAJOR_VERSION}") file(STRINGS "${GDK3_VERSION_HEADER}" GDK3_MINOR_VERSION REGEX "^#define GDK_MINOR_VERSION +\\(?([0-9]+)\\)?$") string(REGEX REPLACE "^#define GDK_MINOR_VERSION \\(?([0-9]+)\\)?$" "\\1" GDK3_MINOR_VERSION "${GDK3_MINOR_VERSION}") file(STRINGS "${GDK3_VERSION_HEADER}" GDK3_MICRO_VERSION REGEX "^#define GDK_MICRO_VERSION +\\(?([0-9]+)\\)?$") string(REGEX REPLACE "^#define GDK_MICRO_VERSION \\(?([0-9]+)\\)?$" "\\1" GDK3_MICRO_VERSION "${GDK3_MICRO_VERSION}") set(GDK3_VERSION "${GDK3_MAJOR_VERSION}.${GDK3_MINOR_VERSION}.${GDK3_MICRO_VERSION}") unset(GDK3_MAJOR_VERSION) unset(GDK3_MINOR_VERSION) unset(GDK3_MICRO_VERSION) endif() endif() if (GDK3_FOUND) find_file(GDK3_WITH_X11 "gdk/gdkx.h" HINTS ${GDK3_INCLUDE_DIRS}) if (GDK3_WITH_X11) set(GDK3_WITH_X11 yes CACHE INTERNAL "Does GDK3 support X11") endif (GDK3_WITH_X11) endif () include(FindPackageHandleStandardArgs) find_package_handle_standard_args(GDK3 REQUIRED_VARS GDK3_LIBRARY VERSION_VAR GDK3_VERSION)dino-0.3.0/cmake/FindGDKPixbuf2.cmake0000644000000000000000000000170414202022370015663 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(GDKPixbuf2 PKG_CONFIG_NAME gdk-pixbuf-2.0 LIB_NAMES gdk_pixbuf-2.0 INCLUDE_NAMES gdk-pixbuf/gdk-pixbuf.h INCLUDE_DIR_SUFFIXES gdk-pixbuf-2.0 gdk-pixbuf-2.0/include DEPENDS GLib ) if(GDKPixbuf2_FOUND AND NOT GDKPixbuf2_VERSION) find_file(GDKPixbuf2_FEATURES_HEADER "gdk-pixbuf/gdk-pixbuf-features.h" HINTS ${GDKPixbuf2_INCLUDE_DIRS}) mark_as_advanced(GDKPixbuf2_FEATURES_HEADER) if(GDKPixbuf2_FEATURES_HEADER) file(STRINGS "${GDKPixbuf2_FEATURES_HEADER}" GDKPixbuf2_VERSION REGEX "^#define GDK_PIXBUF_VERSION \\\"[^\\\"]+\\\"") string(REGEX REPLACE "^#define GDK_PIXBUF_VERSION \\\"([0-9]+)\\.([0-9]+)\\.([0-9]+)\\\"$" "\\1.\\2.\\3" GDKPixbuf2_VERSION "${GDKPixbuf2_VERSION}") endif() endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(GDKPixbuf2 REQUIRED_VARS GDKPixbuf2_LIBRARY VERSION_VAR GDKPixbuf2_VERSION)dino-0.3.0/cmake/FindGIO.cmake0000644000000000000000000000073614202022370014440 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(GIO PKG_CONFIG_NAME gio-2.0 LIB_NAMES gio-2.0 INCLUDE_NAMES gio/gio.h INCLUDE_DIR_SUFFIXES glib-2.0 glib-2.0/include DEPENDS GObject ) if(GIO_FOUND AND NOT GIO_VERSION) find_package(GLib ${GLib_GLOBAL_VERSION}) set(GIO_VERSION ${GLib_VERSION}) endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(GIO REQUIRED_VARS GIO_LIBRARY VERSION_VAR GIO_VERSION)dino-0.3.0/cmake/FindGLib.cmake0000644000000000000000000000305714202022370014636 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(GLib PKG_CONFIG_NAME glib-2.0 LIB_NAMES glib-2.0 INCLUDE_NAMES glib.h glibconfig.h INCLUDE_DIR_HINTS ${CMAKE_LIBRARY_PATH} ${CMAKE_SYSTEM_LIBRARY_PATH} INCLUDE_DIR_PATHS ${CMAKE_PREFIX_PATH}/lib64 ${CMAKE_PREFIX_PATH}/lib INCLUDE_DIR_SUFFIXES glib-2.0 glib-2.0/include ) if(GLib_FOUND AND NOT GLib_VERSION) find_file(GLib_CONFIG_HEADER "glibconfig.h" HINTS ${GLib_INCLUDE_DIRS}) mark_as_advanced(GLib_CONFIG_HEADER) if(GLib_CONFIG_HEADER) file(STRINGS "${GLib_CONFIG_HEADER}" GLib_MAJOR_VERSION REGEX "^#define GLIB_MAJOR_VERSION +([0-9]+)") string(REGEX REPLACE "^#define GLIB_MAJOR_VERSION ([0-9]+)$" "\\1" GLib_MAJOR_VERSION "${GLib_MAJOR_VERSION}") file(STRINGS "${GLib_CONFIG_HEADER}" GLib_MINOR_VERSION REGEX "^#define GLIB_MINOR_VERSION +([0-9]+)") string(REGEX REPLACE "^#define GLIB_MINOR_VERSION ([0-9]+)$" "\\1" GLib_MINOR_VERSION "${GLib_MINOR_VERSION}") file(STRINGS "${GLib_CONFIG_HEADER}" GLib_MICRO_VERSION REGEX "^#define GLIB_MICRO_VERSION +([0-9]+)") string(REGEX REPLACE "^#define GLIB_MICRO_VERSION ([0-9]+)$" "\\1" GLib_MICRO_VERSION "${GLib_MICRO_VERSION}") set(GLib_VERSION "${GLib_MAJOR_VERSION}.${GLib_MINOR_VERSION}.${GLib_MICRO_VERSION}") unset(GLib_MAJOR_VERSION) unset(GLib_MINOR_VERSION) unset(GLib_MICRO_VERSION) endif() endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(GLib REQUIRED_VARS GLib_LIBRARY VERSION_VAR GLib_VERSION)dino-0.3.0/cmake/FindGModule.cmake0000644000000000000000000000101214202022370015342 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(GModule PKG_CONFIG_NAME gmodule-2.0 LIB_NAMES gmodule-2.0 INCLUDE_NAMES gmodule.h INCLUDE_DIR_SUFFIXES glib-2.0 glib-2.0/include DEPENDS GLib ) if(GModule_FOUND AND NOT GModule_VERSION) # TODO find_package(GLib ${GLib_GLOBAL_VERSION}) set(GModule_VERSION ${GLib_VERSION}) endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(GModule REQUIRED_VARS GModule_LIBRARY VERSION_VAR GModule_VERSION)dino-0.3.0/cmake/FindGObject.cmake0000644000000000000000000000102214202022370015324 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(GObject PKG_CONFIG_NAME gobject-2.0 LIB_NAMES gobject-2.0 INCLUDE_NAMES gobject/gobject.h INCLUDE_DIR_SUFFIXES glib-2.0 glib-2.0/include DEPENDS GLib ) if(GObject_FOUND AND NOT GObject_VERSION) # TODO find_package(GLib ${GLib_GLOBAL_VERSION}) set(GObject_VERSION ${GLib_VERSION}) endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(GObject REQUIRED_VARS GObject_LIBRARY VERSION_VAR GObject_VERSION)dino-0.3.0/cmake/FindGPGME.cmake0000644000000000000000000000045214202022370014654 0ustar rootrootinclude(PkgConfigWithFallbackOnConfigScript) find_pkg_config_with_fallback_on_config_script(GPGME PKG_CONFIG_NAME gpgme CONFIG_SCRIPT_NAME gpgme ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(GPGME REQUIRED_VARS GPGME_LIBRARY VERSION_VAR GPGME_VERSION) dino-0.3.0/cmake/FindGTK3.cmake0000644000000000000000000000276414202022370014535 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(GTK3 PKG_CONFIG_NAME gtk+-3.0 LIB_NAMES gtk-3 INCLUDE_NAMES gtk/gtk.h INCLUDE_DIR_SUFFIXES gtk-3.0 gtk-3.0/include gtk+-3.0 gtk+-3.0/include DEPENDS GDK3 ATK ) if(GTK3_FOUND AND NOT GTK3_VERSION) find_file(GTK3_VERSION_HEADER "gtk/gtkversion.h" HINTS ${GTK3_INCLUDE_DIRS}) mark_as_advanced(GTK3_VERSION_HEADER) if(GTK3_VERSION_HEADER) file(STRINGS "${GTK3_VERSION_HEADER}" GTK3_MAJOR_VERSION REGEX "^#define GTK_MAJOR_VERSION +\\(?([0-9]+)\\)?$") string(REGEX REPLACE "^#define GTK_MAJOR_VERSION \\(?([0-9]+)\\)?$" "\\1" GTK3_MAJOR_VERSION "${GTK3_MAJOR_VERSION}") file(STRINGS "${GTK3_VERSION_HEADER}" GTK3_MINOR_VERSION REGEX "^#define GTK_MINOR_VERSION +\\(?([0-9]+)\\)?$") string(REGEX REPLACE "^#define GTK_MINOR_VERSION \\(?([0-9]+)\\)?$" "\\1" GTK3_MINOR_VERSION "${GTK3_MINOR_VERSION}") file(STRINGS "${GTK3_VERSION_HEADER}" GTK3_MICRO_VERSION REGEX "^#define GTK_MICRO_VERSION +\\(?([0-9]+)\\)?$") string(REGEX REPLACE "^#define GTK_MICRO_VERSION \\(?([0-9]+)\\)?$" "\\1" GTK3_MICRO_VERSION "${GTK3_MICRO_VERSION}") set(GTK3_VERSION "${GTK3_MAJOR_VERSION}.${GTK3_MINOR_VERSION}.${GTK3_MICRO_VERSION}") unset(GTK3_MAJOR_VERSION) unset(GTK3_MINOR_VERSION) unset(GTK3_MICRO_VERSION) endif() endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(GTK3 REQUIRED_VARS GTK3_LIBRARY VERSION_VAR GTK3_VERSION) dino-0.3.0/cmake/FindGee.cmake0000644000000000000000000000053214202022370014514 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(Gee PKG_CONFIG_NAME gee-0.8 LIB_NAMES gee-0.8 INCLUDE_NAMES gee.h INCLUDE_DIR_SUFFIXES gee-0.8 gee-0.8/include DEPENDS GObject ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Gee REQUIRED_VARS Gee_LIBRARY VERSION_VAR Gee_VERSION)dino-0.3.0/cmake/FindGettext.cmake0000644000000000000000000000160514202022370015442 0ustar rootrootfind_program(XGETTEXT_EXECUTABLE xgettext) find_program(MSGMERGE_EXECUTABLE msgmerge) find_program(MSGFMT_EXECUTABLE msgfmt) find_program(MSGCAT_EXECUTABLE msgcat) mark_as_advanced(XGETTEXT_EXECUTABLE MSGMERGE_EXECUTABLE MSGFMT_EXECUTABLE MSGCAT_EXECUTABLE) if(XGETTEXT_EXECUTABLE) execute_process(COMMAND ${XGETTEXT_EXECUTABLE} "--version" OUTPUT_VARIABLE Gettext_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE) string(REGEX REPLACE "xgettext \\(GNU gettext-tools\\) ([0-9\\.]*).*" "\\1" Gettext_VERSION "${Gettext_VERSION}") endif(XGETTEXT_EXECUTABLE) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Gettext FOUND_VAR Gettext_FOUND REQUIRED_VARS XGETTEXT_EXECUTABLE MSGMERGE_EXECUTABLE MSGFMT_EXECUTABLE MSGCAT_EXECUTABLE VERSION_VAR Gettext_VERSION) set(GETTEXT_USE_FILE "${CMAKE_CURRENT_LIST_DIR}/UseGettext.cmake")dino-0.3.0/cmake/FindGnuTLS.cmake0000644000000000000000000000055114202022370015131 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(GnuTLS PKG_CONFIG_NAME gnutls LIB_NAMES gnutls INCLUDE_NAMES gnutls/gnutls.h INCLUDE_DIR_SUFFIXES gnutls gnutls/include DEPENDS GLib ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(GnuTLS REQUIRED_VARS GnuTLS_LIBRARY VERSION_VAR GnuTLS_VERSION)dino-0.3.0/cmake/FindGspell.cmake0000644000000000000000000000055214202022370015244 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(Gspell PKG_CONFIG_NAME gspell-1 LIB_NAMES gspell-1 INCLUDE_NAMES gspell.h INCLUDE_DIR_SUFFIXES gspell-1 gspell-1/gspell DEPENDS Gtk ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Gspell REQUIRED_VARS Gspell_LIBRARY VERSION_VAR Gspell_VERSION) dino-0.3.0/cmake/FindGst.cmake0000644000000000000000000000054314202022370014553 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(Gst PKG_CONFIG_NAME gstreamer-1.0 LIB_NAMES gstreamer-1.0 INCLUDE_NAMES gst/gst.h INCLUDE_DIR_SUFFIXES gstreamer-1.0 gstreamer-1.0/include ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Gst REQUIRED_VARS Gst_LIBRARY VERSION_VAR Gst_VERSION) dino-0.3.0/cmake/FindGstApp.cmake0000644000000000000000000000071414202022370015214 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(GstApp PKG_CONFIG_NAME gstreamer-app-1.0 LIB_NAMES gstapp LIB_DIR_HINTS gstreamer-1.0 INCLUDE_NAMES gst/app/app.h INCLUDE_DIR_SUFFIXES gstreamer-1.0 gstreamer-1.0/include gstreamer-app-1.0 gstreamer-app-1.0/include DEPENDS Gst ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(GstApp REQUIRED_VARS GstApp_LIBRARY VERSION_VAR GstApp_VERSION) dino-0.3.0/cmake/FindGstAudio.cmake0000644000000000000000000000074014202022370015534 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(GstAudio PKG_CONFIG_NAME gstreamer-audio-1.0 LIB_NAMES gstaudio LIB_DIR_HINTS gstreamer-1.0 INCLUDE_NAMES gst/audio/audio.h INCLUDE_DIR_SUFFIXES gstreamer-1.0 gstreamer-1.0/include gstreamer-audio-1.0 gstreamer-audio-1.0/include DEPENDS Gst ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(GstAudio REQUIRED_VARS GstAudio_LIBRARY VERSION_VAR GstAudio_VERSION) dino-0.3.0/cmake/FindGstRtp.cmake0000644000000000000000000000107214202022370015237 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(GstRtp PKG_CONFIG_NAME gstreamer-rtp-1.0 LIB_NAMES gstrtp LIB_DIR_HINTS gstreamer-1.0 INCLUDE_NAMES gst/rtp/rtp.h INCLUDE_DIR_SUFFIXES gstreamer-1.0 gstreamer-1.0/include gstreamer-rtp-1.0 gstreamer-rtp-1.0/include DEPENDS Gst ) if(GstRtp_FOUND AND NOT GstRtp_VERSION) find_package(Gst) set(GstRtp_VERSION ${Gst_VERSION}) endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(GstRtp REQUIRED_VARS GstRtp_LIBRARY VERSION_VAR GstRtp_VERSION) dino-0.3.0/cmake/FindGstVideo.cmake0000644000000000000000000000074014202022370015541 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(GstVideo PKG_CONFIG_NAME gstreamer-video-1.0 LIB_NAMES gstvideo LIB_DIR_HINTS gstreamer-1.0 INCLUDE_NAMES gst/video/video.h INCLUDE_DIR_SUFFIXES gstreamer-1.0 gstreamer-1.0/include gstreamer-video-1.0 gstreamer-video-1.0/include DEPENDS Gst ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(GstVideo REQUIRED_VARS GstVideo_LIBRARY VERSION_VAR GstVideo_VERSION) dino-0.3.0/cmake/FindICU.cmake0000644000000000000000000000045014202022370014433 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(ICU PKG_CONFIG_NAME icu-uc LIB_NAMES icuuc icudata INCLUDE_NAMES unicode/umachine.h ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(ICU REQUIRED_VARS ICU_LIBRARY VERSION_VAR ICU_VERSION) dino-0.3.0/cmake/FindNice.cmake0000644000000000000000000000052014202022370014667 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(Nice PKG_CONFIG_NAME nice LIB_NAMES nice INCLUDE_NAMES nice.h INCLUDE_DIR_SUFFIXES nice nice/include DEPENDS GIO ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Nice REQUIRED_VARS Nice_LIBRARY VERSION_VAR Nice_VERSION) dino-0.3.0/cmake/FindPango.cmake0000644000000000000000000000306114202022370015060 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(Pango PKG_CONFIG_NAME pango LIB_NAMES pango-1.0 INCLUDE_NAMES pango/pango.h INCLUDE_DIR_SUFFIXES pango-1.0 pango-1.0/include DEPENDS GObject ) if(Pango_FOUND AND NOT Pango_VERSION) find_file(Pango_FEATURES_HEADER "pango/pango-features.h" HINTS ${Pango_INCLUDE_DIRS}) mark_as_advanced(Pango_FEATURES_HEADER) if(Pango_FEATURES_HEADER) file(STRINGS "${Pango_FEATURES_HEADER}" Pango_MAJOR_VERSION REGEX "^#define PANGO_VERSION_MAJOR +\\(?([0-9]+)\\)?$") string(REGEX REPLACE "^#define PANGO_VERSION_MAJOR \\(?([0-9]+)\\)?$" "\\1" Pango_MAJOR_VERSION "${Pango_MAJOR_VERSION}") file(STRINGS "${Pango_FEATURES_HEADER}" Pango_MINOR_VERSION REGEX "^#define PANGO_VERSION_MINOR +\\(?([0-9]+)\\)?$") string(REGEX REPLACE "^#define PANGO_VERSION_MINOR \\(?([0-9]+)\\)?$" "\\1" Pango_MINOR_VERSION "${Pango_MINOR_VERSION}") file(STRINGS "${Pango_FEATURES_HEADER}" Pango_MICRO_VERSION REGEX "^#define PANGO_VERSION_MICRO +\\(?([0-9]+)\\)?$") string(REGEX REPLACE "^#define PANGO_VERSION_MICRO \\(?([0-9]+)\\)?$" "\\1" Pango_MICRO_VERSION "${Pango_MICRO_VERSION}") set(Pango_VERSION "${Pango_MAJOR_VERSION}.${Pango_MINOR_VERSION}.${Pango_MICRO_VERSION}") unset(Pango_MAJOR_VERSION) unset(Pango_MINOR_VERSION) unset(Pango_MICRO_VERSION) endif() endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Pango FOUND_VAR Pango_FOUND REQUIRED_VARS Pango_LIBRARY VERSION_VAR Pango_VERSION )dino-0.3.0/cmake/FindQrencode.cmake0000644000000000000000000000046414202022370015560 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(Qrencode PKG_CONFIG_NAME libqrencode LIB_NAMES qrencode INCLUDE_NAMES qrencode.h ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Qrencode REQUIRED_VARS Qrencode_LIBRARY VERSION_VAR Qrencode_VERSION) dino-0.3.0/cmake/FindSQLite3.cmake0000644000000000000000000000135514202022370015244 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(SQLite3 PKG_CONFIG_NAME sqlite3 LIB_NAMES sqlite3 INCLUDE_NAMES sqlite3.h ) if(SQLite3_FOUND AND NOT SQLite3_VERSION) find_file(SQLite3_HEADER "sqlite3.h" HINTS ${SQLite3_INCLUDE_DIRS}) mark_as_advanced(SQLite3_HEADER) if(SQLite3_HEADER) file(STRINGS "${SQLite3_HEADER}" SQLite3_VERSION REGEX "^#define SQLITE_VERSION +\\\"[^\\\"]+\\\"") string(REGEX REPLACE "^#define SQLITE_VERSION +\\\"([0-9]+)\\.([0-9]+)\\.([0-9]+)\\\"$" "\\1.\\2.\\3" SQLite3_VERSION "${SQLite3_VERSION}") endif() endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(SQLite3 REQUIRED_VARS SQLite3_LIBRARY VERSION_VAR SQLite3_VERSION)dino-0.3.0/cmake/FindSignalProtocol.cmake0000644000000000000000000000055414202022370016757 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(SignalProtocol PKG_CONFIG_NAME libsignal-protocol-c LIB_NAMES signal-protocol-c INCLUDE_NAMES signal/signal_protocol.h ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(SignalProtocol REQUIRED_VARS SignalProtocol_LIBRARY VERSION_VAR SignalProtocol_VERSION) dino-0.3.0/cmake/FindSoup.cmake0000644000000000000000000000301414202022370014740 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(Soup PKG_CONFIG_NAME libsoup-2.4 LIB_NAMES soup-2.4 INCLUDE_NAMES libsoup/soup.h INCLUDE_DIR_SUFFIXES libsoup-2.4 libsoup-2.4/include libsoup libsoup/include DEPENDS GIO ) if(Soup_FOUND AND NOT Soup_VERSION) find_file(Soup_VERSION_HEADER "libsoup/soup-version.h" HINTS ${Soup_INCLUDE_DIRS}) mark_as_advanced(Soup_VERSION_HEADER) if(Soup_VERSION_HEADER) file(STRINGS "${Soup_VERSION_HEADER}" Soup_MAJOR_VERSION REGEX "^#define SOUP_MAJOR_VERSION +\\(?([0-9]+)\\)?$") string(REGEX REPLACE "^#define SOUP_MAJOR_VERSION \\(?([0-9]+)\\)?$" "\\1" Soup_MAJOR_VERSION "${Soup_MAJOR_VERSION}") file(STRINGS "${Soup_VERSION_HEADER}" Soup_MINOR_VERSION REGEX "^#define SOUP_MINOR_VERSION +\\(?([0-9]+)\\)?$") string(REGEX REPLACE "^#define SOUP_MINOR_VERSION \\(?([0-9]+)\\)?$" "\\1" Soup_MINOR_VERSION "${Soup_MINOR_VERSION}") file(STRINGS "${Soup_VERSION_HEADER}" Soup_MICRO_VERSION REGEX "^#define SOUP_MICRO_VERSION +\\(?([0-9]+)\\)?$") string(REGEX REPLACE "^#define SOUP_MICRO_VERSION \\(?([0-9]+)\\)?$" "\\1" Soup_MICRO_VERSION "${Soup_MICRO_VERSION}") set(Soup_VERSION "${Soup_MAJOR_VERSION}.${Soup_MINOR_VERSION}.${Soup_MICRO_VERSION}") unset(Soup_MAJOR_VERSION) unset(Soup_MINOR_VERSION) unset(Soup_MICRO_VERSION) endif() endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Soup REQUIRED_VARS Soup_LIBRARY VERSION_VAR Soup_VERSION) dino-0.3.0/cmake/FindSrtp2.cmake0000644000000000000000000000052014202022370015023 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(Srtp2 PKG_CONFIG_NAME libsrtp2 LIB_NAMES srtp2 INCLUDE_NAMES srtp2/srtp.h INCLUDE_DIR_SUFFIXES srtp2 srtp2/include ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Srtp2 REQUIRED_VARS Srtp2_LIBRARY VERSION_VAR Srtp2_VERSION)dino-0.3.0/cmake/FindVala.cmake0000644000000000000000000000625214202022370014704 0ustar rootroot## # Find module for the Vala compiler (valac) # # This module determines wheter a Vala compiler is installed on the current # system and where its executable is. # # Call the module using "find_package(Vala) from within your CMakeLists.txt. # # The following variables will be set after an invocation: # # VALA_FOUND Whether the vala compiler has been found or not # VALA_EXECUTABLE Full path to the valac executable if it has been found # VALA_VERSION Version number of the available valac # VALA_USE_FILE Include this file to define the vala_precompile function ## ## # Copyright 2009-2010 Jakob Westhoff. All rights reserved. # Copyright 2010-2011 Daniel Pfeifer # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY JAKOB WESTHOFF ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO # EVENT SHALL JAKOB WESTHOFF OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # The views and conclusions contained in the software and documentation are those # of the authors and should not be interpreted as representing official policies, # either expressed or implied, of Jakob Westhoff ## # Search for the valac executable in the usual system paths # Some distributions rename the valac to contain the major.minor in the binary name find_package(GObject REQUIRED) find_program(VALA_EXECUTABLE NAMES valac valac-0.38 valac-0.36 valac-0.34 valac-0.32) mark_as_advanced(VALA_EXECUTABLE) # Determine the valac version if(VALA_EXECUTABLE) file(TO_NATIVE_PATH "${VALA_EXECUTABLE}" VALA_EXECUTABLE) execute_process(COMMAND ${VALA_EXECUTABLE} "--version" OUTPUT_VARIABLE VALA_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE) string(REPLACE "Vala " "" VALA_VERSION "${VALA_VERSION}") endif(VALA_EXECUTABLE) # Handle the QUIETLY and REQUIRED arguments, which may be given to the find call. # Furthermore set VALA_FOUND to TRUE if Vala has been found (aka. # VALA_EXECUTABLE is set) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Vala FOUND_VAR VALA_FOUND REQUIRED_VARS VALA_EXECUTABLE VERSION_VAR VALA_VERSION) set(VALA_USE_FILE "${CMAKE_CURRENT_LIST_DIR}/UseVala.cmake") dino-0.3.0/cmake/FindWebRTCAudioProcessing.cmake0000644000000000000000000000077414202022370020131 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(WebRTCAudioProcessing PKG_CONFIG_NAME webrtc-audio-processing LIB_NAMES webrtc_audio_processing INCLUDE_NAMES webrtc/modules/audio_processing/include/audio_processing.h INCLUDE_DIR_SUFFIXES webrtc-audio-processing webrtc_audio_processing ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(WebRTCAudioProcessing REQUIRED_VARS WebRTCAudioProcessing_LIBRARY VERSION_VAR WebRTCAudioProcessing_VERSION) dino-0.3.0/cmake/GenerateGXML.cmake0000644000000000000000000001240114202022370015433 0ustar rootrootinclude(CMakeParseArguments) # Generates the resource XML controlling file from resource list (and saves it # to xml_path). It's not recommended to use this function directly, since it # doesn't handle invalid arguments. It is used by the function # COMPILE_GRESOURCES() to create a custom command, so that this function is # invoked at build-time in script mode from CMake. function(GENERATE_GXML xml_path) # Available options: # COMPRESS_ALL, NO_COMPRESS_ALL Overrides the COMPRESS flag in all # registered resources. # STRIPBLANKS_ALL, NO_STRIPBLANKS_ALL Overrides the STRIPBLANKS flag in all # registered resources. # TOPIXDATA_ALL, NO_TOPIXDATA_ALL Overrides the TOPIXDATA flag in all # registered resources. set(GXML_OPTIONS COMPRESS_ALL NO_COMPRESS_ALL STRIPBLANKS_ALL NO_STRIPBLANKS_ALL TOPIXDATA_ALL NO_TOPIXDATA_ALL) # Available one value options: # PREFIX Overrides the resource prefix that is prepended to each # relative file name in registered resources. set(GXML_ONEVALUEARGS PREFIX) # Available multi-value options: # RESOURCES The list of resource files. Whether absolute or relative path is # equal, absolute paths are stripped down to relative ones. If the # absolute path is not inside the given base directory SOURCE_DIR # or CMAKE_SOURCE_DIR (if SOURCE_DIR is not overriden), this # function aborts. set(GXML_MULTIVALUEARGS RESOURCES) # Parse the arguments. cmake_parse_arguments(GXML_ARG "${GXML_OPTIONS}" "${GXML_ONEVALUEARGS}" "${GXML_MULTIVALUEARGS}" "${ARGN}") # Variable to store the double-quote (") string. Since escaping # double-quotes in strings is not possible we need a helper variable that # does this job for us. set(Q \") # Process resources and generate XML file. # Begin with the XML header and header nodes. set(GXML_XML_FILE "") set(GXML_XML_FILE "${GXML_XML_FILE}") # Process each resource. foreach(res ${GXML_ARG_RESOURCES}) if ("${res}" STREQUAL "COMPRESS") set(GXML_COMPRESSION_FLAG ON) elseif ("${res}" STREQUAL "STRIPBLANKS") set(GXML_STRIPBLANKS_FLAG ON) elseif ("${res}" STREQUAL "TOPIXDATA") set(GXML_TOPIXDATA_FLAG ON) else() # The file name. set(GXML_RESOURCE_PATH "${res}") # Append to real resource file dependency list. list(APPEND GXML_RESOURCES_DEPENDENCIES ${GXML_RESOURCE_PATH}) # Assemble node. set(GXML_RES_LINE "${GXML_RESOURCE_PATH}") # Append to file string. set(GXML_XML_FILE "${GXML_XML_FILE}${GXML_RES_LINE}") # Unset variables. unset(GXML_COMPRESSION_FLAG) unset(GXML_STRIPBLANKS_FLAG) unset(GXML_TOPIXDATA_FLAG) endif() endforeach() # Append closing nodes. set(GXML_XML_FILE "${GXML_XML_FILE}") # Use "file" function to generate XML controlling file. get_filename_component(xml_path_only_name "${xml_path}" NAME) file(WRITE ${xml_path} ${GXML_XML_FILE}) endfunction() dino-0.3.0/cmake/GlibCompileResourcesSupport.cmake0000644000000000000000000000062414202022370020673 0ustar rootroot# Path to this file. set(GCR_CMAKE_MACRO_DIR ${CMAKE_CURRENT_LIST_DIR}) # Finds the glib-compile-resources executable. find_program(GLIB_COMPILE_RESOURCES_EXECUTABLE glib-compile-resources) mark_as_advanced(GLIB_COMPILE_RESOURCES_EXECUTABLE) # Include the cmake files containing the functions. include(${GCR_CMAKE_MACRO_DIR}/CompileGResources.cmake) include(${GCR_CMAKE_MACRO_DIR}/GenerateGXML.cmake) dino-0.3.0/cmake/LargeFileOffsets.c0000644000000000000000000000043714202022370015545 0ustar rootroot#include #define _K ((off_t)1024) #define _M ((off_t)1024 * _K) #define _G ((off_t)1024 * _M) #define _T ((off_t)1024 * _G) int test[(((64 * _G -1) % 671088649) == 268434537) && (((_T - (64 * _G -1) + 255) % 1792151290) == 305159546)? 1: -1]; int main() { return 0; }dino-0.3.0/cmake/MultiFind.cmake0000644000000000000000000000277614202022370015122 0ustar rootrootinclude(CMakeParseArguments) function(find_packages result) cmake_parse_arguments(ARGS "" "" "REQUIRED;OPTIONAL" ${ARGN}) set(_res "") set(_res_libs "") foreach(pkg ${ARGS_REQUIRED}) string(REPLACE ">=" ";" pkg_ ${pkg}) list(GET pkg_ "0" pkg) list(LENGTH pkg_ pkg_has_version) if(pkg_has_version GREATER 1) list(GET pkg_ "1" pkg_version) else() if(${pkg}_GLOBAL_VERSION) set(pkg_version ${${pkg}_GLOBAL_VERSION}) else() unset(pkg_version) endif() endif() find_package(${pkg} ${pkg_version} REQUIRED) list(APPEND _res ${${pkg}_PKG_CONFIG_NAME}) list(APPEND _res_libs ${${pkg}_LIBRARIES}) endforeach(pkg) foreach(pkg ${ARGS_OPTIONAL}) string(REPLACE ">=" ";" pkg_ ${pkg}) list(GET pkg_ "0" pkg) list(LENGTH pkg_ pkg_has_version) if(pkg_has_version GREATER 1) list(GET pkg_ "1" pkg_version) else() if(${pkg}_GLOBAL_VERSION) set(pkg_version ${${pkg}_GLOBAL_VERSION}) else() unset(pkg_version) endif() endif() find_package(${pkg} ${pkg_version}) if(${pkg}_FOUND) list(APPEND _res ${${pkg}_PKG_CONFIG_NAME}) list(APPEND _res_libs ${${pkg}_LIBRARIES}) endif() endforeach(pkg) set(${result} "${_res}" PARENT_SCOPE) set(${result}_LIBS "${_res_libs}" PARENT_SCOPE) endfunction() dino-0.3.0/cmake/PkgConfigWithFallback.cmake0000644000000000000000000001162614202022370017344 0ustar rootrootinclude(CMakeParseArguments) function(find_pkg_config_with_fallback name) cmake_parse_arguments(ARGS "" "PKG_CONFIG_NAME" "LIB_NAMES;LIB_DIR_HINTS;INCLUDE_NAMES;INCLUDE_DIR_PATHS;INCLUDE_DIR_HINTS;INCLUDE_DIR_SUFFIXES;DEPENDS" ${ARGN}) set(${name}_PKG_CONFIG_NAME ${ARGS_PKG_CONFIG_NAME} PARENT_SCOPE) find_package(PkgConfig) if(PKG_CONFIG_FOUND) pkg_search_module(${name}_PKG_CONFIG QUIET ${ARGS_PKG_CONFIG_NAME}) endif(PKG_CONFIG_FOUND) if (${name}_PKG_CONFIG_FOUND) # Found via pkg-config, using its result values set(${name}_FOUND ${${name}_PKG_CONFIG_FOUND}) # Try to find real file name of libraries foreach(lib ${${name}_PKG_CONFIG_LIBRARIES}) find_library(${name}_${lib}_LIBRARY ${lib} HINTS ${${name}_PKG_CONFIG_LIBRARY_DIRS}) mark_as_advanced(${name}_${lib}_LIBRARY) if(NOT ${name}_${lib}_LIBRARY) unset(${name}_FOUND) endif(NOT ${name}_${lib}_LIBRARY) endforeach(lib) if(${name}_FOUND) set(${name}_LIBRARIES "") foreach(lib ${${name}_PKG_CONFIG_LIBRARIES}) list(APPEND ${name}_LIBRARIES ${${name}_${lib}_LIBRARY}) endforeach(lib) list(REMOVE_DUPLICATES ${name}_LIBRARIES) set(${name}_LIBRARIES ${${name}_LIBRARIES} PARENT_SCOPE) list(GET ${name}_LIBRARIES "0" ${name}_LIBRARY) set(${name}_FOUND ${${name}_FOUND} PARENT_SCOPE) set(${name}_INCLUDE_DIRS ${${name}_PKG_CONFIG_INCLUDE_DIRS} PARENT_SCOPE) set(${name}_LIBRARIES ${${name}_PKG_CONFIG_LIBRARIES} PARENT_SCOPE) set(${name}_LIBRARY ${${name}_LIBRARY} PARENT_SCOPE) set(${name}_VERSION ${${name}_PKG_CONFIG_VERSION} PARENT_SCOPE) if(NOT TARGET ${ARGS_PKG_CONFIG_NAME}) add_library(${ARGS_PKG_CONFIG_NAME} INTERFACE IMPORTED) set_property(TARGET ${ARGS_PKG_CONFIG_NAME} PROPERTY INTERFACE_COMPILE_OPTIONS "${${name}_PKG_CONFIG_CFLAGS_OTHER}") set_property(TARGET ${ARGS_PKG_CONFIG_NAME} PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${${name}_PKG_CONFIG_INCLUDE_DIRS}") set_property(TARGET ${ARGS_PKG_CONFIG_NAME} PROPERTY INTERFACE_LINK_LIBRARIES "${${name}_LIBRARIES}") endif(NOT TARGET ${ARGS_PKG_CONFIG_NAME}) endif(${name}_FOUND) else(${name}_PKG_CONFIG_FOUND) # No success with pkg-config, try via find_library on all lib_names set(${name}_FOUND "1") foreach(lib ${ARGS_LIB_NAMES}) find_library(${name}_${lib}_LIBRARY ${ARGS_LIB_NAMES} HINTS ${ARGS_LIB_DIR_HINTS}) mark_as_advanced(${name}_${lib}_LIBRARY) if(NOT ${name}_${lib}_LIBRARY) unset(${name}_FOUND) endif(NOT ${name}_${lib}_LIBRARY) endforeach(lib) foreach(inc ${ARGS_INCLUDE_NAMES}) find_path(${name}_${inc}_INCLUDE_PATH ${inc} HINTS ${ARGS_INCLUDE_DIR_HINTS} PATHS ${ARGS_INCLUDE_DIR_PATHS} PATH_SUFFIXES ${ARGS_INCLUDE_DIR_SUFFIXES}) mark_as_advanced(${name}_${inc}_INCLUDE_PATH) if(NOT ${name}_${inc}_INCLUDE_PATH) unset(${name}_FOUND) endif(NOT ${name}_${inc}_INCLUDE_PATH) endforeach(inc) if(${name}_FOUND) set(${name}_LIBRARIES "") set(${name}_INCLUDE_DIRS "") foreach(lib ${ARGS_LIB_NAMES}) list(APPEND ${name}_LIBRARIES ${${name}_${lib}_LIBRARY}) endforeach(lib) foreach(inc ${ARGS_INCLUDE_NAMES}) list(APPEND ${name}_INCLUDE_DIRS ${${name}_${inc}_INCLUDE_PATH}) endforeach(inc) list(GET ${name}_LIBRARIES "0" ${name}_LIBRARY) foreach(dep ${ARGS_DEPENDS}) find_package(${dep} ${${dep}_GLOBAL_VERSION} QUIET) if(${dep}_FOUND) list(APPEND ${name}_INCLUDE_DIRS ${${dep}_INCLUDE_DIRS}) list(APPEND ${name}_LIBRARIES ${${dep}_LIBRARIES}) else(${dep}_FOUND) unset(${name}_FOUND) endif(${dep}_FOUND) endforeach(dep) set(${name}_FOUND ${${name}_FOUND} PARENT_SCOPE) set(${name}_INCLUDE_DIRS ${${name}_INCLUDE_DIRS} PARENT_SCOPE) set(${name}_LIBRARIES ${${name}_LIBRARIES} PARENT_SCOPE) set(${name}_LIBRARY ${${name}_LIBRARY} PARENT_SCOPE) unset(${name}_VERSION PARENT_SCOPE) if(NOT TARGET ${ARGS_PKG_CONFIG_NAME}) add_library(${ARGS_PKG_CONFIG_NAME} INTERFACE IMPORTED) set_property(TARGET ${ARGS_PKG_CONFIG_NAME} PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${${name}_INCLUDE_DIRS}") set_property(TARGET ${ARGS_PKG_CONFIG_NAME} PROPERTY INTERFACE_LINK_LIBRARIES "${${name}_LIBRARIES}") endif(NOT TARGET ${ARGS_PKG_CONFIG_NAME}) endif(${name}_FOUND) endif(${name}_PKG_CONFIG_FOUND) endfunction() dino-0.3.0/cmake/PkgConfigWithFallbackOnConfigScript.cmake0000644000000000000000000001262014202022370022147 0ustar rootrootinclude(CMakeParseArguments) function(find_pkg_config_with_fallback_on_config_script name) cmake_parse_arguments(ARGS "" "PKG_CONFIG_NAME" "CONFIG_SCRIPT_NAME" ${ARGN}) set(${name}_PKG_CONFIG_NAME ${ARGS_PKG_CONFIG_NAME} PARENT_SCOPE) find_package(PkgConfig) if(PKG_CONFIG_FOUND) pkg_search_module(${name}_PKG_CONFIG QUIET ${ARGS_PKG_CONFIG_NAME}) endif(PKG_CONFIG_FOUND) if (${name}_PKG_CONFIG_FOUND) # Found via pkg-config, using it's result values set(${name}_FOUND ${${name}_PKG_CONFIG_FOUND}) # Try to find real file name of libraries foreach(lib ${${name}_PKG_CONFIG_LIBRARIES}) find_library(${name}_${lib}_LIBRARY ${lib} HINTS ${${name}_PKG_CONFIG_LIBRARY_DIRS}) mark_as_advanced(${name}_${lib}_LIBRARY) if(NOT ${name}_${lib}_LIBRARY) unset(${name}_FOUND) endif(NOT ${name}_${lib}_LIBRARY) endforeach(lib) if(${name}_FOUND) set(${name}_LIBRARIES "") foreach(lib ${${name}_PKG_CONFIG_LIBRARIES}) list(APPEND ${name}_LIBRARIES ${${name}_${lib}_LIBRARY}) endforeach(lib) list(REMOVE_DUPLICATES ${name}_LIBRARIES) set(${name}_LIBRARIES ${${name}_LIBRARIES} PARENT_SCOPE) list(GET ${name}_LIBRARIES "0" ${name}_LIBRARY) set(${name}_FOUND ${${name}_FOUND} PARENT_SCOPE) set(${name}_INCLUDE_DIRS ${${name}_PKG_CONFIG_INCLUDE_DIRS} PARENT_SCOPE) set(${name}_LIBRARIES ${${name}_PKG_CONFIG_LIBRARIES} PARENT_SCOPE) set(${name}_LIBRARY ${${name}_LIBRARY} PARENT_SCOPE) set(${name}_VERSION ${${name}_PKG_CONFIG_VERSION} PARENT_SCOPE) if(NOT TARGET ${ARGS_PKG_CONFIG_NAME}) add_library(${ARGS_PKG_CONFIG_NAME} INTERFACE IMPORTED) set_property(TARGET ${ARGS_PKG_CONFIG_NAME} PROPERTY INTERFACE_COMPILE_OPTIONS "${${name}_PKG_CONFIG_CFLAGS_OTHER}") set_property(TARGET ${ARGS_PKG_CONFIG_NAME} PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${${name}_PKG_CONFIG_INCLUDE_DIRS}") set_property(TARGET ${ARGS_PKG_CONFIG_NAME} PROPERTY INTERFACE_LINK_LIBRARIES "${${name}_LIBRARIES}") endif(NOT TARGET ${ARGS_PKG_CONFIG_NAME}) endif(${name}_FOUND) else(${name}_PKG_CONFIG_FOUND) # No success with pkg-config, try via custom a *-config script find_program(${name}_CONFIG_EXECUTABLE NAMES ${ARGS_CONFIG_SCRIPT_NAME}-config) mark_as_advanced(${name}_CONFIG_EXECUTABLE) find_program(${name}_SH_EXECUTABLE NAMES sh) mark_as_advanced(${name}_SH_EXECUTABLE) if(${name}_CONFIG_EXECUTABLE) macro(config_script_fail errcode) if(${errcode}) message(FATAL_ERROR "Error invoking ${ARGS_CONFIG_SCRIPT_NAME}-config: ${errcode}") endif(${errcode}) endmacro(config_script_fail) file(TO_NATIVE_PATH "${${name}_CONFIG_EXECUTABLE}" ${name}_CONFIG_EXECUTABLE) file(TO_NATIVE_PATH "${${name}_SH_EXECUTABLE}" ${name}_SH_EXECUTABLE) execute_process(COMMAND "${${name}_SH_EXECUTABLE}" "${${name}_CONFIG_EXECUTABLE}" --version OUTPUT_VARIABLE ${name}_VERSION RESULT_VARIABLE ERRCODE OUTPUT_STRIP_TRAILING_WHITESPACE) config_script_fail(${ERRCODE}) execute_process(COMMAND "${${name}_SH_EXECUTABLE}" "${${name}_CONFIG_EXECUTABLE}" --api-version OUTPUT_VARIABLE ${name}_API_VERSION RESULT_VARIABLE ERRCODE OUTPUT_STRIP_TRAILING_WHITESPACE) config_script_fail(${ERRCODE}) execute_process(COMMAND "${${name}_SH_EXECUTABLE}" "${${name}_CONFIG_EXECUTABLE}" --cflags OUTPUT_VARIABLE ${name}_CFLAGS RESULT_VARIABLE ERRCODE OUTPUT_STRIP_TRAILING_WHITESPACE) config_script_fail(${ERRCODE}) execute_process(COMMAND "${${name}_SH_EXECUTABLE}" "${${name}_CONFIG_EXECUTABLE}" --libs OUTPUT_VARIABLE ${name}_LDFLAGS RESULT_VARIABLE ERRCODE OUTPUT_STRIP_TRAILING_WHITESPACE) config_script_fail(${ERRCODE}) string(TOLOWER ${name} "${name}_LOWER") string(REGEX REPLACE "^(.* |)-l([^ ]*${${name}_LOWER}[^ ]*)( .*|)$" "\\2" ${name}_LIBRARY_NAME "${${name}_LDFLAGS}") string(REGEX REPLACE "^(.* |)-L([^ ]*)( .*|)$" "\\2" ${name}_LIBRARY_DIRS "${${name}_LDFLAGS}") find_library(${name}_LIBRARY ${${name}_LIBRARY_NAME} HINTS ${${name}_LIBRARY_DIRS}) mark_as_advanced(${name}_LIBRARY) set(${name}_LIBRARY ${${name}_LIBRARY} PARENT_SCOPE) set(${name}_VERSION ${${name}_VERSION} PARENT_SCOPE) unset(${name}_LIBRARY_NAME) unset(${name}_LIBRARY_DIRS) if(NOT TARGET ${name}_LOWER) add_library(${name}_LOWER INTERFACE IMPORTED) set_property(TARGET ${name}_LOWER PROPERTY INTERFACE_LINK_LIBRARIES "${${name}_LDFLAGS}") set_property(TARGET ${name}_LOWER PROPERTY INTERFACE_COMPILE_OPTIONS "${${name}_CFLAGS}") endif(NOT TARGET ${name}_LOWER) endif(${name}_CONFIG_EXECUTABLE) endif(${name}_PKG_CONFIG_FOUND) endfunction() dino-0.3.0/cmake/UseGettext.cmake0000644000000000000000000000237414202022370015322 0ustar rootrootfunction(_gettext_mkdir_for_file file) get_filename_component(dir "${file}" DIRECTORY) file(MAKE_DIRECTORY "${dir}") endfunction() function(gettext_compile project_name) cmake_parse_arguments(ARGS "" "MO_FILES_NAME;TARGET_NAME;SOURCE_DIR;PROJECT_NAME" "" ${ARGN}) if(NOT ARGS_SOURCE_DIR) set(ARGS_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) endif(NOT ARGS_SOURCE_DIR) file(STRINGS "${ARGS_SOURCE_DIR}/LINGUAS" LINGUAS) set(target_files) foreach(lang ${LINGUAS}) set(source_file ${ARGS_SOURCE_DIR}/${lang}.po) set(target_file ${CMAKE_BINARY_DIR}/locale/${lang}/LC_MESSAGES/${project_name}.mo) _gettext_mkdir_for_file(${target_file}) list(APPEND target_files ${target_file}) add_custom_command(OUTPUT ${target_file} COMMAND ${MSGFMT_EXECUTABLE} --check-format -o ${target_file} ${source_file} DEPENDS ${source_file}) install(FILES ${target_file} DESTINATION ${LOCALE_INSTALL_DIR}/${lang}/LC_MESSAGES) endforeach(lang) if(ARGS_MO_FILES_NAME) set(${ARGS_MO_FILES_NAME} ${target_files} PARENT_SCOPE) endif(ARGS_MO_FILES_NAME) if(ARGS_TARGET_NAME) add_custom_target(${ARGS_TARGET_NAME} DEPENDS ${target_files}) endif(ARGS_TARGET_NAME) endfunction(gettext_compile) dino-0.3.0/cmake/UseVala.cmake0000644000000000000000000003147714202022370014567 0ustar rootroot## # Compile vala files to their c equivalents for further processing. # # The "vala_precompile" function takes care of calling the valac executable on # the given source to produce c files which can then be processed further using # default cmake functions. # # The first parameter provided is a variable, which will be filled with a list # of c files outputted by the vala compiler. This list can than be used in # conjuction with functions like "add_executable" or others to create the # neccessary compile rules with CMake. # # The following sections may be specified afterwards to provide certain options # to the vala compiler: # # SOURCES # A list of .vala files to be compiled. Please take care to add every vala # file belonging to the currently compiled project or library as Vala will # otherwise not be able to resolve all dependencies. # # PACKAGES # A list of vala packages/libraries to be used during the compile cycle. The # package names are exactly the same, as they would be passed to the valac # "--pkg=" option. # # OPTIONS # A list of optional options to be passed to the valac executable. This can be # used to pass "--thread" for example to enable multi-threading support. # # DEFINITIONS # A list of symbols to be used for conditional compilation. They are the same # as they would be passed using the valac "--define=" option. # # CUSTOM_VAPIS # A list of custom vapi files to be included for compilation. This can be # useful to include freshly created vala libraries without having to install # them in the system. # # GENERATE_VAPI # Pass all the needed flags to the compiler to create a vapi for # the compiled library. The provided name will be used for this and a # .vapi file will be created. # # GENERATE_HEADER # Let the compiler generate a header file for the compiled code. There will # be a header file as well as an internal header file being generated called # .h and _internal.h # # The following call is a simple example to the vala_precompile macro showing # an example to every of the optional sections: # # find_package(Vala "0.12" REQUIRED) # include(${VALA_USE_FILE}) # # vala_precompile(VALA_C # SOURCES # source1.vala # source2.vala # source3.vala # PACKAGES # gtk+-2.0 # gio-1.0 # posix # DIRECTORY # gen # OPTIONS # --thread # CUSTOM_VAPIS # some_vapi.vapi # GENERATE_VAPI # myvapi # GENERATE_HEADER # myheader # ) # # Most important is the variable VALA_C which will contain all the generated c # file names after the call. ## ## # Copyright 2009-2010 Jakob Westhoff. All rights reserved. # Copyright 2010-2011 Daniel Pfeifer # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY JAKOB WESTHOFF ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO # EVENT SHALL JAKOB WESTHOFF OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # The views and conclusions contained in the software and documentation are those # of the authors and should not be interpreted as representing official policies, # either expressed or implied, of Jakob Westhoff ## include(CMakeParseArguments) function(_vala_mkdir_for_file file) get_filename_component(dir "${file}" DIRECTORY) file(MAKE_DIRECTORY "${dir}") endfunction() function(vala_precompile output) cmake_parse_arguments(ARGS "FAST_VAPI" "DIRECTORY;GENERATE_HEADER;GENERATE_VAPI;EXPORTS_DIR" "SOURCES;PACKAGES;OPTIONS;DEFINITIONS;CUSTOM_VAPIS;CUSTOM_DEPS;GRESOURCES" ${ARGN}) # Header and internal header is needed to generate internal vapi if (ARGS_GENERATE_VAPI AND NOT ARGS_GENERATE_HEADER) set(ARGS_GENERATE_HEADER ${ARGS_GENERATE_VAPI}) endif(ARGS_GENERATE_VAPI AND NOT ARGS_GENERATE_HEADER) if("Ninja" STREQUAL ${CMAKE_GENERATOR} AND NOT DISABLE_FAST_VAPI AND NOT ARGS_GENERATE_HEADER) set(ARGS_FAST_VAPI true) endif() if(ARGS_DIRECTORY) get_filename_component(DIRECTORY ${ARGS_DIRECTORY} ABSOLUTE) else(ARGS_DIRECTORY) set(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) endif(ARGS_DIRECTORY) if(ARGS_EXPORTS_DIR) set(ARGS_EXPORTS_DIR ${CMAKE_BINARY_DIR}/${ARGS_EXPORTS_DIR}) else(ARGS_EXPORTS_DIR) set(ARGS_EXPORTS_DIR ${CMAKE_BINARY_DIR}/exports) endif(ARGS_EXPORTS_DIR) file(MAKE_DIRECTORY "${ARGS_EXPORTS_DIR}") include_directories(${DIRECTORY} ${ARGS_EXPORTS_DIR}) set(vala_pkg_opts "") foreach(pkg ${ARGS_PACKAGES}) list(APPEND vala_pkg_opts "--pkg=${pkg}") endforeach(pkg ${ARGS_PACKAGES}) set(vala_define_opts "") foreach(def ${ARGS_DEFINITIONS}) list(APPEND vala_define_opts "--define=${def}") endforeach(def ${ARGS_DEFINITIONS}) set(custom_vapi_arguments "") if(ARGS_CUSTOM_VAPIS) foreach(vapi ${ARGS_CUSTOM_VAPIS}) if(${vapi} MATCHES ${CMAKE_SOURCE_DIR} OR ${vapi} MATCHES ${CMAKE_BINARY_DIR}) list(APPEND custom_vapi_arguments ${vapi}) else (${vapi} MATCHES ${CMAKE_SOURCE_DIR} OR ${vapi} MATCHES ${CMAKE_BINARY_DIR}) list(APPEND custom_vapi_arguments ${CMAKE_CURRENT_SOURCE_DIR}/${vapi}) endif(${vapi} MATCHES ${CMAKE_SOURCE_DIR} OR ${vapi} MATCHES ${CMAKE_BINARY_DIR}) endforeach(vapi ${ARGS_CUSTOM_VAPIS}) endif(ARGS_CUSTOM_VAPIS) set(gresources_args "") if(ARGS_GRESOURCES) set(gresources_args --gresources "${ARGS_GRESOURCES}") endif(ARGS_GRESOURCES) set(in_files "") set(fast_vapi_files "") set(out_files "") set(out_extra_files "") set(out_deps_files "") set(vapi_arguments "") if(ARGS_GENERATE_VAPI) list(APPEND out_extra_files "${ARGS_EXPORTS_DIR}/${ARGS_GENERATE_VAPI}.vapi") list(APPEND out_extra_files "${ARGS_EXPORTS_DIR}/${ARGS_GENERATE_VAPI}_internal.vapi") set(vapi_arguments "--vapi=${ARGS_EXPORTS_DIR}/${ARGS_GENERATE_VAPI}.vapi" "--internal-vapi=${ARGS_EXPORTS_DIR}/${ARGS_GENERATE_VAPI}_internal.vapi") if(ARGS_PACKAGES) string(REPLACE ";" "\\n" pkgs "${ARGS_PACKAGES};${ARGS_CUSTOM_DEPS}") add_custom_command(OUTPUT "${ARGS_EXPORTS_DIR}/${ARGS_GENERATE_VAPI}.deps" COMMAND echo -e "\"${pkgs}\"" > "${ARGS_EXPORTS_DIR}/${ARGS_GENERATE_VAPI}.deps" COMMENT "Generating ${ARGS_GENERATE_VAPI}.deps") endif(ARGS_PACKAGES) endif(ARGS_GENERATE_VAPI) set(header_arguments "") if(ARGS_GENERATE_HEADER) list(APPEND out_extra_files "${ARGS_EXPORTS_DIR}/${ARGS_GENERATE_HEADER}.h") list(APPEND out_extra_files "${ARGS_EXPORTS_DIR}/${ARGS_GENERATE_HEADER}_internal.h") list(APPEND header_arguments "--header=${ARGS_EXPORTS_DIR}/${ARGS_GENERATE_HEADER}.h") list(APPEND header_arguments "--internal-header=${ARGS_EXPORTS_DIR}/${ARGS_GENERATE_HEADER}_internal.h") endif(ARGS_GENERATE_HEADER) string(REPLACE " " ";" VALAC_FLAGS ${CMAKE_VALA_FLAGS}) if (VALA_VERSION VERSION_GREATER "0.38") set(VALAC_COLORS "--color=always") endif () if(ARGS_FAST_VAPI) foreach(src ${ARGS_SOURCES} ${ARGS_UNPARSED_ARGUMENTS}) set(in_file "${CMAKE_CURRENT_SOURCE_DIR}/${src}") list(APPEND in_files "${in_file}") string(REPLACE ".vala" ".c" src ${src}) string(REPLACE ".gs" ".c" src ${src}) string(REPLACE ".c" ".vapi" fast_vapi ${src}) set(fast_vapi_file "${DIRECTORY}/${fast_vapi}") list(APPEND fast_vapi_files "${fast_vapi_file}") list(APPEND out_files "${DIRECTORY}/${src}") _vala_mkdir_for_file("${fast_vapi_file}") add_custom_command(OUTPUT ${fast_vapi_file} COMMAND ${VALA_EXECUTABLE} ARGS ${VALAC_COLORS} --fast-vapi ${fast_vapi_file} ${vala_define_opts} ${ARGS_OPTIONS} ${VALAC_FLAGS} ${in_file} DEPENDS ${in_file} COMMENT "Generating fast VAPI ${fast_vapi}" ) endforeach(src ${ARGS_SOURCES} ${ARGS_UNPARSED_ARGUMENTS}) foreach(src ${ARGS_SOURCES} ${ARGS_UNPARSED_ARGUMENTS}) set(in_file "${CMAKE_CURRENT_SOURCE_DIR}/${src}") string(REPLACE ".vala" ".c" c_code ${src}) string(REPLACE ".gs" ".c" c_code ${c_code}) string(REPLACE ".c" ".vapi" fast_vapi ${c_code}) set(my_fast_vapi_file "${DIRECTORY}/${fast_vapi}") set(c_code_file "${DIRECTORY}/${c_code}") set(fast_vapi_flags "") set(fast_vapi_stamp "") foreach(fast_vapi_file ${fast_vapi_files}) if(NOT "${fast_vapi_file}" STREQUAL "${my_fast_vapi_file}") list(APPEND fast_vapi_flags --use-fast-vapi "${fast_vapi_file}") list(APPEND fast_vapi_stamp "${fast_vapi_file}") endif() endforeach(fast_vapi_file) _vala_mkdir_for_file("${fast_vapi_file}") get_filename_component(dir "${c_code_file}" DIRECTORY) add_custom_command(OUTPUT ${c_code_file} COMMAND ${VALA_EXECUTABLE} ARGS ${VALAC_COLORS} "-C" "-d" ${dir} ${vala_pkg_opts} ${vala_define_opts} ${gresources_args} ${ARGS_OPTIONS} ${VALAC_FLAGS} ${fast_vapi_flags} ${in_file} ${custom_vapi_arguments} DEPENDS ${fast_vapi_stamp} ${in_file} ${ARGS_CUSTOM_VAPIS} ${ARGS_GRESOURCES} COMMENT "Generating C source ${c_code}" ) endforeach(src) if(NOT "${out_extra_files}" STREQUAL "") add_custom_command(OUTPUT ${out_extra_files} COMMAND ${VALA_EXECUTABLE} ARGS ${VALAC_COLORS} -C -q --disable-warnings ${header_arguments} ${vapi_arguments} "-b" ${CMAKE_CURRENT_SOURCE_DIR} "-d" ${DIRECTORY} ${vala_pkg_opts} ${vala_define_opts} ${gresources_args} ${ARGS_OPTIONS} ${VALAC_FLAGS} ${in_files} ${custom_vapi_arguments} DEPENDS ${in_files} ${ARGS_CUSTOM_VAPIS} ${ARGS_GRESOURCES} COMMENT "Generating VAPI and headers for target ${output}" ) endif() else(ARGS_FAST_VAPI) foreach(src ${ARGS_SOURCES} ${ARGS_UNPARSED_ARGUMENTS}) set(in_file "${CMAKE_CURRENT_SOURCE_DIR}/${src}") list(APPEND in_files "${in_file}") string(REPLACE ".vala" ".c" src ${src}) string(REPLACE ".gs" ".c" src ${src}) list(APPEND out_files "${DIRECTORY}/${src}") _vala_mkdir_for_file("${fast_vapi_file}") endforeach(src ${ARGS_SOURCES} ${ARGS_UNPARSED_ARGUMENTS}) add_custom_command(OUTPUT ${out_files} ${out_extra_files} COMMAND ${VALA_EXECUTABLE} ARGS ${VALAC_COLORS} -C ${header_arguments} ${vapi_arguments} "-b" ${CMAKE_CURRENT_SOURCE_DIR} "-d" ${DIRECTORY} ${vala_pkg_opts} ${vala_define_opts} ${gresources_args} ${ARGS_OPTIONS} ${VALAC_FLAGS} ${in_files} ${custom_vapi_arguments} DEPENDS ${in_files} ${ARGS_CUSTOM_VAPIS} ${ARGS_GRESOURCES} COMMENT "Generating C code for target ${output}" ) endif(ARGS_FAST_VAPI) set(${output} ${out_files} PARENT_SCOPE) endfunction(vala_precompile) dino-0.3.0/cmake/cmake_uninstall.cmake.in0000644000000000000000000000201414202022370016766 0ustar rootrootif(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") message(FATAL_ERROR "Cannot find install manifest: @CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") endif(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") file(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) string(REGEX REPLACE "\n" ";" files "${files}") foreach(file ${files}) message(STATUS "Uninstalling: $ENV{DESTDIR}${file}") if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") exec_program( "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" OUTPUT_VARIABLE rm_out RETURN_VALUE rm_retval ) if(NOT "${rm_retval}" STREQUAL 0) message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") endif(NOT "${rm_retval}" STREQUAL 0) else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") message(STATUS "File $ENV{DESTDIR}${file} does not exist.") endif(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") endforeach(file) dino-0.3.0/configure0000755000000000000000000002402014202022370013036 0ustar rootroot#!/bin/sh OPTS=`getopt -o "h" --long \ help,fetch-only,no-debug,disable-fast-vapi,with-tests,release,with-libsignal-in-tree,\ enable-plugin:,disable-plugin:,\ prefix:,program-prefix:,exec-prefix:,lib-suffix:,\ bindir:,libdir:,includedir:,datadir:,\ host:,build:,\ sbindir:,sysconfdir:,libexecdir:,localstatedir:,sharedstatedir:,mandir:,infodir:,\ -n './configure' -- "$@"` if [ $? != 0 ] ; then echo "-- Ignoring unrecognized options." >&2 ; fi eval set -- "$OPTS" PREFIX=${PREFIX:-/usr/local} ENABLED_PLUGINS= DISABLED_PLUGINS= BUILD_LIBSIGNAL_IN_TREE= BUILD_TESTS= BUILD_TYPE=Debug DISABLE_FAST_VAPI= LIB_SUFFIX= NO_DEBUG= FETCH_ONLY= EXEC_PREFIX= BINDIR= SBINDIR=n SYSCONFDIR= DATADIR= INCLUDEDIR= LIBDIR= LIBEXECDIR= LOCALSTATEDIR= SHAREDSTATEDIR= MANDIR= INFODIR= help() { cat << EOF Usage: ./configure [OPTION]... Defaults for the options (based on current environment) are specified in brackets. Configuration: -h, --help Print this help and exit --disable-fast-vapi Disable the usage of Vala compilers fast-vapi feature. fast-vapi mode is slower when doing clean builds, but faster when doing incremental builds (during development). --fetch-only Only fetch the files required to run ./configure without network access later and exit. --no-debug Build without debug symbols --release Configure to build an optimized release version --with-libsignal-in-tree Build libsignal-protocol-c in tree and link it statically. --with-tests Also build tests. Plugin configuration: --enable-plugin=PLUGIN Enable compilation of plugin PLUGIN. --disable-plugin=PLUGIN Disable compilation of plugin PLUGIN. Installation directories: --prefix=PREFIX Install architecture-independent files in PREFIX [$PREFIX] --program-prefix=PREFIX Same as --prefix --exec-prefix= Install architecture-dependent files in EPREFIX [PREFIX] --lib-suffix=SUFFIX Append SUFFIX to the directory name for libraries By default, \`make install' will install all the files in \`/usr/local/bin', \`/usr/local/lib' etc. You can specify an installation prefix other than \`/usr/local' using \`--prefix', for instance \`--prefix=\$HOME'. For better control, use the options below. Fine tuning of the installation directories: --bindir=DIR user executables [EPREFIX/bin] --libdir=DIR object code libraries [EPREFIX/lib] --includedir=DIR C header files [PREFIX/include] --datadir=DIR read-only data [PREFIX/share] For compatibility with autotools, these options will be silently ignored: --host, --build, --sbindir, --sysconfdir, --libexecdir, --sharedstatedir, --localstatedir, --mandir, --infodir Some influential environment variables: CC C compiler command CFLAGS C compiler flags PKG_CONFIG_PATH directories to add to pkg-config's search path PKG_CONFIG_LIBDIR path overriding pkg-config's built-in search path USE_CCACHE decide to use ccache when compiling C objects VALAC Vala compiler command VALACFLAGS Vala compiler flags Use these variables to override the choices made by \`configure' or to help it to find libraries and programs with nonstandard names/locations. EOF } while true; do case "$1" in --prefix ) PREFIX="$2"; shift; shift ;; --enable-plugin ) if [ -z "$ENABLED_PLUGINS" ]; then ENABLED_PLUGINS="$2"; else ENABLED_PLUGINS="$ENABLED_PLUGINS;$2"; fi; shift; shift ;; --disable-plugin ) if [ -z "$DISABLED_PLUGINS" ]; then DISABLED_PLUGINS="$2"; else DISABLED_PLUGINS="$DISABLED_PLUGINS;$2"; fi; shift; shift ;; --valac ) VALA_EXECUTABLE="$2"; shift; shift ;; --valac-flags ) VALAC_FLAGS="$2"; shift; shift ;; --lib-suffix ) LIB_SUFFIX="$2"; shift; shift ;; --with-libsignal-in-tree ) BUILD_LIBSIGNAL_IN_TREE=yes; shift ;; --disable-fast-vapi ) DISABLE_FAST_VAPI=yes; shift ;; --no-debug ) NO_DEBUG=yes; shift ;; --fetch-only ) FETCH_ONLY=yes; shift ;; --release ) BUILD_TYPE=RelWithDebInfo; shift ;; --with-tests ) BUILD_TESTS=yes; shift ;; # Autotools paths --program-prefix ) PREFIX="$2"; shift; shift ;; --exec-prefix ) EXEC_PREFIX="$2"; shift; shift ;; --bindir ) BINDIR="$2"; shift; shift ;; --datadir ) DATADIR="$2"; shift; shift ;; --includedir ) INCLUDEDIR="$2"; shift; shift ;; --libdir ) LIBDIR="$2"; shift; shift ;; # Autotools paths not used --sbindir ) SBINDIR="$2"; shift; shift ;; --sysconfdir ) SYSCONFDIR="$2"; shift; shift ;; --libexecdir ) LIBEXECDIR="$2"; shift; shift ;; --localstatedir ) LOCALSTATEDIR="$2"; shift; shift ;; --sharedstatedir ) SHAREDSTATEDIR="$2"; shift; shift ;; --mandir ) MANDIR="$2"; shift; shift ;; --infodir ) INFODIR="$2"; shift; shift ;; --host | --build ) shift; shift ;; -h | --help ) help; exit 0 ;; -- ) shift; break ;; * ) break ;; esac done if [ "$BUILD_LIBSIGNAL_IN_TREE" = "yes" ] || [ "$FETCH_ONLY" = "yes" ]; then if [ -d ".git" ]; then git submodule update --init 2>/dev/null else tmp=0 for i in $(cat .gitmodules | grep -n submodule | awk -F ':' '{print $1}') $(wc -l .gitmodules | awk '{print $1}'); do if ! [ $tmp -eq 0 ]; then name=$(cat .gitmodules | head -n $tmp | tail -n 1 | awk -F '"' '{print $2}') def=$(cat .gitmodules | head -n $i | tail -n $(expr "$i" - "$tmp") | awk -F ' ' '{print $1 $2 $3}') path=$(echo "$def" | grep '^path=' | awk -F '=' '{print $2}') url=$(echo "$def" | grep '^url=' | awk -F '=' '{print $2}') branch=$(echo "$def" | grep '^branch=' | awk -F '=' '{print $2}') if ! ls "$path"/* >/dev/null 2>/dev/null; then git=$(which git) if ! [ $? -eq 0 ] || ! [ -x $git ]; then echo "Failed retrieving missing files" exit 5 fi res=$(git clone "$url" "$path" 2>&1) if ! [ $? -eq 0 ] || ! [ -d $path ]; then echo "Failed retrieving missing files: $res" exit 5 fi if [ -n "$branch" ]; then olddir="$(pwd)" cd "$path" res=$(git checkout "$branch" 2>&1) if ! [ $? -eq 0 ]; then echo "Failed retrieving missing files: $res" exit 5 fi cd "$olddir" fi echo "Submodule path '$path': checked out '$branch' (via git clone)" fi fi tmp=$i done fi fi if [ "$FETCH_ONLY" = "yes" ]; then exit 0; fi if [ ! -x "$(which cmake 2>/dev/null)" ] then echo "-!- CMake required." exit 1 fi ninja_bin="$(which ninja-build 2>/dev/null)" if ! [ -x "$ninja_bin" ]; then ninja_bin="$(which ninja 2>/dev/null)" fi if [ -x "$ninja_bin" ]; then ninja_version=`$ninja_bin --version 2>/dev/null` if [ $? -eq 0 ]; then if [ -d build ]; then last_ninja_version=`cat build/.ninja_version 2>/dev/null` else last_ninja_version=0 fi if [ "$ninja_version" != "$last_ninja_version" ]; then echo "-- Found Ninja: $ninja_bin (found version \"$ninja_version\")" fi cmake_type="Ninja" exec_bin="$ninja_bin" exec_command="$exec_bin" elif [ "/usr/sbin/ninja" = "$ninja_bin" ]; then echo "-- Ninja at $ninja_bin is not usable. Did you install 'ninja' instead of 'ninja-build'?" fi fi if ! [ -x "$exec_bin" ]; then make_bin="$(which make 2>/dev/null)" if [ -x "$make_bin" ]; then echo "-- Found Make: $make_bin" cmake_type="Unix Makefiles" exec_bin="$make_bin" exec_command="$exec_bin" echo "-- Running with make. Using Ninja (ninja-build) might improve build experience." fi fi if ! [ -x "$exec_bin" ]; then echo "-!- No compatible build system (Ninja, Make) found." exit 4 fi if [ -f ./build ]; then echo "-!- ./build file exists. ./configure can't continue" exit 2 fi if [ -d build ]; then last_type=`cat build/.cmake_type` if [ "$cmake_type" != "$last_type" ] then echo "-- Using different build system, cleaning build system files" cd build rm -r CMakeCache.txt CMakeFiles cd .. fi fi mkdir -p build cd build echo "$cmake_type" > .cmake_type echo "$ninja_version" > .ninja_version cmake -G "$cmake_type" \ -DCMAKE_INSTALL_PREFIX="$PREFIX" \ -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \ -DENABLED_PLUGINS="$ENABLED_PLUGINS" \ -DDISABLED_PLUGINS="$DISABLED_PLUGINS" \ -DBUILD_TESTS="$BUILD_TESTS" \ -DBUILD_LIBSIGNAL_IN_TREE="$BUILD_LIBSIGNAL_IN_TREE" \ -DVALA_EXECUTABLE="$VALAC" \ -DCMAKE_VALA_FLAGS="$VALACFLAGS" \ -DDISABLE_FAST_VAPI="$DISABLE_FAST_VAPI" \ -DLIB_SUFFIX="$LIB_SUFFIX" \ -DNO_DEBUG="$NO_DEBUG" \ -DEXEC_INSTALL_PREFIX="$EXEC_PREFIX" \ -DSHARE_INSTALL_PREFIX="$DATADIR" \ -DBIN_INSTALL_DIR="$BINDIR" \ -DINCLUDE_INSTALL_DIR="$INCLUDEDIR" \ -DLIB_INSTALL_DIR="$LIBDIR" \ -Wno-dev \ .. || exit 9 if [ "$cmake_type" = "Ninja" ]; then cat << EOF > Makefile default: @sh -c "$exec_command" %: @sh -c "$exec_command \"\$@\"" EOF fi cd .. cat << EOF > Makefile default: @sh -c "cd build; $exec_command" distclean: clean uninstall test: default echo "make test not yet supported" %: @sh -c "cd build; $exec_command \"\$@\"" EOF echo "-- Configured. Type 'make' to build, 'make install' to install." dino-0.3.0/crypto-vala/0000755000000000000000000000000014202022370013372 5ustar rootrootdino-0.3.0/crypto-vala/CMakeLists.txt0000644000000000000000000000232014202022370016127 0ustar rootrootfind_package(GCrypt REQUIRED) find_package(Srtp2 REQUIRED) find_packages(CRYPTO_VALA_PACKAGES REQUIRED GLib GObject GIO ) vala_precompile(CRYPTO_VALA_C SOURCES "src/cipher.vala" "src/cipher_converter.vala" "src/error.vala" "src/random.vala" "src/srtp.vala" CUSTOM_VAPIS "${CMAKE_CURRENT_SOURCE_DIR}/vapi/gcrypt.vapi" "${CMAKE_CURRENT_SOURCE_DIR}/vapi/libsrtp2.vapi" PACKAGES ${CRYPTO_VALA_PACKAGES} GENERATE_VAPI crypto-vala GENERATE_HEADER crypto-vala ) add_custom_target(crypto-vala-vapi DEPENDS ${CMAKE_BINARY_DIR}/exports/crypto-vala.vapi ${CMAKE_BINARY_DIR}/exports/crypto-vala.deps ) add_definitions(${VALA_CFLAGS} -DG_LOG_DOMAIN="crypto-vala") add_library(crypto-vala SHARED ${CRYPTO_VALA_C}) add_dependencies(crypto-vala crypto-vala-vapi) target_link_libraries(crypto-vala ${CRYPTO_VALA_PACKAGES} gcrypt libsrtp2) set_target_properties(crypto-vala PROPERTIES VERSION 0.0 SOVERSION 0) install(TARGETS crypto-vala ${TARGET_INSTALL}) install(FILES ${CMAKE_BINARY_DIR}/exports/crypto-vala.vapi ${CMAKE_BINARY_DIR}/exports/crypto-vala.deps DESTINATION ${VAPI_INSTALL_DIR}) install(FILES ${CMAKE_BINARY_DIR}/exports/crypto-vala.h DESTINATION ${INCLUDE_INSTALL_DIR}) dino-0.3.0/crypto-vala/src/0000755000000000000000000000000014202022370014161 5ustar rootrootdino-0.3.0/crypto-vala/src/cipher.vala0000644000000000000000000001364714202022370016313 0ustar rootrootnamespace Crypto { public class SymmetricCipher { private GCrypt.Cipher.Cipher cipher; public static bool supports(string algo_name) { GCrypt.Cipher.Algorithm algo; GCrypt.Cipher.Mode mode; GCrypt.Cipher.Flag flags; return parse(algo_name, out algo, out mode, out flags); } private static unowned string mode_to_string(GCrypt.Cipher.Mode mode) { switch (mode) { case GCrypt.Cipher.Mode.ECB: return "ECB"; case GCrypt.Cipher.Mode.CFB: return "CFB"; case GCrypt.Cipher.Mode.CBC: return "CBC"; case GCrypt.Cipher.Mode.STREAM: return "STREAM"; case GCrypt.Cipher.Mode.OFB: return "OFB"; case GCrypt.Cipher.Mode.CTR: return "CTR"; case GCrypt.Cipher.Mode.AESWRAP: return "AESWRAP"; case GCrypt.Cipher.Mode.GCM: return "GCM"; case GCrypt.Cipher.Mode.POLY1305: return "POLY1305"; case GCrypt.Cipher.Mode.OCB: return "OCB"; case GCrypt.Cipher.Mode.CFB8: return "CFB8"; // case GCrypt.Cipher.Mode.XTS: return "XTS"; // Not supported in gcrypt < 1.8 } return "NONE"; } private static GCrypt.Cipher.Mode mode_from_string(string name) { switch (name) { case "ECB": return GCrypt.Cipher.Mode.ECB; case "CFB": return GCrypt.Cipher.Mode.CFB; case "CBC": return GCrypt.Cipher.Mode.CBC; case "STREAM": return GCrypt.Cipher.Mode.STREAM; case "OFB": return GCrypt.Cipher.Mode.OFB; case "CTR": return GCrypt.Cipher.Mode.CTR; case "AESWRAP": return GCrypt.Cipher.Mode.AESWRAP; case "GCM": return GCrypt.Cipher.Mode.GCM; case "POLY1305": return GCrypt.Cipher.Mode.POLY1305; case "OCB": return GCrypt.Cipher.Mode.OCB; case "CFB8": return GCrypt.Cipher.Mode.CFB8; // case "XTS": return GCrypt.Cipher.Mode.XTS; // Not supported in gcrypt < 1.8 } return GCrypt.Cipher.Mode.NONE; } private static string flags_to_string(GCrypt.Cipher.Flag flags) { string? s = null; if ((GCrypt.Cipher.Flag.CBC_MAC & flags) != 0) s = (s == null ? "" : @"$s-") + "MAC"; if ((GCrypt.Cipher.Flag.CBC_CTS & flags) != 0) s = (s == null ? "" : @"$s-") + "CTS"; if ((GCrypt.Cipher.Flag.ENABLE_SYNC & flags) != 0) s = (s == null ? "" : @"$s-") + "SYNC"; if ((GCrypt.Cipher.Flag.SECURE & flags) != 0) s = (s == null ? "" : @"$s-") + "SECURE"; return s ?? "NONE"; } private static GCrypt.Cipher.Flag flag_from_string(string flag_name) { if (flag_name == "SECURE") return GCrypt.Cipher.Flag.SECURE; if (flag_name == "SYNC") return GCrypt.Cipher.Flag.ENABLE_SYNC; if (flag_name == "CTS") return GCrypt.Cipher.Flag.CBC_CTS; if (flag_name == "MAC") return GCrypt.Cipher.Flag.CBC_MAC; return 0; } private static GCrypt.Cipher.Flag flags_from_string(string flag_names) { GCrypt.Cipher.Flag flags = 0; foreach(string flag in flag_names.split("-")) { flags |= flag_from_string(flag); } return flags; } private static bool parse(string algo_name, out GCrypt.Cipher.Algorithm algo, out GCrypt.Cipher.Mode mode, out GCrypt.Cipher.Flag flags) { algo = GCrypt.Cipher.Algorithm.NONE; mode = GCrypt.Cipher.Mode.NONE; flags = 0; string[] algo_parts = algo_name.split("-", 3); algo = GCrypt.Cipher.Algorithm.from_string(algo_parts[0]); if (algo_parts.length >= 2) { mode = mode_from_string(algo_parts[1]); } if (algo_parts.length == 3) { flags |= flags_from_string(algo_parts[2]); } return to_algo_name(algo, mode, flags) == algo_name; } private static string to_algo_name(GCrypt.Cipher.Algorithm algo = GCrypt.Cipher.Algorithm.NONE, GCrypt.Cipher.Mode mode = GCrypt.Cipher.Mode.NONE, GCrypt.Cipher.Flag flags = 0) { if (flags != 0) { return @"$algo-$(mode_to_string(mode))-$(flags_to_string(flags))"; } else if (mode != GCrypt.Cipher.Mode.NONE) { return @"$algo-$(mode_to_string(mode))"; } else { return algo.to_string(); } } public SymmetricCipher(string algo_name) throws Error { GCrypt.Cipher.Algorithm algo; GCrypt.Cipher.Mode mode; GCrypt.Cipher.Flag flags; if (parse(algo_name, out algo, out mode, out flags)) { this.gcrypt(algo, mode, flags); } else { throw new Error.ILLEGAL_ARGUMENTS(@"The algorithm $algo_name is not supported"); } } private SymmetricCipher.gcrypt(GCrypt.Cipher.Algorithm algo, GCrypt.Cipher.Mode mode, GCrypt.Cipher.Flag flags) throws Error { may_throw_gcrypt_error(GCrypt.Cipher.Cipher.open(out this.cipher, algo, mode, flags)); } public void set_key(uint8[] key) throws Error { may_throw_gcrypt_error(cipher.set_key(key)); } public void set_iv(uint8[] iv) throws Error { may_throw_gcrypt_error(cipher.set_iv(iv)); } public void set_counter_vector(uint8[] ctr) throws Error { may_throw_gcrypt_error(cipher.set_counter_vector(ctr)); } public void reset() throws Error { may_throw_gcrypt_error(cipher.reset()); } public uint8[] get_tag(size_t taglen) throws Error { uint8[] tag = new uint8[taglen]; may_throw_gcrypt_error(cipher.get_tag(tag)); return tag; } public void check_tag(uint8[] tag) throws Error { may_throw_gcrypt_error(cipher.check_tag(tag)); } public void encrypt(uint8[] output, uint8[] input) throws Error { may_throw_gcrypt_error(cipher.encrypt(output, input)); } public void decrypt(uint8[] output, uint8[] input) throws Error { may_throw_gcrypt_error(cipher.decrypt(output, input)); } public void sync() throws Error { may_throw_gcrypt_error(cipher.sync()); } } } dino-0.3.0/crypto-vala/src/cipher_converter.vala0000644000000000000000000001040014202022370020362 0ustar rootrootusing GLib; namespace Crypto { public abstract class SymmetricCipherConverter : Converter, Object { internal SymmetricCipher cipher; internal size_t attached_taglen; public abstract ConverterResult convert(uint8[] inbuf, uint8[] outbuf, ConverterFlags flags, out size_t bytes_read, out size_t bytes_written) throws IOError; public uint8[] get_tag(size_t taglen) throws Error { return cipher.get_tag(taglen); } public void check_tag(uint8[] tag) throws Error { cipher.check_tag(tag); } public void reset() { try { cipher.reset(); } catch (Crypto.Error e) { warning(@"$(e.domain) error while resetting cipher: $(e.message)"); } } } public class SymmetricCipherEncrypter : SymmetricCipherConverter { public SymmetricCipherEncrypter(owned SymmetricCipher cipher, size_t attached_taglen = 0) { this.cipher = (owned) cipher; this.attached_taglen = attached_taglen; } public override ConverterResult convert(uint8[] inbuf, uint8[] outbuf, ConverterFlags flags, out size_t bytes_read, out size_t bytes_written) throws IOError { if (inbuf.length > outbuf.length) { throw new IOError.NO_SPACE("CipherConverter needs at least the size of input as output space"); } if ((flags & ConverterFlags.INPUT_AT_END) != 0 && inbuf.length + attached_taglen > outbuf.length) { throw new IOError.NO_SPACE("CipherConverter needs additional output space to attach tag"); } try { if (inbuf.length > 0) { cipher.encrypt(outbuf, inbuf); } bytes_read = inbuf.length; bytes_written = inbuf.length; if ((flags & ConverterFlags.INPUT_AT_END) != 0) { if (attached_taglen > 0) { Memory.copy((uint8*)outbuf + inbuf.length, get_tag(attached_taglen), attached_taglen); bytes_written = inbuf.length + attached_taglen; } return ConverterResult.FINISHED; } if ((flags & ConverterFlags.FLUSH) != 0) { return ConverterResult.FLUSHED; } return ConverterResult.CONVERTED; } catch (Crypto.Error e) { throw new IOError.FAILED(@"$(e.domain) error while decrypting: $(e.message)"); } } } public class SymmetricCipherDecrypter : SymmetricCipherConverter { public SymmetricCipherDecrypter(owned SymmetricCipher cipher, size_t attached_taglen = 0) { this.cipher = (owned) cipher; this.attached_taglen = attached_taglen; } public override ConverterResult convert(uint8[] inbuf, uint8[] outbuf, ConverterFlags flags, out size_t bytes_read, out size_t bytes_written) throws IOError { if (inbuf.length > outbuf.length + attached_taglen) { throw new IOError.NO_SPACE("CipherConverter needs at least the size of input as output space"); } if ((flags & ConverterFlags.INPUT_AT_END) != 0 && inbuf.length < attached_taglen) { throw new IOError.PARTIAL_INPUT("CipherConverter needs additional input to read tag"); } else if ((flags & ConverterFlags.INPUT_AT_END) == 0 && inbuf.length < attached_taglen + 1) { throw new IOError.PARTIAL_INPUT("CipherConverter needs additional input to make sure to not accidentally read tag"); } try { inbuf.length -= (int) attached_taglen; if (inbuf.length > 0) { cipher.decrypt(outbuf, inbuf); } bytes_read = inbuf.length; bytes_written = inbuf.length; inbuf.length += (int) attached_taglen; if ((flags & ConverterFlags.INPUT_AT_END) != 0) { if (attached_taglen > 0) { check_tag(inbuf[(inbuf.length - attached_taglen):inbuf.length]); bytes_read = inbuf.length; } return ConverterResult.FINISHED; } if ((flags & ConverterFlags.FLUSH) != 0) { return ConverterResult.FLUSHED; } return ConverterResult.CONVERTED; } catch (Crypto.Error e) { throw new IOError.FAILED(@"$(e.domain) error while decrypting: $(e.message)"); } } } } dino-0.3.0/crypto-vala/src/error.vala0000644000000000000000000000043014202022370016154 0ustar rootrootnamespace Crypto { public errordomain Error { ILLEGAL_ARGUMENTS, GCRYPT, AUTHENTICATION_FAILED, UNKNOWN } internal void may_throw_gcrypt_error(GCrypt.Error e) throws Error { if (((int)e) != 0) { throw new Crypto.Error.GCRYPT(e.to_string()); } } }dino-0.3.0/crypto-vala/src/random.vala0000644000000000000000000000015214202022370016304 0ustar rootrootnamespace Crypto { public static void randomize(uint8[] buffer) { GCrypt.Random.randomize(buffer); } }dino-0.3.0/crypto-vala/src/srtp.vala0000644000000000000000000001044514202022370016022 0ustar rootrootusing Srtp; namespace Crypto.Srtp { public const string AES_CM_128_HMAC_SHA1_80 = "AES_CM_128_HMAC_SHA1_80"; public const string AES_CM_128_HMAC_SHA1_32 = "AES_CM_128_HMAC_SHA1_32"; public const string F8_128_HMAC_SHA1_80 = "F8_128_HMAC_SHA1_80"; public class Session { public bool has_encrypt { get; private set; default = false; } public bool has_decrypt { get; private set; default = false; } private Context encrypt_context; private Context decrypt_context; static construct { init(); install_log_handler(log); } private static void log(LogLevel level, string msg) { print(@"SRTP[$level]: $msg\n"); } public Session() { Context.create(out encrypt_context, null); Context.create(out decrypt_context, null); } public uint8[] encrypt_rtp(uint8[] data) throws Error { uint8[] buf = new uint8[data.length + MAX_TRAILER_LEN]; Memory.copy(buf, data, data.length); int buf_use = data.length; ErrorStatus res = encrypt_context.protect(buf, ref buf_use); if (res != ErrorStatus.ok) { throw new Error.UNKNOWN(@"SRTP encrypt failed: $res"); } buf.length = buf_use; return buf; } public uint8[] decrypt_rtp(uint8[] data) throws Error { uint8[] buf = new uint8[data.length]; Memory.copy(buf, data, data.length); int buf_use = data.length; ErrorStatus res = decrypt_context.unprotect(buf, ref buf_use); switch (res) { case ErrorStatus.auth_fail: throw new Error.AUTHENTICATION_FAILED("SRTP packet failed the message authentication check"); case ErrorStatus.ok: break; default: throw new Error.UNKNOWN(@"SRTP decrypt failed: $res"); } uint8[] ret = new uint8[buf_use]; GLib.Memory.copy(ret, buf, buf_use); return ret; } public uint8[] encrypt_rtcp(uint8[] data) throws Error { uint8[] buf = new uint8[data.length + MAX_TRAILER_LEN + 4]; Memory.copy(buf, data, data.length); int buf_use = data.length; ErrorStatus res = encrypt_context.protect_rtcp(buf, ref buf_use); if (res != ErrorStatus.ok) { throw new Error.UNKNOWN(@"SRTCP encrypt failed: $res"); } buf.length = buf_use; return buf; } public uint8[] decrypt_rtcp(uint8[] data) throws Error { uint8[] buf = new uint8[data.length]; Memory.copy(buf, data, data.length); int buf_use = data.length; ErrorStatus res = decrypt_context.unprotect_rtcp(buf, ref buf_use); switch (res) { case ErrorStatus.auth_fail: throw new Error.AUTHENTICATION_FAILED("SRTCP packet failed the message authentication check"); case ErrorStatus.ok: break; default: throw new Error.UNKNOWN(@"SRTCP decrypt failed: $res"); } uint8[] ret = new uint8[buf_use]; GLib.Memory.copy(ret, buf, buf_use); return ret; } private Policy create_policy(string profile) { Policy policy = Policy(); switch (profile) { case AES_CM_128_HMAC_SHA1_80: policy.rtp.set_aes_cm_128_hmac_sha1_80(); policy.rtcp.set_aes_cm_128_hmac_sha1_80(); break; } return policy; } public void set_encryption_key(string profile, uint8[] key, uint8[] salt) { Policy policy = create_policy(profile); policy.ssrc.type = SsrcType.any_outbound; policy.key = new uint8[key.length + salt.length]; Memory.copy(policy.key, key, key.length); Memory.copy(((uint8*)policy.key) + key.length, salt, salt.length); policy.next = null; encrypt_context.add_stream(ref policy); has_encrypt = true; } public void set_decryption_key(string profile, uint8[] key, uint8[] salt) { Policy policy = create_policy(profile); policy.ssrc.type = SsrcType.any_inbound; policy.key = new uint8[key.length + salt.length]; Memory.copy(policy.key, key, key.length); Memory.copy(((uint8*)policy.key) + key.length, salt, salt.length); policy.next = null; decrypt_context.add_stream(ref policy); has_decrypt = true; } } }dino-0.3.0/crypto-vala/vapi/0000755000000000000000000000000014202022370014331 5ustar rootrootdino-0.3.0/crypto-vala/vapi/gcrypt.vapi0000644000000000000000000004650414202022370016533 0ustar rootroot/* gcrypt.vapi * * Copyright: * 2008 Jiqing Qiang * 2008, 2010, 2012-2013 Evan Nemerson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * This library 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 * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Author: * Jiqing Qiang * Evan Nemerson */ [CCode (cheader_filename = "gcrypt.h", lower_case_cprefix = "gcry_")] namespace GCrypt { [CCode (cname = "gpg_err_source_t", cprefix = "GPG_ERR_SOURCE_")] public enum ErrorSource { UNKNOWN, GCRYPT, GPG, GPGSM, GPGAGENT, PINENTRY, SCD, GPGME, KEYBOX, KSBA, DIRMNGR, GSTI, ANY, USER_1, USER_2, USER_3, USER_4, /* This is one more than the largest allowed entry. */ DIM } [CCode (cname = "gpg_err_code_t", cprefix = "GPG_ERR_")] public enum ErrorCode { NO_ERROR, GENERAL, UNKNOWN_PACKET, UNKNOWN_VERSION, PUBKEY_ALGO, DIGEST_ALGO, BAD_PUBKEY, BAD_SECKEY, BAD_SIGNATURE, NO_PUBKEY, CHECKSUM, BAD_PASSPHRASE, CIPHER_ALGO, KEYRING_OPEN, INV_PACKET, INV_ARMOR, NO_USER_ID, NO_SECKEY, WRONG_SECKEY, BAD_KEY, COMPR_ALGO, NO_PRIME, NO_ENCODING_METHOD, NO_ENCRYPTION_SCHEME, NO_SIGNATURE_SCHEME, INV_ATTR, NO_VALUE, NOT_FOUND, VALUE_NOT_FOUND, SYNTAX, BAD_MPI, INV_PASSPHRASE, SIG_CLASS, RESOURCE_LIMIT, INV_KEYRING, TRUSTDB, BAD_CERT, INV_USER_ID, UNEXPECTED, TIME_CONFLICT, KEYSERVER, WRONG_PUBKEY_ALGO, TRIBUTE_TO_D_A, WEAK_KEY, INV_KEYLEN, INV_ARG, BAD_URI, INV_URI, NETWORK, UNKNOWN_HOST, SELFTEST_FAILED, NOT_ENCRYPTED, NOT_PROCESSED, UNUSABLE_PUBKEY, UNUSABLE_SECKEY, INV_VALUE, BAD_CERT_CHAIN, MISSING_CERT, NO_DATA, BUG, NOT_SUPPORTED, INV_OP, TIMEOUT, INTERNAL, EOF_GCRYPT, INV_OBJ, TOO_SHORT, TOO_LARGE, NO_OBJ, NOT_IMPLEMENTED, CONFLICT, INV_CIPHER_MODE, INV_FLAG, INV_HANDLE, TRUNCATED, INCOMPLETE_LINE, INV_RESPONSE, NO_AGENT, AGENT, INV_DATA, ASSUAN_SERVER_FAULT, ASSUAN, INV_SESSION_KEY, INV_SEXP, UNSUPPORTED_ALGORITHM, NO_PIN_ENTRY, PIN_ENTRY, BAD_PIN, INV_NAME, BAD_DATA, INV_PARAMETER, WRONG_CARD, NO_DIRMNGR, DIRMNGR, CERT_REVOKED, NO_CRL_KNOWN, CRL_TOO_OLD, LINE_TOO_LONG, NOT_TRUSTED, CANCELED, BAD_CA_CERT, CERT_EXPIRED, CERT_TOO_YOUNG, UNSUPPORTED_CERT, UNKNOWN_SEXP, UNSUPPORTED_PROTECTION, CORRUPTED_PROTECTION, AMBIGUOUS_NAME, CARD, CARD_RESET, CARD_REMOVED, INV_CARD, CARD_NOT_PRESENT, NO_PKCS15_APP, NOT_CONFIRMED, CONFIGURATION, NO_POLICY_MATCH, INV_INDEX, INV_ID, NO_SCDAEMON, SCDAEMON, UNSUPPORTED_PROTOCOL, BAD_PIN_METHOD, CARD_NOT_INITIALIZED, UNSUPPORTED_OPERATION, WRONG_KEY_USAGE, NOTHING_FOUND, WRONG_BLOB_TYPE, MISSING_VALUE, HARDWARE, PIN_BLOCKED, USE_CONDITIONS, PIN_NOT_SYNCED, INV_CRL, BAD_BER, INV_BER, ELEMENT_NOT_FOUND, IDENTIFIER_NOT_FOUND, INV_TAG, INV_LENGTH, INV_KEYINFO, UNEXPECTED_TAG, NOT_DER_ENCODED, NO_CMS_OBJ, INV_CMS_OBJ, UNKNOWN_CMS_OBJ, UNSUPPORTED_CMS_OBJ, UNSUPPORTED_ENCODING, UNSUPPORTED_CMS_VERSION, UNKNOWN_ALGORITHM, INV_ENGINE, PUBKEY_NOT_TRUSTED, DECRYPT_FAILED, KEY_EXPIRED, SIG_EXPIRED, ENCODING_PROBLEM, INV_STATE, DUP_VALUE, MISSING_ACTION, MODULE_NOT_FOUND, INV_OID_STRING, INV_TIME, INV_CRL_OBJ, UNSUPPORTED_CRL_VERSION, INV_CERT_OBJ, UNKNOWN_NAME, LOCALE_PROBLEM, NOT_LOCKED, PROTOCOL_VIOLATION, INV_MAC, INV_REQUEST, UNKNOWN_EXTN, UNKNOWN_CRIT_EXTN, LOCKED, UNKNOWN_OPTION, UNKNOWN_COMMAND, BUFFER_TOO_SHORT, SEXP_INV_LEN_SPEC, SEXP_STRING_TOO_LONG, SEXP_UNMATCHED_PAREN, SEXP_NOT_CANONICAL, SEXP_BAD_CHARACTER, SEXP_BAD_QUOTATION, SEXP_ZERO_PREFIX, SEXP_NESTED_DH, SEXP_UNMATCHED_DH, SEXP_UNEXPECTED_PUNC, SEXP_BAD_HEX_CHAR, SEXP_ODD_HEX_NUMBERS, SEXP_BAD_OCT_CHAR, ASS_GENERAL, ASS_ACCEPT_FAILED, ASS_CONNECT_FAILED, ASS_INV_RESPONSE, ASS_INV_VALUE, ASS_INCOMPLETE_LINE, ASS_LINE_TOO_LONG, ASS_NESTED_COMMANDS, ASS_NO_DATA_CB, ASS_NO_INQUIRE_CB, ASS_NOT_A_SERVER, ASS_NOT_A_CLIENT, ASS_SERVER_START, ASS_READ_ERROR, ASS_WRITE_ERROR, ASS_TOO_MUCH_DATA, ASS_UNEXPECTED_CMD, ASS_UNKNOWN_CMD, ASS_SYNTAX, ASS_CANCELED, ASS_NO_INPUT, ASS_NO_OUTPUT, ASS_PARAMETER, ASS_UNKNOWN_INQUIRE, USER_1, USER_2, USER_3, USER_4, USER_5, USER_6, USER_7, USER_8, USER_9, USER_10, USER_11, USER_12, USER_13, USER_14, USER_15, USER_16, MISSING_ERRNO, UNKNOWN_ERRNO, EOF, E2BIG, EACCES, EADDRINUSE, EADDRNOTAVAIL, EADV, EAFNOSUPPORT, EAGAIN, EALREADY, EAUTH, EBACKGROUND, EBADE, EBADF, EBADFD, EBADMSG, EBADR, EBADRPC, EBADRQC, EBADSLT, EBFONT, EBUSY, ECANCELED, ECHILD, ECHRNG, ECOMM, ECONNABORTED, ECONNREFUSED, ECONNRESET, ED, EDEADLK, EDEADLOCK, EDESTADDRREQ, EDIED, EDOM, EDOTDOT, EDQUOT, EEXIST, EFAULT, EFBIG, EFTYPE, EGRATUITOUS, EGREGIOUS, EHOSTDOWN, EHOSTUNREACH, EIDRM, EIEIO, EILSEQ, EINPROGRESS, EINTR, EINVAL, EIO, EISCONN, EISDIR, EISNAM, EL2HLT, EL2NSYNC, EL3HLT, EL3RST, ELIBACC, ELIBBAD, ELIBEXEC, ELIBMAX, ELIBSCN, ELNRNG, ELOOP, EMEDIUMTYPE, EMFILE, EMLINK, EMSGSIZE, EMULTIHOP, ENAMETOOLONG, ENAVAIL, ENEEDAUTH, ENETDOWN, ENETRESET, ENETUNREACH, ENFILE, ENOANO, ENOBUFS, ENOCSI, ENODATA, ENODEV, ENOENT, ENOEXEC, ENOLCK, ENOLINK, ENOMEDIUM, ENOMEM, ENOMSG, ENONET, ENOPKG, ENOPROTOOPT, ENOSPC, ENOSR, ENOSTR, ENOSYS, ENOTBLK, ENOTCONN, ENOTDIR, ENOTEMPTY, ENOTNAM, ENOTSOCK, ENOTSUP, ENOTTY, ENOTUNIQ, ENXIO, EOPNOTSUPP, EOVERFLOW, EPERM, EPFNOSUPPORT, EPIPE, EPROCLIM, EPROCUNAVAIL, EPROGMISMATCH, EPROGUNAVAIL, EPROTO, EPROTONOSUPPORT, EPROTOTYPE, ERANGE, EREMCHG, EREMOTE, EREMOTEIO, ERESTART, EROFS, ERPCMISMATCH, ESHUTDOWN, ESOCKTNOSUPPORT, ESPIPE, ESRCH, ESRMNT, ESTALE, ESTRPIPE, ETIME, ETIMEDOUT, ETOOMANYREFS, ETXTBSY, EUCLEAN, EUNATCH, EUSERS, EWOULDBLOCK, EXDEV, EXFULL, /* This is one more than the largest allowed entry. */ CODE_DIM } [CCode (cname = "gcry_error_t", cprefix = "gpg_err_")] public struct Error : uint { [CCode (cname = "gcry_err_make")] public Error (ErrorSource source, ErrorCode code); [CCode (cname = "gcry_err_make_from_errno")] public Error.from_errno (ErrorSource source, int err); public ErrorCode code (); public ErrorSource source (); [CCode (cname = "gcry_strerror")] public unowned string to_string (); [CCode (cname = "gcry_strsource")] public unowned string source_to_string (); } [CCode (cname = "enum gcry_ctl_cmds", cprefix = "GCRYCTL_")] public enum ControlCommand { SET_KEY, SET_IV, CFB_SYNC, RESET, FINALIZE, GET_KEYLEN, GET_BLKLEN, TEST_ALGO, IS_SECURE, GET_ASNOID, ENABLE_ALGO, DISABLE_ALGO, DUMP_RANDOM_STATS, DUMP_SECMEM_STATS, GET_ALGO_NPKEY, GET_ALGO_NSKEY, GET_ALGO_NSIGN, GET_ALGO_NENCR, SET_VERBOSITY, SET_DEBUG_FLAGS, CLEAR_DEBUG_FLAGS, USE_SECURE_RNDPOOL, DUMP_MEMORY_STATS, INIT_SECMEM, TERM_SECMEM, DISABLE_SECMEM_WARN, SUSPEND_SECMEM_WARN, RESUME_SECMEM_WARN, DROP_PRIVS, ENABLE_M_GUARD, START_DUMP, STOP_DUMP, GET_ALGO_USAGE, IS_ALGO_ENABLED, DISABLE_INTERNAL_LOCKING, DISABLE_SECMEM, INITIALIZATION_FINISHED, INITIALIZATION_FINISHED_P, ANY_INITIALIZATION_P, SET_CBC_CTS, SET_CBC_MAC, SET_CTR, ENABLE_QUICK_RANDOM, SET_RANDOM_SEED_FILE, UPDATE_RANDOM_SEED_FILE, SET_THREAD_CBS, FAST_POLL } public Error control (ControlCommand cmd, ...); [CCode (lower_case_cname = "cipher_")] namespace Cipher { [CCode (cname = "enum gcry_cipher_algos", cprefix = "GCRY_CIPHER_")] public enum Algorithm { NONE, IDEA, 3DES, CAST5, BLOWFISH, SAFER_SK128, DES_SK, AES, AES128, RIJNDAEL, RIJNDAEL128, AES192, RIJNDAEL192, AES256, RIJNDAEL256, TWOFISH, TWOFISH128, ARCFOUR, DES, SERPENT128, SERPENT192, SERPENT256, RFC2268_40, RFC2268_128, SEED, CAMELLIA128, CAMELLIA192, CAMELLIA256, SALSA20, SALSA20R12, GOST28147, CHACHA20; [CCode (cname = "gcry_cipher_algo_info")] public Error info (ControlCommand what, ref uchar[] buffer); [CCode (cname = "gcry_cipher_algo_name")] public unowned string to_string (); [CCode (cname = "gcry_cipher_map_name")] public static Algorithm from_string (string name); [CCode (cname = "gcry_cipher_map_oid")] public static Algorithm from_oid (string oid); } [CCode (cname = "enum gcry_cipher_modes", cprefix = "GCRY_CIPHER_MODE_")] public enum Mode { NONE, /* No mode specified */ ECB, /* Electronic Codebook */ CFB, /* Cipher Feedback */ CBC, /* Cipher Block Chaining */ STREAM, /* Used with stream ciphers */ OFB, /* Output Feedback */ CTR, /* Counter */ AESWRAP, /* AES-WRAP algorithm */ CCM, /* Counter with CBC-MAC */ GCM, /* Galois/Counter Mode */ POLY1305, /* Poly1305 based AEAD mode */ OCB, /* OCB3 mode */ CFB8, /* Cipher Feedback /* Poly1305 based AEAD mode. */ XTS; /* XTS mode */ public unowned string to_string () { switch (this) { case ECB: return "ECB"; case CFB: return "CFB"; case CBC: return "CBC"; case STREAM: return "STREAM"; case OFB: return "OFB"; case CTR: return "CTR"; case AESWRAP: return "AESWRAP"; case GCM: return "GCM"; case POLY1305: return "POLY1305"; case OCB: return "OCB"; case CFB8: return "CFB8"; case XTS: return "XTS"; } return "NONE"; } public static Mode from_string (string name) { switch (name) { case "ECB": return ECB; case "CFB": return CFB; case "CBC": return CBC; case "STREAM": return STREAM; case "OFB": return OFB; case "CTR": return CTR; case "AESWRAP": return AESWRAP; case "GCM": return GCM; case "POLY1305": return POLY1305; case "OCB": return OCB; case "CFB8": return CFB8; case "XTS": return XTS; } return NONE; } } [CCode (cname = "enum gcry_cipher_flags", cprefix = "GCRY_CIPHER_")] public enum Flag { SECURE, /* Allocate in secure memory. */ ENABLE_SYNC, /* Enable CFB sync mode. */ CBC_CTS, /* Enable CBC cipher text stealing (CTS). */ CBC_MAC /* Enable CBC message auth. code (MAC). */ } [CCode (cname = "gcry_cipher_hd_t", lower_case_cprefix = "gcry_cipher_", free_function = "gcry_cipher_close")] [SimpleType] public struct Cipher { public static Error open (out Cipher cipher, Algorithm algo, Mode mode, Flag flags); public void close (); [CCode (cname = "gcry_cipher_ctl")] public Error control (ControlCommand cmd, uchar[] buffer); public Error info (ControlCommand what, ref uchar[] buffer); public Error encrypt (uchar[] out_buffer, uchar[] in_buffer); public Error decrypt (uchar[] out_buffer, uchar[] in_buffer); [CCode (cname = "gcry_cipher_setkey")] public Error set_key (uchar[] key_data); [CCode (cname = "gcry_cipher_setiv")] public Error set_iv (uchar[] iv_data); [CCode (cname = "gcry_cipher_setctr")] public Error set_counter_vector (uchar[] counter_vector); [CCode (cname = "gcry_cipher_gettag")] public Error get_tag(uchar[] out_buffer); [CCode (cname = "gcry_cipher_checktag")] public Error check_tag(uchar[] in_buffer); public Error reset (); public Error sync (); } } [Compact, CCode (cname = "struct gcry_md_handle", cprefix = "gcry_md_", free_function = "gcry_md_close")] public class Hash { [CCode (cname = "enum gcry_md_algos", cprefix = "GCRY_MD_")] public enum Algorithm { NONE, SHA1, RMD160, MD5, MD4, MD2, TIGER, TIGER1, TIGER2, HAVAL, SHA224, SHA256, SHA384, SHA512, SHA3_224, SHA3_256, SHA3_384, SHA3_512, SHAKE128, SHAKE256, CRC32, CRC32_RFC1510, CRC24_RFC2440, WHIRLPOOL, GOSTR3411_94, STRIBOG256, STRIBOG512; [CCode (cname = "gcry_md_get_algo_dlen")] public size_t get_digest_length (); [CCode (cname = "gcry_md_algo_info")] public Error info (ControlCommand what, ref uchar[] buffer); [CCode (cname = "gcry_md_algo_name")] public unowned string to_string (); [CCode (cname = "gcry_md_map_name")] public static Algorithm from_string (string name); [CCode (cname = "gcry_md_test_algo")] public Error is_available (); [CCode (cname = "gcry_md_get_asnoid")] public Error get_oid (uchar[] buffer); } [CCode (cname = "enum gcry_md_flags", cprefix = "GCRY_MD_FLAG_")] public enum Flag { SECURE, HMAC, BUGEMU1 } public static Error open (out Hash hash, Algorithm algo, Flag flag); public void close (); public Error enable (Algorithm algo); [CCode (instance_pos = -1)] public Error copy (out Hash dst); public void reset (); [CCode (cname = "enum gcry_md_ctl")] public Error control (ControlCommand cmd, uchar[] buffer); public void write (uchar[] buffer); [CCode (array_length = false)] public unowned uchar[] read (Algorithm algo); public static void hash_buffer (Algorithm algo, [CCode (array_length = false)] uchar[] digest, uchar[] buffer); public Algorithm get_algo (); public bool is_enabled (Algorithm algo); public bool is_secure (); public Error info (ControlCommand what, uchar[] buffer); [CCode (cname = "gcry_md_setkey")] public Error set_key (uchar[] key_data); public void putc (char c); public void final (); public static Error list (ref Algorithm[] algos); } namespace Random { [CCode (cname = "gcry_random_level_t")] public enum Level { [CCode (cname = "GCRY_WEAK_RANDOM")] WEAK, [CCode (cname = "GCRY_STRONG_RANDOM")] STRONG, [CCode (cname = "GCRY_VERY_STRONG_RANDOM")] VERY_STRONG } [CCode (cname = "gcry_randomize")] public static void randomize (uchar[] buffer, Level level = Level.VERY_STRONG); [CCode (cname = "gcry_fast_random_poll")] public static Error poll (); [CCode (cname = "gcry_random_bytes", array_length = false)] public static uchar[] random_bytes (size_t nbytes, Level level = Level.VERY_STRONG); [CCode (cname = "gcry_random_bytes_secure")] public static uchar[] random_bytes_secure (size_t nbytes, Level level = Level.VERY_STRONG); [CCode (cname = "gcry_create_nonce")] public static void nonce (uchar[] buffer); } [Compact, CCode (cname = "struct gcry_mpi", cprefix = "gcry_mpi_", free_function = "gcry_mpi_release")] public class MPI { [CCode (cname = "enum gcry_mpi_format", cprefix = "GCRYMPI_FMT_")] public enum Format { NONE, STD, PGP, SSH, HEX, USG } [CCode (cname = "enum gcry_mpi_flag", cprefix = "GCRYMPI_FLAG_")] public enum Flag { SECURE, OPAQUE } public MPI (uint nbits); [CCode (cname = "gcry_mpi_snew")] public MPI.secure (uint nbits); public MPI copy (); public void set (MPI u); public void set_ui (ulong u); public void swap (); public int cmp (MPI v); public int cmp_ui (ulong v); public static Error scan (out MPI ret, MPI.Format format, [CCode (array_length = false)] uchar[] buffer, size_t buflen, out size_t nscanned); [CCode (instance_pos = -1)] public Error print (MPI.Format format, [CCode (array_length = false)] uchar[] buffer, size_t buflen, out size_t nwritter); [CCode (instance_pos = -1)] public Error aprint (MPI.Format format, out uchar[] buffer); public void add (MPI u, MPI v); public void add_ui (MPI u, ulong v); public void addm (MPI u, MPI v, MPI m); public void sub (MPI u, MPI v); public void sub_ui (MPI u, MPI v); public void subm (MPI u, MPI v, MPI m); public void mul (MPI u, MPI v); public void mul_ui (MPI u, ulong v); public void mulm (MPI u, MPI v, MPI m); public void mul_2exp (MPI u, ulong cnt); public void div (MPI q, MPI r, MPI dividend, MPI divisor, int round); public void mod (MPI dividend, MPI divisor); public void powm (MPI b, MPI e, MPI m); public int gcd (MPI a, MPI b); public int invm (MPI a, MPI m); public uint get_nbits (); public int test_bit (uint n); public void set_bit (uint n); public void clear_bit (uint n); public void set_highbit (uint n); public void clear_highbit (uint n); public void rshift (MPI a, uint n); public void lshift (MPI a, uint n); public void set_flag (MPI.Flag flag); public void clear_flag (MPI.Flag flag); public int get_flag (MPI.Flag flag); } [Compact, CCode (cname = "struct gcry_sexp", free_function = "gcry_sexp_release")] public class SExp { [CCode (cprefix = "GCRYSEXP_FMT_")] public enum Format { DEFAULT, CANON, BASE64, ADVANCED } public static Error @new (out SExp retsexp, void * buffer, size_t length, int autodetect); public static Error create (out SExp retsexp, void * buffer, size_t length, int autodetect, GLib.DestroyNotify free_function); public static Error sscan (out SExp retsexp, out size_t erroff, char[] buffer); public static Error build (out SExp retsexp, out size_t erroff, string format, ...); public size_t sprint (Format mode, char[] buffer); public static size_t canon_len (uchar[] buffer, out size_t erroff, out int errcode); public SExp find_token (string token, size_t token_length = 0); public int length (); public SExp? nth (int number); public SExp? car (); public SExp? cdr (); public unowned char[] nth_data (int number); public gcry_string nth_string (int number); public MPI nth_mpi (int number, MPI.Format mpifmt); } [CCode (cname = "char", free_function = "gcry_free")] public class gcry_string : string { } [CCode (lower_case_cprefix = "gcry_pk_")] namespace PublicKey { [CCode (cname = "enum gcry_pk_algos")] public enum Algorithm { RSA, ELG_E, DSA, ELG, ECDSA; [CCode (cname = "gcry_pk_algo_name")] public unowned string to_string (); [CCode (cname = "gcry_pk_map_name")] public static Algorithm map_name (string name); } public static Error encrypt (out SExp ciphertext, SExp data, SExp pkey); public static Error decrypt (out SExp plaintext, SExp data, SExp skey); public static Error sign (out SExp signature, SExp data, SExp skey); public static Error verify (SExp signature, SExp data, SExp pkey); public static Error testkey (SExp key); public static Error genkey (out SExp r_key, SExp s_params); public static uint get_nbits (SExp key); } [CCode (lower_case_cprefix = "gcry_kdf_")] namespace KeyDerivation { [CCode (cname = "gcry_kdf_algos", cprefix = "GCRY_KDF_", has_type_id = false)] public enum Algorithm { NONE, SIMPLE_S2K, SALTED_S2K, ITERSALTED_S2K, PBKDF1, PBKDF2, SCRYPT } public GCrypt.Error derive ([CCode (type = "const void*", array_length_type = "size_t")] uint8[] passphrasse, GCrypt.KeyDerivation.Algorithm algo, GCrypt.Hash.Algorithm subalgo, [CCode (type = "const void*", array_length_type = "size_t")] uint8[] salt, ulong iterations, [CCode (type = "void*", array_length_type = "size_t", array_length_pos = 5.5)] uint8[] keybuffer); } } dino-0.3.0/crypto-vala/vapi/libsrtp2.vapi0000644000000000000000000001030114202022370016746 0ustar rootroot[CCode (cheader_filename = "srtp2/srtp.h")] namespace Srtp { public const uint MAX_TRAILER_LEN; public static ErrorStatus init(); public static ErrorStatus shutdown(); [Compact] [CCode (cname = "srtp_ctx_t", cprefix = "srtp_", free_function = "srtp_dealloc")] public class Context { public static ErrorStatus create(out Context session, Policy? policy); public ErrorStatus protect([CCode (type = "void*", array_length = false)] uint8[] rtp, ref int len); public ErrorStatus unprotect([CCode (type = "void*", array_length = false)] uint8[] rtp, ref int len); public ErrorStatus protect_rtcp([CCode (type = "void*", array_length = false)] uint8[] rtcp, ref int len); public ErrorStatus unprotect_rtcp([CCode (type = "void*", array_length = false)] uint8[] rtcp, ref int len); public ErrorStatus add_stream(ref Policy policy); public ErrorStatus update_stream(ref Policy policy); public ErrorStatus remove_stream(uint ssrc); public ErrorStatus update(ref Policy policy); } [CCode (cname = "srtp_ssrc_t")] public struct Ssrc { public SsrcType type; public uint value; } [CCode (cname = "srtp_ssrc_type_t", cprefix = "ssrc_")] public enum SsrcType { undefined, specific, any_inbound, any_outbound } [CCode (cname = "srtp_policy_t", destroy_function = "")] public struct Policy { public Ssrc ssrc; public CryptoPolicy rtp; public CryptoPolicy rtcp; [CCode (array_length = false)] public uint8[] key; public ulong num_master_keys; public ulong window_size; [CCode (ctype = "int")] public bool allow_repeat_tx; [CCode (array_length_cname = "enc_xtn_hdr_count")] public int[] enc_xtn_hdr; public Policy* next; } [CCode (cname = "srtp_crypto_policy_t")] public struct CryptoPolicy { public CipherType cipher_type; public int cipher_key_len; public AuthType auth_type; public int auth_key_len; public int auth_tag_len; public SecurityServices sec_serv; public void set_aes_cm_128_hmac_sha1_80(); public void set_aes_cm_128_hmac_sha1_32(); public void set_aes_cm_128_null_auth(); public void set_aes_cm_192_hmac_sha1_32(); public void set_aes_cm_192_hmac_sha1_80(); public void set_aes_cm_192_null_auth(); public void set_aes_cm_256_hmac_sha1_32(); public void set_aes_cm_256_hmac_sha1_80(); public void set_aes_cm_256_null_auth(); public void set_aes_gcm_128_16_auth(); public void set_aes_gcm_128_8_auth(); public void set_aes_gcm_128_8_only_auth(); public void set_aes_gcm_256_16_auth(); public void set_aes_gcm_256_8_auth(); public void set_aes_gcm_256_8_only_auth(); public void set_null_cipher_hmac_null(); public void set_null_cipher_hmac_sha1_80(); public void set_rtp_default(); public void set_rtcp_default(); public void set_from_profile_for_rtp(Profile profile); public void set_from_profile_for_rtcp(Profile profile); } [CCode (cname = "srtp_profile_t", cprefix = "srtp_profile_")] public enum Profile { reserved, aes128_cm_sha1_80, aes128_cm_sha1_32, null_sha1_80, null_sha1_32, aead_aes_128_gcm, aead_aes_256_gcm } [CCode (cname = "srtp_cipher_type_id_t")] public struct CipherType : uint32 {} [CCode (cname = "srtp_auth_type_id_t")] public struct AuthType : uint32 {} [CCode (cname = "srtp_sec_serv_t", cprefix = "sec_serv_")] public enum SecurityServices { none, conf, auth, conf_and_auth; } [CCode (cname = "srtp_err_status_t", cprefix = "srtp_err_status_", has_type_id = false)] public enum ErrorStatus { ok, fail, bad_param, alloc_fail, dealloc_fail, init_fail, terminus, auth_fail, cipher_fail, replay_fail, replay_old, algo_fail, no_such_op, no_ctx, cant_check, key_expired, socket_err, signal_err, nonce_bad, read_fail, write_fail, parse_err, encode_err, semaphore_err, pfkey_err, bad_mki, pkt_idx_old, pkt_idx_adv } [CCode (cname = "srtp_log_level_t", cprefix = "srtp_log_level_", has_type_id = false)] public enum LogLevel { error, warning, info, debug } [CCode (cname = "srtp_log_handler_func_t")] public delegate void LogHandler(LogLevel level, string msg); public static ErrorStatus install_log_handler(LogHandler func); }dino-0.3.0/dino.doap0000644000000000000000000007504014202022370012735 0ustar rootroot Dino dino Modern XMPP Chat Client 現代化的 XMPP 用戶端聊天軟件 现代 XMPP 聊天客户端 Modern XMPP Sohbet İstemcisi Modern XMPP-chattklient Klient Modern Fjalosjesh XMPP Современный XMPP клиент Client XMPP de discuții modern Cliente de Chat XMPP Moderno Moderno cliente de chat XMPP Nowoczesny komunikator XMPP Client XMPP modèrn Modernen XMPP-chatcliënt Een moderne XMPP-chatclient Moderne XMPP-sludreklient Šiuolaikinė XMPP pokalbių kliento programa Modernen XMPP Chat Client 現代的な XMPP チャット クライアント Client di chat moderno per XMPP Un modern client de conversationes XMPP Aplikasi chat XMPP modern Modern XMPP csevegőprogram Cliente moderno para conversas XMPP Client de clavardage XMPP moderne کلاینت نوین گپ XMPP XMPP txat bezero modernoa Un cliente de XMPP moderno Moderna XMPP-Retebabililo Modernes XMPP-Chat-Programm Moderní XMPP klient Client de xat XMPP modern تطبيق حديث للدردشة عبر XMPP Dino is a modern open-source chat client for the desktop. It focuses on providing a clean and reliable Jabber/XMPP experience while having your privacy in mind. It supports end-to-end encryption with OMEMO and OpenPGP and allows configuring privacy-related features such as read receipts and typing notifications. Dino fetches history from the server and synchronizes messages with other devices. Dino 是一個爲桌面打造的現代化開放原始碼用戶端聊天軟件。它致力於提供一份簡洁而可靠的 Jabber/XMPP 體驗,同時亦尊重您的私隱。 它支持 OMEMO 和 OpenPGP 兩種端到端加密方式,並且容許設定私隱相關的特性譬如已讀回條和正在輸入提示。 Dino 從伺服器取得訊息並與其他裝置同步。 Dino 是一个现代的开源聊天桌面客户端。它致力于提供一个清爽又可靠的 Jabber/XMPP 体验,同时又保护您的隐私。 它支持 OMEMO 和 OpenPGP 端对端加密并允许配置隐私相关的特性比如已读回执和输入提醒。 Dino 从服务器获取消息并和其他设备同步。 Dino masaüstü bilgisayarlar için modern, açık kaynaklı bir sohbet uygulamasıdır. Gizlilik hassasiyetinizi göz önünde bulundurarak temiz ve güvenilir bir Jabber/XMPP deneyimi sunmaya odaklanır. OMEMO ve OpenPGP ile baştan sona şifreleme destekler ve "okundu" bilgisi, "yazıyor..." bildirimi gibi gizlilikle alakalı özelliklerin ayarlanabilmesini sağlar. Dino sunucudan konuşma geçmişini sunucudan çeker ve diğer cihazlara senkronize eder. Dino är en moden chattklient för skrivbordet med öppen källkod. Den erbjuder en elegant och pålitligt upplevelse av Jabber/XMPP samtidigt som den ser efter din integritet. Dino stödjer end-to-end-kryptering med OMEMO och OpenPGP och tillåter konfigurering av funktioner med integritetspåverkan som läsbekräftelser och skriftaviseringar. Dino hämtar historik från servern och synkroniserar meddelanden med andra enheter. Dino është një klient modern fjalosjesh, me burim të hapur, për desktop. Ai përqendrohet në dhënien e një mënyre funksionimi të qartë dhe të qëndrueshme për protokoll Jabber/XMPP, teksa ka në mendje privatësinë tuaj. Mbulon fshehtëzim skaj-më-skaj me OMEMO dhe OpenPGP dhe lejon formësim veçorish të lidhura me privatësinë, bie fjala, dëshmish leximi dhe njoftime shtypjeje. Dino e sjell historikun prej shërbyesve dhe njëkohëson mesazhet në pajisje të tjera. Dino - это современный клиент для чатов с открытым исходным кодом, направленный на надёжное и приватное использование Jabber/XMPP на персональных компьютерах. Он поддерживает сквозное шифрование через OMEMO и OpenPGP и позволяет настраивать такие функции, как уведомления о прочтении и наборе сообщений. Dino загружает историю с сервера и синхронизирует сообщения с другими устройствами. Dino este un client de chat modern, cu sursă deschisă, pentru calculatoare. Se concentrează să furnizeze o experiență Jabber/XMPP clară și fiabilă, ținând cont de confidențialitatea dumneavoastră. Suportă criptare de la un capăt la altul prin intermediul OMEMO și OpenPGP, și permite configurarea caracteristicilor legate de confidențialitate precum trimiterea notificărilor de primire și tastare. Dino preia istoricul discuțiilor de pe server și sincronizează mesajele cu celelalte dispozitive. Dino é um cliente moderno de código aberto de chat para desktop. Ele foca em oferecer uma experiência Jabber/XMPP transparente e confiável levando em consideração a sua privacidade. Ele suporta criptografia ponta-a-ponta com OMEMO e OpenPGP e, além disso, permite configurar funcionalidades relativas à privacidade como notificações de leitura, recebimento e escrita. Dino obtém o histórico do servidor e sincroniza mensagens com outros dispositivos. Dino é um moderno chat de código aberto para desktop. Ele é focado em prover uma transparente e confiável experiência Jabber/XMPP, tendo em mente a sua privacidade. Suporte criptografia ponta a ponta com OMEMO e OpenPGP e permite configurar privacidade—características relacionadas às notificações de leitura, recebimento e escrita. Dino obtém o histórico do servidor e sincroniza mensagens com outros aparelhos. Dino jest nowoczesnym, otwartym komunikatorem. Skupia się na prostej obsłudze sieci Jabber/XMPP dbając o twoją prywatność. Obsługuje szyfrowanie od końca do końca za pomocą OMEMO i OpenPGP, a także daje kontrolę nad funkcjami wpływającymi na prywatność, jak powiadomienia o pisaniu czy odczytaniu wiadomości. Dino pobiera historię rozmów z serwera i synchronizuje wiadomości z innymi urządzeniami. Dino es un client de chat liure e modèrn per l’ordenador de burèu. Ensaja de provesir una experiéncia neta e fisabla de Jabber/XMPP en téner en compte vòstra confidencialitat. Compatible amb lo chiframent OMEMO e OpenPGP del cap a la fin e permet de configurar de foncionalitats ligadas amb la confidencialitat coma los acusats de lectura e las notificacions d’escritura. Dino recupèra l’istoric del servidor e sincroniza los messatges amb d’autres periferics. Dino is een moderne, vrije chattoepassing voor uw bureaublad. Ze biedt een eenvoudige en betrouwbare Jabber/XMPP-ervaring, met uw privacy in het achterhoofd. Ze ondersteunt eind-tot-eind-versleuteling met OMEMO en OpenPGP, en laat u toe privacygerelateerde functies, gelijk leesbevestigingen en typmeldingen, in te stellen. Dino haalt de geschiedenis op van de server en synchroniseert berichten met andere apparaten. Dino is een moderne, vrije chattoepassing voor je computer. Dino biedt een eenvoudige en betrouwbare Jabber-/XMPP-ervaring, met privacy in het achterhoofd. Dino ondersteunt end-to-endversleuteling met OMEMO en OpenPGP en staat je toe privacy-gerelateerde functies, zoals leesbevestigingen en aan-het-typenmeldingen, in te stellen. Dino haalt de geschiedenis op van de server en synchroniseert berichten met andere apparaten. Dino er en moderne friporg-sludringsklient for skrivebordet. Det fokuserer på rask og pålitelig XMPP-opplevelse, samtidig som det hegner om personvernet. Det støtter ende-til-ende -kryptering med OMEMO og OpenPGP, og tillater oppsett av personvernsrelaterte funksjoner som meldingskvitteringer og skrivevarsling. Dino henter historikk fra tjeneren og synkroniserer meldinger med andre enheter. Dino yra šiuolaikinė atvirojo kodo kliento programa skirta darbalaukiui. Jos pagrindinis dėmesys yra pateikti tvarkingą ir patikimą Jabber/XMPP patyrimą nepamirštant apie jūsų privatumą. Ji palaiko ištisinį šifravimą naudojant OMEMO ir OpenPGP bei leidžia konfigūruoti su privatumu susijusias ypatybes, tokias kaip pranešimus apie žinučių skaitymą ir rašymą. Dino gauna istoriją iš serverio ir sinchronizuoja žinutes su kitais įrenginiais. Dino ass e modernen, quell-offene Chat Client fir den Desktop. Hien biet eng opgeraumt a robust Jabber/XMPP Erfarung a leet ee Schwéierpunkt op Privatsphär. Hien ënnerstëtz Enn-zu-Enn Verschlësselung mat OMEMO an OpenPGP an enthält Privatsphäre-Astellungen zu Liesbestätegungen an Tipp-Benoriichtegungen. Dino rifft  Gespréichverläf vum Server of a synchroniséiert Noriichte mat anere Geräter. Dino はオープンソースの現代的なデスクトップ向けチャットクライアントです。プライバシーを考慮しつつ、シンプルで信頼できる Jabber/XMPP エクスペリエンスの提供を第一に考えて開発されています。 OMEMO と OpenPGP を利用したエンドツーエンド暗号化に対応しており、既読状態の送信や入力通知などのプライバシー関連の設定も可能です。 Dino はサーバーから履歴を取得し、ほかのデバイスとメッセージを同期します。 Dino è un client di chat per il desktop, moderno e open-source. Si concentra nel fornire un'esperienza Jabber/XMPP pulita e affidabile tenendo presente la tua privacy. Support la crittografia end-to-end tramite OMEMO e OpenPGP e permette di configurare le funzioni relative alla privacy come le ricevute di lettura e le notifiche di digitazione. Dino recupera la cronologia dal server e sincronizza i messaggi con gli altri dispositivi. Dino es un modern cliente de conversationes con fonte apert. It foca se sur provider un nett e fidibil experientie de Jabber/XMPP con un attention a confidentialitá. It supporta ciffration terminal per OMEMO e OpenPGP e permisse configurar sensitiv functiones quam confirmation de lectada e notificationes de tippada. Dino obtene li diarium del servitore e sincronisa missages inter altri apparates. Dino adalah aplikasi chat open source modern untuk PC. Menyediakan pengalaman Jabber / XMPP yang handal dengan tetap menjunjung privasi Anda. Mendukung enkripsi end-to-end dengan OMEMO dan OpenPGP, dan memungkinkan pengaturan fitur terkait privasi seperti tanda pesan dibaca dan pemberitahuan pengetikan. Dino mengambil riwayat pesan dari server dan menyinkronkan pesan dengan perangkat lain. A Dino egy modern, nyílt forráskódú csevegőprogram az asztali gépekre. Arra összpontosít, hogy tiszta és megbízható Jabber/XMPP-élményt nyújtson, miközben a magánszféra megőrzését is fontosnak tartja. Támogatja az OMEMO és az OpenPGP használatával történő végpontok közötti titkosítást, és lehetővé teszi a magánszférához kapcsolódó funkciókat, mint például az olvasási visszaigazolást és a gépelési értesítéseket. A Dino lekéri az előzményeket a kiszolgálóról, és szinkronizálja az üzeneteket a többi eszközzel. Dino é un cliente moderno e de código aberto para o escritorio. Orientado a fornecer unha experiencia Jabber/XMPP limpa e fiábel tendo a privacidade e seguranza presentes. Suporta o cifrado de punto-a-punto con OMEMO e OpenPGP e permite configurar trazos orientados á privacidade tales coma confirmación de lectura e notificacións de escritura. Dino obtén o histórico dende o servidor e sincroniza as mensaxes con outros dispositivos. Dino est un client de clavardage libre et moderne pour le bureau. Il se concentre sur la fourniture d’une expérience XMPP simple et fiable tout en ayant toujours à l’esprit votre confidentialité. Il prend en charge le chiffrement de bout en bout avec OMEMO et OpenPGP et permet de configurer les fonctions liées à la confidentialité telles que les accusés de réception et les notifications d’écriture. Dino récupère l’historique du serveur et synchronise les messages avec les autres clients. Dino on nykyaikainen avoimen lähdekoodin jutteluohjelma työpöydälle. Se keskittyy tarjoamaan selkeän ja luotettavan Jabber/XMPP-kokemuksen unohtamatta yksityisyyttäsi. Se tukee päästä päähän -salausta OMEMO:n ja OpenPGP:n avulla ja mahdollistaa yksityisyyteen liittyvien ominaisuuksien, kuten lukukuittausten ja kirjoitusilmoitusten asetusten määrittämisen. Dino hakee historian palvelimelta ja synkronisoi viestit muiden laitteiden kanssa. دینو یک کلاینت چت متن‌باز برای دسکتاپ است. تمرکز آن بر فراهم‌کردن تجربه‌ای شسته‌رفته و قابل‌اتکا از جَبِر/XMPP است درحالی که به حریم خصوصی‌تان اهمیت می‌دهد. از رمزگذاری سرتاسر با اُمیمو و اُپن‌پی‌جی‌پی پشتیبانی می‌کند و اجازه تنظیم قابلیت‌های مربوط به حریم خصوصی را می‌دهد، از جمله: رسید خوانده‌شدن پیام‌ها و اعلان در حال نوشتن بودن. دینو تاریخچه را از سرور دریافت می‌کند و پیام‌ها را با دیگر دستگاه‌ها همگام‌سازی می‌کند. Dino mahaigainerako iturburu irekiko txat bezero moderno bat da. Jabber/XMPP esperientzia garbi eta fidagarri bat ematen du zure pribatutasuna kontuan hartzeaz gain. Amaieratik amaierarako enkriptazioa onartzen du OMEMO eta OpenPGPrekin eta pribatutasun ezaugarriak konfiguratzea baimentzen du irakurtze markak eta idazketa jakinarazpenak bezala. Dinok zerbitzaritik hartzen du historia eta beste gailuekin mezuak sinkronizatzen ditu. Dino es un cliente de mensajería moderno y libre para escritorio y móvil. Está enfocado en proveer una experiencia Jabber/XMPP limpia y confiable teniendo la privacidad en mente. Soporta encriptación fin-a-fin a través de OMEMO y OpenPGP y permite configurar las características relacionadas con la privacidad, como confirmaciones de lectura y notificaciones de escritura. Dino recupera el historial de mensajes desde el servidor y sincroniza los mensajes con otros dispositivos. Dino estas moderna malfermfonta retbabililo por la tabla komputilo. Ĝi celas provizi puran kaj fidindan sperton de Jabber/XMPP, protektante vian privatecon. Ĝi subtenas fin-al-finan ĉifradon per OMEMO kaj OpenPGP kaj permesas agordi funkciojn pri privateco kiel kvitancojn de legiteco kaj sciigojn pri tajpado. Dino prenas historion el la servilo kaj sinkronigas mesaĝojn kun aliaj aparatoj. Dino ist ein modernes, quelloffenes Chat-Programm für den Desktop. Es bietet ein aufgeräumtes und robustes Jabber-/XMPP-Erlebnis und legt einen Schwerpunkt auf Privatsphäre. Er unterstützt Ende-zu-Ende Verschlüsselung mit OMEMO und OpenPGP und enthält Privatsphäre-Einstellungen zu Lesebestätigungen und Tippbenachrichtigungen. Dino ruft Gesprächsverläufe vom Server ab und synchronisiert Nachrichten mit anderen Geräten. Dino je moderní open-source chatovací klient pro stolní počítače. Jeho cílem je poskytování čistého a spolehlivého prostředí Jabber/XMPP s důrazem na zachování vašeho soukromí. Podporuje šifrování end-to-end pomocí OMEMO a OpenPGP a umožňuje konfigurovat funkce související se soukromím, jako jsou potvrzení o přečtení a oznámení o psaní. Dino načítá historii ze serveru a synchronizuje zprávy s ostatními zařízeními. Dino és un client de xat lliure i modern per a l'escriptori. Està centrat en proveir una experiència neta i fiable de Jabber/XMPP, sempre tenint en compte la vostra privacitat. Implementa xifratge punt a punt amb OMEMO i OpenPGP, i permet configurar funcionalitats relacionades amb la privacitat com per exemple rebuts de lectura i notificacions d'escriptura. Dino recupera l'historial del servidor i sincronitza els missatges amb altres dispositius. إنّ Dino برنامج عصري ومفتوح المصدر للدردشة صُمّم لسطح المكتب. ويُركّز علي تقديم تجربة نظيفة وموثوق منها لجابر/XMPP مع أخذ خصوصيتكم بعين الإعتبار. وهو يدعم التشفير بواسطة OMEMO و OpenPGP يسمح بإعداد ميزات الخصوصية كالإيصالات المقروءة والإخطارات عند الكتابة. يقوم Dino بجلب السِجلّ مِن السيرفر ثم يُزامِن الرسائل مع الأجهزة الأخرى. Vala Linux FreeBSD complete complete complete partial complete partial Only for viewing avatars partial complete complete complete complete complete complete partial complete partial complete complete complete 1.0 complete complete complete 1.2.0 0.2 partial Not for MUCs complete complete complete partial Only for outgoing messages complete complete dino-0.3.0/dino.doap.in0000644000000000000000000002717714202022370013352 0ustar rootroot Dino dino Modern XMPP Chat Client Dino is a modern open-source chat client for the desktop. It focuses on providing a clean and reliable Jabber/XMPP experience while having your privacy in mind. It supports end-to-end encryption with OMEMO and OpenPGP and allows configuring privacy-related features such as read receipts and typing notifications. Dino fetches history from the server and synchronizes messages with other devices. Vala Linux FreeBSD complete complete complete partial complete partial Only for viewing avatars partial complete complete complete complete complete complete partial complete partial complete complete complete 1.0 complete complete complete 1.2.0 0.2 partial Not for MUCs complete complete complete partial Only for outgoing messages complete complete dino-0.3.0/libdino/0000755000000000000000000000000014202022370012551 5ustar rootrootdino-0.3.0/libdino/CMakeLists.txt0000644000000000000000000000712014202022370015311 0ustar rootrootfind_packages(LIBDINO_PACKAGES REQUIRED GDKPixbuf2 Gee GLib GModule GObject ) vala_precompile(LIBDINO_VALA_C SOURCES src/application.vala src/dbus/login1.vala src/dbus/notifications.vala src/dbus/upower.vala src/entity/account.vala src/entity/call.vala src/entity/conversation.vala src/entity/encryption.vala src/entity/file_transfer.vala src/entity/message.vala src/entity/settings.vala src/plugin/interfaces.vala src/plugin/loader.vala src/plugin/registry.vala src/service/avatar_manager.vala src/service/blocking_manager.vala src/service/call_store.vala src/service/call_state.vala src/service/call_peer_state.vala src/service/calls.vala src/service/chat_interaction.vala src/service/connection_manager.vala src/service/content_item_store.vala src/service/conversation_manager.vala src/service/counterpart_interaction_manager.vala src/service/database.vala src/service/entity_capabilities_storage.vala src/service/entity_info.vala src/service/file_manager.vala src/service/file_transfer_storage.vala src/service/jingle_file_transfers.vala src/service/message_correction.vala src/service/message_processor.vala src/service/message_storage.vala src/service/module_manager.vala src/service/muc_manager.vala src/service/notification_events.vala src/service/presence_manager.vala src/service/registration.vala src/service/roster_manager.vala src/service/search_processor.vala src/service/stream_interactor.vala src/service/util.vala src/util/display_name.vala src/util/util.vala src/util/weak_map.vala CUSTOM_VAPIS "${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi" "${CMAKE_BINARY_DIR}/exports/qlite.vapi" CUSTOM_DEPS xmpp-vala qlite PACKAGES ${LIBDINO_PACKAGES} GENERATE_VAPI dino GENERATE_HEADER dino ) add_custom_command(OUTPUT "${CMAKE_BINARY_DIR}/exports/dino_i18n.h" COMMAND cp "${CMAKE_CURRENT_SOURCE_DIR}/src/dino_i18n.h" "${CMAKE_BINARY_DIR}/exports/dino_i18n.h" DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/dino_i18n.h" COMMENT Copy header file dino_i18n.h ) add_custom_target(dino-vapi DEPENDS ${CMAKE_BINARY_DIR}/exports/dino.vapi ${CMAKE_BINARY_DIR}/exports/dino.deps ${CMAKE_BINARY_DIR}/exports/dino_i18n.h ) add_definitions(${VALA_CFLAGS} -DDINO_SYSTEM_PLUGIN_DIR="${PLUGIN_INSTALL_DIR}" -DDINO_SYSTEM_LIBDIR_NAME="${LIBDIR_NAME}" -DG_LOG_DOMAIN="libdino") add_library(libdino SHARED ${LIBDINO_VALA_C} ${CMAKE_BINARY_DIR}/exports/dino_i18n.h) add_dependencies(libdino dino-vapi) target_link_libraries(libdino xmpp-vala qlite ${LIBDINO_PACKAGES} m) set_target_properties(libdino PROPERTIES PREFIX "" VERSION 0.0 SOVERSION 0) install(TARGETS libdino ${TARGET_INSTALL}) install(FILES ${CMAKE_BINARY_DIR}/exports/dino.vapi ${CMAKE_BINARY_DIR}/exports/dino.deps DESTINATION ${VAPI_INSTALL_DIR}) install(FILES ${CMAKE_BINARY_DIR}/exports/dino.h ${CMAKE_BINARY_DIR}/exports/dino_i18n.h DESTINATION ${INCLUDE_INSTALL_DIR}) if(BUILD_TESTS) vala_precompile(LIBDINO_TEST_VALA_C SOURCES "tests/weak_map.vala" "tests/testcase.vala" "tests/common.vala" CUSTOM_VAPIS ${CMAKE_BINARY_DIR}/exports/dino_internal.vapi ${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi ${CMAKE_BINARY_DIR}/exports/qlite.vapi PACKAGES ${LIBDINO_PACKAGES} OPTIONS ${LIBDINO_EXTRA_OPTIONS} ) add_definitions(${VALA_CFLAGS}) add_executable(libdino-test ${LIBDINO_TEST_VALA_C}) target_link_libraries(libdino-test libdino) endif(BUILD_TESTS) dino-0.3.0/libdino/src/0000755000000000000000000000000014202022370013340 5ustar rootrootdino-0.3.0/libdino/src/application.vala0000644000000000000000000001332414202022370016513 0ustar rootrootusing Dino.Entities; namespace Dino { extern const string VERSION; public interface Application : GLib.Application { public abstract Database db { get; set; } public abstract Dino.Entities.Settings settings { get; set; } public abstract StreamInteractor stream_interactor { get; set; } public abstract Plugins.Registry plugin_registry { get; set; } public abstract SearchPathGenerator? search_path_generator { get; set; } internal static string print_xmpp; private const OptionEntry[] options = { { "print-xmpp", 0, 0, OptionArg.STRING, ref print_xmpp, "Print XMPP stanzas identified by DESC to stderr", "DESC" }, { null } }; public abstract void handle_uri(string jid, string query, Gee.Map options); public void init() throws Error { if (DirUtils.create_with_parents(get_storage_dir(), 0700) == -1) { throw new Error(-1, 0, "Could not create storage dir \"%s\": %s", get_storage_dir(), FileUtils.error_from_errno(errno).to_string()); } this.db = new Database(Path.build_filename(get_storage_dir(), "dino.db")); this.settings = new Dino.Entities.Settings.from_db(db); this.stream_interactor = new StreamInteractor(db); MessageProcessor.start(stream_interactor, db); MessageStorage.start(stream_interactor, db); PresenceManager.start(stream_interactor); CounterpartInteractionManager.start(stream_interactor); BlockingManager.start(stream_interactor); ConversationManager.start(stream_interactor, db); MucManager.start(stream_interactor); AvatarManager.start(stream_interactor, db); RosterManager.start(stream_interactor, db); FileManager.start(stream_interactor, db); Calls.start(stream_interactor, db); CallStore.start(stream_interactor, db); ContentItemStore.start(stream_interactor, db); ChatInteraction.start(stream_interactor); NotificationEvents.start(stream_interactor); SearchProcessor.start(stream_interactor, db); Register.start(stream_interactor, db); EntityInfo.start(stream_interactor, db); MessageCorrection.start(stream_interactor, db); FileTransferStorage.start(stream_interactor, db); create_actions(); startup.connect(() => { stream_interactor.connection_manager.log_options = print_xmpp; Idle.add(() => { restore(); return false; }); }); shutdown.connect(() => { stream_interactor.connection_manager.make_offline_all(); }); open.connect((files, hint) => { if (files.length != 1) { warning("Can't handle more than one URI at once."); return; } File file = files[0]; if (!file.has_uri_scheme("xmpp")) { warning("xmpp:-URI expected"); return; } string uri = file.get_uri(); if (!uri.contains(":")) { warning("Invalid URI"); return; } string r = uri.split(":", 2)[1]; string[] m = r.split("?", 2); string jid = m[0]; while (jid[0] == '/') { jid = jid.substring(1); } jid = Uri.unescape_string(jid); try { jid = new Xmpp.Jid(jid).to_string(); } catch (Xmpp.InvalidJidError e) { warning("Received invalid jid in xmpp:-URI: %s", e.message); } string query = "message"; Gee.Map options = new Gee.HashMap(); if (m.length == 2) { string[] cmds = m[1].split(";"); query = cmds[0]; for (int i = 1; i < cmds.length; ++i) { string[] opt = cmds[i].split("=", 2); options[Uri.unescape_string(opt[0])] = opt.length == 2 ? Uri.unescape_string(opt[1]) : ""; } } activate(); handle_uri(jid, query, options); }); add_main_option_entries(options); } public static string get_storage_dir() { return Path.build_filename(Environment.get_user_data_dir(), "dino"); } public static unowned Application get_default() { return (Dino.Application) GLib.Application.get_default(); } public void create_actions() { SimpleAction accept_subscription_action = new SimpleAction("accept-subscription", VariantType.INT32); accept_subscription_action.activate.connect((variant) => { Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation_by_id(variant.get_int32()); if (conversation == null) return; stream_interactor.get_module(PresenceManager.IDENTITY).approve_subscription(conversation.account, conversation.counterpart); stream_interactor.get_module(PresenceManager.IDENTITY).request_subscription(conversation.account, conversation.counterpart); }); add_action(accept_subscription_action); } protected void add_connection(Account account) { if ((get_flags() & ApplicationFlags.IS_SERVICE) == ApplicationFlags.IS_SERVICE) hold(); stream_interactor.connect_account(account); } protected void remove_connection(Account account) { if ((get_flags() & ApplicationFlags.IS_SERVICE) == ApplicationFlags.IS_SERVICE) release(); stream_interactor.disconnect_account.begin(account); } private void restore() { foreach (Account account in db.get_accounts()) { if (account.enabled) add_connection(account); } } } } dino-0.3.0/libdino/src/dbus/0000755000000000000000000000000014202022370014275 5ustar rootrootdino-0.3.0/libdino/src/dbus/login1.vala0000644000000000000000000000065214202022370016336 0ustar rootrootnamespace Dino { [DBus (name = "org.freedesktop.login1.Manager")] public interface Login1Manager : Object { public signal void PrepareForSleep(bool suspend); } public static async Login1Manager? get_login1() { try { return yield Bus.get_proxy(BusType.SYSTEM, "org.freedesktop.login1", "/org/freedesktop/login1"); } catch (IOError e) { stderr.printf("%s\n", e.message); } return null; } }dino-0.3.0/libdino/src/dbus/notifications.vala0000644000000000000000000000237514202022370020022 0ustar rootrootnamespace Dino { [DBus (name = "org.freedesktop.Notifications")] public interface DBusNotifications : GLib.Object { public signal void action_invoked(uint32 key, string action_key); public signal void notification_closed (uint32 id, uint32 reason); public abstract async uint32 notify(string app_name, uint32 replaces_id, string app_icon, string summary, string body, string[] actions, HashTable hints, int32 expire_timeout) throws DBusError, IOError; public abstract async void get_capabilities(out string[] capabilities) throws Error; public abstract async void close_notification(uint id) throws DBusError, IOError; public abstract async void get_server_information(out string name, out string vendor, out string version, out string spec_version) throws DBusError, IOError; } public static async DBusNotifications? get_notifications_dbus() { try { return yield Bus.get_proxy(BusType.SESSION, "org.freedesktop.Notifications", "/org/freedesktop/Notifications"); } catch (IOError e) { warning("Couldn't get org.freedesktop.Notifications DBus instance: %s\n", e.message); } return null; } }dino-0.3.0/libdino/src/dbus/upower.vala0000644000000000000000000000067514202022370016473 0ustar rootrootnamespace Dino { [DBus (name = "org.freedesktop.UPower")] public interface UPower : Object { public signal void Sleeping(); public signal void Resuming(); } public static UPower? get_upower() { UPower? upower = null; try { upower = Bus.get_proxy_sync(BusType.SYSTEM, "org.freedesktop.UPower", "/org/freedesktop/UPower"); } catch (IOError e) { stderr.printf ("%s\n", e.message); } return upower; } }dino-0.3.0/libdino/src/dino_i18n.h0000644000000000000000000000040514202022370015300 0ustar rootroot#ifndef __DINO_I18N_H__ #define __DINO_I18N_H__ #include #define dino_gettext(MsgId) ((char *) dgettext (GETTEXT_PACKAGE, MsgId)) #define dino_ngettext(MsgId, MsgIdPlural, Int) ((char *) dngettext (GETTEXT_PACKAGE, MsgId, MsgIdPlural, Int)) #endifdino-0.3.0/libdino/src/entity/0000755000000000000000000000000014202022370014654 5ustar rootrootdino-0.3.0/libdino/src/entity/account.vala0000644000000000000000000001031514202022370017155 0ustar rootrootusing Gee; using Xmpp; namespace Dino.Entities { public class Account : Object { public int id { get; set; } public string localpart { get { return full_jid.localpart; } } public string domainpart { get { return full_jid.domainpart; } } public string resourcepart { get { return full_jid.resourcepart;} } public Jid bare_jid { owned get { return full_jid.bare_jid; } } public Jid full_jid { get; private set; } public string? password { get; set; } public string display_name { owned get { return alias ?? bare_jid.to_string(); } } public string? alias { get; set; } public bool enabled { get; set; default = false; } public string? roster_version { get; set; } public DateTime mam_earliest_synced { get; set; default=new DateTime.from_unix_utc(0); } private Database? db; public Account(Jid bare_jid, string? resourcepart, string? password, string? alias) { this.id = -1; if (resourcepart != null) { try { this.full_jid = bare_jid.with_resource(resourcepart); } catch (InvalidJidError e) { warning("Tried to create account with invalid resource (%s), defaulting to auto generated", e.message); } } if (this.full_jid == null) { try { this.full_jid = bare_jid.with_resource("dino." + Random.next_int().to_string("%x")); } catch (InvalidJidError e) { error("Auto-generated resource was invalid (%s)", e.message); } } this.password = password; this.alias = alias; } public Account.from_row(Database db, Qlite.Row row) throws InvalidJidError { this.db = db; id = row[db.account.id]; full_jid = new Jid(row[db.account.bare_jid]).with_resource(row[db.account.resourcepart]); password = row[db.account.password]; alias = row[db.account.alias]; enabled = row[db.account.enabled]; roster_version = row[db.account.roster_version]; mam_earliest_synced = new DateTime.from_unix_utc(row[db.account.mam_earliest_synced]); notify.connect(on_update); } public void persist(Database db) { if (id > 0) return; this.db = db; id = (int) db.account.insert() .value(db.account.bare_jid, bare_jid.to_string()) .value(db.account.resourcepart, resourcepart) .value(db.account.password, password) .value(db.account.alias, alias) .value(db.account.enabled, enabled) .value(db.account.roster_version, roster_version) .value(db.account.mam_earliest_synced, (long)mam_earliest_synced.to_unix()) .perform(); notify.connect(on_update); } public void remove() { db.account.delete().with(db.account.bare_jid, "=", bare_jid.to_string()).perform(); notify.disconnect(on_update); id = -1; db = null; } public bool equals(Account acc) { return equals_func(this, acc); } public static bool equals_func(Account acc1, Account acc2) { return acc1.bare_jid.to_string() == acc2.bare_jid.to_string(); } public static uint hash_func(Account acc) { return acc.bare_jid.to_string().hash(); } private void on_update(Object o, ParamSpec sp) { var update = db.account.update().with(db.account.id, "=", id); switch (sp.name) { case "bare-jid": update.set(db.account.bare_jid, bare_jid.to_string()); break; case "resourcepart": update.set(db.account.resourcepart, resourcepart); break; case "password": update.set(db.account.password, password); break; case "alias": update.set(db.account.alias, alias); break; case "enabled": update.set(db.account.enabled, enabled); break; case "roster-version": update.set(db.account.roster_version, roster_version); break; case "mam-earliest-synced": update.set(db.account.mam_earliest_synced, (long)mam_earliest_synced.to_unix()); break; } update.perform(); } } } dino-0.3.0/libdino/src/entity/call.vala0000644000000000000000000001523214202022370016437 0ustar rootrootusing Xmpp; namespace Dino.Entities { public class Call : Object { public const bool DIRECTION_OUTGOING = true; public const bool DIRECTION_INCOMING = false; public enum State { RINGING, ESTABLISHING, IN_PROGRESS, OTHER_DEVICE, ENDED, DECLINED, MISSED, FAILED } public int id { get; set; default=-1; } public Account account { get; set; } public Jid counterpart { get; set; } public Gee.List counterparts = new Gee.ArrayList(Jid.equals_bare_func); public Jid ourpart { get; set; } public Jid proposer { get { return direction == DIRECTION_OUTGOING ? ourpart : counterpart; } } public bool direction { get; set; } public DateTime time { get; set; } public DateTime local_time { get; set; } public DateTime end_time { get; set; } public Encryption encryption { get; set; default=Encryption.NONE; } public State state { get; set; } private Database? db; public Call.from_row(Database db, Qlite.Row row) throws InvalidJidError { this.db = db; id = row[db.call.id]; account = db.get_account_by_id(row[db.call.account_id]); string our_resource = row[db.call.our_resource]; if (our_resource != null) { ourpart = account.bare_jid.with_resource(our_resource); } else { ourpart = account.bare_jid; } direction = row[db.call.direction]; time = new DateTime.from_unix_utc(row[db.call.time]); local_time = new DateTime.from_unix_utc(row[db.call.local_time]); end_time = new DateTime.from_unix_utc(row[db.call.end_time]); encryption = (Encryption) row[db.call.encryption]; state = (State) row[db.call.state]; Qlite.QueryBuilder counterparts_select = db.call_counterpart.select().with(db.call_counterpart.call_id, "=", id); foreach (Qlite.Row counterparts_row in counterparts_select) { Jid peer = db.get_jid_by_id(counterparts_row[db.call_counterpart.jid_id]); if (!counterparts.contains(peer)) { // Legacy: The first peer is also in the `call` table. Don't add twice. counterparts.add(peer); } } counterpart = db.get_jid_by_id(row[db.call.counterpart_id]); string counterpart_resource = row[db.call.counterpart_resource]; if (counterpart_resource != null) counterpart = counterpart.with_resource(counterpart_resource); if (counterparts.is_empty) { counterparts.add(counterpart); } notify.connect(on_update); } public void persist(Database db) { if (id != -1) return; this.db = db; Qlite.InsertBuilder builder = db.call.insert() .value(db.call.account_id, account.id) .value(db.call.our_resource, ourpart.resourcepart) .value(db.call.direction, direction) .value(db.call.time, (long) time.to_unix()) .value(db.call.local_time, (long) local_time.to_unix()) .value(db.call.encryption, encryption) .value(db.call.state, State.ENDED); // No point in persisting states that can't survive a restart if (end_time != null) { builder.value(db.call.end_time, (long) end_time.to_unix()); } else { builder.value(db.call.end_time, (long) local_time.to_unix()); } if (counterpart != null) { builder.value(db.call.counterpart_id, db.get_jid_id(counterpart)) .value(db.call.counterpart_resource, counterpart.resourcepart); } id = (int) builder.perform(); foreach (Jid peer in counterparts) { db.call_counterpart.insert() .value(db.call_counterpart.call_id, id) .value(db.call_counterpart.jid_id, db.get_jid_id(peer)) .value(db.call_counterpart.resource, peer.resourcepart) .perform(); } notify.connect(on_update); } public void add_peer(Jid peer) { if (counterparts.contains(peer)) return; counterparts.add(peer); if (db != null) { db.call_counterpart.insert() .value(db.call_counterpart.call_id, id) .value(db.call_counterpart.jid_id, db.get_jid_id(peer)) .value(db.call_counterpart.resource, peer.resourcepart) .perform(); } } public bool equals(Call c) { return equals_func(this, c); } public static bool equals_func(Call c1, Call c2) { if (c1.id == c2.id) { return true; } return false; } public static uint hash_func(Call call) { return (uint)call.id; } private void on_update(Object o, ParamSpec sp) { Qlite.UpdateBuilder update_builder = db.call.update().with(db.call.id, "=", id); switch (sp.name) { case "counterpart": update_builder.set(db.call.counterpart_id, db.get_jid_id(counterpart)); update_builder.set(db.call.counterpart_resource, counterpart.resourcepart); break; case "ourpart": update_builder.set(db.call.our_resource, ourpart.resourcepart); break; case "direction": update_builder.set(db.call.direction, direction); break; case "time": update_builder.set(db.call.time, (long) time.to_unix()); break; case "local-time": update_builder.set(db.call.local_time, (long) local_time.to_unix()); break; case "end-time": update_builder.set(db.call.end_time, (long) end_time.to_unix()); break; case "encryption": update_builder.set(db.call.encryption, encryption); break; case "state": // No point in persisting states that can't survive a restart if (state == State.RINGING || state == State.ESTABLISHING || state == State.IN_PROGRESS) return; update_builder.set(db.call.state, state); break; } update_builder.perform(); } } } dino-0.3.0/libdino/src/entity/conversation.vala0000644000000000000000000001770514202022370020245 0ustar rootrootusing Xmpp; namespace Dino.Entities { public class Conversation : Object { public signal void object_updated(Conversation conversation); public enum Type { CHAT, GROUPCHAT, GROUPCHAT_PM; public bool is_muc_semantic() { return this == GROUPCHAT || this == GROUPCHAT_PM; } } public int id { get; set; } public Type type_ { get; set; } public Account account { get; private set; } public Jid counterpart { get; private set; } public string? nickname { get; set; } public bool active { get; set; default = false; } private DateTime? _last_active; public DateTime? last_active { get { return _last_active; } set { if (_last_active == null || (value != null && value.difference(_last_active) > 0)) { _last_active = value; } } } public Encryption encryption { get; set; default = Encryption.NONE; } public Message? read_up_to { get; set; } public int read_up_to_item { get; set; default=-1; } public enum NotifySetting { DEFAULT, ON, OFF, HIGHLIGHT } public NotifySetting notify_setting { get; set; default = NotifySetting.DEFAULT; } public enum Setting { DEFAULT, ON, OFF } public Setting send_typing { get; set; default = Setting.DEFAULT; } public Setting send_marker { get; set; default = Setting.DEFAULT; } private Database? db; public Conversation(Jid jid, Account account, Type type) { this.account = account; this.counterpart = jid; this.type_ = type; } public Conversation.from_row(Database db, Qlite.Row row) throws InvalidJidError { this.db = db; id = row[db.conversation.id]; type_ = (Conversation.Type) row[db.conversation.type_]; account = db.get_account_by_id(row[db.conversation.account_id]); string? resource = row[db.conversation.resource]; counterpart = db.get_jid_by_id(row[db.conversation.jid_id]); if (type_ == Conversation.Type.GROUPCHAT_PM) counterpart = counterpart.with_resource(resource); nickname = type_ == Conversation.Type.GROUPCHAT ? resource : null; active = row[db.conversation.active]; int64? last_active = row[db.conversation.last_active]; if (last_active != null) this.last_active = new DateTime.from_unix_utc(last_active); encryption = (Encryption) row[db.conversation.encryption]; int? read_up_to = row[db.conversation.read_up_to]; if (read_up_to != null) this.read_up_to = db.get_message_by_id(read_up_to); read_up_to_item = row[db.conversation.read_up_to_item]; notify_setting = (NotifySetting) row[db.conversation.notification]; send_typing = (Setting) row[db.conversation.send_typing]; send_marker = (Setting) row[db.conversation.send_marker]; notify.connect(on_update); } public void persist(Database db) { this.db = db; var insert = db.conversation.insert() .value(db.conversation.account_id, account.id) .value(db.conversation.jid_id, db.get_jid_id(counterpart)) .value(db.conversation.type_, type_) .value(db.conversation.encryption, encryption) .value(db.conversation.active, active) .value(db.conversation.notification, notify_setting) .value(db.conversation.send_typing, send_typing) .value(db.conversation.send_marker, send_marker); if (read_up_to != null) { insert.value(db.conversation.read_up_to, read_up_to.id); } if (read_up_to_item != -1) { insert.value(db.conversation.read_up_to_item, read_up_to_item); } if (nickname != null) { insert.value(db.conversation.resource, nickname); } if (counterpart.is_full()) { insert.value(db.conversation.resource, counterpart.resourcepart); } if (last_active != null) { insert.value(db.conversation.last_active, (long) last_active.to_unix()); } id = (int) insert.perform(); notify.connect(on_update); } public NotifySetting get_notification_setting(StreamInteractor stream_interactor) { return notify_setting != NotifySetting.DEFAULT ? notify_setting : get_notification_default_setting(stream_interactor); } public NotifySetting get_notification_default_setting(StreamInteractor stream_interactor) { if (!Application.get_default().settings.notifications) return NotifySetting.OFF; if (type_ == Type.GROUPCHAT) { if (stream_interactor.get_module(MucManager.IDENTITY).is_private_room(this.account, this.counterpart)) { return NotifySetting.ON; } else { return NotifySetting.HIGHLIGHT; } } return NotifySetting.ON; } public Setting get_send_typing_setting(StreamInteractor stream_interactor) { if (send_typing != Setting.DEFAULT) return send_typing; if (stream_interactor.get_module(MucManager.IDENTITY).is_public_room(this.account, this.counterpart)) return Setting.OFF; return Application.get_default().settings.send_typing ? Setting.ON : Setting.OFF; } public Setting get_send_marker_setting(StreamInteractor stream_interactor) { if (send_marker != Setting.DEFAULT) return send_marker; if (stream_interactor.get_module(MucManager.IDENTITY).is_public_room(this.account, this.counterpart)) return Setting.OFF; return Application.get_default().settings.send_marker ? Setting.ON : Setting.OFF; } public bool equals(Conversation? conversation) { if (conversation == null) return false; return equals_func(this, conversation); } public static bool equals_func(Conversation conversation1, Conversation conversation2) { return conversation1.counterpart.equals(conversation2.counterpart) && conversation1.account.equals(conversation2.account) && conversation1.type_ == conversation2.type_; } public static uint hash_func(Conversation conversation) { return conversation.counterpart.to_string().hash() ^ conversation.account.bare_jid.to_string().hash(); } private void on_update(Object o, ParamSpec sp) { var update = db.conversation.update().with(db.conversation.id, "=", id); switch (sp.name) { case "type-": update.set(db.conversation.type_, type_); break; case "encryption": update.set(db.conversation.encryption, encryption); break; case "read-up-to": if (read_up_to != null) { update.set(db.conversation.read_up_to, read_up_to.id); } else { update.set_null(db.conversation.read_up_to); } break; case "read-up-to-item": if (read_up_to_item != -1) { update.set(db.conversation.read_up_to_item, read_up_to_item); } else { update.set_null(db.conversation.read_up_to_item); } break; case "nickname": update.set(db.conversation.resource, nickname); break; case "active": update.set(db.conversation.active, active); break; case "last-active": if (last_active != null) { update.set(db.conversation.last_active, (long) last_active.to_unix()); } else { update.set_null(db.conversation.last_active); } break; case "notify-setting": update.set(db.conversation.notification, notify_setting); break; case "send-typing": update.set(db.conversation.send_typing, send_typing); break; case "send-marker": update.set(db.conversation.send_marker, send_marker); break; } update.perform(); } } } dino-0.3.0/libdino/src/entity/encryption.vala0000644000000000000000000000022414202022370017711 0ustar rootrootnamespace Dino.Entities { public enum Encryption { NONE, PGP, OMEMO, DTLS_SRTP, SRTP, UNKNOWN, } }dino-0.3.0/libdino/src/entity/file_transfer.vala0000644000000000000000000001561314202022370020352 0ustar rootrootusing Xmpp; namespace Dino.Entities { public class FileTransfer : Object { public const bool DIRECTION_SENT = true; public const bool DIRECTION_RECEIVED = false; public enum State { COMPLETE, IN_PROGRESS, NOT_STARTED, FAILED } public int id { get; set; default=-1; } public Account account { get; set; } public Jid counterpart { get; set; } public Jid ourpart { get; set; } public Jid? from { get { return direction == DIRECTION_SENT ? ourpart : counterpart; } } public Jid? to { get { return direction == DIRECTION_SENT ? counterpart : ourpart; } } public bool direction { get; set; } public DateTime time { get; set; } public DateTime? local_time { get; set; } public Encryption encryption { get; set; default=Encryption.NONE; } private InputStream? input_stream_ = null; public InputStream input_stream { get { if (input_stream_ == null) { File file = File.new_for_path(Path.build_filename(storage_dir, path ?? file_name)); try { input_stream_ = file.read(); } catch (Error e) { } } return input_stream_; } set { input_stream_ = value; } } private string file_name_; public string file_name { get { return file_name_; } set { file_name_ = Path.get_basename(value); if (file_name_ == Path.DIR_SEPARATOR_S || file_name_ == ".") { file_name_ = "unknown filename"; } else if (file_name_.has_prefix(".")) { file_name_ = "_" + file_name_; } } } private string? server_file_name_ = null; public string server_file_name { get { return server_file_name_ ?? file_name; } set { server_file_name_ = value; } } public string path { get; set; } public string? mime_type { get; set; } // TODO(hrxi): expand to 64 bit public int size { get; set; default=-1; } public State state { get; set; default=State.NOT_STARTED; } public int provider { get; set; } public string info { get; set; } private Database? db; private string storage_dir; public FileTransfer.from_row(Database db, Qlite.Row row, string storage_dir) throws InvalidJidError { this.db = db; this.storage_dir = storage_dir; id = row[db.file_transfer.id]; account = db.get_account_by_id(row[db.file_transfer.account_id]); // TODO don’t have to generate acc new counterpart = db.get_jid_by_id(row[db.file_transfer.counterpart_id]); string counterpart_resource = row[db.file_transfer.counterpart_resource]; if (counterpart_resource != null) counterpart = counterpart.with_resource(counterpart_resource); string our_resource = row[db.file_transfer.our_resource]; if (our_resource != null) { ourpart = account.bare_jid.with_resource(our_resource); } else { ourpart = account.bare_jid; } direction = row[db.file_transfer.direction]; time = new DateTime.from_unix_utc(row[db.file_transfer.time]); local_time = new DateTime.from_unix_utc(row[db.file_transfer.local_time]); encryption = (Encryption) row[db.file_transfer.encryption]; file_name = row[db.file_transfer.file_name]; path = row[db.file_transfer.path]; mime_type = row[db.file_transfer.mime_type]; size = row[db.file_transfer.size]; state = (State) row[db.file_transfer.state]; provider = row[db.file_transfer.provider]; info = row[db.file_transfer.info]; notify.connect(on_update); } public void persist(Database db) { if (id != -1) return; this.db = db; Qlite.InsertBuilder builder = db.file_transfer.insert() .value(db.file_transfer.account_id, account.id) .value(db.file_transfer.counterpart_id, db.get_jid_id(counterpart)) .value(db.file_transfer.counterpart_resource, counterpart.resourcepart) .value(db.file_transfer.our_resource, ourpart.resourcepart) .value(db.file_transfer.direction, direction) .value(db.file_transfer.time, (long) time.to_unix()) .value(db.file_transfer.local_time, (long) local_time.to_unix()) .value(db.file_transfer.encryption, encryption) .value(db.file_transfer.file_name, file_name) .value(db.file_transfer.size, size) .value(db.file_transfer.state, state) .value(db.file_transfer.provider, provider) .value(db.file_transfer.info, info); if (file_name != null) builder.value(db.file_transfer.file_name, file_name); if (path != null) builder.value(db.file_transfer.path, path); if (mime_type != null) builder.value(db.file_transfer.mime_type, mime_type); id = (int) builder.perform(); notify.connect(on_update); } public File get_file() { return File.new_for_path(Path.build_filename(Dino.get_storage_dir(), "files", path)); } private void on_update(Object o, ParamSpec sp) { Qlite.UpdateBuilder update_builder = db.file_transfer.update().with(db.file_transfer.id, "=", id); switch (sp.name) { case "counterpart": update_builder.set(db.file_transfer.counterpart_id, db.get_jid_id(counterpart)); update_builder.set(db.file_transfer.counterpart_resource, counterpart.resourcepart); break; case "ourpart": update_builder.set(db.file_transfer.our_resource, ourpart.resourcepart); break; case "direction": update_builder.set(db.file_transfer.direction, direction); break; case "time": update_builder.set(db.file_transfer.time, (long) time.to_unix()); break; case "local-time": update_builder.set(db.file_transfer.local_time, (long) local_time.to_unix()); break; case "encryption": update_builder.set(db.file_transfer.encryption, encryption); break; case "file-name": update_builder.set(db.file_transfer.file_name, file_name); break; case "path": update_builder.set(db.file_transfer.path, path); break; case "mime-type": update_builder.set(db.file_transfer.mime_type, mime_type); break; case "size": update_builder.set(db.file_transfer.size, size); break; case "state": if (state == State.IN_PROGRESS) return; update_builder.set(db.file_transfer.state, state); break; case "provider": update_builder.set(db.file_transfer.provider, provider); break; case "info": update_builder.set(db.file_transfer.info, info); break; } update_builder.perform(); } } } dino-0.3.0/libdino/src/entity/message.vala0000644000000000000000000001662614202022370017160 0ustar rootrootusing Gee; using Xmpp; namespace Dino.Entities { public class Message : Object { public const bool DIRECTION_SENT = true; public const bool DIRECTION_RECEIVED = false; public enum Marked { NONE, RECEIVED, READ, ACKNOWLEDGED, UNSENT, WONTSEND, SENDING, SENT, ERROR } public static Marked[] MARKED_RECEIVED = new Marked[] { Marked.READ, Marked.RECEIVED, Marked.ACKNOWLEDGED }; public enum Type { ERROR, CHAT, GROUPCHAT, GROUPCHAT_PM, UNKNOWN; public bool is_muc_semantic() { return this == GROUPCHAT || this == GROUPCHAT_PM; } } public int id { get; set; default = -1; } public Account account { get; set; } public Jid? counterpart { get; set; } public Jid? ourpart { get; set; } public Jid? from { get { return direction == DIRECTION_SENT ? ourpart : counterpart; } } public Jid? to { get { return direction == DIRECTION_SENT ? counterpart : ourpart; } } public bool direction { get; set; } public Jid? real_jid { get; set; } public Type type_ { get; set; default = Type.UNKNOWN; } private string? body_; public string? body { get { return body_; } set { body_ = value != null ? value.make_valid() : null; } } public string? stanza_id { get; set; } public string? server_id { get; set; } public DateTime? time { get; set; } /** UTC **/ public DateTime? local_time { get; set; } public Encryption encryption { get; set; default = Encryption.NONE; } private Marked marked_ = Marked.NONE; public Marked marked { get { return marked_; } set { if (value == Marked.RECEIVED && marked == Marked.READ) return; marked_ = value; } } public string? edit_to = null; private Database? db; public Message(string? body) { this.body = body; } public Message.from_row(Database db, Qlite.Row row) throws InvalidJidError { this.db = db; id = row[db.message.id]; account = db.get_account_by_id(row[db.message.account_id]); stanza_id = row[db.message.stanza_id]; server_id = row[db.message.server_id]; type_ = (Message.Type) row[db.message.type_]; counterpart = db.get_jid_by_id(row[db.message.counterpart_id]); string counterpart_resource = row[db.message.counterpart_resource]; if (counterpart_resource != null) counterpart = counterpart.with_resource(counterpart_resource); string our_resource = row[db.message.our_resource]; if (type_ == Type.GROUPCHAT && our_resource != null) { ourpart = counterpart.with_resource(our_resource); } else if (our_resource != null) { ourpart = account.bare_jid.with_resource(our_resource); } else { ourpart = account.bare_jid; } direction = row[db.message.direction]; time = new DateTime.from_unix_utc(row[db.message.time]); local_time = new DateTime.from_unix_utc(row[db.message.local_time]); body = row[db.message.body]; marked = (Message.Marked) row[db.message.marked]; encryption = (Encryption) row[db.message.encryption]; string? real_jid_str = row[db.real_jid.real_jid]; if (real_jid_str != null) real_jid = new Jid(real_jid_str); edit_to = row[db.message_correction.to_stanza_id]; notify.connect(on_update); } public void persist(Database db) { if (id != -1) return; this.db = db; Qlite.InsertBuilder builder = db.message.insert() .value(db.message.account_id, account.id) .value(db.message.counterpart_id, db.get_jid_id(counterpart)) .value(db.message.counterpart_resource, counterpart.resourcepart) .value(db.message.our_resource, ourpart.resourcepart) .value(db.message.direction, direction) .value(db.message.type_, type_) .value(db.message.time, (long) time.to_unix()) .value(db.message.local_time, (long) local_time.to_unix()) .value(db.message.body, body) .value(db.message.encryption, encryption) .value(db.message.marked, marked); if (stanza_id != null) builder.value(db.message.stanza_id, stanza_id); if (server_id != null) builder.value(db.message.server_id, server_id); id = (int) builder.perform(); if (real_jid != null) { db.real_jid.insert() .value(db.real_jid.message_id, id) .value(db.real_jid.real_jid, real_jid.to_string()) .perform(); } notify.connect(on_update); } public void set_type_string(string type) { switch (type) { case Xmpp.MessageStanza.TYPE_CHAT: type_ = Type.CHAT; break; case Xmpp.MessageStanza.TYPE_GROUPCHAT: type_ = Type.GROUPCHAT; break; } } public new string get_type_string() { switch (type_) { case Type.CHAT: return Xmpp.MessageStanza.TYPE_CHAT; case Type.GROUPCHAT: return Xmpp.MessageStanza.TYPE_GROUPCHAT; default: return Xmpp.MessageStanza.TYPE_NORMAL; } } public bool equals(Message? m) { if (m == null) return false; return equals_func(this, m); } public static bool equals_func(Message m1, Message m2) { if (m1.stanza_id == m2.stanza_id && m1.body == m2.body) { return true; } return false; } public static uint hash_func(Message message) { return message.body.hash(); } private void on_update(Object o, ParamSpec sp) { Qlite.UpdateBuilder update_builder = db.message.update().with(db.message.id, "=", id); switch (sp.name) { case "stanza-id": update_builder.set(db.message.stanza_id, stanza_id); break; case "server-id": update_builder.set(db.message.server_id, server_id); break; case "counterpart": update_builder.set(db.message.counterpart_id, db.get_jid_id(counterpart)); update_builder.set(db.message.counterpart_resource, counterpart.resourcepart); break; case "ourpart": update_builder.set(db.message.our_resource, ourpart.resourcepart); break; case "direction": update_builder.set(db.message.direction, direction); break; case "type-": update_builder.set(db.message.type_, type_); break; case "time": update_builder.set(db.message.time, (long) time.to_unix()); break; case "local-time": update_builder.set(db.message.local_time, (long) local_time.to_unix()); break; case "body": update_builder.set(db.message.body, body); break; case "encryption": update_builder.set(db.message.encryption, encryption); break; case "marked": update_builder.set(db.message.marked, marked); break; } update_builder.perform(); if (sp.get_name() == "real-jid") { db.real_jid.upsert() .value(db.real_jid.message_id, id, true) .value(db.real_jid.real_jid, real_jid.to_string()) .perform(); } } } } dino-0.3.0/libdino/src/entity/settings.vala0000644000000000000000000000507014202022370017363 0ustar rootrootnamespace Dino.Entities { public class Settings : Object { private Database db; public Settings.from_db(Database db) { this.db = db; send_typing_ = col_to_bool_or_default("send_typing", true); send_marker_ = col_to_bool_or_default("send_marker", true); notifications_ = col_to_bool_or_default("notifications", true); convert_utf8_smileys_ = col_to_bool_or_default("convert_utf8_smileys", true); check_spelling = col_to_bool_or_default("check_spelling", true); } private bool col_to_bool_or_default(string key, bool def) { string? val = db.settings.select({db.settings.value}).with(db.settings.key, "=", key)[db.settings.value]; return val != null ? bool.parse(val) : def; } private bool send_typing_; public bool send_typing { get { return send_typing_; } set { db.settings.upsert() .value(db.settings.key, "send_typing", true) .value(db.settings.value, value.to_string()) .perform(); send_typing_ = value; } } private bool send_marker_; public bool send_marker { get { return send_marker_; } set { db.settings.upsert() .value(db.settings.key, "send_marker", true) .value(db.settings.value, value.to_string()) .perform(); send_marker_ = value; } } private bool notifications_; public bool notifications { get { return notifications_; } set { db.settings.upsert() .value(db.settings.key, "notifications", true) .value(db.settings.value, value.to_string()) .perform(); notifications_ = value; } } private bool convert_utf8_smileys_; public bool convert_utf8_smileys { get { return convert_utf8_smileys_; } set { db.settings.upsert() .value(db.settings.key, "convert_utf8_smileys", true) .value(db.settings.value, value.to_string()) .perform(); convert_utf8_smileys_ = value; } } private bool check_spelling_; public bool check_spelling { get { return check_spelling_; } set { db.settings.upsert() .value(db.settings.key, "check_spelling", true) .value(db.settings.value, value.to_string()) .perform(); check_spelling_ = value; } } } } dino-0.3.0/libdino/src/plugin/0000755000000000000000000000000014202022370014636 5ustar rootrootdino-0.3.0/libdino/src/plugin/interfaces.vala0000644000000000000000000001570114202022370017632 0ustar rootrootusing Dino.Entities; using Xmpp; namespace Dino.Plugins { public enum Priority { LOWEST, LOWER, DEFAULT, HIGHER, HIGHEST } public enum WidgetType { GTK } public interface RootInterface : Object { public abstract void registered(Dino.Application app); public abstract void shutdown(); } public interface EncryptionListEntry : Object { public abstract Entities.Encryption encryption { get; } public abstract string name { get; } public abstract void encryption_activated(Entities.Conversation conversation, Plugins.SetInputFieldStatus callback); public abstract Object? get_encryption_icon(Entities.Conversation conversation, ContentItem content_item); } public interface CallEncryptionEntry : Object { public abstract CallEncryptionWidget? get_widget(Account account, Xmpp.Xep.Jingle.ContentEncryption encryption); } public interface CallEncryptionWidget : Object { public abstract string? get_title(); public abstract bool show_keys(); public abstract string? get_icon_name(); } public abstract class AccountSettingsEntry : Object { public abstract string id { get; } public virtual Priority priority { get { return Priority.DEFAULT; } } public abstract string name { get; } public virtual int16 label_top_padding { get { return -1; } } public abstract AccountSettingsWidget? get_widget(WidgetType type); } public interface AccountSettingsWidget : Object { public abstract void set_account(Account account); public abstract signal void activated(); public abstract void deactivate(); } public interface ContactDetailsProvider : Object { public abstract string id { get; } public abstract void populate(Conversation conversation, ContactDetails contact_details, WidgetType type); } public class ContactDetails : Object { public signal void save(); public signal void add(string category, string label, string? desc, Object widget); } public interface TextCommand : Object { public abstract string cmd { get; } public abstract string? handle_command(string? text, Entities.Conversation? conversation); } public interface ConversationTitlebarEntry : Object { public abstract string id { get; } public abstract double order { get; } public abstract ConversationTitlebarWidget? get_widget(WidgetType type); } public interface ConversationTitlebarWidget : Object { public abstract void set_conversation(Conversation conversation); public abstract void unset_conversation(); } public abstract interface ConversationItemPopulator : Object { public abstract string id { get; } public abstract void init(Conversation conversation, ConversationItemCollection summary, WidgetType type); public abstract void close(Conversation conversation); } public abstract interface ConversationAdditionPopulator : ConversationItemPopulator { public virtual void populate_timespan(Conversation conversation, DateTime from, DateTime to) { } } public abstract interface VideoCallPlugin : Object { public abstract bool supports(string? media); // Video widget public abstract VideoCallWidget? create_widget(WidgetType type); // Devices public signal void devices_changed(string media, bool incoming); public abstract Gee.List get_devices(string media, bool incoming); public abstract MediaDevice? get_preferred_device(string media, bool incoming); public abstract MediaDevice? get_device(Xmpp.Xep.JingleRtp.Stream? stream, bool incoming); public abstract void set_pause(Xmpp.Xep.JingleRtp.Stream? stream, bool pause); public abstract void set_device(Xmpp.Xep.JingleRtp.Stream? stream, MediaDevice? device); public abstract void dump_dot(); } public abstract interface VideoCallWidget : Object { public signal void resolution_changed(uint width, uint height); public abstract void display_stream(Xmpp.Xep.JingleRtp.Stream? stream, Jid jid); public abstract void display_device(MediaDevice device); public abstract void detach(); } public abstract interface MediaDevice : Object { public abstract string id { owned get; } public abstract string display_name { owned get; } public abstract string? detail_name { owned get; } public abstract string? media { owned get; } public abstract bool incoming { get; } } public abstract interface NotificationPopulator : Object { public abstract string id { get; } public abstract void init(Conversation conversation, NotificationCollection summary, WidgetType type); public abstract void close(Conversation conversation); } public abstract class MetaConversationItem : Object { public virtual string populator_id { get; set; } public virtual Jid? jid { get; set; default=null; } public virtual DateTime time { get; set; default = new DateTime.now_utc(); } public virtual int secondary_sort_indicator { get; set; } public virtual Encryption encryption { get; set; default = Encryption.NONE; } public virtual Entities.Message.Marked mark { get; set; default = Entities.Message.Marked.NONE; } public bool can_merge { get; set; default=false; } public bool requires_avatar { get; set; default=false; } public bool requires_header { get; set; default=false; } public bool in_edit_mode { get; set; default=false; } public abstract Object? get_widget(WidgetType type); public abstract Gee.List? get_item_actions(WidgetType type); } public delegate void MessageActionEvoked(Object button, Plugins.MetaConversationItem evoked_on, Object widget); public class MessageAction : Object { public string icon_name; public MessageActionEvoked callback; } public abstract class MetaConversationNotification : Object { public abstract Object? get_widget(WidgetType type); } public interface ConversationItemCollection : Object { public signal void inserted_item(MetaConversationItem item); public signal void removed_item(MetaConversationItem item); public abstract void insert_item(MetaConversationItem item); public abstract void remove_item(MetaConversationItem item); } public interface NotificationCollection : Object { public signal void add_meta_notification(MetaConversationNotification item); public signal void remove_meta_notification(MetaConversationNotification item); } public delegate void SetInputFieldStatus(InputFieldStatus field_status); public class InputFieldStatus : Object { public enum MessageType { NONE, INFO, WARNING, ERROR } public enum InputState { NORMAL, DISABLED, NO_SEND } public string? message; public MessageType message_type; public InputState input_state; public bool contains_markup; public InputFieldStatus(string? message, MessageType message_type, InputState input_state, bool contains_markup = false) { this.message = message; this.message_type = message_type; this.input_state = input_state; this.contains_markup = contains_markup; } } } dino-0.3.0/libdino/src/plugin/loader.vala0000644000000000000000000000532214202022370016753 0ustar rootrootusing Gee; namespace Dino.Plugins { private class Info : Object { public Module module; public Type gtype; public Info(Type type, owned Module module) { this.module = (owned) module; this.gtype = type; } } public class Loader : Object { [CCode (has_target = false)] private delegate Type RegisterPluginFunction(Module module); private Application app; private string[] search_paths; private RootInterface[] plugins = new RootInterface[0]; private Info[] infos = new Info[0]; public Loader(Application app) { this.app = app; this.search_paths = app.search_path_generator.get_plugin_paths(); } public void load_all() throws Error { if (Module.supported() == false) { throw new Error(-1, 0, "Plugins are not supported"); } HashSet plugin_names = new HashSet(); foreach (string path in search_paths) { try { Dir dir = Dir.open(path, 0); string? file = null; while ((file = dir.read_name()) != null) { if (file.has_suffix(Module.SUFFIX)) plugin_names.add(file); } } catch (Error e) { // Ignore this folder } } foreach (string plugin in plugin_names) { load(plugin); } } public RootInterface load(string name) throws Error { if (Module.supported() == false) { throw new Error(-1, 0, "Plugins are not supported"); } Module module = null; string path = ""; foreach (string prefix in search_paths) { path = Path.build_filename(prefix, name); module = Module.open(path, ModuleFlags.BIND_LAZY); if (module != null) break; } if (module == null) { throw new Error(-1, 1, "%s", Module.error().replace(path, name)); } void* function; module.symbol("register_plugin", out function); if (function == null) { throw new Error(-1, 2, "register_plugin () not found"); } RegisterPluginFunction register_plugin = (RegisterPluginFunction) function; Type type = register_plugin(module); if (type.is_a(typeof(RootInterface)) == false) { throw new Error(-1, 3, "Unexpected type"); } Info info = new Plugins.Info(type, (owned) module); infos += info; RootInterface plugin = (RootInterface) Object.new (type); plugins += plugin; plugin.registered(app); return plugin; } public void shutdown() { foreach (RootInterface p in plugins) { p.shutdown(); } } } } dino-0.3.0/libdino/src/plugin/registry.vala0000644000000000000000000000750314202022370017360 0ustar rootrootusing Gee; namespace Dino.Plugins { public class Registry { internal ArrayList encryption_list_entries = new ArrayList(); internal HashMap call_encryption_entries = new HashMap(); internal ArrayList account_settings_entries = new ArrayList(); internal ArrayList contact_details_entries = new ArrayList(); internal Map text_commands = new HashMap(); internal Gee.List conversation_addition_populators = new ArrayList(); internal Gee.List notification_populators = new ArrayList(); internal Gee.Collection conversation_titlebar_entries = new Gee.TreeSet((a, b) => { return (int)(a.order - b.order); }); public VideoCallPlugin? video_call_plugin; public bool register_encryption_list_entry(EncryptionListEntry entry) { lock(encryption_list_entries) { foreach(var e in encryption_list_entries) { if (e.encryption == entry.encryption) return false; } encryption_list_entries.add(entry); encryption_list_entries.sort((a,b) => b.name.collate(a.name)); return true; } } public bool register_call_entryption_entry(string ns, CallEncryptionEntry entry) { lock (call_encryption_entries) { call_encryption_entries[ns] = entry; } return true; } public bool register_account_settings_entry(AccountSettingsEntry entry) { lock(account_settings_entries) { foreach(var e in account_settings_entries) { if (e.id == entry.id) return false; } account_settings_entries.add(entry); // TODO: Order by priority account_settings_entries.sort((a,b) => b.name.collate(a.name)); return true; } } public bool register_contact_details_entry(ContactDetailsProvider entry) { lock(contact_details_entries) { foreach(ContactDetailsProvider e in contact_details_entries) { if (e.id == entry.id) return false; } contact_details_entries.add(entry); return true; } } public bool register_text_command(TextCommand cmd) { lock(text_commands) { if (text_commands.has_key(cmd.cmd)) return false; text_commands[cmd.cmd] = cmd; return true; } } public bool register_contact_titlebar_entry(ConversationTitlebarEntry entry) { lock(conversation_titlebar_entries) { foreach(ConversationTitlebarEntry e in conversation_titlebar_entries) { if (e.id == entry.id) return false; } conversation_titlebar_entries.add(entry); return true; } } public bool register_conversation_addition_populator(ConversationAdditionPopulator populator) { lock (conversation_addition_populators) { foreach(ConversationItemPopulator p in conversation_addition_populators) { if (p.id == populator.id) return false; } conversation_addition_populators.add(populator); return true; } } public bool register_notification_populator(NotificationPopulator populator) { lock (notification_populators) { foreach(NotificationPopulator p in notification_populators) { if (p.id == populator.id) return false; } notification_populators.add(populator); return true; } } } } dino-0.3.0/libdino/src/service/0000755000000000000000000000000014202022370015000 5ustar rootrootdino-0.3.0/libdino/src/service/avatar_manager.vala0000644000000000000000000002345114202022370020622 0ustar rootrootusing Gdk; using Gee; using Qlite; using Xmpp; using Dino.Entities; namespace Dino { public class AvatarManager : StreamInteractionModule, Object { public static ModuleIdentity IDENTITY = new ModuleIdentity("avatar_manager"); public string id { get { return IDENTITY.id; } } public signal void received_avatar(Jid jid, Account account); private enum Source { USER_AVATARS, VCARD } private StreamInteractor stream_interactor; private Database db; private string folder = null; private HashMap user_avatars = new HashMap(Jid.hash_func, Jid.equals_func); private HashMap vcard_avatars = new HashMap(Jid.hash_func, Jid.equals_func); private HashMap cached_pixbuf = new HashMap(); private HashMap> pending_pixbuf = new HashMap>(); private const int MAX_PIXEL = 192; public static void start(StreamInteractor stream_interactor, Database db) { AvatarManager m = new AvatarManager(stream_interactor, db); stream_interactor.add_module(m); } private AvatarManager(StreamInteractor stream_interactor, Database db) { this.stream_interactor = stream_interactor; this.db = db; this.folder = Path.build_filename(Dino.get_storage_dir(), "avatars"); DirUtils.create_with_parents(this.folder, 0700); stream_interactor.account_added.connect(on_account_added); stream_interactor.module_manager.initialize_account_modules.connect((_, modules) => { modules.add(new Xep.UserAvatars.Module()); modules.add(new Xep.VCard.Module()); }); } private string? get_avatar_hash(Account account, Jid jid_) { Jid jid = jid_; if (!stream_interactor.get_module(MucManager.IDENTITY).is_groupchat_occupant(jid_, account)) { jid = jid_.bare_jid; } if (user_avatars.has_key(jid)) { return user_avatars[jid]; } else if (vcard_avatars.has_key(jid)) { return vcard_avatars[jid]; } else { return null; } } public bool has_avatar_cached(Account account, Jid jid) { string? hash = get_avatar_hash(account, jid); return hash != null && cached_pixbuf.has_key(hash); } public bool has_avatar(Account account, Jid jid) { return get_avatar_hash(account, jid) != null; } public Pixbuf? get_cached_avatar(Account account, Jid jid_) { string? hash = get_avatar_hash(account, jid_); if (hash == null) return null; if (cached_pixbuf.has_key(hash)) return cached_pixbuf[hash]; return null; } public async Pixbuf? get_avatar(Account account, Jid jid_) { Jid jid = jid_; if (!stream_interactor.get_module(MucManager.IDENTITY).is_groupchat_occupant(jid_, account)) { jid = jid_.bare_jid; } int source = -1; string? hash = null; if (user_avatars.has_key(jid)) { hash = user_avatars[jid]; source = 1; } else if (vcard_avatars.has_key(jid)) { hash = vcard_avatars[jid]; source = 2; } if (hash == null) return null; if (cached_pixbuf.has_key(hash)) { return cached_pixbuf[hash]; } XmppStream? stream = stream_interactor.get_stream(account); if (stream == null || !stream.negotiation_complete) return null; if (pending_pixbuf.has_key(hash)) { pending_pixbuf[hash].add(new SourceFuncWrapper(get_avatar.callback)); yield; return cached_pixbuf[hash]; } pending_pixbuf[hash] = new ArrayList(); Pixbuf? image = yield get_image(hash); if (image != null) { cached_pixbuf[hash] = image; } else { Bytes? bytes = null; if (source == 1) { bytes = yield Xmpp.Xep.UserAvatars.fetch_image(stream, jid, hash); } else if (source == 2) { bytes = yield Xmpp.Xep.VCard.fetch_image(stream, jid, hash); if (bytes == null && jid.is_bare()) { db.avatar.delete().with(db.avatar.jid_id, "=", db.get_jid_id(jid)).perform(); } } if (bytes != null) { store_image(hash, bytes); image = yield get_image(hash); } cached_pixbuf[hash] = image; } foreach (SourceFuncWrapper sfw in pending_pixbuf[hash]) { sfw.sfun(); } return image; } public void publish(Account account, string file) { try { Pixbuf pixbuf = new Pixbuf.from_file(file); if (pixbuf.width >= pixbuf.height && pixbuf.width > MAX_PIXEL) { int dest_height = (int) ((float) MAX_PIXEL / pixbuf.width * pixbuf.height); pixbuf = pixbuf.scale_simple(MAX_PIXEL, dest_height, InterpType.BILINEAR); } else if (pixbuf.height > pixbuf.width && pixbuf.width > MAX_PIXEL) { int dest_width = (int) ((float) MAX_PIXEL / pixbuf.height * pixbuf.width); pixbuf = pixbuf.scale_simple(dest_width, MAX_PIXEL, InterpType.BILINEAR); } uint8[] buffer; pixbuf.save_to_buffer(out buffer, "png"); XmppStream stream = stream_interactor.get_stream(account); if (stream != null) { Xmpp.Xep.UserAvatars.publish_png(stream, buffer, pixbuf.width, pixbuf.height); } } catch (Error e) { warning(e.message); } } private void on_account_added(Account account) { stream_interactor.module_manager.get_module(account, Xep.UserAvatars.Module.IDENTITY).received_avatar_hash.connect((stream, jid, id) => on_user_avatar_received.begin(account, jid, id) ); stream_interactor.module_manager.get_module(account, Xep.VCard.Module.IDENTITY).received_avatar_hash.connect((stream, jid, id) => on_vcard_avatar_received.begin(account, jid, id) ); foreach (var entry in get_avatar_hashes(account, Source.USER_AVATARS).entries) { user_avatars[entry.key] = entry.value; } foreach (var entry in get_avatar_hashes(account, Source.VCARD).entries) { // FIXME: remove. temporary to remove falsely saved avatars. if (stream_interactor.get_module(MucManager.IDENTITY).is_groupchat(entry.key, account)) { db.avatar.delete().with(db.avatar.jid_id, "=", db.get_jid_id(entry.key)).perform(); continue; } vcard_avatars[entry.key] = entry.value; } } private async void on_user_avatar_received(Account account, Jid jid_, string id) { Jid jid = jid_.bare_jid; if (!user_avatars.has_key(jid) || user_avatars[jid] != id) { user_avatars[jid] = id; set_avatar_hash(account, jid, id, Source.USER_AVATARS); } received_avatar(jid, account); } private async void on_vcard_avatar_received(Account account, Jid jid_, string id) { bool is_gc = stream_interactor.get_module(MucManager.IDENTITY).might_be_groupchat(jid_.bare_jid, account); Jid jid = is_gc ? jid_ : jid_.bare_jid; if (!vcard_avatars.has_key(jid) || vcard_avatars[jid] != id) { vcard_avatars[jid] = id; if (jid.is_bare()) { // don't save MUC occupant avatars set_avatar_hash(account, jid, id, Source.VCARD); } } received_avatar(jid, account); } public void set_avatar_hash(Account account, Jid jid, string hash, int type) { db.avatar.insert() .value(db.avatar.jid_id, db.get_jid_id(jid)) .value(db.avatar.account_id, account.id) .value(db.avatar.hash, hash) .value(db.avatar.type_, type) .perform(); } public HashMap get_avatar_hashes(Account account, int type) { HashMap ret = new HashMap(Jid.hash_func, Jid.equals_func); foreach (Row row in db.avatar.select({db.avatar.jid_id, db.avatar.hash}) .with(db.avatar.type_, "=", type) .with(db.avatar.account_id, "=", account.id)) { ret[db.get_jid_by_id(row[db.avatar.jid_id])] = row[db.avatar.hash]; } return ret; } public void store_image(string id, Bytes data) { File file = File.new_for_path(Path.build_filename(folder, id)); try { if (file.query_exists()) file.delete(); //TODO y? DataOutputStream fos = new DataOutputStream(file.create(FileCreateFlags.REPLACE_DESTINATION)); fos.write_bytes_async.begin(data); } catch (Error e) { // Ignore: we failed in storing, so we refuse to display later... } } public bool has_image(string id) { File file = File.new_for_path(Path.build_filename(folder, id)); return file.query_exists(); } public async Pixbuf? get_image(string id) { try { File file = File.new_for_path(Path.build_filename(folder, id)); FileInputStream stream = yield file.read_async(Priority.LOW); uint8 fbuf[1024]; size_t size; Checksum checksum = new Checksum (ChecksumType.SHA1); while ((size = yield stream.read_async(fbuf, Priority.LOW)) > 0) { checksum.update(fbuf, size); } if (checksum.get_string() != id) { FileUtils.remove(file.get_path()); } stream.seek(0, SeekType.SET); return yield new Pixbuf.from_stream_async(stream, null); } catch (Error e) { return null; } } } } dino-0.3.0/libdino/src/service/blocking_manager.vala0000644000000000000000000000312214202022370021125 0ustar rootrootusing Gee; using Xmpp; using Dino.Entities; namespace Dino { public class BlockingManager : StreamInteractionModule, Object { public static ModuleIdentity IDENTITY = new ModuleIdentity("blocking_manager"); public string id { get { return IDENTITY.id; } } private StreamInteractor stream_interactor; public static void start(StreamInteractor stream_interactor) { BlockingManager m = new BlockingManager(stream_interactor); stream_interactor.add_module(m); } private BlockingManager(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; } public bool is_blocked(Account account, Jid jid) { XmppStream stream = stream_interactor.get_stream(account); return stream != null && stream.get_module(Xmpp.Xep.BlockingCommand.Module.IDENTITY).is_blocked(stream, jid.to_string()); } public void block(Account account, Jid jid) { XmppStream stream = stream_interactor.get_stream(account); stream.get_module(Xmpp.Xep.BlockingCommand.Module.IDENTITY).block(stream, { jid.to_string() }); } public void unblock(Account account, Jid jid) { XmppStream stream = stream_interactor.get_stream(account); stream.get_module(Xmpp.Xep.BlockingCommand.Module.IDENTITY).unblock(stream, { jid.to_string() }); } public bool is_supported(Account account) { XmppStream stream = stream_interactor.get_stream(account); return stream != null && stream.get_module(Xmpp.Xep.BlockingCommand.Module.IDENTITY).is_supported(stream); } } } dino-0.3.0/libdino/src/service/call_peer_state.vala0000644000000000000000000005134214202022370021000 0ustar rootrootusing Dino.Entities; using Gee; using Xmpp; public class Dino.PeerState : Object { public signal void stream_created(string media); public signal void counterpart_sends_video_updated(bool mute); public signal void info_received(Xep.JingleRtp.CallSessionInfo session_info); public signal void connection_ready(); public signal void session_terminated(bool we_terminated, string? reason_name, string? reason_text); public signal void encryption_updated(Xep.Jingle.ContentEncryption? audio_encryption, Xep.Jingle.ContentEncryption? video_encryption, bool same); public StreamInteractor stream_interactor; public CallState call_state; public Calls calls; public Call call; public Jid jid; public Xep.Jingle.Session session; public string sid; public string internal_id = Xmpp.random_uuid(); public Xep.JingleRtp.Parameters? audio_content_parameter = null; public Xep.JingleRtp.Parameters? video_content_parameter = null; public Xep.Jingle.Content? audio_content = null; public Xep.Jingle.Content? video_content = null; public Xep.Jingle.ContentEncryption? video_encryption = null; public Xep.Jingle.ContentEncryption? audio_encryption = null; public bool encryption_keys_same = false; public HashMap? video_encryptions = null; public HashMap? audio_encryptions = null; public bool first_peer = false; public bool waiting_for_inbound_muji_connection = false; public Xep.Muji.GroupCall? group_call { get; set; } public bool counterpart_sends_video = false; public bool we_should_send_audio { get; set; default=false; } public bool we_should_send_video { get; set; default=false; } public PeerState(Jid jid, Call call, CallState call_state, StreamInteractor stream_interactor) { this.jid = jid; this.call = call; this.call_state = call_state; this.stream_interactor = stream_interactor; this.calls = stream_interactor.get_module(Calls.IDENTITY); var session_info_type = stream_interactor.module_manager.get_module(call.account, Xep.JingleRtp.Module.IDENTITY).session_info_type; session_info_type.mute_update_received.connect((session,mute, name) => { if (this.sid != session.sid) return; foreach (Xep.Jingle.Content content in session.contents) { if (name == null || content.content_name == name) { Xep.JingleRtp.Parameters? rtp_content_parameter = content.content_params as Xep.JingleRtp.Parameters; if (rtp_content_parameter != null) { on_counterpart_mute_update(mute, rtp_content_parameter.media); } } } }); session_info_type.info_received.connect((session, session_info) => { if (this.sid != session.sid) return; info_received(session_info); }); } public async void initiate_call(Jid counterpart) { Gee.List call_resources = yield calls.get_call_resources(call.account, counterpart); bool do_jmi = false; Jid? jid_for_direct = null; if (yield calls.contains_jmi_resources(call.account, call_resources)) { do_jmi = true; } else if (!call_resources.is_empty) { jid_for_direct = call_resources[0]; } else if (calls.has_jmi_resources(jid)) { do_jmi = true; } sid = Xmpp.random_uuid(); if (do_jmi) { XmppStream? stream = stream_interactor.get_stream(call.account); var descriptions = new ArrayList(); descriptions.add(new StanzaNode.build("description", Xep.JingleRtp.NS_URI).add_self_xmlns().put_attribute("media", "audio")); if (we_should_send_video) { descriptions.add(new StanzaNode.build("description", Xep.JingleRtp.NS_URI).add_self_xmlns().put_attribute("media", "video")); } stream.get_module(Xmpp.Xep.JingleMessageInitiation.Module.IDENTITY).send_session_propose_to_peer(stream, jid, sid, descriptions); // Uncomment this use CIM instead of JMI // call_state.cim_call_id = sid; // stream.get_module(Xmpp.Xep.CallInvites.Module.IDENTITY).send_jingle_propose(stream, call_state.cim_call_id, jid, sid, we_should_send_video); } else if (jid_for_direct != null) { yield call_resource(jid_for_direct); } } public async void call_resource(Jid full_jid) { if (!call_state.accepted) { warning("Tried to call resource in an unaccepted call?!"); return; } XmppStream? stream = stream_interactor.get_stream(call.account); if (stream == null) return; if (sid == null) sid = Xmpp.random_uuid(); Xep.Jingle.Session session = yield stream.get_module(Xep.JingleRtp.Module.IDENTITY).start_call(stream, full_jid, we_should_send_video, sid, group_call != null ? group_call.muc_jid : null); set_session(session); } public void accept() { if (!call_state.accepted) { critical("Tried to accept peer in unaccepted call?! Something's fishy. Abort."); return; } if (session != null) { foreach (Xep.Jingle.Content content in session.contents) { Xep.JingleRtp.Parameters? rtp_content_parameter = content.content_params as Xep.JingleRtp.Parameters; if (rtp_content_parameter != null && rtp_content_parameter.media == "video") { // We didn't accept video but our peer wants to negotiate that content if (!we_should_send_video && session.senders_include_us(content.senders)) { if (session.senders_include_counterpart(content.senders)) { // If our peer wants to send, let them content.modify(session.we_initiated ? Xep.Jingle.Senders.RESPONDER : Xep.Jingle.Senders.INITIATOR); } else { // If only we're supposed to send, reject content.reject(); continue; } } } content.accept(); } } else { // Only a JMI so far XmppStream stream = stream_interactor.get_stream(call.account); if (stream == null) return; stream.get_module(Xep.JingleMessageInitiation.Module.IDENTITY).send_session_accept_to_self(stream, sid); stream.get_module(Xep.JingleMessageInitiation.Module.IDENTITY).send_session_proceed_to_peer(stream, jid, sid); } } public void reject() { if (session != null) { foreach (Xep.Jingle.Content content in session.contents) { content.reject(); } } else { // Only a JMI so far XmppStream stream = stream_interactor.get_stream(call.account); if (stream == null) return; stream.get_module(Xep.JingleMessageInitiation.Module.IDENTITY).send_session_reject_to_peer(stream, jid, sid); stream.get_module(Xep.JingleMessageInitiation.Module.IDENTITY).send_session_reject_to_self(stream, sid); } } public void end(string terminate_reason, string? reason_text = null) { switch (terminate_reason) { case Xep.Jingle.ReasonElement.SUCCESS: if (session != null) { session.terminate(terminate_reason, reason_text, "success"); } break; case Xep.Jingle.ReasonElement.CANCEL: if (session != null) { session.terminate(terminate_reason, reason_text, "cancel"); } else if (group_call != null) { // We don't have to do anything (?) } else { // Only a JMI so far XmppStream? stream = stream_interactor.get_stream(call.account); if (stream == null) return; stream.get_module(Xep.JingleMessageInitiation.Module.IDENTITY).send_session_retract_to_peer(stream, jid, sid); } break; } } internal void mute_own_audio(bool mute) { // Call isn't fully established yet. Audio will be muted once the stream is created. if (session == null || audio_content_parameter == null || audio_content_parameter.stream == null) return; Xep.JingleRtp.Stream stream = audio_content_parameter.stream; // Inform our counterpart that we (un)muted our audio stream_interactor.module_manager.get_module(call.account, Xep.JingleRtp.Module.IDENTITY).session_info_type.send_mute(session, mute, "audio"); // Start/Stop sending audio data Application.get_default().plugin_registry.video_call_plugin.set_pause(stream, mute); } internal void mute_own_video(bool mute) { if (session == null) { // Call hasn't been established yet return; } Xep.JingleRtp.Module rtp_module = stream_interactor.module_manager.get_module(call.account, Xep.JingleRtp.Module.IDENTITY); if (video_content_parameter != null && video_content_parameter.stream != null && session.senders_include_us(video_content.senders)) { // A video content already exists // Start/Stop sending video data Xep.JingleRtp.Stream stream = video_content_parameter.stream; if (stream != null) { Application.get_default().plugin_registry.video_call_plugin.set_pause(stream, mute); } // Inform our counterpart that we started/stopped our video rtp_module.session_info_type.send_mute(session, mute, "video"); } else if (!mute) { // Add a new video content XmppStream stream = stream_interactor.get_stream(call.account); rtp_module.add_outgoing_video_content.begin(stream, session, group_call != null ? group_call.muc_jid : null, (_, res) => { if (video_content_parameter == null) { Xep.Jingle.Content content = rtp_module.add_outgoing_video_content.end(res); Xep.JingleRtp.Parameters? rtp_content_parameter = content.content_params as Xep.JingleRtp.Parameters; if (rtp_content_parameter != null) { connect_content_signals(content, rtp_content_parameter); } } }); } // If video_content_parameter == null && !mute we're trying to mute a non-existant feed. It will be muted as soon as it is created. } public Xep.JingleRtp.Stream? get_video_stream() { if (video_content_parameter != null) { return video_content_parameter.stream; } return null; } public Xep.JingleRtp.Stream? get_audio_stream() { if (audio_content_parameter != null) { return audio_content_parameter.stream; } return null; } internal void set_session(Xep.Jingle.Session session) { this.session = session; this.sid = session.sid; session.terminated.connect((stream, we_terminated, reason_name, reason_text) => session_terminated(we_terminated, reason_name, reason_text) ); session.additional_content_add_incoming.connect((stream, content) => on_incoming_content_add(stream, content.session, content) ); foreach (Xep.Jingle.Content content in session.contents) { Xep.JingleRtp.Parameters? rtp_content_parameter = content.content_params as Xep.JingleRtp.Parameters; if (rtp_content_parameter == null) continue; connect_content_signals(content, rtp_content_parameter); } } public PeerInfo get_info() { var ret = new PeerInfo(); if (audio_content != null || audio_content_parameter != null) { ret.audio = get_content_info(audio_content, audio_content_parameter); } if (video_content != null || video_content_parameter != null) { ret.video = get_content_info(video_content, video_content_parameter); } return ret; } private PeerContentInfo get_content_info(Xep.Jingle.Content? content, Xep.JingleRtp.Parameters? parameter) { PeerContentInfo ret = new PeerContentInfo(); if (parameter != null) { ret.rtcp_ready = parameter.rtcp_ready; ret.rtp_ready = parameter.rtp_ready; if (parameter.agreed_payload_type != null) { ret.codec = parameter.agreed_payload_type.name; ret.clockrate = parameter.agreed_payload_type.clockrate; } if (parameter.stream != null && parameter.stream.remb_enabled) { ret.target_receive_bytes = parameter.stream.target_receive_bitrate; ret.target_send_bytes = parameter.stream.target_send_bitrate; } } if (content != null) { Xmpp.Xep.Jingle.ComponentConnection? component0 = content.get_transport_connection(1); if (component0 != null) { ret.bytes_received = component0.bytes_received; ret.bytes_sent = component0.bytes_sent; } } return ret; } private void connect_content_signals(Xep.Jingle.Content content, Xep.JingleRtp.Parameters rtp_content_parameter) { if (rtp_content_parameter.media == "audio") { audio_content = content; audio_content_parameter = rtp_content_parameter; } else if (rtp_content_parameter.media == "video") { video_content = content; video_content_parameter = rtp_content_parameter; } debug(@"[%s] %s connecting content signals %s", call.account.bare_jid.to_string(), jid.to_string(), rtp_content_parameter.media); rtp_content_parameter.stream_created.connect((stream) => on_stream_created(rtp_content_parameter.media, stream)); rtp_content_parameter.connection_ready.connect((status) => { Idle.add(() => { on_connection_ready(content, rtp_content_parameter.media); return false; }); }); content.senders_modify_incoming.connect((content, proposed_senders) => { if (content.session.senders_include_us(content.senders) != content.session.senders_include_us(proposed_senders)) { warning("counterpart set us to (not)sending %s. ignoring", content.content_name); return; } if (!content.session.senders_include_counterpart(content.senders) && content.session.senders_include_counterpart(proposed_senders)) { // Counterpart wants to start sending. Ok. content.accept_content_modify(proposed_senders); on_counterpart_mute_update(false, "video"); } }); } private void on_incoming_content_add(XmppStream stream, Xep.Jingle.Session session, Xep.Jingle.Content content) { Xep.JingleRtp.Parameters? rtp_content_parameter = content.content_params as Xep.JingleRtp.Parameters; if (rtp_content_parameter == null) { content.reject(); return; } // Our peer shouldn't tell us to start sending, that's for us to initiate if (session.senders_include_us(content.senders)) { if (session.senders_include_counterpart(content.senders)) { // If our peer wants to send, let them content.modify(session.we_initiated ? Xep.Jingle.Senders.RESPONDER : Xep.Jingle.Senders.INITIATOR); } else { // If only we're supposed to send, reject content.reject(); } } connect_content_signals(content, rtp_content_parameter); content.accept(); } private void on_stream_created(string media, Xep.JingleRtp.Stream stream) { if (media == "video" && stream.receiving) { counterpart_sends_video = true; video_content_parameter.connection_ready.connect((status) => { Idle.add(() => { counterpart_sends_video_updated(false); return false; }); }); } // Outgoing audio/video might have been muted in the meanwhile. if (media == "video" && !we_should_send_video) { mute_own_video(true); } else if (media == "audio" && !we_should_send_audio) { mute_own_audio(true); } stream_created(media); } private void on_counterpart_mute_update(bool mute, string? media) { if (!call.equals(call)) return; if (media == "video") { counterpart_sends_video = !mute; debug(@"[%s] %s video muted %s", call.account.bare_jid.to_string(), jid.to_string(), mute.to_string()); counterpart_sends_video_updated(mute); } } private void on_connection_ready(Xep.Jingle.Content content, string media) { debug("[%s] %s on_connection_ready", call.account.bare_jid.to_string(), jid.to_string()); connection_ready(); if (call.state == Call.State.RINGING || call.state == Call.State.ESTABLISHING) { call.state = Call.State.IN_PROGRESS; } if (media == "audio") { audio_encryptions = content.encryptions; } else if (media == "video") { video_encryptions = content.encryptions; } if ((audio_encryptions != null && audio_encryptions.is_empty) || (video_encryptions != null && video_encryptions.is_empty)) { call.encryption = Encryption.NONE; encryption_updated(null, null, true); return; } HashMap encryptions = audio_encryptions ?? video_encryptions; Xep.Jingle.ContentEncryption? omemo_encryption = null, dtls_encryption = null, srtp_encryption = null; foreach (string encr_name in encryptions.keys) { if (video_encryptions != null && !video_encryptions.has_key(encr_name)) continue; var encryption = encryptions[encr_name]; if (encryption.encryption_ns == "http://gultsch.de/xmpp/drafts/omemo/dlts-srtp-verification") { omemo_encryption = encryption; } else if (encryption.encryption_ns == Xep.JingleIceUdp.DTLS_NS_URI) { dtls_encryption = encryption; } else if (encryption.encryption_name == "SRTP") { srtp_encryption = encryption; } } if (omemo_encryption != null && dtls_encryption != null) { call.encryption = Encryption.OMEMO; omemo_encryption.peer_key = dtls_encryption.peer_key; omemo_encryption.our_key = dtls_encryption.our_key; audio_encryption = omemo_encryption; encryption_keys_same = true; video_encryption = video_encryptions != null ? video_encryptions["http://gultsch.de/xmpp/drafts/omemo/dlts-srtp-verification"] : null; } else if (dtls_encryption != null) { call.encryption = Encryption.DTLS_SRTP; audio_encryption = dtls_encryption; video_encryption = video_encryptions != null ? video_encryptions[Xep.JingleIceUdp.DTLS_NS_URI] : null; encryption_keys_same = true; if (video_encryption != null && dtls_encryption.peer_key.length == video_encryption.peer_key.length) { for (int i = 0; i < dtls_encryption.peer_key.length; i++) { if (dtls_encryption.peer_key[i] != video_encryption.peer_key[i]) { encryption_keys_same = false; break; } } } } else if (srtp_encryption != null) { call.encryption = Encryption.SRTP; audio_encryption = srtp_encryption; video_encryption = video_encryptions != null ? video_encryptions["SRTP"] : null; encryption_keys_same = false; } else { call.encryption = Encryption.NONE; encryption_keys_same = true; } encryption_updated(audio_encryption, video_encryption, encryption_keys_same); } } public class Dino.PeerContentInfo { public bool rtp_ready { get; set; } public bool rtcp_ready { get; set; } public ulong? bytes_sent { get; set; default=0; } public ulong? bytes_received { get; set; default=0; } public string? codec { get; set; } public uint32 clockrate { get; set; } public uint target_receive_bytes { get; set; default=-1; } public uint target_send_bytes { get; set; default=-1; } } public class Dino.PeerInfo { public PeerContentInfo? audio = null; public PeerContentInfo? video = null; }dino-0.3.0/libdino/src/service/call_state.vala0000644000000000000000000005012114202022370017757 0ustar rootrootusing Dino.Entities; using Gee; using Xmpp; public class Dino.CallState : Object { public signal void terminated(Jid who_terminated, string? reason_name, string? reason_text); public signal void peer_joined(Jid jid, PeerState peer_state); public signal void peer_left(Jid jid, PeerState peer_state, string? reason_name, string? reason_text); public StreamInteractor stream_interactor; public Plugins.VideoCallPlugin call_plugin = Dino.Application.get_default().plugin_registry.video_call_plugin; public Call call; public Jid? parent_muc { get; set; } public Jid? invited_to_group_call = null; public bool accepted { get; private set; default=false; } public bool use_cim = false; public string? cim_call_id = null; public Jid? cim_counterpart = null; public string cim_message_type { get; set; default=Xmpp.MessageStanza.TYPE_CHAT; } public Xep.Muji.GroupCall? group_call { get; set; } public bool we_should_send_audio { get; set; default=false; } public bool we_should_send_video { get; set; default=false; } public HashMap peers = new HashMap(Jid.hash_func, Jid.equals_func); private Plugins.MediaDevice selected_microphone_device; private Plugins.MediaDevice selected_speaker_device; private Plugins.MediaDevice selected_video_device; public CallState(Call call, StreamInteractor stream_interactor) { this.call = call; this.stream_interactor = stream_interactor; if (call.direction == Call.DIRECTION_OUTGOING && call.state != Call.State.OTHER_DEVICE) { accepted = true; Timeout.add_seconds(30, () => { if (this == null) return false; // TODO enough? if (call.state == Call.State.ESTABLISHING) { call.state = Call.State.MISSED; terminated(call.account.bare_jid, null, null); } return false; }); } } internal async void initiate_groupchat_call(Jid muc) { parent_muc = muc; cim_message_type = MessageStanza.TYPE_GROUPCHAT; if (this.group_call == null) yield convert_into_group_call(); if (this.group_call == null) return; // The user might have retracted the call in the meanwhile if (this.call.state != Call.State.RINGING) return; XmppStream stream = stream_interactor.get_stream(call.account); if (stream == null) return; Gee.List occupants = stream_interactor.get_module(MucManager.IDENTITY).get_other_occupants(muc, call.account); foreach (Jid occupant in occupants) { Jid? real_jid = stream_interactor.get_module(MucManager.IDENTITY).get_real_jid(occupant, call.account); if (real_jid == null) continue; debug(@"Adding MUC member as MUJI MUC owner %s", real_jid.bare_jid.to_string()); yield stream.get_module(Xep.Muc.Module.IDENTITY).change_affiliation(stream, group_call.muc_jid, real_jid.bare_jid, null, "owner"); } stream.get_module(Xep.CallInvites.Module.IDENTITY).send_muji_propose(stream, cim_call_id, muc, group_call.muc_jid, we_should_send_video, cim_message_type); } internal PeerState set_first_peer(Jid peer) { var peer_state = new PeerState(peer, call, this, stream_interactor); peer_state.first_peer = true; add_peer(peer_state); return peer_state; } internal void add_peer(PeerState peer) { call.add_peer(peer.jid.bare_jid); connect_peer_signals(peer); peer_joined(peer.jid, peer); } internal void on_peer_stream_created(PeerState peer, string media) { if (media == "audio") { call_plugin.set_device(peer.get_audio_stream(), get_microphone_device()); call_plugin.set_device(peer.get_audio_stream(), get_speaker_device()); } else if (media == "video") { call_plugin.set_device(peer.get_video_stream(), get_video_device()); } } public void accept() { accepted = true; call.state = Call.State.ESTABLISHING; if (use_cim) { XmppStream stream = stream_interactor.get_stream(call.account); if (stream == null) return; StanzaNode? inner_node = null; if (group_call != null) { inner_node = new StanzaNode.build("muji", Xep.Muji.NS_URI).add_self_xmlns() .put_attribute("room", group_call.muc_jid.to_string()); } else if (peers.size == 1) { foreach (PeerState peer in peers.values) { inner_node = new StanzaNode.build("jingle", Xep.CallInvites.NS_URI) .put_attribute("sid", peer.sid); } } stream.get_module(Xep.CallInvites.Module.IDENTITY).send_accept(stream, cim_counterpart, cim_call_id, inner_node, cim_message_type); } else { foreach (PeerState peer in peers.values) { peer.accept(); } } if (invited_to_group_call != null) { join_group_call.begin(invited_to_group_call); } } public void reject() { call.state = Call.State.DECLINED; if (use_cim) { XmppStream stream = stream_interactor.get_stream(call.account); if (stream == null) return; stream.get_module(Xep.CallInvites.Module.IDENTITY).send_reject(stream, cim_counterpart, cim_call_id, cim_message_type); } var peers_cpy = new ArrayList(); peers_cpy.add_all(peers.values); foreach (PeerState peer in peers_cpy) { peer.reject(); } terminated(call.account.bare_jid, null, null); } public void end(string? reason_text = null) { var peers_cpy = new ArrayList(); peers_cpy.add_all(peers.values); if (group_call != null) { XmppStream stream = stream_interactor.get_stream(call.account); if (stream != null) { stream.get_module(Xep.Muc.Module.IDENTITY).exit(stream, group_call.muc_jid); } } if (call.state == Call.State.IN_PROGRESS || call.state == Call.State.ESTABLISHING) { foreach (PeerState peer in peers_cpy) { peer.end(Xep.Jingle.ReasonElement.SUCCESS, reason_text); } if (use_cim) { XmppStream stream = stream_interactor.get_stream(call.account); if (stream == null) return; stream.get_module(Xep.CallInvites.Module.IDENTITY).send_finish(stream, cim_counterpart, cim_call_id, cim_message_type); } call.state = Call.State.ENDED; } else if (call.state == Call.State.RINGING) { foreach (PeerState peer in peers_cpy) { peer.end(Xep.Jingle.ReasonElement.CANCEL, reason_text); } if (call.direction == Call.DIRECTION_OUTGOING && use_cim) { XmppStream stream = stream_interactor.get_stream(call.account); if (stream == null) return; stream.get_module(Xep.CallInvites.Module.IDENTITY).send_retract(stream, cim_counterpart, cim_call_id, cim_message_type); } call.state = Call.State.MISSED; } else { return; } call.end_time = new DateTime.now_utc(); terminated(call.account.bare_jid, null, reason_text); } public void mute_own_audio(bool mute) { we_should_send_audio = !mute; foreach (PeerState peer in peers.values) { peer.mute_own_audio(mute); } } public void mute_own_video(bool mute) { we_should_send_video = !mute; foreach (PeerState peer in peers.values) { peer.mute_own_video(mute); } } public bool should_we_send_video() { return we_should_send_video; } public async void invite_to_call(Jid invitee) { if (this.group_call == null) yield convert_into_group_call(); if (this.group_call == null) return; XmppStream stream = stream_interactor.get_stream(call.account); if (stream == null) return; debug("[%s] Inviting to muji call %s", call.account.bare_jid.to_string(), invitee.to_string()); yield stream.get_module(Xep.Muc.Module.IDENTITY).change_affiliation(stream, group_call.muc_jid, invitee, null, "owner"); stream.get_module(Xep.CallInvites.Module.IDENTITY).send_muji_propose(stream, cim_call_id, invitee, group_call.muc_jid, we_should_send_video, "chat"); // If the peer hasn't accepted within a minute, retract the invite // TODO this should be unset when we retract the invite. otherwise a second invite attempt might break due to this Timeout.add_seconds(60, () => { if (this == null) return false; bool contains_peer = false; foreach (Jid peer in peers.keys) { if (peer.equals_bare(invitee)) { contains_peer = true; } } if (!contains_peer) { debug("[%s] Retracting invite to %s from %s", call.account.bare_jid.to_string(), group_call.muc_jid.to_string(), invitee.to_string()); // stream.get_module(Xep.CallInvites.Module.IDENTITY).send_retract(stream, invitee, invite_id); // stream.get_module(Xep.Muc.Module.IDENTITY).change_affiliation.begin(stream, group_call.muc_jid, invitee, null, "none"); } return false; }); } public Plugins.MediaDevice? get_microphone_device() { if (selected_microphone_device == null) { if (!peers.is_empty) { var audio_stream = peers.values.to_array()[0].get_audio_stream(); selected_microphone_device = call_plugin.get_device(audio_stream, false); } if (selected_microphone_device == null) { selected_microphone_device = call_plugin.get_preferred_device("audio", false); } } return selected_microphone_device; } public Plugins.MediaDevice? get_speaker_device() { if (selected_speaker_device == null) { if (!peers.is_empty) { var audio_stream = peers.values.to_array()[0].get_audio_stream(); selected_speaker_device = call_plugin.get_device(audio_stream, true); } if (selected_speaker_device == null) { selected_speaker_device = call_plugin.get_preferred_device("audio", true); } } return selected_speaker_device; } public Plugins.MediaDevice? get_video_device() { if (selected_video_device == null) { if (!peers.is_empty) { var video_stream = peers.values.to_array()[0].get_video_stream(); selected_video_device = call_plugin.get_device(video_stream, false); } if (selected_video_device == null) { selected_video_device = call_plugin.get_preferred_device("video", false); } } return selected_video_device; } public void set_audio_device(Plugins.MediaDevice? device) { if (device.incoming) { selected_speaker_device = device; } else { selected_microphone_device = device; } foreach (PeerState peer_state in peers.values) { call_plugin.set_device(peer_state.get_audio_stream(), device); } } public void set_video_device(Plugins.MediaDevice? device) { selected_video_device = device; foreach (PeerState peer_state in peers.values) { call_plugin.set_device(peer_state.get_video_stream(), device); } } internal void rename_peer(Jid from_jid, Jid to_jid) { debug("[%s] Renaming %s to %s exists %s", call.account.bare_jid.to_string(), from_jid.to_string(), to_jid.to_string(), peers.has_key(from_jid).to_string()); PeerState? peer_state = peers[from_jid]; if (peer_state == null) return; // Adjust the internal mapping of this `PeerState` object peers.unset(from_jid); peers[to_jid] = peer_state; peer_state.jid = to_jid; } private void on_call_terminated(Jid who_terminated, bool we_terminated, string? reason_name, string? reason_text) { if (call.state == Call.State.RINGING || call.state == Call.State.IN_PROGRESS || call.state == Call.State.ESTABLISHING) { call.end_time = new DateTime.now_utc(); } if (call.state == Call.State.IN_PROGRESS) { call.state = Call.State.ENDED; } else if (call.state == Call.State.RINGING || call.state == Call.State.ESTABLISHING) { if (reason_name == Xep.Jingle.ReasonElement.DECLINE) { call.state = Call.State.DECLINED; } else { call.state = Call.State.FAILED; } } terminated(who_terminated, reason_name, reason_text); } private void connect_peer_signals(PeerState peer_state) { peers[peer_state.jid] = peer_state; this.bind_property("we-should-send-audio", peer_state, "we-should-send-audio", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); this.bind_property("we-should-send-video", peer_state, "we-should-send-video", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); this.bind_property("group-call", peer_state, "group-call", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); peer_state.stream_created.connect((peer, media) => { on_peer_stream_created(peer, media); }); peer_state.session_terminated.connect((we_terminated, reason_name, reason_text) => { debug("[%s] Peer left %s: %s %s (%i peers remaining)", call.account.bare_jid.to_string(), reason_text ?? "", reason_name ?? "", peer_state.jid.to_string(), peers.size); handle_peer_left(peer_state, we_terminated, reason_name, reason_text); }); } public async bool can_convert_into_groupcall() { if (peers.size == 0) return false; Jid peer = peers.keys.to_array()[0]; bool peer_has_feature = yield stream_interactor.get_module(EntityInfo.IDENTITY).has_feature(call.account, peer, Xep.Muji.NS_URI); bool can_initiate = stream_interactor.get_module(Calls.IDENTITY).can_initiate_groupcall(call.account); return peer_has_feature && can_initiate; } public async void convert_into_group_call() { XmppStream stream = stream_interactor.get_stream(call.account); if (stream == null) return; Jid? muc_jid = stream_interactor.get_module(MucManager.IDENTITY).default_muc_server[call.account]; if (muc_jid == null) { warning("Failed to initiate group call: MUC server not known."); return; } if (cim_call_id == null) cim_call_id = Xmpp.random_uuid(); muc_jid = new Jid("%08x@".printf(Random.next_int()) + muc_jid.to_string()); // TODO longer? debug("[%s] Converting call to groupcall %s", call.account.bare_jid.to_string(), muc_jid.to_string()); yield join_group_call(muc_jid); Xep.DataForms.DataForm? data_form = yield stream_interactor.get_module(MucManager.IDENTITY).get_config_form(call.account, muc_jid); if (data_form == null) return; foreach (Xep.DataForms.DataForm.Field field in data_form.fields) { switch (field.var) { case "muc#roomconfig_allowinvites": if (field.type_ == Xep.DataForms.DataForm.Type.BOOLEAN) { ((Xep.DataForms.DataForm.BooleanField) field).value = true; } break; case "muc#roomconfig_persistentroom": if (field.type_ == Xep.DataForms.DataForm.Type.BOOLEAN) { ((Xep.DataForms.DataForm.BooleanField) field).value = false; } break; case "muc#roomconfig_membersonly": if (field.type_ == Xep.DataForms.DataForm.Type.BOOLEAN) { ((Xep.DataForms.DataForm.BooleanField) field).value = true; } break; case "muc#roomconfig_whois": if (field.type_ == Xep.DataForms.DataForm.Type.LIST_SINGLE) { ((Xep.DataForms.DataForm.ListSingleField) field).value = "anyone"; } break; } } yield stream_interactor.get_module(MucManager.IDENTITY).set_config_form(call.account, muc_jid, data_form); foreach (Jid peer_jid in peers.keys) { debug("[%s] Group call inviting %s", call.account.bare_jid.to_string(), peer_jid.to_string()); yield invite_to_call(peer_jid); } } public async void join_group_call(Jid muc_jid) { debug("[%s] Joining group call %s", call.account.bare_jid.to_string(), muc_jid.to_string()); XmppStream stream = stream_interactor.get_stream(call.account); if (stream == null) return; this.group_call = yield stream.get_module(Xep.Muji.Module.IDENTITY).join_call(stream, muc_jid, we_should_send_video); if (this.group_call == null) { warning("[%s] Couldn't join MUJI MUC", call.account.bare_jid.to_string()); return; } this.group_call.peer_joined.connect((jid) => { debug("[%s] Group call peer joined: %s", call.account.bare_jid.to_string(), jid.to_string()); // Newly joined peers have to call us, not the other way round // Maybe they called us already. Accept the call. // (Except for the first peer, we already have a connection to that one.) if (peers.has_key(jid)) { if (!peers[jid].first_peer) { peers[jid].accept(); } // else: Connection to first peer already active } else { var peer_state = new PeerState(jid, call, this, stream_interactor); peer_state.waiting_for_inbound_muji_connection = true; debug("[%s] Waiting for call from %s", call.account.bare_jid.to_string(), jid.to_string()); add_peer(peer_state); } }); this.group_call.peer_left.connect((jid) => { debug("[%s] Group call peer left: %s", call.account.bare_jid.to_string(), jid.to_string()); PeerState? peer_state = peers[jid]; if (peer_state == null) return; peer_state.end(Xep.Jingle.ReasonElement.CANCEL, "Peer left the MUJI MUC"); handle_peer_left(peer_state, false, Xep.Jingle.ReasonElement.CANCEL, "Peer left the MUJI MUC"); }); if (group_call.peers_to_connect_to.size > 4) { end("Call too full - P2p calls don't work well with many participants"); return; } // Call all peers that are in the room already foreach (Jid peer_jid in group_call.peers_to_connect_to) { // Don't establish connection if we have one already (the person that invited us to the call) if (peers.has_key(peer_jid)) continue; debug("[%s] Calling %s because they were in the MUC already", call.account.bare_jid.to_string(), peer_jid.to_string()); PeerState peer_state = new PeerState(peer_jid, call, this, stream_interactor); add_peer(peer_state); peer_state.call_resource.begin(peer_jid); } debug("[%s] Finished joining MUJI muc %s", call.account.bare_jid.to_string(), muc_jid.to_string()); } private void handle_peer_left(PeerState peer_state, bool we_terminated, string? reason_name, string? reason_text) { if (!peers.has_key(peer_state.jid)) return; peers.unset(peer_state.jid); if (peers.is_empty) { if (group_call != null) { group_call.leave(stream_interactor.get_stream(call.account)); on_call_terminated(peer_state.jid, we_terminated, null, "All participants have left the call"); } else { on_call_terminated(peer_state.jid, we_terminated, reason_name, reason_text); } } else { peer_left(peer_state.jid, peer_state, reason_name, reason_text); } } }dino-0.3.0/libdino/src/service/call_store.vala0000644000000000000000000000404414202022370017776 0ustar rootrootusing Xmpp; using Gee; using Qlite; using Dino.Entities; namespace Dino { public class CallStore : StreamInteractionModule, Object { public static ModuleIdentity IDENTITY = new ModuleIdentity("call_store"); public string id { get { return IDENTITY.id; } } private StreamInteractor stream_interactor; private Database db; private WeakMap calls_by_db_id = new WeakMap(); public static void start(StreamInteractor stream_interactor, Database db) { CallStore m = new CallStore(stream_interactor, db); stream_interactor.add_module(m); } private CallStore(StreamInteractor stream_interactor, Database db) { this.stream_interactor = stream_interactor; this.db = db; } public void add_call(Call call, Conversation conversation) { call.persist(db); cache_call(call); } public Call? get_call_by_id(int id, Conversation conversation) { Call? call = calls_by_db_id[id]; if (call != null) { return call; } RowOption row_option = db.call.select().with(db.call.id, "=", id).row(); return create_call_from_row_opt(row_option, conversation); } private Call? create_call_from_row_opt(RowOption row_opt, Conversation conversation) { if (!row_opt.is_present()) return null; try { Call call = new Call.from_row(db, row_opt.inner); if (conversation.type_.is_muc_semantic()) { call.ourpart = conversation.counterpart.with_resource(call.ourpart.resourcepart); } cache_call(call); return call; } catch (InvalidJidError e) { warning("Got message with invalid Jid: %s", e.message); } return null; } private void cache_call(Call call) { calls_by_db_id[call.id] = call; } } }dino-0.3.0/libdino/src/service/calls.vala0000644000000000000000000006626214202022370016757 0ustar rootrootusing Gee; using Xmpp; using Dino.Entities; namespace Dino { public class Calls : StreamInteractionModule, Object { public signal void call_incoming(Call call, CallState state, Conversation conversation, bool video, bool multiparty); public signal void call_outgoing(Call call, CallState state, Conversation conversation); public signal void call_terminated(Call call, string? reason_name, string? reason_text); public signal void conference_info_received(Call call, Xep.Coin.ConferenceInfo conference_info); public static ModuleIdentity IDENTITY = new ModuleIdentity("calls"); public string id { get { return IDENTITY.id; } } private StreamInteractor stream_interactor; private Database db; // public HashMap current_jmi_request_call = new HashMap(Account.hash_func, Account.equals_func); public HashMap jmi_request_peer = new HashMap(Call.hash_func, Call.equals_func); public HashMap call_states = new HashMap(Call.hash_func, Call.equals_func); public static void start(StreamInteractor stream_interactor, Database db) { Calls m = new Calls(stream_interactor, db); stream_interactor.add_module(m); } private Calls(StreamInteractor stream_interactor, Database db) { this.stream_interactor = stream_interactor; this.db = db; stream_interactor.account_added.connect(on_account_added); } public async CallState? initiate_call(Conversation conversation, bool video) { Call call = new Call(); call.direction = Call.DIRECTION_OUTGOING; call.account = conversation.account; call.counterpart = conversation.counterpart; call.ourpart = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account) ?? conversation.account.full_jid; call.time = call.local_time = call.end_time = new DateTime.now_utc(); call.encryption = Encryption.UNKNOWN; call.state = Call.State.RINGING; stream_interactor.get_module(CallStore.IDENTITY).add_call(call, conversation); var call_state = new CallState(call, stream_interactor); connect_call_state_signals(call_state); call_state.we_should_send_video = video; call_state.we_should_send_audio = true; if (conversation.type_ == Conversation.Type.CHAT) { call.add_peer(conversation.counterpart); PeerState peer_state = call_state.set_first_peer(conversation.counterpart); jmi_request_peer[call] = peer_state; yield peer_state.initiate_call(conversation.counterpart); } else { call_state.initiate_groupchat_call.begin(conversation.counterpart); } conversation.last_active = call.time; call_outgoing(call, call_state, conversation); return call_state; } public bool can_we_do_calls(Account account) { Plugins.VideoCallPlugin? plugin = Application.get_default().plugin_registry.video_call_plugin; if (plugin == null) return false; return plugin.supports(null); } public async bool can_conversation_do_calls(Conversation conversation) { if (!can_we_do_calls(conversation.account)) return false; if (conversation.type_ == Conversation.Type.CHAT) { return (yield get_call_resources(conversation.account, conversation.counterpart)).size > 0 || has_jmi_resources(conversation.counterpart); } else { bool is_private = stream_interactor.get_module(MucManager.IDENTITY).is_private_room(conversation.account, conversation.counterpart); EntityInfo entity_info = stream_interactor.get_module(EntityInfo.IDENTITY); return is_private && can_initiate_groupcall(conversation.account); } } public bool can_initiate_groupcall(Account account) { return stream_interactor.get_module(MucManager.IDENTITY).default_muc_server[account] != null; } public async Gee.List get_call_resources(Account account, Jid counterpart) { ArrayList ret = new ArrayList(); XmppStream? stream = stream_interactor.get_stream(account); if (stream == null) return ret; Presence.Flag? presence_flag = stream.get_flag(Presence.Flag.IDENTITY); if (presence_flag == null) return ret; Gee.List? full_jids = presence_flag.get_resources(counterpart); if (full_jids == null) return ret; foreach (Jid full_jid in full_jids) { var module = stream.get_module(Xep.JingleRtp.Module.IDENTITY); if (module == null) return ret; bool supports_rtc = yield module.is_available(stream, full_jid); if (!supports_rtc) continue; ret.add(full_jid); } return ret; } public async bool contains_jmi_resources(Account account, Gee.List full_jids) { XmppStream? stream = stream_interactor.get_stream(account); if (stream == null) return false; foreach (Jid full_jid in full_jids) { bool does_jmi = yield stream_interactor.get_module(EntityInfo.IDENTITY).has_feature(account, full_jid, Xep.JingleMessageInitiation.NS_URI); if (does_jmi) return true; } return false; } public bool has_jmi_resources(Jid counterpart) { int64 jmi_resources = db.entity.select() .with(db.entity.jid_id, "=", db.get_jid_id(counterpart)) .join_with(db.entity_feature, db.entity.caps_hash, db.entity_feature.entity) .with(db.entity_feature.feature, "=", Xep.JingleMessageInitiation.NS_URI) .count(); return jmi_resources > 0; } public bool is_call_in_progress() { foreach (Call call in call_states.keys) { if (call.state == Call.State.IN_PROGRESS || call.state == Call.State.RINGING || call.state == Call.State.ESTABLISHING) { return true; } } return false; } private void on_incoming_call(Account account, Xep.Jingle.Session session) { Jid? muji_room = session.muji_room; bool counterpart_wants_video = false; foreach (Xep.Jingle.Content content in session.contents) { Xep.JingleRtp.Parameters? rtp_content_parameter = content.content_params as Xep.JingleRtp.Parameters; if (rtp_content_parameter == null) continue; if (rtp_content_parameter.media == "video" && session.senders_include_us(content.senders)) { counterpart_wants_video = true; } } // Check if this comes from a MUJI MUC => accept if (muji_room != null) { debug("[%s] Incoming call from %s from MUJI muc %s", account.bare_jid.to_string(), session.peer_full_jid.to_string(), muji_room.to_string()); foreach (CallState call_state in call_states.values) { if (call_state.call.account.equals(account) && call_state.group_call != null && call_state.group_call.muc_jid.equals(muji_room)) { if (call_state.peers.keys.contains(session.peer_full_jid)) { PeerState peer_state = call_state.peers[session.peer_full_jid]; debug("[%s] Incoming call, we know the peer. Expected %s", account.bare_jid.to_string(), peer_state.waiting_for_inbound_muji_connection.to_string()); if (!peer_state.waiting_for_inbound_muji_connection) return; peer_state.set_session(session); debug(@"[%s] Accepting incoming MUJI call from %s", account.bare_jid.to_string(), session.peer_full_jid.to_string()); peer_state.accept(); } else { debug(@"[%s] Incoming call, but didn't see peer in MUC yet", account.bare_jid.to_string()); PeerState peer_state = new PeerState(session.peer_full_jid, call_state.call, call_state, stream_interactor); peer_state.set_session(session); call_state.add_peer(peer_state); } return; } } return; } debug(@"[%s] Incoming call from %s", account.bare_jid.to_string(), session.peer_full_jid.to_string()); // Check if we already got this call via Jingle Message Initiation => accept // PeerState.accept() checks if the call was accepted and ensures that we don't accidentally send video PeerState? peer_state = get_peer_by_sid(account, session.sid, session.peer_full_jid); if (peer_state != null) { jmi_request_peer[peer_state.call].set_session(session); jmi_request_peer[peer_state.call].accept(); jmi_request_peer.unset(peer_state.call); return; } // This is a direct call without prior JMI. Ask user. if (stream_interactor.get_module(MucManager.IDENTITY).might_be_groupchat(session.peer_full_jid.bare_jid, account)) return; peer_state = create_received_call(account, session.peer_full_jid, account.full_jid, counterpart_wants_video); peer_state.set_session(session); Conversation conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(peer_state.call.counterpart.bare_jid, account, Conversation.Type.CHAT); call_incoming(peer_state.call, peer_state.call_state, conversation, counterpart_wants_video, false); stream_interactor.module_manager.get_module(account, Xep.JingleRtp.Module.IDENTITY).session_info_type.send_ringing(session); } private PeerState create_received_call(Account account, Jid from, Jid to, bool video_requested) { Call call = new Call(); if (from.equals_bare(account.bare_jid)) { // Call requested by another of our devices call.direction = Call.DIRECTION_OUTGOING; call.ourpart = from; call.state = Call.State.OTHER_DEVICE; call.counterpart = to; } else { call.direction = Call.DIRECTION_INCOMING; call.ourpart = account.full_jid; call.state = Call.State.RINGING; call.counterpart = from; } call.add_peer(call.counterpart); call.account = account; call.time = call.local_time = call.end_time = new DateTime.now_utc(); call.encryption = Encryption.UNKNOWN; Conversation conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(call.counterpart.bare_jid, account, Conversation.Type.CHAT); stream_interactor.get_module(CallStore.IDENTITY).add_call(call, conversation); conversation.last_active = call.time; var call_state = new CallState(call, stream_interactor); connect_call_state_signals(call_state); PeerState peer_state = call_state.set_first_peer(call.counterpart); call_state.we_should_send_video = video_requested; call_state.we_should_send_audio = true; return peer_state; } private CallState? get_call_state_by_call_id(Account account, string call_id, Jid? counterpart_jid = null) { foreach (CallState call_state in call_states.values) { if (!call_state.call.account.equals(account)) continue; if (call_state.cim_call_id == call_id) { if (counterpart_jid == null) return call_state; foreach (Jid jid in call_state.peers.keys) { if (jid.equals_bare(counterpart_jid)) { return call_state; } } } } return null; } private PeerState? get_peer_by_sid(Account account, string sid, Jid jid1, Jid? jid2 = null) { Jid relevant_jid = jid1.equals_bare(account.bare_jid) && jid2 != null ? jid2 : jid1; foreach (CallState call_state in call_states.values) { if (!call_state.call.account.equals(account)) continue; foreach (PeerState peer_state in call_state.peers.values) { if (peer_state.sid != sid) continue; if (peer_state.jid.equals_bare(relevant_jid)) { return peer_state; } } } return null; } private CallState? create_recv_muji_call(Account account, string call_id, Jid inviter_jid, Jid muc_jid, string message_type) { debug("[%s] Muji call received from %s for MUC %s, type %s", account.bare_jid.to_string(), inviter_jid.to_string(), muc_jid.to_string(), message_type); foreach (Call call in call_states.keys) { if (!call.account.equals(account)) continue; CallState call_state = call_states[call]; if (call.counterparts.size == 1 && call.counterparts.contains(inviter_jid) && call_state.accepted) { // A call is converted into a group call. call_state.cim_call_id = call_id; call_state.join_group_call.begin(muc_jid); return null; } } Call call = new Call(); call.direction = Call.DIRECTION_INCOMING; call.ourpart = account.full_jid; call.counterpart = inviter_jid; call.account = account; call.time = call.local_time = call.end_time = new DateTime.now_utc(); call.encryption = Encryption.UNKNOWN; call.state = Call.State.RINGING; // TODO create conv Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(inviter_jid.bare_jid, account); if (conversation == null) return null; stream_interactor.get_module(CallStore.IDENTITY).add_call(call, conversation); conversation.last_active = call.time; CallState call_state = new CallState(call, stream_interactor); connect_call_state_signals(call_state); call_state.invited_to_group_call = muc_jid; call_state.parent_muc = inviter_jid.bare_jid; debug("[%s] on_muji_call_received accepting", account.bare_jid.to_string()); return call_state; } private void remove_call_from_datastructures(Call call) { jmi_request_peer.unset(call); call_states.unset(call); } private void connect_call_state_signals(CallState call_state) { call_states[call_state.call] = call_state; ulong terminated_handler_id = -1; terminated_handler_id = call_state.terminated.connect((who_terminated, reason_name, reason_text) => { remove_call_from_datastructures(call_state.call); call_terminated(call_state.call, reason_name, reason_text); call_state.disconnect(terminated_handler_id); }); } private void on_account_added(Account account) { Xep.Jingle.Module jingle_module = stream_interactor.module_manager.get_module(account, Xep.Jingle.Module.IDENTITY); jingle_module.session_initiate_received.connect((stream, session) => { foreach (Xep.Jingle.Content content in session.contents) { Xep.JingleRtp.Parameters? rtp_content_parameter = content.content_params as Xep.JingleRtp.Parameters; if (rtp_content_parameter != null) { on_incoming_call(account, session); break; } } }); Xep.JingleMessageInitiation.Module mi_module = stream_interactor.module_manager.get_module(account, Xep.JingleMessageInitiation.Module.IDENTITY); mi_module.session_proposed.connect((from, to, sid, descriptions) => { if (stream_interactor.get_module(MucManager.IDENTITY).might_be_groupchat(from.bare_jid, account)) return; bool audio_requested = descriptions.any_match((description) => description.ns_uri == Xep.JingleRtp.NS_URI && description.get_attribute("media") == "audio"); bool video_requested = descriptions.any_match((description) => description.ns_uri == Xep.JingleRtp.NS_URI && description.get_attribute("media") == "video"); if (!audio_requested && !video_requested) return; PeerState peer_state = create_received_call(account, from, to, video_requested); peer_state.sid = sid; CallState call_state = call_states[peer_state.call]; jmi_request_peer[peer_state.call] = peer_state; Conversation conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(call_state.call.counterpart.bare_jid, account, Conversation.Type.CHAT); if (call_state.call.direction == Call.DIRECTION_INCOMING) { call_incoming(call_state.call, call_state, conversation, video_requested, false); } else { call_outgoing(call_state.call, call_state, conversation); } }); mi_module.session_accepted.connect((from, to, sid) => { PeerState? peer_state = get_peer_by_sid(account, sid, from, to); if (peer_state == null) return; Call call = peer_state.call; // Carboned message from our account if (from.equals_bare(account.bare_jid)) { // Ignore carbon from ourselves if (from.equals(account.full_jid)) return; call.ourpart = from; call.state = Call.State.OTHER_DEVICE; remove_call_from_datastructures(call); return; } // We proposed the call. This is a message from our peer. if (call.direction == Call.DIRECTION_OUTGOING && from.equals_bare(peer_state.jid) && to.equals(account.full_jid)) { // We know the full jid of our peer now call_states[call].rename_peer(jmi_request_peer[call].jid, from); jmi_request_peer[call].call_resource.begin(from); } }); mi_module.session_rejected.connect((from, to, sid) => { PeerState? peer_state = get_peer_by_sid(account, sid, from, to); if (peer_state == null) return; Call call = peer_state.call; bool outgoing_reject = call.direction == Call.DIRECTION_OUTGOING && from.equals_bare(call.counterparts[0]); bool incoming_reject = call.direction == Call.DIRECTION_INCOMING && from.equals_bare(account.bare_jid); if (!outgoing_reject && !incoming_reject) return; // We don't care if a single person in a group call rejected the call if (incoming_reject && call_states[call].group_call != null) return; call.state = Call.State.DECLINED; call_states[call].terminated(from, Xep.Jingle.ReasonElement.DECLINE, "JMI reject"); remove_call_from_datastructures(call); }); mi_module.session_retracted.connect((from, to, sid) => { PeerState? peer_state = get_peer_by_sid(account, sid, from, to); if (peer_state == null) return; Call call = peer_state.call; bool outgoing_retract = call.direction == Call.DIRECTION_OUTGOING && from.equals_bare(account.bare_jid); bool incoming_retract = call.direction == Call.DIRECTION_INCOMING && from.equals_bare(call.counterpart); if (!(outgoing_retract || incoming_retract)) return; call.state = Call.State.MISSED; call_states[call].terminated(from, Xep.Jingle.ReasonElement.CANCEL, "JMI retract"); remove_call_from_datastructures(call); }); Xep.CallInvites.Module call_invites_module = stream_interactor.module_manager.get_module(account, Xep.CallInvites.Module.IDENTITY); call_invites_module.call_proposed.connect((from_jid, to_jid, call_id, video_requested, join_methods, message_stanza) => { if (from_jid.equals_bare(account.bare_jid)) return; if (stream_interactor.get_module(MucManager.IDENTITY).is_own_muc_jid(from_jid, account)) return; bool multiparty = false; CallState? call_state = null; foreach (StanzaNode join_method_node in join_methods) { if (join_method_node.name == "muji" && join_method_node.ns_uri == Xep.Muji.NS_URI) { // This is a MUJI invite // Disregard calls from muc history DateTime? delay = Xep.DelayedDelivery.get_time_for_message(message_stanza, from_jid.bare_jid); if (delay != null) return; string? room_jid_str = join_method_node.get_attribute("room"); if (room_jid_str == null) return; Jid room_jid = new Jid(room_jid_str); call_state = create_recv_muji_call(account, call_id, from_jid, room_jid, message_stanza.type_); multiparty = true; break; } else if (join_method_node.name == "jingle" && join_method_node.ns_uri == Xep.CallInvites.NS_URI) { // This is an invite for a direct Jingle session if (stream_interactor.get_module(MucManager.IDENTITY).might_be_groupchat(from_jid.bare_jid, account)) return; string? sid = join_method_node.get_attribute("sid"); if (sid == null) return; PeerState peer_state = create_received_call(account, from_jid, to_jid, video_requested); peer_state.sid = sid; call_state = call_states[peer_state.call]; jmi_request_peer[call_state.call] = peer_state; break; } } if (call_state == null) return; call_state.we_should_send_audio = true; call_state.we_should_send_video = video_requested; call_state.use_cim = true; call_state.cim_call_id = call_id; call_state.cim_counterpart = message_stanza.type_ == MessageStanza.TYPE_GROUPCHAT ? from_jid.bare_jid : from_jid; call_state.cim_message_type = message_stanza.type_; Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).approx_conversation_for_stanza(from_jid, to_jid, account, message_stanza.type_); conversation.last_active = call_state.call.time; if (conversation == null) return; if (call_state.call.direction == Call.DIRECTION_INCOMING) { call_incoming(call_state.call, call_state, conversation, video_requested, multiparty); } else { call_outgoing(call_state.call, call_state, conversation); } }); call_invites_module.call_accepted.connect((from_jid, to_jid, call_id, message_type) => { // Carboned message from our account if (from_jid.equals_bare(account.bare_jid)) { CallState? call_state = get_call_state_by_call_id(account, call_id); if (call_state == null) return; Call call = call_state.call; // Ignore carbon from ourselves if (from_jid.equals(account.full_jid)) return; // We accepted the call from another device call.ourpart = from_jid; call.state = Call.State.OTHER_DEVICE; remove_call_from_datastructures(call); return; } CallState? call_state = get_call_state_by_call_id(account, call_id, from_jid); if (call_state == null) return; Call call = call_state.call; // We proposed the call. This is a message from our peer. if (call.direction == Call.DIRECTION_OUTGOING && to_jid.equals(account.full_jid)) { // We know the full jid of our peer now call_state.rename_peer(jmi_request_peer[call].jid, from_jid); jmi_request_peer[call].call_resource.begin(from_jid); } }); call_invites_module.call_retracted.connect((from_jid, to_jid, call_id, message_type) => { if (from_jid.equals_bare(account.bare_jid)) return; // The call was retracted by the counterpart CallState? call_state = get_call_state_by_call_id(account, call_id, from_jid); if (call_state == null) return; if (call_state.call.state != Call.State.RINGING) { debug("%s tried to retract a call that's in state %s. Ignoring.", from_jid.to_string(), call_state.call.state.to_string()); return; } // TODO prevent other MUC occupants from retracting a call call_state.call.state = Call.State.MISSED; remove_call_from_datastructures(call_state.call); }); call_invites_module.call_rejected.connect((from_jid, to_jid, call_id, message_type) => { // We rejected an invite from another device if (from_jid.equals_bare(account.bare_jid)) { CallState? call_state = get_call_state_by_call_id(account, call_id); if (call_state == null) return; Call call = call_state.call; call.state = Call.State.DECLINED; } if (from_jid.equals_bare(account.bare_jid)) return; debug(@"[%s] %s rejected our MUJI invite", account.bare_jid.to_string(), from_jid.to_string()); }); stream_interactor.module_manager.get_module(account, Xep.Coin.Module.IDENTITY).coin_info_received.connect((jid, info) => { foreach (Call call in call_states.keys) { if (call.counterparts[0].equals_bare(jid)) { conference_info_received(call, info); return; } } }); } } }dino-0.3.0/libdino/src/service/chat_interaction.vala0000644000000000000000000002760114202022370021171 0ustar rootrootusing Gee; using Xmpp; using Dino.Entities; namespace Dino { public class ChatInteraction : StreamInteractionModule, Object { public static ModuleIdentity IDENTITY = new ModuleIdentity("chat_interaction"); public string id { get { return IDENTITY.id; } } public signal void focused_in(Conversation conversation); public signal void focused_out(Conversation conversation); private StreamInteractor stream_interactor; private Conversation? selected_conversation; private HashMap last_input_interaction = new HashMap(Conversation.hash_func, Conversation.equals_func); private HashMap last_interface_interaction = new HashMap(Conversation.hash_func, Conversation.equals_func); private bool focus_in = false; public static void start(StreamInteractor stream_interactor) { ChatInteraction m = new ChatInteraction(stream_interactor); stream_interactor.add_module(m); } private ChatInteraction(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; Timeout.add_seconds(30, update_interactions); stream_interactor.get_module(MessageProcessor.IDENTITY).received_pipeline.connect(new ReceivedMessageListener(stream_interactor)); stream_interactor.get_module(MessageProcessor.IDENTITY).message_sent.connect(on_message_sent); stream_interactor.get_module(ContentItemStore.IDENTITY).new_item.connect(new_item); } public int get_num_unread(Conversation conversation) { Database db = Dino.Application.get_default().db; Qlite.QueryBuilder query = db.content_item.select() .with(db.content_item.conversation_id, "=", conversation.id) .with(db.content_item.hide, "=", false); ContentItem? read_up_to_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_item_by_id(conversation, conversation.read_up_to_item); if (read_up_to_item != null) { string time = read_up_to_item.time.to_unix().to_string(); string id = read_up_to_item.id.to_string(); query.where(@"time > ? OR (time = ? AND id > ?)", { time, time, id }); } // If it's a new conversation with read_up_to_item == null, all items are new. return (int) query.count(); } public bool is_active_focus(Conversation? conversation = null) { if (conversation != null) { return focus_in && conversation.equals(this.selected_conversation); } else { return focus_in; } } public void on_window_focus_in(Conversation? conversation) { on_conversation_focused(conversation); } public void on_window_focus_out(Conversation? conversation) { on_conversation_unfocused(conversation); } public void on_message_entered(Conversation? conversation) { if (!last_input_interaction.has_key(conversation)) { send_chat_state_notification(conversation, Xep.ChatStateNotifications.STATE_COMPOSING); } last_input_interaction[conversation] = new DateTime.now_utc(); last_interface_interaction[conversation] = new DateTime.now_utc(); } public void on_message_cleared(Conversation? conversation) { if (last_input_interaction.has_key(conversation)) { last_input_interaction.unset(conversation); send_chat_state_notification(conversation, Xep.ChatStateNotifications.STATE_ACTIVE); } } public void on_conversation_selected(Conversation conversation) { on_conversation_unfocused(selected_conversation); selected_conversation = conversation; on_conversation_focused(conversation); } private void new_item(ContentItem item, Conversation conversation) { bool mark_read = is_active_focus(conversation); if (!mark_read) { MessageItem? message_item = item as MessageItem; if (message_item != null) { if (message_item.message.direction == Message.DIRECTION_SENT) { mark_read = true; } } if (message_item == null) { FileItem? file_item = item as FileItem; if (file_item != null) { if (file_item.file_transfer.direction == FileTransfer.DIRECTION_SENT) { mark_read = true; } } } } if (mark_read) { ContentItem? read_up_to = stream_interactor.get_module(ContentItemStore.IDENTITY).get_item_by_id(conversation, conversation.read_up_to_item); if (read_up_to != null) { if (read_up_to.compare(item) < 0) { conversation.read_up_to_item = item.id; } } else { conversation.read_up_to_item = item.id; } } } private void on_message_sent(Entities.Message message, Conversation conversation) { last_input_interaction.unset(conversation); last_interface_interaction.unset(conversation); } private void on_conversation_focused(Conversation? conversation) { focus_in = true; if (conversation == null) return; focused_in(conversation); check_send_read(); ContentItem? latest_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_latest(conversation); if (latest_item != null) { conversation.read_up_to_item = latest_item.id; } } private void on_conversation_unfocused(Conversation? conversation) { focus_in = false; if (conversation == null) return; focused_out(conversation); if (last_input_interaction.has_key(conversation)) { send_chat_state_notification(conversation, Xep.ChatStateNotifications.STATE_PAUSED); last_input_interaction.unset(conversation); } } private void check_send_read() { if (selected_conversation == null) return; Entities.Message? message = stream_interactor.get_module(MessageStorage.IDENTITY).get_last_message(selected_conversation); if (message != null && message.direction == Entities.Message.DIRECTION_RECEIVED) { send_chat_marker(message, null, selected_conversation, Xep.ChatMarkers.MARKER_DISPLAYED); } } private bool update_interactions() { for (MapIterator iter = last_input_interaction.map_iterator(); iter.has_next(); iter.next()) { if (!iter.valid && iter.has_next()) iter.next(); Conversation conversation = iter.get_key(); if (last_input_interaction.has_key(conversation) && (new DateTime.now_utc()).difference(last_input_interaction[conversation]) >= 15 * TimeSpan.SECOND) { iter.unset(); send_chat_state_notification(conversation, Xep.ChatStateNotifications.STATE_PAUSED); } } for (MapIterator iter = last_interface_interaction.map_iterator(); iter.has_next(); iter.next()) { if (!iter.valid && iter.has_next()) iter.next(); Conversation conversation = iter.get_key(); if (last_interface_interaction.has_key(conversation) && (new DateTime.now_utc()).difference(last_interface_interaction[conversation]) >= 1.5 * TimeSpan.MINUTE) { iter.unset(); send_chat_state_notification(conversation, Xep.ChatStateNotifications.STATE_GONE); } } return true; } private class ReceivedMessageListener : MessageListener { public string[] after_actions_const = new string[]{ "DEDUPLICATE", "FILTER_EMPTY", "STORE_CONTENT_ITEM" }; public override string action_group { get { return "OTHER_NODES"; } } public override string[] after_actions { get { return after_actions_const; } } private StreamInteractor stream_interactor; public ReceivedMessageListener(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; } public override async bool run(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) { if (Xep.MessageArchiveManagement.MessageFlag.get_flag(stanza) != null) return false; ChatInteraction outer = stream_interactor.get_module(ChatInteraction.IDENTITY); outer.send_delivery_receipt(message, stanza, conversation); // Send chat marker if (message.direction == Entities.Message.DIRECTION_SENT) return false; if (outer.is_active_focus(conversation)) { outer.check_send_read(); outer.send_chat_marker(message, stanza, conversation, Xep.ChatMarkers.MARKER_DISPLAYED); } else { outer.send_chat_marker(message, stanza, conversation, Xep.ChatMarkers.MARKER_RECEIVED); } return false; } } private void send_chat_marker(Entities.Message message, Xmpp.MessageStanza? stanza, Conversation conversation, string marker) { XmppStream? stream = stream_interactor.get_stream(conversation.account); if (stream == null) return; switch (marker) { case Xep.ChatMarkers.MARKER_RECEIVED: if (stanza != null && Xep.ChatMarkers.Module.requests_marking(stanza) && message.type_ != Message.Type.GROUPCHAT) { if (message.stanza_id == null) return; stream.get_module(Xep.ChatMarkers.Module.IDENTITY).send_marker(stream, message.from, message.stanza_id, message.get_type_string(), Xep.ChatMarkers.MARKER_RECEIVED); } break; case Xep.ChatMarkers.MARKER_DISPLAYED: if (conversation.get_send_marker_setting(stream_interactor) == Conversation.Setting.ON) { if (message.equals(conversation.read_up_to)) return; conversation.read_up_to = message; if (message.type_ == Message.Type.GROUPCHAT || message.type_ == Message.Type.GROUPCHAT_PM) { if (message.server_id == null) return; stream.get_module(Xep.ChatMarkers.Module.IDENTITY).send_marker(stream, message.from.bare_jid, message.server_id, message.get_type_string(), Xep.ChatMarkers.MARKER_DISPLAYED); } else { if (message.stanza_id == null) return; stream.get_module(Xep.ChatMarkers.Module.IDENTITY).send_marker(stream, message.from, message.stanza_id, message.get_type_string(), Xep.ChatMarkers.MARKER_DISPLAYED); } } break; } } private void send_delivery_receipt(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) { if (message.direction == Entities.Message.DIRECTION_SENT) return; if (!Xep.MessageDeliveryReceipts.Module.requests_receipt(stanza)) return; if (conversation.type_ == Conversation.Type.GROUPCHAT) return; XmppStream? stream = stream_interactor.get_stream(conversation.account); if (stream != null) { stream.get_module(Xep.MessageDeliveryReceipts.Module.IDENTITY).send_received(stream, message.from, message.stanza_id); } } private void send_chat_state_notification(Conversation conversation, string state) { if (conversation.get_send_typing_setting(stream_interactor) != Conversation.Setting.ON) return; XmppStream? stream = stream_interactor.get_stream(conversation.account); if (stream != null) { string message_type = conversation.type_ == Conversation.Type.GROUPCHAT ? Xmpp.MessageStanza.TYPE_GROUPCHAT : Xmpp.MessageStanza.TYPE_CHAT; stream.get_module(Xep.ChatStateNotifications.Module.IDENTITY).send_state(stream, conversation.counterpart, message_type, state); } } } } dino-0.3.0/libdino/src/service/connection_manager.vala0000644000000000000000000003414714202022370021507 0ustar rootrootusing Gee; using Xmpp; using Dino.Entities; namespace Dino { public class ConnectionManager : Object { public signal void stream_opened(Account account, XmppStream stream); public signal void stream_attached_modules(Account account, XmppStream stream); public signal void connection_state_changed(Account account, ConnectionState state); public signal void connection_error(Account account, ConnectionError error); public enum ConnectionState { CONNECTED, CONNECTING, DISCONNECTED } private HashMap connections = new HashMap(Account.hash_func, Account.equals_func); private HashMap connection_errors = new HashMap(Account.hash_func, Account.equals_func); private HashMap connection_ongoing = new HashMap(Account.hash_func, Account.equals_func); private HashMap connection_directly_retry = new HashMap(Account.hash_func, Account.equals_func); private NetworkMonitor? network_monitor; private Login1Manager? login1; private ModuleManager module_manager; public string? log_options; public class ConnectionError { public enum Source { CONNECTION, SASL, TLS, STREAM_ERROR } public enum Reconnect { NOW, LATER, NEVER } public Source source; public string? identifier; public Reconnect reconnect_recomendation { get; set; default=Reconnect.NOW; } public ConnectionError(Source source, string? identifier) { this.source = source; this.identifier = identifier; } } private class Connection { public string uuid { get; set; } public XmppStream? stream { get; set; } public ConnectionState connection_state { get; set; default = ConnectionState.DISCONNECTED; } public DateTime? established { get; set; } public DateTime? last_activity { get; set; } public Connection() { reset(); } public void reset() { if (stream != null) { stream.detach_modules(); stream.disconnect.begin(); } stream = null; established = last_activity = null; uuid = Xmpp.random_uuid(); } public void make_offline() { Xmpp.Presence.Stanza presence = new Xmpp.Presence.Stanza(); presence.type_ = Xmpp.Presence.Stanza.TYPE_UNAVAILABLE; if (stream != null) { stream.get_module(Presence.Module.IDENTITY).send_presence(stream, presence); } } public async void disconnect_account() { make_offline(); if (stream != null) { try { yield stream.disconnect(); } catch (Error e) { debug("Error disconnecting stream: %s", e.message); } } } } public ConnectionManager(ModuleManager module_manager) { this.module_manager = module_manager; network_monitor = GLib.NetworkMonitor.get_default(); if (network_monitor != null) { network_monitor.network_changed.connect(on_network_changed); network_monitor.notify["connectivity"].connect(on_network_changed); } get_login1.begin((_, res) => { login1 = get_login1.end(res); if (login1 != null) { login1.PrepareForSleep.connect(on_prepare_for_sleep); } }); Timeout.add_seconds(60, () => { foreach (Account account in connections.keys) { if (connections[account].last_activity != null && connections[account].last_activity.compare(new DateTime.now_utc().add_minutes(-1)) < 0) { check_reconnect(account); } } return true; }); } public XmppStream? get_stream(Account account) { if (get_state(account) == ConnectionState.CONNECTED) { return connections[account].stream; } return null; } public ConnectionState get_state(Account account) { if (connections.has_key(account)){ return connections[account].connection_state; } return ConnectionState.DISCONNECTED; } public ConnectionError? get_error(Account account) { if (connection_errors.has_key(account)) { return connection_errors[account]; } return null; } public Collection get_managed_accounts() { return connections.keys; } public void connect_account(Account account) { if (!connections.has_key(account)) { connections[account] = new Connection(); connection_ongoing[account] = false; connection_directly_retry[account] = false; connect_stream.begin(account); } else { check_reconnect(account); } } public void make_offline_all() { foreach (Account account in connections.keys) { make_offline(account); } } private void make_offline(Account account) { connections[account].make_offline(); change_connection_state(account, ConnectionState.DISCONNECTED); } public async void disconnect_account(Account account) { if (connections.has_key(account)) { make_offline(account); connections[account].disconnect_account.begin(); connections.unset(account); } } private async void connect_stream(Account account, string? resource = null) { if (!connections.has_key(account)) return; debug("[%s] (Maybe) Establishing a new connection", account.bare_jid.to_string()); connection_errors.unset(account); if (resource == null) resource = account.resourcepart; XmppStreamResult stream_result; if (connection_ongoing[account]) { debug("[%s] Connection attempt already in progress. Directly retry if it fails.", account.bare_jid.to_string()); connection_directly_retry[account] = true; return; } else if (connections[account].stream != null) { debug("[%s] Cancelling connecting because there is already a stream", account.bare_jid.to_string()); return; } else { connection_ongoing[account] = true; connection_directly_retry[account] = false; change_connection_state(account, ConnectionState.CONNECTING); stream_result = yield Xmpp.establish_stream(account.bare_jid, module_manager.get_modules(account, resource), log_options, (peer_cert, errors) => { return on_invalid_certificate(account.domainpart, peer_cert, errors); } ); connections[account].stream = stream_result.stream; connection_ongoing[account] = false; } if (stream_result.stream == null) { if (stream_result.tls_errors != null) { set_connection_error(account, new ConnectionError(ConnectionError.Source.TLS, null) { reconnect_recomendation=ConnectionError.Reconnect.NEVER}); return; } debug("[%s] Could not connect", account.bare_jid.to_string()); change_connection_state(account, ConnectionState.DISCONNECTED); check_reconnect(account, connection_directly_retry[account]); return; } XmppStream stream = stream_result.stream; debug("[%s] New connection with resource %s: %p", account.bare_jid.to_string(), resource, stream); connections[account].established = new DateTime.now_utc(); stream.attached_modules.connect((stream) => { stream_attached_modules(account, stream); change_connection_state(account, ConnectionState.CONNECTED); // stream.get_module(Xep.Muji.Module.IDENTITY).join_call(stream, new Jid("test@muc.poez.io"), true); }); stream.get_module(Sasl.Module.IDENTITY).received_auth_failure.connect((stream, node) => { set_connection_error(account, new ConnectionError(ConnectionError.Source.SASL, null)); }); string connection_uuid = connections[account].uuid; stream.received_node.connect(() => { if (connections[account].uuid == connection_uuid) { connections[account].last_activity = new DateTime.now_utc(); } else { warning("Got node for outdated connection"); } }); stream_opened(account, stream); try { yield stream.loop(); } catch (Error e) { debug("[%s %p] Connection error: %s", account.bare_jid.to_string(), stream, e.message); change_connection_state(account, ConnectionState.DISCONNECTED); connections[account].reset(); StreamError.Flag? flag = stream.get_flag(StreamError.Flag.IDENTITY); if (flag != null) { warning(@"[%s %p] Stream Error: %s", account.bare_jid.to_string(), stream, flag.error_type); set_connection_error(account, new ConnectionError(ConnectionError.Source.STREAM_ERROR, flag.error_type)); if (flag.resource_rejected) { connect_stream.begin(account, account.resourcepart + "-" + random_uuid()); return; } } ConnectionError? error = connection_errors[account]; if (error != null && error.source == ConnectionError.Source.SASL) { return; } check_reconnect(account); } } private void check_reconnects() { foreach (Account account in connections.keys) { check_reconnect(account); } } private void check_reconnect(Account account, bool directly_reconnect = false) { if (!connections.has_key(account)) return; bool acked = false; DateTime? last_activity_was = connections[account].last_activity; if (connections[account].stream == null) { Timeout.add_seconds(10, () => { if (!connections.has_key(account)) return false; if (connections[account].stream != null) return false; if (connections[account].last_activity != last_activity_was) return false; connect_stream.begin(account); return false; }); return; } XmppStream stream = connections[account].stream; stream.get_module(Xep.Ping.Module.IDENTITY).send_ping.begin(stream, account.bare_jid.domain_jid, () => { acked = true; if (connections[account].stream != stream) return; change_connection_state(account, ConnectionState.CONNECTED); }); Timeout.add_seconds(10, () => { if (!connections.has_key(account)) return false; if (connections[account].stream != stream) return false; if (acked) return false; if (connections[account].last_activity != last_activity_was) return false; // Reconnect. Nothing gets through the stream. debug("[%s %p] Ping timeouted. Reconnecting", account.bare_jid.to_string(), stream); change_connection_state(account, ConnectionState.DISCONNECTED); connections[account].reset(); connect_stream.begin(account); return false; }); } private bool network_is_online() { /* FIXME: We should also check for connectivity eventually. For more * details on why we don't do it for now, see: * * - https://github.com/dino/dino/pull/236#pullrequestreview-86851793 * - https://bugzilla.gnome.org/show_bug.cgi?id=792240 */ return network_monitor != null && network_monitor.network_available; } private void on_network_changed() { if (network_is_online()) { debug("NetworkMonitor: Network reported online"); check_reconnects(); } else { debug("NetworkMonitor: Network reported offline"); foreach (Account account in connections.keys) { change_connection_state(account, ConnectionState.DISCONNECTED); } } } private async void on_prepare_for_sleep(bool suspend) { foreach (Account account in connections.keys) { change_connection_state(account, ConnectionState.DISCONNECTED); } if (suspend) { debug("Login1: Device suspended"); foreach (Account account in connections.keys) { try { make_offline(account); if (connections[account].stream != null) { yield connections[account].stream.disconnect(); } } catch (Error e) { debug("Error disconnecting stream %p: %s", connections[account].stream, e.message); } } } else { debug("Login1: Device un-suspend"); check_reconnects(); } } private void change_connection_state(Account account, ConnectionState state) { if (connections.has_key(account)) { connections[account].connection_state = state; connection_state_changed(account, state); } } private void set_connection_error(Account account, ConnectionError error) { connection_errors[account] = error; connection_error(account, error); } public static bool on_invalid_certificate(string domain, TlsCertificate peer_cert, TlsCertificateFlags errors) { if (domain.has_suffix(".onion") && errors == TlsCertificateFlags.UNKNOWN_CA) { // It's barely possible for .onion servers to provide a non-self-signed cert. // But that's fine because encryption is provided independently though TOR. warning("Accepting TLS certificate from unknown CA from .onion address %s", domain); return true; } return false; } } } dino-0.3.0/libdino/src/service/content_item_store.vala0000644000000000000000000003114514202022370021555 0ustar rootrootusing Gee; using Dino.Entities; using Qlite; using Xmpp; namespace Dino { public class ContentItemStore : StreamInteractionModule, Object { public static ModuleIdentity IDENTITY = new ModuleIdentity("content_item_store"); public string id { get { return IDENTITY.id; } } public signal void new_item(ContentItem item, Conversation conversation); private StreamInteractor stream_interactor; private Database db; private HashMap collection_conversations = new HashMap(Conversation.hash_func, Conversation.equals_func); public static void start(StreamInteractor stream_interactor, Database db) { ContentItemStore m = new ContentItemStore(stream_interactor, db); stream_interactor.add_module(m); } public ContentItemStore(StreamInteractor stream_interactor, Database db) { this.stream_interactor = stream_interactor; this.db = db; stream_interactor.get_module(FileManager.IDENTITY).received_file.connect(insert_file_transfer); stream_interactor.get_module(MessageProcessor.IDENTITY).message_received.connect(announce_message); stream_interactor.get_module(MessageProcessor.IDENTITY).message_sent.connect(announce_message); stream_interactor.get_module(Calls.IDENTITY).call_incoming.connect(insert_call); stream_interactor.get_module(Calls.IDENTITY).call_outgoing.connect(insert_call); } public void init(Conversation conversation, ContentItemCollection item_collection) { collection_conversations[conversation] = item_collection; } public void uninit(Conversation conversation, ContentItemCollection item_collection) { collection_conversations.unset(conversation); } public Gee.List get_items_from_query(QueryBuilder select, Conversation conversation) { Gee.TreeSet items = new Gee.TreeSet(ContentItem.compare_func); foreach (var row in select) { int provider = row[db.content_item.content_type]; int foreign_id = row[db.content_item.foreign_id]; DateTime time = new DateTime.from_unix_utc(row[db.content_item.time]); switch (provider) { case 1: Message? message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_id(foreign_id, conversation); if (message != null) { var message_item = new MessageItem(message, conversation, row[db.content_item.id]); message_item.time = time; // In case of message corrections, the original time should be used items.add(message_item); } break; case 2: FileTransfer? file_transfer = stream_interactor.get_module(FileTransferStorage.IDENTITY).get_file_by_id(foreign_id, conversation); if (file_transfer != null) { Message? message = null; if (file_transfer.provider == 0 && file_transfer.info != null) { message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_id(int.parse(file_transfer.info), conversation); } var file_item = new FileItem(file_transfer, conversation, row[db.content_item.id], message); items.add(file_item); } break; case 3: Call? call = stream_interactor.get_module(CallStore.IDENTITY).get_call_by_id(foreign_id, conversation); if (call != null) { var call_item = new CallItem(call, conversation, row[db.content_item.id]); items.add(call_item); } break; } } Gee.List ret = new ArrayList(); foreach (ContentItem item in items) { ret.add(item); } return ret; } public ContentItem? get_item(Conversation conversation, int type, int foreign_id) { QueryBuilder select = db.content_item.select() .with(db.content_item.content_type, "=", type) .with(db.content_item.foreign_id, "=", foreign_id); Gee.List item = get_items_from_query(select, conversation); return item.size > 0 ? item[0] : null; } public ContentItem? get_item_by_id(Conversation conversation, int id) { QueryBuilder select = db.content_item.select() .with(db.content_item.id, "=", id); Gee.List item = get_items_from_query(select, conversation); return item.size > 0 ? item[0] : null; } public ContentItem? get_latest(Conversation conversation) { Gee.List items = get_n_latest(conversation, 1); if (items.size > 0) { return items.get(0); } return null; } public Gee.List get_n_latest(Conversation conversation, int count) { QueryBuilder select = db.content_item.select() .with(db.content_item.conversation_id, "=", conversation.id) .with(db.content_item.hide, "=", false) .order_by(db.content_item.time, "DESC") .order_by(db.content_item.id, "DESC") .limit(count); return get_items_from_query(select, conversation); } public Gee.List get_before(Conversation conversation, ContentItem item, int count) { long time = (long) item.time.to_unix(); QueryBuilder select = db.content_item.select() .where(@"time < ? OR (time = ? AND id < ?)", { time.to_string(), time.to_string(), item.id.to_string() }) .with(db.content_item.conversation_id, "=", conversation.id) .with(db.content_item.hide, "=", false) .order_by(db.content_item.time, "DESC") .order_by(db.content_item.id, "DESC") .limit(count); return get_items_from_query(select, conversation); } public Gee.List get_after(Conversation conversation, ContentItem item, int count) { long time = (long) item.time.to_unix(); QueryBuilder select = db.content_item.select() .where(@"time > ? OR (time = ? AND id > ?)", { time.to_string(), time.to_string(), item.id.to_string() }) .with(db.content_item.conversation_id, "=", conversation.id) .with(db.content_item.hide, "=", false) .order_by(db.content_item.time, "ASC") .order_by(db.content_item.id, "ASC") .limit(count); return get_items_from_query(select, conversation); } public void insert_message(Message message, Conversation conversation, bool hide = false) { MessageItem item = new MessageItem(message, conversation, -1); item.id = db.add_content_item(conversation, message.time, message.local_time, 1, message.id, hide); } private void announce_message(Message message, Conversation conversation) { QueryBuilder select = db.content_item.select(); select.with(db.content_item.foreign_id, "=", message.id); select.with(db.content_item.content_type, "=", 1); select.with(db.content_item.hide, "=", false); foreach (Row row in select) { MessageItem item = new MessageItem(message, conversation, row[db.content_item.id]); if (collection_conversations.has_key(conversation)) { collection_conversations.get(conversation).insert_item(item); } new_item(item, conversation); break; } } private void insert_file_transfer(FileTransfer file_transfer, Conversation conversation) { FileItem item = new FileItem(file_transfer, conversation, -1); item.id = db.add_content_item(conversation, file_transfer.time, file_transfer.local_time, 2, file_transfer.id, false); if (collection_conversations.has_key(conversation)) { collection_conversations.get(conversation).insert_item(item); } new_item(item, conversation); } private void insert_call(Call call, CallState call_state, Conversation conversation) { CallItem item = new CallItem(call, conversation, -1); item.id = db.add_content_item(conversation, call.time, call.local_time, 3, call.id, false); if (collection_conversations.has_key(conversation)) { collection_conversations.get(conversation).insert_item(item); } new_item(item, conversation); } public bool get_item_hide(ContentItem content_item) { return db.content_item.row_with(db.content_item.id, content_item.id)[db.content_item.hide, false]; } public void set_item_hide(ContentItem content_item, bool hide) { db.content_item.update() .with(db.content_item.id, "=", content_item.id) .set(db.content_item.hide, hide) .perform(); } } public interface ContentItemCollection : Object { public abstract void insert_item(ContentItem item); public abstract void remove_item(ContentItem item); } public abstract class ContentItem : Object { public int id { get; set; } public string type_ { get; set; } public Jid jid { get; set; } public DateTime time { get; set; } public Encryption encryption { get; set; } public Entities.Message.Marked mark { get; set; } ContentItem(int id, string ty, Jid jid, DateTime time, Encryption encryption, Entities.Message.Marked mark) { this.id = id; this.type_ = ty; this.jid = jid; this.time = time; this.encryption = encryption; this.mark = mark; } public int compare(ContentItem c) { return compare_func(this, c); } public static int compare_func(ContentItem a, ContentItem b) { int res = a.time.compare(b.time); if (res == 0) { res = a.id - b.id > 0 ? 1 : -1; } return res; } } public class MessageItem : ContentItem { public const string TYPE = "message"; public Message message; public Conversation conversation; public MessageItem(Message message, Conversation conversation, int id) { base(id, TYPE, message.from, message.time, message.encryption, message.marked); this.message = message; this.conversation = conversation; message.bind_property("marked", this, "mark"); } } public class FileItem : ContentItem { public const string TYPE = "file"; public FileTransfer file_transfer; public Conversation conversation; public FileItem(FileTransfer file_transfer, Conversation conversation, int id, Message? message = null) { Entities.Message.Marked mark = Entities.Message.Marked.NONE; if (message != null) { mark = message.marked; } else if (file_transfer.direction == FileTransfer.DIRECTION_SENT) { mark = file_to_message_state(file_transfer.state); } base(id, TYPE, file_transfer.from, file_transfer.time, file_transfer.encryption, mark); this.file_transfer = file_transfer; this.conversation = conversation; // TODO those don't work if (message != null) { message.bind_property("marked", this, "mark"); } else if (file_transfer.direction == FileTransfer.DIRECTION_SENT) { file_transfer.bind_property("state", this, "mark", BindingFlags.DEFAULT, (_, from_value, ref to_value) => { to_value = file_to_message_state((FileTransfer.State)from_value.get_enum()); return true; }); } } private static Entities.Message.Marked file_to_message_state(FileTransfer.State state) { switch (state) { case FileTransfer.State.IN_PROGRESS: return Entities.Message.Marked.UNSENT; case FileTransfer.State.COMPLETE: return Entities.Message.Marked.NONE; case FileTransfer.State.NOT_STARTED: return Entities.Message.Marked.UNSENT; case FileTransfer.State.FAILED: return Entities.Message.Marked.WONTSEND; } assert_not_reached(); } } public class CallItem : ContentItem { public const string TYPE = "call"; public Call call; public Conversation conversation; public CallItem(Call call, Conversation conversation, int id) { base(id, TYPE, call.proposer, call.time, call.encryption, Message.Marked.NONE); this.call = call; this.conversation = conversation; call.bind_property("encryption", this, "encryption"); } } } dino-0.3.0/libdino/src/service/conversation_manager.vala0000644000000000000000000002133214202022370022052 0ustar rootrootusing Gee; using Xmpp; using Dino.Entities; namespace Dino { public class ConversationManager : StreamInteractionModule, Object { public static ModuleIdentity IDENTITY = new ModuleIdentity("conversation_manager"); public string id { get { return IDENTITY.id; } } public signal void conversation_activated(Conversation conversation); public signal void conversation_deactivated(Conversation conversation); private StreamInteractor stream_interactor; private Database db; private HashMap>> conversations = new HashMap>>(Account.hash_func, Account.equals_func); public static void start(StreamInteractor stream_interactor, Database db) { ConversationManager m = new ConversationManager(stream_interactor, db); stream_interactor.add_module(m); } private ConversationManager(StreamInteractor stream_interactor, Database db) { this.db = db; this.stream_interactor = stream_interactor; stream_interactor.add_module(this); stream_interactor.account_added.connect(on_account_added); stream_interactor.account_removed.connect(on_account_removed); stream_interactor.get_module(MessageProcessor.IDENTITY).received_pipeline.connect(new MessageListener(stream_interactor)); stream_interactor.get_module(MessageProcessor.IDENTITY).message_sent.connect(handle_sent_message); } public Conversation create_conversation(Jid jid, Account account, Conversation.Type? type = null) { assert(conversations.has_key(account)); Jid store_jid = type == Conversation.Type.GROUPCHAT ? jid.bare_jid : jid; // Do we already have a conversation for this jid? if (conversations[account].has_key(store_jid)) { foreach (var conversation in conversations[account][store_jid]) { if (conversation.type_ == type) { return conversation; } } } // Create a new converation Conversation conversation = new Conversation(jid, account, type); add_conversation(conversation); conversation.persist(db); return conversation; } public Conversation? get_conversation_for_message(Entities.Message message) { if (message.type_ == Entities.Message.Type.CHAT) { return create_conversation(message.counterpart.bare_jid, message.account, Conversation.Type.CHAT); } else if (message.type_ == Entities.Message.Type.GROUPCHAT) { return create_conversation(message.counterpart.bare_jid, message.account, Conversation.Type.GROUPCHAT); } else if (message.type_ == Entities.Message.Type.GROUPCHAT_PM) { return create_conversation(message.counterpart, message.account, Conversation.Type.GROUPCHAT_PM); } return null; } public Gee.List get_conversations(Jid jid, Account account) { Gee.List ret = new ArrayList(Conversation.equals_func); Conversation? bare_conversation = get_conversation(jid, account); if (bare_conversation != null) ret.add(bare_conversation); Conversation? full_conversation = get_conversation(jid.bare_jid, account); if (full_conversation != null) ret.add(full_conversation); return ret; } public Conversation? get_conversation(Jid jid, Account account, Conversation.Type? type = null) { if (conversations.has_key(account)) { if (conversations[account].has_key(jid)) { foreach (var conversation in conversations[account][jid]) { if (type == null || conversation.type_ == type) { return conversation; } } } } return null; } public Conversation? approx_conversation_for_stanza(Jid from, Jid to, Account account, string msg_ty) { if (msg_ty == Xmpp.MessageStanza.TYPE_GROUPCHAT) { return get_conversation(from.bare_jid, account, Conversation.Type.GROUPCHAT); } Jid counterpart = from.equals_bare(account.bare_jid) ? to : from; if (msg_ty == Xmpp.MessageStanza.TYPE_CHAT && counterpart.is_full() && get_conversation(counterpart.bare_jid, account, Conversation.Type.GROUPCHAT) != null) { var pm = get_conversation(counterpart, account, Conversation.Type.GROUPCHAT_PM); if (pm != null) return pm; } return get_conversation(counterpart.bare_jid, account, Conversation.Type.CHAT); } public Conversation? get_conversation_by_id(int id) { foreach (HashMap> hm in conversations.values) { foreach (Gee.List hm2 in hm.values) { foreach (Conversation conversation in hm2) { if (conversation.id == id) { return conversation; } } } } return null; } public Gee.List get_active_conversations(Account? account = null) { Gee.List ret = new ArrayList(Conversation.equals_func); foreach (Account account_ in conversations.keys) { if (account != null && !account_.equals(account)) continue; foreach (Gee.List list in conversations[account_].values) { foreach (var conversation in list) { if(conversation.active) ret.add(conversation); } } } return ret; } public void start_conversation(Conversation conversation) { if (conversation.last_active == null) { conversation.last_active = new DateTime.now_utc(); if (conversation.active) conversation_activated(conversation); } if (!conversation.active) { conversation.active = true; conversation_activated(conversation); } } public void close_conversation(Conversation conversation) { if (!conversation.active) return; conversation.active = false; conversation_deactivated(conversation); } private void on_account_added(Account account) { conversations[account] = new HashMap>(Jid.hash_func, Jid.equals_func); foreach (Conversation conversation in db.get_conversations(account)) { add_conversation(conversation); } } private void on_account_removed(Account account) { foreach (Gee.List list in conversations[account].values) { foreach (var conversation in list) { if(conversation.active) conversation_deactivated(conversation); } } conversations.unset(account); } private class MessageListener : Dino.MessageListener { public string[] after_actions_const = new string[]{ "DEDUPLICATE", "FILTER_EMPTY" }; public override string action_group { get { return "MANAGER"; } } public override string[] after_actions { get { return after_actions_const; } } private StreamInteractor stream_interactor; public MessageListener(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; } public override async bool run(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) { conversation.last_active = message.time; if (stanza != null) { bool is_mam_message = Xep.MessageArchiveManagement.MessageFlag.get_flag(stanza) != null; bool is_recent = message.time.compare(new DateTime.now_utc().add_days(-3)) > 0; if (is_mam_message && !is_recent) return false; } stream_interactor.get_module(ConversationManager.IDENTITY).start_conversation(conversation); return false; } } private void handle_sent_message(Entities.Message message, Conversation conversation) { conversation.last_active = message.time; bool is_recent = message.time.compare(new DateTime.now_utc().add_hours(-24)) > 0; if (is_recent) { start_conversation(conversation); } } private void add_conversation(Conversation conversation) { if (!conversations[conversation.account].has_key(conversation.counterpart)) { conversations[conversation.account][conversation.counterpart] = new ArrayList(Conversation.equals_func); } conversations[conversation.account][conversation.counterpart].add(conversation); if (conversation.active) { conversation_activated(conversation); } } } } dino-0.3.0/libdino/src/service/counterpart_interaction_manager.vala0000644000000000000000000002707014202022370024312 0ustar rootrootusing Gee; using Xmpp; using Dino.Entities; namespace Dino { public class CounterpartInteractionManager : StreamInteractionModule, Object { public static ModuleIdentity IDENTITY = new ModuleIdentity("counterpart_interaction_manager"); public string id { get { return IDENTITY.id; } } public signal void received_state(Conversation conversation, string state); public signal void received_marker(Account account, Jid jid, Entities.Message message, Entities.Message.Marked marker); public signal void received_message_received(Account account, Jid jid, Entities.Message message); public signal void received_message_displayed(Account account, Jid jid, Entities.Message message); private StreamInteractor stream_interactor; private HashMap> typing_since = new HashMap>(Conversation.hash_func, Conversation.equals_func); private HashMap marker_wo_message = new HashMap(); public static void start(StreamInteractor stream_interactor) { CounterpartInteractionManager m = new CounterpartInteractionManager(stream_interactor); stream_interactor.add_module(m); } private CounterpartInteractionManager(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; stream_interactor.account_added.connect(on_account_added); stream_interactor.get_module(MessageProcessor.IDENTITY).message_received.connect((message, conversation) => clear_chat_state(conversation, message.from)); stream_interactor.get_module(MessageProcessor.IDENTITY).message_sent_or_received.connect(check_if_got_marker); stream_interactor.get_module(PresenceManager.IDENTITY).received_offline_presence.connect((jid, account) => { foreach (Conversation conversation in stream_interactor.get_module(ConversationManager.IDENTITY).get_conversations(jid, account)) { clear_chat_state(conversation, jid); } }); stream_interactor.stream_negotiated.connect((account) => clear_all_chat_states(account) ); Timeout.add_seconds(60, () => { var one_min_ago = new DateTime.now_utc().add_seconds(-1); foreach (Conversation conversation in typing_since.keys) { ArrayList to_remove = new ArrayList(); foreach (Jid jid in typing_since[conversation].keys) { if (typing_since[conversation][jid].compare(one_min_ago) < 0) { to_remove.add(jid); } } foreach (Jid jid in to_remove) { clear_chat_state(conversation, jid); } } return true; }); } public Gee.List? get_typing_jids(Conversation conversation) { if (stream_interactor.connection_manager.get_state(conversation.account) != ConnectionManager.ConnectionState.CONNECTED) return null; if (!typing_since.has_key(conversation) || typing_since[conversation].size == 0) return null; var jids = new ArrayList(); foreach (Jid jid in typing_since[conversation].keys) { jids.add(jid); } return jids; } private void on_account_added(Account account) { stream_interactor.module_manager.get_module(account, Xep.ChatMarkers.Module.IDENTITY).marker_received.connect( (stream, jid, marker, id, message_stanza) => { on_chat_marker_received.begin(account, jid, marker, id, message_stanza); }); stream_interactor.module_manager.get_module(account, Xep.MessageDeliveryReceipts.Module.IDENTITY).receipt_received.connect((stream, jid, id, stanza) => { on_receipt_received(account, jid, id, stanza); }); stream_interactor.module_manager.get_module(account, Xep.ChatStateNotifications.Module.IDENTITY).chat_state_received.connect((stream, jid, state, stanza) => { on_chat_state_received.begin(account, jid, state, stanza); }); } private void clear_chat_state(Conversation conversation, Jid jid) { if (!(typing_since.has_key(conversation) && typing_since[conversation].has_key(jid))) return; typing_since[conversation].unset(jid); received_state(conversation, Xmpp.Xep.ChatStateNotifications.STATE_ACTIVE); } private void clear_all_chat_states(Account account) { foreach (Conversation conversation in typing_since.keys) { if (conversation.account.equals(account)) { received_state(conversation, Xmpp.Xep.ChatStateNotifications.STATE_ACTIVE); typing_since[conversation].clear(); } } } private async void on_chat_state_received(Account account, Jid jid, string state, MessageStanza stanza) { // Don't show our own (other devices) typing notification if (jid.equals_bare(account.bare_jid)) return; Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).approx_conversation_for_stanza(stanza.from, stanza.to, account, stanza.type_); if (conversation == null) return; // Don't show our own typing notification in MUCs if (conversation.type_ == Conversation.Type.GROUPCHAT) { Jid? own_muc_jid = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(jid.bare_jid, account); if (own_muc_jid != null && own_muc_jid.equals(jid)) { return; } } if (!typing_since.has_key(conversation)) { typing_since[conversation] = new HashMap(Jid.hash_func, Jid.equals_func); } if (state == Xmpp.Xep.ChatStateNotifications.STATE_COMPOSING) { typing_since[conversation][jid] = new DateTime.now_utc(); received_state(conversation, state); } else { clear_chat_state(conversation, jid); } } private async void on_chat_marker_received(Account account, Jid jid, string marker, string stanza_id, MessageStanza message_stanza) { Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).approx_conversation_for_stanza(message_stanza.from, message_stanza.to, account, message_stanza.type_); if (conversation == null) return; handle_chat_marker(conversation, jid, marker, stanza_id); } private void handle_chat_marker(Conversation conversation, Jid jid, string marker, string stanza_id) { // Check if the marker comes from ourselves (own jid or our jid in a MUC) bool own_marker = false; if (conversation.type_ == Conversation.Type.CHAT) { own_marker = conversation.account.bare_jid.to_string() == jid.bare_jid.to_string(); } else { Jid? own_muc_jid = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(jid.bare_jid, conversation.account); if (own_muc_jid != null && own_muc_jid.equals(jid)) { own_marker = true; } } if (own_marker) { // If we received a display marker from ourselves (other device), set the conversation read up to that message. if (marker != Xep.ChatMarkers.MARKER_DISPLAYED && marker != Xep.ChatMarkers.MARKER_ACKNOWLEDGED) return; Entities.Message? message = null; if (conversation.type_ == Conversation.Type.GROUPCHAT || conversation.type_ == Conversation.Type.GROUPCHAT_PM) { message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_server_id(stanza_id, conversation); // Outdated clients might use the message id. Or in MUCs that don't send server ids. if (message == null) { message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_stanza_id(stanza_id, conversation); } } else { message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_stanza_id(stanza_id, conversation); } if (message == null) return; // Don't move read marker backwards because we get old info from another client if (conversation.read_up_to != null && conversation.read_up_to.local_time.compare(message.local_time) > 0) return; conversation.read_up_to = message; // TODO: This only marks messages as read, not http file transfers. ContentItem? content_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_item(conversation, 1, message.id); if (content_item == null) return; ContentItem? read_up_to_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_item_by_id(conversation, conversation.read_up_to_item); if (read_up_to_item != null && read_up_to_item.compare(content_item) > 0) return; conversation.read_up_to_item = content_item.id; } else { // We can't currently handle chat markers in MUCs if (conversation.type_ == Conversation.Type.GROUPCHAT) return; Entities.Message? message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_stanza_id(stanza_id, conversation); if (message != null) { switch (marker) { case Xep.ChatMarkers.MARKER_RECEIVED: // If we got a received marker, mark the respective message received. received_message_received(conversation.account, jid, message); message.marked = Entities.Message.Marked.RECEIVED; break; case Xep.ChatMarkers.MARKER_DISPLAYED: // If we got a display marker, set all messages up to that message as read (if we know they've been received). received_message_displayed(conversation.account, jid, message); Gee.List messages = stream_interactor.get_module(MessageStorage.IDENTITY).get_messages(conversation); foreach (Entities.Message m in messages) { if (m.equals(message)) break; if (m.marked == Entities.Message.Marked.RECEIVED) m.marked = Entities.Message.Marked.READ; } message.marked = Entities.Message.Marked.READ; break; } } else { // We might get a marker before the actual message (on catchup). Save the marker. if (marker_wo_message.has_key(stanza_id) && marker_wo_message[stanza_id] == Xep.ChatMarkers.MARKER_DISPLAYED && marker == Xep.ChatMarkers.MARKER_RECEIVED) { return; } marker_wo_message[stanza_id] = marker; } } } private void check_if_got_marker(Entities.Message message, Conversation conversation) { if (marker_wo_message.has_key(message.stanza_id)) { handle_chat_marker(conversation, conversation.counterpart, marker_wo_message[message.stanza_id], message.stanza_id); marker_wo_message.unset(message.stanza_id); } } private void on_receipt_received(Account account, Jid jid, string id, MessageStanza stanza) { Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).approx_conversation_for_stanza(stanza.from, stanza.to, account, stanza.type_); if (conversation == null) return; handle_chat_marker(conversation, jid,Xep.ChatMarkers.MARKER_RECEIVED, id); } } } dino-0.3.0/libdino/src/service/database.vala0000644000000000000000000007275014202022370017424 0ustar rootrootusing Gee; using Qlite; using Xmpp; using Dino.Entities; namespace Dino { public class Database : Qlite.Database { private const int VERSION = 22; public class AccountTable : Table { public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; public Column bare_jid = new Column.Text("bare_jid") { unique = true, not_null = true }; public Column resourcepart = new Column.Text("resourcepart"); public Column password = new Column.Text("password"); public Column alias = new Column.Text("alias"); public Column enabled = new Column.BoolInt("enabled"); public Column roster_version = new Column.Text("roster_version") { min_version=2 }; public Column mam_earliest_synced = new Column.Long("mam_earliest_synced") { min_version=4 }; internal AccountTable(Database db) { base(db, "account"); init({id, bare_jid, resourcepart, password, alias, enabled, roster_version, mam_earliest_synced}); } } public class JidTable : Table { public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; public Column bare_jid = new Column.Text("bare_jid") { unique = true, not_null = true }; internal JidTable(Database db) { base(db, "jid"); init({id, bare_jid}); } } public class EntityTable : Table { public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; public Column account_id = new Column.Integer("account_id"); public Column jid_id = new Column.Integer("jid_id"); public Column resource = new Column.Text("resource"); public Column caps_hash = new Column.Text("caps_hash"); public Column last_seen = new Column.Long("last_seen"); internal EntityTable(Database db) { base(db, "entity"); init({id, account_id, jid_id, resource, caps_hash, last_seen}); unique({account_id, jid_id, resource}, "IGNORE"); } } public class ContentItemTable : Table { public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; public Column conversation_id = new Column.Integer("conversation_id") { not_null = true }; public Column time = new Column.Long("time") { not_null = true }; public Column local_time = new Column.Long("local_time") { not_null = true }; public Column content_type = new Column.Integer("content_type") { not_null = true }; public Column foreign_id = new Column.Integer("foreign_id") { not_null = true }; public Column hide = new Column.BoolInt("hide") { default = "0", not_null = true, min_version = 9 }; internal ContentItemTable(Database db) { base(db, "content_item"); init({id, conversation_id, time, local_time, content_type, foreign_id, hide}); index("contentitem_conversation_hide_time_idx", {conversation_id, hide, time}); unique({content_type, foreign_id}, "IGNORE"); } } public class MessageTable : Table { public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; public Column stanza_id = new Column.Text("stanza_id"); public Column server_id = new Column.Text("server_id") { min_version=10 }; public Column account_id = new Column.Integer("account_id") { not_null = true }; public Column counterpart_id = new Column.Integer("counterpart_id") { not_null = true }; public Column counterpart_resource = new Column.Text("counterpart_resource"); public Column our_resource = new Column.Text("our_resource"); public Column direction = new Column.BoolInt("direction") { not_null = true }; public Column type_ = new Column.Integer("type"); public Column time = new Column.Long("time"); public Column local_time = new Column.Long("local_time"); public Column body = new Column.Text("body"); public Column encryption = new Column.Integer("encryption"); public Column marked = new Column.Integer("marked"); internal MessageTable(Database db) { base(db, "message"); init({id, stanza_id, server_id, account_id, counterpart_id, our_resource, counterpart_resource, direction, type_, time, local_time, body, encryption, marked}); // get latest messages index("message_account_counterpart_time_idx", {account_id, counterpart_id, time}); // deduplication index("message_account_counterpart_stanzaid_idx", {account_id, counterpart_id, stanza_id}); fts({body}); } } public class MessageCorrectionTable : Table { public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; public Column message_id = new Column.Integer("message_id") { unique=true }; public Column to_stanza_id = new Column.Text("to_stanza_id"); internal MessageCorrectionTable(Database db) { base(db, "message_correction"); init({id, message_id, to_stanza_id}); index("message_correction_to_stanza_id_idx", {to_stanza_id}); } } public class RealJidTable : Table { public Column message_id = new Column.Integer("message_id") { primary_key = true }; public Column real_jid = new Column.Text("real_jid"); internal RealJidTable(Database db) { base(db, "real_jid"); init({message_id, real_jid}); } } public class UndecryptedTable : Table { public Column message_id = new Column.Integer("message_id"); public Column type_ = new Column.Integer("type"); public Column data = new Column.Text("data"); internal UndecryptedTable(Database db) { base(db, "undecrypted"); init({message_id, type_, data}); } } public class FileTransferTable : Table { public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; public Column account_id = new Column.Integer("account_id") { not_null = true }; public Column counterpart_id = new Column.Integer("counterpart_id") { not_null = true }; public Column counterpart_resource = new Column.Text("counterpart_resource"); public Column our_resource = new Column.Text("our_resource"); public Column direction = new Column.BoolInt("direction") { not_null = true }; public Column time = new Column.Long("time"); public Column local_time = new Column.Long("local_time"); public Column encryption = new Column.Integer("encryption"); public Column file_name = new Column.Text("file_name"); public Column path = new Column.Text("path"); public Column mime_type = new Column.Text("mime_type"); public Column size = new Column.Integer("size"); public Column state = new Column.Integer("state"); public Column provider = new Column.Integer("provider"); public Column info = new Column.Text("info"); internal FileTransferTable(Database db) { base(db, "file_transfer"); init({id, account_id, counterpart_id, counterpart_resource, our_resource, direction, time, local_time, encryption, file_name, path, mime_type, size, state, provider, info}); } } public class CallTable : Table { public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; public Column account_id = new Column.Integer("account_id") { not_null = true }; public Column counterpart_id = new Column.Integer("counterpart_id") { not_null = true }; public Column counterpart_resource = new Column.Text("counterpart_resource"); public Column our_resource = new Column.Text("our_resource"); public Column direction = new Column.BoolInt("direction") { not_null = true }; public Column time = new Column.Long("time") { not_null = true }; public Column local_time = new Column.Long("local_time") { not_null = true }; public Column end_time = new Column.Long("end_time"); public Column encryption = new Column.Integer("encryption") { min_version=21 }; public Column state = new Column.Integer("state"); internal CallTable(Database db) { base(db, "call"); init({id, account_id, counterpart_id, counterpart_resource, our_resource, direction, time, local_time, end_time, encryption, state}); } } public class CallCounterpartTable : Table { public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; public Column call_id = new Column.Integer("call_id") { not_null = true }; public Column jid_id = new Column.Integer("jid_id") { not_null = true }; public Column resource = new Column.Text("resource"); internal CallCounterpartTable(Database db) { base(db, "call_counterpart"); init({call_id, jid_id, resource}); index("call_counterpart_call_jid_idx", {call_id}); } } public class ConversationTable : Table { public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; public Column account_id = new Column.Integer("account_id") { not_null = true }; public Column jid_id = new Column.Integer("jid_id") { not_null = true }; public Column resource = new Column.Text("resource") { min_version=1 }; public Column active = new Column.BoolInt("active"); public Column last_active = new Column.Long("last_active"); public Column type_ = new Column.Integer("type"); public Column encryption = new Column.Integer("encryption"); public Column read_up_to = new Column.Integer("read_up_to"); public Column read_up_to_item = new Column.Integer("read_up_to_item") { not_null=true, default="-1", min_version=15 }; public Column notification = new Column.Integer("notification") { min_version=3 }; public Column send_typing = new Column.Integer("send_typing") { min_version=3 }; public Column send_marker = new Column.Integer("send_marker") { min_version=3 }; internal ConversationTable(Database db) { base(db, "conversation"); init({id, account_id, jid_id, resource, active, last_active, type_, encryption, read_up_to, read_up_to_item, notification, send_typing, send_marker}); } } public class AvatarTable : Table { public Column jid_id = new Column.Integer("jid_id"); public Column account_id = new Column.Integer("account_id"); public Column hash = new Column.Text("hash"); public Column type_ = new Column.Integer("type"); internal AvatarTable(Database db) { base(db, "contact_avatar"); init({jid_id, account_id, hash, type_}); unique({jid_id, account_id, type_}, "REPLACE"); } } public class EntityIdentityTable : Table { public Column entity = new Column.Text("entity"); public Column category = new Column.Text("category"); public Column type = new Column.Text("type"); public Column entity_name = new Column.Text("name"); internal EntityIdentityTable(Database db) { base(db, "entity_identity"); init({entity, category, entity_name, type}); unique({entity, category, type}, "IGNORE"); index("entity_identity_idx", {entity}); } } public class EntityFeatureTable : Table { public Column entity = new Column.Text("entity"); public Column feature = new Column.Text("feature"); internal EntityFeatureTable(Database db) { base(db, "entity_feature"); init({entity, feature}); unique({entity, feature}, "IGNORE"); index("entity_feature_idx", {entity}); } } public class RosterTable : Table { public Column account_id = new Column.Integer("account_id"); public Column jid = new Column.Text("jid"); public Column handle = new Column.Text("name"); public Column subscription = new Column.Text("subscription"); internal RosterTable(Database db) { base(db, "roster"); init({account_id, jid, handle, subscription}); unique({account_id, jid}, "IGNORE"); } } public class MamCatchupTable : Table { public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; public Column account_id = new Column.Integer("account_id") { not_null = true }; public Column from_end = new Column.BoolInt("from_end"); public Column from_id = new Column.Text("from_id"); public Column from_time = new Column.Long("from_time") { not_null = true }; public Column to_id = new Column.Text("to_id"); public Column to_time = new Column.Long("to_time") { not_null = true }; internal MamCatchupTable(Database db) { base(db, "mam_catchup"); init({id, account_id, from_end, from_id, from_time, to_id, to_time}); } } public class SettingsTable : Table { public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; public Column key = new Column.Text("key") { unique = true, not_null = true }; public Column value = new Column.Text("value"); internal SettingsTable(Database db) { base(db, "settings"); init({id, key, value}); } } public class ConversationSettingsTable : Table { public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; public Column conversation_id = new Column.Integer("conversation_id") {not_null=true}; public Column key = new Column.Text("key") { not_null=true }; public Column value = new Column.Text("value"); internal ConversationSettingsTable(Database db) { base(db, "conversation_settings"); init({id, conversation_id, key, value}); index("settings_conversationid_key", { conversation_id, key }, true); } } public AccountTable account { get; private set; } public JidTable jid { get; private set; } public EntityTable entity { get; private set; } public ContentItemTable content_item { get; private set; } public MessageTable message { get; private set; } public MessageCorrectionTable message_correction { get; private set; } public RealJidTable real_jid { get; private set; } public FileTransferTable file_transfer { get; private set; } public CallTable call { get; private set; } public CallCounterpartTable call_counterpart { get; private set; } public ConversationTable conversation { get; private set; } public AvatarTable avatar { get; private set; } public EntityIdentityTable entity_identity { get; private set; } public EntityFeatureTable entity_feature { get; private set; } public RosterTable roster { get; private set; } public MamCatchupTable mam_catchup { get; private set; } public SettingsTable settings { get; private set; } public ConversationSettingsTable conversation_settings { get; private set; } public Map jid_table_cache = new HashMap(); public Map jid_table_reverse = new HashMap(Jid.hash_func, Jid.equals_func); public Map account_table_cache = new HashMap(); public Database(string fileName) { base(fileName, VERSION); account = new AccountTable(this); jid = new JidTable(this); entity = new EntityTable(this); content_item = new ContentItemTable(this); message = new MessageTable(this); message_correction = new MessageCorrectionTable(this); real_jid = new RealJidTable(this); file_transfer = new FileTransferTable(this); call = new CallTable(this); call_counterpart = new CallCounterpartTable(this); conversation = new ConversationTable(this); avatar = new AvatarTable(this); entity_identity = new EntityIdentityTable(this); entity_feature = new EntityFeatureTable(this); roster = new RosterTable(this); mam_catchup = new MamCatchupTable(this); settings = new SettingsTable(this); conversation_settings = new ConversationSettingsTable(this); init({ account, jid, entity, content_item, message, message_correction, real_jid, file_transfer, call, call_counterpart, conversation, avatar, entity_identity, entity_feature, roster, mam_catchup, settings, conversation_settings }); try { exec("PRAGMA journal_mode = WAL"); exec("PRAGMA synchronous = NORMAL"); exec("PRAGMA secure_delete = ON"); } catch (Error e) { error("Failed to set database properties: %s", e.message); } } public override void migrate(long oldVersion) { // new table columns are added, outdated columns are still present if (oldVersion < 7) { message.fts_rebuild(); } if (oldVersion < 8) { try { exec(""" insert into content_item (conversation_id, time, local_time, content_type, foreign_id, hide) select conversation.id, message.time, message.local_time, 1, message.id, 0 from message join conversation on message.account_id=conversation.account_id and message.counterpart_id=conversation.jid_id and message.type=conversation.type+1 and (message.counterpart_resource=conversation.resource or message.type != 3) where message.body not in (select info from file_transfer where info not null) and message.id not in (select info from file_transfer where info not null) union select conversation.id, message.time, message.local_time, 2, file_transfer.id, 0 from file_transfer join message on file_transfer.info=message.id join conversation on file_transfer.account_id=conversation.account_id and file_transfer.counterpart_id=conversation.jid_id and message.type=conversation.type+1 and (message.counterpart_resource=conversation.resource or message.type != 3)"""); } catch (Error e) { error("Failed to upgrade to database version 8: %s", e.message); } } if (oldVersion < 9) { try { exec(""" insert into content_item (conversation_id, time, local_time, content_type, foreign_id, hide) select conversation.id, message.time, message.local_time, 1, message.id, 1 from message join conversation on message.account_id=conversation.account_id and message.counterpart_id=conversation.jid_id and message.type=conversation.type+1 and (message.counterpart_resource=conversation.resource or message.type != 3) where message.body in (select info from file_transfer where info not null) or message.id in (select info from file_transfer where info not null)"""); } catch (Error e) { error("Failed to upgrade to database version 9: %s", e.message); } } if (oldVersion < 11) { try { exec(""" insert into mam_catchup (account_id, from_end, from_time, to_time) select id, 1, 0, mam_earliest_synced from account where mam_earliest_synced not null and mam_earliest_synced > 0"""); } catch (Error e) { error("Failed to upgrade to database version 11: %s", e.message); } } if (oldVersion < 12) { try { exec("delete from avatar"); } catch (Error e) { error("Failed to upgrade to database version 12: %s", e.message); } } if (oldVersion < 15) { // Initialize `conversation.read_up_to_item` with the content item id corresponding to the `read_up_to` message. try { exec(" update conversation set read_up_to_item=ifnull(( select content_item.id from content_item where content_item.foreign_id=conversation.read_up_to and content_type=1) , -1);"); } catch (Error e) { error("Failed to upgrade to database version 15: %s", e.message); } } if (oldVersion < 16) { try { exec("DROP TABLE contact_avatar"); avatar.create_table_at_version(VERSION); } catch (Error e) { error("Failed to upgrade to database version 16: %s", e.message); } } if (oldVersion < 17) { try { exec("DROP INDEX IF EXISTS contentitem_localtime_counterpart_idx"); exec("CREATE INDEX IF NOT EXISTS contentitem_conversation_hide_localtime_time_idx ON content_item (conversation_id, hide, local_time, time)"); } catch (Error e) { error("Failed to upgrade to database version 17: %s", e.message); } } if (oldVersion < 18) { try { exec("DROP INDEX IF EXISTS contentitem_conversation_hide_localtime_time_idx"); exec("CREATE INDEX IF NOT EXISTS contentitem_conversation_hide_time_idx ON content_item (conversation_id, hide, time)"); exec("DROP INDEX IF EXISTS message_account_counterpart_localtime_idx"); exec("CREATE INDEX IF NOT EXISTS message_account_counterpart_time_idx ON message (account_id, counterpart_id, time)"); exec("DROP INDEX IF EXISTS filetransfer_localtime_counterpart_idx"); } catch (Error e) { error("Failed to upgrade to database version 18: %s", e.message); } } if (oldVersion < 22) { try { exec("INSERT INTO call_counterpart (call_id, jid_id, resource) SELECT id, counterpart_id, counterpart_resource FROM call"); } catch (Error e) { error("Failed to upgrade to database version 22: %s", e.message); } // exec("ALTER TABLE call RENAME TO call2"); // call.create_table_at_version(VERSION); // exec("INSERT INTO call (id, account_id, our_resource, direction, time, local_time, end_time, encryption, state) // SELECT id, account_id, our_resource, direction, time, local_time, end_time, encryption, state // FROM call2"); // exec("DROP TABLE call2"); } } public ArrayList get_accounts() { ArrayList ret = new ArrayList(Account.equals_func); foreach(Row row in account.select()) { try { Account account = new Account.from_row(this, row); ret.add(account); account_table_cache[account.id] = account; } catch (InvalidJidError e) { warning("Ignoring account with invalid Jid: %s", e.message); } } return ret; } public Account? get_account_by_id(int id) { if (account_table_cache.has_key(id)) { return account_table_cache[id]; } else { Row? row = account.row_with(account.id, id).inner; if (row != null) { try { Account a = new Account.from_row(this, row); account_table_cache[a.id] = a; return a; } catch (InvalidJidError e) { warning("Ignoring account with invalid Jid: %s", e.message); } } return null; } } public int add_content_item(Conversation conversation, DateTime time, DateTime local_time, int content_type, int foreign_id, bool hide) { return (int) content_item.insert() .value(content_item.conversation_id, conversation.id) .value(content_item.local_time, (long) local_time.to_unix()) .value(content_item.time, (long) time.to_unix()) .value(content_item.content_type, content_type) .value(content_item.foreign_id, foreign_id) .value(content_item.hide, hide) .perform(); } public Gee.List get_messages(Jid jid, Account account, Message.Type? type, int count, DateTime? before, DateTime? after, int id) { QueryBuilder select = message.select(); if (before != null) { if (id > 0) { select.where(@"time < ? OR (time = ? AND message.id < ?)", { before.to_unix().to_string(), before.to_unix().to_string(), id.to_string() }); } else { select.with(message.id, "<", id); } } if (after != null) { if (id > 0) { select.where(@"time > ? OR (time = ? AND message.id > ?)", { after.to_unix().to_string(), after.to_unix().to_string(), id.to_string() }); } else { select.with(message.time, ">", (long) after.to_unix()); } if (id > 0) { select.with(message.id, ">", id); } } else { select.order_by(message.time, "DESC"); } select.with(message.counterpart_id, "=", get_jid_id(jid)) .with(message.account_id, "=", account.id) .limit(count); if (jid.resourcepart != null) { select.with(message.counterpart_resource, "=", jid.resourcepart); } if (type != null) { select.with(message.type_, "=", (int) type); } select.outer_join_with(real_jid, real_jid.message_id, message.id); select.outer_join_with(message_correction, message_correction.message_id, message.id); LinkedList ret = new LinkedList(); foreach (Row row in select) { try { ret.insert(0, new Message.from_row(this, row)); } catch (InvalidJidError e) { warning("Ignoring message with invalid Jid: %s", e.message); } } return ret; } public Message? get_message_by_id(int id) { Row? row = message.row_with(message.id, id).inner; if (row != null) { try { return new Message.from_row(this, row); } catch (InvalidJidError e) { warning("Ignoring message with invalid Jid: %s", e.message); } } return null; } public ArrayList get_conversations(Account account) { ArrayList ret = new ArrayList(); foreach (Row row in conversation.select().with(conversation.account_id, "=", account.id)) { try { ret.add(new Conversation.from_row(this, row)); } catch (InvalidJidError e) { warning("Ignoring conversation with invalid Jid: %s", e.message); } } return ret; } public int get_jid_id(Jid jid_obj) { var bare_jid = jid_obj.bare_jid; if (jid_table_reverse.has_key(bare_jid)) { return jid_table_reverse[bare_jid]; } else { Row? row = jid.row_with(jid.bare_jid, jid_obj.bare_jid.to_string()).inner; if (row != null) { int id = row[jid.id]; jid_table_cache[id] = bare_jid; jid_table_reverse[bare_jid] = id; return id; } else { return add_jid(jid_obj); } } } public Jid? get_jid_by_id(int id) throws InvalidJidError { if (jid_table_cache.has_key(id)) { return jid_table_cache[id]; } else { string? bare_jid = jid.select({jid.bare_jid}).with(jid.id, "=", id)[jid.bare_jid]; if (bare_jid != null) { Jid jid_parsed = new Jid(bare_jid); jid_table_cache[id] = jid_parsed; // Only store fully normalized Jids for reverse lookup if (jid_parsed.to_string() == bare_jid) { jid_table_reverse[jid_parsed] = id; } return jid_parsed; } return null; } } private int add_jid(Jid jid_obj) { Jid bare_jid = jid_obj.bare_jid; int id = (int) jid.insert().value(jid.bare_jid, bare_jid.to_string()).perform(); jid_table_cache[id] = bare_jid; jid_table_reverse[bare_jid] = id; return id; } } } dino-0.3.0/libdino/src/service/entity_capabilities_storage.vala0000644000000000000000000000475414202022370023430 0ustar rootrootusing Gee; using Qlite; using Xmpp; using Xmpp.Xep.ServiceDiscovery; namespace Dino { public class EntityCapabilitiesStorage : Xep.EntityCapabilities.Storage, Object { private Database db; private HashMap> features_cache = new HashMap>(); private HashMap identity_cache = new HashMap(); public EntityCapabilitiesStorage(Database db) { this.db = db; } public void store_features(string entity, Gee.List features) { if (features_cache.has_key(entity)) return; foreach (string feature in features) { db.entity_feature.insert() .value(db.entity_feature.entity, entity) .value(db.entity_feature.feature, feature) .perform(); } } public void store_identities(string entity, Gee.Set identities) { foreach (Identity identity in identities) { if (identity.category == Identity.CATEGORY_CLIENT) { db.entity_identity.insert() .value(db.entity_identity.entity, entity) .value(db.entity_identity.category, identity.category) .value(db.entity_identity.type, identity.type_) .value(db.entity_identity.entity_name, identity.name) .perform(); return; } } } public Gee.List get_features(string entity) { Gee.List? features = features_cache[entity]; if (features != null) { return features; } features = new ArrayList(); foreach (Row row in db.entity_feature.select({db.entity_feature.feature}).with(db.entity_feature.entity, "=", entity)) { features.add(row[db.entity_feature.feature]); } features_cache[entity] = features; return features; } public Identity? get_identities(string entity) { Identity? identity = identity_cache[entity]; if (identity != null) { return identity; } RowOption row = db.entity_identity.select().with(db.entity_identity.entity, "=", entity).single().row(); if (row.is_present()) { identity = new Identity(row[db.entity_identity.category], row[db.entity_identity.type], row[db.entity_identity.entity_name]); } identity_cache[entity] = identity; return identity; } } } dino-0.3.0/libdino/src/service/entity_info.vala0000644000000000000000000002167614202022370020210 0ustar rootrootusing Gee; using Dino.Entities; using Qlite; using Xmpp; using Xmpp.Xep; using Xmpp.Xep.ServiceDiscovery; namespace Dino { public class EntityInfo : StreamInteractionModule, Object { public static ModuleIdentity IDENTITY = new ModuleIdentity("entity_info"); public string id { get { return IDENTITY.id; } } private StreamInteractor stream_interactor; private Database db; private EntityCapabilitiesStorage entity_capabilities_storage; private HashMap entity_caps_hashes = new HashMap(Jid.hash_func, Jid.equals_func); private HashMap> entity_features = new HashMap>(); private HashMap> jid_features = new HashMap>(Jid.hash_func, Jid.equals_func); private HashMap> entity_identity = new HashMap>(); private HashMap> jid_identity = new HashMap>(Jid.hash_func, Jid.equals_func); public static void start(StreamInteractor stream_interactor, Database db) { EntityInfo m = new EntityInfo(stream_interactor, db); stream_interactor.add_module(m); } private EntityInfo(StreamInteractor stream_interactor, Database db) { this.stream_interactor = stream_interactor; this.db = db; this.entity_capabilities_storage = new EntityCapabilitiesStorage(db); stream_interactor.account_added.connect(on_account_added); stream_interactor.connection_manager.stream_opened.connect((account, stream) => { stream.received_features_node.connect(() => { string? hash = EntityCapabilities.get_server_caps_hash(stream); if (hash != null) { entity_caps_hashes[account.bare_jid.domain_jid] = hash; } }); }); stream_interactor.module_manager.initialize_account_modules.connect(initialize_modules); remove_old_entities(); Timeout.add_seconds(60 * 60, () => { remove_old_entities(); return true; }); } public async Gee.Set? get_identities(Account account, Jid jid) { if (jid_identity.has_key(jid)) { return jid_identity[jid]; } string? hash = entity_caps_hashes[jid]; if (hash != null) { Gee.Set? identities = get_stored_identities(hash); if (identities != null) return identities; } ServiceDiscovery.InfoResult? info_result = yield get_info_result(account, jid, hash); if (info_result != null) { return info_result.identities; } return null; } public async Identity? get_identity(Account account, Jid jid) { Gee.Set? identities = yield get_identities(account, jid); if (identities == null) return null; foreach (var identity in identities) { if (identity.category == Identity.CATEGORY_CLIENT) { return identity; } } return null; } public async bool has_feature(Account account, Jid jid, string feature) { if (jid_features.has_key(jid)) { return jid_features[jid].contains(feature); } string? hash = entity_caps_hashes[jid]; if (hash != null) { Gee.List? features = get_stored_features(hash); if (features != null) { return features.contains(feature); } } ServiceDiscovery.InfoResult? info_result = yield get_info_result(account, jid, hash); if (info_result == null) return false; return info_result.features.contains(feature); } private void on_received_available_presence(Account account, Presence.Stanza presence) { bool is_gc = stream_interactor.get_module(MucManager.IDENTITY).might_be_groupchat(presence.from.bare_jid, account); if (is_gc) return; string? caps_hash = EntityCapabilities.get_caps_hash(presence); if (caps_hash == null) return; db.entity.upsert() .value(db.entity.account_id, account.id, true) .value(db.entity.jid_id, db.get_jid_id(presence.from), true) .value(db.entity.resource, presence.from.resourcepart, true) .value(db.entity.last_seen, (long)(new DateTime.now_local()).to_unix()) .value(db.entity.caps_hash, caps_hash) .perform(); if (caps_hash != null) { entity_caps_hashes[presence.from] = caps_hash; } } private void remove_old_entities() { long timestamp = (long)(new DateTime.now_local().add_days(-14)).to_unix(); db.entity.delete().with(db.entity.last_seen, "<", timestamp).perform(); } private void store_features(string entity, Gee.List features) { if (entity_features.has_key(entity)) return; foreach (string feature in features) { db.entity_feature.insert() .value(db.entity_feature.entity, entity) .value(db.entity_feature.feature, feature) .perform(); } entity_features[entity] = features; } private void store_identities(string entity, Gee.Set identities) { foreach (Identity identity in identities) { db.entity_identity.insert() .value(db.entity_identity.entity, entity) .value(db.entity_identity.category, identity.category) .value(db.entity_identity.type, identity.type_) .value(db.entity_identity.entity_name, identity.name) .perform(); } entity_identity[entity] = identities; } private Gee.List? get_stored_features(string entity) { Gee.List? features = entity_features[entity]; if (features != null) { return features; } features = new ArrayList(); foreach (Row row in db.entity_feature.select({db.entity_feature.feature}).with(db.entity_feature.entity, "=", entity)) { features.add(row[db.entity_feature.feature]); } if (features.size == 0) { return null; } entity_features[entity] = features; return features; } private Gee.Set? get_stored_identities(string entity) { Gee.Set? identities = entity_identity[entity]; if (identities != null) { return identities; } identities = new HashSet(Identity.hash_func, Identity.equals_func); var qry = db.entity_identity.select().with(db.entity_identity.entity, "=", entity); foreach (Row row in qry) { var identity = new Identity(row[db.entity_identity.category], row[db.entity_identity.type], row[db.entity_identity.entity_name]); identities.add(identity); } if (identities.size == 0) { return null; } entity_identity[entity] = identities; return identities; } private async ServiceDiscovery.InfoResult? get_info_result(Account account, Jid jid, string? hash = null) { XmppStream? stream = stream_interactor.get_stream(account); if (stream == null) return null; ServiceDiscovery.InfoResult? info_result = yield stream.get_module(ServiceDiscovery.Module.IDENTITY).request_info(stream, jid); if (info_result == null) return null; if (hash != null && EntityCapabilities.Module.compute_hash_for_info_result(info_result) == hash) { store_features(hash, info_result.features); store_identities(hash, info_result.identities); } else { jid_features[jid] = info_result.features; jid_identity[jid] = info_result.identities; } return info_result; } private void on_account_added(Account account) { var cache = new CapsCacheImpl(account, this); stream_interactor.module_manager.get_module(account, ServiceDiscovery.Module.IDENTITY).cache = cache; stream_interactor.module_manager.get_module(account, Presence.Module.IDENTITY).received_available.connect((stream, presence) => on_received_available_presence(account, presence)); } private void initialize_modules(Account account, ArrayList modules) { modules.add(new Xep.EntityCapabilities.Module(entity_capabilities_storage)); } } public class CapsCacheImpl : CapsCache, Object { private Account account; private EntityInfo entity_info; public CapsCacheImpl(Account account, EntityInfo entity_info) { this.account = account; this.entity_info = entity_info; } public async bool has_entity_feature(Jid jid, string feature) { return yield entity_info.has_feature(account, jid, feature); } public async Gee.Set get_entity_identities(Jid jid) { return yield entity_info.get_identities(account, jid); } } } dino-0.3.0/libdino/src/service/file_manager.vala0000644000000000000000000004372714202022370020273 0ustar rootrootusing Gdk; using Gee; using Xmpp; using Dino.Entities; namespace Dino { public class FileManager : StreamInteractionModule, Object { public static ModuleIdentity IDENTITY = new ModuleIdentity("file"); public string id { get { return IDENTITY.id; } } public signal void upload_available(Account account); public signal void received_file(FileTransfer file_transfer, Conversation conversation); private StreamInteractor stream_interactor; private Database db; private Gee.List file_senders = new ArrayList(); private Gee.List file_encryptors = new ArrayList(); private Gee.List file_decryptors = new ArrayList(); private Gee.List file_providers = new ArrayList(); public static void start(StreamInteractor stream_interactor, Database db) { FileManager m = new FileManager(stream_interactor, db); stream_interactor.add_module(m); } public static string get_storage_dir() { return Path.build_filename(Dino.get_storage_dir(), "files"); } private FileManager(StreamInteractor stream_interactor, Database db) { this.stream_interactor = stream_interactor; this.db = db; DirUtils.create_with_parents(get_storage_dir(), 0700); this.add_provider(new JingleFileProvider(stream_interactor)); this.add_sender(new JingleFileSender(stream_interactor)); } public async HashMap get_file_size_limits(Conversation conversation) { HashMap ret = new HashMap(); foreach (FileSender sender in file_senders) { ret[sender.get_id()] = yield sender.get_file_size_limit(conversation); } return ret; } public async void send_file(File file, Conversation conversation) { FileTransfer file_transfer = new FileTransfer(); file_transfer.account = conversation.account; file_transfer.counterpart = conversation.counterpart; if (conversation.type_.is_muc_semantic()) { file_transfer.ourpart = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account) ?? conversation.account.bare_jid; } else { file_transfer.ourpart = conversation.account.full_jid; } file_transfer.direction = FileTransfer.DIRECTION_SENT; file_transfer.time = new DateTime.now_utc(); file_transfer.local_time = new DateTime.now_utc(); file_transfer.encryption = conversation.encryption; try { FileInfo file_info = file.query_info("*", FileQueryInfoFlags.NONE); file_transfer.file_name = file_info.get_display_name(); file_transfer.mime_type = file_info.get_content_type(); file_transfer.size = (int)file_info.get_size(); file_transfer.input_stream = yield file.read_async(); yield save_file(file_transfer); stream_interactor.get_module(FileTransferStorage.IDENTITY).add_file(file_transfer); conversation.last_active = file_transfer.time; received_file(file_transfer, conversation); } catch (Error e) { file_transfer.state = FileTransfer.State.FAILED; warning("Error saving outgoing file: %s", e.message); return; } try { var file_meta = new FileMeta(); file_meta.size = file_transfer.size; file_meta.mime_type = file_transfer.mime_type; FileSender file_sender = null; FileEncryptor file_encryptor = null; foreach (FileSender sender in file_senders) { if (yield sender.can_send(conversation, file_transfer)) { if (file_transfer.encryption == Encryption.NONE || yield sender.can_encrypt(conversation, file_transfer)) { file_sender = sender; break; } else { foreach (FileEncryptor encryptor in file_encryptors) { if (encryptor.can_encrypt_file(conversation, file_transfer)) { file_encryptor = encryptor; break; } } if (file_encryptor != null) { file_sender = sender; break; } } } } if (file_sender == null) { throw new FileSendError.UPLOAD_FAILED("No sender/encryptor combination available"); } if (file_encryptor != null) { file_meta = file_encryptor.encrypt_file(conversation, file_transfer); } FileSendData file_send_data = yield file_sender.prepare_send_file(conversation, file_transfer, file_meta); if (file_encryptor != null) { file_send_data = file_encryptor.preprocess_send_file(conversation, file_transfer, file_send_data, file_meta); } yield file_sender.send_file(conversation, file_transfer, file_send_data, file_meta); } catch (Error e) { warning("Send file error: %s", e.message); file_transfer.state = FileTransfer.State.FAILED; } } public async void download_file(FileTransfer file_transfer) { Conversation conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(file_transfer.counterpart.bare_jid, file_transfer.account); FileProvider? file_provider = null; foreach (FileProvider fp in file_providers) { if (file_transfer.provider == fp.get_id()) { file_provider = fp; } } yield download_file_internal(file_provider, file_transfer, conversation); } public async bool is_upload_available(Conversation? conversation) { if (conversation == null) return false; foreach (FileSender file_sender in file_senders) { if (yield file_sender.is_upload_available(conversation)) return true; } return false; } public void add_provider(FileProvider file_provider) { file_providers.add(file_provider); file_provider.file_incoming.connect((info, from, time, local_time, conversation, receive_data, file_meta) => { handle_incoming_file.begin(file_provider, info, from, time, local_time, conversation, receive_data, file_meta); }); } public void add_sender(FileSender file_sender) { file_senders.add(file_sender); file_sender.upload_available.connect((account) => { upload_available(account); }); file_senders.sort((a, b) => { return (int) (b.get_priority() - a.get_priority()); }); } public void add_file_encryptor(FileEncryptor encryptor) { file_encryptors.add(encryptor); } public void add_file_decryptor(FileDecryptor decryptor) { file_decryptors.add(decryptor); } public bool is_sender_trustworthy(FileTransfer file_transfer, Conversation conversation) { if (file_transfer.direction == FileTransfer.DIRECTION_SENT) return true; Jid relevant_jid = conversation.counterpart; if (conversation.type_ == Conversation.Type.GROUPCHAT) { relevant_jid = stream_interactor.get_module(MucManager.IDENTITY).get_real_jid(file_transfer.from, conversation.account); } if (relevant_jid == null) return false; bool in_roster = stream_interactor.get_module(RosterManager.IDENTITY).get_roster_item(conversation.account, relevant_jid) != null; return in_roster; } private async FileMeta get_file_meta(FileProvider file_provider, FileTransfer file_transfer, Conversation conversation, FileReceiveData receive_data_) throws FileReceiveError { FileReceiveData receive_data = receive_data_; FileMeta file_meta = file_provider.get_file_meta(file_transfer); if (file_meta.size == -1) { foreach (FileDecryptor file_decryptor in file_decryptors) { if (file_decryptor.can_decrypt_file(conversation, file_transfer, receive_data)) { receive_data = file_decryptor.prepare_get_meta_info(conversation, file_transfer, receive_data); break; } } file_meta = yield file_provider.get_meta_info(file_transfer, receive_data, file_meta); file_transfer.size = (int)file_meta.size; file_transfer.file_name = file_meta.file_name; file_transfer.mime_type = file_meta.mime_type; } return file_meta; } private async void download_file_internal(FileProvider file_provider, FileTransfer file_transfer, Conversation conversation) { try { // Get meta info FileReceiveData receive_data = file_provider.get_file_receive_data(file_transfer); FileDecryptor? file_decryptor = null; foreach (FileDecryptor decryptor in file_decryptors) { if (decryptor.can_decrypt_file(conversation, file_transfer, receive_data)) { file_decryptor = decryptor; break; } } if (file_decryptor != null) { receive_data = file_decryptor.prepare_get_meta_info(conversation, file_transfer, receive_data); } FileMeta file_meta = yield get_file_meta(file_provider, file_transfer, conversation, receive_data); InputStream? input_stream = null; // Download and decrypt file file_transfer.state = FileTransfer.State.IN_PROGRESS; if (file_decryptor != null) { file_meta = file_decryptor.prepare_download_file(conversation, file_transfer, receive_data, file_meta); } input_stream = yield file_provider.download(file_transfer, receive_data, file_meta); if (file_decryptor != null) { input_stream = yield file_decryptor.decrypt_file(input_stream, conversation, file_transfer, receive_data); } // Save file string filename = Random.next_int().to_string("%x") + "_" + file_transfer.file_name; File file = File.new_for_path(Path.build_filename(get_storage_dir(), filename)); OutputStream os = file.create(FileCreateFlags.REPLACE_DESTINATION); yield os.splice_async(input_stream, OutputStreamSpliceFlags.CLOSE_SOURCE|OutputStreamSpliceFlags.CLOSE_TARGET); file_transfer.path = file.get_basename(); file_transfer.input_stream = yield file.read_async(); FileInfo file_info = file_transfer.get_file().query_info("*", FileQueryInfoFlags.NONE); file_transfer.mime_type = file_info.get_content_type(); file_transfer.state = FileTransfer.State.COMPLETE; } catch (Error e) { warning("Error downloading file: %s", e.message); file_transfer.state = FileTransfer.State.FAILED; } } private async void handle_incoming_file(FileProvider file_provider, string info, Jid from, DateTime time, DateTime local_time, Conversation conversation, FileReceiveData receive_data, FileMeta file_meta) { FileTransfer file_transfer = new FileTransfer(); file_transfer.account = conversation.account; file_transfer.counterpart = file_transfer.direction == FileTransfer.DIRECTION_RECEIVED ? from : conversation.counterpart; if (conversation.type_.is_muc_semantic()) { file_transfer.ourpart = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account) ?? conversation.account.bare_jid; file_transfer.direction = from.equals(file_transfer.ourpart) ? FileTransfer.DIRECTION_SENT : FileTransfer.DIRECTION_RECEIVED; } else { file_transfer.ourpart = conversation.account.full_jid; file_transfer.direction = from.equals_bare(file_transfer.ourpart) ? FileTransfer.DIRECTION_SENT : FileTransfer.DIRECTION_RECEIVED; } file_transfer.time = time; file_transfer.local_time = local_time; file_transfer.provider = file_provider.get_id(); file_transfer.file_name = file_meta.file_name; file_transfer.size = (int)file_meta.size; file_transfer.info = info; var encryption = file_provider.get_encryption(file_transfer, receive_data, file_meta); if (encryption != Encryption.NONE) file_transfer.encryption = encryption; foreach (FileDecryptor decryptor in file_decryptors) { if (decryptor.can_decrypt_file(conversation, file_transfer, receive_data)) { file_transfer.encryption = decryptor.get_encryption(); } } stream_interactor.get_module(FileTransferStorage.IDENTITY).add_file(file_transfer); if (is_sender_trustworthy(file_transfer, conversation)) { try { yield get_file_meta(file_provider, file_transfer, conversation, receive_data); if (file_transfer.size >= 0 && file_transfer.size < 5000000) { yield download_file_internal(file_provider, file_transfer, conversation); } } catch (Error e) { warning("Error downloading file: %s", e.message); file_transfer.state = FileTransfer.State.FAILED; } } conversation.last_active = file_transfer.time; received_file(file_transfer, conversation); } private async void save_file(FileTransfer file_transfer) throws FileSendError { try { string filename = Random.next_int().to_string("%x") + "_" + file_transfer.file_name; File file = File.new_for_path(Path.build_filename(get_storage_dir(), filename)); OutputStream os = file.create(FileCreateFlags.REPLACE_DESTINATION); yield os.splice_async(file_transfer.input_stream, OutputStreamSpliceFlags.CLOSE_SOURCE|OutputStreamSpliceFlags.CLOSE_TARGET); file_transfer.state = FileTransfer.State.COMPLETE; file_transfer.path = filename; file_transfer.input_stream = yield file.read_async(); } catch (Error e) { throw new FileSendError.SAVE_FAILED("Saving file error: %s".printf(e.message)); } } } public errordomain FileSendError { ENCRYPTION_FAILED, UPLOAD_FAILED, SAVE_FAILED } public errordomain FileReceiveError { GET_METADATA_FAILED, DECRYPTION_FAILED, DOWNLOAD_FAILED } public class FileMeta { public int64 size = -1; public string? mime_type = null; public string? file_name = null; public Encryption encryption = Encryption.NONE; } public class HttpFileMeta : FileMeta { public Message message; } public class FileSendData { } public class HttpFileSendData : FileSendData { public string url_down { get; set; } public string url_up { get; set; } public HashMap headers { get; set; } public bool encrypt_message { get; set; default=true; } } public class FileReceiveData { } public class HttpFileReceiveData : FileReceiveData { public string url { get; set; } } public interface FileProvider : Object { public signal void file_incoming(string info, Jid from, DateTime time, DateTime local_time, Conversation conversation, FileReceiveData receive_data, FileMeta file_meta); public abstract Encryption get_encryption(FileTransfer file_transfer, FileReceiveData receive_data, FileMeta file_meta); public abstract FileMeta get_file_meta(FileTransfer file_transfer) throws FileReceiveError; public abstract FileReceiveData? get_file_receive_data(FileTransfer file_transfer); public abstract async FileMeta get_meta_info(FileTransfer file_transfer, FileReceiveData receive_data, FileMeta file_meta) throws FileReceiveError; public abstract async InputStream download(FileTransfer file_transfer, FileReceiveData receive_data, FileMeta file_meta) throws FileReceiveError; public abstract int get_id(); } public interface FileSender : Object { public signal void upload_available(Account account); public abstract async bool is_upload_available(Conversation conversation); public abstract async long get_file_size_limit(Conversation conversation); public abstract async bool can_send(Conversation conversation, FileTransfer file_transfer); public abstract async FileSendData? prepare_send_file(Conversation conversation, FileTransfer file_transfer, FileMeta file_meta) throws FileSendError; public abstract async void send_file(Conversation conversation, FileTransfer file_transfer, FileSendData file_send_data, FileMeta file_meta) throws FileSendError; public abstract async bool can_encrypt(Conversation conversation, FileTransfer file_transfer); public abstract int get_id(); public abstract float get_priority(); } public interface FileEncryptor : Object { public abstract bool can_encrypt_file(Conversation conversation, FileTransfer file_transfer); public abstract FileMeta encrypt_file(Conversation conversation, FileTransfer file_transfer) throws FileSendError; public abstract FileSendData? preprocess_send_file(Conversation conversation, FileTransfer file_transfer, FileSendData file_send_data, FileMeta file_meta) throws FileSendError; } public interface FileDecryptor : Object { public abstract Encryption get_encryption(); public abstract FileReceiveData prepare_get_meta_info(Conversation conversation, FileTransfer file_transfer, FileReceiveData receive_data); public abstract FileMeta prepare_download_file(Conversation conversation, FileTransfer file_transfer, FileReceiveData receive_data, FileMeta file_meta); public abstract bool can_decrypt_file(Conversation conversation, FileTransfer file_transfer, FileReceiveData receive_data); public abstract async InputStream decrypt_file(InputStream encrypted_stream, Conversation conversation, FileTransfer file_transfer, FileReceiveData receive_data) throws FileReceiveError; } } dino-0.3.0/libdino/src/service/file_transfer_storage.vala0000644000000000000000000000450414202022370022217 0ustar rootrootusing Xmpp; using Gee; using Qlite; using Dino.Entities; namespace Dino { public class FileTransferStorage : StreamInteractionModule, Object { public static ModuleIdentity IDENTITY = new ModuleIdentity("file_store"); public string id { get { return IDENTITY.id; } } private StreamInteractor stream_interactor; private Database db; private WeakMap files_by_db_id = new WeakMap(); public static void start(StreamInteractor stream_interactor, Database db) { FileTransferStorage m = new FileTransferStorage(stream_interactor, db); stream_interactor.add_module(m); } private FileTransferStorage(StreamInteractor stream_interactor, Database db) { this.stream_interactor = stream_interactor; this.db = db; } public void add_file(FileTransfer file_transfer) { file_transfer.persist(db); cache_file(file_transfer); } public FileTransfer? get_file_by_id(int id, Conversation conversation) { FileTransfer? file_transfer = files_by_db_id[id]; if (file_transfer != null) { return file_transfer; } RowOption row_option = db.file_transfer.select().with(db.file_transfer.id, "=", id).row(); return create_file_from_row_opt(row_option, conversation); } private FileTransfer? create_file_from_row_opt(RowOption row_opt, Conversation conversation) { if (!row_opt.is_present()) return null; try { FileTransfer file_transfer = new FileTransfer.from_row(db, row_opt.inner, FileManager.get_storage_dir()); if (conversation.type_.is_muc_semantic()) { file_transfer.ourpart = conversation.counterpart.with_resource(file_transfer.ourpart.resourcepart); } cache_file(file_transfer); return file_transfer; } catch (InvalidJidError e) { warning("Got file transfer with invalid Jid: %s", e.message); } return null; } private void cache_file(FileTransfer file_transfer) { files_by_db_id[file_transfer.id] = file_transfer; } } }dino-0.3.0/libdino/src/service/jingle_file_transfers.vala0000644000000000000000000002436214202022370022212 0ustar rootrootusing Gdk; using Gee; using Xmpp; using Dino.Entities; namespace Dino { public interface JingleFileEncryptionHelper : Object { public abstract bool can_transfer(Conversation conversation); public abstract async bool can_encrypt(Conversation conversation, FileTransfer file_transfer, Jid? full_jid = null); public abstract string? get_precondition_name(Conversation conversation, FileTransfer file_transfer); public abstract Object? get_precondition_options(Conversation conversation, FileTransfer file_transfer); public abstract Encryption get_encryption(Xmpp.Xep.JingleFileTransfer.FileTransfer jingle_transfer); } public class JingleFileEncryptionHelperTransferOnly : JingleFileEncryptionHelper, Object { public bool can_transfer(Conversation conversation) { return true; } public async bool can_encrypt(Conversation conversation, FileTransfer file_transfer, Jid? full_jid) { return false; } public string? get_precondition_name(Conversation conversation, FileTransfer file_transfer) { return null; } public Object? get_precondition_options(Conversation conversation, FileTransfer file_transfer) { return null; } public Encryption get_encryption(Xmpp.Xep.JingleFileTransfer.FileTransfer jingle_transfer) { return Encryption.NONE; } } public class JingleFileHelperRegistry { private static JingleFileHelperRegistry INSTANCE; public static JingleFileHelperRegistry instance { get { if (INSTANCE == null) { INSTANCE = new JingleFileHelperRegistry(); INSTANCE.add_encryption_helper(Encryption.NONE, new JingleFileEncryptionHelperTransferOnly()); } return INSTANCE; } } internal HashMap encryption_helpers = new HashMap(); public void add_encryption_helper(Encryption encryption, JingleFileEncryptionHelper helper) { encryption_helpers[encryption] = helper; } public JingleFileEncryptionHelper? get_encryption_helper(Encryption encryption) { if (encryption_helpers.has_key(encryption)) { return encryption_helpers[encryption]; } return null; } } public class JingleFileProvider : FileProvider, Object { private StreamInteractor stream_interactor; private HashMap file_transfers = new HashMap(); public JingleFileProvider(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; stream_interactor.stream_negotiated.connect(on_stream_negotiated); } public FileMeta get_file_meta(FileTransfer file_transfer) throws FileReceiveError { var file_meta = new FileMeta(); file_meta.file_name = file_transfer.file_name; file_meta.size = file_transfer.size; return file_meta; } public FileReceiveData? get_file_receive_data(FileTransfer file_transfer) { return new FileReceiveData(); } public async FileMeta get_meta_info(FileTransfer file_transfer, FileReceiveData receive_data, FileMeta file_meta) throws FileReceiveError { return file_meta; } public Encryption get_encryption(FileTransfer file_transfer, FileReceiveData receive_data, FileMeta file_meta) { Xmpp.Xep.JingleFileTransfer.FileTransfer? jingle_file_transfer = file_transfers[file_transfer.info]; if (jingle_file_transfer == null) { warning("Could not determine jingle encryption - transfer data not available anymore"); return Encryption.NONE; } foreach (JingleFileEncryptionHelper helper in JingleFileHelperRegistry.instance.encryption_helpers.values) { var encryption = helper.get_encryption(jingle_file_transfer); if (encryption != Encryption.NONE) return encryption; } return Encryption.NONE; } public async InputStream download(FileTransfer file_transfer, FileReceiveData receive_data, FileMeta file_meta) throws FileReceiveError { // TODO(hrxi) What should happen if `stream == null`? XmppStream? stream = stream_interactor.get_stream(file_transfer.account); Xmpp.Xep.JingleFileTransfer.FileTransfer? jingle_file_transfer = file_transfers[file_transfer.info]; if (jingle_file_transfer == null) { throw new FileReceiveError.DOWNLOAD_FAILED("Transfer data not available anymore"); } try { yield jingle_file_transfer.accept(stream); } catch (IOError e) { throw new FileReceiveError.DOWNLOAD_FAILED("Establishing connection did not work"); } return jingle_file_transfer.stream; } public int get_id() { return 1; } private void on_stream_negotiated(Account account, XmppStream stream) { stream_interactor.module_manager.get_module(account, Xmpp.Xep.JingleFileTransfer.Module.IDENTITY).file_incoming.connect((stream, jingle_file_transfer) => { Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(jingle_file_transfer.peer.bare_jid, account); if (conversation == null) { // TODO(hrxi): What to do? return; } string id = random_uuid(); file_transfers[id] = jingle_file_transfer; FileMeta file_meta = new FileMeta(); file_meta.size = jingle_file_transfer.size; file_meta.file_name = jingle_file_transfer.file_name; var time = new DateTime.now_utc(); var from = jingle_file_transfer.peer.bare_jid; file_incoming(id, from, time, time, conversation, new FileReceiveData(), file_meta); }); } } public class JingleFileSender : FileSender, Object { private StreamInteractor stream_interactor; public JingleFileSender(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; } public async bool is_upload_available(Conversation conversation) { if (conversation.type_ != Conversation.Type.CHAT) return false; JingleFileEncryptionHelper? helper = JingleFileHelperRegistry.instance.get_encryption_helper(conversation.encryption); if (helper == null) return false; if (!helper.can_transfer(conversation)) return false; XmppStream? stream = stream_interactor.get_stream(conversation.account); if (stream == null) return false; Gee.List? resources = stream.get_flag(Presence.Flag.IDENTITY).get_resources(conversation.counterpart); if (resources == null) return false; foreach (Jid full_jid in resources) { if (yield stream.get_module(Xep.JingleFileTransfer.Module.IDENTITY).is_available(stream, full_jid)) { return true; } } return false; } public async long get_file_size_limit(Conversation conversation) { if (yield can_send_conv(conversation)) { return int.MAX; } return -1; } public async bool can_send(Conversation conversation, FileTransfer file_transfer) { return yield can_send_conv(conversation); } private async bool can_send_conv(Conversation conversation) { if (conversation.type_ != Conversation.Type.CHAT) return false; // No file specific restrictions apply to Jingle file transfers return yield is_upload_available(conversation); } public async bool can_encrypt(Conversation conversation, FileTransfer file_transfer) { JingleFileEncryptionHelper? helper = JingleFileHelperRegistry.instance.get_encryption_helper(file_transfer.encryption); if (helper == null) return false; return yield helper.can_encrypt(conversation, file_transfer); } public async FileSendData? prepare_send_file(Conversation conversation, FileTransfer file_transfer, FileMeta file_meta) throws FileSendError { if (file_meta is HttpFileMeta) { throw new FileSendError.UPLOAD_FAILED("Cannot upload http file meta over Jingle"); } return new FileSendData(); } public async void send_file(Conversation conversation, FileTransfer file_transfer, FileSendData file_send_data, FileMeta file_meta) throws FileSendError { XmppStream? stream = stream_interactor.get_stream(file_transfer.account); if (stream == null) throw new FileSendError.UPLOAD_FAILED("No stream available"); JingleFileEncryptionHelper? helper = JingleFileHelperRegistry.instance.get_encryption_helper(file_transfer.encryption); bool must_encrypt = helper != null && yield helper.can_encrypt(conversation, file_transfer); // TODO(hrxi): Prioritization of transports (and resources?). foreach (Jid full_jid in stream.get_flag(Presence.Flag.IDENTITY).get_resources(conversation.counterpart)) { if (full_jid.equals(stream.get_flag(Bind.Flag.IDENTITY).my_jid)) { continue; } if (!yield stream.get_module(Xep.JingleFileTransfer.Module.IDENTITY).is_available(stream, full_jid)) { continue; } if (must_encrypt && !yield helper.can_encrypt(conversation, file_transfer, full_jid)) { continue; } string? precondition_name = null; Object? precondition_options = null; if (must_encrypt) { precondition_name = helper.get_precondition_name(conversation, file_transfer); precondition_options = helper.get_precondition_options(conversation, file_transfer); if (precondition_name == null) { throw new FileSendError.ENCRYPTION_FAILED("Should have created a precondition, but did not"); } } try { yield stream.get_module(Xep.JingleFileTransfer.Module.IDENTITY).offer_file_stream(stream, full_jid, file_transfer.input_stream, file_transfer.server_file_name, file_meta.size, precondition_name, precondition_options); } catch (Error e) { throw new FileSendError.UPLOAD_FAILED(@"offer_file_stream failed: $(e.message)"); } return; } } public int get_id() { return 1; } public float get_priority() { return 50; } } } dino-0.3.0/libdino/src/service/message_correction.vala0000644000000000000000000002151614202022370021525 0ustar rootrootusing Gee; using Xmpp; using Xmpp.Xep; using Dino.Entities; using Qlite; namespace Dino { public class MessageCorrection : StreamInteractionModule, MessageListener { public static ModuleIdentity IDENTITY = new ModuleIdentity("message_correction"); public string id { get { return IDENTITY.id; } } public signal void received_correction(ContentItem content_item); private StreamInteractor stream_interactor; private Database db; private HashMap> last_messages = new HashMap>(Conversation.hash_func, Conversation.equals_func); private HashMap outstanding_correction_nodes = new HashMap(); public static void start(StreamInteractor stream_interactor, Database db) { MessageCorrection m = new MessageCorrection(stream_interactor, db); stream_interactor.add_module(m); } public MessageCorrection(StreamInteractor stream_interactor, Database db) { this.stream_interactor = stream_interactor; this.db = db; stream_interactor.account_added.connect(on_account_added); stream_interactor.get_module(MessageProcessor.IDENTITY).received_pipeline.connect(this); stream_interactor.get_module(MessageProcessor.IDENTITY).build_message_stanza.connect(check_add_correction_node); stream_interactor.get_module(PresenceManager.IDENTITY).received_offline_presence.connect((jid, account) => { Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(jid.bare_jid, account, Conversation.Type.GROUPCHAT); if (conversation != null) { if (last_messages.has_key(conversation)) last_messages[conversation].unset(jid); } }); } public void send_correction(Conversation conversation, Message old_message, string correction_text) { string stanza_id = old_message.edit_to ?? old_message.stanza_id; Message out_message = stream_interactor.get_module(MessageProcessor.IDENTITY).create_out_message(correction_text, conversation); out_message.edit_to = stanza_id; outstanding_correction_nodes[out_message.stanza_id] = stanza_id; stream_interactor.get_module(MessageProcessor.IDENTITY).send_xmpp_message(out_message, conversation); db.message_correction.insert() .value(db.message_correction.message_id, out_message.id) .value(db.message_correction.to_stanza_id, stanza_id) .perform(); db.content_item.update() .with(db.content_item.foreign_id, "=", old_message.id) .with(db.content_item.content_type, "=", 1) .set(db.content_item.foreign_id, out_message.id) .perform(); on_received_correction(conversation, out_message.id); } public bool is_own_correction_allowed(Conversation conversation, Message message) { string stanza_id = message.edit_to ?? message.stanza_id; Jid? own_jid = null; if (conversation.type_ == Conversation.Type.CHAT) { own_jid = conversation.account.full_jid; } else if (conversation.type_ == Conversation.Type.GROUPCHAT) { own_jid = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account); } if (own_jid == null) return false; return last_messages.has_key(conversation) && last_messages[conversation].has_key(own_jid) && last_messages[conversation][own_jid].stanza_id == stanza_id; } private void check_add_correction_node(Entities.Message message, Xmpp.MessageStanza message_stanza, Conversation conversation) { if (outstanding_correction_nodes.has_key(message.stanza_id)) { LastMessageCorrection.set_replace_id(message_stanza, outstanding_correction_nodes[message.stanza_id]); outstanding_correction_nodes.unset(message.stanza_id); } else { if (!last_messages.has_key(conversation)) { last_messages[conversation] = new HashMap(Jid.hash_func, Jid.equals_func); } last_messages[conversation][message.from] = message; } } public string[] after_actions_const = new string[]{ "DEDUPLICATE", "DECRYPT", "FILTER_EMPTY" }; public override string action_group { get { return "CORRECTION"; } } public override string[] after_actions { get { return after_actions_const; } } public override async bool run(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) { if (conversation.type_ != Conversation.Type.CHAT) { // Don't process messages or corrections from MUC history DateTime? mam_delay = Xep.DelayedDelivery.get_time_for_message(stanza, message.from.bare_jid); if (mam_delay != null) return false; } string? replace_id = Xep.LastMessageCorrection.get_replace_id(stanza); if (replace_id == null) { if (!last_messages.has_key(conversation)) { last_messages[conversation] = new HashMap(Jid.hash_func, Jid.equals_func); } last_messages[conversation][message.from] = message; return false; } if (!last_messages.has_key(conversation) || !last_messages[conversation].has_key(message.from)) return false; Message original_message = last_messages[conversation][message.from]; if (original_message.stanza_id != replace_id) return false; int message_id_to_be_updated = get_latest_correction_message_id(conversation.account.id, replace_id, db.get_jid_id(message.counterpart), message.counterpart.resourcepart); if (message_id_to_be_updated == -1) { message_id_to_be_updated = original_message.id; } db.message_correction.insert() .value(db.message_correction.message_id, message.id) .value(db.message_correction.to_stanza_id, replace_id) .perform(); int current_correction_message_id = get_latest_correction_message_id(conversation.account.id, replace_id, db.get_jid_id(message.counterpart), message.counterpart.resourcepart); if (current_correction_message_id != message_id_to_be_updated) { db.content_item.update() .with(db.content_item.foreign_id, "=", message_id_to_be_updated) .with(db.content_item.content_type, "=", 1) .set(db.content_item.foreign_id, current_correction_message_id) .perform(); message.edit_to = replace_id; on_received_correction(conversation, current_correction_message_id); return true; } return false; } private void on_received_correction(Conversation conversation, int message_id) { ContentItem? content_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_item(conversation, 1, message_id); if (content_item != null) { received_correction(content_item); } } private int get_latest_correction_message_id(int account_id, string stanza_id, int counterpart_jid_id, string? counterpart_resource) { var qry = db.message_correction.select({db.message.id}) .join_with(db.message, db.message.id, db.message_correction.message_id) .with(db.message.account_id, "=", account_id) .with(db.message.counterpart_id, "=", counterpart_jid_id) .with(db.message_correction.to_stanza_id, "=", stanza_id) .order_by(db.message.time, "DESC"); if (counterpart_resource != null) { qry.with(db.message.counterpart_resource, "=", counterpart_resource); } RowOption row = qry.single().row(); if (row.is_present()) { return row[db.message.id]; } return -1; } private void on_account_added(Account account) { Gee.List conversations = stream_interactor.get_module(ConversationManager.IDENTITY).get_active_conversations(account); foreach (Conversation conversation in conversations) { if (conversation.type_ != Conversation.Type.CHAT) continue; HashMap last_conversation_messages = new HashMap(Jid.hash_func, Jid.equals_func); Gee.List messages = stream_interactor.get_module(MessageStorage.IDENTITY).get_messages(conversation); for (int i = messages.size - 1; i > 0; i--) { Message message = messages[i]; if (!last_conversation_messages.has_key(message.from) && message.edit_to == null) { last_conversation_messages[message.from] = message; } } last_messages[conversation] = last_conversation_messages; } } } } dino-0.3.0/libdino/src/service/message_processor.vala0000644000000000000000000011220514202022370021371 0ustar rootrootusing Gee; using Xmpp; using Xmpp.Xep; using Dino.Entities; using Qlite; namespace Dino { public class MessageProcessor : StreamInteractionModule, Object { public static ModuleIdentity IDENTITY = new ModuleIdentity("message_processor"); public string id { get { return IDENTITY.id; } } public signal void message_received(Entities.Message message, Conversation conversation); public signal void build_message_stanza(Entities.Message message, Xmpp.MessageStanza message_stanza, Conversation conversation); public signal void pre_message_send(Entities.Message message, Xmpp.MessageStanza message_stanza, Conversation conversation); public signal void message_sent(Entities.Message message, Conversation conversation); public signal void message_sent_or_received(Entities.Message message, Conversation conversation); public signal void history_synced(Account account); public MessageListenerHolder received_pipeline = new MessageListenerHolder(); private StreamInteractor stream_interactor; private Database db; private HashMap current_catchup_id = new HashMap(Account.hash_func, Account.equals_func); private HashMap> mam_times = new HashMap>(); public HashMap hitted_range = new HashMap(); public HashMap catchup_until_id = new HashMap(Account.hash_func, Account.equals_func); public HashMap catchup_until_time = new HashMap(Account.hash_func, Account.equals_func); public static void start(StreamInteractor stream_interactor, Database db) { MessageProcessor m = new MessageProcessor(stream_interactor, db); stream_interactor.add_module(m); } private MessageProcessor(StreamInteractor stream_interactor, Database db) { this.stream_interactor = stream_interactor; this.db = db; received_pipeline.connect(new DeduplicateMessageListener(this, db)); received_pipeline.connect(new FilterMessageListener()); received_pipeline.connect(new StoreMessageListener(stream_interactor)); received_pipeline.connect(new StoreContentItemListener(stream_interactor)); received_pipeline.connect(new MamMessageListener(stream_interactor)); stream_interactor.account_added.connect(on_account_added); stream_interactor.stream_negotiated.connect(send_unsent_chat_messages); stream_interactor.stream_resumed.connect(send_unsent_chat_messages); stream_interactor.connection_manager.stream_opened.connect((account, stream) => { debug("MAM: [%s] Reset catchup_id", account.bare_jid.to_string()); current_catchup_id.unset(account); }); } public Entities.Message send_text(string text, Conversation conversation) { Entities.Message message = create_out_message(text, conversation); return send_message(message, conversation); } public Entities.Message send_message(Entities.Message message, Conversation conversation) { stream_interactor.get_module(ContentItemStore.IDENTITY).insert_message(message, conversation); send_xmpp_message(message, conversation); message_sent(message, conversation); return message; } private void convert_sending_to_unsent_msgs(Account account) { db.message.update() .with(db.message.account_id, "=", account.id) .with(db.message.marked, "=", Message.Marked.SENDING) .set(db.message.marked, Message.Marked.UNSENT) .perform(); } private void send_unsent_chat_messages(Account account) { var select = db.message.select() .with(db.message.account_id, "=", account.id) .with(db.message.marked, "=", (int) Message.Marked.UNSENT) .with(db.message.type_, "=", (int) Message.Type.CHAT); send_unsent_messages(account, select); } public void send_unsent_muc_messages(Account account, Jid muc_jid) { var select = db.message.select() .with(db.message.account_id, "=", account.id) .with(db.message.marked, "=", (int) Message.Marked.UNSENT) .with(db.message.counterpart_id, "=", db.get_jid_id(muc_jid)); send_unsent_messages(account, select); } private void send_unsent_messages(Account account, QueryBuilder select) { foreach (Row row in select) { try { Message message = new Message.from_row(db, row); Conversation? msg_conv = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(message.counterpart, account, Util.get_conversation_type_for_message(message)); if (msg_conv != null) { Message cached_msg = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_id(message.id, msg_conv); send_xmpp_message(cached_msg ?? message, msg_conv, true); } } catch (InvalidJidError e) { warning("Ignoring message with invalid Jid: %s", e.message); } } } private void on_account_added(Account account) { mam_times[account] = new HashMap(); stream_interactor.module_manager.get_module(account, Xmpp.MessageModule.IDENTITY).received_message.connect( (stream, message) => { on_message_received.begin(account, message); }); XmppStream? stream_bak = null; stream_interactor.module_manager.get_module(account, Xmpp.Xep.MessageArchiveManagement.Module.IDENTITY).feature_available.connect( (stream) => { if (stream == stream_bak) return; current_catchup_id.unset(account); stream_bak = stream; debug("MAM: [%s] MAM available", account.bare_jid.to_string()); do_mam_catchup.begin(account); }); stream_interactor.module_manager.get_module(account, Xmpp.MessageModule.IDENTITY).received_message_unprocessed.connect((stream, message) => { if (!message.from.equals(account.bare_jid)) return; Xep.MessageArchiveManagement.Flag? mam_flag = stream != null ? stream.get_flag(Xep.MessageArchiveManagement.Flag.IDENTITY) : null; if (mam_flag == null) return; string? id = message.stanza.get_deep_attribute(mam_flag.ns_ver + ":result", "id"); if (id == null) return; StanzaNode? delay_node = message.stanza.get_deep_subnode(mam_flag.ns_ver + ":result", "urn:xmpp:forward:0:forwarded", "urn:xmpp:delay:delay"); if (delay_node == null) { warning("MAM result did not contain delayed time %s", message.stanza.to_string()); return; } DateTime? time = DelayedDelivery.get_time_for_node(delay_node); if (time == null) return; mam_times[account][id] = time; string? query_id = message.stanza.get_deep_attribute(mam_flag.ns_ver + ":result", mam_flag.ns_ver + ":queryid"); if (query_id != null && id == catchup_until_id[account]) { debug("MAM: [%s] Hitted range (id) %s", account.bare_jid.to_string(), id); hitted_range[query_id] = -2; } }); stream_interactor.module_manager.get_module(account, Xmpp.MessageModule.IDENTITY).received_error.connect((stream, message_stanza, error_stanza) => { Message? message = null; Gee.List conversations = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversations(message_stanza.from, account); foreach (Conversation conversation in conversations) { message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_stanza_id(message_stanza.id, conversation); if (message != null) break; } if (message == null) return; // We don't care about delivery errors if our counterpart already ACKed the message. if (message.marked in Message.MARKED_RECEIVED) return; warning("Message delivery error from %s. Type: %s, Condition: %s, Text: %s", message_stanza.from.to_string(), error_stanza.type_ ?? "-", error_stanza.condition, error_stanza.text ?? "-"); if (error_stanza.condition == Xmpp.ErrorStanza.CONDITION_RECIPIENT_UNAVAILABLE && error_stanza.type_ == Xmpp.ErrorStanza.TYPE_CANCEL) return; message.marked = Message.Marked.ERROR; }); convert_sending_to_unsent_msgs(account); } private async void do_mam_catchup(Account account) { debug("MAM: [%s] Start catchup", account.bare_jid.to_string()); string? earliest_id = null; DateTime? earliest_time = null; bool continue_sync = true; while (continue_sync) { continue_sync = false; // Get previous row var previous_qry = db.mam_catchup.select().with(db.mam_catchup.account_id, "=", account.id).order_by(db.mam_catchup.to_time, "DESC"); if (current_catchup_id.has_key(account)) { previous_qry.with(db.mam_catchup.id, "!=", current_catchup_id[account]); } RowOption previous_row = previous_qry.single().row(); if (previous_row.is_present()) { catchup_until_id[account] = previous_row[db.mam_catchup.to_id]; catchup_until_time[account] = (new DateTime.from_unix_utc(previous_row[db.mam_catchup.to_time])).add_minutes(-5); debug("MAM: [%s] Previous entry exists", account.bare_jid.to_string()); } else { catchup_until_id.unset(account); catchup_until_time.unset(account); } string query_id = Xmpp.random_uuid(); yield get_mam_range(account, query_id, null, null, earliest_time, earliest_id); if (!hitted_range.has_key(query_id)) { debug("MAM: [%s] Set catchup end reached", account.bare_jid.to_string()); db.mam_catchup.update() .set(db.mam_catchup.from_end, true) .with(db.mam_catchup.id, "=", current_catchup_id[account]) .perform(); } if (hitted_range.has_key(query_id)) { if (merge_ranges(account, null)) { RowOption current_row = db.mam_catchup.row_with(db.mam_catchup.id, current_catchup_id[account]); bool range_from_complete = current_row[db.mam_catchup.from_end]; if (!range_from_complete) { continue_sync = true; earliest_id = current_row[db.mam_catchup.from_id]; earliest_time = (new DateTime.from_unix_utc(current_row[db.mam_catchup.from_time])).add_seconds(1); } } } } } /* * Merges the row with `current_catchup_id` with the previous range (optional: with `earlier_id`) * Changes `current_catchup_id` to the previous range */ private bool merge_ranges(Account account, int? earlier_id) { RowOption current_row = db.mam_catchup.row_with(db.mam_catchup.id, current_catchup_id[account]); RowOption previous_row = null; if (earlier_id != null) { previous_row = db.mam_catchup.row_with(db.mam_catchup.id, earlier_id); } else { previous_row = db.mam_catchup.select() .with(db.mam_catchup.account_id, "=", account.id) .with(db.mam_catchup.id, "!=", current_catchup_id[account]) .order_by(db.mam_catchup.to_time, "DESC").single().row(); } if (!previous_row.is_present()) { debug("MAM: [%s] Merging: No previous row", account.bare_jid.to_string()); return false; } var qry = db.mam_catchup.update().with(db.mam_catchup.id, "=", previous_row[db.mam_catchup.id]); debug("MAM: [%s] Merging %ld-%ld with %ld- %ld", account.bare_jid.to_string(), previous_row[db.mam_catchup.from_time], previous_row[db.mam_catchup.to_time], current_row[db.mam_catchup.from_time], current_row[db.mam_catchup.to_time]); if (current_row[db.mam_catchup.from_time] < previous_row[db.mam_catchup.from_time]) { qry.set(db.mam_catchup.from_id, current_row[db.mam_catchup.from_id]) .set(db.mam_catchup.from_time, current_row[db.mam_catchup.from_time]); } if (current_row[db.mam_catchup.to_time] > previous_row[db.mam_catchup.to_time]) { qry.set(db.mam_catchup.to_id, current_row[db.mam_catchup.to_id]) .set(db.mam_catchup.to_time, current_row[db.mam_catchup.to_time]); } qry.perform(); current_catchup_id[account] = previous_row[db.mam_catchup.id]; db.mam_catchup.delete().with(db.mam_catchup.id, "=", current_row[db.mam_catchup.id]).perform(); return true; } private async bool get_mam_range(Account account, string? query_id, DateTime? from_time, string? from_id, DateTime? to_time, string? to_id) { debug("MAM: [%s] Get range %s - %s", account.bare_jid.to_string(), from_time != null ? from_time.to_string() : "", to_time != null ? to_time.to_string() : ""); XmppStream stream = stream_interactor.get_stream(account); Iq.Stanza? iq = yield stream.get_module(Xep.MessageArchiveManagement.Module.IDENTITY).query_archive(stream, null, query_id, from_time, from_id, to_time, to_id); if (iq == null) { debug(@"MAM: [%s] IQ null", account.bare_jid.to_string()); return true; } if (iq.stanza.get_deep_string_content("urn:xmpp:mam:2:fin", "http://jabber.org/protocol/rsm" + ":set", "first") == null) { return true; } while (iq != null) { string? earliest_id = iq.stanza.get_deep_string_content("urn:xmpp:mam:2:fin", "http://jabber.org/protocol/rsm" + ":set", "first"); if (earliest_id == null) return true; string? latest_id = iq.stanza.get_deep_string_content("urn:xmpp:mam:2:fin", "http://jabber.org/protocol/rsm" + ":set", "last"); // We wait until all the messages from the page are processed (and we got the `mam_times` from them) Idle.add(get_mam_range.callback, Priority.LOW); yield; int wait_ms = 1000; if (mam_times[account].has_key(earliest_id) && (current_catchup_id.has_key(account) || mam_times[account].has_key(latest_id))) { debug("MAM: [%s] Update from_id %s", account.bare_jid.to_string(), earliest_id); if (!current_catchup_id.has_key(account)) { debug("MAM: [%s] We get our first MAM page", account.bare_jid.to_string()); current_catchup_id[account] = (int) db.mam_catchup.insert() .value(db.mam_catchup.account_id, account.id) .value(db.mam_catchup.from_id, earliest_id) .value(db.mam_catchup.from_time, (long)mam_times[account][earliest_id].to_unix()) .value(db.mam_catchup.to_id, latest_id) .value(db.mam_catchup.to_time, (long)mam_times[account][latest_id].to_unix()) .perform(); } else { // Update existing id db.mam_catchup.update() .set(db.mam_catchup.from_id, earliest_id) .set(db.mam_catchup.from_time, (long)mam_times[account][earliest_id].to_unix()) .with(db.mam_catchup.id, "=", current_catchup_id[account]) .perform(); } TimeSpan catchup_time_ago = (new DateTime.now_utc()).difference(mam_times[account][earliest_id]); if (catchup_time_ago > 14 * TimeSpan.DAY) { wait_ms = 2000; } else if (catchup_time_ago > 5 * TimeSpan.DAY) { wait_ms = 1000; } else if (catchup_time_ago > 2 * TimeSpan.DAY) { wait_ms = 200; } else if (catchup_time_ago > TimeSpan.DAY) { wait_ms = 50; } else { wait_ms = 10; } } else { warning("Didn't have time for MAM id; earliest_id:%s latest_id:%s", mam_times[account].has_key(earliest_id).to_string(), mam_times[account].has_key(latest_id).to_string()); } mam_times[account] = new HashMap(); Timeout.add(wait_ms, () => { if (hitted_range.has_key(query_id)) { debug(@"MAM: [%s] Hitted contains key %s", account.bare_jid.to_string(), query_id); iq = null; Idle.add(get_mam_range.callback); return false; } stream.get_module(Xep.MessageArchiveManagement.Module.IDENTITY).page_through_results.begin(stream, null, query_id, from_time, to_time, iq, (_, res) => { iq = stream.get_module(Xep.MessageArchiveManagement.Module.IDENTITY).page_through_results.end(res); Idle.add(get_mam_range.callback); }); return false; }); yield; } return false; } private async void on_message_received(Account account, Xmpp.MessageStanza message_stanza) { Entities.Message message = yield parse_message_stanza(account, message_stanza); Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation_for_message(message); if (conversation == null) return; // MAM state database update Xep.MessageArchiveManagement.MessageFlag? mam_flag = Xep.MessageArchiveManagement.MessageFlag.get_flag(message_stanza); if (mam_flag == null) { if (current_catchup_id.has_key(account)) { string? stanza_id = UniqueStableStanzaIDs.get_stanza_id(message_stanza, account.bare_jid); if (stanza_id != null) { db.mam_catchup.update() .with(db.mam_catchup.id, "=", current_catchup_id[account]) .set(db.mam_catchup.to_time, (long)message.local_time.to_unix()) .set(db.mam_catchup.to_id, stanza_id) .perform(); } } } bool abort = yield received_pipeline.run(message, message_stanza, conversation); if (abort) return; if (message.direction == Entities.Message.DIRECTION_RECEIVED) { message_received(message, conversation); } else if (message.direction == Entities.Message.DIRECTION_SENT) { message_sent(message, conversation); } message_sent_or_received(message, conversation); } private async Entities.Message parse_message_stanza(Account account, Xmpp.MessageStanza message) { string? body = message.body; if (body != null) body = body.strip(); Entities.Message new_message = new Entities.Message(body); new_message.account = account; new_message.stanza_id = Xep.UniqueStableStanzaIDs.get_origin_id(message) ?? message.id; Jid? counterpart_override = null; if (message.from.equals(stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(message.from.bare_jid, account))) { new_message.direction = Entities.Message.DIRECTION_SENT; counterpart_override = message.from.bare_jid; } else if (account.bare_jid.equals_bare(message.from)) { new_message.direction = Entities.Message.DIRECTION_SENT; } else { new_message.direction = Entities.Message.DIRECTION_RECEIVED; } new_message.counterpart = counterpart_override ?? (new_message.direction == Entities.Message.DIRECTION_SENT ? message.to : message.from); new_message.ourpart = new_message.direction == Entities.Message.DIRECTION_SENT ? message.from : message.to; XmppStream? stream = stream_interactor.get_stream(account); Xep.MessageArchiveManagement.MessageFlag? mam_message_flag = Xep.MessageArchiveManagement.MessageFlag.get_flag(message); Xep.MessageArchiveManagement.Flag? mam_flag = stream != null ? stream.get_flag(Xep.MessageArchiveManagement.Flag.IDENTITY) : null; EntityInfo entity_info = stream_interactor.get_module(EntityInfo.IDENTITY); if (mam_message_flag != null && mam_flag != null && mam_flag.ns_ver == Xep.MessageArchiveManagement.NS_URI_2 && mam_message_flag.mam_id != null) { new_message.server_id = mam_message_flag.mam_id; } else if (message.type_ == Xmpp.MessageStanza.TYPE_GROUPCHAT) { bool server_supports_sid = (yield entity_info.has_feature(account, new_message.counterpart.bare_jid, Xep.UniqueStableStanzaIDs.NS_URI)) || (yield entity_info.has_feature(account, new_message.counterpart.bare_jid, Xep.MessageArchiveManagement.NS_URI_2)); if (server_supports_sid) { new_message.server_id = Xep.UniqueStableStanzaIDs.get_stanza_id(message, new_message.counterpart.bare_jid); } } else if (message.type_ == Xmpp.MessageStanza.TYPE_CHAT) { bool server_supports_sid = (yield entity_info.has_feature(account, account.bare_jid, Xep.UniqueStableStanzaIDs.NS_URI)) || (yield entity_info.has_feature(account, account.bare_jid, Xep.MessageArchiveManagement.NS_URI_2)); if (server_supports_sid) { new_message.server_id = Xep.UniqueStableStanzaIDs.get_stanza_id(message, account.bare_jid); } } if (mam_message_flag != null) new_message.local_time = mam_message_flag.server_time; DateTime now = new DateTime.from_unix_utc(new DateTime.now_utc().to_unix()); // Remove milliseconds. They are not stored in the db and might lead to ordering issues when compared with times from the db. if (new_message.local_time == null || new_message.local_time.compare(now) > 0) new_message.local_time = now; Xep.DelayedDelivery.MessageFlag? delayed_message_flag = Xep.DelayedDelivery.MessageFlag.get_flag(message); if (delayed_message_flag != null) new_message.time = delayed_message_flag.datetime; if (new_message.time == null || new_message.time.compare(new_message.local_time) > 0) new_message.time = new_message.local_time; new_message.type_ = yield determine_message_type(account, message, new_message); return new_message; } private async Entities.Message.Type determine_message_type(Account account, Xmpp.MessageStanza message_stanza, Entities.Message message) { if (message_stanza.type_ == Xmpp.MessageStanza.TYPE_GROUPCHAT) { return Entities.Message.Type.GROUPCHAT; } if (message_stanza.type_ == Xmpp.MessageStanza.TYPE_CHAT) { Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(message.counterpart.bare_jid, account); if (conversation != null) { if (conversation.type_ == Conversation.Type.CHAT) { return Entities.Message.Type.CHAT; } else if (conversation.type_ == Conversation.Type.GROUPCHAT) { return Entities.Message.Type.GROUPCHAT_PM; } } else { XmppStream stream = stream_interactor.get_stream(account); if (stream != null) { Gee.Set? identities = yield stream.get_module(Xep.ServiceDiscovery.Module.IDENTITY).get_entity_identities(stream, message.counterpart.bare_jid); if (identities == null) { return Entities.Message.Type.CHAT; } foreach (Xep.ServiceDiscovery.Identity identity in identities) { if (identity.category == Xep.ServiceDiscovery.Identity.CATEGORY_CONFERENCE) { return Entities.Message.Type.GROUPCHAT_PM; } else { return Entities.Message.Type.CHAT; } } } } } return Entities.Message.Type.CHAT; } private class DeduplicateMessageListener : MessageListener { public string[] after_actions_const = new string[]{ "FILTER_EMPTY", "MUC" }; public override string action_group { get { return "DEDUPLICATE"; } } public override string[] after_actions { get { return after_actions_const; } } private MessageProcessor outer; private Database db; public DeduplicateMessageListener(MessageProcessor outer, Database db) { this.outer = outer; this.db = db; } public override async bool run(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) { Account account = conversation.account; Xep.MessageArchiveManagement.MessageFlag? mam_flag = Xep.MessageArchiveManagement.MessageFlag.get_flag(stanza); // Deduplicate by server_id if (message.server_id != null) { QueryBuilder builder = db.message.select() .with(db.message.server_id, "=", message.server_id) .with(db.message.counterpart_id, "=", db.get_jid_id(message.counterpart)) .with(db.message.account_id, "=", account.id); bool duplicate = builder.count() > 0; if (duplicate && mam_flag != null) { debug(@"MAM: [%s] Hitted range duplicate server id. id %s qid %s", account.bare_jid.to_string(), message.server_id, mam_flag.query_id); if (outer.catchup_until_time.has_key(account) && mam_flag.server_time.compare(outer.catchup_until_time[account]) < 0) { outer.hitted_range[mam_flag.query_id] = -1; debug(@"MAM: [%s] In range (time) %s < %s", account.bare_jid.to_string(), mam_flag.server_time.to_string(), outer.catchup_until_time[account].to_string()); } } if (duplicate) return true; } // Deduplicate messages by uuid bool is_uuid = message.stanza_id != null && Regex.match_simple("""[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}""", message.stanza_id); if (is_uuid) { QueryBuilder builder = db.message.select() .with(db.message.stanza_id, "=", message.stanza_id) .with(db.message.counterpart_id, "=", db.get_jid_id(message.counterpart)) .with(db.message.account_id, "=", account.id); if (message.direction == Message.DIRECTION_RECEIVED) { if (message.counterpart.resourcepart != null) { builder.with(db.message.counterpart_resource, "=", message.counterpart.resourcepart); } else { builder.with_null(db.message.counterpart_resource); } } else if (message.direction == Message.DIRECTION_SENT) { if (message.ourpart.resourcepart != null) { builder.with(db.message.our_resource, "=", message.ourpart.resourcepart); } else { builder.with_null(db.message.our_resource); } } RowOption row_opt = builder.single().row(); bool duplicate = row_opt.is_present(); if (duplicate && mam_flag != null && row_opt[db.message.server_id] == null && outer.catchup_until_time.has_key(account) && mam_flag.server_time.compare(outer.catchup_until_time[account]) > 0) { outer.hitted_range[mam_flag.query_id] = -1; debug(@"MAM: [%s] Hitted range duplicate message id. id %s qid %s", account.bare_jid.to_string(), message.stanza_id, mam_flag.query_id); } return duplicate; } // Deduplicate messages based on content and metadata QueryBuilder builder = db.message.select() .with(db.message.account_id, "=", account.id) .with(db.message.counterpart_id, "=", db.get_jid_id(message.counterpart)) .with(db.message.body, "=", message.body) .with(db.message.time, "<", (long) message.time.add_minutes(1).to_unix()) .with(db.message.time, ">", (long) message.time.add_minutes(-1).to_unix()); if (message.stanza_id != null) { builder.with(db.message.stanza_id, "=", message.stanza_id); } else { builder.with_null(db.message.stanza_id); } if (message.counterpart.resourcepart != null) { builder.with(db.message.counterpart_resource, "=", message.counterpart.resourcepart); } else { builder.with_null(db.message.counterpart_resource); } return builder.count() > 0; } } private class FilterMessageListener : MessageListener { public string[] after_actions_const = new string[]{ "DECRYPT" }; public override string action_group { get { return "FILTER_EMPTY"; } } public override string[] after_actions { get { return after_actions_const; } } public override async bool run(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) { return (message.body == null); } } private class StoreMessageListener : MessageListener { public string[] after_actions_const = new string[]{ "DEDUPLICATE", "DECRYPT", "FILTER_EMPTY" }; public override string action_group { get { return "STORE"; } } public override string[] after_actions { get { return after_actions_const; } } private StreamInteractor stream_interactor; public StoreMessageListener(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; } public override async bool run(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) { if (message.body == null) return true; stream_interactor.get_module(MessageStorage.IDENTITY).add_message(message, conversation); return false; } } private class StoreContentItemListener : MessageListener { public string[] after_actions_const = new string[]{ "DEDUPLICATE", "DECRYPT", "FILTER_EMPTY", "STORE", "CORRECTION", "MESSAGE_REINTERPRETING" }; public override string action_group { get { return "STORE_CONTENT_ITEM"; } } public override string[] after_actions { get { return after_actions_const; } } private StreamInteractor stream_interactor; public StoreContentItemListener(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; } public override async bool run(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) { if (message.body == null) return true; stream_interactor.get_module(ContentItemStore.IDENTITY).insert_message(message, conversation); return false; } } private class MamMessageListener : MessageListener { public string[] after_actions_const = new string[]{ "DEDUPLICATE" }; public override string action_group { get { return "MAM_NODE"; } } public override string[] after_actions { get { return after_actions_const; } } private StreamInteractor stream_interactor; public MamMessageListener(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; } public override async bool run(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) { bool is_mam_message = Xep.MessageArchiveManagement.MessageFlag.get_flag(stanza) != null; XmppStream? stream = stream_interactor.get_stream(conversation.account); Xep.MessageArchiveManagement.Flag? mam_flag = stream != null ? stream.get_flag(Xep.MessageArchiveManagement.Flag.IDENTITY) : null; if (is_mam_message || (mam_flag != null && mam_flag.cought_up == true)) { conversation.account.mam_earliest_synced = message.local_time; } return false; } } public Entities.Message create_out_message(string text, Conversation conversation) { Entities.Message message = new Entities.Message(text); message.type_ = Util.get_message_type_for_conversation(conversation); message.stanza_id = random_uuid(); message.account = conversation.account; message.body = text; DateTime now = new DateTime.from_unix_utc(new DateTime.now_utc().to_unix()); // Remove milliseconds. They are not stored in the db and might lead to ordering issues when compared with times from the db. message.time = now; message.local_time = now; message.direction = Entities.Message.DIRECTION_SENT; message.counterpart = conversation.counterpart; if (conversation.type_ == Conversation.Type.GROUPCHAT) { message.ourpart = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account) ?? conversation.account.bare_jid; message.real_jid = conversation.account.bare_jid; } else { message.ourpart = conversation.account.full_jid; } message.marked = Entities.Message.Marked.UNSENT; message.encryption = conversation.encryption; stream_interactor.get_module(MessageStorage.IDENTITY).add_message(message, conversation); return message; } public void send_xmpp_message(Entities.Message message, Conversation conversation, bool delayed = false) { XmppStream stream = stream_interactor.get_stream(conversation.account); message.marked = Entities.Message.Marked.SENDING; if (stream == null) { message.marked = Entities.Message.Marked.UNSENT; return; } MessageStanza new_message = new MessageStanza(message.stanza_id); new_message.to = message.counterpart; new_message.body = message.body; if (conversation.type_ == Conversation.Type.GROUPCHAT) { new_message.type_ = MessageStanza.TYPE_GROUPCHAT; } else { new_message.type_ = MessageStanza.TYPE_CHAT; } build_message_stanza(message, new_message, conversation); pre_message_send(message, new_message, conversation); if (message.marked == Entities.Message.Marked.UNSENT || message.marked == Entities.Message.Marked.WONTSEND) return; if (delayed) { DelayedDelivery.Module.set_message_delay(new_message, message.time); } // Set an origin ID if a MUC doen't guarantee to keep IDs if (conversation.type_ == Conversation.Type.GROUPCHAT) { Xep.Muc.Flag? flag = stream.get_flag(Xep.Muc.Flag.IDENTITY); if (flag == null) { message.marked = Entities.Message.Marked.UNSENT; return; } if(!flag.has_room_feature(conversation.counterpart, Xep.Muc.Feature.STABLE_ID)) { UniqueStableStanzaIDs.set_origin_id(new_message, message.stanza_id); } } stream.get_module(MessageModule.IDENTITY).send_message.begin(stream, new_message, (_, res) => { try { stream.get_module(MessageModule.IDENTITY).send_message.end(res); if (message.marked == Message.Marked.SENDING) { message.marked = Message.Marked.SENT; } // The server might not have given us the resource we asked for. In that case, store the actual resource the message was sent with. Relevant for deduplication. Jid? current_own_jid = stream.get_flag(Bind.Flag.IDENTITY).my_jid; if (!conversation.type_.is_muc_semantic() && current_own_jid != null && !current_own_jid.equals(message.ourpart)) { message.ourpart = current_own_jid; } } catch (IOStreamError e) { message.marked = Entities.Message.Marked.UNSENT; if (stream != stream_interactor.get_stream(conversation.account)) { Timeout.add_seconds(3, () => { send_unsent_chat_messages(conversation.account); return false; }); } } }); } } public abstract class MessageListener : Xmpp.OrderedListener { public abstract async bool run(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation); } public class MessageListenerHolder : Xmpp.ListenerHolder { public async bool run(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) { foreach (OrderedListener ol in listeners) { MessageListener l = ol as MessageListener; bool stop = yield l.run(message, stanza, conversation); if (stop) return true; } return false; } } } dino-0.3.0/libdino/src/service/message_storage.vala0000644000000000000000000002007714202022370021023 0ustar rootrootusing Xmpp; using Gee; using Qlite; using Dino.Entities; namespace Dino { public class MessageStorage : StreamInteractionModule, Object { public static ModuleIdentity IDENTITY = new ModuleIdentity("message_cache"); public string id { get { return IDENTITY.id; } } private StreamInteractor stream_interactor; private Database db; private WeakMap messages_by_db_id = new WeakMap(); private HashMap> messages_by_stanza_id = new HashMap>(Conversation.hash_func, Conversation.equals_func); private HashMap> messages_by_server_id = new HashMap>(Conversation.hash_func, Conversation.equals_func); // This is to keep the last 300 messages such that we don't have to recreate the newest ones all the time private LinkedList message_refs = new LinkedList(); public static void start(StreamInteractor stream_interactor, Database db) { MessageStorage m = new MessageStorage(stream_interactor, db); stream_interactor.add_module(m); } private MessageStorage(StreamInteractor stream_interactor, Database db) { this.stream_interactor = stream_interactor; this.db = db; } public void add_message(Message message, Conversation conversation) { message.persist(db); cache_message(message, conversation); } public Gee.List get_messages(Conversation conversation, int count = 50) { var query = db.message.select() .with(db.message.account_id, "=", conversation.account.id) .with(db.message.counterpart_id, "=", db.get_jid_id(conversation.counterpart)) .with(db.message.type_, "=", (int) Util.get_message_type_for_conversation(conversation)) .order_by(db.message.time, "DESC") .outer_join_with(db.message_correction, db.message_correction.message_id, db.message.id) .limit(count); Gee.List ret = new LinkedList(Message.equals_func); foreach (Row row in query) { Message? message = messages_by_db_id[row[db.message.id]]; if (message == null) { message = create_message_from_row(row, conversation); } ret.insert(0, message); } return ret; } public Message? get_last_message(Conversation conversation) { Gee.List messages = get_messages(conversation, 1); if (messages.size > 0) { return messages[0]; } return null; } public Gee.List get_messages_before_message(Conversation? conversation, DateTime before, int id, int count = 20) { Gee.List db_messages = db.get_messages(conversation.counterpart, conversation.account, Util.get_message_type_for_conversation(conversation), count, before, null, id); Gee.List ret = new ArrayList(); foreach (Message message in db_messages) { ret.add(new MessageItem(message, conversation, -1)); } return ret; } public Gee.List get_messages_after_message(Conversation? conversation, DateTime after, int id, int count = 20) { Gee.List db_messages = db.get_messages(conversation.counterpart, conversation.account, Util.get_message_type_for_conversation(conversation), count, null, after, id); Gee.List ret = new ArrayList(); foreach (Message message in db_messages) { ret.add(new MessageItem(message, conversation, -1)); } return ret; } public Message? get_message_by_id(int id, Conversation conversation) { Message? message = messages_by_db_id[id]; if (message != null) { return message; } RowOption row_option = db.message.select().with(db.message.id, "=", id) .outer_join_with(db.message_correction, db.message_correction.message_id, db.message.id) .row(); return create_message_from_row_opt(row_option, conversation); } public Message? get_message_by_stanza_id(string stanza_id, Conversation conversation) { if (messages_by_stanza_id.has_key(conversation)) { Message? message = messages_by_stanza_id[conversation][stanza_id]; if (message != null) { return message; } } var query = db.message.select() .with(db.message.account_id, "=", conversation.account.id) .with(db.message.counterpart_id, "=", db.get_jid_id(conversation.counterpart)) .with(db.message.type_, "=", (int) Util.get_message_type_for_conversation(conversation)) .with(db.message.stanza_id, "=", stanza_id) .order_by(db.message.time, "DESC") .outer_join_with(db.message_correction, db.message_correction.message_id, db.message.id); if (conversation.counterpart.resourcepart == null) { query.with_null(db.message.counterpart_resource); } else { query.with(db.message.counterpart_resource, "=", conversation.counterpart.resourcepart); } RowOption row_option = query.single().row(); return create_message_from_row_opt(row_option, conversation); } public Message? get_message_by_server_id(string server_id, Conversation conversation) { if (messages_by_server_id.has_key(conversation)) { Message? message = messages_by_server_id[conversation][server_id]; if (message != null) { return message; } } var query = db.message.select() .with(db.message.account_id, "=", conversation.account.id) .with(db.message.counterpart_id, "=", db.get_jid_id(conversation.counterpart)) .with(db.message.type_, "=", (int) Util.get_message_type_for_conversation(conversation)) .with(db.message.server_id, "=", server_id) .order_by(db.message.time, "DESC") .outer_join_with(db.message_correction, db.message_correction.message_id, db.message.id); if (conversation.counterpart.resourcepart == null) { query.with_null(db.message.counterpart_resource); } else { query.with(db.message.counterpart_resource, "=", conversation.counterpart.resourcepart); } RowOption row_option = query.single().row(); return create_message_from_row_opt(row_option, conversation); } private Message? create_message_from_row_opt(RowOption row_option, Conversation conversation) { if (!row_option.is_present()) return null; return create_message_from_row(row_option.inner, conversation); } private Message? create_message_from_row(Row row, Conversation conversation) { try { Message message = new Message.from_row(db, row); cache_message(message, conversation); return message; } catch (InvalidJidError e) { warning("Got message with invalid Jid: %s", e.message); } return null; } private void cache_message(Message message, Conversation conversation) { messages_by_db_id[message.id] = message; if (message.stanza_id != null) { if (!messages_by_stanza_id.has_key(conversation)) { messages_by_stanza_id[conversation] = new WeakMap(); } messages_by_stanza_id[conversation][message.stanza_id] = message; } if (message.server_id != null) { if (!messages_by_server_id.has_key(conversation)) { messages_by_server_id[conversation] = new WeakMap(); } messages_by_server_id[conversation][message.server_id] = message; } message_refs.insert(0, message); if (message_refs.size > 300) { message_refs.remove_at(message_refs.size - 1); } } } } dino-0.3.0/libdino/src/service/module_manager.vala0000644000000000000000000001100514202022370020621 0ustar rootrootusing Gee; using Dino.Entities; using Xmpp; namespace Dino { public class ModuleManager { private HashMap> module_map = new HashMap>(Account.hash_func, Account.equals_func); public signal void initialize_account_modules(Account account, ArrayList modules); public T? get_module(Account account, Xmpp.ModuleIdentity identity) { if (identity == null) return null; lock (module_map) { if (!module_map.has_key(account)) { initialize(account); } var res = module_map[account].filter((module) => identity.matches(module)); if (res != null && res.next()) { return identity.cast(res.get()); } } return null; } public ArrayList get_modules(Account account, string? resource = null) { ArrayList modules = new ArrayList(); lock (module_map) { if (!module_map.has_key(account)) initialize(account); foreach (XmppStreamModule module in module_map[account]) modules.add(module); } foreach (XmppStreamModule module in module_map[account]) { if (module.get_id() == Bind.Module.IDENTITY.id) { ((Bind.Module) module).requested_resource = resource ?? account.resourcepart; } else if (module.get_id() == Sasl.Module.IDENTITY.id) { ((Sasl.Module) module).password = account.password; } } return modules; } public void initialize(Account account) { lock(module_map) { module_map[account] = new ArrayList(); module_map[account].add(new Iq.Module()); module_map[account].add(new Sasl.Module(account.bare_jid.to_string(), account.password)); module_map[account].add(new Xep.StreamManagement.Module()); module_map[account].add(new Bind.Module(account.resourcepart)); module_map[account].add(new Session.Module()); module_map[account].add(new Roster.Module()); module_map[account].add(new Xep.ServiceDiscovery.Module.with_identity("client", "pc", "Dino")); module_map[account].add(new Xep.PrivateXmlStorage.Module()); module_map[account].add(new Xep.Bookmarks.Module()); module_map[account].add(new Xep.Bookmarks2.Module()); module_map[account].add(new Presence.Module()); module_map[account].add(new Xmpp.MessageModule()); module_map[account].add(new Xep.MessageArchiveManagement.Module()); module_map[account].add(new Xep.MessageCarbons.Module()); module_map[account].add(new Xep.Muc.Module()); module_map[account].add(new Xep.Pubsub.Module()); module_map[account].add(new Xep.MessageDeliveryReceipts.Module()); module_map[account].add(new Xep.BlockingCommand.Module()); module_map[account].add(new Xep.ChatStateNotifications.Module()); module_map[account].add(new Xep.ChatMarkers.Module()); module_map[account].add(new Xep.Ping.Module()); module_map[account].add(new Xep.DelayedDelivery.Module()); module_map[account].add(new StreamError.Module()); module_map[account].add(new Xep.InBandRegistration.Module()); module_map[account].add(new Xep.HttpFileUpload.Module()); module_map[account].add(new Xep.Socks5Bytestreams.Module()); module_map[account].add(new Xep.InBandBytestreams.Module()); module_map[account].add(new Xep.Jingle.Module()); module_map[account].add(new Xep.JingleSocks5Bytestreams.Module()); module_map[account].add(new Xep.JingleInBandBytestreams.Module()); module_map[account].add(new Xep.JingleFileTransfer.Module()); module_map[account].add(new Xep.Jet.Module()); module_map[account].add(new Xep.LastMessageCorrection.Module()); module_map[account].add(new Xep.DirectMucInvitations.Module()); module_map[account].add(new Xep.JingleMessageInitiation.Module()); module_map[account].add(new Xep.JingleRawUdp.Module()); module_map[account].add(new Xep.Muji.Module()); module_map[account].add(new Xep.CallInvites.Module()); module_map[account].add(new Xep.Coin.Module()); initialize_account_modules(account, module_map[account]); } } } } dino-0.3.0/libdino/src/service/muc_manager.vala0000644000000000000000000007260714202022370020137 0ustar rootrootusing Gee; using Xmpp; using Xmpp.Xep; using Dino.Entities; namespace Dino { public class MucManager : StreamInteractionModule, Object { public static ModuleIdentity IDENTITY = new ModuleIdentity("muc_manager"); public string id { get { return IDENTITY.id; } } public signal void left(Account account, Jid jid); public signal void subject_set(Account account, Jid jid, string? subject); public signal void room_info_updated(Account account, Jid muc_jid); public signal void private_room_occupant_updated(Account account, Jid room, Jid occupant); public signal void invite_received(Account account, Jid room_jid, Jid from_jid, string? password, string? reason); public signal void voice_request_received(Account account, Jid room_jid, Jid from_jid, string nick); public signal void received_occupant_role(Account account, Jid jid, Xep.Muc.Role? role); public signal void bookmarks_updated(Account account, Set conferences); public signal void conference_added(Account account, Conference conference); public signal void conference_removed(Account account, Jid jid); private StreamInteractor stream_interactor; private HashMap> mucs_todo = new HashMap>(Account.hash_func, Account.equals_func); private HashMap> mucs_joining = new HashMap>(Account.hash_func, Account.equals_func); private HashMap enter_errors = new HashMap(Jid.hash_func, Jid.equals_func); private ReceivedMessageListener received_message_listener; private HashMap bookmarks_provider = new HashMap(Account.hash_func, Account.equals_func); private HashMap> invites = new HashMap>(Account.hash_func, Account.equals_func); public HashMap default_muc_server = new HashMap(Account.hash_func, Account.equals_func); public static void start(StreamInteractor stream_interactor) { MucManager m = new MucManager(stream_interactor); stream_interactor.add_module(m); } private MucManager(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; this.received_message_listener = new ReceivedMessageListener(stream_interactor); stream_interactor.account_added.connect(on_account_added); stream_interactor.stream_negotiated.connect(on_stream_negotiated); stream_interactor.get_module(MessageProcessor.IDENTITY).received_pipeline.connect(received_message_listener); stream_interactor.get_module(ConversationManager.IDENTITY).conversation_deactivated.connect((conversation) => { if (conversation.type_ == Conversation.Type.GROUPCHAT) { part(conversation.account, conversation.counterpart); } }); stream_interactor.stream_resumed.connect((account, stream) => self_ping(account)); Timeout.add_seconds(60 * 3, () => { foreach (Account account in stream_interactor.get_accounts()) { self_ping(account); } return true; }); } // already_autojoin: Without this flag we'd be retrieving bookmarks (to check for autojoin) from the sender on every join public async Muc.JoinResult? join(Account account, Jid jid, string? nick, string? password, bool already_autojoin = false) { XmppStream? stream = stream_interactor.get_stream(account); if (stream == null) return null; string nick_ = (nick ?? account.localpart) ?? account.domainpart; DateTime? history_since = null; Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(jid, account); if (conversation != null) { Entities.Message? last_message = stream_interactor.get_module(MessageStorage.IDENTITY).get_last_message(conversation); if (last_message != null) history_since = last_message.time; } if (!mucs_joining.has_key(account)) { mucs_joining[account] = new HashSet(Jid.hash_bare_func, Jid.equals_bare_func); } mucs_joining[account].add(jid); if (!mucs_todo.has_key(account)) { mucs_todo[account] = new HashSet(Jid.hash_bare_func, Jid.equals_bare_func); } mucs_todo[account].add(jid.with_resource(nick_)); Muc.JoinResult? res = yield stream.get_module(Xep.Muc.Module.IDENTITY).enter(stream, jid.bare_jid, nick_, password, history_since, null); mucs_joining[account].remove(jid); if (res.nick != null) { // Join completed enter_errors.unset(jid); if (!already_autojoin) set_autojoin(account, stream, jid, nick, password); stream_interactor.get_module(MessageProcessor.IDENTITY).send_unsent_muc_messages(account, jid); Conversation joined_conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(jid, account, Conversation.Type.GROUPCHAT); joined_conversation.nickname = nick; stream_interactor.get_module(ConversationManager.IDENTITY).start_conversation(joined_conversation); } else if (res.muc_error != null) { // Join failed enter_errors[jid] = res.muc_error; } return res; } public void part(Account account, Jid jid) { if (!mucs_todo.has_key(account) || !mucs_todo[account].contains(jid)) return; mucs_todo[account].remove(jid); XmppStream? stream = stream_interactor.get_stream(account); if (stream == null) return; unset_autojoin(account, stream, jid); stream.get_module(Xep.Muc.Module.IDENTITY).exit(stream, jid.bare_jid); Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(jid, account); if (conversation != null) stream_interactor.get_module(ConversationManager.IDENTITY).close_conversation(conversation); } public async DataForms.DataForm? get_config_form(Account account, Jid jid) { XmppStream? stream = stream_interactor.get_stream(account); if (stream == null) return null; return yield stream.get_module(Xep.Muc.Module.IDENTITY).get_config_form(stream, jid); } public async void set_config_form(Account account, Jid jid, DataForms.DataForm data_form) { XmppStream? stream = stream_interactor.get_stream(account); if (stream == null) return; yield stream.get_module(Xep.Muc.Module.IDENTITY).set_config_form(stream, jid, data_form); } public void change_subject(Account account, Jid jid, string subject) { XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) stream.get_module(Xep.Muc.Module.IDENTITY).change_subject(stream, jid.bare_jid, subject); } public async void change_nick(Conversation conversation, string new_nick) { XmppStream? stream = stream_interactor.get_stream(conversation.account); if (stream == null) return; // Check if this would be a valid nick try { conversation.counterpart.with_resource(new_nick); } catch (InvalidJidError error) { return; } stream.get_module(Xep.Muc.Module.IDENTITY).change_nick(stream, conversation.counterpart, new_nick); conversation.nickname = new_nick; if (mucs_todo.has_key(conversation.account)) { mucs_todo[conversation.account].remove(conversation.counterpart); mucs_todo[conversation.account].add(conversation.counterpart.with_resource(new_nick)); } // Update nick in bookmark Set? conferences = yield bookmarks_provider[conversation.account].get_conferences(stream); if (conferences == null) return; foreach (Conference conference in conferences) { if (conference.jid.equals(conversation.counterpart)) { Conference new_conference = new Conference() { jid=conversation.counterpart, nick=new_nick, name=conference.name, password=conference.password, autojoin=conference.autojoin }; bookmarks_provider[conversation.account].replace_conference.begin(stream, conversation.counterpart, new_conference); break; } } } public void invite(Account account, Jid muc, Jid invitee) { XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) stream.get_module(Xep.Muc.Module.IDENTITY).invite(stream, muc.bare_jid, invitee.bare_jid); } public void kick(Account account, Jid jid, string nick) { XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) stream.get_module(Xep.Muc.Module.IDENTITY).kick(stream, jid.bare_jid, nick); } public void change_affiliation(Account account, Jid jid, string nick, string role) { XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) stream.get_module(Xep.Muc.Module.IDENTITY).change_affiliation.begin(stream, jid.bare_jid, null, nick, role); } public void change_role(Account account, Jid jid, string nick, string role) { XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) stream.get_module(Xep.Muc.Module.IDENTITY).change_role(stream, jid.bare_jid, nick, role); } public void request_voice(Account account, Jid jid) { XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) stream.get_module(Xep.Muc.Module.IDENTITY).request_voice(stream, jid.bare_jid); } public bool kick_possible(Account account, Jid occupant) { XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) return stream.get_module(Xep.Muc.Module.IDENTITY).kick_possible(stream, occupant); return false; } //the term `private room` is a short hand for members-only+non-anonymous rooms public bool is_private_room(Account account, Jid jid) { XmppStream? stream = stream_interactor.get_stream(account); if (stream == null) { return false; } Xep.Muc.Flag? flag = stream.get_flag(Xep.Muc.Flag.IDENTITY); if (flag == null) { return false; } return flag.has_room_feature(jid, Xep.Muc.Feature.NON_ANONYMOUS) && flag.has_room_feature(jid, Xep.Muc.Feature.MEMBERS_ONLY); } public bool is_moderated_room(Account account, Jid jid) { XmppStream? stream = stream_interactor.get_stream(account); if (stream == null) { return false; } Xep.Muc.Flag? flag = stream.get_flag(Xep.Muc.Flag.IDENTITY); if (flag == null) { return false; } return flag.has_room_feature(jid, Xep.Muc.Feature.MODERATED); } public bool is_public_room(Account account, Jid jid) { return is_groupchat(jid, account) && !is_private_room(account, jid); } public Gee.List? get_occupants(Jid jid, Account account) { if (is_groupchat(jid, account)) { Gee.List ret = new ArrayList(Jid.equals_func); Gee.List? full_jids = stream_interactor.get_module(PresenceManager.IDENTITY).get_full_jids(jid, account); if (full_jids != null) { ret.add_all(full_jids); // Remove eventual presence from bare jid ret.remove(jid); } return ret; } return null; } public Gee.List? get_other_occupants(Jid jid, Account account) { Gee.List? occupants = get_occupants(jid, account); Jid? own_jid = get_own_jid(jid, account); if (occupants != null && own_jid != null) { occupants.remove(own_jid); } return occupants; } public bool is_groupchat(Jid jid, Account account) { Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(jid, account, Conversation.Type.GROUPCHAT); return !jid.is_full() && conversation != null; } public bool might_be_groupchat(Jid jid, Account account) { if (mucs_joining.has_key(account) && mucs_joining[account].contains(jid)) return true; return is_groupchat(jid, account); } public bool is_groupchat_occupant(Jid jid, Account account) { return is_groupchat(jid.bare_jid, account) && jid.resourcepart != null; } public async Set? get_bookmarks(Account account) { XmppStream? stream = stream_interactor.get_stream(account); if (stream == null) return null; return yield bookmarks_provider[account].get_conferences(stream); } public void add_bookmark(Account account, Conference conference) { XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) { bookmarks_provider[account].add_conference.begin(stream, conference); } } public void remove_bookmark(Account account, Conference conference) { XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) { bookmarks_provider[account].remove_conference.begin(stream, conference); } } public string? get_room_name(Account account, Jid jid) { Xep.Muc.Flag? flag = get_muc_flag(account); if (flag != null) { return flag.get_room_name(jid); } return null; } public string? get_groupchat_subject(Jid jid, Account account) { Xep.Muc.Flag? flag = get_muc_flag(account); if (flag != null) { return flag.get_muc_subject(jid.bare_jid); } return null; } public Jid? get_real_jid(Jid jid, Account account) { Xep.Muc.Flag? flag = get_muc_flag(account); if (flag != null) { return flag.get_real_jid(jid); } return null; } public Xep.Muc.Role? get_role(Jid jid, Account account) { Xep.Muc.Flag? flag = get_muc_flag(account); if (flag != null) { return flag.get_occupant_role(jid); } return null; } public Xep.Muc.Affiliation? get_affiliation(Jid muc_jid, Jid jid, Account account) { Xep.Muc.Flag? flag = get_muc_flag(account); if (flag != null) { return flag.get_affiliation(muc_jid, jid); } return null; } public Gee.List? get_offline_members(Jid jid, Account account) { Xep.Muc.Flag? flag = get_muc_flag(account); if (flag != null) { return flag.get_offline_members(jid); } return null; } public Gee.List? get_other_offline_members(Jid jid, Account account) { Gee.List? occupants = get_offline_members(jid, account); if (occupants != null) { occupants.remove(account.bare_jid); } return occupants; } public Jid? get_own_jid(Jid muc_jid, Account account) { try { Xep.Muc.Flag? flag = get_muc_flag(account); if (flag != null) { string? nick = flag.get_muc_nick(muc_jid); if (nick != null) return muc_jid.with_resource(nick); } } catch (InvalidJidError e) { warning("Joined MUC with invalid Jid: %s", e.message); } return null; } public bool is_own_muc_jid(Jid full_jid, Account account) { if (!is_groupchat(full_jid.bare_jid, account)) return false; Jid? own_jid = get_own_jid(full_jid, account); return own_jid != null && own_jid.equals(full_jid); } private Xep.Muc.Flag? get_muc_flag(Account account) { XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) { return stream.get_flag(Xep.Muc.Flag.IDENTITY); } return null; } public bool is_joined(Jid jid, Account account) { return get_own_jid(jid, account) != null; } private void on_account_added(Account account) { stream_interactor.module_manager.get_module(account, Xep.Muc.Module.IDENTITY).self_removed_from_room.connect( (stream, jid, code) => { left(account, jid); }); stream_interactor.module_manager.get_module(account, Xep.Muc.Module.IDENTITY).subject_set.connect( (stream, subject, jid) => { subject_set(account, jid, subject); }); stream_interactor.module_manager.get_module(account, Xep.Muc.Module.IDENTITY).invite_received.connect( (stream, room_jid, from_jid, password, reason) => { on_invite_received(account, room_jid, from_jid, password, reason); }); stream_interactor.module_manager.get_module(account, Xep.DirectMucInvitations.Module.IDENTITY).invite_received.connect( (stream, room_jid, from_jid, password, reason) => { on_invite_received(account, room_jid, from_jid, password, reason); }); stream_interactor.module_manager.get_module(account, Xep.Muc.Module.IDENTITY).voice_request_received.connect( (stream, room_jid, from_jid, nick) => { voice_request_received(account, room_jid, from_jid, nick); }); stream_interactor.module_manager.get_module(account, Xep.Muc.Module.IDENTITY).received_occupant_role.connect( (stream, from_jid, role) => { received_occupant_role(account, from_jid, role); }); stream_interactor.module_manager.get_module(account, Xep.Muc.Module.IDENTITY).room_info_updated.connect( (stream, muc_jid) => { room_info_updated(account, muc_jid); }); stream_interactor.module_manager.get_module(account, Xep.Muc.Module.IDENTITY).received_occupant_jid.connect( (stream, room, occupant) => { if (is_private_room(account, room.bare_jid)) { private_room_occupant_updated(account, room, occupant); } }); } private async void search_default_muc_server(Account account) { XmppStream? stream = stream_interactor.get_stream(account); if (stream == null) return; ServiceDiscovery.ItemsResult? items_result = yield stream.get_module(ServiceDiscovery.Module.IDENTITY).request_items(stream, stream.remote_name); if (items_result == null) return; for (int i = 0; i < 2; i++) { foreach (Xep.ServiceDiscovery.Item item in items_result.items) { // First try the promising items and only afterwards all the others bool promising_upload_item = item.jid.to_string().has_prefix("conference") || item.jid.to_string().has_prefix("muc") || item.jid.to_string().has_prefix("chat"); if ((i == 0 && !promising_upload_item) || (i == 1) && promising_upload_item) continue; Gee.Set identities = yield stream_interactor.get_module(EntityInfo.IDENTITY).get_identities(account, item.jid); if (identities == null) return; foreach (Xep.ServiceDiscovery.Identity identity in identities) { if (identity.category == Xep.ServiceDiscovery.Identity.CATEGORY_CONFERENCE) { default_muc_server[account] = item.jid; debug("[%s] Default MUC: %s", account.bare_jid.to_string(), item.jid.to_string()); return; } } } } } private async void on_stream_negotiated(Account account, XmppStream stream) { yield initialize_bookmarks_provider(account); Set? conferences = yield bookmarks_provider[account].get_conferences(stream); if (conferences == null) { join_all_active(account); } else { sync_autojoin_active(account, conferences); } if (!default_muc_server.has_key(account)) { search_default_muc_server.begin(account); } } private async void initialize_bookmarks_provider(Account account) { if (bookmarks_provider.has_key(account)) return; // Use PEP native bookmarks (urn:xmpp:bookmarks:1) if conversion is available, legacy bookmarks (storage:bookmarks) otherwise. bool has_feature = yield stream_interactor.get_module(EntityInfo.IDENTITY).has_feature(account, account.bare_jid, Xep.Bookmarks2.NS_URI_COMPAT); if (has_feature) { debug("[%s] Using PEP native bookmarks (urn:xmpp:bookmarks:1)", account.bare_jid.to_string()); bookmarks_provider[account] = stream_interactor.module_manager.get_module(account, Xep.Bookmarks2.Module.IDENTITY); } else { debug("[%s] Using legacy bookmarks (storage:bookmarks)", account.bare_jid.to_string()); bookmarks_provider[account] = stream_interactor.module_manager.get_module(account, Xep.Bookmarks.Module.IDENTITY); } bookmarks_provider[account].received_conferences.connect( (stream, conferences) => { sync_autojoin_active(account, conferences); bookmarks_updated(account, conferences); }); bookmarks_provider[account].conference_added.connect( (stream, conference) => { on_conference_added(account, conference); }); bookmarks_provider[account].conference_removed.connect( (stream, jid) => { on_conference_removed(account, jid); }); } private void on_invite_received(Account account, Jid room_jid, Jid from_jid, string? password, string? reason) { if (!invites.has_key(account)) { invites[account] = new LinkedList(Jid.equals_func); } if (invites[account].contains(room_jid)) return; invites[account].add(room_jid); invite_received(account, room_jid, from_jid, password, reason); Timeout.add_seconds(5, () => { // We don't want to show the same invite (direct+mediated) twice, but a distinct invite is fine invites[account].remove(room_jid); return false; }); } private void join_all_active(Account account) { Gee.List conversations = stream_interactor.get_module(ConversationManager.IDENTITY).get_active_conversations(account); foreach (Conversation conversation in conversations) { if (conversation.type_ == Conversation.Type.GROUPCHAT && conversation.nickname != null) { join.begin(account, conversation.counterpart, conversation.nickname, null); } } } private void sync_autojoin_active(Account account, Set conferences) { Gee.List active_conversations = stream_interactor.get_module(ConversationManager.IDENTITY).get_active_conversations(account); // Join auto-join MUCs foreach (Conference conference in conferences) { if (!conference.autojoin) continue; bool is_active = false; foreach (Conversation conversation in active_conversations) { if (conference.jid.equals(conversation.counterpart)) { is_active = true; } } if (!is_active || !is_joined(conference.jid, account)) { join.begin(account, conference.jid, conference.nick, conference.password, true); } } // Part MUCs that aren't auto-join (which closes those conversations) foreach (Conversation conversation in active_conversations) { if (conversation.type_ != Conversation.Type.GROUPCHAT) continue; bool should_be_active = false; foreach (Conference conference in conferences) { if (conference.jid.equals(conversation.counterpart) && conference.autojoin) { should_be_active = true; } } if (!should_be_active) { part(conversation.account, conversation.counterpart); } } } private void set_autojoin(Account account, XmppStream stream, Jid jid, string? nick, string? password) { bookmarks_provider[account].get_conferences.begin(stream, (_, res) => { Set? conferences = bookmarks_provider[account].get_conferences.end(res); if (conferences == null) return; foreach (Conference conference in conferences) { if (conference.jid.equals(jid)) { if (!conference.autojoin) { Conference new_conference = new Conference() { jid=jid, nick=nick ?? conference.nick, name=conference.name, password=password ?? conference.password, autojoin=true }; bookmarks_provider[account].replace_conference.begin(stream, jid, new_conference); } return; } } Conference changed = new Xep.Bookmarks.Bookmarks1Conference(jid) { nick=nick, password=password, autojoin=true }; bookmarks_provider[account].add_conference.begin(stream, changed); }); } private void unset_autojoin(Account account, XmppStream stream, Jid jid) { bookmarks_provider[account].get_conferences.begin(stream, (_, res) => { Set? conferences = bookmarks_provider[account].get_conferences.end(res); if (conferences == null) return; foreach (Conference conference in conferences) { if (conference.jid.equals(jid)) { if (conference.autojoin) { Conference new_conference = new Conference() { jid=jid, nick=conference.nick, name=conference.name, password=conference.password, autojoin=false }; bookmarks_provider[account].replace_conference.begin(stream, jid, new_conference); return; } } } }); } private void on_conference_added(Account account, Xmpp.Conference conference) { Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(conference.jid, account, Conversation.Type.GROUPCHAT); if (conversation != null) { if (!conversation.active && conference.autojoin) { join.begin(account, conference.jid, conference.nick, conference.password); } else if (conversation.active && !conference.autojoin) { part(account, conference.jid); } } if (conference.autojoin) { join.begin(account, conference.jid, conference.nick, conference.password); } conference_added(account, conference); } private void on_conference_removed(Account account, Jid jid) { Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(jid, account, Conversation.Type.GROUPCHAT); if (conversation != null && conversation.active) { part(account, jid); } conference_removed(account, jid); } private void self_ping(Account account) { XmppStream? stream = stream_interactor.get_stream(account); if (stream == null) return; if (!mucs_todo.has_key(account)) return; foreach (Jid jid in mucs_todo[account]) { bool joined = false; Xmpp.Xep.MucSelfPing.is_joined.begin(stream, jid, (_, res) => { joined = Xmpp.Xep.MucSelfPing.is_joined.end(res); }); Timeout.add_seconds(10, () => { if (joined || !mucs_todo.has_key(account) || stream_interactor.get_stream(account) != stream) return false; join.begin(account, jid.bare_jid, jid.resourcepart, null, true); return false; }); } } private class ReceivedMessageListener : MessageListener { public string[] after_actions_const = new string[]{ }; public override string action_group { get { return "MUC"; } } public override string[] after_actions { get { return after_actions_const; } } private StreamInteractor stream_interactor; public ReceivedMessageListener(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; } public override async bool run(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) { if (conversation.type_ != Conversation.Type.GROUPCHAT) return false; XmppStream stream = stream_interactor.get_stream(conversation.account); if (stream == null) return false; if (Xep.DelayedDelivery.MessageFlag.get_flag(stanza) == null) { Jid? real_jid = stream.get_flag(Xep.Muc.Flag.IDENTITY).get_real_jid(message.counterpart); if (real_jid != null && !real_jid.equals(message.counterpart)) { message.real_jid = real_jid.bare_jid; } } Jid? own_muc_jid = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(message.counterpart.bare_jid, conversation.account); if (stanza.id != null && own_muc_jid != null && message.from.equals(own_muc_jid)) { Entities.Message? m = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_stanza_id(stanza.id, conversation); if (m != null) { // For own messages from this device (msg is a duplicate) m.marked = Message.Marked.RECEIVED; } // For own messages from other devices (msg is not a duplicate msg) message.marked = Message.Marked.RECEIVED; } return false; } } } } dino-0.3.0/libdino/src/service/notification_events.vala0000644000000000000000000002315214202022370021722 0ustar rootrootusing Gee; using Dino.Entities; using Xmpp; namespace Dino { public class NotificationEvents : StreamInteractionModule, Object { public static ModuleIdentity IDENTITY = new ModuleIdentity("notification_events"); public string id { get { return IDENTITY.id; } } public signal void notify_content_item(ContentItem content_item, Conversation conversation); private StreamInteractor stream_interactor; private Future notifier; private Promise notifier_promise; private bool notifier_outstanding = true; public static void start(StreamInteractor stream_interactor) { NotificationEvents m = new NotificationEvents(stream_interactor); stream_interactor.add_module(m); } public NotificationEvents(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; stream_interactor.get_module(ContentItemStore.IDENTITY).new_item.connect((item, conversation) => on_content_item_received.begin(item, conversation)); stream_interactor.get_module(PresenceManager.IDENTITY).received_subscription_request.connect((jid, account) => on_received_subscription_request.begin(jid, account)); stream_interactor.get_module(MucManager.IDENTITY).invite_received.connect((account, room_jid, from_jid, password, reason) => on_invite_received.begin(account, room_jid, from_jid, password, reason)); stream_interactor.get_module(MucManager.IDENTITY).voice_request_received.connect((account, room_jid, from_jid, nick) => on_voice_request_received.begin(account, room_jid, from_jid, nick)); stream_interactor.get_module(Calls.IDENTITY).call_incoming.connect((call, state, conversation, video, multiparty) => on_call_incoming.begin(call, state, conversation, video, multiparty)); stream_interactor.connection_manager.connection_error.connect((account, error) => on_connection_error.begin(account, error)); stream_interactor.get_module(ChatInteraction.IDENTITY).focused_in.connect((conversation) => on_focused_in.begin(conversation)); notifier_promise = new Promise(); notifier = notifier_promise.future; } public async void register_notification_provider(NotificationProvider notification_provider) { if (notifier_outstanding || (yield notifier.wait_async()).get_priority() < notification_provider.get_priority()) { notifier_outstanding = false; notifier_promise.set_value(notification_provider); } } private async void on_content_item_received(ContentItem item, Conversation conversation) { ContentItem last_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_latest(conversation); if (item.id != last_item.id) return; if (item.id == conversation.read_up_to_item) return; if (stream_interactor.get_module(ChatInteraction.IDENTITY).is_active_focus()) return; Conversation.NotifySetting notify = conversation.get_notification_setting(stream_interactor); if (notify == Conversation.NotifySetting.OFF) return; string conversation_display_name = get_conversation_display_name(stream_interactor, conversation, null); string? participant_display_name = null; if (conversation.type_ == Conversation.Type.GROUPCHAT) { participant_display_name = get_participant_display_name(stream_interactor, conversation, item.jid); } switch (item.type_) { case MessageItem.TYPE: Message message = ((MessageItem) item).message; if (message.direction == Message.DIRECTION_SENT) return; if (notify == Conversation.NotifySetting.HIGHLIGHT) { Jid? nick = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account); if (nick == null) return; bool highlight = Regex.match_simple("\\b" + Regex.escape_string(nick.resourcepart) + "\\b", message.body, RegexCompileFlags.CASELESS); if (!highlight) return; } notify_content_item(item, conversation); if (notify != Conversation.NotifySetting.OFF) { NotificationProvider notifier = yield notifier.wait_async(); yield notifier.notify_message(message, conversation, conversation_display_name, participant_display_name); } break; case FileItem.TYPE: FileTransfer file_transfer = ((FileItem) item).file_transfer; bool is_image = file_transfer.mime_type != null && file_transfer.mime_type.has_prefix("image"); // Don't notify on file transfers in a groupchat set to "mention only" if (notify == Conversation.NotifySetting.HIGHLIGHT) return; if (file_transfer.direction == FileTransfer.DIRECTION_SENT) return; notify_content_item(item, conversation); if (notify != Conversation.NotifySetting.OFF) { NotificationProvider notifier = yield notifier.wait_async(); yield notifier.notify_file(file_transfer, conversation, is_image, conversation_display_name, participant_display_name); } break; case CallItem.TYPE: // handled in `on_call_incoming` break; } } private async void on_voice_request_received(Account account, Jid room_jid, Jid from_jid, string nick) { Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(room_jid, account, Conversation.Type.GROUPCHAT); if (conversation == null) return; NotificationProvider notifier = yield notifier.wait_async(); yield notifier.notify_voice_request(conversation, from_jid); } private async void on_received_subscription_request(Jid jid, Account account) { Conversation conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(jid, account, Conversation.Type.CHAT); if (stream_interactor.get_module(ChatInteraction.IDENTITY).is_active_focus(conversation)) return; NotificationProvider notifier = yield notifier.wait_async(); yield notifier.notify_subscription_request(conversation); } private async void on_call_incoming(Call call, CallState call_state, Conversation conversation, bool video, bool multiparty) { if (!stream_interactor.get_module(Calls.IDENTITY).can_we_do_calls(call.account)) return; string conversation_display_name = get_conversation_display_name(stream_interactor, conversation, null); NotificationProvider notifier = yield notifier.wait_async(); yield notifier.notify_call(call, conversation, video, multiparty, conversation_display_name); call.notify["state"].connect(() => { if (call.state != Call.State.RINGING) { notifier.retract_call_notification.begin(call, conversation); } }); } private async void on_invite_received(Account account, Jid room_jid, Jid from_jid, string? password, string? reason) { string inviter_display_name; if (room_jid.equals_bare(from_jid)) { Conversation conversation = new Conversation(room_jid, account, Conversation.Type.GROUPCHAT); inviter_display_name = get_participant_display_name(stream_interactor, conversation, from_jid); } else { Conversation direct_conversation = new Conversation(from_jid, account, Conversation.Type.CHAT); inviter_display_name = get_participant_display_name(stream_interactor, direct_conversation, from_jid); } NotificationProvider notifier = yield notifier.wait_async(); yield notifier.notify_muc_invite(account, room_jid, from_jid, inviter_display_name); } private async void on_connection_error(Account account, ConnectionManager.ConnectionError error) { NotificationProvider notifier = yield notifier.wait_async(); yield notifier.notify_connection_error(account, error); } private async void on_focused_in(Conversation conversation) { NotificationProvider notifier = yield notifier.wait_async(); yield notifier.retract_content_item_notifications(); yield notifier.retract_conversation_notifications(conversation); } } public interface NotificationProvider : Object { public abstract double get_priority(); public abstract async void notify_message(Message message, Conversation conversation, string conversation_display_name, string? participant_display_name); public abstract async void notify_file(FileTransfer file_transfer, Conversation conversation, bool is_image, string conversation_display_name, string? participant_display_name); public abstract async void notify_call(Call call, Conversation conversation, bool video, bool multiparty, string conversation_display_name); public abstract async void retract_call_notification(Call call, Conversation conversation); public abstract async void notify_subscription_request(Conversation conversation); public abstract async void notify_connection_error(Account account, ConnectionManager.ConnectionError error); public abstract async void notify_muc_invite(Account account, Jid room_jid, Jid from_jid, string inviter_display_name); public abstract async void notify_voice_request(Conversation conversation, Jid from_jid); public abstract async void retract_content_item_notifications(); public abstract async void retract_conversation_notifications(Conversation conversation); } } dino-0.3.0/libdino/src/service/presence_manager.vala0000644000000000000000000001174314202022370021151 0ustar rootrootusing Gee; using Xmpp; using Dino.Entities; namespace Dino { public class PresenceManager : StreamInteractionModule, Object { public static ModuleIdentity IDENTITY = new ModuleIdentity("presence_manager"); public string id { get { return IDENTITY.id; } } public signal void show_received(Jid jid, Account account); public signal void received_offline_presence(Jid jid, Account account); public signal void received_subscription_request(Jid jid, Account account); public signal void received_subscription_approval(Jid jid, Account account); private StreamInteractor stream_interactor; private HashMap> resources = new HashMap>(Jid.hash_bare_func, Jid.equals_bare_func); private Gee.List subscription_requests = new ArrayList(Jid.equals_func); public static void start(StreamInteractor stream_interactor) { PresenceManager m = new PresenceManager(stream_interactor); stream_interactor.add_module(m); } private PresenceManager(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; stream_interactor.account_added.connect(on_account_added); } public string? get_last_show(Jid jid, Account account) { XmppStream? stream = stream_interactor.get_stream(account); if (stream == null) return null; Xmpp.Presence.Stanza? presence = stream.get_flag(Presence.Flag.IDENTITY).get_presence(jid); if (presence == null) return null; return presence.show; } public Gee.List? get_full_jids(Jid jid, Account account) { XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) { Xmpp.Presence.Flag flag = stream.get_flag(Presence.Flag.IDENTITY); if (flag == null) return null; return flag.get_resources(jid.bare_jid); } return null; } public bool exists_subscription_request(Account account, Jid jid) { return subscription_requests.contains(jid); } public void request_subscription(Account account, Jid jid) { XmppStream stream = stream_interactor.get_stream(account); if (stream != null) stream.get_module(Xmpp.Presence.Module.IDENTITY).request_subscription(stream, jid.bare_jid); } public void approve_subscription(Account account, Jid jid) { XmppStream stream = stream_interactor.get_stream(account); if (stream != null) { stream.get_module(Xmpp.Presence.Module.IDENTITY).approve_subscription(stream, jid.bare_jid); subscription_requests.remove(jid); } } public void deny_subscription(Account account, Jid jid) { XmppStream stream = stream_interactor.get_stream(account); if (stream != null) { stream.get_module(Xmpp.Presence.Module.IDENTITY).deny_subscription(stream, jid.bare_jid); subscription_requests.remove(jid); } } public void cancel_subscription(Account account, Jid jid) { XmppStream stream = stream_interactor.get_stream(account); if (stream != null) stream.get_module(Xmpp.Presence.Module.IDENTITY).cancel_subscription(stream, jid.bare_jid); } private void on_account_added(Account account) { stream_interactor.module_manager.get_module(account, Presence.Module.IDENTITY).received_available_show.connect((stream, jid, show) => on_received_available_show(account, jid, show) ); stream_interactor.module_manager.get_module(account, Presence.Module.IDENTITY).received_unavailable.connect((stream, presence) => on_received_unavailable(account, presence.from) ); stream_interactor.module_manager.get_module(account, Presence.Module.IDENTITY).received_subscription_request.connect((stream, jid) => { if (!subscription_requests.contains(jid)) { subscription_requests.add(jid); } received_subscription_request(jid, account); }); stream_interactor.module_manager.get_module(account, Presence.Module.IDENTITY).received_subscription_approval.connect((stream, jid) => { received_subscription_approval(jid, account); }); } private void on_received_available_show(Account account, Jid jid, string show) { lock (resources) { if (!resources.has_key(jid)){ resources[jid] = new ArrayList(Jid.equals_func); } if (!resources[jid].contains(jid)) { resources[jid].add(jid); } } show_received(jid, account); } private void on_received_unavailable(Account account, Jid jid) { lock (resources) { if (resources.has_key(jid)) { resources[jid].remove(jid); if (resources[jid].size == 0 || jid.is_bare()) { resources.unset(jid); } } } received_offline_presence(jid, account); } } } dino-0.3.0/libdino/src/service/registration.vala0000644000000000000000000001725114202022370020365 0ustar rootrootusing Gee; using Xmpp; using Dino.Entities; namespace Dino { public class Register : StreamInteractionModule, Object{ public static ModuleIdentity IDENTITY = new ModuleIdentity("registration"); public string id { get { return IDENTITY.id; } } private StreamInteractor stream_interactor; private Database db; public static void start(StreamInteractor stream_interactor, Database db) { Register m = new Register(stream_interactor, db); stream_interactor.add_module(m); } private Register(StreamInteractor stream_interactor, Database db) { this.stream_interactor = stream_interactor; this.db = db; } public async ConnectionManager.ConnectionError.Source? add_check_account(Account account) { ConnectionManager.ConnectionError.Source? ret = null; Gee.List list = new ArrayList(); list.add(new Iq.Module()); list.add(new Sasl.Module(account.bare_jid.to_string(), account.password)); XmppStreamResult stream_result = yield Xmpp.establish_stream(account.bare_jid.domain_jid, list, Application.print_xmpp, (peer_cert, errors) => { return ConnectionManager.on_invalid_certificate(account.domainpart, peer_cert, errors); } ); if (stream_result.stream == null) { if (stream_result.tls_errors != null) { ret = ConnectionManager.ConnectionError.Source.TLS; } return ret; } XmppStream stream = stream_result.stream; SourceFunc callback = add_check_account.callback; stream.stream_negotiated.connect(() => { if (callback == null) return; Idle.add((owned)callback); }); stream.get_module(Sasl.Module.IDENTITY).received_auth_failure.connect((stream, node) => { if (callback == null) return; ret = ConnectionManager.ConnectionError.Source.SASL; Idle.add((owned)callback); }); stream.loop.begin((_, res) => { try { stream.loop.end(res); } catch (Error e) { debug("Error connecting to stream: %s", e.message); } if (callback != null) { ret = ConnectionManager.ConnectionError.Source.CONNECTION; Idle.add((owned)callback); } }); yield; try { yield stream_result.stream.disconnect(); } catch (Error e) {} return ret; } public class ServerAvailabilityReturn { public bool available { get; set; } public TlsCertificateFlags? error_flags { get; set; } } public static async ServerAvailabilityReturn check_server_availability(Jid jid) { ServerAvailabilityReturn ret = new ServerAvailabilityReturn() { available=false }; Gee.List list = new ArrayList(); list.add(new Iq.Module()); XmppStreamResult stream_result = yield Xmpp.establish_stream(jid.domain_jid, list, Application.print_xmpp, (peer_cert, errors) => { return ConnectionManager.on_invalid_certificate(jid.domainpart, peer_cert, errors); } ); if (stream_result.stream == null) { if (stream_result.io_error != null) { debug("Error connecting to stream: %s", stream_result.io_error.message); } if (stream_result.tls_errors != null) { ret.error_flags = stream_result.tls_errors; } return ret; } XmppStream stream = stream_result.stream; SourceFunc callback = check_server_availability.callback; stream.stream_negotiated.connect(() => { if (callback != null) { ret.available = true; Idle.add((owned)callback); } }); stream.loop.begin((_, res) => { try { stream.loop.end(res); } catch (Error e) { debug("Error connecting to stream: %s", e.message); } if (callback != null) { Idle.add((owned)callback); } }); yield; try { yield stream.disconnect(); } catch (Error e) {} return ret; } public class RegistrationFormReturn { public Xep.InBandRegistration.Form? form { get; set; } public TlsCertificateFlags? error_flags { get; set; } } public static async RegistrationFormReturn get_registration_form(Jid jid) { RegistrationFormReturn ret = new RegistrationFormReturn(); Gee.List list = new ArrayList(); list.add(new Iq.Module()); list.add(new Xep.InBandRegistration.Module()); XmppStreamResult stream_result = yield Xmpp.establish_stream(jid.domain_jid, list, Application.print_xmpp, (peer_cert, errors) => { return ConnectionManager.on_invalid_certificate(jid.domainpart, peer_cert, errors); } ); if (stream_result.stream == null) { if (stream_result.io_error != null) { debug("Error connecting to stream: %s", stream_result.io_error.message); } if (stream_result.tls_errors != null) { ret.error_flags = stream_result.tls_errors; } return ret; } XmppStream stream = stream_result.stream; SourceFunc callback = get_registration_form.callback; stream.stream_negotiated.connect(() => { if (callback != null) { Idle.add((owned)callback); } }); stream.loop.begin((_, res) => { try { stream.loop.end(res); } catch (Error e) { debug("Error connecting to stream: %s", e.message); } if (callback != null) { Idle.add((owned)callback); } }); yield; if (stream.negotiation_complete) { ret.form = yield stream.get_module(Xep.InBandRegistration.Module.IDENTITY).get_from_server(stream, jid); } try { yield stream.disconnect(); } catch (Error e) {} return ret; } public static async string? submit_form(Jid jid, Xep.InBandRegistration.Form form) { Gee.List list = new ArrayList(); list.add(new Iq.Module()); list.add(new Xep.InBandRegistration.Module()); XmppStreamResult stream_result = yield Xmpp.establish_stream(jid.domain_jid, list, Application.print_xmpp, (peer_cert, errors) => { return ConnectionManager.on_invalid_certificate(jid.domainpart, peer_cert, errors); } ); if (stream_result.stream == null) { return null; } XmppStream stream = stream_result.stream; SourceFunc callback = submit_form.callback; stream.stream_negotiated.connect(() => { if (callback != null) { Idle.add((owned)callback); } }); stream.loop.begin((_, res) => { try { stream.loop.end(res); } catch (Error e) { debug("Error connecting to stream: %s", e.message); } if (callback != null) { Idle.add((owned)callback); } }); yield; string? ret = null; if (stream.negotiation_complete) { ret = yield stream.get_module(Xep.InBandRegistration.Module.IDENTITY).submit_to_server(stream, jid, form); } try { yield stream.disconnect(); } catch (Error e) {} return ret; } } } dino-0.3.0/libdino/src/service/roster_manager.vala0000644000000000000000000001341214202022370020656 0ustar rootrootusing Gee; using Xmpp; using Dino.Entities; namespace Dino { public class RosterManager : StreamInteractionModule, Object { public static ModuleIdentity IDENTITY = new ModuleIdentity("roster_manager"); public string id { get { return IDENTITY.id; } } public signal void removed_roster_item(Account account, Jid jid, Roster.Item roster_item); public signal void updated_roster_item(Account account, Jid jid, Roster.Item roster_item); public signal void mutual_subscription(Account account, Jid jid); private StreamInteractor stream_interactor; private Database db; private Gee.Map roster_stores = new HashMap(Account.hash_func, Account.equals_func); public static void start(StreamInteractor stream_interactor, Database db) { RosterManager m = new RosterManager(stream_interactor, db); stream_interactor.add_module(m); } public RosterManager(StreamInteractor stream_interactor, Database db) { this.stream_interactor = stream_interactor; this.db = db; stream_interactor.account_added.connect(on_account_added); stream_interactor.module_manager.initialize_account_modules.connect((account, modules) => { if (!roster_stores.has_key(account)) roster_stores[account] = new RosterStoreImpl(account, db); modules.add(new Roster.VersioningModule(roster_stores[account])); }); } public Collection get_roster(Account account) { if (roster_stores[account] == null) return new ArrayList(); return roster_stores[account].get_roster(); } public Roster.Item? get_roster_item(Account account, Jid jid) { if (roster_stores[account] == null) return null; return roster_stores[account].get_item(jid); } public void remove_jid(Account account, Jid jid) { XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) stream.get_module(Xmpp.Roster.Module.IDENTITY).remove_jid(stream, jid.bare_jid); } public void add_jid(Account account, Jid jid, string? handle) { XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) stream.get_module(Xmpp.Roster.Module.IDENTITY).add_jid(stream, jid.bare_jid, handle); } public void set_jid_handle(Account account, Jid jid, string? handle) { XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) stream.get_module(Xmpp.Roster.Module.IDENTITY).set_jid_handle(stream, jid.bare_jid, handle); } private void on_account_added(Account account) { stream_interactor.module_manager.get_module(account, Roster.Module.IDENTITY).received_roster.connect_after( (stream, roster) => { foreach (Roster.Item roster_item in roster) { on_roster_item_updated(account, roster_item); } }); stream_interactor.module_manager.get_module(account, Roster.Module.IDENTITY).item_removed.connect_after( (stream, roster_item) => { removed_roster_item(account, roster_item.jid, roster_item); }); stream_interactor.module_manager.get_module(account, Roster.Module.IDENTITY).item_updated.connect_after( (stream, roster_item) => { on_roster_item_updated(account, roster_item); }); stream_interactor.module_manager.get_module(account, Roster.Module.IDENTITY).mutual_subscription.connect_after( (stream, jid) => { mutual_subscription(account, jid); }); } private void on_roster_item_updated(Account account, Roster.Item roster_item) { updated_roster_item(account, roster_item.jid, roster_item); } } public class RosterStoreImpl : Roster.Storage, Object { private Account account; private Database db; private HashMap items = new HashMap(Jid.hash_bare_func, Jid.equals_bare_func); public class RosterStoreImpl(Account account, Database db) { this.account = account; this.db = db; foreach (Qlite.Row row in db.roster.select().with(db.roster.account_id, "=", account.id)) { try { Roster.Item item = new Roster.Item(); item.jid = new Jid(row[db.roster.jid]); item.name = row[db.roster.handle]; item.subscription = row[db.roster.subscription]; items[item.jid] = item; } catch (InvalidJidError e) { warning("Ignoring roster entry with invalid Jid: %s", e.message); } } } public string? get_roster_version() { return account.roster_version; } public Collection get_roster() { return items.values; } public Roster.Item? get_item(Jid jid) { return items.has_key(jid) ? items[jid] : null; } public void set_roster_version(string version) { account.roster_version = version; } public void set_roster(Collection items) { db.roster.delete().with(db.roster.account_id, "=", account.id).perform(); foreach (Roster.Item item in items) { set_item(item); } } public void set_item(Roster.Item item) { items[item.jid] = item; db.roster.upsert() .value(db.roster.account_id, account.id, true) .value(db.roster.jid, item.jid.to_string(), true) .value(db.roster.handle, item.name) .value(db.roster.subscription, item.subscription) .perform(); } public void remove_item(Roster.Item item) { items.unset(item.jid); db.roster.delete() .with(db.roster.account_id, "=", account.id) .with(db.roster.jid, "=", item.jid.to_string()).perform(); } } } dino-0.3.0/libdino/src/service/search_processor.vala0000644000000000000000000003553514202022370021224 0ustar rootrootusing Gee; using Xmpp; using Qlite; using Dino.Entities; namespace Dino { public class SearchProcessor : StreamInteractionModule, Object { public static ModuleIdentity IDENTITY = new ModuleIdentity("search_processor"); public string id { get { return IDENTITY.id; } } private StreamInteractor stream_interactor; private Database db; public static void start(StreamInteractor stream_interactor, Database db) { SearchProcessor m = new SearchProcessor(stream_interactor, db); stream_interactor.add_module(m); } public SearchProcessor(StreamInteractor stream_interactor, Database db) { this.stream_interactor = stream_interactor; this.db = db; } private QueryBuilder prepare_search(string query, bool join_content) { string words = ""; string? with = null; string? in_ = null; string? from = null; foreach(string word in query.split(" ")) { if (word.has_prefix("with:")) { if (with == null) { @with = word.substring(5); } else { return db.message.select().where("0"); } } else if (word.has_prefix("in:")) { if (in_ == null) { in_ = word.substring(3); } else { return db.message.select().where("0"); } } else if (word.has_prefix("from:")) { if (from == null) { from = word.substring(5); } else { return db.message.select().where("0"); } } else { words += word + "* "; } } if (in_ != null && with != null) { return db.message.select().where("0"); } QueryBuilder rows = db.message .match(db.message.body, words) .order_by(db.message.id, "DESC") .join_with(db.jid, db.jid.id, db.message.counterpart_id) .join_with(db.account, db.account.id, db.message.account_id) .outer_join_with(db.real_jid, db.real_jid.message_id, db.message.id) .with(db.account.enabled, "=", true); if (join_content) { rows.join_on(db.content_item, "message.id=content_item.foreign_id AND content_item.content_type=1") .with(db.content_item.content_type, "=", 1); } if (with != null) { if (with.index_of("/") > 0) { rows.with(db.message.type_, "=", Message.Type.GROUPCHAT_PM) .with(db.jid.bare_jid, "LIKE", with.substring(0, with.index_of("/"))) .with(db.message.counterpart_resource, "LIKE", with.substring(with.index_of("/") + 1)); } else { rows.where(@"($(db.message.type_) = $((int)Message.Type.CHAT) AND $(db.jid.bare_jid) LIKE ?)" + @" OR ($(db.message.type_) = $((int)Message.Type.GROUPCHAT_PM) AND $(db.real_jid.real_jid) LIKE ?)" + @" OR ($(db.message.type_) = $((int)Message.Type.GROUPCHAT_PM) AND $(db.message.counterpart_resource) LIKE ?)", {with, with, with}); } } else if (in_ != null) { rows.with(db.jid.bare_jid, "LIKE", in_) .with(db.message.type_, "=", Message.Type.GROUPCHAT); } if (from != null) { rows.where(@"($(db.message.direction) = 1 AND $(db.account.bare_jid) LIKE ?)" + @" OR ($(db.message.direction) = 1 AND $(db.message.type_) IN ($((int)Message.Type.GROUPCHAT), $((int)Message.Type.GROUPCHAT_PM)) AND $(db.message.our_resource) LIKE ?)" + @" OR ($(db.message.direction) = 0 AND $(db.message.type_) = $((int)Message.Type.CHAT) AND $(db.jid.bare_jid) LIKE ?)" + @" OR ($(db.message.direction) = 0 AND $(db.message.type_) IN ($((int)Message.Type.GROUPCHAT), $((int)Message.Type.GROUPCHAT_PM)) AND $(db.real_jid.real_jid) LIKE ?)" + @" OR ($(db.message.direction) = 0 AND $(db.message.type_) IN ($((int)Message.Type.GROUPCHAT), $((int)Message.Type.GROUPCHAT_PM)) AND $(db.message.counterpart_resource) LIKE ?)", {from, from, from, from, from}); } return rows; } public Gee.List suggest_auto_complete(string query, int cursor_position, int limit = 5) { int after_prev_space = query.substring(0, cursor_position).last_index_of(" ") + 1; int next_space = query.index_of(" ", after_prev_space); if (next_space < 0) next_space = query.length; string current_query = query.substring(after_prev_space, next_space - after_prev_space); Gee.List suggestions = new ArrayList(); if (current_query.has_prefix("from:")) { if (cursor_position < after_prev_space + 5) return suggestions; string current_from = current_query.substring(5); string[] splitted = query.split(" "); foreach(string s in splitted) { if (s.has_prefix("from:") && s != "from:" + current_from) { // Already have an from: filter -> no useful autocompletion possible return suggestions; } } string? current_in = null; string? current_with = null; foreach(string s in splitted) { if (s.has_prefix("in:")) { current_in = s.substring(3); } else if (s.has_prefix("with:")) { current_with = s.substring(5); } } if (current_in != null && current_with != null) { // in: and with: -> no useful autocompletion possible return suggestions; } if (current_with != null) { // Can only be the other one or us // Normal chat QueryBuilder chats = db.conversation.select() .join_with(db.jid, db.jid.id, db.conversation.jid_id) .join_with(db.account, db.account.id, db.conversation.account_id) .with(db.jid.bare_jid, "=", current_with) .with(db.account.enabled, "=", true) .with(db.conversation.type_, "=", Conversation.Type.CHAT) .order_by(db.conversation.last_active, "DESC"); foreach(Row chat in chats) { try { if (suggestions.size == 0) { suggestions.add(new SearchSuggestion(new Conversation.from_row(db, chat), new Jid(chat[db.jid.bare_jid]), "from:"+chat[db.jid.bare_jid], after_prev_space, next_space)); } suggestions.add(new SearchSuggestion(new Conversation.from_row(db, chat), new Jid(chat[db.account.bare_jid]), "from:"+chat[db.account.bare_jid], after_prev_space, next_space)); } catch (InvalidJidError e) { warning("Ignoring search suggestion with invalid Jid: %s", e.message); } } return suggestions; } if (current_in != null) { // All members of the MUC with history QueryBuilder msgs = db.message.select() .select_string(@"account.*, $(db.message.counterpart_resource), conversation.*") .join_with(db.jid, db.jid.id, db.message.counterpart_id) .join_with(db.account, db.account.id, db.message.account_id) .join_on(db.conversation, @"$(db.conversation.account_id)=$(db.account.id) AND $(db.conversation.jid_id)=$(db.jid.id)") .with(db.jid.bare_jid, "=", current_in) .with(db.account.enabled, "=", true) .with(db.message.type_, "=", Message.Type.GROUPCHAT) .with(db.conversation.type_, "=", Conversation.Type.GROUPCHAT) .with(db.message.counterpart_resource, "LIKE", @"%$current_from%") .group_by({db.message.counterpart_resource}) .order_by_name(@"MAX($(db.message.time))", "DESC") .limit(5); foreach(Row msg in msgs) { try { suggestions.add(new SearchSuggestion(new Conversation.from_row(db, msg), new Jid(current_in).with_resource(msg[db.message.counterpart_resource]), "from:"+msg[db.message.counterpart_resource], after_prev_space, next_space)); } catch (InvalidJidError e) { warning("Ignoring search suggestion with invalid Jid: %s", e.message); } } } // TODO: auto complete from } else if (current_query.has_prefix("with:")) { if (cursor_position < after_prev_space + 5) return suggestions; string current_with = current_query.substring(5); string[] splitted = query.split(" "); foreach(string s in splitted) { if ((s.has_prefix("with:") && s != "with:" + current_with) || s.has_prefix("in:")) { // Already have an in: or with: filter -> no useful autocompletion possible return suggestions; } } // Normal chat QueryBuilder chats = db.conversation.select() .join_with(db.jid, db.jid.id, db.conversation.jid_id) .join_with(db.account, db.account.id, db.conversation.account_id) .outer_join_on(db.roster, @"$(db.jid.bare_jid) = $(db.roster.jid) AND $(db.account.id) = $(db.roster.account_id)") .where(@"$(db.jid.bare_jid) LIKE ? OR $(db.roster.handle) LIKE ?", {@"%$current_with%", @"%$current_with%"}) .with(db.account.enabled, "=", true) .with(db.conversation.type_, "=", Conversation.Type.CHAT) .order_by(db.conversation.last_active, "DESC") .limit(limit); foreach(Row chat in chats) { try { suggestions.add(new SearchSuggestion(new Conversation.from_row(db, chat), new Jid(chat[db.jid.bare_jid]), "with:"+chat[db.jid.bare_jid], after_prev_space, next_space) { order = chat[db.conversation.last_active]}); } catch (InvalidJidError e) { warning("Ignoring search suggestion with invalid Jid: %s", e.message); } } // Groupchat PM if (suggestions.size < 5) { chats = db.conversation.select() .join_with(db.jid, db.jid.id, db.conversation.jid_id) .join_with(db.account, db.account.id, db.conversation.account_id) .where(@"$(db.jid.bare_jid) LIKE ? OR $(db.conversation.resource) LIKE ?", {@"%$current_with%", @"%$current_with%"}) .with(db.account.enabled, "=", true) .with(db.conversation.type_, "=", Conversation.Type.GROUPCHAT_PM) .order_by(db.conversation.last_active, "DESC") .limit(limit - suggestions.size); foreach(Row chat in chats) { try { suggestions.add(new SearchSuggestion(new Conversation.from_row(db, chat), new Jid(chat[db.jid.bare_jid]).with_resource(chat[db.conversation.resource]), "with:"+chat[db.jid.bare_jid]+"/"+chat[db.conversation.resource], after_prev_space, next_space) { order = chat[db.conversation.last_active]}); } catch (InvalidJidError e) { warning("Ignoring search suggestion with invalid Jid: %s", e.message); } } suggestions.sort((a, b) => (int)(b.order - a.order)); } } else if (current_query.has_prefix("in:")) { if (cursor_position < after_prev_space + 3) return suggestions; string current_in = current_query.substring(3); string[] splitted = query.split(" "); foreach(string s in splitted) { if ((s.has_prefix("in:") && s != "in:" + current_in) || s.has_prefix("with:")) { // Already have an in: or with: filter -> no useful autocompletion possible return suggestions; } } QueryBuilder groupchats = db.conversation.select() .join_with(db.jid, db.jid.id, db.conversation.jid_id) .join_with(db.account, db.account.id, db.conversation.account_id) .with(db.jid.bare_jid, "LIKE", @"%$current_in%") .with(db.account.enabled, "=", true) .with(db.conversation.type_, "=", Conversation.Type.GROUPCHAT) .order_by(db.conversation.last_active, "DESC") .limit(limit); foreach(Row chat in groupchats) { try { suggestions.add(new SearchSuggestion(new Conversation.from_row(db, chat), new Jid(chat[db.jid.bare_jid]), "in:"+chat[db.jid.bare_jid], after_prev_space, next_space)); } catch (InvalidJidError e) { warning("Ignoring search suggestion with invalid Jid: %s", e.message); } } } else { // Other auto complete? } return suggestions; } public Gee.List match_messages(string query, int offset = -1) { Gee.List ret = new ArrayList(); QueryBuilder rows = prepare_search(query, true).limit(10); if (offset > 0) { rows.offset(offset); } foreach (Row row in rows) { try { Message message = new Message.from_row(db, row); Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation_for_message(message); ret.add(new MessageItem(message, conversation, row[db.content_item.id])); } catch (InvalidJidError e) { warning("Ignoring search result with invalid Jid: %s", e.message); } } return ret; } public int count_match_messages(string query) { return (int)prepare_search(query, false).select({db.message.id}).count(); } } public class SearchSuggestion : Object { public Account account { get { return conversation.account; } } public Conversation conversation { get; private set; } public Jid? jid { get; private set; } public string completion { get; private set; } public int start_index { get; private set; } public int end_index { get; private set; } public long order { get; set; } public SearchSuggestion(Conversation conversation, Jid? jid, string completion, int start_index, int end_index) { this.conversation = conversation; this.jid = jid; this.completion = completion; this.start_index = start_index; this.end_index = end_index; } } } dino-0.3.0/libdino/src/service/stream_interactor.vala0000644000000000000000000000631114202022370021373 0ustar rootrootusing Gee; using Xmpp; using Dino.Entities; namespace Dino { public class StreamInteractor : Object { public signal void account_added(Account account); public signal void account_removed(Account account); public signal void stream_resumed(Account account, XmppStream stream); public signal void stream_negotiated(Account account, XmppStream stream); public signal void stream_attached_modules(Account account, XmppStream stream); public ModuleManager module_manager; public ConnectionManager connection_manager; private ArrayList modules = new ArrayList(); public StreamInteractor(Database db) { module_manager = new ModuleManager(); connection_manager = new ConnectionManager(module_manager); connection_manager.stream_opened.connect(on_stream_opened); connection_manager.stream_attached_modules.connect((account, stream) => { stream_attached_modules(account, stream); }); } public void connect_account(Account account) { module_manager.initialize(account); account_added(account); connection_manager.connect_account(account); } public async void disconnect_account(Account account) { yield connection_manager.disconnect_account(account); account_removed(account); } public ArrayList get_accounts() { ArrayList ret = new ArrayList(Account.equals_func); foreach (Account account in connection_manager.get_managed_accounts()) { ret.add(account); } return ret; } public XmppStream? get_stream(Account account) { return connection_manager.get_stream(account); } public void add_module(StreamInteractionModule module) { modules.add(module); } public T? get_module(ModuleIdentity? identity) { if (identity == null) return null; foreach (StreamInteractionModule module in modules) { if (identity.matches(module)) return identity.cast(module); } return null; } public new T? get() { foreach (StreamInteractionModule module in modules) { if (module.get_type() == typeof(T)) return (T?) module; } return null; } private void on_stream_opened(Account account, XmppStream stream) { stream.stream_negotiated.connect( (stream) => { var flag = stream.get_flag(Xep.StreamManagement.Flag.IDENTITY); if (flag == null || flag.resumed == false) { stream_negotiated(account, stream); } else if (flag != null && flag.resumed == true) { stream_resumed(account, stream); } }); } } public class ModuleIdentity : Object { public string id { get; private set; } public ModuleIdentity(string id) { this.id = id; } public T? cast(StreamInteractionModule module) { return module.get_type().is_a(typeof(T)) ? (T?) module : null; } public bool matches(StreamInteractionModule module) { return module.id == id; } } public interface StreamInteractionModule : Object { public abstract string id { get; } } } dino-0.3.0/libdino/src/service/util.vala0000644000000000000000000000206214202022370016622 0ustar rootrootusing Dino.Entities; namespace Dino { public class Util { public static Message.Type get_message_type_for_conversation(Conversation conversation) { switch (conversation.type_) { case Conversation.Type.CHAT: return Entities.Message.Type.CHAT; case Conversation.Type.GROUPCHAT: return Entities.Message.Type.GROUPCHAT; case Conversation.Type.GROUPCHAT_PM: return Entities.Message.Type.GROUPCHAT_PM; default: assert_not_reached(); } } public static Conversation.Type get_conversation_type_for_message(Message message) { switch (message.type_) { case Entities.Message.Type.CHAT: return Conversation.Type.CHAT; case Entities.Message.Type.GROUPCHAT: return Conversation.Type.GROUPCHAT; case Entities.Message.Type.GROUPCHAT_PM: return Conversation.Type.GROUPCHAT_PM; default: assert_not_reached(); } } } } dino-0.3.0/libdino/src/util/0000755000000000000000000000000014202022370014315 5ustar rootrootdino-0.3.0/libdino/src/util/display_name.vala0000644000000000000000000001230314202022370017626 0ustar rootrootusing Gee; using Dino.Entities; using Xmpp; namespace Dino { public static string get_conversation_display_name(StreamInteractor stream_interactor, Conversation conversation, string? muc_pm_format) { if (conversation.type_ == Conversation.Type.CHAT) { string? display_name = get_real_display_name(stream_interactor, conversation.account, conversation.counterpart); if (display_name != null) return display_name; return conversation.counterpart.to_string(); } if (conversation.type_ == Conversation.Type.GROUPCHAT) { return get_groupchat_display_name(stream_interactor, conversation.account, conversation.counterpart); } if (conversation.type_ == Conversation.Type.GROUPCHAT_PM) { return (muc_pm_format ?? "%s / %s").printf(get_occupant_display_name(stream_interactor, conversation, conversation.counterpart), get_groupchat_display_name(stream_interactor, conversation.account, conversation.counterpart.bare_jid)); } return conversation.counterpart.to_string(); } public static string get_participant_display_name(StreamInteractor stream_interactor, Conversation conversation, Jid participant, string? self_word = null) { if (self_word != null) { if (conversation.account.bare_jid.equals_bare(participant) || (conversation.type_ == Conversation.Type.GROUPCHAT || conversation.type_ == Conversation.Type.GROUPCHAT_PM) && conversation.nickname != null && participant.equals_bare(conversation.counterpart) && conversation.nickname == participant.resourcepart) { return self_word; } } if (conversation.type_ == Conversation.Type.CHAT) { return get_real_display_name(stream_interactor, conversation.account, participant, self_word) ?? participant.bare_jid.to_string(); } if ((conversation.type_ == Conversation.Type.GROUPCHAT || conversation.type_ == Conversation.Type.GROUPCHAT_PM) && conversation.counterpart.equals_bare(participant)) { return get_occupant_display_name(stream_interactor, conversation, participant); } return participant.bare_jid.to_string(); } public static string? get_real_display_name(StreamInteractor stream_interactor, Account account, Jid jid, string? self_word = null) { if (jid.equals_bare(account.bare_jid)) { if (self_word != null || account.alias == null || account.alias.length == 0) { return self_word; } return account.alias; } Roster.Item roster_item = stream_interactor.get_module(RosterManager.IDENTITY).get_roster_item(account, jid); if (roster_item != null && roster_item.name != null && roster_item.name != "") { return roster_item.name; } return null; } public static string get_groupchat_display_name(StreamInteractor stream_interactor, Account account, Jid jid) { MucManager muc_manager = stream_interactor.get_module(MucManager.IDENTITY); string? room_name = muc_manager.get_room_name(account, jid); if (room_name != null && room_name != jid.localpart) { return room_name; } if (muc_manager.is_private_room(account, jid)) { Gee.List? other_occupants = muc_manager.get_other_offline_members(jid, account); if (other_occupants != null && other_occupants.size > 0) { var builder = new StringBuilder (); foreach(Jid occupant in other_occupants) { if (builder.len != 0) { builder.append(", "); } builder.append((get_real_display_name(stream_interactor, account, occupant) ?? occupant.localpart ?? occupant.domainpart).split(" ")[0]); } return builder.str; } } return jid.to_string(); } public static string get_occupant_display_name(StreamInteractor stream_interactor, Conversation conversation, Jid jid, string? self_word = null, bool muc_real_name = false) { if (muc_real_name) { MucManager muc_manager = stream_interactor.get_module(MucManager.IDENTITY); if (muc_manager.is_private_room(conversation.account, jid.bare_jid)) { Jid? real_jid = muc_manager.get_real_jid(jid, conversation.account); if (real_jid != null) { string? display_name = get_real_display_name(stream_interactor, conversation.account, real_jid, self_word); if (display_name != null) return display_name; } } } // If it's us (jid=our real full JID), display our nick if (conversation.type_ == Conversation.Type.GROUPCHAT_PM && conversation.account.bare_jid.equals_bare(jid)) { var muc_conv = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(conversation.counterpart.bare_jid, conversation.account, Conversation.Type.GROUPCHAT); if (muc_conv != null && muc_conv.nickname != null) { return muc_conv.nickname; } } return jid.resourcepart ?? jid.to_string(); } }dino-0.3.0/libdino/src/util/util.vala0000644000000000000000000000565714202022370016154 0ustar rootrootnamespace Dino { private extern const string SYSTEM_LIBDIR_NAME; private extern const string SYSTEM_PLUGIN_DIR; public class SearchPathGenerator { public string? exec_path { get; private set; } public SearchPathGenerator(string? exec_path) { this.exec_path = exec_path; } public string get_locale_path(string gettext_package, string locale_install_dir) { string? locale_dir = null; if (Path.get_dirname(exec_path).contains("dino") || Path.get_dirname(exec_path) == "." || Path.get_dirname(exec_path).contains("build")) { string exec_locale = Path.build_filename(Path.get_dirname(exec_path), "locale"); if (FileUtils.test(Path.build_filename(exec_locale, "en", "LC_MESSAGES", gettext_package + ".mo"), FileTest.IS_REGULAR)) { locale_dir = exec_locale; } } return locale_dir ?? locale_install_dir; } public string[] get_plugin_paths() { string[] search_paths = new string[0]; if (Environment.get_variable("DINO_PLUGIN_DIR") != null) { search_paths += Environment.get_variable("DINO_PLUGIN_DIR"); } search_paths += Path.build_filename(Environment.get_home_dir(), ".local", "lib", "dino", "plugins"); string? exec_path = this.exec_path; if (exec_path != null) { if (!exec_path.contains(Path.DIR_SEPARATOR_S)) { exec_path = Environment.find_program_in_path(this.exec_path); } if (Path.get_dirname(exec_path).contains("dino") || Path.get_dirname(exec_path) == "." || Path.get_dirname(exec_path).contains("build")) { search_paths += Path.build_filename(Path.get_dirname(exec_path), "plugins"); } if (Path.get_basename(Path.get_dirname(exec_path)) == "bin") { search_paths += Path.build_filename(Path.get_dirname(Path.get_dirname(exec_path)), SYSTEM_LIBDIR_NAME, "dino", "plugins"); } } search_paths += SYSTEM_PLUGIN_DIR; return search_paths; } } public static string get_storage_dir() { return Path.build_filename(Environment.get_user_data_dir(), "dino"); } [CCode (cname = "dino_gettext", cheader_filename = "dino_i18n.h")] public static extern unowned string _(string s); [CCode (cname = "dino_ngettext", cheader_filename = "dino_i18n.h")] public static extern unowned string n(string msgid, string plural, ulong number); [CCode (cname = "bindtextdomain", cheader_filename = "libintl.h")] private static extern unowned string? bindtextdomain(string domainname, string? dirname); [CCode (cname = "bind_textdomain_codeset", cheader_filename = "libintl.h")] private static extern unowned string? bind_textdomain_codeset(string domainname, string? codeset); public static void internationalize(string gettext_package, string locales_dir) { Intl.bind_textdomain_codeset(gettext_package, "UTF-8"); Intl.bindtextdomain(gettext_package, locales_dir); } } dino-0.3.0/libdino/src/util/weak_map.vala0000644000000000000000000000710214202022370016746 0ustar rootrootusing Gee; public class WeakMap : Gee.AbstractMap { private HashMap hash_map; private HashMap notify_map; public HashDataFunc? key_hash_func = null; public EqualDataFunc? key_equal_func = null; public EqualDataFunc? value_equal_func = null; public WeakMap(owned HashDataFunc? key_hash_func = null, owned EqualDataFunc? key_equal_func = null, owned EqualDataFunc? value_equal_func = null) { if (!typeof(V).is_object()) { error("WeakMap only takes values that are Objects"); } this.key_hash_func = (owned)key_hash_func; this.key_equal_func = (owned)key_equal_func; this.value_equal_func = (owned)value_equal_func; if (this.key_equal_func == null || this.key_equal_func == null || this.value_equal_func == null) { hash_map = new HashMap(); notify_map = new HashMap(); } else { hash_map = new HashMap((v) => { return this.key_hash_func(v); }, (a, b) => { return this.key_equal_func(a, b); }, (a, b) => { return this.value_equal_func(a, b); }); notify_map = new HashMap((v) => { return this.key_hash_func(v); }, (a, b) => { return this.key_equal_func(a, b); }, (a, b) => { return this.value_equal_func(a, b); }); } } public override void clear() { foreach (K key in notify_map.keys) { Object o = (Object) hash_map[key]; o.weak_unref(notify_map[key].func); } hash_map.clear(); notify_map.clear(); } public override V @get(K key) { if (!hash_map.has_key(key)) return null; var v = hash_map[key]; return (owned) v; } public override bool has(K key, V value) { assert_not_reached(); } public override bool has_key(K key) { return hash_map.has_key(key); } public override Gee.MapIterator map_iterator() { assert_not_reached(); } public override void @set(K key, V value) { assert(value != null); unset(key); Object v_obj = (Object) value; var notify_wrap = new WeakNotifyWrapper((obj) => { hash_map.unset(key); notify_map.unset(key); }); notify_map[key] = notify_wrap; v_obj.weak_ref(notify_wrap.func); hash_map[key] = value; } public override bool unset(K key, out V value = null) { if (!hash_map.has_key(key)) { value = null; return false; } Object v_obj = (Object) hash_map[key]; v_obj.weak_unref(notify_map[key].func); notify_map.unset(key); return hash_map.unset(key, out value); } public override Gee.Set> entries { owned get; } public override Gee.Set keys { owned get { return hash_map.keys; } } public override bool read_only { get { assert_not_reached(); } } public override int size { get { return hash_map.size; } } public override Gee.Collection values { owned get { assert_not_reached(); } } public override void dispose() { foreach (K key in notify_map.keys) { Object o = (Object) hash_map[key]; o.weak_unref(notify_map[key].func); } } } internal class WeakNotifyWrapper { public WeakNotify func; public WeakNotifyWrapper(owned WeakNotify func) { this.func = (owned) func; } }dino-0.3.0/libdino/tests/0000755000000000000000000000000014202022370013713 5ustar rootrootdino-0.3.0/libdino/tests/common.vala0000644000000000000000000000373714202022370016062 0ustar rootrootnamespace Dino.Test { int main(string[] args) { GLib.Test.init(ref args); GLib.Test.set_nonfatal_assertions(); TestSuite.get_root().add_suite(new WeakMapTest().get_suite()); return GLib.Test.run(); } bool fail_if(bool exp, string? reason = null) { if (exp) { if (reason != null) GLib.Test.message(reason); GLib.Test.fail(); return true; } return false; } void fail_if_reached(string? reason = null) { fail_if(true, reason); } delegate void ErrorFunc() throws Error; void fail_if_not_error_code(ErrorFunc func, int expectedCode, string? reason = null) { try { func(); fail_if_reached(@"$(reason + ": " ?? "")no error thrown"); } catch (Error e) { fail_if_not_eq_int(e.code, expectedCode, @"$(reason + ": " ?? "")catched unexpected error"); } } bool fail_if_not(bool exp, string? reason = null) { return fail_if(!exp, reason); } bool fail_if_eq_int(int left, int right, string? reason = null) { return fail_if(left == right, @"$(reason + ": " ?? "")$left == $right"); } bool fail_if_not_eq_int(int left, int right, string? reason = null) { return fail_if_not(left == right, @"$(reason + ": " ?? "")$left != $right"); } bool fail_if_not_eq_str(string left, string right, string? reason = null) { return fail_if_not(left == right, @"$(reason + ": " ?? "")$left != $right"); } bool fail_if_not_eq_uint8_arr(uint8[] left, uint8[] right, string? reason = null) { if (fail_if_not_eq_int(left.length, right.length, @"$(reason + ": " ?? "")array length not equal")) return true; return fail_if_not_eq_str(Base64.encode(left), Base64.encode(right), reason); } bool fail_if_not_zero_int(int zero, string? reason = null) { return fail_if_not_eq_int(zero, 0, reason); } bool fail_if_zero_int(int zero, string? reason = null) { return fail_if_eq_int(zero, 0, reason); } bool fail_if_null(void* what, string? reason = null) { return fail_if(what == null || (size_t)what == 0, reason); } } dino-0.3.0/libdino/tests/jid.vala0000644000000000000000000000221514202022370015326 0ustar rootrootusing Dino.Entities; namespace Dino.Test { class JidTest : Gee.TestCase { public JidTest() { base("Jid"); add_test("parse", test_parse); add_test("components", test_components); add_test("with_res", test_with_res); } private void test_parse() { Jid jid = new Jid("user@example.com/res"); fail_if(jid.localpart != "user"); fail_if(jid.domainpart != "example.com"); fail_if(jid.resourcepart != "res"); fail_if(jid.to_string() != "user@example.com/res"); } private void test_components() { Jid jid = new Jid.components("user", "example.com", "res"); fail_if(jid.localpart != "user"); fail_if(jid.domainpart != "example.com"); fail_if(jid.resourcepart != "res"); fail_if(jid.to_string() != "user@example.com/res"); } private void test_with_res() { Jid jid = new Jid.with_resource("user@example.com", "res"); fail_if(jid.localpart != "user"); fail_if(jid.domainpart != "example.com"); fail_if(jid.resourcepart != "res"); fail_if(jid.to_string() != "user@example.com/res"); } } } dino-0.3.0/libdino/tests/testcase.vala0000644000000000000000000000450314202022370016375 0ustar rootroot/* testcase.vala * * Copyright (C) 2009 Julien Peeters * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * This library 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 * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Author: * Julien Peeters */ public abstract class Gee.TestCase : Object { private GLib.TestSuite suite; private Adaptor[] adaptors = new Adaptor[0]; public delegate void TestMethod (); protected TestCase (string name) { this.suite = new GLib.TestSuite (name); } public void add_test (string name, owned TestMethod test) { var adaptor = new Adaptor (name, (owned)test, this); this.adaptors += adaptor; this.suite.add (new GLib.TestCase (adaptor.name, adaptor.set_up, adaptor.run, adaptor.tear_down )); } public virtual void set_up () { } public virtual void tear_down () { } public GLib.TestSuite get_suite () { return this.suite; } private class Adaptor { [CCode (notify = false)] public string name { get; private set; } private TestMethod test; private TestCase test_case; public Adaptor (string name, owned TestMethod test, TestCase test_case) { this.name = name; this.test = (owned)test; this.test_case = test_case; } public void set_up (void* fixture) { this.test_case.set_up (); } public void run (void* fixture) { this.test (); } public void tear_down (void* fixture) { this.test_case.tear_down (); } } } dino-0.3.0/libdino/tests/weak_map.vala0000644000000000000000000000475614202022370016360 0ustar rootrootusing Dino.Entities; namespace Dino.Test { class WeakMapTest : Gee.TestCase { public WeakMapTest() { base("WeakMapTest"); add_test("set", test_set); add_test("set2", test_set2); add_test("set3", test_set3); add_test("set4", test_unset); add_test("remove_when_out_of_scope", test_remove_when_out_of_scope); // add_test("non_object_construction", test_non_object_construction); } private void test_set() { WeakMap map = new WeakMap(); var o = new Object(); map[1] = o; assert(map.size == 1); assert(map.has_key(1)); assert(map[1] == o); } private void test_set2() { WeakMap map = new WeakMap(); var o = new Object(); var o2 = new Object(); map[1] = o; map[1] = o2; assert(map.size == 1); assert(map.has_key(1)); assert(map[1] == o2); } private void test_set3() { WeakMap map = new WeakMap(); var o1 = new Object(); var o2 = new Object(); map[0] = o1; map[3] = o2; { var o3 = new Object(); var o4 = new Object(); map[7] = o3; map[50] = o4; } var o5 = new Object(); map[5] = o5; assert(map.size == 3); assert(map.has_key(0)); assert(map.has_key(3)); assert(map.has_key(5)); assert(map[0] == o1); assert(map[3] == o2); assert(map[5] == o5); } private void test_unset() { WeakMap map = new WeakMap(); var o1 = new Object(); map[7] = o1; map.unset(7); assert_true(map.size == 0); assert_true(map.is_empty); assert_false(map.has_key(7)); } private void test_remove_when_out_of_scope() { WeakMap map = new WeakMap(); { map[0] = new Object(); } assert_false(map.has_key(0)); } private void test_non_object_construction() { WeakMap map = new WeakMap(); assert_not_reached(); } } } dino-0.3.0/main/0000755000000000000000000000000014202022370012055 5ustar rootrootdino-0.3.0/main/CMakeLists.txt0000644000000000000000000002044214202022370014617 0ustar rootrootset(GETTEXT_PACKAGE "dino") find_package(Gettext) include(${GETTEXT_USE_FILE}) gettext_compile(${GETTEXT_PACKAGE} SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/po TARGET_NAME ${GETTEXT_PACKAGE}-translations) find_packages(MAIN_PACKAGES REQUIRED Gee GLib GModule GObject GTK3 ICU Gspell ) set(RESOURCE_LIST icons/dino-account-plus-symbolic.svg icons/dino-changes-allowed-symbolic.svg icons/dino-changes-prevent-symbolic.svg icons/dino-conversation-list-placeholder-arrow.svg icons/dino-double-tick-symbolic.svg icons/dino-emoticon-symbolic.svg icons/dino-qr-code-symbolic.svg icons/dino-security-high-symbolic.svg icons/dino-microphone-off-symbolic.svg icons/dino-microphone-symbolic.svg icons/dino-party-popper-symbolic.svg icons/dino-phone-hangup-symbolic.svg icons/dino-phone-in-talk-symbolic.svg icons/dino-phone-missed-symbolic.svg icons/dino-phone-ring-symbolic.svg icons/dino-phone-symbolic.svg icons/dino-status-away.svg icons/dino-status-chat.svg icons/dino-status-dnd.svg icons/dino-status-online.svg icons/im.dino.Dino.svg icons/im.dino.Dino-symbolic.svg icons/dino-tick-symbolic.svg icons/dino-video-off-symbolic.svg icons/dino-video-symbolic.svg icons/dino-device-desktop-symbolic.svg icons/dino-device-phone-symbolic.svg icons/dino-file-document-symbolic.svg icons/dino-file-download-symbolic.svg icons/dino-file-image-symbolic.svg icons/dino-file-music-symbolic.svg icons/dino-file-symbolic.svg icons/dino-file-table-symbolic.svg icons/dino-file-video-symbolic.svg add_conversation/add_contact_dialog.ui add_conversation/add_groupchat_dialog.ui add_conversation/conference_details_fragment.ui add_conversation/list_row.ui add_conversation/select_jid_fragment.ui call_widget.ui chat_input.ui contact_details_dialog.ui conversation_list_titlebar.ui conversation_list_titlebar_csd.ui conversation_row.ui conversation_view.ui file_default_widget.ui file_send_overlay.ui emojichooser.ui global_search.ui conversation_content_view/item_metadata_header.ui conversation_content_view/view.ui manage_accounts/account_row.ui manage_accounts/add_account_dialog.ui manage_accounts/dialog.ui menu_add.ui menu_app.ui menu_conversation.ui menu_encryption.ui message_item_widget_edit_mode.ui occupant_list.ui occupant_list_item.ui search_autocomplete.ui settings_dialog.ui shortcuts.ui unified_main_content.ui unified_window_placeholder.ui theme.css ) compile_gresources( MAIN_GRESOURCES_TARGET MAIN_GRESOURCES_XML TARGET ${CMAKE_CURRENT_BINARY_DIR}/resources/resources.c TYPE EMBED_C RESOURCES ${RESOURCE_LIST} PREFIX /im/dino/Dino SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/data ) unset(MAIN_EXTRA_OPTIONS) unset(MAIN_EXTRA_PACKAGES) find_package(GDK3) if(GDK3_WITH_X11) set(MAIN_EXTRA_OPTIONS ${MAIN_EXTRA_OPTIONS} -D GDK3_WITH_X11) set(MAIN_EXTRA_PACKAGES ${MAIN_EXTRA_PACKAGES} gdk-x11-3.0) endif(GDK3_WITH_X11) set(MAIN_EXTRA_OPTIONS ${MAIN_EXTRA_OPTIONS} --vapidir=${CMAKE_CURRENT_SOURCE_DIR}/vapi) vala_precompile(MAIN_VALA_C SOURCES src/main.vala src/ui/application.vala src/ui/avatar_drawer.vala src/ui/avatar_image.vala src/ui/conversation_list_titlebar.vala src/ui/conversation_view.vala src/ui/conversation_view_controller.vala src/ui/file_send_overlay.vala src/ui/global_search.vala src/ui/notifier_freedesktop.vala src/ui/notifier_gnotifications.vala src/ui/settings_dialog.vala src/ui/main_window.vala src/ui/main_window_controller.vala src/ui/add_conversation/add_conference_dialog.vala src/ui/add_conversation/add_contact_dialog.vala src/ui/add_conversation/add_groupchat_dialog.vala src/ui/add_conversation/conference_details_fragment.vala src/ui/add_conversation/conference_list.vala src/ui/add_conversation/list_row.vala src/ui/add_conversation/roster_list.vala src/ui/add_conversation/select_contact_dialog.vala src/ui/add_conversation/select_jid_fragment.vala src/ui/call_window/audio_settings_popover.vala src/ui/call_window/call_bottom_bar.vala src/ui/call_window/call_connection_details_window.vala src/ui/call_window/call_encryption_button.vala src/ui/call_window/call_window.vala src/ui/call_window/call_window_controller.vala src/ui/call_window/participant_widget.vala src/ui/call_window/video_settings_popover.vala src/ui/chat_input/chat_input_controller.vala src/ui/chat_input/chat_text_view.vala src/ui/chat_input/edit_history.vala src/ui/chat_input/encryption_button.vala src/ui/chat_input/occupants_tab_completer.vala src/ui/chat_input/smiley_converter.vala src/ui/chat_input/spell_checker.vala src/ui/chat_input/view.vala src/ui/contact_details/blocking_provider.vala src/ui/contact_details/settings_provider.vala src/ui/contact_details/permissions_provider.vala src/ui/contact_details/dialog.vala src/ui/contact_details/muc_config_form_provider.vala src/ui/conversation_selector/conversation_selector_row.vala src/ui/conversation_selector/conversation_selector.vala src/ui/conversation_content_view/call_widget.vala src/ui/conversation_content_view/chat_state_populator.vala src/ui/conversation_content_view/content_populator.vala src/ui/conversation_content_view/conversation_item_skeleton.vala src/ui/conversation_content_view/conversation_view.vala src/ui/conversation_content_view/date_separator_populator.vala src/ui/conversation_content_view/file_default_widget.vala src/ui/conversation_content_view/file_image_widget.vala src/ui/conversation_content_view/file_widget.vala src/ui/conversation_content_view/message_widget.vala src/ui/conversation_content_view/subscription_notification.vala src/ui/conversation_titlebar/call_entry.vala src/ui/conversation_titlebar/menu_entry.vala src/ui/conversation_titlebar/occupants_entry.vala src/ui/conversation_titlebar/search_entry.vala src/ui/conversation_titlebar/conversation_titlebar.vala src/ui/manage_accounts/account_row.vala src/ui/manage_accounts/add_account_dialog.vala src/ui/manage_accounts/dialog.vala src/ui/occupant_menu/list.vala src/ui/occupant_menu/list_row.vala src/ui/occupant_menu/view.vala src/ui/util/accounts_combo_box.vala src/ui/util/config.vala src/ui/util/data_forms.vala src/ui/util/helper.vala src/ui/util/label_hybrid.vala src/ui/util/sizing_bin.vala src/ui/util/size_request_box.vala src/ui/util/scaling_image.vala src/ui/util/preview_file_chooser_native.vala CUSTOM_VAPIS ${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi ${CMAKE_BINARY_DIR}/exports/qlite.vapi ${CMAKE_BINARY_DIR}/exports/dino_internal.vapi vapi/emojichooser.vapi PACKAGES ${MAIN_PACKAGES} ${MAIN_EXTRA_PACKAGES} GRESOURCES ${MAIN_GRESOURCES_XML} OPTIONS ${MAIN_EXTRA_OPTIONS} ) add_definitions(${VALA_CFLAGS} -DGETTEXT_PACKAGE=\"${GETTEXT_PACKAGE}\" -DLOCALE_INSTALL_DIR=\"${LOCALE_INSTALL_DIR}\" -DDINO_VERSION=\"${PROJECT_VERSION}\") add_executable(dino ${MAIN_VALA_C} ${MAIN_GRESOURCES_TARGET} src/emojichooser.c) add_dependencies(dino ${GETTEXT_PACKAGE}-translations) target_include_directories(dino PRIVATE src) target_link_libraries(dino libdino ${MAIN_PACKAGES}) if(WIN32) target_link_libraries(dino -mwindows) endif(WIN32) install(TARGETS dino ${TARGET_INSTALL}) install(FILES data/im.dino.Dino.appdata.xml DESTINATION ${APPDATA_FILE_INSTALL_DIR}) install(FILES data/im.dino.Dino.desktop DESTINATION ${DESKTOP_FILE_INSTALL_DIR}) install(FILES data/im.dino.Dino.service DESTINATION ${SERVICE_FILE_INSTALL_DIR}) install(FILES data/icons/im.dino.Dino.svg DESTINATION ${ICON_INSTALL_DIR}/hicolor/scalable/apps) install(FILES data/icons/im.dino.Dino-symbolic.svg DESTINATION ${ICON_INSTALL_DIR}/hicolor/symbolic/apps) install(FILES data/icons/dino-status-away.svg data/icons/dino-status-chat.svg data/icons/dino-status-dnd.svg data/icons/dino-status-online.svg data/icons/dino-changes-prevent-symbolic.svg data/icons/dino-double-tick-symbolic.svg data/icons/dino-qr-code-symbolic.svg data/icons/dino-tick-symbolic.svg DESTINATION ${ICON_INSTALL_DIR}/hicolor/scalable/status ) dino-0.3.0/main/data/0000755000000000000000000000000014202022370012766 5ustar rootrootdino-0.3.0/main/data/add_conversation/0000755000000000000000000000000014202022370016310 5ustar rootrootdino-0.3.0/main/data/add_conversation/add_contact_dialog.ui0000644000000000000000000001613014202022370022432 0ustar rootroot dino-0.3.0/main/data/add_conversation/add_groupchat_dialog.ui0000644000000000000000000002653314202022370023003 0ustar rootroot dino-0.3.0/main/data/add_conversation/conference_details_fragment.ui0000644000000000000000000004221414202022370024351 0ustar rootroot dino-0.3.0/main/data/add_conversation/list_row.ui0000644000000000000000000000623614202022370020520 0ustar rootroot dino-0.3.0/main/data/add_conversation/select_jid_fragment.ui0000644000000000000000000001303714202022370022643 0ustar rootroot dino-0.3.0/main/data/call_widget.ui0000644000000000000000000001567414202022370015620 0ustar rootroot dino-0.3.0/main/data/chat_input.ui0000644000000000000000000000673414202022370015475 0ustar rootroot dino-0.3.0/main/data/contact_details_dialog.ui0000644000000000000000000002003314202022370020002 0ustar rootroot dino-0.3.0/main/data/conversation_content_view/0000755000000000000000000000000014202022370020264 5ustar rootrootdino-0.3.0/main/data/conversation_content_view/item_metadata_header.ui0000644000000000000000000000227414202022370024736 0ustar rootroot dino-0.3.0/main/data/conversation_content_view/view.ui0000644000000000000000000001677714202022370021617 0ustar rootroot dino-0.3.0/main/data/conversation_list_titlebar.ui0000644000000000000000000000434414202022370020765 0ustar rootroot dino-0.3.0/main/data/conversation_list_titlebar_csd.ui0000644000000000000000000000324714202022370021617 0ustar rootroot dino-0.3.0/main/data/conversation_row.ui0000644000000000000000000002664714202022370016745 0ustar rootroot dino-0.3.0/main/data/conversation_view.ui0000644000000000000000000000764514202022370017105 0ustar rootroot dino-0.3.0/main/data/emojichooser.ui0000644000000000000000000005011314202022370016013 0ustar rootroot dino-0.3.0/main/data/file_default_widget.ui0000644000000000000000000001441114202022370017314 0ustar rootroot dino-0.3.0/main/data/file_send_overlay.ui0000644000000000000000000001144014202022370017016 0ustar rootroot dino-0.3.0/main/data/global_search.ui0000644000000000000000000002325614202022370016122 0ustar rootroot dino-0.3.0/main/data/icons/0000755000000000000000000000000014202022370014101 5ustar rootrootdino-0.3.0/main/data/icons/dino-account-plus-symbolic.svg0000644000000000000000000000071014202022370022003 0ustar rootrootdino-0.3.0/main/data/icons/dino-changes-allowed-symbolic.svg0000644000000000000000000000224214202022370022425 0ustar rootroot dino-0.3.0/main/data/icons/dino-changes-prevent-symbolic.svg0000644000000000000000000000433614202022370022467 0ustar rootroot image/svg+xml Gnome Symbolic Icon Theme Gnome Symbolic Icon Theme dino-0.3.0/main/data/icons/dino-conversation-list-placeholder-arrow.svg0000644000000000000000000000124114202022370024642 0ustar rootroot dino-0.3.0/main/data/icons/dino-device-desktop-symbolic.svg0000644000000000000000000000053214202022370022276 0ustar rootroot dino-0.3.0/main/data/icons/dino-device-phone-symbolic.svg0000644000000000000000000000075214202022370021742 0ustar rootroot dino-0.3.0/main/data/icons/dino-double-tick-symbolic.svg0000644000000000000000000000171714202022370021600 0ustar rootroot dino-0.3.0/main/data/icons/dino-emoticon-symbolic.svg0000644000000000000000000000112114202022370021200 0ustar rootroot dino-0.3.0/main/data/icons/dino-file-document-symbolic.svg0000644000000000000000000000073314202022370022126 0ustar rootroot dino-0.3.0/main/data/icons/dino-file-download-symbolic.svg0000644000000000000000000000071114202022370022113 0ustar rootroot dino-0.3.0/main/data/icons/dino-file-image-symbolic.svg0000644000000000000000000000124314202022370021367 0ustar rootroot dino-0.3.0/main/data/icons/dino-file-music-symbolic.svg0000644000000000000000000000211714202022370021426 0ustar rootroot dino-0.3.0/main/data/icons/dino-file-symbolic.svg0000644000000000000000000000064114202022370020310 0ustar rootroot dino-0.3.0/main/data/icons/dino-file-table-symbolic.svg0000644000000000000000000000112714202022370021375 0ustar rootroot dino-0.3.0/main/data/icons/dino-file-video-symbolic.svg0000644000000000000000000000123514202022370021414 0ustar rootroot dino-0.3.0/main/data/icons/dino-microphone-off-symbolic.svg0000644000000000000000000000122514202022370022303 0ustar rootrootdino-0.3.0/main/data/icons/dino-microphone-symbolic.svg0000644000000000000000000000073414202022370021537 0ustar rootrootdino-0.3.0/main/data/icons/dino-party-popper-symbolic.svg0000644000000000000000000001025714202022370022037 0ustar rootroot dino-0.3.0/main/data/icons/dino-phone-hangup-symbolic.svg0000644000000000000000000000146014202022370021762 0ustar rootrootdino-0.3.0/main/data/icons/dino-phone-in-talk-symbolic.svg0000644000000000000000000000123014202022370022032 0ustar rootrootdino-0.3.0/main/data/icons/dino-phone-missed-symbolic.svg0000644000000000000000000000157214202022370021770 0ustar rootrootdino-0.3.0/main/data/icons/dino-phone-ring-symbolic.svg0000644000000000000000000000145414202022370021442 0ustar rootrootdino-0.3.0/main/data/icons/dino-phone-symbolic.svg0000644000000000000000000000107614202022370020505 0ustar rootrootdino-0.3.0/main/data/icons/dino-qr-code-symbolic.svg0000644000000000000000000000107014202022370020720 0ustar rootroot dino-0.3.0/main/data/icons/dino-security-high-symbolic.svg0000644000000000000000000000246414202022370022162 0ustar rootroot dino-0.3.0/main/data/icons/dino-status-away.svg0000644000000000000000000000066214202022370020037 0ustar rootroot dino-0.3.0/main/data/icons/dino-status-chat.svg0000644000000000000000000000146714202022370020021 0ustar rootroot dino-0.3.0/main/data/icons/dino-status-dnd.svg0000644000000000000000000000062114202022370017636 0ustar rootroot dino-0.3.0/main/data/icons/dino-status-online.svg0000644000000000000000000000045614202022370020363 0ustar rootroot dino-0.3.0/main/data/icons/dino-tick-symbolic.svg0000644000000000000000000000120314202022370020316 0ustar rootroot dino-0.3.0/main/data/icons/dino-video-off-symbolic.svg0000644000000000000000000000070614202022370021251 0ustar rootrootdino-0.3.0/main/data/icons/dino-video-symbolic.svg0000644000000000000000000000061714202022370020502 0ustar rootrootdino-0.3.0/main/data/icons/im.dino.Dino-symbolic.svg0000644000000000000000000000407214202022370020671 0ustar rootroot dino-0.3.0/main/data/icons/im.dino.Dino.svg0000644000000000000000000001630614202022370017055 0ustar rootroot dino-0.3.0/main/data/im.dino.Dino.appdata.xml0000644000000000000000000005221114202022370017347 0ustar rootroot im.dino.Dino im.dino.Dino.desktop CC0-1.0 GPL-3.0+ Dino Modern XMPP Chat Client 現代化的 XMPP 用戶端聊天軟件 现代 XMPP 聊天客户端 Modern XMPP Sohbet İstemcisi Modern XMPP-chattklient Klient Modern Fjalosjesh XMPP Современный XMPP клиент Client XMPP de discuții modern Cliente de Chat XMPP Moderno Moderno cliente de chat XMPP Nowoczesny komunikator XMPP Client XMPP modèrn Modernen XMPP-chatcliënt Een moderne XMPP-chatclient Moderne XMPP-sludreklient Šiuolaikinė XMPP pokalbių kliento programa Modernen XMPP Chat Client 現代的な XMPP チャット クライアント Client di chat moderno per XMPP Un modern client de conversationes XMPP Aplikasi chat XMPP modern Modern XMPP csevegőprogram Cliente moderno para conversas XMPP Client de clavardage XMPP moderne کلاینت نوین گپ XMPP XMPP txat bezero modernoa Un cliente de XMPP moderno Moderna XMPP-Retebabililo Modernes XMPP-Chat-Programm Moderní XMPP klient Client de xat XMPP modern تطبيق حديث للدردشة عبر XMPP

Dino is a modern open-source chat client for the desktop. It focuses on providing a clean and reliable Jabber/XMPP experience while having your privacy in mind.

Dino 是一個爲桌面打造的現代化開放原始碼用戶端聊天軟件。它致力於提供一份簡洁而可靠的 Jabber/XMPP 體驗,同時亦尊重您的私隱。

Dino 是一个现代的开源聊天桌面客户端。它致力于提供一个清爽又可靠的 Jabber/XMPP 体验,同时又保护您的隐私。

Dino masaüstü bilgisayarlar için modern, açık kaynaklı bir sohbet uygulamasıdır. Gizlilik hassasiyetinizi göz önünde bulundurarak temiz ve güvenilir bir Jabber/XMPP deneyimi sunmaya odaklanır.

Dino är en moden chattklient för skrivbordet med öppen källkod. Den erbjuder en elegant och pålitligt upplevelse av Jabber/XMPP samtidigt som den ser efter din integritet.

Dino është një klient modern fjalosjesh, me burim të hapur, për desktop. Ai përqendrohet në dhënien e një mënyre funksionimi të qartë dhe të qëndrueshme për protokoll Jabber/XMPP, teksa ka në mendje privatësinë tuaj.

Dino - это современный клиент для чатов с открытым исходным кодом, направленный на надёжное и приватное использование Jabber/XMPP на персональных компьютерах.

Dino este un client de chat modern, cu sursă deschisă, pentru calculatoare. Se concentrează să furnizeze o experiență Jabber/XMPP clară și fiabilă, ținând cont de confidențialitatea dumneavoastră.

Dino é um cliente moderno de código aberto de chat para desktop. Ele foca em oferecer uma experiência Jabber/XMPP transparente e confiável levando em consideração a sua privacidade.

Dino é um moderno chat de código aberto para desktop. Ele é focado em prover uma transparente e confiável experiência Jabber/XMPP, tendo em mente a sua privacidade.

Dino jest nowoczesnym, otwartym komunikatorem. Skupia się na prostej obsłudze sieci Jabber/XMPP dbając o twoją prywatność.

Dino es un client de chat liure e modèrn per l’ordenador de burèu. Ensaja de provesir una experiéncia neta e fisabla de Jabber/XMPP en téner en compte vòstra confidencialitat.

Dino is een moderne, vrije chattoepassing voor uw bureaublad. Ze biedt een eenvoudige en betrouwbare Jabber/XMPP-ervaring, met uw privacy in het achterhoofd.

Dino is een moderne, vrije chattoepassing voor je computer. Dino biedt een eenvoudige en betrouwbare Jabber-/XMPP-ervaring, met privacy in het achterhoofd.

Dino er en moderne friporg-sludringsklient for skrivebordet. Det fokuserer på rask og pålitelig XMPP-opplevelse, samtidig som det hegner om personvernet.

Dino yra šiuolaikinė atvirojo kodo kliento programa skirta darbalaukiui. Jos pagrindinis dėmesys yra pateikti tvarkingą ir patikimą Jabber/XMPP patyrimą nepamirštant apie jūsų privatumą.

Dino ass e modernen, quell-offene Chat Client fir den Desktop. Hien biet eng opgeraumt a robust Jabber/XMPP Erfarung a leet ee Schwéierpunkt op Privatsphär.

Dino はオープンソースの現代的なデスクトップ向けチャットクライアントです。プライバシーを考慮しつつ、シンプルで信頼できる Jabber/XMPP エクスペリエンスの提供を第一に考えて開発されています。

Dino è un client di chat per il desktop, moderno e open-source. Si concentra nel fornire un'esperienza Jabber/XMPP pulita e affidabile tenendo presente la tua privacy.

Dino es un modern cliente de conversationes con fonte apert. It foca se sur provider un nett e fidibil experientie de Jabber/XMPP con un attention a confidentialitá.

Dino adalah aplikasi chat open source modern untuk PC. Menyediakan pengalaman Jabber / XMPP yang handal dengan tetap menjunjung privasi Anda.

A Dino egy modern, nyílt forráskódú csevegőprogram az asztali gépekre. Arra összpontosít, hogy tiszta és megbízható Jabber/XMPP-élményt nyújtson, miközben a magánszféra megőrzését is fontosnak tartja.

Dino é un cliente moderno e de código aberto para o escritorio. Orientado a fornecer unha experiencia Jabber/XMPP limpa e fiábel tendo a privacidade e seguranza presentes.

Dino est un client de clavardage libre et moderne pour le bureau. Il se concentre sur la fourniture d’une expérience XMPP simple et fiable tout en ayant toujours à l’esprit votre confidentialité.

Dino on nykyaikainen avoimen lähdekoodin jutteluohjelma työpöydälle. Se keskittyy tarjoamaan selkeän ja luotettavan Jabber/XMPP-kokemuksen unohtamatta yksityisyyttäsi.

دینو یک کلاینت چت متن‌باز برای دسکتاپ است. تمرکز آن بر فراهم‌کردن تجربه‌ای شسته‌رفته و قابل‌اتکا از جَبِر/XMPP است درحالی که به حریم خصوصی‌تان اهمیت می‌دهد.

Dino mahaigainerako iturburu irekiko txat bezero moderno bat da. Jabber/XMPP esperientzia garbi eta fidagarri bat ematen du zure pribatutasuna kontuan hartzeaz gain.

Dino es un cliente de mensajería moderno y libre para escritorio y móvil. Está enfocado en proveer una experiencia Jabber/XMPP limpia y confiable teniendo la privacidad en mente.

Dino estas moderna malfermfonta retbabililo por la tabla komputilo. Ĝi celas provizi puran kaj fidindan sperton de Jabber/XMPP, protektante vian privatecon.

Dino ist ein modernes, quelloffenes Chat-Programm für den Desktop. Es bietet ein aufgeräumtes und robustes Jabber-/XMPP-Erlebnis und legt einen Schwerpunkt auf Privatsphäre.

Dino je moderní open-source chatovací klient pro stolní počítače. Jeho cílem je poskytování čistého a spolehlivého prostředí Jabber/XMPP s důrazem na zachování vašeho soukromí.

Dino és un client de xat lliure i modern per a l'escriptori. Està centrat en proveir una experiència neta i fiable de Jabber/XMPP, sempre tenint en compte la vostra privacitat.

إنّ Dino برنامج عصري ومفتوح المصدر للدردشة صُمّم لسطح المكتب. ويُركّز علي تقديم تجربة نظيفة وموثوق منها لجابر/XMPP مع أخذ خصوصيتكم بعين الإعتبار.

It supports end-to-end encryption with OMEMO and OpenPGP and allows configuring privacy-related features such as read receipts and typing notifications.

它支持 OMEMO 和 OpenPGP 兩種端到端加密方式,並且容許設定私隱相關的特性譬如已讀回條和正在輸入提示。

它支持 OMEMO 和 OpenPGP 端对端加密并允许配置隐私相关的特性比如已读回执和输入提醒。

OMEMO ve OpenPGP ile baştan sona şifreleme destekler ve "okundu" bilgisi, "yazıyor..." bildirimi gibi gizlilikle alakalı özelliklerin ayarlanabilmesini sağlar.

Dino stödjer end-to-end-kryptering med OMEMO och OpenPGP och tillåter konfigurering av funktioner med integritetspåverkan som läsbekräftelser och skriftaviseringar.

Mbulon fshehtëzim skaj-më-skaj me OMEMO dhe OpenPGP dhe lejon formësim veçorish të lidhura me privatësinë, bie fjala, dëshmish leximi dhe njoftime shtypjeje.

Он поддерживает сквозное шифрование через OMEMO и OpenPGP и позволяет настраивать такие функции, как уведомления о прочтении и наборе сообщений.

Suportă criptare de la un capăt la altul prin intermediul OMEMO și OpenPGP, și permite configurarea caracteristicilor legate de confidențialitate precum trimiterea notificărilor de primire și tastare.

Ele suporta criptografia ponta-a-ponta com OMEMO e OpenPGP e, além disso, permite configurar funcionalidades relativas à privacidade como notificações de leitura, recebimento e escrita.

Suporte criptografia ponta a ponta com OMEMO e OpenPGP e permite configurar privacidade—características relacionadas às notificações de leitura, recebimento e escrita.

Obsługuje szyfrowanie od końca do końca za pomocą OMEMO i OpenPGP, a także daje kontrolę nad funkcjami wpływającymi na prywatność, jak powiadomienia o pisaniu czy odczytaniu wiadomości.

Compatible amb lo chiframent OMEMO e OpenPGP del cap a la fin e permet de configurar de foncionalitats ligadas amb la confidencialitat coma los acusats de lectura e las notificacions d’escritura.

Ze ondersteunt eind-tot-eind-versleuteling met OMEMO en OpenPGP, en laat u toe privacygerelateerde functies, gelijk leesbevestigingen en typmeldingen, in te stellen.

Dino ondersteunt end-to-endversleuteling met OMEMO en OpenPGP en staat je toe privacy-gerelateerde functies, zoals leesbevestigingen en aan-het-typenmeldingen, in te stellen.

Det støtter ende-til-ende -kryptering med OMEMO og OpenPGP, og tillater oppsett av personvernsrelaterte funksjoner som meldingskvitteringer og skrivevarsling.

Ji palaiko ištisinį šifravimą naudojant OMEMO ir OpenPGP bei leidžia konfigūruoti su privatumu susijusias ypatybes, tokias kaip pranešimus apie žinučių skaitymą ir rašymą.

Hien ënnerstëtz Enn-zu-Enn Verschlësselung mat OMEMO an OpenPGP an enthält Privatsphäre-Astellungen zu Liesbestätegungen an Tipp-Benoriichtegungen.

OMEMO と OpenPGP を利用したエンドツーエンド暗号化に対応しており、既読状態の送信や入力通知などのプライバシー関連の設定も可能です。

Support la crittografia end-to-end tramite OMEMO e OpenPGP e permette di configurare le funzioni relative alla privacy come le ricevute di lettura e le notifiche di digitazione.

It supporta ciffration terminal per OMEMO e OpenPGP e permisse configurar sensitiv functiones quam confirmation de lectada e notificationes de tippada.

Mendukung enkripsi end-to-end dengan OMEMO dan OpenPGP, dan memungkinkan pengaturan fitur terkait privasi seperti tanda pesan dibaca dan pemberitahuan pengetikan.

Támogatja az OMEMO és az OpenPGP használatával történő végpontok közötti titkosítást, és lehetővé teszi a magánszférához kapcsolódó funkciókat, mint például az olvasási visszaigazolást és a gépelési értesítéseket.

Suporta o cifrado de punto-a-punto con OMEMO e OpenPGP e permite configurar trazos orientados á privacidade tales coma confirmación de lectura e notificacións de escritura.

Il prend en charge le chiffrement de bout en bout avec OMEMO et OpenPGP et permet de configurer les fonctions liées à la confidentialité telles que les accusés de réception et les notifications d’écriture.

Se tukee päästä päähän -salausta OMEMO:n ja OpenPGP:n avulla ja mahdollistaa yksityisyyteen liittyvien ominaisuuksien, kuten lukukuittausten ja kirjoitusilmoitusten asetusten määrittämisen.

از رمزگذاری سرتاسر با اُمیمو و اُپن‌پی‌جی‌پی پشتیبانی می‌کند و اجازه تنظیم قابلیت‌های مربوط به حریم خصوصی را می‌دهد، از جمله: رسید خوانده‌شدن پیام‌ها و اعلان در حال نوشتن بودن.

Amaieratik amaierarako enkriptazioa onartzen du OMEMO eta OpenPGPrekin eta pribatutasun ezaugarriak konfiguratzea baimentzen du irakurtze markak eta idazketa jakinarazpenak bezala.

Soporta encriptación fin-a-fin a través de OMEMO y OpenPGP y permite configurar las características relacionadas con la privacidad, como confirmaciones de lectura y notificaciones de escritura.

Ĝi subtenas fin-al-finan ĉifradon per OMEMO kaj OpenPGP kaj permesas agordi funkciojn pri privateco kiel kvitancojn de legiteco kaj sciigojn pri tajpado.

Er unterstützt Ende-zu-Ende Verschlüsselung mit OMEMO und OpenPGP und enthält Privatsphäre-Einstellungen zu Lesebestätigungen und Tippbenachrichtigungen.

Podporuje šifrování end-to-end pomocí OMEMO a OpenPGP a umožňuje konfigurovat funkce související se soukromím, jako jsou potvrzení o přečtení a oznámení o psaní.

Implementa xifratge punt a punt amb OMEMO i OpenPGP, i permet configurar funcionalitats relacionades amb la privacitat com per exemple rebuts de lectura i notificacions d'escriptura.

وهو يدعم التشفير بواسطة OMEMO و OpenPGP يسمح بإعداد ميزات الخصوصية كالإيصالات المقروءة والإخطارات عند الكتابة.

Dino fetches history from the server and synchronizes messages with other devices.

Dino 從伺服器取得訊息並與其他裝置同步。

Dino 从服务器获取消息并和其他设备同步。

Dino sunucudan konuşma geçmişini sunucudan çeker ve diğer cihazlara senkronize eder.

Dino hämtar historik från servern och synkroniserar meddelanden med andra enheter.

Dino e sjell historikun prej shërbyesve dhe njëkohëson mesazhet në pajisje të tjera.

Dino загружает историю с сервера и синхронизирует сообщения с другими устройствами.

Dino preia istoricul discuțiilor de pe server și sincronizează mesajele cu celelalte dispozitive.

Dino obtém o histórico do servidor e sincroniza mensagens com outros dispositivos.

Dino obtém o histórico do servidor e sincroniza mensagens com outros aparelhos.

Dino pobiera historię rozmów z serwera i synchronizuje wiadomości z innymi urządzeniami.

Dino recupèra l’istoric del servidor e sincroniza los messatges amb d’autres periferics.

Dino haalt de geschiedenis op van de server en synchroniseert berichten met andere apparaten.

Dino haalt de geschiedenis op van de server en synchroniseert berichten met andere apparaten.

Dino henter historikk fra tjeneren og synkroniserer meldinger med andre enheter.

Dino gauna istoriją iš serverio ir sinchronizuoja žinutes su kitais įrenginiais.

Dino rifft  Gespréichverläf vum Server of a synchroniséiert Noriichte mat anere Geräter.

Dino はサーバーから履歴を取得し、ほかのデバイスとメッセージを同期します。

Dino recupera la cronologia dal server e sincronizza i messaggi con gli altri dispositivi.

Dino obtene li diarium del servitore e sincronisa missages inter altri apparates.

Dino mengambil riwayat pesan dari server dan menyinkronkan pesan dengan perangkat lain.

A Dino lekéri az előzményeket a kiszolgálóról, és szinkronizálja az üzeneteket a többi eszközzel.

Dino obtén o histórico dende o servidor e sincroniza as mensaxes con outros dispositivos.

Dino récupère l’historique du serveur et synchronise les messages avec les autres clients.

Dino hakee historian palvelimelta ja synkronisoi viestit muiden laitteiden kanssa.

دینو تاریخچه را از سرور دریافت می‌کند و پیام‌ها را با دیگر دستگاه‌ها همگام‌سازی می‌کند.

Dinok zerbitzaritik hartzen du historia eta beste gailuekin mezuak sinkronizatzen ditu.

Dino recupera el historial de mensajes desde el servidor y sincroniza los mensajes con otros dispositivos.

Dino prenas historion el la servilo kaj sinkronigas mesaĝojn kun aliaj aparatoj.

Dino ruft Gesprächsverläufe vom Server ab und synchronisiert Nachrichten mit anderen Geräten.

Dino načítá historii ze serveru a synchronizuje zprávy s ostatními zařízeními.

Dino recupera l'historial del servidor i sincronitza els missatges amb altres dispositius.

يقوم Dino بجلب السِجلّ مِن السيرفر ثم يُزامِن الرسائل مع الأجهزة الأخرى.

https://dino.im/img/appdata/2022-02_screenshot-main.png https://dino.im/img/appdata/2022-02_screenshot-call.png https://dino.im/img/appdata/start_chat.png dino Dino Development Team https://dino.im https://github.com/dino/dino/issues https://dino.im/#donate https://hosted.weblate.org/projects/dino/ appstream@dino.im
dino-0.3.0/main/data/im.dino.Dino.appdata.xml.in0000644000000000000000000000413714202022370017760 0ustar rootroot im.dino.Dino im.dino.Dino.desktop CC0-1.0 GPL-3.0+ Dino Modern XMPP Chat Client

Dino is a modern open-source chat client for the desktop. It focuses on providing a clean and reliable Jabber/XMPP experience while having your privacy in mind.

It supports end-to-end encryption with OMEMO and OpenPGP and allows configuring privacy-related features such as read receipts and typing notifications.

Dino fetches history from the server and synchronizes messages with other devices.

https://dino.im/img/appdata/2022-02_screenshot-main.png https://dino.im/img/appdata/2022-02_screenshot-call.png https://dino.im/img/appdata/start_chat.png dino Dino Development Team https://dino.im https://github.com/dino/dino/issues https://dino.im/#donate https://hosted.weblate.org/projects/dino/ appstream@dino.im
dino-0.3.0/main/data/im.dino.Dino.desktop0000644000000000000000000000046014202022370016606 0ustar rootroot[Desktop Entry] Version=1.0 Name=Dino GenericName=Jabber/XMPP Client Keywords=chat;talk;im;message;xmpp;jabber; Exec=dino %U Icon=im.dino.Dino StartupNotify=false Terminal=false Type=Application Categories=GTK;Network;Chat;InstantMessaging; X-GNOME-UsesNotifications=true MimeType=x-scheme-handler/xmpp; dino-0.3.0/main/data/im.dino.Dino.service0000644000000000000000000000005414202022370016574 0ustar rootroot[D-BUS Service] Name=im.dino.Dino Exec=dino dino-0.3.0/main/data/manage_accounts/0000755000000000000000000000000014202022370016115 5ustar rootrootdino-0.3.0/main/data/manage_accounts/account_row.ui0000644000000000000000000000266414202022370021007 0ustar rootroot dino-0.3.0/main/data/manage_accounts/add_account_dialog.ui0000644000000000000000000014764614202022370022261 0ustar rootroot dino-0.3.0/main/data/manage_accounts/dialog.ui0000644000000000000000000005112514202022370017717 0ustar rootroot dino-0.3.0/main/data/menu_add.ui0000644000000000000000000000106514202022370015103 0ustar rootroot
app.add_chat Start Conversation
app.add_conference Join Channel
dino-0.3.0/main/data/menu_app.ui0000644000000000000000000000164214202022370015134 0ustar rootroot
app.accounts Accounts app.settings Settings
app.open_shortcuts Keyboard Shortcuts app.about About Dino
dino-0.3.0/main/data/menu_conversation.ui0000644000000000000000000000053214202022370017063 0ustar rootroot
app.contact_details Contact Details
dino-0.3.0/main/data/menu_encryption.ui0000644000000000000000000000275414202022370016553 0ustar rootroot False True False vertical 10 Unencrypted True True False True True False True 0 main dino-0.3.0/main/data/message_item_widget_edit_mode.ui0000644000000000000000000000631714202022370021352 0ustar rootroot dino-0.3.0/main/data/occupant_list.ui0000644000000000000000000000301414202022370016172 0ustar rootroot dino-0.3.0/main/data/occupant_list_item.ui0000644000000000000000000000424014202022370017212 0ustar rootroot dino-0.3.0/main/data/search_autocomplete.ui0000644000000000000000000000166514202022370017363 0ustar rootroot horizontal True 4 6 6 24 24 True False True end dino-0.3.0/main/data/settings_dialog.ui0000644000000000000000000001103514202022370016504 0ustar rootroot dino-0.3.0/main/data/shortcuts.ui0000644000000000000000000000745614202022370015377 0ustar rootroot True True shortcuts True General True <ctrl>T Start Conversation True <ctrl>G Join Channel True Conversation True <ctrl>F Search messages True <ctrl>U Send a file True Navigation True <ctrl>Tab Jump to next conversation True <ctrl><Shift>Tab Jump to previous conversation dino-0.3.0/main/data/theme.css0000644000000000000000000002064614202022370014612 0ustar rootroot/** * This theme file is applied after the operating system theme * It provides sane defaults for things that are very Dino-specific. */ window.dino-main .dino-header-right { background: @theme_base_color; } window.dino-main .dino-header-left { background: @insensitive_bg_color; } window.dino-main headerbar.dino-left label.title { opacity: 0; font-size: 0; color: transparent; } window.dino-main .dino-conversation { background: @theme_base_color; } window.dino-main .dino-conversation undershoot, window.dino-main .dino-conversation viewport /* Some themes set this */ { background: none; } @keyframes highlight { from { background: alpha(@warning_color, 0.5) } to { background: transparent } } window.dino-main .dino-conversation .highlight-once { animation-duration: 3s; animation-timing-function: ease-out; animation-iteration-count: 1; animation-name: highlight; } window.dino-main .dino-conversation .message-box:hover { background: alpha(@theme_fg_color, 0.04); } window.dino-main .unread-count-notify { background-color: alpha(@theme_fg_color, 0.8); color: @theme_base_color; font-family: monospace; border-radius: 999em; padding: .2em .41em; } window.dino-main .unread-count-notify:backdrop { background-color: alpha(@theme_unfocused_fg_color, 0.8); color: @theme_unfocused_base_color; } window.dino-main .unread-count { background-color: alpha(@theme_fg_color, 0.1); color: @theme_fg_color; font-family: monospace; border-radius: 999em; padding: .2em .41em; } window.dino-main .unread-count:backdrop { background-color: alpha(@theme_unfocused_fg_color, 0.1); color: @theme_unfocused_fg_color; } window.dino-main .dino-sidebar > frame { background: @insensitive_bg_color; border-left: 1px solid @borders; border-bottom: 1px solid @borders; } .message-box { transition: background .05s ease; } window.dino-main .circular-button { padding: 0; border-radius: 1000px; } window.dino-main .dino-conversation .message-box.edit-mode { background: alpha(@theme_selected_bg_color, 0.1); } window.dino-main .dino-conversation .message-box.edit-mode:hover { background: alpha(@theme_selected_bg_color, 0.12); } window.dino-main .dino-conversation .message-box.error { background: alpha(@error_color, 0.1); } window.dino-main .dino-conversation .message-box.error:hover { background: alpha(@error_color, 0.12); } window.dino-main .file-box-outer, window.dino-main .call-box-outer { background: @theme_base_color; border-radius: 3px; border: 1px solid alpha(@theme_fg_color, 0.1); } window.dino-main .file-box, window.dino-main .call-box { margin: 12px 16px 12px 12px; } window.dino-main .file-image-widget { border: 1px solid alpha(@theme_fg_color, 0.1); border-radius: 3px; } window.dino-main .file-image-widget .file-box-outer { color: #eee; background: rgba(0, 0, 0, 0.5); } window.dino-main .file-image-widget .file-box-outer button { color: #eee; background: transparent; border: none; box-shadow: none; } window.dino-main .file-image-widget .file-box-outer button:hover { background: rgba(100, 100, 100, 0.5); } window.dino-main .call-box-outer.incoming { border-color: alpha(@theme_selected_bg_color, 0.3); } window.dino-main .incoming-call-box { background: alpha(@theme_selected_bg_color, 0.1); } window.dino-main .multiparty-participants { border-top: 1px solid alpha(@theme_fg_color, 0.05); background: alpha(@theme_fg_color, 0.04) } window.dino-main .dino-sidebar > frame.collapsed { border-bottom: 1px solid @borders; } window.dino-main .dino-sidebar frame.auto-complete { background: @theme_base_color; } window.dino-main .dino-sidebar frame.auto-complete list > row { transition: none; } window.dino-main .dino-white-overlay { background: @theme_base_color; } window.dino-main .dino-file-overlay { border-radius: 5px; border: 1px solid alpha(black, 0.2); box-shadow: 0 2px 3px alpha(black, 0.1); } window.dino-main .dino-chatinput frame box { background: transparent; } window.dino-main button.dino-attach-button { min-width: 24px; /* Make button the same with as avatars */ } window.dino-main button.dino-chatinput-button { border: none; background: transparent; box-shadow: none; min-height: 0; padding: 7px 5px; color: alpha(@theme_fg_color, 0.6); outline: none; } window.dino-main button.dino-chatinput-button:hover { color: @theme_selected_bg_color; } window.dino-main button.dino-chatinput-button:backdrop { color: alpha(@theme_unfocused_fg_color, 0.6); } window.dino-main button.dino-chatinput-button:active, window.dino-main button.dino-chatinput-button:checked { color: alpha(@theme_selected_bg_color, 0.8); } window.dino-main button.dino-chatinput-button:checked:backdrop { color: alpha(@theme_unfocused_selected_bg_color, 0.8); } .dino-chatinput, .dino-chatinput textview, .dino-chatinput textview text { background-color: @theme_base_color; } /*Chat input warning*/ box.dino-input-warning frame border { border-color: @warning_color; } box.dino-input-warning frame separator { background-color: @warning_color; border: none; } box.dino-input-warning label { color: mix(@warning_color, @theme_fg_color, 0.5); } /*Chat input error*/ box.dino-input-error frame border { border-color: @error_color; } box.dino-input-error frame separator { background-color: @error_color; border: none; } box.dino-input-error label { color: @error_color; } @keyframes input-error-highlight { 0% { color: mix(@error_color, @theme_fg_color, 0.3);} 30% { color: @error_color; text-shadow: 0px 0px 2px alpha(@error_color, 0.4); } 100% { color: mix(@error_color, @theme_fg_color, 0.3); } } box.dino-input-error label.input-status-highlight-once { animation-duration: 1s; animation-timing-function: linear; animation-iteration-count: 1; animation-name: input-error-highlight; } /* Call window */ .dino-call-window decoration { border-radius: 0; } .dino-call-window .titlebar { min-height: 0; } .dino-call-window headerbar, .call-button { box-shadow: none; } .dino-call-window button.call-button { outline: 0; border-radius: 1000px; } .dino-call-window button.white-button { color: #1d1c1d; background: rgba(255,255,255,0.85); border: lightgrey; } .dino-call-window button.white-button:hover { background: rgba(255,255,255,1); } .dino-call-window button.transparent-white-button { color: white; background: rgba(255,255,255,0.15); border: none; } .dino-call-window button.transparent-white-button:hover { background: rgba(255,255,255,0.25); } .dino-call-window button.call-mediadevice-settings-button { border-radius: 1000px; min-height: 0; min-width: 0; padding: 3px; margin: 2px; transition-duration: 0; } .dino-call-window button.call-mediadevice-settings-button:hover, .dino-call-window button.call-mediadevice-settings-button:checked { /* Effect that makes the button slightly larger on hover :) */ border-radius: 1000px; min-height: 0; min-width: 0; padding: 5px; margin: 0; } .dino-call-window .participant-header-bar { background: none; border: none; border-radius: 0; color: #ededec; text-shadow: 0 0 2px black; } .dino-call-window .call-header-background { background: linear-gradient(rgba(0,0,0,0.4), rgba(0,0,0,0)); border: 0; border-radius: 0; } .dino-call-window .participant-header-bar button { background: none; } .dino-call-window .participant-header-bar button:hover { background: rgba(255,255,255,0.15); border-color: rgba(255,255,255,0); box-shadow: none; } .dino-call-window .participant-header-bar button image { color: alpha(white, 0.7); -gtk-icon-shadow: none; } .dino-call-window .participant-header-bar button:hover image { color: white; } .dino-call-window .participant-header-bar button.unencrypted image { color: @error_color; } .dino-call-window .call-bottom-bar { background: linear-gradient(rgba(0,0,0,0), rgba(0,0,0,0.3)); border: 0; } .dino-call-window { background-color: #212121; } .dino-call-window .participant-name { color: white; text-shadow: black; } .dino-call-window .text-no-controls { color: black; background: white; border-radius: 5px; padding: 5px 10px; } .dino-call-window .own-video { box-shadow: 0 0 2px 0 rgba(0,0,0,0.5); }dino-0.3.0/main/data/unified_main_content.ui0000644000000000000000000001667214202022370017522 0ustar rootroot 300 horizontal True True True never True True content 20 10 start start True True start True 70 50 True end Click here to start a conversation or join a channel. placeholder False False True True True content vertical True center center True im.dino.Dino-symbolic 144 30 True True You have no open chats 0.5 0.5 placeholder True end slide-left True 400 none True True False dino-0.3.0/main/data/unified_window_placeholder.ui0000644000000000000000000000605314202022370020705 0ustar rootroot dino-0.3.0/main/po/0000755000000000000000000000000014202022370012473 5ustar rootrootdino-0.3.0/main/po/LINGUAS0000644000000000000000000000020214202022370013512 0ustar rootrootar ca cs da de el en eo es eu fa fi fr gl hu id ie it ja kab ko lb lt nb nl nl_BE oc pl pt pt_BR ro ru sq sv ta tr uk zh_CN zh_TW dino-0.3.0/main/po/ar.po0000644000000000000000000011263114202022370013441 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2022-02-06 17:57+0000\n" "Language-Team: Arabic \n" "Language: ar\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " "&& n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n" "X-Generator: Weblate 4.11-dev\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "حجم الملف يتجاوز الحد الذي يسمح به الخادم." #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "أنا" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "أُرسلت الصورة" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "أُرسل الملف" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "استلمت الصورة" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "اُستلم الملف" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "مكالمة صادرة" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "مكالمة واردة" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "%b %d" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "البارحة" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "%H∶%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "%l∶%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "في هذه اللحظة" msgstr[1] "منذ دقيقة واحدة" msgstr[2] "منذ دقيقتين" msgstr[3] "منذ %i دقائق" msgstr[4] "منذ %i دقيقة" msgstr[5] "منذ %i دقيقة" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "الآن" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "المالك" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "المدير" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "عضو" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "مستخدِم" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "ادعو" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "ابدأ محادثة خاصة" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "اطرده" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "منح تصريح الكتابة" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "إبطال تصريح الكتابة" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "دعوة إلى فريق المحادثة" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "اختر ملفا" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "اختر" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "ألغاء" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "%s مِن %s" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "هذا المؤتمر لا يسمح لك بإرسال الرسائل." #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "طلب الإذن" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "ابدأ مكالمة" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "مكالمة صوتية" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "مكالمة مرئية" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "ابحث في الرسائل" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "الأعضاء" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "يتصل…" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "يرِن…" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "الإتصال جارٍ …" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "أنهى %s المكالمة" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "رفض %s المكالمة" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "" #: main/src/ui/call_window/call_window_controller.vala:128 msgid "Invite to Call" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "ابدأ محادثة" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "إبدأ" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "انضم للقناة" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "التالي" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "انضمّ" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "العودة" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "ينضم…" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "كلمة السر مطلوبة للدخول إلى غرفة المحادثة" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "محظور مِن الإنضمام أو إنشاء فِرَق محادثة" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "الغرفة غير موجودة" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "لا تملك تصريحًا لإنشاء غرفة" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "غرفة خاصة لذوي العضوية" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "اختر لقبًا آخر" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "الغرفة مكتضة" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "تعذر الاتصال مع %s" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "العنوان غير صالح" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "اضف" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "ختصارات لوحة المفاتيح" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "عن Dino" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "لا نتائج" msgstr[1] "نتيجة واحدة" msgstr[2] "نتيجتان" msgstr[3] "%i نتائج" msgstr[4] "%i نتيجة" msgstr[5] "%i نتيجة" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "في %s" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "مع %s" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "مكالمة مرئية واردة" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "رفض" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "قبول" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "طلب اشتراك" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "رفض" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "دعوة إلى %s" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "دعاك %s إلى %s" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "طلب إذن" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "طلب %s الاذن بالكتابة في %s" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "الإعدادات" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "الإعدادات المحلية" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "إرسال إشعارات عند الكتابة" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "إرسال إيصالات القراءة" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "الإشعارات" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "نشط" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "معطّل" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "فقط عندما أُذكر" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "الافتراضي: %s" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "اطلب" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "التصريحات" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "اطلب تصريحًا لكتابة الرسائل" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "تفاصيل فريق المحادثة" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "تفاصيل عن المُتراسل" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "احجبه" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "حجب الإتصال وتحديثات الحالة في كلا الإتجاهين" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "اسم الغرفة" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "وصف الغرفة" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "دائمة" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "سوف تبقى الغرفة حتى و إن غادرها آخِر مقيم" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "يمكن البحث عنها للعلن" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "يمكن للمقيمين تغيير الموضوع" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "السماح بعرض JIDs" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "من يمكنه رؤية JIDs الخاصة بالمشارِكين؟" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "كلمة السر" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "كلمة مرور لتقييد الوصول إلى الغرفة" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "تحت الإشراف" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "إلا المشاركون الذين يتمتعون بحق الكتابة يمكنهم ارسال الرسائل" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "الأعضاء فقط" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "يمكن للأعضاء فقط الإلتحاق بالغرفة" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "إدارة التأريخ" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "الحد الأقصى للسجِل المؤرَّخ الصادر عن فريق المحادثة" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "إعدادات الغرفة" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "مرحبا بكم على Dino!" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "إتّصل أو قم بإنشاء حساب للمواصلة." #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "تهيئة الحساب" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "ليس هناك أية حسابات نشطة" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "إدارة الحسابات" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "كلمة المرور خاطئة" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "شهادة TLS غير صالحة" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "إزالة الحساب %s ؟" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "حذف" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "اختيار الصورة الرمزية" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "الصور" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "كافة الملفات" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "متصل" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "غير متصل" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "خطأ" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "إضافة حساب" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "لم يتمكن الخادم من إثبات أنه %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "شهادة الأمان الخاصة به غير موثوق فيها من قبل نظامك للشغيل." #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "أصدرت شهادة الأمان الخاصة به إلى مجال آخر." #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "ستصبح شهادة الأمان الخاصة به صالحة فقط في المستقبل." #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "انتهت صلاحية شهادة الأمان الخاصة به." #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "تسجيل الدخول إلى %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "يمكنك الآن استخدام حساب %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "اسم المستخدم أو كلمة السر غير صحيحة" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "حدث خطأ ما" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "لا ردّ مِن طرف الخادم" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "قم بالتسجيل على %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "يتطلب الخادم التسجيل من خلال موقع ويب" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "افتح موقع الويب" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "إنشاء حساب" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "إطّلع على %s للمزيد من المعلومات حول التسجيل" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "الرسالة طويلة جدًا" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "تم تعديله" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "فشل التسليم" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, c-format msgid "%s, %s and %i others are typing…" msgstr "%s و %s و %i آخرون يكتبون…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "%s و %s و %s يكتبون…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "%s و %s يكتبان…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "%s يكتب حاليا…" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "بدأتْ المكالمة" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "بدأتْ منذ %s" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "ردَدْتَ على هذه المكالمة من جهاز آخر" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "انتهت المكالمة" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "انتهت على %s" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "دامتْ %s" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "مكالمة فائتة" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "فاتتك هذه المكالمة" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "فاتت %s هذه المكالمة" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "تم رفض المكالمة" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "لقد رفضت هذه المكالمة" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "رفض %s هذه المكالمة" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "فشلتْ المكالمة" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgstr[3] "" msgstr[4] "" msgstr[5] "" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgstr[3] "" msgstr[4] "" msgstr[5] "" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "غير مشفّرة" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "تعذر إرسال الرسالة" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "%x, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "%x, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "%b %d, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "%b %d, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "%a, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "%a, %l∶%M %p" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "يريد هذا المراسل إضافتك إلى قائمة مراسليه" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "جارٍ تنزيل %s…" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "عرض عليك %s: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "الملف المقدّم: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "تم تقديم الملف" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "فشل نقل الملف" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "اليوم" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "%a, %b %d" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "إرسال ملف" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "تحديث الرسالة" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "اضغط هنا لبداية المحادثة أو للإنضمام إلى قناة." #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "ليس لديك أية محادثة مفتوحة" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "الحساب" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "الإسم المستعار" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "اللقب" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "إضافة مُراسِل" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "الإشعار عند تلقي رسائل جديدة" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "تحويل الوجوه المبتسمة إلى إيموجي" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "التدقيق الإملائي" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "تطبيق حديث للدردشة عبر XMPP" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "إنّ Dino برنامج عصري ومفتوح المصدر للدردشة صُمّم لسطح المكتب. ويُركّز علي تقديم " "تجربة نظيفة وموثوق منها لجابر/XMPP مع أخذ خصوصيتكم بعين الإعتبار." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "وهو يدعم التشفير بواسطة OMEMO و OpenPGP يسمح بإعداد ميزات الخصوصية " "كالإيصالات المقروءة والإخطارات عند الكتابة." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "يقوم Dino بجلب السِجلّ مِن السيرفر ثم يُزامِن الرسائل مع الأجهزة الأخرى." #: main/data/global_search.ui:37 msgid "No active search" msgstr "لا يوجد بحث نشط" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "أكتب للشروع في البحث" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "لا توجد رسائل متطابقة" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "تحقق مِن التدقيق الإملائي أو حاول إزالة فلاتر" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "أرسل" #: main/data/shortcuts.ui:12 msgid "General" msgstr "عام" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "محادثة" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "الإبحار" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "الإنتقال إلى المحادثة التالية" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "الإنتقال إلى المحادثة السابقة" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "الحسابات" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "اللقب المحلي" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "لم يتم إعداد أي حساب بعد" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "إضافة حساب" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "إنشاء حساب" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "إنشاء حساب" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "لا يمكن إنشاء اتصال آمن" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "اتصل" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "إختر خادمًا عموميًا" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "أو قم بإدخال عنوان خادم" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "قم بتسجيل الدخول بدلاً من ذلك" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "إختيار سيرفر آخَر" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "لقد أعددت كل شيء!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "أنهي" #~ msgid "No active conversations" #~ msgstr "ليس هناك أية محادثات نشطة" #~ msgid "Main window with conversations" #~ msgstr "النافذة الرئيسية بالمُحادثات" #~ msgid "%s, %s and %i others" #~ msgstr "%s و %s و %i آخَرين" #~ msgid "You can now start using %s" #~ msgstr "بإمكانك الآن الشروع في استخدام %s" #~ msgid "Open Registration" #~ msgstr "التسجيلات مفتوحة" #~ msgid "Save" #~ msgstr "حفظ" #~ msgid "%s, %s and %s" #~ msgstr "%s, %s و %s" #~ msgid "%s and %s" #~ msgstr "%s و %s" #~ msgid "is typing…" #~ msgid_plural "are typing…" #~ msgstr[0] "يكتب…" #~ msgstr[1] "يكتب…" #~ msgstr[2] "يكتُبان…" #~ msgstr[3] "يكتبون…" #~ msgstr[4] "يكتبون…" #~ msgstr[5] "يكتبون…" #~ msgid "has stopped typing" #~ msgstr "توقفَ عن الكتابة" #~ msgid "%i search results" #~ msgstr "%i نتيجة بحث" #~ msgid "Discover real JIDs" #~ msgstr "استكشاف مُعرّفي JID الحقيقيين" #~ msgid "Who may discover real JIDs?" #~ msgstr "من يمكنهم استكشاف مُعرّفي JID الحقيقيين؟" #~ msgid "Password required for room entry, if any" #~ msgstr "مطلوب كلمة السر لدخول الغرفة، إن وُجِدت" #~ msgid "Failed connecting to %s" #~ msgstr "فشل الاتصال بـ %s" #~ msgid "Join Conference" #~ msgstr "الإنضمام إلى فريق محادثة" #~ msgid "File" #~ msgstr "ملف" #~ msgid "Communicate happiness." #~ msgstr "أنشر السعادة." #~ msgid "Preferences" #~ msgstr "التفضيلات" #~ msgid "Quit" #~ msgstr "الخروج" #~ msgid "JID should be of the form “user@example.com”" #~ msgstr "يجب أن يكون JID على شكل “user@example.com”" #~ msgid "Copy Link Address" #~ msgstr "نسخ العنوان" #~ msgid "Copy" #~ msgstr "نسخ" #~ msgid "Select All" #~ msgstr "اختيار الكل" #~ msgid "Search" #~ msgstr "البحث" #~ msgid "Start Chat" #~ msgstr "إبدأ الدردشة" dino-0.3.0/main/po/ca.po0000644000000000000000000010545114202022370013424 0ustar rootroot# Catalan translation for Dino. # This file is distributed under the same license as the dino package. # Jordi Mallach , 2018-2019. # msgid "" msgstr "" "Project-Id-Version: dino 20180510\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2022-01-09 03:56+0000\n" "Language-Team: Catalan; Valencian \n" "Language: ca\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: Weblate 4.10.1\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "El fitxer supera la mida màxima de càrrega del servidor." #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "Jo" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "Imatge enviada" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "Fitxer enviat" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "Imatge rebuda" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "Fitxer rebut" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "Trucada sortint" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "Trucada entrant" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "%b %d" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "Ahir" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "%H.%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "%l.%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "fa %i minut" msgstr[1] "fa %i minuts" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "Ara mateix" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "Propietari" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "Administrador" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "Membre" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "Usuari" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "Invita" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "Inicia una convesa privada" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "Expulsa" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "Autoritza el permís d'escriptura" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "Revoca el permís d'escriptura" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "Invita a la conferència" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "Selecciona el fitxer" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Selecciona" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Cancel·la" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "%s de %s" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "Aquesta conferència no us permet enviar missatges." #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "Sol·licita el permís" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "Inicia la trucada" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "Trucada d'àudio" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "Trucada de vídeo" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "Cerca els missatges" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Membres" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "Trucant…" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "Sonant…" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "S'està connectant…" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "%s trucada finalitzada" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "%s ha rebutjat la trucada" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "" #: main/src/ui/call_window/call_window_controller.vala:128 msgid "Invite to Call" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "Inicia una conversa" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Inicia" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Uneix-m'hi al canal" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Següent" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "Uneix-te" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Enrere" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "S'està unint…" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "Es requereix una contrasenya per entrar a la sala" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "Heu estat bandejat d'unir-vos o crear conferències" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "La sala no existeix" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "No esteu autoritzat a crear una sala" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "Sala només per membres" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "Seleccioneu un sobrenom diferent" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "Massa ocupants a la sala" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "No s'ha pogut connectar a %s" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "L'adreça no és vàlida" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Afegeix" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Dreceres de teclat" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "Quant al Dino" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "%i resultat de cerca" msgstr[1] "%i resultats de cerca" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "En %s" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "Amb %s" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "Trucada de vídeo entrant" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "Rebutjar" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "Accepta" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "Petició de subscripció" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "Rebutja" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "Invitació a %s" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "%s us ha convidat a %s" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "Sol·licitud de permís" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "%s demana el permís per a escriure a %s" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Preferències" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Preferències locals" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Envia notificacions de tecleig" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Envia rebuts de lectura" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Notificacions" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "Habilitat" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "Inhabilitat" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "Només quan rebeu una menció" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "Per defecte: %s" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "Sol·licita" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "Permisos" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "Sol·licita permís per a enviar missatges" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Detalls de la conferència" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "Detalls del contacte" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Bloca" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "" "La comunicació i actualitzacions d'estat en ambdúes direccions estan blocades" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Nom de la sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Descripció de la sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Persistent" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "La sala romandrà després de la sortida del l'últim ocupant" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Cercable públicament" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Els ocupants poden canviar el tema" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "Permís per a veure JID" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "Qui té permís per a veure els JID dels membres?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "Contrasenya" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "Una contrasenya per a restringir l'accés a la sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Moderat" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Només els ocupants amb veu poden enviar missatges" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Només per membres" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Només els membres poden entrar a la sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Historial dels missatges" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Quantitat màxima de l'historial proporcionat per la sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Configuració de la sala" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "Us donem la benvinguda al Dino!" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "Entreu o creeu un compte per a començar." #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "Configureu un compte" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "No hi ha cap compte actiu" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "Gestiona els comptes" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Contrasenya incorrecta" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "El certificat TLS és invàlid" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Voleu suprimir el compte %s?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Suprimeix" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Seleccioneu l'àvatar" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Imatges" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Tots els fitxers" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Connectat" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Desconnectat" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Error" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Afegeix un compte" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "El servidor no ha pogut demostrar que és %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "" "El seu certificat de seguretat no és de confiança pel seu sistema operatiu." #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "El seu certificat de seguretat s'emet a un altre domini." #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "El seu certificat de seguretat només serà vàlid en el futur." #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "El seu certificat de seguretat ha caducat." #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "Entra a %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "Ara podeu emprar el compte %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "Usuari o contrasenya incorrecta" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "Ha fallat alguna cosa" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "No s'ha rebut una resposta del servidor" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "Registre en %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "El servidor requereix un registre a través d'una pàgina web" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "Obre el lloc web" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "Registre" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "Mireu %s per obtenir informació sobre com registrar-vos" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "El missatge és massa llarg" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "editat" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "pendent…" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "l'enviament ha fallat" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, c-format msgid "%s, %s and %i others are typing…" msgstr "%s, %s i %i altres estan escrivint…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "%s, %s i %s estan escrivint…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "%s i %s estan escrivint…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "%s està escrivint…" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "S'ha iniciat la trucada" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "Iniciat fa %s" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "Heu gestionat aquesta trucada en un altre dispositiu" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "La trucada ha finalitzat" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "Acabat a %s" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "Durada %s" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "Trucada perduda" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "T'has perdut aquesta trucada" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "%s s'ha perdut aquesta trucada" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "Trucada rebutjada" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "Has rebutjat aquesta trucada" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "%s ha rebutjat aquesta trucada" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "La trucada ha fallat" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "%d hora" msgstr[1] "%d hores" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "%d minut" msgstr[1] "%d minuts" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "uns segons" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Sense xifrar" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "No s'ha pogut enviar el missatge" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "%x, %H.%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "%x, %l.%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "%b %d, %H.%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "%b %d, %l.%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "%a, %H.%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "%a, %l.%M %p" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Aquest contacte vol afegir-vos a la seva llista de contactes" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "S'està baixant %s…" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "%s ha oferit: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "Fitxer oferit: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "Fitxer oferit" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "Ha fallat la transferència del fitxer" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "Avui" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "%a, %b %d" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "Envia un fitxer" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "Actualitza el missatge" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "Feu clic aquí per a començar una conversa o unir-vos a un canal." #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "No teniu cap xat obert" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Compte" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Sobrenom" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Àlias" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Afegeix el contacte" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Notifica'm quan arriba un missatge nou" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Converteix «smileys» a emoji" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "Revisa l'ortografia" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Client de xat XMPP modern" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino és un client de xat lliure i modern per a l'escriptori. Està centrat en " "proveir una experiència neta i fiable de Jabber/XMPP, sempre tenint en " "compte la vostra privacitat." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Implementa xifratge punt a punt amb OMEMO i OpenPGP, i permet configurar " "funcionalitats relacionades amb la privacitat com per exemple rebuts de " "lectura i notificacions d'escriptura." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino recupera l'historial del servidor i sincronitza els missatges amb " "altres dispositius." #: main/data/global_search.ui:37 msgid "No active search" msgstr "No hi ha cap cerca activa" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Teclegeu per a començar una cerca" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "No hi ha missatges coincidents" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Comproveu l'ortografia o proveu de suprimir filtres" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "Envia" #: main/data/shortcuts.ui:12 msgid "General" msgstr "General" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "Conversa" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "Navegació" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "Salta a la conversa següent" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "Salta a la conversa anterior" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "Comptes" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Àlias local" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "No hi ha cap compte configurat" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Afegeix un compte" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Entra" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Crea un compte" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "No s'ha pogut una connexió segura" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Connecta" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Trieu un servidor públic" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "O especifiqueu l'adreça d'un servidor" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Entra" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Trieu un altre servidor" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Tot llest!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Finalitza" #~ msgid "No active conversations" #~ msgstr "Cap conversa activa" #~ msgid "Main window with conversations" #~ msgstr "Finestra principal amb converses" #~ msgid "%s, %s and %i others" #~ msgstr "%s, %s i %i altres" #~ msgid "You can now start using %s" #~ msgstr "Ara podeu començar a emprar %s" #~ msgid "Open Registration" #~ msgstr "Registre obert" #~ msgid "Save" #~ msgstr "Desa" #~ msgid "%s, %s and %s" #~ msgstr "%s, %s i %s" #~ msgid "%s and %s" #~ msgstr "%s i %s" #~ msgid "is typing…" #~ msgid_plural "are typing…" #~ msgstr[0] "està escrivint…" #~ msgstr[1] "estan escrivint…" #~ msgid "has stopped typing" #~ msgstr "ha parat d'escriure" #~ msgid "%i search results" #~ msgstr "%i resultats de cerca" #~ msgid "Discover real JIDs" #~ msgstr "Descobreix els JID reals" #~ msgid "Who may discover real JIDs?" #~ msgstr "Qui pot descobrir els JID reals?" #~ msgid "Password required for room entry, if any" #~ msgstr "La contrasenya requerida per entrar a la sala, si cal" #~ msgid "Failed connecting to %s" #~ msgstr "No s'ha pogut connectar a %s" #~ msgid "Join Conference" #~ msgstr "Uneix-te a la conferència" #~ msgid "File" #~ msgstr "Fitxer" #~ msgid "Preferences" #~ msgstr "Preferències" #~ msgid "Quit" #~ msgstr "Surt" #~ msgid "Copy Link Address" #~ msgstr "Copia l'adreça de l'enllaç" #~ msgid "Copy" #~ msgstr "Copia" #~ msgid "Select All" #~ msgstr "Selecciona-ho tot" #~ msgid "Search" #~ msgstr "Cerca" #~ msgid "Send message marker" #~ msgstr "Envia marcador de missatge" #~ msgid "Start Chat" #~ msgstr "Inicia un xat" dino-0.3.0/main/po/cs.po0000644000000000000000000010262014202022370013441 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2022-01-06 09:53+0000\n" "Language-Team: none\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" "X-Generator: Weblate 4.10.1\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "Velikost souboru překračuje maximální limit nastavený na serveru." #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "Já" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "Obrázek byl odeslán" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "Soubor byl odeslán" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "Obrázek byl přijat" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "Soubor byl přijat" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "Odchozí hovor" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "Příchozí hovor" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "%d. %b" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "Včera" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "%H∶%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "%l∶%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "Před %i minutou" msgstr[1] "Před %i minutami" msgstr[2] "Před %i minutami" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "Nyní" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "Vlastník" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "Admin" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "Člen" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "Uživatel" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "Pozvat" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "Začít soukromou konverzaci" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "Vyhodit" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "Udělit práva pro zapisování" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "Odebrat práva pro zapisování" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "Pozvat na konferenci" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "Vybrat soubor" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Vybrat" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Zrušit" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "%s z %s" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "Na této konferenci nemůžete posílat zprávy." #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "Požádat o oprávnění" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "Začít hovor" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "Hlasový hovor" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "Video hovor" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "Hledat zprávy" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Členové" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "Vyzvánění…" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "Připojování…" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "%s ukončil hovor" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "%s odmítl hovor" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "" #: main/src/ui/call_window/call_window_controller.vala:128 msgid "Invite to Call" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "Začít konverzaci" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Začít" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Připojit se ke kanálu" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Další" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "Připojit" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Zpět" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "Připojování…" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "Pro vstup do místnosti je zapotřebí heslo" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "Zákaz připojování ke konferencím nebo jejich vytváření" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "Místnost neexistuje" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "Nemáte oprávnění pro vytvoření místnosti" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "Místnost pouze pro členy" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "Vyberte jinou přezdívku" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "V místnosti je příliš mnoho účastníků" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "Připojení k %s se nezdařilo" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "Nesprávná adresa" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Přidat" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Klávesové zkratky" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "O Dino" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "%i výsledek hledání" msgstr[1] "%i výsledky vyhledávání" msgstr[2] "%i výsledků vyhledávání" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "V %s" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "S %s" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "Příchozí video hovor" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "Odmítnout" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "Přijmout" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "Požadavek na odběr" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "Zamítnout" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "Pozvánka do %s" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "%s Vás pozval do %s" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "Žádost o oprávnění" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "%s žádá o oprávnění pro zápis do %s" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Nastavení" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Lokální Nastavení" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Odesílat notifikace o psaní" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Odesiílat potvrzení o přečtení" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Notifikace" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "Zapnuto" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "Vypnuto" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "Pouze pokud je zmíněno" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "Výchozí: %s" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "Žádost" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "Oprávnění" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "Požádat o oprávnění k odesílání zpráv" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Detaily Konference" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "Detaily Kontaktu" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Zablokovat" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "Komunikace a aktualizace stavu jsou blokovány v obou směrech" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Název místnosti" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Popis místnosti" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Stálá" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "Místnost bude zachována i poté, co ji opustí poslední účastník" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Veřejně vyhledatelná" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Účastníci mohou měnit předmět" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "Oprávnění k náhledu JID" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "Kdo může vidět JID účastníků?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "Heslo" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "Heslo pro omezení přístupu do místnosti" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Moderováno" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Pouze účastníci s hlasem mohou zasílat zprávy" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Pouze pro členy" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Do místnosti mohou vstoupit pouze členové" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Historie zpráv" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Maximální výše nevyřízených žádostí vydaných místností" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Nastavení místnosti" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "Vítejte v Dino!" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "Chcete-li začít, přihlaste se nebo si vytvořte účet." #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "Nastavit účet" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "Žádné aktivní účty" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "Správa účtů" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Nesprávné heslo" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Neplatný TLS certifikát" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Odstranit účet %s?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Odstranit" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Vybrat avatar" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Obrázky" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Všechny soubory" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Připojeno" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Odpojeno" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Chyba" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Přidat účet" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "Server nemůže dokázat, že je %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "Operační systém nedůvěřuje jeho bezpečnostnímu certifikátu." #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "Jeho bezpečnostní certifikát byl vydán pro jinou doménu." #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "Jeho bezpečnostní certifikát bude platný až v budoucnu." #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "Platnost jeho bezpečnostního certifikátu vypršela." #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "Přihlásit se k %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "Účet %s můžete nyní používat." #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "Nesprávné uživatelské jméno nebo heslo" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "Něco se pokazilo" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "Žádná odpověď od serveru" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "Registrovat na %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "Server vyžaduje přihlášení prostřednictvím webové stránky" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "Otevřít internetovou stránku" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "Registrovat" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "Podívejte se na %s pro získání informací ohledně způsobu přihlášení" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "Zpráva je příliš dlouhá" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "upraveno" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "čeká na vyřízení…" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "doručení se nezdařilo" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, c-format msgid "%s, %s and %i others are typing…" msgstr "%s, %s a %i dalších píše …" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "%s, %s a %s se píší…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "%s a %s píší…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "%s píše…" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "Hovor začal" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "Začal před %s" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "Tento hovor byl přijat na jiném zařízení" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "Hovor byl ukončen" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "Skončil v %s" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "Trval %s" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "Nepřijatý hovor" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "Máte zmeškaný hovor" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "%s zmeškal(a) tento hovor" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "Hovor byl odmítnut" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "Odmítl(a) jste tento hovor" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "%s odmítl(a) tento hovor" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "Hovor selhal" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "%i hodina" msgstr[1] "%i hodiny" msgstr[2] "%i hodin" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "" msgstr[1] "" msgstr[2] "" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Nešifrované" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "Zprávu nelze odeslat" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "%x, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "%x, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "%d. %b, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "%d. %b, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "%a, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "%a, %l∶%M %p" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Tento kontakt by si vás rád přidal do svého seznamu kontaktů" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "Stahování %s…" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "%s ke stažení: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "Soubor ke stažení: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "Soubor ke stažení" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "Přenos souboru se nezdařil" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "Dnes" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "%a, %d. %b" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "Odeslat soubor" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "Aktualizovat zprávu" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "Kliknutím sem zahájíte konverzaci nebo se připojíte ke kanálu." #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "Nemáte žádné otevřené chaty" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Účet" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Přezdívka" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Alias" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Přidat kontakt" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Upozornit, jakmile přijde nová zpráva" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Převádět smajlíky na emotikony" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "Kontrola pravopisu" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Moderní XMPP klient" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino je moderní open-source chatovací klient pro stolní počítače. Jeho cílem " "je poskytování čistého a spolehlivého prostředí Jabber/XMPP s důrazem na " "zachování vašeho soukromí." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Podporuje šifrování end-to-end pomocí OMEMO a OpenPGP a umožňuje " "konfigurovat funkce související se soukromím, jako jsou potvrzení o přečtení " "a oznámení o psaní." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino načítá historii ze serveru a synchronizuje zprávy s ostatními " "zařízeními." #: main/data/global_search.ui:37 msgid "No active search" msgstr "Žádné aktivní vyhledávání" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Pro zahájení vyhledávání začněte psát" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Žádné odpovídající zprávy" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Zkontrolujte pravopis nebo zkuste odstranit filtry" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "Odeslat" #: main/data/shortcuts.ui:12 msgid "General" msgstr "Obecné" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "Konverzace" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "Navigace" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "Přejít na další konverzaci" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "Přejít na předchozí konverzaci" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "Účty" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Místní alias" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Nejsou nakonfigurovány žádné účty" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Přidat účet" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Přihlásit se" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Vytvořit účet" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "Nepodařilo se navázat zabezpečené připojení" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Připojit" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Vyberte veřejný server" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "Nebo zadejte adresu serveru" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Místo toho se přihlásit" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Vybrat jiný server" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Vše připraveno!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Dokončit" dino-0.3.0/main/po/da.po0000644000000000000000000007064014202022370013426 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2021-09-14 14:36+0000\n" "Language-Team: none\n" "Language: da\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: Weblate 4.9-dev\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "Filen overstiger serverens maksimale uploadstørrelse." #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "Mig" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "Billede sendt" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "Fil sendt" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "Billede modtaget" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "Fil modtaget" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "" msgstr[1] "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "" #: main/src/ui/call_window/call_window_controller.vala:128 msgid "Invite to Call" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "" msgstr[1] "" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "" #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, c-format msgid "%s, %s and %i others are typing…" msgstr "" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "" msgstr[1] "" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "" msgstr[1] "" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "" #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" #: main/data/global_search.ui:37 msgid "No active search" msgstr "" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "" #: main/data/shortcuts.ui:12 msgid "General" msgstr "" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "" dino-0.3.0/main/po/de.po0000644000000000000000000010732014202022370013426 0ustar rootrootmsgid "" msgstr "" "Project-Id-Version: dino-0.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2022-02-11 18:58+0000\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" "X-Generator: Weblate 4.11-dev\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "Die Datei übersteigt die maximale Uploadgröße des Servers." #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "Ich" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "Bild gesendet" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "Datei gesendet" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "Bild empfangen" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "Datei empfangen" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "Ausgehender Anruf" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "Eingehender Anruf" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "%d. %b" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "Gestern" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "%H∶%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "%l∶%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "vor %i min" msgstr[1] "vor %i min" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "Gerade eben" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "Eigentümer" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "Admin" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "Mitglied" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "Gast" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "Einladen" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "Private Unterhaltung beginnen" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "Hinauswerfen" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "Schreibberechtigung erteilen" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "Schreibberechtigung entziehen" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "Zur Konferenz einladen" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "Datei auswählen" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Auswählen" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Abbrechen" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "%s aus %s" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "Diese Konferenz erlaubt es dir nicht, Nachrichten zu senden." #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "Berechtigung anfordern" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "Anruf starten" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "Audioanruf" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "Videoanruf" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "Nachrichten suchen" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Mitglieder" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "Anrufen…" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "Klingeln…" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "Verbinden…" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "%s hat den Anruf beendet" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "%s hat den Anruf abgelehnt" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "Kameras" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "Keine Kamera gefunden." #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "Mikrofone" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "Kein Mikrofon gefunden." #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "Lautsprecher" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "Kein Lautsprecher gefunden." #: main/src/ui/call_window/call_window_controller.vala:128 msgid "Invite to Call" msgstr "Zum Anruf einladen" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "Unterhaltung starten" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Starten" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Kanal beitreten" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Weiter" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "Beitreten" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Zurück" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "Beitreten…" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "Raum erfordert Passwort" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "Beitreten oder erstellen der Konferenz verboten" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "Raum existiert nicht" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "Raum erzeugen verboten" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "Raum nur für Mitglieder" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "Wähle einen anderen Spitznamen" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "Zu viele Nutzer im Raum" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "Verbindung zu %s konnte nicht hergestellt werden" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "Ungültige Adresse" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Hinzufügen" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Tastenkombinationen" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "Info zu Dino" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "%i Suchergebnis" msgstr[1] "%i Suchergebnisse" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "In %s" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "Mit %s" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "Eingehender Videoanruf" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "Eingehender Gruppen-Videoanruf" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "Eingehender Gruppenanruf" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "Ablehnen" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "Annehmen" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "Kontaktanfrage" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "Ablehnen" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "Einladung zu %s" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "%s hat dich in %s eingeladen" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "Berechtigungsanfrage" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "%s erbittet die Berechtigung zum Schreiben in %s" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Einstellungen" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Lokale Einstellungen" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Tippbenachrichtigungen senden" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Lesebestätigungen senden" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Benachrichtigungen" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "An" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "Aus" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "Nur bei Erwähnung" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "Standard: %s" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "Anfragen" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "Berechtigungen" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "Berechtigung zum Senden von Nachrichten anfordern" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Konferenzdetails" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "Kontaktdetails" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Blockieren" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "" "Kommunikation und Statusaktualisierungen sind in beide Richtungen blockiert" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Name des Raums" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Beschreibung des Raums" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Dauerhaft" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "" "Der Raum bleibt erhalten, nachdem der letzte Benutzer ihn verlassen hat" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Öffentlich sichtbar" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Nutzer können das Thema ändern" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "Erlaubnis JIDs zu sehen" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "Wer darf die JIDs von Nutzern sehen?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "Passwort" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "Ein Passwort, um den Zugang zum Raum zu beschränken" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Moderiert" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Nur Nutzer mit Stimme können Nachrichten senden" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Ausschließlich Mitglieder" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Nur Mitglieder dürfen den Raum betreten" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Gesprächsverlauf" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Anzahl der für diesen Raum gespeicherten Nachrichten" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Raum Konfiguration" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "Willkommen bei Dino!" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "Melde dich an oder erstelle ein Konto, um loszulegen." #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "Konto einrichten" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "Keine Konten aktiv" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "Konten verwalten" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Passwort falsch" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Ungültiges TLS-Zertifikat" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Konto %s löschen?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Löschen" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Kontaktbild auswählen" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Bilder" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Alle Dateien" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Verbunden" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Nicht verbunden" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Fehler" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Konto hinzufügen" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "Der Server konnte nicht beweisen, dass er %s ist." #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "" "Sein Sicherheitszertifikat wird von deinem Betriebssystem nicht akzeptiert." #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "Sein Sicherheitszertifikat ist für eine andere Domain ausgestellt." #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "Sein Sicherheitszertifikat ist erst in der Zukunft gültig." #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "Sein Sicherheitszertifikat ist abgelaufen." #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "Bei %s einloggen" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "Du kannst das Konto %s jetzt benutzen." #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "Benutzername oder Passwort falsch" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "Etwas ist schief gelaufen" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "Keine Antwort vom Server" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "Konto auf %s erstellen" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "Der Server erfordert die Registrierung durch eine Webseite" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "Webseite öffnen" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "Registrieren" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "Eventuell sind auf %s Informationen zur Registrierung zu finden" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "Nachricht zu lang" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "bearbeitet" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "ausstehend…" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "Zustellung fehlgeschlagen" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, c-format msgid "%s, %s and %i others are typing…" msgstr "%s, %s und %i andere tippen…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "%s, %s und %s tippen…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "%s und %s tippen…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "%s tippt…" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "Anruf gestartet" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "Gestartet vor %s" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "Du hast diesen Anruf mit einem anderen Gerät geführt" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "Anruf beendet" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "Endete um %s" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "Dauerte %s" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "Anruf verpasst" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "Du hast diesen Anruf verpasst" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "%s hat diesen Anruf verpasst" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "Anruf abgelehnt" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "Du hast diesen Anruf abgelehnt" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "%s hat diesen Anruf abgelehnt" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "Fehlgeschlagener Anruf" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "%i Stunde" msgstr[1] "%i Stunden" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "%i Minute" msgstr[1] "%i Minuten" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "ein paar Sekunden" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Unverschlüsselt" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "Nachricht kann nicht gesendet werden" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "%x, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "%x, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "%d. %b, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "%d. %b, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "%a, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "%a, %l∶%M %p" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "Speichern unter…" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Dieser Kontakt will dich zu seiner Kontaktliste hinzufügen" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "Öffnen" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "Lade %s herunter…" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "%s angeboten: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "Datei angeboten: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "Datei angeboten" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "Dateiübertragung fehlgeschlagen" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "Heute" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "%a, %d %b" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "Datei senden" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "Nachricht aktualisieren" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "" "Klicke hier, um eine Unterhaltung zu starten oder einem Kanal beizutreten." #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "Du hast keine offenen Chats" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Konto" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Anzeigename" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Alias" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Kontakt hinzufügen" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Benachrichtigen bei Empfang einer neuen Nachricht" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Smileys zu Emojis umwandeln" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "Rechtschreibung prüfen" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Modernes XMPP-Chat-Programm" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino ist ein modernes, quelloffenes Chat-Programm für den Desktop. Es bietet " "ein aufgeräumtes und robustes Jabber-/XMPP-Erlebnis und legt einen " "Schwerpunkt auf Privatsphäre." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Er unterstützt Ende-zu-Ende Verschlüsselung mit OMEMO und OpenPGP und " "enthält Privatsphäre-Einstellungen zu Lesebestätigungen und " "Tippbenachrichtigungen." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino ruft Gesprächsverläufe vom Server ab und synchronisiert Nachrichten mit " "anderen Geräten." #: main/data/global_search.ui:37 msgid "No active search" msgstr "Keine Suche gestartet" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Tippe um eine Suche zu beginnen" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Keine Nachrichten gefunden" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Überprüfe die Schreibweise oder entferne Filter" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "Senden" #: main/data/shortcuts.ui:12 msgid "General" msgstr "Allgemein" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "Unterhaltung" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "Navigation" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "Zur nächsten Konversation springen" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "Zur letzten Konversation springen" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "Konten" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Lokaler Alias" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Keine Konten konfiguriert" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Konto hinzufügen" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Einloggen" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Konto erstellen" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "Es konnte keine sichere Verbindung hergestellt werden" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Verbinden" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Wähle einen öffentlichen Server" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "Oder gib eine Serveradresse an" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Stattdessen einloggen" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Anderen Server wählen" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Fertig!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Fertig" #~ msgid "Video call incoming" #~ msgstr "Eingehender Videoanruf" #~ msgid "Call incoming" #~ msgstr "Eingehender Anruf" #~ msgid "Establishing call" #~ msgstr "Anruf aufbauen" #~ msgid "Video call establishing" #~ msgstr "Videoanruf aufbauen" #~ msgid "Call establishing" #~ msgstr "Rufaufbau" #~ msgid "Call in progress…" #~ msgstr "Anruf wird ausgeführt…" #, c-format #~ msgid "Lasted for %s" #~ msgstr "Dauerte %s" #~ msgid "seconds" #~ msgstr "Sekunden" #~ msgid "No active conversations" #~ msgstr "Keine Unterhaltung aktiv" #~ msgid "Main window with conversations" #~ msgstr "Hauptfenster mit Konversationen" #~ msgid "%s, %s and %i others" #~ msgstr "%s, %s und %i weitere" #~ msgid "You can now start using %s" #~ msgstr "Du kannst %s ab jetzt nutzen" #~ msgid "Open Registration" #~ msgstr "Registrierung öffnen" #~ msgid "Save" #~ msgstr "Speichern" #~ msgid "%s, %s and %s" #~ msgstr "%s, %s und %s" #~ msgid "%s and %s" #~ msgstr "%s und %s" #~ msgid "is typing…" #~ msgid_plural "are typing…" #~ msgstr[0] "tippt gerade…" #~ msgstr[1] "tippen gerade…" #~ msgid "has stopped typing" #~ msgstr "hat aufgehört zu tippen" #~ msgid "%i search results" #~ msgstr "%i Suchergebnisse" #~ msgid "Discover real JIDs" #~ msgstr "Echte JIDs finden" #~ msgid "Who may discover real JIDs?" #~ msgstr "Wer kann echte JIDs sehen?" #~ msgid "Password required for room entry, if any" #~ msgstr "Passwort zum Betreten des Raums, falls gesetzt" #~ msgid "Failed connecting to %s" #~ msgstr "Verbindung zu %s fehlgeschlagen" #~ msgid "Join Conference" #~ msgstr "Konferenz beitreten" #~ msgid "File" #~ msgstr "Datei" #~ msgid "Communicate happiness." #~ msgstr "Glücklich(keit) kommunizieren." #~ msgid "Preferences" #~ msgstr "Einstellungen" #~ msgid "Quit" #~ msgstr "Beenden" #~ msgid "JID should be of the form “user@example.com”" #~ msgstr "JID sollte das Format “benutzer@beispiel.de” haben" #~ msgid "Copy Link Address" #~ msgstr "Link-Adresse Kopieren" #~ msgid "Copy" #~ msgstr "Kopieren" #~ msgid "Select All" #~ msgstr "Alles Auswählen" #~ msgid "Search" #~ msgstr "Suchen" #~ msgid "Send message marker" #~ msgstr "Lesebestätigung senden" #~ msgid "Start Chat" #~ msgstr "Chat starten" #~ msgid "Request presence updates" #~ msgstr "Online-Status erfragen" #~ msgid "Join on startup" #~ msgstr "Beim Start beitreten" #~ msgid "Add Chat" #~ msgstr "Chat hinzufügen" #~ msgid "Name" #~ msgstr "Name" #~ msgid "Description" #~ msgstr "Beschreibung" dino-0.3.0/main/po/dino.pot0000644000000000000000000007054414202022370014162 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "" msgstr[1] "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "" #: main/src/ui/call_window/call_window_controller.vala:128 msgid "Invite to Call" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "" msgstr[1] "" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "" #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, c-format msgid "%s, %s and %i others are typing…" msgstr "" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "" msgstr[1] "" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "" msgstr[1] "" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "" #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" #: main/data/global_search.ui:37 msgid "No active search" msgstr "" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "" #: main/data/shortcuts.ui:12 msgid "General" msgstr "" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "" dino-0.3.0/main/po/el.po0000644000000000000000000011441214202022370013436 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2022-02-11 18:58+0000\n" "Language-Team: none\n" "Language: el\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: Weblate 4.11-dev\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "Το αρχείο υπερβαίνει το μέγιστο μέγεθος μεταφόρτωσης του διακομιστή." #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "Εγώ" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "Η εικόνα εστάλη" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "Το αρχείο εστάλη" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "Η εικόνα ελήφθη" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "Το αρχείο ελήφθη" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "Εξερχόμενη κλήση" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "Εισερχόμενη κλήση" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "%d %b" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "Εχθές" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "%H∶%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "%l∶%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "%i λεπτό πριν" msgstr[1] "%i λεπτά πριν" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "Μόλις τώρα" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "Ιδιοκτήτης" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "Διαχειριστής" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "Μέλος" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "Χρήστης" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "Πρόσκληση" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "Ξεκινήστε ιδιωτική συνομιλία" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "Αποβολή" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "Εκχώρηση άδειας εγγραφής" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "Ανάκληση άδειας εγγραφής" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "Πρόσκληση σε Συνεδρία" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "Επιλέξτε αρχείο" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Επιλογή" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Ακύρωση" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "%s από %s" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "Αυτή η συνεδρία δεν σας επιτρέπει να στέλνετε μηνύματα." #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "Ζητήστε άδεια" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "Έναρξη κλήσης" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "Ηχητική κλήση" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "Βίντεο κλήση" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "Αναζήτηση μηνυμάτων" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Μέλη" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "Κλήση…" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "Κουδούνισμα…" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "Σύνδεση…" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "%s τερμάτισε την κλήση" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "%s απέρριψε την κλήση" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "Κάμερες" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "Δεν βρέθηκε κάμερα." #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "Μικρόφωνα" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "Δε βρέθηκε μικρόφωνο." #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "Ηχεία" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "Δε βρέθηκε ηχείο." #: main/src/ui/call_window/call_window_controller.vala:128 msgid "Invite to Call" msgstr "Πρόσκληση σε Κλήση" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "Έναρξη Συνομιλίας" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Έναρξη" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Συμμετοχή σε Ομαδική συνομιλία" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Επόμενο" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "Συμμετοχή" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Πίσω" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "Είσοδος…" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "Απαιτείται κωδικός πρόσβασης για την είσοδο στο δωμάτιο συνομιλίας" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "Απαγορεύεται η συμμετοχή ή η δημιουργία συνεδρίου" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "Το δωμάτιο συζήτησης δεν υπάρχει" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "Δεν επιτρέπεται η δημιουργία δωματίου συνομιλίας" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "Είσοδος μόνο για Μέλη" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "Επιλέξτε ένα διαφορετικό ψευδώνυμο" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "Πάρα πολλοί χρήστες στο δωμάτιο συνομιλίας" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "Δεν ήταν δυνατή η σύνδεση στο %s" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "Μη έγκυρη διεύθυνση" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Προσθήκη" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Συντομεύσεις πληκτρολογίου" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "Σχετικά με το Dino" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "%i αποτέλεσμα αναζήτησης" msgstr[1] "%i αποτελέσματα αναζήτησης" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "Στο %s" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "Με %s" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "Εισερχόμενη βιντεοκλήση" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "Εισερχόμενη ομαδική βιντεοκλήση" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "Εισερχόμενη ομαδική κλήση" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "Απόρριψη" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "Αποδοχή" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "Αίτημα συνδρομής" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "Απόρριψη" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "Πρόσκληση σε %s" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "%s σας προσκάλεσε στο %s" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "Αίτημα άδειας" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "%s ζητά την άδεια να γράψει σε %s" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Ρυθμίσεις" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Τοπικές Ρυθμίσεις" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Αποστολή ειδοποιήσεων πληκτρολόγησης" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Αποστολή αποδείξεων ανάγνωσης" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Ειδοποιήσεις" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "Ενεργό" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "Ανενεργό" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "Μόνο όταν υπάρξει αναφορά σε εμένα" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "Προεπιλογή: %s" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "Αίτηση" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "Άδειες" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "Ζητήστε άδεια για αποστολή μηνυμάτων" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Λεπτομέρειες Συνεδρίου" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "Στοιχεία Επικοινωνίας" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Αποκλεισμός" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "" "Η επικοινωνία και οι ενημερώσεις κατάστασης προς οποιαδήποτε κατεύθυνση " "είναι αποκλεισμένες" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Όνομα του δωματίου συνομιλίας" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Περιγραφή του δωματίου συνομιλίας" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Διηνεκές" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "Το δωμάτιο θα παραμείνει μετά την αποχώρηση του τελευταίου χρήστη" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Δημόσια αναζητήσιμο" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Οι χρήστες έχουν τη δυνατότητα να αλλάξουν θέμα" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "Άδεια θέασης JIDs" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "Ποιος επιτρέπεται να δει τα JIDs των χρηστών;" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "Κωδικός πρόσβασης" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "" "Ένας κωδικός πρόσβασης για τον περιορισμό της πρόσβασης στο δωμάτιο " "συνομιλίας" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Εποπτευόμενο" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Μόνο οι χρήστες με \"φωνή\" μπορούν να στέλνουν μηνύματα" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Μόνο για μέλη" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Μόνο μέλη μπορούν να εισέλθουν στην δωμάτιο συνομιλίας" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Ιστορικό μηνυμάτων" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Μέγιστο ιστορικό που εκδίδεται από το δωμάτιο συνομιλίας" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Διαμόρφωση δωματίου συνομιλίας" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "Καλώς ήρθατε στο Dino!" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "Συνδεθείτε ή δημιουργήστε έναν λογαριασμό για να ξεκινήσετε." #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "Ρύθμιση λογαριασμού" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "Δεν υπάρχουν ενεργοί λογαριασμοί" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "Διαχείριση λογαριασμών" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Λάθος κωδικός" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Μη έγκυρο πιστοποιητικό TLS" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Αφαίρεση λογαριασμού %s;" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Αφαίρεση" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Επιλογή avatar" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Εικόνες" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Όλα τα αρχεία" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Συνδεδεμένο" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Ασύνδετο" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Λάθος" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Προσθήκη Λογαριασμού" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "Ο διακομιστής δεν μπόρεσε να αποδείξει ότι είναι %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "" "Το πιστοποιητικό ασφαλείας του δεν είναι αξιόπιστο από το λειτουργικό σας " "σύστημα." #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "Το πιστοποιητικό ασφαλείας του έχει εκδοθεί για διαφορετικό domain." #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "Το πιστοποιητικό ασφαλείας του θα είναι έγκυρο μόνο στο μέλλον." #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "Το πιστοποιητικό ασφαλείας του έχει λήξει." #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "Συνδεθείτε στο %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "Τώρα μπορείτε να χρησιμοποιήσετε τον λογαριασμό %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "Λάθος όνομα χρήστη ή κωδικός" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "Κάτι πήγε στραβά" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "Καμία απάντηση από τον διακομιστή" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "Εγγραφή στο %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "Ο διακομιστής απαιτεί την εγγραφή μέσω ενός ιστότοπου" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "Άνοιγμα ιστότοπου" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "Εγγραφή" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "Ελέγξτε %s για πληροφορίες σχετικά με τον τρόπο εγγραφής" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "Το μήνυμα είναι πολύ μεγάλο" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "διόρθωση" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "εκκρεμεί…" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "αποτυχία παράδοσης" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, c-format msgid "%s, %s and %i others are typing…" msgstr "%s, %s και %i άλλοι πληκτρολογούν…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "%s, %s και %s πληκτρολογούν…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "%s και %s πληκτρολογούν…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "%s πληκτρολογεί…" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "Η κλήση ξεκίνησε" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "Ξεκίνησε πριν από %s" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "Διαχειριστήκατε αυτήν την κλήση σε άλλη συσκευή" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "Τέλος κλήσης" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "Τερματισμός στις %s" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "Διήρκεσε %s" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "Αναπάντητη κλήση" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "Χάσατε αυτήν την κλήση" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "%s έχασε αυτήν την κλήση" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "Η κλήση απορρίφθηκε" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "Απορρίψατε αυτήν την κλήση" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "%s απέρριψε αυτήν την κλήση" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "Αποτυχία κλήσης" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "%i ώρα" msgstr[1] "%i ώρες" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "%i λεπτό" msgstr[1] "%i λεπτά" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "μερικά δευτερόλεπτα" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Μη κρυπτογραφημένο" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "Δεν είναι δυνατή η αποστολή μηνύματος" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "%x, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "%x, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "%d %b, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "%d %b, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "%a, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "%a, %l∶%M %p" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "Αποθήκευση ως…" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Αυτή η επαφή θα ήθελε να σας προσθέσει στη λίστα επαφών της" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "Άνοιγμα" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "Λήψη %s…" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "%s για μεταφόρτωση: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "Αρχείο για μεταφόρτωση: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "Αρχείο για μεταφόρτωση" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "Η μεταφορά αρχείων απέτυχε" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "Σήμερα" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "%a, %d %b" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "Αποστολή αρχείου" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "Ενημέρωση μηνύματος" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "" "Κάντε κλικ εδώ για να ξεκινήσετε μια συνομιλία ή να εισέλθετε σε ένα δωμάτιο " "ομαδικής συνομιλίας." #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "Δεν έχετε ανοιχτές συνομιλίες" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Λογαριασμός" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Υποκοριστικό (nickname)" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Ψευδώνυμο" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Προσθήκη Επαφής" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Ειδοποίηση όταν έρχεται νέο μήνυμα" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Μετατροπή smileys σε emojis" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "Ορθογραφικός έλεγχος" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Σύγχρονος XMPP Chat Client" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Το Dino είναι ένας σύγχρονος πελάτης συνομιλίας ανοιχτού κώδικα για desktop " "υπολογιστές. Επικεντρώνεται στην παροχή μιας καθαρής και αξιόπιστης " "εμπειρίας Jabber/XMPP έχοντας παράλληλα υπόψη την προστασία των προσωπικών " "δεδομένων σας." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Υποστηρίζει κρυπτογράφηση από άκρο σε άκρο με OMEMO και OpenPGP και " "επιτρέπει την ρύθμιση λειτουργιών που σχετίζονται με το απόρρητο, όπως " "αποδείξεις ανάγνωσης και ειδοποιήσεις πληκτρολόγησης." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Το Dino ανακτά το ιστορικό από τον διακομιστή και συγχρονίζει τα μηνύματα με " "άλλες συσκευές." #: main/data/global_search.ui:37 msgid "No active search" msgstr "Δεν υπάρχει ενεργή αναζήτηση" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Πληκτρολογήστε για να ξεκινήσει μια αναζήτηση" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Δεν βρέθηκαν ανάλογα μηνύματα" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Ελέγξτε την ορθογραφία ή προσπαθήστε να αφαιρέσετε φίλτρα" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "Αποστολή" #: main/data/shortcuts.ui:12 msgid "General" msgstr "Γενικά" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "Συνομιλία" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "Πλοήγηση" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "Μετάβαση στην επόμενη συνομιλία" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "Μετάβαση στην προηγούμενη συνομιλία" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "Λογαριασμοί" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Τοπικό ψευδώνυμο" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Δεν έχουν διαμορφωθεί λογαριασμοί" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Προσθήκη λογαριασμού" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Σύνδεση" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Δημιουργία λογαριασμού" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "Δεν ήταν δυνατή η δημιουργία ασφαλούς σύνδεσης" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Σύνδεση" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Επιλέξτε έναν δημόσιο διακομιστή" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "Ή καθορίστε μια διεύθυνση διακομιστή" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Αντ' αυτού, συνδεθείτε" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Επιλέξτε άλλο διακομιστή" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Όλα έτοιμα!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Ολοκλήρωση" dino-0.3.0/main/po/en.po0000644000000000000000000006773414202022370013456 0ustar rootrootmsgid "" msgstr "" "Project-Id-Version: dino-0.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "Language: en\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "" msgstr[1] "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "" #: main/src/ui/call_window/call_window_controller.vala:128 msgid "Invite to Call" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "" msgstr[1] "" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "" #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, c-format msgid "%s, %s and %i others are typing…" msgstr "" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "" msgstr[1] "" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "" msgstr[1] "" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "" #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" #: main/data/global_search.ui:37 msgid "No active search" msgstr "" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "" #: main/data/shortcuts.ui:12 msgid "General" msgstr "" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "" dino-0.3.0/main/po/eo.po0000644000000000000000000010375414202022370013450 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2021-09-29 07:37+0000\n" "Language-Team: Esperanto \n" "Language: eo\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: Weblate 4.9-dev\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "La dosiero superas la maksimuman grandon de alŝuto al la servilo." #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "Mi" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "Bildon sendis" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "Dosieron sendis" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "Bildon ricevis" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "Dosieron ricevis" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "Eliranta alvoko" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "Envenanta alvoko" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "%b %d" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "Hieraŭ" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "%H∶%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "%l∶%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "Antaŭ %i minuto" msgstr[1] "Antaŭ %i minutoj" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "Ĵus" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "Posedanto" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "Administranto" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "Membro" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "Uzanto" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "Inviti" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "Komenci privatan interparolon" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "Forpeli" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "Doni skriban permeson" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "Eksvalidigi skriban permeson" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "Inviti en la Konferencon" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "Elekti dosieron" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Elekti" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Nuligi" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "%s el %s" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "Ĉi tiu konferenco ne permesas al vi sendi mesaĝojn." #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "Peti permeson" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "Komenci alvokon" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "Telefona alvoko" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "Videa alvoko" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "Serĉi mesaĝojn" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Anoj" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "Alvokante…" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "Voksonante…" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "Konektado…" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "%s finis la alvokon" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "%s malakceptis la alvokon" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "" #: main/src/ui/call_window/call_window_controller.vala:128 msgid "Invite to Call" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "Komenci Konversacion" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Komenci" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Aliĝi al Kanalo" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Sekva" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "Aliĝi" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Reen" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "Aliĝante…" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "Pasvorto estas bezonata por eniri en babilejon" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "Forbarita je aliĝi aŭ krei konferencon" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "Babilejo ne ekzistas" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "Ne estas permesata, ke vi kreas babilejon" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "Nurmembra babilejo" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "Elektu malsaman kromnomon" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "Tro da ĉeestantoj en babilejo" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "Ne povus konekti al %s" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "Nevalida adreso" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Aldoni" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Fulmoklavoj" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "Pri Dino" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "%i serĉrezulto" msgstr[1] "%i serĉrezultoj" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "En %s" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "Kun %s" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "Envenanta videa alvoko" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "Rifuzi" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "Akcepti" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "Abona peto" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "Rifuzi" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "invitilo por %s" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "invitilo de %s por %s" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "Peto pri permeso" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "%s petas la permeson skribi en %s" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Agordoj" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Lokaj Agordoj" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Sciigi pri tajpado" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Sendi kvitancojn de legiteco" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Sciigoj" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "Ek" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "For" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "Nur menciite" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "Defaŭlto: %s" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "Peto" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "Permesoj" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "Peti permeson por sendi mesaĝojn" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Detaloj pri Konferenco" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "Kontaktaj Detaloj" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Forbari" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "Komunikado kaj statoĝisdatigoj ambaŭdirektaj estas forbaritaj" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Nomo de la babilejo" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Priskribo de la babilejo" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Daŭra" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "La babilejo daŭros post eliro de la lasta ĉeestinto" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Publike serĉebla" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Ĉeestantoj rajtas ŝanĝi temon" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "Permeso por vidi JID-ojn" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "Kiu rajtas vidi la JID-ojn de la ĉeestantoj?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "Pasvorto" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "Pasvorto por limigi aliron al la babilejo" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Kontrolata" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Nur ĉeestantoj kun voĉo rajtas sendi mesaĝojn" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Nur anoj" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Nur anoj rajtas eniri la babilejon" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Mesaĝa historio" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Maksimuma nombro da historiaj mesaĝoj donotaj de la babilejo" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Babilejaj Agordoj" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "Bonvenon al Dino!" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "Ensalutu aŭ kreu konton por komenci." #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "Agordi konton" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "Neniuj aktivaj kontoj" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "Administri kontojn" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Malĝusta pasvorto" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Nevalida TLSa atesto" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Ĉu forigi konton %s?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Forigi" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Elekti profilbildon" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Bildoj" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Ĉiuj dosieroj" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Konektita" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Malkonektita" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Eraro" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Aldoni Konton" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "La servilo ne povis pruvi, ke ĝi estas %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "Ĝia atestilo ne estas fidata de via mastruma sistemo." #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "Ĝia atestilo estas eldonita por alia retejo." #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "Ĝia atestilo validos nur estonte." #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "Ĝia atestilo eksvalidiĝis." #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "Ensalutu al %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "Vi povas nun uzi la konton %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "Malĝusta uzantnomo aŭ pasvorto" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "Io misis" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "Nenia respondo de servilo" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "Registriĝi sur %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "La servilo postulas registradon per retejo" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "Malfermi retejon" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "Registriĝi" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "Rigardi %s por informo pri kiel registriĝi" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "Mesaĝo tro longa" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "redaktita" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "sendata…" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "sendo malsukcesis" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, c-format msgid "%s, %s and %i others are typing…" msgstr "%s, %s, kaj %i ceteruloj tajpas mesaĝon…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "%s, %s, kaj %s tajpas mesaĝon…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "%s kaj %s tajpas mesaĝon…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "%s tajpas mesaĝon…" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "Alvoko komenciĝis" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "Komencita antaŭ %s" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "Vi akceptis ĉi tiun alvokon per alia aparato" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "Alvoko finiĝis" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "Finiĝis je %s" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "Daŭris %sn" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "Alvoko ne estis akceptita" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "Vi ne akceptis ĉi tiun alvokon" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "%s ne akceptis ĉi tiun alvokon" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "Alvoko estis malakceptita" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "Vi malakceptis ĉi tiun alvokon" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "%s malakceptis ĉi tiun alvokon" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "Alvoko malsukcesis" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "%i horo" msgstr[1] "%i horoj" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "%i minuto" msgstr[1] "%i minutoj" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "kelke da sekundoj" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Neĉifrita" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "Ne povas sendi mesaĝon" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "%x, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "%x, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "%d-a %b, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "%d-a %b, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "%a, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "%a, %l∶%M %p" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Ĉi tiu kontakto ŝatus aligi vin al sia kontaktlisto" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "Elŝutado de %s…" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "%s ofertis: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "Dosiero ofertita: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "Dosiero ofertita" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "Dosiertransigo malsukcesis" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "Hodiaŭ" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "%a, %d-a %b" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "Sendi dosieron" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "Ĝisdatigi mesaĝon" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "Alklaku ĉi tie por komenci konversacion aŭ aliĝi al kanalo." #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "Vi ne havas malfermitajn babilojn" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Konto" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Kaŝnomo" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Kaŝnomo" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Aldoni kontakton" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Sciigi pri envenaj mesaĝoj" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Grafikigi tekstajn mienetojn" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "Kontroli ortografion" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Moderna XMPP-Retebabililo" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino estas moderna malfermfonta retbabililo por la tabla komputilo. Ĝi celas " "provizi puran kaj fidindan sperton de Jabber/XMPP, protektante vian " "privatecon." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Ĝi subtenas fin-al-finan ĉifradon per OMEMO kaj OpenPGP kaj permesas agordi " "funkciojn pri privateco kiel kvitancojn de legiteco kaj sciigojn pri tajpado." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino prenas historion el la servilo kaj sinkronigas mesaĝojn kun aliaj " "aparatoj." #: main/data/global_search.ui:37 msgid "No active search" msgstr "Neniu aktiva serĉo" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Ektajpu por ekserĉi" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Neniuj kongruaj mesaĝoj" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Korektu la ortografion aŭ provu forigi filtrilojn" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "Sendi" #: main/data/shortcuts.ui:12 msgid "General" msgstr "Ĝenerala" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "Konversacio" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "Navigado" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "Salti al sekva konversacio" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "Salti al antaŭa konversacio" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "Kontoj" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Loka kaŝnomo" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Neniu aktiva konto" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Aldoni konton" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Ensaluti" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Krei konton" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "Ne povis sekure konekti" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Konekti" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Elektu publikan servilon" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "Aŭ specifu servilan adreson" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Ensaluti anstataŭe" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Elekti alian servilon" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Ĉio pretas!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Fini" #~ msgid "No active conversations" #~ msgstr "Neniu aktiva interparolo" #~ msgid "%s, %s and %i others" #~ msgstr "%s, %s, kaj %i aliaj" #~ msgid "You can now start using %s" #~ msgstr "Vi nun povas komenci uzanta %s" #~ msgid "Open Registration" #~ msgstr "Malfermi registriĝon" #~ msgid "Save" #~ msgstr "Konservi" #~ msgid "%s, %s and %s" #~ msgstr "%s, %s kaj %s" #~ msgid "%s and %s" #~ msgstr "%s kaj %s" #~ msgid "is typing…" #~ msgid_plural "are typing…" #~ msgstr[0] "tajpas…" #~ msgstr[1] "tajpas…" #~ msgid "has stopped typing" #~ msgstr "ĉesis tajpi" #~ msgid "Discover real JIDs" #~ msgstr "Eltrovi verajn JID-ojn" #~ msgid "Who may discover real JIDs?" #~ msgstr "Kiu rajtas eltrovi verajn JID-ojn?" #~ msgid "Failed connecting to %s" #~ msgstr "Malsukcesis konektanta al %s" #~ msgid "Join Conference" #~ msgstr "Aliĝi al Kunveno" #~ msgid "Preferences" #~ msgstr "Preferoj" #~ msgid "Quit" #~ msgstr "Ĉesi" #~ msgid "Search" #~ msgstr "Serĉi" #~ msgid "Send message marker" #~ msgstr "Sendi mesaĝmarkilon" #~ msgid "Start Chat" #~ msgstr "Ekbabili" #~ msgid "Request presence updates" #~ msgstr "Peti sciigojn pri ĉeesto" #~ msgid "Join on startup" #~ msgstr "Aliĝi je lanĉo" #~ msgid "Add Chat" #~ msgstr "Aldoni Babilon" dino-0.3.0/main/po/es.po0000644000000000000000000010672614202022370013456 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2021-12-31 21:54+0000\n" "Language-Team: Spanish \n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 4.10.1\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "El archivo excede el tamaño máximo de subida del servidor." #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "mí" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "Imagen enviada" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "Archivo enviado" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "Imagen recibida" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "Archivo recibido" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "Llamada saliente" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "Llamada entrante" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "%d %b" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "Ayer" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "%H∶%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "%l∶%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "hace %i min" msgstr[1] "hace %i mins" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "Justo ahora" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "Propietario" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "Administrador" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "Miembro" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "Usuario" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "Invitar" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "Iniciar conversación privada" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "Expulsar" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "Conceder permiso para escribir" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "Revocar permiso para escribir" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "Invitar a Conferencia" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "Seleccionar archivo" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Seleccionar" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Cancelar" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "%s desde %s" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "Esta conferencia no permite enviar mensajes." #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "Solicitar permiso" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "Iniciar llamada" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "Audiollamada" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "Videollamada" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "Buscar mensajes" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Miembros" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "Llamando…" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "Timbrando…" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "Conectando…" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "%s Llamada finalizada" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "%s ha rechazado la llamada" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "" #: main/src/ui/call_window/call_window_controller.vala:128 msgid "Invite to Call" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "Iniciar Conversación" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Iniciar" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Unirse al Canal" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Siguiente" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "Unirse" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Volver" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "Uniéndose…" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "Contraseña requerida para entrar a la sala" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "Prohibido unirse o creando una conferencia" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "La sala no existe" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "No está permitido crear una sala" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "La sala es solo para miembros" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "Elige un alias diferente" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "La sala tiene demasiados ocupantes" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "No se pudo conectar a %s" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "Dirección inválida" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Añadir" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Atajos de teclado" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "Acerca de Dino" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "%i resultado de búsqueda" msgstr[1] "%i resultado de búsquedas" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "En %s" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "Con %s" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "Videollamada entrante" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "Rechazar" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "Aceptar" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "Solicitud de suscripción" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "Denegar" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "Invitación a %s" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "%s invitado a %s" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "Solicitud de permiso" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "%s solicitar permiso para escribir en %s" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Configuraciones" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Configuraciones locales" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Enviar notificación de tecleo" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Enviar notificación de leer" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Notificaciones" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "Sí" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "No" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "Solo cuando te mencionan" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "Por defecto: %s" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "Solicitar" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "Permisos" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "Solicitar permiso para enviar mensajes" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Detalles de Conferencia" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "Detalles de Contacto" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Bloquear" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "" "La comunicación y las actualizaciones de estado en cualquier dirección están " "bloqueadas" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Nombre de la sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Descripción de la sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Persistente" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "La sala persistirá después de que el último ocupante se salga" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Búsqueda pública" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Los ocupantes pueden cambiar de asunto" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "Permiso para ver los JID" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "¿Quién puede ver los JID's de los ocupantes?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "Contraseña" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "Una contraseña para restringir el acceso de la sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Moderada" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Solo los participantes con voz pueden enviar mensajes" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Solo miembros" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Solo los miembros pueden entrar a la sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Historial de mensajes" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Cantidad máxima de atrasos emitido por la sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Configuración de la sala" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "¡Bienvenido a Dino!" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "Inicia sesión o crea una cuenta para iniciar." #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "Establecer cuenta" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "No hay cuentas activas" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "Gestionar cuentas" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Contraseña incorrecta" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Certificado TLS inválido" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "¿Quitar cuenta %s?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Quitar" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Seleccionar imagen" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Imágenes" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Todos los archivos" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Conectado" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Desconectado" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Error" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Añadir Cuenta" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "El servidor no pudo probar que es %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "El certificado no es confiable para el sistema operativo." #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "El certificado de seguridad está emitido para otro dominio." #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "El certificado de seguridad solo será válido en el futuro." #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "El certificado de seguridad está expirado." #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "Iniciar sesión a %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "Ya puedes usar la cuenta %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "Usuario o contraseña incorrecta" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "Algo ha ido incorrecto" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "No hay respuesta del servidor" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "Registrarte en %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "El servidor requiere registrarse a través de un sitio web" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "Abrir sitio web" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "Registrarse" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "Comprobar %s para información sobre como registrarse" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "Mensaje demasiado largo" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "editado" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "pendiente…" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "entrega fallida" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, c-format msgid "%s, %s and %i others are typing…" msgstr "%s, %s y otros %i están tecleando…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "%s, %s y %s están tecleando…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "%s y %s están tecleando…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "%s está tecleando…" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "Llamada iniciada" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "Inició hace %s" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "Ha gestionado esta llamada en otro dispositivo" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "Llamada finalizada" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "Finalizada a las %s" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "Duración %s" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "Llamada perdida" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "Hay una llamadas perdida" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "%s tiene una llamada perdida" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "Llamada rechazada" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "Has rechazado esta llamada" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "%s ha rechazado esta llamada" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "Llamada fallida" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "%i hora" msgstr[1] "%i horas" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "%i minuto" msgstr[1] "%i minutos" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "hace segundos" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Desencriptado" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "No se pudo enviar el mensaje" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "%x, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "%x, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "%d %b, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "%d %b, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "%a, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "%a, %l∶%M %p" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "A este contacto le gustaría añadirte a la lista de contactos" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "Bajando %s…" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "%s ofreció: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "Archivo ofrecido: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "Archivo ofrecido" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "Transferencia de archivo fallida" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "Hoy" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "%a, %d %b" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "Enviar un archivo" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "Actualizar mensaje" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "Pulsar aquí para iniciar una conversación o unirse a un canal." #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "No tienes conversaciones abiertas" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Cuenta" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Alias" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Alias" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Añadir Contacto" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Notificar cada nuevo mensaje" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Convertir sonrisas en emoticonos" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "Comprobar deletreo" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Un cliente de XMPP moderno" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino es un cliente de mensajería moderno y libre para escritorio y móvil. " "Está enfocado en proveer una experiencia Jabber/XMPP limpia y confiable " "teniendo la privacidad en mente." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Soporta encriptación fin-a-fin a través de OMEMO y OpenPGP y permite " "configurar las características relacionadas con la privacidad, como " "confirmaciones de lectura y notificaciones de escritura." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino recupera el historial de mensajes desde el servidor y sincroniza los " "mensajes con otros dispositivos." #: main/data/global_search.ui:37 msgid "No active search" msgstr "No hay búsquedas activas" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Escribir para iniciar una búsqueda" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "No hay mensajes coincidentes" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Comprueba el deletreo o intenta quitar los filtros" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "Enviar" #: main/data/shortcuts.ui:12 msgid "General" msgstr "General" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "Conversación" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "Navegación" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "Saltar a siguiente conversación" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "Saltar a anterior conversación" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "Cuentas" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Alias local" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "No hay cuentas configuradas" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Añadir una cuenta" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Iniciar Sesión" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Crear cuenta" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "No se pudo establecer una conexión segura" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Conectar" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Elegir un servidor público" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "O especificar la dirección de servidor" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Iniciar sesión" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Elegir otro servidor" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "¡Todo listo!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Finalizado" #~ msgid "No active conversations" #~ msgstr "No hay conversaciones activa" #~ msgid "Main window with conversations" #~ msgstr "Ventana principal con conversaciones" #~ msgid "%s, %s and %i others" #~ msgstr "%s, %s y %i otros" #~ msgid "You can now start using %s" #~ msgstr "Puedes empezar a usar %s" #~ msgid "Open Registration" #~ msgstr "Registro abierto" #~ msgid "Save" #~ msgstr "Guardar" #~ msgid "%s, %s and %s" #~ msgstr "%s, %s y %s" #~ msgid "%s and %s" #~ msgstr "%s y %s" #~ msgid "is typing…" #~ msgid_plural "are typing…" #~ msgstr[0] "está escribiendo…" #~ msgstr[1] "están escribiendo…" #~ msgid "has stopped typing" #~ msgstr "ha dejado de escribir" #~ msgid "%i search results" #~ msgstr "%i resultados de la búsqueda" #~ msgid "Discover real JIDs" #~ msgstr "Descubrir JIDs reales" #~ msgid "Who may discover real JIDs?" #~ msgstr "¿Quién puede ver los JIDs reales?" #~ msgid "Password required for room entry, if any" #~ msgstr "" #~ "Contraseña requerida para entrar en la conversación en grupo. Dejar vacío " #~ "para ninguna" #~ msgid "Failed connecting to %s" #~ msgstr "Fallo conectando con %s" #~ msgid "Join Conference" #~ msgstr "Unirse a Conversación en grupo" #~ msgid "File" #~ msgstr "Archivo" #~ msgid "Communicate happiness." #~ msgstr "Comunícate felizmente." #~ msgid "Preferences" #~ msgstr "Preferencias" #~ msgid "Quit" #~ msgstr "Salir" #~ msgid "JID should be of the form “user@example.com”" #~ msgstr "El JID debe tener la forma \"usuario@ejemplo.com\"" #~ msgid "Copy Link Address" #~ msgstr "Copiar dirección del enlace" #~ msgid "Copy" #~ msgstr "Copiar" #~ msgid "Select All" #~ msgstr "Seleccionar todo" #~ msgid "Search" #~ msgstr "Buscar" #~ msgid "Send message marker" #~ msgstr "Enviar confirmación al recibir y al leer los mensajes" #~ msgid "Start Chat" #~ msgstr "Iniciar Conversación" #~ msgid "Request presence updates" #~ msgstr "Solicitar actualizaciones de presencia" #~ msgid "This is an app-notification. Click the button to dismiss" #~ msgstr "" #~ "Esto es una notificación de la aplicación. Haz click en el botón para " #~ "descartar" #~ msgid "Join on startup" #~ msgstr "Unirse al inicio" #~ msgid "Add Chat" #~ msgstr "Añadir conversación" dino-0.3.0/main/po/eu.po0000644000000000000000000010607614202022370013456 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2021-12-09 19:53+0000\n" "Language-Team: Basque \n" "Language: eu\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: Weblate 4.10-dev\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "" "Fitxategiak zerbitzariaren kargatzeko gehienezko tamaina gainditzen du." #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "Ni" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "Irudia bidali da" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "Fitxategia bidali da" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "Irudia jaso da" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "Fitxategia jaso da" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "Irteten ari den deia" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "Sarrerako deia" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "%b %d" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "Atzo" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "%H∶%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "%l∶%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "orain dela min %i" msgstr[1] "orain dela %i min" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "Orain" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "Jabea" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "Administratzailea" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "Kidea" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "Erabiltzailea" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "Gonbidatu" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "Solasaldi pribatua hasi" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "Kanporatu" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "Idazteko baimena eman" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "Idazteko baimena ezeztatu" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "Konferentziara gonbidatu" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "Fitxategia hautatu" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Hautatu" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Utzi" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "%s hemendik: %s" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "Konferentzia honek ez dizu mezuak bidaltzea baimentzen." #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "Baimena eskatu" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "Hasi deia" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "Audio-deia" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "Bideo-deia" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "Mezuak bilatu" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Kideak" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "Deika…" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "Soinua jotzen…" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "Konektatzen…" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "%s(e)k deia amaitu zuen" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "%s(e)k uko egin dio deiari" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "" #: main/src/ui/call_window/call_window_controller.vala:128 msgid "Invite to Call" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "Elkarrizketa hasi" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Hasi" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Kanalera gehitu" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Hurrengoa" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "Batu" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Atzera" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "Batzen…" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "Pasahitza behar da gelara sartzeko" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "Konferentzia sortu edo batzea debekatuta" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "Gela ez da existitzen" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "Ez duzu gela sortzeko baimenik" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "Gela kideentzat da soilik" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "Ezizen ezberdin bat hautatu" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "Gelak kide gehiegi ditu" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "Ezin izan da %s(e)ra konektatu" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "Helbide ez balioduna" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Gehitu" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Laster-teklak" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "Dinori buruz" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "Bilaketa emaitza %i" msgstr[1] "%i bilaketa emaitza" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "hemen %s" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "%s(r)ekin" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #, fuzzy msgid "Incoming video call" msgstr "Sarrerako bideo-deia" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "Ukatu" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "Onartu" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "Harpidetzaren eskaera" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "Ukatu" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "%s(e)ra gonbidapena" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "%s(e)k %s(e)ra gonbidatu zaitu" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "Baimen eskaera" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "%s baimena eskatzen du %s(e)n idazteko" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Ezarpenak" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Bertako ezarpenak" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Idazte jakinarazpenak bidali" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Bidali irakurtze baieztapena" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Jakinarazpenak" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "Piztuta" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "Itzalita" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "Soilik aipatua izaterakoan" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "Lehenetsia: %s" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "Eskatu" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "Baimenak" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "Mezuak bidaltzeko baimena eskatu" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Konferentziaren xehetasunak" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "Kontaktuaren xehetasunak" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Blokeatu" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "" "Komunikazioa eta egoeraren eguneraketak edozein norabideetan blokeatuta daude" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Gelaren izena" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Gelaren deskribapena" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Iraunkorra" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "Gelak iraun egingo du azken parte-hartzailea irten ondoren" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Publikoki bilagarria" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Parte-hartzaileek gaia alda dezakete" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "JIDak ikusteko baimena" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "Nor dago baimenduta parte-hartzaileen JIDak ikusteko?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "Pasahitza" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "Gelarako sarbidea murrizteko pasahitza" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Moderatua" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Ahotsa duten parte-hartzaileek soilik bidali dezakete mezuak" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Kideak soilik" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Kideak soilik sar daitezke gelara" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Mezuen historia" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Gelak itzuliko dituen gehienezko historiako mezuak" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Gelaren konfigurazioa" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "Ongi etorri Dino!" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "Saioa hasi edo kontu bat sortu hasteko." #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "Kontu bat ezarri" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "Ez dago kontu aktiborik" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "Kontuak kudeatu" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Pasahitz okerra" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "TLS ziurtagiri ez balidouna" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "%s kontua kendu?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Kendu" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Irudia hautatu" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Irudiak" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Fitxategi guztiak" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Konektatuta" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Ez konektatuta" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Akatsa" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Kontua gehitu" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "Zerbitzariak ezin izan du frogatu %s dela." #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "Zure sistema eragilea ez da bere segurtasun ziurtagiriaz fido." #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "Bere segurtasun ziurtagiria beste domeinu baterako eman da." #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "Bere segurtasun ziurtagiria etorkizunean izango da baliozkoa soilik." #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "Bere segurtasun ziurtagiria iraungi da." #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "Hasi saioa %s(e)n" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "Orain %s kontua erabili dezakezu." #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "Erabiltzaile izen edo pasahitz okerra" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "Zerbait oker joan da" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "Erantzunik ez zerbitzaritik" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "Izena eman hemen: %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "Zerbitzariak webgune baten bidez izena ematea eskatzen du" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "Webgunea ireki" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "Izena eman" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "Ikusi %s izena ematearen inguruko informazioa lortzeko" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "Mezu luzeegia" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "editatuta" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "bidaltzeko zain…" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "entregak huts egin du" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, c-format msgid "%s, %s and %i others are typing…" msgstr "%s, %s eta beste %i idatzen ari dira…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "%s, %s eta %s idazten ari dira…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "%s eta %s idazten ari dira…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "%s idazten ari da…" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "Deia hasi da" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "Duela %s hasi zen" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "Deia hau beste gailu batean kudeatu duzu" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "Deia amaitu da" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "%s-tan amaituta" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "%s iraun zuen" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "Dei galdua" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "Dei hau galdu duzu" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "%s(e)k dei hau galdu du" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "Deia atzera bota da" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "Dei horri uko egin diozu" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "%s(e)k uko egin dio dei horri" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "Deia huts egin da" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "ordu %i" msgstr[1] "%i ordu" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "minutu %i" msgstr[1] "%i minutu" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "duela segundo batzuk" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Enkriptatu gabe" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "Ezin da mezua bidali" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "%x, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "%x, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "%b %d, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "%b %d, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "%a, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "%a, %l∶%M %p" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Kontaktu honek bere kontaktuen zerrendara gehitu nahi zaitu" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "%s deskargatzen…" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "%s(e)k eskeini du: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "Fitxategia eskeini da: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "Fitxategia eskeinita" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "Fitxategiaren transmisioak huts egin du" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "Gaur" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "%a, %b %d" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "Fitxategi bat bidali" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "Mezua eguneratu" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "Klikatu hemen elkarrizketa berri bat hasi edo kanal batean sartzeko." #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "Ez duzu irekitako txatik" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Kontuak" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Goitizena" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Ezizena" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Kontaktua gehitu" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Mezu berri bat heltzerakoan jakinarazi" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Irrifartxoak emotikono bihurtu" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "Ortografia egiaztatu" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "XMPP txat bezero modernoa" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino mahaigainerako iturburu irekiko txat bezero moderno bat da. Jabber/XMPP " "esperientzia garbi eta fidagarri bat ematen du zure pribatutasuna kontuan " "hartzeaz gain." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Amaieratik amaierarako enkriptazioa onartzen du OMEMO eta OpenPGPrekin eta " "pribatutasun ezaugarriak konfiguratzea baimentzen du irakurtze markak eta " "idazketa jakinarazpenak bezala." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dinok zerbitzaritik hartzen du historia eta beste gailuekin mezuak " "sinkronizatzen ditu." #: main/data/global_search.ui:37 msgid "No active search" msgstr "Ez dago bilaketa aktiborik" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Idatzi bilatzen hasteko" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Ez dago bat egiten duen mezurik" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Ortografia egiaztatu edo saiatu iragazkiak kentzen" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "Bidali" #: main/data/shortcuts.ui:12 msgid "General" msgstr "Orokorra" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "Elkarrizketa" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "Nabigazioa" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "Jauzi hurrengo elkarrizketara" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "Jauzi aurreko elkarrizketara" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "Kontuak" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Bertako ezizena" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Ez dago konfiguratutako konturik" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Kontu bat gehitu" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Saioa hasi" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Kontua sortu" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "Ezin izan da konexio seguru bat ezarri" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Konektatu" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Zerbitzari publiko bat hautatu" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "Edo zerbitzari baten helbidea zehaztu" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Saioa hasi" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Beste zerbitzari bat hartu" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Guztia ezarri da!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Amaitu" #~ msgid "No active conversations" #~ msgstr "Ez dago solasaldi aktiborik" #~ msgid "Main window with conversations" #~ msgstr "Leiho nagusia elkarrizketekin" #~ msgid "%s, %s and %i others" #~ msgstr "%s, %s eta beste %i" #~ msgid "You can now start using %s" #~ msgstr "Orain %s erabiltzen hasi zaitezke" #~ msgid "Open Registration" #~ msgstr "Izen emate irekia" #~ msgid "Save" #~ msgstr "Gorde" #~ msgid "%s, %s and %s" #~ msgstr "%s, %s eta %s" #~ msgid "%s and %s" #~ msgstr "%s eta %s" #~ msgid "is typing…" #~ msgid_plural "are typing…" #~ msgstr[0] "idazten ari da…" #~ msgstr[1] "idazten ari dira…" #~ msgid "has stopped typing" #~ msgstr "idazteari utzi dio" #~ msgid "%i search results" #~ msgstr "Bilaketaren emaitzak: %i" #~ msgid "Discover real JIDs" #~ msgstr "Egiazko JIDak ikusi" #~ msgid "Who may discover real JIDs?" #~ msgstr "Nork ikus ditzake egiazko JIDak?" #~ msgid "Password required for room entry, if any" #~ msgstr "" #~ "Gelan sartzeko beharrezko pasahitza, utzi zuriz pasahitzik ez ezartzeko" #~ msgid "Failed connecting to %s" #~ msgstr "Huts %s(e)ra konektatzerakoan" #~ msgid "Join Conference" #~ msgstr "Konferentziara batu" #~ msgid "File" #~ msgstr "Fitxategia" #~ msgid "Communicate happiness." #~ msgstr "Poztasuna komunikatu." #~ msgid "Preferences" #~ msgstr "Hobespenak" #~ msgid "Quit" #~ msgstr "Irten" #~ msgid "JID should be of the form “user@example.com”" #~ msgstr "JIDak \"erabiltzailea@eredua.com\" itxura izan beharko luke" #~ msgid "Copy Link Address" #~ msgstr "Loturaren helbidea kopiatu" #~ msgid "Copy" #~ msgstr "Kopiatu" #~ msgid "Select All" #~ msgstr "Dena hautatu" #~ msgid "Search" #~ msgstr "Bilatu" #~ msgid "Send message marker" #~ msgstr "Mezu marka bidali" #~ msgid "Start Chat" #~ msgstr "Berriketa hasi" #~ msgid "Request presence updates" #~ msgstr "Presentzia eguneraketak eskatu" #~ msgid "Join on startup" #~ msgstr "Hasieran batu" #~ msgid "Add Chat" #~ msgstr "Berriketa gehitu" dino-0.3.0/main/po/fa.po0000644000000000000000000010544514202022370013432 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2021-09-29 07:37+0000\n" "Language-Team: none\n" "Language: fa\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: Weblate 4.9-dev\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "حجم فایل از حداکثر حجم بارگذاری سرور بیشتر است." #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "من" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "تصویر ارسال شد" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "فایل ارسال شد" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "تصویر دریافت شد" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "فایل دریافت شد" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "%b %d" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "دیروز" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "%H∶%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "%l∶%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "%i دقیقه پیش" msgstr[1] "%i دقیقه پیش" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "همین الان" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "مالک" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "مدیر" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "عضو" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "کاربر" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "دعوت" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "گفتگوی خصوصی را شروع کنید" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "بیرون کردن" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "مجوز نوشتن بدهید" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "مجوز نوشتن را لغو کنید" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "دعوت کردن به کنفرانس" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "انتخاب فایل" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "انتخاب" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "لغو" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "%s از %s" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "این کنفرانس به شما اجازهٔ ارسال پیام نمی دهد." #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "درخواست مجوز" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "جست‌جوی پیام‌ها" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "اعضا" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "در حال اتصال…" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "" #: main/src/ui/call_window/call_window_controller.vala:128 msgid "Invite to Call" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "آغاز گفتگو" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "آغاز" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "پیوستن به کانال" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "بعدی" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "پیوستن" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "بازگشت" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "در حال پیوستن…" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "گذرواژه برای ورود به اتاق لازم است" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "از پیوستن به کنفرانس یا ساختن آن منع شده‌اید" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "اتاق وجود ندارد" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "مجاز به ایجاد اتاق نیست" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "اتاق مخصوص اعضا" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "یک نام دیگر انتخاب کنید" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "تعداد زیادی از افراد در اتاق هستند" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "اتصال با %s برقرار نشد" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "آدرس نامعتبر" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "اضافه کردن" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "میانبر های صفحه کلید" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 #, fuzzy msgid "About Dino" msgstr "درباره دینو" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "%i نتیجه جست وجو" msgstr[1] "%iنتایج جست وجو" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "در %s" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "با %s" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "رد کردن" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "پذیرش" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "درخواست اشتراک" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "رد کردن" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "دعوت به %s" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "%s شما را به %s دعوت کرد" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "درخواست مجوز" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "%s درخواست نوشتن در %s را دارد" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "تنظیمات" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "تنظیمات محلی" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "ارسال اعلان تایپ کردن" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "فرستادن رسید خوانده‌شدن" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "اعلان‌ها" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "روشن" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "خاموش" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "فقط وقتی ذکر شده" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "پیش‌فرض: %s" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "درخواست" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "مجوزها" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "درخواست مجوز برای ارسال پیام" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "جزئیات کنفرانس" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "اطلاعات مخاطب" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "مسدود کردن" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "ارتباط و به‌روز رسانی وضعیت در هر دو جهت مسدود شده است" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "نام اتاق" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "توضیحات اتاق" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "مداوم" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "بعد از رفتن آخرین فرد اتاق دوام خواهد داشت" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "قابلیت جستجوی عمومی" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "افراد ممکن است موضوع را تغییر دهند" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "اجازه مشاهده JIDها" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "چه کسی مجاز است JIDهای افراد را مشاهده کند؟" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "رمز" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "رمز برای محدود کردن دسترسی به اتاق" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "تعدیل شده" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "فقط افراد دارای صدا می توانند پیام ارسال کنند" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "فقط اعضا" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "فقط اعضا می توانند وارد اتاق شوند" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "تاریخچه پیام" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "حداکثر مقدار عقب مانده ارسال شده توسط اتاق" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "پیکربندی اتاق" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "به دینو خوش آمدید!" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "برای شروع یک حساب بسازید یا وارد شوید." #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "حساب تنظیم کنید" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "حساب فعالی وجود ندارد" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "مدیریت حساب‌ها" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "رمز اشتباه است" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "گواهی تی‌ال‌اس نامعتبر است" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "حساب %s حذف شود؟" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "حذف" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "انتخاب آواتار" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "تصاویر" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "تمامی فایل‌ها" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "متصل" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "قطع شده" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "خطا" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "اضافه کردن حساب" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "سرور نمی تواند اثبات کند که %s است." #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "گواهی امنیتی آن مورد اعنماد سیستم عامل شما نیست." #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "گواهی امنیتی آن برای دامنه دیگری صادر شده است." #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "گواهی امنیتی آن فقط در آینده معتبرخواهد شد." #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "گواهی امنیتی آن منقضی شده است." #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "ورود به %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "اکنون می توانید از این حساب استفاده کنید: %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "نام کاربری یا رمز اشتباه است" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "مشکلی پیش آمد" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "هیچ پاسخی از طرف سرور وجود ندارد" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "ثبت نام در %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "سرور نیاز به ثبت نام از طریق یک وب سایت دارد" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "بازکردن وب‌سایت" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "ثبت نام" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "بررسی %s برای کسب اطلاعات در مورد نحوه ثبت نام" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "پیام خیلی طولانی است" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "ویرایش شده" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, c-format msgid "%s, %s and %i others are typing…" msgstr "%s، %s و %i نفر دیگر در حال نوشتن اند…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "%s، %s و %s در حال نوشتن اند…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "%s و %s در حال نوشتن اند…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "%s در حال نوشتن است…" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "" msgstr[1] "" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "" msgstr[1] "" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "رمزگذاری نشده" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "ارسال پیام ممکن نیست" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "%x, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "%x, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "%b %d, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "%b %d, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "%a, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "%a, %l∶%M %p" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "این مخاطب می‌خواهد شما را به لیست مخاطبین خود اضافه کند" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "در حال دانلود %s…" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "%s ارائه شده: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "فایل ارائه شده: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "فایل ارائه شده است" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "انتقال فایل انجام نشد" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "امروز" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "%a , %b %d" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "ارسال فایل" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "به‌روز کردن پیام" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "برای شروع گفتگو یا پیوستن به کانال اینجا کلیک کنید." #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "شما هیچ گپ بازی ندارید" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "حساب" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "نام مستعار" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "نام مستعار" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "افزودن مخاطب" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "اعلان در هنگام رسیدن پیام جدید" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "تبدیل صورتک‌ها به ایموجی" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "بررسی املا" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "کلاینت نوین گپ XMPP" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "دینو یک کلاینت چت متن‌باز برای دسکتاپ است. تمرکز آن بر فراهم‌کردن تجربه‌ای " "شسته‌رفته و قابل‌اتکا از جَبِر/XMPP است درحالی که به حریم خصوصی‌تان اهمیت می‌دهد." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "از رمزگذاری سرتاسر با اُمیمو و اُپن‌پی‌جی‌پی پشتیبانی می‌کند و اجازه تنظیم " "قابلیت‌های مربوط به حریم خصوصی را می‌دهد، از جمله: رسید خوانده‌شدن پیام‌ها و " "اعلان در حال نوشتن بودن." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "دینو تاریخچه را از سرور دریافت می‌کند و پیام‌ها را با دیگر دستگاه‌ها همگام‌سازی " "می‌کند." #: main/data/global_search.ui:37 msgid "No active search" msgstr "جستجوی فعالی وجود ندارد" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "برای شروع جستجو تایپ کنید" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "هیچ پیام مطابقی وجود ندارد" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "املا را بررسی کنید یا فیلترها را حذف کنید" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "فرستادن" #: main/data/shortcuts.ui:12 msgid "General" msgstr "فراگیر" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "گفتگو" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "جهت‌یابی" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "رفتن به گفت‌گوی بعدی" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "رفتن به گفت‌گوی قبلی" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "حساب‌ها" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "نام مستعار محلی" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "هیچ حسابی پیکربندی نشده است" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "اضافه کردن حساب" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "ورود" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "ساخت کاربری" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "اتصال ایمن برقرار نشد" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "اتصال" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "یک سرور عمومی انتخاب کنید" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "یا آدرس سرور را مشخص کنید" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "در عوض وارد شوید" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "سرور دیگری انتخاب کنید" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "همه تنظیم شده!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "اتمام" #~ msgid "No active conversations" #~ msgstr "هیچ گفتگوی فعالی نیست" dino-0.3.0/main/po/fi.po0000644000000000000000000010066014202022370013434 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-04-12 20:01+0000\n" "Language-Team: Finnish \n" "Language: fi\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: Weblate 4.0-dev\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "Minä" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "Kuva lähetetty" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "Tiedosto lähetetty" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "Kuva vastaanotettu" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "Tiedosto vastaanotettu" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "%d. %b" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "Eilen" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "%H∶%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "%l∶%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "%i min sitten" msgstr[1] "%i min sitten" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "Juuri nyt" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "Omistaja" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "Ylläpitäjä" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "Jäsen" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "Käyttäjä" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "Kutsu" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "Aloita yksityiskeskustelu" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "Potkaise pihalle" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "Kutsu ryhmäkeskusteluun" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Valitse" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Peruuta" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "Yhdistää…" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "" #: main/src/ui/call_window/call_window_controller.vala:128 msgid "Invite to Call" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "Aloita keskustelu" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Aloita" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Liity kanavalle" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Seuraava" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "Liity" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Takaisin" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "Yhdistää…" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "Huoneeseen vaaditaan salasana" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "Estetty liittymästä tai luomasta ryhmäkeskustelua" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "Huonetta ei ole olemassa" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "Huoneen luomista ei sallita" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "Huone vain jäsenille" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "Valitse jokin muu nimimerkki" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "Liikaa käyttäjiä huoneessa" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, fuzzy, c-format msgid "Could not connect to %s" msgstr "Ei voitu yhdistää: %s" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "Väärä osoite" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Lisää" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "" msgstr[1] "" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "Hyväksy" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "Yhteyspyyntö" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "Hylkää" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, fuzzy, c-format msgid "Invitation to %s" msgstr "Kutsu %s" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, fuzzy, c-format msgid "%s invited you to %s" msgstr "%s on kutsunut sinut %s-ryhmään" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Asetukset" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Paikalliset asetukset" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Lähetä kirjoittamisilmoitukset" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Lähetä lukukuittaukset" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Ilmoitukset" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "Käytössä" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "Pois" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "Vain mainittaessa" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "Oletus: %s" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Ryhmäkeskustelun yksityiskohdat" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "Yhteystiedot" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Estä" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "Viestit ja tilapäivitykset estetään molempiin suuntiin" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Huoneen nimi" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Huoneen kuvaus" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Pysyvä" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "Huone säilyy viimeisen käyttäjän poistuttua" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Julkisesti löydettävissä" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Käyttäjät voivat vaihtaa aihetta" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "Salasana" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Moderoitu" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Vain äänen saaneet käyttäjät voivat lähettää viestejä" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Vain jäsenille" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Vain jäsenet pääsevät huoneeseen" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Viestihistoria" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Säilytettyjen viestien enimmäismäärä huoneessa" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Huoneen asetukset" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "Tervetuloa Dino!" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "Kirjaudu sisään tai luo tili päästäksesi alkuun." #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "Ei aktiivisia tilejä" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "Hallitse tilejä" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Väärä salasana" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "TLS-varmenne ei kelpaa" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Poista tili %s?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Poista" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Valitse keskustelukuvake" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Kuvat" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Kaikki tiedostot" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Yhdistetty" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Ei yhdistetty" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Virhe" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Lisää tili" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, fuzzy, c-format msgid "Sign in to %s" msgstr "Kirjaudu sisään %s-ryhmään" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "Väärä käyttäjätunnus tai salasana" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "Jokin meni pieleen" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "Palvelin ei vastaa" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "Palvelin vatii kirjautumaan verkkosivuston kautta" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "Rekisteröity" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "Tarkista %s ohjeet kirjautumiseen" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "Viesti liian pitkä" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "muokattu" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, fuzzy, c-format msgid "%s, %s and %i others are typing…" msgstr "%s, %s ja %i muuta kirjoittavat" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "%s, %s ja %s kirjoittavat…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "%s ja %s kirjoittavat…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "%s kirjoittaa…" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "" msgstr[1] "" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "" msgstr[1] "" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Salaamaton" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "Viestiä ei voi lähettää" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "%x, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "%x, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "%d. %b, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "%d. %b, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "%a, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "%a, %l∶%M %p" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Tämä käyttäjä haluaisi lisätä sinut yhteystietoihinsa" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "Lataa %s…" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "Tänään" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "%a, %b %d" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "" #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Tili" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Nimimerkki" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Alias" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Lisää yhteystieto" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Ilmoita kun uusi viesti saapuu" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Muuta hymiöt emojeiksi" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:8 #, fuzzy msgid "Modern XMPP Chat Client" msgstr "Moderni XMPP-asiakasohjelma" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino on nykyaikainen avoimen lähdekoodin jutteluohjelma työpöydälle. Se " "keskittyy tarjoamaan selkeän ja luotettavan Jabber/XMPP-kokemuksen " "unohtamatta yksityisyyttäsi." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Se tukee päästä päähän -salausta OMEMO:n ja OpenPGP:n avulla ja " "mahdollistaa yksityisyyteen liittyvien ominaisuuksien, kuten lukukuittausten " "ja kirjoitusilmoitusten asetusten määrittämisen." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino hakee historian palvelimelta ja synkronisoi viestit muiden laitteiden " "kanssa." #: main/data/global_search.ui:37 msgid "No active search" msgstr "" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "" #: main/data/shortcuts.ui:12 msgid "General" msgstr "" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "Tilit" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Paikallinen alias" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Ei asetettuja tilejä" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Lisää tili" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "" #~ msgid "No active conversations" #~ msgstr "Ei aktiivisia keskusteluja" #~ msgid "Main window with conversations" #~ msgstr "Keskustelut pääikkunassa" #~ msgid "%s, %s and %i others" #~ msgstr "%s, %s and %i muuta" #~ msgid "Save" #~ msgstr "Tallenna" #~ msgid "%s, %s and %s" #~ msgstr "%s, %s ja %s" #~ msgid "%s and %s" #~ msgstr "%s ja %s" #~ msgid "is typing…" #~ msgid_plural "are typing…" #~ msgstr[0] "kirjoittaa…" #~ msgstr[1] "kirjoittavat…" #~ msgid "has stopped typing" #~ msgstr "on lakannut kirjoittamasta" #~ msgid "Discover real JIDs" #~ msgstr "Näytä oikeat JID:t" #~ msgid "Who may discover real JIDs?" #~ msgstr "Kuka voi nähdä oikeat JID:t?" #~ msgid "Password required for room entry, if any" #~ msgstr "Huoneeseen vaadittava salasana, jos asetettu" #~ msgid "Join Conference" #~ msgstr "Liity ryhmäkeskusteluun" #~ msgid "File" #~ msgstr "Tiedosto" #~ msgid "Preferences" #~ msgstr "Asetukset" #~ msgid "Quit" #~ msgstr "Lopeta" #~ msgid "JID should be of the form “user@example.com”" #~ msgstr "Anna JID muodossa \"user@example.com\"" #~ msgid "Copy Link Address" #~ msgstr "Kopioi linkin osoite" #~ msgid "Copy" #~ msgstr "Kopioi" #~ msgid "Select All" #~ msgstr "Valitse kaikki" #~ msgid "Search" #~ msgstr "Etsi" #~ msgid "Send message marker" #~ msgstr "Lähetä lukukuittaus" #~ msgid "Start Chat" #~ msgstr "Aloita keskustelu" dino-0.3.0/main/po/fr.po0000644000000000000000000011006714202022370013447 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2022-02-11 18:58+0000\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: Weblate 4.11-dev\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "Le fichier dépasse la taille maximale autorisée par le serveur." #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "Moi" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "Image envoyée" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "Fichier envoyé" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "Image reçue" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "Fichier reçu" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "Appel sortant" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "Appel entrant" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "%d %b" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "Hier" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "%H∶%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "%l∶%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "Il y a %i minute" msgstr[1] "Il y a %i minutes" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "À l’instant" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "Propriétaire" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "Administrateur" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "Membre" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "Utilisateur" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "Inviter" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "Commencer une discussion privée" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "Éjecter du salon" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "Accorder la permission d’écrire" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "Révoquer la permission d’écrire" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "Inviter dans ce salon" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "Sélectionner un fichier" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Sélectionner" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Annuler" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "%s de %s" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "Vous n’êtes pas autorisé(e) à envoyer de messages sur ce salon." #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "Demander la permission" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "Démarrer un appel" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "Appel audio" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "Appel vidéo" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "Rechercher dans les messages" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Membres" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "Appel en cours…" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "Ça sonne…" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "Connexion…" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "%s a arrêté l’appel" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "%s a rejeté cet appel" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "Caméras" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "Aucune caméra trouvée." #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "Microphones" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "Aucun microphone trouvé." #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "Haut-parleurs" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "Aucun haut-parleur trouvé." #: main/src/ui/call_window/call_window_controller.vala:128 msgid "Invite to Call" msgstr "Inviter à un appel" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "Commencer une discussion" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Démarrer" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Rejoindre un salon" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Suivant" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "Rejoindre" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Retour" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "Connexion au salon…" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "Un mot de passe est nécessaire pour rejoindre ce salon" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "Vous n’avez pas le droit de rejoindre ou de créer ce salon" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "Ce salon n’existe pas" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "Création de salon interdite" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "Salon réservé aux membres" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "Choisissez un autre pseudo" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "Trop de participants dans ce salon" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "Impossible de se connecter à %s" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "Adresse invalide" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Ajouter" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Raccourcis clavier" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "À propos de Dino" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "%i résultat" msgstr[1] "%i résultats" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "Sur %s" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "Avec %s" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "Appel vidéo entrant" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "Appel vidéo de groupe entrant" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "Appel de groupe entrant" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "Rejet" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "Accepter" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "Demande d’ajout de contact" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "Refuser" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "Invitation à %s" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "%s vous a invité(e) à rejoindre le salon %s" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "Demande de permission" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "%s demande la permission d’écrire dans %s" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Paramètres" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Paramètres locaux" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Envoyer les notifications d’écriture" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Envoyer les accusés de réception" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Notifications" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "Activé" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "Désactivé" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "Seulement quand mentionné" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "Par défaut : %s" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "Demander" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "Permissions" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "Demander la permission d’envoyer des messages" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Informations du salon" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "Informations du contact" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Bloquer" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "Tous les échanges de messages et de statuts sont bloqués" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Nom du salon" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Description du salon" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Persistant" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "Ce salon persistera après le départ du dernier participant" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Visible publiquement" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Les participants peuvent changer le sujet" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "Autorisation de voir les JIDs" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "Qui peut voir les JIDs des participants ?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "Mot de passe" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "Un mot de passe pour restreindre l’accès au salon" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Modéré" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "" "Seuls les participants avec la permission d’écrire peuvent envoyer des " "messages" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Membres uniquement" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Seuls les membres peuvent accéder au salon" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Historique des messages" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Nombre maximum de messages d’historique renvoyé par le salon" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Configuration du salon" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "Bienvenue dans Dino !" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "Connectez-vous ou créez un compte pour commencer." #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "Configurer un compte" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "Aucun compte actif" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "Gérer les comptes" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Mauvais mot de passe" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Certificat TLS invalide" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Supprimer le compte %s ?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Supprimer" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Sélectionner un avatar" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Images" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Tous les fichiers" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Connecté" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Déconnecté" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Erreur" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Ajouter un compte" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "Le serveur n’a pas su prouver qu’il est %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "" "Votre système d’exploitation ne fait pas confiance au certificat proposé." #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "Son certificat a été généré pour un autre domaine." #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "Son certificat ne deviendra valide que dans le futur." #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "Son certificat a expiré." #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "Se connecter en tant que %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "Vous pouvez maintenant utiliser le compte %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "Mauvais nom d’utilisateur ou mot de passe" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "Quelque chose s’est mal passé" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "Pas de réponse du serveur" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "S’inscrire sur %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "Ce serveur nécessite de s’inscrire depuis un site Web" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "Ouvrir le site Web" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "S’inscrire" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "Consultez %s pour plus d’informations sur comment s’inscrire" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "Message trop long" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "modifié" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "en cours d’envoi…" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "échec de l’envoi" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, c-format msgid "%s, %s and %i others are typing…" msgstr "%s, %s et %i autres sont en train d’écrire…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "%s, %s et %s sont en train d’écrire…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "%s et %s sont en train d’écrire…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "%s est en train d’écrire…" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "Appel démarré" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "A commencé il y a %s" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "Vous avez réagi à cet appel depuis un autre client" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "Appel terminé" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "S’est terminé à %s" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "A duré %s" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "Appel manqué" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "Vous avez manqué cet appel" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "%s a manqué cet appel" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "Appel rejeté" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "Vous avez rejeté cet appel" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "%s a rejeté cet appel" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "Appel échoué" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "%i heure" msgstr[1] "%i heures" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "%i minute" msgstr[1] "%i minutes" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "quelques secondes" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Non chiffré" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "Impossible d’envoyer le message" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "le %x à %Hh %M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "le %x à %lh %M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "le %d %b à %Hh %M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "le %d %b à %lh %M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "%a à %Hh %M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "%a à %lh %M %p" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "Enregistrer sous…" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Cette personne souhaiterait vous ajouter à sa liste de contacts" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "Ouvrir" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "Téléchargement de %s…" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "%s proposé au téléchargement : %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "Fichier proposé au téléchargement : %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "Fichier proposé au téléchargement" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "Échec du transfert de fichier" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "Aujourd’hui" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "%a %d %b" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "Envoyer un fichier" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "Modifier ce message" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "Cliquez ici pour commencer une discussion ou rejoindre un salon." #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "Vous n’avez aucune discussion ouverte" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Compte" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Pseudo" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Alias" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Ajouter un contact" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Notifier de l’arrivée de nouveaux messages" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Convertir les émoticônes typographiques en emojis" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "Vérifier l'orthographe" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Client de clavardage XMPP moderne" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino est un client de clavardage libre et moderne pour le bureau. Il se " "concentre sur la fourniture d’une expérience XMPP simple et fiable tout en " "ayant toujours à l’esprit votre confidentialité." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Il prend en charge le chiffrement de bout en bout avec OMEMO et OpenPGP et " "permet de configurer les fonctions liées à la confidentialité telles que les " "accusés de réception et les notifications d’écriture." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino récupère l’historique du serveur et synchronise les messages avec les " "autres clients." #: main/data/global_search.ui:37 msgid "No active search" msgstr "Pas de recherche en cours" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Écrivez pour lancer une recherche" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Aucun message correspondant à la recherche" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Vérifiez l’orthographe ou essayez de supprimer des filtres" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "Envoyer" #: main/data/shortcuts.ui:12 msgid "General" msgstr "Général" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "Discussion" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "Navigation" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "Aller à la discussion suivante" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "Revenir à la discussion précédente" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "Comptes" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Alias local" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Aucun compte configuré" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Ajouter un compte" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "S’identifier" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Créer un compte" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "Impossible d’établir une connexion sécurisée" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Connexion" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Choisissez un serveur public" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "Ou indiquez l’adresse d’un serveur" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Annuler et se connecter" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Choisir un autre serveur" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Tout est prêt !" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Terminer" #~ msgid "No active conversations" #~ msgstr "Aucune discussion en cours" #~ msgid "Main window with conversations" #~ msgstr "Fenêtre principale avec des conversations" #~ msgid "%s, %s and %i others" #~ msgstr "%s, %s et %i autres" #~ msgid "You can now start using %s" #~ msgstr "Vous pouvez maintenant utiliser %s" #~ msgid "Open Registration" #~ msgstr "Inscription ouverte" #~ msgid "Save" #~ msgstr "Sauver" #~ msgid "%s, %s and %s" #~ msgstr "%s, %s et %s" #~ msgid "%s and %s" #~ msgstr "%s et %s" #~ msgid "is typing…" #~ msgid_plural "are typing…" #~ msgstr[0] "est en train d’écrire…" #~ msgstr[1] "sont en train d’écrire…" #~ msgid "has stopped typing" #~ msgstr "a arrêté d’écrire" #~ msgid "%i search results" #~ msgstr "%i résultats de recherche" #~ msgid "Discover real JIDs" #~ msgstr "Découverte des vrais JID" #~ msgid "Who may discover real JIDs?" #~ msgstr "Qui peut découvrir les vrais JID ?" #~ msgid "Password required for room entry, if any" #~ msgstr "Mot de passe pour rejoindre le salon, vide pour aucun" #~ msgid "Failed connecting to %s" #~ msgstr "Échec de la connexion à %s" #~ msgid "Join Conference" #~ msgstr "Rejoindre une conférence" #~ msgid "File" #~ msgstr "Fichier" #~ msgid "Communicate happiness." #~ msgstr "Communiquer avec bonheur." #~ msgid "Preferences" #~ msgstr "Préférences" #~ msgid "Quit" #~ msgstr "Quitter" #~ msgid "JID should be of the form “user@example.com”" #~ msgstr "Le JID doit être de la forme « utilisateur@example.com »" #~ msgid "Copy Link Address" #~ msgstr "Copier l’adresse du lien" #~ msgid "Copy" #~ msgstr "Copier" #~ msgid "Select All" #~ msgstr "Tout sélectionner" #~ msgid "Search" #~ msgstr "Rechercher" #~ msgid "Send message marker" #~ msgstr "Envoyer les marqueurs de message" #~ msgid "Start Chat" #~ msgstr "Commencer une discussion" #~ msgid "Request presence updates" #~ msgstr "Demander les mises à jour de la présence" #~ msgid "Join on startup" #~ msgstr "Rejoindre au démarrage" #~ msgid "Add Chat" #~ msgstr "Ajouter discussion" dino-0.3.0/main/po/gl.po0000644000000000000000000010652514202022370013446 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2022-02-11 18:58+0000\n" "Language-Team: Galician \n" "Language: gl\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: Weblate 4.11-dev\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "O ficheiro excede o tamaño máximo de subida permitido." #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "Eu" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "Imaxe enviada" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "Ficheiro enviado" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "Imaxe recibida" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "Ficheiro recibido" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "Chamada saínte" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "Chamada entrante" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "%d %b" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "Onte" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "%H∶%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "%l∶%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "fai %i min" msgstr[1] "fai %i minutos" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "Xusto agora" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "Dono" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "Administrador" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "Membro" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "Usuario" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "Convidar" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "Comezar conversa privada" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "Botar" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "Conceder permiso para escribir" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "Revogar o permiso de escritura" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "Convidar ó grupo" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "Escolle ficheiro" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Escoller" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Cancelar" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "%s de %s" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "Non tes permiso para escribir en esta sala de conferencia." #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "Solicita permiso" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "Iniciar chamada" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "Chamada de audio" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "Chamada de vídeo" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "Buscar mensaxes" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Membresía" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "Chamando…" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "Soando…" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "Conectando…" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "%s rematou a chamada" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "%s rexeitou a chamada" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "Cámaras" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "Non se atopa unha cámara." #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "Micrófonos" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "Non se atopa o micrófono." #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "Altofalantes" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "Non se atopa o altofalante." #: main/src/ui/call_window/call_window_controller.vala:128 msgid "Invite to Call" msgstr "Convidar a Chamada" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "Comezar conversa" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Comezar" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Unirse a unha canle" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Seguinte" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "Unirse" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Atrás" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "Uníndose…" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "Precisas dun contrasinal para entrar na sala" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "Non ten permiso para unirse ou crea-lo grupo" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "A sala non existe" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "Non pode crea-la sala" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "Sala só para membros" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "Escolle un alcume diferente" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "Demasiada xente na sala" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "Non se conectou a %s" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "Enderezo non válido" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Engadir" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Atallos de teclado" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "Acerca de Dino" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "%i resultado da procura" msgstr[1] "%i resultados da procura" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "En %s" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "Con %s" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "Chamada de vídeo entrante" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "Chamada de vídeo en grupo" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "Chamada en grupo" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "Rexeitar" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "Aceptar" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "Solicitude de subscrición" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "Rexeitar" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "Convite a %s" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "%s convidoute a %s" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "Solicitude de permiso" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "%s pide permiso para escribir en %s" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Axustes" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Axustes locais" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Enviar notificacións ó escribir" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Enviar confirmacións de lectura" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Notificacións" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "Activado" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "Desactivado" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "Só cando te mencionan" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "Por defecto: %s" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "Solicitar" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "Permisos" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "Solicita permiso para enviar mensaxes" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Detalles do grupo" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "Detalles do contacto" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Bloquear" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "" "As actualizacións do estado e comunicación están bloqueadas en calquera " "dirección" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Nome da sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Descrición da sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Persistente" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "A sala permanecerá despois de que saia o derradeiro participante" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Pódese atopar de xeito público" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Os participantes poden muda-lo asunto" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "Permiso para ollar JIDs" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "Quen pode ollar os JIDs dos participantes?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "Contrasinal" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "Un contrasinal para restrinxir o acceso á sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Moderado" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Só os participantes con voz poden enviar mensaxes" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Só membros" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Só os membros poden entrar na sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Histórico das mensaxes" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Morea máxima de rexistros fornecidos pola sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Axustes da sala" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "Benvida a Dino!" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "Conectarse ou crear unha conta para comezar." #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "Configurar conta" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "Sen contas activas" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "Xestionar contas" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Contrasinal incorrecto" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Certificado TLS non válido" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Eliminar a conta %s?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Eliminar" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Escoller imaxe do perfil" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Imaxes" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Tódolos ficheiros" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Conectado" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Desconectado" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Erro" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Engadir conta" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "O servidor non puido probar que é %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "" "O teu sistema non estableceu a confianza no seu certificado de seguridade." #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "O seu certificado de seguridade foi proporcionado para outro dominio." #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "O seu certificado de seguridade só terá validez no futuro." #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "O certificado de seguridade caducou." #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "Conectar con %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "Agora é posible usar a conta %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "Usuario ou contrasinal incorrectos" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "Algo fallou" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "Sen resposta do servidor" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "Rexistrarse en %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "O servidor precisa que te rexistres a través dun sitio web" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "Abrir sitio web" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "Rexistrar" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "Olla en %s para máis información sobre o rexistro" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "Mensaxe demasiado longa" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "editado" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "pendente…" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "fallou a entrega" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, c-format msgid "%s, %s and %i others are typing…" msgstr "%s, %s e outros %i están escribindo…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "%s, %s e %s están a escribir…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "%s e %s están a escribir…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "%s está a escribir…" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "Comezou a chamada" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "Iniciada hai %s" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "Atendeches a chamada noutro dispositivo" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "Chamada finalizada" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "Rematou ás %s" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "Durou %s" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "Chamada perdida" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "Perdeches esta chamada" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "%s perdeu esta chamada" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "Chamada rexeitada" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "Rexeitaches esta chamada" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "%s rexeitou esta chamada" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "Fallou a chamada" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "%i hora" msgstr[1] "%i horas" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "%i minuto" msgstr[1] "%i minutos" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "uns poucos segundos" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Non cifrado" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "Imposible enviar a mensaxe" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "%x, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "%x, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "%d %b, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "%d %b, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "%a, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "%a, %l∶%M %p" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "Gardar como…" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Esta persoa gostaríalle engadirte á súa listaxe dos contactos" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "Abrir" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "Descargando %s…" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "%s ofreceuche: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "Ficheiro ofrecido: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "Ficheiro ofrecido" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "Fallou a transferencia do ficheiro" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "Hoxe" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "%a, %d %b" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "Enviar ficheiro" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "Actualizar mensaxe" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "Preme aquí para iniciar unha conversa ou unirte a unha canle." #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "Non tes conversas abertas" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Conta" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Sobrenome" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Alcume" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Engadir contacto" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Notificar cando chega unha nova mensaxe" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Converter risoños a emojis" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "Comprobar ortografía" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Cliente moderno para conversas XMPP" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino é un cliente moderno e de código aberto para o escritorio. Orientado a " "fornecer unha experiencia Jabber/XMPP limpa e fiábel tendo a privacidade e " "seguranza presentes." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Suporta o cifrado de punto-a-punto con OMEMO e OpenPGP e permite configurar " "trazos orientados á privacidade tales coma confirmación de lectura e " "notificacións de escritura." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino obtén o histórico dende o servidor e sincroniza as mensaxes con outros " "dispositivos." #: main/data/global_search.ui:37 msgid "No active search" msgstr "Sen procura activa" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Escriba para comezar unha procura" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Sen mensaxes coincidentes" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Comprobe a escrita ou tente eliminar filtros" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "Enviar" #: main/data/shortcuts.ui:12 msgid "General" msgstr "Xeral" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "Conversa" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "Navegación" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "Ir a seguinte conversa" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "Ir a conversa anterior" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "Contas" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Alcume local" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Sen contas configuradas" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Engadir unha conta" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Conectar" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Crear unha conta" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "Non se estableceu unha conexión segura" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Conectar" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Escoller un servidor público" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "Ou indicar un enderezo de servidor" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Utiliza conta existente" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Escoller outro servidor" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Todo feito!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Rematar" #~ msgid "Video call incoming" #~ msgstr "Chamada de vídeo entrante" #~ msgid "Call incoming" #~ msgstr "Chamada entrante" #~ msgid "Establishing call" #~ msgstr "Establecendo chamada" #~ msgid "Video call establishing" #~ msgstr "Establecendo chamada de vídeo" #~ msgid "Call establishing" #~ msgstr "Establecendo chamada" #~ msgid "Call in progress…" #~ msgstr "Chamada en curso…" #, c-format #~ msgid "Lasted for %s" #~ msgstr "Durou %s" #~ msgid "seconds" #~ msgstr "segundos" #~ msgid "No active conversations" #~ msgstr "Sen conversas activas" #~ msgid "Main window with conversations" #~ msgstr "Lapela principal con conversas" #~ msgid "%s, %s and %i others" #~ msgstr "%s, %s e %i outros" #~ msgid "You can now start using %s" #~ msgstr "Agora xa podes comezar a utilizar %s" #~ msgid "Open Registration" #~ msgstr "Rexistro aberto" #~ msgid "Save" #~ msgstr "Gardar" #~ msgid "%s, %s and %s" #~ msgstr "%s, %s e %s" #~ msgid "%s and %s" #~ msgstr "%s e %s" #~ msgid "is typing…" #~ msgid_plural "are typing…" #~ msgstr[0] "está a escribir…" #~ msgstr[1] "están a escribir…" #~ msgid "has stopped typing" #~ msgstr "deixou de escribir" #~ msgid "%i search results" #~ msgstr "%i resultados da procura" #~ msgid "Discover real JIDs" #~ msgstr "Descobri-los JIDs reais" #~ msgid "Who may discover real JIDs?" #~ msgstr "Quen pode descobri-los JIDs reais?" #~ msgid "Password required for room entry, if any" #~ msgstr "Contrasinal precisada para entrar na sala, se houbese" #~ msgid "Failed connecting to %s" #~ msgstr "Ocorreu un erro ó conectar cara %s" #~ msgid "Join Conference" #~ msgstr "Unirse a un grupo" #~ msgid "File" #~ msgstr "Ficheiro" #~ msgid "Communicate happiness." #~ msgstr "Espalla a felicidade." #~ msgid "Preferences" #~ msgstr "Preferencias" #~ msgid "Quit" #~ msgstr "Saír" #~ msgid "JID should be of the form “user@example.com”" #~ msgstr "O JID debe te-lo formato \"usuario@exemplo.com\"" #~ msgid "Copy Link Address" #~ msgstr "Copiar enderezo da ligazón" #~ msgid "Copy" #~ msgstr "Copiar" #~ msgid "Select All" #~ msgstr "Escoller todo" dino-0.3.0/main/po/hu.po0000644000000000000000000010652414202022370013457 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2021-12-05 01:51+0000\n" "Language-Team: Hungarian \n" "Language: hu\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: Weblate 4.10-dev\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "A fájl meghaladja a kiszolgáló legnagyobb feltöltési méretét." #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "Én" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "Kép elküldve" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "Fájl elküldve" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "Kép érkezett" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "Fájl érkezett" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "Kimenő hívás" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "Bejövő hívás" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "%b. %e." #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "Tegnap" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "%H∶%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "%p %I∶%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "%i perccel ezelőtt" msgstr[1] "%i perccel ezelőtt" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "Épp most" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "Tulajdonos" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "Adminisztrátor" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "Tag" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "Felhasználó" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "Meghívás" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "Magánbeszélgetés indítása" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "Kirúgás" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "Írási jogosultság megadása" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "Írási jogosultság visszavonása" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "Meghívás a konferenciába" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "Fájl kiválasztása" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Kiválasztás" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Mégse" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "%s tőle: %s" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "Ez a konferencia nem engedélyezi az üzenetek küldését." #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "Jogosultság kérése" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "Hívás indítása" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "Hanghívás" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "Videohívás" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "Üzenetek keresése" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Tagok" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "Hívás…" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "Csörgetés…" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "Kapcsolódás…" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "%s befejezte a hívást" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "%s elutasította a hívást" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "" #: main/src/ui/call_window/call_window_controller.vala:128 msgid "Invite to Call" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "Beszélgetés indítása" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Indítás" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Csatlakozás csatornához" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Következő" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "Csatlakozás" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Vissza" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "Csatlakozás…" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "Jelszó szükséges a szobába való belépéshez" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "" "Eltiltva a konferenciához való csatlakozástól vagy a konferencia " "létrehozásától" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "A szoba nem létezik" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "Nem engedélyezett a szoba létrehozása" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "Csak tagoknak engedélyezett szoba" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "Válasszon másik becenevet" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "Túl sok résztvevő van a szobában" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "Nem sikerült kapcsolódni ehhez: %s" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "Érvénytelen cím" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Hozzáadás" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Gyorsbillentyűk" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "A Dino névjegye" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "%i keresési eredmény" msgstr[1] "%i keresési eredmény" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "Ebben: %s" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "Ezzel: %s" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "Bejövő videohívás" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "Elutasítás" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "Elfogadás" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "Feliratkozási kérés" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "Elutasítás" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "Meghívás ide: %s" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "%s meghívta Önt ide: %s" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "Jogosultsági kérés" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "%s jogosultságot kér, hogy ide írjon: %s" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Beállítások" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Helyi beállítások" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Gépelési értesítések küldése" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Olvasási visszaigazolások küldése" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Értesítések" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "Be" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "Ki" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "Csak ha említenek" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "Alapértelmezett: %s" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "Kérés" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "Jogosultságok" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "Jogosultság kérése az üzenetek küldéséhez" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Konferencia részletei" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "Partner részletei" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Tiltás" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "" "A kommunikáció és az állapotfrissítések mindkét irányból le vannak tiltva" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "A szoba neve" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "A szoba leírása" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Állandó" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "A szoba megmarad azután is, hogy az utolsó résztvevő elhagyta" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Nyilvánosan kereshető" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "A résztvevők megváltoztathatják a témát" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "Jogosultság a Jabber-azonosítók megtekintéséhez" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "Kinek engedélyezett a résztvevők Jabber-azonosítóinak megtekintése?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "Jelszó" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "Jelszó a szobához való hozzáférés korlátozásához" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Moderált" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Csak a hanggal rendelkező résztvevők küldhetnek üzeneteket" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Csak tagok" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Csak tagok léphetnek be a szobába" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Üzenetelőzmények" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "A szoba üzenetelőzményeinek legnagyobb mennyisége" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Szoba beállítása" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "Üdvözli a Dino!" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "Jelentkezzen be, vagy hozzon létre egy fiókot az induláshoz." #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "Fiók beállítása" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "Nincsenek aktív fiókok" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "Fiókok kezelése" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Hibás jelszó" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Érvénytelen TLS-tanúsítvány" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Eltávolítja a(z) %s fiókot?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Eltávolítás" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Profilkép kiválasztása" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Képek" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Összes fájl" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Kapcsolódva" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Leválasztva" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Hiba" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Fiók hozzáadása" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "A kiszolgáló nem tudta bizonyítani, hogy ez %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "Az operációs rendszer nem bízik a biztonsági tanúsítványában." #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "A biztonsági tanúsítványát egy másik tartományra állították ki." #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "A biztonsági tanúsítványa csak a jövőben válik érvényessé." #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "A biztonsági tanúsítványa lejárt." #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "Bejelentkezés ide: %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "Most már használhatja ezt a fiókot: %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "Hibás felhasználónév vagy jelszó" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "Valami tönkre ment" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "Nincs válasz a kiszolgálótól" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "Regisztráció ezen: %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "A kiszolgáló azt követeli, hogy regisztráljon egy weboldalon keresztül" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "Weboldal megnyitása" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "Regisztráció" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "" "Nézze meg a %s oldalt azon információkért, hogy hogyan kell regisztrálni" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "Az üzenet túl hosszú" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "szerkesztve" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "függőben…" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "sikertelen kézbesítés" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, c-format msgid "%s, %s and %i others are typing…" msgstr "%s, %s és %i partner éppen ír…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "%s, %s és %s éppen ír…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "%s és %s éppen ír…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "%s éppen ír…" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "Hívás elindítva" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "Elindítva ennyi ideje: %s" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "Ön ezt a hívást egy másik eszközön kezelte" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "Hívás befejezve" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "Befejezve ekkor: %s" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "Eddig tartott: %s" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "Nem fogadott hívás" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "Ön nem fogadta ezt a hívást" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "%s nem fogadta ezt a hívást" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "Hívás elutasítva" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "Ön elutasította ezt a hívást" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "%s elutasította ezt a hívást" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "Hívás sikertelen" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "%i óra" msgstr[1] "%i óra" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "%i perc" msgstr[1] "%i perc" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "néhány másodperc" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Titkosítatlan" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "Nem lehet elküldeni az üzenetet" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "%x, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "%x, %p %I∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "%b. %e., %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "%b. %e., %p %I∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "%a, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "%a, %p %I∶%M" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Ez a partner hozzá szeretné adni Önt a partnerlistájához" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "%s letöltése…" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "%s ezt ajánlotta: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "Felajánlott fájl: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "Felajánlott fájl" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "A fájlátvitel sikertelen" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "Ma" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "%b. %e., %a" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "Fájl küldése" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "Üzenet frissítése" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "" "Kattintson ide egy beszélgetés indításához vagy egy csatornához való " "csatlakozáshoz." #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "Önnek nincsenek nyitott csevegései" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Fiók" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Becenév" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Álnév" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Partner hozzáadása" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Értesítés új üzenet érkezésekor" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Hangulatjelek átalakítása emodzsikká" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "Helyesírás-ellenőrzés" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Modern XMPP csevegőprogram" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "A Dino egy modern, nyílt forráskódú csevegőprogram az asztali gépekre. Arra " "összpontosít, hogy tiszta és megbízható Jabber/XMPP-élményt nyújtson, " "miközben a magánszféra megőrzését is fontosnak tartja." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Támogatja az OMEMO és az OpenPGP használatával történő végpontok közötti " "titkosítást, és lehetővé teszi a magánszférához kapcsolódó funkciókat, mint " "például az olvasási visszaigazolást és a gépelési értesítéseket." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "A Dino lekéri az előzményeket a kiszolgálóról, és szinkronizálja az " "üzeneteket a többi eszközzel." #: main/data/global_search.ui:37 msgid "No active search" msgstr "Nincs aktív keresés" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Gépeljen a keresés elkezdéséhez" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Nincsenek illeszkedő üzenetek" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Ellenőrizze a helyesírást vagy próbálja meg eltávolítani a szűrőket" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "Küldés" #: main/data/shortcuts.ui:12 msgid "General" msgstr "Általános" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "Beszélgetés" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "Navigáció" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "Ugrás a következő beszélgetésre" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "Ugrás az előző beszélgetésre" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "Fiókok" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Helyi álnév" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Nincsenek beállított fiókok" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Fiók hozzáadása" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Bejelentkezés" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Fiók létrehozása" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "Nem sikerült kiépíteni biztonságos kapcsolatot" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Kapcsolódás" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Nyilvános kiszolgáló kiválasztása" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "Vagy egy kiszolgáló címének megadása" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Bejelentkezés inkább" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Másik kiszolgáló választása" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Minden készen áll!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Befejezés" #~ msgid "No active conversations" #~ msgstr "Nincs aktív beszélgetés" #~ msgid "Main window with conversations" #~ msgstr "A fő ablak a beszélgetésekkel" #~ msgid "%s, %s and %i others" #~ msgstr "%s, %s és %i további felhasználó" #~ msgid "Save" #~ msgstr "Mentés" #~ msgid "%s, %s and %s" #~ msgstr "%s, %s, és %s" #~ msgid "%s and %s" #~ msgstr "%s és %s" #~ msgid "is typing…" #~ msgid_plural "are typing…" #~ msgstr[0] "gépel…" #~ msgstr[1] "gépel…" #~ msgid "has stopped typing" #~ msgstr "abbahagyta a gépelést" #~ msgid "Discover real JIDs" #~ msgstr "Valódi JID-k megtekintése" #~ msgid "Who may discover real JIDs?" #~ msgstr "Ki láthatja a valódi JID-ket?" #~ msgid "Password required for room entry, if any" #~ msgstr "A szobába lépéshez szükséges jelszó, ha van" #~ msgid "Join Conference" #~ msgstr "Csatlakozás konferenciához" #~ msgid "File" #~ msgstr "Fájl" #~ msgid "Preferences" #~ msgstr "Beállítások" #~ msgid "Quit" #~ msgstr "Kilépés" #~ msgid "JID should be of the form “user@example.com”" #~ msgstr "" #~ "A JID-nek ebben a formátumban kell lennie: \"felhasználó@szolgáltató.com\"" #~ msgid "Copy Link Address" #~ msgstr "Link címének másolása" #~ msgid "Copy" #~ msgstr "Másolás" #~ msgid "Select All" #~ msgstr "Összes kijelölése" #~ msgid "Search" #~ msgstr "Keresés" dino-0.3.0/main/po/id.po0000644000000000000000000010072514202022370013434 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2022-01-04 01:53+0000\n" "Language-Team: none\n" "Language: id\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Weblate 4.10.1\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "Ukuran file melebihi ukuran unggahan maksimum." #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "Saya" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "Gambar terkirim" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "File terkirim" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "Gambar diterima" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "File diterima" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "Panggilan keluar" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "Panggilan masuk" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "%d %b" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "Kemarin" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "%H∶%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "%l∶%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "%i menit yang lalu" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "Sesaat lalu" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "Pemilik" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "Moderator" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "Anggota" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "Pengguna" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "Undang" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "Mulai percakapan pribadi" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "Depak" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "Beri ijin menulis" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "Batalkan ijin menulis" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "Undang ke Grup" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "Pilih file" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Pilih" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Batal" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "%s dari %s" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "Grup ini tidak mengijinkan anda mengirim pesan." #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "Meminta ijin" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "Mulai panggilan" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "Panggilan audio" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "Panggilan video" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "Cari pesan" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Anggota" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "Memanggil…" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "Berdering…" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "Menghubungi…" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "%s mengakhiri panggilan" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "%s menolak panggilan" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "" #: main/src/ui/call_window/call_window_controller.vala:128 msgid "Invite to Call" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "Mulai percakapan" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Mulai" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Bergabung dengan Channel" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Selanjutnya" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "Bergabung" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Kembali" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "Bergabung…" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "Password diperlukan untuk memasuki kamar" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "Dilarang bergabung atau membuat grup" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "Kamar tidak ada" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "Tidak diperbolehkan membuat kamar" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "Kamar khusus anggota" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "Pilih nama panggilan lain" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "Terlalu banyak penghuni di kamar" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "Tidak terhubung ke %s" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "Alamat tidak valid" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Tambah" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Kombinasi tombol" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "Tentang Dino" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "%i hasil pencarian" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "di %s" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "Pada %s" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "Panggilan video masuk" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "Tolak" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "Terima" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "Meminta ijin" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "Tolak" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "Undangan untuk %s" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "%s mengundang anda ke %s" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "Permintaan ijin" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "%smeminta ijin untuk menulis ke dalam %s" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Pengaturan" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Pengaturan lokal" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Kirim pemberitahuan pengetikan" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Kirim tanda telah dibaca" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Notifikasi" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "On" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "Mati" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "Hanya jika dipanggil" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "Pengaturan awal: %s" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "Permintaan" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "Ijin" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "Meminta ijin untuk mengirim pesan" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Detail Grup" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "Detail kontak" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Blokir" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "Komunikasi dan pembaruan status diblokir pada kedua arah" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Nama kamar" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Deskripsi kamar" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Abadi" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "Kamar tetap ada setelah penghuni terakhir pergi" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Dapat ditemukan" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Penghuni dapat mengubah subyek pembicaraan" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "Ijin untuk melihat JID" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "Siapa yang diijinkan untuk melihat JID penghuni?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "Kata sandi" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "Kata sandi untuk membatasi akses ke kamar" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Dimoderasi" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Hanya penghuni dengan akses suara yang boleh mengirim pesan" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Khusus anggota" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Hanya anggota yang boleh memasuki kamar" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Riwayat pesan" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Jumlah maksimum backlog yang diterbitkan oleh kamar" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Konfigurasi kamar" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "Selamat datang di Dino!" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "Masuk atau buat akun untuk memulai." #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "Buat akun" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "Tidak ada akun aktif" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "Atur akun" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Password salah" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Sertifikat TLS tidak valid" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Hapus akun %s?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Hapus" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Pilih gambar profil" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Gambar" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Semua file" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Terhubung" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Terputus" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Rusak" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Tambah akun" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "Server tidak dapat membuktikan bahwa ini adalah %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "Sertifikat keamanannya tidak dipercaya oleh OS Anda." #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "Sertifikat keamanan dikeluarkan untuk domain lain." #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "Sertifikat keamanan hanya akan berlaku di masa mendatang." #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "Sertifikat keamanan tidak berlaku." #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "Masuk ke %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "Akun dapat digunakan %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "Nama pengguna atau kata sandi salah" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "Ada yang tidak beres" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "Server tidak merespon" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "Terdaftar pada %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "Server ini mengharuskan pendaftaran melalui website" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "Buka website" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "Daftar" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "Periksa %s untuk informasi pendaftaran" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "Pesan terlalu panjang" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "Diedit" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "tertunda…" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "pengiriman gagal" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, c-format msgid "%s, %s and %i others are typing…" msgstr "%s, %s, dan %i lainnya sedang mengetik…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "%s, %s, dan %s sedang mengetik…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "%s dan%s sedang mengetik…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "%s sedang mengetik…" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "Panggilan dimulai" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "Dimulai %s yang lalu" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "Anda menangani panggilan ini di perangkat lain" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "Panggilan berakhir" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "Berakhir pada %s" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "Berlangsung %s" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "Panggilan tak terjawab" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "Anda melewatkan panggilan ini" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "%s melewatkan panggilan ini" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "Panggilan ditolak" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "Anda menolak panggilan ini" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "%s menolak panggilan ini" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "Panggilan gagal" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "%i jam" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "%i menit" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "beberapa detik" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Tidak terenkripsi" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "Tidak bisa mengirimkan pesan" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "%x, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "%x, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "%d %b, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "%d %b, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "%a, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "%a, %l∶%M %p" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Kontak ini ingin menambahkan anda kedalam daftar kontaknya" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "Mengunduh %s…" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "%s ditawarkan: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "File ditawarkan: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "File ditawarkan" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "Transfer file gagal" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "Hari ini" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "%a, %d %b" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "Kirim file" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "Update pesan" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "Klik untuk memulai percakapan atau bergabung dengan channel." #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "Anda tidak memiliki percakapan terbuka" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Akun" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Panggilan" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Alias" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Tambah kontak" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Beri tahu saat ada pesan" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Ubah smiley menjadi emoji" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "Periksa ejaan" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Aplikasi chat XMPP modern" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino adalah aplikasi chat open source modern untuk PC. Menyediakan " "pengalaman Jabber / XMPP yang handal dengan tetap menjunjung privasi Anda." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Mendukung enkripsi end-to-end dengan OMEMO dan OpenPGP, dan memungkinkan " "pengaturan fitur terkait privasi seperti tanda pesan dibaca dan " "pemberitahuan pengetikan." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino mengambil riwayat pesan dari server dan menyinkronkan pesan dengan " "perangkat lain." #: main/data/global_search.ui:37 msgid "No active search" msgstr "Tidak ada pencarian aktif" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Ketik untuk memulai pencarian" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Tidak ada pesan yang cocok" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Periksa ejaan atau coba hapus filter" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "Kirim" #: main/data/shortcuts.ui:12 msgid "General" msgstr "Umum" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "Percakapan" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "Navigasi" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "Pesan selanjutnya" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "Pesan sebelumnya" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "Akun" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Alias lokal" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Tidak ada akun yang dikonfigurasi" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Tambah akun" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Masuk" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Buat akun" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "Tidak dapat membuat sambungan aman" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Hubungi" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Pilih server publik" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "Atau tentukan alamat server" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Masuk saja" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Pilih server lain" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Selesai!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Selesai" dino-0.3.0/main/po/ie.po0000644000000000000000000010236114202022370013433 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2021-03-01 10:50+0000\n" "Language-Team: none\n" "Language: ie\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: Weblate 4.5\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "Li grandore de file excede li maximum del servitor." #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "Yo" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "Image sta inviat" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "File sta inviat" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "Image sta recivet" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "Image es recivet" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "%e %b" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "Yer" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "%H:%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "%l:%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "ante %i min" msgstr[1] "ante %i mins" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "Strax" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "Proprietario" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "Administrator" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "Membre" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "Usator" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "Invitar" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "Iniciar un privat conversation" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "Remover" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "Dar li permission scrir" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "Revocar li permission scrir" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "Invitar a un conferentie" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "Selecter un file" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Selecter" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Anullar" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "%s de %s" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "Ti-ci conferentie ne permisse a vos inviar missages." #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "Demandar li permission" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "Serchar missages" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Membres" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "Conexion…" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "" #: main/src/ui/call_window/call_window_controller.vala:128 msgid "Invite to Call" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "Iniciar un conversation" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Iniciar" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Adherer al chanele" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Avan" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "Adherer" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Retro" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "Adherente…" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "Un contrasigne es besonat por intrar li chambre" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "Prohibit de adhesion o creation de conferenties" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "Chambre ne existe" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "Ne es permisset crear chambres" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "Solmen por membres" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "Selecte un different nómine" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "Tro mult occupantes in li chambre" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "Ne successat conexer a %s" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "Adresse es ínvalid" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Adjunter" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Rapid-tastes" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "Pri Dino" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "%i resultate de sercha" msgstr[1] "%i resultates de sercha" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "In %s" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "Con %s" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "Refusar" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "Acceptar" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "Petition de abonnament" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "Refusar" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "Invitation a(l) %s" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "%s invitat vos a(l) %s" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "Demande de permission" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "%s demanda li permission scrir in %s" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Parametres" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Local parametres" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Inviar notificationes pri li tippada" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Inviar confirmationes de letion" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Notificationes" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "Yes" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "No" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "Solmen quande es mentionat" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "Predefinit: %s" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "Demandar" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "Permissiones" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "Demandar li permission inviar missages" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Detallies del conferentie" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "Detallies del contacte" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Blocar" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "Actualisation del communication e statu es blocat in ambi directiones" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Nómine del chambre" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Descrition del chambre" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Persistent" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "Li chambre va persister pos que li ultim occupant surti" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Serchabil publicmen" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Occupantes posse cambiar li tema" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "Permission vider JIDs" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "Qui es permisset vider li JIDs del membres?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "Contrasigne" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "Un contrasigne por restricter li accesse al chambre" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Moderat" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Solmen occupantes con voce posse inviar missages" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Solmen membres" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Solmen li membres posse intrar li chambre" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Diarium de missages" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Max númere de old missages gardat per li chambre" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Configuration del chambre" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "Benevenit a Dino!" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "Inregistra vos o crea un conto por iniciar." #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "Etablisser un conto" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "Null activ contos" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "Gerer contos" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Contrasigne es ínvalid" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Certificate TLS es ínvalid" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Remover li conto %s?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Remover" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Selecte un avatar" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Images" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Omni files" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Conexet" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Disconexet" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Errore" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Adjunter un conto" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "Li servitor ne posset provar que it es %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "Su certificat ne have fide de vor sistema operativ." #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "Su certificat es emisset por un altri dominia." #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "Su certificat va esser valid solmen in li futur." #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "Su certificat ha expirat." #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "Inregistrar se in %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "Nu vu posse usar li conto %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "Contrasigne o nómine ínvalid" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "Un errore evenit" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "Servitore ne responde" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "Registrar sur %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "Li servitor besona r" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "Aperter li website" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "Registrar" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "Visita %s por li information pri registration" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "Li missage es tre long" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "redactet" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, c-format msgid "%s, %s and %i others are typing…" msgstr "%s, %s e %i altri tippa…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "%s, %s e %s tippa…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "%s e %s tippa…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "%s tippa…" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "" msgstr[1] "" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "" msgstr[1] "" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Ínciffrat" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "Ne successat inviar li missage" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "%x, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "%x, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "%e %b, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "%e %b, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "%a, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "%a, %l∶%M %p" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Ti-ci contacte desira adjunter vos al su contact-liste" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "Descargante %s…" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "%s ha ofertat: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "File sta ofertat: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "Un file sta ofertat" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "Transferte de un file ne successat" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "Hodie" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "%a, %d %b" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "Inviar un file" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "Actualisar li missage" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "Fa un clic ti-ci por iniciar un conversation o adherer a un channel." #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "Vu ne have apertet conversationes" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Conto" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Nómine" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Pseudonim" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Adjunter li contacte" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Notificar quande un missage ariva" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Converter smileys a emojis" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Un modern client de conversationes XMPP" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino es un modern cliente de conversationes con fonte apert. It foca se sur " "provider un nett e fidibil experientie de Jabber/XMPP con un attention a " "confidentialitá." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "It supporta ciffration terminal per OMEMO e OpenPGP e permisse configurar " "sensitiv functiones quam confirmation de lectada e notificationes de tippada." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino obtene li diarium del servitore e sincronisa missages inter altri " "apparates." #: main/data/global_search.ui:37 msgid "No active search" msgstr "Null activ sercha" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Tippa por iniciar un sercha" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Null correspondent missages" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Controla li ortografie o remove filtres" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "Inviar" #: main/data/shortcuts.ui:12 msgid "General" msgstr "General" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "Conversation" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "Navigation" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "Ear al sequent conversation" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "Ear al precedent conversation" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "Contos" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Local pseudonim" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Null contos etablisset" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Adjunter un conto" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Inregistrar se" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Crear un conto" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "Ne successat etablisser un secur conexion" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Connexer" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Selecte un public servitore" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "O provide un adresse de servitore" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "O inregistrar se" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Selecte un altri servitore" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Omni es pret!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Finir" #~ msgid "No active conversations" #~ msgstr "Null activ conversationes" #~ msgid "Main window with conversations" #~ msgstr "Li primari fenestre con conversationes" #~ msgid "%s, %s and %i others" #~ msgstr "%s, %s e %i altri" #~ msgid "You can now start using %s" #~ msgstr "Nu vu posse usar %s" #~ msgid "Open Registration" #~ msgstr "Apert registration" #~ msgid "Save" #~ msgstr "Gardar quam" #~ msgid "%s, %s and %s" #~ msgstr "%s, %s e %s" #~ msgid "%s and %s" #~ msgstr "%s e %s" #~ msgid "is typing…" #~ msgid_plural "are typing…" #~ msgstr[0] "tippa…" #~ msgstr[1] "tippa…" #~ msgid "has stopped typing" #~ msgstr "ne tippa plu" #~ msgid "%i search results" #~ msgstr "resultates: %i" #~ msgid "Discover real JIDs" #~ msgstr "Decovrir real JIDs" #~ msgid "Who may discover real JIDs?" #~ msgstr "Qui posse decovrir real JIDs?" #~ msgid "Password required for room entry, if any" #~ msgstr "Un contrasigne por intrar li chambre" dino-0.3.0/main/po/it.po0000644000000000000000000010726414202022370013461 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2022-02-11 18:58+0000\n" "Language-Team: Italian \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 4.11-dev\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "Il file eccede la dimensione massima di upload." #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "Io" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "Immagine inviata" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "File inviato" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "Immagine ricevuta" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "File ricevuto" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "Chiamata in uscita" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "Chiamata in arrivo" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "%d %b" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "Ieri" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "%H:%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "%l:%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "%i minuto fa" msgstr[1] "%i minuti fa" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "Adesso" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "Proprietario" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "Amministratore" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "Membro" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "Utente" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "Invita" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "Inizia una conversazione privata" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "Espelli" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "Concedi il permesso di scrivere" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "Revoca il permesso di scrivere" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "Invita alla conferenza" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "Seleziona un file" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Seleziona" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Annulla" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "%s da %s" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "Questa conferenza non ti permette di inviare messaggi." #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "Richiesta di autorizzazione" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "Chiama" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "Chiamata audio" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "Videochiamata" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "Cerca tra i messaggi" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Partecipanti" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "Chiamando…" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "Sta squillando…" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "Connessione…" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "%s ha terminato la chiamata" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "%s ha rifiutato la chiamata" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "Telecamere" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "Nessuna telecamera trovata." #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "Microfoni" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "Nessun microfono trovato." #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "Altoparlanti" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "Nessun altoparlante trovato." #: main/src/ui/call_window/call_window_controller.vala:128 msgid "Invite to Call" msgstr "Invita alla chiamata" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "Inizia una conversazione" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Inizia" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Entra nel canale" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Avanti" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "Partecipa" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Indietro" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "Accesso in corso…" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "Una password è richiesta per entrare nella stanza" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "Ti è proibito entrare o creare una conferenza" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "La stanza non esiste" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "Non sei abilitato a creare la stanza" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "La stanza è solo per membri" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "Scegli un soprannome differente" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "La stanza ha troppi partecipanti" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "Impossibile connettersi a %s" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "Indirizzo non valido" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Aggiungi" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Scorciatoie da tastiera" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "Informazioni su Dino" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "%i risultato per la ricerca" msgstr[1] "%i risultati per la ricerca" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "In %s" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "Con %s" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "Videochiamata in arrivo" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "Videochiamata di gruppo in arrivo" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "Chiamata di gruppo in arrivo" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "Rifiuta" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "Accetta" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "Richiesta di iscrizione" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "Rifiuta" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "Invito per %s" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "%s ti ha invitato a %s" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "Richiesta di autorizzazione" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "%s ha chiesto il permesso di scrivere in %s" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Impostazioni" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Impostazioni locali" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Invia notifiche di digitazione" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Invia ricevute di lettura" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Notifiche" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "Attivo" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "Disattivo" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "Solo se menzionato" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "Predefinito: %s" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "Richiedi" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "Permessi" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "Richiedi il permesso di inviare messaggi" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Dettagli della conferenza" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "Dettagli del contatto" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Blocca" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "" "La comunicazione e gli aggiornamenti di stato sono bloccati in entrambe le " "direzioni" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Nome della stanza" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Descrizione della stanza" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Persistente" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "La stanza continuerà ad esistere dopo l'uscita dell'ultimo occupante" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Ricercabile pubblicamente" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "I partecipanti possono cambiare l'argomento" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "Autorizzazione per visualizzare i JID" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "Chi è autorizzato a vedere i JID degli occupanti?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "Password" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "Una password per limitare l’accesso al gruppo" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Moderata" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Solo i partecipanti autorizzati possono mandare messaggi" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Solo per membri" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Solo i membri possono entrare nella stanza" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Cronologia dei messaggi" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Numero massimo di messaggi in cronologia restituiti dalla stanza" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Configurazione della stanza" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "Benvenuto in Dino!" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "Accedi o crea un account per iniziare." #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "Configura account" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "Nessun account attivo" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "Gestisci account" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Password sbagliata" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Il certificato TLS non è valido" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Rimuovere l'account %s?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Rimuovi" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Scegli una foto profilo" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Immagini" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Tutti i documenti" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Connesso" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Disconnesso" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Errore" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Aggiungi un account" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "Il server non può dimostrare di essere %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "Il certificato di sicurezza non è riconosciuto dal tuo sistema." #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "Il certificato di sicurezza è rilasciato per un altro dominio." #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "Il certificato di sicurezza diventerà valido solo in futuro." #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "Il certificato di sicurezza è scaduto." #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "Accedi a %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "Ora è possibile iniziare a usare l’account %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "Nome utente o password sbagliati" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "Qualcosa è andato storto" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "Nessuna risposta dal server" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "Registrati su %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "Il server richiede di registrarsi tramite un sito web" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "Apri il sito web" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "Registrati" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "Controlla %s per informazioni su come registrarsi" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "Messaggio troppo lungo" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "modificato" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "in attesa…" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "invio fallito" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, c-format msgid "%s, %s and %i others are typing…" msgstr "%s, %s e %i altri stanno scrivendo…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "%s, %s e %s stanno scrivendo…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "%s e %s stanno scrivendo…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "%s sta scrivendo…" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "Chiamata iniziata" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "Iniziata %s fa" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "Hai gestito questa chiamata su un altro dispositivo" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "Chiamata terminata" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "Terminata alle %s" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "Durata %s" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "Chiamata persa" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "Hai perso questa chiamata" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "%s ha perso questa chiamata" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "Chiamata rifiutata" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "Hai rifiutato questa chiamata" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "%s ha rifiutato questa chiamata" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "Chiamata non riuscita" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "%i ora" msgstr[1] "%i ore" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "%i minuto" msgstr[1] "%i minuti" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "qualche secondo" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Non cifrato" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "Impossibile inviare il messaggio" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "%x, %H:%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "%x, %l:%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "%d %b, %H:%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "%d %b, %l:%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "%a, %H:%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "%a, %l:%M %p" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "Salva come…" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Questo contatto vorrebbe aggiungerti alla sua lista contatti" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "Apri" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "Scaricamento di %s in corso…" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "%s ha offerto: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "File offerto: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "File offerto" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "Trasferimento del file non riuscito" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "Oggi" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "%a, %d %b" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "Invia un file" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "Aggiorna il messaggio" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "" "Fai clic qui per iniziare una conversazione o per entrare in un canale." #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "Non hai chat aperte" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Account" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Soprannome" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Alias" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Aggiungi contatto" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Notifica quando arriva un nuovo messaggio" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Trasforma le faccine in emoji" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "Controlla l'ortografia" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Client di chat moderno per XMPP" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino è un client di chat per il desktop, moderno e open-source. Si concentra " "nel fornire un'esperienza Jabber/XMPP pulita e affidabile tenendo presente " "la tua privacy." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Support la crittografia end-to-end tramite OMEMO e OpenPGP e permette di " "configurare le funzioni relative alla privacy come le ricevute di lettura e " "le notifiche di digitazione." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino recupera la cronologia dal server e sincronizza i messaggi con gli " "altri dispositivi." #: main/data/global_search.ui:37 msgid "No active search" msgstr "Nessuna ricerca attiva" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Digita per iniziare una ricerca" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Nessun messaggio corrispondente" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Controlla l'ortografia o prova a rimuovere dei filtri" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "Invia" #: main/data/shortcuts.ui:12 msgid "General" msgstr "Generali" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "Conversazione" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "Navigazione" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "Passa alla conversazione successiva" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "Passa alla conversazione precedente" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "Account" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Alias locale" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Nessun account configurato" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Aggiungi un account" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Accedi" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Crea account" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "Impossibile stabilire una connessione sicura" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Connetti" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Scegli un server pubblico" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "Oppure inserisci l'indirizzo di un server" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Altrimenti accedi" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Scegli un altro server" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Tutto pronto!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Fine" #~ msgid "No active conversations" #~ msgstr "Nessuna conversazione attiva" #~ msgid "Main window with conversations" #~ msgstr "La finestra principale con le conversazioni" #~ msgid "%s, %s and %i others" #~ msgstr "%s, %s e %i altri" #~ msgid "You can now start using %s" #~ msgstr "Adesso puoi iniziare ad usare %s" #~ msgid "Open Registration" #~ msgstr "Registrazioni aperte" #~ msgid "Save" #~ msgstr "Salva" #~ msgid "%s, %s and %s" #~ msgstr "%s, %s, e %s" #~ msgid "%s and %s" #~ msgstr "%s e %s" #~ msgid "is typing…" #~ msgid_plural "are typing…" #~ msgstr[0] "sta scrivendo…" #~ msgstr[1] "stanno scrivendo…" #~ msgid "has stopped typing" #~ msgstr "ha smesso di scrivere" #~ msgid "%i search results" #~ msgstr "%i risultati per la ricerca" #~ msgid "Discover real JIDs" #~ msgstr "Scopri i veri JID" #~ msgid "Who may discover real JIDs?" #~ msgstr "Chi può scoprire i veri JID?" #~ msgid "Password required for room entry, if any" #~ msgstr "Password richiesta per entrare nella stanza, se impostata" #~ msgid "Failed connecting to %s" #~ msgstr "Connessione a %s fallita" #~ msgid "Join Conference" #~ msgstr "Partecipa alla conferenza" #~ msgid "File" #~ msgstr "File" #~ msgid "Communicate happiness." #~ msgstr "Comunica felicità." #~ msgid "Preferences" #~ msgstr "Preferenze" #~ msgid "Quit" #~ msgstr "Esci" #~ msgid "JID should be of the form “user@example.com”" #~ msgstr "Il JID dovrebbe essere nella forma \"utente@example.com\"" #~ msgid "Copy Link Address" #~ msgstr "Copia l'indirizzo del collegamento" #~ msgid "Copy" #~ msgstr "Copia" #~ msgid "Select All" #~ msgstr "Seleziona Tutto" #~ msgid "Search" #~ msgstr "Cerca" #~ msgid "Send message marker" #~ msgstr "Invia lo stato del messaggio" #~ msgid "Start Chat" #~ msgstr "Inizia una conversazione" #~ msgid "Request presence updates" #~ msgstr "Richiedi gli aggiornamenti di stato" #~ msgid "This is an app-notification. Click the button to dismiss" #~ msgstr "Questa è una notifica dell'app. Clicca il bottone per chiudere" #~ msgid "Join on startup" #~ msgstr "Entra all'avvio" #~ msgid "Add Chat" #~ msgstr "Aggiungi conversazione" dino-0.3.0/main/po/ja.po0000644000000000000000000011106214202022370013426 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2021-12-15 01:54+0000\n" "Language-Team: Japanese \n" "Language: ja\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Weblate 4.10-dev\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "ファイルサイズがサーバーの最大アップロード可能サイズを超過しています。" #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "自分" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "画像を送信しました" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "ファイルを送信しました" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "画像を受信しました" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "ファイルを受信しました" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "発信呼び出し" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "着信" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "%b%d日" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "昨日" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "%H∶%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "%p %l∶%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "%i 分前" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "たった今" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "オーナー" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "管理人" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "メンバー" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "ユーザー" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "招待" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "個人チャットを始める" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "退出させる" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "送信権限を許可" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "送信権限を取り消す" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "トークルームへ招待" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "ファイルを選択" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "選択" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "キャンセル" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "%2$s の %1$s" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "このトークルームでは、メッセージの送信が許可されていません。" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "送信権限を要求" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "通話開始" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "音声通話" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "ビデオ通話" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "メッセージを検索" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "メンバー" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "呼び出し中…" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "鳴っている…" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "接続試行中…" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "%sは通話を終了しました" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "%s は通話を拒否しました" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "" #: main/src/ui/call_window/call_window_controller.vala:128 msgid "Invite to Call" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "トークを開始" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "開始" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "チャンネルに参加" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "次へ" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "参加" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "戻る" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "参加試行中…" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "トークルームに参加するにはパスワードが必要です" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "参加中または作成中のトークルームから退会させられました" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "トークルームは存在しません" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "トークルームを作成する権限がありません" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "メンバー制トークルーム" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "別のニックネームを選んでください" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "トークルームの参加者が多すぎます" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "%s に接続できませんでした" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "アドレスが不正です" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "追加" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "キーボードショートカット" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "Dino について" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "%i 件の検索結果" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "%s での検索結果" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "%s との会話での検索結果" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "着信ビデオ通話" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "拒否" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "承諾" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "在席通知の申込" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "拒否" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "%s への招待" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "%s さんがあなたを %s に招待しました" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "送信権限の要求" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "%s さんが %s での送信権限を要求しています" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "設定" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "ローカル設定" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "入力中であることを通知" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "既読状態を送信" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "通知" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "オン" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "オフ" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "メンションされた場合のみ" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "デフォルト: %s" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "要求" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "送信権限" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "メッセージの送信権限を要求" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "トークルームの詳細" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "連絡先の詳細" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "ブロック" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "双方のコミュニケーションと状態の更新がブロックされます" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "トークルームの名前" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "トークルームの説明" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "トークルームを自動削除しない" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "最後の参加者が退会してもトークルームを残します" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "外部から検索可能にする" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "参加者にタイトルの変更を許可する" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "JID を表示できるメンバー" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "参加者の JID を表示できるメンバーを選択します" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "パスワード" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "トークルームへのアクセスを制限するためのパスワード" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "発言制限" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "発言権のある参加者のみがメッセージを送れます" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "メンバー制" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "メンバーのみが入室できます" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "メッセージの履歴" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "保存するトーク履歴の数" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "トークルームの設定" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "Dino へようこそ!" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "サインインまたはアカウント登録をしてください。" #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "アカウントをセットアップ" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "アクティブなアカウントがありません" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "アカウントを管理" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "パスワードが違います" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "TLS 証明書が不正です" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "アカウント %s を削除しますか?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "削除" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "アバターを選択" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "画像" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "すべてのファイル" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "接続済み" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "切断" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "エラー" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "アカウントを追加" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "サーバーはこれが %s だと証明できませんでした。" #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "" "このセキュリティー証明書はオペレーティングシステムに信頼されていません。" #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "このセキュリティー証明書はほかのドメインのために発行されています。" #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "このセキュリティー証明書が有効になる日時になっていません。" #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "このセキュリティー証明書は有効期限切れです。" #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "%s にサインイン" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "アカウント %s を使用できるようになりました。" #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "ユーザー名またはパスワードが違います" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "問題が発生しました" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "サーバーから応答がありません" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "%s に登録" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "サーバーが Web サイトでのサインアップを要求しています" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "Web サイトを開く" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "登録" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "サインアップの方法に関する情報は、%s をご確認ください" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "メッセージが長すぎます" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "編集済み" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "発信中…" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "配信に失敗しました" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, c-format msgid "%s, %s and %i others are typing…" msgstr "%s さん、%s さんに加えて、%i 人のメンバーが入力しています…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "%s さん、%s さん、%s さんが入力しています…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "%s さんと %s さんが入力しています…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "%s さんが入力しています…" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "通話開始" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "%s 前に始まりました" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "別のデバイスでこの呼び出しを処理しました" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "通話終了" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "%s で終了" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "%s 継続していた" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "不在着信" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "この電話に出られなかった" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "%s はこの呼び出しに出られなかった" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "通話が拒否されました" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "通話を拒否しました" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "%s はこの呼び出しを拒否しました" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "呼び出しに失敗しました" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "%i 時間" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "%i 分" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "非暗号化" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "メッセージを送信できません" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "%x、%H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "%x、%p %l∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "%b%d日、%H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "%b%d日、%p %l∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "%a曜日、%H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "%a曜日、%p %l∶%M" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "この相手があなたを連絡先に入れようとしています" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "%s をダウンロードしています…" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "%s を受信しました: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "ファイルを受信しました: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "ファイルが提供されています" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "ファイルの転送に失敗しました" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "今日" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "%b%d日 (%a)" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "ファイルを送信" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "メッセージを更新" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "" "トークを始めたりトークルームに参加したりするには、ここをクリックしてくださ" "い。" #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "表示するトークはまだありません" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "アカウント" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "ニックネーム" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "別名" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "連絡先を追加" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "新しいメッセージが届いたときに通知" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "スマイリーを絵文字に変換" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "スペルチェック" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "現代的な XMPP チャット クライアント" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino はオープンソースの現代的なデスクトップ向けチャットクライアントです。プラ" "イバシーを考慮しつつ、シンプルで信頼できる Jabber/XMPP エクスペリエンスの提供" "を第一に考えて開発されています。" #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "OMEMO と OpenPGP を利用したエンドツーエンド暗号化に対応しており、既読状態の送" "信や入力通知などのプライバシー関連の設定も可能です。" #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino はサーバーから履歴を取得し、ほかのデバイスとメッセージを同期します。" #: main/data/global_search.ui:37 msgid "No active search" msgstr "まだ検索していません" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "入力して検索を開始してください" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "一致するメッセージはありません" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "スペルを確認するか、フィルターを消去してみてください" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "送信" #: main/data/shortcuts.ui:12 msgid "General" msgstr "一般" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "トーク" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "ナビゲーション" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "次のトークへ移動" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "前のトークへ移動" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "アカウント" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "ローカルでの別名" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "アカウントが設定されていません" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "アカウントを追加" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "サインイン" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "アカウントを作成" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "安全な接続を確立できませんでした" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "接続" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "公開サーバーを選択" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "または、サーバーのアドレスを手入力" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "代わりにサインイン" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "別のサーバーを選択" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "すべてのセットアップが完了しました!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "完了" #~ msgid "No active conversations" #~ msgstr "アクティブなトークがありません" #~ msgid "Main window with conversations" #~ msgstr "トーク中のメインウィンドウ" #~ msgid "%s, %s and %i others" #~ msgstr "%s さん、%s さんとその他 %i 人" #~ msgid "You can now start using %s" #~ msgstr "%s をお使いいただけます" #~ msgid "Open Registration" #~ msgstr "登録を開く" #~ msgid "Save" #~ msgstr "保存" #~ msgid "%s, %s and %s" #~ msgstr "%s さん、%s さんと %s さん" #~ msgid "%s and %s" #~ msgstr "%s さんと %s さん" #~ msgid "is typing…" #~ msgid_plural "are typing…" #~ msgstr[0] "が入力しています…" #~ msgid "has stopped typing" #~ msgstr "が入力を中断しました" #~ msgid "%i search results" #~ msgstr "%i 件の検索結果" #~ msgid "Discover real JIDs" #~ msgstr "JID の検索" #~ msgid "Who may discover real JIDs?" #~ msgstr "JID の検索を許可する対象" #~ msgid "Password required for room entry, if any" #~ msgstr "トークルームへの参加に必要なパスワード" #~ msgid "Failed connecting to %s" #~ msgstr "%s への接続に失敗しました" #~ msgid "Join Conference" #~ msgstr "トークルームに参加" #~ msgid "File" #~ msgstr "ファイル" #~ msgid "JID should be of the form “user@example.com”" #~ msgstr "JID は “user@example.com” の形式にしてください" #~ msgid "Copy Link Address" #~ msgstr "リンクアドレスをコピー" #~ msgid "Copy" #~ msgstr "コピー" #~ msgid "Select All" #~ msgstr "すべて選択" #~ msgid "Search" #~ msgstr "検索" #~ msgid "Start Chat" #~ msgstr "会話を開始" dino-0.3.0/main/po/kab.po0000644000000000000000000007053514202022370013602 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2021-09-20 18:38+0000\n" "Language-Team: none\n" "Language: kab\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: Weblate 4.9-dev\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "" msgstr[1] "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "" #: main/src/ui/call_window/call_window_controller.vala:128 msgid "Invite to Call" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "" msgstr[1] "" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "Awal n uɛeddi" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "" #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Awal n uɛeddi d arameɣtu" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, c-format msgid "%s, %s and %i others are typing…" msgstr "" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "" msgstr[1] "" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "" msgstr[1] "" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "" #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" #: main/data/global_search.ui:37 msgid "No active search" msgstr "" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "" #: main/data/shortcuts.ui:12 msgid "General" msgstr "" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "" dino-0.3.0/main/po/ko.po0000644000000000000000000007147414202022370013461 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-08-08 22:32+0000\n" "Language-Team: none\n" "Language: ko\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Weblate 4.2-dev\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "어제" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "%i 분 이전에" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "방금 전" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "초대" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "파일 선택" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "선택" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "취소" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "연결 중…" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "" #: main/src/ui/call_window/call_window_controller.vala:128 msgid "Invite to Call" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "대화 시작" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "시작" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "채널 참가" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "다음" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "참가" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "이전" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "참가 중…" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "추가" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "%i 개의 검색 결과" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "설정" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "켜기" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "끄기" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "비밀번호" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "Dino에 오신 것을 환영합니다!" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "로그인 또는 계정을 생성 하십시오." #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "계정 설정" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "활성화 된 계정이 없습니다" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "계정 관리" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "잘못 된 비밀번호" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "이미지" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "모든 파일" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "연결 됨" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "연결 해제 됨" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "계정 추가" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "웹사이트 열기" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "등록" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, c-format msgid "%s, %s and %i others are typing…" msgstr "" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "%s 와(과) %s, %s이(가) 입력 중…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "%s 와(과) %s 이(가) 입력 중…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "%s 이(가) 입력 중…" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "전송 실패" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "오늘" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "" #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" #: main/data/global_search.ui:37 msgid "No active search" msgstr "" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "" #: main/data/shortcuts.ui:12 msgid "General" msgstr "" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "" dino-0.3.0/main/po/lb.po0000644000000000000000000010362714202022370013441 0ustar rootrootmsgid "" msgstr "" "Project-Id-Version: Luxembourgish (Dino)\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-04-15 00:11+0000\n" "Language-Team: Luxembourgish \n" "Language: lb\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: Weblate 4.0-dev\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "Ech" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "Bild geschéckt" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "Datei geschéckt" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "Bild kritt" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "Datei kritt" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "%b %d" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "Gëschter" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "%H:%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "%l:%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "%i Minutt hier" msgstr[1] "%i Minutten hier" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "Just elo" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "Besëtzer" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "Administrateur" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "Member" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "Benotzer" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "Alueden" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "Privat Conversatioun starten" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "Erausgeheien" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "Invitéieren an Konferenz" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Auswielen" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Ofbriechen" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "%s vum %s" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "Messagen duerchsichen" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Memberen" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "Verbannen…" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "" #: main/src/ui/call_window/call_window_controller.vala:128 msgid "Invite to Call" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "Konversatioun starten" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Start" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Kanal bäitrieden" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Nächst" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "Bäitrieden" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Zeréck" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "Bäitrieden…" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "Passwuert gëtt gebraucht fir de Raum ze betrieden" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "Gebannt vum bäitrieden an erstelle vu Konferenzen" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "Raum existéiert net" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "Net erlaabt ee Raum ze erstellen" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "Raum ass nëmme fir Memberen" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "Wiel een anere Spëtznumm" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "De Raum huet ze vill Benotzer" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "Konnt net op %s connectéieren" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "Ongülteg Adress" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Bäisetzen" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Tastaturofkierzungen" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "Iwwert Dino" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "%i Sichresultat" msgstr[1] "%i Sichresultater" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "An %s" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "Mat %s" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "Akzeptéieren" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "Abonnement Ufro" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "Verweigeren" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "Aluedung an %s" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "%s huet dech zu %s agelueden" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Astellungen" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Lokal Astellungen" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Tipp-Notifikatioune schécken" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Scheck Lies Bestätegungen" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Notifikatiounen" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "Un" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "Aus" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "Nëmme wann erwäänt" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "Standard: %s" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Konferenz Detailer" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "Kontaktdetailer" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Blockéieren" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "Kommunikatioun a Status Updates sinn an all Richtung blockéiert" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Numm vum Raum" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Beschreiwung vum Raum" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Dauerhaft" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "De Raum bleift bestoen, nodeems de leschte Benotzer erausgaangen ass" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Ëffentlech siichtbar" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Benotzer däerfe Sujet änneren" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "Erlabnis JIDs ze gesinn" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "Wien ass erlaabt den Gäscht hier JIDs ze gesinn?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "Passwuert" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "Ee Passwuert fir den Accès zu dësem Raum anzeschränken" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Moderéiert" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Nëmme Benotzer mat Stemm däerfe Messagë schécken" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Nëmme fir Memberen" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Nëmme Memberen däerfen de Raum betrieden" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Messagen Historique" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Maximal Unzuel vum ale Messagen, déi vum Raum geschéckt ginn" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Raum Astellungen" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "Wëllkomm bei Dino!" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "Logg dech an oder kreéier een Account fir lass ze leen." #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "Konto opsetzen" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "Keng Kontoen aktiv" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "Kontoe managen" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Falsch Passwuert" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Invaliden TLS Zertifikat" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Konto %s läschen?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Läschen" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Avatar auswielen" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Biller" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "All Dateien" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Verbonnen" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Getrennt" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Feeler" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Account bäisetzen" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "Bei %s umellen" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "Du kanns elo den Account %s benotzen." #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "Falsche Benotzernumm oder Passwuert" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "Eppes ass schif gaangen" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "Keng Äntwert vum Server" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "Ob %s registréieren" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "De Server setzt eng Registréierung duerch eng Websäit viraus" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "Websäit opmaachen" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "Registréieren" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "Kuck %s fir Informatioune iwwert Registréierung" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "Noriicht ze laang" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "beaarbecht" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, fuzzy, c-format msgid "%s, %s and %i others are typing…" msgstr "%s, %s an %i anerer sinn um schreiwen" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "%s, %s an %s sinn um schreiwen…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "%s an %s sinn um schreiwen…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "%s ass um schreiwen…" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "" msgstr[1] "" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "" msgstr[1] "" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Onverschlësselt" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "Konnt de Message net schécken" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "%x, %H:%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "%x, %l:%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "%b %d, %H:%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "%b %d, %l:%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "%a, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "%a, %l∶%M %p" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Dëse Kontakt wéilt dech gären an seng Kontaktlecht ophuelen" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "Lueden %s erof…" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "%s offréiert: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "Datei offréiert: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "Datei offréiert" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "Dateitransfert ass feelgeschloen" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "Haut" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "%a, %b %d" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "De Message updaten" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "" "Klick hei fir eng Konversatioun ze starten oder engem Channel bäizetrieden." #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "Du hues keng oppen Chats" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Konto" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Spëtznumm" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Pseudonym" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Kontakt bäisetzen" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Benoriichtege wann een neie Message ukënnt" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Smileyen an Emojien konvertéieren" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Modernen XMPP Chat Client" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino ass e modernen, quell-offene Chat Client fir den Desktop. Hien biet eng " "opgeraumt a robust Jabber/XMPP Erfarung a leet ee Schwéierpunkt op " "Privatsphär." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Hien ënnerstëtz Enn-zu-Enn Verschlësselung mat OMEMO an OpenPGP an enthält " "Privatsphäre-Astellungen zu Liesbestätegungen an Tipp-Benoriichtegungen." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino rifft  Gespréichverläf vum Server of a synchroniséiert Noriichte mat " "anere Geräter." #: main/data/global_search.ui:37 msgid "No active search" msgstr "Keng aktiv Sich" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Tipp fir eng Sich ze starten" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Keng Message fonnt" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Iwwerpréif Schreifweis oder läsch Filteren" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "" #: main/data/shortcuts.ui:12 msgid "General" msgstr "Allgemeng" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "Navigatioun" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "Zu nächster Konversatioun sprangen" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "Zu viregter Konversatioun sprangen" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "Kontoen" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Lokalen Pseudonym" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Keng Kontoe konfiguréiert" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Ee Konto bäisetzen" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Aloggen" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Account kreéieren" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "Konnt keng geséchert Verbindung opbauen" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Connectéieren" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Ëffentleche Server auswielen" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "Oder spezifizéier eng Server Adress" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Aloggen a Platz" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Anere Server auswielen" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Alles ageriicht!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Ofschléissen" #~ msgid "No active conversations" #~ msgstr "Keng Conversatiounen aktiv" #~ msgid "Main window with conversations" #~ msgstr "Haaptfënster mat den Conversatiounen" #~ msgid "%s, %s and %i others" #~ msgstr "%s, %s an %i anerer" #~ msgid "You can now start using %s" #~ msgstr "Du kanns elo %s benotzen" #~ msgid "Open Registration" #~ msgstr "Oppe Registréierung" #~ msgid "Save" #~ msgstr "Späicheren" #~ msgid "%s, %s and %s" #~ msgstr "%s, %s an %s" #~ msgid "%s and %s" #~ msgstr "%s an %s" #~ msgid "is typing…" #~ msgid_plural "are typing…" #~ msgstr[0] "tippt grad…" #~ msgstr[1] "tippe grad…" #~ msgid "has stopped typing" #~ msgstr "huet opgehalen ze tippen" #~ msgid "%i search results" #~ msgstr "%i Sichresultater" #~ msgid "Discover real JIDs" #~ msgstr "Echt JIDs gesinn" #~ msgid "Who may discover real JIDs?" #~ msgstr "Ween däerf echt JIDs gesinn?" #~ msgid "Password required for room entry, if any" #~ msgstr "Passwuert fir de Raum ze betrieden, wann een gebraucht gëtt" #~ msgid "Failed connecting to %s" #~ msgstr "Feeler beim connectéieren op %s" #~ msgid "Join Conference" #~ msgstr "Konferenz bäitrieden" #~ msgid "File" #~ msgstr "Datei" #~ msgid "Communicate happiness." #~ msgstr "Gléck kommunizéieren." #~ msgid "Preferences" #~ msgstr "Astellungen" #~ msgid "Quit" #~ msgstr "Zoumaachen" #~ msgid "JID should be of the form “user@example.com”" #~ msgstr "JID sollt folgend Form hunn „user@example.com“" #~ msgid "Copy Link Address" #~ msgstr "Kopéier Link Adress" #~ msgid "Copy" #~ msgstr "Kopéieren" #~ msgid "Select All" #~ msgstr "Alles auswielen" #~ msgid "Search" #~ msgstr "Sichen" #~ msgid "Send message marker" #~ msgstr "Message Marker verschécken" #~ msgid "Start Chat" #~ msgstr "Chat starten" #~ msgid "Request presence updates" #~ msgstr "Presenz Updaten ufroen" #~ msgid "This is an app-notification. Click the button to dismiss" #~ msgstr "" #~ "Dat heiten ass eng App-Notifikatioun. Klick de Knäppche fir ze ewech ze " #~ "maachen" dino-0.3.0/main/po/lt.po0000644000000000000000000010570114202022370013456 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2021-09-29 07:37+0000\n" "Language-Team: none\n" "Language: lt\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%100<10 || n%100>=20) ? 1 : 2);\n" "X-Generator: Weblate 4.9-dev\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "Failas viršija didžiausią įkėlimo į serverį dydį." #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "Aš" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "Paveikslas išsiųstas" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "Failas išsiųstas" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "Paveikslas gautas" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "Failas gautas" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "Išeinantis skambutis" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "Įeinantis skambutis" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "%b %d" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "Vakar" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "%H∶%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "%l∶%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "prieš %i minutę" msgstr[1] "prieš %i minutes" msgstr[2] "prieš %i minučių" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "Ką tik" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "Savininkas" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "Administratorius" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "Narys" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "Naudotojas" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "Pakviesti" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "Pradėti privatų pokalbį" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "Išvaryti" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "Suteikti leidimą rašyti" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "Panaikinti leidimą rašyti" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "Pakviesti į konferenciją" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "Pasirinkti failą" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Pasirinkti" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Atsisakyti" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "%s iš %s" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "Ši konferencija jums neleidžia siųsti žinučių." #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "Užklausti leidimo" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "Pradėti skambutį" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "Garso skambutis" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "Vaizdo skambutis" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "Ieškoti žinučių" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Nariai" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "Skambinama…" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "Skamba…" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "Jungiamasi…" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "%s baigė pokalbį" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "%s atmetė skambutį" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "" #: main/src/ui/call_window/call_window_controller.vala:128 msgid "Invite to Call" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "Pradėti pokalbį" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Pradėti" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Prisijungti prie kanalo" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Kitas" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "Prisijungti" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Atgal" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "Prisijungiama…" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "Norint užeiti į kambarį, reikalingas slaptažodis" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "Uždrausta prisijungti ar sukurti konferenciją" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "Kambario nėra" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "Neleidžiama kurti kambarių" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "Kambarys tik nariams" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "Pasirinkti kitą slapyvardį" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "Kambaryje per daug žmonių" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "Nepavyko prisijungti prie %s" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "Neteisingas adresas" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Pridėti" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Trumpiniai" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "Apie Dino" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "%i paieškos rezultatas" msgstr[1] "%i paieškos rezultatai" msgstr[2] "%i paieškos rezultatų" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "Ties %s" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "Su %s" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "Įeinantis vaizdo skambutis" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "Atmesti" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "Priimti" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "Prenumeratos užklausa" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "Atmesti" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "Pakvietimas į %s" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "%s pakvietė jus į %s" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "Leidimo užklausa" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "%s prašo leidimo rašyti ties %s" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Nustatymai" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Vietiniai nustatymai" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Siųsti pranešimus apie žinutės rašymą" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Siųsti pranešimus apie žinučių skaitymą" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Pranešimai" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "Įjungta" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "Išjungta" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "Tik paminėjus" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "Pagal numatymą: %s" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "Prašyti" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "Leidimai" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "Prašyti leidimo siųsti žinutes" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Išsamiau apie konferenciją" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "Išsamiau apie adresatą" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Blokuoti" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "" "Abiejomis kryptimis bus blokuojamas bendravimas ir būsenos atnaujinimai" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Kambario pavadinimas" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Kambario aprašas" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Ilgalaikis" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "Kambarys išliks netgi po to, kai išeis paskutinis lankytojas" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Viešai randamas" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Lankytojai gali keisti temą" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "Leidimas žiūrėti JID" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "Kam leidžiama matyti lankytojų JID?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "Slaptažodis" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "Slaptažodis, apribojantis prieigą prie kambario" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Moderuojamas" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Tik lankytojai su balso teise gali siųsti žinutes" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Tik nariai" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Tik nariai gali užeiti į kambarį" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Žinučių istorija" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Didžiausias kambario išduodamas praeities žurnalo skaičius" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Kambario konfigūracija" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "Sveiki atvykę į Dino!" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "Norėdami pradėti, prisijunkite arba susikurkite paskyrą." #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "Nusistatyti paskyrą" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "Aktyvių paskyrų nėra" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "Tvarkyti paskyras" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Neteisingas slaptažodis" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Negaliojantis TLS liudijimas" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Šalinti paskyrą %s?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Šalinti" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Pasirinkti avatarą" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Paveikslai" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Visi failai" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Prisijungta" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Atsijungta" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Klaida" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Pridėti paskyrą" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "Serveriui nepavyko įrodyti, kad jis yra %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "Jūsų operacinė sistema nepasitiki jo saugumo liudijimu." #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "Jo saugumo liudijimas yra išduotas kitai sričiai." #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "Jo saugumo liudijimas pradės galioti tik ateityje." #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "Jo saugumo liudijimas nebegalioja." #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "Prisijungti prie %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "Dabar galite naudoti paskyrą %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "Neteisingas naudotojo vardas ar slaptažodis" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "Kažkas nutiko" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "Nėra atsakymo iš serverio" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "Registruotis %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "Serveris reikalauja registruotis per internetinę svetainę" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "Atverti internetinę svetainę" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "Registruotis" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "Išsamesnei informacijai apie registraciją, žiūrėkite %s" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "Žinutė per ilga" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "redaguota" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "laukiama…" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "pristatymas nepavyko" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, c-format msgid "%s, %s and %i others are typing…" msgstr "%s, %s ir dar %i rašo…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "%s, %s ir %s rašo…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "%s ir %s rašo…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "%s rašo…" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "Pokalbis prasidėjo" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "Pradėta prieš %s" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "Šį skambutį atlikote kitame įrenginyje" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "Pokalbis baigėsi" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "Baigėsi %s" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "Truko %s" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "Praleistas skambutis" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "Praleidote šį skambutį" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "%s praleido šį skambutį" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "Skambutis atmestas" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "Atmetėte šį skambutį" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "%s atmetė šį skambutį" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "Skambutis nepavyko" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "%i valandą" msgstr[1] "%i valandas" msgstr[2] "%i valandų" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "%i minutę" msgstr[1] "%i minutes" msgstr[2] "%i minučių" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "kelias sekundes" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Nešifruota" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "Nepavyko išsiųsti žinutės" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "%x, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "%x, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "%b %d, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "%b %d, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "%a, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "%a, %l∶%M %p" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Šis adresatas norėtų jus pridėti į savo adresatų sąrašą" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "Atsisiunčiama %s…" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "%s pasiūlė: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "Pasiūlytas failas: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "Pasiūlytas failas" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "Failo persiuntimas nepavyko" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "Šiandien" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "%b %d, %a" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "Siųsti failą" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "Atnaujinti žinutę" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "Spustelėkite čia norėdami pradėti pokalbį ar prisijungti prie kanalo." #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "Neturite jokių atvertų pokalbių" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Paskyra" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Slapyvardis" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Pseudonimas" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Pridėti adresatą" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Pranešti, kai gaunama nauja žinutė" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Keisti šypsenėles į jaustukus" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "Tikrinti rašybą" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Šiuolaikinė XMPP pokalbių kliento programa" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino yra šiuolaikinė atvirojo kodo kliento programa skirta darbalaukiui. Jos " "pagrindinis dėmesys yra pateikti tvarkingą ir patikimą Jabber/XMPP patyrimą " "nepamirštant apie jūsų privatumą." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Ji palaiko ištisinį šifravimą naudojant OMEMO ir OpenPGP bei leidžia " "konfigūruoti su privatumu susijusias ypatybes, tokias kaip pranešimus apie " "žinučių skaitymą ir rašymą." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino gauna istoriją iš serverio ir sinchronizuoja žinutes su kitais " "įrenginiais." #: main/data/global_search.ui:37 msgid "No active search" msgstr "Nėra aktyvios paieškos" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Rašykite norėdami atlikti paiešką" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Nėra atitinkančių žinučių" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Patikrinkite rašybą arba pabandykite pašalinti filtrus" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "Siųsti" #: main/data/shortcuts.ui:12 msgid "General" msgstr "Bendri" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "Pokalbis" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "Naršymas" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "Peršokti prie kito pokalbio" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "Peršokti prie ankstesnio pokalbio" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "Paskyros" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Vietinis pseudonimas" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Nėra sukonfigūruotų paskyrų" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Pridėti paskyrą" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Prisijungti" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Sukurti paskyrą" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "Nepavyko užmegzti saugaus ryšio" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Prisijungti" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Pasirinkite viešąjį serverį" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "Arba nurodykite serverio adresą" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Vietoj to, prisijungti" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Pasirinkti kitą serverį" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Viskas nustatyta!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Užbaigti" #~ msgid "Video call incoming" #~ msgstr "Įeinantis vaizdo skambutis" #~ msgid "Call incoming" #~ msgstr "Įeinantis skambutis" #~ msgid "Establishing call" #~ msgstr "Rengiamasi pokalbiui" #~ msgid "Video call establishing" #~ msgstr "Rengiamasi vaizdo skambučiui" #~ msgid "Call establishing" #~ msgstr "Rengiamasi pokalbiui" #~ msgid "Call in progress…" #~ msgstr "Vyksta pokalbis…" #, c-format #~ msgid "Lasted for %s" #~ msgstr "Truko %s" #~ msgid "seconds" #~ msgstr "sek." #~ msgid "No active conversations" #~ msgstr "Aktyvių pokalbių nėra" #~ msgid "Main window with conversations" #~ msgstr "Pagrindinis langas su pokalbiais" #~ msgid "%s, %s and %i others" #~ msgstr "%s, %s ir dar %i" #~ msgid "You can now start using %s" #~ msgstr "Dabar, galite pradėti naudoti %s" #~ msgid "Open Registration" #~ msgstr "Atverti registraciją" #~ msgid "Save" #~ msgstr "Įrašyti" #~ msgid "%s, %s and %s" #~ msgstr "%s, %s ir %s" #~ msgid "%s and %s" #~ msgstr "%s ir %s" #~ msgid "is typing…" #~ msgid_plural "are typing…" #~ msgstr[0] "rašo…" #~ msgstr[1] "rašo…" #~ msgstr[2] "rašo…" #~ msgid "has stopped typing" #~ msgstr "nustojo rašyti" #~ msgid "%i search results" #~ msgstr "Paieškos rezultatų: %i" #~ msgid "Discover real JIDs" #~ msgstr "Atrasti tikruosius JID" #~ msgid "Who may discover real JIDs?" #~ msgstr "Kas gali atrasti tikruosius JID?" #~ msgid "Password required for room entry, if any" #~ msgstr "" #~ "Jei nustatytas, norint užeiti į kambarį bus reikalaujama slaptažodžio" dino-0.3.0/main/po/nb.po0000644000000000000000000010557614202022370013450 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2022-02-11 18:58+0000\n" "Language-Team: Norwegian Bokmål \n" "Language: nb\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: Weblate 4.11-dev\n" #: main/src/ui/file_send_overlay.vala:85 #, fuzzy msgid "The file exceeds the server's maximum upload size." msgstr "Filen er større enn opplastingsstørrelsen som tillates av tjeneren." #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "Meg" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "Bilde sendt" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "Fil sendt" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "Bilde mottatt" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "Fil mottatt" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "Utgående anrop" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "Innkommende anrop" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "%b %d" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "I går" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "%H∶%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "%l∶%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "%i minutt siden" msgstr[1] "%i minutter siden" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "Akkurat nå" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "Eier" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "Admin" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "Medlem" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "Bruker" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "Inviter" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "Start privat samtale" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "Kast ut" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "Innvilg skrivetilgang" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "Tilbakekall skrivetilgang" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "Inviter til konferanse" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "Velg fil" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Velg" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Avbryt" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "%s fra %s" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "Du tillates ikke å sende meldinger i denne konferansen." #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "Forespør tilgang" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "Start samtale" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "Lydsamtale" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "Videoanrop" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "Søk i meldinger" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Medlemmer" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "Ringer …" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "Ringer …" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "Kobler til…" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "%s avsluttet samtalen" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "%s avslo samtalen" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "Kamera" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "Mikrofoner" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "Høyttalere" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "" #: main/src/ui/call_window/call_window_controller.vala:128 #, fuzzy msgid "Invite to Call" msgstr "Inviter til samtalen" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "Start samtale" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Start" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Ta del i kanal" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Neste" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "Ta del" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Tilbake" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "Tar del…" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "Passord kreves for å ta del i rommet" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "Bannlyst fra å ta del i eller å opprette konferanser" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "Rommet finnes ikke" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "Ikke tillatt å opprette rom" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "Rommet er kun for medlemmer" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "Velg et annet kallenavn" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "Rommet har for mange deltagere" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "Kunne ikke koble til %s" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "Ugyldig adresse" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Legg til" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Tastatursnarveier" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "Om Dino" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "%i søkeresultat" msgstr[1] "%i søkeresultater" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "I %s" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "Med %s" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "Innkommende videosamtale" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "Innkommende videogruppeanrop" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "Innkommende gruppeanrop" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 #, fuzzy msgid "Reject" msgstr "Avslå" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "Godta" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "Abonnementsforespørsel" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "Nekt" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "Invitasjon til %s" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "%s inviterte deg til %s" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "Tilgangsforespørsel" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, fuzzy, c-format msgid "%s requests the permission to write in %s" msgstr "%s forespør tilgang til å skrive til %s" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Innstillinger" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Lokale innstillinger" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Send skrivevarsling" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Send meldingskvitteringer" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Merknader" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "På" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "Av" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "Bare når nevnt" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "Forvalg: %s" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "Forespør" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "Tilganger" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "Forespør tilgang til meldingsforsendelse" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Konferansedetaljer" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "Kontaktdetaljer" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Blokker" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "Kommunikasjon og statusoppdateringer i begge retninger er blokkert" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Romnavn" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Rombeskrivelse" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Vedvarende" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "Rommet vil vedvare etter at siste deltager forlater" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Offentlig søkbart" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Deltagere kan endre emne" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "Tilgang til visning av JID-er" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "Hvem skal tillates å vise brukernes JID-er?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "Passord" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "Et passord for å begrense tilgang til rommet" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Moderert" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Bare deltagere med stemme kan sende meldinger" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Kun for medlemmer" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Kun medlemmer har adgang til rommet" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Meldingshistorikk" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Maksimalt mengde historikk som skal utstedes av rommet" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Rom-oppsett" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "Velkommen til Dino." #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "Logg inn eller opprett en konto for å starte." #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "Sett opp konto" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "Ingen kontoer aktive" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "Behandle kontoer" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Feil passord" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Ugyldig TLS-sertifikat" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Fjern konto %s?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Fjern" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Velg avatar" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Bilder" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Alle filer" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Tilkoblet" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Frakoblet" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Feil" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Legg til konto" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "Tjeneren kunne ikke bevise at det er %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "Ditt operativsystem mangler tiltro til dets sikkerhetssertifikat." #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "Dets sikkerhetssertifikat er utstedt til et annet domene." #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "Dets sikkerhetssertifikat vil kun bli gyldig i fremtiden." #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "Dets sikkerhetssertifikat er utløpt." #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "Logg inn på %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "Du kan nå bruke kontoen %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "Feil brukernavn eller passord" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "Noe gikk galt" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "Inget svar fra tjener" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "Registrer på %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "Tjeneren krever at man logger inn via en nettside" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "Åpne nettside" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "Registrer" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "Se %s for å lære hvordan du registrerer deg" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "Meldingen er for lang" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "redigert" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "venter …" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "levering mislyktes" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, c-format msgid "%s, %s and %i others are typing…" msgstr "%s, %s og %i andre skriver…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "%s, %s og %s skriver…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "%s og %s skriver…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "%s skriver…" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "Samtale startet" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "Startet for %s siden" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "Du håndterte denne samtalen på en annen enhet" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "Samtale avsluttet" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "Avsluttet %s" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, fuzzy, c-format msgid "Lasted %s" msgstr "Varte %s" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "Tapt anrop" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "Du besvarte ikke dette anropet" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "%s tok ikke dette anropet" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "Anrop avvist" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "Du avslo dette anropet" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "%s avslo dette anropet" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "Anrop mislyktes" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "én time" msgstr[1] "%i timer" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "ett minutt" msgstr[1] "%i minutter" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "et par sekunder" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Ukryptert" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "Kunneikke sende melding" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "%x, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "%x, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "%b %d, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "%b %d, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "%a, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "%a, %l∶%M %p" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "Lagre som …" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Denne kontakten ønsker å legge deg til på kontaktlisten sin" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "Åpne" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "Laster ned %s…" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "%s tilbød: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "Fil tilbudt: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "Fil tilbudt" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "Filoverføring mislyktes" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "I dag" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "%a, %d %b" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "Send fil" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "Oppdater melding" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "Klikk her for å starte en samtale, eller ta del i en kanal." #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "Du har ingen åpne sludringer" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Konto" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Kallenavn" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Alias" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Legg til kontakt" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Varsle ved mottak av ny melding" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Konverter smilefjes til emoji-er" #: main/data/settings_dialog.ui:70 #, fuzzy msgid "Check spelling" msgstr "Stavekontroll" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Moderne XMPP-sludreklient" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino er en moderne friporg-sludringsklient for skrivebordet. Det fokuserer " "på rask og pålitelig XMPP-opplevelse, samtidig som det hegner om " "personvernet." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Det støtter ende-til-ende -kryptering med OMEMO og OpenPGP, og tillater " "oppsett av personvernsrelaterte funksjoner som meldingskvitteringer og " "skrivevarsling." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino henter historikk fra tjeneren og synkroniserer meldinger med andre " "enheter." #: main/data/global_search.ui:37 msgid "No active search" msgstr "Inget aktivt søk" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Skriv for å starte et søk" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Ingen samsvarende meldinger" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Sjekk stavingen eller prøv å fjerne filter" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "Send" #: main/data/shortcuts.ui:12 msgid "General" msgstr "Generelt" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "Samtale" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "Navigasjon" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "Hopp til neste samtale" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "Hopp til forrige samtale" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "Kontoer" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Lokalt alias" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Ingen kontoer satt opp" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Legg til en konto" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Logg inn" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Opprett konto" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "Kunne ikke etablere sikker forbindelse" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Koble til" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Velg en offentlig tjener" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "Eller angi en tjeneradresse" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Logg inn istedenfor" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Velg en annen tjener" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Ferdig oppsatt." #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Fullfør" #~ msgid "No active conversations" #~ msgstr "Ingen kontoer aktive" #~ msgid "Main window with conversations" #~ msgstr "Hovedvindu med samtaler" #~ msgid "%s, %s and %i others" #~ msgstr "%s, %s og %i andre" #~ msgid "You can now start using %s" #~ msgstr "Du kan nå begynne å bruke %s" #~ msgid "Open Registration" #~ msgstr "Åpne registrering" #~ msgid "Save" #~ msgstr "Lagre" #~ msgid "%s, %s and %s" #~ msgstr "%s, %s og %s" #~ msgid "%s and %s" #~ msgstr "%s og %s" #~ msgid "is typing…" #~ msgid_plural "are typing…" #~ msgstr[0] "skriver…" #~ msgstr[1] "skriver…" #~ msgid "has stopped typing" #~ msgstr "har tatt pause i skrivinga" #~ msgid "%i search results" #~ msgstr "%i søkeresultater" #~ msgid "Discover real JIDs" #~ msgstr "Oppdag ekte JID-er" #~ msgid "Who may discover real JIDs?" #~ msgstr "Hvem har tilgang til oppdagelse av ekte JID-er?" #~ msgid "Password required for room entry, if any" #~ msgstr "Passord som kreves for å ta del i rommet. La stå tomt for inget" #~ msgid "Failed connecting to %s" #~ msgstr "Klarte ikke å koble til %s" #~ msgid "Join Conference" #~ msgstr "Ta del i konferanse" #~ msgid "File" #~ msgstr "Fil" #~ msgid "Communicate happiness." #~ msgstr "Kommuniser glede." #~ msgid "Preferences" #~ msgstr "Innstillinger" #~ msgid "Quit" #~ msgstr "Avslutt" #~ msgid "JID should be of the form “user@example.com”" #~ msgstr "JID skal være av typen \"bruker@eksmepel.no\"" #~ msgid "Copy Link Address" #~ msgstr "Kopier lenkeadresse" #~ msgid "Copy" #~ msgstr "Kopier" #~ msgid "Select All" #~ msgstr "Velg alle" #~ msgid "Search" #~ msgstr "Søk" #~ msgid "Send message marker" #~ msgstr "Send meldingsmarkør" #~ msgid "Start Chat" #~ msgstr "Start sludring" #~ msgid "Request presence updates" #~ msgstr "Forespør tilstedeværelsesoppdateringer" #~ msgid "This is an app-notification. Click the button to dismiss" #~ msgstr "Dette er en merknad i programmet. Klikk på knappen for å avvise" #~ msgid "Join on startup" #~ msgstr "Ta del ved oppstart" #~ msgid "Add Chat" #~ msgstr "Legg til sludring" dino-0.3.0/main/po/nl.po0000644000000000000000000010774314202022370013460 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2022-02-11 18:58+0000\n" "Language-Team: Dutch \n" "Language: nl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 4.11-dev\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "Het bestand overschrijdt de maximaal toegestane grootte." #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "Ik" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "Afbeelding verstuurd" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "Bestand verstuurd" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "Afbeelding ontvangen" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "Bestand ontvangen" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "Uitgaande oproep" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "Inkomende oproep" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "%d %b" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "Gisteren" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "%H∶%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "%l∶%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "%i minuut geleden" msgstr[1] "%i minuten geleden" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "Zojuist" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "Eigenaar" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "Beheerder" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "Lid" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "Gebruiker" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "Uitnodigen" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "Privégesprek starten" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "Wegsturen" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "Schrijfmachtiging verlenen" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "Schrijfmachtiging weigeren" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "Uitnodigen voor groepsgesprek" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "Kies een bestand" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Kiezen" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Annuleren" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "%s op %s" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "In deze conferentie mag je geen berichten versturen." #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "Toestemming vragen" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "Bellen" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "Audiogesprek" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "Videogesprek" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "Zoeken naar berichten" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Deelnemers" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "Bezig met bellen…" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "Bezig met overgaan…" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "Bezig met verbinden…" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "%s heeft de oproep beëindigd" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "%s hebt/heeft de oproep geweigerd" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "Camera's" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "Geen camera aangetroffen." #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "Microfoons" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "Geen microfoon aangetroffen." #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "Luidsprekers" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "Geen luidspreker aangetroffen." #: main/src/ui/call_window/call_window_controller.vala:128 msgid "Invite to Call" msgstr "Uitnodigen voor gesprek" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "Gesprek starten" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Starten" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Deelnemen aan kanaal" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Volgende" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "Deelnemen" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Terug" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "Bezig met deelnemen…" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "Wachtwoord voor groepsgesprek vereist" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "Deelnemen aan of aanmaken van groepsgesprekken verboden" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "Groepsgesprek bestaat niet" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "Aanmaken van groepsgesprek niet toegestaan" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "Groepsgesprek is alleen voor leden" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "Kies een andere bijnaam" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "Groepsgesprek heeft te veel deelnemers" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "Kan geen verbinding maken met %s" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "Ongeldig adres" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Toevoegen" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Sneltoetsen" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "Over Dino" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "%i zoekresultaat" msgstr[1] "%i zoekresultaten" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "Op %s" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "Met %s" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "Inkomende video-oproep" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "Inkomend videogroepsgesprek" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "Inkomend groepsgesprek" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "Afwijzen" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "Toestaan" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "Abonneerverzoek" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "Weigeren" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "Uitnodiging voor %s" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "%s heeft je uitgenodigd voor %s" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "Machtigingsverzoek" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "%s verzoekt toestemming om te mogen schrijven in %s" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Instellingen" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Lokale instellingen" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Aan-het-typenmeldingen versturen" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Leesbevestigingen versturen" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Meldingen" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "Aan" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "Uit" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "Alleen bij vermeldingen" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "Standaard: %s" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "Verzoek sturen" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "Machtigingen" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "Vraag toestemming om berichten te versturen" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Groepsgesprekinformatie" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "Contactpersooninformatie" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Blokkeren" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "Communicatie en statusupdates worden in beide richtingen geblokkeerd" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Naam van groepsgesprek" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Beschrijving van groepsgesprek" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Blijvend" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "Het groepsgesprek blijft bestaan als de laatste deelnemer het verlaat" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Openbaar" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Deelnemers mogen het onderwerp aanpassen" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "Toestemming om JID's te bekijken" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "Wie mag de JID's bekijken?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "Wachtwoord" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "Een wachtwoord om toegang tot het gesprek te beperken" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Gemodereerd" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Enkel deelnemers met stem mogen berichten sturen" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Enkel leden" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Enkel leden mogen deelnemen aan het groepsgesprek" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Berichtgeschiedenis" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Het maximumaantal berichten dat het groepsgesprek toont" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Gespreksinstellingen" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "Welkom bij Dino!" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "Log in of maak een account om aan de slag te gaan." #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "Account instellen" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "Geen actieve accounts" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "Accounts beheren" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Onjuist wachtwoord" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Ongeldig TLS-certificaat" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Wil je '%s' verwijderen?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Verwijderen" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Kies een profielfoto" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Afbeeldingen" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Alle bestanden" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Verbonden" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Verbinding verbroken" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Fout" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Account toevoegen" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "De server kan niet aantonen dat het %s is." #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "Je besturingssysteem vertrouwt het beveiligingscertificaat niet." #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "Het beveiligingscertificaat is afgegeven voor een ander domein." #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "Het beveiligingscertificaat is pas geldig vanaf een latere datum." #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "Het beveiligingscertificaat is verlopen." #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "Inloggen op %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "Je kunt '%s' nu gebruiken." #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "Onjuiste gebruikersnaam of wachtwoord" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "Er is iets misgegaan" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "Geen antwoord van server" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "Registreren op %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "De server vereist registratie via een website" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "Website openen" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "Registreren" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "Ga naar %s voor meer informatie over het registreren" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "Het bericht is te lang" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "bewerkt" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "in wachtrij…" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "verzenden mislukt" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, c-format msgid "%s, %s and %i others are typing…" msgstr "%s, %s en %i anderen zijn aan het typen…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "%s, %s en %s zijn aan het typen…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "%s zijn %s aan het typen…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "%s is aan het typen…" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "Oproep gestart" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "Begonnen: %s geleden" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "Deze oproep is afgehandeld op een ander apparaat" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "Het gesprek is beëindigd" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "Beëindigd: %s" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "Duur: %s" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "Oproep gemist" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "Je hebt deze oproep gemist" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "%s heeft deze oproep gemist" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "Oproep geweigerd" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "Je hebt deze oproep geweigerd" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "%s heeft deze oproep geweigerd" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "Oproep mislukt" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "%i uur" msgstr[1] "%i uur" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "%i minuut" msgstr[1] "%i minuten" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "enkele seconden" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Onversleuteld" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "Het bericht kan niet worden verstuurd" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "%x, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "%x, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "%d %b, %H:%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "%d %b, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "%a, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "%a, %l∶%M %p" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "Opslaan als…" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Deze contactpersoon wil je toevoegen aan zijn/haar lijst" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "Openen" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "Bezig met downloaden van %s…" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "%s biedt aan: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "Bestand aangeboden: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "Bestand aangeboden" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "Bestandsoverdracht mislukt" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "Vandaag" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "%a, %b %d" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "Verstuur een bestand" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "Bericht bijwerken" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "Klik hier om een gesprek te starten of deel te nemen aan een kanaal." #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "Je hebt geen geopende gesprekken" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Account" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Bijnaam" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Alias" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Contactpersoon toevoegen" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Melding tonen bij nieuw bericht" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Smileys omzetten naar emoji’s" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "Spelling controleren" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Een moderne XMPP-chatclient" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino is een moderne, vrije chattoepassing voor je computer. Dino biedt een " "eenvoudige en betrouwbare Jabber-/XMPP-ervaring, met privacy in het " "achterhoofd." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Dino ondersteunt end-to-endversleuteling met OMEMO en OpenPGP en staat je " "toe privacy-gerelateerde functies, zoals leesbevestigingen en aan-het-" "typenmeldingen, in te stellen." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino haalt de geschiedenis op van de server en synchroniseert berichten met " "andere apparaten." #: main/data/global_search.ui:37 msgid "No active search" msgstr "Geen actieve zoekopdracht" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Typ om te beginnen met zoeken" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Geen overeenkomende berichten" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Controleer de spelling of verwijder filters" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "Versturen" #: main/data/shortcuts.ui:12 msgid "General" msgstr "Algemeen" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "Gesprek" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "Navigatie" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "Ga naar volgend gesprek" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "Ga naar vorig gesprek" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "Accounts" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Lokale alias" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Geen accounts ingesteld" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Account toevoegen" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Inloggen" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Account aanmaken" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "Kan geen beveiligde verbinding opzetten" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Verbinden" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Kies een openbare server" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "Of voer een serveradres in" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Inloggen op account" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Andere server kiezen" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Klaar!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Voltooien" #~ msgid "Video call incoming" #~ msgstr "Inkomend videogesprek" #~ msgid "Call incoming" #~ msgstr "Inkomende oproep" #~ msgid "Establishing call" #~ msgstr "Bezig met verbinden…" #~ msgid "Video call establishing" #~ msgstr "Bezig met verbinden…" #~ msgid "Call establishing" #~ msgstr "Bezig met verbinden…" #~ msgid "Call in progress…" #~ msgstr "Bezig met bellen…" #, c-format #~ msgid "Lasted for %s" #~ msgstr "Duur: %s" #~ msgid "seconds" #~ msgstr "seconden" #~ msgid "No active conversations" #~ msgstr "Geen gesprekken actief" #~ msgid "Main window with conversations" #~ msgstr "Hoofdvenster met gesprekken" #~ msgid "%s, %s and %i others" #~ msgstr "%s, %s en %i anderen" #~ msgid "You can now start using %s" #~ msgstr "U kunt %s nu beginnen gebruiken" #~ msgid "Open Registration" #~ msgstr "Open registratie" #~ msgid "Save" #~ msgstr "Opslaan" #~ msgid "%s, %s and %s" #~ msgstr "%s, %s en %s" #~ msgid "%s and %s" #~ msgstr "%s en %s" #~ msgid "is typing…" #~ msgid_plural "are typing…" #~ msgstr[0] "is aan het typen…" #~ msgstr[1] "zijn aan het typen…" #~ msgid "has stopped typing" #~ msgstr "is gestopt met typen" #~ msgid "%i search results" #~ msgstr "%i zoekresultaten" #~ msgid "Discover real JIDs" #~ msgstr "Zichtbaarheid van JID’s" #~ msgid "Who may discover real JIDs?" #~ msgstr "Wie mag de JID’s van de deelnemers zien?" #~ msgid "Password required for room entry, if any" #~ msgstr "Wachtwoord voor groepsgesprek, indien nodig" #~ msgid "Failed connecting to %s" #~ msgstr "Verbinden met %s mislukt" #~ msgid "Join Conference" #~ msgstr "Deelnemen aan groepsgesprek" #~ msgid "File" #~ msgstr "Bestand" #~ msgid "Communicate happiness." #~ msgstr "Communiceer geluk." #~ msgid "Preferences" #~ msgstr "Voorkeuren" #~ msgid "Quit" #~ msgstr "Afsluiten" #~ msgid "JID should be of the form “user@example.com”" #~ msgstr "JID moet de vorm ‘gebruiker@voorbeeld.nl’ volgen" #~ msgid "Copy Link Address" #~ msgstr "Verwijzingsadres kopiëren" #~ msgid "Copy" #~ msgstr "Kopiëren" #~ msgid "Select All" #~ msgstr "Alles selecteren" #~ msgid "Search" #~ msgstr "Zoeken" #~ msgid "Send message marker" #~ msgstr "Berichtmarkers sturen" #~ msgid "Start Chat" #~ msgstr "Gesprek beginnen" #~ msgid "Request presence updates" #~ msgstr "Vraag om aanwezigheidsupdates" #~ msgid "This is an app-notification. Click the button to dismiss" #~ msgstr "Dit is een melding. Klik om te sluiten" #~ msgid "Join on startup" #~ msgstr "Deelnemen bij opstarten" #~ msgid "Add Chat" #~ msgstr "Gesprek beginnen" dino-0.3.0/main/po/nl_BE.po0000644000000000000000000010173714202022370014023 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2019-01-12 20:06+0000\n" "Language-Team: Flemish \n" "Language: nl_BE\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: Weblate 3.4-dev\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "Ik" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "Afbeelding verzonden" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "Bestand verzonden" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "Afbeelding ontvangen" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "Bestand ontvangen" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "%d %b" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "Gisteren" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "%Hu%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "%lu%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "%i min geleden" msgstr[1] "%i min geleden" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "Zojuist" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "Eigenaar" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "Beheerder" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "Lid" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "Gebruiker" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "Uitnodigen" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "Privégesprek beginnen" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "Schoppen" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "Uitnodigen in groepsgesprek" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Selecteren" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Annuleren" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "Verbinding maken…" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "" #: main/src/ui/call_window/call_window_controller.vala:128 msgid "Invite to Call" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "Gesprek beginnen" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Beginnen" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Deelnemen aan kanaal" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Volgende" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "Deelnemen" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Terug" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "Deelnemen…" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "Paswoord voor groepsgesprek vereist" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "Deelnemen aan of aanmaken van groepsgesprekken verboden" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "Groepsgesprek bestaat niet" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "Aanmaken van groepsgesprek niet toegestaan" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "Groepsgesprek is alleen-leden" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "Kiest nen anderen bijnaam" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "Groepsgesprek heeft te veel deelnemers" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "Kon geen verbinding maken met %s" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Toevoegen" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "" msgstr[1] "" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "In %s" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "Met %s" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "Toestaan" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "Abonneringsverzoek" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "Afwijzen" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Instellingen" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Lokale instellingen" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Aan-het-typen-meldingen sturen" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Leesbevestigingen sturen" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Meldingen" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "Aan" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "Uit" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "Enkel wanneer vermeld" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "Standaard: %s" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Groepsgespreksgegevens" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "Contactgegevens" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Blokkeren" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "Communicatie en statusupdates worden in beide richtingen geblokkeerd" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Naam van groepsgesprek" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Beschrijving van groepsgesprek" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Blijvend" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "" "Het groepsgesprek blijft bestaan wanneer dat de laatsten deelnemer het " "verlaat" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Publiek vindbaar" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Deelnemers kunnen onderwerp aanpassen" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "Paswoord" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Gemodereerd" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Enkel deelnemers met stem mogen berichten sturen" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Enkel leden" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Enkel leden mogen deelnemen aan het groepsgesprek" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Berichtgeschiedenis" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "" "Maximaal aantal berichten uit geschiedenis dat het groepsgesprek weergeeft" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Gespreksconfiguratie" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "Welkom bij Dino!" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "" #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "Account instellen" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "Geen accounts actief" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "Accounts beheren" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Verkeerd paswoord" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Ongeldig TLS-certificaat" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Account %s verwijderen?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Verwijderen" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Kiest nen avatar" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Afbeeldingen" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Alle bestanden" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Verbonden" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Verbinding verbroken" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Fout" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Account toevoegen" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "Inloggen bij %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "Verkeerde gebruikersnaam of paswoord" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "Geen antwoord van server" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "Registreren bij %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "De server vereist registratie via ne website" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "Registreren" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "Bekijkt %s voor meer informatie over hoe te registreren" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "Bericht te lang" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, c-format msgid "%s, %s and %i others are typing…" msgstr "" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "" msgstr[1] "" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "" msgstr[1] "" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Onversleuteld" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "%x, %Hu%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "%x, %lu%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "%d %b, %Hu%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "%d %b, %lu%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "%a, %Hu%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "%a, %lu%M %p" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Dit contact wilt u toevoegen aan zijn/haar contacten" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "Vandaag" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "%a, %b %d" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "" #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Account" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Schermnaam" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Alias" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Contact toevoegen" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Melding tonen bij nieuw bericht" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Smileys omzetten naar emoji’s" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Modernen XMPP-chatcliënt" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino is een moderne, vrije chattoepassing voor uw bureaublad. Ze biedt een " "eenvoudige en betrouwbare Jabber/XMPP-ervaring, met uw privacy in het " "achterhoofd." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Ze ondersteunt eind-tot-eind-versleuteling met OMEMO en OpenPGP, en laat u " "toe privacygerelateerde functies, gelijk leesbevestigingen en typmeldingen, " "in te stellen." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino haalt de geschiedenis op van de server en synchroniseert berichten met " "andere apparaten." #: main/data/global_search.ui:37 msgid "No active search" msgstr "Genen actieve zoekopdracht" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Typt voor te beginnen zoeken" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Geen overeenkomende berichten" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Controleert de spelling of probeert filters te verwijderen" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "" #: main/data/shortcuts.ui:12 msgid "General" msgstr "" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "Accounts" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Lokalen alias" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Geen accounts ingesteld" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Account toevoegen" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Inloggen" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Account aanmaken" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "Kon geen beveiligde verbinding opstellen" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Verbinden" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Kiest nen openbare server" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "Of voert een serveradres in" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Inloggen" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Kiest nen andere server" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Klaar!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Voltooien" #~ msgid "No active conversations" #~ msgstr "Geen gesprekken actief" #~ msgid "Main window with conversations" #~ msgstr "Hoofdvenster met gesprekken" #~ msgid "%s, %s and %i others" #~ msgstr "%s, %s en %i anderen" #~ msgid "You can now start using %s" #~ msgstr "Ge kunt %s nu beginnen gebruiken" #~ msgid "Open Registration" #~ msgstr "Open registratie" #~ msgid "Save" #~ msgstr "Opslaan" #~ msgid "%s, %s and %s" #~ msgstr "%s, %s en %s" #~ msgid "%s and %s" #~ msgstr "%s en %s" #~ msgid "is typing…" #~ msgid_plural "are typing…" #~ msgstr[0] "is aan het typen…" #~ msgstr[1] "zijn aan het typen…" #~ msgid "has stopped typing" #~ msgstr "is gestopt met typen" #~ msgid "%i search results" #~ msgstr "%i zoekresultaten" #~ msgid "Discover real JIDs" #~ msgstr "Zichtbaarheid van JID’s" #~ msgid "Who may discover real JIDs?" #~ msgstr "Wie mag de JID’s van de deelnemers zien?" #~ msgid "Password required for room entry, if any" #~ msgstr "Paswoord voor groepsgesprek, indien nodig" #~ msgid "Failed connecting to %s" #~ msgstr "Verbinden met %s mislukt" #~ msgid "Join Conference" #~ msgstr "Deelnemen aan groepsgesprek" #~ msgid "File" #~ msgstr "Bestand" #~ msgid "Communicate happiness." #~ msgstr "Communiceert geluk." #~ msgid "Preferences" #~ msgstr "Voorkeuren" #~ msgid "Quit" #~ msgstr "Afsluiten" #~ msgid "JID should be of the form “user@example.com”" #~ msgstr "JID moet de vorm ‘gebruiker@voorbeeld.be’ volgen" #~ msgid "Copy Link Address" #~ msgstr "Verwijzingsadres kopiëren" #~ msgid "Copy" #~ msgstr "Kopiëren" #~ msgid "Select All" #~ msgstr "Alles selecteren" #~ msgid "Search" #~ msgstr "Zoeken" #~ msgid "Send message marker" #~ msgstr "Berichtmarkers sturen" #~ msgid "Start Chat" #~ msgstr "Gesprek beginnen" dino-0.3.0/main/po/oc.po0000644000000000000000000010506014202022370013436 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2022-01-02 16:52+0000\n" "Language-Team: none\n" "Language: oc\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: Weblate 4.10.1\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "" "Lo fichièr es mai pesuc que la talha maximala de mandadís sul servidor." #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "Ieu" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "Imatge enviat" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "Fichièr enviat" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "Imatge recebuda" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "Fichièr recebut" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "Sonada sortissenta" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "Sonada dintranta" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "%b %d" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "Ièr" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "%H∶%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "%l∶%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "fa %i minuta" msgstr[1] "fa %i minutas" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "Ara meteis" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "Proprietari" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "Administrador" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "Membre" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "Utilizaire" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "Convidar" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "Començar una discussion privada" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "Expulsar" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "Autorizar l’escritura" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "Revocar l’autorizacion d’ecritura" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "Convidar a la conferéncia" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "Seleccionar fichièr" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Seleccionar" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Anullar" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "%s de %s" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "Aquesta conferéncia vos autoriza pas a enviar de messatges." #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "Demanda d’autorizacion" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "Aviar una sonada" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "Sonada àudio" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "Sonada vidèo" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "Cercar de messatges" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Membres" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "Sonada…" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "Tinda…" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "Connexion…" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "%s a arrestat la sonada" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "%s a regetat la sonada" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "" #: main/src/ui/call_window/call_window_controller.vala:128 msgid "Invite to Call" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "Començar la discussion" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Començar" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Rejónher una sala" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Seguent" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "Rejónher" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Tornar" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "Connexion a la sala…" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "Senhal requesit per dintrar dins la sala" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "Sètz forabandit e podètz pas participar o crear de conferéncias" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "La sala existís pas" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "Creacion de sala defenduda" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "Sala solament pels membres" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "Causissètz un escais-nom diferent" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "Tròp de participants a la sala" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "Connexion impossibla a %s" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "L’adreça es invalida" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Ajustar" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Acorchis clavièr" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "A prepaus de Dino" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "%i resultat de recèrca" msgstr[1] "%i resultats de recèrca" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "En %s" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "Amb %s" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "Sonada vidèo dintranta" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "Regetar" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "Acceptar" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "Demanda d’abonament" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "Regetar" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "Convit a %s" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "%s vos convidèt a %s" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "Demanda d’autorizacion" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "%s demanda l’autorizacion d’escriure dins %s" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Paramètres" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Paramètres locals" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Enviar una notificacion quand escrivètz" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Enviar un acusat de lectura" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Notificacions" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "Activat" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "Desactivat" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "Solament quand vos mencionan" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "Per defaut : %s" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "Demandar" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "Permissions" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "Demandar la permission d’enviar de messatges" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Detalhs de la conferéncia" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "Detalhs del contacte" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Blocar" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "" "Totes los escambis de messatges e d’estat son blocats dins las doas " "direccions" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Nom de la sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Descripcion de la sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Persistent" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "La sala demorarà après la sortida del darrièr ocupant" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Discussion publica" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Los ocupants pòdon cambiar lo subjècte" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "Permission de veire los JID" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "Qual pòt veire los JID dels participants ?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "Senhal" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "Un senhal per restrénher l’accès a sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Moderada" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Solament los ocupant amb votz pòdon enviar de messatges" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Membres solament" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Solament los membres pòdon dintrar a la sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Istoric dels messatges" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Nombre maximum de messatge tornats per la sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Configuracion de la sala" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "La benvenguda a Dino !" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "Connectatz-vos o creatz un compte per començar." #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "Configurar lo compte" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "I a pas cap de compte actiu" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "Gerir los comptes" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Marrit senhal" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Lo certificat TLS es invalid" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Volètz suprimir lo compte %s ?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Suprimir" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Seleccionar l’avatar" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Imatges" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Totes los fichièrs" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Connectat" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Desconnectat" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Error" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Ajustar un compte" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "Lo servidor a pas pogut provar qu’es %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "Vòstre sistèma operatiu se fisa pas del certificat prepausat." #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "Son certificat de seguretat foguèt provesit per un autre domeni." #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "Son certificat de seguretat serà pas que valid dins lo futur." #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "Son certificat a expirat." #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "Se connectar a %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "Ara podètz utilizar lo compte %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "Marrit nom d’utilizaire o senhal" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "Quicòm a trucat" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "Cap de responsa del servidor" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "Se marcar sus %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "Lo servidor requesís una inscripcion via lo site web" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "Dobrir lo site web" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "Se marcar" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "Consultatz %s per mai d’informacions tocant cossí se marchar" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "Lo messatge es tròp long" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "modificat" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "mandadís…" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "fracàs del mandadís" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, c-format msgid "%s, %s and %i others are typing…" msgstr "%s, %s e %i son a escriure…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "%s, %s e %s son a escriure…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "%s e %s escrivon…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "%s escriu…" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "Sonada començada" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "Començat fa %s" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "Avètz pres aquesta sonada d’un autre aparelh estant" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "Sonada acabda" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "Acabat a %s" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "Durada %s" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "Sonada mancada" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "Avètz mancat aquesta sonada" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "%s a mancat una sonada" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "Sonada regetada" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "Avètz regetada aquesta sonada" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "%s a regetada aquesta sonada" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "Sonada fracassada" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "%i ora" msgstr[1] "%i oras" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "%i minuta" msgstr[1] "%i minutas" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "qualques segondas" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Pas chifrat" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "Mandadís del messatge impossible" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "%x, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "%x, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "%b %d, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "%b %d, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "%a, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "%a, %l∶%M %p" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Aqueste contacte volriá vos ajustar a sa lista de contactes" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "Telecargament %s…" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "%s a ofrit : %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "Fichièr ofrit : %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "Fichièr ofrit" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "Fracàs del transferiment de fichièr" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "Uèi" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "%a, %b %d" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "Enviar un fichièr" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "Actualizar lo messatge" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "Clicatz aquí per començar una conversacion o jónher una sala." #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "Avètz pas cap conversacion dobèrta" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Compte" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Escais-nom" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Alias" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Ajustar un contacte" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "M’avisar quand un nòu messatge arriba" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Convertir los « smileys » en emojis" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "Verificar l’ortografia" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Client XMPP modèrn" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino es un client de chat liure e modèrn per l’ordenador de burèu. Ensaja de " "provesir una experiéncia neta e fisabla de Jabber/XMPP en téner en compte " "vòstra confidencialitat." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Compatible amb lo chiframent OMEMO e OpenPGP del cap a la fin e permet de " "configurar de foncionalitats ligadas amb la confidencialitat coma los " "acusats de lectura e las notificacions d’escritura." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino recupèra l’istoric del servidor e sincroniza los messatges amb d’autres " "periferics." #: main/data/global_search.ui:37 msgid "No active search" msgstr "Cap de recèrca activa" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Començatz d’escriure per aviar una recèrca" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Cap de messatge correspondent" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Verificatz l’ortografia o ensajatz de suprimir de filtres" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "Enviar" #: main/data/shortcuts.ui:12 msgid "General" msgstr "General" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "Discussion" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "Navegacion" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "Sautar la discussion seguenta" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "Sautar a la discussion precedenta" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "Comptes" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Alias local" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Cap de compte pas configurat" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Ajustar un compte" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "S’identificar" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Crear un compte" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "Establiment impossible d’una connexion segura" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Connexion" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Causissètz un servidor public" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "O indicatz l’adreça d’un servidor" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Connectatz-vos allòc" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Causissètz un autre servidor" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Tot es prèst !" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Terminar" #~ msgid "No active conversations" #~ msgstr "Cap de discussion activa" #~ msgid "Main window with conversations" #~ msgstr "Fenèstra màger amb conversacions" #~ msgid "%s, %s and %i others" #~ msgstr "%s, %s e %i autres" #~ msgid "You can now start using %s" #~ msgstr "Podètz ara començar d’utilizar %s" #~ msgid "Open Registration" #~ msgstr "Inscripcion dubèrtas" #~ msgid "Save" #~ msgstr "Enregistrar" #~ msgid "%s, %s and %s" #~ msgstr "%s, %s e %s" #~ msgid "%s and %s" #~ msgstr "%s e %s" #~ msgid "is typing…" #~ msgid_plural "are typing…" #~ msgstr[0] "es a escriure…" #~ msgstr[1] "son a escriure…" #~ msgid "has stopped typing" #~ msgstr "a quitat d‘escriure" #~ msgid "%i search results" #~ msgstr "%i resultats de recèrca" #~ msgid "Discover real JIDs" #~ msgstr "Descobèrta dels JID reals" #~ msgid "Who may discover real JIDs?" #~ msgstr "Qual pòt descobrir los JID reals ?" #~ msgid "Password required for room entry, if any" #~ msgstr "Senhal requesit per dintrar a la sala, se cal" #~ msgid "Failed connecting to %s" #~ msgstr "Fracàs de la connexion a %s" #~ msgid "Join Conference" #~ msgstr "Rejónher la conferéncia" #~ msgid "File" #~ msgstr "Fichièr" dino-0.3.0/main/po/pl.po0000644000000000000000000010631514202022370013454 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2022-02-04 09:55+0000\n" "Language-Team: Polish \n" "Language: pl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " "|| n%100>=20) ? 1 : 2;\n" "X-Generator: Weblate 4.11-dev\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "Plik przekracza maksymalny rozmiar wysyłanych plików dla tego serwera." #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "Ja" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "Wysłano obraz" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "Wysłano plik" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "Odebrano obraz" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "Odebrano plik" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "Połączenie wychodzące" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "Połączenie przychodzące" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "%d %b" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "Wczoraj" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "%H∶%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "%l∶%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "%i minuta temu" msgstr[1] "%i minuty temu" msgstr[2] "%i minut temu" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "Przed chwilą" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "Właściciel" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "Administrator" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "Członek" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "Użytkownik" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "Zaproś" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "Rozpocznij prywatną rozmowę" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "Wyrzuć" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "Udziel pozwolenia na pisanie" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "Odwołaj pozwolenie na pisanie" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "Zaproś do konferencji" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "Wybierz plik" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Wybierz" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Anuluj" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "%s z %s" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "Ta konferencja nie pozwala na wysyłanie wiadomości." #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "Prośba o dostęp" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "Rozpocznij połączenie" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "Połączenie głosowe" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "Połączenie wideo" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "Wyszukaj wiadomości" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Członkowie" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "Łączenie…" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "Łączenie…" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "Łączenie…" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "%s zakończył połączenie" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "%s odrzucił połączenie" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "" #: main/src/ui/call_window/call_window_controller.vala:128 msgid "Invite to Call" msgstr "Zaproś do Rozmowy" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "Rozpocznij rozmowę" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Rozpocznij" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Dołącz do kanału" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Dalej" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "Dołącz" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Wstecz" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "Dołączanie…" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "Wstęp do pokoju wymaga hasła" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "Dołączanie lub założenie konferencji zostało zablokowane" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "Pokój nie istnieje" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "Utworzenie pokoju niedozwolone" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "Pokój tylko dla członków" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "Wybierz inny nick" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "Zbyt wiele osób w pokoju" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "Nie udało się połączyć z %s" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "Niepoprawny adres" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Dodaj" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Skróty klawiszowe" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "O Dino" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "%i wynik wyszukiwania" msgstr[1] "%i wyniki wyszukiwania" msgstr[2] "%i wyników wyszukiwania" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "W %s" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "Z %s" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "Przychodzące połączenie wideo" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "Odrzuć" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "Akceptuj" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "Prośba o subskrypcję" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "Odmów" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "Zaproszenie do %s" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "%s zaprosił cię do %s" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "Prośba o dostęp" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "%s prosi o pozwolenie na pisanie na czacie z %s" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Ustawienia" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Ustawienia lokalne" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Wyślij powiadomienie o pisaniu" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Wyślij potwierdzenia odczytu" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Powiadomienia" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "Włączone" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "Wyłączone" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "Tylko, gdy wspomniano nick" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "Domyślnie: %s" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "Prośba" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "Pozwolenia" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "Poproś o pozwolenie na wysyłanie wiadomości" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Szczegóły konferencji" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "Szczegóły kontaktu" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Zablokuj" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "Blokowanie wysyłania statusu oraz wiadomości w obu kierunkach" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Nazwa pokoju" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Opis pokoju" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Trwały" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "Pokój nie zostanie usunięty po opuszczeniu przez ostatniego członka" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Publicznie wyszukiwalny" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Zmiana tematu możliwa przez użytkowników" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "Zezwolenie na przeglądanie JID" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "Kto może wyświetlać identyfikatory JID użytkowników?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "Hasło" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "Hasło ograniczające dostęp do pokoju" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Moderowany" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Tylko użytkownicy z prawem głosu mogą wysyłać wiadomości" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Tylko dla członków" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Tylko członkowie mogą dołączyć do pokoju" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Historia wiadomości" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Maksymalna liczba przechowywanych wiadomości" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Konfiguracja pokoju" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "Witaj w Dino!" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "Zaloguj się lub załóż konto, aby rozpocząć." #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "Załóż konto" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "Brak aktywnych kont" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "Zarządzaj kontami" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Błędne hasło" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Nieprawidłowy certyfikat TLS" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Usunąć konto %s?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Usuń" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Wybierz awatar" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Obrazy" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Wszystkie pliki" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Połączony" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Rozłączony" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Błąd" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Dodaj konto" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "Serwer nie mógł udowodnić że jest %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "" "Jego certyfikat bezpieczeństwa nie jest zaufany w systemie operacyjnym tego " "komputera." #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "Jego certyfikat bezpieczeństwa został wystawiony dla innej domeny." #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "Jego certyfikat bezpieczeństwa nie jest jeszcze ważny." #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "Jego certyfikat bezpieczeństwa utracił ważność." #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "Zaloguj się do %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "Możesz teraz użyć konta %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "Niepoprawna nazwa użytkownika lub hasło" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "Coś się nie powiodło" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "Brak odpowiedzi z serwera" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "Zarejestruj się na %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "Serwer wymaga rejestracji przez stronę internetową" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "Otwórz stronę internetową" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "Zarejestruj się" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "Dowiedz się na %s, jak się zarejestrować" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "Wiadomość zbyt długa" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "edytowane" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "wysyłanie…" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "wysyłanie nie powiodło się" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, c-format msgid "%s, %s and %i others are typing…" msgstr "%s, %s i %i innych piszą…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "%s, %s i %s piszą…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "%s i %s piszą…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "%s pisze…" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "Połączenie rozpoczęte" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "Połączenie rozpoczęte %s temu" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "To połączenie zostało odebrane na innym urządzeniu" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "Połączenie zakończone" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "Zakończone o %s" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "Trwało %s" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "Połączenie nieodebrane" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "Nie odebrałeś tego połączenia" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "%s nie odebrał tego połączenia" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "Połączenie odrzucone" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "Odrzuciłeś to połączenie" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "%s odrzucił to połączenie" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "Łączenie nie powiodło się" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "godzinę" msgstr[1] "%i godziny" msgstr[2] "%i godzin" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "minutę" msgstr[1] "%i minuty" msgstr[2] "%i minut" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "kilka sekund temu" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Bez szyfrowania" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "Nie można wysłać wiadomości" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "%x, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "%x, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "%d %b, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "%d %b, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "%a, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "%a, %l∶%M %p" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "Zapisz jako…" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Ta osoba chciałaby Cię dodać do swoich kontaktów" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "Otwórz" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "Pobieranie %s…" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "oferowany %s: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "Oferowany plik: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "Oferta pliku" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "Przesyłanie pliku nie powiodło się" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "Dzisiaj" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "%a, %d %b" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "Wyślij plik" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "Aktualizuj wiadomość" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "Kliknij tutaj, aby rozpocząć rozmowę albo dołączyć do kanału." #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "Nie masz otwartych czatów" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Konto" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Pseudonim" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Pseudonim" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Dodaj kontakt" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Powiadom o przychodzącej wiadomości" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Zamieniaj tekst na emotikony" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "Sprawdź pisownie" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Nowoczesny komunikator XMPP" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino jest nowoczesnym, otwartym komunikatorem. Skupia się na prostej " "obsłudze sieci Jabber/XMPP dbając o twoją prywatność." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Obsługuje szyfrowanie od końca do końca za pomocą OMEMO i OpenPGP, a także " "daje kontrolę nad funkcjami wpływającymi na prywatność, jak powiadomienia o " "pisaniu czy odczytaniu wiadomości." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino pobiera historię rozmów z serwera i synchronizuje wiadomości z innymi " "urządzeniami." #: main/data/global_search.ui:37 msgid "No active search" msgstr "Brak aktywnego wyszukiwania" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Zacznij pisać, aby wyszukać" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Brak pasujących wiadomości" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Sprawdź pisownię lub spróbuj usunąć filtry" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "Wyślij" #: main/data/shortcuts.ui:12 msgid "General" msgstr "Ogólne" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "Rozmowa" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "Nawigacja" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "Przejdź do następnej rozmowy" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "Przejdź do poprzedniej rozmowy" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "Konta" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Lokalny nick" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Brak skonfigurowanych kont" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Dodaj konto" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Zaloguj się" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Załóż konto" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "Nie udało się nawiązać bezpiecznego połączenia" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Połącz" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Wybierz serwer publiczny" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "Lub wprowadź adres serwera" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Przejdź do logowania" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Wybierz inny serwer" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Wszystko gotowe!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Zakończ" #~ msgid "No active conversations" #~ msgstr "Brak aktywnych rozmów" #~ msgid "Main window with conversations" #~ msgstr "Główne okno rozmów" #~ msgid "%s, %s and %i others" #~ msgstr "%s, %s i %i innych" #~ msgid "You can now start using %s" #~ msgstr "Możesz teraz używać konto %s" #~ msgid "Open Registration" #~ msgstr "Otwórz stronę" #~ msgid "Save" #~ msgstr "Zapisz" #~ msgid "%s, %s and %s" #~ msgstr "%s, %s i %s" #~ msgid "%s and %s" #~ msgstr "%s i %s" #~ msgid "is typing…" #~ msgid_plural "are typing…" #~ msgstr[0] "pisze…" #~ msgstr[1] "piszą…" #~ msgstr[2] "piszą…" #~ msgid "has stopped typing" #~ msgstr "przestał pisać" #~ msgid "%i search results" #~ msgstr "%i wyniki wyszukiwania" #~ msgid "Discover real JIDs" #~ msgstr "Uprawnienie do wyświetlenia JID" #~ msgid "Who may discover real JIDs?" #~ msgstr "Kto może widzieć JID użytkowników?" #~ msgid "Password required for room entry, if any" #~ msgstr "Hasło, aby ograniczyć dostęp do pokoju" #~ msgid "Failed connecting to %s" #~ msgstr "Nie udało się połączyć z %s" #~ msgid "Join Conference" #~ msgstr "Dołącz do konferencji" #~ msgid "File" #~ msgstr "Plik" #~ msgid "Preferences" #~ msgstr "Preferencje" #~ msgid "Quit" #~ msgstr "Wyjdź" #~ msgid "JID should be of the form “user@example.com”" #~ msgstr "JID powinien być w formie \"użytkownik@przykład.com\"" #~ msgid "Copy Link Address" #~ msgstr "Skopiuj adres odnośnika" #~ msgid "Copy" #~ msgstr "Kopiuj" #~ msgid "Select All" #~ msgstr "Wybierz wszystkie" #~ msgid "Search" #~ msgstr "Wyszukaj" #~ msgid "Send message marker" #~ msgstr "Wyślij informację o odczytaniu wiadomości" #~ msgid "Start Chat" #~ msgstr "Rozpocznij rozmowę" dino-0.3.0/main/po/pt.po0000644000000000000000000010112714202022370013460 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2021-05-10 09:33+0000\n" "Language-Team: none\n" "Language: pt\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: Weblate 4.7-dev\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "O ficheiro excede o tamanho máximo de envios do servidor." #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "Eu" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "Imagem enviada" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "Ficheiro enviado" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "Imagem recebida" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "Ficheiro recebido" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "%b %d" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "Ontem" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "%H∶%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "%l∶%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "%i min atrás" msgstr[1] "%i mins atrás" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "Agora há pouco" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "Dono" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "Admin" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "Membro" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "Utilizador" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "Convidar" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "Iniciar conversa privada" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "Kick" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "Conceder permissão de escrita" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "Revogar permissão de escrita" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "Convidar para Conferência" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "Selecionar ficheiro" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Selecionar" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Cancelar" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "%s de %s" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "Essa conferência não permite que envie mensagens." #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "Pedir permissão" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "Buscar mensagens" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Membros" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "Conectando…" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "" #: main/src/ui/call_window/call_window_controller.vala:128 msgid "Invite to Call" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "Iniciar conversa" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Iniciar" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Juntar-se a um canal" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Próximo" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "Juntar-se" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Voltar" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "Juntando-se…" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "Palavra-passe necessária para entrar na sala" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "Proibido de entrar ou criar uma conferência" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "Sala não existe" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "Não permitido criar sala" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "Sala somente para membros" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "Escolha um apelido diferente" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "Quantidade máxima da sala" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "Não foi possível se conectar a %s" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "Endereço inválido" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Adicionar" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Atalhos de Teclado" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "Sobre Dino" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "%i resultado da pesquisa" msgstr[1] "%i resultados da pesquisa" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "Em %s" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "Com %s" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "Rejeitar" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "Aceitar" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "Pedido de assinatura" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "Negar" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "Convite para %s" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "%s te convidou para %s" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "Pedido de permissão" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "%s está pedindo permissão para escrever em %s" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Configurações" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Configurações locais" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Enviar notificação ao digitar" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Enviar confirmação de leitura" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Notificações" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "Ligado" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "Desligado" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "Somente quando mencionado" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "Predefinição: %s" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "Pedir" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "Permissões" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "Pedir permissão para enviar mensagens" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Detalhes da Conferência" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "Detalhes do contato" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Bloquear" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "Comunicação e atualizações de estado dos dois lados estão bloqueadas" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Nome da sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Descrição da sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Persistente" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "A sala vai continuar depois do último integrante sair" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Publicamente pesquisável" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Integrantes podem mudar o assunto" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "Permissão de visualizar JIDs" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "Quem consegue visualizar os JIDs dos utilizadores?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "Palavra-passe" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "Uma palavra-passe para restringir o acesso à sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Moderado" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Somente integrantes com voz podem enviar mensagens" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Somente membros" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Somente membros podem entrar na sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Histórico de mensagens" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Quantidade máxima de histórico gerado pela sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Configuração da sala" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "Seja bem vindo ao Dino!" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "Entre ou crie uma conta para começar." #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "Configurar a conta" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "Nenhuma conta ativa" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "Gerir contas" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Palavra-passe incorreta" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Certificado TLS inválido" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Remover conta %s?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Remover" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Escolher avatar" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Imagens" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Todos os ficheiros" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Conectado" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Desconectado" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Erro" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Adicionar conta" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "O servidor não conseguiu provar ser %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "" "O seu certificado securitário não é confiável para o seu sistema operacional." #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "O seu certificado securitário é emitido para outro domínio." #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "O seu certificado securitário só se tornará válido no futuro." #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "O seu certificado securitário está expirado." #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "Registar-se em %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "Agora pode usar a conta %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "Nome ou Palavra-passe errada" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "Algo deu errado" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "Sem resposta do servidor" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "Registe-se em %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "O servidor requer que se inscreva através de um site" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "Abrir website" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "Registar" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "Verifique %s para informação de como se inscrever" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "Mensagem muito longa" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "editado" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, c-format msgid "%s, %s and %i others are typing…" msgstr "%s, %s e %i outros estão digitando…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "%s, %s e %s estão digitando…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "%s e %s estão digitando…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "%s está digitando…" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "" msgstr[1] "" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "" msgstr[1] "" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Não-criptografado" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "Não foi possível enviar a mensagem" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "%x, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "%x, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "%b %d, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "%b %d, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "%a, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "%a, %l∶%M %p" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Este contato gostaria de adicioná-lo à sua lista de contatos" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "A descarregar %s…" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "%s ofereceu: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "Ficheiro oferecido: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "Ficheiro oferecido" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "Transferência de ficheiro falhou" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "Hoje" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "%a, %b %d" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "Enviar um ficheiro" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "Atualizar mensagem" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "Clique aqui para iniciar uma conversa or entrar num canal." #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "Não tem conversas abertas" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Conta" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Apelido" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Pseudônimo" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Adicionar contato" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Notificar quando uma nova mensagem chegar" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Converter smileys para emojis" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "Verificar ortografia" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Moderno cliente de chat XMPP" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino é um moderno chat de código aberto para desktop. Ele é focado em prover " "uma transparente e confiável experiência Jabber/XMPP, tendo em mente a sua " "privacidade." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Suporte criptografia ponta a ponta com OMEMO e OpenPGP e permite configurar " "privacidade—características relacionadas às notificações de leitura, " "recebimento e escrita." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino obtém o histórico do servidor e sincroniza mensagens com outros " "aparelhos." #: main/data/global_search.ui:37 msgid "No active search" msgstr "Sem busca ativa" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Escreva para iniciar a busca" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Sem mensagens correspondentes" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Verifique a grafia ou tente remover filtros" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "Enviar" #: main/data/shortcuts.ui:12 msgid "General" msgstr "Geral" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "Conversa" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "Navegação" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "Ir para a próxima conversa" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "Ir para a conversa anterior" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "Contas" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Pseudônimo local" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Nenhuma conta configurada" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Adicionar conta" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Registar" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Criar conta" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "Não foi possível estabelecer uma conexão segura" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Conectar" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Escolha um servidor publico" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "Ou especifique um endereço de servidor" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Entrar em vez disso" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Escolha outro servidor" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Tudo configurado!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Finalizado" dino-0.3.0/main/po/pt_BR.po0000644000000000000000000010526414202022370014051 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2021-02-22 00:50+0000\n" "Language-Team: Portuguese (Brazil) \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: Weblate 4.5\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "O arquivo ultrapassa o máximo do tamanho de upload." #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "Eu" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "Imagem enviada" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "Arquivo enviado" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "Imagem recebida" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "Arquivo recebido" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "%b %d" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "Ontem" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "%H∶%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "%l∶%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "%i min atrás" msgstr[1] "%i min atrás" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "Agora há pouco" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "Dono" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "Admin" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "Membro" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "Usuário" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "Convidar" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "Iniciar conversa privada" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "Expulsar" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "Conceder permissão de escrita" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "Revogar permissão de escrita" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "Convidar para Conferência" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "Selecionar arquivo" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Selecionar" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Cancelar" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "%s de %s" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "Essa conferência não permite que você envie mensagens." #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "Pedir permissão" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "Buscar mensagens" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Membros" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "Conectando…" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "" #: main/src/ui/call_window/call_window_controller.vala:128 msgid "Invite to Call" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "Iniciar Conversa" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Iniciar" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Juntar-se a um Canal" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Próximo" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "Juntar-se" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Voltar" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "Juntando-se…" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "Senha necessária para entrar na sala" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "Proibido de entrar ou criar uma conferência" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "Sala não existe" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "Sem permissão de criar sala" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "Sala somente para membros" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "Escolha um apelido diferente" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "Capacidade de usuários na sala excedida" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "Não foi possível se conectar a %s" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "Endereço inválido" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Adicionar" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Atalhos de Teclado" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "Sobre Dino" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "%i resultado da pesquisa" msgstr[1] "%i resultados da pesquisa" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "Em %s" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "Com %s" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "Rejeitar" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "Aceitar" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "Pedido de assinatura" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "Negar" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "Convite para %s" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "%s te convidou para %s" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "Pedido de permissão" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "%s está pedindo permissão para escrever em %s" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Configurações" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Configurações Locais" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Enviar notificação ao digitar" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Enviar confirmação de leitura" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Notificações" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "Ligado" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "Desligado" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "Somente quando mencionado" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "Padrão: %s" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "Pedir" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "Permissões" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "Pedir permissão para enviar mensagens" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Detalhes da Conferência" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "Detalhes do Contato" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Bloquear" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "Comunicação e atualizações de status dos dois lados estão bloqueadas" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Nome da sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Descrição da sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Persistente" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "A sala vai continuar existindo mesmo que o último integrante saia" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Publicamente pesquisável" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Integrantes podem mudar o assunto" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "Permissão de visualizar JIDs" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "Quem consegue visualizar os JIDs dos usuários?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "Senha" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "Uma senha para restringir o acesso à sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Moderado" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Somente integrantes com voz podem enviar mensagens" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Somente membros" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Somente membros podem entrar na sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Histórico de mensagens" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Quantidade máxima de histórico armazenado na sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Configuração da Sala" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "Bem vindo ao Dino!" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "Entre ou crie uma conta para começar." #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "Configurar a conta" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "Nenhuma conta ativa" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "Gerenciar contas" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Senha incorreta" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Certificado TLS inválido" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Remover conta %s?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Remover" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Escolher avatar" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Imagens" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Todos os arquivos" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Conectado" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Desconectado" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Erro" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Adicionar Conta" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "O servidor contactado não conseguiu provar que representa %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "" "O certificado de segurança do servidor não é considerado confiável por seu " "sistema operacional." #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "Seu certificado de segurança for emitido para outro domínio." #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "Seu certificado de segurança se tornará válido apenas no futuro." #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "Seu certificado de segurança está expirado." #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "Entre em %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "Você agora pode usar a conta %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "Nome ou senha incorretos" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "Algo deu errado" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "Sem resposta do servidor" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "Registre-se em %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "O servidor requer registro através de um site" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "Abrir website" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "Registrar" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "Verifique %s para informação de como se registrar" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "Mensagem muito longa" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "editado" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, c-format msgid "%s, %s and %i others are typing…" msgstr "%s, %s e %i outros estão digitando…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "%s, %s e %s estão digitando…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "%s e %s estão digitando…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "%s está digitando…" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "" msgstr[1] "" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "" msgstr[1] "" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Sem Criptografia" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "Não foi possível enviar a mensagem" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "%x, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "%x, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "%d de %b, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "%d de %b, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "%a, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "%a, %l∶%M %p" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Este contato gostaria de adicioná-lo à sua lista de contatos" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "Baixando %s…" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "%s ofereceu: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "Arquivo oferecido: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "Arquivo oferecido" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "Transferência de arquivo falhou" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "Hoje" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "%a, %b %d" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "Enviar um arquivo" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "Atualizar mensagem" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "Clique aqui para inicial uma conversa ou entrar em um canal." #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "Você não tem conversas abertas" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Conta" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Apelido" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Pseudônimo" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Adicionar Contato" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Notificar quando uma nova mensagem chegar" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Converter smileys para emojis" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Cliente de Chat XMPP Moderno" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino é um cliente moderno de código aberto de chat para desktop. Ele foca em " "oferecer uma experiência Jabber/XMPP transparente e confiável levando em " "consideração a sua privacidade." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Ele suporta criptografia ponta-a-ponta com OMEMO e OpenPGP e, além disso, " "permite configurar funcionalidades relativas à privacidade como notificações " "de leitura, recebimento e escrita." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino obtém o histórico do servidor e sincroniza mensagens com outros " "dispositivos." #: main/data/global_search.ui:37 msgid "No active search" msgstr "Sem busca ativa" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Escreva para iniciar uma busca" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Sem mensagens correspondentes" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Verifique a grafia ou tente remover filtros" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "Enviar" #: main/data/shortcuts.ui:12 msgid "General" msgstr "Geral" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "Conversa" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "Navegação" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "Ir para a próxima conversa" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "Ir para a conversa anterior" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "Contas" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Pseudônimo local" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Nenhuma conta configurada" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Adicionar uma conta" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Entrar" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Criar conta" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "Não foi possível estabelecer uma conexão segura" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Conectar" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Escolha um servidor público" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "Ou especifique um endereço de servidor" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Entrar em vez disso" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Escolher outro servidor" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Tudo configurado!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Finalizado" #~ msgid "No active conversations" #~ msgstr "Nenhuma conversa ativa" #~ msgid "Main window with conversations" #~ msgstr "Janela principal com as conversas" #~ msgid "%s, %s and %i others" #~ msgstr "%s, %s e outros %i" #~ msgid "You can now start using %s" #~ msgstr "Agora você pode começar a usar %s" #~ msgid "Open Registration" #~ msgstr "Abrir Site de Inscrição" #~ msgid "Save" #~ msgstr "Salvar" #~ msgid "%s, %s and %s" #~ msgstr "%s, %s e %s" #~ msgid "%s and %s" #~ msgstr "%s e %s" #~ msgid "is typing…" #~ msgid_plural "are typing…" #~ msgstr[0] "está digitando…" #~ msgstr[1] "estão digitando…" #~ msgid "has stopped typing" #~ msgstr "parou de digitar" #~ msgid "%i search results" #~ msgstr "%i resultado(s) da busca" #~ msgid "Discover real JIDs" #~ msgstr "Descobrir JIDs reais" #~ msgid "Who may discover real JIDs?" #~ msgstr "Quem pode encontrar JIDs reais?" #~ msgid "Password required for room entry, if any" #~ msgstr "Senha requerida para entrar na sala, se houver" #~ msgid "Failed connecting to %s" #~ msgstr "Falha ao se conectar com %s" #~ msgid "Join Conference" #~ msgstr "Juntar-se a uma conferência" #~ msgid "File" #~ msgstr "Arquivo" #~ msgid "Communicate happiness." #~ msgstr "Comunique-se alegremente." #~ msgid "Preferences" #~ msgstr "Preferências" #~ msgid "Quit" #~ msgstr "Sair" #~ msgid "JID should be of the form “user@example.com”" #~ msgstr "O Jabber ID deve estar no formato \"usuário@exemplo.com\"" #~ msgid "Copy Link Address" #~ msgstr "Copiar endereço do link" #~ msgid "Copy" #~ msgstr "Copiar" #~ msgid "Select All" #~ msgstr "Selecionar tudo" #~ msgid "Search" #~ msgstr "Pesquisar" #~ msgid "Send message marker" #~ msgstr "Enviar marcador de mensagem" #~ msgid "Start Chat" #~ msgstr "Iniciar bate-papo" #~ msgid "Request presence updates" #~ msgstr "Pedir atualizações sobre presença" #~ msgid "Join on startup" #~ msgstr "Conectar-se ao inicializar" #~ msgid "Add Chat" #~ msgstr "Adicionar bate-papo" dino-0.3.0/main/po/ro.po0000644000000000000000000010704314202022370013460 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2022-02-11 18:58+0000\n" "Language-Team: Romanian \n" "Language: ro\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==0 || (n%100 > 0 && n%100 < " "20)) ? 1 : 2;\n" "X-Generator: Weblate 4.11-dev\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "Fișierul depășește dimensiunea maximă de încărcare a serverului." #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "Eu" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "Imagine trimisă" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "Fișier trimis" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "Imagine primitä" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "Fișier primit" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "Apel efectuat" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "Apel primit" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "%d %b" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "Ieri" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "%H∶%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "%l∶%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "Acum %i minut" msgstr[1] "Acum %i minute" msgstr[2] "Acum %i de minute" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "Chiar acum" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "Proprietar" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "Administrator" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "Membru/ă" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "Utilizator" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "Invită" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "Pornește o conversație privată" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "Dă afară" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "Acordă permisiunea de a scrie" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "Revoca permisiunea de a scrie" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "Invită la o conferință" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "Selectare fișier" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Selectare" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Anulare" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "%s de la %s" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "Această conferință nu vă permite să trimiteți mesaje." #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "Solicitare permisiune" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "Începe un apel" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "Apel audio" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "Apel video" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "Caută mesaje" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Membri" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "Se apelează…" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "Sună…" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "Conectare…" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "%s a încheiat apelul" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "%s a refuzat apelul" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "Camere" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "Nici o cameră găsită." #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "Microfoane" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "Nici un microfon găsit." #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "Difuzoare" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "Nici un difuzor găsit." #: main/src/ui/call_window/call_window_controller.vala:128 msgid "Invite to Call" msgstr "Invitați la apel" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "Pornește o conversație" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Pornire" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Alăturați-vă canalului" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Următorul" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "Alăturați-vă" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Înapoi" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "Conectare…" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "Este necesară o parolă pentru a te putea alătura acestei discuții" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "Aveți interdicție de a vă alătura sau de a crea o conferință" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "Această discuție nu există" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "Este interzisă crearea unei discuții" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "Discuție accesibilă numai membrilor" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "Alegeți un nume diferit" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "Discuția are prea mulți membrii" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "Nu s-a putut face conectarea la %s" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "Adresă invalidă" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Adaugă" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Scurtături de tastatură" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "Despre Dino" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "%i rezultat de căutare" msgstr[1] "%i rezultate de căutare" msgstr[2] "%i de rezultate de căutare" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "În %s" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "Cu %s" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "Apel video primit" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "Apel de grup video primit" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "Apelul de grup primit" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "Respinge" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "Acceptă" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "Cerere de abonare" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "Refuză" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "Invitaţie la %s" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "%s v-a invitat la %s" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "Solicitare de permisiune" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "%s solicită permisiunea de a scrie în %s" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Setări" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Setări locale" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Trimite notificare la tastare" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Trimite notificare la primire" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Notificări" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "Da" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "Nu" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "Doar la mențiuni" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "Implicit: %s" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "Cerere" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "Permisiuni" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "Solicita permisiunea de a trimite mesaje" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Detalii conferință" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "Detalii contact" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Blochează" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "Comunicațiile și actualizările de stare sunt blocate în ambele sensuri" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Numele discuției" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Descrierea discuției" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Persistentă" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "Discuția va continua să existe și după ce ultima persoană va ieși" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Vizibilă public" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Membrii pot schimba subiectul" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "Permisiunea de a vizualiza JID-uri" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "Cine are voie să vadă JID-urile ocupanților?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "Parolă" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "O parolă pentru a restricționa accesul la discuție" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Moderată" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Doar membrii cu drept de voce vor putea trimite mesaje" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Doar pentru membri" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Numai membrii se pot alătura discuției" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Istoric mesaje" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Numărul maxim de mesaje din istoric ce va fi afișat în discuție" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Configurare discuție" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "Bine ați venit la Dino!" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "Autentificați-vă sau creați un cont pentru a începe." #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "Configurare cont" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "Nici un cont activ" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "Administrare conturi" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Parolă greșită" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Certificat TLS invalid" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Se șterge contul %s?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Șterge" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Selectare avatar" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Imagini" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Toate fișierele" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Conectat" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Deconectat" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Eroare" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Adaugă un cont" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "Serverul nu a putut dovedi că este %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "" "Sistemul dumneavoastră de operare nu are încredere în certificatul său de " "securitate." #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "Certificatul său de securitate este emis pentru alt domeniu." #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "Certificatul său de securitate va deveni valabil numai în viitor." #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "Certificatul său de securitate este expirat." #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "Autentificare în %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "Acum puteți începe să utilizați contul %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "Nume utilizator sau parolă greșite" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "A apărut o problemă" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "Nici un răspuns de la server" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "Înregistrare pe %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "Serverul necesită înregistrarea printr-un site web" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "Deschide site-ul" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "Înregistrează-te" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "Aflați pe %s informații despre cum se face înregistrarea" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "Mesaj prea lung" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "editat" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "în așteptare…" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "livrare eșuată" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, c-format msgid "%s, %s and %i others are typing…" msgstr "%s, %s și încă %i tastează…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "%s, %s și %s tastează…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "%s și %s tastează…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "%s tastează…" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "Apelul a început" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "A început acum %s" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "Ați recepționat acest apel pe un alt dispozitiv" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "Apel încheiat" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "S-a încheiat la %s" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "A durat %s" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "Apel ratat" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "Ați pierdut acest apel" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "%s a pierdut acest apel" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "Apel refuzat" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "Ați refuzat acest apel" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "%s a refuzat acest apel" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "Apelul a eșuat" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "%i oră" msgstr[1] "%i ore" msgstr[2] "%i de ore" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "%i minut" msgstr[1] "%i minute" msgstr[2] "%i de minut" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "câteva secunde" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Necriptat" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "Nu se poate transmite mesajul" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "%x, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "%x, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "%b %d, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "%b %d, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "%a, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "%a, %l∶%M %p" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "Salvează ca…" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Acest contact dorește să vă adauge la lista ei/lui de contacte" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "Deschide" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "Se descarcă %s…" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "%s a oferit: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "Fișier oferit: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "Fișier oferit" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "Transferul fișierului a eșuat" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "Azi" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "%a, %d %b" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "Trimite fișier" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "Actualizare mesaj" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "Apăsați aici pentru a porni o conversație sau a vă alătura unui canal." #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "Nu aveți nici o discuție deschisă" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Cont" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Nume" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Alias" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Adaugă contact" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Notifică atunci când este primit un mesaj nou" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Convertește zâmbilici în emoticoane" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "Verificare ortografie" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Client XMPP de discuții modern" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino este un client de chat modern, cu sursă deschisă, pentru calculatoare. " "Se concentrează să furnizeze o experiență Jabber/XMPP clară și fiabilă, " "ținând cont de confidențialitatea dumneavoastră." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Suportă criptare de la un capăt la altul prin intermediul OMEMO și OpenPGP, " "și permite configurarea caracteristicilor legate de confidențialitate precum " "trimiterea notificărilor de primire și tastare." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino preia istoricul discuțiilor de pe server și sincronizează mesajele cu " "celelalte dispozitive." #: main/data/global_search.ui:37 msgid "No active search" msgstr "Nici o căutare activă" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Tastați ceva pentru a porni căutarea" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Nici un mesaj nu se potrivește" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Verificați ortografia sau încercați să eliminați filtre" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "Trimite" #: main/data/shortcuts.ui:12 msgid "General" msgstr "General" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "Discuție" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "Navigare" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "Salt la următoarea conversație" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "Salt la conversația anterioară" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "Conturi" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Alias local" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Nici un cont configurat" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Adaugă un cont" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Autentificare" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Creează cont" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "Nu s-a putut stabili o conexiune securizată" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Conectare" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Alegeți un server public" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "Sau specificați adresa serverului" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Înapoi la conectare" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Alege alt server" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Gata!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Finalizare" #~ msgid "No active conversations" #~ msgstr "Nici o conversație activă" #~ msgid "Main window with conversations" #~ msgstr "Fereastra principală de conversații" #~ msgid "%s, %s and %i others" #~ msgstr "%s, %s și încă %i alții" #~ msgid "You can now start using %s" #~ msgstr "Acum puteți utiliza %s" #~ msgid "Open Registration" #~ msgstr "Deschis pentru înregistrare" #~ msgid "Save" #~ msgstr "Salvare" #~ msgid "%s, %s and %s" #~ msgstr "%s, %s și %s" #~ msgid "%s and %s" #~ msgstr "%s și %s" #~ msgid "is typing…" #~ msgid_plural "are typing…" #~ msgstr[0] "tastează…" #~ msgstr[1] "tastează…" #~ msgstr[2] "tastează…" #~ msgid "has stopped typing" #~ msgstr "s-a oprit din scris" #~ msgid "%i search results" #~ msgstr "%i rezultate la căutare" #~ msgid "Discover real JIDs" #~ msgstr "Descoperă adresele adevărate" #~ msgid "Who may discover real JIDs?" #~ msgstr "Cine poate descoperi adresele adevărate?" #~ msgid "Password required for room entry, if any" #~ msgstr "Dacă există, o parolă va fi necesară pentru alăturarea la discuție" #~ msgid "Failed connecting to %s" #~ msgstr "Eroare la conectarea cu %s" #~ msgid "Join Conference" #~ msgstr "Alătură-te unei conferințe" #~ msgid "File" #~ msgstr "Fișier" #~ msgid "Preferences" #~ msgstr "Preferințe" #~ msgid "Quit" #~ msgstr "Ieșire" #~ msgid "JID should be of the form “user@example.com”" #~ msgstr "JID trebuie să fie de forma “utilizator@exemplu.ro”" #~ msgid "Copy Link Address" #~ msgstr "Copiază adresa legăturii" #~ msgid "Copy" #~ msgstr "Copiere" #~ msgid "Select All" #~ msgstr "Selectează tot" #~ msgid "Search" #~ msgstr "Căutare" #~ msgid "Send message marker" #~ msgstr "Trimite marcajul mesajului" #~ msgid "Start Chat" #~ msgstr "Pornește o discuție" dino-0.3.0/main/po/ru.po0000644000000000000000000011645414202022370013474 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2022-02-06 17:57+0000\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" "X-Generator: Weblate 4.11-dev\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "Размер файла превышает максимальный размер загрузки сервера." #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "Я" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "Изображение отправлено" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "Файл отправлен" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "Изображение получено" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "Файл получен" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "Исходящий звонок" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "Входящий звонок" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "%d %b" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "Вчера" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "%H∶%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "%l∶%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "%i минута назад" msgstr[1] "%i минуты назад" msgstr[2] "%i минут назад" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "Только что" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "Владелец" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "Администратор" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "Участник" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "Пользователь" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "Пригласить" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "Начать личный чат" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "Выгнать" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "Выдать разрешение писать" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "Отобрать разрешение писать" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "Пригласить в чат" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "Выберите файл" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Выбрать" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Отмена" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "%s из %s" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "Вам запретили отправлять сообщения в этой беседе." #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "Запросить разрешение" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "Начать звонок" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "Аудиозвонок" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "Видеозвонок" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "Поиск сообщений" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Участники" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "Звонок…" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "Звонок…" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "Подключение…" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "%s завершил(а) звонок" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "%s отклонил(а) звонок" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "" #: main/src/ui/call_window/call_window_controller.vala:128 #, fuzzy msgid "Invite to Call" msgstr "Пригласить на звонок" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "Начать чат" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Начать" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Войти в канал" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Далее" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "Войти" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Назад" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "Присоединение…" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "Требуется пароль для входа в комнату" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "Вам запрещено заходить в конференции или создавать их" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "Данной комнаты не существует" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "Вам запрещено создавать комнаты" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "Комната только для участников" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "Выберите другой никнейм" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "В комнате слишком много посетителей" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "Не удаётся подключиться к %s" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "Недействительный адрес" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Добавить" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Горячие клавиши" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "О Dino" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "%i результат поиска" msgstr[1] "%i результата поиска" msgstr[2] "%i результатов поиска" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "В %s" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "С %s" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "Входящий видеозвонок" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "Отклонить" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "Принять" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "Требуется авторизация" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "Отклонить" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "Приглашение в %s" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "%s пригласил вас в %s" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "Запрос на разрешения" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "%s запросил(а) разрешение на возможность писать в %s" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Настройки" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Локальные настройки" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Отправлять уведомления при наборе сообщения" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Отправлять уведомления о прочтении" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Уведомления" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "ВКЛ" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "ВЫКЛ" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "Только при упоминании" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "По умолчанию: %s" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "Запросить" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "Разрешения" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "Запросить разрешение на отправку сообщений" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Информация о конференции" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "Информация о контакте" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Заблокировать" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "Переписка и обновления статусов заблокированы в оба направления" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Название комнаты" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Описание комнаты" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Сохранить комнату пустой" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "Комната сохранится, даже если уйдёт последний посетитель" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Доступность для публичного поиска" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Посетители могут менять тему чата" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "Разрешение на просмотр JID" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "Кому разрешено просматривать JID посетителей?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "Пароль" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "Пароль для ограничения доступа к комнате" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Модерируемый" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Только посетители с голосом(разрешение) могут отправлять сообщения" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Только для участников" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Только участники могут заходить в комнату" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "История сообщений" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Максимальное количество логов, оставляемое комнатой" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Настройки комнаты" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "Добро пожаловать в Dino!" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "Войдите или создайте учётную запись, чтобы начать использование." #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "Настроить учётную запись" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "Нет активных учётных записей" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "Управление учётными записями" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Неверный пароль" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Недействительный TSL сертификат" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Удалить аккаунт %s?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Удалить" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Выбрать аватарку" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Изображения" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Все файлы" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Подключено" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Отключено" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Ошибка" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Добавить аккаунт" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "Сервер не смог доказать, что %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "Его сертификат безопасности не доверяет вашей операционной системе." #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "Его сертификат безопасности выдается другому домену." #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "Его сертификат безопасности станет действительным только в будущем." #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "Срок действия его сертификата безопасности истек." #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "Вход в уч. запись %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "Теперь вы можете использовать учётную запись %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "Неправильный логин или пароль" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "Что-то пошло не так" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "Нет ответа от сервера" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "Регистрация в %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "Сервер требует пройти авторизацию через сайт" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "Открыть веб-сайт" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "Зарегистрироваться" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "Прочтите %s, чтобы узнать о процессе авторизации" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "Сообщение слишком длинное" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "ред." #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "ожидание…" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "доставка не удалась" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, c-format msgid "%s, %s and %i others are typing…" msgstr "%s, %s и ещё %i печатают…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "%s, %s и %s печатают…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "%s и %s печатают…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "%s печатает…" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "Звонок начат" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "Начат %s назад" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "Вы обработали этот звонок на другом устройстве" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "Звонок завершен" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "Завершен в %s" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "Продлился %s" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "Пропущенный звонок" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "Вы пропустили этот звонок" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "%s пропустил(а) этот звонок" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "Звонок отклонен" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "Вы отклонили этот звонок" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "%s отклонил(а) этот звонок" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "Звонок не удался" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "%i час" msgstr[1] "%i часа" msgstr[2] "%i часов" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "%i минута" msgstr[1] "%i минуты" msgstr[2] "%i минут" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "несколько секунд" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Не зашифровано" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "Не удаётся отправить сообщение" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "%H∶%M, %x" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "%l∶%M, %x %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "%d %b, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "%d %b, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "%a, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "%a, %l∶%M %p" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "Сохранить как…" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Этот контакт хочет добавить вас в свой список контактов" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "Открыть" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "Скачивание %s…" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "%s. Приблизительный размер: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "Приблизительный размер файла: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "Предложен файл" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "Ошибка передачи файла" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "Сегодня" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "%a, %d %b" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "Прикрепить файл" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "Редактировать сообщение" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "Нажмите здесь, чтобы начать беседу или присоединиться к каналу." #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "Тут пустовато" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Уч. запись" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Никнейм" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Псевдоним" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Добавить контакт" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Уведомлять о новых сообщениях" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Превращать смайлы в эмодзи" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "Проверка орфографии" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Современный XMPP клиент" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino - это современный клиент для чатов с открытым исходным кодом, " "направленный на надёжное и приватное использование Jabber/XMPP на " "персональных компьютерах." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Он поддерживает сквозное шифрование через OMEMO и OpenPGP и позволяет " "настраивать такие функции, как уведомления о прочтении и наборе сообщений." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino загружает историю с сервера и синхронизирует сообщения с другими " "устройствами." #: main/data/global_search.ui:37 msgid "No active search" msgstr "Поиск по сообщениям" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Начните писать то, что хотите найти" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Нет подходящих сообщений" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Проверьте словари или измените критерии поиска" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "Отправить" #: main/data/shortcuts.ui:12 msgid "General" msgstr "Основное" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "Беседа" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "Навигация" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "Перейти к следующему чату" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "Перейти к предыдущему чату" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "Учётные записи" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Псевдоним" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Нет настроенных уч. записей" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Добавить аккаунт" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Войти" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Создать аккаунт" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "Не удалось установить защищённое соединение" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Присоединиться" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Выберите публичный сервер" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "Или введите адрес сервера сами" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Войти" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Выбрать другой сервер" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Всё готово!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Закончить" #~ msgid "No active conversations" #~ msgstr "Нет активных чатов" #~ msgid "Main window with conversations" #~ msgstr "Главное окно с чатами" #~ msgid "%s, %s and %i others" #~ msgstr "%s, %s и ещё %i человек" #~ msgid "You can now start using %s" #~ msgstr "Теперь вы можете использовать %s" #~ msgid "Open Registration" #~ msgstr "Открытая регистрация" #~ msgid "Save" #~ msgstr "Сохранить" #~ msgid "%s, %s and %s" #~ msgstr "%s, %s и %s" #~ msgid "%s and %s" #~ msgstr "%s и %s" #~ msgid "is typing…" #~ msgid_plural "are typing…" #~ msgstr[0] "печатает…" #~ msgstr[1] "печатают…" #~ msgstr[2] "печатают…" #~ msgid "has stopped typing" #~ msgstr "перестал печатать" #~ msgid "%i search results" #~ msgstr "Результаты поиска по запросу «%i»" #~ msgid "Discover real JIDs" #~ msgstr "Показывать настоящие JID" #~ msgid "Who may discover real JIDs?" #~ msgstr "Кто может видеть настоящие JID?" #~ msgid "Password required for room entry, if any" #~ msgstr "Требуется пароль для входа в комнату, если" #~ msgid "Failed connecting to %s" #~ msgstr "Не удалось соединение с %s" #~ msgid "Join Conference" #~ msgstr "Войти в конференцию" #~ msgid "File" #~ msgstr "Файл" #~ msgid "Preferences" #~ msgstr "Опции" #~ msgid "Quit" #~ msgstr "Выйти" #~ msgid "JID should be of the form “user@example.com”" #~ msgstr "JID должен быть в виде «user@example.com»" #~ msgid "Copy Link Address" #~ msgstr "Копировать адресс ссылки" #~ msgid "Copy" #~ msgstr "Копировать" #~ msgid "Select All" #~ msgstr "Выбрать всё" #~ msgid "Search" #~ msgstr "Поиск" #~ msgid "Send message marker" #~ msgstr "Отправлять маркеры сообщений" #~ msgid "Start Chat" #~ msgstr "Начать чат" dino-0.3.0/main/po/sq.po0000644000000000000000000010242414202022370013461 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2022-02-06 17:57+0000\n" "Language-Team: none\n" "Language: sq\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: Weblate 4.11-dev\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "Kjo kartelë e tejkalon madhësinë maksimum për ngarkime të shërbyesit." #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "Unë" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "Figura u dërgua" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "Kartela u dërgua" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "U mor figurë" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "U mor kartelë" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "Thirrje e dërguar" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "Thirrje e marrë" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "%d %B" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "Dje" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "%H:%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "%l∶%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "%i minutë më parë" msgstr[1] "%i minuta më parë" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "Mu tani" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "Pronar" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "Përgjegjës" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "Anëtar" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "Përdorues" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "Ftoje" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "Nisni bisedë private" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "Përzëre" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "Akordojini leje shkrimi" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "Shfuqizojini leje shkrimi" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "Ftoni në Konferencë" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "Përzgjidhni kartelë" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Përzgjidhe" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Anuloje" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "%s prej %s" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "Kjo konferencë s’ju lejon të dërgoni mesazhe." #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "Kërkesë për leje" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "Ndihmëz për butonin “nis thirrje”" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "Thirrje me zë" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "Thirrje me video" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "Kërko te mesazhet" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Anëtarë" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "Po thirret…" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "Po i bihet ziles…" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "Po lidhet…" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "%s përfundoi thirrjen" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "%s s’pranoi thirrjen" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "" #: main/src/ui/call_window/call_window_controller.vala:128 msgid "Invite to Call" msgstr "Ftoni për Thirrje" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "Nisni Bisedë" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Fillo" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Hyni në Kanal" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Pasuesi" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "Merrni pjesë" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Mbrapsht" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "Po hyhet…" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "Lypset fjalëkalim për të hyrë në dhomë" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "I dëbuar nga pjesëmarrje apo krijim konference" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "Dhoma s’ekziston" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "S’keni leje të krijoni dhomë" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "Dhomë vetëm për anëtarë" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "Zgjidhni nofkë tjetër" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "Shumë të pranishëm në dhomë" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "S’u lidh dot te %s" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "Adresë e pavlefshme" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Shto" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Shkurtore Tastiere" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "Mbi Dino-s" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "%i përfundim kërkimi" msgstr[1] "%i përfundime kërkimi" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "Në %s" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "Me %s" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "Thirrje ardhëse me video" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "Hidhe tej" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "Pranojeni" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "Kërkesë pajtimi" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "Mohojeni" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "Ftesë për te %s" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "%s ju ftoi te %s" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "Kërkesë për leje" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "%s kërkon leje për shkrim te %s" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Rregullime" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Rregullime Vendore" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Dërgo njoftime shtypjesh" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Dërgo dëftesa leximi" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Njoftime" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "On" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "Off" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "Vetëm kur përmendet" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "Parazgjedhje: %s" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "Kërkesë" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "Leje" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "Kërko leje për dërgim mesazhesh" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Hollësi Konference" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "Hollësi Kontakti" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Bllokoje" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "" "Përditësimet mbi komunikimin dhe gjendjet, nga cilido kah, janë të bllokuara" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Emër i dhomës" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Përshkrim i dhomës" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "E qëndrueshme" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "Dhoma do të mbetet, pasi të dalë i pranishmi i fundit" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Mund të kërkohet publikisht" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Të pranishmit mund të ndryshojnë subjektin" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "Leje për të parë JID-ra" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "Cilit i është lejuar të shohë JID pjesëmarrësish?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "Fjalëkalim" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "Një fjalëkalim për të kufizuar hyrje te dhoma" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "I moderuar" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Vetëm të pranishëm me zë mund të dërgojnë mesazhe" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Vetëm anëtarët" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Në dhomë mund të futen vetëm anëtarë" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Historik mesazhesh" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Sasi maksimum mesazhesh historiku të emetuar nga dhoma" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Formësim Dhome" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "Mirë se vini te Dino!" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "Që t’ia filloni, bëni hyrjen ose krijoni një llogari." #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "Ujdisni llogari" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "Pa llogari aktive" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "Administroni llogari" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Fjalëkalim i gabuar" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Dëshmi TLS e pavlefshme" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Të hiqet llogaria %s?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Hiqe" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Përzgjidhni avatar" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Figura" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Krejt kartelat" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "U lidh" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "U shkëput" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Gabim" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Shtoni Llogari" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "Shërbyesi s’provoi dot se është %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "Dëshmia e tij e sigurisë nuk besohet nga sistemi juaj operativ." #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "Dëshmia e tij e sigurisë është emetuar nga tjetër përkatësi." #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "Dëshmia e tij e sigurisë do të bëhet e vlefshme vetëm në të ardhmen." #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "Dëshmia e tij e sigurisë ka skaduar." #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "Hyni te %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "Tani mund të përdorni llogarinë %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "Emër përdoruesi ose fjalëkalim i gabuar" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "Diçka shkoi keq" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "S’ka përgjigje nga shërbyesi" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "Regjistrohuni në %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "Shërbyesi kërkon doemos të bëhet regjistrim përmes një sajti" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "Hap sajtin" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "Regjistrohuni" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "Për hollësi se si të regjistroheni, shihni te %s" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "Mesazh shumë i gjatë" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "përpunoi" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "pezull…" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "dërgimi dështoi" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, c-format msgid "%s, %s and %i others are typing…" msgstr "%s, %s dhe %i të tjerë po shtypin…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "%s, %s dhe %s po shtypin…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "%s, dhe %s po shtypin…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "%s po shkruan…" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "Nisi thirrja" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "Filluar %s më parë" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "Me këtë thirrje u morët në pajisje tjetër" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "Thirrja përfundoi" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "Përfundoi më %s" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "Zgjati %s" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "Thirrje e humbur" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "E humbët këtë thirrje" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "%s e humbi këtë thirrje" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "Thirrja s’u pranua" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "S’e pranuat këtë thirrje" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "%s s’e pranoi këtë thirrje" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "Thirrja dështoi" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "%i orë" msgstr[1] "%i orë" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "%i minutë" msgstr[1] "%i minuta" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "pak sekonda" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "I pafshehtëzuar" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "S’arrihet të dërgohet mesazh" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "%x, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "%x, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "%d %b, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "%d %b, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "%a, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "%a, %l∶%M %p" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "Ruajeni si…" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Ky kontakt do të donte t’ju shtonte te lista e tyre e kontakteve" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "Hape" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "Po shkarkohet %s…" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "%s ofroi: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "U ofrua kartelë: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "U ofrua kartelë" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "Shpërngulja e kartelës dështoi" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "Sot" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "%a, %d %b" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "Dërgoni një kartelë" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "Përditësoni mesazhin" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "Klikoni këtu që të nisni një bisedë ose të hyni në një kanal." #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "S’keni fjalosje të hapura" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Llogari" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Nofkë" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Alias" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Shtoni Kontakt" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Njoftomë kur mbërrin mesazh i ri" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Shndërro emotikonet në emoji" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "Kontroll drejtshkrimi" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Klient Modern Fjalosjesh XMPP" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino është një klient modern fjalosjesh, me burim të hapur, për desktop. Ai " "përqendrohet në dhënien e një mënyre funksionimi të qartë dhe të qëndrueshme " "për protokoll Jabber/XMPP, teksa ka në mendje privatësinë tuaj." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Mbulon fshehtëzim skaj-më-skaj me OMEMO dhe OpenPGP dhe lejon formësim " "veçorish të lidhura me privatësinë, bie fjala, dëshmish leximi dhe njoftime " "shtypjeje." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino e sjell historikun prej shërbyesve dhe njëkohëson mesazhet në pajisje " "të tjera." #: main/data/global_search.ui:37 msgid "No active search" msgstr "S’ka kërkim aktiv" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Shtypni që të niset një kërkim" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "S’ka mesazhe me përputhje" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Kontrolloni drejtshkrimin ose provoni të hiqni filtra" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "Dërgoje" #: main/data/shortcuts.ui:12 msgid "General" msgstr "Të përgjithshme" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "Bisedë" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "Lëvizje" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "Hidhu te biseda pasuese" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "Hidhu te biseda e mëparshme" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "Llogari" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Alias vendor" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "S’ka llogari të formësuara" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Shtoni një llogari" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Hyni" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Krijoni llogari" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "S’u vendos dot një lidhje të sigurt" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Lidhuni" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Zgjidhni një shërbyes publik" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "Ose specifikoni një adresë shërbyesi" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Hyni, më mirë" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Zgjidhni një tjetër shërbyes" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Gjithçka e ujdisur!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Përfundoje" dino-0.3.0/main/po/sv.po0000644000000000000000000010514714202022370013473 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2022-02-11 18:58+0000\n" "Language-Team: none\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" "X-Generator: Weblate 4.11-dev\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "Filen är större än vad servern tillåter." #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "Jag" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "Bild skickad" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "Fil skickad" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "Bild mottagen" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "Fil mottagen" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "Utgående samtal" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "Inkommande samtal" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "%b %d" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "Igår" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "%H : %M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "%l∶%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "%i minut sedan" msgstr[1] "%i minuter sedan" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "Nyss" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "Ägare" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "Admin" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "Medlem" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "Användare" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "Bjud in" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "Starta privat konversation" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "Kasta ut" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "Ge skrivrättigheter" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "Återkalla skrivrättigheter" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "Bjud in till gruppchatt" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "Välj fil" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Välj" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Avbryt" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "%s från %s" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "Den här konferensen låter dig inte skicka meddelanden." #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "Be om tillstånd" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "Starta samtal" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "Ljudsamtal" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "Videosamtal" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "Sök meddelanden" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Medlemmar" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "Ringer…" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "Ringer…" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "Ansluter…" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "%s avslutade samtalet" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "%s avvisade samtalet" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "Kameror" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "Ingen kamera hittades." #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "Mikrofoner" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "Ingen mikrofon hittades." #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "Högtalare" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "Ingen högtalare hittades." #: main/src/ui/call_window/call_window_controller.vala:128 msgid "Invite to Call" msgstr "Bjud in till samtal" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "Starta Chatt" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Starta" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Gå med i kanal" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Nästa" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "Gå med" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Tillbaka" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "Går med…" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "Lösenord krävs för att gå med i rummet" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "Förbjuden att gå med eller skapa gruppchattar" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "Rummet finns inte" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "Saknar tillåtelse att skapa rum" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "Endast medlemmar tillåtna i rummet" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "Välj ett annat smeknamn" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "För många användare i rummet" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "Kunde inte ansluta till %s" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "Ogiltig adress" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Lägg till" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Snabbkommandon" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "Om Dino" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "%i sökresultat" msgstr[1] "%i sökresultat" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "I %s" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "Med %s" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "Inkommande videosamtal" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "Inkommande videogruppsamtal" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "Inkommande gruppsamtal" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "Avslå" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "Godkänn" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "Prenumerationsförfrågan" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "Neka" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "Inbjudan till %s" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "%s bjöd in dig till %s" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "Åtkomstbegäran" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "%s ber om tillstånd att skriva i %s" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Inställningar" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Lokala inställningar" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Skicka skriftaviseringar" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Skicka läsbekräftelser" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Aviseringar" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "På" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "Av" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "Vid omnämnande" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "Standard: %s" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "Fråga" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "Tillstånd" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "Be om tillstånd att skicka meddelanden" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Konferensdetaljer" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "Kontaktdetaljer" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Blockera" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "Kommunikation och statusuppdatering i båda riktningar blockeras" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Namn på rummet" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Beskrivning av rummet" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Behåll" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "" "Rummet kommer att finnas kvar även efter att sista användaren har lämnat" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Offentligt sökbart" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Användare får ändra ämnet" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "Tillstånd att visa JID" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "Vem har tillstånd att se användarnas JID?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "Lösenord" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "Det krävs ett lösenord för att få tillträde till rummet" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Moderering" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Endast användare med tillåtelse får skicka meddelanden" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Endast för medlemmar" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Endast medlemmar får ansluta till rummet" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Meddelandehistorik" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Maxgräns på meddelandehistoriken i rummet" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Rumskonfiguration" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "Välkommen till Dino!" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "Logga in eller skapa ett konto för att komma igång." #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "Ställ in konto" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "Inga aktiva konton" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "Hantera konton" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Fel lösenord" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Ogiltigt TLS-certifikat" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Ta bort konto %s?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Ta bort" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Välj avatar" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Bilder" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Alla filer" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Ansluten" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Frånkopplad" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Fel" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Lägg till konto" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "Servern kunde inte bevisa att den är %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "Ditt operativsystem litar inte på dess säkerhetscertifikat." #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "Dess säkerhetscertifikat är utgivet till en annan domän." #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "Dess säkerhetscertifikat blir giltigt i framtiden." #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "Dess säkerhetscertifikat har gått ut." #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "Logga in på %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "Du kan nu börja använda kontot %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "Fel användarnamn eller lösenord" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "Något gick fel" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "Inget svar från servern" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "Registrera konto på %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "Servern kräver registrering genom en webbsida" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "Öppna webbsida" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "Registrera" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "Kolla %s för information om registrering" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "Meddelandet är för långt" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "redigerad" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "väntar…" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "leverans misslyckades" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, c-format msgid "%s, %s and %i others are typing…" msgstr "%s, %s och %i andra skriver…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "%s, %s och %s skriver…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "%s och %s skriver…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "%s skriver…" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "Samtal startat" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "Startade för %s sedan" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "Du hanterade detta samtal på en annan enhet" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "Samtal avlutat" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "Avslutades %s" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "Varade %s" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "Missat samtal" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "Du svarade inte på det här samtalet" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "%s svarade inte på det här samtalet" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "Avvisat samtal" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "Du avvisade detta samtal" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "%s avvisade detta samtal" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "Samtal misslyckades" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "%i timme" msgstr[1] "%i timmar" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "%i minut" msgstr[1] "%i minuter" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "några sekunder" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Okrypterat" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "Kunde inte skicka meddelandet" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "%x, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "%x, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "%b %d, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "%b %d, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "%a, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "%a, %l∶%M %p" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "Spara som…" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Denna kontakt vill lägga till dig i sin kontaktlista" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "Öppna" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "Tar emot %s…" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "%s erbjöd: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "Fil erbjuden: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "Fil erbjuden" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "Filöverföring misslyckades" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "Idag" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "%a, %b %d" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "Skicka en fil" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "Uppdatera meddelande" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "Klicka här för att starta en konversation eller gå med i en kanal." #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "Du har inga öppna konversationer" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Konto" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Smeknamn" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Alias" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Lägg till kontakt" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Avisera när ett nytt meddelande mottages" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Konvertera smileys till emojin" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "Kontrollera stavning" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Modern XMPP-chattklient" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino är en moden chattklient för skrivbordet med öppen källkod. Den erbjuder " "en elegant och pålitligt upplevelse av Jabber/XMPP samtidigt som den ser " "efter din integritet." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Dino stödjer end-to-end-kryptering med OMEMO och OpenPGP och tillåter " "konfigurering av funktioner med integritetspåverkan som läsbekräftelser och " "skriftaviseringar." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino hämtar historik från servern och synkroniserar meddelanden med andra " "enheter." #: main/data/global_search.ui:37 msgid "No active search" msgstr "Ingen aktiv sökning" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Skriv för att börja söka" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Inga meddelande matchade" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Kolla stavningen eller ta bort filter" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "Skicka" #: main/data/shortcuts.ui:12 msgid "General" msgstr "Allmänt" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "Konversation" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "Navigering" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "Hoppa till nästa konversation" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "Hoppa till föregående konversation" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "Konton" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Lokalt alias" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Inga konfigurerade konton" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Lägg till konto" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Logga in" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Skapa konto" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "Kunde inte etablera en säker anslutning" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Anslut" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Välj en offentlig server" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "Eller ange en serveraddress" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Logga in i stället" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Välj en annan server" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Färdigt!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Slutför" #~ msgid "Video call incoming" #~ msgstr "Inkommande videosamtal" #~ msgid "Call incoming" #~ msgstr "Inkommande samtal" #~ msgid "Establishing call" #~ msgstr "Upprättar samtal" #~ msgid "Video call establishing" #~ msgstr "Upprättar videosamtal" #~ msgid "Call establishing" #~ msgstr "Upprättar samtal" #~ msgid "Call in progress…" #~ msgstr "Samtal pågår…" #, c-format #~ msgid "Lasted for %s" #~ msgstr "Varade i %s" #~ msgid "seconds" #~ msgstr "några sekunder" #~ msgid "No active conversations" #~ msgstr "Inga aktiva chattar" #~ msgid "Main window with conversations" #~ msgstr "Huvudfönster med konversationer" #~ msgid "%s, %s and %i others" #~ msgstr "%s, %s och %i andra" #~ msgid "You can now start using %s" #~ msgstr "Du kan nu börja använda %s" #~ msgid "Open Registration" #~ msgstr "Öppna Registreringen" #~ msgid "Save" #~ msgstr "Spara" #~ msgid "%s, %s and %s" #~ msgstr "%s, %s och %s" #~ msgid "%s and %s" #~ msgstr "%s och %s" #~ msgid "is typing…" #~ msgid_plural "are typing…" #~ msgstr[0] "skriver…" #~ msgstr[1] "skriver…" #~ msgid "has stopped typing" #~ msgstr "har slutat skriva" #~ msgid "%i search results" #~ msgstr "%i sökresultat" #~ msgid "Discover real JIDs" #~ msgstr "Upptäckt riktiga JID:n" #~ msgid "Who may discover real JIDs?" #~ msgstr "Vem får upptäcka riktiga JID:n?" #~ msgid "Password required for room entry, if any" #~ msgstr "Lösenord som krävs för tillträde, om något" #~ msgid "Failed connecting to %s" #~ msgstr "Anslutning till %s misslyckades" #~ msgid "Join Conference" #~ msgstr "Anslut till gruppchatt" #~ msgid "File" #~ msgstr "Fil" dino-0.3.0/main/po/ta.po0000644000000000000000000007112714202022370013447 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-04-16 20:11+0000\n" "Language-Team: none\n" "Language: ta\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: Weblate 4.0.1-dev\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "நேற்று" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "" msgstr[1] "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "" #: main/src/ui/call_window/call_window_controller.vala:128 msgid "Invite to Call" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "" msgstr[1] "" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "" #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, c-format msgid "%s, %s and %i others are typing…" msgstr "" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "%s, %s, %s தட்டச்சிடுகின்றனர்…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "%s, %s தட்டச்சிடுகின்றனர்…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "%s தட்டச்சிடுகின்றார்…" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "" msgstr[1] "" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "" msgstr[1] "" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "%s பதிவிறக்கப்படுகிறது…" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "இன்று" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "" #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" #: main/data/global_search.ui:37 msgid "No active search" msgstr "" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "" #: main/data/shortcuts.ui:12 msgid "General" msgstr "" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "" dino-0.3.0/main/po/tr.po0000644000000000000000000010461714202022370013471 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2022-02-11 18:58+0000\n" "Language-Team: none\n" "Language: tr\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: Weblate 4.11-dev\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "Dosya, sunucunun azami yükleme boyutunu aşıyor." #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "Ben" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "Resim gönderildi" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "Dosya gönderildi" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "Resim alındı" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "Dosya alındı" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "Giden arama" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "Gelen arama" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "%d %b" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "Dün" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "%H.%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "%l∶%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "%i dakika önce" msgstr[1] "%i dakıka önce" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "Şu an" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "Sahip" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "Yönetici" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "Üye" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "Kullanıcı" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "Davet et" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "Özel sohbet başlat" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "Kov" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "Yazma izni ver" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "Yazma iznini iptal et" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "Görüşmeye Davet et" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "Dosya seç" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Seç" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "İptal" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "%s - %s" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "Bu görüşme mesaj göndermenize izin vermiyor." #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "İzin iste" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "Arama başlat" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "Sesli arama" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "Görüntülü arama" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "Mesajları ara" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Üyeler" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "Aranıyor…" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "Çalıyor…" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "Bağlanıyor…" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "%s aramayı sonlandırdı" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "%s aramayı reddetti" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "Kameralar" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "Kamera bulunamadı." #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "Mikrofonlar" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "Mikrofon bulunamadı." #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "Hoparlörler" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "Hoparlör bulunamadı." #: main/src/ui/call_window/call_window_controller.vala:128 msgid "Invite to Call" msgstr "Aramaya Davet Et" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "Sohbet Başlat" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Başla" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Kanala katıl" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "İleri" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "Katıl" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Geri" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "Katılıyor…" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "Odaya girmek için parola gerekli" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "Görüşmeye katılmak veya oluşturmaktan engellendiniz" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "Böyle bir oda yok" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "Oda oluşturulmasına izin verilmedi" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "Sadece üyeler odası" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "Başka bir takma isim seçin" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "Odada çok fazla katılımcı var" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "Bağlantı kurulamadı %s" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "Geçersiz adres" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Ekle" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Klavye Kısayolları" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "Dino Hakkında" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "%i arama sonucu" msgstr[1] "%i arama sonucu" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "%s içinden" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "%s ile" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "Gelen görüntülü arama" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "Gelen görüntülü grup araması" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "Gelen grup araması" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "Reddet" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "Kabul et" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "Abonelik isteği" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "Reddet" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "%s'e davet" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "%s seni %s'e davet etti" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "İzin isteği" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "%s, %s için yazma izni istiyor" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Ayarlar" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Yerel Ayarlar" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "\"Yazıyor\" bildirimi gönder" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "\"Okundu\" bilgisi gönder" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Bildirimler" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "Açık" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "Kapalı" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "Sadece atıf yapıldığında" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "Varsayılan: %s" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "İste" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "İzinler" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "Mesaj göndermek için izin isteyin" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Görüşme Ayrıntıları" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "Kişi Ayrıntıları" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Engelle" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "İletişim ve durum güncellemeler her iki taraf için engellenir" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Oda ismi" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Oda tanımı" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Kalıcılık" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "Oda, son katılımcı ayrıldıktan sonra da devam eder" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Herkese açık aranabilir" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Katılımcılar başlığı değiştirebilir" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "JID'leri görüntüleme izni" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "Katılımcıların JID'lerini kimler görüntüleyebilir?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "Parola" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "Odaya erişimi kısıtlayan bir şifre" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Moderasyonlu" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Sadece sesli katılımcılar mesaj gönderebilir" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Sadece üyeler" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Sadece üyeler odaya giriş yapabilir" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Mesaj geçmişi" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Odada kaydedilecek en fazla mesaj sayısı" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Oda Ayarları" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "Dino'ya hoş geldiniz!" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "Başlamak için oturum açın veya bir hesap oluşturun." #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "Hesap oluştur" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "Etkin hesap yok" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "Hesapları yönet" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Yanlış parola" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Geçersiz TLS Sertifikası" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Hesabı sil %s?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Sil" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Avatar seç" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Görseller" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Tüm dosyalar" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Bağlandı" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Bağlantı koptu" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Hata" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Hesap Ekle" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "Sunucu, %s olduğunu kanıtlayamadı." #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "Güvenlik sertifikasına işletim sisteminiz güvenmiyor." #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "Güvenlik sertifikası başka bir etki alanına verildi." #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "Güvenlik sertifikası yalnızca gelecekte geçerli olacak." #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "Güvenlik sertifikasının süresi doldu." #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "%s'e giriş yap" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "Artık %s hesabını kullanabilirsiniz." #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "Yalnış kullanıcı adı veya parola" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "Bir şeyler yanlış gitti" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "Sunucudan yanıt yok" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "Kaydol - %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "Sunucu bir websitesine kayıt olmanı şart koşuyor" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "Web sitesini aç" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "Kayıt" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "Nasıl kaydolacağınızla ilgili bilgi için %s sayfasına bakın" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "Mesaj çok uzun" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "düzenlendi" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "bekliyor…" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "teslimat başarısız" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, c-format msgid "%s, %s and %i others are typing…" msgstr "%s, %s ve %i diğerleri yazıyor…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "%s, %s ve %s yazıyor…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "%s ve %s yazıyor…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "%s yazıyor…" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "Arama başladı" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "%s önce başladı" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "Bu aramayı başka bir aygıtta yaptınız" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "Arama sona erdi" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "%s'de sona erdi" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "%s sürdü" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "Cevapsız arama" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "Bu aramayı kaçırdınız" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "%s bu aramayı kaçırdı" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "Arama reddedildi" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "Bu aramayı reddettiniz" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "%s bu aramayı reddetti" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "Arama başarısız" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "%i saat" msgstr[1] "%i saat" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "%i dakika" msgstr[1] "%i dakika" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "birkaç saniye" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Şifrelenmemiş" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "Mesaj gönderilemedi" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "%x, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "%x, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "%b %d, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "%b %d, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "%a, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "%a, %l∶%M %p" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "Farklı kaydet…" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Bu kişi seni kendi kişiler listesine eklemek istiyor" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "Aç" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "İndiriliyor %s…" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "%s teklif etti: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "Dosya teklif edildi: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "Dosya teklif edildi" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "Dosya transferi başarısız" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "Bugün" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "%a, %d %b" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "Dosya gönder" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "Mesajı güncelle" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "Bir sohbet başlatmak ya da kanala katılmak için buraya tıkla." #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "Henüz burada sohbet yok" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Hesap" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Takma isim" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Mahlas" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Kişi Ekle" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Mesaj ulaştığında bildirim gönder" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "İfadeleri emoji olarak göster" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "Yazım denetimi" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Modern XMPP Sohbet İstemcisi" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino masaüstü bilgisayarlar için modern, açık kaynaklı bir sohbet " "uygulamasıdır. Gizlilik hassasiyetinizi göz önünde bulundurarak temiz ve " "güvenilir bir Jabber/XMPP deneyimi sunmaya odaklanır." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "OMEMO ve OpenPGP ile baştan sona şifreleme destekler ve \"okundu\" bilgisi, " "\"yazıyor...\" bildirimi gibi gizlilikle alakalı özelliklerin " "ayarlanabilmesini sağlar." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino sunucudan konuşma geçmişini sunucudan çeker ve diğer cihazlara " "senkronize eder." #: main/data/global_search.ui:37 msgid "No active search" msgstr "Henüz arama yapılmadı" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Arama başlatmak için bir şey yaz" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Hiç mesaj bulunamadı" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Yazım hatalarını kontrol et ya da filtreleri kaldırmayı dene" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "Gönder" #: main/data/shortcuts.ui:12 msgid "General" msgstr "Genel" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "Sohbet" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "Navigasyon" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "Bir sonraki sohbete geç" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "Bir önceki sohbete geç" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "Hesaplar" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Yerel mahlas" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Hiç hesap ayarlanmamış" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Bir hesap ekle" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Giriş yap" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Hesap oluştur" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "Güvenli bir bağlantı oluşturulamadı" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Bağlan" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Genel bir sunucu seç" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "Veya özel bir sunucu adresi" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Bunun yerine oturum açın" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Başka sunucu seç" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Hepsi tamam!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Bitir" #~ msgid "Video call incoming" #~ msgstr "Gelen görüntülü arama" #~ msgid "Call incoming" #~ msgstr "Gelen arama" #~ msgid "Establishing call" #~ msgstr "Arama oluşturuluyor" #~ msgid "Video call establishing" #~ msgstr "Görüntülü arama oluşturuluyor" #~ msgid "Call establishing" #~ msgstr "Arama oluşturuluyor" #~ msgid "Call in progress…" #~ msgstr "Arama devam ediyor…" #, c-format #~ msgid "Lasted for %s" #~ msgstr "%s sürdü" #~ msgid "seconds" #~ msgstr "saniye" #~ msgid "No active conversations" #~ msgstr "Aktif sohbet yok" #~ msgid "%s, %s and %i others" #~ msgstr "%s, %s ve %i diğerleri" #~ msgid "You can now start using %s" #~ msgstr "Şimdi %s kullanmaya başlayabilirsin" #~ msgid "Open Registration" #~ msgstr "Açık Kaydolma" #~ msgid "Save" #~ msgstr "Kaydet" #~ msgid "%s, %s and %s" #~ msgstr "%s, %s ve %s" #~ msgid "%s and %s" #~ msgstr "%s ve %s" #~ msgid "is typing…" #~ msgid_plural "are typing…" #~ msgstr[0] "yazıyor…" #~ msgstr[1] "yazıyorlar…" #~ msgid "has stopped typing" #~ msgstr "yazmayı sonlandırdı" #~ msgid "%i search results" #~ msgstr "%i adet sonuc bulundu" #~ msgid "Discover real JIDs" #~ msgstr "Gerçek JIDs keşfetme" #~ msgid "Who may discover real JIDs?" #~ msgstr "Kimler gerçek JIDs keşfedebilir?" #~ msgid "Password required for room entry, if any" #~ msgstr "Odaya giriş için yapmak için parola ( gerekirse )" dino-0.3.0/main/po/uk.po0000644000000000000000000007764514202022370013475 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-12-31 17:29+0000\n" "Language-Team: none\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=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" "X-Generator: Weblate 4.4.1-dev\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "Я" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "Зображення надіслано" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "Файл надіслано" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "Зображення отримано" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "Файл отримано" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "%b %d" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "Учора" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "%H∶%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "%l∶%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "%i хвилина тому" msgstr[1] "%i хвилини тому" msgstr[2] "%i хвилин тому" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "Щойно" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "Власник" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "Адміністратор" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "Учасник" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "Користувач" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "Запросити" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "Почати приватну розмову" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "Вигнати" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "Видати дозвіл писати" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "Відкликати дозвіл писати" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "Запросити до конференції" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Вибрати" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Скасувати" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "%s з %s" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "Вам заборонено відправляти повідомлення в цій бесіді." #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "Попросити дозвіл" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "З'єднання…" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "" #: main/src/ui/call_window/call_window_controller.vala:128 msgid "Invite to Call" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "Почати розмову" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Почати" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Приєднатись до каналу" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Далі" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "Приєднатися" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Назад" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "Приєднання…" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "Для входу в кімнату потрібно ввести пароль" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "Вам заборонено приєднуватися або створювати конференції" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "Кімната не існує" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "Вам заборонено створювати кімнати" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "Кімната тільки для учасників" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "Оберіть інший нікнейм" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "У кімнаті занадто багато відвідувачів" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "Не вдалося підключитися до %s" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "Недійсна адреса" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Додати" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "%i результат пошуку" msgstr[1] "%i результати пошуку" msgstr[2] "%i результатів пошуку" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "Прийняти" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "Необхідна авторизація" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "Відхилити" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "Запрошення до %s" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "%s запросив вас до %s" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "Запит на дозвіл" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "%s запитує дозвіл писати в %s" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "Ласкаво просимо в Діно!" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "Увійдіть в систему або створіть обліковий запис, щоб почати." #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "Налаштування облікового запису" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "Немає активних облікових записів" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "Керування обліковими записами" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Хибний пароль" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Недійсний сертифікат TLS" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Видалити обліковий запис %s?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Видалити" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Вибрати аватар" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Зображення" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Усі файли" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Під’єднано" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Роз'єднано" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Помилка" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Додати обліковий запис" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "Вхід в обліковий запис %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "Тепер ви можете використовувати обліковий запис %s." #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "Неправильне ім'я користувача чи пароль" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "Щось пішло не так" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "Немає відповіді від сервера" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "Зареєструватись в %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "Сервер вимагає реєстрації через веб-сайт" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "Відкрити веб-сайт" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "Зареєструватися" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "Прочитайте %s, щоб дізнатися про процес реєстрації" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "Повідомлення задовге" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "відредаговано" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, c-format msgid "%s, %s and %i others are typing…" msgstr "%s, %s та %i інших друкують…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "%s, %s та %s друкують…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "%s і %s друкують…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "%s друкує…" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "" msgstr[1] "" msgstr[2] "" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "" msgstr[1] "" msgstr[2] "" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Нешифровано" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "Не вдалося надіслати повідомлення" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "%H∶%M, %x" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "%l∶%M, %x %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "%d %b, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "%d %b, %l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "%a, %H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "%a, %l∶%M %p" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Цей контакт хоче додати вас до свого списку контактів" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "Завантаження %s…" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "%s приблизний розмір: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "Приблизний розмір файлу: %s" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "Запропонований Файл" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "Помилка передачі файлу" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "Сьогодні" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "%a, %b %d" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "" #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" #: main/data/global_search.ui:37 msgid "No active search" msgstr "" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "" #: main/data/shortcuts.ui:12 msgid "General" msgstr "" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "" dino-0.3.0/main/po/zh_CN.po0000644000000000000000000010374214202022370014043 0ustar rootroot# Chinese translations for PACKAGE package # PACKAGE 软件包的简体中文翻译. # Copyright (C) 2017 THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Tong Hui , 2017. msgid "" msgstr "" "Project-Id-Version: dino\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2021-11-11 13:51+0000\n" "Language-Team: Chinese (Simplified) \n" "Language: zh_CN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Weblate 4.9.1-dev\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "文件超过了服务器最大上传大小限制。" #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "我" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "图片已发送" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "文件已发送" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "图片已接收" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "文件已接收" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "呼出通话" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "呼入通话" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "%_m 月 %_d 日" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "昨天" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "%H∶%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "%l∶%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "%i 分钟以前" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "刚刚" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "所有者" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "管理员" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "成员" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "用户" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "邀请" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "启动私密会话" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "踢出" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "授予写入权限" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "撤回写入权限" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "邀请到聊天室里" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "选择文件" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "选择" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "取消" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "%s 来自 %s" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "该会议不允许你发送讯息。" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "请求权限" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "开始通话" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "音频通话" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "视频通话" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "搜索消息" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "成员" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "正在呼叫…" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "对方已响铃…" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "连接中…" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "%s 结束了通话" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "%s 拒绝了通话" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "" #: main/src/ui/call_window/call_window_controller.vala:128 msgid "Invite to Call" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "开始聊天" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "开始" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "加入频道" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "下一个" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "加入" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "返回" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "加入中…" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "需要输入密码才能加入聊天室" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "被禁止加入或创建聊天室" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "聊天室不存在" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "不允许创建聊天室" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "聊天室不对外开放" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "选择一个不同的昵称" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "聊天室内人数过多" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "无法连接到 %s" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "地址无效" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "添加" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "键盘快捷键" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "关于 Dino" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "%i 条搜索结果" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "在%s中" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "用%s" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "传入视频通话" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "拒绝" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "接受" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "订阅请求" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "拒绝" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "对 %s 的邀请" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "%s邀请你加入 %s" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "权限请求" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "%s 请求写入 %s 的权限" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "设置" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "本地设置" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "发送打字通知" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "发送已读回执" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "通知" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "开启" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "关闭" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "只有被提到时" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "默认:%s" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "请求" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "权限" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "请求发送讯息的权限" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "聊天室详情" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "联系人详情" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "封禁" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "双向通讯和状态更新已经被封禁" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "聊天室名称" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "聊天室描述" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "一直在线" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "聊天室在最后一个成员离开后仍然保留" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "可被公开检索到" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "参与者可以更改主题" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "查看JID的权限" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "谁被允许查看成员的JID?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "密码" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "一个限制对该聊天室访问的密码" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "主持" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "仅限有发言权的参与者可以发送消息" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "仅限成员" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "只有成员才可以进入聊天室" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "消息历史记录" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "聊天室储存的历史消息的最大数目" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "聊天室设置" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "欢迎来到 Dino!" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "登录或者创建一个账户后开始。" #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "设置帐户" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "没有活动的帐号" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "管理帐号" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "密码错误" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "无效的 TLS 证书" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "移除账户 %s?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "删除" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "选择头像" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "图片" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "所有文件" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "已连接" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "已断开连接" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "错误" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "添加帐号" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "服务器无法证明它是 %s。" #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "其安全证书不受你的操作系统信任。" #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "其安全证书已颁发给另一个域名。" #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "其安全证书只会在将来有效。" #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "其安全证书已过期。" #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "登录到%s" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "你现在可以使用账户 %s 了。" #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "错误的用户名或密码" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "出错了" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "服务器无响应" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "在 %s 注册" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "服务器要求在网站上注册" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "打开网站" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "注册" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "检查 %s 以获取如何注册的信息" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "消息太长" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "已編辑" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "发送中…" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "发送失败" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, c-format msgid "%s, %s and %i others are typing…" msgstr "%s, %s 和其他 %i 个人正在输入…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "%s, %s 和 %s 正在输入…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "%s 和 %s 正在输入…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "%s 正在输入…" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "通话已开始" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "开始于%s前" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "您在另一个设备上处理了该通话" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "通话已结束" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "结束于%s" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "持续了%s" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "通话已错过" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "您错过了此通话" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "%s错过了此通话" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "通话已拒绝" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "您拒绝了此通话" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "%s错过了此通话" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "通话失败" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "%i小时" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "%i分钟" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "几秒" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "未加密" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "无法发送消息" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "%x,%H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "%x,%l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "%_m 月 %_d 日,%H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "%_m 月 %_d 日,%l∶%M %p" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "%A,%H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "%A,%l∶%M %p" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "该联系人想把你加到他们的联系人列表" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "正在下载 %s …" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "分享了 %s:%s" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "分享了文件:%s" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "分享的文件" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "文件传输失败" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "今天" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "%a,%b%d日" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "发送一个文件" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "更新讯息" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "点击此处以开始对话或加入频道。" #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "你没有开启的对话" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "账号" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "昵称" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "别名" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "增加联系人" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "新消息到达时通知" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "将笑脸转换成 Emoji" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "检查拼写" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "现代 XMPP 聊天客户端" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino 是一个现代的开源聊天桌面客户端。它致力于提供一个清爽又可靠的 Jabber/" "XMPP 体验,同时又保护您的隐私。" #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "它支持 OMEMO 和 OpenPGP 端对端加密并允许配置隐私相关的特性比如已读回执和输入" "提醒。" #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "Dino 从服务器获取消息并和其他设备同步。" #: main/data/global_search.ui:37 msgid "No active search" msgstr "无活动的搜索" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "输入以开始搜索" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "无匹配的消息" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "检查拼写或尝试移除过滤器" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "发送" #: main/data/shortcuts.ui:12 msgid "General" msgstr "常规" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "对话" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "导航" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "转到下一个对话" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "转到上一个对话" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "帐号" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "本地别名" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "没有配置账户" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "添加新账号" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "登录" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "创建账户" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "无法建立安全连接" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "连接" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "选择一个公共服务器" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "或指定一个服务器地址" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "代替登录" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "选择另外一个服务器" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "都准备好了!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "完成" #~ msgid "No active conversations" #~ msgstr "没有活动的会话" #~ msgid "Main window with conversations" #~ msgstr "带有对话的主窗口" #~ msgid "%s, %s and %i others" #~ msgstr "%s、%s 以及 %i 个其他人" #~ msgid "You can now start using %s" #~ msgstr "您现在可以开始使用%s" #~ msgid "Open Registration" #~ msgstr "开放注册" #~ msgid "Save" #~ msgstr "保存" #~ msgid "%s, %s and %s" #~ msgstr "%s、%s 和 %s" #~ msgid "%s and %s" #~ msgstr "%s 和 %s" #~ msgid "is typing…" #~ msgid_plural "are typing…" #~ msgstr[0] "正在输入…" #~ msgid "has stopped typing" #~ msgstr "已经停止输入" #~ msgid "%i search results" #~ msgstr "%i 搜索结果" #~ msgid "Discover real JIDs" #~ msgstr "发现真实 JID" #~ msgid "Who may discover real JIDs?" #~ msgstr "谁可能发现真实 JID?" #~ msgid "Password required for room entry, if any" #~ msgstr "如果有的话需要密码才能进入房间" #~ msgid "Failed connecting to %s" #~ msgstr "连接 %s 失败" #~ msgid "Join Conference" #~ msgstr "加入群聊" #~ msgid "File" #~ msgstr "文件" #~ msgid "Communicate happiness." #~ msgstr "沟通快乐。" #~ msgid "Preferences" #~ msgstr "选项" #~ msgid "Quit" #~ msgstr "退出" #~ msgid "JID should be of the form “user@example.com”" #~ msgstr "JID 必须形如 “user@example.com”" #~ msgid "Copy Link Address" #~ msgstr "复制链接地址" #~ msgid "Copy" #~ msgstr "复制" #~ msgid "Select All" #~ msgstr "全选" #~ msgid "Search" #~ msgstr "搜索" #~ msgid "Send message marker" #~ msgstr "发送消息标记" #~ msgid "Start Chat" #~ msgstr "开始聊天" #~ msgid "Request presence updates" #~ msgstr "请求在线更新" #~ msgid "Join on startup" #~ msgstr "启动时加入" #~ msgid "Add Chat" #~ msgstr "添加聊天" dino-0.3.0/main/po/zh_TW.po0000644000000000000000000010122414202022370014066 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-12-10 19:29+0000\n" "Language-Team: none\n" "Language: zh_TW\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Weblate 4.4-dev\n" #: main/src/ui/file_send_overlay.vala:85 msgid "The file exceeds the server's maximum upload size." msgstr "檔案大小超過了伺服器的的上傳限制。" #: main/src/ui/conversation_selector/conversation_selector_row.vala:165 #: main/src/ui/conversation_selector/conversation_selector_row.vala:190 #: main/src/ui/conversation_selector/conversation_selector_row.vala:205 #: main/src/ui/util/helper.vala:122 main/src/ui/util/helper.vala:126 #: main/src/ui/util/helper.vala:134 msgid "Me" msgstr "我" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "Image sent" msgstr "影像已傳送" #: main/src/ui/conversation_selector/conversation_selector_row.vala:196 #: main/src/ui/notifier_gnotifications.vala:36 #: main/src/ui/notifier_freedesktop.vala:71 msgid "File sent" msgstr "檔案已傳送" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "Image received" msgstr "影像已接收" #: main/src/ui/conversation_selector/conversation_selector_row.vala:198 #: main/src/ui/notifier_gnotifications.vala:38 #: main/src/ui/notifier_freedesktop.vala:73 msgid "File received" msgstr "檔案已接收" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Outgoing call" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:207 #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 #: main/src/ui/conversation_content_view/call_widget.vala:166 msgid "Incoming call" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:342 #: main/src/ui/conversation_content_view/date_separator_populator.vala:126 #, no-c-format msgid "%b %d" msgstr "%b%d日" #: main/src/ui/conversation_selector/conversation_selector_row.vala:346 #: main/src/ui/conversation_content_view/date_separator_populator.vala:117 msgid "Yesterday" msgstr "昨天" #: main/src/ui/conversation_selector/conversation_selector_row.vala:349 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:303 #, no-c-format msgid "%H∶%M" msgstr "%H:%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:350 #: main/src/ui/conversation_content_view/call_widget.vala:173 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:304 #, no-c-format msgid "%l∶%M %p" msgstr "%p%l∶%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:353 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:308 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "%i 分鐘前" #: main/src/ui/conversation_selector/conversation_selector_row.vala:355 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:310 msgid "Just now" msgstr "剛才" #: main/src/ui/occupant_menu/list.vala:105 msgid "Owner" msgstr "主人" #: main/src/ui/occupant_menu/list.vala:107 msgid "Admin" msgstr "管理員" #: main/src/ui/occupant_menu/list.vala:109 msgid "Member" msgstr "會員" #: main/src/ui/occupant_menu/list.vala:111 msgid "User" msgstr "使用者" #: main/src/ui/occupant_menu/view.vala:28 #: main/src/ui/occupant_menu/view.vala:146 #: main/src/ui/call_window/call_window_controller.vala:129 msgid "Invite" msgstr "邀請" #: main/src/ui/occupant_menu/view.vala:83 msgid "Start private conversation" msgstr "開始私人對話" #: main/src/ui/occupant_menu/view.vala:91 msgid "Kick" msgstr "踢走" #: main/src/ui/occupant_menu/view.vala:97 msgid "Grant write permission" msgstr "同意寫入權限" #: main/src/ui/occupant_menu/view.vala:103 msgid "Revoke write permission" msgstr "撤回寫入權限" #: main/src/ui/occupant_menu/view.vala:145 msgid "Invite to Conference" msgstr "邀請加入聊天室" #: main/src/ui/conversation_view_controller.vala:219 msgid "Select file" msgstr "選取檔案" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "選擇" #: main/src/ui/conversation_view_controller.vala:219 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/application.vala:308 main/src/ui/manage_accounts/dialog.vala:152 #: main/data/message_item_widget_edit_mode.ui:54 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "取消" #: main/src/ui/util/helper.vala:118 #, c-format msgid "%s from %s" msgstr "%s 來自 %s" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "This conference does not allow you to send messages." msgstr "此聊天室不允許您傳送訊息。" #: main/src/ui/chat_input/chat_input_controller.vala:179 msgid "Request permission" msgstr "請求權限" #: main/src/ui/conversation_titlebar/call_entry.vala:19 msgid "Start call" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:37 msgid "Audio call" msgstr "" #: main/src/ui/conversation_titlebar/call_entry.vala:38 msgid "Video call" msgstr "" #: main/src/ui/conversation_titlebar/search_entry.vala:11 #: main/data/shortcuts.ui:37 msgid "Search messages" msgstr "搜尋訊息" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "會員" #: main/src/ui/call_window/participant_widget.vala:96 msgid "Debug information" msgstr "" #: main/src/ui/call_window/participant_widget.vala:105 #: main/src/ui/conversation_content_view/call_widget.vala:143 msgid "Calling…" msgstr "" #: main/src/ui/call_window/participant_widget.vala:107 msgid "Ringing…" msgstr "" #: main/src/ui/call_window/participant_widget.vala:109 #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connecting…" msgstr "正在連線…" #: main/src/ui/call_window/call_window.vala:181 #, c-format msgid "%s ended the call" msgstr "" #: main/src/ui/call_window/call_window.vala:183 #, c-format msgid "%s declined the call" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:25 msgid "Cameras" msgstr "" #: main/src/ui/call_window/video_settings_popover.vala:28 msgid "No camera found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:29 msgid "Microphones" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:32 msgid "No microphone found." msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:85 msgid "Speakers" msgstr "" #: main/src/ui/call_window/audio_settings_popover.vala:88 msgid "No speaker found." msgstr "" #: main/src/ui/call_window/call_window_controller.vala:128 msgid "Invite to Call" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/data/conversation_list_titlebar_csd.ui:12 main/data/menu_add.ui:7 #: main/data/shortcuts.ui:17 main/data/conversation_list_titlebar.ui:16 msgid "Start Conversation" msgstr "開始對話" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "開始" #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/application.vala:308 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "加入聊天室" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "下一個" #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 #: main/src/ui/application.vala:308 msgid "Join" msgstr "加入" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "返回" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joining…" msgstr "正在加入…" #: main/src/ui/add_conversation/conference_details_fragment.vala:170 msgid "Password required to enter room" msgstr "需要密碼才能加入聊天室" #: main/src/ui/add_conversation/conference_details_fragment.vala:175 msgid "Banned from joining or creating conference" msgstr "被禁止加入或建立聊天室" #: main/src/ui/add_conversation/conference_details_fragment.vala:177 msgid "Room does not exist" msgstr "聊天室不存在" #: main/src/ui/add_conversation/conference_details_fragment.vala:179 msgid "Not allowed to create room" msgstr "不允許建立聊天室" #: main/src/ui/add_conversation/conference_details_fragment.vala:181 msgid "Members-only room" msgstr "聊天室不對外開放" #: main/src/ui/add_conversation/conference_details_fragment.vala:184 msgid "Choose a different nick" msgstr "選取一個不同的暱稱" #: main/src/ui/add_conversation/conference_details_fragment.vala:186 msgid "Too many occupants in room" msgstr "聊天室人數過多" #: main/src/ui/add_conversation/conference_details_fragment.vala:189 #: main/src/ui/notifier_gnotifications.vala:104 #: main/src/ui/notifier_freedesktop.vala:185 #: main/src/ui/manage_accounts/add_account_dialog.vala:261 #, c-format msgid "Could not connect to %s" msgstr "無法連線到 %s" #: main/src/ui/add_conversation/conference_details_fragment.vala:192 #: main/src/ui/manage_accounts/add_account_dialog.vala:266 #: main/src/ui/manage_accounts/add_account_dialog.vala:299 #: main/src/ui/manage_accounts/add_account_dialog.vala:309 #: main/src/ui/manage_accounts/add_account_dialog.vala:397 msgid "Invalid address" msgstr "無效的位址" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:26 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "新增" #: main/src/ui/application.vala:213 main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "鍵盤快捷鍵" #: main/src/ui/application.vala:284 main/data/menu_app.ui:21 msgid "About Dino" msgstr "關於 Dino" #: main/src/ui/global_search.vala:147 #, c-format msgid "%i search result" msgid_plural "%i search results" msgstr[0] "%i 個搜尋結果" #: main/src/ui/global_search.vala:174 #, c-format msgid "In %s" msgstr "在 %s" #: main/src/ui/global_search.vala:174 #, c-format msgid "With %s" msgstr "和 %s" #: main/src/ui/notifier_gnotifications.vala:70 #: main/src/ui/notifier_freedesktop.vala:113 #: main/src/ui/conversation_content_view/call_widget.vala:128 msgid "Incoming video call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming video group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:72 #: main/src/ui/notifier_freedesktop.vala:115 #: main/src/ui/conversation_content_view/call_widget.vala:130 msgid "Incoming group call" msgstr "" #: main/src/ui/notifier_gnotifications.vala:80 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:220 main/data/call_widget.ui:80 msgid "Reject" msgstr "" #: main/src/ui/notifier_gnotifications.vala:81 #: main/src/ui/notifier_gnotifications.vala:97 #: main/src/ui/notifier_gnotifications.vala:131 #: main/src/ui/notifier_gnotifications.vala:147 #: main/src/ui/notifier_freedesktop.vala:123 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:220 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:37 #: main/data/call_widget.ui:89 msgid "Accept" msgstr "同意" #: main/src/ui/notifier_gnotifications.vala:91 #: main/src/ui/notifier_freedesktop.vala:155 msgid "Subscription request" msgstr "線上狀態訂閱請求" #: main/src/ui/notifier_gnotifications.vala:98 #: main/src/ui/notifier_gnotifications.vala:130 #: main/src/ui/notifier_gnotifications.vala:146 #: main/src/ui/notifier_freedesktop.vala:161 #: main/src/ui/notifier_freedesktop.vala:253 #: main/src/ui/conversation_content_view/subscription_notification.vala:38 msgid "Deny" msgstr "拒絕" #: main/src/ui/notifier_gnotifications.vala:120 #: main/src/ui/notifier_freedesktop.vala:211 #, c-format msgid "Invitation to %s" msgstr "邀請加入 %s" #: main/src/ui/notifier_gnotifications.vala:121 #: main/src/ui/notifier_freedesktop.vala:212 #, c-format msgid "%s invited you to %s" msgstr "%s 邀請您加入 %s" #: main/src/ui/notifier_gnotifications.vala:138 #: main/src/ui/notifier_freedesktop.vala:244 msgid "Permission request" msgstr "權限請求" #: main/src/ui/notifier_gnotifications.vala:139 #: main/src/ui/notifier_freedesktop.vala:245 #, c-format msgid "%s requests the permission to write in %s" msgstr "%s 請求權限以寫入 %s" #: main/src/ui/contact_details/settings_provider.vala:12 #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "設定" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "本地設定" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "傳送「正在輸入」提示" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "傳送已讀回條" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "通知" #: main/src/ui/contact_details/settings_provider.vala:83 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "On" msgstr "啓用" #: main/src/ui/contact_details/settings_provider.vala:85 #: main/src/ui/contact_details/settings_provider.vala:123 #: main/src/ui/contact_details/settings_provider.vala:126 msgid "Off" msgstr "停用" #: main/src/ui/contact_details/settings_provider.vala:87 msgid "Only when mentioned" msgstr "僅限被提及時" #: main/src/ui/contact_details/settings_provider.vala:89 #: main/src/ui/contact_details/settings_provider.vala:124 #, c-format msgid "Default: %s" msgstr "預設:%s" #: main/src/ui/contact_details/permissions_provider.vala:23 msgid "Request" msgstr "請求" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Permissions" msgstr "權限" #: main/src/ui/contact_details/permissions_provider.vala:25 msgid "Request permission to send messages" msgstr "請求權限以傳送訊息" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "聊天室詳細資訊" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 msgid "Contact Details" msgstr "聯絡人詳細資訊" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "封鎖" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "封鎖雙方向的通訊以及狀態更新" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "聊天室名稱" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "聊天室介紹" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "保留" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "聊天室在最後一名成員離開後仍然保留" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "可被公開搜尋" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "成員可以變更主題" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Permission to view JIDs" msgstr "JID 檢視權限" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who is allowed to view the occupants' JIDs?" msgstr "誰可以檢視成員的 JID?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 msgid "Password" msgstr "密碼" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "A password to restrict access to the room" msgstr "加入聊天室需要的密碼" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "發言" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "僅限有發言權的成員可以傳送訊息" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "僅限會員" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "只有會員才可以加入聊天室" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "聊天紀錄" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "聊天室儲存的最大聊天紀錄數目" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "聊天室設定" #: main/src/ui/main_window.vala:198 msgid "Welcome to Dino!" msgstr "歡迎來到 Dino!" #: main/src/ui/main_window.vala:199 msgid "Sign in or create an account to get started." msgstr "開始前請先登入或建立一個帳號。" #: main/src/ui/main_window.vala:200 msgid "Set up account" msgstr "設定帳號" #: main/src/ui/main_window.vala:208 msgid "No active accounts" msgstr "没有啓用的帳號" #: main/src/ui/main_window.vala:209 msgid "Manage accounts" msgstr "管理帳號" #: main/src/ui/notifier_freedesktop.vala:189 #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "密碼錯誤" #: main/src/ui/notifier_freedesktop.vala:192 #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "無效的 TLS 憑證" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "移除帳號 %s ?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "移除" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "選取頭像" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "影像" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "所有檔案" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "已連線" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "連線已斷開" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "錯誤" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "新增帳號" #: main/src/ui/manage_accounts/add_account_dialog.vala:153 #, c-format msgid "The server could not prove that it is %s." msgstr "該伺服器無法證明其為 %s。" #: main/src/ui/manage_accounts/add_account_dialog.vala:155 msgid "Its security certificate is not trusted by your operating system." msgstr "其安全憑證不被您的作業系統信任。" #: main/src/ui/manage_accounts/add_account_dialog.vala:157 msgid "Its security certificate is issued to another domain." msgstr "其安全憑證已被簽發給另一域名。" #: main/src/ui/manage_accounts/add_account_dialog.vala:159 msgid "Its security certificate will only become valid in the future." msgstr "其安全憑證在未來方會生效。" #: main/src/ui/manage_accounts/add_account_dialog.vala:161 msgid "Its security certificate is expired." msgstr "其安全憑證已過時。" #: main/src/ui/manage_accounts/add_account_dialog.vala:180 #, c-format msgid "Sign in to %s" msgstr "登入到 %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:224 #, c-format msgid "You can now use the account %s." msgstr "您現在可以使用帳號 %s 了。" #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Wrong username or password" msgstr "使用者名稱或密碼錯誤" #: main/src/ui/manage_accounts/add_account_dialog.vala:284 msgid "Something went wrong" msgstr "出現了錯誤" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 msgid "No response from server" msgstr "伺服器没有回應" #: main/src/ui/manage_accounts/add_account_dialog.vala:333 #, c-format msgid "Register on %s" msgstr "在 %s 註冊" #: main/src/ui/manage_accounts/add_account_dialog.vala:336 msgid "The server requires to sign up through a website" msgstr "伺服器要求通過網頁進行註冊" #: main/src/ui/manage_accounts/add_account_dialog.vala:338 msgid "Open website" msgstr "開啓網頁" #: main/src/ui/manage_accounts/add_account_dialog.vala:359 msgid "Register" msgstr "註冊" #: main/src/ui/manage_accounts/add_account_dialog.vala:361 #, c-format msgid "Check %s for information on how to sign up" msgstr "查閱 %s 以取得關於如何註冊的資訊" #: main/src/ui/conversation_content_view/message_widget.vala:194 msgid "Message too long" msgstr "訊息太長" #: main/src/ui/conversation_content_view/message_widget.vala:220 msgid "edited" msgstr "已編輯" #: main/src/ui/conversation_content_view/message_widget.vala:229 msgid "pending…" msgstr "" #: main/src/ui/conversation_content_view/message_widget.vala:242 msgid "delivery failed" msgstr "" #: main/src/ui/conversation_content_view/chat_state_populator.vala:110 #, c-format msgid "%s, %s and %i others are typing…" msgstr "%s, %s 和其他 %i 個人正在輸入…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:112 #, c-format msgid "%s, %s and %s are typing…" msgstr "%s, %s 和 %s 正在輸入…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:114 #, c-format msgid "%s and %s are typing…" msgstr "%s 和 %s 正在輸入…" #: main/src/ui/conversation_content_view/chat_state_populator.vala:116 #, c-format msgid "%s is typing…" msgstr "%s 正在輸入…" #: main/src/ui/conversation_content_view/call_widget.vala:150 msgid "Call started" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:152 #, c-format msgid "Started %s ago" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:167 msgid "You handled this call on another device" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:172 msgid "Call ended" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:175 #, c-format msgid "Ended at %s" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:177 #, c-format msgid "Lasted %s" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:181 msgid "Call missed" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:183 msgid "You missed this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:186 #, c-format msgid "%s missed this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:191 msgid "Call declined" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:193 msgid "You declined this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:196 #, c-format msgid "%s declined this call" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:201 msgid "Call failed" msgstr "" #: main/src/ui/conversation_content_view/call_widget.vala:225 #, c-format msgid "%i hour" msgid_plural "%i hours" msgstr[0] "" #: main/src/ui/conversation_content_view/call_widget.vala:232 #, c-format msgid "%i minute" msgid_plural "%i minutes" msgstr[0] "" #: main/src/ui/conversation_content_view/call_widget.vala:239 msgid "a few seconds" msgstr "" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:188 #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "未加密" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:232 msgid "Unable to send message" msgstr "無法傳送訊息" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:291 #, no-c-format msgid "%x, %H∶%M" msgstr "%x,%H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:292 #, no-c-format msgid "%x, %l∶%M %p" msgstr "%x,%p%l∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:295 #, no-c-format msgid "%b %d, %H∶%M" msgstr "%b%d日,%H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:296 #, no-c-format msgid "%b %d, %l∶%M %p" msgstr "%b%d日,%p%l:%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:299 #, no-c-format msgid "%a, %H∶%M" msgstr "%A,%H∶%M" #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:300 #, no-c-format msgid "%a, %l∶%M %p" msgstr "%A,%p%l∶%M" #: main/src/ui/conversation_content_view/file_widget.vala:175 #: main/src/ui/conversation_content_view/file_default_widget.vala:29 msgid "Save as…" msgstr "" #: main/src/ui/conversation_content_view/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "此聯絡人想把您新增到他們的聯絡人清單" #: main/src/ui/conversation_content_view/file_default_widget.vala:28 msgid "Open" msgstr "" #: main/src/ui/conversation_content_view/file_default_widget.vala:59 #, c-format msgid "Downloading %s…" msgstr "正在下載 %s …" #: main/src/ui/conversation_content_view/file_default_widget.vala:65 #, c-format msgid "%s offered: %s" msgstr "分享了 %s:%s" #: main/src/ui/conversation_content_view/file_default_widget.vala:67 #, c-format msgid "File offered: %s" msgstr "分享了檔案:%s" #: main/src/ui/conversation_content_view/file_default_widget.vala:69 msgid "File offered" msgstr "分享的檔案" #: main/src/ui/conversation_content_view/file_default_widget.vala:75 msgid "File transfer failed" msgstr "檔案傳送失敗" #: main/src/ui/conversation_content_view/date_separator_populator.vala:111 msgid "Today" msgstr "今天" #: main/src/ui/conversation_content_view/date_separator_populator.vala:124 msgid "%a, %b %d" msgstr "%b%d日,%A" #: main/data/chat_input.ui:22 main/data/file_send_overlay.ui:28 #: main/data/shortcuts.ui:44 msgid "Send a file" msgstr "傳送檔案" #: main/data/message_item_widget_edit_mode.ui:60 msgid "Update message" msgstr "更新訊息" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "點擊此處開始對話或加入聊天室。" #: main/data/unified_main_content.ui:100 msgid "You have no open chats" msgstr "您没有開啓的對話" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "帳號" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "暱稱" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "別名" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "新增聯絡人" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "有新訊息時提醒" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "將文字表情轉換爲表情圖標" #: main/data/settings_dialog.ui:70 msgid "Check spelling" msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "現代化的 XMPP 用戶端聊天軟件" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino 是一個爲桌面打造的現代化開放原始碼用戶端聊天軟件。它致力於提供一份簡洁而" "可靠的 Jabber/XMPP 體驗,同時亦尊重您的私隱。" #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "它支持 OMEMO 和 OpenPGP 兩種端到端加密方式,並且容許設定私隱相關的特性譬如已" "讀回條和正在輸入提示。" #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "Dino 從伺服器取得訊息並與其他裝置同步。" #: main/data/global_search.ui:37 msgid "No active search" msgstr "目前没有搜尋" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "輸入關鍵字進行搜尋" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "找不到符合條件的訊息" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "檢查拼字或嘗試移除篩選器" #: main/data/file_send_overlay.ui:80 msgid "Send" msgstr "傳送" #: main/data/shortcuts.ui:12 msgid "General" msgstr "一般" #: main/data/shortcuts.ui:32 msgid "Conversation" msgstr "對話" #: main/data/shortcuts.ui:52 msgid "Navigation" msgstr "導航" #: main/data/shortcuts.ui:57 msgid "Jump to next conversation" msgstr "跳到下一個對話" #: main/data/shortcuts.ui:64 msgid "Jump to previous conversation" msgstr "跳到上一個對話" #: main/data/menu_app.ui:7 main/data/manage_accounts/dialog.ui:9 msgid "Accounts" msgstr "帳號" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "本地別名" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "未有設定好的帳號" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "新增帳號" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "登入" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "建立帳號" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "無法建立安全連線" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "連線" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "選取一個公眾伺服器" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "或指定一個伺服器位址" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "登入" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "選取另一臺伺服器" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "全部設定好了!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "完成" #~ msgid "No active conversations" #~ msgstr "没有進行中的對話" #~ msgid "Main window with conversations" #~ msgstr "含有對話的主視窗" #~ msgid "%s, %s and %i others" #~ msgstr "%s、%s 和另外 %i 人" #~ msgid "You can now start using %s" #~ msgstr "您現在可以開始使用 %s" #~ msgid "Open Registration" #~ msgstr "開啟網頁" #~ msgid "Save" #~ msgstr "儲存" #~ msgid "%s, %s and %s" #~ msgstr "%s、%s 和 %s" #~ msgid "%s and %s" #~ msgstr "%s 和 %s" #~ msgid "is typing…" #~ msgid_plural "are typing…" #~ msgstr[0] "正在輸入…" #~ msgid "has stopped typing" #~ msgstr "已經停止輸入" #~ msgid "%i search results" #~ msgstr "%i 個搜尋結果" #~ msgid "Discover real JIDs" #~ msgstr "檢閱真正的 JID" #~ msgid "Who may discover real JIDs?" #~ msgstr "誰可以檢閱真正的 JID?" #~ msgid "Password required for room entry, if any" #~ msgstr "加入聊天室需要的密碼,可不填" dino-0.3.0/main/src/0000755000000000000000000000000014202022370012644 5ustar rootrootdino-0.3.0/main/src/emojichooser.c0000644000000000000000000006635714202022370015517 0ustar rootroot/* gtkemojichooser.c: An Emoji chooser widget * Copyright 2017, Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "emojichooser.h" #define BOX_SPACE 6 typedef struct { GtkWidget *box; GtkWidget *heading; GtkWidget *button; const char *first; gunichar label; gboolean empty; } EmojiSection; struct _DinoEmojiChooser { GtkPopover parent_instance; GtkWidget *search_entry; GtkWidget *stack; GtkWidget *scrolled_window; int emoji_max_width; EmojiSection recent; EmojiSection people; EmojiSection body; EmojiSection nature; EmojiSection food; EmojiSection travel; EmojiSection activities; EmojiSection objects; EmojiSection symbols; EmojiSection flags; GtkGesture *recent_long_press; GtkGesture *recent_multi_press; GtkGesture *people_long_press; GtkGesture *people_multi_press; GtkGesture *body_long_press; GtkGesture *body_multi_press; GVariant *data; GtkWidget *box; GVariantIter *iter; guint populate_idle; GSettings *settings; }; struct _DinoEmojiChooserClass { GtkPopoverClass parent_class; gboolean (* popover_button_release_event) (GtkWidget *widget, GdkEventButton *event); }; enum { EMOJI_PICKED, LAST_SIGNAL }; static int signals[LAST_SIGNAL]; G_DEFINE_TYPE (DinoEmojiChooser, dino_emoji_chooser, GTK_TYPE_POPOVER) static void dino_emoji_chooser_finalize (GObject *object) { DinoEmojiChooser *chooser = DINO_EMOJI_CHOOSER (object); if (chooser->populate_idle) g_source_remove (chooser->populate_idle); g_variant_unref (chooser->data); g_object_unref (chooser->settings); g_clear_object (&chooser->recent_long_press); g_clear_object (&chooser->recent_multi_press); g_clear_object (&chooser->people_long_press); g_clear_object (&chooser->people_multi_press); g_clear_object (&chooser->body_long_press); g_clear_object (&chooser->body_multi_press); G_OBJECT_CLASS (dino_emoji_chooser_parent_class)->finalize (object); } static void scroll_to_section (GtkButton *button, gpointer data) { EmojiSection *section = data; DinoEmojiChooser *chooser; GtkAdjustment *adj; GtkAllocation alloc = { 0, 0, 0, 0 }; chooser = DINO_EMOJI_CHOOSER (gtk_widget_get_ancestor (GTK_WIDGET (button), GTK_TYPE_EMOJI_CHOOSER)); adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (chooser->scrolled_window)); if (section->heading) gtk_widget_get_allocation (section->heading, &alloc); gtk_adjustment_set_value (adj, alloc.y - BOX_SPACE); } static void add_emoji (GtkWidget *box, gboolean prepend, GVariant *item, gunichar modifier, DinoEmojiChooser *chooser); #define MAX_RECENT (7*3) static void populate_recent_section (DinoEmojiChooser *chooser) { GVariant *variant; GVariant *item; GVariantIter iter; gboolean empty = FALSE; variant = g_settings_get_value (chooser->settings, "recent-emoji"); g_variant_iter_init (&iter, variant); while ((item = g_variant_iter_next_value (&iter))) { GVariant *emoji_data; gunichar modifier; emoji_data = g_variant_get_child_value (item, 0); g_variant_get_child (item, 1, "u", &modifier); add_emoji (chooser->recent.box, FALSE, emoji_data, modifier, chooser); g_variant_unref (emoji_data); g_variant_unref (item); empty = FALSE; } if (!empty) { gtk_widget_show (chooser->recent.box); gtk_widget_set_sensitive (chooser->recent.button, TRUE); } g_variant_unref (variant); } static void add_recent_item (DinoEmojiChooser *chooser, GVariant *item, gunichar modifier) { GList *children, *l; int i; GVariantBuilder builder; g_variant_ref (item); g_variant_builder_init (&builder, G_VARIANT_TYPE ("a((auss)u)")); g_variant_builder_add (&builder, "(@(auss)u)", item, modifier); children = gtk_container_get_children (GTK_CONTAINER (chooser->recent.box)); for (l = children, i = 1; l; l = l->next, i++) { GVariant *item2 = g_object_get_data (G_OBJECT (l->data), "emoji-data"); gunichar modifier2 = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (l->data), "modifier")); if (modifier == modifier2 && g_variant_equal (item, item2)) { gtk_widget_destroy (GTK_WIDGET (l->data)); i--; continue; } if (i >= MAX_RECENT) { gtk_widget_destroy (GTK_WIDGET (l->data)); continue; } g_variant_builder_add (&builder, "(@(auss)u)", item2, modifier2); } g_list_free (children); add_emoji (chooser->recent.box, TRUE, item, modifier, chooser); /* Enable recent */ gtk_widget_show (chooser->recent.box); gtk_widget_set_sensitive (chooser->recent.button, TRUE); g_settings_set_value (chooser->settings, "recent-emoji", g_variant_builder_end (&builder)); g_variant_unref (item); } static void emoji_activated (GtkFlowBox *box, GtkFlowBoxChild *child, gpointer data) { DinoEmojiChooser *chooser = data; char *text; GtkWidget *ebox; GtkWidget *label; GVariant *item; gunichar modifier; gtk_popover_popdown (GTK_POPOVER (chooser)); ebox = gtk_bin_get_child (GTK_BIN (child)); label = gtk_bin_get_child (GTK_BIN (ebox)); text = g_strdup (gtk_label_get_label (GTK_LABEL (label))); item = (GVariant*) g_object_get_data (G_OBJECT (child), "emoji-data"); modifier = (gunichar) GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (child), "modifier")); add_recent_item (chooser, item, modifier); g_signal_emit (data, signals[EMOJI_PICKED], 0, text); g_free (text); } static gboolean has_variations (GVariant *emoji_data) { GVariant *codes; gsize i; gboolean has_variations; has_variations = FALSE; codes = g_variant_get_child_value (emoji_data, 0); for (i = 0; i < g_variant_n_children (codes); i++) { gunichar code; g_variant_get_child (codes, i, "u", &code); if (code == 0) { has_variations = TRUE; break; } } g_variant_unref (codes); return has_variations; } static void show_variations (DinoEmojiChooser *chooser, GtkWidget *child) { GtkWidget *popover; GtkWidget *view; GtkWidget *box; GVariant *emoji_data; GtkWidget *parent_popover; gunichar modifier; if (!child) return; emoji_data = (GVariant*) g_object_get_data (G_OBJECT (child), "emoji-data"); if (!emoji_data) return; if (!has_variations (emoji_data)) return; parent_popover = gtk_widget_get_ancestor (child, GTK_TYPE_POPOVER); popover = gtk_popover_new (child); view = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); gtk_style_context_add_class (gtk_widget_get_style_context (view), "view"); box = gtk_flow_box_new (); gtk_flow_box_set_homogeneous (GTK_FLOW_BOX (box), TRUE); gtk_flow_box_set_min_children_per_line (GTK_FLOW_BOX (box), 6); gtk_flow_box_set_max_children_per_line (GTK_FLOW_BOX (box), 6); gtk_flow_box_set_activate_on_single_click (GTK_FLOW_BOX (box), TRUE); gtk_flow_box_set_selection_mode (GTK_FLOW_BOX (box), GTK_SELECTION_NONE); gtk_container_add (GTK_CONTAINER (popover), view); gtk_container_add (GTK_CONTAINER (view), box); g_signal_connect (box, "child-activated", G_CALLBACK (emoji_activated), parent_popover); add_emoji (box, FALSE, emoji_data, 0, chooser); for (modifier = 0x1f3fb; modifier <= 0x1f3ff; modifier++) add_emoji (box, FALSE, emoji_data, modifier, chooser); gtk_widget_show_all (view); gtk_popover_popup (GTK_POPOVER (popover)); } static void update_hover (GtkWidget *widget, GdkEvent *event, gpointer data) { if (event->type == GDK_ENTER_NOTIFY) gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_PRELIGHT, FALSE); else gtk_widget_unset_state_flags (widget, GTK_STATE_FLAG_PRELIGHT); } static void long_pressed_cb (GtkGesture *gesture, double x, double y, gpointer data) { DinoEmojiChooser *chooser = data; GtkWidget *box; GtkWidget *child; box = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture)); child = GTK_WIDGET (gtk_flow_box_get_child_at_pos (GTK_FLOW_BOX (box), x, y)); show_variations (chooser, child); } static void pressed_cb (GtkGesture *gesture, int n_press, double x, double y, gpointer data) { DinoEmojiChooser *chooser = data; GtkWidget *box; GtkWidget *child; box = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture)); child = GTK_WIDGET (gtk_flow_box_get_child_at_pos (GTK_FLOW_BOX (box), x, y)); show_variations (chooser, child); } static gboolean popup_menu (GtkWidget *widget, gpointer data) { DinoEmojiChooser *chooser = data; show_variations (chooser, widget); return TRUE; } static void add_emoji (GtkWidget *box, gboolean prepend, GVariant *item, gunichar modifier, DinoEmojiChooser *chooser) { GtkWidget *child; GtkWidget *ebox; GtkWidget *label; PangoAttrList *attrs; GVariant *codes; char text[64]; char *p = text; gsize i; PangoLayout *layout; PangoRectangle rect; codes = g_variant_get_child_value (item, 0); for (i = 0; i < g_variant_n_children (codes); i++) { gunichar code; g_variant_get_child (codes, i, "u", &code); if (code == 0) code = modifier; if (code != 0) p += g_unichar_to_utf8 (code, p); } g_variant_unref (codes); p += g_unichar_to_utf8 (0xFE0F, p); /* U+FE0F is the Emoji variation selector */ p[0] = 0; label = gtk_label_new (text); attrs = pango_attr_list_new (); pango_attr_list_insert (attrs, pango_attr_scale_new (PANGO_SCALE_X_LARGE)); gtk_label_set_attributes (GTK_LABEL (label), attrs); pango_attr_list_unref (attrs); layout = gtk_label_get_layout (GTK_LABEL (label)); pango_layout_get_extents (layout, &rect, NULL); /* Check for fallback rendering that generates too wide items */ if (pango_layout_get_unknown_glyphs_count (layout) > 0 || rect.width >= 1.5 * chooser->emoji_max_width) { gtk_widget_destroy (label); return; } child = gtk_flow_box_child_new (); gtk_style_context_add_class (gtk_widget_get_style_context (child), "emoji"); g_object_set_data_full (G_OBJECT (child), "emoji-data", g_variant_ref (item), (GDestroyNotify)g_variant_unref); if (modifier != 0) g_object_set_data (G_OBJECT (child), "modifier", GUINT_TO_POINTER (modifier)); ebox = gtk_event_box_new (); gtk_widget_add_events (ebox, GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK); g_signal_connect (ebox, "enter-notify-event", G_CALLBACK (update_hover), FALSE); g_signal_connect (ebox, "leave-notify-event", G_CALLBACK (update_hover), FALSE); gtk_container_add (GTK_CONTAINER (child), ebox); gtk_container_add (GTK_CONTAINER (ebox), label); gtk_widget_show_all (child); if (chooser) g_signal_connect (child, "popup-menu", G_CALLBACK (popup_menu), chooser); gtk_flow_box_insert (GTK_FLOW_BOX (box), child, prepend ? 0 : -1); } static gboolean populate_emoji_chooser (gpointer data) { DinoEmojiChooser *chooser = data; GBytes *bytes = NULL; GVariant *item; guint64 start, now; start = g_get_monotonic_time (); if (!chooser->data) { bytes = g_resources_lookup_data ("/org/gtk/libgtk/emoji/emoji.data", 0, NULL); if (bytes == NULL) { bytes = g_resources_lookup_data ("/org/gtk/libgtk/emoji/en.data", 0, NULL); } chooser->data = g_variant_ref_sink (g_variant_new_from_bytes (G_VARIANT_TYPE ("a(auss)"), bytes, TRUE)); } if (!chooser->iter) { chooser->iter = g_variant_iter_new (chooser->data); chooser->box = chooser->people.box; } while ((item = g_variant_iter_next_value (chooser->iter))) { const char *name; g_variant_get_child (item, 1, "&s", &name); if (g_strcmp0 (name, chooser->body.first) == 0) chooser->box = chooser->body.box; else if (g_strcmp0 (name, chooser->nature.first) == 0) chooser->box = chooser->nature.box; else if (g_strcmp0 (name, chooser->food.first) == 0) chooser->box = chooser->food.box; else if (g_strcmp0 (name, chooser->travel.first) == 0) chooser->box = chooser->travel.box; else if (g_strcmp0 (name, chooser->activities.first) == 0) chooser->box = chooser->activities.box; else if (g_strcmp0 (name, chooser->objects.first) == 0) chooser->box = chooser->objects.box; else if (g_strcmp0 (name, chooser->symbols.first) == 0) chooser->box = chooser->symbols.box; else if (g_strcmp0 (name, chooser->flags.first) == 0) chooser->box = chooser->flags.box; add_emoji (chooser->box, FALSE, item, 0, chooser); g_variant_unref (item); now = g_get_monotonic_time (); if (now > start + 8000) return G_SOURCE_CONTINUE; } /* We scroll to the top on show, so check the right button for the 1st time */ gtk_widget_set_state_flags (chooser->recent.button, GTK_STATE_FLAG_CHECKED, FALSE); g_variant_iter_free (chooser->iter); chooser->iter = NULL; chooser->box = NULL; chooser->populate_idle = 0; return G_SOURCE_REMOVE; } static void adj_value_changed (GtkAdjustment *adj, gpointer data) { DinoEmojiChooser *chooser = data; double value = gtk_adjustment_get_value (adj); EmojiSection const *sections[] = { &chooser->recent, &chooser->people, &chooser->body, &chooser->nature, &chooser->food, &chooser->travel, &chooser->activities, &chooser->objects, &chooser->symbols, &chooser->flags, }; EmojiSection const *select_section = sections[0]; gsize i; /* Figure out which section the current scroll position is within */ for (i = 0; i < G_N_ELEMENTS (sections); ++i) { EmojiSection const *section = sections[i]; GtkAllocation alloc; if (section->heading) gtk_widget_get_allocation (section->heading, &alloc); else gtk_widget_get_allocation (section->box, &alloc); if (value < alloc.y - BOX_SPACE) break; select_section = section; } /* Un/Check the section buttons accordingly */ for (i = 0; i < G_N_ELEMENTS (sections); ++i) { EmojiSection const *section = sections[i]; if (section == select_section) gtk_widget_set_state_flags (section->button, GTK_STATE_FLAG_CHECKED, FALSE); else gtk_widget_unset_state_flags (section->button, GTK_STATE_FLAG_CHECKED); } } static gboolean filter_func (GtkFlowBoxChild *child, gpointer data) { EmojiSection *section = data; DinoEmojiChooser *chooser; GVariant *emoji_data; const char *text; const char *name; gboolean res; res = TRUE; chooser = DINO_EMOJI_CHOOSER (gtk_widget_get_ancestor (GTK_WIDGET (child), GTK_TYPE_EMOJI_CHOOSER)); text = gtk_entry_get_text (GTK_ENTRY (chooser->search_entry)); emoji_data = (GVariant *) g_object_get_data (G_OBJECT (child), "emoji-data"); if (text[0] == 0) goto out; if (!emoji_data) goto out; g_variant_get_child (emoji_data, 1, "&s", &name); res = g_str_match_string (text, name, TRUE); out: if (res) section->empty = FALSE; return res; } static void invalidate_section (EmojiSection *section) { section->empty = TRUE; gtk_flow_box_invalidate_filter (GTK_FLOW_BOX (section->box)); } static void update_headings (DinoEmojiChooser *chooser) { gtk_widget_set_visible (chooser->people.heading, !chooser->people.empty); gtk_widget_set_visible (chooser->people.box, !chooser->people.empty); gtk_widget_set_visible (chooser->body.heading, !chooser->body.empty); gtk_widget_set_visible (chooser->body.box, !chooser->body.empty); gtk_widget_set_visible (chooser->nature.heading, !chooser->nature.empty); gtk_widget_set_visible (chooser->nature.box, !chooser->nature.empty); gtk_widget_set_visible (chooser->food.heading, !chooser->food.empty); gtk_widget_set_visible (chooser->food.box, !chooser->food.empty); gtk_widget_set_visible (chooser->travel.heading, !chooser->travel.empty); gtk_widget_set_visible (chooser->travel.box, !chooser->travel.empty); gtk_widget_set_visible (chooser->activities.heading, !chooser->activities.empty); gtk_widget_set_visible (chooser->activities.box, !chooser->activities.empty); gtk_widget_set_visible (chooser->objects.heading, !chooser->objects.empty); gtk_widget_set_visible (chooser->objects.box, !chooser->objects.empty); gtk_widget_set_visible (chooser->symbols.heading, !chooser->symbols.empty); gtk_widget_set_visible (chooser->symbols.box, !chooser->symbols.empty); gtk_widget_set_visible (chooser->flags.heading, !chooser->flags.empty); gtk_widget_set_visible (chooser->flags.box, !chooser->flags.empty); if (chooser->recent.empty && chooser->people.empty && chooser->body.empty && chooser->nature.empty && chooser->food.empty && chooser->travel.empty && chooser->activities.empty && chooser->objects.empty && chooser->symbols.empty && chooser->flags.empty) gtk_stack_set_visible_child_name (GTK_STACK (chooser->stack), "empty"); else gtk_stack_set_visible_child_name (GTK_STACK (chooser->stack), "list"); } static void search_changed (GtkEntry *entry, gpointer data) { DinoEmojiChooser *chooser = data; invalidate_section (&chooser->recent); invalidate_section (&chooser->people); invalidate_section (&chooser->body); invalidate_section (&chooser->nature); invalidate_section (&chooser->food); invalidate_section (&chooser->travel); invalidate_section (&chooser->activities); invalidate_section (&chooser->objects); invalidate_section (&chooser->symbols); invalidate_section (&chooser->flags); update_headings (chooser); } static void setup_section (DinoEmojiChooser *chooser, EmojiSection *section, const char *first, const char *icon) { GtkAdjustment *adj; GtkWidget *image; section->first = first; image = gtk_bin_get_child (GTK_BIN (section->button)); gtk_image_set_from_icon_name (GTK_IMAGE (image), icon, GTK_ICON_SIZE_BUTTON); adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (chooser->scrolled_window)); gtk_container_set_focus_vadjustment (GTK_CONTAINER (section->box), adj); gtk_flow_box_set_filter_func (GTK_FLOW_BOX (section->box), filter_func, section, NULL); g_signal_connect (section->button, "clicked", G_CALLBACK (scroll_to_section), section); } static void dino_emoji_chooser_init (DinoEmojiChooser *chooser) { GtkAdjustment *adj; chooser->settings = g_settings_new ("org.gtk.Settings.EmojiChooser"); gtk_widget_init_template (GTK_WIDGET (chooser)); /* Get a reasonable maximum width for an emoji. We do this to * skip overly wide fallback rendering for certain emojis the * font does not contain and therefore end up being rendered * as multiply glyphs. */ { PangoLayout *layout = gtk_widget_create_pango_layout (GTK_WIDGET (chooser), "🙂"); PangoAttrList *attrs; PangoRectangle rect; attrs = pango_attr_list_new (); pango_attr_list_insert (attrs, pango_attr_scale_new (PANGO_SCALE_X_LARGE)); pango_layout_set_attributes (layout, attrs); pango_attr_list_unref (attrs); pango_layout_get_extents (layout, &rect, NULL); chooser->emoji_max_width = rect.width; g_object_unref (layout); } chooser->recent_long_press = gtk_gesture_long_press_new (chooser->recent.box); g_signal_connect (chooser->recent_long_press, "pressed", G_CALLBACK (long_pressed_cb), chooser); chooser->recent_multi_press = gtk_gesture_multi_press_new (chooser->recent.box); gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (chooser->recent_multi_press), GDK_BUTTON_SECONDARY); g_signal_connect (chooser->recent_multi_press, "pressed", G_CALLBACK (pressed_cb), chooser); chooser->people_long_press = gtk_gesture_long_press_new (chooser->people.box); g_signal_connect (chooser->people_long_press, "pressed", G_CALLBACK (long_pressed_cb), chooser); chooser->people_multi_press = gtk_gesture_multi_press_new (chooser->people.box); gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (chooser->people_multi_press), GDK_BUTTON_SECONDARY); g_signal_connect (chooser->people_multi_press, "pressed", G_CALLBACK (pressed_cb), chooser); chooser->body_long_press = gtk_gesture_long_press_new (chooser->body.box); g_signal_connect (chooser->body_long_press, "pressed", G_CALLBACK (long_pressed_cb), chooser); chooser->body_multi_press = gtk_gesture_multi_press_new (chooser->body.box); gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (chooser->body_multi_press), GDK_BUTTON_SECONDARY); g_signal_connect (chooser->body_multi_press, "pressed", G_CALLBACK (pressed_cb), chooser); adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (chooser->scrolled_window)); g_signal_connect (adj, "value-changed", G_CALLBACK (adj_value_changed), chooser); setup_section (chooser, &chooser->recent, NULL, "emoji-recent-symbolic"); setup_section (chooser, &chooser->people, "grinning face", "emoji-people-symbolic"); setup_section (chooser, &chooser->body, "selfie", "emoji-body-symbolic"); setup_section (chooser, &chooser->nature, "monkey face", "emoji-nature-symbolic"); setup_section (chooser, &chooser->food, "grapes", "emoji-food-symbolic"); setup_section (chooser, &chooser->travel, "globe showing Europe-Africa", "emoji-travel-symbolic"); setup_section (chooser, &chooser->activities, "jack-o-lantern", "emoji-activities-symbolic"); setup_section (chooser, &chooser->objects, "muted speaker", "emoji-objects-symbolic"); setup_section (chooser, &chooser->symbols, "ATM sign", "emoji-symbols-symbolic"); setup_section (chooser, &chooser->flags, "chequered flag", "emoji-flags-symbolic"); populate_recent_section (chooser); chooser->populate_idle = g_idle_add (populate_emoji_chooser, chooser); g_source_set_name_by_id (chooser->populate_idle, "[gtk] populate_emoji_chooser"); } static void dino_emoji_chooser_show (GtkWidget *widget) { DinoEmojiChooser *chooser = DINO_EMOJI_CHOOSER (widget); GtkAdjustment *adj; GTK_WIDGET_CLASS (dino_emoji_chooser_parent_class)->show (widget); adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (chooser->scrolled_window)); gtk_adjustment_set_value (adj, 0); gtk_entry_set_text (GTK_ENTRY (chooser->search_entry), ""); } static gboolean dino_emoji_chooser_button_release (GtkWidget *widget, GdkEventButton *event) { DinoEmojiChooserClass *klass = DINO_EMOJI_CHOOSER_GET_CLASS(widget); GtkWidget *event_widget = gtk_get_event_widget ((GdkEvent *) event); if (!event_widget && event->window != gtk_widget_get_window (widget)) { return GDK_EVENT_PROPAGATE; } return klass->popover_button_release_event (widget, event); } static void dino_emoji_chooser_class_init (DinoEmojiChooserClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->finalize = dino_emoji_chooser_finalize; widget_class->show = dino_emoji_chooser_show; klass->popover_button_release_event = widget_class->button_release_event; widget_class->button_release_event = dino_emoji_chooser_button_release; signals[EMOJI_PICKED] = g_signal_new ("emoji-picked", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_STRING|G_SIGNAL_TYPE_STATIC_SCOPE); gtk_widget_class_set_template_from_resource (widget_class, "/im/dino/Dino/emojichooser.ui"); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, search_entry); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, stack); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, scrolled_window); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, recent.box); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, recent.button); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, people.box); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, people.heading); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, people.button); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, body.box); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, body.heading); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, body.button); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, nature.box); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, nature.heading); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, nature.button); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, food.box); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, food.heading); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, food.button); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, travel.box); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, travel.heading); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, travel.button); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, activities.box); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, activities.heading); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, activities.button); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, objects.box); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, objects.heading); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, objects.button); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, symbols.box); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, symbols.heading); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, symbols.button); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, flags.box); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, flags.heading); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, flags.button); gtk_widget_class_bind_template_callback (widget_class, emoji_activated); gtk_widget_class_bind_template_callback (widget_class, search_changed); } DinoEmojiChooser * dino_emoji_chooser_new (void) { return DINO_EMOJI_CHOOSER (g_object_new (GTK_TYPE_EMOJI_CHOOSER, NULL)); } dino-0.3.0/main/src/emojichooser.h0000644000000000000000000000330514202022370015504 0ustar rootroot/* gtkemojichooser.h: An Emoji chooser widget * Copyright 2017, Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #pragma once #include G_BEGIN_DECLS #define GTK_TYPE_EMOJI_CHOOSER (dino_emoji_chooser_get_type ()) #define DINO_EMOJI_CHOOSER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_EMOJI_CHOOSER, DinoEmojiChooser)) #define DINO_EMOJI_CHOOSER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_EMOJI_CHOOSER, DinoEmojiChooserClass)) #define GTK_IS_EMOJI_CHOOSER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_EMOJI_CHOOSER)) #define GTK_IS_EMOJI_CHOOSER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_EMOJI_CHOOSER)) #define DINO_EMOJI_CHOOSER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_EMOJI_CHOOSER, DinoEmojiChooserClass)) typedef struct _DinoEmojiChooser DinoEmojiChooser; typedef struct _DinoEmojiChooserClass DinoEmojiChooserClass; GType dino_emoji_chooser_get_type (void) G_GNUC_CONST; DinoEmojiChooser *dino_emoji_chooser_new (void); G_END_DECLS dino-0.3.0/main/src/main.vala0000644000000000000000000000150414202022370014435 0ustar rootrootusing Dino.Entities; using Dino.Ui; extern const string GETTEXT_PACKAGE; extern const string LOCALE_INSTALL_DIR; namespace Dino { void main(string[] args) { try{ string? exec_path = args.length > 0 ? args[0] : null; SearchPathGenerator search_path_generator = new SearchPathGenerator(exec_path); Intl.textdomain(GETTEXT_PACKAGE); internationalize(GETTEXT_PACKAGE, search_path_generator.get_locale_path(GETTEXT_PACKAGE, LOCALE_INSTALL_DIR)); Gtk.init(ref args); Dino.Ui.Application app = new Dino.Ui.Application() { search_path_generator=search_path_generator }; Plugins.Loader loader = new Plugins.Loader(app); loader.load_all(); app.run(args); loader.shutdown(); } catch (Error e) { warning(@"Fatal error: $(e.message)"); } } } dino-0.3.0/main/src/ui/0000755000000000000000000000000014202022370013261 5ustar rootrootdino-0.3.0/main/src/ui/add_conversation/0000755000000000000000000000000014202022370016603 5ustar rootrootdino-0.3.0/main/src/ui/add_conversation/add_conference_dialog.vala0000644000000000000000000002026014202022370023706 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; using Xmpp; using Xmpp.Xep; namespace Dino.Ui { public class AddConferenceDialog : Gtk.Dialog { private Stack stack = new Stack(); private Button cancel_button; private Button ok_button; private Label cancel_label = new Label(_("Cancel")) {visible=true}; private Image cancel_image = new Image.from_icon_name("go-previous-symbolic", IconSize.MENU) {visible=true}; private SelectJidFragment select_fragment; private ConferenceDetailsFragment details_fragment; private ConferenceList conference_list; private StreamInteractor stream_interactor; public AddConferenceDialog(StreamInteractor stream_interactor) { Object(use_header_bar : Util.use_csd() ? 1 : 0); this.title = _("Join Channel"); this.modal = true; this.stream_interactor = stream_interactor; stack.visible = true; stack.vhomogeneous = false; get_content_area().add(stack); setup_headerbar(); setup_jid_add_view(); setup_conference_details_view(); show_jid_add_view(); } private void show_jid_add_view() { // Rewire headerbar (if CSD) if (Util.use_csd()) { if (cancel_image.get_parent() != null) cancel_button.remove(cancel_image); cancel_button.add(cancel_label); cancel_button.clicked.disconnect(show_jid_add_view); cancel_button.clicked.connect(on_cancel); ok_button.label = _("Next"); ok_button.sensitive = select_fragment.done; ok_button.clicked.connect(on_next_button_clicked); details_fragment.fragment_active = false; details_fragment.notify["done"].disconnect(set_ok_sensitive_from_details); select_fragment.notify["done"].connect(set_ok_sensitive_from_select); } stack.transition_type = StackTransitionType.SLIDE_RIGHT; stack.set_visible_child_name("select"); } private void show_conference_details_view() { // Rewire headerbar (if CSD) if (Util.use_csd()) { if (cancel_label.get_parent() != null) cancel_button.remove(cancel_label); cancel_button.add(cancel_image); cancel_button.clicked.disconnect(on_cancel); cancel_button.clicked.connect(show_jid_add_view); ok_button.label = _("Join"); ok_button.sensitive = details_fragment.done; ok_button.clicked.disconnect(on_next_button_clicked); details_fragment.fragment_active = true; select_fragment.notify["done"].disconnect(set_ok_sensitive_from_select); details_fragment.notify["done"].connect(set_ok_sensitive_from_details); } stack.transition_type = StackTransitionType.SLIDE_LEFT; stack.set_visible_child_name("details"); animate_window_resize(); } private void setup_headerbar() { cancel_button = new Button() { visible=true }; ok_button = new Button() { can_focus=true, can_default=true, visible=true }; ok_button.get_style_context().add_class("suggested-action"); if (Util.use_csd()) { HeaderBar header_bar = get_header_bar() as HeaderBar; header_bar.show_close_button = false; header_bar.pack_start(cancel_button); header_bar.pack_end(ok_button); ok_button.has_default = true; } } private void setup_jid_add_view() { conference_list = new ConferenceList(stream_interactor); conference_list.row_activated.connect(() => { ok_button.clicked(); }); select_fragment = new SelectJidFragment(stream_interactor, conference_list, stream_interactor.get_accounts()); select_fragment.add_jid.connect((row) => { AddGroupchatDialog dialog = new AddGroupchatDialog(stream_interactor); dialog.set_transient_for(this); dialog.present(); }); select_fragment.remove_jid.connect((row) => { ConferenceListRow conference_row = row as ConferenceListRow; stream_interactor.get_module(MucManager.IDENTITY).remove_bookmark(conference_row.account, conference_row.bookmark); }); Box wrap_box = new Box(Orientation.VERTICAL, 0) { visible=true }; wrap_box.add(select_fragment); stack.add_named(wrap_box, "select"); if (!Util.use_csd()) { Box box = new Box(Orientation.HORIZONTAL, 5) { halign=Align.END, margin_bottom=15, margin_start=80, margin_end=80, visible=true }; Button ok_button = new Button() { label=_("Next"), sensitive=false, halign = Align.END, can_focus=true, can_default=true, visible=true }; ok_button.get_style_context().add_class("suggested-action"); ok_button.clicked.connect(on_next_button_clicked); select_fragment.notify["done"].connect(() => { ok_button.sensitive = select_fragment.done; }); Button cancel_button = new Button() { label=_("Cancel"), halign=Align.START, visible=true }; cancel_button.clicked.connect(on_cancel); box.add(cancel_button); box.add(ok_button); wrap_box.add(box); ok_button.has_default = true; } } private void setup_conference_details_view() { details_fragment = new ConferenceDetailsFragment(stream_interactor) { ok_button=ok_button }; details_fragment.joined.connect(() => this.close()); Box wrap_box = new Box(Orientation.VERTICAL, 0) { visible=true }; wrap_box.add(details_fragment); if (!Util.use_csd()) { Box box = new Box(Orientation.HORIZONTAL, 5) { halign=Align.END, margin_bottom=15, margin_start=80, margin_end=80, visible=true }; Button ok_button = new Button() { label=_("Join"), halign = Align.END, can_focus=true, can_default=true, visible=true }; ok_button.get_style_context().add_class("suggested-action"); details_fragment.notify["done"].connect(() => { ok_button.sensitive = select_fragment.done; }); details_fragment.ok_button = ok_button; Button cancel_button = new Button() { label=_("Back"), halign=Align.START, visible=true }; cancel_button.clicked.connect(show_jid_add_view); box.add(cancel_button); box.add(ok_button); wrap_box.add(box); } stack.add_named(wrap_box, "details"); } private void set_ok_sensitive_from_select() { ok_button.sensitive = select_fragment.done; } private void set_ok_sensitive_from_details() { ok_button.sensitive = details_fragment.done; } private void on_next_button_clicked() { details_fragment.clear(); ListRow? row = conference_list.get_selected_row() as ListRow; ConferenceListRow? conference_row = conference_list.get_selected_row() as ConferenceListRow; if (conference_row != null) { details_fragment.account = conference_row.account; details_fragment.jid = conference_row.bookmark.jid.to_string(); details_fragment.nick = conference_row.bookmark.nick; if (conference_row.bookmark.password != null) details_fragment.password = conference_row.bookmark.password; ok_button.grab_focus(); } else if (row != null) { details_fragment.account = row.account; details_fragment.jid = row.jid.to_string(); } show_conference_details_view(); } private void on_cancel() { close(); } private void animate_window_resize() { int def_height, curr_width, curr_height; get_size(out curr_width, out curr_height); stack.get_preferred_height(null, out def_height); int difference = def_height - curr_height; Timer timer = new Timer(); Timeout.add((int) (stack.transition_duration / 30), () => { ulong microsec; timer.elapsed(out microsec); ulong millisec = microsec / 1000; double partial = double.min(1, (double) millisec / stack.transition_duration); resize(curr_width, (int) (curr_height + difference * partial)); return millisec < stack.transition_duration; }); } } } dino-0.3.0/main/src/ui/add_conversation/add_contact_dialog.vala0000644000000000000000000000407214202022370023235 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; using Xmpp; namespace Dino.Ui { [GtkTemplate (ui = "/im/dino/Dino/add_conversation/add_contact_dialog.ui")] protected class AddContactDialog : Gtk.Dialog { public Account? account { get { return account_combobox.selected; } set { account_combobox.selected = value; } } public string jid { get { return jid_entry.text; } set { jid_entry.text = value; } } [GtkChild] private unowned AccountComboBox account_combobox; [GtkChild] private unowned Button ok_button; [GtkChild] private unowned Button cancel_button; [GtkChild] private unowned Entry jid_entry; [GtkChild] private unowned Entry alias_entry; private StreamInteractor stream_interactor; public AddContactDialog(StreamInteractor stream_interactor) { Object(use_header_bar : 1); this.stream_interactor = stream_interactor; account_combobox.initialize(stream_interactor); cancel_button.clicked.connect(() => { close(); }); ok_button.clicked.connect(on_ok_button_clicked); jid_entry.changed.connect(on_jid_entry_changed); } private void on_ok_button_clicked() { string? alias = alias_entry.text == "" ? null : alias_entry.text; try { Jid jid = new Jid(jid_entry.text); stream_interactor.get_module(RosterManager.IDENTITY).add_jid(account, jid, alias); stream_interactor.get_module(PresenceManager.IDENTITY).request_subscription(account, jid); close(); } catch (InvalidJidError e) { warning("Tried to add contact with invalid Jid: %s", e.message); } } private void on_jid_entry_changed() { try { Jid parsed_jid = new Jid(jid_entry.text); ok_button. sensitive = parsed_jid != null && parsed_jid.resourcepart == null && stream_interactor.get_module(RosterManager.IDENTITY).get_roster_item(account, parsed_jid) == null; } catch (InvalidJidError e) { ok_button.sensitive = false; } } } } dino-0.3.0/main/src/ui/add_conversation/add_groupchat_dialog.vala0000644000000000000000000000515014202022370023574 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; using Xmpp; namespace Dino.Ui { [GtkTemplate (ui = "/im/dino/Dino/add_conversation/add_groupchat_dialog.ui")] protected class AddGroupchatDialog : Gtk.Dialog { [GtkChild] private unowned Stack accounts_stack; [GtkChild] private unowned AccountComboBox account_combobox; [GtkChild] private unowned Button ok_button; [GtkChild] private unowned Button cancel_button; [GtkChild] private unowned Entry jid_entry; [GtkChild] private unowned Entry alias_entry; [GtkChild] private unowned Entry nick_entry; private StreamInteractor stream_interactor; private bool alias_entry_changed = false; public AddGroupchatDialog(StreamInteractor stream_interactor) { Object(use_header_bar : 1); this.stream_interactor = stream_interactor; ok_button.label = _("Add"); ok_button.get_style_context().add_class("suggested-action"); // TODO why doesn't it work in XML accounts_stack.set_visible_child_name("combobox"); account_combobox.initialize(stream_interactor); cancel_button.clicked.connect(() => { close(); }); ok_button.clicked.connect(on_ok_button_clicked); jid_entry.key_release_event.connect(on_jid_key_release); nick_entry.key_release_event.connect(check_ok); } private bool on_jid_key_release() { check_ok(); if (!alias_entry_changed) { try { Jid parsed_jid = new Jid(jid_entry.text); alias_entry.text = parsed_jid != null && parsed_jid.localpart != null ? parsed_jid.localpart : jid_entry.text; } catch (InvalidJidError e) { alias_entry.text = jid_entry.text; } } return false; } private bool check_ok() { try { Jid parsed_jid = new Jid(jid_entry.text); ok_button.sensitive = parsed_jid != null && parsed_jid.localpart != null && parsed_jid.resourcepart == null; } catch (InvalidJidError e) { ok_button.sensitive = false; } return false; } private void on_ok_button_clicked() { try { Conference conference = new Conference(); conference.jid = new Jid(jid_entry.text); conference.nick = nick_entry.text != "" ? nick_entry.text : null; conference.name = alias_entry.text; stream_interactor.get_module(MucManager.IDENTITY).add_bookmark(account_combobox.selected, conference); close(); } catch (InvalidJidError e) { warning("Ignoring invalid conference Jid: %s", e.message); } } } } dino-0.3.0/main/src/ui/add_conversation/conference_details_fragment.vala0000644000000000000000000002123714202022370025154 0ustar rootrootusing Gdk; using Gtk; using Dino.Entities; using Xmpp; using Xmpp.Xep; namespace Dino.Ui { [GtkTemplate (ui = "/im/dino/Dino/add_conversation/conference_details_fragment.ui")] protected class ConferenceDetailsFragment : Box { public signal void joined(); public bool done { get { try { Jid parsed_jid = new Jid(jid); return parsed_jid.localpart != null && parsed_jid.resourcepart == null && nick != null; } catch (InvalidJidError e) { return false; } } private set {} } public Account account { owned get { return account_combobox.selected; } set { accounts_label.label = value.bare_jid.to_string(); account_combobox.selected = value; if (nick == null && value.alias != null) { nick = value.alias; } accounts_stack.set_visible_child_name("label"); } } public string jid { get { return jid_entry.text; } set { jid_label.label = value; jid_entry.text = value; jid_stack.set_visible_child_name("label"); } } public string? nick { get { return nick_entry.text != "" ? nick_entry.text : null; } set { nick_label.label = value ?? ""; nick_entry.text = value ?? ""; nick_stack.set_visible_child_name("label"); } } public string? password { get { return password_entry.text == "" ? null : password_entry.text; } set { password_label.label = value; password_entry.text = value; nick_stack.set_visible_child_name("label"); } } public bool fragment_active { get; set; default=true; } [GtkChild] private unowned Stack accounts_stack; [GtkChild] private unowned Button accounts_button; [GtkChild] private unowned Label accounts_label; [GtkChild] private unowned AccountComboBox account_combobox; [GtkChild] private unowned Stack jid_stack; [GtkChild] private unowned Button jid_button; [GtkChild] private unowned Label jid_label; [GtkChild] private unowned Entry jid_entry; [GtkChild] private unowned Stack nick_stack; [GtkChild] private unowned Button nick_button; [GtkChild] private unowned Label nick_label; [GtkChild] private unowned Entry nick_entry; [GtkChild] private unowned Stack password_stack; [GtkChild] private unowned Button password_button; [GtkChild] private unowned Label password_label; [GtkChild] private unowned Label password_text_label; [GtkChild] private unowned Entry password_entry; [GtkChild] private unowned Revealer notification_revealer; [GtkChild] private unowned Button notification_button; [GtkChild] private unowned Label notification_label; private StreamInteractor stream_interactor; private Button ok_button_; public Button ok_button { get { return ok_button_; } set { if (value != null) { value.clicked.connect(() => { on_ok_button_clicked.begin(); }); ok_button_ = value; } } } public ConferenceDetailsFragment(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; account_combobox.initialize(stream_interactor); accounts_button.clicked.connect(() => { set_active_stack(accounts_stack); }); jid_button.clicked.connect(() => { set_active_stack(jid_stack); }); nick_button.clicked.connect(() => { set_active_stack(nick_stack); }); password_button.clicked.connect(() => { set_active_stack(password_stack); }); account_combobox.changed.connect(() => { accounts_label.label = account_combobox.selected.bare_jid.to_string(); }); accounts_label.label = account_combobox.selected.bare_jid.to_string(); jid_entry.key_release_event.connect(on_jid_key_release_event); nick_entry.key_release_event.connect(on_nick_key_release_event); password_entry.key_release_event.connect(on_password_key_release_event); jid_entry.key_release_event.connect(() => { done = true; return false; }); // just for notifying nick_entry.key_release_event.connect(() => { done = true; return false; }); notification_button.clicked.connect(() => { notification_revealer.set_reveal_child(false); }); clear(); } public void clear() { jid = ""; nick = ""; password = ""; password_text_label.visible = false; password_stack.visible = false; notification_revealer.set_reveal_child(false); reset_editable(); } public void reset_editable() { jid_stack.set_visible_child_name("entry"); accounts_stack.set_visible_child_name("entry"); nick_stack.set_visible_child_name("entry"); password_stack.set_visible_child_name("entry"); } private async void on_ok_button_clicked() { if (!fragment_active) return; ok_button.label = _("Joining…"); ok_button.sensitive = false; string label_text = ""; try { Jid parsed_jid = new Jid(jid); Muc.JoinResult? join_result = yield stream_interactor.get_module(MucManager.IDENTITY).join(account, parsed_jid, nick, password); ok_button.label = _("Join"); ok_button.sensitive = true; if (join_result == null || join_result.nick != null) { Conversation conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(parsed_jid, account, Conversation.Type.GROUPCHAT); Application app = GLib.Application.get_default() as Application; app.controller.select_conversation(conversation); joined(); return; } if (join_result.muc_error != null) { switch (join_result.muc_error) { case Muc.MucEnterError.PASSWORD_REQUIRED: label_text = _("Password required to enter room"); password_text_label.visible = true; password_stack.visible = true; break; case Muc.MucEnterError.BANNED: label_text = _("Banned from joining or creating conference"); break; case Muc.MucEnterError.ROOM_DOESNT_EXIST: label_text = _("Room does not exist"); break; case Muc.MucEnterError.CREATION_RESTRICTED: label_text = _("Not allowed to create room"); break; case Muc.MucEnterError.NOT_IN_MEMBER_LIST: label_text = _("Members-only room"); break; case Muc.MucEnterError.USE_RESERVED_ROOMNICK: case Muc.MucEnterError.NICK_CONFLICT: label_text = _("Choose a different nick"); break; case Muc.MucEnterError.OCCUPANT_LIMIT_REACHED: label_text = _("Too many occupants in room"); break; } } else if (join_result.stanza_error != null) { label_text = _("Could not connect to %s").printf((new Jid(jid)).domainpart); } } catch (InvalidJidError e) { label_text = _("Invalid address"); } notification_label.label = label_text; notification_revealer.set_reveal_child(true); } private bool on_jid_key_release_event(EventKey event) { jid_label.label = jid_entry.text; if (event.keyval == Key.Return) jid_stack.set_visible_child_name("label"); return false; } private bool on_nick_key_release_event(EventKey event) { nick_label.label = nick_entry.text; if (event.keyval == Key.Return) nick_stack.set_visible_child_name("label"); return false; } private bool on_password_key_release_event(EventKey event) { string filler = ""; for (int i = 0; i < password_entry.text.length; i++) filler += password_entry.get_invisible_char().to_string(); password_label.label = filler; if (event.keyval == Key.Return) password_stack.set_visible_child_name("label"); return false; } private void set_active_stack(Stack stack) { stack.set_visible_child_name("entry"); if (stack != accounts_stack) accounts_stack.set_visible_child_name("label"); if (stack != jid_stack) jid_stack.set_visible_child_name("label"); if (stack != nick_stack) nick_stack.set_visible_child_name("label"); if (stack != password_stack) password_stack.set_visible_child_name("label"); } } } dino-0.3.0/main/src/ui/add_conversation/conference_list.vala0000644000000000000000000001067114202022370022617 0ustar rootrootusing Gee; using Gtk; using Xmpp; using Xmpp.Xep.Bookmarks; using Dino.Entities; namespace Dino.Ui { protected class ConferenceList : FilterableList { public signal void conversation_selected(Conversation? conversation); private StreamInteractor stream_interactor; private HashMap> lists = new HashMap>(Account.hash_func, Account.equals_func); private HashMap> widgets = new HashMap>(Account.hash_func, Account.equals_func); public ConferenceList(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; set_filter_func(filter); set_header_func(header); set_sort_func(sort); stream_interactor.get_module(MucManager.IDENTITY).bookmarks_updated.connect((account, conferences) => { lists[account] = conferences; refresh_conferences(); }); foreach (Account account in stream_interactor.get_accounts()) { stream_interactor.get_module(MucManager.IDENTITY).get_bookmarks.begin(account, (_, res) => { Set? conferences = stream_interactor.get_module(MucManager.IDENTITY).get_bookmarks.end(res); set_bookmarks(account, conferences); }); } stream_interactor.get_module(MucManager.IDENTITY).conference_added.connect(add_conference); stream_interactor.get_module(MucManager.IDENTITY).conference_removed.connect(remove_conference); } private void add_conference(Account account, Conference conference) { if (!widgets.has_key(account)) { widgets[account] = new HashMap(Jid.hash_func, Jid.equals_func); } var widget = new ConferenceListRow(stream_interactor, conference, account); widgets[account][conference.jid] = widget; add(widget); } private void remove_conference(Account account, Jid jid) { if (widgets.has_key(account) && widgets[account].has_key(jid)) { remove(widgets[account][jid]); widgets[account].unset(jid); } } public void refresh_conferences() { @foreach((widget) => { remove(widget); }); foreach (Account account in lists.keys) { foreach (Conference conference in lists[account]) { add_conference(account, conference); } } } private void set_bookmarks(Account account, Set? conferences) { if (conferences == null) { lists.unset(account); } else { lists[account] = conferences; } refresh_conferences(); } private void header(ListBoxRow row, ListBoxRow? before_row) { if (row.get_header() == null && before_row != null) { row.set_header(new Separator(Orientation.HORIZONTAL)); } } private bool filter(ListBoxRow r) { if (r.get_type().is_a(typeof(ListRow))) { ListRow row = r as ListRow; if (filter_values != null) { foreach (string filter in filter_values) { if (!(row.name_label.label.down().contains(filter.down()) || row.jid.to_string().down().contains(filter.down()))) { return false; } } } } return true; } public override int sort(ListBoxRow row1, ListBoxRow row2) { ListRow c1 = (row1 as ListRow); ListRow c2 = (row2 as ListRow); return c1.name_label.label.collate(c2.name_label.label); } } internal class ConferenceListRow : ListRow { public Conference bookmark; public ConferenceListRow(StreamInteractor stream_interactor, Conference bookmark, Account account) { this.jid = bookmark.jid; this.account = account; this.bookmark = bookmark; name_label.label = bookmark.name != null && bookmark.name != "" ? bookmark.name : bookmark.jid.to_string(); if (stream_interactor.get_accounts().size > 1) { via_label.label = "via " + account.bare_jid.to_string(); } else if (bookmark.name != null && bookmark.name != bookmark.jid.to_string()) { via_label.label = bookmark.jid.to_string(); } else { via_label.visible = false; } image.set_conversation(stream_interactor, new Conversation(jid, account, Conversation.Type.GROUPCHAT)); } } } dino-0.3.0/main/src/ui/add_conversation/list_row.vala0000644000000000000000000000234514202022370021316 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; using Xmpp; namespace Dino.Ui { [GtkTemplate (ui = "/im/dino/Dino/add_conversation/list_row.ui")] public class ListRow : ListBoxRow { [GtkChild] public unowned AvatarImage image; [GtkChild] public unowned Label name_label; [GtkChild] public unowned Label via_label; public Jid? jid; public Account? account; public ListRow() {} public ListRow.from_jid(StreamInteractor stream_interactor, Jid jid, Account account, bool show_account) { this.jid = jid; this.account = account; Conversation conv = new Conversation(jid, account, Conversation.Type.CHAT); string display_name = Util.get_conversation_display_name(stream_interactor, conv); if (show_account && stream_interactor.get_accounts().size > 1) { via_label.label = @"via $(account.bare_jid)"; this.has_tooltip = true; set_tooltip_text(jid.to_string()); } else if (display_name != jid.bare_jid.to_string()){ via_label.label = jid.bare_jid.to_string(); } else { via_label.visible = false; } name_label.label = display_name; image.set_conversation(stream_interactor, conv); } } } dino-0.3.0/main/src/ui/add_conversation/roster_list.vala0000644000000000000000000000664114202022370022030 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; using Xmpp; namespace Dino.Ui { protected class RosterList : FilterableList { public signal void conversation_selected(Conversation? conversation); private StreamInteractor stream_interactor; private Gee.List accounts; private ulong[] handler_ids = new ulong[0]; private HashMap> rows = new HashMap>(Account.hash_func, Account.equals_func); public RosterList(StreamInteractor stream_interactor, Gee.List accounts) { this.stream_interactor = stream_interactor; this.accounts = accounts; set_filter_func(filter); set_header_func(header); set_sort_func(sort); handler_ids += stream_interactor.get_module(RosterManager.IDENTITY).removed_roster_item.connect( (account, jid, roster_item) => { if (accounts.contains(account)) { on_removed_roster_item(account, jid, roster_item); } }); handler_ids += stream_interactor.get_module(RosterManager.IDENTITY).updated_roster_item.connect( (account, jid, roster_item) => { if (accounts.contains(account)) { on_updated_roster_item(account, jid, roster_item); } }); destroy.connect(() => { foreach (ulong handler_id in handler_ids) stream_interactor.get_module(RosterManager.IDENTITY).disconnect(handler_id); }); foreach (Account a in accounts) fetch_roster_items(a); } private void on_removed_roster_item(Account account, Jid jid, Roster.Item roster_item) { if (rows.has_key(account) && rows[account].has_key(jid)) { remove(rows[account][jid]); rows[account].unset(jid); } } private void on_updated_roster_item(Account account, Jid jid, Roster.Item roster_item) { on_removed_roster_item(account, jid, roster_item); ListRow row = new ListRow.from_jid(stream_interactor, roster_item.jid, account, accounts.size > 1); rows[account][jid] = row; add(row); invalidate_sort(); invalidate_filter(); } private void fetch_roster_items(Account account) { rows[account] = new HashMap(Jid.hash_func, Jid.equals_func); foreach (Roster.Item roster_item in stream_interactor.get_module(RosterManager.IDENTITY).get_roster(account)) { on_updated_roster_item(account, roster_item.jid, roster_item); } } private void header(ListBoxRow row, ListBoxRow? before_row) { if (row.get_header() == null && before_row != null) { row.set_header(new Separator(Orientation.HORIZONTAL)); } } private bool filter(ListBoxRow r) { if (r.get_type().is_a(typeof(ListRow))) { ListRow row = r as ListRow; if (filter_values != null) { foreach (string filter in filter_values) { if (!(row.name_label.label.down().contains(filter.down()) || row.jid.to_string().down().contains(filter.down()))) { return false; } } } } return true; } public override int sort(ListBoxRow row1, ListBoxRow row2) { ListRow c1 = (row1 as ListRow); ListRow c2 = (row2 as ListRow); return c1.name_label.label.collate(c2.name_label.label); } } } dino-0.3.0/main/src/ui/add_conversation/select_contact_dialog.vala0000644000000000000000000000723214202022370023765 0ustar rootrootusing Gee; using Gdk; using Gtk; using Dino.Entities; using Xmpp; namespace Dino.Ui { public class SelectContactDialog : Gtk.Dialog { public signal void selected(Account account, Jid jid); public Button ok_button; private RosterList roster_list; private SelectJidFragment select_jid_fragment; private StreamInteractor stream_interactor; private Gee.List accounts; public SelectContactDialog(StreamInteractor stream_interactor, Gee.List accounts) { Object(use_header_bar : Util.use_csd() ? 1 : 0); modal = true; this.stream_interactor = stream_interactor; this.accounts = accounts; setup_view(); setup_headerbar(); } public void set_filter(string str) { select_jid_fragment.set_filter(str); } private void setup_headerbar() { Button cancel_button = new Button(); cancel_button.set_label(_("Cancel")); cancel_button.visible = true; ok_button = new Button(); ok_button.get_style_context().add_class("suggested-action"); ok_button.sensitive = false; ok_button.visible = true; if (Util.use_csd()) { HeaderBar header_bar = get_header_bar() as HeaderBar; header_bar.show_close_button = false; header_bar.pack_start(cancel_button); header_bar.pack_end(ok_button); } else { Box box = new Box(Orientation.HORIZONTAL, 5) { halign=Align.END, margin_bottom=15, margin_start=80, margin_end=80, visible=true }; cancel_button.halign = Align.START; ok_button.halign = Align.END; box.add(cancel_button); box.add(ok_button); get_content_area().add(box); } cancel_button.clicked.connect(() => { close(); }); ok_button.clicked.connect(() => { ListRow? selected_row = roster_list.get_selected_row() as ListRow; if (selected_row != null) selected(selected_row.account, selected_row.jid); close(); }); } private void setup_view() { roster_list = new RosterList(stream_interactor, accounts); roster_list.row_activated.connect(() => { ok_button.clicked(); }); select_jid_fragment = new SelectJidFragment(stream_interactor, roster_list, accounts); select_jid_fragment.add_jid.connect((row) => { AddContactDialog add_contact_dialog = new AddContactDialog(stream_interactor); add_contact_dialog.set_transient_for(this); add_contact_dialog.present(); }); select_jid_fragment.remove_jid.connect((row) => { ListRow list_row = roster_list.get_selected_row() as ListRow; stream_interactor.get_module(RosterManager.IDENTITY).remove_jid(list_row.account, list_row.jid); }); select_jid_fragment.notify["done"].connect(() => { ok_button.sensitive = select_jid_fragment.done; }); get_content_area().add(select_jid_fragment); } } public class AddChatDialog : SelectContactDialog { public signal void added(Conversation conversation); public AddChatDialog(StreamInteractor stream_interactor, Gee.List accounts) { base(stream_interactor, accounts); title = _("Start Conversation"); ok_button.label = _("Start"); selected.connect((account, jid) => { Conversation conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(jid, account, Conversation.Type.CHAT); stream_interactor.get_module(ConversationManager.IDENTITY).start_conversation(conversation); added(conversation); }); } } } dino-0.3.0/main/src/ui/add_conversation/select_jid_fragment.vala0000644000000000000000000000744514202022370023452 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; using Xmpp; namespace Dino.Ui { [GtkTemplate (ui = "/im/dino/Dino/add_conversation/select_jid_fragment.ui")] public class SelectJidFragment : Gtk.Box { public signal void add_jid(); public signal void remove_jid(ListRow row); public bool done { get { return filterable_list.get_selected_row() != null; } private set {} } [GtkChild] private unowned Entry entry; [GtkChild] private unowned Box box; [GtkChild] private unowned Button add_button; [GtkChild] private unowned Button remove_button; private StreamInteractor stream_interactor; private FilterableList filterable_list; private Gee.List accounts; private ArrayList added_rows = new ArrayList(); public SelectJidFragment(StreamInteractor stream_interactor, FilterableList filterable_list, Gee.List accounts) { this.stream_interactor = stream_interactor; this.filterable_list = filterable_list; this.accounts = accounts; filterable_list.visible = true; filterable_list.activate_on_single_click = false; filterable_list.vexpand = true; box.add(filterable_list); filterable_list.set_sort_func(sort); filterable_list.row_selected.connect(check_buttons_active); filterable_list.row_selected.connect(() => { done = true; }); // just for notifying entry.changed.connect(() => { set_filter(entry.text); }); add_button.clicked.connect(() => { add_jid(); }); remove_button.clicked.connect(() => { remove_jid(filterable_list.get_selected_row() as ListRow); }); } public void set_filter(string str) { if (entry.text != str) entry.text = str; foreach (AddListRow row in added_rows) filterable_list.remove(row); added_rows.clear(); string[] ? values = str == "" ? null : str.split(" "); filterable_list.set_filter_values(values); try { Jid parsed_jid = new Jid(str); if (parsed_jid != null && parsed_jid.localpart != null) { foreach (Account account in accounts) { AddListRow row = new AddListRow(stream_interactor, parsed_jid, account); filterable_list.add(row); added_rows.add(row); } } } catch (InvalidJidError ignored) { // Ignore } } private void check_buttons_active() { ListBoxRow? row = filterable_list.get_selected_row(); bool active = row != null && !row.get_type().is_a(typeof(AddListRow)); remove_button.sensitive = active; } private int sort(ListBoxRow row1, ListBoxRow row2) { AddListRow al1 = (row1 as AddListRow); AddListRow al2 = (row2 as AddListRow); if (al1 != null && al2 == null) { return -1; } else if (al2 != null && al1 == null) { return 1; } return filterable_list.sort(row1, row2); } private class AddListRow : ListRow { public AddListRow(StreamInteractor stream_interactor, Jid jid, Account account) { this.account = account; this.jid = jid; name_label.label = jid.to_string(); if (stream_interactor.get_accounts().size > 1) { via_label.label = account.bare_jid.to_string(); } else { via_label.visible = false; } image.set_text("?"); } } } public abstract class FilterableList : Gtk.ListBox { public string[]? filter_values; public void set_filter_values(string[] values) { if (filter_values == values) return; filter_values = values; invalidate_filter(); } public abstract int sort(ListBoxRow row1, ListBoxRow row2); } } dino-0.3.0/main/src/ui/application.vala0000644000000000000000000003766214202022370016447 0ustar rootrootusing Gtk; using Dino.Entities; using Dino.Ui; using Xmpp; public class Dino.Ui.Application : Gtk.Application, Dino.Application { private const string[] KEY_COMBINATION_QUIT = {"Q", null}; private const string[] KEY_COMBINATION_ADD_CHAT = {"T", null}; private const string[] KEY_COMBINATION_ADD_CONFERENCE = {"G", null}; private const string[] KEY_COMBINATION_LOOP_CONVERSATIONS = {"Tab", null}; private const string[] KEY_COMBINATION_LOOP_CONVERSATIONS_REV = {"Tab", null}; private MainWindow window; public MainWindowController controller; public Database db { get; set; } public Dino.Entities.Settings settings { get; set; } private Config config { get; set; } public StreamInteractor stream_interactor { get; set; } public Plugins.Registry plugin_registry { get; set; default = new Plugins.Registry(); } public SearchPathGenerator? search_path_generator { get; set; } internal static bool print_version = false; private const OptionEntry[] options = { { "version", 0, 0, OptionArg.NONE, ref print_version, "Display version number", null }, { null } }; public Application() throws Error { Object(application_id: "im.dino.Dino", flags: ApplicationFlags.HANDLES_OPEN); init(); Environment.set_application_name("Dino"); Window.set_default_icon_name("im.dino.Dino"); CssProvider provider = new CssProvider(); provider.load_from_resource("/im/dino/Dino/theme.css"); StyleContext.add_provider_for_screen(Gdk.Screen.get_default(), provider, STYLE_PROVIDER_PRIORITY_APPLICATION); create_actions(); add_main_option_entries(options); startup.connect(() => { if (print_version) { print(@"Dino $(Dino.VERSION)\n"); Process.exit(0); } NotificationEvents notification_events = stream_interactor.get_module(NotificationEvents.IDENTITY); get_notifications_dbus.begin((_, res) => { // It might take a bit to get the interface. NotificationEvents will queue any notifications in the meantime. try { DBusNotifications? dbus_notifications = get_notifications_dbus.end(res); if (dbus_notifications != null) { FreeDesktopNotifier free_desktop_notifier = new FreeDesktopNotifier(stream_interactor, dbus_notifications); notification_events.register_notification_provider.begin(free_desktop_notifier); } else { notification_events.register_notification_provider.begin(new GNotificationsNotifier(stream_interactor)); } } catch (Error e) { debug("Failed accessing fdo notification server: %s", e.message); } }); notification_events.notify_content_item.connect((content_item, conversation) => { // Set urgency hint also if (normal) notifications are disabled // Don't set urgency hint in GNOME, produces "Window is active" notification var desktop_env = Environment.get_variable("XDG_CURRENT_DESKTOP"); if (desktop_env == null || !desktop_env.down().contains("gnome")) { if (this.active_window != null) { this.active_window.urgency_hint = true; } } }); }); activate.connect(() => { if (window == null) { controller = new MainWindowController(this, stream_interactor, db); config = new Config(db); window = new MainWindow(this, stream_interactor, db, config); controller.set_window(window); if ((get_flags() & ApplicationFlags.IS_SERVICE) == ApplicationFlags.IS_SERVICE) window.delete_event.connect(window.hide_on_delete); } window.present(); }); } public void handle_uri(string jid, string query, Gee.Map options) { switch (query) { case "join": show_join_muc_dialog(null, jid); break; case "message": Gee.List accounts = stream_interactor.get_accounts(); Jid parsed_jid = null; try { parsed_jid = new Jid(jid); } catch (InvalidJidError ignored) { // Ignored } if (accounts.size == 1 && parsed_jid != null) { Conversation conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(parsed_jid, accounts[0], Conversation.Type.CHAT); stream_interactor.get_module(ConversationManager.IDENTITY).start_conversation(conversation); controller.select_conversation(conversation); } else { AddChatDialog dialog = new AddChatDialog(stream_interactor, stream_interactor.get_accounts()); dialog.set_filter(jid); dialog.set_transient_for(window); dialog.added.connect((conversation) => { controller.select_conversation(conversation); }); dialog.present(); } break; } } private void create_actions() { SimpleAction accounts_action = new SimpleAction("accounts", null); accounts_action.activate.connect(show_accounts_window); add_action(accounts_action); SimpleAction settings_action = new SimpleAction("settings", null); settings_action.activate.connect(show_settings_window); add_action(settings_action); SimpleAction about_action = new SimpleAction("about", null); about_action.activate.connect(show_about_window); add_action(about_action); SimpleAction quit_action = new SimpleAction("quit", null); quit_action.activate.connect(quit); add_action(quit_action); set_accels_for_action("app.quit", KEY_COMBINATION_QUIT); SimpleAction open_conversation_action = new SimpleAction("open-conversation", VariantType.INT32); open_conversation_action.activate.connect((variant) => { Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation_by_id(variant.get_int32()); if (conversation != null) controller.select_conversation(conversation); Util.present_window(window); }); add_action(open_conversation_action); SimpleAction deny_subscription_action = new SimpleAction("deny-subscription", VariantType.INT32); deny_subscription_action.activate.connect((variant) => { Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation_by_id(variant.get_int32()); if (conversation == null) return; stream_interactor.get_module(PresenceManager.IDENTITY).deny_subscription(conversation.account, conversation.counterpart); }); add_action(deny_subscription_action); SimpleAction contacts_action = new SimpleAction("add_chat", null); contacts_action.activate.connect(() => { AddChatDialog add_chat_dialog = new AddChatDialog(stream_interactor, stream_interactor.get_accounts()); add_chat_dialog.set_transient_for(window); add_chat_dialog.added.connect((conversation) => controller.select_conversation(conversation)); add_chat_dialog.present(); }); add_action(contacts_action); set_accels_for_action("app.add_chat", KEY_COMBINATION_ADD_CHAT); SimpleAction conference_action = new SimpleAction("add_conference", null); conference_action.activate.connect(() => { AddConferenceDialog add_conference_dialog = new AddConferenceDialog(stream_interactor); add_conference_dialog.set_transient_for(window); add_conference_dialog.present(); }); add_action(conference_action); set_accels_for_action("app.add_conference", KEY_COMBINATION_ADD_CONFERENCE); SimpleAction accept_muc_invite_action = new SimpleAction("open-muc-join", VariantType.INT32); accept_muc_invite_action.activate.connect((variant) => { Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation_by_id(variant.get_int32()); if (conversation == null) return; show_join_muc_dialog(conversation.account, conversation.counterpart.to_string()); }); add_action(accept_muc_invite_action); SimpleAction accept_voice_request_action = new SimpleAction("accept-voice-request", new VariantType.tuple(new VariantType[]{VariantType.INT32, VariantType.STRING})); accept_voice_request_action.activate.connect((variant) => { int conversation_id = variant.get_child_value(0).get_int32(); Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation_by_id(conversation_id); if (conversation == null) return; string nick = variant.get_child_value(1).get_string(); stream_interactor.get_module(MucManager.IDENTITY).change_role(conversation.account, conversation.counterpart, nick, "participant"); }); add_action(accept_voice_request_action); SimpleAction loop_conversations_action = new SimpleAction("loop_conversations", null); loop_conversations_action.activate.connect(() => { window.loop_conversations(false); }); add_action(loop_conversations_action); set_accels_for_action("app.loop_conversations", KEY_COMBINATION_LOOP_CONVERSATIONS); SimpleAction loop_conversations_bw_action = new SimpleAction("loop_conversations_bw", null); loop_conversations_bw_action.activate.connect(() => { window.loop_conversations(true); }); add_action(loop_conversations_bw_action); set_accels_for_action("app.loop_conversations_bw", KEY_COMBINATION_LOOP_CONVERSATIONS_REV); SimpleAction open_shortcuts_action = new SimpleAction("open_shortcuts", null); open_shortcuts_action.activate.connect((variant) => { Builder builder = new Builder.from_resource("/im/dino/Dino/shortcuts.ui"); ShortcutsWindow dialog = (ShortcutsWindow) builder.get_object("shortcuts-window"); if (!use_csd()) { // Hack to prevent CRITICAL in Gtk when trying to destroy non-existant headerbar Widget shortcuts_hack = dialog.get_titlebar(); dialog.destroy.connect_after(() => { shortcuts_hack = null; }); dialog.set_titlebar(null); } dialog.title = _("Keyboard Shortcuts"); dialog.set_transient_for(get_active_window()); dialog.present(); }); add_action(open_shortcuts_action); SimpleAction accept_call_action = new SimpleAction("accept-call", new VariantType.tuple(new VariantType[]{VariantType.INT32, VariantType.INT32})); accept_call_action.activate.connect((variant) => { int conversation_id = variant.get_child_value(0).get_int32(); Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation_by_id(conversation_id); if (conversation == null) return; int call_id = variant.get_child_value(1).get_int32(); Call? call = stream_interactor.get_module(CallStore.IDENTITY).get_call_by_id(call_id, conversation); CallState? call_state = stream_interactor.get_module(Calls.IDENTITY).call_states[call]; if (call_state == null) return; call_state.accept(); var call_window = new CallWindow(); call_window.controller = new CallWindowController(call_window, call_state, stream_interactor); call_window.present(); }); add_action(accept_call_action); SimpleAction deny_call_action = new SimpleAction("reject-call", new VariantType.tuple(new VariantType[]{VariantType.INT32, VariantType.INT32})); deny_call_action.activate.connect((variant) => { int conversation_id = variant.get_child_value(0).get_int32(); Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation_by_id(conversation_id); if (conversation == null) return; int call_id = variant.get_child_value(1).get_int32(); Call? call = stream_interactor.get_module(CallStore.IDENTITY).get_call_by_id(call_id, conversation); CallState? call_state = stream_interactor.get_module(Calls.IDENTITY).call_states[call]; if (call_state == null) return; call_state.reject(); }); add_action(deny_call_action); } public bool use_csd() { return Environment.get_variable("GTK_CSD") != "0"; } private void show_accounts_window() { ManageAccounts.Dialog dialog = new ManageAccounts.Dialog(stream_interactor, db); dialog.set_transient_for(get_active_window()); dialog.account_enabled.connect(add_connection); dialog.account_disabled.connect(remove_connection); dialog.present(); } private void show_settings_window() { SettingsDialog dialog = new SettingsDialog(); dialog.set_transient_for(get_active_window()); dialog.present(); } private void show_about_window() { string? version = Dino.VERSION.strip().length == 0 ? null : Dino.VERSION; if (version != null && !version.contains("git")) { switch (version.substring(0, 3)) { case "0.2": version = @"$version - Mexican Caribbean Coral Reefs"; break; case "0.3": version = @"$version - Theikenmeer"; break; } } Gtk.AboutDialog dialog = new Gtk.AboutDialog(); dialog.destroy_with_parent = true; dialog.transient_for = window; dialog.modal = true; dialog.title = _("About Dino"); dialog.logo_icon_name = "im.dino.Dino"; dialog.program_name = "Dino"; dialog.version = version; dialog.comments = "Dino. Communicating happiness."; dialog.website = "https://dino.im/"; dialog.website_label = "dino.im"; dialog.copyright = "Copyright © 2016-2022 - Dino Team"; dialog.license_type = License.GPL_3_0; dialog.response.connect((response_id) => { if (response_id == Gtk.ResponseType.CANCEL || response_id == Gtk.ResponseType.DELETE_EVENT) { dialog.destroy(); } }); if (!use_csd()) { dialog.set_titlebar(null); } dialog.present(); } private void show_join_muc_dialog(Account? account, string jid) { Dialog dialog = new Dialog.with_buttons(_("Join Channel"), window, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.USE_HEADER_BAR, _("Join"), ResponseType.OK, _("Cancel"), ResponseType.CANCEL); dialog.modal = true; Button ok_button = dialog.get_widget_for_response(ResponseType.OK) as Button; ok_button.get_style_context().add_class("suggested-action"); ConferenceDetailsFragment conference_fragment = new ConferenceDetailsFragment(stream_interactor) { ok_button=ok_button }; conference_fragment.jid = jid; if (account != null) { conference_fragment.account = account; } Box content_area = dialog.get_content_area(); content_area.add(conference_fragment); conference_fragment.joined.connect(() => { dialog.destroy(); }); dialog.response.connect((response_id) => { if (response_id == ResponseType.CANCEL) { dialog.destroy(); } }); dialog.present(); } } dino-0.3.0/main/src/ui/avatar_drawer.vala0000644000000000000000000001756614202022370016767 0ustar rootrootusing Cairo; using Gee; using Gdk; using Gtk; using Xmpp.Util; namespace Dino.Ui { public class AvatarDrawer { public const string GRAY = "555753"; private Gee.List tiles = new ArrayList(); private int height = 35; private int width = 35; private bool gray; private int base_factor = 1; private string font_family = "Sans"; public AvatarDrawer size(int height, int width = height) { this.height = height; this.width = width; return this; } public AvatarDrawer grayscale() { this.gray = true; return this; } public AvatarDrawer tile(Pixbuf? image, string? name, string? hex_color) { tiles.add(new AvatarTile(image, name, hex_color)); return this; } public AvatarDrawer plus() { tiles.add(new AvatarTile(null, "…", GRAY)); return this; } public AvatarDrawer scale(int base_factor) { this.base_factor = base_factor; return this; } public AvatarDrawer font(string font_family) { this.font_family = font_family; return this; } public ImageSurface draw_image_surface() { ImageSurface surface = new ImageSurface(Format.ARGB32, width, height); draw_on_context(new Context(surface)); return surface; } public void draw_on_context(Cairo.Context ctx) { double radius = 3 * base_factor; double degrees = Math.PI / 180.0; ctx.new_sub_path(); ctx.arc(width - radius, radius, radius, -90 * degrees, 0 * degrees); ctx.arc(width - radius, height - radius, radius, 0 * degrees, 90 * degrees); ctx.arc(radius, height - radius, radius, 90 * degrees, 180 * degrees); ctx.arc(radius, radius, radius, 180 * degrees, 270 * degrees); ctx.close_path(); ctx.clip(); if (this.tiles.size == 4) { Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width, height); Cairo.Context bufctx = new Cairo.Context(buffer); bufctx.scale(0.5, 0.5); bufctx.set_source_surface(sub_surface_idx(ctx, 0, width - 1, height - 1, 2 * base_factor), 0, 0); bufctx.paint(); bufctx.set_source_surface(sub_surface_idx(ctx, 1, width - 1, height - 1, 2 * base_factor), width + 1, 0); bufctx.paint(); bufctx.set_source_surface(sub_surface_idx(ctx, 2, width - 1, height - 1, 2 * base_factor), 0, height + 1); bufctx.paint(); bufctx.set_source_surface(sub_surface_idx(ctx, 3, width - 1, height - 1, 2 * base_factor), width + 1, height + 1); bufctx.paint(); ctx.set_source_surface(buffer, 0, 0); ctx.paint(); } else if (this.tiles.size == 3) { Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width, height); Cairo.Context bufctx = new Cairo.Context(buffer); bufctx.scale(0.5, 0.5); bufctx.set_source_surface(sub_surface_idx(ctx, 0, width - 1, height - 1, 2 * base_factor), 0, 0); bufctx.paint(); bufctx.set_source_surface(sub_surface_idx(ctx, 1, width - 1, height * 2, 2 * base_factor), width + 1, 0); bufctx.paint(); bufctx.set_source_surface(sub_surface_idx(ctx, 2, width - 1, height - 1, 2 * base_factor), 0, height + 1); bufctx.paint(); ctx.set_source_surface(buffer, 0, 0); ctx.paint(); } else if (this.tiles.size == 2) { Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width, height); Cairo.Context bufctx = new Cairo.Context(buffer); bufctx.scale(0.5, 0.5); bufctx.set_source_surface(sub_surface_idx(ctx, 0, width - 1, height * 2, 2 * base_factor), 0, 0); bufctx.paint(); bufctx.set_source_surface(sub_surface_idx(ctx, 1, width - 1, height * 2, 2 * base_factor), width + 1, 0); bufctx.paint(); ctx.set_source_surface(buffer, 0, 0); ctx.paint(); } else if (this.tiles.size == 1) { ctx.set_source_surface(sub_surface_idx(ctx, 0, width, height, base_factor), 0, 0); ctx.paint(); } else if (this.tiles.size == 0) { ctx.set_source_surface(sub_surface_idx(ctx, -1, width, height, base_factor), 0, 0); ctx.paint(); } if (gray) { // convert to greyscale ctx.set_operator(Cairo.Operator.HSL_COLOR); ctx.set_source_rgb(1, 1, 1); ctx.rectangle(0, 0, width, height); ctx.fill(); // make the visible part more light ctx.set_operator(Cairo.Operator.ATOP); ctx.set_source_rgba(1, 1, 1, 0.7); ctx.rectangle(0, 0, width, height); ctx.fill(); } ctx.set_source_rgb(0, 0, 0); } private Cairo.Surface sub_surface_idx(Cairo.Context ctx, int idx, int width, int height, int font_factor = 1) { Gdk.Pixbuf? avatar = idx >= 0 ? tiles[idx].image : null; string? name = idx >= 0 ? tiles[idx].name : ""; string hex_color = !gray && idx >= 0 ? tiles[idx].hex_color : GRAY; return sub_surface(ctx, font_family, avatar, name, hex_color, width, height, font_factor); } private static Cairo.Surface sub_surface(Cairo.Context ctx, string font_family, Gdk.Pixbuf? avatar, string? name, string? hex_color, int width, int height, int font_factor = 1) { Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width, height); Cairo.Context bufctx = new Cairo.Context(buffer); if (avatar == null) { set_source_hex_color(bufctx, hex_color ?? GRAY); bufctx.rectangle(0, 0, width, height); bufctx.fill(); string text = name == null ? "…" : name.get_char(0).toupper().to_string(); bufctx.select_font_face(font_family, Cairo.FontSlant.NORMAL, Cairo.FontWeight.NORMAL); bufctx.set_font_size(width / font_factor < 40 ? font_factor * 17 : font_factor * 25); Cairo.TextExtents extents; bufctx.text_extents(text, out extents); double x_pos = width/2 - (extents.width/2 + extents.x_bearing); double y_pos = height/2 - (extents.height/2 + extents.y_bearing); bufctx.move_to(x_pos, y_pos); bufctx.set_source_rgba(1, 1, 1, 1); bufctx.show_text(text); } else { double w_scale = (double) width / avatar.width; double h_scale = (double) height / avatar.height; double scale = double.max(w_scale, h_scale); bufctx.scale(scale, scale); double x_off = 0, y_off = 0; if (scale == h_scale) { x_off = (width / scale - avatar.width) / 2.0; } else { y_off = (height / scale - avatar.height) / 2.0; } Gdk.cairo_set_source_pixbuf(bufctx, avatar, x_off, y_off); bufctx.get_source().set_filter(Cairo.Filter.BEST); bufctx.paint(); } return buffer; } private static void set_source_hex_color(Cairo.Context ctx, string hex_color) { ctx.set_source_rgba((double) from_hex(hex_color.substring(0, 2)) / 255, (double) from_hex(hex_color.substring(2, 2)) / 255, (double) from_hex(hex_color.substring(4, 2)) / 255, hex_color.length > 6 ? (double) from_hex(hex_color.substring(6, 2)) / 255 : 1); } } private class AvatarTile { public Pixbuf? image { get; private set; } public string? name { get; private set; } public string? hex_color { get; private set; } public AvatarTile(Pixbuf? image, string? name, string? hex_color) { this.image = image; this.name = name; this.hex_color = hex_color; } } }dino-0.3.0/main/src/ui/avatar_generator.vala0000644000000000000000000000000014202022370017440 0ustar rootrootdino-0.3.0/main/src/ui/avatar_image.vala0000644000000000000000000002657414202022370016564 0ustar rootrootusing Gtk; using Dino.Entities; using Xmpp; using Xmpp.Util; namespace Dino.Ui { public class AvatarImage : Misc { public int height { get; set; default = 35; } public int width { get; set; default = 35; } public bool allow_gray { get; set; default = true; } public bool force_gray { get; set; default = false; } public StreamInteractor? stream_interactor { get; set; } public AvatarManager? avatar_manager { owned get { return stream_interactor == null ? null : stream_interactor.get_module(AvatarManager.IDENTITY); } } public MucManager? muc_manager { owned get { return stream_interactor == null ? null : stream_interactor.get_module(MucManager.IDENTITY); } } public PresenceManager? presence_manager { owned get { return stream_interactor == null ? null : stream_interactor.get_module(PresenceManager.IDENTITY); } } public ConnectionManager? connection_manager { owned get { return stream_interactor == null ? null : stream_interactor.connection_manager; } } public Account account { get { return conversation.account; } } private AvatarDrawer? drawer; private Conversation conversation; private Jid[] jids; private Cairo.ImageSurface? cached_surface; private static int8 use_image_surface = -1; public AvatarImage() { can_focus = false; get_style_context().add_class("avatar"); } public override void dispose() { base.dispose(); drawer = null; cached_surface = null; disconnect_stream_interactor(); } public override void get_preferred_width(out int minimum_width, out int natural_width) { minimum_width = width; natural_width = width; } public override void get_preferred_height(out int minimum_height, out int natural_height) { minimum_height = height; natural_height = height; } public override bool draw(Cairo.Context ctx_in) { Cairo.Context ctx = ctx_in; int width = this.width, height = this.height, base_factor = 1; if (use_image_surface == -1) { // TODO: detect if we have to buffer in image surface use_image_surface = 1; } if (use_image_surface == 1) { ctx_in.scale(1f / scale_factor, 1f / scale_factor); if (cached_surface != null) { ctx_in.set_source_surface(cached_surface, 0, 0); ctx_in.paint(); ctx_in.set_source_rgb(0, 0, 0); return true; } width *= scale_factor; height *= scale_factor; base_factor *= scale_factor; cached_surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, width, height); ctx = new Cairo.Context(cached_surface); } AvatarDrawer drawer = this.drawer; Jid[] jids = this.jids; if (drawer == null && jids.length == 0) { switch (conversation.type_) { case Conversation.Type.CHAT: case Conversation.Type.GROUPCHAT_PM: // In direct chats or group chats, conversation avatar is same as counterpart avatar jids = { conversation.counterpart }; break; case Conversation.Type.GROUPCHAT: string user_color = Util.get_avatar_hex_color(stream_interactor, account, conversation.counterpart, conversation); if (avatar_manager.has_avatar_cached(account, conversation.counterpart)) { drawer = new AvatarDrawer().tile(avatar_manager.get_cached_avatar(account, conversation.counterpart), "#", user_color); if (force_gray || allow_gray && (!is_self_online() || !is_counterpart_online())) drawer.grayscale(); } else { Gee.List? occupants = muc_manager.get_other_offline_members(conversation.counterpart, account); if (muc_manager.is_private_room(account, conversation.counterpart) && occupants != null && occupants.size > 0) { jids = occupants.to_array(); } else { drawer = new AvatarDrawer().tile(null, "#", user_color); if (force_gray || allow_gray && (!is_self_online() || !is_counterpart_online())) drawer.grayscale(); } try_load_avatar_async(conversation.counterpart); } break; } } if (drawer == null && jids.length > 0) { drawer = new AvatarDrawer(); for (int i = 0; i < (jids.length <= 4 ? jids.length : 3); i++) { Jid avatar_jid = jids[i]; Jid? real_avatar_jid = null; if (conversation.type_ != Conversation.Type.CHAT && avatar_jid.equals_bare(conversation.counterpart) && muc_manager.is_private_room(account, conversation.counterpart.bare_jid)) { // In private room, consider real jid real_avatar_jid = muc_manager.get_real_jid(avatar_jid, account) ?? avatar_jid; } string display_name = Util.get_participant_display_name(stream_interactor, conversation, jids[i]); string user_color = Util.get_avatar_hex_color(stream_interactor, account, jids[i], conversation); if (avatar_manager.has_avatar_cached(account, avatar_jid)) { drawer.tile(avatar_manager.get_cached_avatar(account, avatar_jid), display_name, user_color); } else if (real_avatar_jid != null && avatar_manager.has_avatar_cached(account, real_avatar_jid)) { drawer.tile(avatar_manager.get_cached_avatar(account, real_avatar_jid), display_name, user_color); } else { drawer.tile(null, display_name, user_color); try_load_avatar_async(avatar_jid); if (real_avatar_jid != null) try_load_avatar_async(real_avatar_jid); } } if (jids.length > 4) { drawer.plus(); } if (force_gray || allow_gray && (!is_self_online() || !is_counterpart_online())) drawer.grayscale(); } if (drawer == null) return false; drawer.size(height, width) .scale(base_factor) .font(get_pango_context().get_font_description().get_family()) .draw_on_context(ctx); if (use_image_surface == 1) { ctx_in.set_source_surface(ctx.get_target(), 0, 0); ctx_in.paint(); ctx_in.set_source_rgb(0, 0, 0); } return true; } private void try_load_avatar_async(Jid jid) { if (avatar_manager.has_avatar(account, jid)) { avatar_manager.get_avatar.begin(account, jid, (_, res) => { var avatar = avatar_manager.get_avatar.end(res); if (avatar != null) force_redraw(); }); } } private void force_redraw() { this.cached_surface = null; queue_draw(); } private void disconnect_stream_interactor() { if (stream_interactor != null) { presence_manager.show_received.disconnect(on_show_received); presence_manager.received_offline_presence.disconnect(on_show_received); avatar_manager.received_avatar.disconnect(on_received_avatar); stream_interactor.connection_manager.connection_state_changed.disconnect(on_connection_changed); stream_interactor.get_module(RosterManager.IDENTITY).updated_roster_item.disconnect(on_roster_updated); muc_manager.private_room_occupant_updated.disconnect(on_private_room_occupant_updated); muc_manager.room_info_updated.disconnect(on_room_info_updated); stream_interactor = null; } } private void on_show_received(Jid jid, Account account) { if (!account.equals(this.account)) return; update_avatar_if_jid(jid); } private void on_received_avatar(Jid jid, Account account) { if (!account.equals(this.account)) return; update_avatar_if_jid(jid); } private void update_avatar_if_jid(Jid jid) { if (jid.equals_bare(this.conversation.counterpart)) { force_redraw(); return; } foreach (Jid ours in this.jids) { if (jid.equals_bare(ours)) { force_redraw(); return; } } } private void on_connection_changed(Account account, ConnectionManager.ConnectionState state) { if (!account.equals(this.account)) return; force_redraw(); } private void on_roster_updated(Account account, Jid jid, Roster.Item roster_item) { if (!account.equals(this.account)) return; update_avatar_if_jid(jid); } private void on_private_room_occupant_updated(Account account, Jid room, Jid occupant) { if (!account.equals(this.account)) return; update_avatar_if_jid(room); } private void on_room_info_updated(Account account, Jid muc_jid) { if (!account.equals(this.account)) return; update_avatar_if_jid(muc_jid); } private bool is_self_online() { if (connection_manager != null) { return connection_manager.get_state(account) == ConnectionManager.ConnectionState.CONNECTED; } return false; } private bool is_counterpart_online() { return presence_manager.get_full_jids(conversation.counterpart, account) != null; } public void set_conversation(StreamInteractor stream_interactor, Conversation conversation) { set_avatar(stream_interactor, conversation, new Jid[0]); } public void set_conversation_participant(StreamInteractor stream_interactor, Conversation conversation, Jid sub_jid) { set_avatar(stream_interactor, conversation, new Jid[] {sub_jid}); } public void set_conversation_participants(StreamInteractor stream_interactor, Conversation conversation, Jid[] sub_jids) { set_avatar(stream_interactor, conversation, sub_jids); } private void set_avatar(StreamInteractor stream_interactor, Conversation conversation, Jid[] jids) { if (this.stream_interactor != null && stream_interactor != this.stream_interactor) { disconnect_stream_interactor(); } if (this.stream_interactor != stream_interactor) { this.stream_interactor = stream_interactor; presence_manager.show_received.connect(on_show_received); presence_manager.received_offline_presence.connect(on_show_received); stream_interactor.get_module(AvatarManager.IDENTITY).received_avatar.connect(on_received_avatar); stream_interactor.connection_manager.connection_state_changed.connect(on_connection_changed); stream_interactor.get_module(RosterManager.IDENTITY).updated_roster_item.connect(on_roster_updated); muc_manager.private_room_occupant_updated.connect(on_private_room_occupant_updated); muc_manager.room_info_updated.connect(on_room_info_updated); } this.cached_surface = null; this.conversation = conversation; this.jids = jids; force_redraw(); } public void set_text(string text, bool gray = true) { disconnect_stream_interactor(); this.drawer = new AvatarDrawer().tile(null, text, null); if (gray) drawer.grayscale(); force_redraw(); } } } dino-0.3.0/main/src/ui/call_window/0000755000000000000000000000000014202022370015563 5ustar rootrootdino-0.3.0/main/src/ui/call_window/audio_settings_popover.vala0000644000000000000000000001610614202022370023227 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; public class Dino.Ui.AudioSettingsPopover : Gtk.Popover { public signal void microphone_selected(Plugins.MediaDevice device); public signal void speaker_selected(Plugins.MediaDevice device); public Plugins.MediaDevice? current_microphone_device { get; set; } public Plugins.MediaDevice? current_speaker_device { get; set; } private HashMap row_microphone_device = new HashMap(); private HashMap row_speaker_device = new HashMap(); public AudioSettingsPopover() { Box box = new Box(Orientation.VERTICAL, 15) { margin=18, visible=true }; box.add(create_microphone_box()); box.add(create_speaker_box()); this.add(box); } private Widget create_microphone_box() { Plugins.VideoCallPlugin call_plugin = Dino.Application.get_default().plugin_registry.video_call_plugin; Gee.List devices = call_plugin.get_devices("audio", false); Box micro_box = new Box(Orientation.VERTICAL, 10) { visible=true }; micro_box.add(new Label("" + _("Microphones") + "") { use_markup=true, xalign=0, visible=true, can_focus=true /* grab initial focus*/ }); if (devices.size == 0) { micro_box.add(new Label(_("No microphone found."))); } else { ListBox micro_list_box = new ListBox() { activate_on_single_click=true, selection_mode=SelectionMode.SINGLE, visible=true }; micro_list_box.set_header_func(listbox_header_func); Frame micro_frame = new Frame(null) { visible=true }; micro_frame.add(micro_list_box); foreach (Plugins.MediaDevice device in devices) { Label display_name_label = new Label(device.display_name) { xalign=0, visible=true }; Image image = new Image.from_icon_name("object-select-symbolic", IconSize.BUTTON) { visible=true }; if (current_microphone_device == null || current_microphone_device.id != device.id) { image.opacity = 0; } this.notify["current-microphone-device"].connect(() => { if (current_microphone_device == null || current_microphone_device.id != device.id) { image.opacity = 0; } else { image.opacity = 1; } }); Box device_box = new Box(Orientation.HORIZONTAL, 0) { spacing=7, margin=7, visible=true }; device_box.add(image); Box label_box = new Box(Orientation.VERTICAL, 0) { visible = true }; label_box.add(display_name_label); if (device.detail_name != null) { Label detail_name_label = new Label(device.detail_name) { xalign=0, visible=true }; detail_name_label.get_style_context().add_class("dim-label"); detail_name_label.attributes = new Pango.AttrList(); detail_name_label.attributes.insert(Pango.attr_scale_new(0.8)); label_box.add(detail_name_label); } device_box.add(label_box); ListBoxRow list_box_row = new ListBoxRow() { visible=true }; list_box_row.add(device_box); micro_list_box.add(list_box_row); row_microphone_device[list_box_row] = device; } micro_list_box.row_activated.connect((row) => { if (!row_microphone_device.has_key(row)) return; microphone_selected(row_microphone_device[row]); micro_list_box.unselect_row(row); }); micro_box.add(micro_frame); } return micro_box; } private Widget create_speaker_box() { Plugins.VideoCallPlugin call_plugin = Dino.Application.get_default().plugin_registry.video_call_plugin; Gee.List devices = call_plugin.get_devices("audio", true); Box speaker_box = new Box(Orientation.VERTICAL, 10) { visible=true }; speaker_box.add(new Label("" + _("Speakers") +"") { use_markup=true, xalign=0, visible=true }); if (devices.size == 0) { speaker_box.add(new Label(_("No speaker found."))); } else { ListBox speaker_list_box = new ListBox() { activate_on_single_click=true, selection_mode=SelectionMode.SINGLE, visible=true }; speaker_list_box.set_header_func(listbox_header_func); speaker_list_box.row_selected.connect((row) => { }); Frame speaker_frame = new Frame(null) { visible=true }; speaker_frame.add(speaker_list_box); foreach (Plugins.MediaDevice device in devices) { Label display_name_label = new Label(device.display_name) { xalign=0, visible=true }; Image image = new Image.from_icon_name("object-select-symbolic", IconSize.BUTTON) { visible=true }; if (current_speaker_device == null || current_speaker_device.id != device.id) { image.opacity = 0; } this.notify["current-speaker-device"].connect(() => { if (current_speaker_device == null || current_speaker_device.id != device.id) { image.opacity = 0; } else { image.opacity = 1; } }); Box device_box = new Box(Orientation.HORIZONTAL, 0) { spacing=7, margin=7, visible=true }; device_box.add(image); Box label_box = new Box(Orientation.VERTICAL, 0) { visible = true }; label_box.add(display_name_label); if (device.detail_name != null) { Label detail_name_label = new Label(device.detail_name) { xalign=0, visible=true }; detail_name_label.get_style_context().add_class("dim-label"); detail_name_label.attributes = new Pango.AttrList(); detail_name_label.attributes.insert(Pango.attr_scale_new(0.8)); label_box.add(detail_name_label); } device_box.add(label_box); ListBoxRow list_box_row = new ListBoxRow() { visible=true }; list_box_row.add(device_box); speaker_list_box.add(list_box_row); row_speaker_device[list_box_row] = device; } speaker_list_box.row_activated.connect((row) => { if (!row_speaker_device.has_key(row)) return; speaker_selected(row_speaker_device[row]); speaker_list_box.unselect_row(row); }); speaker_box.add(speaker_frame); } return speaker_box; } private void listbox_header_func(ListBoxRow row, ListBoxRow? before_row) { if (row.get_header() == null && before_row != null) { row.set_header(new Separator(Orientation.HORIZONTAL)); } } }dino-0.3.0/main/src/ui/call_window/call_bottom_bar.vala0000644000000000000000000001636714202022370021570 0ustar rootrootusing Dino.Entities; using Gtk; using Pango; public class Dino.Ui.CallBottomBar : Gtk.Box { public signal void hang_up(); public bool audio_enabled { get; set; } public bool video_enabled { get; set; } public static IconSize ICON_SIZE_MEDIADEVICE_BUTTON = Gtk.icon_size_register("im.dino.Dino.CALL_MEDIADEVICE_BUTTON", 10, 10); public string counterpart_display_name { get; set; } private Button audio_button = new Button() { height_request=45, width_request=45, halign=Align.START, valign=Align.START, visible=true }; private Overlay audio_button_overlay = new Overlay() { visible=true }; private Image audio_image = new Image() { visible=true }; private MenuButton audio_settings_button = new MenuButton() { halign=Align.END, valign=Align.END }; public AudioSettingsPopover? audio_settings_popover; private Button video_button = new Button() { height_request=45, width_request=45, halign=Align.START, valign=Align.START, visible=true }; private Overlay video_button_overlay = new Overlay() { visible=true }; private Image video_image = new Image() { visible=true }; private MenuButton video_settings_button = new MenuButton() { halign=Align.END, valign=Align.END }; public VideoSettingsPopover? video_settings_popover; private Label label = new Label("") { margin=20, halign=Align.CENTER, valign=Align.CENTER, wrap=true, wrap_mode=Pango.WrapMode.WORD_CHAR, hexpand=true, visible=true }; private Stack stack = new Stack() { visible=true }; public CallBottomBar() { Object(orientation:Orientation.HORIZONTAL, spacing:0); Box main_buttons = new Box(Orientation.HORIZONTAL, 20) { margin_start=40, margin_end=40, margin=20, halign=Align.CENTER, hexpand=true, visible=true }; audio_button.add(audio_image); audio_button.get_style_context().add_class("call-button"); audio_button.clicked.connect(() => { audio_enabled = !audio_enabled; }); audio_button.margin_end = audio_button.margin_bottom = 5; // space for the small settings button audio_button_overlay.add(audio_button); audio_button_overlay.add_overlay(audio_settings_button); audio_settings_button.set_image(new Image.from_icon_name("go-up-symbolic", ICON_SIZE_MEDIADEVICE_BUTTON) { visible=true }); audio_settings_button.get_style_context().add_class("call-mediadevice-settings-button"); audio_settings_button.use_popover = true; main_buttons.add(audio_button_overlay); video_button.add(video_image); video_button.get_style_context().add_class("call-button"); video_button.clicked.connect(() => { video_enabled = !video_enabled; }); video_button.margin_end = video_button.margin_bottom = 5; video_button_overlay.add(video_button); video_button_overlay.add_overlay(video_settings_button); video_settings_button.set_image(new Image.from_icon_name("go-up-symbolic", ICON_SIZE_MEDIADEVICE_BUTTON) { visible=true }); video_settings_button.get_style_context().add_class("call-mediadevice-settings-button"); video_settings_button.use_popover = true; main_buttons.add(video_button_overlay); Button button_hang = new Button.from_icon_name("dino-phone-hangup-symbolic", IconSize.LARGE_TOOLBAR) { height_request=45, width_request=45, halign=Align.START, valign=Align.START, visible=true }; button_hang.get_style_context().add_class("call-button"); button_hang.get_style_context().add_class("destructive-action"); button_hang.clicked.connect(() => hang_up()); main_buttons.add(button_hang); label.get_style_context().add_class("text-no-controls"); stack.add_named(main_buttons, "control-buttons"); stack.add_named(label, "label"); this.add(stack); this.notify["audio-enabled"].connect(on_audio_enabled_changed); this.notify["video-enabled"].connect(on_video_enabled_changed); audio_enabled = true; video_enabled = false; on_audio_enabled_changed(); on_video_enabled_changed(); this.get_style_context().add_class("call-bottom-bar"); } public AudioSettingsPopover? show_audio_device_choices(bool show) { audio_settings_button.visible = show; if (audio_settings_popover != null) audio_settings_popover.visible = false; if (!show) return null; audio_settings_popover = new AudioSettingsPopover(); audio_settings_button.popover = audio_settings_popover; audio_settings_popover.set_relative_to(audio_settings_button); audio_settings_popover.microphone_selected.connect(() => { audio_settings_button.active = false; }); audio_settings_popover.speaker_selected.connect(() => { audio_settings_button.active = false; }); return audio_settings_popover; } public void show_audio_device_error() { audio_settings_button.set_image(new Image.from_icon_name("dialog-warning-symbolic", IconSize.BUTTON) { visible=true }); Util.force_error_color(audio_settings_button); } public VideoSettingsPopover? show_video_device_choices(bool show) { video_settings_button.visible = show; if (video_settings_popover != null) video_settings_popover.visible = false; if (!show) return null; video_settings_popover = new VideoSettingsPopover(); video_settings_button.popover = video_settings_popover; video_settings_popover.set_relative_to(video_settings_button); video_settings_popover.camera_selected.connect(() => { video_settings_button.active = false; }); return video_settings_popover; } public void show_video_device_error() { video_settings_button.set_image(new Image.from_icon_name("dialog-warning-symbolic", IconSize.BUTTON) { visible=true }); Util.force_error_color(video_settings_button); } public void on_audio_enabled_changed() { if (audio_enabled) { audio_image.set_from_icon_name("dino-microphone-symbolic", IconSize.LARGE_TOOLBAR); audio_button.get_style_context().add_class("white-button"); audio_button.get_style_context().remove_class("transparent-white-button"); } else { audio_image.set_from_icon_name("dino-microphone-off-symbolic", IconSize.LARGE_TOOLBAR); audio_button.get_style_context().remove_class("white-button"); audio_button.get_style_context().add_class("transparent-white-button"); } } public void on_video_enabled_changed() { if (video_enabled) { video_image.set_from_icon_name("dino-video-symbolic", IconSize.LARGE_TOOLBAR); video_button.get_style_context().add_class("white-button"); video_button.get_style_context().remove_class("transparent-white-button"); } else { video_image.set_from_icon_name("dino-video-off-symbolic", IconSize.LARGE_TOOLBAR); video_button.get_style_context().remove_class("white-button"); video_button.get_style_context().add_class("transparent-white-button"); } } public void show_counterpart_ended(string text) { stack.set_visible_child_name("label"); label.label = text; } public bool is_menu_active() { return video_settings_button.active || audio_settings_button.active; } }dino-0.3.0/main/src/ui/call_window/call_connection_details_window.vala0000644000000000000000000001164714202022370024667 0ustar rootrootusing Gtk; namespace Dino.Ui { public class CallConnectionDetailsWindow : Gtk.Window { public Box box = new Box(Orientation.VERTICAL, 15) { margin=10, halign=Align.CENTER, valign=Align.CENTER, visible=true }; private bool video_added = false; private CallContentDetails audio_details = new CallContentDetails("Audio") { visible=true }; private CallContentDetails video_details = new CallContentDetails("Video"); public CallConnectionDetailsWindow() { box.add(audio_details); box.add(video_details); add(box); } public void update_content(PeerInfo peer_info) { if (peer_info.audio != null) { audio_details.update_content(peer_info.audio); } if (peer_info.video != null) { add_video_widgets(); video_details.update_content(peer_info.video); } } private void add_video_widgets() { if (video_added) return; video_details.visible = true; video_added = true; } } public class CallContentDetails : Gtk.Grid { public Label rtp_title = new Label("RTP") { xalign=0, visible=true }; public Label rtcp_title = new Label("RTCP") { xalign=0, visible=true }; public Label target_recv_title = new Label("Target receive bitrate") { xalign=0, visible=true }; public Label target_send_title = new Label("Target send bitrate") { xalign=0, visible=true }; public Label rtp_ready = new Label("?") { xalign=0, visible=true }; public Label rtcp_ready = new Label("?") { xalign=0, visible=true }; public Label sent_bps = new Label("?") { use_markup=true, xalign=0, visible=true }; public Label recv_bps = new Label("?") { use_markup=true, xalign=0, visible=true }; public Label codec = new Label("?") { xalign=0, visible=true }; public Label target_receive_bitrate = new Label("n/a") { use_markup=true, xalign=0, visible=true }; public Label target_send_bitrate = new Label("n/a") { use_markup=true, xalign=0, visible=true }; private PeerContentInfo? prev_info = null; private int row_at = 0; public CallContentDetails(string headline) { attach(new Label("%s".printf(headline)) { use_markup=true, xalign=0, visible=true }, 0, row_at++, 1, 1); attach(rtp_title, 0, row_at, 1, 1); attach(rtp_ready, 1, row_at++, 1, 1); attach(rtcp_title, 0, row_at, 1, 1); attach(rtcp_ready, 1, row_at++, 1, 1); put_row("Sent"); attach(sent_bps, 1, row_at++, 1, 1); put_row("Received"); attach(recv_bps, 1, row_at++, 1, 1); put_row("Codec"); attach(codec, 1, row_at++, 1, 1); attach(target_recv_title, 0, row_at, 1, 1); attach(target_receive_bitrate, 1, row_at++, 1, 1); attach(target_send_title, 0, row_at, 1, 1); attach(target_send_bitrate, 1, row_at++, 1, 1); this.column_spacing = 5; } public void update_content(PeerContentInfo info) { if (!info.rtp_ready) { rtp_ready.visible = rtcp_ready.visible = true; rtp_title.visible = rtcp_title.visible = true; rtp_ready.label = info.rtp_ready.to_string(); rtcp_ready.label = info.rtcp_ready.to_string(); } else { rtp_ready.visible = rtcp_ready.visible = false; rtp_title.visible = rtcp_title.visible = false; } if (info.target_send_bytes != -1) { target_receive_bitrate.visible = target_send_bitrate.visible = true; target_recv_title.visible = target_send_title.visible = true; target_receive_bitrate.label = "%u kbps".printf(info.target_receive_bytes); target_send_bitrate.label = "%u kbps".printf(info.target_send_bytes); } else { target_receive_bitrate.visible = target_send_bitrate.visible = false; target_recv_title.visible = target_send_title.visible = false; } codec.label = info.codec + " " + info.clockrate.to_string(); if (prev_info != null) { ulong audio_sent_kbps = (info.bytes_sent - prev_info.bytes_sent) * 8 / 1000; sent_bps.label = "%lu kbps".printf(audio_sent_kbps); ulong audio_recv_kbps = (info.bytes_received - prev_info.bytes_received) * 8 / 1000; recv_bps.label = "%lu kbps".printf(audio_recv_kbps); } prev_info = info; } private void put_row(string label) { attach(new Label(label) { xalign=0, visible=true }, 0, row_at, 1, 1); } } } dino-0.3.0/main/src/ui/call_window/call_encryption_button.vala0000644000000000000000000000722014202022370023211 0ustar rootrootusing Dino.Entities; using Gtk; using Pango; public class Dino.Ui.CallEncryptionButton : MenuButton { private Image encryption_image = new Image.from_icon_name("", IconSize.BUTTON) { visible=true }; private bool has_been_set = false; public bool controls_active { get; set; default=false; } public CallEncryptionButton() { this.opacity = 0; add(encryption_image); this.set_popover(popover); this.notify["controls-active"].connect(update_opacity); } public void set_icon(bool encrypted, string? icon_name) { if (encrypted) { encryption_image.set_from_icon_name(icon_name ?? "changes-prevent-symbolic", IconSize.BUTTON); get_style_context().remove_class("unencrypted"); } else { encryption_image.set_from_icon_name(icon_name ?? "changes-allow-symbolic", IconSize.BUTTON); get_style_context().add_class("unencrypted"); } has_been_set = true; update_opacity(); } public void set_info(string? title, bool show_keys, Xmpp.Xep.Jingle.ContentEncryption? audio_encryption, Xmpp.Xep.Jingle.ContentEncryption? video_encryption) { Popover popover = new Popover(this); this.set_popover(popover); if (audio_encryption == null) { popover.add(new Label("This call is unencrypted.") { margin=10, visible=true } ); return; } if (title != null && !show_keys) { popover.add(new Label(title) { use_markup=true, margin=10, visible=true } ); return; } Box box = new Box(Orientation.VERTICAL, 10) { margin=10, visible=true }; box.add(new Label("%s".printf(title ?? "This call is end-to-end encrypted.")) { use_markup=true, xalign=0, visible=true }); if (video_encryption == null) { box.add(create_media_encryption_grid(audio_encryption)); } else { box.add(new Label("Audio") { use_markup=true, xalign=0, visible=true }); box.add(create_media_encryption_grid(audio_encryption)); box.add(new Label("Video") { use_markup=true, xalign=0, visible=true }); box.add(create_media_encryption_grid(video_encryption)); } popover.add(box); } public void update_opacity() { this.opacity = controls_active && has_been_set ? 1 : 0; } private Grid create_media_encryption_grid(Xmpp.Xep.Jingle.ContentEncryption? encryption) { Grid ret = new Grid() { row_spacing=3, column_spacing=5, visible=true }; if (encryption.peer_key.length > 0) { ret.attach(new Label("Peer call key") { xalign=0, visible=true }, 1, 2, 1, 1); ret.attach(new Label("" + format_fingerprint(encryption.peer_key) + "") { use_markup=true, max_width_chars=25, ellipsize=EllipsizeMode.MIDDLE, xalign=0, hexpand=true, visible=true }, 2, 2, 1, 1); } if (encryption.our_key.length > 0) { ret.attach(new Label("Your call key") { xalign=0, visible=true }, 1, 3, 1, 1); ret.attach(new Label("" + format_fingerprint(encryption.our_key) + "") { use_markup=true, max_width_chars=25, ellipsize=EllipsizeMode.MIDDLE, xalign=0, hexpand=true, visible=true }, 2, 3, 1, 1); } return ret; } private string format_fingerprint(uint8[] fingerprint) { var sb = new StringBuilder(); for (int i = 0; i < fingerprint.length; i++) { sb.append("%02x".printf(fingerprint[i])); if (i < fingerprint.length - 1) { sb.append(":"); } } return sb.str; } }dino-0.3.0/main/src/ui/call_window/call_window.vala0000644000000000000000000002574514202022370020747 0ustar rootrootusing Gee; using Xmpp; using Dino.Entities; using Gtk; namespace Dino.Ui { public class CallWindow : Gtk.Window { public signal void menu_dump_dot(); public CallWindowController controller; public Overlay overlay = new Overlay() { visible=true }; public Grid grid = new Grid() { visible=true }; public CallBottomBar bottom_bar = new CallBottomBar() { visible=true }; public Revealer bottom_bar_revealer = new Revealer() { valign=Align.END, transition_type=RevealerTransitionType.CROSSFADE, transition_duration=200, visible=true }; public HeaderBar header_bar = new HeaderBar() { valign=Align.START, halign=Align.END, show_close_button=true, visible=true, opacity=0.0 }; public Revealer header_bar_revealer = new Revealer() { halign=Align.END, valign=Align.START, transition_type=RevealerTransitionType.SLIDE_LEFT, transition_duration=200, visible=true, reveal_child=false }; public Box own_video_box = new Box(Orientation.HORIZONTAL, 0) { halign=Align.END, valign=Align.END, visible=true }; private Widget? own_video = null; private HashMap participant_widgets = new HashMap(); private ArrayList participants = new ArrayList(); private int own_video_width = 150; private int own_video_height = 100; private bool hide_control_elements = false; private uint hide_control_handler = 0; public bool controls_active { get; set; default=true; } construct { header_bar.get_style_context().add_class("call-header-bar"); header_bar.custom_title = new Box(Orientation.VERTICAL, 0); header_bar.spacing = 0; header_bar_revealer.add(header_bar); bottom_bar_revealer.add(bottom_bar); own_video_box.get_style_context().add_class("own-video"); this.get_style_context().add_class("dino-call-window"); overlay.add(grid); overlay.add_overlay(own_video_box); overlay.add_overlay(bottom_bar_revealer); overlay.add_overlay(header_bar_revealer); overlay.get_child_position.connect(on_get_child_position); add(overlay); } public CallWindow() { this.bind_property("controls-active", bottom_bar_revealer, "reveal-child", BindingFlags.SYNC_CREATE); this.motion_notify_event.connect(reveal_control_elements); this.enter_notify_event.connect(reveal_control_elements); this.leave_notify_event.connect(reveal_control_elements); this.configure_event.connect(reveal_control_elements); // upon resizing this.configure_event.connect(reposition_participant_widgets); this.set_titlebar(new OutsideHeaderBar(this.header_bar) { visible=true }); reveal_control_elements(); } public void add_participant(string participant, ParticipantWidget participant_widget) { participant_widget.visible = true; this.bind_property("controls-active", participant_widget, "controls-active", BindingFlags.SYNC_CREATE); this.bind_property("controls-active", participant_widget.encryption_button, "controls-active", BindingFlags.SYNC_CREATE); participants.add(participant); participant_widgets[participant] = participant_widget; grid.attach(participant_widget, 0, 0); reposition_participant_widgets(); } public void remove_participant(string participant) { participants.remove(participant); grid.remove(participant_widgets[participant]); participant_widgets.unset(participant); reposition_participant_widgets(); } public void set_video(string participant, Widget widget) { participant_widgets[participant].set_video(widget); hide_control_elements = true; timeout_hide_control_elements(); } public void set_placeholder(string participant, Conversation? conversation, StreamInteractor stream_interactor) { participant_widgets[participant].set_placeholder(conversation, stream_interactor); hide_control_elements = false; foreach (ParticipantWidget participant_widget in participant_widgets.values) { if (participant_widget.shows_video) { hide_control_elements = true; } } if (!hide_control_elements) { reveal_control_elements(); } } private bool reposition_participant_widgets() { int width, height; this.get_size(out width,out height); reposition_participant_widgets_rec(participants, width, height, 0, 0, 0, 0); return false; } private void reposition_participant_widgets_rec(ArrayList participants, int width, int height, int margin_top, int margin_right, int margin_bottom, int margin_left) { if (participants.size == 0) return; if (participants.size == 1) { participant_widgets[participants[0]].margin_top = margin_top; participant_widgets[participants[0]].margin_end = margin_right; participant_widgets[participants[0]].margin_bottom = margin_bottom; participant_widgets[participants[0]].margin_start = margin_left; participant_widgets[participants[0]].on_row_changed(margin_top == 0, margin_bottom == 0, margin_left == 0, margin_right == 0); return; } ArrayList first_part = new ArrayList(); ArrayList last_part = new ArrayList(); for (int i = 0; i < participants.size; i++) { if (i < Math.ceil((double)participants.size / (double)2)) { first_part.add(participants[i]); } else { last_part.add(participants[i]); } } if (width > height) { reposition_participant_widgets_rec(first_part, width / 2, height, margin_top, margin_right + width / 2, margin_bottom, margin_left); reposition_participant_widgets_rec(last_part, width / 2, height, margin_top, margin_right, margin_bottom, margin_left + width / 2); } else { reposition_participant_widgets_rec(first_part, width, height / 2, margin_top, margin_right, margin_bottom + height / 2, margin_left); reposition_participant_widgets_rec(last_part, width, height / 2, margin_top + height / 2, margin_right, margin_bottom, margin_left); } } public void set_own_video(Widget? widget_) { own_video_box.foreach((widget) => { own_video_box.remove(widget); }); own_video = widget_; if (own_video == null) { own_video = new Box(Orientation.HORIZONTAL, 0) { expand=true }; } own_video.visible = true; own_video_box.add(own_video); } public void set_own_video_ratio(int width, int height) { if (width / height > 150 / 100) { this.own_video_width = 150; this.own_video_height = height * 150 / width; } else { this.own_video_width = width * 100 / height; this.own_video_height = 100; } } public void unset_own_video() { own_video_box.foreach((widget) => { own_video_box.remove(widget); }); } public void set_status(string participant_id, string state) { participant_widgets[participant_id].set_status(state); } public void show_counterpart_ended(string who_terminated, string? reason_name, string? reason_text) { hide_control_elements = false; reveal_control_elements(); string text = ""; if (reason_name == Xmpp.Xep.Jingle.ReasonElement.SUCCESS) { text = _("%s ended the call").printf(who_terminated); } else if (reason_name == Xmpp.Xep.Jingle.ReasonElement.DECLINE || reason_name == Xmpp.Xep.Jingle.ReasonElement.BUSY) { text = _("%s declined the call").printf(who_terminated); } else { if (reason_text == null) { text = "The call has been terminated" + " " + (reason_name ?? ""); } else { text = reason_text + " " + (reason_name ?? ""); } } bottom_bar.show_counterpart_ended(text); } private bool reveal_control_elements() { if (!bottom_bar_revealer.child_revealed) { controls_active = true; } timeout_hide_control_elements(); return false; } private void timeout_hide_control_elements() { if (hide_control_handler != 0) { Source.remove(hide_control_handler); hide_control_handler = 0; } if (!hide_control_elements) { return; } hide_control_handler = Timeout.add_seconds(3, () => { if (!hide_control_elements) { return false; } if (bottom_bar.is_menu_active()) { return false; } controls_active = false; hide_control_handler = 0; return false; }); } private bool on_get_child_position(Widget widget, out Gdk.Rectangle allocation) { if (widget == own_video_box) { int width, height; this.get_size(out width,out height); allocation = Gdk.Rectangle(); allocation.width = own_video_width; allocation.height = own_video_height; allocation.x = width - own_video_width - 20; allocation.y = height - own_video_height - 20; return true; } return false; } } /* Hack to make the CallHeaderBar feel like a HeaderBar (right click menu, double click, ..) although it isn't set as headerbar. * OutsideHeaderBar is set as a headerbar and it doesn't take any space, but claims to take space (which is actually taken by CallHeaderBar). */ public class OutsideHeaderBar : Gtk.Box { HeaderBar header_bar; public OutsideHeaderBar(HeaderBar header_bar) { this.header_bar = header_bar; size_allocate.connect_after(on_header_bar_size_allocate); header_bar.size_allocate.connect(on_header_bar_size_allocate); } public void on_header_bar_size_allocate() { Allocation header_bar_alloc; header_bar.get_allocation(out header_bar_alloc); Allocation alloc; get_allocation(out alloc); alloc.height = header_bar_alloc.height; set_allocation(alloc); } } }dino-0.3.0/main/src/ui/call_window/call_window_controller.vala0000644000000000000000000004047314202022370023205 0ustar rootrootusing Xmpp; using Gee; using Dino.Entities; using Gtk; public class Dino.Ui.CallWindowController : Object { private CallWindow call_window; private Call call; private CallState call_state; private StreamInteractor stream_interactor; private Calls calls; private Plugins.VideoCallPlugin call_plugin = Dino.Application.get_default().plugin_registry.video_call_plugin; private Plugins.VideoCallWidget? own_video = null; private HashMap participant_videos = new HashMap(); private HashMap participant_widgets = new HashMap(); private HashMap peer_states = new HashMap(); private HashMap invite_handler_ids = new HashMap(); private int window_height = -1; private int window_width = -1; private bool window_size_changed = false; private ulong[] call_window_handler_ids = new ulong[0]; private ulong[] bottom_bar_handler_ids = new ulong[0]; public CallWindowController(CallWindow call_window, CallState call_state, StreamInteractor stream_interactor) { this.call_window = call_window; this.call = call_state.call; this.call_state = call_state; this.stream_interactor = stream_interactor; this.calls = stream_interactor.get_module(Calls.IDENTITY); this.own_video = call_plugin.create_widget(Plugins.WidgetType.GTK); call_window.set_default_size(704, 528); // 640x480 * 1.1 this.call_window.bottom_bar.video_enabled = call_state.should_we_send_video(); call_state.terminated.connect((who_terminated, reason_name, reason_text) => { Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(who_terminated.bare_jid, call.account, Conversation.Type.CHAT); string display_name = conversation != null ? Util.get_conversation_display_name(stream_interactor, conversation) : who_terminated.bare_jid.to_string(); call_window.show_counterpart_ended(display_name, reason_name, reason_text); Timeout.add_seconds(3, () => { call_window.close(); call_window.destroy(); return false; }); }); call_state.peer_joined.connect((jid, peer_state) => { connect_peer_signals(peer_state); add_new_participant(peer_state.internal_id, peer_state.jid); }); call_state.peer_left.connect((jid, peer_state, reason_name, reason_text) => { remove_participant(peer_state.internal_id); }); foreach (PeerState peer_state in call_state.peers.values) { connect_peer_signals(peer_state); add_new_participant(peer_state.internal_id, peer_state.jid); } // Call window signals bottom_bar_handler_ids += call_window.bottom_bar.hang_up.connect(() => { call_state.end(); call_window.close(); call_window.destroy(); this.dispose(); }); call_window_handler_ids += call_window.destroy.connect(() => { call_state.end(); this.dispose(); }); bottom_bar_handler_ids += call_window.bottom_bar.notify["audio-enabled"].connect(() => { call_state.mute_own_audio(!call_window.bottom_bar.audio_enabled); }); bottom_bar_handler_ids += call_window.bottom_bar.notify["video-enabled"].connect(() => { call_state.mute_own_video(!call_window.bottom_bar.video_enabled); update_own_video(); }); call_window_handler_ids += call_window.configure_event.connect((event) => { if (window_width == -1 || window_height == -1) return false; int current_height = this.call_window.get_allocated_height(); int current_width = this.call_window.get_allocated_width(); if (window_width != current_width || window_height != current_height) { debug("Call window size changed by user. Disabling auto window-to-video size adaptation. %i->%i x %i->%i", window_width, current_width, window_height, current_height); window_size_changed = true; } return false; }); call_window_handler_ids += call_window.realize.connect(() => { capture_window_size(); }); calls.conference_info_received.connect((call, conference_info) => { if (!this.call.equals(call)) return; var participants = new ArrayList(); participants.add_all(participant_videos.keys); foreach (string participant in participants) { remove_participant(participant); } foreach (Jid participant in conference_info.users.keys) { add_new_participant(participant.to_string(), participant); } }); own_video.resolution_changed.connect((width, height) => { if (width == 0 || height == 0) return; call_window.set_own_video_ratio((int)width, (int)height); }); call_window.menu_dump_dot.connect(() => { call_plugin.dump_dot(); }); update_own_video(); update_audio_device_choices(); update_video_device_choices(); } private void invite_button_clicked() { Gee.List acc_list = new ArrayList(Account.equals_func); acc_list.add(call.account); SelectContactDialog add_chat_dialog = new SelectContactDialog(stream_interactor, acc_list); add_chat_dialog.set_transient_for((Window) call_window.get_toplevel()); add_chat_dialog.title = _("Invite to Call"); add_chat_dialog.ok_button.label = _("Invite"); add_chat_dialog.selected.connect((account, jid) => { call_state.invite_to_call.begin(jid); }); add_chat_dialog.present(); } private void connect_peer_signals(PeerState peer_state) { string peer_id = peer_state.internal_id; Jid peer_jid = peer_state.jid; peer_states[peer_id] = peer_state; peer_state.connection_ready.connect(() => { call_window.set_status(peer_id, ""); if (participant_widgets.size == 1) { // This is the first peer. // If it can do MUJI, show invite button. call_state.can_convert_into_groupcall.begin((_, res) => { bool can_convert = call_state.can_convert_into_groupcall.end(res); participant_widgets[peer_id].may_show_invite_button = can_convert; }); call_plugin.devices_changed.connect((media, incoming) => { if (media == "audio") update_audio_device_choices(); if (media == "video") update_video_device_choices(); }); update_audio_device_choices(); update_video_device_choices(); } else if (participant_widgets.size > 1) { participant_widgets.values.@foreach((widget) => widget.may_show_invite_button = true); } }); peer_state.counterpart_sends_video_updated.connect((mute) => { if (mute) { Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(peer_jid.bare_jid, call.account, Conversation.Type.CHAT); call_window.set_placeholder(peer_id, conversation, stream_interactor); participant_videos[peer_id].detach(); } else { if (!(participant_videos[peer_id] is Widget)) return; Widget widget = (Widget) participant_videos[peer_id]; call_window.set_video(peer_id, widget); participant_videos[peer_id].display_stream(peer_state.get_video_stream(), peer_jid); } }); peer_state.info_received.connect((session_info) => { if (session_info == Xmpp.Xep.JingleRtp.CallSessionInfo.RINGING) { call_window.set_status(peer_id, "ringing"); } }); peer_state.encryption_updated.connect((audio_encryption, video_encryption, same) => { update_encryption_indicator(participant_widgets[peer_id].encryption_button, audio_encryption, video_encryption, same); }); } private void update_encryption_indicator(CallEncryptionButton encryption_button, Xep.Jingle.ContentEncryption? audio_encryption, Xep.Jingle.ContentEncryption? video_encryption, bool same) { string? title = null; string? icon_name = null; bool show_keys = true; Plugins.Registry registry = Dino.Application.get_default().plugin_registry; Plugins.CallEncryptionEntry? encryption_entry = audio_encryption != null ? registry.call_encryption_entries[audio_encryption.encryption_ns] : null; if (encryption_entry != null) { Plugins.CallEncryptionWidget? encryption_widgets = encryption_entry.get_widget(call.account, audio_encryption); if (encryption_widgets != null) { title = encryption_widgets.get_title(); icon_name = encryption_widgets.get_icon_name(); show_keys = encryption_widgets.show_keys(); } } encryption_button.set_info(title, show_keys, audio_encryption, same ? null : video_encryption); encryption_button.set_icon(audio_encryption != null, icon_name); } private void add_new_participant(string participant_id, Jid jid) { if (participant_widgets.has_key(participant_id)) { warning("[%s] Attempted to add same participant twice: %s", call.account.bare_jid.to_string(), jid.to_string()); return; } debug("[%s] Call window controller | Add participant: %s", call.account.bare_jid.to_string(), jid.to_string()); Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(jid.bare_jid, call.account, Conversation.Type.CHAT); string participant_name = conversation != null ? Util.get_conversation_display_name(stream_interactor, conversation) : jid.bare_jid.to_string(); ParticipantWidget participant_widget = new ParticipantWidget(participant_name); participant_widget.may_show_invite_button = !participant_widgets.is_empty; participant_widget.debug_information_clicked.connect(() => { var conn_details_window = new CallConnectionDetailsWindow() { title=participant_name, visible=true }; conn_details_window.update_content(peer_states[participant_id].get_info()); uint timeout_handle_id = Timeout.add_seconds(1, () => { conn_details_window.update_content(peer_states[participant_id].get_info()); return true; }); conn_details_window.set_transient_for(call_window); conn_details_window.destroy.connect(() => Source.remove(timeout_handle_id)); conn_details_window.present(); this.call_window.destroy.connect(() => conn_details_window.close() ); }); invite_handler_ids[participant_id] += participant_widget.invite_button_clicked.connect(() => invite_button_clicked()); participant_widgets[participant_id] = participant_widget; call_window.add_participant(participant_id, participant_widget); participant_videos[participant_id] = call_plugin.create_widget(Plugins.WidgetType.GTK); participant_videos[participant_id].resolution_changed.connect((width, height) => { if (window_size_changed || participant_widgets.size > 1) return; if (width == 0 || height == 0) return; if (width > height) { call_window.resize(704, (int) (height * 704 / width)); } else { call_window.resize((int) (width * 704 / height), 704); } capture_window_size(); }); participant_widget.set_placeholder(conversation, stream_interactor); if (call.direction == Call.DIRECTION_INCOMING) { call_window.set_status(participant_id, "establishing"); } else { call_window.set_status(participant_id, "requested"); } } private void remove_participant(string participant_id) { if (peer_states.has_key(participant_id)) debug(@"[%s] Call window controller | Remove participant: %s", call.account.bare_jid.to_string(), peer_states[participant_id].jid.to_string()); participant_videos.unset(participant_id); participant_widgets[participant_id].disconnect(invite_handler_ids[participant_id]); participant_widgets.unset(participant_id); invite_handler_ids.unset(participant_id); peer_states.unset(participant_id); call_window.remove_participant(participant_id); } private void capture_window_size() { Allocation allocation; this.call_window.get_allocation(out allocation); this.window_height = this.call_window.get_allocated_height(); this.window_width = this.call_window.get_allocated_width(); } private void update_audio_device_choices() { if (call_plugin.get_devices("audio", true).size == 0 || call_plugin.get_devices("audio", false).size == 0) { call_window.bottom_bar.show_audio_device_error(); } else if (call_plugin.get_devices("audio", true).size == 1 && call_plugin.get_devices("audio", false).size == 1) { call_window.bottom_bar.show_audio_device_choices(false); return; } AudioSettingsPopover? audio_settings_popover = call_window.bottom_bar.show_audio_device_choices(true); update_current_audio_device(audio_settings_popover); audio_settings_popover.microphone_selected.connect((device) => { call_state.set_audio_device(device); update_current_audio_device(audio_settings_popover); }); audio_settings_popover.speaker_selected.connect((device) => { call_state.set_audio_device(device); update_current_audio_device(audio_settings_popover); }); } private void update_current_audio_device(AudioSettingsPopover audio_settings_popover) { audio_settings_popover.current_microphone_device = call_state.get_microphone_device(); audio_settings_popover.current_speaker_device = call_state.get_speaker_device(); } private void update_video_device_choices() { int device_count = call_plugin.get_devices("video", false).size; if (device_count == 0) { call_window.bottom_bar.show_video_device_error(); } else if (device_count == 1 || call_state.get_video_device() == null) { call_window.bottom_bar.show_video_device_choices(false); return; } VideoSettingsPopover? video_settings_popover = call_window.bottom_bar.show_video_device_choices(true); update_current_video_device(video_settings_popover); video_settings_popover.camera_selected.connect((device) => { call_state.set_video_device(device); update_current_video_device(video_settings_popover); own_video.display_device(device); }); } private void update_current_video_device(VideoSettingsPopover video_settings_popover) { video_settings_popover.current_device = call_state.get_video_device(); } private void update_own_video() { if (this.call_window.bottom_bar.video_enabled) { Gee.List devices = call_plugin.get_devices("video", false); if (!(own_video is Widget) || devices.is_empty) { call_window.set_own_video(null); } else { Widget widget = (Widget) own_video; call_window.set_own_video(widget); own_video.display_device(call_state.get_video_device()); } } else { own_video.detach(); call_window.unset_own_video(); } } public override void dispose() { foreach (ulong handler_id in call_window_handler_ids) call_window.disconnect(handler_id); foreach (ulong handler_id in bottom_bar_handler_ids) call_window.bottom_bar.disconnect(handler_id); participant_widgets.keys.@foreach((peer_id) => { remove_participant(peer_id); return true; }); call_window_handler_ids = bottom_bar_handler_ids = new ulong[0]; base.dispose(); } } dino-0.3.0/main/src/ui/call_window/participant_widget.vala0000644000000000000000000001232014202022370022307 0ustar rootrootusing Pango; using Gee; using Xmpp; using Dino.Entities; using Gtk; namespace Dino.Ui { public class ParticipantWidget : Gtk.Overlay { public Widget main_widget; public HeaderBar header_bar = new HeaderBar() { valign=Align.START, visible=true }; public Box inner_box = new Box(Orientation.HORIZONTAL, 0) { margin_start=5, margin_top=5, hexpand=true, visible=true }; public Box title_box = new Box(Orientation.VERTICAL, 0) { valign=Align.CENTER, hexpand=true, visible=true }; public CallEncryptionButton encryption_button = new CallEncryptionButton() { opacity=0, relief=ReliefStyle.NONE, height_request=30, width_request=30, margin_end=5, visible=true }; public MenuButton menu_button = new MenuButton() { relief=ReliefStyle.NONE, visible=true }; public Button invite_button = new Button.from_icon_name("dino-account-plus") { relief=ReliefStyle.NONE, visible=true }; public bool shows_video = false; public string? participant_name; bool is_highest_row = false; bool is_start_row = false; public bool controls_active { get; set; } public bool may_show_invite_button { get; set; } public signal void debug_information_clicked(); public signal void invite_button_clicked(); public ParticipantWidget(string participant_name) { this.participant_name = participant_name; header_bar.title = participant_name; header_bar.get_style_context().add_class("participant-header-bar"); header_bar.pack_start(invite_button); header_bar.pack_start(encryption_button); header_bar.pack_end(menu_button); menu_button.image = new Image.from_icon_name("open-menu-symbolic", IconSize.MENU); menu_button.set_popover(create_menu()); invite_button.clicked.connect(() => invite_button_clicked()); this.add_overlay(header_bar); this.notify["controls-active"].connect(reveal_or_hide_controls); this.notify["may-show-invite-button"].connect(reveal_or_hide_controls); } public void on_row_changed(bool is_highest, bool is_lowest, bool is_start, bool is_end) { this.is_highest_row = is_highest; this.is_start_row = is_start; header_bar.show_close_button = is_highest_row; if (is_highest_row) { header_bar.get_style_context().add_class("call-header-background"); Gtk.Settings? gtk_settings = Gtk.Settings.get_default(); if (gtk_settings != null) { string[] buttons = gtk_settings.gtk_decoration_layout.split(":"); header_bar.decoration_layout = (is_start ? buttons[0] : "") + ":" + (is_end && buttons.length == 2 ? buttons[1] : ""); } } else { header_bar.get_style_context().remove_class("call-header-background"); } reveal_or_hide_controls(); } public void set_video(Widget widget) { shows_video = true; widget.visible = true; set_participant_widget(widget); } public void set_placeholder(Conversation? conversation, StreamInteractor stream_interactor) { shows_video = false; Box box = new Box(Orientation.HORIZONTAL, 0) { visible=true }; box.get_style_context().add_class("video-placeholder-box"); AvatarImage avatar = new AvatarImage() { allow_gray=false, hexpand=true, vexpand=true, halign=Align.CENTER, valign=Align.CENTER, height=100, width=100, visible=true }; if (conversation != null) { avatar.set_conversation(stream_interactor, conversation); } else { avatar.set_text("?", false); } box.add(avatar); set_participant_widget(box); } private void set_participant_widget(Widget widget) { widget.expand = true; if (main_widget != null) this.remove(main_widget); main_widget = widget; this.add(main_widget); } private PopoverMenu create_menu() { PopoverMenu menu = new PopoverMenu(); Box box = new Box(Orientation.VERTICAL, 0) { margin=10, visible=true }; ModelButton debug_information_button = new ModelButton() { text=_("Debug information"), visible=true }; debug_information_button.clicked.connect(() => debug_information_clicked()); box.add(debug_information_button); menu.add(box); return menu; } public void set_status(string state) { if (state == "requested") { header_bar.subtitle = _("Calling…"); } else if (state == "ringing") { header_bar.subtitle = _("Ringing…"); } else if (state == "establishing") { header_bar.subtitle = _("Connecting…"); } else { header_bar.subtitle = ""; } } private void reveal_or_hide_controls() { header_bar.opacity = controls_active ? 1.0 : 0.0; invite_button.visible = may_show_invite_button && is_highest_row && is_start_row; } } }dino-0.3.0/main/src/ui/call_window/video_settings_popover.vala0000644000000000000000000000714514202022370023237 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; public class Dino.Ui.VideoSettingsPopover : Gtk.Popover { public signal void camera_selected(Plugins.MediaDevice device); public Plugins.MediaDevice? current_device { get; set; } private HashMap row_device = new HashMap(); public VideoSettingsPopover() { Box box = new Box(Orientation.VERTICAL, 15) { margin=18, visible=true }; box.add(create_camera_box()); this.add(box); } private Widget create_camera_box() { Plugins.VideoCallPlugin call_plugin = Dino.Application.get_default().plugin_registry.video_call_plugin; Gee.List devices = call_plugin.get_devices("video", false); Box camera_box = new Box(Orientation.VERTICAL, 10) { visible=true }; camera_box.add(new Label("" + _("Cameras") + "") { use_markup=true, xalign=0, visible=true, can_focus=true /* grab initial focus*/ }); if (devices.size == 0) { camera_box.add(new Label(_("No camera found.")) { visible=true }); } else { ListBox list_box = new ListBox() { activate_on_single_click=true, selection_mode=SelectionMode.SINGLE, visible=true }; list_box.set_header_func(listbox_header_func); Frame frame = new Frame(null) { visible=true }; frame.add(list_box); foreach (Plugins.MediaDevice device in devices) { Label display_name_label = new Label(device.display_name) { xalign=0, visible=true }; Image image = new Image.from_icon_name("object-select-symbolic", IconSize.BUTTON) { visible=true }; if (current_device == null || current_device.id != device.id) { image.opacity = 0; } this.notify["current-device"].connect(() => { if (current_device == null || current_device.id != device.id) { image.opacity = 0; } else { image.opacity = 1; } }); Box device_box = new Box(Orientation.HORIZONTAL, 0) { spacing=7, margin=7, visible=true }; device_box.add(image); Box label_box = new Box(Orientation.VERTICAL, 0) { visible = true }; label_box.add(display_name_label); if (device.detail_name != null) { Label detail_name_label = new Label(device.detail_name) { xalign=0, visible=true }; detail_name_label.get_style_context().add_class("dim-label"); detail_name_label.attributes = new Pango.AttrList(); detail_name_label.attributes.insert(Pango.attr_scale_new(0.8)); label_box.add(detail_name_label); } device_box.add(label_box); ListBoxRow list_box_row = new ListBoxRow() { visible=true }; list_box_row.add(device_box); list_box.add(list_box_row); row_device[list_box_row] = device; } list_box.row_activated.connect((row) => { if (!row_device.has_key(row)) return; camera_selected(row_device[row]); list_box.unselect_row(row); }); camera_box.add(frame); } return camera_box; } private void listbox_header_func(ListBoxRow row, ListBoxRow? before_row) { if (row.get_header() == null && before_row != null) { row.set_header(new Separator(Orientation.HORIZONTAL)); } } }dino-0.3.0/main/src/ui/chat_input/0000755000000000000000000000000014202022370015417 5ustar rootrootdino-0.3.0/main/src/ui/chat_input/chat_input_controller.vala0000644000000000000000000002140314202022370022665 0ustar rootrootusing Gee; using Gdk; using Gtk; using Dino.Entities; namespace Dino.Ui { private const string OPEN_CONVERSATION_DETAILS_URI = "x-dino:open-conversation-details"; public class ChatInputController : Object { public signal void activate_last_message_correction(); public signal void file_picker_selected(); public signal void clipboard_pasted(); public new string? conversation_display_name { get; set; } public string? conversation_topic { get; set; } private Conversation? conversation; private ChatInput.View chat_input; private Label status_description_label; private StreamInteractor stream_interactor; private Plugins.InputFieldStatus input_field_status; private ChatTextViewController chat_text_view_controller; public ChatInputController(ChatInput.View chat_input, StreamInteractor stream_interactor) { this.chat_input = chat_input; this.status_description_label = chat_input.chat_input_status; this.stream_interactor = stream_interactor; this.chat_text_view_controller = new ChatTextViewController(chat_input.chat_text_view, stream_interactor); chat_input.init(stream_interactor); reset_input_field_status(); chat_input.chat_text_view.text_view.buffer.changed.connect(on_text_input_changed); chat_input.chat_text_view.text_view.key_press_event.connect(on_text_input_key_press); chat_input.chat_text_view.text_view.paste_clipboard.connect(() => clipboard_pasted()); chat_text_view_controller.send_text.connect(send_text); chat_input.encryption_widget.encryption_changed.connect(on_encryption_changed); chat_input.file_button.clicked.connect(() => file_picker_selected()); stream_interactor.get_module(MucManager.IDENTITY).received_occupant_role.connect(update_moderated_input_status); stream_interactor.get_module(MucManager.IDENTITY).room_info_updated.connect(update_moderated_input_status); status_description_label.activate_link.connect((uri) => { if (uri == OPEN_CONVERSATION_DETAILS_URI){ ContactDetails.Dialog contact_details_dialog = new ContactDetails.Dialog(stream_interactor, conversation); contact_details_dialog.set_transient_for((Gtk.Window) chat_input.get_toplevel()); contact_details_dialog.present(); } return true; }); } public void set_conversation(Conversation conversation) { this.conversation = conversation; reset_input_field_status(); chat_input.encryption_widget.set_conversation(conversation); chat_input.initialize_for_conversation(conversation); chat_text_view_controller.initialize_for_conversation(conversation); update_moderated_input_status(conversation.account); } public void set_file_upload_active(bool active) { chat_input.set_file_upload_active(active); } private void on_encryption_changed(Plugins.EncryptionListEntry? encryption_entry) { reset_input_field_status(); if (encryption_entry == null) return; encryption_entry.encryption_activated(conversation, set_input_field_status); } private void set_input_field_status(Plugins.InputFieldStatus? status) { input_field_status = status; chat_input.set_input_state(status.message_type); status_description_label.use_markup = status.contains_markup; status_description_label.label = status.message; chat_input.file_button.sensitive = status.input_state == Plugins.InputFieldStatus.InputState.NORMAL; } private void reset_input_field_status() { set_input_field_status(new Plugins.InputFieldStatus("", Plugins.InputFieldStatus.MessageType.NONE, Plugins.InputFieldStatus.InputState.NORMAL)); } private void send_text() { // Don't do anything if we're in a NO_SEND state. Don't clear the chat input, don't send. if (input_field_status.input_state == Plugins.InputFieldStatus.InputState.NO_SEND) { chat_input.highlight_state_description(); return; } string text = chat_input.chat_text_view.text_view.buffer.text; chat_input.chat_text_view.text_view.buffer.text = ""; if (text.has_prefix("/")) { string[] token = text.split(" ", 2); switch(token[0]) { case "/me": // Just send as is. break; case "/say": if (token.length == 1) return; text = token[1]; break; case "/kick": stream_interactor.get_module(MucManager.IDENTITY).kick(conversation.account, conversation.counterpart, token[1]); return; case "/affiliate": if (token.length > 1) { string[] user_role = token[1].split(" "); if (user_role.length >= 2) { string nick = string.joinv(" ", user_role[0:user_role.length - 1]).strip(); string role = user_role[user_role.length - 1].strip(); stream_interactor.get_module(MucManager.IDENTITY).change_affiliation(conversation.account, conversation.counterpart, nick, role); } } return; case "/nick": stream_interactor.get_module(MucManager.IDENTITY).change_nick.begin(conversation, token[1]); return; case "/ping": Xmpp.XmppStream? stream = stream_interactor.get_stream(conversation.account); try { stream.get_module(Xmpp.Xep.Ping.Module.IDENTITY).send_ping.begin(stream, conversation.counterpart.with_resource(token[1]), null); } catch (Xmpp.InvalidJidError e) { warning("Could not ping invalid Jid: %s", e.message); } return; case "/topic": stream_interactor.get_module(MucManager.IDENTITY).change_subject(conversation.account, conversation.counterpart, token[1]); return; default: if (token[0].has_prefix("//")) { text = text.substring(1); } else { string cmd_name = token[0].substring(1); Dino.Application app = GLib.Application.get_default() as Dino.Application; if (app != null && app.plugin_registry.text_commands.has_key(cmd_name)) { string? new_text = app.plugin_registry.text_commands[cmd_name].handle_command(token[1], conversation); if (new_text == null) return; text = (!)new_text; } } break; } } stream_interactor.get_module(MessageProcessor.IDENTITY).send_text(text, conversation); } private void on_text_input_changed() { if (chat_input.chat_text_view.text_view.buffer.text != "") { stream_interactor.get_module(ChatInteraction.IDENTITY).on_message_entered(conversation); } else { stream_interactor.get_module(ChatInteraction.IDENTITY).on_message_cleared(conversation); } } private void update_moderated_input_status(Account account, Xmpp.Jid? jid = null) { if (conversation != null && conversation.type_ == Conversation.Type.GROUPCHAT){ Xmpp.Jid? own_jid = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account); if (own_jid == null) return; if (stream_interactor.get_module(MucManager.IDENTITY).is_moderated_room(conversation.account, conversation.counterpart) && stream_interactor.get_module(MucManager.IDENTITY).get_role(own_jid, conversation.account) == Xmpp.Xep.Muc.Role.VISITOR) { string msg_str = _("This conference does not allow you to send messages.") + " " + _("Request permission") + ""; set_input_field_status(new Plugins.InputFieldStatus(msg_str, Plugins.InputFieldStatus.MessageType.ERROR, Plugins.InputFieldStatus.InputState.NO_SEND, true)); } else { reset_input_field_status(); } } } private bool on_text_input_key_press(EventKey event) { if (event.keyval == Gdk.Key.Up && chat_input.chat_text_view.text_view.buffer.text == "") { activate_last_message_correction(); return true; } else { chat_input.chat_text_view.text_view.grab_focus(); } return false; } } } dino-0.3.0/main/src/ui/chat_input/chat_text_view.vala0000644000000000000000000000605214202022370021304 0ustar rootrootusing Gdk; using Gee; using Gtk; using Dino.Entities; using Xmpp; namespace Dino.Ui { public class ChatTextViewController : Object { public signal void send_text(); public OccupantsTabCompletor occupants_tab_completor; private ChatTextView widget; public ChatTextViewController(ChatTextView widget, StreamInteractor stream_interactor) { this.widget = widget; occupants_tab_completor = new OccupantsTabCompletor(stream_interactor, widget.text_view); widget.send_text.connect(() => { send_text(); }); } public void initialize_for_conversation(Conversation conversation) { occupants_tab_completor.initialize_for_conversation(conversation); widget.initialize_for_conversation(conversation); } } public class ChatTextView : ScrolledWindow { public signal void send_text(); public signal void cancel_input(); public TextView text_view = new TextView() { can_focus=true, hexpand=true, margin=8, wrap_mode=Gtk.WrapMode.WORD_CHAR, valign=Align.CENTER, visible=true }; private int vscrollbar_min_height; private SmileyConverter smiley_converter; public EditHistory edit_history; private SpellChecker spell_checker; construct { max_content_height = 300; propagate_natural_height = true; this.add(text_view); smiley_converter = new SmileyConverter(text_view); edit_history = new EditHistory(text_view); spell_checker = new SpellChecker(text_view); this.get_vscrollbar().get_preferred_height(out vscrollbar_min_height, null); this.vadjustment.notify["upper"].connect_after(on_upper_notify); text_view.key_press_event.connect(on_text_input_key_press); Gtk.drag_dest_unset(text_view); } public void initialize_for_conversation(Conversation conversation) { edit_history.initialize_for_conversation(conversation); spell_checker.initialize_for_conversation(conversation); } public override void get_preferred_height(out int min_height, out int nat_height) { base.get_preferred_height(out min_height, out nat_height); min_height = nat_height; } private void on_upper_notify() { this.vadjustment.value = this.vadjustment.upper - this.vadjustment.page_size; // hack for vscrollbar not requiring space and making textview higher //TODO doesn't resize immediately this.get_vscrollbar().visible = (this.vadjustment.upper > this.max_content_height - 2 * this.vscrollbar_min_height); } private bool on_text_input_key_press(EventKey event) { if (event.keyval in new uint[]{Key.Return, Key.KP_Enter}) { if ((event.state & ModifierType.SHIFT_MASK) > 0) { text_view.buffer.insert_at_cursor("\n", 1); } else if (text_view.buffer.text.strip() != "") { send_text(); edit_history.reset_history(); } return true; } if (event.keyval == Key.Escape) { cancel_input(); } return false; } } } dino-0.3.0/main/src/ui/chat_input/edit_history.vala0000644000000000000000000000522514202022370020776 0ustar rootrootusing Gdk; using Gee; using Gtk; using Dino.Entities; namespace Dino.Ui { public class EditHistory { private Conversation? conversation; private TextView text_input; private HashMap> histories = new HashMap>(Conversation.hash_func, Conversation.equals_func); private HashMap indices = new HashMap(Conversation.hash_func, Conversation.equals_func); public EditHistory(TextView text_input) { this.text_input = text_input; text_input.key_press_event.connect(on_text_input_key_press); text_input.cut_clipboard.connect_after(save_state); text_input.paste_clipboard.connect_after(save_state); text_input.move_cursor.connect_after(save_state); text_input.button_release_event.connect_after(() => { save_state(); return false; }); } public void initialize_for_conversation(Conversation conversation) { this.conversation = conversation; if (!histories.has_key(conversation)) { reset_history(); } } public bool on_text_input_key_press(EventKey event) { bool ctrl_pressed = (event.state & ModifierType.CONTROL_MASK) > 0; if (ctrl_pressed && event.keyval == Key.z) { undo(); } else if (ctrl_pressed && (event.keyval in new uint[]{ Key.Z, Key.y } )) { redo(); } else if (event.keyval in new uint[]{ Key.space, Key.Tab, Key.ISO_Left_Tab }) { save_state(); } return false; } private void undo() { save_state(); if (indices[conversation] > 0) { indices[conversation] = indices[conversation] - 1; text_input.buffer.text = histories[conversation][indices[conversation]]; } } private void redo() { if (indices[conversation] < histories[conversation].size - 1) { indices[conversation] = indices[conversation] + 1; text_input.buffer.text = histories[conversation][indices[conversation]]; } } private void save_state() { if (histories[conversation][indices[conversation]] == text_input.buffer.text) return; if (indices[conversation] < histories[conversation].size - 1) { histories[conversation] = histories[conversation].slice(0, indices[conversation] + 1); } histories[conversation].add(text_input.buffer.text); indices[conversation] = indices[conversation] + 1; } public void reset_history() { histories[conversation] = new ArrayList(); histories[conversation].add(""); indices[conversation] = 0; } } } dino-0.3.0/main/src/ui/chat_input/encryption_button.vala0000644000000000000000000001013714202022370022053 0ustar rootrootusing Gtk; using Gee; using Dino.Entities; namespace Dino.Ui { public class EncryptionButton : MenuButton { public signal void encryption_changed(Plugins.EncryptionListEntry? encryption_entry); private Conversation? conversation; private RadioButton? button_unencrypted; private Map encryption_radios = new HashMap(); private string? current_icon; private StreamInteractor stream_interactor; public EncryptionButton(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; use_popover = true; image = new Image.from_icon_name("changes-allow-symbolic", IconSize.BUTTON); get_style_context().add_class("flat"); Builder builder = new Builder.from_resource("/im/dino/Dino/menu_encryption.ui"); popover = builder.get_object("menu_encryption") as PopoverMenu; Box encryption_box = builder.get_object("encryption_box") as Box; button_unencrypted = builder.get_object("button_unencrypted") as RadioButton; button_unencrypted.toggled.connect(encryption_button_toggled); stream_interactor.get_module(MucManager.IDENTITY).room_info_updated.connect((account, muc_jid) => { if (conversation != null && conversation.account.equals(account) && conversation.counterpart.equals(muc_jid)) { update_visibility(); } }); Application app = GLib.Application.get_default() as Application; foreach (var e in app.plugin_registry.encryption_list_entries) { RadioButton btn = new RadioButton.with_label(button_unencrypted.get_group(), e.name); encryption_radios[btn] = e; btn.toggled.connect(encryption_button_toggled); btn.visible = true; encryption_box.pack_end(btn, false); } clicked.connect(update_encryption_menu_state); } private void encryption_button_toggled() { foreach (RadioButton e in encryption_radios.keys) { if (e.get_active()) { conversation.encryption = encryption_radios[e].encryption; encryption_changed(encryption_radios[e]); update_encryption_menu_icon(); return; } } // Selected unencrypted conversation.encryption = Encryption.NONE; update_encryption_menu_icon(); encryption_changed(null); } private void update_encryption_menu_state() { foreach (RadioButton e in encryption_radios.keys) { if (conversation.encryption == encryption_radios[e].encryption) { e.set_active(true); encryption_changed(encryption_radios[e]); } } if (conversation.encryption == Encryption.NONE) { button_unencrypted.set_active(true); encryption_changed(null); } } private void set_icon(string icon) { if (icon != current_icon) { image = new Image.from_icon_name(icon, IconSize.BUTTON); current_icon = icon; } } private void update_encryption_menu_icon() { set_icon(conversation.encryption == Encryption.NONE ? "changes-allow-symbolic" : "changes-prevent-symbolic"); } private void update_visibility() { if (conversation.encryption != Encryption.NONE) { visible = true; return; } switch (conversation.type_) { case Conversation.Type.CHAT: visible = true; break; case Conversation.Type.GROUPCHAT_PM: visible = false; break; case Conversation.Type.GROUPCHAT: visible = stream_interactor.get_module(MucManager.IDENTITY).is_private_room(conversation.account, conversation.counterpart); break; } } public new void set_conversation(Conversation conversation) { this.conversation = conversation; update_encryption_menu_state(); update_encryption_menu_icon(); update_visibility(); } } } dino-0.3.0/main/src/ui/chat_input/occupants_tab_completer.vala0000644000000000000000000001163514202022370023171 0ustar rootrootusing Gdk; using Gee; using Gtk; using Dino.Entities; using Xmpp; namespace Dino.Ui { /** * - With given prefix: Complete from occupant list (sorted lexicographically) * - W/o prefix: Complete from received messages (most recent first) * - At the start (with ",") and in the middle of a text * - Backwards tabbing */ public class OccupantsTabCompletor { private StreamInteractor stream_interactor; private Conversation? conversation; private TextView text_input; private Gee.List completions = new ArrayList(); private bool active = false; private int index = -1; public OccupantsTabCompletor(StreamInteractor stream_interactor, TextView text_input) { this.stream_interactor = stream_interactor; this.text_input = text_input; text_input.key_press_event.connect(on_text_input_key_press); } public void initialize_for_conversation(Conversation conversation) { this.conversation = conversation; } public bool on_text_input_key_press(EventKey event) { if (conversation.type_ == Conversation.Type.GROUPCHAT) { if (event.keyval == Key.Tab || event.keyval == Key.ISO_Left_Tab) { string text = text_input.buffer.text; int start_index = int.max(text.last_index_of(" "), text.last_index_of("\n")) + 1; string word = text.substring(start_index); if (!active) { if (word == "") { completions = generate_completions_from_messages(); } else { completions = generate_completions_from_occupants(word); } if (completions.size > 0) { active = true; index = -1; } } if (event.keyval != Key.ISO_Group_Shift && active) { text_input.buffer.text = next_completion(event.keyval == Key.ISO_Left_Tab); return true; } } else if (event.keyval != Key.Shift_L && active) { active = false; } } return false; } private string next_completion(bool backwards) { string text = text_input.buffer.text; int start_index = int.max(text.last_index_of(" "), text.last_index_of("\n")) + 1; string prev_completion = text.substring(start_index); if (index > -1) { start_index = int.max( text.last_index_of(completions[index]), text.substring(0, text.length - 1).last_index_of("\n") ); prev_completion = text.substring(start_index); } if (backwards) { index = int.max(index, 0) - 1; if (index < 0) index = completions.size - 1; } else { index = (index + 1) % (completions.size); } if (start_index == 0) { return completions[index] + ", "; } else { return text.substring(0, text.length - prev_completion.length) + completions[index] + " "; } } private Gee.List generate_completions_from_messages(string? prefix = null) { Gee.List ret = new ArrayList(); Gee.List content_items = stream_interactor.get_module(ContentItemStore.IDENTITY).get_n_latest(conversation, 10); for (int i = content_items.size - 1; i > 0; i--) { string resourcepart = content_items[i].jid.resourcepart; Jid? own_nick = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account); if (resourcepart != null && resourcepart != "" && (own_nick == null || resourcepart != own_nick.resourcepart) && !ret.contains(resourcepart)) { if (prefix != null && !resourcepart.to_string().down().has_prefix(prefix.down())) continue; ret.add(resourcepart); } } return ret; } private Gee.List generate_completions_from_occupants(string prefix) { // First suggest nicks that have recently writen something Gee.List ret = generate_completions_from_messages(prefix); // Then, suggest other nicks in alphabetical order Gee.List? occupants = stream_interactor.get_module(MucManager.IDENTITY).get_other_occupants(conversation.counterpart, conversation.account); Gee.List filtered_occupants = new ArrayList(); if (occupants != null) { foreach (Jid jid in occupants) { string resourcepart = jid.resourcepart.to_string(); if (resourcepart.down().has_prefix(prefix.down()) && !ret.contains(resourcepart)) { filtered_occupants.add(resourcepart); } } } filtered_occupants.sort(); ret.add_all(filtered_occupants); return ret; } } } dino-0.3.0/main/src/ui/chat_input/smiley_converter.vala0000644000000000000000000000506614202022370021664 0ustar rootrootusing Gdk; using Gee; using Gtk; using Dino.Entities; namespace Dino.Ui { class SmileyConverter { private TextView text_input; private static HashMap smiley_translations = new HashMap(); private Regex whitespace_regex = /\s/; static construct { smiley_translations[":)"] = "🙂"; smiley_translations[":D"] = "😀"; smiley_translations[";)"] = "😉"; smiley_translations["O:)"] = "😇"; smiley_translations["O:-)"] = "😇"; smiley_translations["]:>"] = "😈"; smiley_translations[":o"] = "😮"; smiley_translations[":P"] = "😛"; smiley_translations[";P"] = "😜"; smiley_translations[":("] = "🙁"; smiley_translations[":'("] = "😢"; smiley_translations[":/"] = "😕"; smiley_translations["<3"] = "❤️"; smiley_translations[":*"] = "😘️"; smiley_translations[":-*"] = "😘️"; } public SmileyConverter(TextView text_input) { this.text_input = text_input; text_input.key_press_event.connect(on_text_input_key_press); } public bool on_text_input_key_press(EventKey event) { if (event.keyval == Key.space || event.keyval == Key.Return) { check_convert(); } return false; } private void check_convert() { if (!Dino.Application.get_default().settings.convert_utf8_smileys) return; TextIter? start_iter; text_input.buffer.get_start_iter(out start_iter); TextIter? cursor_iter; text_input.buffer.get_iter_at_mark(out cursor_iter, text_input.buffer.get_insert()); if (start_iter == null || cursor_iter == null) return; string text = text_input.buffer.get_text(start_iter, cursor_iter, true); foreach (string smiley in smiley_translations.keys) { if (text.has_suffix(smiley)) { if (text.length == smiley.length || whitespace_regex.match(text[text.length - smiley.length - 1].to_string())) { TextIter? end_iter; text_input.buffer.get_end_iter(out end_iter); if (end_iter == null) continue; TextIter smiley_start_iter = cursor_iter; smiley_start_iter.backward_chars(smiley.length); text_input.buffer.delete(ref smiley_start_iter, ref cursor_iter); text_input.buffer.insert_text(ref cursor_iter, smiley_translations[smiley], smiley_translations[smiley].length); } } } } } } dino-0.3.0/main/src/ui/chat_input/spell_checker.vala0000644000000000000000000000621214202022370021070 0ustar rootrootusing Gdk; using Gee; using Dino.Entities; namespace Dino.Ui { public class SpellChecker { private Conversation? conversation; private Gtk.TextView text_input; public SpellChecker(Gtk.TextView text_input) { this.text_input = text_input; // We can't keep a reference to GspellTextView/Buffer around, otherwise they'd get freed twice Gspell.TextView text_view = Gspell.TextView.get_from_gtk_text_view(text_input); Gspell.TextBuffer text_buffer = Gspell.TextBuffer.get_from_gtk_text_buffer(text_view.view.buffer); text_view.basic_setup(); text_buffer.spell_checker.notify["language"].connect(lang_changed); // Enable/Disable spell checking live Dino.Application.get_default().settings.notify["check-spelling"].connect((obj, _) => { if (((Dino.Entities.Settings) obj).check_spelling) { initialize_for_conversation(this.conversation); } else { text_buffer.set_spell_checker(null); } }); } public void initialize_for_conversation(Conversation conversation) { this.conversation = conversation; Gspell.TextView text_view = Gspell.TextView.get_from_gtk_text_view(text_input); Gspell.TextBuffer text_buffer = Gspell.TextBuffer.get_from_gtk_text_buffer(text_view.view.buffer); if (!Dino.Application.get_default().settings.check_spelling) { text_buffer.set_spell_checker(null); return; } if (text_buffer.spell_checker == null) text_buffer.spell_checker = new Gspell.Checker(null); // Set the conversation language (from cache or db) text_buffer.spell_checker.notify["language"].disconnect(lang_changed); var db = Dino.Application.get_default().db; Qlite.RowOption row_option = db.conversation_settings.select() .with(db.conversation_settings.conversation_id, "=", conversation.id) .with(db.conversation_settings.key, "=", "lang") .single().row(); if (row_option.is_present()) { string lang_code = row_option.inner[db.conversation_settings.value]; Gspell.Language? lang = Gspell.Language.lookup(lang_code); text_buffer.spell_checker.language = lang; } else { text_buffer.spell_checker.language = null; } text_buffer.spell_checker.notify["language"].connect(lang_changed); } private void lang_changed() { var db = Dino.Application.get_default().db; Gspell.TextView text_view = Gspell.TextView.get_from_gtk_text_view(text_input); Gspell.TextBuffer text_buffer = Gspell.TextBuffer.get_from_gtk_text_buffer(text_view.view.buffer); Gspell.Checker spell_checker = text_buffer.spell_checker; if (spell_checker.language.get_code() == null) return; db.conversation_settings.upsert() .value(db.conversation_settings.conversation_id, conversation.id, true) .value(db.conversation_settings.key, "lang", true) .value(db.conversation_settings.value, spell_checker.language.get_code()) .perform(); } } } dino-0.3.0/main/src/ui/chat_input/view.vala0000644000000000000000000001051214202022370017235 0ustar rootrootusing Gdk; using Gee; using Gtk; using Dino.Entities; using Xmpp; namespace Dino.Ui.ChatInput { [GtkTemplate (ui = "/im/dino/Dino/chat_input.ui")] public class View : Box { public string text { owned get { return chat_text_view.text_view.buffer.text; } set { chat_text_view.text_view.buffer.text = value; } } private StreamInteractor stream_interactor; private Conversation? conversation; private HashMap entry_cache = new HashMap(Conversation.hash_func, Conversation.equals_func); [GtkChild] public unowned Frame frame; [GtkChild] public unowned ChatTextView chat_text_view; [GtkChild] public unowned Box outer_box; [GtkChild] public unowned Button file_button; [GtkChild] public unowned Separator file_separator; [GtkChild] public unowned Label chat_input_status; public EncryptionButton encryption_widget; public View init(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; encryption_widget = new EncryptionButton(stream_interactor) { relief=ReliefStyle.NONE, margin_top=3, valign=Align.START, visible=true }; file_button.get_style_context().add_class("dino-attach-button"); encryption_widget.get_style_context().add_class("dino-chatinput-button"); // Emoji button for emoji picker (recents don't work < 3.22.19, category icons don't work <3.23.2) if (Gtk.get_major_version() >= 3 && Gtk.get_minor_version() >= 24) { MenuButton emoji_button = new MenuButton() { relief=ReliefStyle.NONE, margin_top=3, valign=Align.START, visible=true }; emoji_button.get_style_context().add_class("flat"); emoji_button.get_style_context().add_class("dino-chatinput-button"); emoji_button.image = new Image.from_icon_name("dino-emoticon-symbolic", IconSize.BUTTON) { visible=true }; EmojiChooser chooser = new EmojiChooser(); chooser.emoji_picked.connect((emoji) => { chat_text_view.text_view.buffer.insert_at_cursor(emoji, emoji.data.length); }); emoji_button.set_popover(chooser); outer_box.add(emoji_button); } outer_box.add(encryption_widget); Util.force_css(frame, "* { border-radius: 3px; }"); return this; } public void set_file_upload_active(bool active) { file_button.visible = active; file_separator.visible = active; } public void initialize_for_conversation(Conversation conversation) { if (this.conversation != null) entry_cache[this.conversation] = chat_text_view.text_view.buffer.text; this.conversation = conversation; chat_text_view.text_view.buffer.text = ""; if (entry_cache.has_key(conversation)) { chat_text_view.text_view.buffer.text = entry_cache[conversation]; } do_focus(); } public void set_input_state(Plugins.InputFieldStatus.MessageType message_type) { switch (message_type) { case Plugins.InputFieldStatus.MessageType.NONE: this.get_style_context().remove_class("dino-input-warning"); this.get_style_context().remove_class("dino-input-error"); break; case Plugins.InputFieldStatus.MessageType.INFO: this.get_style_context().remove_class("dino-input-warning"); this.get_style_context().remove_class("dino-input-error"); break; case Plugins.InputFieldStatus.MessageType.WARNING: this.get_style_context().add_class("dino-input-warning"); this.get_style_context().remove_class("dino-input-error"); break; case Plugins.InputFieldStatus.MessageType.ERROR: this.get_style_context().remove_class("dino-input-warning"); this.get_style_context().add_class("dino-input-error"); break; } } public void highlight_state_description() { chat_input_status.get_style_context().add_class("input-status-highlight-once"); Timeout.add_seconds(1, () => { chat_input_status.get_style_context().remove_class("input-status-highlight-once"); return false; }); } public void do_focus() { chat_text_view.text_view.grab_focus(); } } } dino-0.3.0/main/src/ui/contact_details/0000755000000000000000000000000014202022370016421 5ustar rootrootdino-0.3.0/main/src/ui/contact_details/blocking_provider.vala0000644000000000000000000000277714202022370023005 0ustar rootrootusing Gtk; using Dino.Entities; namespace Dino.Ui.ContactDetails { public class BlockingProvider : Plugins.ContactDetailsProvider, Object { public string id { get { return "blocking"; } } private StreamInteractor stream_interactor; public BlockingProvider(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; } public void populate(Conversation conversation, Plugins.ContactDetails contact_details, Plugins.WidgetType type) { if (type != Plugins.WidgetType.GTK) return; if (conversation.type_ != Conversation.Type.CHAT) return; if (stream_interactor.get_module(BlockingManager.IDENTITY).is_supported(conversation.account)) { bool is_blocked = stream_interactor.get_module(BlockingManager.IDENTITY).is_blocked(conversation.account, conversation.counterpart); Switch sw = new Switch() { active=is_blocked, valign=Align.CENTER, visible=true }; sw.state_set.connect((state) => { if (state) { stream_interactor.get_module(BlockingManager.IDENTITY).block(conversation.account, conversation.counterpart); } else { stream_interactor.get_module(BlockingManager.IDENTITY).unblock(conversation.account, conversation.counterpart); } return false; }); contact_details.add(_("Settings"), _("Block"), _("Communication and status updates in either direction are blocked"), sw); } } } } dino-0.3.0/main/src/ui/contact_details/dialog.vala0000644000000000000000000001450214202022370020527 0ustar rootrootusing Gee; using Gtk; using Markup; using Pango; using Dino.Entities; namespace Dino.Ui.ContactDetails { [GtkTemplate (ui = "/im/dino/Dino/contact_details_dialog.ui")] public class Dialog : Gtk.Dialog { [GtkChild] public unowned AvatarImage avatar; [GtkChild] public unowned Util.EntryLabelHybrid name_hybrid; [GtkChild] public unowned Label name_label; [GtkChild] public unowned Label jid_label; [GtkChild] public unowned Label account_label; [GtkChild] public unowned Box main_box; private StreamInteractor stream_interactor; private Conversation conversation; private Plugins.ContactDetails contact_details = new Plugins.ContactDetails(); private HashMap categories = new HashMap(); private Util.LabelHybridGroup hybrid_group = new Util.LabelHybridGroup(); construct { name_hybrid.label.attributes = new AttrList(); name_hybrid.label.attributes.insert(attr_weight_new(Weight.BOLD)); } public Dialog(StreamInteractor stream_interactor, Conversation conversation) { Object(use_header_bar : Util.use_csd() ? 1 : 0); this.stream_interactor = stream_interactor; this.conversation = conversation; title = conversation.type_ == Conversation.Type.GROUPCHAT ? _("Conference Details") : _("Contact Details"); if (Util.use_csd()) { // TODO get_header_bar directly returns a HeaderBar in vala > 0.48 ((HeaderBar) get_header_bar()).set_subtitle(Util.get_conversation_display_name(stream_interactor, conversation)); } setup_top(); contact_details.add.connect(add_entry); Application app = GLib.Application.get_default() as Application; app.plugin_registry.register_contact_details_entry(new SettingsProvider(stream_interactor)); app.plugin_registry.register_contact_details_entry(new BlockingProvider(stream_interactor)); app.plugin_registry.register_contact_details_entry(new MucConfigFormProvider(stream_interactor)); app.plugin_registry.register_contact_details_entry(new PermissionsProvider(stream_interactor)); foreach (Plugins.ContactDetailsProvider provider in app.plugin_registry.contact_details_entries) { provider.populate(conversation, contact_details, Plugins.WidgetType.GTK); } destroy.connect(() => { contact_details.save(); }); } private void setup_top() { if (conversation.type_ == Conversation.Type.CHAT) { name_label.visible = false; jid_label.margin_start = new Button().get_style_context().get_padding(StateFlags.NORMAL).left + 1; name_hybrid.text = Util.get_conversation_display_name(stream_interactor, conversation); destroy.connect(() => { if (name_hybrid.text != Util.get_conversation_display_name(stream_interactor, conversation)) { stream_interactor.get_module(RosterManager.IDENTITY).set_jid_handle(conversation.account, conversation.counterpart, name_hybrid.text); } }); } else { name_hybrid.visible = false; name_label.label = Util.get_conversation_display_name(stream_interactor, conversation); } jid_label.label = conversation.counterpart.to_string(); account_label.label = "via " + conversation.account.bare_jid.to_string(); avatar.set_conversation(stream_interactor, conversation); } private void add_entry(string category, string label, string? description, Object wo) { if (!(wo is Widget)) return; Widget w = (Widget) wo; add_category(category); ListBoxRow list_row = new ListBoxRow() { activatable=false, visible=true }; Box row = new Box(Orientation.HORIZONTAL, 20) { margin_start=15, margin_end=15, margin_top=3, margin_bottom=3, visible=true }; list_row.add(row); Label label_label = new Label(label) { xalign=0, yalign=0.5f, hexpand=true, visible=true }; if (description != null && description != "") { Box box = new Box(Orientation.VERTICAL, 0) { visible=true }; box.add(label_label); Label desc_label = new Label("") { xalign=0, yalign=0.5f, hexpand=true, visible=true }; desc_label.set_markup("%s".printf(Markup.escape_text(description))); desc_label.get_style_context().add_class("dim-label"); box.add(desc_label); row.add(box); } else { row.add(label_label); } Widget widget = w; if (widget.get_type().is_a(typeof(Entry))) { Util.EntryLabelHybrid hybrid = new Util.EntryLabelHybrid.wrap(widget as Entry) { xalign=1, visible=true }; hybrid_group.add(hybrid); widget = hybrid; } else if (widget.get_type().is_a(typeof(ComboBoxText))) { Util.ComboBoxTextLabelHybrid hybrid = new Util.ComboBoxTextLabelHybrid.wrap(widget as ComboBoxText) { xalign=1, visible=true }; hybrid_group.add(hybrid); widget = hybrid; } widget.margin_bottom = 5; widget.margin_top = 5; row.add(widget); categories[category].add(list_row); int pref_height, pref_width; get_content_area().get_preferred_height(null, out pref_height); get_preferred_width(out pref_width, null); resize(pref_width, int.min(500, pref_height)); } private void add_category(string category) { if (!categories.has_key(category)) { ListBox list_box = new ListBox() { selection_mode=SelectionMode.NONE, visible=true }; categories[category] = list_box; list_box.set_header_func((row, before_row) => { if (row.get_header() == null && before_row != null) { row.set_header(new Separator(Orientation.HORIZONTAL)); } }); Box box = new Box(Orientation.VERTICAL, 5) { margin_top=12, margin_bottom=12, visible=true }; Label category_label = new Label("") { xalign=0, visible=true }; category_label.set_markup(@"$(Markup.escape_text(category))"); box.add(category_label); Frame frame = new Frame(null) { visible=true }; frame.add(list_box); box.add(frame); main_box.add(box); } } } } dino-0.3.0/main/src/ui/contact_details/muc_config_form_provider.vala0000644000000000000000000000765514202022370024351 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; using Xmpp.Xep; namespace Dino.Ui.ContactDetails { public class MucConfigFormProvider : Plugins.ContactDetailsProvider, Object { public string id { get { return "muc_config_form"; } } private StreamInteractor stream_interactor; public MucConfigFormProvider(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; } public void populate(Conversation conversation, Plugins.ContactDetails contact_details, Plugins.WidgetType type) { if (type != Plugins.WidgetType.GTK) return; if (conversation.type_ == Conversation.Type.GROUPCHAT) { Xmpp.XmppStream? stream = stream_interactor.get_stream(conversation.account); if (stream == null) return; stream_interactor.get_module(MucManager.IDENTITY).get_config_form.begin(conversation.account, conversation.counterpart, (_, res) => { DataForms.DataForm? data_form = stream_interactor.get_module(MucManager.IDENTITY).get_config_form.end(res); if (data_form == null) return; for (int i = 0; i < data_form.fields.size; i++) { DataForms.DataForm.Field field = data_form.fields[i]; add_field(field, contact_details); } string config_backup = data_form.stanza_node.to_string(); contact_details.save.connect(() => { // Only send the config form if something was changed if (config_backup != data_form.stanza_node.to_string()) { stream_interactor.get_module(MucManager.IDENTITY).set_config_form.begin(conversation.account, conversation.counterpart, data_form); } }); }); } } public static void add_field(DataForms.DataForm.Field field, Plugins.ContactDetails contact_details) { string label = field.label ?? ""; string? desc = null; if (field.var != null) { switch (field.var) { case "muc#roomconfig_roomname": label = _("Name of the room"); break; case "muc#roomconfig_roomdesc": label = _("Description of the room"); break; case "muc#roomconfig_persistentroom": label = _("Persistent"); desc = _("The room will persist after the last occupant leaves"); break; case "muc#roomconfig_publicroom": label = _("Publicly searchable"); break; case "muc#roomconfig_changesubject": label = _("Occupants may change the subject"); break; case "muc#roomconfig_whois": label = _("Permission to view JIDs"); desc = _("Who is allowed to view the occupants' JIDs?"); break; case "muc#roomconfig_roomsecret": label = _("Password"); desc = _("A password to restrict access to the room"); break; case "muc#roomconfig_moderatedroom": label = _("Moderated"); desc = _("Only occupants with voice may send messages"); break; case "muc#roomconfig_membersonly": label = _("Members only"); desc = _("Only members may enter the room"); break; case "muc#roomconfig_historylength": label = _("Message history"); desc = _("Maximum amount of backlog issued by the room"); break; } } Widget? widget = Util.get_data_form_field_widget(field); if (widget != null) contact_details.add(_("Room Configuration"), label, desc, widget); } } } dino-0.3.0/main/src/ui/contact_details/permissions_provider.vala0000644000000000000000000000233614202022370023557 0ustar rootrootusing Gtk; using Dino.Entities; namespace Dino.Ui.ContactDetails { public class PermissionsProvider : Plugins.ContactDetailsProvider, Object { public string id { get { return "permissions"; } } private StreamInteractor stream_interactor; public PermissionsProvider(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; } public void populate(Conversation conversation, Plugins.ContactDetails contact_details, Plugins.WidgetType type) { if (type != Plugins.WidgetType.GTK) return; Xmpp.Jid? own_jid = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account); if (own_jid == null) return; if (stream_interactor.get_module(MucManager.IDENTITY).get_role(own_jid, conversation.account) == Xmpp.Xep.Muc.Role.VISITOR){ Button voice_request = new Button() {visible=true, label=_("Request")}; voice_request.clicked.connect(()=>stream_interactor.get_module(MucManager.IDENTITY).request_voice(conversation.account, conversation.counterpart)); contact_details.add(_("Permissions"), _("Request permission to send messages"), "", voice_request); } } } } dino-0.3.0/main/src/ui/contact_details/settings_provider.vala0000644000000000000000000001350114202022370023040 0ustar rootrootusing Gtk; using Dino.Entities; namespace Dino.Ui.ContactDetails { public class SettingsProvider : Plugins.ContactDetailsProvider, Object { public string id { get { return "chat_settings"; } } private StreamInteractor stream_interactor; private string DETAILS_HEADLINE_CHAT = _("Settings"); private string DETAILS_HEADLINE_ROOM = _("Local Settings"); public SettingsProvider(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; } public void populate(Conversation conversation, Plugins.ContactDetails contact_details, Plugins.WidgetType type) { if (type != Plugins.WidgetType.GTK) return; if (!stream_interactor.get().is_public_room(conversation.account, conversation.counterpart)) { string details_headline = conversation.type_ == Conversation.Type.GROUPCHAT ? DETAILS_HEADLINE_ROOM : DETAILS_HEADLINE_CHAT; ComboBoxText combobox_typing = get_combobox(Dino.Application.get_default().settings.send_typing); combobox_typing.active_id = get_setting_id(conversation.send_typing); combobox_typing.changed.connect(() => { conversation.send_typing = get_setting(combobox_typing.active_id); } ); contact_details.add(details_headline, _("Send typing notifications"), "", combobox_typing); } if (conversation.type_ == Conversation.Type.CHAT) { ComboBoxText combobox_marker = get_combobox(Dino.Application.get_default().settings.send_marker); contact_details.add(DETAILS_HEADLINE_CHAT, _("Send read receipts"), "", combobox_marker); combobox_marker.active_id = get_setting_id(conversation.send_marker); combobox_marker.changed.connect(() => { conversation.send_marker = get_setting(combobox_marker.active_id); } ); ComboBoxText combobox_notifications = get_combobox(Dino.Application.get_default().settings.notifications); contact_details.add(DETAILS_HEADLINE_CHAT, _("Notifications"), "", combobox_notifications); combobox_notifications.active_id = get_notify_setting_id(conversation.notify_setting); combobox_notifications.changed.connect(() => { conversation.notify_setting = get_notify_setting(combobox_notifications.active_id); } ); } else if (conversation.type_ == Conversation.Type.GROUPCHAT) { ComboBoxText combobox = new ComboBoxText() { visible=true }; combobox.append("default", get_notify_setting_string(Conversation.NotifySetting.DEFAULT, conversation.get_notification_default_setting(stream_interactor))); combobox.append("highlight", get_notify_setting_string(Conversation.NotifySetting.HIGHLIGHT)); combobox.append("on", get_notify_setting_string(Conversation.NotifySetting.ON)); combobox.append("off", get_notify_setting_string(Conversation.NotifySetting.OFF)); contact_details.add(DETAILS_HEADLINE_ROOM, _("Notifications"), "", combobox); combobox.active_id = get_notify_setting_id(conversation.notify_setting); combobox.changed.connect(() => { conversation.notify_setting = get_notify_setting(combobox.active_id); } ); } } private Conversation.Setting get_setting(string id) { switch (id) { case "default": return Conversation.Setting.DEFAULT; case "on": return Conversation.Setting.ON; case "off": return Conversation.Setting.OFF; } assert_not_reached(); } private Conversation.NotifySetting get_notify_setting(string id) { switch (id) { case "default": return Conversation.NotifySetting.DEFAULT; case "on": return Conversation.NotifySetting.ON; case "off": return Conversation.NotifySetting.OFF; case "highlight": return Conversation.NotifySetting.HIGHLIGHT; } assert_not_reached(); } private string get_notify_setting_string(Conversation.NotifySetting setting, Conversation.NotifySetting? default_setting = null) { switch (setting) { case Conversation.NotifySetting.ON: return _("On"); case Conversation.NotifySetting.OFF: return _("Off"); case Conversation.NotifySetting.HIGHLIGHT: return _("Only when mentioned"); case Conversation.NotifySetting.DEFAULT: return _("Default: %s").printf(get_notify_setting_string(default_setting)); } assert_not_reached(); } private string get_setting_id(Conversation.Setting setting) { switch (setting) { case Conversation.Setting.DEFAULT: return "default"; case Conversation.Setting.ON: return "on"; case Conversation.Setting.OFF: return "off"; } assert_not_reached(); } private string get_notify_setting_id(Conversation.NotifySetting setting) { switch (setting) { case Conversation.NotifySetting.DEFAULT: return "default"; case Conversation.NotifySetting.ON: return "on"; case Conversation.NotifySetting.OFF: return "off"; case Conversation.NotifySetting.HIGHLIGHT: return "highlight"; } assert_not_reached(); } private ComboBoxText get_combobox(bool default_val) { ComboBoxText combobox = new ComboBoxText(); combobox = new ComboBoxText() { visible=true }; string default_setting = default_val ? _("On") : _("Off"); combobox.append("default", _("Default: %s").printf(default_setting) ); combobox.append("on", _("On")); combobox.append("off", _("Off")); return combobox; } } } dino-0.3.0/main/src/ui/conversation_content_view/0000755000000000000000000000000014202022370020557 5ustar rootrootdino-0.3.0/main/src/ui/conversation_content_view/call_widget.vala0000644000000000000000000002747014202022370023714 0ustar rootrootusing Gee; using Gdk; using Gtk; using Pango; using Xmpp; using Dino.Entities; namespace Dino.Ui { public class CallMetaItem : ConversationSummary.ContentMetaItem { private StreamInteractor stream_interactor; public CallMetaItem(ContentItem content_item, StreamInteractor stream_interactor) { base(content_item); this.stream_interactor = stream_interactor; } public override Object? get_widget(Plugins.WidgetType type) { CallItem call_item = content_item as CallItem; CallState? call_state = stream_interactor.get_module(Calls.IDENTITY).call_states[call_item.call]; return new CallWidget(stream_interactor, call_item.call, call_state, call_item.conversation) { visible=true }; } public override Gee.List? get_item_actions(Plugins.WidgetType type) { return null; } } [GtkTemplate (ui = "/im/dino/Dino/call_widget.ui")] public class CallWidget : SizeRequestBox { [GtkChild] public unowned Image image; [GtkChild] public unowned Label title_label; [GtkChild] public unowned Label subtitle_label; [GtkChild] public unowned Revealer incoming_call_revealer; [GtkChild] public unowned Box outer_additional_box; [GtkChild] public unowned Box incoming_call_box; [GtkChild] public unowned Box multiparty_peer_box; [GtkChild] public unowned Button accept_call_button; [GtkChild] public unowned Button reject_call_button; private StreamInteractor stream_interactor; private CallState call_manager; private Call call; private Conversation conversation; public Call.State call_state { get; set; } // needs to be public for binding private uint time_update_handler_id = 0; construct { margin_top = 4; size_request_mode = SizeRequestMode.HEIGHT_FOR_WIDTH; } /** @param call_state Null if it's an old call and we can't interact with it anymore */ public CallWidget(StreamInteractor stream_interactor, Call call, CallState? call_state, Conversation conversation) { this.stream_interactor = stream_interactor; this.call_manager = call_state; this.call = call; this.conversation = conversation; size_allocate.connect((allocation) => { if (allocation.height > parent.get_allocated_height()) { Idle.add(() => { parent.queue_resize(); return false; }); } }); call.bind_property("state", this, "call-state"); this.notify["call-state"].connect(update_call_state); if (call_manager != null && (call.state == Call.State.ESTABLISHING || call.state == Call.State.IN_PROGRESS)) { call_manager.peer_joined.connect(update_counterparts); } accept_call_button.clicked.connect(() => { call_manager.accept(); var call_window = new CallWindow(); call_window.controller = new CallWindowController(call_window, call_state, stream_interactor); call_window.present(); }); reject_call_button.clicked.connect(call_manager.reject); update_call_state(); } private void update_counterparts() { if (call.state != Call.State.IN_PROGRESS && call.state != Call.State.ENDED) return; if (call.counterparts.size <= 1 && conversation.type_ == Conversation.Type.CHAT) return; multiparty_peer_box.foreach((widget) => { multiparty_peer_box.remove(widget); }); foreach (Jid counterpart in call.counterparts) { AvatarImage image = new AvatarImage() { force_gray=true, margin_top=2, visible=true }; image.set_conversation_participant(stream_interactor, conversation, counterpart.bare_jid); multiparty_peer_box.add(image); } AvatarImage image2 = new AvatarImage() { force_gray=true, margin_top=2, visible=true }; image2.set_conversation_participant(stream_interactor, conversation, call.account.bare_jid); multiparty_peer_box.add(image2); outer_additional_box.get_style_context().add_class("multiparty-participants"); multiparty_peer_box.visible = true; incoming_call_box.visible = false; incoming_call_revealer.reveal_child = true; } private void update_call_state() { incoming_call_revealer.reveal_child = false; incoming_call_revealer.get_style_context().remove_class("incoming"); outer_additional_box.get_style_context().remove_class("incoming-call-box"); // It doesn't make sense to display MUC calls as missed or declined by the whole MUC. Just display as ended. // TODO: maybe not let them be missed/declined in first place. Call.State relevant_state = call.state; if (conversation.type_ == Conversation.Type.GROUPCHAT && call.direction == Call.DIRECTION_OUTGOING && (relevant_state == Call.State.MISSED || relevant_state == Call.State.DECLINED)) { relevant_state = Call.State.ENDED; } switch (relevant_state) { case Call.State.RINGING: image.set_from_icon_name("dino-phone-ring-symbolic", IconSize.LARGE_TOOLBAR); if (call.direction == Call.DIRECTION_INCOMING) { bool video = call_manager.should_we_send_video(); title_label.label = video ? _("Incoming video call") : _("Incoming call"); if (call_manager.invited_to_group_call != null) { title_label.label = video ? _("Incoming video group call") : _("Incoming group call"); } if (stream_interactor.get_module(Calls.IDENTITY).can_we_do_calls(call.account)) { subtitle_label.label = "Ring ring…!"; incoming_call_box.visible = true; incoming_call_revealer.reveal_child = true; incoming_call_revealer.get_style_context().add_class("incoming"); outer_additional_box.get_style_context().add_class("incoming-call-box"); } else { subtitle_label.label = "Dependencies for call support not met"; } } else { title_label.label = _("Calling…"); subtitle_label.label = "Ring ring…?"; } break; case Call.State.ESTABLISHING: case Call.State.IN_PROGRESS: image.set_from_icon_name("dino-phone-in-talk-symbolic", IconSize.LARGE_TOOLBAR); title_label.label = _("Call started"); string duration = get_duration_string((new DateTime.now_utc()).difference(call.local_time)); subtitle_label.label = _("Started %s ago").printf(duration); time_update_handler_id = Timeout.add_seconds(get_next_time_change() + 1, () => { if (time_update_handler_id != 0) { Source.remove(time_update_handler_id); time_update_handler_id = 0; update_call_state(); } return true; }); break; case Call.State.OTHER_DEVICE: image.set_from_icon_name("dino-phone-hangup-symbolic", IconSize.LARGE_TOOLBAR); title_label.label = call.direction == Call.DIRECTION_INCOMING ? _("Incoming call") : _("Outgoing call"); subtitle_label.label = _("You handled this call on another device"); break; case Call.State.ENDED: image.set_from_icon_name("dino-phone-hangup-symbolic", IconSize.LARGE_TOOLBAR); title_label.label = _("Call ended"); string formated_end = Util.format_time(call.end_time, _("%H∶%M"), _("%l∶%M %p")); string duration = get_duration_string(call.end_time.difference(call.local_time)); subtitle_label.label = _("Ended at %s").printf(formated_end) + " · " + _("Lasted %s").printf(duration); break; case Call.State.MISSED: image.set_from_icon_name("dino-phone-missed-symbolic", IconSize.LARGE_TOOLBAR); title_label.label = _("Call missed"); if (call.direction == Call.DIRECTION_INCOMING) { subtitle_label.label = _("You missed this call"); } else { string who = Util.get_conversation_display_name(stream_interactor, conversation); subtitle_label.label = _("%s missed this call").printf(who); } break; case Call.State.DECLINED: image.set_from_icon_name("dino-phone-hangup-symbolic", IconSize.LARGE_TOOLBAR); title_label.label = _("Call declined"); if (call.direction == Call.DIRECTION_INCOMING) { subtitle_label.label = _("You declined this call"); } else { string who = Util.get_conversation_display_name(stream_interactor, conversation); subtitle_label.label = _("%s declined this call").printf(who); } break; case Call.State.FAILED: image.set_from_icon_name("dino-phone-hangup-symbolic", IconSize.LARGE_TOOLBAR); title_label.label = _("Call failed"); subtitle_label.label = "Call failed to establish"; break; } update_counterparts(); } private string get_duration_string(TimeSpan duration) { DateTime a = new DateTime.now_utc(); DateTime b = new DateTime.now_utc(); a.difference(b); TimeSpan remainder_duration = duration; int hours = (int) Math.floor(remainder_duration / TimeSpan.HOUR); remainder_duration -= hours * TimeSpan.HOUR; int minutes = (int) Math.floor(remainder_duration / TimeSpan.MINUTE); remainder_duration -= minutes * TimeSpan.MINUTE; string ret = ""; if (hours > 0) { ret += n("%i hour", "%i hours", hours).printf(hours); } if (minutes > 0) { if (ret.length > 0) { ret += " "; } ret += n("%i minute", "%i minutes", minutes).printf(minutes); } if (ret.length > 0) { return ret; } return _("a few seconds"); } private int get_next_time_change() { DateTime now = new DateTime.now_local(); DateTime item_time = call.local_time; if (now.get_second() < item_time.get_second()) { return item_time.get_second() - now.get_second(); } else { return 60 - (now.get_second() - item_time.get_second()); } } public override void dispose() { base.dispose(); if (time_update_handler_id != 0) { Source.remove(time_update_handler_id); time_update_handler_id = 0; } if (call_manager != null) { call_manager.peer_joined.disconnect(update_counterparts); } } } } dino-0.3.0/main/src/ui/conversation_content_view/chat_state_populator.vala0000644000000000000000000001107114202022370025650 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; using Xmpp; namespace Dino.Ui.ConversationSummary { class ChatStatePopulator : Plugins.ConversationItemPopulator, Plugins.ConversationAdditionPopulator, Object { public string id { get { return "chat_state"; } } private StreamInteractor? stream_interactor; private Conversation? current_conversation; private Plugins.ConversationItemCollection? item_collection; private MetaChatStateItem? meta_item; public ChatStatePopulator(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; stream_interactor.get_module(CounterpartInteractionManager.IDENTITY).received_state.connect((conversation, state) => { if (current_conversation != null && current_conversation.equals(conversation)) { update_chat_state(); } }); stream_interactor.get_module(MessageProcessor.IDENTITY).message_sent.connect((message, conversation) => { if (conversation.equals(current_conversation)) { update_chat_state(); } }); } public void init(Conversation conversation, Plugins.ConversationItemCollection item_collection, Plugins.WidgetType type) { current_conversation = conversation; this.item_collection = item_collection; this.meta_item = null; update_chat_state(); } public void close(Conversation conversation) { } public void populate_timespan(Conversation conversation, DateTime from, DateTime to) { } private void update_chat_state() { Gee.List? typing_jids = stream_interactor.get_module(CounterpartInteractionManager.IDENTITY).get_typing_jids(current_conversation); if (meta_item != null && typing_jids == null) { // Remove state (stoped typing) item_collection.remove_item(meta_item); meta_item = null; } else if (meta_item != null && typing_jids != null) { // Update state (other people typing in MUC) meta_item.set_new(typing_jids); } else if (typing_jids != null) { // New state (started typing) meta_item = new MetaChatStateItem(stream_interactor, current_conversation, typing_jids); item_collection.insert_item(meta_item); } } } private class MetaChatStateItem : Plugins.MetaConversationItem { public override DateTime time { get; set; default=new DateTime.now_utc().add_years(10); } private StreamInteractor stream_interactor; private Conversation conversation; private Gee.List jids = new ArrayList(); private Label label; private AvatarImage image; public MetaChatStateItem(StreamInteractor stream_interactor, Conversation conversation, Gee.List jids) { this.stream_interactor = stream_interactor; this.conversation = conversation; this.jids = jids; } public override Object? get_widget(Plugins.WidgetType widget_type) { label = new Label("") { xalign=0, vexpand=true, visible=true }; label.get_style_context().add_class("dim-label"); image = new AvatarImage() { margin_top=2, valign=Align.START, visible=true }; Box image_content_box = new Box(Orientation.HORIZONTAL, 8) { visible=true }; image_content_box.add(image); image_content_box.add(label); update(); return image_content_box; } public override Gee.List? get_item_actions(Plugins.WidgetType type) { return null; } public void set_new(Gee.List jids) { this.jids = jids; update(); } private void update() { if (image == null || label == null) return; image.set_conversation_participants(stream_interactor, conversation, jids.to_array()); Gee.List display_names = new ArrayList(); foreach (Jid jid in jids) { display_names.add(Util.get_participant_display_name(stream_interactor, conversation, jid)); } string new_text = ""; if (jids.size > 3) { new_text = _("%s, %s and %i others are typing…").printf(display_names[0], display_names[1], jids.size - 2); } else if (jids.size == 3) { new_text = _("%s, %s and %s are typing…").printf(display_names[0], display_names[1], display_names[2]); } else if (jids.size == 2) { new_text =_("%s and %s are typing…").printf(display_names[0], display_names[1]); } else { new_text = _("%s is typing…").printf(display_names[0]); } label.label = new_text; } } } dino-0.3.0/main/src/ui/conversation_content_view/content_populator.vala0000644000000000000000000000737414202022370025216 0ustar rootrootusing Gee; using Gtk; using Xmpp; using Dino.Entities; namespace Dino.Ui.ConversationSummary { public class ContentProvider : ContentItemCollection, Object { private StreamInteractor stream_interactor; private Conversation? current_conversation; private Plugins.ConversationItemCollection? item_collection; public ContentProvider(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; } public void init(Plugins.ConversationItemCollection item_collection, Conversation conversation, Plugins.WidgetType type) { if (current_conversation != null) { stream_interactor.get_module(ContentItemStore.IDENTITY).uninit(current_conversation, this); } current_conversation = conversation; this.item_collection = item_collection; stream_interactor.get_module(ContentItemStore.IDENTITY).init(conversation, this); } public void insert_item(ContentItem item) { item_collection.insert_item(create_content_meta_item(item)); } public void remove_item(ContentItem item) { } public Gee.List populate_latest(Conversation conversation, int n) { Gee.List items = stream_interactor.get_module(ContentItemStore.IDENTITY).get_n_latest(conversation, n); Gee.List ret = new ArrayList(); foreach (ContentItem item in items) { ret.add(create_content_meta_item(item)); } return ret; } public Gee.List populate_before(Conversation conversation, ContentItem before_item, int n) { Gee.List ret = new ArrayList(); Gee.List items = stream_interactor.get_module(ContentItemStore.IDENTITY).get_before(conversation, before_item, n); foreach (ContentItem item in items) { ret.add(create_content_meta_item(item)); } return ret; } public Gee.List populate_after(Conversation conversation, ContentItem after_item, int n) { Gee.List ret = new ArrayList(); Gee.List items = stream_interactor.get_module(ContentItemStore.IDENTITY).get_after(conversation, after_item, n); foreach (ContentItem item in items) { ret.add(create_content_meta_item(item)); } return ret; } public ContentMetaItem get_content_meta_item(ContentItem content_item) { return create_content_meta_item(content_item); } private ContentMetaItem create_content_meta_item(ContentItem content_item) { if (content_item.type_ == MessageItem.TYPE) { return new MessageMetaItem(content_item, stream_interactor); } else if (content_item.type_ == FileItem.TYPE) { return new FileMetaItem(content_item, stream_interactor); } else if (content_item.type_ == CallItem.TYPE) { return new CallMetaItem(content_item, stream_interactor); } critical("Got unknown content item type %s", content_item.type_); return null; } } public abstract class ContentMetaItem : Plugins.MetaConversationItem { public ContentItem content_item; protected ContentMetaItem(ContentItem content_item) { this.jid = content_item.jid; this.time = content_item.time; this.secondary_sort_indicator = content_item.id; this.encryption = content_item.encryption; this.mark = content_item.mark; content_item.bind_property("mark", this, "mark"); content_item.bind_property("encryption", this, "encryption"); this.can_merge = true; this.requires_avatar = true; this.requires_header = true; this.content_item = content_item; } } } dino-0.3.0/main/src/ui/conversation_content_view/conversation_item_skeleton.vala0000644000000000000000000003217314202022370027066 0ustar rootrootusing Gee; using Gdk; using Gtk; using Markup; using Dino.Entities; namespace Dino.Ui.ConversationSummary { public class ConversationItemSkeleton : EventBox { public bool show_skeleton { get; set; default=false; } public bool last_group_item { get; set; default=true; } public StreamInteractor stream_interactor; public Conversation conversation { get; set; } public Plugins.MetaConversationItem item; public bool item_in_edit_mode { get; set; } public Entities.Message.Marked item_mark { get; set; } public ContentMetaItem? content_meta_item = null; public Widget? widget = null; private Box image_content_box = new Box(Orientation.HORIZONTAL, 8) { visible=true }; private Box header_content_box = new Box(Orientation.VERTICAL, 0) { visible=true }; private ItemMetaDataHeader? metadata_header = null; private AvatarImage? image = null; public ConversationItemSkeleton(StreamInteractor stream_interactor, Conversation conversation, Plugins.MetaConversationItem item, bool initial_item) { this.stream_interactor = stream_interactor; this.conversation = conversation; this.item = item; this.content_meta_item = item as ContentMetaItem; this.get_style_context().add_class("message-box"); item.bind_property("in-edit-mode", this, "item-in-edit-mode"); this.notify["item-in-edit-mode"].connect(update_edit_mode); item.bind_property("mark", this, "item-mark", BindingFlags.SYNC_CREATE); this.notify["item-mark"].connect(update_error_mode); update_error_mode(); widget = item.get_widget(Plugins.WidgetType.GTK) as Widget; if (widget != null) { widget.valign = Align.END; header_content_box.add(widget); } image_content_box.add(header_content_box); if (initial_item) { this.add(image_content_box); } else { Revealer revealer = new Revealer() { transition_duration=200, transition_type=RevealerTransitionType.SLIDE_UP, reveal_child=false, visible=true }; revealer.add_with_properties(image_content_box); this.add(revealer); revealer.reveal_child = true; } this.notify["show-skeleton"].connect(update_margin); this.notify["last-group-item"].connect(update_margin); update_margin(); } private void update_margin() { if (item.requires_header && show_skeleton && metadata_header == null) { metadata_header = new ItemMetaDataHeader(stream_interactor, conversation, item) { visible=true }; header_content_box.add(metadata_header); header_content_box.reorder_child(metadata_header, 0); } if (item.requires_avatar && show_skeleton && image == null) { image = new AvatarImage() { margin_top=2, valign=Align.START, visible=true, allow_gray = false }; image.set_conversation_participant(stream_interactor, conversation, item.jid); image_content_box.add(image); image_content_box.reorder_child(image, 0); } if (image != null) { image.visible = this.show_skeleton; } if (metadata_header != null) { metadata_header.visible = this.show_skeleton; } image_content_box.margin_start = this.show_skeleton ? 15 : 58; image_content_box.margin_end = 15; if (this.show_skeleton && this.last_group_item) { image_content_box.margin_top = 8; image_content_box.margin_bottom = 8; } else { image_content_box.margin_top = 4; image_content_box.margin_bottom = 4; } } private void update_edit_mode() { if (item.in_edit_mode) { this.get_style_context().add_class("edit-mode"); } else { this.get_style_context().remove_class("edit-mode"); } } private void update_error_mode() { if (item_mark == Message.Marked.ERROR) { this.get_style_context().add_class("error"); } else { this.get_style_context().remove_class("error"); } } } [GtkTemplate (ui = "/im/dino/Dino/conversation_content_view/item_metadata_header.ui")] public class ItemMetaDataHeader : Box { [GtkChild] public unowned Label name_label; [GtkChild] public unowned Label time_label; public Image received_image = new Image() { opacity=0.4 }; public Widget? encryption_image = null; public static IconSize ICON_SIZE_HEADER = Gtk.icon_size_register("im.dino.Dino.HEADER_ICON", 17, 12); private StreamInteractor stream_interactor; private Conversation conversation; private Plugins.MetaConversationItem item; public Entities.Message.Marked item_mark { get; set; } private ArrayList items = new ArrayList(); private uint time_update_timeout = 0; private ulong updated_roster_handler_id = 0; public ItemMetaDataHeader(StreamInteractor stream_interactor, Conversation conversation, Plugins.MetaConversationItem item) { this.stream_interactor = stream_interactor; this.conversation = conversation; this.item = item; items.add(item); update_name_label(); name_label.style_updated.connect(update_name_label); updated_roster_handler_id = stream_interactor.get_module(RosterManager.IDENTITY).updated_roster_item.connect((account, jid, roster_item) => { if (this.conversation.account.equals(account) && this.conversation.counterpart.equals(jid)) { update_name_label(); } }); conversation.notify["encryption"].connect(update_unencrypted_icon); item.notify["encryption"].connect(update_encryption_icon); update_encryption_icon(); this.add(received_image); if (item.time != null) { update_time(); } item.bind_property("mark", this, "item-mark"); this.notify["item-mark"].connect_after(update_received_mark); update_received_mark(); } private void update_encryption_icon() { Application app = GLib.Application.get_default() as Application; ContentMetaItem ci = item as ContentMetaItem; if (item.encryption != Encryption.NONE && item.encryption != Encryption.UNKNOWN && ci != null) { Widget? widget = null; foreach(var e in app.plugin_registry.encryption_list_entries) { if (e.encryption == item.encryption) { widget = e.get_encryption_icon(conversation, ci.content_item) as Widget; break; } } if (widget == null) { widget = new Image.from_icon_name("dino-changes-prevent-symbolic", ICON_SIZE_HEADER) { opacity=0.4, visible = true }; } update_encryption_image(widget); } if (item.encryption == Encryption.NONE) { update_unencrypted_icon(); } } private void update_unencrypted_icon() { if (item.encryption != Encryption.NONE) return; if (conversation.encryption != Encryption.NONE && encryption_image == null) { Image image = new Image() { opacity=0.4, visible = true }; image.set_from_icon_name("dino-changes-allowed-symbolic", ICON_SIZE_HEADER); image.tooltip_text = _("Unencrypted"); update_encryption_image(image); Util.force_error_color(image); } else if (conversation.encryption == Encryption.NONE && encryption_image != null) { update_encryption_image(null); } } private void update_encryption_image(Widget? widget) { if (encryption_image != null) { this.remove(encryption_image); encryption_image = null; } if (widget != null) { this.add(widget); this.reorder_child(widget, 3); encryption_image = widget; } } private void update_time() { time_label.label = get_relative_time(item.time.to_local()).to_string(); time_update_timeout = Timeout.add_seconds((int) get_next_time_change(), () => { if (this.parent == null) return false; update_time(); return false; }); } private void update_name_label() { name_label.label = Util.get_participant_display_name(stream_interactor, conversation, item.jid); } private void update_received_mark() { bool all_received = true; bool all_read = true; bool all_sent = true; foreach (Plugins.MetaConversationItem item in items) { if (item.mark == Message.Marked.WONTSEND) { received_image.visible = true; received_image.set_from_icon_name("dialog-warning-symbolic", ICON_SIZE_HEADER); Util.force_error_color(received_image); Util.force_error_color(time_label); string error_text = _("Unable to send message"); received_image.tooltip_text = error_text; time_label.tooltip_text = error_text; return; } else if (item.mark != Message.Marked.READ) { all_read = false; if (item.mark != Message.Marked.RECEIVED) { all_received = false; if (item.mark == Message.Marked.UNSENT) { all_sent = false; } } } } if (all_read) { received_image.visible = true; received_image.set_from_icon_name("dino-double-tick-symbolic", ICON_SIZE_HEADER); } else if (all_received) { received_image.visible = true; received_image.set_from_icon_name("dino-tick-symbolic", ICON_SIZE_HEADER); } else if (!all_sent) { received_image.visible = true; received_image.set_from_icon_name("image-loading-symbolic", ICON_SIZE_HEADER); } else if (received_image.visible) { received_image.set_from_icon_name("image-loading-symbolic", ICON_SIZE_HEADER); } } private int get_next_time_change() { DateTime now = new DateTime.now_local(); DateTime item_time = item.time; TimeSpan timespan = now.difference(item_time); if (timespan < 10 * TimeSpan.MINUTE) { if (now.get_second() < item_time.get_second()) { return item_time.get_second() - now.get_second(); } else { return 60 - (now.get_second() - item_time.get_second()); } } else { return (23 - now.get_hour()) * 3600 + (59 - now.get_minute()) * 60 + (59 - now.get_second()); } } public static string format_time(DateTime datetime, string format_24h, string format_12h) { string format = Util.is_24h_format() ? format_24h : format_12h; if (!get_charset(null)) { // No UTF-8 support, use simple colon for time instead format = format.replace("∶", ":"); } return datetime.format(format); } public static string get_relative_time(DateTime datetime) { DateTime now = new DateTime.now_local(); TimeSpan timespan = now.difference(datetime); if (timespan > 365 * TimeSpan.DAY) { return format_time(datetime, /* xgettext:no-c-format */ /* Date + time in 24h format (w/o seconds) */ _("%x, %H∶%M"), /* xgettext:no-c-format */ /* Date + time in 12h format (w/o seconds)*/ _("%x, %l∶%M %p")); } else if (timespan > 7 * TimeSpan.DAY) { return format_time(datetime, /* xgettext:no-c-format */ /* Month, day and time in 24h format (w/o seconds) */ _("%b %d, %H∶%M"), /* xgettext:no-c-format */ /* Month, day and time in 12h format (w/o seconds) */ _("%b %d, %l∶%M %p")); } else if (datetime.get_day_of_month() != now.get_day_of_month()) { return format_time(datetime, /* xgettext:no-c-format */ /* Day of week and time in 24h format (w/o seconds) */ _("%a, %H∶%M"), /* xgettext:no-c-format */ /* Day of week and time in 12h format (w/o seconds) */_("%a, %l∶%M %p")); } else if (timespan > 9 * TimeSpan.MINUTE) { return format_time(datetime, /* xgettext:no-c-format */ /* Time in 24h format (w/o seconds) */ _("%H∶%M"), /* xgettext:no-c-format */ /* Time in 12h format (w/o seconds) */ _("%l∶%M %p")); } else if (timespan > TimeSpan.MINUTE) { ulong mins = (ulong) (timespan.abs() / TimeSpan.MINUTE); /* xgettext:this is the beginning of a sentence. */ return n("%i min ago", "%i mins ago", mins).printf(mins); } else { return _("Just now"); } } public override void dispose() { if (time_update_timeout != 0) { Source.remove(time_update_timeout); time_update_timeout = 0; } if (updated_roster_handler_id != 0){ stream_interactor.get_module(RosterManager.IDENTITY).disconnect(updated_roster_handler_id); updated_roster_handler_id = 0; } base.dispose(); } } } dino-0.3.0/main/src/ui/conversation_content_view/conversation_view.vala0000644000000000000000000005027014202022370025174 0ustar rootrootusing Gee; using Gtk; using Gdk; using Pango; using Dino.Entities; namespace Dino.Ui.ConversationSummary { [GtkTemplate (ui = "/im/dino/Dino/conversation_content_view/view.ui")] public class ConversationView : Box, Plugins.ConversationItemCollection, Plugins.NotificationCollection { public Conversation? conversation { get; private set; } [GtkChild] public unowned ScrolledWindow scrolled; [GtkChild] private unowned Revealer notification_revealer; [GtkChild] private unowned Box message_menu_box; [GtkChild] private unowned Button button1; [GtkChild] private unowned Image button1_icon; [GtkChild] private unowned Box notifications; [GtkChild] private unowned Box main; [GtkChild] private unowned EventBox main_event_box; [GtkChild] private unowned EventBox main_wrap_event_box; [GtkChild] private unowned Stack stack; private StreamInteractor stream_interactor; private Gee.TreeSet content_items = new Gee.TreeSet(compare_meta_items); private Gee.TreeSet meta_items = new TreeSet(compare_meta_items); private Gee.HashMap item_item_skeletons = new Gee.HashMap(); private Gee.HashMap widgets = new Gee.HashMap(); private Gee.List item_skeletons = new Gee.ArrayList(); private ContentProvider content_populator; private SubscriptionNotitication subscription_notification; private double? was_value; private double? was_upper; private double? was_page_size; private Mutex reloading_mutex = Mutex(); private bool animate = false; private bool firstLoad = true; private bool at_current_content = true; private bool reload_messages = true; ConversationItemSkeleton currently_highlighted = null; ContentMetaItem? current_meta_item = null; int last_y_root = -1; public ConversationView init(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; scrolled.vadjustment.notify["upper"].connect_after(on_upper_notify); scrolled.vadjustment.notify["value"].connect(on_value_notify); content_populator = new ContentProvider(stream_interactor); subscription_notification = new SubscriptionNotitication(stream_interactor); add_meta_notification.connect(on_add_meta_notification); remove_meta_notification.connect(on_remove_meta_notification); Application app = GLib.Application.get_default() as Application; app.plugin_registry.register_conversation_addition_populator(new ChatStatePopulator(stream_interactor)); app.plugin_registry.register_conversation_addition_populator(new DateSeparatorPopulator(stream_interactor)); // Rather than connecting to the leave event of the main_event_box directly, // we connect to the parent event box that also wraps the overlaying message_menu_box. // This eliminates the unwanted leave events emitted on the main_event_box when hovering // the overlaying menu buttons. main_wrap_event_box.events = EventMask.ENTER_NOTIFY_MASK; main_wrap_event_box.events = EventMask.LEAVE_NOTIFY_MASK; main_wrap_event_box.leave_notify_event.connect(on_leave_notify_event); main_wrap_event_box.enter_notify_event.connect(on_enter_notify_event); // The buttons of the overlaying message_menu_box may partially overlap the adjacent // conversation items. We connect to the main_event_box directly to avoid emitting // the pointer motion events as long as the pointer is above the message menu. // This ensures that the currently highlighted item remains unchanged when the pointer // reaches the overlapping part of a button. main_event_box.events = EventMask.POINTER_MOTION_MASK; main_event_box.motion_notify_event.connect(on_motion_notify_event); button1.clicked.connect(() => { current_meta_item.get_item_actions(Plugins.WidgetType.GTK)[0].callback(button1, current_meta_item, currently_highlighted.widget); update_message_menu(); }); return this; } public void activate_last_message_correction() { Gee.BidirIterator iter = content_items.bidir_iterator(); iter.last(); for (int i = 0; i < 10 && content_items.size > i; i++) { Plugins.MetaConversationItem item = iter.get(); MessageMetaItem message_item = item as MessageMetaItem; if (message_item != null) { if ((conversation.type_ == Conversation.Type.CHAT && message_item.jid.equals_bare(conversation.account.bare_jid)) || (conversation.type_ == Conversation.Type.GROUPCHAT && message_item.jid.equals(stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account)))) { message_item.in_edit_mode = true; break; } } iter.previous(); } } private bool on_enter_notify_event(Gdk.EventCrossing event) { update_highlight((int)event.x_root, (int)event.y_root); return false; } private bool on_leave_notify_event(Gdk.EventCrossing event) { if (currently_highlighted != null) { currently_highlighted.unset_state_flags(StateFlags.PRELIGHT); currently_highlighted = null; } message_menu_box.visible = false; return false; } private bool on_motion_notify_event(Gdk.EventMotion event) { update_highlight((int)event.x_root, (int)event.y_root); return false; } private void update_highlight(int x_root, int y_root) { if (currently_highlighted != null && (last_y_root - y_root).abs() <= 2) { return; } last_y_root = y_root; int toplevel_window_pos_x, toplevel_window_pos_y, dest_x, dest_y; Widget toplevel_widget = this.get_toplevel(); // Obtain the position of the main application window relative to the root window toplevel_widget.get_window().get_origin(out toplevel_window_pos_x, out toplevel_window_pos_y); // Get the pointer location relative to the `main` box toplevel_widget.translate_coordinates(main, x_root - toplevel_window_pos_x, y_root - toplevel_window_pos_y, out dest_x, out dest_y); // Get widget under pointer int h = 0; ConversationItemSkeleton? w = null; foreach (Widget widget in main.get_children()) { h += widget.get_allocated_height(); if (h >= dest_y) { w = widget as ConversationItemSkeleton; break; } }; if (currently_highlighted != null) currently_highlighted.unset_state_flags(StateFlags.PRELIGHT); if (w == null) { currently_highlighted = null; current_meta_item = null; update_message_menu(); return; } // Get widget coordinates in main int widget_x, widget_y; w.translate_coordinates(main, 0, 0, out widget_x, out widget_y); // Get MessageItem foreach (Plugins.MetaConversationItem item in item_item_skeletons.keys) { if (item_item_skeletons[item] == w) { current_meta_item = item as ContentMetaItem; } } update_message_menu(); if (current_meta_item != null) { // Highlight widget w.set_state_flags(StateFlags.PRELIGHT, true); currently_highlighted = w; // Move message menu message_menu_box.margin_top = widget_y - 10; } } private void update_message_menu() { if (current_meta_item == null) { message_menu_box.visible = false; return; } var actions = current_meta_item.get_item_actions(Plugins.WidgetType.GTK); message_menu_box.visible = actions != null && actions.size > 0; if (actions != null && actions.size == 1) { button1.visible = true; button1_icon.set_from_icon_name(actions[0].icon_name, IconSize.SMALL_TOOLBAR); } } public void initialize_for_conversation(Conversation? conversation) { // Workaround for rendering issues if (firstLoad) { main.visible = false; Idle.add(() => { main.visible=true; return false; }); firstLoad = false; } stack.set_visible_child_name("void"); clear(); initialize_for_conversation_(conversation); display_latest(); stack.set_visible_child_name("main"); } public void initialize_around_message(Conversation conversation, ContentItem content_item) { stack.set_visible_child_name("void"); clear(); initialize_for_conversation_(conversation); Gee.List before_items = content_populator.populate_before(conversation, content_item, 40); foreach (ContentMetaItem item in before_items) { do_insert_item(item); } ContentMetaItem meta_item = content_populator.get_content_meta_item(content_item); meta_item.can_merge = false; Widget w = insert_new(meta_item); content_items.add(meta_item); meta_items.add(meta_item); Gee.List after_items = content_populator.populate_after(conversation, content_item, 40); foreach (ContentMetaItem item in after_items) { do_insert_item(item); } if (after_items.size == 40) { at_current_content = false; } // Compute where to jump to for centered message, jump, highlight. reload_messages = false; Timeout.add(700, () => { int h = 0, i = 0; bool @break = false; main.@foreach((widget) => { if (widget == w || @break) { @break = true; return; } h += widget.get_allocated_height(); i++; }); scrolled.vadjustment.value = h - scrolled.vadjustment.page_size * 1/3; w.get_style_context().add_class("highlight-once"); reload_messages = true; stack.set_visible_child_name("main"); return false; }); } private void initialize_for_conversation_(Conversation? conversation) { // Deinitialize old conversation Dino.Application app = Dino.Application.get_default(); if (this.conversation != null) { foreach (Plugins.ConversationItemPopulator populator in app.plugin_registry.conversation_addition_populators) { populator.close(conversation); } foreach (Plugins.NotificationPopulator populator in app.plugin_registry.notification_populators) { populator.close(conversation); } } // Clear data structures clear_notifications(); this.conversation = conversation; // Init for new conversation foreach (Plugins.ConversationItemPopulator populator in app.plugin_registry.conversation_addition_populators) { populator.init(conversation, this, Plugins.WidgetType.GTK); } content_populator.init(this, conversation, Plugins.WidgetType.GTK); subscription_notification.init(conversation, this); animate = false; Timeout.add(20, () => { animate = true; return false; }); } private void display_latest() { Gee.List items = content_populator.populate_latest(conversation, 40); foreach (ContentMetaItem item in items) { do_insert_item(item); } Application app = GLib.Application.get_default() as Application; foreach (Plugins.NotificationPopulator populator in app.plugin_registry.notification_populators) { populator.init(conversation, this, Plugins.WidgetType.GTK); } Idle.add(() => { on_value_notify(); return false; }); } public void insert_item(Plugins.MetaConversationItem item) { if (meta_items.size > 0) { bool after_last = meta_items.last().time.compare(item.time) <= 0; bool within_range = meta_items.last().time.compare(item.time) > 0 && meta_items.first().time.compare(item.time) < 0; bool accept = within_range || (at_current_content && after_last); if (!accept) { return; } } do_insert_item(item); } public void do_insert_item(Plugins.MetaConversationItem item) { lock (meta_items) { insert_new(item); if (item as ContentMetaItem != null) { content_items.add(item); } meta_items.add(item); } inserted_item(item); } private void remove_item(Plugins.MetaConversationItem item) { ConversationItemSkeleton? skeleton = item_item_skeletons[item]; if (skeleton != null) { main.remove(skeleton); widgets.unset(item); item_skeletons.remove(skeleton); item_item_skeletons.unset(item); content_items.remove(item); meta_items.remove(item); } removed_item(item); } public void on_add_meta_notification(Plugins.MetaConversationNotification notification) { Widget? widget = (Widget) notification.get_widget(Plugins.WidgetType.GTK); if (widget != null) { add_notification(widget); } } public void on_remove_meta_notification(Plugins.MetaConversationNotification notification){ Widget? widget = (Widget) notification.get_widget(Plugins.WidgetType.GTK); if (widget != null) { remove_notification(widget); } } public void add_notification(Widget widget) { notifications.add(widget); Timeout.add(20, () => { notification_revealer.transition_duration = 200; notification_revealer.reveal_child = true; return false; }); } public void remove_notification(Widget widget) { notification_revealer.reveal_child = false; notifications.remove(widget); } private Widget insert_new(Plugins.MetaConversationItem item) { Plugins.MetaConversationItem? lower_item = meta_items.lower(item); // Fill datastructure ConversationItemSkeleton item_skeleton = new ConversationItemSkeleton(stream_interactor, conversation, item, !animate) { visible=true }; item_item_skeletons[item] = item_skeleton; int index = lower_item != null ? item_skeletons.index_of(item_item_skeletons[lower_item]) + 1 : 0; item_skeletons.insert(index, item_skeleton); // Insert widget widgets[item] = item_skeleton; main.add(item_skeleton); main.reorder_child(item_skeleton, index); if (lower_item != null) { if (can_merge(item, lower_item)) { ConversationItemSkeleton lower_skeleton = item_item_skeletons[lower_item]; item_skeleton.show_skeleton = false; lower_skeleton.last_group_item = false; } else { item_skeleton.show_skeleton = true; } } else { item_skeleton.show_skeleton = true; } Plugins.MetaConversationItem? upper_item = meta_items.higher(item); if (upper_item != null) { if (!can_merge(upper_item, item)) { ConversationItemSkeleton upper_skeleton = item_item_skeletons[upper_item]; upper_skeleton.show_skeleton = true; } } // If an item from the past was added, add everything between that item and the (post-)first present item if (index == 0) { Dino.Application app = Dino.Application.get_default(); if (item_skeletons.size == 1) { foreach (Plugins.ConversationAdditionPopulator populator in app.plugin_registry.conversation_addition_populators) { populator.populate_timespan(conversation, item.time, new DateTime.now_utc()); } } else { foreach (Plugins.ConversationAdditionPopulator populator in app.plugin_registry.conversation_addition_populators) { populator.populate_timespan(conversation, item.time, meta_items.higher(item).time); } } } return item_skeleton; } private bool can_merge(Plugins.MetaConversationItem upper_item /*more recent, displayed below*/, Plugins.MetaConversationItem lower_item /*less recent, displayed above*/) { return upper_item.time != null && lower_item.time != null && upper_item.time.difference(lower_item.time) < TimeSpan.MINUTE && upper_item.jid != null && lower_item.jid != null && upper_item.jid.equals(lower_item.jid) && upper_item.encryption == lower_item.encryption && (upper_item.mark == Message.Marked.WONTSEND) == (lower_item.mark == Message.Marked.WONTSEND); } private void on_upper_notify() { if (was_upper == null || scrolled.vadjustment.value > was_upper - was_page_size - 1) { // scrolled down or content smaller than page size if (at_current_content) { scrolled.vadjustment.value = scrolled.vadjustment.upper - scrolled.vadjustment.page_size; // scroll down } } else if (scrolled.vadjustment.value < scrolled.vadjustment.upper - scrolled.vadjustment.page_size - 1) { scrolled.vadjustment.value = scrolled.vadjustment.upper - was_upper + scrolled.vadjustment.value; // stay at same content } was_upper = scrolled.vadjustment.upper; was_page_size = scrolled.vadjustment.page_size; was_value = scrolled.vadjustment.value; reloading_mutex.trylock(); reloading_mutex.unlock(); } private void on_value_notify() { if (scrolled.vadjustment.value < 400) { load_earlier_messages(); } else if (scrolled.vadjustment.upper - (scrolled.vadjustment.value + scrolled.vadjustment.page_size) < 400) { load_later_messages(); } } private void load_earlier_messages() { was_value = scrolled.vadjustment.value; if (!reloading_mutex.trylock()) return; if (content_items.size > 0) { Gee.List items = content_populator.populate_before(conversation, ((ContentMetaItem) content_items.first()).content_item, 20); foreach (ContentMetaItem item in items) { do_insert_item(item); } } else { reloading_mutex.unlock(); } } private void load_later_messages() { if (!reloading_mutex.trylock()) return; if (content_items.size > 0 && !at_current_content) { Gee.List items = content_populator.populate_after(conversation, ((ContentMetaItem) content_items.last()).content_item, 20); if (items.size == 0) { at_current_content = true; } foreach (ContentMetaItem item in items) { do_insert_item(item); } } else { reloading_mutex.unlock(); } } private static int compare_meta_items(Plugins.MetaConversationItem a, Plugins.MetaConversationItem b) { int cmp1 = a.time.compare(b.time); if (cmp1 != 0) return cmp1; return a.secondary_sort_indicator - b.secondary_sort_indicator; } private void clear() { was_upper = null; was_page_size = null; content_items.clear(); meta_items.clear(); item_skeletons.clear(); item_item_skeletons.clear(); widgets.clear(); main.@foreach((widget) => { main.remove(widget); }); } private void clear_notifications() { notifications.@foreach((widget) => { notifications.remove(widget); }); notification_revealer.transition_duration = 0; notification_revealer.set_reveal_child(false); } } } dino-0.3.0/main/src/ui/conversation_content_view/date_separator_populator.vala0000644000000000000000000001164714202022370026537 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; using Xmpp; namespace Dino.Ui.ConversationSummary { class DateSeparatorPopulator : Plugins.ConversationItemPopulator, Plugins.ConversationAdditionPopulator, Object { public string id { get { return "date_separator"; } } private StreamInteractor stream_interactor; private Conversation? current_conversation; private Plugins.ConversationItemCollection? item_collection; private Gee.TreeSet insert_times; public DateSeparatorPopulator(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; } public void init(Conversation conversation, Plugins.ConversationItemCollection item_collection, Plugins.WidgetType type) { current_conversation = conversation; this.item_collection = item_collection; item_collection.inserted_item.connect(on_inserted_item); this.insert_times = new TreeSet((a, b) => { return a.compare(b); }); } public void close(Conversation conversation) { item_collection.inserted_item.disconnect(on_inserted_item); } public void populate_timespan(Conversation conversation, DateTime after, DateTime before) { } private void on_inserted_item(Plugins.MetaConversationItem item) { if (!(item is ContentMetaItem)) return; DateTime time = item.time.to_local(); DateTime msg_date = new DateTime.local(time.get_year(), time.get_month(), time.get_day_of_month(), 0, 0, 0); if (!insert_times.contains(msg_date)) { if (insert_times.lower(msg_date) != null) { item_collection.insert_item(new MetaDateItem(msg_date.to_utc())); } else if (insert_times.size > 0) { item_collection.insert_item(new MetaDateItem(insert_times.first().to_utc())); } insert_times.add(msg_date); } } } public class MetaDateItem : Plugins.MetaConversationItem { public override DateTime time { get; set; } private DateTime date; public MetaDateItem(DateTime date) { this.date = date; this.time = date; } public override Object? get_widget(Plugins.WidgetType widget_type) { return new DateSeparatorWidget(date); } public override Gee.List? get_item_actions(Plugins.WidgetType type) { return null; } } public class DateSeparatorWidget : Box { private DateTime date; private Label label; private uint time_update_timeout = 0; public DateSeparatorWidget(DateTime date) { Object(orientation:Orientation.HORIZONTAL, spacing:10); width_request = 300; halign = Align.CENTER; visible = true; this.date = date; label = new Label("") { use_markup=true, halign=Align.CENTER, hexpand=false, visible=true }; label.get_style_context().add_class("dim-label"); this.add(new Separator(Orientation.HORIZONTAL) { valign=Align.CENTER, hexpand=true, visible=true }); this.add(label); this.add(new Separator(Orientation.HORIZONTAL) { valign=Align.CENTER, hexpand=true, visible=true }); update_time(); } private void update_time() { label.label = @"$(get_relative_time(date))"; time_update_timeout = Timeout.add_seconds((int) get_next_time_change(), () => { if (this.parent == null) return false; update_time(); return false; }); } private static string get_relative_time(DateTime time) { DateTime time_local = time.to_local(); DateTime now_local = new DateTime.now_local(); if (time_local.get_year() == now_local.get_year() && time_local.get_month() == now_local.get_month() && time_local.get_day_of_month() == now_local.get_day_of_month()) { return _("Today"); } DateTime now_local_minus = now_local.add_days(-1); if (time_local.get_year() == now_local_minus.get_year() && time_local.get_month() == now_local_minus.get_month() && time_local.get_day_of_month() == now_local_minus.get_day_of_month()) { return _("Yesterday"); } if (time_local.get_year() != now_local.get_year()) { return time_local.format("%x"); } TimeSpan timespan = now_local.difference(time_local); if (timespan < 7 * TimeSpan.DAY) { return time_local.format(_("%a, %b %d")); } else { return time_local.format(_("%b %d")); } } private int get_next_time_change() { DateTime now = new DateTime.now_local(); return (23 - now.get_hour()) * 3600 + (59 - now.get_minute()) * 60 + (59 - now.get_second()) + 1; } public override void dispose() { base.dispose(); if (time_update_timeout != 0) { Source.remove(time_update_timeout); time_update_timeout = 0; } } } } dino-0.3.0/main/src/ui/conversation_content_view/file_default_widget.vala0000644000000000000000000001306614202022370025420 0ustar rootrootusing Gee; using Gdk; using Gtk; using Dino.Entities; namespace Dino.Ui { [GtkTemplate (ui = "/im/dino/Dino/file_default_widget.ui")] public class FileDefaultWidget : EventBox { [GtkChild] public unowned Stack image_stack; [GtkChild] public unowned Label name_label; [GtkChild] public unowned Label mime_label; [GtkChild] public unowned Image content_type_image; [GtkChild] public unowned Spinner spinner; [GtkChild] public unowned EventBox stack_event_box; [GtkChild] public unowned MenuButton file_menu; public ModelButton file_open_button; public ModelButton file_save_button; private FileTransfer.State state; public FileDefaultWidget() { this.enter_notify_event.connect(on_pointer_entered_event); this.leave_notify_event.connect(on_pointer_left_event); file_open_button = new ModelButton() { text=_("Open"), visible=true }; file_save_button = new ModelButton() { text=_("Save as…"), visible=true }; } public void update_file_info(string? mime_type, FileTransfer.State state, long size) { this.state = state; spinner.active = false; // A hidden spinning spinner still uses CPU. Deactivate asap content_type_image.icon_name = get_file_icon_name(mime_type); string? mime_description = mime_type != null ? ContentType.get_description(mime_type) : null; switch (state) { case FileTransfer.State.COMPLETE: mime_label.label = mime_description; image_stack.set_visible_child_name("content_type_image"); // Create a menu Gtk.PopoverMenu popover_menu = new Gtk.PopoverMenu(); Box file_menu_box = new Box(Orientation.VERTICAL, 0) { margin=10, visible=true }; file_menu_box.add(file_open_button); file_menu_box.add(file_save_button); popover_menu.add(file_menu_box); file_menu.popover = popover_menu; file_menu.button_release_event.connect(() => { popover_menu.visible = true; return true; }); popover_menu.closed.connect(on_pointer_left); break; case FileTransfer.State.IN_PROGRESS: mime_label.label = _("Downloading %s…").printf(get_size_string(size)); spinner.active = true; image_stack.set_visible_child_name("spinner"); break; case FileTransfer.State.NOT_STARTED: if (mime_description != null) { mime_label.label = _("%s offered: %s").printf(mime_description, get_size_string(size)); } else if (size != -1) { mime_label.label = _("File offered: %s").printf(get_size_string(size)); } else { mime_label.label = _("File offered"); } image_stack.set_visible_child_name("content_type_image"); break; case FileTransfer.State.FAILED: mime_label.use_markup = true; mime_label.label = "" + _("File transfer failed") + ""; image_stack.set_visible_child_name("content_type_image"); break; } } private bool on_pointer_entered_event(Gdk.EventCrossing event) { event.get_window().set_cursor(new Cursor.for_display(Gdk.Display.get_default(), CursorType.HAND2)); content_type_image.opacity = 0.7; if (state == FileTransfer.State.NOT_STARTED) { image_stack.set_visible_child_name("download_image"); } if (state == FileTransfer.State.COMPLETE) { file_menu.opacity = 1; } return false; } private bool on_pointer_left_event(Gdk.EventCrossing event) { if (event.detail == Gdk.NotifyType.INFERIOR) return false; if (file_menu.popover != null && file_menu.popover.visible) return false; event.get_window().set_cursor(new Cursor.for_display(Gdk.Display.get_default(), CursorType.XTERM)); on_pointer_left(); return false; } private void on_pointer_left() { content_type_image.opacity = 0.5; if (state == FileTransfer.State.NOT_STARTED) { image_stack.set_visible_child_name("content_type_image"); } file_menu.opacity = 0; } private static string get_file_icon_name(string? mime_type) { if (mime_type == null) return "dino-file-symbolic"; string generic_icon_name = ContentType.get_generic_icon_name(mime_type) ?? ""; switch (generic_icon_name) { case "audio-x-generic": return "dino-file-music-symbolic"; case "image-x-generic": return "dino-file-image-symbolic"; case "text-x-generic": return "dino-file-document-symbolic"; case "text-x-generic-template": return "dino-file-document-symbolic"; case "video-x-generic": return "dino-file-video-symbolic"; case "x-office-document": return "dino-file-document-symbolic"; case "x-office-spreadsheet": return "dino-file-table-symbolic"; default: return "dino-file-symbolic"; } } private static string get_size_string(long size) { if (size < 1024) { return @"$(size) B"; } else if (size < 1000 * 1000) { return @"$(size / 1000) kB"; } else if (size < 1000 * 1000 * 1000) { return @"$(size / 1000 / 1000) MB"; } else { return @"$(size / 1000 / 1000 / 1000) GB"; } } } } dino-0.3.0/main/src/ui/conversation_content_view/file_image_widget.vala0000644000000000000000000000503314202022370025051 0ustar rootrootusing Gee; using Gdk; using Gtk; using Dino.Entities; namespace Dino.Ui { public class FileImageWidget : EventBox { private ScalingImage image; FileDefaultWidget file_default_widget; FileDefaultWidgetController file_default_widget_controller; public FileImageWidget() { this.halign = Align.START; this.events = EventMask.POINTER_MOTION_MASK; this.get_style_context().add_class("file-image-widget"); } public async void load_from_file(File file, string file_name, int MAX_WIDTH=600, int MAX_HEIGHT=300) throws GLib.Error { // Load and prepare image in tread Thread thread = new Thread (null, () => { ScalingImage image = new ScalingImage() { halign=Align.START, visible = true, max_width = MAX_WIDTH, max_height = MAX_HEIGHT }; Gdk.Pixbuf pixbuf; try { pixbuf = new Gdk.Pixbuf.from_file(file.get_path()); } catch (Error error) { warning("Can't load picture %s - %s", file.get_path(), error.message); Idle.add(load_from_file.callback); return null; } pixbuf = pixbuf.apply_embedded_orientation(); image.load(pixbuf); Idle.add(load_from_file.callback); return image; }); yield; image = thread.join(); if (image == null) throw new Error(-1, 0, "Error loading image"); FileInfo file_info = file.query_info("*", FileQueryInfoFlags.NONE); string? mime_type = file_info.get_content_type(); file_default_widget = new FileDefaultWidget() { valign=Align.END, vexpand=false }; file_default_widget.stack_event_box.visible = false; file_default_widget_controller = new FileDefaultWidgetController(file_default_widget); file_default_widget_controller.set_file(file, file_name, mime_type); Overlay overlay = new Overlay() { visible=true }; overlay.add(image); overlay.add_overlay(file_default_widget); this.enter_notify_event.connect((event) => { file_default_widget.visible = true; return false; }); this.leave_notify_event.connect((event) => { if (event.detail == Gdk.NotifyType.INFERIOR) return false; if (file_default_widget.file_menu.popover != null && file_default_widget.file_menu.popover.visible) return false; file_default_widget.visible = false; return false; }); this.add(overlay); } } } dino-0.3.0/main/src/ui/conversation_content_view/file_widget.vala0000644000000000000000000001645014202022370023714 0ustar rootrootusing Gee; using Gdk; using Gtk; using Pango; using Dino.Entities; namespace Dino.Ui { public class FileMetaItem : ConversationSummary.ContentMetaItem { private StreamInteractor stream_interactor; public FileMetaItem(ContentItem content_item, StreamInteractor stream_interactor) { base(content_item); this.stream_interactor = stream_interactor; } public override Object? get_widget(Plugins.WidgetType type) { FileItem file_item = content_item as FileItem; FileTransfer transfer = file_item.file_transfer; return new FileWidget(stream_interactor, transfer) { visible=true }; } public override Gee.List? get_item_actions(Plugins.WidgetType type) { return null; } } public class FileWidget : SizeRequestBox { enum State { IMAGE, DEFAULT } private StreamInteractor stream_interactor; private FileTransfer file_transfer; public FileTransfer.State file_transfer_state { get; set; } public string file_transfer_mime_type { get; set; } private State? state = null; private FileDefaultWidgetController default_widget_controller; private Widget? content = null; construct { margin_top = 4; size_request_mode = SizeRequestMode.HEIGHT_FOR_WIDTH; } public FileWidget(StreamInteractor stream_interactor, FileTransfer file_transfer) { this.stream_interactor = stream_interactor; this.file_transfer = file_transfer; update_widget.begin(); size_allocate.connect((allocation) => { if (allocation.height > parent.get_allocated_height()) { Idle.add(() => { parent.queue_resize(); return false; }); } }); file_transfer.bind_property("state", this, "file-transfer-state"); file_transfer.bind_property("mime-type", this, "file-transfer-mime-type"); this.notify["file-transfer-state"].connect(update_widget); this.notify["file-transfer-mime-type"].connect(update_widget); } private async void update_widget() { if (show_image() && state != State.IMAGE) { var content_bak = content; FileImageWidget file_image_widget = null; try { file_image_widget = new FileImageWidget() { visible=true }; yield file_image_widget.load_from_file(file_transfer.get_file(), file_transfer.file_name); // If the widget changed in the meanwhile, stop if (content != content_bak) return; if (content != null) this.remove(content); content = file_image_widget; state = State.IMAGE; this.add(content); return; } catch (Error e) { } } if (state != State.DEFAULT) { if (content != null) this.remove(content); FileDefaultWidget default_file_widget = new FileDefaultWidget() { visible=true }; default_widget_controller = new FileDefaultWidgetController(default_file_widget); default_widget_controller.set_file_transfer(file_transfer, stream_interactor); content = default_file_widget; this.state = State.DEFAULT; this.add(content); } } private bool show_image() { if (file_transfer.mime_type == null) return false; if (file_transfer.state != FileTransfer.State.COMPLETE && !(file_transfer.direction == FileTransfer.DIRECTION_SENT && file_transfer.state == FileTransfer.State.IN_PROGRESS)) { return false; } foreach (PixbufFormat pixbuf_format in Pixbuf.get_formats()) { foreach (string mime_type in pixbuf_format.get_mime_types()) { if (mime_type == file_transfer.mime_type) { return true; } } } return false; } } public class FileDefaultWidgetController : Object { private FileDefaultWidget widget; private FileTransfer? file_transfer; public string file_transfer_path { get; set; } public string file_transfer_state { get; set; } public string file_transfer_mime_type { get; set; } private StreamInteractor? stream_interactor; private string file_uri; private string file_name; private FileTransfer.State state; public FileDefaultWidgetController(FileDefaultWidget widget) { this.widget = widget; widget.button_release_event.connect(on_clicked); widget.file_open_button.clicked.connect(open_file); widget.file_save_button.clicked.connect(save_file); } public void set_file_transfer(FileTransfer file_transfer, StreamInteractor stream_interactor) { this.file_transfer = file_transfer; this.stream_interactor = stream_interactor; widget.name_label.label = file_name = file_transfer.file_name; file_transfer.bind_property("path", this, "file-transfer-path"); file_transfer.bind_property("state", this, "file-transfer-state"); file_transfer.bind_property("mime-type", this, "file-transfer-mime-type"); this.notify["file-transfer-path"].connect(update_file_info); this.notify["file-transfer-state"].connect(update_file_info); this.notify["file-transfer-mime-type"].connect(update_file_info); update_file_info(); } public void set_file(File file, string file_name, string? mime_type) { file_uri = file.get_uri(); state = FileTransfer.State.COMPLETE; widget.name_label.label = this.file_name = file_name; widget.update_file_info(mime_type, state, -1); } private void update_file_info() { file_uri = file_transfer.get_file().get_uri(); state = file_transfer.state; widget.update_file_info(file_transfer.mime_type, file_transfer.state, file_transfer.size); } private void open_file() { try{ AppInfo.launch_default_for_uri(file_uri, null); } catch (Error err) { warning("Failed to open %s - %s", file_uri, err.message); } } private void save_file() { var save_dialog = new FileChooserNative(_("Save as…"), widget.get_toplevel() as Gtk.Window, FileChooserAction.SAVE, null, null); save_dialog.set_do_overwrite_confirmation(true); save_dialog.set_modal(true); save_dialog.set_current_name(file_name); if (save_dialog.run() == Gtk.ResponseType.ACCEPT) { try{ GLib.File.new_for_uri(file_uri).copy(save_dialog.get_file(), GLib.FileCopyFlags.OVERWRITE, null); } catch (Error err) { warning("Failed copy file %s - %s", file_uri, err.message); } } } private bool on_clicked(EventButton event_button) { switch (state) { case FileTransfer.State.COMPLETE: if (event_button.button == 1) { open_file(); } break; case FileTransfer.State.NOT_STARTED: assert(stream_interactor != null && file_transfer != null); stream_interactor.get_module(FileManager.IDENTITY).download_file.begin(file_transfer); break; default: // Clicking doesn't do anything in FAILED and IN_PROGRESS states break; } return false; } } } dino-0.3.0/main/src/ui/conversation_content_view/message_widget.vala0000644000000000000000000002702214202022370024416 0ustar rootrootusing Gee; using Gdk; using Gtk; using Pango; using Xmpp; using Dino.Entities; namespace Dino.Ui.ConversationSummary { public class MessageMetaItem : ContentMetaItem { private StreamInteractor stream_interactor; private MessageItemWidget message_item_widget; private MessageItem message_item; public MessageMetaItem(ContentItem content_item, StreamInteractor stream_interactor) { base(content_item); message_item = content_item as MessageItem; this.stream_interactor = stream_interactor; } public override Object? get_widget(Plugins.WidgetType type) { message_item_widget = new MessageItemWidget(stream_interactor, content_item) { visible=true }; message_item_widget.edit_cancelled.connect(() => { this.in_edit_mode = false; }); message_item_widget.edit_sent.connect(on_edit_send); stream_interactor.get_module(MessageCorrection.IDENTITY).received_correction.connect(on_received_correction); this.notify["in-edit-mode"].connect(() => { if (in_edit_mode == false) return; bool allowed = stream_interactor.get_module(MessageCorrection.IDENTITY).is_own_correction_allowed(message_item.conversation, message_item.message); if (allowed) { message_item_widget.set_edit_mode(); } else { this.in_edit_mode = false; } }); return message_item_widget; } public override Gee.List? get_item_actions(Plugins.WidgetType type) { if (content_item as FileItem != null) return null; bool allowed = stream_interactor.get_module(MessageCorrection.IDENTITY).is_own_correction_allowed(message_item.conversation, message_item.message); Gee.List actions = new ArrayList(); if (allowed && !in_edit_mode) { Plugins.MessageAction action1 = new Plugins.MessageAction(); action1.icon_name = "document-edit-symbolic"; action1.callback = (button, content_meta_item_activated, widget) => { this.in_edit_mode = true; }; actions.add(action1); } return actions; } private void on_edit_send(string text) { stream_interactor.get_module(MessageCorrection.IDENTITY).send_correction(message_item.conversation, message_item.message, text); this.in_edit_mode = false; } private void on_received_correction(ContentItem content_item) { if (this.content_item.id == content_item.id) { this.content_item = content_item; message_item = content_item as MessageItem; message_item_widget.content_item = content_item; message_item_widget.update_label(); } } } public class MessageItemWidget : SizeRequestBin { public signal void edit_cancelled(); public signal void edit_sent(string text); enum AdditionalInfo { NONE, PENDING, DELIVERY_FAILED } StreamInteractor stream_interactor; public ContentItem content_item; public Message.Marked marked { get; set; } Label label = new Label("") { use_markup=true, xalign=0, selectable=true, wrap=true, wrap_mode=Pango.WrapMode.WORD_CHAR, vexpand=true, visible=true }; MessageItemEditMode? edit_mode = null; ChatTextViewController? controller = null; AdditionalInfo additional_info = AdditionalInfo.NONE; ulong realize_id = -1; ulong style_updated_id = -1; ulong marked_notify_handler_id = -1; construct { this.add(label); label.activate_link.connect(on_label_activate_link); this.size_request_mode = SizeRequestMode.HEIGHT_FOR_WIDTH; } public MessageItemWidget(StreamInteractor stream_interactor, ContentItem content_item) { this.stream_interactor = stream_interactor; this.content_item = content_item; Message message = ((MessageItem) content_item).message; if (message.direction == Message.DIRECTION_SENT && !(message.marked in Message.MARKED_RECEIVED)) { var binding = message.bind_property("marked", this, "marked"); marked_notify_handler_id = this.notify["marked"].connect(() => { // Currently "pending", but not anymore if (additional_info == AdditionalInfo.PENDING && message.marked != Message.Marked.SENDING && message.marked != Message.Marked.UNSENT) { update_label(); } // Currently "error", but not anymore if (additional_info == AdditionalInfo.DELIVERY_FAILED && message.marked != Message.Marked.ERROR) { update_label(); } // Currently not error, but should be if (additional_info != AdditionalInfo.DELIVERY_FAILED && message.marked == Message.Marked.ERROR) { update_label(); } // Nothing bad can happen anymore if (message.marked in Message.MARKED_RECEIVED) { binding.unbind(); this.disconnect(marked_notify_handler_id); } }); } update_label(); } public void set_edit_mode() { MessageItem message_item = content_item as MessageItem; Message message = message_item.message; if (edit_mode == null) { edit_mode = new MessageItemEditMode(); controller = new ChatTextViewController(edit_mode.chat_text_view, stream_interactor); Conversation conversation = message_item.conversation; controller.initialize_for_conversation(conversation); edit_mode.cancelled.connect(() => { edit_cancelled(); unset_edit_mode(); }); edit_mode.send.connect(() => { if (((MessageItem) content_item).message.body != edit_mode.chat_text_view.text_view.buffer.text) { edit_sent(edit_mode.chat_text_view.text_view.buffer.text); } else { edit_cancelled(); } unset_edit_mode(); }); } edit_mode.chat_text_view.text_view.buffer.text = message.body; this.remove(label); this.add(edit_mode); edit_mode.chat_text_view.text_view.grab_focus(); } public void unset_edit_mode() { this.remove(edit_mode); this.add(label); label.grab_focus(); label.selectable = false; label.selectable = true; } public void update_label() { label.label = generate_markup_text(content_item); } private string generate_markup_text(ContentItem item) { MessageItem message_item = item as MessageItem; Conversation conversation = message_item.conversation; Message message = message_item.message; bool theme_dependent = false; string markup_text = message.body; if (markup_text.length > 10000) { markup_text = markup_text.substring(0, 10000) + " [" + _("Message too long") + "]"; } if (message.body.has_prefix("/me ")) { markup_text = markup_text.substring(4); } if (conversation.type_ == Conversation.Type.GROUPCHAT) { markup_text = Util.parse_add_markup_theme(markup_text, conversation.nickname, true, true, true, Util.is_dark_theme(this), ref theme_dependent); } else { markup_text = Util.parse_add_markup_theme(markup_text, null, true, true, true, Util.is_dark_theme(this), ref theme_dependent); } if (message.body.has_prefix("/me ")) { string display_name = Util.get_participant_display_name(stream_interactor, conversation, message.from); markup_text = @"$(Markup.escape_text(display_name)) " + markup_text + ""; } int only_emoji_count = Util.get_only_emoji_count(markup_text); if (only_emoji_count != -1) { string size_str = only_emoji_count < 5 ? "xx-large" : "large"; markup_text = @"" + markup_text + ""; } string dim_color = Util.is_dark_theme(this) ? "#BDBDBD" : "#707070"; if (message.edit_to != null) { markup_text += @" (%s)".printf(_("edited")); theme_dependent = true; } // Append message status info additional_info = AdditionalInfo.NONE; if (message.direction == Message.DIRECTION_SENT && (message.marked == Message.Marked.SENDING || message.marked == Message.Marked.UNSENT)) { // Append "pending..." iff message has not been sent yet if (message.time.compare(new DateTime.now_utc().add_seconds(-10)) < 0) { markup_text += @" %s".printf(_("pending…")); theme_dependent = true; additional_info = AdditionalInfo.PENDING; } else { int time_diff = (- (int) message.time.difference(new DateTime.now_utc()) / 1000); Timeout.add(10000 - time_diff, () => { update_label(); return false; }); } } else if (message.direction == Message.DIRECTION_SENT && message.marked == Message.Marked.ERROR) { // Append "delivery failed" if there was a server error string error_color = Util.rgba_to_hex(Util.get_label_pango_color(label, "@error_color")); markup_text += " %s".printf(error_color, _("delivery failed")); theme_dependent = true; additional_info = AdditionalInfo.DELIVERY_FAILED; } if (theme_dependent && realize_id == -1) { realize_id = label.realize.connect(update_label); style_updated_id = label.style_updated.connect(update_label); } else if (!theme_dependent && realize_id != -1) { label.disconnect(realize_id); label.disconnect(style_updated_id); } return markup_text; } public static bool on_label_activate_link(string uri) { // Always handle xmpp URIs with Dino if (!uri.has_prefix("xmpp:")) return false; File file = File.new_for_uri(uri); Dino.Application.get_default().open(new File[]{file}, ""); return true; } } [GtkTemplate (ui = "/im/dino/Dino/message_item_widget_edit_mode.ui")] public class MessageItemEditMode : Box { public signal void cancelled(); public signal void send(); [GtkChild] public unowned MenuButton emoji_button; [GtkChild] public unowned ChatTextView chat_text_view; [GtkChild] public unowned Button cancel_button; [GtkChild] public unowned Button send_button; [GtkChild] public unowned Frame frame; construct { Util.force_css(frame, "* { border-radius: 3px; }"); EmojiChooser chooser = new EmojiChooser(); chooser.emoji_picked.connect((emoji) => { chat_text_view.text_view.buffer.insert_at_cursor(emoji, emoji.data.length); }); emoji_button.set_popover(chooser); chat_text_view.text_view.buffer.changed.connect_after(on_text_view_changed); cancel_button.clicked.connect(() => cancelled()); send_button.clicked.connect(() => send()); chat_text_view.cancel_input.connect(() => cancelled()); chat_text_view.send_text.connect(() => send()); } private void on_text_view_changed() { send_button.sensitive = chat_text_view.text_view.buffer.text != ""; } } } dino-0.3.0/main/src/ui/conversation_content_view/subscription_notification.vala0000644000000000000000000000441214202022370026717 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; namespace Dino.Ui.ConversationSummary { public class SubscriptionNotitication : Object { private StreamInteractor stream_interactor; private Conversation conversation; private ConversationView conversation_view; public SubscriptionNotitication(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; stream_interactor.get_module(PresenceManager.IDENTITY).received_subscription_request.connect((jid, account) => { Conversation relevant_conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(jid, account, Conversation.Type.CHAT); stream_interactor.get_module(ConversationManager.IDENTITY).start_conversation(relevant_conversation); if (conversation != null && account.equals(conversation.account) && jid.equals(conversation.counterpart)) { show_notification(); } }); } public void init(Conversation conversation, ConversationView conversation_view) { this.conversation = conversation; this.conversation_view = conversation_view; if (stream_interactor.get_module(PresenceManager.IDENTITY).exists_subscription_request(conversation.account, conversation.counterpart)) { show_notification(); } } private void show_notification() { Box box = new Box(Orientation.HORIZONTAL, 5) { visible=true }; Button accept_button = new Button() { label=_("Accept"), visible=true }; Button deny_button = new Button() { label=_("Deny"), visible=true }; GLib.Application app = GLib.Application.get_default(); accept_button.clicked.connect(() => { app.activate_action("accept-subscription", conversation.id); conversation_view.remove_notification(box); }); deny_button.clicked.connect(() => { app.activate_action("deny-subscription", conversation.id); conversation_view.remove_notification(box); }); box.add(new Label(_("This contact would like to add you to their contact list")) { margin_end=10, visible=true }); box.add(accept_button); box.add(deny_button); conversation_view.add_notification(box); } } } dino-0.3.0/main/src/ui/conversation_list_titlebar.vala0000644000000000000000000000255614202022370021571 0ustar rootrootusing Gtk; using Dino.Entities; namespace Dino.Ui { [GtkTemplate (ui = "/im/dino/Dino/conversation_list_titlebar.ui")] public class ConversationListTitlebar : Gtk.Box { [GtkChild] private unowned MenuButton add_button; [GtkChild] private unowned MenuButton menu_button; public ConversationListTitlebar() { create_add_menu(add_button, menu_button); } } [GtkTemplate (ui = "/im/dino/Dino/conversation_list_titlebar_csd.ui")] public class ConversationListTitlebarCsd : Gtk.HeaderBar { [GtkChild] private unowned MenuButton add_button; [GtkChild] private unowned MenuButton menu_button; public ConversationListTitlebarCsd() { custom_title = new Label("Dino") { visible = true, hexpand = true, xalign = 0 }; custom_title.get_style_context().add_class("title"); create_add_menu(add_button, menu_button); } } private static void create_add_menu(MenuButton add_button, MenuButton menu_button) { Builder add_builder = new Builder.from_resource("/im/dino/Dino/menu_add.ui"); MenuModel add_menu_model = add_builder.get_object("menu_add") as MenuModel; add_button.set_menu_model(add_menu_model); Builder menu_builder = new Builder.from_resource("/im/dino/Dino/menu_app.ui"); MenuModel menu_menu_model = menu_builder.get_object("menu_app") as MenuModel; menu_button.set_menu_model(menu_menu_model); } } dino-0.3.0/main/src/ui/conversation_selector/0000755000000000000000000000000014202022370017673 5ustar rootrootdino-0.3.0/main/src/ui/conversation_selector/conversation_selector.vala0000644000000000000000000001370214202022370025155 0ustar rootrootusing Gee; using Gtk; using Xmpp; using Dino.Entities; namespace Dino.Ui { public class ConversationSelector : ListBox { public signal void conversation_selected(Conversation conversation); private StreamInteractor stream_interactor; private uint? drag_timeout; private HashMap rows = new HashMap(Conversation.hash_func, Conversation.equals_func); public ConversationSelector init(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; stream_interactor.get_module(ConversationManager.IDENTITY).conversation_activated.connect(add_conversation); stream_interactor.get_module(ConversationManager.IDENTITY).conversation_deactivated.connect(remove_conversation); stream_interactor.get_module(ContentItemStore.IDENTITY).new_item.connect(on_content_item_received); Timeout.add_seconds(60, () => { foreach (ConversationSelectorRow row in rows.values) row.update(); return true; }); foreach (Conversation conversation in stream_interactor.get_module(ConversationManager.IDENTITY).get_active_conversations()) { add_conversation(conversation); } return this; } construct { get_style_context().add_class("sidebar"); set_header_func(header); set_sort_func(sort); realize.connect(() => { ListBoxRow? first_row = get_row_at_index(0); if (first_row != null) { select_row(first_row); row_activated(first_row); } }); } public override void row_activated(ListBoxRow r) { ConversationSelectorRow? row = r as ConversationSelectorRow; if (row != null) { conversation_selected(row.conversation); } } public void on_conversation_selected(Conversation conversation) { if (!rows.has_key(conversation)) { add_conversation(conversation); } this.select_row(rows[conversation]); } private void on_content_item_received(ContentItem item, Conversation conversation) { if (rows.has_key(conversation)) { invalidate_sort(); } } private void add_conversation(Conversation conversation) { ConversationSelectorRow row; if (!rows.has_key(conversation)) { row = new ConversationSelectorRow(stream_interactor, conversation); rows[conversation] = row; add(row); row.main_revealer.set_reveal_child(true); drag_dest_set(row, DestDefaults.MOTION, null, Gdk.DragAction.COPY); drag_dest_set_track_motion(row, true); row.drag_motion.connect(this.on_drag_motion); row.drag_leave.connect(this.on_drag_leave); } invalidate_sort(); } public bool on_drag_motion(Widget widget, Gdk.DragContext context, int x, int y, uint time) { if (this.drag_timeout != null) return false; this.drag_timeout = Timeout.add(200, () => { if (widget.get_type().is_a(typeof(ConversationSelectorRow))) { ConversationSelectorRow row = widget as ConversationSelectorRow; conversation_selected(row.conversation); } this.drag_timeout = null; return false; }); return false; } public void on_drag_leave(Widget widget, Gdk.DragContext context, uint time) { if (this.drag_timeout != null) { Source.remove(this.drag_timeout); this.drag_timeout = null; } } private void select_fallback_conversation(Conversation conversation) { if (get_selected_row() == rows[conversation]) { int index = rows[conversation].get_index(); ListBoxRow? next_select_row = get_row_at_index(index + 1); if (next_select_row == null) { next_select_row = get_row_at_index(index - 1); } if (next_select_row != null) { select_row(next_select_row); row_activated(next_select_row); } } } private async void remove_conversation(Conversation conversation) { select_fallback_conversation(conversation); if (rows.has_key(conversation)) { yield rows[conversation].colapse(); remove(rows[conversation]); rows.unset(conversation); } } public void loop_conversations(bool backwards) { int index = get_selected_row().get_index(); int new_index = ((index + (backwards ? -1 : 1)) + rows.size) % rows.size; ListBoxRow? next_select_row = get_row_at_index(new_index); if (next_select_row != null) { select_row(next_select_row); row_activated(next_select_row); } } private void header(ListBoxRow row, ListBoxRow? before_row) { if (row.get_header() == null && before_row != null) { row.set_header(new Separator(Orientation.HORIZONTAL)); } else if (row.get_header() != null && before_row == null) { row.set_header(null); } } private int sort(ListBoxRow row1, ListBoxRow row2) { ConversationSelectorRow cr1 = row1 as ConversationSelectorRow; ConversationSelectorRow cr2 = row2 as ConversationSelectorRow; if (cr1 != null && cr2 != null) { Conversation c1 = cr1.conversation; Conversation c2 = cr2.conversation; if (c1.last_active == null) return -1; if (c2.last_active == null) return 1; int comp = c2.last_active.compare(c1.last_active); if (comp == 0) { return Util.get_conversation_display_name(stream_interactor, c1) .collate(Util.get_conversation_display_name(stream_interactor, c2)); } else { return comp; } } return 0; } } } dino-0.3.0/main/src/ui/conversation_selector/conversation_selector_row.vala0000644000000000000000000003773614202022370026061 0ustar rootrootusing Gee; using Gdk; using Gtk; using Pango; using Dino; using Dino.Entities; using Xmpp; namespace Dino.Ui { [GtkTemplate (ui = "/im/dino/Dino/conversation_row.ui")] public class ConversationSelectorRow : ListBoxRow { [GtkChild] protected unowned AvatarImage image; [GtkChild] protected unowned Label name_label; [GtkChild] protected unowned Label time_label; [GtkChild] protected unowned Label nick_label; [GtkChild] protected unowned Label message_label; [GtkChild] protected unowned Label unread_count_label; [GtkChild] protected unowned Button x_button; [GtkChild] protected unowned Revealer time_revealer; [GtkChild] protected unowned Revealer xbutton_revealer; [GtkChild] protected unowned Revealer unread_count_revealer; [GtkChild] public unowned Revealer main_revealer; public Conversation conversation { get; private set; } protected const int AVATAR_SIZE = 40; protected ContentItem? last_content_item; protected int num_unread = 0; protected StreamInteractor stream_interactor; construct { name_label.attributes = new AttrList(); } public ConversationSelectorRow(StreamInteractor stream_interactor, Conversation conversation) { this.conversation = conversation; this.stream_interactor = stream_interactor; switch (conversation.type_) { case Conversation.Type.CHAT: stream_interactor.get_module(RosterManager.IDENTITY).updated_roster_item.connect((account, jid, roster_item) => { if (conversation.account.equals(account) && conversation.counterpart.equals(jid)) { update_name_label(); } }); break; case Conversation.Type.GROUPCHAT: stream_interactor.get_module(MucManager.IDENTITY).room_info_updated.connect((account, jid) => { if (conversation != null && conversation.counterpart.equals_bare(jid) && conversation.account.equals(account)) { update_name_label(); update_read(true); // bubble color might have changed } }); stream_interactor.get_module(MucManager.IDENTITY).private_room_occupant_updated.connect((account, room, occupant) => { if (conversation != null && conversation.counterpart.equals_bare(room.bare_jid) && conversation.account.equals(account)) { update_name_label(); } }); break; case Conversation.Type.GROUPCHAT_PM: break; } // Set tooltip switch (conversation.type_) { case Conversation.Type.CHAT: has_tooltip = true; query_tooltip.connect ((x, y, keyboard_tooltip, tooltip) => { tooltip.set_custom(generate_tooltip()); return true; }); break; case Conversation.Type.GROUPCHAT: has_tooltip = true; set_tooltip_text(conversation.counterpart.bare_jid.to_string()); break; case Conversation.Type.GROUPCHAT_PM: break; } stream_interactor.get_module(ContentItemStore.IDENTITY).new_item.connect((item, c) => { if (conversation.equals(c)) { content_item_received(item); } }); stream_interactor.get_module(MessageCorrection.IDENTITY).received_correction.connect((item) => { if (last_content_item != null && last_content_item.id == item.id) { content_item_received(item); } }); last_content_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_latest(conversation); x_button.clicked.connect(() => { stream_interactor.get_module(ConversationManager.IDENTITY).close_conversation(conversation); }); image.set_conversation(stream_interactor, conversation); conversation.notify["read-up-to-item"].connect(() => update_read()); update_name_label(); content_item_received(); } public void update() { update_time_label(); } public void content_item_received(ContentItem? ci = null) { last_content_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_latest(conversation) ?? ci; update_message_label(); update_time_label(); update_read(); } public async void colapse() { main_revealer.set_transition_type(RevealerTransitionType.SLIDE_UP); main_revealer.set_reveal_child(false); // Animations can be diabled (=> child_revealed immediately false). Wait for completion in case they're enabled. if (main_revealer.child_revealed) { main_revealer.notify["child-revealed"].connect(() => { Idle.add(colapse.callback); }); yield; } } protected void update_name_label() { name_label.label = Util.get_conversation_display_name(stream_interactor, conversation); } protected void update_time_label(DateTime? new_time = null) { if (last_content_item != null) { time_label.visible = true; time_label.label = get_relative_time(last_content_item.time.to_local()); } } protected void update_message_label() { if (last_content_item != null) { switch (last_content_item.type_) { case MessageItem.TYPE: MessageItem message_item = last_content_item as MessageItem; Message last_message = message_item.message; string body = last_message.body; bool me_command = body.has_prefix("/me "); /* If we have a /me command, we always show the display * name, and we don't set me_is_me on * get_participant_display_name, since that will return * "Me" (internationalized), whereas /me commands expect to * be in the third person. We also omit the colon in this * case, and strip off the /me prefix itself. */ if (conversation.type_ == Conversation.Type.GROUPCHAT || me_command) { nick_label.label = Util.get_participant_display_name(stream_interactor, conversation, last_message.from, !me_command); } else if (last_message.direction == Message.DIRECTION_SENT) { nick_label.label = _("Me"); } else { nick_label.label = ""; } if (me_command) { /* Don't slice off the space after /me */ body = body.slice("/me".length, body.length); } else if (nick_label.label.length > 0) { /* TODO: Is this valid for RTL languages? */ nick_label.label += ": "; } message_label.attributes.filter((attr) => attr.equal(attr_style_new(Pango.Style.ITALIC))); message_label.label = Util.summarize_whitespaces_to_space(body); break; case FileItem.TYPE: FileItem file_item = last_content_item as FileItem; FileTransfer transfer = file_item.file_transfer; if (conversation.type_ == Conversation.Type.GROUPCHAT) { // TODO properly display nick for oneself nick_label.label = Util.get_participant_display_name(stream_interactor, conversation, file_item.file_transfer.from, true) + ": "; } else { nick_label.label = transfer.direction == Message.DIRECTION_SENT ? _("Me") + ": " : ""; } bool file_is_image = transfer.mime_type != null && transfer.mime_type.has_prefix("image"); message_label.attributes.insert(attr_style_new(Pango.Style.ITALIC)); if (transfer.direction == Message.DIRECTION_SENT) { message_label.label = (file_is_image ? _("Image sent") : _("File sent") ); } else { message_label.label = (file_is_image ? _("Image received") : _("File received") ); } break; case CallItem.TYPE: CallItem call_item = (CallItem) last_content_item; Call call = call_item.call; nick_label.label = call.direction == Call.DIRECTION_OUTGOING ? _("Me") + ": " : ""; message_label.attributes.insert(attr_style_new(Pango.Style.ITALIC)); message_label.label = call.direction == Call.DIRECTION_OUTGOING ? _("Outgoing call") : _("Incoming call"); break; } nick_label.visible = true; message_label.visible = true; } } protected void update_read(bool force_update = false) { int current_num_unread = stream_interactor.get_module(ChatInteraction.IDENTITY).get_num_unread(conversation); if (num_unread == current_num_unread && !force_update) return; num_unread = current_num_unread; if (num_unread == 0) { unread_count_label.visible = false; name_label.attributes.filter((attr) => attr.equal(attr_weight_new(Weight.BOLD))); time_label.attributes.filter((attr) => attr.equal(attr_weight_new(Weight.BOLD))); nick_label.attributes.filter((attr) => attr.equal(attr_weight_new(Weight.BOLD))); message_label.attributes.filter((attr) => attr.equal(attr_weight_new(Weight.BOLD))); } else { unread_count_label.label = num_unread.to_string(); unread_count_label.visible = true; if (conversation.get_notification_setting(stream_interactor) == Conversation.NotifySetting.ON) { unread_count_label.get_style_context().add_class("unread-count-notify"); unread_count_label.get_style_context().remove_class("unread-count"); } else { unread_count_label.get_style_context().add_class("unread-count"); unread_count_label.get_style_context().remove_class("unread-count-notify"); } name_label.attributes.insert(attr_weight_new(Weight.BOLD)); time_label.attributes.insert(attr_weight_new(Weight.BOLD)); nick_label.attributes.insert(attr_weight_new(Weight.BOLD)); message_label.attributes.insert(attr_weight_new(Weight.BOLD)); } name_label.label = name_label.label; // TODO initializes redrawing, which would otherwise not happen. nicer? time_label.label = time_label.label; nick_label.label = nick_label.label; message_label.label = message_label.label; } public override void state_flags_changed(StateFlags flags) { StateFlags curr_flags = get_state_flags(); if ((curr_flags & StateFlags.PRELIGHT) != 0) { time_revealer.set_reveal_child(false); unread_count_revealer.set_reveal_child(false); xbutton_revealer.set_reveal_child(true); } else { time_revealer.set_reveal_child(true); unread_count_revealer.set_reveal_child(true); xbutton_revealer.set_reveal_child(false); } } private static Regex dino_resource_regex = /^dino\.[a-f0-9]{8}$/; private Widget generate_tooltip() { Grid grid = new Grid() { row_spacing=5, column_homogeneous=false, column_spacing=2, margin_start=5, margin_end=5, margin_top=2, margin_bottom=2, visible=true }; Label label = new Label(conversation.counterpart.to_string()) { valign=Align.START, xalign=0, visible=true }; label.attributes = new AttrList(); label.attributes.insert(attr_weight_new(Weight.BOLD)); grid.attach(label, 0, 0, 2, 1); Gee.List? full_jids = stream_interactor.get_module(PresenceManager.IDENTITY).get_full_jids(conversation.counterpart, conversation.account); if (full_jids == null) return grid; for (int i = 0; i < full_jids.size; i++) { Jid full_jid = full_jids[i]; string? show = stream_interactor.get_module(PresenceManager.IDENTITY).get_last_show(full_jid, conversation.account); if (show == null) continue; int i_cache = i; stream_interactor.get_module(EntityInfo.IDENTITY).get_identity.begin(conversation.account, full_jid, (_, res) => { Xep.ServiceDiscovery.Identity? identity = stream_interactor.get_module(EntityInfo.IDENTITY).get_identity.end(res); Image image = new Image() { hexpand=false, valign=Align.CENTER, visible=true }; if (identity != null && (identity.type_ == Xep.ServiceDiscovery.Identity.TYPE_PHONE || identity.type_ == Xep.ServiceDiscovery.Identity.TYPE_TABLET)) { image.set_from_icon_name("dino-device-phone-symbolic", IconSize.SMALL_TOOLBAR); } else { image.set_from_icon_name("dino-device-desktop-symbolic", IconSize.SMALL_TOOLBAR); } if (show == Presence.Stanza.SHOW_AWAY) { Util.force_color(image, "#FF9800"); } else if (show == Presence.Stanza.SHOW_XA || show == Presence.Stanza.SHOW_DND) { Util.force_color(image, "#FF5722"); } else { Util.force_color(image, "#4CAF50"); } string? status = null; if (show == Presence.Stanza.SHOW_AWAY) { status = "away"; } else if (show == Presence.Stanza.SHOW_XA) { status = "not available"; } else if (show == Presence.Stanza.SHOW_DND) { status = "do not disturb"; } var sb = new StringBuilder(); if (identity != null && identity.name != null) { sb.append(identity.name); } else if (full_jid.resourcepart != null && dino_resource_regex.match(full_jid.resourcepart)) { sb.append("Dino"); } else if (full_jid.resourcepart != null) { sb.append(full_jid.resourcepart); } else { return; } if (status != null) { sb.append(" (").append(status).append(")"); } Label resource = new Label(sb.str) { use_markup=true, hexpand=true, xalign=0, visible=true }; grid.attach(image, 0, i_cache + 1, 1, 1); grid.attach(resource, 1, i_cache + 1, 1, 1); }); } return grid; } private static string get_relative_time(DateTime datetime) { DateTime now = new DateTime.now_local(); TimeSpan timespan = now.difference(datetime); if (timespan > 365 * TimeSpan.DAY) { return datetime.get_year().to_string(); } else if (timespan > 7 * TimeSpan.DAY) { // Day and month // xgettext:no-c-format return datetime.format(_("%b %d")); } else if (timespan > 2 * TimeSpan.DAY) { return datetime.format("%a"); } else if (datetime.get_day_of_month() != now.get_day_of_month()) { return _("Yesterday"); } else if (timespan > 9 * TimeSpan.MINUTE) { return datetime.format(Util.is_24h_format() ? /* xgettext:no-c-format */ /* Time in 24h format (w/o seconds) */ _("%H∶%M") : /* xgettext:no-c-format */ /* Time in 12h format (w/o seconds) */ _("%l∶%M %p")); } else if (timespan > 1 * TimeSpan.MINUTE) { ulong mins = (ulong) (timespan.abs() / TimeSpan.MINUTE); return n("%i min ago", "%i mins ago", mins).printf(mins); } else { return _("Just now"); } } } } dino-0.3.0/main/src/ui/conversation_titlebar/0000755000000000000000000000000014202022370017661 5ustar rootrootdino-0.3.0/main/src/ui/conversation_titlebar/call_entry.vala0000644000000000000000000001124714202022370022667 0ustar rootrootusing Xmpp; using Gtk; using Gee; using Dino.Entities; namespace Dino.Ui { public class CallTitlebarEntry : Plugins.ConversationTitlebarEntry, Object { public string id { get { return "call"; } } public CallButton call_button; private StreamInteractor stream_interactor; public CallTitlebarEntry(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; call_button = new CallButton(stream_interactor) { tooltip_text=_("Start call") }; call_button.set_image(new Gtk.Image.from_icon_name("dino-phone-symbolic", Gtk.IconSize.MENU) { visible=true }); } public double order { get { return 4; } } public Plugins.ConversationTitlebarWidget? get_widget(Plugins.WidgetType type) { if (type == Plugins.WidgetType.GTK) { return call_button; } return null; } } public class CallButton : Plugins.ConversationTitlebarWidget, Gtk.MenuButton { private StreamInteractor stream_interactor; private Conversation conversation; private ModelButton audio_button = new ModelButton() { text=_("Audio call"), visible=true }; private ModelButton video_button = new ModelButton() { text=_("Video call"), visible=true }; public CallButton(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; use_popover = true; image = new Gtk.Image.from_icon_name("dino-phone-symbolic", Gtk.IconSize.MENU) { visible=true }; Gtk.PopoverMenu popover_menu = new Gtk.PopoverMenu(); Box box = new Box(Orientation.VERTICAL, 0) { margin=10, visible=true }; audio_button.clicked.connect(() => { stream_interactor.get_module(Calls.IDENTITY).initiate_call.begin(conversation, false, (_, res) => { CallState call_state = stream_interactor.get_module(Calls.IDENTITY).initiate_call.end(res); open_call_window(call_state); }); }); box.add(audio_button); video_button.clicked.connect(() => { stream_interactor.get_module(Calls.IDENTITY).initiate_call.begin(conversation, true, (_, res) => { CallState call_state = stream_interactor.get_module(Calls.IDENTITY).initiate_call.end(res); open_call_window(call_state); }); }); box.add(video_button); popover_menu.add(box); popover = popover_menu; clicked.connect(() => { popover_menu.visible = true; }); stream_interactor.get_module(Calls.IDENTITY).call_incoming.connect((call, state,conversation) => { update_button_state(); }); stream_interactor.get_module(Calls.IDENTITY).call_terminated.connect((call) => { update_button_state(); }); stream_interactor.get_module(PresenceManager.IDENTITY).show_received.connect((jid, account) => { if (this.conversation == null) return; if (this.conversation.counterpart.equals_bare(jid) && this.conversation.account.equals(account)) { update_visibility.begin(); } }); stream_interactor.connection_manager.connection_state_changed.connect((account, state) => { update_visibility.begin(); }); } private void open_call_window(CallState call_state) { var call_window = new CallWindow(); var call_controller = new CallWindowController(call_window, call_state, stream_interactor); call_window.controller = call_controller; call_window.present(); update_button_state(); } public new void set_conversation(Conversation conversation) { this.conversation = conversation; update_visibility.begin(); update_button_state(); } private void update_button_state() { this.sensitive = !stream_interactor.get_module(Calls.IDENTITY).is_call_in_progress(); } private async void update_visibility() { if (conversation == null) { visible = false; return; } Conversation conv_bak = conversation; bool can_do_calls = yield stream_interactor.get_module(Calls.IDENTITY).can_conversation_do_calls(conversation); if (conv_bak != conversation) return; visible = video_button.visible = can_do_calls; } public new void unset_conversation() { } } } dino-0.3.0/main/src/ui/conversation_titlebar/conversation_titlebar.vala0000644000000000000000000000542614202022370025135 0ustar rootrootusing Gtk; using Gee; using Pango; using Dino.Entities; namespace Dino.Ui { public interface ConversationTitlebar : Widget { public abstract string? subtitle { get; set; } public abstract string? title { get; set; } public abstract void insert_entry(Plugins.ConversationTitlebarEntry entry); } public class ConversationTitlebarNoCsd : ConversationTitlebar, Gtk.Box { public string? title { get { return title_label.label; } set { this.title_label.label = value; } } public string? subtitle { get { return subtitle_label.label; } set { this.subtitle_label.label = "" + value + ""; this.subtitle_label.visible = (value != null); } } private Box widgets_box = new Box(Orientation.HORIZONTAL, 0) { margin_start=15, valign=Align.END, visible=true }; private Label title_label = new Label("") { ellipsize=EllipsizeMode.END, visible=true }; private Label subtitle_label = new Label("") { use_markup=true, ellipsize=EllipsizeMode.END, visible=false }; construct { Box content_box = new Box(Orientation.HORIZONTAL, 0) { margin=5, margin_start=15, margin_end=10, hexpand=true, visible=true }; this.add(content_box); Box titles_box = new Box(Orientation.VERTICAL, 0) { valign=Align.CENTER, hexpand=true, visible=true }; content_box.add(titles_box); titles_box.add(title_label); subtitle_label.attributes = new AttrList(); subtitle_label.get_style_context().add_class("dim-label"); titles_box.add(subtitle_label); content_box.add(widgets_box); } public ConversationTitlebarNoCsd() { this.get_style_context().add_class("dino-header-right"); } public void insert_entry(Plugins.ConversationTitlebarEntry entry) { Plugins.ConversationTitlebarWidget widget = entry.get_widget(Plugins.WidgetType.GTK); if (widget != null) { Button gtk_widget = (Gtk.Button) widget; gtk_widget.relief = ReliefStyle.NONE; widgets_box.pack_end(gtk_widget); } } } public class ConversationTitlebarCsd : ConversationTitlebar, Gtk.HeaderBar { public new string? title { get { return this.get_title(); } set { base.set_title(value); } } public new string? subtitle { get { return this.get_subtitle(); } set { base.set_subtitle(value); } } public ConversationTitlebarCsd() { this.get_style_context().add_class("dino-right"); show_close_button = true; hexpand = true; } public void insert_entry(Plugins.ConversationTitlebarEntry entry) { Plugins.ConversationTitlebarWidget widget = entry.get_widget(Plugins.WidgetType.GTK); Button gtk_widget = (Gtk.Button)widget; this.pack_end(gtk_widget); } } } dino-0.3.0/main/src/ui/conversation_titlebar/menu_entry.vala0000644000000000000000000000343114202022370022714 0ustar rootrootusing Gtk; using Dino.Entities; namespace Dino.Ui { class MenuEntry : Plugins.ConversationTitlebarEntry, Object { public string id { get { return "menu"; } } StreamInteractor stream_interactor; MenuWidget widget; public MenuEntry(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; } public double order { get { return 0; } } public Plugins.ConversationTitlebarWidget? get_widget(Plugins.WidgetType type) { if (type == Plugins.WidgetType.GTK) { if (widget == null) { widget = new MenuWidget(stream_interactor) { visible=true, sensitive=false }; } return widget; } return null; } } class MenuWidget : Button, Plugins.ConversationTitlebarWidget { private StreamInteractor stream_interactor; private Conversation? conversation; public MenuWidget(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; image = new Image.from_icon_name("open-menu-symbolic", IconSize.MENU); clicked.connect(on_clicked); } public new void set_conversation(Conversation conversation) { this.sensitive = true; this.conversation = conversation; if (conversation.type_ == Conversation.Type.GROUPCHAT) { tooltip_text = "Channel details"; } else { tooltip_text = "Conversation details"; } } public new void unset_conversation() { this.sensitive = false; } private void on_clicked() { ContactDetails.Dialog contact_details_dialog = new ContactDetails.Dialog(stream_interactor, conversation); contact_details_dialog.set_transient_for((Window) get_toplevel()); contact_details_dialog.present(); } } } dino-0.3.0/main/src/ui/conversation_titlebar/occupants_entry.vala0000644000000000000000000000325214202022370023750 0ustar rootrootusing Gtk; using Dino.Entities; namespace Dino.Ui { class OccupantsEntry : Plugins.ConversationTitlebarEntry, Object { public string id { get { return "occupants"; } } StreamInteractor stream_interactor; OccupantsWidget widget; public OccupantsEntry(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; } public double order { get { return 3; } } public Plugins.ConversationTitlebarWidget? get_widget(Plugins.WidgetType type) { if (type == Plugins.WidgetType.GTK) { if (widget == null) { widget = new OccupantsWidget(stream_interactor); } return widget; } return null; } } class OccupantsWidget : MenuButton, Plugins.ConversationTitlebarWidget { private Conversation? conversation; private StreamInteractor stream_interactor; private OccupantMenu.View menu = null; public OccupantsWidget(StreamInteractor stream_interactor) { image = new Image.from_icon_name("system-users-symbolic", IconSize.MENU); tooltip_text = _("Members"); this.stream_interactor = stream_interactor; set_use_popover(true); } public new void set_conversation(Conversation conversation) { this.conversation = conversation; visible = conversation.type_ == Conversation.Type.GROUPCHAT; if (conversation.type_ == Conversation.Type.GROUPCHAT) { OccupantMenu.View new_menu = new OccupantMenu.View(stream_interactor, conversation); set_popover(new_menu); menu = new_menu; } } public new void unset_conversation() { visible = false; } } } dino-0.3.0/main/src/ui/conversation_titlebar/search_entry.vala0000644000000000000000000000165614202022370023224 0ustar rootrootusing Gtk; using Gee; using Dino.Entities; namespace Dino.Ui { public class SearchMenuEntry : Plugins.ConversationTitlebarEntry, Object { public string id { get { return "search"; } } public GlobalSearchButton search_button = new GlobalSearchButton() { tooltip_text=_("Search messages"), visible = true }; public SearchMenuEntry() { search_button.set_image(new Gtk.Image.from_icon_name("system-search-symbolic", Gtk.IconSize.MENU) { visible = true }); } public double order { get { return 1; } } public Plugins.ConversationTitlebarWidget? get_widget(Plugins.WidgetType type) { if (type == Plugins.WidgetType.GTK) { return search_button; } return null; } } public class GlobalSearchButton : Plugins.ConversationTitlebarWidget, Gtk.ToggleButton { public new void set_conversation(Conversation conversation) { } public new void unset_conversation() { } } } dino-0.3.0/main/src/ui/conversation_view.vala0000644000000000000000000000312314202022370017671 0ustar rootrootusing Gee; using Gdk; using Gtk; using Dino.Entities; namespace Dino.Ui { [GtkTemplate (ui = "/im/dino/Dino/conversation_view.ui")] public class ConversationView : Gtk.Overlay { [GtkChild] public unowned Revealer goto_end_revealer; [GtkChild] public unowned Button goto_end_button; [GtkChild] public unowned ChatInput.View chat_input; [GtkChild] public unowned ConversationSummary.ConversationView conversation_frame; [GtkChild] public unowned Revealer white_revealer; construct { white_revealer.notify["child-revealed"].connect_after(on_child_revealed_changed); } public void add_overlay_dialog(Widget widget) { Revealer revealer = new Revealer() { transition_type=RevealerTransitionType.CROSSFADE , transition_duration= 100, visible=true }; revealer.add(widget); this.add_overlay(revealer); revealer.reveal_child = true; white_revealer.visible = true; white_revealer.reveal_child = true; widget.destroy.connect(() => { revealer.destroy(); // GTK4: this.remove_overlay(revealer); white_revealer.reveal_child = false; chat_input.do_focus(); }); } private void on_child_revealed_changed() { if (!white_revealer.child_revealed) { white_revealer.visible = false; } } public override void dispose() { // To prevent a warning when closing Dino // "Can't set a target list on a widget until you've called gtk_drag_dest_set() to make the widget into a drag destination" Gtk.drag_dest_unset(this); } } } dino-0.3.0/main/src/ui/conversation_view_controller.vala0000644000000000000000000002755314202022370022151 0ustar rootrootusing Gee; using Gdk; using Gtk; using Dino.Entities; namespace Dino.Ui { enum Target { URI_LIST, STRING } const TargetEntry[] target_list = { { "text/uri-list", 0, Target.URI_LIST } }; public class ConversationViewController : Object { public new string? conversation_display_name { get; set; } public string? conversation_topic { get; set; } private Application app; private ConversationView view; private Widget? overlay_dialog; private ConversationTitlebar titlebar; public SearchMenuEntry search_menu_entry = new SearchMenuEntry(); private ChatInputController chat_input_controller; private StreamInteractor stream_interactor; private Conversation? conversation; public ConversationViewController(ConversationView view, ConversationTitlebar titlebar, StreamInteractor stream_interactor) { this.view = view; this.titlebar = titlebar; this.stream_interactor = stream_interactor; this.app = GLib.Application.get_default() as Application; this.chat_input_controller = new ChatInputController(view.chat_input, stream_interactor); chat_input_controller.activate_last_message_correction.connect(view.conversation_frame.activate_last_message_correction); chat_input_controller.file_picker_selected.connect(open_file_picker); chat_input_controller.clipboard_pasted.connect(on_clipboard_paste); view.conversation_frame.init(stream_interactor); // drag 'n drop file upload view.drag_data_received.connect(this.on_drag_data_received); // forward key presses view.chat_input.key_press_event.connect(forward_key_press_to_chat_input); view.conversation_frame.key_press_event.connect(forward_key_press_to_chat_input); titlebar.key_press_event.connect(forward_key_press_to_chat_input); // goto-end floating button var vadjustment = view.conversation_frame.scrolled.vadjustment; vadjustment.notify["value"].connect(() => { bool button_active = vadjustment.value < vadjustment.upper - vadjustment.page_size; view.goto_end_revealer.reveal_child = button_active; view.goto_end_revealer.visible = button_active; }); view.goto_end_button.clicked.connect(() => { view.conversation_frame.initialize_for_conversation(conversation); }); // Update conversation display name & topic this.bind_property("conversation-display-name", titlebar, "title"); this.bind_property("conversation-topic", titlebar, "subtitle"); stream_interactor.get_module(MucManager.IDENTITY).room_info_updated.connect((account, jid) => { if (conversation != null && conversation.counterpart.equals_bare(jid) && conversation.account.equals(account)) { update_conversation_display_name(); } }); stream_interactor.get_module(MucManager.IDENTITY).private_room_occupant_updated.connect((account, room, occupant) => { if (conversation != null && conversation.counterpart.equals_bare(room.bare_jid) && conversation.account.equals(account)) { update_conversation_display_name(); } }); stream_interactor.get_module(MucManager.IDENTITY).subject_set.connect((account, jid, subject) => { if (conversation != null && conversation.counterpart.equals_bare(jid) && conversation.account.equals(account)) { update_conversation_topic(subject); } }); stream_interactor.get_module(RosterManager.IDENTITY).updated_roster_item.connect((account, jid, roster_item) => { if (conversation.account.equals(account) && conversation.counterpart.equals(jid)) { update_conversation_display_name(); } }); stream_interactor.get_module(FileManager.IDENTITY).upload_available.connect(update_file_upload_status); // Headerbar plugins app.plugin_registry.register_contact_titlebar_entry(new MenuEntry(stream_interactor)); app.plugin_registry.register_contact_titlebar_entry(search_menu_entry); app.plugin_registry.register_contact_titlebar_entry(new OccupantsEntry(stream_interactor)); app.plugin_registry.register_contact_titlebar_entry(new CallTitlebarEntry(stream_interactor)); foreach(var entry in app.plugin_registry.conversation_titlebar_entries) { titlebar.insert_entry(entry); } AccelGroup accel_group = new AccelGroup(); accel_group.connect(Gdk.Key.U, ModifierType.CONTROL_MASK, AccelFlags.VISIBLE, () => { if (conversation == null) return false; stream_interactor.get_module(FileManager.IDENTITY).is_upload_available.begin(conversation, (_, res) => { if (stream_interactor.get_module(FileManager.IDENTITY).is_upload_available.end(res)) { open_file_picker(); } }); return false; }); ((Gtk.Window)view.get_toplevel()).add_accel_group(accel_group); } public void select_conversation(Conversation? conversation, bool default_initialize_conversation) { if (this.conversation != null) { conversation.notify["encryption"].disconnect(update_file_upload_status); } if (overlay_dialog != null) { overlay_dialog.destroy(); } this.conversation = conversation; conversation.notify["encryption"].connect(update_file_upload_status); chat_input_controller.set_conversation(conversation); update_conversation_display_name(); update_conversation_topic(); foreach(var e in this.app.plugin_registry.conversation_titlebar_entries) { Plugins.ConversationTitlebarWidget view = e.get_widget(Plugins.WidgetType.GTK); if (view != null) { view.set_conversation(conversation); } } if (default_initialize_conversation) { view.conversation_frame.initialize_for_conversation(conversation); } update_file_upload_status(); } public void unset_conversation() { conversation_display_name = null; conversation_topic = null; } private void update_file_upload_status() { stream_interactor.get_module(FileManager.IDENTITY).is_upload_available.begin(conversation, (_, res) => { bool upload_available = stream_interactor.get_module(FileManager.IDENTITY).is_upload_available.end(res); chat_input_controller.set_file_upload_active(upload_available); if (upload_available && overlay_dialog == null) { Gtk.drag_dest_set(view, DestDefaults.ALL, target_list, Gdk.DragAction.COPY); } else { Gtk.drag_dest_unset(view); } }); } private void update_conversation_display_name() { conversation_display_name = Util.get_conversation_display_name(stream_interactor, conversation); } private void update_conversation_topic(string? subtitle = null) { if (subtitle != null) { conversation_topic = Util.summarize_whitespaces_to_space(subtitle); } else if (conversation.type_ == Conversation.Type.GROUPCHAT) { string? subject = stream_interactor.get_module(MucManager.IDENTITY).get_groupchat_subject(conversation.counterpart, conversation.account); if (subject != null) { conversation_topic = Util.summarize_whitespaces_to_space(subject); } else { conversation_topic = null; } } else { conversation_topic = null; } } private void on_clipboard_paste() { Clipboard clipboard = Clipboard.get(Gdk.SELECTION_CLIPBOARD); if (clipboard.wait_is_image_available()) { clipboard.request_image((_, pixbuf) => { File file = File.new_for_path(Path.build_filename(FileManager.get_storage_dir(), Xmpp.random_uuid() + ".png")); try { FileOutputStream fos = file.create(FileCreateFlags.REPLACE_DESTINATION); pixbuf.save_to_stream_async.begin(fos, "png", null, () => { open_send_file_overlay(file); }); } catch (Error e) { warning("Could not create file to store pasted image in %s, %s", file.get_path(), e.message); } }); } } private void on_drag_data_received(Widget widget, Gdk.DragContext context, int x, int y, SelectionData selection_data, uint target_type, uint time) { if ((selection_data != null) && (selection_data.get_length() >= 0)) { switch (target_type) { case Target.URI_LIST: string[] uris = selection_data.get_uris(); // For now we only process the first dragged file if (uris.length >= 1) { try { string file_path = Filename.from_uri(uris[0]); open_send_file_overlay(File.new_for_path(file_path)); } catch (ConvertError e) { warning("Could not handle dragged file %s, %s", uris[0], e.message); } } break; default: break; } } } private void open_file_picker() { PreviewFileChooserNative chooser = new PreviewFileChooserNative(_("Select file"), view.get_toplevel() as Gtk.Window, FileChooserAction.OPEN, _("Select"), _("Cancel")); if (chooser.run() == Gtk.ResponseType.ACCEPT) { open_send_file_overlay(File.new_for_path(chooser.get_filename())); } } private void open_send_file_overlay(File file) { FileInfo file_info; try { file_info = file.query_info("*", FileQueryInfoFlags.NONE); } catch (Error e) { return; } FileSendOverlay overlay = new FileSendOverlay(file, file_info); overlay.send_file.connect(() => send_file(file)); stream_interactor.get_module(FileManager.IDENTITY).get_file_size_limits.begin(conversation, (_, res) => { HashMap limits = stream_interactor.get_module(FileManager.IDENTITY).get_file_size_limits.end(res); bool something_works = false; foreach (var limit in limits.values) { if (limit >= file_info.get_size()) { something_works = true; } } if (!something_works && limits.has_key(0)) { if (!something_works && file_info.get_size() > limits[0]) { overlay.set_file_too_large(); } } }); overlay.close.connect(() => { // We don't want drag'n'drop to be active while the overlay is active overlay_dialog = null; update_file_upload_status(); }); view.add_overlay_dialog(overlay); overlay_dialog = overlay; update_file_upload_status(); } private void send_file(File file) { stream_interactor.get_module(FileManager.IDENTITY).send_file.begin(file, conversation); } private bool forward_key_press_to_chat_input(EventKey event) { if (((Gtk.Window)view.get_toplevel()).get_focus() is TextView) { return false; } // Don't forward / change focus on Control / Alt if (event.keyval == Gdk.Key.Control_L || event.keyval == Gdk.Key.Control_R || event.keyval == Gdk.Key.Alt_L || event.keyval == Gdk.Key.Alt_R) { return false; } // Don't forward / change focus on Control + ... if ((event.state & ModifierType.CONTROL_MASK) > 0) { return false; } if (view.chat_input.chat_text_view.text_view.key_press_event(event)) { return true; } return false; } } } dino-0.3.0/main/src/ui/file_send_overlay.vala0000644000000000000000000000520214202022370017616 0ustar rootrootusing Gee; using Gdk; using Gtk; using Dino.Entities; namespace Dino.Ui { [GtkTemplate (ui = "/im/dino/Dino/file_send_overlay.ui")] public class FileSendOverlay : Gtk.EventBox { public signal void close(); public signal void send_file(); [GtkChild] public unowned Button close_button; [GtkChild] public unowned Button send_button; [GtkChild] public unowned SizingBin file_widget_insert; [GtkChild] public unowned Label info_label; private bool can_send = true; public FileSendOverlay(File file, FileInfo file_info) { close_button.clicked.connect(() => { this.close(); this.destroy(); }); send_button.clicked.connect(() => { send_file(); this.close(); this.destroy(); }); load_file_widget.begin(file, file_info); this.realize.connect(() => { if (can_send) { send_button.grab_focus(); } else { close_button.grab_focus(); } }); this.key_release_event.connect((event) => { if (event.keyval == Gdk.Key.Escape) { this.destroy(); } return false; }); } private async void load_file_widget(File file, FileInfo file_info) { string file_name = file_info.get_display_name(); string mime_type = file_info.get_content_type(); bool is_image = false; foreach (PixbufFormat pixbuf_format in Pixbuf.get_formats()) { foreach (string supported_mime_type in pixbuf_format.get_mime_types()) { if (supported_mime_type == mime_type) { is_image = true; } } } Widget? widget = null; if (is_image) { FileImageWidget image_widget = new FileImageWidget() { visible=true }; try { yield image_widget.load_from_file(file, file_name); widget = image_widget; } catch (Error e) { } } if (widget == null) { FileDefaultWidget default_widget = new FileDefaultWidget() { visible=true }; default_widget.name_label.label = file_name; default_widget.update_file_info(mime_type, FileTransfer.State.COMPLETE, (long)file_info.get_size()); widget = default_widget; } file_widget_insert.add(widget); } public void set_file_too_large() { info_label.label= _("The file exceeds the server's maximum upload size."); Util.force_error_color(info_label); send_button.sensitive = false; can_send = false; } } } dino-0.3.0/main/src/ui/global_search.vala0000644000000000000000000003222114202022370016713 0ustar rootrootusing Gee; using Gtk; using Pango; using Dino.Entities; namespace Dino.Ui { [GtkTemplate (ui = "/im/dino/Dino/global_search.ui")] public class GlobalSearch : Overlay { public signal void selected_item(MessageItem item); private StreamInteractor stream_interactor; private string search = ""; private int loaded_results = -1; private Mutex reloading_mutex = Mutex(); [GtkChild] public unowned SearchEntry search_entry; [GtkChild] public unowned Label entry_number_label; [GtkChild] public unowned ScrolledWindow results_scrolled; [GtkChild] public unowned Box results_box; [GtkChild] public unowned Stack results_empty_stack; [GtkChild] public unowned Frame auto_complete_overlay; [GtkChild] public unowned ListBox auto_complete_list; public GlobalSearch init(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; search_entry.search_changed.connect(() => { set_search(search_entry.text); }); search_entry.notify["text"].connect_after(update_auto_complete); search_entry.notify["cursor-position"].connect_after(update_auto_complete); results_scrolled.vadjustment.notify["value"].connect(on_scrolled_window_vadjustment_value); results_scrolled.vadjustment.notify["upper"].connect_after(on_scrolled_window_vadjustment_upper); event.connect(on_event); return this; } private void on_scrolled_window_vadjustment_value() { if (results_scrolled.vadjustment.upper - (results_scrolled.vadjustment.value + results_scrolled.vadjustment.page_size) < 100) { if (!reloading_mutex.trylock()) return; Gee.List new_messages = stream_interactor.get_module(SearchProcessor.IDENTITY).match_messages(search, loaded_results); if (new_messages.size == 0) { reloading_mutex.unlock(); return; } loaded_results += new_messages.size; append_messages(new_messages); } } private void on_scrolled_window_vadjustment_upper() { reloading_mutex.trylock(); reloading_mutex.unlock(); } private bool on_event(Gdk.Event event) { if (auto_complete_overlay.visible) { if (event.type == Gdk.EventType.KEY_PRESS && event.key.keyval == Gdk.Key.Up) { var row = auto_complete_list.get_selected_row(); var index = row == null ? -1 : row.get_index() - 1; if (index == -1) index = (int)auto_complete_list.get_children().length() - 1; auto_complete_list.select_row(auto_complete_list.get_row_at_index(index)); return true; } if (event.type == Gdk.EventType.KEY_PRESS && event.key.keyval == Gdk.Key.Down) { var row = auto_complete_list.get_selected_row(); var index = row == null ? 0 : row.get_index() + 1; if (index == auto_complete_list.get_children().length()) index = 0; auto_complete_list.select_row(auto_complete_list.get_row_at_index(index)); return true; } if (event.type == Gdk.EventType.KEY_PRESS && event.key.keyval == Gdk.Key.Tab || event.type == Gdk.EventType.KEY_RELEASE && event.key.keyval == Gdk.Key.Return) { auto_complete_list.get_selected_row().activate(); return true; } } // TODO: Handle cursor movement in results // TODO: Direct all keystrokes to text input return false; } private void update_auto_complete() { Gee.List suggestions = stream_interactor.get_module(SearchProcessor.IDENTITY).suggest_auto_complete(search_entry.text, search_entry.cursor_position); auto_complete_overlay.visible = suggestions.size > 0; if (suggestions.size > 0) { auto_complete_list.@foreach((widget) => auto_complete_list.remove(widget)); foreach(SearchSuggestion suggestion in suggestions) { Builder builder = new Builder.from_resource("/im/dino/Dino/search_autocomplete.ui"); AvatarImage avatar = (AvatarImage)builder.get_object("image"); Label label = (Label)builder.get_object("label"); string display_name; if (suggestion.conversation.type_ == Conversation.Type.GROUPCHAT && !suggestion.conversation.counterpart.equals(suggestion.jid) || suggestion.conversation.type_ == Conversation.Type.GROUPCHAT_PM) { display_name = Util.get_participant_display_name(stream_interactor, suggestion.conversation, suggestion.jid); avatar.set_conversation_participant(stream_interactor, suggestion.conversation, suggestion.jid); } else { display_name = Util.get_conversation_display_name(stream_interactor, suggestion.conversation); avatar.set_conversation(stream_interactor, suggestion.conversation); } if (display_name != suggestion.jid.to_string()) { label.set_markup("%s %s".printf(Markup.escape_text(display_name), Markup.escape_text(suggestion.jid.to_string()))); } else { label.label = display_name; } ListBoxRow row = new ListBoxRow() { visible = true, can_focus = false }; row.add((Widget)builder.get_object("root")); row.activate.connect(() => { handle_suggestion(suggestion); }); auto_complete_list.add(row); } auto_complete_list.select_row(auto_complete_list.get_row_at_index(0)); } } private void handle_suggestion(SearchSuggestion suggestion) { search_entry.move_cursor(MovementStep.LOGICAL_POSITIONS, suggestion.start_index - search_entry.cursor_position, false); search_entry.delete_from_cursor(DeleteType.CHARS, suggestion.end_index - suggestion.start_index); search_entry.insert_at_cursor(suggestion.completion + " "); } private void clear_search() { results_box.@foreach((widget) => { results_box.remove(widget); }); loaded_results = 0; } private void set_search(string search) { clear_search(); this.search = search; if (get_keywords(search).is_empty) { results_empty_stack.set_visible_child_name("empty"); return; } Gee.List messages = stream_interactor.get_module(SearchProcessor.IDENTITY).match_messages(search); if (messages.size == 0) { results_empty_stack.set_visible_child_name("no-result"); } else { results_empty_stack.set_visible_child_name("results"); int match_count = messages.size < 10 ? messages.size : stream_interactor.get_module(SearchProcessor.IDENTITY).count_match_messages(search); entry_number_label.label = "" + n("%i search result", "%i search results", match_count).printf(match_count) + ""; loaded_results += messages.size; append_messages(messages); } } private void append_messages(Gee.List messages) { foreach (MessageItem item in messages) { Gee.List before_message = stream_interactor.get_module(MessageStorage.IDENTITY).get_messages_before_message(item.conversation, item.message.time, item.message.id, 1); Gee.List after_message = stream_interactor.get_module(MessageStorage.IDENTITY).get_messages_after_message(item.conversation, item.message.time, item.message.id, 1); Box context_box = new Box(Orientation.VERTICAL, 5) { visible=true }; if (before_message != null && before_message.size > 0) { context_box.add(get_context_message_widget(before_message.first())); } Widget match_widget = get_match_message_widget(item); context_box.add(match_widget); if (after_message != null && after_message.size > 0) { context_box.add(get_context_message_widget(after_message.first())); } Label date_label = new Label(ConversationSummary.ItemMetaDataHeader.get_relative_time(item.time.to_local())) { xalign=0, visible=true }; date_label.get_style_context().add_class("dim-label"); string display_name = Util.get_conversation_display_name(stream_interactor, item.conversation); string title = item.message.type_ == Message.Type.GROUPCHAT ? _("In %s").printf(display_name) : _("With %s").printf(display_name); Box header_box = new Box(Orientation.HORIZONTAL, 10) { margin_start=7, visible=true }; header_box.add(new Label(@"$(Markup.escape_text(title))") { ellipsize=EllipsizeMode.END, xalign=0, use_markup=true, visible=true }); header_box.add(date_label); Box result_box = new Box(Orientation.VERTICAL, 7) { visible=true }; result_box.add(header_box); result_box.add(context_box); results_box.add(result_box); } } private Widget get_match_message_widget(MessageItem item) { Grid grid = get_skeleton(item); grid.margin_top = 3; grid.margin_bottom = 3; string text = item.message.body.replace("\n", "").replace("\r", ""); if (text.length > 200) { int index = text.index_of(search); if (index + search.length <= 100) { text = text.substring(0, 150) + " … " + text.substring(text.length - 50, 50); } else if (index >= text.length - 100) { text = text.substring(0, 50) + " … " + text.substring(text.length - 150, 150); } else { text = text.substring(0, 25) + " … " + text.substring(index - 50, 50) + text.substring(index, 100) + " … " + text.substring(text.length - 25, 25); } } Label label = new Label("") { use_markup=true, xalign=0, selectable=true, wrap=true, wrap_mode=Pango.WrapMode.WORD_CHAR, vexpand=true, visible=true }; // Build regex containing all keywords string regex_str = "("; Gee.List keywords = get_keywords(Regex.escape_string(search.down())); bool first = true; foreach (string keyword in keywords) { if (first) { first = false; } else { regex_str += "|"; } regex_str += "\\b" + keyword; } regex_str += ")"; // Color the keywords string markup_text = ""; try { Regex highlight_regex = new Regex(regex_str, RegexCompileFlags.CASELESS); MatchInfo match_info; highlight_regex.match(text, 0, out match_info); int last_end = 0; for (; match_info.matches(); match_info.next()) { int start, end; match_info.fetch_pos(0, out start, out end); markup_text += Markup.escape_text(text[last_end:start]) + "" + Markup.escape_text(text[start:end]) + ""; last_end = end; } markup_text += Markup.escape_text(text[last_end:text.length]); } catch (RegexError e) { assert_not_reached(); } label.label = markup_text; grid.attach(label, 1, 1, 1, 1); Button button = new Button() { relief=ReliefStyle.NONE, visible=true }; button.clicked.connect(() => { selected_item(item); }); button.add(grid); return button; } private Grid get_context_message_widget(MessageItem item) { Grid grid = get_skeleton(item); grid.margin_start = 7; Label label = new Label(item.message.body.replace("\n", "").replace("\r", "")) { ellipsize=EllipsizeMode.MIDDLE, xalign=0, visible=true }; grid.attach(label, 1, 1, 1, 1); grid.opacity = 0.55; return grid; } private Grid get_skeleton(MessageItem item) { AvatarImage image = new AvatarImage() { height=32, width=32, margin_end=7, valign=Align.START, visible=true, allow_gray = false }; image.set_conversation_participant(stream_interactor, item.conversation, item.jid); Grid grid = new Grid() { row_homogeneous=false, visible=true }; grid.attach(image, 0, 0, 1, 2); string display_name = Util.get_participant_display_name(stream_interactor, item.conversation, item.jid); Label name_label = new Label(display_name) { ellipsize=EllipsizeMode.END, xalign=0, visible=true }; name_label.attributes = new AttrList(); name_label.attributes.insert(attr_weight_new(Weight.BOLD)); grid.attach(name_label, 1, 0, 1, 1); return grid; } private static Gee.List get_keywords(string search_string) { Gee.List ret = new ArrayList(); foreach (string search in search_string.split(" ")) { bool is_filter = search.has_prefix("from:") || search.has_prefix("in:") || search.has_prefix("with:"); if (!is_filter && search != "") { ret.add(search); } } return ret; } } } dino-0.3.0/main/src/ui/main_window.vala0000644000000000000000000002140314202022370016441 0ustar rootrootusing Gee; using Gdk; using Gtk; using Dino.Entities; namespace Dino.Ui { public class MainWindow : Gtk.Window { public signal void conversation_selected(Conversation conversation); public new string? title { get; set; } public string? subtitle { get; set; } public WelcomePlaceholder welcome_placeholder = new WelcomePlaceholder() { visible=true }; public NoAccountsPlaceholder accounts_placeholder = new NoAccountsPlaceholder() { visible=true }; public ConversationView conversation_view; public ConversationSelector conversation_selector; public ConversationTitlebar conversation_titlebar; public ConversationTitlebarCsd conversation_titlebar_csd; public ConversationListTitlebarCsd conversation_list_titlebar_csd; public HeaderBar placeholder_headerbar = new HeaderBar() { title="Dino", show_close_button=true, visible=true }; public Box box = new Box(Orientation.VERTICAL, 0) { orientation=Orientation.VERTICAL, visible=true }; public Paned headerbar_paned = new Paned(Orientation.HORIZONTAL) { visible=true }; public Paned paned; public Revealer search_revealer; public SearchEntry search_entry; public GlobalSearch search_box; private Stack stack = new Stack() { visible=true }; private Stack left_stack; private Stack right_stack; private StreamInteractor stream_interactor; private Database db; private Config config; public MainWindow(Application application, StreamInteractor stream_interactor, Database db, Config config) { Object(application : application); this.stream_interactor = stream_interactor; this.db = db; this.config = config; restore_window_size(); this.get_style_context().add_class("dino-main"); setup_headerbar(); Gtk.Settings.get_default().notify["gtk-decoration-layout"].connect(set_window_buttons); this.realize.connect(set_window_buttons); setup_unified(); setup_stack(); paned.bind_property("position", headerbar_paned, "position", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); } private void setup_unified() { Builder builder = new Builder.from_resource("/im/dino/Dino/unified_main_content.ui"); paned = (Paned) builder.get_object("paned"); box.add(paned); left_stack = (Stack) builder.get_object("left_stack"); right_stack = (Stack) builder.get_object("right_stack"); conversation_view = (ConversationView) builder.get_object("conversation_view"); conversation_selector = ((ConversationSelector) builder.get_object("conversation_list")).init(stream_interactor); search_box = ((GlobalSearch) builder.get_object("search_box")).init(stream_interactor); search_revealer = (Revealer) builder.get_object("search_revealer"); search_entry = (SearchEntry) builder.get_object("search_entry"); Image conversation_list_placeholder_image = (Image) builder.get_object("conversation_list_placeholder_image"); conversation_list_placeholder_image.set_from_pixbuf(new Pixbuf.from_resource("/im/dino/Dino/icons/dino-conversation-list-placeholder-arrow.svg")); } private void setup_headerbar() { if (Util.use_csd()) { conversation_list_titlebar_csd = new ConversationListTitlebarCsd() { visible=true }; headerbar_paned.pack1(conversation_list_titlebar_csd, false, false); conversation_titlebar_csd = new ConversationTitlebarCsd() { visible=true }; conversation_titlebar = conversation_titlebar_csd; headerbar_paned.pack2(conversation_titlebar_csd, true, false); } else { ConversationListTitlebar conversation_list_titlebar = new ConversationListTitlebar() { visible=true }; headerbar_paned.pack1(conversation_list_titlebar, false, false); conversation_titlebar = new ConversationTitlebarNoCsd() { visible=true }; headerbar_paned.pack2(conversation_titlebar, true, false); box.add(headerbar_paned); } } private void set_window_buttons() { if (!Util.use_csd()) return; Gtk.Settings? gtk_settings = Gtk.Settings.get_default(); if (gtk_settings == null) return; string[] buttons = gtk_settings.gtk_decoration_layout.split(":"); this.conversation_list_titlebar_csd.decoration_layout = buttons[0] + ":"; this.conversation_titlebar_csd.decoration_layout = ((buttons.length == 2) ? ":" + buttons[1] : ""); } private void setup_stack() { stack.add_named(box, "main"); stack.add_named(welcome_placeholder, "welcome_placeholder"); stack.add_named(accounts_placeholder, "accounts_placeholder"); add(stack); } public enum StackState { CLEAN_START, NO_ACTIVE_ACCOUNTS, NO_ACTIVE_CONVERSATIONS, CONVERSATION } public void set_stack_state(StackState stack_state) { if (stack_state == StackState.CONVERSATION) { left_stack.set_visible_child_name("content"); right_stack.set_visible_child_name("content"); stack.set_visible_child_name("main"); if (Util.use_csd()) { set_titlebar(headerbar_paned); } } else if (stack_state == StackState.CLEAN_START || stack_state == StackState.NO_ACTIVE_ACCOUNTS) { if (stack_state == StackState.CLEAN_START) { stack.set_visible_child_name("welcome_placeholder"); } else if (stack_state == StackState.NO_ACTIVE_ACCOUNTS) { stack.set_visible_child_name("accounts_placeholder"); } if (Util.use_csd()) { set_titlebar(placeholder_headerbar); } } else if (stack_state == StackState.NO_ACTIVE_CONVERSATIONS) { stack.set_visible_child_name("main"); left_stack.set_visible_child_name("placeholder"); right_stack.set_visible_child_name("placeholder"); if (Util.use_csd()) { set_titlebar(headerbar_paned); } } } public void loop_conversations(bool backwards) { conversation_selector.loop_conversations(backwards); } public void restore_window_size() { Gdk.Display? display = Gdk.Display.get_default(); if (display != null) { Gdk.Monitor? monitor = display.get_primary_monitor(); if (monitor == null) { monitor = display.get_monitor_at_point(1, 1); } if (monitor != null && config.window_width <= monitor.geometry.width && config.window_height <= monitor.geometry.height) { set_default_size(config.window_width, config.window_height); } } this.window_position = Gtk.WindowPosition.CENTER; if (config.window_maximize) { maximize(); } this.delete_event.connect(() => { save_window_size(); config.window_maximize = this.is_maximized; return false; }); } public void save_window_size() { if (this.is_maximized) return; Gdk.Display? display = get_display(); Gdk.Window? window = get_window(); if (display != null && window != null) { Gdk.Monitor monitor = display.get_monitor_at_window(window); int width = 0; int height = 0; get_size(out width, out height); // Only store if the values have changed and are reasonable-looking. if (config.window_width != width && width > 0 && width <= monitor.geometry.width) { config.window_width = width; } if (config.window_height != height && height > 0 && height <= monitor.geometry.height) { config.window_height = height; } } } } public class WelcomePlaceholder : MainWindowPlaceholder { public WelcomePlaceholder() { title_label.label = _("Welcome to Dino!"); label.label = _("Sign in or create an account to get started."); primary_button.label = _("Set up account"); title_label.visible = true; secondary_button.visible = false; } } public class NoAccountsPlaceholder : MainWindowPlaceholder { public NoAccountsPlaceholder() { title_label.label = _("No active accounts"); primary_button.label = _("Manage accounts"); title_label.visible = true; label.visible = false; secondary_button.visible = false; } } [GtkTemplate (ui = "/im/dino/Dino/unified_window_placeholder.ui")] public class MainWindowPlaceholder : Box { [GtkChild] public unowned Label title_label; [GtkChild] public unowned Label label; [GtkChild] public unowned Button primary_button; [GtkChild] public unowned Button secondary_button; } } dino-0.3.0/main/src/ui/main_window_controller.vala0000644000000000000000000001637414202022370020717 0ustar rootrootusing Gee; using Gdk; using Gtk; using Dino.Entities; namespace Dino.Ui { public class MainWindowController : Object { private StreamInteractor stream_interactor; private Conversation? conversation; private Application app; private Database db; private MainWindow window; private ConversationViewController conversation_view_controller; public MainWindowController(Application application, StreamInteractor stream_interactor, Database db) { this.app = application; this.stream_interactor = stream_interactor; this.db = db; stream_interactor.get_module(ConversationManager.IDENTITY).conversation_deactivated.connect(check_unset_conversation); stream_interactor.account_removed.connect(check_unset_conversation); } public void set_window(MainWindow window) { this.window = window; this.conversation_view_controller = new ConversationViewController(window.conversation_view, window.conversation_titlebar, stream_interactor); conversation_view_controller.search_menu_entry.search_button.bind_property("active", window.search_revealer, "reveal_child"); window.search_revealer.notify["child-revealed"].connect(() => { if (window.search_revealer.child_revealed) { if (window.conversation_view.conversation_frame.conversation != null && window.search_box.search_entry.text == "") { reset_search_entry(); } window.search_box.search_entry.grab_focus_without_selecting(); window.search_box.search_entry.set_position((int)window.search_box.search_entry.text_length); } }); window.search_box.selected_item.connect((item) => { select_conversation(item.conversation, false, false); window.conversation_view.conversation_frame.initialize_around_message(item.conversation, item); close_search(); }); window.welcome_placeholder.primary_button.clicked.connect(() => { ManageAccounts.AddAccountDialog dialog = new ManageAccounts.AddAccountDialog(stream_interactor, db); dialog.set_transient_for(app.get_active_window()); dialog.present(); }); window.accounts_placeholder.primary_button.clicked.connect(() => { app.activate_action("accounts", null); }); window.conversation_selector.conversation_selected.connect((conversation) => select_conversation(conversation)); window.event.connect((event) => { if (event.type == EventType.BUTTON_PRESS) { int dest_x, dest_y; bool ret = window.search_box.translate_coordinates(window, 0, 0, out dest_x, out dest_y); int geometry_x, geometry_y, geometry_width, geometry_height; window.get_window().get_geometry(out geometry_x, out geometry_y, out geometry_width, out geometry_height); if (ret && event.button.x_root - geometry_x < dest_x || event.button.y_root - geometry_y < dest_y) { close_search(); } } else if (event.type == EventType.KEY_RELEASE) { if (event.key.keyval == Gdk.Key.Escape) { close_search(); } } return false; }); window.focus_in_event.connect(() => { stream_interactor.get_module(ChatInteraction.IDENTITY).on_window_focus_in(conversation); window.urgency_hint = false; return false; }); window.focus_out_event.connect(() => { stream_interactor.get_module(ChatInteraction.IDENTITY).on_window_focus_out(conversation); return false; }); window.conversation_selected.connect(conversation => select_conversation(conversation)); stream_interactor.account_added.connect((account) => { update_stack_state(true); }); stream_interactor.account_removed.connect((account) => { update_stack_state(); }); stream_interactor.get_module(ConversationManager.IDENTITY).conversation_activated.connect(() => update_stack_state()); stream_interactor.get_module(ConversationManager.IDENTITY).conversation_deactivated.connect(() => update_stack_state()); update_stack_state(); AccelGroup accel_group = new AccelGroup(); accel_group.connect(Gdk.Key.F, ModifierType.CONTROL_MASK, AccelFlags.VISIBLE, () => { window.search_revealer.reveal_child = true; return false; }); window.add_accel_group(accel_group); } public void select_conversation(Conversation? conversation, bool do_reset_search = true, bool default_initialize_conversation = true) { this.conversation = conversation; conversation_view_controller.select_conversation(conversation, default_initialize_conversation); stream_interactor.get_module(ChatInteraction.IDENTITY).on_conversation_selected(conversation); conversation.active = true; // only for conversation_selected window.conversation_selector.on_conversation_selected(conversation); // In case selection was not via ConversationSelector if (do_reset_search) { reset_search_entry(); } } private void check_unset_conversation() { if (stream_interactor.get_module(ConversationManager.IDENTITY).get_active_conversations().size == 0) { unset_conversation(); } } private void unset_conversation() { this.conversation = null; conversation_view_controller.unset_conversation(); foreach(var e in this.app.plugin_registry.conversation_titlebar_entries) { Plugins.ConversationTitlebarWidget widget = e.get_widget(Plugins.WidgetType.GTK); if (widget != null) { widget.unset_conversation(); } } } private void update_stack_state(bool know_exists = false) { ArrayList accounts = stream_interactor.get_accounts(); if (!know_exists && accounts.size == 0) { if (db.get_accounts().size == 0) { window.set_stack_state(MainWindow.StackState.CLEAN_START); } else { window.set_stack_state(MainWindow.StackState.NO_ACTIVE_ACCOUNTS); } } else if (stream_interactor.get_module(ConversationManager.IDENTITY).get_active_conversations().size == 0) { window.set_stack_state(MainWindow.StackState.NO_ACTIVE_CONVERSATIONS); } else { window.set_stack_state(MainWindow.StackState.CONVERSATION); } } private void reset_search_entry() { if (window.conversation_view.conversation_frame.conversation != null) { switch (conversation.type_) { case Conversation.Type.CHAT: case Conversation.Type.GROUPCHAT_PM: window.search_box.search_entry.text = @"with:$(conversation.counterpart) "; break; case Conversation.Type.GROUPCHAT: window.search_box.search_entry.text = @"in:$(conversation.counterpart) "; break; } } } private void close_search() { conversation_view_controller.search_menu_entry.search_button.active = false; window.search_revealer.reveal_child = false; } } } dino-0.3.0/main/src/ui/manage_accounts/0000755000000000000000000000000014202022370016410 5ustar rootrootdino-0.3.0/main/src/ui/manage_accounts/account_row.vala0000644000000000000000000000252514202022370021604 0ustar rootrootusing Gtk; using Dino.Entities; namespace Dino.Ui.ManageAccounts { [GtkTemplate (ui = "/im/dino/Dino/manage_accounts/account_row.ui")] public class AccountRow : Gtk.ListBoxRow { [GtkChild] public unowned AvatarImage image; [GtkChild] public unowned Label jid_label; [GtkChild] public unowned Image icon; public Account account; private StreamInteractor stream_interactor; public AccountRow(StreamInteractor stream_interactor, Account account) { this.stream_interactor = stream_interactor; this.account = account; image.set_conversation(stream_interactor, new Conversation(account.bare_jid, account, Conversation.Type.CHAT)); jid_label.set_label(account.bare_jid.to_string()); stream_interactor.connection_manager.connection_error.connect((account, error) => { if (account.equals(this.account)) { update_warning_icon(); } }); stream_interactor.connection_manager.connection_state_changed.connect((account, state) => { if (account.equals(this.account)) { update_warning_icon(); } }); } private void update_warning_icon() { ConnectionManager.ConnectionError? error = stream_interactor.connection_manager.get_error(account); icon.visible = (error != null); } } } dino-0.3.0/main/src/ui/manage_accounts/add_account_dialog.vala0000644000000000000000000004430214202022370023043 0ustar rootrootusing Gee; using Gtk; using Pango; using Dino.Entities; using Xmpp; namespace Dino.Ui.ManageAccounts { [GtkTemplate (ui = "/im/dino/Dino/manage_accounts/add_account_dialog.ui")] public class AddAccountDialog : Gtk.Dialog { public signal void added(Account account); [GtkChild] private unowned Stack stack; [GtkChild] private unowned Revealer notification_revealer; [GtkChild] private unowned Label notification_label; // Sign in - JID [GtkChild] private unowned Box sign_in_jid_box; [GtkChild] private unowned Label sign_in_jid_error_label; [GtkChild] private unowned Entry jid_entry; [GtkChild] private unowned Stack sign_in_jid_continue_stack; [GtkChild] private unowned Button sign_in_jid_continue_button; [GtkChild] private unowned Button sign_in_jid_serverlist_button; // Sign in - TLS error [GtkChild] private unowned Box sign_in_tls_box; [GtkChild] private unowned Label sign_in_tls_label; [GtkChild] private unowned Stack sign_in_password_continue_stack; [GtkChild] private unowned Button sign_in_tls_back_button; // Sign in - Password [GtkChild] private unowned Box sign_in_password_box; [GtkChild] private unowned Label sign_in_password_title; [GtkChild] private unowned Label sign_in_password_error_label; [GtkChild] private unowned Entry password_entry; [GtkChild] private unowned Button sign_in_password_continue_button; [GtkChild] private unowned Button sign_in_password_back_button; // Select Server [GtkChild] private unowned Box create_account_box; [GtkChild] private unowned Button login_button; [GtkChild] private unowned Stack select_server_continue_stack; [GtkChild] private unowned Button select_server_continue; [GtkChild] private unowned Label register_form_continue_label; [GtkChild] private unowned ListBox server_list_box; [GtkChild] private unowned Entry server_entry; // Register Form [GtkChild] private unowned Box register_box; [GtkChild] private unowned Label register_title; [GtkChild] private unowned Box form_box; [GtkChild] private unowned Button register_form_back; [GtkChild] private unowned Stack register_form_continue_stack; [GtkChild] private unowned Button register_form_continue; // Success [GtkChild] private unowned Box success_box; [GtkChild] private unowned Label success_description; [GtkChild] private unowned Button success_continue_button; private static string[] server_list = new string[]{ "5222.de", "jabber.fr", "movim.eu", "yax.im" }; private StreamInteractor stream_interactor; private Database db; private HashMap list_box_jids = new HashMap(); private Jid? server_jid = null; private Jid? login_jid = null; private Xep.InBandRegistration.Form? form = null; public AddAccountDialog(StreamInteractor stream_interactor, Database db) { this.stream_interactor = stream_interactor; this.db = db; this.title = _("Add Account"); // Sign in - Jid Util.force_error_color(sign_in_jid_error_label); jid_entry.changed.connect(on_jid_entry_changed); sign_in_jid_continue_button.clicked.connect(on_sign_in_jid_continue_button_clicked); sign_in_jid_serverlist_button.clicked.connect(show_select_server); // Sign in - TLS error sign_in_tls_back_button.clicked.connect(show_sign_in_jid); // Sign in - Password Util.force_error_color(sign_in_password_error_label); password_entry.changed.connect(() => { sign_in_password_continue_button.set_sensitive(password_entry.text.length > 0); }); sign_in_password_continue_button.clicked.connect(on_sign_in_password_continue_button_clicked); sign_in_password_back_button.clicked.connect(show_sign_in_jid); // Select Server server_entry.changed.connect(() => { try { Jid jid = new Jid(server_entry.text); select_server_continue.sensitive = jid != null && jid.localpart == null && jid.resourcepart == null; } catch (InvalidJidError e) { select_server_continue.sensitive = false; } }); select_server_continue.clicked.connect(on_select_server_continue); login_button.clicked.connect(show_sign_in_jid); foreach (string server in server_list) { ListBoxRow list_box_row = new ListBoxRow() { visible=true }; list_box_row.add(new Label(server) { xalign=0, margin=3, margin_start=7, margin_end=7, visible=true }); list_box_jids[list_box_row] = server; server_list_box.add(list_box_row); } // Register Form register_form_continue.clicked.connect(on_register_form_continue_clicked); register_form_back.clicked.connect(show_select_server); // Success success_continue_button.clicked.connect(() => close()); show_sign_in_jid(); } private void show_sign_in_jid() { sign_in_jid_box.visible = true; jid_entry.grab_focus(); stack.visible_child_name = "login_jid"; sign_in_tls_box.visible = false; sign_in_password_box.visible = false; create_account_box.visible = false; register_box.visible = false; success_box.visible = false; set_default(sign_in_jid_continue_button); sign_in_jid_error_label.label = ""; jid_entry.sensitive = true; animate_window_resize(sign_in_jid_box); } private void show_tls_error(string domain, TlsCertificateFlags error_flags) { sign_in_tls_box.visible = true; stack.visible_child_name = "tls_error"; sign_in_jid_box.visible = false; sign_in_password_box.visible = false; create_account_box.visible = false; register_box.visible = false; success_box.visible = false; string error_desc = _("The server could not prove that it is %s.").printf("" + domain + ""); if (TlsCertificateFlags.UNKNOWN_CA in error_flags) { error_desc += " " + _("Its security certificate is not trusted by your operating system."); } else if (TlsCertificateFlags.BAD_IDENTITY in error_flags) { error_desc += " " + _("Its security certificate is issued to another domain."); } else if (TlsCertificateFlags.NOT_ACTIVATED in error_flags) { error_desc += " " + _("Its security certificate will only become valid in the future."); } else if (TlsCertificateFlags.EXPIRED in error_flags) { error_desc += " " + _("Its security certificate is expired."); } sign_in_tls_label.label = error_desc; animate_window_resize(sign_in_tls_box); } private void show_sign_in_password() { sign_in_password_box.visible = true; password_entry.grab_focus(); stack.visible_child_name = "login_password"; sign_in_jid_box.visible = false; sign_in_tls_box.visible = false; create_account_box.visible = false; register_box.visible = false; success_box.visible = false; set_default(sign_in_password_continue_button); sign_in_password_error_label.label = ""; sign_in_password_title.label = _("Sign in to %s").printf(login_jid.to_string()); animate_window_resize(sign_in_password_box); } private void show_select_server() { server_entry.text = ""; server_entry.grab_focus(); set_default(select_server_continue); server_list_box.row_activated.disconnect(on_server_list_row_activated); server_list_box.unselect_all(); server_list_box.row_activated.connect(on_server_list_row_activated); create_account_box.visible = true; stack.visible_child_name = "server"; sign_in_jid_box.visible = false; sign_in_tls_box.visible = false; register_box.visible = false; success_box.visible = false; animate_window_resize(create_account_box); } private void show_register_form() { register_box.visible = true; stack.visible_child_name = "form"; sign_in_jid_box.visible = false; sign_in_tls_box.visible = false; sign_in_password_box.visible = false; create_account_box.visible = false; success_box.visible = false; set_default(register_form_continue); animate_window_resize(register_box); } private void show_success(Account account) { success_box.visible = true; stack.visible_child_name = "success"; sign_in_jid_box.visible = false; sign_in_tls_box.visible = false; sign_in_password_box.visible = false; create_account_box.visible = false; register_box.visible = false; success_description.label = _("You can now use the account %s.").printf("" + Markup.escape_text(account.bare_jid.to_string()) + ""); set_default(success_continue_button); } private void on_jid_entry_changed() { try { login_jid = new Jid(jid_entry.text); if (login_jid.localpart != null && login_jid.resourcepart == null) { sign_in_jid_continue_button.sensitive = true; jid_entry.secondary_icon_name = null; } else { sign_in_jid_continue_button.sensitive = false; } } catch (InvalidJidError e) { sign_in_jid_continue_button.sensitive = false; } } private async void on_sign_in_jid_continue_button_clicked() { try { login_jid = new Jid(jid_entry.text); jid_entry.sensitive = false; sign_in_tls_label.label = ""; sign_in_jid_error_label.label = ""; sign_in_jid_continue_button.sensitive = false; sign_in_jid_continue_stack.visible_child_name = "spinner"; Register.ServerAvailabilityReturn server_status = yield Register.check_server_availability(login_jid); sign_in_jid_continue_stack.visible_child_name = "label"; sign_in_jid_continue_button.sensitive = true; if (server_status.available) { show_sign_in_password(); } else { jid_entry.sensitive = true; if (server_status.error_flags != null) { show_tls_error(login_jid.domainpart, server_status.error_flags); } else { sign_in_jid_error_label.label = _("Could not connect to %s").printf(login_jid.domainpart); } } } catch (InvalidJidError e) { warning("Invalid address from interface allowed login: %s", e.message); sign_in_jid_error_label.label = _("Invalid address"); } } private async void on_sign_in_password_continue_button_clicked() { string password = password_entry.text; Account account = new Account(login_jid, null, password, null); sign_in_password_continue_stack.visible_child_name = "spinner"; ConnectionManager.ConnectionError.Source? error = yield stream_interactor.get_module(Register.IDENTITY).add_check_account(account); sign_in_password_continue_stack.visible_child_name = "label"; if (error != null) { switch (error) { case ConnectionManager.ConnectionError.Source.SASL: sign_in_password_error_label.label = _("Wrong username or password"); break; default: sign_in_password_error_label.label = _("Something went wrong"); break; } } else { add_activate_account(account); show_success(account); } } private void on_select_server_continue() { try { server_jid = new Jid(server_entry.text); request_show_register_form.begin(server_jid); } catch (InvalidJidError e) { warning("Invalid address from interface allowed server: %s", e.message); display_notification(_("Invalid address")); } } private void on_server_list_row_activated(ListBox box, ListBoxRow row) { try { server_jid = new Jid(list_box_jids[row]); request_show_register_form.begin(server_jid); } catch (InvalidJidError e) { warning("Invalid address from selected server: %s", e.message); display_notification(_("Invalid address")); } } private async void request_show_register_form(Jid server_jid) { select_server_continue_stack.visible_child_name = "spinner"; Register.RegistrationFormReturn form_return = yield Register.get_registration_form(server_jid); if (select_server_continue_stack == null) { return; } select_server_continue_stack.visible_child_name = "label"; if (form_return.form != null) { form = form_return.form; set_register_form(server_jid, form); show_register_form(); } else if (form_return.error_flags != null) { show_tls_error(server_jid.domainpart, form_return.error_flags); } else { display_notification(_("No response from server")); } } private void set_register_form(Jid server, Xep.InBandRegistration.Form form) { form_box.foreach((widget) => { form_box.remove(widget); }); register_title.label = _("Register on %s").printf(server.to_string()); if (form.oob != null) { form_box.add(new Label(_("The server requires to sign up through a website")){ visible=true } ); form_box.add(new Label(@"$(form.oob)") { use_markup=true, visible=true }); register_form_continue_label.label = _("Open website"); register_form_continue.visible = true; register_form_continue.grab_focus(); } else if (form.fields.size > 0) { if (form.instructions != null && form.instructions != "") { string markup_instructions = Util.parse_add_markup(form.instructions, null, true, false); form_box.add(new Label(markup_instructions) { use_markup=true, halign=Align.CENTER, xalign=0, margin_top=7, wrap=true, wrap_mode=Pango.WrapMode.WORD_CHAR, visible=true }); } foreach (Xep.DataForms.DataForm.Field field in form.fields) { Widget? field_widget = Util.get_data_form_field_widget(field); if (field.label != null && field.label != "" && field_widget != null) { form_box.add(new Label(field.label) { xalign=0, margin_top=7, visible=true }); form_box.add(field_widget); } else if (field.type_ == Xep.DataForms.DataForm.Type.FIXED && field.get_value_string() != "") { string markup_fixed_field = Util.parse_add_markup(field.get_value_string(), null, true, false); form_box.add(new Label(markup_fixed_field) { use_markup=true, xalign=0, margin_top=7, wrap=true, wrap_mode=Pango.WrapMode.WORD_CHAR, visible=true }); } } register_form_continue.visible = true; register_form_continue_label.label = _("Register"); } else { form_box.add(new Label(_("Check %s for information on how to sign up").printf(@"$(server)")) { use_markup=true, visible=true }); register_form_continue.visible = false; } } private async void on_register_form_continue_clicked() { notification_revealer.set_reveal_child(false); // Button is opening a registration website if (form.oob != null) { try { AppInfo.launch_default_for_uri(form.oob, null); } catch (Error e) { } show_sign_in_jid(); return; } register_form_continue_stack.visible_child_name = "spinner"; string? error = yield Register.submit_form(server_jid, form); if (register_form_continue_stack == null) { return; } register_form_continue_stack.visible_child_name = "label"; if (error == null) { string? username = null, password = null; foreach (Xep.DataForms.DataForm.Field field in form.fields) { switch (field.var) { case "username": username = field.get_value_string(); break; case "password": password = field.get_value_string(); break; } } try { Account account = new Account(new Jid.components(username, server_jid.domainpart, null), null, password, null); add_activate_account(account); show_success(account); } catch (InvalidJidError e) { warning("Invalid address from components of registration: %s", e.message); display_notification(_("Invalid address")); } } else { display_notification(error); } } private void display_notification(string text) { notification_label.label = text; notification_revealer.set_reveal_child(true); Timeout.add_seconds(5, () => { notification_revealer.set_reveal_child(false); return false; }); } private void add_activate_account(Account account) { account.enabled = true; account.persist(db); stream_interactor.connect_account(account); added(account); } private void animate_window_resize(Widget widget) { // TODO code duplication int def_height, curr_width, curr_height; get_size(out curr_width, out curr_height); widget.get_preferred_height(null, out def_height); def_height += 5; int difference = def_height - curr_height; Timer timer = new Timer(); Timeout.add((int) (stack.transition_duration / 30), () => { ulong microsec; timer.elapsed(out microsec); ulong millisec = microsec / 1000; double partial = double.min(1, (double) millisec / stack.transition_duration); resize(curr_width, (int) (curr_height + difference * partial)); return millisec < stack.transition_duration; }); } } } dino-0.3.0/main/src/ui/manage_accounts/dialog.vala0000644000000000000000000002405614202022370020523 0ustar rootrootusing Gdk; using Gee; using Gtk; using Markup; using Dino.Entities; using Xmpp; namespace Dino.Ui.ManageAccounts { [GtkTemplate (ui = "/im/dino/Dino/manage_accounts/dialog.ui")] public class Dialog : Gtk.Dialog { public signal void account_enabled(Account account); public signal void account_disabled(Account account); [GtkChild] public unowned Stack main_stack; [GtkChild] public unowned ListBox account_list; [GtkChild] public unowned Button no_accounts_add; [GtkChild] public unowned ToolButton add_account_button; [GtkChild] public unowned ToolButton remove_account_button; [GtkChild] public unowned AvatarImage image; [GtkChild] public unowned Button image_button; [GtkChild] public unowned Label jid_label; [GtkChild] public unowned Label state_label; [GtkChild] public unowned Switch active_switch; [GtkChild] public unowned Util.EntryLabelHybrid password_hybrid; [GtkChild] public unowned Util.EntryLabelHybrid alias_hybrid; [GtkChild] public unowned Grid settings_list; private ArrayList plugin_widgets = new ArrayList(); private Database db; private StreamInteractor stream_interactor; private Account? selected_account; construct { Util.force_error_color(state_label, ".is_error"); account_list.row_selected.connect(on_account_list_row_selected); add_account_button.clicked.connect(show_add_account_dialog); no_accounts_add.clicked.connect(show_add_account_dialog); remove_account_button.clicked.connect(() => { AccountRow? account_row = account_list.get_selected_row() as AccountRow; if (selected_account != null) remove_account(account_row); }); image_button.clicked.connect(show_select_avatar); alias_hybrid.entry.key_release_event.connect(() => { selected_account.alias = alias_hybrid.text; return false; }); password_hybrid.entry.key_release_event.connect(() => { selected_account.password = password_hybrid.text; return false; }); Util.LabelHybridGroup label_hybrid_group = new Util.LabelHybridGroup(); label_hybrid_group.add(alias_hybrid); label_hybrid_group.add(password_hybrid); main_stack.set_visible_child_name("no_accounts"); int row_index = 4; int16 default_top_padding = new Gtk.Button().get_style_context().get_padding(Gtk.StateFlags.NORMAL).top + 1; Application app = GLib.Application.get_default() as Application; foreach (var e in app.plugin_registry.account_settings_entries) { Plugins.AccountSettingsWidget widget = e.get_widget(Plugins.WidgetType.GTK); plugin_widgets.add(widget); Label label = new Label(e.name) { xalign=1, yalign=0, visible=true }; label.get_style_context().add_class("dim-label"); label.margin_top = e.label_top_padding == -1 ? default_top_padding : e.label_top_padding; settings_list.attach(label, 0, row_index); if (widget is Widget) { Widget gtkw = (Widget) widget; plugin_widgets.add(widget); gtkw.visible = true; settings_list.attach(gtkw, 1, row_index, 2); } else { // TODO } row_index++; } } public Dialog(StreamInteractor stream_interactor, Database db) { Object(use_header_bar : Util.use_csd() ? 1 : 0); this.db = db; this.stream_interactor = stream_interactor; foreach (Account account in db.get_accounts()) { add_account(account); } stream_interactor.get_module(AvatarManager.IDENTITY).received_avatar.connect(on_received_avatar); stream_interactor.connection_manager.connection_error.connect((account, error) => { if (account.equals(selected_account)) { update_status_label(account); } }); stream_interactor.connection_manager.connection_state_changed.connect((account, state) => { if (account.equals(selected_account)) { update_status_label(account); } }); if (account_list.get_row_at_index(0) != null) account_list.select_row(account_list.get_row_at_index(0)); } public AccountRow add_account(Account account) { AccountRow account_item = new AccountRow (stream_interactor, account); account_list.add(account_item); main_stack.set_visible_child_name("accounts_exist"); return account_item; } private void show_add_account_dialog() { AddAccountDialog add_account_dialog = new AddAccountDialog(stream_interactor, db); add_account_dialog.set_transient_for(this); add_account_dialog.added.connect((account) => { AccountRow account_item = add_account(account); account_list.select_row(account_item); account_list.queue_draw(); }); add_account_dialog.present(); } private void remove_account(AccountRow account_item) { Gtk.MessageDialog msg = new Gtk.MessageDialog ( this, Gtk.DialogFlags.DESTROY_WITH_PARENT | Gtk.DialogFlags.MODAL, Gtk.MessageType.WARNING, Gtk.ButtonsType.OK_CANCEL, _("Remove account %s?"), account_item.jid_label.get_text()); msg.secondary_text = "You won't be able to access your conversation history anymore."; // TODO remove history! Button ok_button = msg.get_widget_for_response(ResponseType.OK) as Button; ok_button.label = _("Remove"); ok_button.get_style_context().add_class("destructive-action"); if (msg.run() == Gtk.ResponseType.OK) { account_list.remove(account_item); if (account_item.account.enabled) account_disabled(account_item.account); account_item.account.remove(); if (account_list.get_row_at_index(0) != null) { account_list.select_row(account_list.get_row_at_index(0)); } else { main_stack.set_visible_child_name("no_accounts"); } } msg.close(); } private void on_account_list_row_selected(ListBoxRow? row) { AccountRow? account_item = row as AccountRow; if (account_item != null) { selected_account = account_item.account; populate_grid_data(account_item.account); } } private void show_select_avatar() { PreviewFileChooserNative chooser = new PreviewFileChooserNative(_("Select avatar"), this, FileChooserAction.OPEN, _("Select"), _("Cancel")); FileFilter filter = new FileFilter(); foreach (PixbufFormat pixbuf_format in Pixbuf.get_formats()) { foreach (string mime_type in pixbuf_format.get_mime_types()) { filter.add_mime_type(mime_type); } } filter.set_filter_name(_("Images")); chooser.add_filter(filter); filter = new FileFilter(); filter.set_filter_name(_("All files")); filter.add_pattern("*"); chooser.add_filter(filter); if (chooser.run() == Gtk.ResponseType.ACCEPT) { string uri = chooser.get_filename(); stream_interactor.get_module(AvatarManager.IDENTITY).publish(selected_account, uri); } } private bool change_account_state(bool state) { selected_account.enabled = state; if (state) { account_enabled(selected_account); } else { account_disabled(selected_account); } return false; } private void on_received_avatar(Jid jid, Account account) { if (selected_account.equals(account) && jid.equals(account.bare_jid)) { image.set_conversation(stream_interactor, new Conversation(account.bare_jid, account, Conversation.Type.CHAT)); } } private void populate_grid_data(Account account) { active_switch.state_set.disconnect(change_account_state); image.set_conversation(stream_interactor, new Conversation(account.bare_jid, account, Conversation.Type.CHAT)); active_switch.set_active(account.enabled); jid_label.label = account.bare_jid.to_string(); alias_hybrid.text = account.alias ?? ""; password_hybrid.entry.input_purpose = InputPurpose.PASSWORD; password_hybrid.text = account.password; update_status_label(account); active_switch.state_set.connect(change_account_state); foreach(Plugins.AccountSettingsWidget widget in plugin_widgets) { widget.set_account(account); } } private void update_status_label(Account account) { state_label.label = ""; ConnectionManager.ConnectionError? error = stream_interactor.connection_manager.get_error(account); if (error != null) { state_label.label = get_connection_error_description(error); state_label.get_style_context().add_class("is_error"); } else { ConnectionManager.ConnectionState state = stream_interactor.connection_manager.get_state(account); switch (state) { case ConnectionManager.ConnectionState.CONNECTING: state_label.label = _("Connecting…"); break; case ConnectionManager.ConnectionState.CONNECTED: state_label.label = _("Connected"); break; case ConnectionManager.ConnectionState.DISCONNECTED: state_label.label = _("Disconnected"); break; } state_label.get_style_context().remove_class("is_error"); } } private string get_connection_error_description(ConnectionManager.ConnectionError error) { switch (error.source) { case ConnectionManager.ConnectionError.Source.SASL: return _("Wrong password"); case ConnectionManager.ConnectionError.Source.TLS: return _("Invalid TLS certificate"); } if (error.identifier != null) { return _("Error") + ": " + error.identifier; } else { return _("Error"); } } } } dino-0.3.0/main/src/ui/notifier_freedesktop.vala0000644000000000000000000003650514202022370020351 0ustar rootrootusing Gee; using Dino.Entities; using Xmpp; public class Dino.Ui.FreeDesktopNotifier : NotificationProvider, Object { public signal void conversation_selected(Conversation conversation); private StreamInteractor stream_interactor; private DBusNotifications dbus_notifications; private bool supports_body_markup = false; private bool supports_body_hyperlinks = false; private HashMap content_notifications = new HashMap(Conversation.hash_func, Conversation.equals_func); private HashMap> conversation_notifications = new HashMap>(Conversation.hash_func, Conversation.equals_func); private HashMap> action_listeners = new HashMap>(); private HashMap call_notifications = new HashMap(Call.hash_func, Call.equals_func); public FreeDesktopNotifier(StreamInteractor stream_interactor, DBusNotifications dbus_notifications) { this.stream_interactor = stream_interactor; this.dbus_notifications = dbus_notifications; init_dbus_notifications.begin(); } private async void init_dbus_notifications() throws Error { string[] caps; yield dbus_notifications.get_capabilities(out caps); foreach (string cap in caps) { switch (cap) { case "body-markup": supports_body_markup = true; break; case "body-hyperlinks": supports_body_hyperlinks = true; break; } } dbus_notifications.action_invoked.connect((id, action) => { if (action_listeners.has_key(id) && action_listeners[id].has_key(action)) { action_listeners[id][action].func(); } }); dbus_notifications.notification_closed.connect((id) => { action_listeners.unset(id); }); } public double get_priority() { return 1; } public async void notify_message(Message message, Conversation conversation, string conversation_display_name, string? participant_display_name) { string body = ""; if (supports_body_hyperlinks) { body = Util.parse_add_markup(message.body, null, true, false); } else if (supports_body_markup) { body = Markup.escape_text(message.body); } else { body = message.body; } yield notify_content_item(conversation, conversation_display_name, participant_display_name, body); } public async void notify_file(FileTransfer file_transfer, Conversation conversation, bool is_image, string conversation_display_name, string? participant_display_name) { string text = ""; if (file_transfer.direction == Message.DIRECTION_SENT) { text = is_image ? _("Image sent") : _("File sent"); } else { text = is_image ? _("Image received") : _("File received"); } if (supports_body_markup) { text = "" + text + ""; } yield notify_content_item(conversation, conversation_display_name, participant_display_name, text); } private async void notify_content_item(Conversation conversation, string conversation_display_name, string? participant_display_name, string body_) { string body = body_; if (participant_display_name != null) { if (supports_body_markup) { body = @"$(Markup.escape_text(participant_display_name)): $body"; } else { body = @"$participant_display_name: $body"; } } uint32 replace_id = content_notifications.has_key(conversation) ? content_notifications[conversation] : 0; HashTable hash_table = new HashTable(null, null); hash_table["image-data"] = yield get_conversation_icon(conversation); hash_table["desktop-entry"] = new Variant.string(Dino.Application.get_default().get_application_id()); string[] actions = new string[] {"default", "Open conversation"}; try { uint32 notification_id = yield dbus_notifications.notify("Dino", replace_id, "", conversation_display_name, body, actions, hash_table, -1); content_notifications[conversation] = notification_id; add_action_listener(notification_id, "default", () => { GLib.Application.get_default().activate_action("open-conversation", new Variant.int32(conversation.id)); }); } catch (Error e) { warning("Failed showing content item notification: %s", e.message); } } public async void notify_call(Call call, Conversation conversation, bool video, bool multiparty, string conversation_display_name) { debug("[%s] Call notification", call.account.bare_jid.to_string()); string summary = Markup.escape_text(conversation_display_name); string body = video ? _("Incoming video call") : _("Incoming call"); if (multiparty) { body = video ? _("Incoming video group call") : _("Incoming group call"); } HashTable hash_table = new HashTable(null, null); hash_table["image-path"] = "call-start-symbolic"; hash_table["sound-name"] = new Variant.string("phone-incoming-call"); hash_table["urgency"] = new Variant.byte(2); hash_table["desktop-entry"] = new Variant.string(Dino.Application.get_default().get_application_id()); string[] actions = new string[] {"default", "Open conversation", "reject", _("Reject"), "accept", _("Accept")}; try { uint32 notification_id = yield dbus_notifications.notify("Dino", 0, "", summary, body, actions, hash_table, 0); call_notifications[call] = notification_id; add_action_listener(notification_id, "default", () => { GLib.Application.get_default().activate_action("open-conversation", new Variant.int32(conversation.id)); }); add_action_listener(notification_id, "reject", () => { var variant = new Variant.tuple(new Variant[] {new Variant.int32(conversation.id), new Variant.int32(call.id)}); GLib.Application.get_default().activate_action("reject-call", variant); }); add_action_listener(notification_id, "accept", () => { var variant = new Variant.tuple(new Variant[] {new Variant.int32(conversation.id), new Variant.int32(call.id)}); GLib.Application.get_default().activate_action("accept-call", variant); }); } catch (Error e) { warning("Failed showing subscription request notification: %s", e.message); } } public async void retract_call_notification(Call call, Conversation conversation) { if (!call_notifications.has_key(call)) return; uint32 notification_id = call_notifications[call]; try { yield dbus_notifications.close_notification(notification_id); action_listeners.unset(notification_id); call_notifications.unset(call); } catch (Error e) { } } public async void notify_subscription_request(Conversation conversation) { string summary = _("Subscription request"); string body = Markup.escape_text(conversation.counterpart.to_string()); HashTable hash_table = new HashTable(null, null); hash_table["image-data"] = yield get_conversation_icon(conversation); hash_table["desktop-entry"] = new Variant.string(Dino.Application.get_default().get_application_id()); string[] actions = new string[] {"default", "Open conversation", "accept", _("Accept"), "deny", _("Deny")}; try { uint32 notification_id = yield dbus_notifications.notify("Dino", 0, "", summary, body, actions, hash_table, -1); if (!conversation_notifications.has_key(conversation)) { conversation_notifications[conversation] = new ArrayList(); } conversation_notifications[conversation].add(notification_id); add_action_listener(notification_id, "default", () => { GLib.Application.get_default().activate_action("open-conversation", new Variant.int32(conversation.id)); }); add_action_listener(notification_id, "accept", () => { GLib.Application.get_default().activate_action("accept-subscription", new Variant.int32(conversation.id)); }); add_action_listener(notification_id, "deny", () => { GLib.Application.get_default().activate_action("deny-subscription", new Variant.int32(conversation.id)); }); } catch (Error e) { warning("Failed showing subscription request notification: %s", e.message); } } public async void notify_connection_error(Account account, ConnectionManager.ConnectionError error) { string summary = _("Could not connect to %s").printf(account.bare_jid.domainpart); string body = ""; switch (error.source) { case ConnectionManager.ConnectionError.Source.SASL: body = _("Wrong password"); break; case ConnectionManager.ConnectionError.Source.TLS: body = _("Invalid TLS certificate"); break; default: break; } HashTable hash_table = new HashTable(null, null); hash_table["desktop-entry"] = new Variant.string(Dino.Application.get_default().get_application_id()); try { yield dbus_notifications.notify("Dino", 0, "im.dino.Dino", summary, body, new string[]{}, hash_table, -1); } catch (Error e) { warning("Failed showing connection error notification: %s", e.message); } } public async void notify_muc_invite(Account account, Jid room_jid, Jid from_jid, string inviter_display_name) { Conversation direct_conversation = new Conversation(from_jid, account, Conversation.Type.CHAT); string display_room = room_jid.bare_jid.to_string(); string summary = _("Invitation to %s").printf(display_room); string body = _("%s invited you to %s").printf(inviter_display_name, display_room); if (supports_body_markup) { body = Markup.escape_text(body); } HashTable hash_table = new HashTable(null, null); hash_table["image-data"] = yield get_conversation_icon(direct_conversation); hash_table["desktop-entry"] = new Variant.string(Dino.Application.get_default().get_application_id()); string[] actions = new string[] {"default", "", "reject", _("Reject"), "accept", _("Accept")}; try { uint32 notification_id = yield dbus_notifications.notify("Dino", 0, "", summary, body, actions, hash_table, -1); Conversation group_conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(room_jid, account, Conversation.Type.GROUPCHAT); add_action_listener(notification_id, "default", () => { GLib.Application.get_default().activate_action("open-muc-join", new Variant.int32(group_conversation.id)); }); add_action_listener(notification_id, "accept", () => { GLib.Application.get_default().activate_action("open-muc-join", new Variant.int32(group_conversation.id)); }); add_action_listener(notification_id, "deny", () => { GLib.Application.get_default().activate_action("deny-invite", new Variant.int32(group_conversation.id)); }); } catch (Error e) { warning("Failed showing muc invite notification: %s", e.message); } } public async void notify_voice_request(Conversation conversation, Jid from_jid) { string display_name = Util.get_participant_display_name(stream_interactor, conversation, from_jid); string display_room = Util.get_conversation_display_name(stream_interactor, conversation); string summary = _("Permission request"); string body = _("%s requests the permission to write in %s").printf(display_name, display_room); if (supports_body_markup) { Markup.escape_text(body); } HashTable hash_table = new HashTable(null, null); hash_table["image-data"] = yield get_conversation_icon(conversation); hash_table["desktop-entry"] = new Variant.string(Dino.Application.get_default().get_application_id()); string[] actions = new string[] {"deny", _("Deny"), "accept", _("Accept")}; try { uint32 notification_id = yield dbus_notifications.notify("Dino", 0, "", summary, body, actions, hash_table, -1); add_action_listener(notification_id, "accept", () => { GLib.Application.get_default().activate_action("accept-voice-request", new Variant.int32(conversation.id)); }); add_action_listener(notification_id, "deny", () => { GLib.Application.get_default().activate_action("deny-voice-request", new Variant.int32(conversation.id)); }); } catch (Error e) { warning("Failed showing voice request notification: %s", e.message); } } public async void retract_content_item_notifications() { if (content_notifications != null) { foreach (uint32 id in content_notifications.values) { try { dbus_notifications.close_notification.begin(id); } catch (Error e) { } } content_notifications.clear(); } } public async void retract_conversation_notifications(Conversation conversation) { if (content_notifications.has_key(conversation)) { try { dbus_notifications.close_notification.begin(content_notifications[conversation]); } catch (Error e) { } } content_notifications.unset(conversation); } private async Variant get_conversation_icon(Conversation conversation) { AvatarDrawer drawer = yield Util.get_conversation_avatar_drawer(stream_interactor, conversation); Cairo.ImageSurface surface = drawer.size(40, 40).draw_image_surface(); Gdk.Pixbuf avatar = Gdk.pixbuf_get_from_surface(surface, 0, 0, surface.get_width(), surface.get_height()); var bytes = avatar.pixel_bytes; var image_bytes = Variant.new_from_data(new VariantType("ay"), bytes.get_data(), true, bytes); return new Variant("(iiibii@ay)", avatar.width, avatar.height, avatar.rowstride, avatar.has_alpha, avatar.bits_per_sample, avatar.n_channels, image_bytes); } private void add_action_listener(uint32 id, string name, owned ListenerFunc func) { if (!action_listeners.has_key(id)) { action_listeners[id] = new HashMap(); } action_listeners[id][name] = new ListenerFuncWrapper((owned) func); } delegate void ListenerFunc(); class ListenerFuncWrapper { public ListenerFunc func; public ListenerFuncWrapper(owned ListenerFunc func) { this.func = (owned) func; } } } dino-0.3.0/main/src/ui/notifier_gnotifications.vala0000644000000000000000000002275114202022370021054 0ustar rootrootusing Gee; using Dino.Entities; using Xmpp; namespace Dino.Ui { public class GNotificationsNotifier : NotificationProvider, Object { public signal void conversation_selected(Conversation conversation); private StreamInteractor stream_interactor; private HashMap notifications = new HashMap(Conversation.hash_func, Conversation.equals_func); private Set? active_conversation_ids = null; private Set? active_ids = new HashSet(); public GNotificationsNotifier(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; } public double get_priority() { return 0; } public async void notify_message(Message message, Conversation conversation, string conversation_display_name, string? participant_display_name) { string text = message.body; if (participant_display_name != null) { text = @"$participant_display_name: $text"; } yield notify_content_item(conversation, conversation_display_name, text); } public async void notify_file(FileTransfer file_transfer, Conversation conversation, bool is_image, string conversation_display_name, string? participant_display_name) { string text = ""; if (file_transfer.direction == Message.DIRECTION_SENT) { text = is_image ? _("Image sent") : _("File sent"); } else { text = is_image ? _("Image received") : _("File received"); } if (participant_display_name != null) { text = @"$participant_display_name: $text"; } yield notify_content_item(conversation, conversation_display_name, text); } private async void notify_content_item(Conversation conversation, string title, string body) { if (!notifications.has_key(conversation)) { notifications[conversation] = new Notification(""); notifications[conversation].set_default_action_and_target_value("app.open-conversation", new Variant.int32(conversation.id)); } Notification notification = notifications[conversation]; notification.set_title(title); notification.set_body(body); try { notification.set_icon(yield get_conversation_icon(conversation)); } catch (Error e) { } GLib.Application.get_default().send_notification(conversation.id.to_string(), notifications[conversation]); if (active_conversation_ids != null) { active_conversation_ids.add(conversation.id.to_string()); } } public async void notify_call(Call call, Conversation conversation, bool video, bool multiparty, string conversation_display_name) { Notification notification = new Notification(conversation_display_name); string body = video ? _("Incoming video call") : _("Incoming call"); if (multiparty) { body = video ? _("Incoming video group call") : _("Incoming group call"); } notification.set_body(body); notification.set_urgent(true); notification.set_icon(new ThemedIcon.from_names(new string[] {"call-start-symbolic"})); notification.set_default_action_and_target_value("app.open-conversation", new Variant.int32(conversation.id)); notification.add_button_with_target_value(_("Reject"), "app.reject-call", new Variant.int32(call.id)); notification.add_button_with_target_value(_("Accept"), "app.accept-call", new Variant.int32(call.id)); GLib.Application.get_default().send_notification(call.id.to_string(), notification); } private async void retract_call_notification(Call call, Conversation conversation) { GLib.Application.get_default().withdraw_notification(call.id.to_string()); } public async void notify_subscription_request(Conversation conversation) { Notification notification = new Notification(_("Subscription request")); notification.set_body(conversation.counterpart.to_string()); try { notification.set_icon(yield get_conversation_icon(conversation)); } catch (Error e) { } notification.set_default_action_and_target_value("app.open-conversation", new Variant.int32(conversation.id)); notification.add_button_with_target_value(_("Accept"), "app.accept-subscription", conversation.id); notification.add_button_with_target_value(_("Deny"), "app.deny-subscription", conversation.id); GLib.Application.get_default().send_notification(conversation.id.to_string() + "-subscription", notification); active_ids.add(conversation.id.to_string() + "-subscription"); } public async void notify_connection_error(Account account, ConnectionManager.ConnectionError error) { Notification notification = new Notification(_("Could not connect to %s").printf(account.bare_jid.domainpart)); switch (error.source) { case ConnectionManager.ConnectionError.Source.SASL: notification.set_body("Wrong password"); break; case ConnectionManager.ConnectionError.Source.TLS: notification.set_body("Invalid TLS certificate"); break; } GLib.Application.get_default().send_notification(account.id.to_string() + "-connection-error", notification); } public async void notify_muc_invite(Account account, Jid room_jid, Jid from_jid, string inviter_display_name) { Conversation direct_conversation = new Conversation(from_jid, account, Conversation.Type.CHAT); string display_room = room_jid.bare_jid.to_string(); Notification notification = new Notification(_("Invitation to %s").printf(display_room)); string body = _("%s invited you to %s").printf(inviter_display_name, display_room); notification.set_body(body); try { notification.set_icon(yield get_conversation_icon(direct_conversation)); } catch (Error e) { } Conversation group_conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(room_jid, account, Conversation.Type.GROUPCHAT); notification.set_default_action_and_target_value("app.open-muc-join", new Variant.int32(group_conversation.id)); notification.add_button_with_target_value(_("Deny"), "app.deny-invite", group_conversation.id); notification.add_button_with_target_value(_("Accept"), "app.open-muc-join", group_conversation.id); GLib.Application.get_default().send_notification(null, notification); } public async void notify_voice_request(Conversation conversation, Jid from_jid) { string display_name = Util.get_participant_display_name(stream_interactor, conversation, from_jid); string display_room = Util.get_conversation_display_name(stream_interactor, conversation); Notification notification = new Notification(_("Permission request")); string body = _("%s requests the permission to write in %s").printf(display_name, display_room); notification.set_body(body); try { notification.set_icon(yield get_conversation_icon(conversation)); } catch (Error e) { } notification.add_button_with_target_value(_("Deny"), "app.deny-voice-request", conversation.id); notification.add_button_with_target_value(_("Accept"), "app.accept-voice-request", conversation.id); GLib.Application.get_default().send_notification(null, notification); } public async void retract_content_item_notifications() { if (active_conversation_ids == null) { Gee.List conversations = stream_interactor.get_module(ConversationManager.IDENTITY).get_active_conversations(); foreach (Conversation conversation in conversations) { GLib.Application.get_default().withdraw_notification(conversation.id.to_string()); } active_conversation_ids = new HashSet(); } else { foreach (string id in active_conversation_ids) { GLib.Application.get_default().withdraw_notification(id); } active_conversation_ids.clear(); } } public async void retract_conversation_notifications(Conversation conversation) { string subscription_id = conversation.id.to_string() + "-subscription"; if (active_ids.contains(subscription_id)) { GLib.Application.get_default().withdraw_notification(subscription_id); } } private async Icon get_conversation_icon(Conversation conversation) throws Error { AvatarDrawer drawer = yield Util.get_conversation_avatar_drawer(stream_interactor, conversation); Cairo.ImageSurface surface = drawer.size(40, 40).draw_image_surface(); Gdk.Pixbuf avatar = Gdk.pixbuf_get_from_surface(surface, 0, 0, surface.get_width(), surface.get_height()); uint8[] buffer; avatar.save_to_buffer(out buffer, "png"); return new BytesIcon(new Bytes(buffer)); } } } dino-0.3.0/main/src/ui/occupant_menu/0000755000000000000000000000000014202022370016121 5ustar rootrootdino-0.3.0/main/src/ui/occupant_menu/list.vala0000644000000000000000000001506514202022370017750 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; using Xmpp; namespace Dino.Ui.OccupantMenu{ [GtkTemplate (ui = "/im/dino/Dino/occupant_list.ui")] public class List : Box { public signal void conversation_selected(Conversation? conversation); private StreamInteractor stream_interactor; [GtkChild] public unowned ListBox list_box; [GtkChild] private unowned SearchEntry search_entry; private Conversation conversation; private string[]? filter_values; private HashMap rows = new HashMap(Jid.hash_func, Jid.equals_func); public List(StreamInteractor stream_interactor, Conversation conversation) { this.stream_interactor = stream_interactor; list_box.set_header_func(header); list_box.set_sort_func(sort); list_box.set_filter_func(filter); search_entry.search_changed.connect(refilter); stream_interactor.get_module(PresenceManager.IDENTITY).show_received.connect(on_show_received); stream_interactor.get_module(PresenceManager.IDENTITY).received_offline_presence.connect(on_received_offline_presence); initialize_for_conversation(conversation); } public void initialize_for_conversation(Conversation conversation) { this.conversation = conversation; Gee.List? occupants = stream_interactor.get_module(MucManager.IDENTITY).get_occupants(conversation.counterpart, conversation.account); if (occupants != null) { foreach (Jid occupant in occupants) { add_occupant(occupant); } } list_box.invalidate_filter(); } private void refilter() { string[]? values = null; string str = search_entry.get_text (); if (str != "") values = str.split(" "); if (filter_values == values) return; filter_values = values; list_box.invalidate_filter(); } public void add_occupant(Jid jid) { rows[jid] = new ListRow(stream_interactor, conversation, jid); list_box.add(rows[jid]); } public void remove_occupant(Jid jid) { list_box.remove(rows[jid]); rows.unset(jid); } private void on_received_offline_presence(Jid jid, Account account) { if (conversation != null && conversation.counterpart.equals_bare(jid) && jid.is_full()) { if (rows.has_key(jid)) { remove_occupant(jid); } list_box.invalidate_filter(); } } private void on_show_received(Jid jid, Account account) { if (conversation != null && conversation.counterpart.equals_bare(jid) && jid.is_full()) { if (!rows.has_key(jid)) { add_occupant(jid); } list_box.invalidate_filter(); } } private void header(ListBoxRow row, ListBoxRow? before_row) { ListRow c1 = row as ListRow; Xmpp.Xep.Muc.Affiliation? a1 = stream_interactor.get_module(MucManager.IDENTITY).get_affiliation(conversation.counterpart, c1.jid, c1.conversation.account); if (a1 == null) return; if (before_row != null) { ListRow c2 = (ListRow) before_row; Xmpp.Xep.Muc.Affiliation? a2 = stream_interactor.get_module(MucManager.IDENTITY).get_affiliation(conversation.counterpart, c2.jid, c2.conversation.account); if (a1 != a2) { row.set_header(generate_header_widget(a1, false)); } else if (row.get_header() != null){ row.set_header(null); } } else { row.set_header(generate_header_widget(a1, true)); } } private Widget generate_header_widget(Xmpp.Xep.Muc.Affiliation affiliation, bool top) { string aff_str; switch (affiliation) { case Xmpp.Xep.Muc.Affiliation.OWNER: aff_str = _("Owner"); break; case Xmpp.Xep.Muc.Affiliation.ADMIN: aff_str = _("Admin"); break; case Xmpp.Xep.Muc.Affiliation.MEMBER: aff_str = _("Member"); break; default: aff_str = _("User"); break; } int count = 0; foreach (ListRow row in rows.values) { Xmpp.Xep.Muc.Affiliation aff = stream_interactor.get_module(MucManager.IDENTITY).get_affiliation(conversation.counterpart, row.jid, conversation.account); if (aff == affiliation) count++; } Label title_label = new Label("") { margin_start=10, xalign=0, visible=true }; title_label.set_markup(@"$(Markup.escape_text(aff_str))"); Label count_label = new Label(@"$count") { xalign=0, margin_end=7, expand=true, visible=true }; count_label.get_style_context().add_class("dim-label"); Grid grid = new Grid() { margin_top=top?5:15, column_spacing=5, hexpand=true, visible=true }; grid.attach(title_label, 0, 0, 1, 1); grid.attach(count_label, 1, 0, 1, 1); grid.attach(new Separator(Orientation.HORIZONTAL) { expand=true, visible=true }, 0, 1, 2, 1); return grid; } private bool filter(ListBoxRow r) { if (r.get_type().is_a(typeof(ListRow))) { ListRow row = r as ListRow; foreach (string filter in filter_values) { return row.name_label.label.down().contains(filter.down()); } } return true; } private int sort(ListBoxRow row1, ListBoxRow row2) { if (row1.get_type().is_a(typeof(ListRow)) && row2.get_type().is_a(typeof(ListRow))) { ListRow c1 = row1 as ListRow; ListRow c2 = row2 as ListRow; int affiliation1 = get_affiliation_ranking(stream_interactor.get_module(MucManager.IDENTITY).get_affiliation(conversation.counterpart, c1.jid, c1.conversation.account) ?? Xmpp.Xep.Muc.Affiliation.NONE); int affiliation2 = get_affiliation_ranking(stream_interactor.get_module(MucManager.IDENTITY).get_affiliation(conversation.counterpart, c2.jid, c2.conversation.account) ?? Xmpp.Xep.Muc.Affiliation.NONE); if (affiliation1 < affiliation2) return -1; else if (affiliation1 > affiliation2) return 1; else return c1.name_label.label.collate(c2.name_label.label); } return 0; } private int get_affiliation_ranking(Xmpp.Xep.Muc.Affiliation affiliation) { switch (affiliation) { case Xmpp.Xep.Muc.Affiliation.OWNER: return 1; case Xmpp.Xep.Muc.Affiliation.ADMIN: return 2; case Xmpp.Xep.Muc.Affiliation.MEMBER: return 3; default: return 4; } } } } dino-0.3.0/main/src/ui/occupant_menu/list_row.vala0000644000000000000000000000144614202022370020635 0ustar rootrootusing Gtk; using Dino.Entities; using Xmpp; namespace Dino.Ui.OccupantMenu { [GtkTemplate (ui = "/im/dino/Dino/occupant_list_item.ui")] public class ListRow : ListBoxRow { [GtkChild] private unowned AvatarImage image; [GtkChild] public unowned Label name_label; public Conversation? conversation; public Jid? jid; public ListRow(StreamInteractor stream_interactor, Conversation conversation, Jid jid) { this.conversation = conversation; this.jid = jid; name_label.label = Util.get_participant_display_name(stream_interactor, conversation, jid); image.set_conversation_participant(stream_interactor, conversation, jid); } public ListRow.label(string c, string text) { name_label.label = text; image.set_text(c); } } } dino-0.3.0/main/src/ui/occupant_menu/view.vala0000644000000000000000000001521214202022370017741 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; using Xmpp; namespace Dino.Ui.OccupantMenu { public class View : Popover { private StreamInteractor stream_interactor; private Conversation conversation; private Stack stack = new Stack() { vhomogeneous=false, visible=true }; private Box list_box = new Box(Orientation.VERTICAL, 1) { visible=true }; private List? list = null; private ListBox invite_list = new ListBox() { visible=true }; private Box? jid_menu = null; private Jid? selected_jid; public View(StreamInteractor stream_interactor, Conversation conversation) { this.stream_interactor = stream_interactor; this.conversation = conversation; this.show.connect(initialize_list); invite_list.add(new ListRow.label("+", _("Invite")) {visible=true}); list_box.add(invite_list); invite_list.row_activated.connect(on_invite_clicked); stack.add_named(list_box, "list"); add(stack); stack.visible_child_name = "list"; hide.connect(reset); } public void reset() { stack.transition_type = StackTransitionType.NONE; stack.visible_child_name = "list"; if (list != null) list.list_box.unselect_all(); invite_list.unselect_all(); } private void initialize_list() { if (list == null) { list = new List(stream_interactor, conversation) { visible=true }; list_box.add(list); list_box.reorder_child(list, 0); list.list_box.row_activated.connect((row) => { ListRow list_row = row as ListRow; show_menu(list_row.jid, list_row.name_label.label); }); } } private void show_list() { if (list != null) list.list_box.unselect_all(); stack.transition_type = StackTransitionType.SLIDE_RIGHT; stack.visible_child_name = "list"; } private void show_menu(Jid jid, string name_) { selected_jid = jid; stack.transition_type = StackTransitionType.SLIDE_LEFT; string name = Markup.escape_text(name_); Jid? real_jid = stream_interactor.get_module(MucManager.IDENTITY).get_real_jid(jid, conversation.account); if (real_jid != null) name += "\n%s".printf(Markup.escape_text(real_jid.bare_jid.to_string())); Box header_box = new Box(Orientation.HORIZONTAL, 5) { visible=true }; header_box.add(new Image.from_icon_name("pan-start-symbolic", IconSize.SMALL_TOOLBAR) { visible=true }); header_box.add(new Label(name) { xalign=0, use_markup=true, hexpand=true, visible=true }); Button header_button = new Button() { relief=ReliefStyle.NONE, visible=true }; header_button.add(header_box); Box outer_box = new Box(Orientation.VERTICAL, 5) { margin=10, visible=true }; outer_box.add(header_button); header_button.clicked.connect(show_list); ModelButton private_button = new ModelButton() { active=true, text=_("Start private conversation"), visible=true }; outer_box.add(private_button); private_button.clicked.connect(private_conversation_button_clicked); Jid? own_jid = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account); Xmpp.Xep.Muc.Role? role = stream_interactor.get_module(MucManager.IDENTITY).get_role(own_jid, conversation.account); if (role == Xmpp.Xep.Muc.Role.MODERATOR && stream_interactor.get_module(MucManager.IDENTITY).kick_possible(conversation.account, jid)) { ModelButton kick_button = new ModelButton() { active=true, text=_("Kick"), visible=true }; outer_box.add(kick_button); kick_button.clicked.connect(kick_button_clicked); } if (stream_interactor.get_module(MucManager.IDENTITY).is_moderated_room(conversation.account, conversation.counterpart) && role == Xmpp.Xep.Muc.Role.MODERATOR){ if (stream_interactor.get_module(MucManager.IDENTITY).get_role(selected_jid, conversation.account) == Xmpp.Xep.Muc.Role.VISITOR) { ModelButton voice_button = new ModelButton() { active=true, text=_("Grant write permission"), visible=true }; outer_box.add(voice_button); voice_button.clicked.connect(() => voice_button_clicked("participant")); } else if (stream_interactor.get_module(MucManager.IDENTITY).get_role(selected_jid, conversation.account) == Xmpp.Xep.Muc.Role.PARTICIPANT){ ModelButton voice_button = new ModelButton() { active=true, text=_("Revoke write permission"), visible=true }; outer_box.add(voice_button); voice_button.clicked.connect(() => voice_button_clicked("visitor")); } } if (jid_menu != null) stack.remove(jid_menu); stack.add_named(outer_box, "menu"); stack.visible_child_name = "menu"; jid_menu = outer_box; } private void private_conversation_button_clicked() { if (selected_jid == null) return; Conversation conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(selected_jid, conversation.account, Conversation.Type.GROUPCHAT_PM); stream_interactor.get_module(ConversationManager.IDENTITY).start_conversation(conversation); Application app = GLib.Application.get_default() as Application; app.controller.select_conversation(conversation); } private void kick_button_clicked() { if (selected_jid == null) return; stream_interactor.get_module(MucManager.IDENTITY).kick(conversation.account, conversation.counterpart, selected_jid.resourcepart); } private void voice_button_clicked(string role) { if (selected_jid == null) return; stream_interactor.get_module(MucManager.IDENTITY).change_role(conversation.account, conversation.counterpart, selected_jid.resourcepart, role); } private void on_invite_clicked() { hide(); Gee.List acc_list = new ArrayList(Account.equals_func); acc_list.add(conversation.account); SelectContactDialog add_chat_dialog = new SelectContactDialog(stream_interactor, acc_list); add_chat_dialog.set_transient_for((Window) get_toplevel()); add_chat_dialog.title = _("Invite to Conference"); add_chat_dialog.ok_button.label = _("Invite"); add_chat_dialog.selected.connect((account, jid) => { stream_interactor.get_module(MucManager.IDENTITY).invite(conversation.account, conversation.counterpart, jid); }); add_chat_dialog.present(); } } } dino-0.3.0/main/src/ui/settings_dialog.vala0000644000000000000000000000275414202022370017315 0ustar rootrootusing Gtk; namespace Dino.Ui { [GtkTemplate (ui = "/im/dino/Dino/settings_dialog.ui")] class SettingsDialog : Dialog { [GtkChild] private unowned CheckButton typing_checkbutton; [GtkChild] private unowned CheckButton marker_checkbutton; [GtkChild] private unowned CheckButton notification_checkbutton; [GtkChild] private unowned CheckButton emoji_checkbutton; [GtkChild] private unowned CheckButton check_spelling_checkbutton; Dino.Entities.Settings settings = Dino.Application.get_default().settings; public SettingsDialog() { Object(use_header_bar : Util.use_csd() ? 1 : 0); typing_checkbutton.active = settings.send_typing; marker_checkbutton.active = settings.send_marker; notification_checkbutton.active = settings.notifications; emoji_checkbutton.active = settings.convert_utf8_smileys; check_spelling_checkbutton.active = settings.check_spelling; typing_checkbutton.toggled.connect(() => { settings.send_typing = typing_checkbutton.active; } ); marker_checkbutton.toggled.connect(() => { settings.send_marker = marker_checkbutton.active; } ); notification_checkbutton.toggled.connect(() => { settings.notifications = notification_checkbutton.active; } ); emoji_checkbutton.toggled.connect(() => { settings.convert_utf8_smileys = emoji_checkbutton.active; }); check_spelling_checkbutton.toggled.connect(() => { settings.check_spelling = check_spelling_checkbutton.active; }); } } } dino-0.3.0/main/src/ui/util/0000755000000000000000000000000014202022370014236 5ustar rootrootdino-0.3.0/main/src/ui/util/accounts_combo_box.vala0000644000000000000000000000317714202022370020761 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; namespace Dino.Ui { class AccountComboBox : ComboBox { public Account? selected { get { TreeIter selected; if (get_active_iter(out selected)) { Value value; list_store.get_value(selected, 1, out value); return value as Account; } return null; } set { TreeIter iter; if (list_store.get_iter_first(out iter)) { int i = 0; do { Value val; list_store.get_value(iter, 1, out val); Account? account = val as Account; if (account != null && account.equals(value)) { active = i; break; } i++; } while (list_store.iter_next(ref iter)); } } } private StreamInteractor? stream_interactor; private Gtk.ListStore list_store = new Gtk.ListStore(2, typeof(string), typeof(Account)); public void initialize(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; CellRendererText renderer = new Gtk.CellRendererText(); pack_start(renderer, true); add_attribute(renderer, "text", 0); TreeIter iter; foreach (Account account in stream_interactor.get_accounts()) { list_store.append(out iter); list_store.set(iter, 0, account.bare_jid.to_string(), 1, account); } set_model(list_store); active = 0; } } } dino-0.3.0/main/src/ui/util/config.vala0000644000000000000000000000414314202022370016352 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; namespace Dino.Ui { public class Config : Object { public Database db { get; private set; } public Config(Database db) { this.db = db; window_maximize = col_to_bool_or_default("window_maximized", false); window_width = col_to_int_or_default("window_width", 1200); window_height = col_to_int_or_default("window_height", 700); } private bool window_maximize_; public bool window_maximize { get { return window_maximize_; } set { if (value == window_maximize_) return; db.settings.upsert() .value(db.settings.key, "window_maximized", true) .value(db.settings.value, value.to_string()) .perform(); window_maximize_ = value; } } public int window_height_; public int window_height { get { return window_height_; } set { if (value == window_height_) return; db.settings.upsert() .value(db.settings.key, "window_height", true) .value(db.settings.value, value.to_string()) .perform(); window_height_ = value; } } public int window_width_; public int window_width { get { return window_width_; } set { if (value == window_width_) return; db.settings.upsert() .value(db.settings.key, "window_width", true) .value(db.settings.value, value.to_string()) .perform(); window_width_ = value; } } private bool col_to_bool_or_default(string key, bool def) { string? val = db.settings.select({db.settings.value}).with(db.settings.key, "=", key)[db.settings.value]; return val != null ? bool.parse(val) : def; } private int col_to_int_or_default(string key, int def) { string? val = db.settings.select({db.settings.value}).with(db.settings.key, "=", key)[db.settings.value]; return val != null ? int.parse(val) : def; } } } dino-0.3.0/main/src/ui/util/data_forms.vala0000644000000000000000000000473414202022370017232 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; using Xmpp.Xep; namespace Dino.Ui.Util { public static Widget? get_data_form_field_widget(DataForms.DataForm.Field field) { if (field.type_ == null) return null; switch (field.type_) { case DataForms.DataForm.Type.BOOLEAN: DataForms.DataForm.BooleanField boolean_field = field as DataForms.DataForm.BooleanField; Switch sw = new Switch() { active=boolean_field.value, halign=Align.START, valign=Align.CENTER, visible=true }; sw.state_set.connect((state) => { boolean_field.value = state; return false; }); return sw; case DataForms.DataForm.Type.JID_MULTI: return null; case DataForms.DataForm.Type.LIST_SINGLE: DataForms.DataForm.ListSingleField list_single_field = field as DataForms.DataForm.ListSingleField; ComboBoxText combobox = new ComboBoxText() { valign=Align.CENTER, visible=true }; for (int i = 0; i < list_single_field.options.size; i++) { DataForms.DataForm.Option option = list_single_field.options[i]; combobox.append(option.value, option.label); if (option.value == list_single_field.value) combobox.active = i; } combobox.changed.connect(() => { list_single_field.value = combobox.get_active_id(); }); return combobox; case DataForms.DataForm.Type.LIST_MULTI: return null; case DataForms.DataForm.Type.TEXT_PRIVATE: DataForms.DataForm.TextPrivateField text_private_field = field as DataForms.DataForm.TextPrivateField; Entry entry = new Entry() { text=text_private_field.value ?? "", valign=Align.CENTER, visible=true, visibility=false }; entry.key_release_event.connect(() => { text_private_field.value = entry.text; return false; }); return entry; case DataForms.DataForm.Type.TEXT_SINGLE: DataForms.DataForm.TextSingleField text_single_field = field as DataForms.DataForm.TextSingleField; Entry entry = new Entry() { text=text_single_field.value ?? "", valign=Align.CENTER, visible=true }; entry.key_release_event.connect(() => { text_single_field.value = entry.text; return false; }); return entry; default: return null; } } } dino-0.3.0/main/src/ui/util/helper.vala0000644000000000000000000005213414202022370016367 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; using Xmpp; namespace Dino.Ui.Util { private static Regex URL_REGEX; private static Regex CODE_BLOCK_REGEX; private static Map MATCHING_CHARS; private const unichar[] NON_TRAILING_CHARS = {'\'', '"', ',', '.', ';', '!', '?', '»', '”', '’', '`', '~', '‽', ':', '>', '*', '_'}; private const string[] ALLOWED_SCHEMAS = {"http", "https", "ftp", "ftps", "irc", "ircs", "xmpp", "mailto", "sms", "smsto", "mms", "tel", "geo", "openpgp4fpr", "im", "news", "nntp", "sip", "ssh", "bitcoin", "sftp", "magnet", "vnc"}; private const string[] tango_colors_light = {"FCE94F", "FCAF3E", "E9B96E", "8AE234", "729FCF", "AD7FA8", "EF2929"}; private const string[] tango_colors_medium = {"EDD400", "F57900", "C17D11", "73D216", "3465A4", "75507B", "CC0000"}; private const string[] material_colors_800 = {"D32F2F", "C2185B", "7B1FA2", "512DA8", "303F9F", "1976D2", "0288D1", "0097A7", "00796B", "388E3C", "689F38", "AFB42B", "FFA000", "F57C00", "E64A19", "5D4037"}; private const string[] material_colors_500 = {"F44336", "E91E63", "9C27B0", "673AB7", "3f51B5", "2196F3", "03A9f4", "00BCD4", "009688", "4CAF50", "8BC34a", "CDDC39", "FFC107", "FF9800", "FF5722", "795548"}; private const string[] material_colors_300 = {"E57373", "F06292", "BA68C8", "9575CD", "7986CB", "64B5F6", "4FC3F7", "4DD0E1", "4DB6AC", "81C784", "AED581", "DCE775", "FFD54F", "FFB74D", "FF8A65", "A1887F"}; private const string[] material_colors_200 = {"EF9A9A", "F48FB1", "CE93D8", "B39DDB", "9FA8DA", "90CAF9", "81D4FA", "80DEEA", "80CBC4", "A5D6A7", "C5E1A5", "E6EE9C", "FFE082", "FFCC80", "FFAB91", "BCAAA4"}; public static string get_avatar_hex_color(StreamInteractor stream_interactor, Account account, Jid jid, Conversation? conversation = null) { uint hash = get_relevant_jid(stream_interactor, account, jid, conversation).to_string().hash(); return material_colors_300[hash % material_colors_300.length]; // return tango_colors_light[name.hash() % tango_colors_light.length]; } public static string get_name_hex_color(StreamInteractor stream_interactor, Account account, Jid jid, bool dark_theme = false, Conversation? conversation = null) { uint hash = get_relevant_jid(stream_interactor, account, jid, conversation).to_string().hash(); if (dark_theme) { return material_colors_300[hash % material_colors_300.length]; } else { return material_colors_500[hash % material_colors_500.length]; } // return tango_colors_medium[name.hash() % tango_colors_medium.length]; } private static Jid get_relevant_jid(StreamInteractor stream_interactor, Account account, Jid jid, Conversation? conversation = null) { Conversation conversation_ = conversation ?? stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(jid.bare_jid, account); if (conversation_ != null && conversation_.type_ == Conversation.Type.GROUPCHAT) { Jid? real_jid = stream_interactor.get_module(MucManager.IDENTITY).get_real_jid(jid, account); if (real_jid != null) { return real_jid.bare_jid; } } else { return jid.bare_jid; } return jid; } public static string color_for_show(string show) { switch(show) { case "online": return "#9CCC65"; case "away": return "#FFCA28"; case "chat": return "#66BB6A"; case "xa": return "#EF5350"; case "dnd": return "#EF5350"; default: return "#BDBDBD"; } } public static async AvatarDrawer get_conversation_avatar_drawer(StreamInteractor stream_interactor, Conversation conversation) { return yield get_conversation_participants_avatar_drawer(stream_interactor, conversation, new Jid[0]); } public static async AvatarDrawer get_conversation_participants_avatar_drawer(StreamInteractor stream_interactor, Conversation conversation, owned Jid[] jids) { AvatarManager avatar_manager = stream_interactor.get_module(AvatarManager.IDENTITY); MucManager muc_manager = stream_interactor.get_module(MucManager.IDENTITY); if (conversation.type_ != Conversation.Type.GROUPCHAT) { Jid jid = jids.length == 1 ? jids[0] : conversation.counterpart; Jid avatar_jid = jid; if (conversation.type_ == Conversation.Type.GROUPCHAT_PM) avatar_jid = muc_manager.get_real_jid(avatar_jid, conversation.account) ?? avatar_jid; return new AvatarDrawer().tile(yield avatar_manager.get_avatar(conversation.account, avatar_jid), jids.length == 1 ? get_participant_display_name(stream_interactor, conversation, jid) : get_conversation_display_name(stream_interactor, conversation), Util.get_avatar_hex_color(stream_interactor, conversation.account, jid, conversation)); } if (jids.length > 0) { AvatarDrawer drawer = new AvatarDrawer(); for (int i = 0; i < (jids.length <= 4 ? jids.length : 3); i++) { Jid avatar_jid = jids[i]; Gdk.Pixbuf? part_avatar = yield avatar_manager.get_avatar(conversation.account, avatar_jid); if (part_avatar == null && avatar_jid.equals_bare(conversation.counterpart) && muc_manager.is_private_room(conversation.account, conversation.counterpart)) { avatar_jid = muc_manager.get_real_jid(avatar_jid, conversation.account) ?? avatar_jid; part_avatar = yield avatar_manager.get_avatar(conversation.account, avatar_jid); } drawer.tile(part_avatar, get_participant_display_name(stream_interactor, conversation, jids[i]), Util.get_avatar_hex_color(stream_interactor, conversation.account, jids[i], conversation)); } if (jids.length > 4) { drawer.plus(); } return drawer; } Gdk.Pixbuf? room_avatar = yield avatar_manager.get_avatar(conversation.account, conversation.counterpart); Gee.List? occupants = muc_manager.get_other_offline_members(conversation.counterpart, conversation.account); if (room_avatar != null || !muc_manager.is_private_room(conversation.account, conversation.counterpart) || occupants == null || occupants.size == 0) { return new AvatarDrawer().tile(room_avatar, "#", Util.get_avatar_hex_color(stream_interactor, conversation.account, conversation.counterpart, conversation)); } AvatarDrawer drawer = new AvatarDrawer(); for (int i = 0; i < (occupants.size <= 4 ? occupants.size : 3); i++) { Jid jid = occupants[i]; Jid avatar_jid = jid; Gdk.Pixbuf? part_avatar = yield avatar_manager.get_avatar(conversation.account, avatar_jid); if (part_avatar == null && avatar_jid.equals_bare(conversation.counterpart) && muc_manager.is_private_room(conversation.account, conversation.counterpart)) { avatar_jid = muc_manager.get_real_jid(avatar_jid, conversation.account) ?? avatar_jid; part_avatar = yield avatar_manager.get_avatar(conversation.account, avatar_jid); } drawer.tile(part_avatar, get_participant_display_name(stream_interactor, conversation, jid), Util.get_avatar_hex_color(stream_interactor, conversation.account, jid, conversation)); } if (occupants.size > 4) { drawer.plus(); } return drawer; } public static string get_conversation_display_name(StreamInteractor stream_interactor, Conversation conversation) { return Dino.get_conversation_display_name(stream_interactor, conversation, _("%s from %s")); } public static string get_participant_display_name(StreamInteractor stream_interactor, Conversation conversation, Jid participant, bool me_is_me = false) { return Dino.get_participant_display_name(stream_interactor, conversation, participant, me_is_me ? _("Me") : null); } public static string? get_real_display_name(StreamInteractor stream_interactor, Account account, Jid jid, bool me_is_me = false) { return Dino.get_real_display_name(stream_interactor, account, jid, me_is_me ? _("Me") : null); } public static string get_groupchat_display_name(StreamInteractor stream_interactor, Account account, Jid jid) { return Dino.get_groupchat_display_name(stream_interactor, account, jid); } public static string get_occupant_display_name(StreamInteractor stream_interactor, Conversation conversation, Jid jid, bool me_is_me = false, bool muc_real_name = false) { return Dino.get_occupant_display_name(stream_interactor, conversation, jid, me_is_me ? _("Me") : null); } public static void image_set_from_scaled_pixbuf(Image image, Gdk.Pixbuf pixbuf, int scale = 0, int width = 0, int height = 0) { if (scale == 0) scale = image.scale_factor; Cairo.Surface surface = Gdk.cairo_surface_create_from_pixbuf(pixbuf, scale, image.get_window()); if (height == 0 && width != 0) { height = (int) ((double) width / pixbuf.width * pixbuf.height); } else if (height != 0 && width == 0) { width = (int) ((double) height / pixbuf.height * pixbuf.width); } if (width != 0) { Cairo.Surface surface_new = new Cairo.Surface.similar_image(surface, Cairo.Format.ARGB32, width, height); Cairo.Context context = new Cairo.Context(surface_new); context.scale((double) width * scale / pixbuf.width, (double) height * scale / pixbuf.height); context.set_source_surface(surface, 0, 0); context.get_source().set_filter(Cairo.Filter.BEST); context.paint(); surface = surface_new; } image.set_from_surface(surface); } public static Gdk.RGBA get_label_pango_color(Label label, string css_color) { Gtk.CssProvider provider = force_color(label, css_color); Gdk.RGBA color_rgba = label.get_style_context().get_color(StateFlags.NORMAL); label.get_style_context().remove_provider(provider); return color_rgba; } public static string rgba_to_hex(Gdk.RGBA rgba) { return "#%02x%02x%02x%02x".printf( (uint)(Math.round(rgba.red*255)), (uint)(Math.round(rgba.green*255)), (uint)(Math.round(rgba.blue*255)), (uint)(Math.round(rgba.alpha*255))) .up(); } private const string force_background_css = "%s { background-color: %s; }"; private const string force_color_css = "%s { color: %s; }"; public static Gtk.CssProvider force_css(Gtk.Widget widget, string css) { var p = new Gtk.CssProvider(); try { p.load_from_data(css); widget.get_style_context().add_provider(p, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); } catch (GLib.Error err) { // handle err } return p; } public static void force_background(Gtk.Widget widget, string color, string selector = "*") { force_css(widget, force_background_css.printf(selector, color)); } public static Gtk.CssProvider force_color(Gtk.Widget widget, string color, string selector = "*") { return force_css(widget, force_color_css.printf(selector, color)); } public static void force_error_color(Gtk.Widget widget, string selector = "*") { force_color(widget, "@error_color", selector); } public static bool is_dark_theme(Gtk.Widget widget) { Gdk.RGBA bg = widget.get_style_context().get_color(StateFlags.NORMAL); return (bg.red > 0.5 && bg.green > 0.5 && bg.blue > 0.5); } private static uint8 is24h = 0; public static bool is_24h_format() { if (is24h == 0) { string p_format = " "; // Leaving room to be filled by strftime Time.local(0).strftime((char[]) p_format.data, "%p"); is24h = p_format.strip() == "" ? 1 : -1; } return is24h == 1; } public static string format_time(DateTime datetime, string format_24h, string format_12h) { string format = Util.is_24h_format() ? format_24h : format_12h; if (!get_charset(null)) { // No UTF-8 support, use simple colon for time instead format = format.replace("∶", ":"); } return datetime.format(format); } public static Regex get_url_regex() { if (URL_REGEX == null) { URL_REGEX = /\b(((http|ftp)s?:\/\/|(ircs?|xmpp|mailto|sms|smsto|mms|tel|geo|openpgp4fpr|im|news|nntp|sip|ssh|bitcoin|sftp|magnet|vnc|urn):)\S+)/; } return URL_REGEX; } public static Map get_matching_chars() { if (MATCHING_CHARS == null) { MATCHING_CHARS = new HashMap(); MATCHING_CHARS[")".get_char(0)] = "(".get_char(0); MATCHING_CHARS["]".get_char(0)] = "[".get_char(0); MATCHING_CHARS["}".get_char(0)] = "{".get_char(0); } return MATCHING_CHARS; } public static string parse_add_markup(string s_, string? highlight_word, bool parse_links, bool parse_text_markup) { bool ignore_out_var = false; return parse_add_markup_theme(s_, highlight_word, parse_links, parse_text_markup, parse_text_markup, false, ref ignore_out_var); } public static string parse_add_markup_theme(string s_, string? highlight_word, bool parse_links, bool parse_text_markup, bool parse_quotes, bool dark_theme, ref bool theme_dependent, bool already_escaped_ = false) { string s = s_; bool already_escaped = already_escaped_; if (parse_quotes) { string gt = already_escaped ? ">" : ">"; Regex quote_regex = new Regex("((?<=\n)" + gt + ".*(\n|$))|(^" + gt + ".*(\n|$))"); MatchInfo quote_match_info; quote_regex.match(s.down(), 0, out quote_match_info); if (quote_match_info.matches()) { int start, end; string dim_color = dark_theme ? "#BDBDBD": "#707070"; theme_dependent = true; quote_match_info.fetch_pos(0, out start, out end); return parse_add_markup_theme(s[0:start], highlight_word, parse_links, parse_text_markup, parse_quotes, dark_theme, ref theme_dependent, already_escaped) + @"$gt " + parse_add_markup_theme(s[start + gt.length + 1:end], highlight_word, parse_links, parse_text_markup, false, dark_theme, ref theme_dependent, already_escaped) + "" + parse_add_markup_theme(s[end:s.length], highlight_word, parse_links, parse_text_markup, parse_quotes, dark_theme, ref theme_dependent, already_escaped); } } if (parse_links && !already_escaped) { MatchInfo match_info; get_url_regex().match(s.down(), 0, out match_info); while (match_info.matches()) { int start, end; match_info.fetch_pos(0, out start, out end); string link = s[start:end]; if (GLib.Uri.parse_scheme(link) in ALLOWED_SCHEMAS) { Map matching_chars = get_matching_chars(); unichar close_char; int last_char_index = link.length; while (link.get_prev_char(ref last_char_index, out close_char)) { if (matching_chars.has_key(close_char)) { unichar open_char = matching_chars[close_char]; unichar char; int index = 0; int open = 0, close = 0; while (link.get_next_char(ref index, out char)) { if (char == open_char) { open++; } else if (char == close_char) { close++; } } if (close > open) { // Remove last char from url end -= close_char.to_string().length; link = s[start:end]; } else { break; } } else if (close_char in NON_TRAILING_CHARS) { // Remove last char from url end -= close_char.to_string().length; link = s[start:end]; } else { break; } } return parse_add_markup_theme(s[0:start], highlight_word, parse_links, parse_text_markup, false, dark_theme, ref theme_dependent, already_escaped) + "" + parse_add_markup_theme(link, highlight_word, false, false, false, dark_theme, ref theme_dependent, already_escaped) + "" + parse_add_markup_theme(s[end:s.length], highlight_word, parse_links, parse_text_markup, false, dark_theme, ref theme_dependent, already_escaped); } match_info.next(); } } if (!already_escaped) { s = Markup.escape_text(s); already_escaped = true; } if (highlight_word != null) { try { Regex highlight_regex = new Regex("\\b" + Regex.escape_string(highlight_word.down()) + "\\b"); MatchInfo match_info; highlight_regex.match(s.down(), 0, out match_info); if (match_info.matches()) { int start, end; match_info.fetch_pos(0, out start, out end); return parse_add_markup_theme(s[0:start], highlight_word, parse_links, parse_text_markup, false, dark_theme, ref theme_dependent, already_escaped) + "" + s[start:end] + "" + parse_add_markup_theme(s[end:s.length], highlight_word, parse_links, parse_text_markup, false, dark_theme, ref theme_dependent, already_escaped); } } catch (RegexError e) { assert_not_reached(); } } if (parse_text_markup) { string[] markup_string = new string[]{"`", "_", "*", "~"}; string[] convenience_tag = new string[]{"tt", "i", "b", "s"}; for (int i = 0; i < markup_string.length; i++) { string markup_esc = Regex.escape_string(markup_string[i]); try { Regex regex = new Regex("(^|\\s)" + markup_esc + "(\\S|\\S.*?\\S)" + markup_esc); MatchInfo match_info; regex.match(s.down(), 0, out match_info); if (match_info.matches()) { int start, end; match_info.fetch_pos(2, out start, out end); return parse_add_markup_theme(s[0:start-1], highlight_word, parse_links, parse_text_markup, false, dark_theme, ref theme_dependent, already_escaped) + "" + s[start-1:start] + "" + @"<$(convenience_tag[i])>" + s[start:end] + @"" + "" + s[end:end+1] + "" + parse_add_markup_theme(s[end+1:s.length], highlight_word, parse_links, parse_text_markup, false, dark_theme, ref theme_dependent, already_escaped); } } catch (RegexError e) { assert_not_reached(); } } } return s; } /** * This is a heuristic to count emojis in a string {@link http://example.com/} * * @param markup_text string to search in * @return number of emojis, or -1 if text includes non-emojis. */ public int get_only_emoji_count(string markup_text) { int emoji_no = 0; int index_ref = 0; unichar curchar = 0, altchar = 0; bool last_was_emoji = false, last_was_modifier_base = false, last_was_keycap = false; while (markup_text.get_next_char(ref index_ref, out curchar)) { if (last_was_emoji && last_was_keycap && curchar == 0x20E3) { // keycap sequence continue; } last_was_keycap = false; if (last_was_emoji && curchar == 0x200D && markup_text.get_next_char(ref index_ref, out curchar)) { // zero width joiner last_was_emoji = false; emoji_no--; } if (last_was_emoji && curchar == 0xFE0F) { // Variation selector after emoji is useless, ignoring. } else if (last_was_emoji && last_was_modifier_base && ICU.has_binary_property(curchar, ICU.Property.EMOJI_MODIFIER)) { // still an emoji, but no longer a modifier base last_was_modifier_base = false; } else if (ICU.has_binary_property(curchar, ICU.Property.EMOJI_PRESENTATION)) { if (ICU.has_binary_property(curchar, ICU.Property.EMOJI_MODIFIER_BASE)) { last_was_modifier_base = true; } emoji_no++; last_was_emoji = true; } else if (curchar == ' ') { last_was_emoji = false; } else if (markup_text.get_next_char(ref index_ref, out altchar) && altchar == 0xFE0F) { // U+FE0F = VARIATION SELECTOR-16 emoji_no++; last_was_emoji = true; last_was_keycap = (curchar >= 0x30 && curchar <= 0x39) || curchar == 0x23 || curchar == 0x2A; } else { return -1; } } return emoji_no; } public string summarize_whitespaces_to_space(string s) { try { return (/\s+/).replace_literal(s, -1, 0, " "); } catch (RegexError e) { assert_not_reached(); } } public void present_window(Window window) { #if GDK3_WITH_X11 Gdk.X11.Window x11window = window.get_window() as Gdk.X11.Window; if (x11window != null) { window.present_with_time(Gdk.X11.get_server_time(x11window)); } else { window.present(); } #else window.present(); #endif } public bool use_csd() { return ((Application) GLib.Application.get_default()).use_csd(); } } dino-0.3.0/main/src/ui/util/label_hybrid.vala0000644000000000000000000001054014202022370017523 0ustar rootrootusing Gee; using Gtk; namespace Dino.Ui.Util { public class LabelHybrid : Stack { public Label label = new Label("") { visible=true, max_width_chars=1, ellipsize=Pango.EllipsizeMode.END }; protected Button button = new Button() { relief=ReliefStyle.NONE, visible=true }; internal virtual void init(Widget widget) { button.add(label); add_named(button, "label"); add_named(widget, "widget"); button.clicked.connect(() => { show_widget(); }); } public void show_widget() { visible_child_name = "widget"; get_child_by_name("widget").grab_focus(); } public void show_label() { visible_child_name = "label"; } } public class EntryLabelHybrid : LabelHybrid { public string text { get { return entry.text; } set { entry.text = value; set_label_label(value); } } public bool visibility { get { return entry.visibility; } set { entry.visibility = value; } } public float xalign { get { return label.xalign; } set { label.xalign = value; entry.set_alignment(value); } } private Entry? entry_; public Entry entry { get { if (entry_ == null) { entry_ = new Entry() { visible=true }; init(entry_); } return entry_; } set { entry_ = value; } } public EntryLabelHybrid.wrap(Entry e) { init(e); } internal override void init(Widget widget) { Entry? e = widget as Entry; if (e == null) return; entry = e; base.init(entry); update_label(); entry.key_release_event.connect((event) => { if (event.keyval == Gdk.Key.Return) { show_label(); } else { set_label_label(entry.text); } return false; }); entry.focus_out_event.connect(() => { show_label(); return false; }); } private void set_label_label(string value) { if (visibility) { label.label = value; } else { string filler = ""; for (int i = 0; i < value.length; i++) filler += entry.get_invisible_char().to_string(); label.label = filler; } } private void update_label() { text = text; } } public class ComboBoxTextLabelHybrid : LabelHybrid { public int active { get { return combobox.active; } set { combobox.active = value; } } public float xalign { get { return label.xalign; } set { label.xalign = value; } } private ComboBoxText combobox_; public ComboBoxText combobox { get { if (combobox_ == null) { combobox_ = new ComboBoxText() { visible=true }; init(combobox_); } return combobox_; } set { combobox_ = combobox; } } public ComboBoxTextLabelHybrid.wrap(ComboBoxText cb) { combobox_ = cb; init(cb); } public void append(string id, string text) { combobox.append(id, text); } public string get_active_text() { return combobox.get_active_text(); } internal override void init(Widget widget) { ComboBoxText? combobox = widget as ComboBoxText; if (combobox == null) return; base.init(combobox); update_label(); combobox.changed.connect(() => { update_label(); show_label(); }); combobox.focus_out_event.connect(() => { update_label(); show_label(); return false; }); button.clicked.connect(() => { combobox.popup(); }); } private void update_label() { label.label = combobox.get_active_text(); } } public class LabelHybridGroup { private Gee.List hybrids = new ArrayList(); public void add(LabelHybrid hybrid) { hybrids.add(hybrid); hybrid.notify["visible-child-name"].connect(() => { if (hybrid.visible_child_name == "label") return; foreach (LabelHybrid h in hybrids) { if (h != hybrid) { h.set_visible_child_name("label"); } } }); } } } dino-0.3.0/main/src/ui/util/preview_file_chooser_native.vala0000644000000000000000000000451514202022370022660 0ustar rootrootusing Gdk; using Gtk; using Dino.Entities; namespace Dino.Ui { public class PreviewFileChooserNative : Object { private const int PREVIEW_SIZE = 180; private const int PREVIEW_PADDING = 5; private Gtk.FileChooserNative? chooser = null; private Image preview_image = new Image(); public PreviewFileChooserNative(string? title, Gtk.Window? parent, FileChooserAction action, string? accept_label, string? cancel_label) { chooser = new FileChooserNative(title, parent, action, accept_label, cancel_label); chooser.set_preview_widget(this.preview_image); chooser.use_preview_label = false; chooser.preview_widget_active = false; chooser.update_preview.connect(on_update_preview); } public void add_filter(owned Gtk.FileFilter filter) { chooser.add_filter(filter); } public SList get_files() { return chooser.get_files(); } public int run() { return chooser.run(); } public string? get_filename() { return chooser.get_filename(); } private void on_update_preview() { Pixbuf preview_pixbuf = get_preview_pixbuf(); if (preview_pixbuf != null) { int extra_space = PREVIEW_SIZE - preview_pixbuf.width; int smaller_half = extra_space/2; int larger_half = extra_space - smaller_half; preview_image.set_margin_start(PREVIEW_PADDING + smaller_half); preview_image.set_margin_end(PREVIEW_PADDING + larger_half); preview_image.set_from_pixbuf(preview_pixbuf); chooser.preview_widget_active = true; } else { chooser.preview_widget_active = false; } } private Pixbuf? get_preview_pixbuf() { string? filename = chooser.get_preview_filename(); if (filename == null) { return null; } int width = 0; int height = 0; Gdk.PixbufFormat? format = Gdk.Pixbuf.get_file_info(filename, out width, out height); if (format == null) { return null; } try { Gdk.Pixbuf pixbuf = new Gdk.Pixbuf.from_file_at_scale(filename, PREVIEW_SIZE, PREVIEW_SIZE, true); pixbuf = pixbuf.apply_embedded_orientation(); return pixbuf; } catch (Error e) { return null; } } } } dino-0.3.0/main/src/ui/util/scaling_image.vala0000644000000000000000000001651014202022370017670 0ustar rootrootusing Gdk; using Gtk; namespace Dino.Ui { class ScalingImage : Misc { public int min_width { get; set; default = -1; } public int target_width { get; set; default = -1; } public int max_width { get; set; default = -1; } public int min_height { get; set; default = -1; } public int max_height { get; set; default = -1; } private Pixbuf image; private double image_ratio; private int image_height = 0; private int image_width = 0; private int last_allocation_height = -1; private int last_allocation_width = -1; private int last_scale_factor = -1; private Cairo.ImageSurface? cached_surface; private static int8 use_image_surface = -1; public void load(Pixbuf image) { this.image = image; this.image_ratio = ((double)image.height) / ((double)image.width); this.image_height = image.height; this.image_width = image.width; queue_resize(); } public override void dispose() { base.dispose(); image = null; cached_surface = null; } private void calculate_size(ref double exact_width, ref double exact_height) { if (exact_width == -1 && exact_height == -1) { if (target_width == -1) { exact_width = ((double)image_width) / ((double)scale_factor); exact_height = ((double)image_height) / ((double)scale_factor); } else { exact_width = target_width; exact_height = exact_width * image_ratio; } } else if (exact_width != -1) { exact_height = exact_width * image_ratio; } else if (exact_height != -1) { exact_width = exact_height / image_ratio; } else { if (exact_width * image_ratio > exact_height + scale_factor) { exact_width = exact_height / image_ratio; } else if (exact_height / image_ratio > exact_width + scale_factor) { exact_height = exact_width * image_ratio; } } if (max_width != -1 && exact_width > max_width) { exact_width = max_width; exact_height = exact_width * image_ratio; } if (max_height != -1 && exact_height > max_height) { exact_height = max_height; exact_width = exact_height / image_ratio; } if (exact_width < min_width) exact_width = min_width; if (exact_height < min_height) exact_height = min_height; } public override void size_allocate(Allocation allocation) { if (max_width != -1) allocation.width = int.min(allocation.width, max_width); if (max_height != -1) allocation.height = int.min(allocation.height, max_height); allocation.height = int.max(allocation.height, min_height); allocation.width = int.max(allocation.width, min_width); double exact_width = allocation.width, exact_height = allocation.height; calculate_size(ref exact_width, ref exact_height); base.size_allocate(allocation); if (last_allocation_height != allocation.height || last_allocation_width != allocation.width || last_scale_factor != scale_factor) { last_allocation_height = allocation.height; last_allocation_width = allocation.width; last_scale_factor = scale_factor; cached_surface = null; } } public override bool draw(Cairo.Context ctx_in) { if (image == null) return false; Cairo.Context ctx = ctx_in; int width = this.get_allocated_width(), height = this.get_allocated_height(), base_factor = 1; if (use_image_surface == -1) { // TODO: detect if we have to buffer in image surface use_image_surface = 1; } if (use_image_surface == 1) { ctx_in.scale(1f / scale_factor, 1f / scale_factor); if (cached_surface != null) { ctx_in.set_source_surface(cached_surface, 0, 0); ctx_in.paint(); ctx_in.set_source_rgb(0, 0, 0); return true; } width *= scale_factor; height *= scale_factor; base_factor *= scale_factor; cached_surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, width, height); ctx = new Cairo.Context(cached_surface); } double radius = 3 * base_factor; double degrees = Math.PI / 180.0; ctx.new_sub_path(); ctx.arc(width - radius, radius, radius, -90 * degrees, 0 * degrees); ctx.arc(width - radius, height - radius, radius, 0 * degrees, 90 * degrees); ctx.arc(radius, height - radius, radius, 90 * degrees, 180 * degrees); ctx.arc(radius, radius, radius, 180 * degrees, 270 * degrees); ctx.close_path(); ctx.clip(); Cairo.Surface buffer = sub_surface(ctx, width, height); ctx.set_source_surface(buffer, 0, 0); ctx.paint(); if (use_image_surface == 1) { ctx_in.set_source_surface(ctx.get_target(), 0, 0); ctx_in.paint(); ctx_in.set_source_rgb(0, 0, 0); } return true; } private Cairo.Surface sub_surface(Cairo.Context ctx, int width, int height) { Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width, height); Cairo.Context bufctx = new Cairo.Context(buffer); double w_scale = (double) width / image_width; double h_scale = (double) height / image_height; double scale = double.max(w_scale, h_scale); bufctx.scale(scale, scale); double x_off = 0, y_off = 0; if (scale == h_scale) { x_off = (width / scale - image_width) / 2.0; } else { y_off = (height / scale - image_height) / 2.0; } Gdk.cairo_set_source_pixbuf(bufctx, image, 0, 0); bufctx.get_source().set_filter(Cairo.Filter.BILINEAR); bufctx.paint(); bufctx.set_source_rgb(0, 0, 0); return buffer; } public override void get_preferred_width(out int minimum_width, out int natural_width) { minimum_width = int.max(0, min_width); double exact_width = -1, exact_height = -1; calculate_size(ref exact_width, ref exact_height); natural_width = (int) Math.ceil(exact_width); } public override void get_preferred_height(out int minimum_height, out int natural_height) { minimum_height = int.max(0, min_height); double exact_width = -1, exact_height = -1; calculate_size(ref exact_width, ref exact_height); natural_height = (int) Math.ceil(exact_height); } public override void get_preferred_height_for_width(int width, out int minimum_height, out int natural_height) { double exact_width = width, exact_height = -1; calculate_size(ref exact_width, ref exact_height); natural_height = (int) Math.ceil(exact_height); minimum_height = natural_height; } public override void get_preferred_width_for_height(int height, out int minimum_width, out int natural_width) { double exact_width = -1, exact_height = height; calculate_size(ref exact_width, ref exact_height); natural_width = (int) Math.ceil(exact_width); minimum_width = natural_width; } public override SizeRequestMode get_request_mode() { return SizeRequestMode.HEIGHT_FOR_WIDTH; } } } dino-0.3.0/main/src/ui/util/size_request_box.vala0000644000000000000000000000100314202022370020467 0ustar rootrootusing Gtk; namespace Dino.Ui { public class SizeRequestBox : Box { public SizeRequestMode size_request_mode { get; set; default = SizeRequestMode.CONSTANT_SIZE; } public override Gtk.SizeRequestMode get_request_mode() { return size_request_mode; } } public class SizeRequestBin : Bin { public SizeRequestMode size_request_mode { get; set; default = SizeRequestMode.CONSTANT_SIZE; } public override Gtk.SizeRequestMode get_request_mode() { return size_request_mode; } } } dino-0.3.0/main/src/ui/util/sizing_bin.vala0000644000000000000000000000324714202022370017244 0ustar rootrootusing Gtk; namespace Dino.Ui { public class SizingBin : Bin { public int min_width { get; set; default = -1; } public int target_width { get; set; default = -1; } public int max_width { get; set; default = -1; } public int min_height { get; set; default = -1; } public int target_height { get; set; default = -1; } public int max_height { get; set; default = -1; } public override void size_allocate(Allocation allocation) { if (max_height != -1) allocation.height = int.min(allocation.height, max_height); if (max_width != -1) allocation.width = int.min(allocation.width, max_width); base.size_allocate(allocation); } public override void get_preferred_width(out int minimum_width, out int natural_width) { base.get_preferred_width(out minimum_width, out natural_width); if (min_width != -1) minimum_width = int.max(minimum_width, min_width); if (max_width != -1) natural_width = int.min(natural_width, max_width); if (target_width != -1) natural_width = int.max(natural_width, target_width); natural_width = int.max(natural_width, minimum_width); } public override void get_preferred_height_for_width(int width, out int minimum_height, out int natural_height) { base.get_preferred_height_for_width(width, out minimum_height, out natural_height); if (min_height != -1) minimum_height = int.max(minimum_height, min_height); if (max_height != -1) natural_height = int.min(natural_height, max_height); if (target_height != -1) natural_height = int.max(natural_height, target_height); natural_height = int.max(natural_height, minimum_height); } } } dino-0.3.0/main/vapi/0000755000000000000000000000000014202022370013014 5ustar rootrootdino-0.3.0/main/vapi/emojichooser.vapi0000644000000000000000000000030714202022370016363 0ustar rootrootnamespace Dino { [CCode (cheader_filename = "emojichooser.h")] class EmojiChooser : Gtk.Popover { public signal void emoji_picked(string text); public EmojiChooser(); } } dino-0.3.0/main/vapi/icu-uc.vapi0000644000000000000000000000050714202022370015064 0ustar rootrootnamespace ICU { [CCode (cprefix = "UCHAR_", cheader_filename = "unicode/uchar.h")] public enum Property { EMOJI, EMOJI_PRESENTATION, EMOJI_MODIFIER, EMOJI_MODIFIER_BASE, } [CCode (cname = "u_hasBinaryProperty", cheader_filename = "unicode/uchar.h")] public bool has_binary_property(unichar c, Property p); } dino-0.3.0/plugins/0000755000000000000000000000000014202022370012612 5ustar rootrootdino-0.3.0/plugins/CMakeLists.txt0000644000000000000000000000035714202022370015357 0ustar rootrootforeach(plugin ${PLUGINS}) if ("omemo" STREQUAL ${plugin}) add_subdirectory(signal-protocol) endif () if ("openpgp" STREQUAL ${plugin}) add_subdirectory(gpgme-vala) endif () add_subdirectory(${plugin}) endforeach(plugin) dino-0.3.0/plugins/gpgme-vala/0000755000000000000000000000000014202022370014632 5ustar rootrootdino-0.3.0/plugins/gpgme-vala/CMakeLists.txt0000644000000000000000000000274014202022370017375 0ustar rootrootfind_package(GPGME REQUIRED) find_packages(GPGME_VALA_PACKAGES REQUIRED Gee GLib GObject ) vala_precompile(GPGME_VALA_C SOURCES "src/gpgme_helper.vala" CUSTOM_VAPIS "${CMAKE_CURRENT_SOURCE_DIR}/vapi/gpgme.vapi" "${CMAKE_CURRENT_SOURCE_DIR}/vapi/gpgme_public.vapi" "${CMAKE_CURRENT_SOURCE_DIR}/vapi/gpg-error.vapi" PACKAGES ${GPGME_VALA_PACKAGES} GENERATE_VAPI gpgme-vala GENERATE_HEADER gpgme-vala ) add_custom_command(OUTPUT "${CMAKE_BINARY_DIR}/exports/gpgme_fix.h" COMMAND cp "${CMAKE_CURRENT_SOURCE_DIR}/src/gpgme_fix.h" "${CMAKE_BINARY_DIR}/exports/gpgme_fix.h" DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/gpgme_fix.h" COMMENT Copy header file gpgme_fix.h ) add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/exports/gpgme.vapi COMMAND cat "${CMAKE_BINARY_DIR}/exports/gpgme-vala.vapi" "${CMAKE_CURRENT_SOURCE_DIR}/vapi/gpgme_public.vapi" > "${CMAKE_BINARY_DIR}/exports/gpgme.vapi" DEPENDS ${CMAKE_BINARY_DIR}/exports/gpgme-vala.vapi ${CMAKE_CURRENT_SOURCE_DIR}/vapi/gpgme_public.vapi ) add_custom_target(gpgme-vapi DEPENDS ${CMAKE_BINARY_DIR}/exports/gpgme_fix.h ${CMAKE_BINARY_DIR}/exports/gpgme.vapi ) set(CFLAGS ${VALA_CFLAGS} -I${CMAKE_CURRENT_SOURCE_DIR}/src) add_definitions(${CFLAGS}) add_library(gpgme-vala STATIC ${GPGME_VALA_C} src/gpgme_fix.c) add_dependencies(gpgme-vala gpgme-vapi) target_link_libraries(gpgme-vala ${GPGME_VALA_PACKAGES} gpgme) set_property(TARGET gpgme-vala PROPERTY POSITION_INDEPENDENT_CODE ON) dino-0.3.0/plugins/gpgme-vala/src/0000755000000000000000000000000014202022370015421 5ustar rootrootdino-0.3.0/plugins/gpgme-vala/src/gpgme_fix.c0000644000000000000000000000040114202022370017525 0ustar rootroot#include static GRecMutex gpgme_global_mutex = {0}; gpgme_key_t gpgme_key_ref_vapi (gpgme_key_t key) { gpgme_key_ref(key); return key; } gpgme_key_t gpgme_key_unref_vapi (gpgme_key_t key) { gpgme_key_unref(key); return key; }dino-0.3.0/plugins/gpgme-vala/src/gpgme_fix.h0000644000000000000000000000034014202022370017534 0ustar rootroot#ifndef GPGME_FIX #define GPGME_FIX 1 #include #include static GRecMutex gpgme_global_mutex; gpgme_key_t gpgme_key_ref_vapi (gpgme_key_t key); gpgme_key_t gpgme_key_unref_vapi (gpgme_key_t key); #endifdino-0.3.0/plugins/gpgme-vala/src/gpgme_helper.vala0000644000000000000000000001230514202022370020725 0ustar rootrootusing Gee; using GPG; namespace GPGHelper { private static bool initialized = false; public static string encrypt_armor(string plain, Key[] keys, EncryptFlags flags) throws GLib.Error { global_mutex.lock(); try { initialize(); Data plain_data = Data.create_from_memory(plain.data, false); Context context = Context.create(); context.set_armor(true); Data enc_data = context.op_encrypt(keys, flags, plain_data); return get_string_from_data(enc_data); } finally { global_mutex.unlock(); } } public static uint8[] encrypt_file(string uri, Key[] keys, EncryptFlags flags, string file_name) throws GLib.Error { global_mutex.lock(); try { initialize(); Data plain_data = Data.create_from_file(uri); plain_data.set_file_name(file_name); Context context = Context.create(); context.set_armor(true); Data enc_data = context.op_encrypt(keys, flags, plain_data); return get_uint8_from_data(enc_data); } finally { global_mutex.unlock(); } } public static string decrypt(string encr) throws GLib.Error { global_mutex.lock(); try { initialize(); Data enc_data = Data.create_from_memory(encr.data, false); Context context = Context.create(); Data dec_data = context.op_decrypt(enc_data); return get_string_from_data(dec_data); } finally { global_mutex.unlock(); } } public class DecryptedData { public uint8[] data { get; set; } public string filename { get; set; } } public static DecryptedData decrypt_data(uint8[] data) throws GLib.Error { global_mutex.lock(); try { initialize(); Data enc_data = Data.create_from_memory(data, false); Context context = Context.create(); Data dec_data = context.op_decrypt(enc_data); DecryptResult* dec_res = context.op_decrypt_result(); return new DecryptedData() { data=get_uint8_from_data(dec_data), filename=dec_res->file_name}; } finally { global_mutex.unlock(); } } public static string sign(string plain, SigMode mode, Key? key = null) throws GLib.Error { global_mutex.lock(); try { initialize(); Data plain_data = Data.create_from_memory(plain.data, false); Context context = Context.create(); if (key != null) context.signers_add(key); Data signed_data = context.op_sign(plain_data, mode); return get_string_from_data(signed_data); } finally { global_mutex.unlock(); } } public static string? get_sign_key(string signature, string? text) throws GLib.Error { global_mutex.lock(); try { initialize(); Data sig_data = Data.create_from_memory(signature.data, false); Data text_data; if (text != null) { text_data = Data.create_from_memory(text.data, false); } else { text_data = Data.create(); } Context context = Context.create(); context.op_verify(sig_data, text_data); VerifyResult* verify_res = context.op_verify_result(); if (verify_res == null || verify_res.signatures == null) return null; return verify_res.signatures.fpr; } finally { global_mutex.unlock(); } } public static Gee.List get_keylist(string? pattern = null, bool secret_only = false) throws GLib.Error { global_mutex.lock(); try { initialize(); Gee.List keys = new ArrayList(); Context context = Context.create(); context.op_keylist_start(pattern, secret_only ? 1 : 0); try { while (true) { Key key = context.op_keylist_next(); keys.add(key); } } catch (Error e) { if (e.code != GPGError.ErrorCode.EOF) throw e; } return keys; } finally { global_mutex.unlock(); } } public static Key? get_public_key(string sig) throws GLib.Error { return get_key(sig, false); } public static Key? get_private_key(string sig) throws GLib.Error { return get_key(sig, true); } private static Key? get_key(string sig, bool priv) throws GLib.Error { global_mutex.lock(); try { initialize(); Context context = Context.create(); Key key = context.get_key(sig, priv); return key; } finally { global_mutex.unlock(); } } private static string get_string_from_data(Data data) { const size_t BUF_SIZE = 256; data.seek(0); uint8[] buf = new uint8[BUF_SIZE + 1]; ssize_t len = 0; string res = ""; do { len = data.read(buf, BUF_SIZE); if (len > 0) { buf[len] = 0; res += (string) buf; } } while (len > 0); return res; } private static uint8[] get_uint8_from_data(Data data) { const size_t BUF_SIZE = 256; data.seek(0); uint8[] buf = new uint8[BUF_SIZE + 1]; ssize_t len = 0; ByteArray res = new ByteArray(); do { len = data.read(buf, BUF_SIZE); if (len > 0) { res.append(buf[0:len]); } } while (len > 0); return res.data; } private static void initialize() { if (!initialized) { check_version(); initialized = true; } } } dino-0.3.0/plugins/gpgme-vala/vapi/0000755000000000000000000000000014202022370015571 5ustar rootrootdino-0.3.0/plugins/gpgme-vala/vapi/gpg-error.vapi0000644000000000000000000001636214202022370020366 0ustar rootroot/* gcrypt.vapi * * Copyright: * 2008 Jiqing Qiang * 2008, 2010, 2012-2013 Evan Nemerson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * This library 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 * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Author: * Jiqing Qiang * Evan Nemerson */ [CCode (cheader_filename = "gpg-error.h")] namespace GPGError { [CCode (cname = "gpg_err_source_t", cprefix = "GPG_ERR_SOURCE_")] public enum ErrorSource { UNKNOWN, GCRYPT, GPG, GPGSM, GPGAGENT, PINENTRY, SCD, GPGME, KEYBOX, KSBA, DIRMNGR, GSTI, ANY, USER_1, USER_2, USER_3, USER_4, /* This is one more than the largest allowed entry. */ DIM } [CCode (cname = "gpg_err_code_t", cprefix = "GPG_ERR_")] public enum ErrorCode { NO_ERROR, GENERAL, UNKNOWN_PACKET, UNKNOWN_VERSION, PUBKEY_ALGO, DIGEST_ALGO, BAD_PUBKEY, BAD_SECKEY, BAD_SIGNATURE, NO_PUBKEY, CHECKSUM, BAD_PASSPHRASE, CIPHER_ALGO, KEYRING_OPEN, INV_PACKET, INV_ARMOR, NO_USER_ID, NO_SECKEY, WRONG_SECKEY, BAD_KEY, COMPR_ALGO, NO_PRIME, NO_ENCODING_METHOD, NO_ENCRYPTION_SCHEME, NO_SIGNATURE_SCHEME, INV_ATTR, NO_VALUE, NOT_FOUND, VALUE_NOT_FOUND, SYNTAX, BAD_MPI, INV_PASSPHRASE, SIG_CLASS, RESOURCE_LIMIT, INV_KEYRING, TRUSTDB, BAD_CERT, INV_USER_ID, UNEXPECTED, TIME_CONFLICT, KEYSERVER, WRONG_PUBKEY_ALGO, TRIBUTE_TO_D_A, WEAK_KEY, INV_KEYLEN, INV_ARG, BAD_URI, INV_URI, NETWORK, UNKNOWN_HOST, SELFTEST_FAILED, NOT_ENCRYPTED, NOT_PROCESSED, UNUSABLE_PUBKEY, UNUSABLE_SECKEY, INV_VALUE, BAD_CERT_CHAIN, MISSING_CERT, NO_DATA, BUG, NOT_SUPPORTED, INV_OP, TIMEOUT, INTERNAL, EOF_GCRYPT, INV_OBJ, TOO_SHORT, TOO_LARGE, NO_OBJ, NOT_IMPLEMENTED, CONFLICT, INV_CIPHER_MODE, INV_FLAG, INV_HANDLE, TRUNCATED, INCOMPLETE_LINE, INV_RESPONSE, NO_AGENT, AGENT, INV_DATA, ASSUAN_SERVER_FAULT, ASSUAN, INV_SESSION_KEY, INV_SEXP, UNSUPPORTED_ALGORITHM, NO_PIN_ENTRY, PIN_ENTRY, BAD_PIN, INV_NAME, BAD_DATA, INV_PARAMETER, WRONG_CARD, NO_DIRMNGR, DIRMNGR, CERT_REVOKED, NO_CRL_KNOWN, CRL_TOO_OLD, LINE_TOO_LONG, NOT_TRUSTED, CANCELED, BAD_CA_CERT, CERT_EXPIRED, CERT_TOO_YOUNG, UNSUPPORTED_CERT, UNKNOWN_SEXP, UNSUPPORTED_PROTECTION, CORRUPTED_PROTECTION, AMBIGUOUS_NAME, CARD, CARD_RESET, CARD_REMOVED, INV_CARD, CARD_NOT_PRESENT, NO_PKCS15_APP, NOT_CONFIRMED, CONFIGURATION, NO_POLICY_MATCH, INV_INDEX, INV_ID, NO_SCDAEMON, SCDAEMON, UNSUPPORTED_PROTOCOL, BAD_PIN_METHOD, CARD_NOT_INITIALIZED, UNSUPPORTED_OPERATION, WRONG_KEY_USAGE, NOTHING_FOUND, WRONG_BLOB_TYPE, MISSING_VALUE, HARDWARE, PIN_BLOCKED, USE_CONDITIONS, PIN_NOT_SYNCED, INV_CRL, BAD_BER, INV_BER, ELEMENT_NOT_FOUND, IDENTIFIER_NOT_FOUND, INV_TAG, INV_LENGTH, INV_KEYINFO, UNEXPECTED_TAG, NOT_DER_ENCODED, NO_CMS_OBJ, INV_CMS_OBJ, UNKNOWN_CMS_OBJ, UNSUPPORTED_CMS_OBJ, UNSUPPORTED_ENCODING, UNSUPPORTED_CMS_VERSION, UNKNOWN_ALGORITHM, INV_ENGINE, PUBKEY_NOT_TRUSTED, DECRYPT_FAILED, KEY_EXPIRED, SIG_EXPIRED, ENCODING_PROBLEM, INV_STATE, DUP_VALUE, MISSING_ACTION, MODULE_NOT_FOUND, INV_OID_STRING, INV_TIME, INV_CRL_OBJ, UNSUPPORTED_CRL_VERSION, INV_CERT_OBJ, UNKNOWN_NAME, LOCALE_PROBLEM, NOT_LOCKED, PROTOCOL_VIOLATION, INV_MAC, INV_REQUEST, UNKNOWN_EXTN, UNKNOWN_CRIT_EXTN, LOCKED, UNKNOWN_OPTION, UNKNOWN_COMMAND, BUFFER_TOO_SHORT, SEXP_INV_LEN_SPEC, SEXP_STRING_TOO_LONG, SEXP_UNMATCHED_PAREN, SEXP_NOT_CANONICAL, SEXP_BAD_CHARACTER, SEXP_BAD_QUOTATION, SEXP_ZERO_PREFIX, SEXP_NESTED_DH, SEXP_UNMATCHED_DH, SEXP_UNEXPECTED_PUNC, SEXP_BAD_HEX_CHAR, SEXP_ODD_HEX_NUMBERS, SEXP_BAD_OCT_CHAR, ASS_GENERAL, ASS_ACCEPT_FAILED, ASS_CONNECT_FAILED, ASS_INV_RESPONSE, ASS_INV_VALUE, ASS_INCOMPLETE_LINE, ASS_LINE_TOO_LONG, ASS_NESTED_COMMANDS, ASS_NO_DATA_CB, ASS_NO_INQUIRE_CB, ASS_NOT_A_SERVER, ASS_NOT_A_CLIENT, ASS_SERVER_START, ASS_READ_ERROR, ASS_WRITE_ERROR, ASS_TOO_MUCH_DATA, ASS_UNEXPECTED_CMD, ASS_UNKNOWN_CMD, ASS_SYNTAX, ASS_CANCELED, ASS_NO_INPUT, ASS_NO_OUTPUT, ASS_PARAMETER, ASS_UNKNOWN_INQUIRE, USER_1, USER_2, USER_3, USER_4, USER_5, USER_6, USER_7, USER_8, USER_9, USER_10, USER_11, USER_12, USER_13, USER_14, USER_15, USER_16, MISSING_ERRNO, UNKNOWN_ERRNO, EOF, E2BIG, EACCES, EADDRINUSE, EADDRNOTAVAIL, EADV, EAFNOSUPPORT, EAGAIN, EALREADY, EAUTH, EBACKGROUND, EBADE, EBADF, EBADFD, EBADMSG, EBADR, EBADRPC, EBADRQC, EBADSLT, EBFONT, EBUSY, ECANCELED, ECHILD, ECHRNG, ECOMM, ECONNABORTED, ECONNREFUSED, ECONNRESET, ED, EDEADLK, EDEADLOCK, EDESTADDRREQ, EDIED, EDOM, EDOTDOT, EDQUOT, EEXIST, EFAULT, EFBIG, EFTYPE, EGRATUITOUS, EGREGIOUS, EHOSTDOWN, EHOSTUNREACH, EIDRM, EIEIO, EILSEQ, EINPROGRESS, EINTR, EINVAL, EIO, EISCONN, EISDIR, EISNAM, EL2HLT, EL2NSYNC, EL3HLT, EL3RST, ELIBACC, ELIBBAD, ELIBEXEC, ELIBMAX, ELIBSCN, ELNRNG, ELOOP, EMEDIUMTYPE, EMFILE, EMLINK, EMSGSIZE, EMULTIHOP, ENAMETOOLONG, ENAVAIL, ENEEDAUTH, ENETDOWN, ENETRESET, ENETUNREACH, ENFILE, ENOANO, ENOBUFS, ENOCSI, ENODATA, ENODEV, ENOENT, ENOEXEC, ENOLCK, ENOLINK, ENOMEDIUM, ENOMEM, ENOMSG, ENONET, ENOPKG, ENOPROTOOPT, ENOSPC, ENOSR, ENOSTR, ENOSYS, ENOTBLK, ENOTCONN, ENOTDIR, ENOTEMPTY, ENOTNAM, ENOTSOCK, ENOTSUP, ENOTTY, ENOTUNIQ, ENXIO, EOPNOTSUPP, EOVERFLOW, EPERM, EPFNOSUPPORT, EPIPE, EPROCLIM, EPROCUNAVAIL, EPROGMISMATCH, EPROGUNAVAIL, EPROTO, EPROTONOSUPPORT, EPROTOTYPE, ERANGE, EREMCHG, EREMOTE, EREMOTEIO, ERESTART, EROFS, ERPCMISMATCH, ESHUTDOWN, ESOCKTNOSUPPORT, ESPIPE, ESRCH, ESRMNT, ESTALE, ESTRPIPE, ETIME, ETIMEDOUT, ETOOMANYREFS, ETXTBSY, EUCLEAN, EUNATCH, EUSERS, EWOULDBLOCK, EXDEV, EXFULL, /* This is one more than the largest allowed entry. */ CODE_DIM } [CCode (cname = "gpg_err_code_t", cprefix = "gpg_err_")] public struct Error : uint { [CCode (cname = "gpg_err_make")] public Error (ErrorSource source, ErrorCode code); [CCode (cname = "gpg_err_make_from_errno")] public Error.from_errno (ErrorSource source, int err); public ErrorCode code { [CCode (cname = "gpg_err_code")] get; } public ErrorSource source { [CCode (cname = "gpg_err_source")] get; } [CCode (cname = "gpg_strerror")] public unowned string to_string (); [CCode (cname = "gpg_strsource")] public unowned string source_to_string (); } }dino-0.3.0/plugins/gpgme-vala/vapi/gpgme.deps0000644000000000000000000000001214202022370017536 0ustar rootrootgpg-error dino-0.3.0/plugins/gpgme-vala/vapi/gpgme.vapi0000644000000000000000000003607714202022370017566 0ustar rootroot/* libgpgme.vapi * * Copyright (C) 2009 Sebastian Reichel * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ [CCode (lower_case_cprefix = "gpgme_", cheader_filename = "gpgme.h,gpgme_fix.h")] namespace GPG { public static GLib.RecMutex global_mutex; [CCode (cname = "struct _gpgme_engine_info")] public struct EngineInfo { EngineInfo* next; Protocol protocol; string file_name; string version; string req_version; string? home_dir; } [CCode (cname = "struct _gpgme_op_verify_result")] public struct VerifyResult { Signature* signatures; string? file_name; } [CCode (cname = "struct _gpgme_op_sign_result")] public struct SignResult { InvalidKey invalid_signers; Signature* signatures; } [CCode (cname = "struct _gpgme_op_encrypt_result")] public struct EncryptResult { InvalidKey invalid_signers; } [CCode (cname = "struct _gpgme_op_decrypt_result")] public struct DecryptResult { string unsupported_algorithm; bool wrong_key_usage; Recipient recipients; string file_name; } [CCode (cname = "struct _gpgme_recipient")] public struct Recipient { Recipient *next; string keyid; PublicKeyAlgorithm pubkey_algo; GPGError.Error status; } [CCode (cname = "struct _gpgme_invalid_key")] public struct InvalidKey { InvalidKey *next; string fpr; GPGError.Error reason; } [CCode (cname = "struct _gpgme_signature")] public struct Signature { Signature *next; Sigsum summary; string fpr; GPGError.Error status; SigNotation notations; ulong timestamp; ulong exp_timestamp; bool wrong_key_usage; PKAStatus pka_trust; bool chain_model; Validity validity; GPGError.Error validity_reason; PublicKeyAlgorithm pubkey_algo; HashAlgorithm hash_algo; string? pka_adress; } public enum PKAStatus { NOT_AVAILABLE, BAD, OKAY, RFU } [CCode (cname = "gpgme_sigsum_t", cprefix = "GPGME_SIGSUM_")] public enum Sigsum { VALID, GREEN, RED, KEY_REVOKED, KEY_EXPIRED, SIG_EXPIRED, KEY_MISSING, CRL_MISSING, CRL_TOO_OLD, BAD_POLICY, SYS_ERROR } [CCode (cname = "gpgme_data_encoding_t", cprefix = "GPGME_DATA_ENCODING_")] public enum DataEncoding { NONE, BINARY, BASE64, ARMOR, URL, URLESC, URL0 } [CCode (cname = "gpgme_hash_algo_t", cprefix = "GPGME_MD_")] public enum HashAlgorithm { NONE, MD5, SHA1, RMD160, MD2, TIGER, HAVAL, SHA256, SHA384, SHA512, MD4, MD_CRC32, MD_CRC32_RFC1510, MD_CRC24_RFC2440 } [CCode (cname = "gpgme_export_mode_t", cprefix = "GPGME_EXPORT_MODE_")] public enum ExportMode { EXTERN } [CCode (cprefix = "GPGME_AUDITLOG_")] public enum AuditLogFlag { HTML, WITH_HELP } [CCode (cname = "gpgme_status_code_t", cprefix = "GPGME_STATUS_")] public enum StatusCode { EOF, ENTER, LEAVE, ABORT, GOODSIG, BADSIG, ERRSIG, BADARMOR, RSA_OR_IDEA, KEYEXPIRED, KEYREVOKED, TRUST_UNDEFINED, TRUST_NEVER, TRUST_MARGINAL, TRUST_FULLY, TRUST_ULTIMATE, SHM_INFO, SHM_GET, SHM_GET_BOOL, SHM_GET_HIDDEN, NEED_PASSPHRASE, VALIDSIG, SIG_ID, SIG_TO, ENC_TO, NODATA, BAD_PASSPHRASE, NO_PUBKEY, NO_SECKEY, NEED_PASSPHRASE_SYM, DECRYPTION_FAILED, DECRYPTION_OKAY, MISSING_PASSPHRASE, GOOD_PASSPHRASE, GOODMDC, BADMDC, ERRMDC, IMPORTED, IMPORT_OK, IMPORT_PROBLEM, IMPORT_RES, FILE_START, FILE_DONE, FILE_ERROR, BEGIN_DECRYPTION, END_DECRYPTION, BEGIN_ENCRYPTION, END_ENCRYPTION, DELETE_PROBLEM, GET_BOOL, GET_LINE, GET_HIDDEN, GOT_IT, PROGRESS, SIG_CREATED, SESSION_KEY, NOTATION_NAME, NOTATION_DATA, POLICY_URL, BEGIN_STREAM, END_STREAM, KEY_CREATED, USERID_HINT, UNEXPECTED, INV_RECP, NO_RECP, ALREADY_SIGNED, SIGEXPIRED, EXPSIG, EXPKEYSIG, TRUNCATED, ERROR, NEWSIG, REVKEYSIG, SIG_SUBPACKET, NEED_PASSPHRASE_PIN, SC_OP_FAILURE, SC_OP_SUCCESS, CARDCTRL, BACKUP_KEY_CREATED, PKA_TRUST_BAD, PKA_TRUST_GOOD, PLAINTEXT } [Flags] [CCode (cname="unsigned int")] public enum ImportStatusFlags { [CCode (cname = "GPGME_IMPORT_NEW")] NEW, [CCode (cname = "GPGME_IMPORT_UID")] UID, [CCode (cname = "GPGME_IMPORT_SIG")] SIG, [CCode (cname = "GPGME_IMPORT_SUBKEY")] SUBKEY, [CCode (cname = "GPGME_IMPORT_SECRET")] SECRET } [Compact] [CCode (cname = "struct gpgme_context", free_function = "gpgme_release", cprefix = "gpgme_")] public class Context { private static GPGError.Error new(out Context ctx); public static Context create() throws GLib.Error { Context ctx; throw_if_error(@new(out ctx)); return ctx; } public GPGError.Error set_protocol(Protocol p); public Protocol get_protocol(); public void set_armor(bool yes); public bool get_armor(); public void set_textmode(bool yes); public bool get_textmode(); public GPGError.Error set_keylist_mode(KeylistMode mode); public KeylistMode get_keylist_mode(); public void set_include_certs(int nr_of_certs = -256); public int get_include_certs(); public void set_passphrase_cb(passphrase_callback cb, void* hook_value = null); public void get_passphrase_cb(out passphrase_callback cb, out void* hook_value); public GPGError.Error set_locale(int category, string val); [CCode (cname = "gpgme_ctx_get_engine_info")] public EngineInfo* get_engine_info(); [CCode (cname = "gpgme_ctx_set_engine_info")] public GPGError.Error set_engine_info(Protocol proto, string file_name, string home_dir); public void signers_clear(); public GPGError.Error signers_add(Key key); public Key* signers_enum(int n); public void sig_notation_clear(); public GPGError.Error sig_notation_add(string name, string val, SigNotationFlags flags); public SigNotation* sig_notation_get(); [CCode (cname = "gpgme_get_key")] private GPGError.Error get_key_(string fpr, out Key key, bool secret); [CCode (cname = "gpgme_get_key_")] public Key get_key(string fpr, bool secret) throws GLib.Error { Key key; throw_if_error(get_key_(fpr, out key, secret)); return key; } public Context* wait(out GPGError.Error status, bool hang); public SignResult* op_sign_result(); [CCode (cname = "gpgme_op_sign")] public GPGError.Error op_sign_(Data plain, Data sig, SigMode mode); [CCode (cname = "gpgme_op_sign_")] public Data op_sign(Data plain, SigMode mode) throws GLib.Error { Data sig = Data.create(); throw_if_error(op_sign_(plain, sig, mode)); return sig; } public VerifyResult* op_verify_result(); [CCode (cname = "gpgme_op_verify")] public GPGError.Error op_verify_(Data sig, Data signed_text, Data? plaintext); [CCode (cname = "gpgme_op_verify_")] public Data op_verify(Data sig, Data signed_text) throws GLib.Error { Data plaintext = Data.create(); throw_if_error(op_verify_(sig, signed_text, plaintext)); return plaintext; } public EncryptResult* op_encrypt_result(); [CCode (cname = "gpgme_op_encrypt")] public GPGError.Error op_encrypt_([CCode (array_length = false)] Key[] recp, EncryptFlags flags, Data plain, Data cipher); [CCode (cname = "gpgme_op_encrypt_")] public Data op_encrypt(Key[] recp, EncryptFlags flags, Data plain) throws GLib.Error { Data cipher = Data.create(); throw_if_error(op_encrypt_(recp, flags, plain, cipher)); return cipher; } public DecryptResult* op_decrypt_result(); [CCode (cname = "gpgme_op_decrypt")] public GPGError.Error op_decrypt_(Data cipher, Data plain); [CCode (cname = "gpgme_op_decrypt_")] public Data op_decrypt(Data cipher) throws GLib.Error { Data plain = Data.create(); throw_if_error(op_decrypt_(cipher, plain)); return plain; } public GPGError.Error op_export(string? pattern, ExportMode mode, Data keydata); public GPGError.Error op_import(Data keydata); public unowned ImportResult op_import_result(); [CCode (cname = "gpgme_op_keylist_start")] private GPGError.Error op_keylist_start_(string? pattern = null, int secret_only = 0); [CCode (cname = "gpgme_op_keylist_start_")] public void op_keylist_start(string? pattern = null, int secret_only = 0) throws GLib.Error { throw_if_error(op_keylist_start_(pattern, secret_only)); } [CCode (cname = "gpgme_op_keylist_next")] private GPGError.Error op_keylist_next_(out Key key); [CCode (cname = "gpgme_op_keylist_next_")] public Key op_keylist_next() throws GLib.Error { Key key; throw_if_error(op_keylist_next_(out key)); return key; } [CCode (cname = "gpgme_op_keylist_end")] private GPGError.Error op_keylist_end_(); [CCode (cname = "gpgme_op_keylist_end_")] public void op_keylist_end() throws GLib.Error { throw_if_error(op_keylist_end_()); } public KeylistResult op_keylist_result(); } [Compact] [CCode (cname = "struct _gpgme_import_status")] public class ImportStatus { public ImportStatus? next; public string fpr; public GPGError.Error result; public ImportStatusFlags status; } [Compact] [CCode (cname = "struct _gpgme_op_import_result")] public class ImportResult { public int considered; public int no_user_id; public int imported; public int imported_rsa; public int unchanged; public int new_user_ids; public int new_sub_keys; public int new_signatures; public int new_revocations; public int secret_read; public int secret_imported; public int secret_unchanged; public int not_imported; public ImportStatus imports; } [Compact] [CCode (cname = "struct _gpgme_op_keylist_result")] public class KeylistResult { uint truncated; } [Compact] [CCode (cname = "struct gpgme_data", free_function = "gpgme_data_release", cprefix = "gpgme_data_")] public class Data { public static GPGError.Error new(out Data d); public static Data create() throws GLib.Error { Data data; throw_if_error(@new(out data)); return data; } [CCode (cname = "gpgme_data_new_from_mem")] public static GPGError.Error new_from_memory(out Data d, char[] buffer, bool copy); public static Data create_from_memory(uint8[] buffer, bool copy) throws GLib.Error { Data data; throw_if_error(new_from_memory(out data, (char[]) buffer, copy)); return data; } [CCode (cname = "gpgme_data_new_from_file")] public static GPGError.Error new_from_file(out Data d, string filename, int copy = 1); public static Data create_from_file(string filename, int copy = 1) throws GLib.Error { Data data; throw_if_error(new_from_file(out data, filename, copy)); return data; } [CCode (cname = "gpgme_data_release_and_get_mem")] public string release_and_get_mem(out size_t len); public ssize_t read([CCode (array_length = false)] uint8[] buf, size_t len); public ssize_t write(uint8[] buf); public long seek(long offset, int whence=0); public GPGError.Error set_file_name(string file_name); public DataEncoding* get_encoding(); public GPGError.Error set_encoding(DataEncoding enc); } [CCode (cname = "gpgme_get_protocol_name")] public unowned string get_protocol_name(Protocol p); [CCode (cname = "gpgme_pubkey_algo_name")] public unowned string get_public_key_algorithm_name(PublicKeyAlgorithm algo); [CCode (cname = "gpgme_hash_algo_name")] public unowned string get_hash_algorithm_name(HashAlgorithm algo); [CCode (cname = "gpgme_passphrase_cb_t", has_target = false)] public delegate GPGError.Error passphrase_callback(void* hook, string uid_hint, string passphrase_info, bool prev_was_bad, int fd); [CCode (cname = "gpgme_engine_check_version")] public GPGError.Error engine_check_version(Protocol proto); [CCode (cname = "gpgme_get_engine_information")] public GPGError.Error get_engine_information(out EngineInfo engine_info); [CCode (cname = "gpgme_strerror_r")] public int strerror_r(GPGError.Error err, uint8[] buf); [CCode (cname = "gpgme_strerror")] public unowned string strerror(GPGError.Error err); private void throw_if_error(GPGError.Error error) throws GLib.Error { if (error.code != GPGError.ErrorCode.NO_ERROR) { throw new GLib.Error(-1, error.code, "%s", error.to_string()); } } } dino-0.3.0/plugins/gpgme-vala/vapi/gpgme_public.vapi0000644000000000000000000000666514202022370021124 0ustar rootroot[CCode (lower_case_cprefix = "gpgme_", cheader_filename = "gpgme.h,gpgme_fix.h")] namespace GPG { [CCode (cname = "gpgme_check_version")] public unowned string check_version(string? required_version = null); [Compact] [CCode (cname = "struct _gpgme_key", ref_function = "gpgme_key_ref_vapi", unref_function = "gpgme_key_unref_vapi", free_function = "gpgme_key_release")] public class Key { public bool revoked; public bool expired; public bool disabled; public bool invalid; public bool can_encrypt; public bool can_sign; public bool can_certify; public bool can_authenticate; public bool is_qualified; public bool secret; public Protocol protocol; public string issuer_serial; public string issuer_name; public string chain_id; public Validity owner_trust; [CCode(array_null_terminated = true)] public SubKey[] subkeys; [CCode(array_null_terminated = true)] public UserID[] uids; public KeylistMode keylist_mode; // public string fpr; // requires gpgme >= 1.7.0 public string fpr { get { return subkeys[0].fpr; } } } [CCode (cname = "struct _gpgme_user_id")] public struct UserID { UserID* next; bool revoked; bool invalid; Validity validity; string uid; string name; string email; string comment; KeySig signatures; } [CCode (cname = "struct _gpgme_key_sig")] public struct KeySig { KeySig* next; bool invoked; bool expired; bool invalid; bool exportable; PublicKeyAlgorithm algo; string keyid; long timestamp; long expires; // GPGError.Error status; string uid; string name; string email; string comment; uint sig_class; SigNotation notations; } [CCode (cname = "struct _gpgme_subkey")] public struct SubKey { SubKey* next; bool revoked; bool expired; bool disabled; bool invalid; bool can_encrypt; bool can_sign; bool can_certify; bool secret; bool can_authenticate; bool is_qualified; bool is_cardkey; PublicKeyAlgorithm algo; uint length; string keyid; string fpr; long timestamp; long expires; string? cardnumber; } [CCode (cname = "struct _gpgme_sig_notation")] public struct SigNotation { SigNotation* next; string? name; string value; int name_len; int value_len; SigNotationFlags flags; bool human_readable; bool critical; } [CCode (cname = "gpgme_sig_notation_flags_t", cprefix = "GPGME_SIG_NOTATION_")] public enum SigNotationFlags { HUMAN_READABLE, CRITICAL } [CCode (cname = "gpgme_sig_mode_t", cprefix = "GPGME_SIG_MODE_")] public enum SigMode { NORMAL, DETACH, CLEAR } [CCode (cname = "gpgme_encrypt_flags_t", cprefix = "GPGME_ENCRYPT_")] public enum EncryptFlags { ALWAYS_TRUST, NO_ENCRYPT_TO } [CCode (cname = "gpgme_pubkey_algo_t", cprefix = "GPGME_PK_")] public enum PublicKeyAlgorithm { RSA, RSA_E, RSA_S, ELG_E, DSA, ELG } [CCode (cname = "gpgme_protocol_t", cprefix = "GPGME_PROTOCOL_")] public enum Protocol { OpenPGP, CMS, GPGCONF, ASSUAN, UNKNOWN } [CCode (cname = "gpgme_keylist_mode_t", cprefix = "GPGME_KEYLIST_MODE_")] public enum KeylistMode { LOCAL, EXTERN, SIGS, SIG_NOTATIONS, EPHEMERAL, VALIDATE } [CCode (cname = "gpgme_validity_t", cprefix = "GPGME_VALIDITY_")] public enum Validity { UNKNOWN, UNDEFINED, NEVER, MARGINAL, FULL, ULTIMATE } }dino-0.3.0/plugins/http-files/0000755000000000000000000000000014202022370014671 5ustar rootrootdino-0.3.0/plugins/http-files/CMakeLists.txt0000644000000000000000000000141614202022370017433 0ustar rootrootfind_packages(HTTP_FILES_PACKAGES REQUIRED Gee GLib GModule GObject GTK3 Soup ) vala_precompile(HTTP_FILES_VALA_C SOURCES src/file_provider.vala src/file_sender.vala src/plugin.vala src/register_plugin.vala CUSTOM_VAPIS ${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi ${CMAKE_BINARY_DIR}/exports/dino.vapi ${CMAKE_BINARY_DIR}/exports/qlite.vapi PACKAGES ${HTTP_FILES_PACKAGES} ) add_definitions(${VALA_CFLAGS}) add_library(http-files SHARED ${HTTP_FILES_VALA_C}) target_link_libraries(http-files libdino ${HTTP_FILES_PACKAGES}) set_target_properties(http-files PROPERTIES PREFIX "") set_target_properties(http-files PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/plugins/) install(TARGETS http-files ${PLUGIN_INSTALL}) dino-0.3.0/plugins/http-files/src/0000755000000000000000000000000014202022370015460 5ustar rootrootdino-0.3.0/plugins/http-files/src/file_provider.vala0000644000000000000000000001414614202022370021164 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; using Xmpp; namespace Dino.Plugins.HttpFiles { public class FileProvider : Dino.FileProvider, Object { private StreamInteractor stream_interactor; private Dino.Database dino_db; private static Regex http_url_regex = /^https?:\/\/([^\s#]*)$/; // Spaces are invalid in URLs and we can't use fragments for downloads private static Regex omemo_url_regex = /^aesgcm:\/\/(.*)#(([A-Fa-f0-9]{2}){48}|([A-Fa-f0-9]{2}){44})$/; public FileProvider(StreamInteractor stream_interactor, Dino.Database dino_db) { this.stream_interactor = stream_interactor; this.dino_db = dino_db; stream_interactor.get_module(MessageProcessor.IDENTITY).received_pipeline.connect(new ReceivedMessageListener(this)); } private class ReceivedMessageListener : MessageListener { public string[] after_actions_const = new string[]{ "STORE" }; public override string action_group { get { return "MESSAGE_REINTERPRETING"; } } public override string[] after_actions { get { return after_actions_const; } } private FileProvider outer; private StreamInteractor stream_interactor; public ReceivedMessageListener(FileProvider outer) { this.outer = outer; this.stream_interactor = outer.stream_interactor; } public override async bool run(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) { string? oob_url = Xmpp.Xep.OutOfBandData.get_url_from_message(stanza); bool normal_file = oob_url != null && oob_url == message.body && FileProvider.http_url_regex.match(message.body); bool omemo_file = FileProvider.omemo_url_regex.match(message.body); if (normal_file || omemo_file) { outer.on_file_message(message, conversation); return true; } return false; } } private void on_file_message(Entities.Message message, Conversation conversation) { var additional_info = message.id.to_string(); var receive_data = new HttpFileReceiveData(); receive_data.url = message.body; var file_meta = new HttpFileMeta(); file_meta.file_name = extract_file_name_from_url(message.body); file_meta.message = message; file_incoming(additional_info, message.from, message.time, message.local_time, conversation, receive_data, file_meta); } public async FileMeta get_meta_info(FileTransfer file_transfer, FileReceiveData receive_data, FileMeta file_meta) throws FileReceiveError { HttpFileReceiveData? http_receive_data = receive_data as HttpFileReceiveData; if (http_receive_data == null) return file_meta; var session = new Soup.Session(); var head_message = new Soup.Message("HEAD", http_receive_data.url); if (head_message != null) { try { yield session.send_async(head_message, null); } catch (Error e) { throw new FileReceiveError.GET_METADATA_FAILED("HEAD request failed"); } string? content_type = null, content_length = null; head_message.response_headers.foreach((name, val) => { if (name == "Content-Type") content_type = val; if (name == "Content-Length") content_length = val; }); file_meta.mime_type = content_type; if (content_length != null) { file_meta.size = int.parse(content_length); } } return file_meta; } public Encryption get_encryption(FileTransfer file_transfer, FileReceiveData receive_data, FileMeta file_meta) { return Encryption.NONE; } public async InputStream download(FileTransfer file_transfer, FileReceiveData receive_data, FileMeta file_meta) throws FileReceiveError { HttpFileReceiveData? http_receive_data = receive_data as HttpFileReceiveData; if (http_receive_data == null) assert(false); try { var session = new Soup.Session(); Soup.Request request = session.request(http_receive_data.url); return yield request.send_async(null); } catch (Error e) { throw new FileReceiveError.DOWNLOAD_FAILED("Downloading file error: %s".printf(e.message)); } } public FileMeta get_file_meta(FileTransfer file_transfer) throws FileReceiveError { Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(file_transfer.counterpart.bare_jid, file_transfer.account); if (conversation == null) throw new FileReceiveError.GET_METADATA_FAILED("No conversation"); Message? message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_id(int.parse(file_transfer.info), conversation); if (message == null) throw new FileReceiveError.GET_METADATA_FAILED("No message"); var file_meta = new HttpFileMeta(); file_meta.size = file_transfer.size; file_meta.mime_type = file_transfer.mime_type; file_meta.file_name = extract_file_name_from_url(message.body); file_meta.message = message; return file_meta; } public FileReceiveData? get_file_receive_data(FileTransfer file_transfer) { Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(file_transfer.counterpart.bare_jid, file_transfer.account); if (conversation == null) return null; Message? message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_id(int.parse(file_transfer.info), conversation); if (message == null) return null; var receive_data = new HttpFileReceiveData(); receive_data.url = message.body; return receive_data; } private string extract_file_name_from_url(string url) { string ret = url; if (ret.contains("#")) { ret = ret.substring(0, ret.last_index_of("#")); } ret = Uri.unescape_string(ret.substring(ret.last_index_of("/") + 1)); return ret; } public int get_id() { return 0; } } } dino-0.3.0/plugins/http-files/src/file_sender.vala0000644000000000000000000001316414202022370020611 0ustar rootrootusing Dino.Entities; using Xmpp; using Gee; namespace Dino.Plugins.HttpFiles { public class HttpFileSender : FileSender, Object { private StreamInteractor stream_interactor; private Database db; private HashMap max_file_sizes = new HashMap(Account.hash_func, Account.equals_func); public HttpFileSender(StreamInteractor stream_interactor, Database db) { this.stream_interactor = stream_interactor; this.db = db; stream_interactor.stream_negotiated.connect(on_stream_negotiated); stream_interactor.get_module(MessageProcessor.IDENTITY).build_message_stanza.connect(check_add_oob); } public async FileSendData? prepare_send_file(Conversation conversation, FileTransfer file_transfer, FileMeta file_meta) throws FileSendError { HttpFileSendData send_data = new HttpFileSendData(); if (send_data == null) return null; Xmpp.XmppStream? stream = stream_interactor.get_stream(file_transfer.account); if (stream == null) return null; try { var slot_result = yield stream_interactor.module_manager.get_module(file_transfer.account, Xmpp.Xep.HttpFileUpload.Module.IDENTITY).request_slot(stream, file_transfer.server_file_name, file_meta.size, file_meta.mime_type); send_data.url_down = slot_result.url_get; send_data.url_up = slot_result.url_put; send_data.headers = slot_result.headers; } catch (Xep.HttpFileUpload.HttpFileTransferError e) { throw new FileSendError.UPLOAD_FAILED("Http file upload XMPP error: %s".printf(e.message)); } return send_data; } public async void send_file(Conversation conversation, FileTransfer file_transfer, FileSendData file_send_data, FileMeta file_meta) throws FileSendError { HttpFileSendData? send_data = file_send_data as HttpFileSendData; if (send_data == null) return; yield upload(file_transfer, send_data, file_meta); Entities.Message message = stream_interactor.get_module(MessageProcessor.IDENTITY).create_out_message(send_data.url_down, conversation); file_transfer.info = message.id.to_string(); message.encryption = send_data.encrypt_message ? conversation.encryption : Encryption.NONE; stream_interactor.get_module(MessageProcessor.IDENTITY).send_xmpp_message(message, conversation); } public async bool can_send(Conversation conversation, FileTransfer file_transfer) { if (!max_file_sizes.has_key(conversation.account)) return false; return file_transfer.size < max_file_sizes[conversation.account]; } public async long get_file_size_limit(Conversation conversation) { long? max_size = max_file_sizes[conversation.account]; if (max_size != null) { return max_size; } return -1; } public async bool can_encrypt(Conversation conversation, FileTransfer file_transfer) { return false; } public async bool is_upload_available(Conversation conversation) { lock (max_file_sizes) { return max_file_sizes.has_key(conversation.account); } } private static void transfer_more_bytes(InputStream stream, Soup.MessageBody body) { uint8[] bytes = new uint8[4096]; ssize_t read = stream.read(bytes); if (read == 0) { body.complete(); return; } bytes.length = (int)read; body.append_buffer(new Soup.Buffer.take(bytes)); } private async void upload(FileTransfer file_transfer, HttpFileSendData file_send_data, FileMeta file_meta) throws FileSendError { Xmpp.XmppStream? stream = stream_interactor.get_stream(file_transfer.account); if (stream == null) return; Soup.Message message = new Soup.Message("PUT", file_send_data.url_up); message.request_headers.set_content_type(file_meta.mime_type, null); message.request_headers.set_content_length(file_meta.size); foreach (var entry in file_send_data.headers.entries) { message.request_headers.append(entry.key, entry.value); } message.request_body.set_accumulate(false); message.wrote_headers.connect(() => transfer_more_bytes(file_transfer.input_stream, message.request_body)); message.wrote_chunk.connect(() => transfer_more_bytes(file_transfer.input_stream, message.request_body)); Soup.Session session = new Soup.Session(); try { yield session.send_async(message); if (message.status_code < 200 || message.status_code >= 300) { throw new FileSendError.UPLOAD_FAILED("HTTP status code %s".printf(message.status_code.to_string())); } } catch (Error e) { throw new FileSendError.UPLOAD_FAILED("HTTP upload error: %s".printf(e.message)); } } private void on_stream_negotiated(Account account, XmppStream stream) { stream_interactor.module_manager.get_module(account, Xmpp.Xep.HttpFileUpload.Module.IDENTITY).feature_available.connect((stream, max_file_size) => { lock (max_file_sizes) { max_file_sizes[account] = max_file_size; } upload_available(account); }); } private void check_add_oob(Entities.Message message, Xmpp.MessageStanza message_stanza, Conversation conversation) { if (message.encryption == Encryption.NONE && message.body.has_prefix("http") && message_is_file(db, message)) { Xep.OutOfBandData.add_url_to_message(message_stanza, message_stanza.body); } } public int get_id() { return 0; } public float get_priority() { return 100; } } } dino-0.3.0/plugins/http-files/src/plugin.vala0000644000000000000000000000173314202022370017627 0ustar rootrootextern const string GETTEXT_PACKAGE; extern const string LOCALE_INSTALL_DIR; namespace Dino.Plugins.HttpFiles { public class Plugin : RootInterface, Object { public Dino.Application app; public FileProvider file_provider; public FileSender file_sender; public void registered(Dino.Application app) { this.app = app; file_provider = new FileProvider(app.stream_interactor, app.db); file_sender = new HttpFileSender(app.stream_interactor, app.db); app.stream_interactor.get_module(FileManager.IDENTITY).add_provider(file_provider); app.stream_interactor.get_module(FileManager.IDENTITY).add_sender(file_sender); } public void shutdown() { // Nothing to do } } private bool message_is_file(Database db, Entities.Message message) { Qlite.QueryBuilder builder = db.file_transfer.select({db.file_transfer.id}).with(db.file_transfer.info, "=", message.id.to_string()); return builder.count() > 0; } } dino-0.3.0/plugins/http-files/src/register_plugin.vala0000644000000000000000000000014214202022370021524 0ustar rootrootpublic Type register_plugin(Module module) { return typeof (Dino.Plugins.HttpFiles.Plugin); } dino-0.3.0/plugins/ice/0000755000000000000000000000000014202022370013352 5ustar rootrootdino-0.3.0/plugins/ice/CMakeLists.txt0000644000000000000000000000175114202022370016116 0ustar rootrootfind_package(Nice 0.1.15 REQUIRED) find_package(GnuTLS REQUIRED) find_packages(ICE_PACKAGES REQUIRED Gee GLib GModule GObject GTK3 ) vala_precompile(ICE_VALA_C SOURCES src/dtls_srtp.vala src/module.vala src/plugin.vala src/transport_parameters.vala src/util.vala src/register_plugin.vala CUSTOM_VAPIS ${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi ${CMAKE_BINARY_DIR}/exports/dino.vapi ${CMAKE_BINARY_DIR}/exports/qlite.vapi ${CMAKE_BINARY_DIR}/exports/crypto-vala.vapi ${CMAKE_CURRENT_SOURCE_DIR}/vapi/nice.vapi ${CMAKE_CURRENT_SOURCE_DIR}/vapi/gnutls.vapi PACKAGES ${ICE_PACKAGES} ) add_definitions(${VALA_CFLAGS} -DG_LOG_DOMAIN="ice") add_library(ice SHARED ${ICE_VALA_C}) target_link_libraries(ice libdino crypto-vala ${ICE_PACKAGES} nice gnutls) set_target_properties(ice PROPERTIES PREFIX "") set_target_properties(ice PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/plugins/) install(TARGETS ice ${PLUGIN_INSTALL}) dino-0.3.0/plugins/ice/src/0000755000000000000000000000000014202022370014141 5ustar rootrootdino-0.3.0/plugins/ice/src/dtls_srtp.vala0000644000000000000000000003101014202022370017017 0ustar rootrootusing GnuTLS; namespace Dino.Plugins.Ice.DtlsSrtp { public class CredentialsCapsule { public uint8[] own_fingerprint; public X509.Certificate[] own_cert; public X509.PrivateKey private_key; } public class Handler { public signal void send_data(uint8[] data); public bool ready { get { return srtp_session.has_encrypt && srtp_session.has_decrypt; }} public Mode mode { get; set; default = Mode.CLIENT; } public uint8[] own_fingerprint { get; private set; } public uint8[] peer_fingerprint { get; set; } public string peer_fp_algo { get; set; } private CredentialsCapsule credentials; private Cond buffer_cond = Cond(); private Mutex buffer_mutex = Mutex(); private Gee.LinkedList buffer_queue = new Gee.LinkedList(); private bool running = false; private bool stop = false; private bool restart = false; private Crypto.Srtp.Session srtp_session = new Crypto.Srtp.Session(); public Handler.with_cert(CredentialsCapsule creds) { this.credentials = creds; this.own_fingerprint = creds.own_fingerprint; } public uint8[]? process_incoming_data(uint component_id, uint8[] data) throws Crypto.Error { if (srtp_session.has_decrypt) { if (component_id == 1) { if (data.length >= 2 && data[1] >= 192 && data[1] < 224) { return srtp_session.decrypt_rtcp(data); } return srtp_session.decrypt_rtp(data); } if (component_id == 2) return srtp_session.decrypt_rtcp(data); } else if (component_id == 1) { on_data_rec(data); } return null; } public uint8[]? process_outgoing_data(uint component_id, uint8[] data) throws Crypto.Error { if (srtp_session.has_encrypt) { if (component_id == 1) { if (data.length >= 2 && data[1] >= 192 && data[1] < 224) { return srtp_session.encrypt_rtcp(data); } return srtp_session.encrypt_rtp(data); } if (component_id == 2) return srtp_session.encrypt_rtcp(data); } return null; } public void on_data_rec(owned uint8[] data) { buffer_mutex.lock(); buffer_queue.add(new Bytes.take(data)); buffer_cond.signal(); buffer_mutex.unlock(); } internal static CredentialsCapsule generate_credentials() throws GLib.Error { int err = 0; X509.PrivateKey private_key = X509.PrivateKey.create(); err = private_key.generate(PKAlgorithm.RSA, 2048); throw_if_error(err); var start_time = new DateTime.now_local().add_days(1); var end_time = start_time.add_days(2); X509.Certificate cert = X509.Certificate.create(); cert.set_key(private_key); cert.set_version(1); cert.set_activation_time ((time_t) start_time.to_unix ()); cert.set_expiration_time ((time_t) end_time.to_unix ()); uint32 serial = 1; cert.set_serial(&serial, sizeof(uint32)); cert.sign(cert, private_key); uint8[] own_fingerprint = get_fingerprint(cert, DigestAlgorithm.SHA256); X509.Certificate[] own_cert = new X509.Certificate[] { (owned)cert }; var creds = new CredentialsCapsule(); creds.own_fingerprint = own_fingerprint; creds.own_cert = (owned) own_cert; creds.private_key = (owned) private_key; return creds; } public void stop_dtls_connection() { buffer_mutex.lock(); stop = true; buffer_cond.signal(); buffer_mutex.unlock(); } public async Xmpp.Xep.Jingle.ContentEncryption? setup_dtls_connection() { MainContext context = MainContext.current_source().get_context(); var thread = new Thread("dtls-connection", () => { var res = setup_dtls_connection_thread(); Source source = new IdleSource(); source.set_callback(setup_dtls_connection.callback); source.attach(context); return res; }); yield; return thread.join(); } private Xmpp.Xep.Jingle.ContentEncryption? setup_dtls_connection_thread() { buffer_mutex.lock(); if (stop) { restart = true; buffer_mutex.unlock(); return null; } if (running || ready) { buffer_mutex.unlock(); return null; } running = true; restart = false; buffer_mutex.unlock(); InitFlags server_or_client = mode == Mode.SERVER ? InitFlags.SERVER : InitFlags.CLIENT; debug("Setting up DTLS connection. We're %s", mode.to_string()); CertificateCredentials cert_cred = CertificateCredentials.create(); int err = cert_cred.set_x509_key(credentials.own_cert, credentials.private_key); throw_if_error(err); Session? session = Session.create(server_or_client | InitFlags.DATAGRAM); session.enable_heartbeat(1); session.set_srtp_profile_direct("SRTP_AES128_CM_HMAC_SHA1_80"); session.set_credentials(GnuTLS.CredentialsType.CERTIFICATE, cert_cred); session.server_set_request(CertificateRequest.REQUEST); session.set_priority_from_string("NORMAL:!VERS-TLS-ALL:+VERS-DTLS-ALL:+CTYPE-CLI-X509"); session.set_transport_pointer(this); session.set_pull_function(pull_function); session.set_pull_timeout_function(pull_timeout_function); session.set_push_function(push_function); session.set_verify_function(verify_function); DateTime maximum_time = new DateTime.now_utc().add_seconds(20); do { err = session.handshake(); DateTime current_time = new DateTime.now_utc(); if (maximum_time.compare(current_time) < 0) { warning("DTLS handshake timeouted"); err = ErrorCode.APPLICATION_ERROR_MIN + 1; break; } if (stop) { debug("DTLS handshake stopped"); err = ErrorCode.APPLICATION_ERROR_MIN + 2; break; } } while (err < 0 && !((ErrorCode)err).is_fatal()); buffer_mutex.lock(); if (stop) { stop = false; running = false; bool restart = restart; buffer_mutex.unlock(); if (restart) { debug("Restarting DTLS handshake"); return setup_dtls_connection_thread(); } return null; } buffer_mutex.unlock(); if (err != ErrorCode.SUCCESS) { warning("DTLS handshake failed: %s", ((ErrorCode)err).to_string()); return null; } uint8[] km = new uint8[150]; Datum? client_key, client_salt, server_key, server_salt; session.get_srtp_keys(km, km.length, out client_key, out client_salt, out server_key, out server_salt); if (client_key == null || client_salt == null || server_key == null || server_salt == null) { warning("SRTP client/server key/salt null"); } debug("Finished DTLS connection. We're %s", mode.to_string()); if (mode == Mode.SERVER) { srtp_session.set_encryption_key(Crypto.Srtp.AES_CM_128_HMAC_SHA1_80, server_key.extract(), server_salt.extract()); srtp_session.set_decryption_key(Crypto.Srtp.AES_CM_128_HMAC_SHA1_80, client_key.extract(), client_salt.extract()); } else { srtp_session.set_encryption_key(Crypto.Srtp.AES_CM_128_HMAC_SHA1_80, client_key.extract(), client_salt.extract()); srtp_session.set_decryption_key(Crypto.Srtp.AES_CM_128_HMAC_SHA1_80, server_key.extract(), server_salt.extract()); } return new Xmpp.Xep.Jingle.ContentEncryption(Xmpp.Xep.JingleIceUdp.DTLS_NS_URI, "DTLS-SRTP", credentials.own_fingerprint, peer_fingerprint); } private static ssize_t pull_function(void* transport_ptr, uint8[] buffer) { Handler self = transport_ptr as Handler; self.buffer_mutex.lock(); while (self.buffer_queue.size == 0) { self.buffer_cond.wait(self.buffer_mutex); if (self.stop) { self.buffer_mutex.unlock(); debug("DTLS handshake pull_function stopped"); return -1; } } Bytes data = self.buffer_queue.remove_at(0); self.buffer_mutex.unlock(); uint8[] data_uint8 = Bytes.unref_to_data((owned) data); Memory.copy(buffer, data_uint8, data_uint8.length); // The callback should return 0 on connection termination, a positive number indicating the number of bytes received, and -1 on error. return (ssize_t)data_uint8.length; } private static int pull_timeout_function(void* transport_ptr, uint ms) { Handler self = transport_ptr as Handler; int64 end_time = get_monotonic_time() + ms * 1000; self.buffer_mutex.lock(); while (self.buffer_queue.size == 0) { self.buffer_cond.wait_until(self.buffer_mutex, end_time); if (self.stop) { self.buffer_mutex.unlock(); debug("DTLS handshake pull_timeout_function stopped"); return -1; } if (get_monotonic_time() > end_time) { self.buffer_mutex.unlock(); return 0; } } self.buffer_mutex.unlock(); // The callback should return 0 on timeout, a positive number if data can be received, and -1 on error. return 1; } private static ssize_t push_function(void* transport_ptr, uint8[] buffer) { Handler self = transport_ptr as Handler; self.send_data(buffer); // The callback should return a positive number indicating the bytes sent, and -1 on error. return (ssize_t)buffer.length; } private static int verify_function(Session session) { Handler self = session.get_transport_pointer() as Handler; try { bool valid = self.verify_peer_cert(session); if (!valid) { warning("DTLS certificate invalid. Aborting handshake."); return 1; } } catch (Error e) { warning("Error during DTLS certificate validation: %s. Aborting handshake.", e.message); return 1; } // The callback function should return 0 for the handshake to continue or non-zero to terminate. return 0; } private bool verify_peer_cert(Session session) throws GLib.Error { unowned Datum[] cert_datums = session.get_peer_certificates(); if (cert_datums.length == 0) { warning("No peer certs"); return false; } if (cert_datums.length > 1) warning("More than one peer cert"); X509.Certificate peer_cert = X509.Certificate.create(); peer_cert.import(ref cert_datums[0], CertificateFormat.DER); DigestAlgorithm algo; switch (peer_fp_algo) { case "sha-256": algo = DigestAlgorithm.SHA256; break; default: warning("Unkown peer fingerprint algorithm: %s", peer_fp_algo); return false; } uint8[] real_peer_fp = get_fingerprint(peer_cert, algo); if (real_peer_fp.length != this.peer_fingerprint.length) { warning("Fingerprint lengths not equal %i vs %i", real_peer_fp.length, peer_fingerprint.length); return false; } for (int i = 0; i < real_peer_fp.length; i++) { if (real_peer_fp[i] != this.peer_fingerprint[i]) { warning("First cert in peer cert list doesn't equal advertised one: %s vs %s", format_fingerprint(real_peer_fp), format_fingerprint(peer_fingerprint)); return false; } } return true; } } private uint8[] get_fingerprint(X509.Certificate certificate, DigestAlgorithm digest_algo) { uint8[] buf = new uint8[512]; size_t buf_out_size = 512; certificate.get_fingerprint(digest_algo, buf, ref buf_out_size); uint8[] ret = new uint8[buf_out_size]; for (int i = 0; i < buf_out_size; i++) { ret[i] = buf[i]; } return ret; } private string format_fingerprint(uint8[] fingerprint) { var sb = new StringBuilder(); for (int i = 0; i < fingerprint.length; i++) { sb.append("%02X".printf(fingerprint[i])); if (i < fingerprint.length - 1) { sb.append(":"); } } return sb.str; } public enum Mode { CLIENT, SERVER } } dino-0.3.0/plugins/ice/src/module.vala0000644000000000000000000000453714202022370016304 0ustar rootrootusing Gee; using Xmpp; using Xmpp.Xep; public class Dino.Plugins.Ice.Module : JingleIceUdp.Module { public string? stun_ip = null; public uint stun_port = 0; public string? turn_ip = null; public Xep.ExternalServiceDiscovery.Service? turn_service = null; private weak Nice.Agent? agent; private HashMap cerds = new HashMap(); private Nice.Agent get_agent() { Nice.Agent? agent = this.agent; if (agent == null) { agent = new Nice.Agent(MainContext.@default(), Nice.Compatibility.RFC5245); if (stun_ip != null) { agent.stun_server = stun_ip; agent.stun_server_port = stun_port; } agent.ice_tcp = false; agent.set_software("Dino"); agent.weak_ref(agent_unweak); this.agent = agent; debug("STUN server for libnice %s %u", agent.stun_server, agent.stun_server_port); } return agent; } public override Jingle.TransportParameters create_transport_parameters(XmppStream stream, uint8 components, Jid local_full_jid, Jid peer_full_jid) { DtlsSrtp.CredentialsCapsule? cred = get_create_credentials(local_full_jid, peer_full_jid); return new TransportParameters(get_agent(), cred, turn_service, turn_ip, components, local_full_jid, peer_full_jid); } public override Jingle.TransportParameters parse_transport_parameters(XmppStream stream, uint8 components, Jid local_full_jid, Jid peer_full_jid, StanzaNode transport) throws Jingle.IqError { DtlsSrtp.CredentialsCapsule? cred = get_create_credentials(local_full_jid, peer_full_jid); return new TransportParameters(get_agent(), cred, turn_service, turn_ip, components, local_full_jid, peer_full_jid, transport); } private DtlsSrtp.CredentialsCapsule? get_create_credentials(Jid local_full_jid, Jid peer_full_jid) { string from_to_id = local_full_jid.to_string() + peer_full_jid.to_string(); try { if (!cerds.has_key(from_to_id)) cerds[from_to_id] = DtlsSrtp.Handler.generate_credentials(); } catch (Error e) { warning("Error creating dtls credentials: %s", e.message); } return cerds[from_to_id]; } private void agent_unweak() { this.agent = null; } }dino-0.3.0/plugins/ice/src/plugin.vala0000644000000000000000000000632114202022370016306 0ustar rootrootusing Gee; using Dino.Entities; using Xmpp; using Xmpp.Xep; private extern const size_t NICE_ADDRESS_STRING_LEN; public class Dino.Plugins.Ice.Plugin : RootInterface, Object { public Dino.Application app; public void registered(Dino.Application app) { Nice.debug_enable(true); this.app = app; app.stream_interactor.module_manager.initialize_account_modules.connect((account, list) => { list.add(new Module()); }); app.stream_interactor.stream_attached_modules.connect((account, stream) => { if (stream.get_module(Socks5Bytestreams.Module.IDENTITY) != null) { stream.get_module(Socks5Bytestreams.Module.IDENTITY).set_local_ip_address_handler(get_local_ip_addresses); } if (stream.get_module(JingleRawUdp.Module.IDENTITY) != null) { stream.get_module(JingleRawUdp.Module.IDENTITY).set_local_ip_address_handler(get_local_ip_addresses); } }); app.stream_interactor.stream_negotiated.connect(on_stream_negotiated); } private async void on_stream_negotiated(Account account, XmppStream stream) { Module? ice_udp_module = stream.get_module(JingleIceUdp.Module.IDENTITY) as Module; if (ice_udp_module == null) return; Gee.List services = yield ExternalServiceDiscovery.request_services(stream); foreach (Xep.ExternalServiceDiscovery.Service service in services) { if (service.transport == "udp" && (service.ty == "stun" || service.ty == "turn")) { InetAddress ip = yield lookup_ipv4_addess(service.host); if (ip == null) continue; if (service.ty == "stun") { debug("Server offers STUN server: %s:%u, resolved to %s", service.host, service.port, ip.to_string()); ice_udp_module.stun_ip = ip.to_string(); ice_udp_module.stun_port = service.port; } else if (service.ty == "turn") { debug("Server offers TURN server: %s:%u, resolved to %s", service.host, service.port, ip.to_string()); ice_udp_module.turn_ip = ip.to_string(); ice_udp_module.turn_service = service; } } } if (ice_udp_module.stun_ip == null) { InetAddress ip = yield lookup_ipv4_addess("stun.dino.im"); if (ip == null) return; debug("Using fallback STUN server: stun.dino.im:7886, resolved to %s", ip.to_string()); ice_udp_module.stun_ip = ip.to_string(); ice_udp_module.stun_port = 7886; } } public void shutdown() { // Nothing to do } private async InetAddress? lookup_ipv4_addess(string host) { try { Resolver resolver = Resolver.get_default(); GLib.List? ips = yield resolver.lookup_by_name_async(host); foreach (GLib.InetAddress ina in ips) { if (ina.get_family() != SocketFamily.IPV4) continue; return ina; } } catch (Error e) { warning("Failed looking up IP address of %s", host); } return null; } }dino-0.3.0/plugins/ice/src/register_plugin.vala0000644000000000000000000000013414202022370020206 0ustar rootrootpublic Type register_plugin(Module module) { return typeof (Dino.Plugins.Ice.Plugin); } dino-0.3.0/plugins/ice/src/transport_parameters.vala0000644000000000000000000004461614202022370021300 0ustar rootrootusing Gee; using Xmpp; using Xmpp.Xep; public class Dino.Plugins.Ice.TransportParameters : JingleIceUdp.IceUdpTransportParameters { private Nice.Agent agent; private uint stream_id; private bool we_want_connection; private bool remote_credentials_set; private Map connections = new HashMap(); private DtlsSrtp.Handler? dtls_srtp_handler; private MainContext thread_context; private MainLoop thread_loop; private class DatagramConnection : Jingle.DatagramConnection { private Nice.Agent agent; private DtlsSrtp.Handler? dtls_srtp_handler; private uint stream_id; private string? error; private ulong datagram_received_id; public DatagramConnection(Nice.Agent agent, DtlsSrtp.Handler? dtls_srtp_handler, uint stream_id, uint8 component_id) { this.agent = agent; this.dtls_srtp_handler = dtls_srtp_handler; this.stream_id = stream_id; this.component_id = component_id; this.datagram_received_id = this.datagram_received.connect((datagram) => { bytes_received += datagram.length; }); } public override async void terminate(bool we_terminated, string? reason_string = null, string? reason_text = null) { yield base.terminate(we_terminated, reason_string, reason_text); this.disconnect(datagram_received_id); agent = null; dtls_srtp_handler = null; } public override void send_datagram(Bytes datagram) { if (this.agent != null && is_component_ready(agent, stream_id, component_id)) { try { if (dtls_srtp_handler != null) { uint8[] encrypted_data = dtls_srtp_handler.process_outgoing_data(component_id, datagram.get_data()); if (encrypted_data == null) return; GLib.OutputVector vector = { encrypted_data, encrypted_data.length }; GLib.OutputVector[] vectors = { vector }; Nice.OutputMessage message = { vectors }; Nice.OutputMessage[] messages = { message }; agent.send_messages_nonblocking(stream_id, component_id, messages); } else { GLib.OutputVector vector = { datagram.get_data(), datagram.get_size() }; GLib.OutputVector[] vectors = { vector }; Nice.OutputMessage message = { vectors }; Nice.OutputMessage[] messages = { message }; agent.send_messages_nonblocking(stream_id, component_id, messages); } bytes_sent += datagram.length; } catch (GLib.Error e) { warning("%s while send_datagram stream %u component %u", e.message, stream_id, component_id); } } } } public TransportParameters(Nice.Agent agent, DtlsSrtp.CredentialsCapsule? credentials, Xep.ExternalServiceDiscovery.Service? turn_service, string? turn_ip, uint8 components, Jid local_full_jid, Jid peer_full_jid, StanzaNode? node = null) { base(components, local_full_jid, peer_full_jid, node); this.we_want_connection = (node == null); this.agent = agent; if (this.peer_fingerprint != null || !incoming) { dtls_srtp_handler = setup_dtls(this, credentials); own_fingerprint = dtls_srtp_handler.own_fingerprint; if (incoming) { own_setup = "active"; dtls_srtp_handler.mode = DtlsSrtp.Mode.CLIENT; dtls_srtp_handler.peer_fingerprint = peer_fingerprint; dtls_srtp_handler.peer_fp_algo = peer_fp_algo; } else { own_setup = "actpass"; dtls_srtp_handler.mode = DtlsSrtp.Mode.SERVER; dtls_srtp_handler.setup_dtls_connection.begin((_, res) => { var content_encryption = dtls_srtp_handler.setup_dtls_connection.end(res); if (content_encryption != null) { this.content.encryptions[content_encryption.encryption_ns] = content_encryption; } }); } } agent.candidate_gathering_done.connect(on_candidate_gathering_done); agent.initial_binding_request_received.connect(on_initial_binding_request_received); agent.component_state_changed.connect(on_component_state_changed); agent.new_selected_pair_full.connect(on_new_selected_pair_full); agent.new_candidate_full.connect(on_new_candidate); agent.controlling_mode = !incoming; stream_id = agent.add_stream(components); thread_context = new MainContext(); new Thread(@"ice-thread-$stream_id", () => { thread_context.push_thread_default(); thread_loop = new MainLoop(thread_context, false); thread_loop.run(); thread_context.pop_thread_default(); return null; }); if (turn_ip != null) { for (uint8 component_id = 1; component_id <= components; component_id++) { agent.set_relay_info(stream_id, component_id, turn_ip, turn_service.port, turn_service.username, turn_service.password, Nice.RelayType.UDP); debug("TURN info (component %i) %s:%u", component_id, turn_ip, turn_service.port); } } string ufrag; string pwd; agent.get_local_credentials(stream_id, out ufrag, out pwd); init(ufrag, pwd); for (uint8 component_id = 1; component_id <= components; component_id++) { // We don't properly get local candidates before this call agent.attach_recv(stream_id, component_id, thread_context, on_recv); } agent.gather_candidates(stream_id); } private static DtlsSrtp.Handler setup_dtls(TransportParameters tp, DtlsSrtp.CredentialsCapsule credentials) { var weak_self = WeakRef(tp); DtlsSrtp.Handler dtls_srtp = new DtlsSrtp.Handler.with_cert(credentials); dtls_srtp.send_data.connect((data) => { TransportParameters self = (TransportParameters) weak_self.get(); if (self != null) self.agent.send(self.stream_id, 1, data); }); return dtls_srtp; } private void on_candidate_gathering_done(uint stream_id) { if (stream_id != this.stream_id) return; debug("on_candidate_gathering_done in %u", stream_id); for (uint8 i = 1; i <= components; i++) { foreach (unowned Nice.Candidate nc in agent.get_local_candidates(stream_id, i)) { if (nc.transport == Nice.CandidateTransport.UDP) { JingleIceUdp.Candidate? candidate = candidate_to_jingle(nc); if (candidate == null) continue; debug("Local candidate summary: %s", agent.generate_local_candidate_sdp(nc)); } } } } private void on_new_candidate(Nice.Candidate nc) { if (nc.stream_id != stream_id) return; JingleIceUdp.Candidate? candidate = candidate_to_jingle(nc); if (candidate == null) return; if (nc.transport == Nice.CandidateTransport.UDP) { // Execution was in the agent thread before add_local_candidate_threadsafe(candidate); } } private bool bytes_equal(uint8[] a1, uint8[] a2) { return a1.length == a2.length && Memory.cmp(a1, a2, a1.length) == 0; } public override void handle_transport_accept(StanzaNode transport) throws Jingle.IqError { debug("on_transport_accept from %s", peer_full_jid.to_string()); base.handle_transport_accept(transport); if (dtls_srtp_handler != null && peer_fingerprint != null) { if (dtls_srtp_handler.peer_fingerprint != null) { if (!bytes_equal(dtls_srtp_handler.peer_fingerprint, peer_fingerprint)) { warning("Tried to replace certificate fingerprint mid use. We don't allow that."); peer_fingerprint = dtls_srtp_handler.peer_fingerprint; peer_fp_algo = dtls_srtp_handler.peer_fp_algo; } } else { dtls_srtp_handler.peer_fingerprint = peer_fingerprint; dtls_srtp_handler.peer_fp_algo = peer_fp_algo; } if (peer_setup == "passive") { dtls_srtp_handler.mode = DtlsSrtp.Mode.CLIENT; dtls_srtp_handler.stop_dtls_connection(); dtls_srtp_handler.setup_dtls_connection.begin((_, res) => { var content_encryption = dtls_srtp_handler.setup_dtls_connection.end(res); if (content_encryption != null) { this.content.encryptions[content_encryption.encryption_ns] = content_encryption; } }); } } else { dtls_srtp_handler = null; } } public override void handle_transport_info(StanzaNode transport) throws Jingle.IqError { debug("on_transport_info from %s", peer_full_jid.to_string()); base.handle_transport_info(transport); if (dtls_srtp_handler != null && peer_fingerprint != null) { if (dtls_srtp_handler.peer_fingerprint != null) { if (!bytes_equal(dtls_srtp_handler.peer_fingerprint, peer_fingerprint)) { warning("Tried to replace certificate fingerprint mid use. We don't allow that."); peer_fingerprint = dtls_srtp_handler.peer_fingerprint; peer_fp_algo = dtls_srtp_handler.peer_fp_algo; } } else { dtls_srtp_handler.peer_fingerprint = peer_fingerprint; dtls_srtp_handler.peer_fp_algo = peer_fp_algo; } } if (!we_want_connection) return; if (remote_ufrag != null && remote_pwd != null && !remote_credentials_set) { agent.set_remote_credentials(stream_id, remote_ufrag, remote_pwd); remote_credentials_set = true; } for (uint8 i = 1; i <= components; i++) { SList candidates = new SList(); foreach (JingleIceUdp.Candidate candidate in remote_candidates) { if (candidate.component == i) { candidates.append(candidate_to_nice(candidate)); } } int new_candidates = agent.set_remote_candidates(stream_id, i, candidates); debug("Updated to %i remote candidates for candidate %u via transport info", new_candidates, i); } } public override void create_transport_connection(XmppStream stream, Jingle.Content content) { debug("create_transport_connection: %s", content.session.sid); debug("local_credentials: %s %s", local_ufrag, local_pwd); debug("remote_credentials: %s %s", remote_ufrag, remote_pwd); debug("expected incoming credentials: %s %s", local_ufrag + ":" + remote_ufrag, local_pwd); debug("expected outgoing credentials: %s %s", remote_ufrag + ":" + local_ufrag, remote_pwd); we_want_connection = true; if (remote_ufrag != null && remote_pwd != null && !remote_credentials_set) { agent.set_remote_credentials(stream_id, remote_ufrag, remote_pwd); remote_credentials_set = true; } for (uint8 i = 1; i <= components; i++) { SList candidates = new SList(); foreach (JingleIceUdp.Candidate candidate in remote_candidates) { if (candidate.ip.has_prefix("fe80::")) continue; if (candidate.component == i) { candidates.append(candidate_to_nice(candidate)); debug("remote candidate: %s", agent.generate_local_candidate_sdp(candidate_to_nice(candidate))); } } int new_candidates = agent.set_remote_candidates(stream_id, i, candidates); debug("Initiated component %u with %i remote candidates", i, new_candidates); connections[i] = new DatagramConnection(agent, dtls_srtp_handler, stream_id, i); content.set_transport_connection(connections[i], i); } base.create_transport_connection(stream, content); } private void on_component_state_changed(uint stream_id, uint component_id, uint state) { if (stream_id != this.stream_id) return; debug("stream %u component %u state changed to %s", stream_id, component_id, agent.get_component_state(stream_id, component_id).to_string()); may_consider_ready(stream_id, component_id); if (incoming && dtls_srtp_handler != null && !dtls_srtp_handler.ready && is_component_ready(agent, stream_id, component_id) && dtls_srtp_handler.mode == DtlsSrtp.Mode.CLIENT) { dtls_srtp_handler.setup_dtls_connection.begin((_, res) => { Jingle.ContentEncryption? encryption = dtls_srtp_handler.setup_dtls_connection.end(res); if (encryption != null) { this.content.encryptions[encryption.encryption_ns] = encryption; } }); } } private void may_consider_ready(uint stream_id, uint component_id) { if (stream_id != this.stream_id) return; if (connections.has_key((uint8) component_id) && !connections[(uint8)component_id].ready && is_component_ready(agent, stream_id, component_id) && (dtls_srtp_handler == null || dtls_srtp_handler.ready)) { connections[(uint8)component_id].ready = true; } } private void on_initial_binding_request_received(uint stream_id) { if (stream_id != this.stream_id) return; debug("initial_binding_request_received"); } private void on_new_selected_pair_full(uint stream_id, uint component_id, Nice.Candidate p1, Nice.Candidate p2) { if (stream_id != this.stream_id) return; debug("new_selected_pair_full %u [%s, %s]", component_id, agent.generate_local_candidate_sdp(p1), agent.generate_local_candidate_sdp(p2)); } private void on_recv(Nice.Agent agent, uint stream_id, uint component_id, uint8[] data) { if (stream_id != this.stream_id) return; uint8[] decrypt_data = null; if (dtls_srtp_handler != null) { try { decrypt_data = dtls_srtp_handler.process_incoming_data(component_id, data); if (decrypt_data == null) return; } catch (Crypto.Error e) { warning("%s while on_recv stream %u component %u", e.message, stream_id, component_id); return; } } may_consider_ready(stream_id, component_id); if (connections.has_key((uint8) component_id)) { if (!connections[(uint8) component_id].ready) { debug("on_recv stream %u component %u when state %s", stream_id, component_id, agent.get_component_state(stream_id, component_id).to_string()); } connections[(uint8) component_id].datagram_received(new Bytes(decrypt_data ?? data)); } else { debug("on_recv stream %u component %u length %u", stream_id, component_id, data.length); } } private static Nice.Candidate candidate_to_nice(JingleIceUdp.Candidate c) { Nice.CandidateType type; switch (c.type_) { case JingleIceUdp.Candidate.Type.HOST: type = Nice.CandidateType.HOST; break; case JingleIceUdp.Candidate.Type.PRFLX: type = Nice.CandidateType.PEER_REFLEXIVE; break; case JingleIceUdp.Candidate.Type.RELAY: type = Nice.CandidateType.RELAYED; break; case JingleIceUdp.Candidate.Type.SRFLX: type = Nice.CandidateType.SERVER_REFLEXIVE; break; default: assert_not_reached(); } Nice.Candidate candidate = new Nice.Candidate(type); candidate.component_id = c.component; char[] foundation = new char[Nice.CANDIDATE_MAX_FOUNDATION]; Memory.copy(foundation, c.foundation.data, size_t.min(c.foundation.length, Nice.CANDIDATE_MAX_FOUNDATION - 1)); candidate.foundation = foundation; candidate.addr = Nice.Address(); candidate.addr.init(); candidate.addr.set_from_string(c.ip); candidate.addr.set_port(c.port); candidate.priority = c.priority; if (c.rel_addr != null) { candidate.base_addr = Nice.Address(); candidate.base_addr.init(); candidate.base_addr.set_from_string(c.rel_addr); candidate.base_addr.set_port(c.rel_port); } candidate.transport = Nice.CandidateTransport.UDP; return candidate; } private static JingleIceUdp.Candidate? candidate_to_jingle(Nice.Candidate nc) { JingleIceUdp.Candidate candidate = new JingleIceUdp.Candidate(); switch (nc.type) { case Nice.CandidateType.HOST: candidate.type_ = JingleIceUdp.Candidate.Type.HOST; break; case Nice.CandidateType.PEER_REFLEXIVE: candidate.type_ = JingleIceUdp.Candidate.Type.PRFLX; break; case Nice.CandidateType.RELAYED: candidate.type_ = JingleIceUdp.Candidate.Type.RELAY; break; case Nice.CandidateType.SERVER_REFLEXIVE: candidate.type_ = JingleIceUdp.Candidate.Type.SRFLX; break; default: assert_not_reached(); } candidate.component = (uint8) nc.component_id; candidate.foundation = ((string)nc.foundation).dup(); candidate.generation = 0; candidate.id = Random.next_int().to_string("%08x"); // TODO char[] res = new char[NICE_ADDRESS_STRING_LEN]; nc.addr.to_string(res); candidate.ip = (string) res; candidate.network = 0; // TODO candidate.port = (uint16) nc.addr.get_port(); candidate.priority = nc.priority; candidate.protocol = "udp"; if (nc.base_addr.is_valid() && !nc.base_addr.equal(nc.addr)) { res = new char[NICE_ADDRESS_STRING_LEN]; nc.base_addr.to_string(res); candidate.rel_addr = (string) res; candidate.rel_port = (uint16) nc.base_addr.get_port(); } if (candidate.ip.has_prefix("fe80::")) return null; return candidate; } public override void dispose() { base.dispose(); agent = null; dtls_srtp_handler = null; connections.clear(); if (thread_loop != null) { thread_loop.quit(); } } } dino-0.3.0/plugins/ice/src/util.vala0000644000000000000000000000103414202022370015761 0ustar rootrootusing Gee; namespace Dino.Plugins.Ice { internal static bool is_component_ready(Nice.Agent agent, uint stream_id, uint component_id) { var state = agent.get_component_state(stream_id, component_id); return state == Nice.ComponentState.CONNECTED || state == Nice.ComponentState.READY; } internal Gee.List get_local_ip_addresses() { Gee.List result = new ArrayList(); foreach (string ip_address in Nice.interfaces_get_local_ips(false)) { result.add(ip_address); } return result; } }dino-0.3.0/plugins/ice/vapi/0000755000000000000000000000000014202022370014311 5ustar rootrootdino-0.3.0/plugins/ice/vapi/gnutls.vapi0000644000000000000000000003667614202022370016530 0ustar rootroot[CCode (cprefix = "gnutls_", lower_case_cprefix = "gnutls_", cheader_filename = "gnutls/gnutls.h")] namespace GnuTLS { public int global_init(); [CCode (cname = "gnutls_pull_func", has_target = false)] public delegate ssize_t PullFunc(void* transport_ptr, [CCode (ctype = "void*", array_length_type="size_t")] uint8[] array); [CCode (cname = "gnutls_pull_timeout_func", has_target = false)] public delegate int PullTimeoutFunc(void* transport_ptr, uint ms); [CCode (cname = "gnutls_push_func", has_target = false)] public delegate ssize_t PushFunc(void* transport_ptr, [CCode (ctype = "void*", array_length_type="size_t")] uint8[] array); [CCode (cname = "gnutls_certificate_verify_function", has_target = false)] public delegate int VerifyFunc(Session session); [Compact] [CCode (cname = "struct gnutls_session_int", free_function = "gnutls_deinit")] public class Session { public static Session? create(int con_end) throws GLib.Error { Session result; var ret = init(out result, con_end); throw_if_error(ret); return result; } [CCode (cname = "gnutls_init")] private static int init(out Session session, int con_end); [CCode (cname = "gnutls_transport_set_push_function")] public void set_push_function(PushFunc func); [CCode (cname = "gnutls_transport_set_pull_function")] public void set_pull_function(PullFunc func); [CCode (cname = "gnutls_transport_set_pull_timeout_function")] public void set_pull_timeout_function(PullTimeoutFunc func); [CCode (cname = "gnutls_transport_set_ptr")] public void set_transport_pointer(void* ptr); [CCode (cname = "gnutls_transport_get_ptr")] public void* get_transport_pointer(); [CCode (cname = "gnutls_heartbeat_enable")] public int enable_heartbeat(uint type); [CCode (cname = "gnutls_certificate_server_set_request")] public void server_set_request(CertificateRequest req); [CCode (cname = "gnutls_credentials_set")] public int set_credentials_(CredentialsType type, void* cred); [CCode (cname = "gnutls_credentials_set_")] public void set_credentials(CredentialsType type, void* cred) throws GLib.Error { int err = set_credentials_(type, cred); throw_if_error(err); } [CCode (cname = "gnutls_priority_set_direct")] public int set_priority_from_string_(string priority, out unowned string err_pos = null); [CCode (cname = "gnutls_priority_set_direct_")] public void set_priority_from_string(string priority, out unowned string err_pos = null) throws GLib.Error { int err = set_priority_from_string_(priority, out err_pos); throw_if_error(err); } [CCode (cname = "gnutls_srtp_set_profile_direct")] public int set_srtp_profile_direct_(string profiles, out unowned string err_pos = null); [CCode (cname = "gnutls_srtp_set_profile_direct_")] public void set_srtp_profile_direct(string profiles, out unowned string err_pos = null) throws GLib.Error { int err = set_srtp_profile_direct_(profiles, out err_pos); throw_if_error(err); } [CCode (cname = "gnutls_transport_set_int")] public void transport_set_int(int fd); [CCode (cname = "gnutls_handshake")] public int handshake(); [CCode (cname = "gnutls_srtp_get_keys")] public int get_srtp_keys_(void *key_material, uint32 key_material_size, out Datum client_key, out Datum client_salt, out Datum server_key, out Datum server_salt); [CCode (cname = "gnutls_srtp_get_keys_")] public void get_srtp_keys(void *key_material, uint32 key_material_size, out Datum client_key, out Datum client_salt, out Datum server_key, out Datum server_salt) throws GLib.Error { get_srtp_keys_(key_material, key_material_size, out client_key, out client_salt, out server_key, out server_salt); } [CCode (cname = "gnutls_certificate_get_peers", array_length_type = "unsigned int")] public unowned Datum[]? get_peer_certificates(); [CCode (cname = "gnutls_session_set_verify_function")] public void set_verify_function(VerifyFunc func); } [Compact] [CCode (cname = "struct gnutls_certificate_credentials_st", free_function = "gnutls_certificate_free_credentials", cprefix = "gnutls_certificate_")] public class CertificateCredentials { [CCode (cname = "gnutls_certificate_allocate_credentials")] private static int allocate(out CertificateCredentials credentials); public static CertificateCredentials create() throws GLib.Error { CertificateCredentials result; var ret = allocate (out result); throw_if_error(ret); return result; } public void get_x509_crt(uint index, [CCode (array_length_type = "unsigned int")] out unowned X509.Certificate[] x509_ca_list); public int set_x509_key(X509.Certificate[] cert_list, X509.PrivateKey key); } [CCode (cheader_filename = "gnutls/x509.h", cprefix = "GNUTLS_")] namespace X509 { [Compact] [CCode (cname = "struct gnutls_x509_crt_int", cprefix = "gnutls_x509_crt_", free_function = "gnutls_x509_crt_deinit")] public class Certificate { [CCode (cname = "gnutls_x509_crt_init")] private static int init (out Certificate cert); public static Certificate create() throws GLib.Error { Certificate result; var ret = init (out result); throw_if_error(ret); return result; } [CCode (cname = "gnutls_x509_crt_import")] public int import_(ref Datum data, CertificateFormat format); [CCode (cname = "gnutls_x509_crt_import_")] public void import(ref Datum data, CertificateFormat format) throws GLib.Error { int err = import_(ref data, format); throw_if_error(err); } [CCode (cname = "gnutls_x509_crt_set_version")] public int set_version_(uint version); [CCode (cname = "gnutls_x509_crt_set_version_")] public void set_version(uint version) throws GLib.Error { int err = set_version_(version); throw_if_error(err); } [CCode (cname = "gnutls_x509_crt_set_key")] public int set_key_(PrivateKey key); [CCode (cname = "gnutls_x509_crt_set_key_")] public void set_key(PrivateKey key) throws GLib.Error { int err = set_key_(key); throw_if_error(err); } [CCode (cname = "gnutls_x509_crt_set_activation_time")] public int set_activation_time_(time_t act_time); [CCode (cname = "gnutls_x509_crt_set_activation_time_")] public void set_activation_time(time_t act_time) throws GLib.Error { int err = set_activation_time_(act_time); throw_if_error(err); } [CCode (cname = "gnutls_x509_crt_set_expiration_time")] public int set_expiration_time_(time_t exp_time); [CCode (cname = "gnutls_x509_crt_set_expiration_time_")] public void set_expiration_time(time_t exp_time) throws GLib.Error { int err = set_expiration_time_(exp_time); throw_if_error(err); } [CCode (cname = "gnutls_x509_crt_set_serial")] public int set_serial_(void* serial, size_t serial_size); [CCode (cname = "gnutls_x509_crt_set_serial_")] public void set_serial(void* serial, size_t serial_size) throws GLib.Error { int err = set_serial_(serial, serial_size); throw_if_error(err); } [CCode (cname = "gnutls_x509_crt_sign")] public int sign_(Certificate issuer, PrivateKey issuer_key); [CCode (cname = "gnutls_x509_crt_sign_")] public void sign(Certificate issuer, PrivateKey issuer_key) throws GLib.Error { int err = sign_(issuer, issuer_key); throw_if_error(err); } [CCode (cname = "gnutls_x509_crt_get_fingerprint")] public int get_fingerprint_(DigestAlgorithm algo, void* buf, ref size_t buf_size); [CCode (cname = "gnutls_x509_crt_get_fingerprint_")] public void get_fingerprint(DigestAlgorithm algo, void* buf, ref size_t buf_size) throws GLib.Error { int err = get_fingerprint_(algo, buf, ref buf_size); throw_if_error(err); } } [Compact] [CCode (cname = "struct gnutls_x509_privkey_int", cprefix = "gnutls_x509_privkey_", free_function = "gnutls_x509_privkey_deinit")] public class PrivateKey { private static int init (out PrivateKey key); public static PrivateKey create () throws GLib.Error { PrivateKey result; var ret = init (out result); throw_if_error(ret); return result; } public int generate(PKAlgorithm algo, uint bits, uint flags = 0); } } [CCode (cname = "gnutls_certificate_request_t", cprefix = "GNUTLS_CERT_", has_type_id = false)] public enum CertificateRequest { IGNORE, REQUEST, REQUIRE } [CCode (cname = "gnutls_pk_algorithm_t", cprefix = "GNUTLS_PK_", has_type_id = false)] public enum PKAlgorithm { UNKNOWN, RSA, DSA; } [CCode (cname = "gnutls_digest_algorithm_t", cprefix = "GNUTLS_DIG_", has_type_id = false)] public enum DigestAlgorithm { NULL, MD5, SHA1, RMD160, MD2, SHA224, SHA256, SHA384, SHA512; } [Flags] [CCode (cname = "gnutls_init_flags_t", cprefix = "GNUTLS_", has_type_id = false)] public enum InitFlags { SERVER, CLIENT, DATAGRAM } [CCode (cname = "gnutls_credentials_type_t", cprefix = "GNUTLS_CRD_", has_type_id = false)] public enum CredentialsType { CERTIFICATE, ANON, SRP, PSK, IA } [CCode (cname = "gnutls_x509_crt_fmt_t", cprefix = "GNUTLS_X509_FMT_", has_type_id = false)] public enum CertificateFormat { DER, PEM } [Flags] [CCode (cname = "gnutls_certificate_status_t", cprefix = "GNUTLS_CERT_", has_type_id = false)] public enum CertificateStatus { INVALID, // will be set if the certificate was not verified. REVOKED, // in X.509 this will be set only if CRLs are checked SIGNER_NOT_FOUND, SIGNER_NOT_CA, INSECURE_ALGORITHM } [SimpleType] [CCode (cname = "gnutls_datum_t", has_type_id = false)] public struct Datum { public uint8* data; public uint size; public uint8[] extract() { uint8[] ret = new uint8[size]; for (int i = 0; i < size; i++) { ret[i] = data[i]; } return ret; } } // Gnutls error codes. The mapping to a TLS alert is also shown in comments. [CCode (cname = "int", cprefix = "GNUTLS_E_", lower_case_cprefix = "gnutls_error_", has_type_id = false)] public enum ErrorCode { SUCCESS, UNKNOWN_COMPRESSION_ALGORITHM, UNKNOWN_CIPHER_TYPE, LARGE_PACKET, UNSUPPORTED_VERSION_PACKET, // GNUTLS_A_PROTOCOL_VERSION UNEXPECTED_PACKET_LENGTH, // GNUTLS_A_RECORD_OVERFLOW INVALID_SESSION, FATAL_ALERT_RECEIVED, UNEXPECTED_PACKET, // GNUTLS_A_UNEXPECTED_MESSAGE WARNING_ALERT_RECEIVED, ERROR_IN_FINISHED_PACKET, UNEXPECTED_HANDSHAKE_PACKET, UNKNOWN_CIPHER_SUITE, // GNUTLS_A_HANDSHAKE_FAILURE UNWANTED_ALGORITHM, MPI_SCAN_FAILED, DECRYPTION_FAILED, // GNUTLS_A_DECRYPTION_FAILED, GNUTLS_A_BAD_RECORD_MAC MEMORY_ERROR, DECOMPRESSION_FAILED, // GNUTLS_A_DECOMPRESSION_FAILURE COMPRESSION_FAILED, AGAIN, EXPIRED, DB_ERROR, SRP_PWD_ERROR, INSUFFICIENT_CREDENTIALS, HASH_FAILED, BASE64_DECODING_ERROR, MPI_PRINT_FAILED, REHANDSHAKE, // GNUTLS_A_NO_RENEGOTIATION GOT_APPLICATION_DATA, RECORD_LIMIT_REACHED, ENCRYPTION_FAILED, PK_ENCRYPTION_FAILED, PK_DECRYPTION_FAILED, PK_SIGN_FAILED, X509_UNSUPPORTED_CRITICAL_EXTENSION, KEY_USAGE_VIOLATION, NO_CERTIFICATE_FOUND, // GNUTLS_A_BAD_CERTIFICATE INVALID_REQUEST, SHORT_MEMORY_BUFFER, INTERRUPTED, PUSH_ERROR, PULL_ERROR, RECEIVED_ILLEGAL_PARAMETER, // GNUTLS_A_ILLEGAL_PARAMETER REQUESTED_DATA_NOT_AVAILABLE, PKCS1_WRONG_PAD, RECEIVED_ILLEGAL_EXTENSION, INTERNAL_ERROR, DH_PRIME_UNACCEPTABLE, FILE_ERROR, TOO_MANY_EMPTY_PACKETS, UNKNOWN_PK_ALGORITHM, // returned if libextra functionality was requested but // gnutls_global_init_extra() was not called. INIT_LIBEXTRA, LIBRARY_VERSION_MISMATCH, // returned if you need to generate temporary RSA // parameters. These are needed for export cipher suites. NO_TEMPORARY_RSA_PARAMS, LZO_INIT_FAILED, NO_COMPRESSION_ALGORITHMS, NO_CIPHER_SUITES, OPENPGP_GETKEY_FAILED, PK_SIG_VERIFY_FAILED, ILLEGAL_SRP_USERNAME, SRP_PWD_PARSING_ERROR, NO_TEMPORARY_DH_PARAMS, // For certificate and key stuff ASN1_ELEMENT_NOT_FOUND, ASN1_IDENTIFIER_NOT_FOUND, ASN1_DER_ERROR, ASN1_VALUE_NOT_FOUND, ASN1_GENERIC_ERROR, ASN1_VALUE_NOT_VALID, ASN1_TAG_ERROR, ASN1_TAG_IMPLICIT, ASN1_TYPE_ANY_ERROR, ASN1_SYNTAX_ERROR, ASN1_DER_OVERFLOW, OPENPGP_UID_REVOKED, CERTIFICATE_ERROR, CERTIFICATE_KEY_MISMATCH, UNSUPPORTED_CERTIFICATE_TYPE, // GNUTLS_A_UNSUPPORTED_CERTIFICATE X509_UNKNOWN_SAN, OPENPGP_FINGERPRINT_UNSUPPORTED, X509_UNSUPPORTED_ATTRIBUTE, UNKNOWN_HASH_ALGORITHM, UNKNOWN_PKCS_CONTENT_TYPE, UNKNOWN_PKCS_BAG_TYPE, INVALID_PASSWORD, MAC_VERIFY_FAILED, // for PKCS #12 MAC CONSTRAINT_ERROR, WARNING_IA_IPHF_RECEIVED, WARNING_IA_FPHF_RECEIVED, IA_VERIFY_FAILED, UNKNOWN_ALGORITHM, BASE64_ENCODING_ERROR, INCOMPATIBLE_CRYPTO_LIBRARY, INCOMPATIBLE_LIBTASN1_LIBRARY, OPENPGP_KEYRING_ERROR, X509_UNSUPPORTED_OID, RANDOM_FAILED, BASE64_UNEXPECTED_HEADER_ERROR, OPENPGP_SUBKEY_ERROR, CRYPTO_ALREADY_REGISTERED, HANDSHAKE_TOO_LARGE, UNIMPLEMENTED_FEATURE, APPLICATION_ERROR_MAX, // -65000 APPLICATION_ERROR_MIN; // -65500 [CCode (cname = "gnutls_error_is_fatal")] public bool is_fatal(); [CCode (cname = "gnutls_perror")] public void print(); [CCode (cname = "gnutls_strerror")] public unowned string to_string(); } public void throw_if_error(int err_int) throws GLib.Error { ErrorCode error = (ErrorCode)err_int; if (error != ErrorCode.SUCCESS) { throw new GLib.Error(-1, error, "%s%s", error.to_string(), error.is_fatal() ? " fatal" : ""); } } }dino-0.3.0/plugins/ice/vapi/metadata/0000755000000000000000000000000014202022370016071 5ustar rootrootdino-0.3.0/plugins/ice/vapi/metadata/Nice-0.1.metadata0000644000000000000000000000066414202022370020753 0ustar rootrootNice cheader_filename="nice.h" Address.to_string.dst type="char[]" Agent.new_reliable#constructor name="create_reliable" Agent.attach_recv skip=false Agent.send.buf type="uint8[]" array_length_idx=2 AgentRecvFunc.buf type="uint8[]" array_length_idx=3 PseudoTcpCallbacks#record skip PseudoTcpSocket#class skip # Not yet supported by vapigen # Candidate copy_function="nice_candidate_copy" free_function="nice_candidate_free" type_id="" dino-0.3.0/plugins/ice/vapi/nice.vapi0000644000000000000000000004100014202022370016103 0ustar rootroot/* nice.vapi generated by vapigen, do not modify. */ [CCode (cprefix = "Nice", gir_namespace = "Nice", gir_version = "0.1", lower_case_cprefix = "nice_")] namespace Nice { [CCode (cheader_filename = "nice.h", type_id = "nice_agent_get_type ()")] public class Agent : GLib.Object { [CCode (has_construct_function = false)] public Agent (GLib.MainContext ctx, Nice.Compatibility compat); public bool add_local_address (Nice.Address addr); public uint add_stream (uint n_components); public bool attach_recv (uint stream_id, uint component_id, GLib.MainContext ctx, Nice.AgentRecvFunc func); [Version (since = "0.1.16")] public async void close_async (); [CCode (cname = "nice_agent_new_reliable", has_construct_function = false)] [Version (since = "0.0.11")] public Agent.create_reliable (GLib.MainContext ctx, Nice.Compatibility compat); [Version (since = "0.1.6")] public bool forget_relays (uint stream_id, uint component_id); [CCode (has_construct_function = false)] [Version (since = "0.1.15")] public Agent.full (GLib.MainContext ctx, Nice.Compatibility compat, Nice.AgentOption flags); public bool gather_candidates (uint stream_id); [Version (since = "0.1.4")] public string generate_local_candidate_sdp (Nice.Candidate candidate); [Version (since = "0.1.4")] public string generate_local_sdp (); [Version (since = "0.1.4")] public string generate_local_stream_sdp (uint stream_id, bool include_non_ice); [Version (since = "0.1.8")] public Nice.ComponentState get_component_state (uint stream_id, uint component_id); public Nice.Candidate get_default_local_candidate (uint stream_id, uint component_id); [Version (since = "0.1.5")] public GLib.IOStream get_io_stream (uint stream_id, uint component_id); public GLib.SList get_local_candidates (uint stream_id, uint component_id); public bool get_local_credentials (uint stream_id, out string ufrag, out string pwd); public GLib.SList get_remote_candidates (uint stream_id, uint component_id); public bool get_selected_pair (uint stream_id, uint component_id, Nice.Candidate local, Nice.Candidate remote); [Version (since = "0.1.5")] public GLib.Socket? get_selected_socket (uint stream_id, uint component_id); [Version (since = "0.1.4")] public unowned string get_stream_name (uint stream_id); [Version (since = "0.1.4")] public Nice.Candidate parse_remote_candidate_sdp (uint stream_id, string sdp); [Version (since = "0.1.4")] public int parse_remote_sdp (string sdp); [Version (since = "0.1.4")] public GLib.SList parse_remote_stream_sdp (uint stream_id, string sdp, string ufrag, string pwd); [Version (since = "0.1.16")] public bool peer_candidate_gathering_done (uint stream_id); [Version (since = "0.1.5")] public ssize_t recv (uint stream_id, uint component_id, [CCode (array_length_cname = "buf_len", array_length_pos = 3.5, array_length_type = "gsize")] out unowned uint8[] buf, GLib.Cancellable? cancellable = null) throws GLib.Error; [Version (since = "0.1.5")] public int recv_messages (uint stream_id, uint component_id, [CCode (array_length_cname = "n_messages", array_length_pos = 3.5, array_length_type = "guint")] out unowned Nice.InputMessage[] messages, GLib.Cancellable? cancellable = null) throws GLib.Error; [Version (since = "0.1.5")] public int recv_messages_nonblocking (uint stream_id, uint component_id, [CCode (array_length_cname = "n_messages", array_length_pos = 3.5, array_length_type = "guint")] out unowned Nice.InputMessage[] messages, GLib.Cancellable? cancellable = null) throws GLib.Error; [Version (since = "0.1.5")] public ssize_t recv_nonblocking (uint stream_id, uint component_id, [CCode (array_length_cname = "buf_len", array_length_pos = 3.5, array_length_type = "gsize")] out unowned uint8[] buf, GLib.Cancellable? cancellable = null) throws GLib.Error; public void remove_stream (uint stream_id); public bool restart (); [Version (since = "0.1.6")] public bool restart_stream (uint stream_id); public int send (uint stream_id, uint component_id, [CCode (array_length_cname = "len", array_length_pos = 2.5, array_length_type = "guint", type = "const gchar*")] uint8[] buf); [Version (since = "0.1.5")] public int send_messages_nonblocking (uint stream_id, uint component_id, [CCode (array_length_cname = "n_messages", array_length_pos = 3.5, array_length_type = "guint")] Nice.OutputMessage[] messages, GLib.Cancellable? cancellable = null) throws GLib.Error; public bool set_local_credentials (uint stream_id, string ufrag, string pwd); public void set_port_range (uint stream_id, uint component_id, uint min_port, uint max_port); public bool set_relay_info (uint stream_id, uint component_id, string server_ip, uint server_port, string username, string password, Nice.RelayType type); public int set_remote_candidates (uint stream_id, uint component_id, GLib.SList candidates); public bool set_remote_credentials (uint stream_id, string ufrag, string pwd); public bool set_selected_pair (uint stream_id, uint component_id, string lfoundation, string rfoundation); public bool set_selected_remote_candidate (uint stream_id, uint component_id, Nice.Candidate candidate); [Version (since = "0.0.10")] public void set_software (string software); [Version (since = "0.1.4")] public bool set_stream_name (uint stream_id, string name); [Version (since = "0.0.9")] public void set_stream_tos (uint stream_id, int tos); [NoAccessorMethod] [Version (since = "0.1.8")] public bool bytestream_tcp { get; } [NoAccessorMethod] public uint compatibility { get; construct; } [NoAccessorMethod] public bool controlling_mode { get; set; } [NoAccessorMethod] [Version (since = "0.1.14")] public bool force_relay { get; set; } [NoAccessorMethod] public bool full_mode { get; construct; } [NoAccessorMethod] [Version (since = "0.1.8")] public bool ice_tcp { get; set; } [NoAccessorMethod] [Version (since = "0.1.16")] public bool ice_trickle { get; set; } [NoAccessorMethod] [Version (since = "0.1.8")] public bool ice_udp { get; set; } [NoAccessorMethod] [Version (since = "0.1.8")] public bool keepalive_conncheck { get; set; } [NoAccessorMethod] public void* main_context { get; construct; } [NoAccessorMethod] public uint max_connectivity_checks { get; set; } [NoAccessorMethod] [Version (since = "0.0.4")] public string proxy_ip { owned get; set; } [NoAccessorMethod] [Version (since = "0.0.4")] public string proxy_password { owned get; set; } [NoAccessorMethod] [Version (since = "0.0.4")] public uint proxy_port { get; set; } [NoAccessorMethod] [Version (since = "0.0.4")] public uint proxy_type { get; set; } [NoAccessorMethod] [Version (since = "0.0.4")] public string proxy_username { owned get; set; } [NoAccessorMethod] [Version (since = "0.0.11")] public bool reliable { get; construct; } [NoAccessorMethod] [Version (since = "0.1.15")] public uint stun_initial_timeout { get; set construct; } [NoAccessorMethod] [Version (since = "0.1.15")] public uint stun_max_retransmissions { get; set construct; } [NoAccessorMethod] public uint stun_pacing_timer { get; set construct; } [NoAccessorMethod] [Version (since = "0.1.15")] public uint stun_reliable_timeout { get; set construct; } [NoAccessorMethod] public string stun_server { owned get; set; } [NoAccessorMethod] public uint stun_server_port { get; set; } [NoAccessorMethod] public bool support_renomination { get; set; } [NoAccessorMethod] [Version (since = "0.0.7")] public bool upnp { get; set construct; } [NoAccessorMethod] [Version (since = "0.0.7")] public uint upnp_timeout { get; set construct; } public signal void candidate_gathering_done (uint stream_id); public signal void component_state_changed (uint stream_id, uint component_id, uint state); public signal void initial_binding_request_received (uint stream_id); [Version (deprecated = true, deprecated_since = "0.1.8")] public signal void new_candidate (uint stream_id, uint component_id, string foundation); [Version (since = "0.1.8")] public signal void new_candidate_full (Nice.Candidate candidate); [Version (deprecated = true, deprecated_since = "0.1.8")] public signal void new_remote_candidate (uint stream_id, uint component_id, string foundation); [Version (since = "0.1.8")] public signal void new_remote_candidate_full (Nice.Candidate candidate); [Version (deprecated = true, deprecated_since = "0.1.8")] public signal void new_selected_pair (uint stream_id, uint component_id, string lfoundation, string rfoundation); [Version (since = "0.1.8")] public signal void new_selected_pair_full (uint stream_id, uint component_id, Nice.Candidate lcandidate, Nice.Candidate rcandidate); [Version (since = "0.0.11")] public signal void reliable_transport_writable (uint stream_id, uint component_id); [Version (since = "0.1.5")] public signal void streams_removed ([CCode (array_length = false, array_null_terminated = true)] uint[] stream_ids); } [CCode (cheader_filename = "nice.h", copy_function = "nice_candidate_copy", free_function = "nice_candidate_free")] [Compact] public class Candidate { public Nice.Address addr; public Nice.Address base_addr; public uint component_id; [CCode (array_length = false)] public weak char foundation[33]; public weak string password; public uint32 priority; public void* sockptr; public uint stream_id; public Nice.CandidateTransport transport; public Nice.TurnServer turn; public Nice.CandidateType type; public weak string username; [CCode (has_construct_function = false)] public Candidate (Nice.CandidateType type); public Nice.Candidate copy (); [Version (since = "0.1.15")] public bool equal_target (Nice.Candidate candidate2); public void free (); } [CCode (cheader_filename = "nice.h", has_type_id = false)] public struct Address { [CCode (cname = "s.addr")] public void* s_addr; [CCode (cname = "s.ip4")] public void* s_ip4; [CCode (cname = "s.ip6")] public void* s_ip6; public void copy_to_sockaddr (void* sin); public bool equal (Nice.Address b); [Version (since = "0.1.8")] public bool equal_no_port (Nice.Address b); public void free (); public uint get_port (); public void init (); public int ip_version (); public bool is_private (); public bool is_valid (); public void set_from_sockaddr (void* sin); public bool set_from_string (string str); public void set_ipv4 (uint32 addr_ipv4); public void set_ipv6 (uint8 addr_ipv6); public void set_port (uint port); public void to_string ([CCode (array_length = false, type = "gchar*")] char[] dst); } [CCode (cheader_filename = "nice.h", has_type_id = false)] [Version (since = "0.1.5")] public struct InputMessage { [CCode (array_length_cname = "n_buffers")] public weak GLib.InputVector[] buffers; public int n_buffers; public Nice.Address from; public size_t length; } [CCode (cheader_filename = "nice.h", has_type_id = false)] [Version (since = "0.1.5")] public struct OutputMessage { [CCode (array_length_cname = "n_buffers")] public weak GLib.OutputVector[] buffers; public int n_buffers; } [CCode (cheader_filename = "nice.h", cname = "TurnServer", has_type_id = false)] public struct TurnServer { public int ref_count; public Nice.Address server; public weak string username; public weak string password; public Nice.RelayType type; } [CCode (cheader_filename = "nice.h", cprefix = "NICE_AGENT_OPTION_", has_type_id = false)] [Flags] [Version (since = "0.1.15")] public enum AgentOption { REGULAR_NOMINATION, RELIABLE, LITE_MODE, ICE_TRICKLE, SUPPORT_RENOMINATION } [CCode (cheader_filename = "nice.h", cprefix = "NICE_CANDIDATE_TRANSPORT_", has_type_id = false)] public enum CandidateTransport { UDP, TCP_ACTIVE, TCP_PASSIVE, TCP_SO } [CCode (cheader_filename = "nice.h", cprefix = "NICE_CANDIDATE_TYPE_", has_type_id = false)] public enum CandidateType { HOST, SERVER_REFLEXIVE, PEER_REFLEXIVE, RELAYED } [CCode (cheader_filename = "nice.h", cprefix = "NICE_COMPATIBILITY_", has_type_id = false)] public enum Compatibility { RFC5245, DRAFT19, GOOGLE, MSN, WLM2009, OC2007, OC2007R2, LAST } [CCode (cheader_filename = "nice.h", cprefix = "NICE_COMPONENT_STATE_", has_type_id = false)] public enum ComponentState { DISCONNECTED, GATHERING, CONNECTING, CONNECTED, READY, FAILED, LAST; [Version (since = "0.1.6")] public unowned string to_string (); } [CCode (cheader_filename = "nice.h", cprefix = "NICE_COMPONENT_TYPE_", has_type_id = false)] public enum ComponentType { RTP, RTCP } [CCode (cheader_filename = "nice.h", cprefix = "NICE_NOMINATION_MODE_", has_type_id = false)] [Version (since = "0.1.15")] public enum NominationMode { REGULAR, AGGRESSIVE } [CCode (cheader_filename = "nice.h", cprefix = "NICE_PROXY_TYPE_", has_type_id = false)] [Version (since = "0.0.4")] public enum ProxyType { NONE, SOCKS5, HTTP, LAST } [CCode (cheader_filename = "nice.h", cname = "PseudoTcpDebugLevel", cprefix = "PSEUDO_TCP_DEBUG_", has_type_id = false)] [Version (since = "0.0.11")] public enum PseudoTcpDebugLevel { NONE, NORMAL, VERBOSE } [CCode (cheader_filename = "nice.h", cname = "PseudoTcpShutdown", cprefix = "PSEUDO_TCP_SHUTDOWN_", has_type_id = false)] [Version (since = "0.1.8")] public enum PseudoTcpShutdown { RD, WR, RDWR } [CCode (cheader_filename = "nice.h", cname = "PseudoTcpState", cprefix = "PSEUDO_TCP_", has_type_id = false)] [Version (since = "0.0.11")] public enum PseudoTcpState { LISTEN, SYN_SENT, SYN_RECEIVED, ESTABLISHED, CLOSED, FIN_WAIT_1, FIN_WAIT_2, CLOSING, TIME_WAIT, CLOSE_WAIT, LAST_ACK } [CCode (cheader_filename = "nice.h", cname = "PseudoTcpWriteResult", cprefix = "WR_", has_type_id = false)] [Version (since = "0.0.11")] public enum PseudoTcpWriteResult { SUCCESS, TOO_LARGE, FAIL } [CCode (cheader_filename = "nice.h", cprefix = "NICE_RELAY_TYPE_TURN_", has_type_id = false)] public enum RelayType { UDP, TCP, TLS } [CCode (cheader_filename = "nice.h", instance_pos = 4.9)] public delegate void AgentRecvFunc (Nice.Agent agent, uint stream_id, uint component_id, [CCode (array_length_cname = "len", array_length_pos = 3.5, array_length_type = "guint", type = "gchar*")] uint8[] buf); [CCode (cheader_filename = "nice.h", cname = "NICE_AGENT_MAX_REMOTE_CANDIDATES")] public const int AGENT_MAX_REMOTE_CANDIDATES; [CCode (cheader_filename = "nice.h", cname = "NICE_CANDIDATE_DIRECTION_MS_PREF_ACTIVE")] public const int CANDIDATE_DIRECTION_MS_PREF_ACTIVE; [CCode (cheader_filename = "nice.h", cname = "NICE_CANDIDATE_DIRECTION_MS_PREF_PASSIVE")] public const int CANDIDATE_DIRECTION_MS_PREF_PASSIVE; [CCode (cheader_filename = "nice.h", cname = "NICE_CANDIDATE_MAX_FOUNDATION")] public const int CANDIDATE_MAX_FOUNDATION; [CCode (cheader_filename = "nice.h", cname = "NICE_CANDIDATE_TRANSPORT_MS_PREF_TCP")] public const int CANDIDATE_TRANSPORT_MS_PREF_TCP; [CCode (cheader_filename = "nice.h", cname = "NICE_CANDIDATE_TRANSPORT_MS_PREF_UDP")] public const int CANDIDATE_TRANSPORT_MS_PREF_UDP; [CCode (cheader_filename = "nice.h", cname = "NICE_CANDIDATE_TYPE_PREF_HOST")] public const int CANDIDATE_TYPE_PREF_HOST; [CCode (cheader_filename = "nice.h", cname = "NICE_CANDIDATE_TYPE_PREF_NAT_ASSISTED")] public const int CANDIDATE_TYPE_PREF_NAT_ASSISTED; [CCode (cheader_filename = "nice.h", cname = "NICE_CANDIDATE_TYPE_PREF_PEER_REFLEXIVE")] public const int CANDIDATE_TYPE_PREF_PEER_REFLEXIVE; [CCode (cheader_filename = "nice.h", cname = "NICE_CANDIDATE_TYPE_PREF_RELAYED")] public const int CANDIDATE_TYPE_PREF_RELAYED; [CCode (cheader_filename = "nice.h", cname = "NICE_CANDIDATE_TYPE_PREF_RELAYED_UDP")] public const int CANDIDATE_TYPE_PREF_RELAYED_UDP; [CCode (cheader_filename = "nice.h", cname = "NICE_CANDIDATE_TYPE_PREF_SERVER_REFLEXIVE")] public const int CANDIDATE_TYPE_PREF_SERVER_REFLEXIVE; [CCode (cheader_filename = "nice.h")] public static void debug_disable (bool with_stun); [CCode (cheader_filename = "nice.h")] public static void debug_enable (bool with_stun); [CCode (cheader_filename = "nice.h")] public static string? interfaces_get_ip_for_interface (string interface_name); [CCode (cheader_filename = "nice.h")] public static GLib.List interfaces_get_local_interfaces (); [CCode (cheader_filename = "nice.h")] public static GLib.List interfaces_get_local_ips (bool include_loopback); [CCode (cheader_filename = "nice.h", cname = "pseudo_tcp_set_debug_level")] [Version (since = "0.0.11")] public static void pseudo_tcp_set_debug_level (Nice.PseudoTcpDebugLevel level); } dino-0.3.0/plugins/notification-sound/0000755000000000000000000000000014202022370016426 5ustar rootrootdino-0.3.0/plugins/notification-sound/CMakeLists.txt0000644000000000000000000000146414202022370021173 0ustar rootrootfind_packages(NOTIFICATION_SOUND_PACKAGES REQUIRED Canberra Gee GLib GModule GObject GDKPixbuf2 ) vala_precompile(NOTIFICATION_SOUND_VALA_C SOURCES src/plugin.vala src/register_plugin.vala CUSTOM_VAPIS ${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi ${CMAKE_BINARY_DIR}/exports/dino.vapi ${CMAKE_BINARY_DIR}/exports/qlite.vapi PACKAGES ${NOTIFICATION_SOUND_PACKAGES} ) add_definitions(${VALA_CFLAGS}) add_library(notification-sound SHARED ${NOTIFICATION_SOUND_VALA_C}) target_link_libraries(notification-sound libdino ${NOTIFICATION_SOUND_PACKAGES}) set_target_properties(notification-sound PROPERTIES PREFIX "") set_target_properties(notification-sound PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/plugins/) install(TARGETS notification-sound ${PLUGIN_INSTALL}) dino-0.3.0/plugins/notification-sound/src/0000755000000000000000000000000014202022370017215 5ustar rootrootdino-0.3.0/plugins/notification-sound/src/plugin.vala0000644000000000000000000000114314202022370021357 0ustar rootrootnamespace Dino.Plugins.NotificationSound { public class Plugin : RootInterface, Object { public Dino.Application app; private Canberra.Context sound_context; public void registered(Dino.Application app) { this.app = app; Canberra.Context.create(out sound_context); app.stream_interactor.get_module(NotificationEvents.IDENTITY).notify_content_item.connect((item, conversation) => { sound_context.play(0, Canberra.PROP_EVENT_ID, "message-new-instant", Canberra.PROP_EVENT_DESCRIPTION, "New Dino message"); }); } public void shutdown() { } } } dino-0.3.0/plugins/notification-sound/src/register_plugin.vala0000644000000000000000000000015214202022370023262 0ustar rootrootpublic Type register_plugin(Module module) { return typeof (Dino.Plugins.NotificationSound.Plugin); } dino-0.3.0/plugins/omemo/0000755000000000000000000000000014202022370013726 5ustar rootrootdino-0.3.0/plugins/omemo/CMakeLists.txt0000644000000000000000000000514614202022370016474 0ustar rootrootset(GETTEXT_PACKAGE "dino-omemo") find_package(Gettext) include(${GETTEXT_USE_FILE}) gettext_compile(${GETTEXT_PACKAGE} SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/po TARGET_NAME ${GETTEXT_PACKAGE}-translations) find_package(Qrencode REQUIRED) find_packages(OMEMO_PACKAGES REQUIRED Gee GLib GModule GObject GTK3 ) set(RESOURCE_LIST contact_details_dialog.ui manage_key_dialog.ui ) compile_gresources( OMEMO_GRESOURCES_TARGET OMEMO_GRESOURCES_XML TARGET ${CMAKE_CURRENT_BINARY_DIR}/resources/resources.c TYPE EMBED_C RESOURCES ${RESOURCE_LIST} PREFIX /im/dino/Dino/omemo SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/data ) vala_precompile(OMEMO_VALA_C SOURCES src/dtls_srtp_verification_draft.vala src/plugin.vala src/register_plugin.vala src/trust_level.vala src/file_transfer/file_decryptor.vala src/file_transfer/file_encryptor.vala src/jingle/jingle_helper.vala src/jingle/jet_omemo.vala src/logic/database.vala src/logic/decrypt.vala src/logic/encrypt.vala src/logic/manager.vala src/logic/pre_key_store.vala src/logic/session_store.vala src/logic/signed_pre_key_store.vala src/logic/trust_manager.vala src/protocol/bundle.vala src/protocol/message_flag.vala src/protocol/stream_module.vala src/ui/account_settings_entry.vala src/ui/account_settings_widget.vala src/ui/bad_messages_populator.vala src/ui/call_encryption_entry.vala src/ui/contact_details_provider.vala src/ui/contact_details_dialog.vala src/ui/device_notification_populator.vala src/ui/own_notifications.vala src/ui/encryption_list_entry.vala src/ui/manage_key_dialog.vala src/ui/util.vala CUSTOM_VAPIS ${CMAKE_BINARY_DIR}/exports/crypto-vala.vapi ${CMAKE_BINARY_DIR}/exports/signal-protocol.vapi ${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi ${CMAKE_BINARY_DIR}/exports/qlite.vapi ${CMAKE_BINARY_DIR}/exports/dino.vapi ${CMAKE_CURRENT_SOURCE_DIR}/vapi/libqrencode.vapi PACKAGES ${OMEMO_PACKAGES} GRESOURCES ${OMEMO_GRESOURCES_XML} ) add_definitions(${VALA_CFLAGS} -DGETTEXT_PACKAGE=\"${GETTEXT_PACKAGE}\" -DLOCALE_INSTALL_DIR=\"${LOCALE_INSTALL_DIR}\" -DG_LOG_DOMAIN="OMEMO") add_library(omemo SHARED ${OMEMO_VALA_C} ${OMEMO_GRESOURCES_TARGET}) add_dependencies(omemo ${GETTEXT_PACKAGE}-translations) target_link_libraries(omemo libdino signal-protocol-vala crypto-vala ${OMEMO_PACKAGES} libqrencode) set_target_properties(omemo PROPERTIES PREFIX "") set_target_properties(omemo PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/plugins/) install(TARGETS omemo ${PLUGIN_INSTALL}) dino-0.3.0/plugins/omemo/data/0000755000000000000000000000000014202022370014637 5ustar rootrootdino-0.3.0/plugins/omemo/data/contact_details_dialog.ui0000644000000000000000000004614114202022370021663 0ustar rootroot False show_qrcode_button left True True 10 True dino-0.3.0/plugins/omemo/data/manage_key_dialog.ui0000644000000000000000000002303414202022370020617 0ustar rootroot dino-0.3.0/plugins/omemo/po/0000755000000000000000000000000014202022370014344 5ustar rootrootdino-0.3.0/plugins/omemo/po/LINGUAS0000644000000000000000000000016514202022370015373 0ustar rootrootar ca cs de el en eo es eu fa fi fr gl hu id ie it ja lb lt nb nl nl_BE oc pl pt pt_BR ro ru sq sv tr uk zh_CN zh_TW dino-0.3.0/plugins/omemo/po/ar.po0000644000000000000000000003100714202022370015307 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2021-08-31 23:44+0000\n" "Language-Team: Arabic \n" "Language: ar\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " "&& n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n" "X-Generator: Weblate 4.8.1-dev\n" #: plugins/omemo/src/ui/bad_messages_populator.vala:157 #, c-format msgid "" "%s has been using an untrusted device. You won't see messages from devices " "that you do not trust." msgstr "يستخدم %s جهازًا غير موثوق به. لن ترى رسائل من أجهزة لا تثق بها." #: plugins/omemo/src/ui/bad_messages_populator.vala:158 msgid "Manage devices" msgstr "إدارة الأجهزة" #: plugins/omemo/src/ui/bad_messages_populator.vala:160 #, c-format msgid "" "%s does not trust this device. That means, you might be missing messages." msgstr "%s لا يثق بهذا الجهاز. هذا يعني أنك قد تفقد بعض الرسائل." #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "إدارة المفتاح" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contact's device." msgstr "قارن البصمة حرفا بحرف ، مع التي تظهر على جهاز مُراسِلك." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Fingerprints differ" msgstr "البصمات غير متطابقة" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Fingerprints match" msgstr "البصمات متطابقة" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "ألغاء" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "تأكيد" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "التحقق مِن المفتاح" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Future messages sent by %s from the device that uses this key will be " "highlighted accordingly in the chat window." msgstr "" "سيتم تمييز الرسائل القادمة المرسلة بواسطة %s من الجهاز الذي يستخدم هذا " "المفتاح وفقًا لذلك في نافذة الدردشة." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "البصمات غير متطابقة" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "يرجى التحقق بالمقارنة مع البصمة الصحيحة.إن عدم تطابق البصمات يعني أنّه مِن " "المحتمل أنّ حساب %s قد تعرض لاختراق وأنه مِن المستحسن التفكير في رفض هذا " "المفتاح." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "التحقق مِن مفتاح البصمة" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "قارن بصمة هذا المفتاح مع البصمة المعروضة على جهاز مُراسِلك." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "رفض المفتاح" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Block encrypted communication with the contact's device that uses this key." msgstr "حظر الاتصال المشفر مع جهاز المراسل الذي يستخدم هذا المفتاح." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "اقبل المفتاح" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Allow encrypted communication with the contact's device that uses this key." msgstr "السماح بالاتصال المشفر بجهاز المراسل الذي يستخدم هذا المفتاح." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "المفتاح الآن %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "مقبول" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send encrypted messages." msgstr "ذلك يعني أنه بإمكان %s أن يستخدمه لتلقي وإرسال رسائل مشفرة." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "تم التحقق منه" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "بالإضافة إلى أنه قد تم التحقق من تطابق المفتاح على جهاز المُراسِل." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "مرفوض" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to decipher your messages, and you won't " "see messages encrypted with it." msgstr "" "هذا يعني أنه لن يكون بمقدور %s استخدامه لفك تشفير رسائلك، ولن ترى الرسائل " "المشفرة به." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "You won't see encrypted messages from the device of %s that uses this key. " "Conversely, that device won't be able to decipher your messages anymore." msgstr "" "لن ترى الرسائل المشفرة من جهاز %s الذي يستخدم هذا المفتاح. على العكس من " "ذلك ، لن يتمكن هذا الجهاز من فك تشفير رسائلك بعد الآن." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "You will be able to exchange encrypted messages with the device of %s that " "uses this key." msgstr "" "سوف يكون بمقدورك تبادل الرسائل المشفرة مع جهاز %s الذي يستخدم هذا المفتاح." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "العودة" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "الإدارة" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "لدى هذا المُراسِل أجهزة جديدة" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "بصمتك الخاصة" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connection" msgstr "سيتم توليدها عند أول اتصال" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "مطلوب قرار ثقة مع OMEMO" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "هل قمت بإضافة جهاز جديد على حساب %s؟" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "التشفير" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d لا يوجد جهاز OMEMO" msgstr[1] "%d جهاز OMEMO" msgstr[2] "جهازين ل OMEMO" msgstr[3] "%d أجهزة OMEMO" msgstr[4] "%d جهاز OMEMO" msgstr[5] "%d أجهزة OMEMO" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "إدارة مفاتيح أوميمو" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "تقبّل المفاتيح الجديدة تلقائياً" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "New encryption keys from this contact will be accepted automatically." msgstr "سيتم قبول مفاتيح التشفير الجديدة الخاصة بهذا المراسل تلقائيًا." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "مفتاحك الخاص" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "المفاتيح الجديدة" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "المفاتيح المقترِنة" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "المفاتيح غير المُنشَّطة" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "New encryption keys from your other devices will be accepted automatically." msgstr "سيتم قبول مفاتيح التشفير الجديدة من أجهزتك الأخرى تلقائيًا." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "تم قبوله" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "تم رفضه" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "تم التحقق منه" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "غير مستخدَم" #~ msgid "Not matching" #~ msgstr "غير مطابقة" #~ msgid "Matching" #~ msgstr "مطابقة" #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "highlighted accordingly in the chat window." #~ msgstr "" #~ "بمجرد تأكيد ذلك ، سيتم تمييز أية رسائل مستقبلية مرسلة مِن %s باستخدام هذا " #~ "المفتاح وفقا لذلك في نافذة الدردشة." #~ msgid "" #~ "Stop accepting this key during communication with its associated contact." #~ msgstr "توقف عن قبول هذا المفتاح أثناء الاتصال مع المُراسِل المرتبط به." #~ msgid "" #~ "Start accepting this key during communication with its associated contact" #~ msgstr "ابدأ في قبول هذا المفتاح أثناء الاتصال مع المُراسِل المرتبط به" #~ msgid "" #~ "This means it cannot be used by %s to receive messages, and any messages " #~ "sent by it will be ignored." #~ msgstr "" #~ "هذا يعني أنه لا يمكن استخدامه مِن طرف %s لاستقبال الرسائل و سيتم تجاهل أية " #~ "رسائل بُعِثت به." #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "ignored and none of your messages will be readable using this key." #~ msgstr "" #~ "بمجرد التأكيد ، سيتم تجاهل أي رسائل مستقبلية يقوم بإرسالها %s باستخدام " #~ "هذا المفتاح ويتصبح كافة رسائلك غير قابلة للقراءة باستخدام هذا المفتاح." #~ msgid "" #~ "Once confirmed this key will be usable by %s to receive and send messages." #~ msgstr "" #~ "بمجرد تأكيد هذا المفتاح سوف يكون قابلا للاستخدام من قِبل %s لتلقي وإرسال " #~ "الرسائل." #~ msgid "" #~ "When this contact adds new encryption keys to their account, " #~ "automatically accept them." #~ msgstr "" #~ "عند قيام جهة الاتصال هذه باضافة مفاتيح تشفير جديدة إلى حسابها ، قم " #~ "بقبولها تلقائيا." #~ msgid "" #~ "When you add new encryption keys to your account, automatically accept " #~ "them." #~ msgstr "عند إضافة مفاتيح تشفير جديدة إلى حسابك ، اقبلها تلقائيا." #~ msgid "Unknown device (0x%.8x)" #~ msgstr "جهاز مجهول (0x%.8x)" #~ msgid "Other devices" #~ msgstr "أجهزة أخرى" #~ msgid "- None -" #~ msgstr "- لا شيء -" dino-0.3.0/plugins/omemo/po/ca.po0000644000000000000000000002005014202022370015264 0ustar rootroot# Catalan translation for dino-omemo. # This file is distributed under the same license as the dino package. # Jordi Mallach , 2018. # msgid "" msgstr "" "Project-Id-Version: dino-omemo 20180123\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-01-14 05:21+0000\n" "Language-Team: Catalan \n" "Language: ca\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: Weblate 3.11-dev\n" #: plugins/omemo/src/ui/bad_messages_populator.vala:157 #, c-format msgid "" "%s has been using an untrusted device. You won't see messages from devices " "that you do not trust." msgstr "" #: plugins/omemo/src/ui/bad_messages_populator.vala:158 msgid "Manage devices" msgstr "" #: plugins/omemo/src/ui/bad_messages_populator.vala:160 #, c-format msgid "" "%s does not trust this device. That means, you might be missing messages." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Gestiona la clau" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contact's device." msgstr "" "Compareu l'empremta, caràcter per caràcter, amb la que es mostra al vostre " "dispositiu de contactes." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Fingerprints differ" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Fingerprints match" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Cancel·la" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Confirma" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Comproveu la clau" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Future messages sent by %s from the device that uses this key will be " "highlighted accordingly in the chat window." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Les empremtes no es corresponen" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Cercioreu-vos que estigueu verificant l'empremta correcta. Si no en " "coincideixen, el compte de %s podria trobar-se en perill i heu de considerar " "rebutjar aquesta clau." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 #, fuzzy msgid "Verify key fingerprint" msgstr "Emprempta pròpia" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Rebutja la clau" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Block encrypted communication with the contact's device that uses this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Accepta la clau" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Allow encrypted communication with the contact's device that uses this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Actualment, aquesta clau ha estat %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "acceptada" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send encrypted messages." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "verificada" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "rebutjada" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to decipher your messages, and you won't " "see messages encrypted with it." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "You won't see encrypted messages from the device of %s that uses this key. " "Conversely, that device won't be able to decipher your messages anymore." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "You will be able to exchange encrypted messages with the device of %s that " "uses this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Enrere" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Gestiona" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Aquest contacte té dispositius nous" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Empremta pròpia" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connection" msgstr "Es generarà durant la primera connexió" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "Heu afegit un dispositiu nou per al compte %s?" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Xifratge" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d dispositiu OMEMO" msgstr[1] "%d dispositius OMEMO" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "Gestió de claus OMEMO" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Accepta claus noves automàticament" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "New encryption keys from this contact will be accepted automatically." msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Clau pròpia" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Claus noves" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Claus associades" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "Claus inactives" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "New encryption keys from your other devices will be accepted automatically." msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Acceptada" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Rebutjada" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Verificada" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "No utilitzada" #~ msgid "Not matching" #~ msgstr "No coincideixen" #~ msgid "Matching" #~ msgstr "Coincideixen" #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "highlighted accordingly in the chat window." #~ msgstr "" #~ "Després de la confirmació, qualssevol missatges que enviï %s amb aquesta " #~ "clau es ressaltarà a la finestra del xat." #~ msgid "Unknown device (0x%.8x)" #~ msgstr "Dispositiu desconegut (0x%.8x)" #~ msgid "Other devices" #~ msgstr "Altres dispositius" #~ msgid "- None -" #~ msgstr "- Cap -" dino-0.3.0/plugins/omemo/po/cs.po0000644000000000000000000002200514202022370015310 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2021-07-05 16:32+0000\n" "Language-Team: none\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" "X-Generator: Weblate 4.8-dev\n" #: plugins/omemo/src/ui/bad_messages_populator.vala:157 #, c-format msgid "" "%s has been using an untrusted device. You won't see messages from devices " "that you do not trust." msgstr "" "%s používá nedůvěryhodné zařízení. Zprávy z nedůvěryhodných zařízení nebudou " "zobrazeny." #: plugins/omemo/src/ui/bad_messages_populator.vala:158 msgid "Manage devices" msgstr "Správa zařízení" #: plugins/omemo/src/ui/bad_messages_populator.vala:160 #, c-format msgid "" "%s does not trust this device. That means, you might be missing messages." msgstr "" "%s nedůvěřuje tomuto zařízení. Z tohoto důvodu nemusí vidět některé zprávy." #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Spravovat klíč" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contact's device." msgstr "" "Porovnejte otisk klíče, znak po znaku, s tím, který je zobrazen na zařízení " "vašeho kontaktu." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Fingerprints differ" msgstr "Otisky klíčů se liší" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Fingerprints match" msgstr "Otisky klíčů odpovídají" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Zrušit" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Potvrdit" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Ověřit klíč" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Future messages sent by %s from the device that uses this key will be " "highlighted accordingly in the chat window." msgstr "" "Budoucí zprávy, s odesílatelem %s, ze zařízení, které používá tento klíč, " "budou odpovídajícím způsobem zvýrazněny v okně chatu." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Otisky klíčů se neshodují" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Ověřte, zda porovnáváte správný otisk klíče. Pokud se otisky neshodují, účet " "%s může být ohrožen a měli byste zvážit zamítnutí tohoto klíče." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Ověření otisku klíče" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "Porovnejte otisk tohoto klíče s otiskem zobrazeným na zařízení kontaktu." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Odmítnout klíč" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Block encrypted communication with the contact's device that uses this key." msgstr "" "Zablokujte šifrovanou komunikaci se zařízením kontaktu, které tento klíč " "používá." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Přijmout klíč" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Allow encrypted communication with the contact's device that uses this key." msgstr "" "Povolit šifrovanou komunikaci se zařízením kontaktu, které používá tento " "klíč." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Tento klíč je v současné době %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "akceptován" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send encrypted messages." msgstr "" "To znamená, že jej %s může používat k přijímání a odesílání šifrovaných " "zpráv." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "ověřen" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "Navíc bylo ověřeno, že odpovídá klíči v zařízení kontaktu." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "odmítnut" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to decipher your messages, and you won't " "see messages encrypted with it." msgstr "" "To znamená, že jej %s nemůže použít k dešifrování zpráv a vy neuvidíte " "zprávy šifrované tímto klíčem." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "You won't see encrypted messages from the device of %s that uses this key. " "Conversely, that device won't be able to decipher your messages anymore." msgstr "" "Ze zařízení %s, které používá tento klíč, se šifrované zprávy nezobrazí. " "Toto zařízení už také nebude schopno rozluštit vaše zprávy." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "You will be able to exchange encrypted messages with the device of %s that " "uses this key." msgstr "" "Budete si moci vyměňovat šifrované zprávy se zařízením %s, které tento klíč " "používá." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Zpět" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Spravovat" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Tento kontakt má nová zařízení" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Otisk vlastního klíče" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connection" msgstr "Budou generovány při prvním připojení" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "Vyžaduje se rozhodnutí o důvěře OMEMO" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "Přidali jste nové zařízení pro účet %s?" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Šifrování" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d OMEMO zařízení" msgstr[1] "%d OMEMO zařízení" msgstr[2] "%d OMEMO zařízení" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "OMEMO Správa klíčů" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Automaticky přijímat nové klíče" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "New encryption keys from this contact will be accepted automatically." msgstr "Nové šifrovací klíče od tohoto kontaktu budou automaticky přijímány." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Vlastní klíč" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Nové klíče" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Přidružené klíče" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "Neaktivní klíče" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "New encryption keys from your other devices will be accepted automatically." msgstr "" "Nové šifrovací klíče z vašich ostatních zařízení budou automaticky přijímány." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Přijat" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Odmítnut" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Ověřen" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Nepoužit" dino-0.3.0/plugins/omemo/po/de.po0000644000000000000000000002732014202022370015300 0ustar rootrootmsgid "" msgstr "" "Project-Id-Version: dino-omemo-0.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-10-10 10:49+0000\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" "X-Generator: Weblate 4.3-dev\n" #: plugins/omemo/src/ui/bad_messages_populator.vala:157 #, c-format msgid "" "%s has been using an untrusted device. You won't see messages from devices " "that you do not trust." msgstr "" "%s hat ein nicht akzeptiertes Gerät benutzt. Du wirst Nachrichten von nicht " "akzeptierten Geräten nicht sehen." #: plugins/omemo/src/ui/bad_messages_populator.vala:158 msgid "Manage devices" msgstr "Geräte verwalten" #: plugins/omemo/src/ui/bad_messages_populator.vala:160 #, c-format msgid "" "%s does not trust this device. That means, you might be missing messages." msgstr "" "%s akzeptiert dieses Gerät nicht. Deshalb verpasst du vielleicht Nachrichten." #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Schlüssel verwalten" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contact's device." msgstr "" "Vergleiche den Fingerabdruck, Zeichen für Zeichen, mit dem der auf dem Gerät " "deines Kontakts angezeigt wird." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Fingerprints differ" msgstr "Fingerabdrücke unterscheiden sich" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Fingerprints match" msgstr "Fingerabdrücke stimmen überein" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Abbrechen" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Bestätigen" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Schlüssel verifizieren" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Future messages sent by %s from the device that uses this key will be " "highlighted accordingly in the chat window." msgstr "" "Zukünftige Nachrichten, die von %s von dem Gerät gesendet werden, das diesen " "Schlüssel verwendet, werden im Chat-Fenster entsprechend hervorgehoben." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Fingerabdrücke stimmen nicht überein" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Bitte überprüfe, ob du den richtigen Fingerabdruck vergleichst. Wenn die " "Fingerabdrücke nicht übereinstimmen, ist das Konto von %s möglicherweise " "kompromittiert und du solltest in Betracht ziehen, den Schlüssel abzulehnen." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Fingerabdruck überprüfen" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "Diesen Fingerabdruck mit dem Fingerabdruck vergleichen, der auf dem Gerät " "des Kontaktes angezeigt wird." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Schlüssel ablehnen" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Block encrypted communication with the contact's device that uses this key." msgstr "" "Blockiert die verschlüsselte Kommunikation mit dem Gerät des Kontakts, das " "diesen Schlüssel verwendet." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Schlüssel akzeptieren" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Allow encrypted communication with the contact's device that uses this key." msgstr "" "Erlaubt die verschlüsselte Kommunikation mit dem Gerät des Kontakts, das " "diesen Schlüssel verwendet." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Dieser Schlüssel ist aktuell %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "akzeptiert" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send encrypted messages." msgstr "" "Das bedeutet, er kann von %s zum Empfangen und Senden verschlüsselter " "Nachrichten verwendet werden." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "überprüft" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" "Darüber hinaus wurde überprüft, dass er mit dem Schlüssel auf dem Gerät des " "Kontaktes übereinstimmt." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "abgelehnt" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to decipher your messages, and you won't " "see messages encrypted with it." msgstr "" "Das bedeutet %s kann ihn nicht benutzen, um deine Nachrichten zu " "entschlüsseln, und du wirst damit verschlüsselte Nachrichten nicht sehen." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "You won't see encrypted messages from the device of %s that uses this key. " "Conversely, that device won't be able to decipher your messages anymore." msgstr "" "Du wirst verschlüsselte Nachrichten von dem Gerät von %s, welches diesen " "Schlüssel benutzt nicht mehr sehen. Umgekehrt kann dieses Gerät deine " "Nachrichten nicht mehr entschlüsseln." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "You will be able to exchange encrypted messages with the device of %s that " "uses this key." msgstr "" "Du wirst verschlüsselte Nachrichten mit dem Gerät von %s, welches diesen " "Schlüssel verwendet, austauschen können." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Zurück" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Verwalten" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Dieser Kontakt hat neue Geräte" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Eigener Fingerabdruck" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connection" msgstr "Wird beim ersten Verbinden erzeugt" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "OMEMO-Vertrauensentscheidung erforderlich" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "Hast du ein neues Gerät für deinen Account %s hinzugefügt?" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Verschlüsselung" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d OMEMO Gerät" msgstr[1] "%d OMEMO Geräte" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "OMEMO-Schlüssel Verwaltung" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Neue Schlüssel automatisch akzeptieren" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "New encryption keys from this contact will be accepted automatically." msgstr "Neue Schlüssel von diesem Kontakt werden automatisch akzeptiert." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Eigener Schlüssel" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Neue Schlüssel" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Zugehörige Schlüssel" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "Inaktive Schlüssel" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "New encryption keys from your other devices will be accepted automatically." msgstr "" "Neue Schlüssel von deinen anderen Geräten werden automatisch akzeptiert." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Akzeptiert" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Zurückgewiesen" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Verifiziert" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Unbenutzt" #~ msgid "Your contact" #~ msgstr "Dein Kontakt" #~ msgid "Not matching" #~ msgstr "Nicht übereinstimmend" #~ msgid "Matching" #~ msgstr "Stimmt überein" #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "highlighted accordingly in the chat window." #~ msgstr "" #~ "Nach der Bestätigung werden alle mit diesem Schlüssel verschlüsselten " #~ "Nachrichten von %s hervorgehoben." #~ msgid "" #~ "Stop accepting this key during communication with its associated contact." #~ msgstr "" #~ "Diesen Schlüssel nicht mehr für die Kommunikation mit dem " #~ "Schlüsselbesitzer akzeptieren." #~ msgid "" #~ "Start accepting this key during communication with its associated contact" #~ msgstr "" #~ "Akzeptiere diesen Schlüssel für die Kommunikation mit dem zugehörigen " #~ "Kontakt" #~ msgid "" #~ "This means it cannot be used by %s to receive messages, and any messages " #~ "sent by it will be ignored." #~ msgstr "" #~ "Das bedeutet, dass es von %s nicht verwendet werden kann, um Nachrichten " #~ "zu empfangen und alle von ihm gesendeten Nachrichten werden ignoriert." #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "ignored and none of your messages will be readable using this key." #~ msgstr "" #~ "Nach der Bestätigung werden alle zukünftigen Nachrichten, die von %s mit " #~ "diesem Schlüssel gesendet werden, ignoriert und keine Ihrer Nachrichten " #~ "ist mit diesem Schlüssel lesbar." #~ msgid "" #~ "Once confirmed this key will be usable by %s to receive and send messages." #~ msgstr "" #~ "Sobald bestätigt, kann %s diesen Schlüssel zum Senden und Empfangen von " #~ "Nachrichten verwenden." #~ msgid "" #~ "When this contact adds new encryption keys to their account, " #~ "automatically accept them." #~ msgstr "" #~ "Wenn dieser Kontakt neue Verschlüsselungscodes zu seinem Konto hinzufügt, " #~ "werden sie automatisch akzeptiert." #~ msgid "" #~ "When you add new encryption keys to your account, automatically accept " #~ "them." #~ msgstr "" #~ "Wenn du einen neuen Schüssel zu deinem Account hinzufügst, wird dieser " #~ "automatisch akzeptiert." #~ msgid "" #~ "Start accepting this key during communication with its associated contact." #~ msgstr "" #~ "Diesen Schlüssel für die Kommunikation mit dem entsprechenden Kontakt " #~ "akzeptieren." #~ msgid "Unknown device (0x%.8x)" #~ msgstr "Unbekanntes Gerät (0x%.8x)" #~ msgid "Other devices" #~ msgstr "Andere Geräte" #~ msgid "- None -" #~ msgstr "- Keine -" #~ msgid "OMEMO" #~ msgstr "OMEMO" #~ msgid "Database error" #~ msgstr "Datenbank Fehler" dino-0.3.0/plugins/omemo/po/dino-omemo.pot0000644000000000000000000001536614202022370017146 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" #: plugins/omemo/src/ui/bad_messages_populator.vala:157 #, c-format msgid "" "%s has been using an untrusted device. You won't see messages from devices " "that you do not trust." msgstr "" #: plugins/omemo/src/ui/bad_messages_populator.vala:158 msgid "Manage devices" msgstr "" #: plugins/omemo/src/ui/bad_messages_populator.vala:160 #, c-format msgid "" "%s does not trust this device. That means, you might be missing messages." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contact's device." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Fingerprints differ" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Fingerprints match" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Future messages sent by %s from the device that uses this key will be " "highlighted accordingly in the chat window." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Block encrypted communication with the contact's device that uses this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Allow encrypted communication with the contact's device that uses this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send encrypted messages." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to decipher your messages, and you won't " "see messages encrypted with it." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "You won't see encrypted messages from the device of %s that uses this key. " "Conversely, that device won't be able to decipher your messages anymore." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "You will be able to exchange encrypted messages with the device of %s that " "uses this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connection" msgstr "" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "" msgstr[1] "" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "New encryption keys from this contact will be accepted automatically." msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "New encryption keys from your other devices will be accepted automatically." msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "" dino-0.3.0/plugins/omemo/po/el.po0000644000000000000000000002653014202022370015312 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2022-02-04 09:55+0000\n" "Language-Team: none\n" "Language: el\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: Weblate 4.11-dev\n" #: plugins/omemo/src/ui/bad_messages_populator.vala:157 #, c-format msgid "" "%s has been using an untrusted device. You won't see messages from devices " "that you do not trust." msgstr "" "%s χρησιμοποιεί μια μη αξιόπιστη συσκευή. Δεν θα βλέπετε μηνύματα από " "συσκευές που δεν εμπιστεύεστε." #: plugins/omemo/src/ui/bad_messages_populator.vala:158 msgid "Manage devices" msgstr "Διαχείριση συσκευών" #: plugins/omemo/src/ui/bad_messages_populator.vala:160 #, c-format msgid "" "%s does not trust this device. That means, you might be missing messages." msgstr "" "%s δεν εμπιστεύεται αυτήν τη συσκευή. Αυτό σημαίνει ότι μπορεί να χάσετε " "μηνύματα." #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Διαχείριση Κλειδιού" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contact's device." msgstr "" "Συγκρίνετε το δακτυλικό αποτύπωμα (fingerprint), χαρακτήρα προς χαρακτήρα, " "με αυτό που εμφανίζεται στη συσκευή της επαφής σας." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Fingerprints differ" msgstr "Τα δακτυλικά αποτυπώματα (fingerprints) διαφέρουν" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Fingerprints match" msgstr "Τα δακτυλικά αποτυπώματα (fingerprints) ταιριάζουν" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Ακύρωση" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Επιβεβαίωση" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Επαλήθευση κλειδιού" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Future messages sent by %s from the device that uses this key will be " "highlighted accordingly in the chat window." msgstr "" "Μελλοντικά μηνύματα που αποστέλλονται από %s από τη συσκευή που χρησιμοποιεί " "αυτό το κλειδί θα επισημαίνονται ανάλογα στο παράθυρο συνομιλίας." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Τα δακτυλικά αποτυπώματα (fingerprints) δεν ταιριάζουν" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Επαληθεύστε ότι συγκρίνετε το σωστό δακτυλικό αποτύπωμα (fingerprint). Εάν " "τα δακτυλικά αποτυπώματα (fingerprints) δεν ταιριάζουν, ο λογαριασμός του %s " "μπορεί να παραβιαστεί και θα πρέπει να σκεφτείτε να απορρίψετε αυτό το " "κλειδί." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Επαλήθευση δακτυλικού αποτυπώματος (fingerprint) κλειδιού" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "Συγκρίνετε το δακτυλικό αποτύπωμα (fingerprint) αυτού του κλειδιού με το " "δακτυλικό αποτύπωμα που εμφανίζεται στη συσκευή της επαφής." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Απόρριψη κλειδιού" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Block encrypted communication with the contact's device that uses this key." msgstr "" "Αποκλεισμός κρυπτογραφημένης επικοινωνίας με τη συσκευή της επαφής που " "χρησιμοποιεί αυτό το κλειδί." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Αποδοχή κλειδιού" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Allow encrypted communication with the contact's device that uses this key." msgstr "" "Να επιτρέπεται η κρυπτογραφημένη επικοινωνία με τη συσκευή της επαφής που " "χρησιμοποιεί αυτό το κλειδί." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Αυτό το κλειδί είναι αυτή τη στιγμή %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "δεκτό" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send encrypted messages." msgstr "" "Αυτό σημαίνει ότι μπορεί να χρησιμοποιηθεί από το %s για λήψη και αποστολή " "κρυπτογραφημένων μηνυμάτων." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "επαληθευμένο" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" "Επιπλέον, έχει επαληθευτεί ότι ταιριάζει με το κλειδί στη συσκευή της επαφής." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "απορριφθέν" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to decipher your messages, and you won't " "see messages encrypted with it." msgstr "" "Αυτό σημαίνει ότι δεν μπορεί να χρησιμοποιηθεί από %s για την " "αποκρυπτογράφηση των μηνυμάτων σας και δεν θα βλέπετε μηνύματα " "κρυπτογραφημένα με αυτό." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "You won't see encrypted messages from the device of %s that uses this key. " "Conversely, that device won't be able to decipher your messages anymore." msgstr "" "Δεν θα βλέπετε κρυπτογραφημένα μηνύματα από τη συσκευή των %s που " "χρησιμοποιούν αυτό το κλειδί. Αντίθετα, αυτή η συσκευή δεν θα μπορεί πλέον " "να αποκρυπτογραφήσει τα μηνύματά σας." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "You will be able to exchange encrypted messages with the device of %s that " "uses this key." msgstr "" "Θα μπορείτε να ανταλλάσσετε κρυπτογραφημένα μηνύματα με τη συσκευή των %s " "που χρησιμοποιούν αυτό το κλειδί." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Πίσω" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Διαχείρηση" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Αυτή η επαφή έχει νέες συσκευές" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Το δακτυλικό μου αποτύπωμα (fingerprint)" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connection" msgstr "Θα δημιουργηθεί με την πρώτη σύνδεση" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "Απαιτείται απόφαση εμπιστοσύνης OMEMO" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "Προσθέσατε νέα συσκευή για τον λογαριασμό %s;" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Κρυπτογράφηση" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d OMEMO συσκευή" msgstr[1] "%d OMEMO συσκευές" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "Διαχείριση κλειδιών OMEMO" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Αυτόματη αποδοχή νέων κλειδιών" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "New encryption keys from this contact will be accepted automatically." msgstr "" "Τα νέα κλειδιά κρυπτογράφησης από αυτήν την επαφή θα γίνονται δεκτά αυτόματα." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Το κλειδί μου" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Νέα κλειδιά" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Συσχετισμένα κλειδιά" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "Ανενεργά κλειδιά" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "New encryption keys from your other devices will be accepted automatically." msgstr "" "Τα νέα κλειδιά κρυπτογράφησης από τις άλλες συσκευές σας θα γίνονται δεκτά " "αυτόματα." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Αποδεκτά" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Απορριφθέντα" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Επαληθευμένα" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Αχρησιμοποίητα" dino-0.3.0/plugins/omemo/po/en.po0000644000000000000000000001456214202022370015316 0ustar rootrootmsgid "" msgstr "" "Project-Id-Version: dino-omemo-0.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "Language: en\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: plugins/omemo/src/ui/bad_messages_populator.vala:157 #, c-format msgid "" "%s has been using an untrusted device. You won't see messages from devices " "that you do not trust." msgstr "" #: plugins/omemo/src/ui/bad_messages_populator.vala:158 msgid "Manage devices" msgstr "" #: plugins/omemo/src/ui/bad_messages_populator.vala:160 #, c-format msgid "" "%s does not trust this device. That means, you might be missing messages." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contact's device." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Fingerprints differ" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Fingerprints match" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Future messages sent by %s from the device that uses this key will be " "highlighted accordingly in the chat window." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Block encrypted communication with the contact's device that uses this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Allow encrypted communication with the contact's device that uses this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send encrypted messages." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to decipher your messages, and you won't " "see messages encrypted with it." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "You won't see encrypted messages from the device of %s that uses this key. " "Conversely, that device won't be able to decipher your messages anymore." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "You will be able to exchange encrypted messages with the device of %s that " "uses this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connection" msgstr "" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "" msgstr[1] "" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "New encryption keys from this contact will be accepted automatically." msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "New encryption keys from your other devices will be accepted automatically." msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "" dino-0.3.0/plugins/omemo/po/eo.po0000644000000000000000000002163114202022370015312 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2021-03-08 22:02+0000\n" "Language-Team: none\n" "Language: eo\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: Weblate 4.5.1\n" #: plugins/omemo/src/ui/bad_messages_populator.vala:157 #, c-format msgid "" "%s has been using an untrusted device. You won't see messages from devices " "that you do not trust." msgstr "" "%s uzas nefidatan aparaton. Vi ne vidos mesaĝojn el aparatoj, kiujn vi ne " "fidas." #: plugins/omemo/src/ui/bad_messages_populator.vala:158 msgid "Manage devices" msgstr "Administri aparatojn" #: plugins/omemo/src/ui/bad_messages_populator.vala:160 #, c-format msgid "" "%s does not trust this device. That means, you might be missing messages." msgstr "" "%s ne fidas ĉi tiun aparaton. Tio signifas, ke eble mankas al vi mesaĝoj." #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Administri Ŝlosilon" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contact's device." msgstr "" "Komparu la fingropremaĵon, signon post signo, kun la fingropremaĵo montrata " "sur la aparato de via kontakto." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Fingerprints differ" msgstr "Fingropremaĵoj diferencas" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Fingerprints match" msgstr "Fingropremaĵoj kongruas" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Nuligi" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Konfirmi" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Konfirmi ŝlosilon" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Future messages sent by %s from the device that uses this key will be " "highlighted accordingly in the chat window." msgstr "" "Mesaĝoj sendotaj de %s per la aparato kiu uzas ĉi tiun ŝlosilon estos " "emfazitaj laŭe en la babila fenestro." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Fingropremaĵoj ne kongruas" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Bonvolu konfirmi, ke vi komparas la ĝustan fingropremaĵon. Se fingropremaĵoj " "ne kongruas, la konto de %s eble estas kompromitita, kaj vi konsideru " "rifuzon de ĉi tiu ŝlosilo." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Konfirmi ŝlosilan fingropremaĵon" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "Komparu la fingropremaĵon de ĉi tiu ŝlosilo kun la fingropremaĵo montrata " "sur la aparato de la kontakto." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Rifuzi ŝlosilon" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Block encrypted communication with the contact's device that uses this key." msgstr "" "Rifuzi ĉifritan komunikadon al la aparato de la kontakto, kiu uzas ĉi tiun " "ŝlosilon." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Akcepti ŝlosilon" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Allow encrypted communication with the contact's device that uses this key." msgstr "" "Akcepti ĉifritan komunikadon al la aparato de la kontakto, kiu uzas ĉi tiun " "ŝlosilon." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Ĉi tiu ŝlosilo estas aktuale %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "akceptata" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send encrypted messages." msgstr "" "Ĉi tio signifas, ke ĝi povas esti uzata de %s por ricevi kaj sendi ĉifritajn " "mesaĝojn." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "konfirmita" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" "Krome ĝi estis kontrolita pri kongruo kun la ŝlosilo ĉe la aparato de la " "kontakto." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "rifuzita" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to decipher your messages, and you won't " "see messages encrypted with it." msgstr "" "Tio signifas, ke ĝi ne povas esti uzata de %s por malĉifri viajn mesaĝojn, " "kaj vi ne vidos mesaĝojn ĉifritajn per ĝi." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "You won't see encrypted messages from the device of %s that uses this key. " "Conversely, that device won't be able to decipher your messages anymore." msgstr "" "Vi ne vidos ĉifritajn mesaĝojn el la aparato de %s kiu uzas ĉi tiun " "ŝlosilon. Male, tiu aparato ne plu povos malĉifri viajn mesaĝojn." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "You will be able to exchange encrypted messages with the device of %s that " "uses this key." msgstr "" "Vi povos interŝanĝi ĉifritajn mesaĝojn kun la aparato de %s kiu uzas ĉi tiun " "ŝlosilon." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Reen" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Administri" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Ĉi tiu kontakto havas novajn aparatojn" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Propra fingropremaĵo" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connection" msgstr "Estos farita dum unua konekto" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "Necesas OMEMO-decido pri fidindeco" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "Ĉu vi aldonis novan aparaton por la konto %s?" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Ĉifrado" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d OMEMO-aparato" msgstr[1] "%d OMEMO-aparatoj" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "Administrado de OMEMO-Ŝlosiloj" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Aŭtomate akcepti novajn ŝlosilojn" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "New encryption keys from this contact will be accepted automatically." msgstr "Novaj ĉifraj ŝlosiloj de ĉi tiu kontakto estos aŭtomate akceptitaj." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Propra ŝlosilo" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Novaj ŝlosiloj" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Asociitaj ŝlosiloj" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "Neaktivaj ŝlosiloj" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "New encryption keys from your other devices will be accepted automatically." msgstr "" "Novaj ĉifraj ŝlosiloj de viaj aliaj aparatoj estos aŭtomate akceptitaj." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Akceptita" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Rifuzita" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Konfirmita" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Ne uzata" dino-0.3.0/plugins/omemo/po/es.po0000644000000000000000000002705214202022370015321 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-11-16 22:33+0000\n" "Language-Team: Spanish \n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 4.4-dev\n" #: plugins/omemo/src/ui/bad_messages_populator.vala:157 #, c-format msgid "" "%s has been using an untrusted device. You won't see messages from devices " "that you do not trust." msgstr "" "%s ha estado usando un dispositivo poco fiable. No verá mensajes de " "dispositivos en los que no confíe." #: plugins/omemo/src/ui/bad_messages_populator.vala:158 msgid "Manage devices" msgstr "Gestionar dispositivos" #: plugins/omemo/src/ui/bad_messages_populator.vala:160 #, c-format msgid "" "%s does not trust this device. That means, you might be missing messages." msgstr "" "%s no confía en este dispositivo. Eso significa que puede que te falten " "mensajes." #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Gestionar Clave" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contact's device." msgstr "" "Compara la huella digital, carácter a carácter, con la huella digital " "mostrada en el dispositivo de tus contactos." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Fingerprints differ" msgstr "Las huellas digitales son diferente" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Fingerprints match" msgstr "Las huellas digitales son idénticas" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Cancelar" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Confirmar" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Verificar clave" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Future messages sent by %s from the device that uses this key will be " "highlighted accordingly in the chat window." msgstr "" "Los mensajes futuros enviados por %s desde el dispositivo que usa esta clave " "será resaltado en consecuencia en esta venta de conversación." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Huellas digitales no coinciden" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Por favor, verifica que estás comparando la huella digital correcta. Si la " "huella digital no coincide con la cuenta %s puede que la clave esté " "comprometida y deberías considerar rechazar esta clave." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Verificar Huella Digital" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "Compara esta huella digital con la huella digital mostrada en el dispositivo " "de tu contacto." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Rechazar clave" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Block encrypted communication with the contact's device that uses this key." msgstr "" "Bloquear comunicación cifrada con el dispositivo del contacto que usa esta " "clave." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Aceptar clave" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Allow encrypted communication with the contact's device that uses this key." msgstr "" "Permitir comunicación cifrada con el dispositivo del contacto que usa esta " "clave." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Esta clave está actualmente %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "aceptada" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send encrypted messages." msgstr "" "Esto significa que puede ser usada por %s para recibir y enviar mensajes " "cifrados." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "verficada" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" "Además, ha sido verificado que las claves coinciden con la clave del " "dispositivo de tu contacto." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "rechazada" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to decipher your messages, and you won't " "see messages encrypted with it." msgstr "" "Esto significa que no puede ser usada por %s para descifrar tus mensajes, y " "tú no verás los mensajes cifrados con ella." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "You won't see encrypted messages from the device of %s that uses this key. " "Conversely, that device won't be able to decipher your messages anymore." msgstr "" "No verás los mensajes cifrados desde el dispositivo de %s que usa esta " "clave. A la inversa, ese dispositivo ya no podrá descifrar tus mensajes." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "You will be able to exchange encrypted messages with the device of %s that " "uses this key." msgstr "" "Tu podrás intercambiar mensajes cifrados con el dispositivo de %s que usa " "esta clave." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Volver" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Gestionar" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Este contacto tiene un nuevo dispositivo" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Tu huella digital" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connection" msgstr "Será generada en la primera conexión" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "Decisión de confianza requerida para clave OMEMO" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "¿Añadiste un nuevo dispositivo para la cuenta %s?" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Cifrado" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d dispositivo OMEMO" msgstr[1] "%d dispositivos OMEMO" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "Huellas digitales OMEMO" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Automáticamente aceptar nuevas claves" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "New encryption keys from this contact will be accepted automatically." msgstr "" "Las nuevas claves de cifrados para este contacto serán aceptadas " "automáticamente." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Mi clave" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Nuevas claves" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Claves asociadas" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "Claves inactivas" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "New encryption keys from your other devices will be accepted automatically." msgstr "" "Las nuevas claves de cifrado de tus otros dispositivos serán aceptadas " "automáticamente." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Aceptada" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Rechazada" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Verificada" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Sin usar" #~ msgid "Your contact" #~ msgstr "Tu contacto" #~ msgid "Not matching" #~ msgstr "No coincide" #~ msgid "Matching" #~ msgstr "Coincide" #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "highlighted accordingly in the chat window." #~ msgstr "" #~ "Una vez confirmada, cualquier futuro mensaje enviado por %s usando esta " #~ "clave será resaltado en el ventana de conversación." #~ msgid "" #~ "Stop accepting this key during communication with its associated contact." #~ msgstr "" #~ "Dejar de aceptar esta clave durante la comunicación con este contacto." #~ msgid "" #~ "Start accepting this key during communication with its associated contact" #~ msgstr "Aceptar esta clave durante la comunicación con este contacto" #~ msgid "" #~ "This means it cannot be used by %s to receive messages, and any messages " #~ "sent by it will be ignored." #~ msgstr "" #~ "Esto significa que no puede ser usada por %s para recibir mensajes, y " #~ "cualquier mensaje enviado con esta clave será ignorado." #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "ignored and none of your messages will be readable using this key." #~ msgstr "" #~ "Una vez confirmado, cualquier futuro mensaje enviado por %s usando esta " #~ "clave será ignorado y ninguno de tus mensajes será legible usando esta " #~ "clave." #~ msgid "" #~ "Once confirmed this key will be usable by %s to receive and send messages." #~ msgstr "" #~ "Una vez confirmada esta clave será usable por %s para recibir y enviar " #~ "mensajes." #~ msgid "" #~ "When this contact adds new encryption keys to their account, " #~ "automatically accept them." #~ msgstr "" #~ "Cuando este contacto añada nuevas claves de cifrado a su cuenta, " #~ "automáticamente aceptarlas." #~ msgid "" #~ "When you add new encryption keys to your account, automatically accept " #~ "them." #~ msgstr "" #~ "Cuando añades nuevas claves de cifrado a tu cuenta, aceptarlas " #~ "automáticamente." #~ msgid "" #~ "Start accepting this key during communication with its associated contact." #~ msgstr "Aceptar esta clave durante la comunicación con este contacto." #~ msgid "Reject Key" #~ msgstr "Rechazar clave" #~ msgid "Accept Key" #~ msgstr "Aceptar clave" #~ msgid "Unknown device (0x%.8x)" #~ msgstr "Dispositivo (0x%.8x) desconocido" #~ msgid "Other devices" #~ msgstr "Otros dispositivos" #~ msgid "- None -" #~ msgstr "- Ninguno -" #~ msgid "OMEMO" #~ msgstr "OMEMO" dino-0.3.0/plugins/omemo/po/eu.po0000644000000000000000000002673114202022370015326 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2022-01-17 17:56+0000\n" "Language-Team: Basque \n" "Language: eu\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: Weblate 4.11-dev\n" #: plugins/omemo/src/ui/bad_messages_populator.vala:157 #, c-format msgid "" "%s has been using an untrusted device. You won't see messages from devices " "that you do not trust." msgstr "" "%s erabiltzaileak fidagarritasun gutxiko gailu bat erabiltzen egon da. Ez " "duzu mezurik ikusiko konfiantza ematen ez dizuten gailuengandik." #: plugins/omemo/src/ui/bad_messages_populator.vala:158 msgid "Manage devices" msgstr "Gailuak kudeatu" #: plugins/omemo/src/ui/bad_messages_populator.vala:160 #, c-format msgid "" "%s does not trust this device. That means, you might be missing messages." msgstr "" "%s erabiltzailea ez da gailu honetaz fidatzen. Honek mezu batzuk gal " "ditzakezula esan nahi du." #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Gakoa kudeatu" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contact's device." msgstr "" "Hatz marka konparatu, hizkiz hizki, zure kontaktuaren gailuan ageri " "denarekin." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Fingerprints differ" msgstr "Hatz markak ez datoz bat" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Fingerprints match" msgstr "Hatz markak bat datoz" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Utzi" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Baieztatu" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Gakoa egiaztatu" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Future messages sent by %s from the device that uses this key will be " "highlighted accordingly in the chat window." msgstr "" "%s erabiltzaileak etorkizunean gako hau erabiltzen duen gailutik bidalitako " "mezuak nabarmenduko dira txat leihoan." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Hatz markak ez datoz bat" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Mesedez egiaztatu ezazu hatz marka egokia konparatzen ari zarela. Hatz " "markak bat ez badatoz baliteke %s(e)ren kontua arriskuan egotea eta gako hau " "ez onartzea kontuan hartu beharko zenuke." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Gakoaren hatz marka egiaztatu" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "Konpara ezazu gako honen hatz marka kontaktuaren gailuan agertzen den hatz " "markarekin." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Gakoa ukatu" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Block encrypted communication with the contact's device that uses this key." msgstr "" "Enkriptatutako komunikazioa blokeatu gako hau erabiltzen duen kontaktuaren " "gailuarekin." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Gakoa onartu" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Allow encrypted communication with the contact's device that uses this key." msgstr "" "Enkriptatutako komunikazioa baimendu gako hau erabiltzen duen kontaktuaren " "gailuarekin." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Gako hau %s dago une honetan." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "onartuta" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send encrypted messages." msgstr "" "Horrek esan nahi du %s-ek erabil dezakeela mezu zifratuak jasotzeko eta " "bidaltzeko." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "egiaztatuta" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" "Gainera zure kontaktuaren gailuan ageri den gakoarekin bat datorrela " "egiaztatu da." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "ukatuta" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to decipher your messages, and you won't " "see messages encrypted with it." msgstr "" "Honek esan nahi du %s erabiltzaileak ezin duela erabili zure mezuak " "deszifratzeko, eta zuk ez dituzula gako honekin enkriptatutako mezuak " "ikusiko." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "You won't see encrypted messages from the device of %s that uses this key. " "Conversely, that device won't be able to decipher your messages anymore." msgstr "" "Ez dituzu ikusiko gako hau erabiltzen duen %s kontaktuaren gailutik " "igorritako mezuak. Era berean, hemendik aurrera gailu horrek ez da zure " "mezuak deszifratzeko gai izango." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "You will be able to exchange encrypted messages with the device of %s that " "uses this key." msgstr "" "Gako hau erabiltzen duen %s kontaktuaren gailuarekin enkriptatutako mezuak " "elkar-trukatzeko gai izango zara." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Atzera" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Kudeatu" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Kontaktu honek gailu berriak ditu" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Norberaren hatz marka" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connection" msgstr "Lehen konexioan sortuko da" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "OMEMO konfiantza erabakia behar da" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "Gailu berri gehitu duzu %s konturako?" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Enkriptazioa" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "OMEMO gailu %d" msgstr[1] "%d OMEMO gailu" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "OMEMO gakoen kudeaketa" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Gako berriak automatikoki onartu" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "New encryption keys from this contact will be accepted automatically." msgstr "" "Kontaktu honen enkriptatze gako berriak onartuak izango dira automatikoki." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Norberaren gakoa" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Gako berriak" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Lotutako gakoak" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "Gako ez aktiboak" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "New encryption keys from your other devices will be accepted automatically." msgstr "" "Zure beste gailuen enkriptatze gako berriak onartuak izango dira " "automatikoki." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Onartuta" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Ukatuta" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Egiaztatuta" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Ez erabilita" #~ msgid "Not matching" #~ msgstr "Ez datoz bat" #~ msgid "Matching" #~ msgstr "Bat datoz" #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "highlighted accordingly in the chat window." #~ msgstr "" #~ "Egiaztatu ondoren, %s(e)k gako honekin etorkizunean bidalitako mezu " #~ "guztiak behar bezala nabarmenduko dira txat leihoan." #~ msgid "" #~ "Stop accepting this key during communication with its associated contact." #~ msgstr "" #~ "Utzi gako hau onartzeari lotuta dagoen kontaktuarekin hitz egiten duzun " #~ "bitartean." #~ msgid "" #~ "Start accepting this key during communication with its associated contact" #~ msgstr "" #~ "Hasi gako hau onartzen lotuta dagoen kontaktuarekin hitz egiten duzun " #~ "bitartean" #~ msgid "" #~ "This means it cannot be used by %s to receive messages, and any messages " #~ "sent by it will be ignored." #~ msgstr "" #~ "Honek esan nahi du %s(e)k ezin duela gakoa mezuak jasotzeko erabili, eta " #~ "berak bidalitako edozein mezu alde batera utziko da." #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "ignored and none of your messages will be readable using this key." #~ msgstr "" #~ "Egiaztatu ondoren, etorkizunean %s(e)k gako hau erabiliz bidalitako " #~ "edozein mezu alde batera utziko da eta zure mezuetako bat ere ez da " #~ "irakurgarria izango gako hau erabiliz." #~ msgid "" #~ "Once confirmed this key will be usable by %s to receive and send messages." #~ msgstr "" #~ "Egiaztatu ondoren %s(e)k gako hau mezuak jaso eta bidaltzeko erabili ahal " #~ "izango du." #~ msgid "" #~ "When this contact adds new encryption keys to their account, " #~ "automatically accept them." #~ msgstr "" #~ "Kontaktu honek enkriptatzeko gako berriak bere kontura gehitzen " #~ "dituenean, automatikoki onartu." #~ msgid "" #~ "When you add new encryption keys to your account, automatically accept " #~ "them." #~ msgstr "" #~ "Zure kontura enkriptazio gako berriak gehitzen dituzunean, automatikoki " #~ "onartu." #~ msgid "" #~ "Start accepting this key during communication with its associated contact." #~ msgstr "" #~ "Hasi gako hau onartzen lotuta dagoen kontaktuarekin hitz giten duzun " #~ "bitartean." #~ msgid "Reject Key" #~ msgstr "Gakoa baztertu" #~ msgid "Accept Key" #~ msgstr "Gakoa onartu" #~ msgid "Unknown device (0x%.8x)" #~ msgstr "Gailu ezezaguna (0x%.8x)" #~ msgid "Other devices" #~ msgstr "Beste gailuak" #~ msgid "- None -" #~ msgstr "- Bat ere ez -" dino-0.3.0/plugins/omemo/po/fa.po0000644000000000000000000002443414202022370015301 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2021-08-10 03:32+0000\n" "Language-Team: none\n" "Language: fa\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: Weblate 4.8-dev\n" #: plugins/omemo/src/ui/bad_messages_populator.vala:157 #, c-format msgid "" "%s has been using an untrusted device. You won't see messages from devices " "that you do not trust." msgstr "" "%s از دستگاهی استفاده می‌کند که اعتبارش را تأیید نکرده‌اید، پیام‌ها از چنین " "دستگاه‌هایی قابل مشاهده نیستند." #: plugins/omemo/src/ui/bad_messages_populator.vala:158 msgid "Manage devices" msgstr "مدیریت دستگاه‌ها" #: plugins/omemo/src/ui/bad_messages_populator.vala:160 #, c-format msgid "" "%s does not trust this device. That means, you might be missing messages." msgstr "" "%s اعتبار این دستگاه را تأیید نکرده است. این یعنی، ممکن است پیام‌هایی را " "دریافت نکنید/نکرده باشید." #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "مدیریت کلید" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contact's device." msgstr "" "اثرانگشت را با آنی که در دستگاه مخاطبتان است مقایسه کنید، کاراکتر به کاراکتر." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Fingerprints differ" msgstr "اثرانگشت‌ها متفاوت‌اند" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Fingerprints match" msgstr "اثرانگشت‌ها تطابق دارند" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "لغو" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "تأیید" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "تأیید صحت کلید" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Future messages sent by %s from the device that uses this key will be " "highlighted accordingly in the chat window." msgstr "" "پیام‌های ارسالی توسط %s از دستگاهی که از این کلید استفاده می‌کند، مطابق آن در " "پنجرهٔ گپ مشخص می‌شوند." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "اثرانگشت‌ها تطابق ندارند" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "لطفاً مطمئن شوید که در حال مقایسهٔ اثرانگشت صحیح هستید. اگه اثرانگشت‌ها تطابق " "نداشته باشند، ممکن است اطلاعات حساب %s لو رفته باشد و باید رد کردن این کلید " "را در نظر بگیرید." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "تأیید صحت اثرانگشت کلید" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "اثرانگشت این کلید را با اثرانگشت نمایش‌داده‌شده در دستگاه مخاطب مقایسه کنید." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "رد کردن کلید" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Block encrypted communication with the contact's device that uses this key." msgstr "" "ارتباط رمزگذاری‌شده را با دستگاهی که از این کلید استفاده می‌کند، مسدود کنید." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "پذیرش کلید" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Allow encrypted communication with the contact's device that uses this key." msgstr "" "اجازهٔ ارتباط رمزگذاری‌شده را با دستگاهی که از این کلید استفاده می‌کند، بدهید." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "این کلید در حال حاضر %s است." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "پذیرفته شده" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send encrypted messages." msgstr "" "این یعنی %s می‌تواند از آن برای ارسال و دریافت پیام رمزگذاری‌شده استفاده کند." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "تأیید صحت شده" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "به علاوه تأیید شده که با کلید دستگاه مخاطب تطابق دارد." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "رد شده" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to decipher your messages, and you won't " "see messages encrypted with it." msgstr "" "این یعنی %s نمی‌تواند با آن پیام‌های شما را رمزگشایی کند، و شما پیام‌های " "رمزگذاری‌شده با آن را نخواهید دید." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "You won't see encrypted messages from the device of %s that uses this key. " "Conversely, that device won't be able to decipher your messages anymore." msgstr "" "شما پیام‌های رمزگذاری‌شده را از آن دستگاهِ %s که از این کلید استفاده می‌کند، " "نخواهید دید. از سوی دیگر، آن دستگاه نیز پس از این قادر به رمزگشایی پیام‌های " "شما نخواهد بود." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "You will be able to exchange encrypted messages with the device of %s that " "uses this key." msgstr "" "شما قادر به تبادل پیام‌های رمزگذاری‌شده با آن دستگاهِ %s که از این کلید استفاده " "می‌کند، هستید." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "بازگشت" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "مدیریت" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "این مخاطب دستگاه‌های جدیدی دارد" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "اثرانگشت خود" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connection" msgstr "در اولین اتصال تولید خواهد شد" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "تصمیم اعتبار اُمیمو لازم است" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "آیا برای حساب %s یک دستگاه جدید اضافه کرده‌اید؟" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "رمزگذاری" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d دستگاه OMEMO" msgstr[1] "%d دستگاه OMEMO" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "مدیریت کلید اُمیمو" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "پذیرش خودکار کلیدهای جدید" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "New encryption keys from this contact will be accepted automatically." msgstr "کلیدهای جدید این مخاطب به طور خودکار پذیرفته می‌شوند." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "کلید خود" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "کلیدهای جدید" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "کلیدهای مرتبط" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "کلیدهای غیرفعال" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "New encryption keys from your other devices will be accepted automatically." msgstr "" "کلیدهای رمزگذاری جدید از دستگاه‌های دیگرتان به طور خودکار پذیرفته می‌شوند." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "پذیرفته شده" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "رد شده" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "تأیید صحت شده" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "استفاده نشده" dino-0.3.0/plugins/omemo/po/fi.po0000644000000000000000000001532414202022370015307 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-04-12 20:01+0000\n" "Language-Team: none\n" "Language: fi\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: Weblate 4.0-dev\n" #: plugins/omemo/src/ui/bad_messages_populator.vala:157 #, c-format msgid "" "%s has been using an untrusted device. You won't see messages from devices " "that you do not trust." msgstr "" #: plugins/omemo/src/ui/bad_messages_populator.vala:158 msgid "Manage devices" msgstr "" #: plugins/omemo/src/ui/bad_messages_populator.vala:160 #, c-format msgid "" "%s does not trust this device. That means, you might be missing messages." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contact's device." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Fingerprints differ" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Fingerprints match" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Peruuta" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Future messages sent by %s from the device that uses this key will be " "highlighted accordingly in the chat window." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Block encrypted communication with the contact's device that uses this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Allow encrypted communication with the contact's device that uses this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send encrypted messages." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to decipher your messages, and you won't " "see messages encrypted with it." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "You won't see encrypted messages from the device of %s that uses this key. " "Conversely, that device won't be able to decipher your messages anymore." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "You will be able to exchange encrypted messages with the device of %s that " "uses this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Takaisin" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connection" msgstr "" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "" msgstr[1] "" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "New encryption keys from this contact will be accepted automatically." msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "New encryption keys from your other devices will be accepted automatically." msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "" dino-0.3.0/plugins/omemo/po/fr.po0000644000000000000000000002701414202022370015317 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-11-12 17:21+0000\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: Weblate 4.4-dev\n" #: plugins/omemo/src/ui/bad_messages_populator.vala:157 #, c-format msgid "" "%s has been using an untrusted device. You won't see messages from devices " "that you do not trust." msgstr "" "%s a utilisé un client inconnu. Vous ne verrez pas les messages provenant de " "clients en lesquels vous n’avez pas confiance." #: plugins/omemo/src/ui/bad_messages_populator.vala:158 msgid "Manage devices" msgstr "Gérer les clients" #: plugins/omemo/src/ui/bad_messages_populator.vala:160 #, c-format msgid "" "%s does not trust this device. That means, you might be missing messages." msgstr "" "%s n’accorde pas sa confiance à ce client. Cela signifie que vous manquez " "peut-être des messages." #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Gérer la clé" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contact's device." msgstr "" "Comparez l’empreinte, lettre par lettre, avec celle affichée sur le client " "de votre contact." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Fingerprints differ" msgstr "L’empreinte ne correspond pas" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Fingerprints match" msgstr "L’empreinte correspond" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Annuler" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Confirmer" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Vérifier la clé" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Future messages sent by %s from the device that uses this key will be " "highlighted accordingly in the chat window." msgstr "" "Les prochains messages envoyés par %s depuis le client utilisant cette clé " "seront mis en valeur dans la fenêtre de chat." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Les empreintes ne correspondent pas" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Veuillez vérifier que vous comparez la bonne empreinte. Si les empreintes ne " "correspondent pas, le compte de %s pourrait être compromis et vous devriez " "envisager de rejeter cette clé." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Vérifier l’empreinte de cette clé" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "Comparez cette empreinte avec celle affichée sur le client du contact." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Rejeter cette clé" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Block encrypted communication with the contact's device that uses this key." msgstr "" "Bloquer les communications chiffrées avec le client du contact qui utilise " "cette clé." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Accepter la clé" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Allow encrypted communication with the contact's device that uses this key." msgstr "" "Autoriser les communications chiffrées avec le client du contact qui utilise " "cette clé." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Cette clé est actuellement %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "acceptée" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send encrypted messages." msgstr "" "Cela signifie qu’elle peut être utilisée par %s pour recevoir et envoyer des " "messages." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "vérifiée" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" "De plus, vous avez précédemment vérifié que cette clé coïncide avec celle du " "client de ce contact." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "rejetée" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to decipher your messages, and you won't " "see messages encrypted with it." msgstr "" "Cela signifie qu’elle ne peut pas être utilisée par %s pour déchiffrer vos " "messages, et que vous ne verrez pas les messages chiffrés avec." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "You won't see encrypted messages from the device of %s that uses this key. " "Conversely, that device won't be able to decipher your messages anymore." msgstr "" "Vous ne verrez pas les messages de %s provenant du client qui utilise cette " "clé. En retour, ce client ne pourra plus déchiffrer vos messages." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "You will be able to exchange encrypted messages with the device of %s that " "uses this key." msgstr "" "Vous pourrez échanger des messages chiffrés avec le client de %s qui utilise " "cette clé." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Retour" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Gérer" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Ce contact a de nouveaux clients" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Empreinte publique" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connection" msgstr "Sera générée lors de la première connexion" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "Décision de confiance OMEMO requise" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "Avez-vous ajouté un nouveau client pour le compte %s ?" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Chiffrement" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d client OMEMO" msgstr[1] "%d clients OMEMO" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "Gestion des clés OMEMO" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Accepter automatiquement les nouvelles clés" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "New encryption keys from this contact will be accepted automatically." msgstr "" "Les nouvelles clés de chiffrement de ce contact seront acceptées " "automatiquement." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Clé publique personnelle" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Nouvelles clés" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Clés associées" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "Clés inactives" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "New encryption keys from your other devices will be accepted automatically." msgstr "" "Les nouvelles clés de chiffrement de vos autres clients seront acceptées " "automatiquement." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Acceptée" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Rejetée" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Vérifiée" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Inutilisée" #~ msgid "Your contact" #~ msgstr "Votre contact" #~ msgid "Not matching" #~ msgstr "Ne correspond pas" #~ msgid "Matching" #~ msgstr "Correspond" #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "highlighted accordingly in the chat window." #~ msgstr "" #~ "Après confirmation, tout futur message envoyé par %s utilisant cette clé " #~ "sera surligné dans la fenêtre de conversation." #~ msgid "" #~ "Stop accepting this key during communication with its associated contact." #~ msgstr "" #~ "Arrêter d'accepter cette clé durant la communication avec son contact " #~ "associé." #~ msgid "" #~ "Start accepting this key during communication with its associated contact" #~ msgstr "Accepter cette clé durant la communication avec son contact associé" #~ msgid "" #~ "This means it cannot be used by %s to receive messages, and any messages " #~ "sent by it will be ignored." #~ msgstr "" #~ "Cela signifie qu'elle ne peut pas être utilisée par %s pour recevoir des " #~ "messages, et que les messages envoyés seront ignorés." #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "ignored and none of your messages will be readable using this key." #~ msgstr "" #~ "Après confirmation, tout futur message envoyé par %s utilisant cette clé " #~ "sera ignoré et vos messages ne seront pas lisibles en utilisant cette clé." #~ msgid "" #~ "Once confirmed this key will be usable by %s to receive and send messages." #~ msgstr "" #~ "Après confirmation, cette clé sera utilisable par %s pour recevoir et " #~ "envoyer des messages." #~ msgid "" #~ "When this contact adds new encryption keys to their account, " #~ "automatically accept them." #~ msgstr "" #~ "Quand ce contact ajoute une nouvelle clé de chiffrement à son compte, " #~ "l'accepter automatiquement." #~ msgid "" #~ "When you add new encryption keys to your account, automatically accept " #~ "them." #~ msgstr "" #~ "Accepter automatiquement les nouvelles clés de chiffrements que vous " #~ "ajoutez à votre compte." #~ msgid "" #~ "Start accepting this key during communication with its associated contact." #~ msgstr "" #~ "Accepter cette clé durant la communication avec son contact associé." #~ msgid "Unknown device (0x%.8x)" #~ msgstr "Périphérique inconnu (0x%.8x)" #~ msgid "Other devices" #~ msgstr "Autres périphériques" #~ msgid "- None -" #~ msgstr "- Aucun -" dino-0.3.0/plugins/omemo/po/gl.po0000644000000000000000000002655314202022370015321 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-10-10 10:49+0000\n" "Language-Team: Galician \n" "Language: gl\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: Weblate 4.3-dev\n" #: plugins/omemo/src/ui/bad_messages_populator.vala:157 #, c-format msgid "" "%s has been using an untrusted device. You won't see messages from devices " "that you do not trust." msgstr "" "%s estivo utilizando un dispositivo non verificado. Non verás mensaxes " "procedentes de dispositivos nos que non confiaches." #: plugins/omemo/src/ui/bad_messages_populator.vala:158 msgid "Manage devices" msgstr "Xestionar dispositivos" #: plugins/omemo/src/ui/bad_messages_populator.vala:160 #, c-format msgid "" "%s does not trust this device. That means, you might be missing messages." msgstr "" "%s non mostrou confianza neste dispositivo. Porén, poderías botar a faltar " "algunha mensaxe." #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Xestionar Chave" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contact's device." msgstr "" "Comparar pegada dixital, caracter a caracter, coa mostrada no dispositivo do " "teu contacto." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Fingerprints differ" msgstr "As pegadas son diferentes" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Fingerprints match" msgstr "As pegadas coinciden" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Cancelar" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Confirmar" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Verificar chave" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Future messages sent by %s from the device that uses this key will be " "highlighted accordingly in the chat window." msgstr "" "As mensaxes futuras enviadas por %s desde o dispositivo que usa esta chave " "será resaltadas na ventá da conversa." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Non coinciden as pegadas dixitais" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Por favor, comproba que estás comparando a pegada correcta. Se as pegadas " "dixitais non coinciden, a conta de %s podería estar comprometida e deberías " "considerar rexeitar esta chave." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Verificar a pegada dixital da chave" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "Compara a pegada dixital desta chave coa pegada mostrada no dispositivo do " "contacto." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Rexeitar chave" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Block encrypted communication with the contact's device that uses this key." msgstr "" "Boque de comunicación cifrada co dispositivo do contacto que usa esta chave." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Aceptar chave" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Allow encrypted communication with the contact's device that uses this key." msgstr "" "Permitir a comunicación cifrada co dispositivo do contacto que usa esta " "chave." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Esta chave está actualmente %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "aceptada" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send encrypted messages." msgstr "" "Esto significa que pode ser utilizada por %s para recibir e enviar mensaxes " "cifradas." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "verficada" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" "Adicionalmente foi verificada e coincide coa chave no dispositivo do " "contacto." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "rexeitada" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to decipher your messages, and you won't " "see messages encrypted with it." msgstr "" "Esto significa que non pode ser usada por %s para descifrar as túas " "mensaxes, e non verás as mensaxes cifradas con ela." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "You won't see encrypted messages from the device of %s that uses this key. " "Conversely, that device won't be able to decipher your messages anymore." msgstr "" "Non verás as mensaxes cifradas desde o dispositivo de %s que usa esta chave. " "De xeito recíproco, ese dispositivo non poderá descifrar as túas." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "You will be able to exchange encrypted messages with the device of %s that " "uses this key." msgstr "" "Poderás intercambiar mensaxes cifradas co dispositivo de %s que usa esta " "chave." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Atrás" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Xestionar" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Este contacto ten novos dispositivos" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Pegada dixital propia" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connection" msgstr "Crearase na primeira conexión" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "Precisa decidir sobre a confianza OMEMO" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "Engadiches un novo dispositivo para a conta %s?" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Cifrado" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d dispositivo OMEMO" msgstr[1] "%d dispositivos OMEMO" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "Xestión das chaves OMEMO" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Aceptar automáticamente novas chaves" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "New encryption keys from this contact will be accepted automatically." msgstr "" "Serán aceptadas de xeito automático novas chaves de cifrado de este contacto." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Chave propia" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Novas chaves" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Chaves asociadas" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "Chaves non activas" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "New encryption keys from your other devices will be accepted automatically." msgstr "" "Aceptaranse de xeito automático novas chaves de cifrado do teus dispositivos." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Aceptada" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Rexeitada" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Verificada" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Sen usar" #~ msgid "Your contact" #~ msgstr "O teu contacto" #~ msgid "Not matching" #~ msgstr "Non coinciden" #~ msgid "Matching" #~ msgstr "Coinciden" #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "highlighted accordingly in the chat window." #~ msgstr "" #~ "Unha vez confirmada, as futuras mensaxes enviadas por %s utilizando esta " #~ "chave será resaltada acorde na ventá de conversa." #~ msgid "" #~ "Stop accepting this key during communication with its associated contact." #~ msgstr "" #~ "Deixar de aceptar esta chave durante a comunicación co contacto asociado " #~ "a ela." #~ msgid "" #~ "Start accepting this key during communication with its associated contact" #~ msgstr "" #~ "Comezar aceptando esta chave durante a comunicación co seu contacto " #~ "asociado" #~ msgid "" #~ "This means it cannot be used by %s to receive messages, and any messages " #~ "sent by it will be ignored." #~ msgstr "" #~ "Esto significa que non pode ser utilizada por %s para recibir mensaxes, e " #~ "calquera mensaxe enviada con ela será ignorada." #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "ignored and none of your messages will be readable using this key." #~ msgstr "" #~ "Unha vez confirmada, calquera mensaxe futura enviada por %s utilizando " #~ "esta chave será ignorada e ningunha das súas mensaxes serán lexibles " #~ "usando esta chave." #~ msgid "" #~ "Once confirmed this key will be usable by %s to receive and send messages." #~ msgstr "" #~ "Unha vez confirmada esta chave será utilizable por %s para recibir e " #~ "enviar mensaxes." #~ msgid "" #~ "When this contact adds new encryption keys to their account, " #~ "automatically accept them." #~ msgstr "" #~ "Cando este contacto engade novas chaves de cifrado a súa conta, aceptalas " #~ "automáticamente." #~ msgid "" #~ "When you add new encryption keys to your account, automatically accept " #~ "them." #~ msgstr "" #~ "Cando engades novas chaves de cifrado a túa conta, aceptalas " #~ "automáticamente." #~ msgid "" #~ "Start accepting this key during communication with its associated contact." #~ msgstr "" #~ "Comezar a aceptar esta chave nas comunicacións co contacto asociado a ela." #~ msgid "Unknown device (0x%.8x)" #~ msgstr "Dispositivo descoñecido (0x%.8x)" #~ msgid "Other devices" #~ msgstr "Outros dispositivos" #~ msgid "- None -" #~ msgstr "-Nada-" dino-0.3.0/plugins/omemo/po/hu.po0000644000000000000000000002262314202022370015325 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2021-12-07 00:53+0000\n" "Language-Team: Hungarian \n" "Language: hu\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: Weblate 4.10-dev\n" #: plugins/omemo/src/ui/bad_messages_populator.vala:157 #, c-format msgid "" "%s has been using an untrusted device. You won't see messages from devices " "that you do not trust." msgstr "" "%s nem megbízható eszközt használt. Nem fogja látni az olyan eszközökről " "érkező üzeneteket, amelyekben nem bízik meg." #: plugins/omemo/src/ui/bad_messages_populator.vala:158 msgid "Manage devices" msgstr "Eszközök kezelése" #: plugins/omemo/src/ui/bad_messages_populator.vala:160 #, c-format msgid "" "%s does not trust this device. That means, you might be missing messages." msgstr "" "%s nem bízik meg ebben az eszközben. Ez azt jelenti, hogy lemaradhat néhány " "üzenetről." #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Kulcs kezelése" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contact's device." msgstr "" "Hasonlítsa össze az ujjlenyomatot karakterről karakterre a partnere eszközén " "láthatóval." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Fingerprints differ" msgstr "Az ujjlenyomatok eltérnek" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Fingerprints match" msgstr "Az ujjlenyomatok egyeznek" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Mégse" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Megerősítés" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Kulcs ellenőrzése" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Future messages sent by %s from the device that uses this key will be " "highlighted accordingly in the chat window." msgstr "" "%s által az ezt a kulcsot használó eszközről küldött jövőbeli üzenetek ennek " "megfelelően lesznek kiemelve a csevegésablakban." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Az ujjlenyomatok nem egyeznek" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Ellenőrizze, hogy a megfelelő ujjlenyomatot hasonlítja-e össze. Ha az " "ujjlenyomatok nem egyeznek, akkor %s fiókja veszélybe kerülhetett, és " "fontolóra kellene vennie a kulcs elutasítását." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Kulcs ujjlenyomatának ellenőrzése" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "Hasonlítsa össze ennek a kulcsnak az ujjlenyomatát a partner eszközén " "megjelenített ujjlenyomattal." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Kulcs elutasítása" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Block encrypted communication with the contact's device that uses this key." msgstr "" "A titkosított kommunikáció tiltása a partner eszközével, amely ezt a kulcsot " "használja." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Kulcs elfogadása" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Allow encrypted communication with the contact's device that uses this key." msgstr "" "A titkosított kommunikáció engedélyezése a partner eszközével, amely ezt a " "kulcsot használja." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Ez a kulcs jelenleg %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "elfogadott" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send encrypted messages." msgstr "" "Ez azt jelenti, hogy %s használhatja titkosított üzenetek fogadásához és " "küldéséhez." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "ellenőrzött" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "Ezenkívül ellenőrizve lett, hogy a kulcs egyezik a partner eszközén." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "elutasított" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to decipher your messages, and you won't " "see messages encrypted with it." msgstr "" "Ez azt jelenti, hogy %s nem használhatja az Ön üzeneteinek visszafejtésére, " "és Ön nem fogja látni az azzal titkosított üzeneteket." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "You won't see encrypted messages from the device of %s that uses this key. " "Conversely, that device won't be able to decipher your messages anymore." msgstr "" "Nem fogja látni %s azon eszközéről érkező titkosított üzeneteket, amely ezt " "a kulcsot használja. Fordítva, a másik eszköz nem lesz képes többé " "visszafejteni az Ön üzeneteit." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "You will be able to exchange encrypted messages with the device of %s that " "uses this key." msgstr "" "Ön képes lesz titkosított üzeneteket váltani %s azon eszközével, amely ezt a " "kulcsot használja." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Vissza" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Kezelés" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Ennek a partnernek új eszközei vannak" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Saját ujjlenyomat" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connection" msgstr "Az első kapcsolódáskor lesz előállítva" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "OMEMO megbízhatósági döntés szükséges" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "Hozzáadott egy új eszközt a(z) %s fiókhoz?" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Titkosítás" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d OMEMO eszköz" msgstr[1] "%d OMEMO eszköz" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "OMEMO kulcskezelés" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Új kulcsok automatikus elfogadása" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "New encryption keys from this contact will be accepted automatically." msgstr "" "Az ettől a partnertől származó új titkosítási kulcsok automatikusan el " "lesznek fogadva." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Saját kulcs" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Új kulcsok" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Hozzárendelt kulcsok" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "Inaktív kulcsok" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "New encryption keys from your other devices will be accepted automatically." msgstr "" "Az egyéb eszközeitől származó új titkosítási kulcsok automatikusan el " "lesznek fogadva." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Elfogadva" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Elutasítva" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Ellenőrizve" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Nem használt" #~ msgid "Unknown device (0x%.8x)" #~ msgstr "Ismeretlen eszköz (0x%.8x)" #~ msgid "Other devices" #~ msgstr "További eszközök" #~ msgid "- None -" #~ msgstr "- Nincs -" dino-0.3.0/plugins/omemo/po/id.po0000644000000000000000000002143114202022370015301 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2021-01-10 15:32+0000\n" "Language-Team: none\n" "Language: id\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Weblate 4.4.1-dev\n" #: plugins/omemo/src/ui/bad_messages_populator.vala:157 #, c-format msgid "" "%s has been using an untrusted device. You won't see messages from devices " "that you do not trust." msgstr "" "%s telah menggunakan perangkat tidak dikenal. Anda tidak akan melihat pesan " "dari perangkat yang tidak Anda kenal." #: plugins/omemo/src/ui/bad_messages_populator.vala:158 msgid "Manage devices" msgstr "Atur perangkat" #: plugins/omemo/src/ui/bad_messages_populator.vala:160 #, c-format msgid "" "%s does not trust this device. That means, you might be missing messages." msgstr "" "%s tidak mengenali perangkat ini. Artinya, Anda mungkin kehilangan pesan." #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Atur kunci" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contact's device." msgstr "" "Bandingkan sidik jari karakter demi karakter, dengan yang tampil di " "perangkat kontak Anda." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Fingerprints differ" msgstr "Sidik jari berbeda" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Fingerprints match" msgstr "Sidik jari cocok" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Batal" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Konfirmasi" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Verifikasi kunci" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Future messages sent by %s from the device that uses this key will be " "highlighted accordingly in the chat window." msgstr "" "Pesan mendatang yang dikirim oleh %s dari perangkat yang menggunakan kunci " "ini akan ditandai di halaman percakapan." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Sidik jari tidak cocok" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Harap Anda memverifikasi sidik jari dengan benar. Jika sidik jari tidak " "cocok, akun %s mungkin telah disusupi dan pertimbangkan untuk menolak kunci " "ini." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Verifikasi sidik jari kunci" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "Bandingkan sidik jari kunci ini dengan sidik jari yang ditampilkan perangkat " "kontak." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Tolak kunci" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Block encrypted communication with the contact's device that uses this key." msgstr "" "Blokir komunikasi terenkripsi dengan perangkat kontak yang menggunakan kunci " "ini." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Terima kunci" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Allow encrypted communication with the contact's device that uses this key." msgstr "" "Izinkan komunikasi terenkripsi dengan perangkat kontak yang menggunakan " "kunci ini." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Status kunci saat ini %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "diterima" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send encrypted messages." msgstr "" "Artinya dapat digunakan oleh %s untuk menerima dan mengirim pesan " "terenkripsi." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "Terverifikasi" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "Telah terverifikasi dengan mencocokkan kunci pada perangkat kontak." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "ditolak" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to decipher your messages, and you won't " "see messages encrypted with it." msgstr "" "Artinya tidak dapat digunakan oleh %s untuk menguraikan pesan Anda, dan Anda " "tidak akan melihat pesan dienkripsi dengannya." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "You won't see encrypted messages from the device of %s that uses this key. " "Conversely, that device won't be able to decipher your messages anymore." msgstr "" "Anda tidak akan melihat pesan terenkripsi dari perangkat %s yang menggunakan " "kunci ini. Sebaliknya, perangkat itu tidak dapat lagi menguraikan pesan Anda." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "You will be able to exchange encrypted messages with the device of %s that " "uses this key." msgstr "" "Anda dapat bertukar pesan terenkripsi dengan perangkat %s yang menggunakan " "kunci ini." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Kembali" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Pengaturan" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Kontak memiliki perangkat baru" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Sidik jari sendiri" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connection" msgstr "Akan dibuat pada koneksi pertama" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "Memerlukan persetujuan atas OMEMO" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "Apakah Anda menambahkan perangkat baru untuk akun %s?" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Enkripsi" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d OMEMO perangkat" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "Pengaturan kunci OMEMO" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Otomatis mengkonfirmasi kunci baru" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "New encryption keys from this contact will be accepted automatically." msgstr "Kunci enkripsi baru dari kontak ini akan diterima secara otomatis." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Kunci milik sendiri" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Kunci baru" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Kunci terkait" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "Kunci non-aktif" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "New encryption keys from your other devices will be accepted automatically." msgstr "" "Kunci enkripsi baru dari perangkat Anda yang lain akan diterima secara " "otomatis." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Diterima" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Ditolak" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Terverifikasi" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Tidak terpakai" #~ msgid "Your contact" #~ msgstr "Kontak anda" dino-0.3.0/plugins/omemo/po/ie.po0000644000000000000000000002427514202022370015313 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2021-03-01 10:50+0000\n" "Language-Team: none\n" "Language: ie\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: Weblate 4.5\n" #: plugins/omemo/src/ui/bad_messages_populator.vala:157 #, c-format msgid "" "%s has been using an untrusted device. You won't see messages from devices " "that you do not trust." msgstr "" #: plugins/omemo/src/ui/bad_messages_populator.vala:158 msgid "Manage devices" msgstr "Gerer aparates" #: plugins/omemo/src/ui/bad_messages_populator.vala:160 #, c-format msgid "" "%s does not trust this device. That means, you might be missing messages." msgstr "%s ne have fide por ti-ci aparate. Vu fórsan manca alcun missages." #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Gerer li clave" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contact's device." msgstr "" "Cuidosi compara li fingre-print con ti que es monstrat sur li aparate de vor " "contacte." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Fingerprints differ" msgstr "Fingre-printes difere" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Fingerprints match" msgstr "Fingre-printes es identic" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Anullar" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Confirmar" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Verificar li clave" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Future messages sent by %s from the device that uses this key will be " "highlighted accordingly in the chat window." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Fingre-printes ne es egal" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Ples verificar que vu compara li just fingre-print. Si fingre-printes ne es " "egal, li conto de %s posse esser compromettet e vu deve rejecter ti-ci clave." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Verificar li fingre-print" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "Comparar li fingre-print de ti-ci clave con clave que es monstrat sur li " "aparate del contacte." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Rejecter li clave" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Block encrypted communication with the contact's device that uses this key." msgstr "" "Blocar ciffrat communication con li aparate del contacte quel usa ti-ci " "clave." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Acceptar li clave" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Allow encrypted communication with the contact's device that uses this key." msgstr "" "Permisser ciffrat communication con li aparate del contacte quel usa ti-ci " "clave." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Ti-ci clave es actualmen %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "acceptat" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send encrypted messages." msgstr "To significa que %s posse usar it por reciver e inviar missages." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "verificat" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" "Adplu, it ha esset verificat corresponder con li clave sur li aparate del " "contacte." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "rejectet" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to decipher your messages, and you won't " "see messages encrypted with it." msgstr "" "To significa que %s posse usar it por deciffrar vor missages e que vu ne va " "vider li missages ciffrat med it." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "You won't see encrypted messages from the device of %s that uses this key. " "Conversely, that device won't be able to decipher your messages anymore." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "You will be able to exchange encrypted messages with the device of %s that " "uses this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Retro" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Gerer" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Ti-ci contacte have nov aparates" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Propri fingre-print" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connection" msgstr "Va esser generat pos li prim conexion" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "Un decision de confidentie OMEMO es besonat" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "Esque vu ha adjuntet un nov aparate por li conto %s?" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Ciffration" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d aparate OMEMO" msgstr[1] "%d aparates OMEMO" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "Gerentie de claves OMEMO" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Automaticmen acceptar nov claves" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "New encryption keys from this contact will be accepted automatically." msgstr "" "Nov claves de ciffration de ti-ci contacte va esser acceptat automaticmen." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Propri clave" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Nov claves" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Associat claves" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "Ínactiv claves" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "New encryption keys from your other devices will be accepted automatically." msgstr "" "Nov claves de ciffration de vor altri aparates va esser acceptat " "automaticmen." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Acceptat" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Rejectet" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Verificat" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Ínusat" #~ msgid "Not matching" #~ msgstr "Ne corresponde" #~ msgid "Matching" #~ msgstr "Corresponde" #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "highlighted accordingly in the chat window." #~ msgstr "" #~ "Pos li confirmation, omni futuri missages inviat per %s con ti-ci clave " #~ "va esser colorat correspondmen in li fenestre de conversation." #~ msgid "" #~ "Stop accepting this key during communication with its associated contact." #~ msgstr "" #~ "Cessar acceptar ti-ci clave por communication con su associat contacte." #~ msgid "" #~ "Start accepting this key during communication with its associated contact" #~ msgstr "Acceptar ti-ci clave por communication con su associat contacte" #~ msgid "" #~ "This means it cannot be used by %s to receive messages, and any messages " #~ "sent by it will be ignored." #~ msgstr "" #~ "To significa que %s ne posse usar it por reciver missages, e su missages " #~ "inviat va esser ignorat." #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "ignored and none of your messages will be readable using this key." #~ msgstr "" #~ "Pos confirmation, omni futuri missages inviat per %s con ti-ci clave va " #~ "esser ignorat, e null vor missages va esser leibil con ti-ci clave." #~ msgid "" #~ "Once confirmed this key will be usable by %s to receive and send messages." #~ msgstr "" #~ "Pos confirmation, ti-ci clave va esser usabil per %s por reciver e inviar " #~ "missages." #~ msgid "" #~ "When this contact adds new encryption keys to their account, " #~ "automatically accept them." #~ msgstr "" #~ "Quande ti-ci contacte adjunte nov ciffre-claves a su conto, acceptar les " #~ "automaticmen." #~ msgid "" #~ "When you add new encryption keys to your account, automatically accept " #~ "them." #~ msgstr "" #~ "Quande vu adjunte nov ciffre-claves a vor conto, acceptar les " #~ "automaticmen." dino-0.3.0/plugins/omemo/po/it.po0000644000000000000000000002725714202022370015335 0ustar rootrootmsgid "" msgstr "" "Project-Id-Version: dino-omemo-0.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-11-25 11:28+0000\n" "Language-Team: Italian \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 4.4-dev\n" #: plugins/omemo/src/ui/bad_messages_populator.vala:157 #, c-format msgid "" "%s has been using an untrusted device. You won't see messages from devices " "that you do not trust." msgstr "" "%s sta usando un dispositivo non verificato. Non vedrai messaggi inviati da " "un dispositivo che non hai verificato." #: plugins/omemo/src/ui/bad_messages_populator.vala:158 msgid "Manage devices" msgstr "Gestisci dispositivi" #: plugins/omemo/src/ui/bad_messages_populator.vala:160 #, c-format msgid "" "%s does not trust this device. That means, you might be missing messages." msgstr "" "%s non ha verificato questo dispositivo. Ciò significa che potresti perdere " "dei messaggi." #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Gestisci la chiave" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contact's device." msgstr "" "Confronta la fingerprint, carattere per carattere, con quella mostrata sul " "dispositivo del tuo contatto." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Fingerprints differ" msgstr "Le impronte non combaciano" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Fingerprints match" msgstr "Le impronte combaciano" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Annulla" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Conferma" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Verifica la chiave" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Future messages sent by %s from the device that uses this key will be " "highlighted accordingly in the chat window." msgstr "" "I messaggi futuri inviati da %s dal dispositivo che usa questa chiave " "saranno evidenziati di conseguenza nella finestra della chat." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Le impronte non corrispondono" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Si prega di verificare che si stia confrontanto la fingerprint corretta. Se " "le fingerprint non corrispondono, l'account di %s potrebbe essere " "compromesso e si dovrebbe rifiutare questa chiave." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Verifica la fingerprint della chiave" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "Confronta la fingerprint di questa chiave con quella mostrata sul " "dispositivo del contatto." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Chiave rifiutata" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Block encrypted communication with the contact's device that uses this key." msgstr "" "Blocca le comunicazioni crittografate con il dispositivo del contatto che " "sta usando questa chiave." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Chiave accettata" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Allow encrypted communication with the contact's device that uses this key." msgstr "" "Permetti la comunicazione crittografata con il dispositivo del contatto che " "sta usando questa chiave." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "La chiave attualmente è %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "accettata" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send encrypted messages." msgstr "" "Questo vuol dire che può essere usata da %s per ricevere ed inviare messaggi." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "verificata" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" "Inoltre è stato verificato che corrisponda con la chiave sul dispositivo del " "contatto." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "rifiutata" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to decipher your messages, and you won't " "see messages encrypted with it." msgstr "" "Questo vuol dire che non può essere usata da %s per decifrare i tuoi " "messaggi, e tu non vedrai i messaggi crittografati con essa." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "You won't see encrypted messages from the device of %s that uses this key. " "Conversely, that device won't be able to decipher your messages anymore." msgstr "" "Non vedrai i messaggi crittografati dal dispositivo di %s che usa questa " "chiave. Al contrario, quel dispositivo non sarà più in grado di decifrare i " "tuoi messaggi." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "You will be able to exchange encrypted messages with the device of %s that " "uses this key." msgstr "" "Sarai in grado di scambiare messaggi crittografati con il dispositivo di %s " "che usa questa chiave." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Indietro" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Gestisci" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Questo contatto ha dei nuovi dispositivi" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Propria impronta" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connection" msgstr "Verrà generata alla prima connessione" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "Una decisione sulla fiducia è necessaria per la chiave OMEMO" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "Hai aggiunto un nuovo dispositivo per l'account %s?" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Crittografia" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d dispositivo OMEMO" msgstr[1] "%d dispositivi OMEMO" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "Gestione delle chiavi OMEMO" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Accetta automaticamente le nuove chiavi" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "New encryption keys from this contact will be accepted automatically." msgstr "" "Le nuove chiavi di crittografia di questo contatto verranno accettate " "automaticamente." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Proprie chiavi" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Nuove chiavi" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Chiavi associate" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "Chiavi inattive" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "New encryption keys from your other devices will be accepted automatically." msgstr "" "Le nuove chiavi di crittografia dei tuoi altri dispositivi verranno " "accettate automaticamente." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Accettata" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Rifiutata" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Verificata" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Inutilizzata" #~ msgid "Your contact" #~ msgstr "Il tuo contatto" #~ msgid "Not matching" #~ msgstr "Non corrispondenti" #~ msgid "Matching" #~ msgstr "Corrispondenti" #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "highlighted accordingly in the chat window." #~ msgstr "" #~ "Una volta confermato, qualsiasi messaggio futuro inviato da %s usando " #~ "questa chiave sarà evidenziato di conseguenza nella finestra della " #~ "conversazione." #~ msgid "" #~ "Stop accepting this key during communication with its associated contact." #~ msgstr "" #~ "Smetti di accettare questa chiave durante le comunicazioni col contatto " #~ "associato." #~ msgid "" #~ "Start accepting this key during communication with its associated contact" #~ msgstr "" #~ "Inizia ad accettare questa chiave durante le comunicazioni con il " #~ "contatto ad essa associato" #~ msgid "" #~ "This means it cannot be used by %s to receive messages, and any messages " #~ "sent by it will be ignored." #~ msgstr "" #~ "Questo vuol dire che non può essere usata da %s per ricevere messaggi, ed " #~ "ogni messaggio inviato attraverso di essa sarà ignorato." #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "ignored and none of your messages will be readable using this key." #~ msgstr "" #~ "Una volta confermato, ogni messaggio futuro inviato da %s usando questa " #~ "chiave sarà ignorato e nessuno dei tuoi messaggi sarà leggibile usando " #~ "questa chiave." #~ msgid "" #~ "Once confirmed this key will be usable by %s to receive and send messages." #~ msgstr "" #~ "Una volta confermata questa chiave sarà utilizzabile da %s per ricevere " #~ "ed inviare messaggi." #~ msgid "" #~ "When this contact adds new encryption keys to their account, " #~ "automatically accept them." #~ msgstr "" #~ "Quando questo contatto aggiunge nuove chiavi di cifratura al proprio " #~ "account, accettale automaticamente." #~ msgid "" #~ "When you add new encryption keys to your account, automatically accept " #~ "them." #~ msgstr "" #~ "Quando aggiungi nuove chiavi di cifratura al tuo account, accettale " #~ "automaticamente." #~ msgid "" #~ "Start accepting this key during communication with its associated contact." #~ msgstr "" #~ "Inizia ad accettare questa chiave durante le comunicazioni col contatto " #~ "associato" #~ msgid "Reject Key" #~ msgstr "Rifiuta la chiave" #~ msgid "Accept Key" #~ msgstr "Accetta la chiave" #~ msgid "Unknown device (0x%.8x)" #~ msgstr "Dispositivo sconosciuto (0x%.8x)" #~ msgid "Other devices" #~ msgstr "Altri dispositivi" #~ msgid "- None -" #~ msgstr "- Nessuno -" #~ msgid "OMEMO" #~ msgstr "OMEMO" #~ msgid "Database error" #~ msgstr "Errore del database" dino-0.3.0/plugins/omemo/po/ja.po0000644000000000000000000002766614202022370015317 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-10-10 10:49+0000\n" "Language-Team: none\n" "Language: ja\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Weblate 4.3-dev\n" #: plugins/omemo/src/ui/bad_messages_populator.vala:157 #, c-format msgid "" "%s has been using an untrusted device. You won't see messages from devices " "that you do not trust." msgstr "" "%s は信頼されていないデバイスを使用しています。信頼していないデバイスから送信" "されたメッセージは閲覧できません。" #: plugins/omemo/src/ui/bad_messages_populator.vala:158 msgid "Manage devices" msgstr "デバイスを管理" #: plugins/omemo/src/ui/bad_messages_populator.vala:160 #, c-format msgid "" "%s does not trust this device. That means, you might be missing messages." msgstr "" "%s はこのデバイスを信頼していません。相手がメッセージを正しく受信できない可能" "性があります。" #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "キーを管理" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contact's device." msgstr "" "以下のフィンガープリントと、お使いのデバイスに表示されているフィンガープリン" "トを、1文字ずつ比較してください。" #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Fingerprints differ" msgstr "フィンガープリントが一致しません" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Fingerprints match" msgstr "フィンガープリントが一致します" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "キャンセル" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "確認" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "キーを確認" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Future messages sent by %s from the device that uses this key will be " "highlighted accordingly in the chat window." msgstr "" "今後このキーを使ったデバイスから %s が送信するメッセージは、チャットウィンド" "ウでハイライト表示されます。" #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "フィンガープリントが一致しません" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "正しいフィンガープリントを比較しているか確認してください。フィンガープリント" "が一致しない場合、%s のアカウントはなりすましの可能性があるので、このキーを拒" "否することをおすすめします。" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "キーのフィンガープリントを検証" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "このキーのフィンガープリントと、連絡先のデバイスに表示されたフィンガープリン" "トを比較します。" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "キーを拒否" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Block encrypted communication with the contact's device that uses this key." msgstr "このキーを使った連絡先のデバイスとの暗号化された会話をブロックします。" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "キーを許可" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Allow encrypted communication with the contact's device that uses this key." msgstr "このキーを使った連絡先のデバイスとの暗号化された会話を許可します。" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "このキーは現在%s。" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "許可されています" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send encrypted messages." msgstr "このキーを使って、%s は暗号化されたメッセージを送受信できます。" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "検証済みです" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "加えて、連絡先のデバイスのキーと一致することを検証済みです。" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "拒否されています" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to decipher your messages, and you won't " "see messages encrypted with it." msgstr "" "このキーを使って、%s はあなたのメッセージを解読できなくなります。また、あなた" "は、このキーで暗号化されたメッセージを確認できなくなります。" #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "You won't see encrypted messages from the device of %s that uses this key. " "Conversely, that device won't be able to decipher your messages anymore." msgstr "" "このキーを使った %s のデバイスから、暗号化されたメッセージを確認できなくなり" "ます。逆に、今後そのデバイスでは、あなたのメッセージを解読できなくなります。" #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "You will be able to exchange encrypted messages with the device of %s that " "uses this key." msgstr "" "このキーを使った %s のデバイスと、暗号化されたメッセージをやり取りできるよう" "になります。" #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "戻る" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "管理" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "この連絡先に新しいデバイスが追加されました" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "自身のフィンガープリント" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connection" msgstr "最初の接続で生成されます" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "OMEMO 信頼選択が必要です" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "アカウント %s に新しいデバイスを追加しましたか?" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "暗号化" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d 台の OMEMO デバイス" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "OMEMO キー管理" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "自動的に新しいキーを許可" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "New encryption keys from this contact will be accepted automatically." msgstr "この連絡先から発行された新しい暗号化キーが自動的に許可されます。" #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "自身のキー" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "新しいキー" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "関連付けられたキー" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "非アクティブなキー" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "New encryption keys from your other devices will be accepted automatically." msgstr "" "あなたのほかのデバイスから発行された新しい暗号化キーが自動的に許可されます。" #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "許可" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "拒否" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "検証済み" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "未使用" #~ msgid "Your contact" #~ msgstr "あなたの連絡先" #~ msgid "Not matching" #~ msgstr "一致しない" #~ msgid "Matching" #~ msgstr "一致" #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "highlighted accordingly in the chat window." #~ msgstr "" #~ "一度確認すると、今後このキーを使って %s から送信されるメッセージは、チャッ" #~ "トウィンドウでハイライト表示されます。" #~ msgid "" #~ "Stop accepting this key during communication with its associated contact." #~ msgstr "" #~ "関連付けられた連絡先とトークをする際に、このキーを許可しないようにします。" #~ msgid "" #~ "Start accepting this key during communication with its associated contact" #~ msgstr "" #~ "関連付けられた連絡先とトークをする際に、このキーを許可するようにします" #~ msgid "" #~ "This means it cannot be used by %s to receive messages, and any messages " #~ "sent by it will be ignored." #~ msgstr "" #~ "このキーを使って %s からのメッセージを受信できません。また、当該アドレスか" #~ "らのメッセージはすべて無視されます。" #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "ignored and none of your messages will be readable using this key." #~ msgstr "" #~ "一度確認すると、今後このキーを使って %s から送信されるメッセージは無視さ" #~ "れ、このキーを使ったご自身のメッセージも読み取れなくなります。" #~ msgid "" #~ "Once confirmed this key will be usable by %s to receive and send messages." #~ msgstr "" #~ "一度確認すると、このキーを使って %s からのメッセージを送受信可能になりま" #~ "す。" #~ msgid "" #~ "When this contact adds new encryption keys to their account, " #~ "automatically accept them." #~ msgstr "" #~ "この連絡先の相手がアカウントに追加する新しい暗号化キーを自動的に許可しま" #~ "す。" #~ msgid "" #~ "When you add new encryption keys to your account, automatically accept " #~ "them." #~ msgstr "アカウントに追加した新しい暗号化キーを自動的に許可します。" dino-0.3.0/plugins/omemo/po/lb.po0000644000000000000000000002713614202022370015312 0ustar rootrootmsgid "" msgstr "" "Project-Id-Version: dino-omemo-0.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-04-12 20:01+0000\n" "Language-Team: Luxembourgish \n" "Language: lb\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: Weblate 4.0-dev\n" #: plugins/omemo/src/ui/bad_messages_populator.vala:157 #, c-format msgid "" "%s has been using an untrusted device. You won't see messages from devices " "that you do not trust." msgstr "" #: plugins/omemo/src/ui/bad_messages_populator.vala:158 msgid "Manage devices" msgstr "Geräter managen" #: plugins/omemo/src/ui/bad_messages_populator.vala:160 #, c-format msgid "" "%s does not trust this device. That means, you might be missing messages." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Schlëssel verwalten" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contact's device." msgstr "" "Vergläich de Fangerofdrock, Zeeche fir Zeechen, mat dem den um Gerät vum " "Kontakt ugewise gëtt." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Fingerprints differ" msgstr "Fangerofdréck stemmen net iwwerteneen" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Fingerprints match" msgstr "Fangerofdréck stemmen iwwerteneen" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Ofbriechen" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Confirméieren" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Schlëssel verifizéieren" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Future messages sent by %s from the device that uses this key will be " "highlighted accordingly in the chat window." msgstr "" "Zukünfteg Noriichten, déi vum Apparat vum %s, den dëse Schlëssel benotzt, " "geschéckt ginn, wäerten deementspriechend am Chatfenster ervirgehuewen ginn." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Fangerofdréck stëmmen net iwwerteneen" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Wann ech gelift iwwerpréif, ob's du mat dem " "korrekte Fangerofdrock vergläichs. Wann Fangerofdréck net iwwerteneestëmmen, " "dann ass dem %s säi Konto villäicht kompromettéiert an du solls dëse " "Schlëssel verweigeren." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Iwwerpréif Schlëssel Fangerofdrock" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "Vergläich de Fangerofdrock vun dësem Schlëssel mam Fangerofdrock den um " "Gerät vun dësem Kontakt steet." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Schlëssel ofleenen" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Block encrypted communication with the contact's device that uses this key." msgstr "" "Blockéiert verschlësselt Kommunikatioun mam Apparat vum Kontakt deen dëse " "Schlëssel benotzt." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Schlëssel acceptéieren" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Allow encrypted communication with the contact's device that uses this key." msgstr "" "Erlaabt verschlësselt Kommunikatioun mam Apparat vum Kontakt, deen dëse " "Schlëssel benotzt." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Den aktuelle Schlëssel ass %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "akzeptéiert" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send encrypted messages." msgstr "" "Dat bedeit, dass et vum %s ka benotzt gi fir Messagen ze empfänken an ze " "verschécken." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "verifizéiert" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" "De Schlëssel ass zousätzlech iwwerpréift gi mam Schlëssel um Gerät vum " "Kontakt iwwerteneen ze stëmmen." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "ofgeleent" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to decipher your messages, and you won't " "see messages encrypted with it." msgstr "" "Dëst bedeit datt et net vum %s ka benotzt ginn fir är Messagen ze " "entschlësselen, an Dir gesitt keng Messagen déi mat dësem verschlësselt sinn." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "You won't see encrypted messages from the device of %s that uses this key. " "Conversely, that device won't be able to decipher your messages anymore." msgstr "" "Dir gesitt net verschlësselt Message vum Apparat vum %s deen dëse Schlëssel " "benotzt. Ëmgekéiert kann dëse Apparat net méi Är Messagen entschlësselen." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "You will be able to exchange encrypted messages with the device of %s that " "uses this key." msgstr "" "Dir kënnt verschlësselte Messagen austausche mat dem Apparat vum %s deen " "dësen Schlëssel benotzt." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Zeréck" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Verwalten" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Dëse Kontakt huet nei Geräter" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Eegene Fangerofdrock" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connection" msgstr "Gett bei der éischter Connectioun generéiert" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "OMEMO Vertrauens Entscheedung noutwenneg" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "Hues du een neit Gerät fir %s dobäi gemaach?" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Verschlësselung" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d OMEMO Gerät" msgstr[1] "%d OMEMO Geräter" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "OMEMO Schlësselenverwaltung" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Automatesch nei Schlësselen akzeptéieren" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "New encryption keys from this contact will be accepted automatically." msgstr "" "Nei Verschlësslungs Schlëssele vun dem Kontakt ginn automatesch akzeptéiert." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Eegene Schlëssel" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Nei Schlësselen" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Zougehéiereg Schlësselen" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "Inaktiv Schlësselen" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "New encryption keys from your other devices will be accepted automatically." msgstr "" "Nei Verschlësslungs Schlëssele vun ären aneren Apparater ginn automatesch " "akzeptéiert." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Akzeptéiert" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Ofgeleent" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Verifizéiert" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Onbenotzt" #~ msgid "Not matching" #~ msgstr "Stemmt net iwwerteneen" #~ msgid "Matching" #~ msgstr "Stemmt iwwerteneen" #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "highlighted accordingly in the chat window." #~ msgstr "" #~ "Wann konforméiert, all zukünfteg Messagen déi vum %s mat dësem Schlëssel " #~ "geschéckt ginn, ginn deementspriechend am Chatfenster markéiert." #~ msgid "" #~ "Stop accepting this key during communication with its associated contact." #~ msgstr "" #~ "Dëse Schlëssel net méi akzeptéiere wärend der Kommunikatioun mat sengem " #~ "verbonnene Kontakt." #~ msgid "" #~ "Start accepting this key during communication with its associated contact" #~ msgstr "Dëse Schlëssel wärend de Conversatioun mat sengem Kontakt benotzen" #~ msgid "" #~ "This means it cannot be used by %s to receive messages, and any messages " #~ "sent by it will be ignored." #~ msgstr "" #~ "Dat bedeit, dass dëse Schlëssel net ka vum %s benotz gi fir Messagen ze " #~ "empfänken an all Message déi geschéckt gi ginn ignoréiert." #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "ignored and none of your messages will be readable using this key." #~ msgstr "" #~ "Wann confirméiert, all zukünfteg Message déi vum %s mat dësem Schlëssel " #~ "geschéckt ginn, ginn ignoréiert a keng vun dengem Messagë wäerte liesbar " #~ "sinn." #~ msgid "" #~ "Once confirmed this key will be usable by %s to receive and send messages." #~ msgstr "" #~ "Wann konforméiert ass de Schlëssel vum %s benotzbar fir Messagen ze " #~ "empfänken an ze schécken." #~ msgid "" #~ "When this contact adds new encryption keys to their account, " #~ "automatically accept them." #~ msgstr "" #~ "Wann dëse Kontakt néi Schlësselen zu sengem Konto bäifüügt, déi " #~ "automatesch akzeptéieren." #~ msgid "" #~ "When you add new encryption keys to your account, automatically accept " #~ "them." #~ msgstr "" #~ "Wann s du nei Schlëssele bei däin Account bäifüügs, déi automatesch " #~ "akzeptéieren." #~ msgid "" #~ "Start accepting this key during communication with its associated contact." #~ msgstr "" #~ "Dëse Schlëssel wärend der Kommunikatioun mat sengem verbonne Kontakt " #~ "akzeptéieren." #~ msgid "Reject Key" #~ msgstr "Schlëssel verweigeren" #~ msgid "Accept Key" #~ msgstr "Schlëssel acceptéieren" #~ msgid "Unknown device (0x%.8x)" #~ msgstr "Onbekannt Gerät (0x%.8x)" #~ msgid "Other devices" #~ msgstr "Aner Geräter" #~ msgid "- None -" #~ msgstr "- Keng -" #~ msgid "OMEMO" #~ msgstr "OMEMO" #~ msgid "Database error" #~ msgstr "Datebank Feeler" dino-0.3.0/plugins/omemo/po/lt.po0000644000000000000000000002603014202022370015324 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-11-21 18:29+0000\n" "Language-Team: none\n" "Language: lt\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%100<10 || n%100>=20) ? 1 : 2);\n" "X-Generator: Weblate 4.4-dev\n" #: plugins/omemo/src/ui/bad_messages_populator.vala:157 #, c-format msgid "" "%s has been using an untrusted device. You won't see messages from devices " "that you do not trust." msgstr "" "%s naudoja nepatikimą įrenginį. Jūs nematysite žinučių iš įrenginių, kuriais " "nepasitikite." #: plugins/omemo/src/ui/bad_messages_populator.vala:158 msgid "Manage devices" msgstr "Tvarkyti įrenginius" #: plugins/omemo/src/ui/bad_messages_populator.vala:160 #, c-format msgid "" "%s does not trust this device. That means, you might be missing messages." msgstr "" "%s nepasitiki šiuo įrenginiu. Tai reiškia, kad galite nematyti žinučių." #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Tvarkyti raktą" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contact's device." msgstr "" "Palyginkite kontrolinį kodą, simbolis po simbolio, su tuo, kuris yra rodomas " "jūsų adresato įrenginyje." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Fingerprints differ" msgstr "Kodas skiriasi" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Fingerprints match" msgstr "Kodas atitinka" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Atsisakyti" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Patvirtinti" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Patikrinkite raktą" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Future messages sent by %s from the device that uses this key will be " "highlighted accordingly in the chat window." msgstr "" "Nuo šiol žinutės nuo %s išsiųstos iš įrenginio naudojančio šį kontrolinį " "kodą bus atitinkamai paryškintos." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Kontroliniai kodai nesutampa" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Įsitikinkite, kad lyginate teisingą kontrolinį kodą. Jei kontroliniai kodai " "nesutampa, gali būti, kad %s paskyra yra sukompromituota ir turėtumėte " "apsvarstyti galimybę atmesti šį raktą." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Patikrinti rakto kontrolinį kodą" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "Palyginti šio rakto kontrolinį kodą su adresato įrenginyje rodomu " "kontroliniu kodu." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Atmesti raktą" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Block encrypted communication with the contact's device that uses this key." msgstr "" "Blokuoti šifruotą bendravimą su kontakto įrenginiu, kuris naudoja šį raktą." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Priimti raktą" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Allow encrypted communication with the contact's device that uses this key." msgstr "" "Leisti šifruotą bendravimą su kontakto įrenginiu, kuris naudoja šį raktą." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Šiuo metu šis raktas yra %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "priimtas" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send encrypted messages." msgstr "" "Tai reiškia, kad %s gali jį naudoti šifruotų žinučių siuntimui ir gavimui." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "patikrintas" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" "Be to, jis buvo patikrintas ir sutampa su adresato įrenginyje rodomu raktu." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "atmestas" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to decipher your messages, and you won't " "see messages encrypted with it." msgstr "" "Tai reiškia, kad %s negali jo naudoti jūsų žinučių iššifravimui ir jūs " "nematysite žinučiu užšifruotų šiuo raktu." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "You won't see encrypted messages from the device of %s that uses this key. " "Conversely, that device won't be able to decipher your messages anymore." msgstr "" "Jūs nematysite šifruotų žinučių išsiustų iš %s įrenginio, kuris naudoją šį " "raktą. Tuo pačiu tas įrenginys nebegalės iššifruoti jūsų žinučių." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "You will be able to exchange encrypted messages with the device of %s that " "uses this key." msgstr "" "Jūs galite keistis šifruotomis žinutėmis su %s įrenginiu, kuris naudoja šį " "raktą." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Atgal" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Tvarkyti" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Šis adresatas turi naujų įrenginių" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Nuosavas kontrolinis kodas" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connection" msgstr "Bus sugeneruotas pirmojo prisijungimo metu" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "OMEMO reikia pasitikėjimo sprendimo" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "Ar savo paskyrai %s pridėjote naują įrenginį?" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Šifravimas" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d OMEMO įrenginys" msgstr[1] "%d OMEMO įrenginiai" msgstr[2] "%d OMEMO įrenginių" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "OMEMO raktų tvarkymas" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Automatiškai priimti naujus raktus" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "New encryption keys from this contact will be accepted automatically." msgstr "Nauji šifravimo raktai iš šio kontakto bus priimti automatiškai." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Nuosavas raktas" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Nauji raktai" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Susieti raktai" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "Neaktyvūs raktai" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "New encryption keys from your other devices will be accepted automatically." msgstr "" "Nauji šifravimo raktai iš jūsų kitų įrenginių bus priimti automatiškai." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Priimtas" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Atmestas" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Patikrintas" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Nenaudojamas" #~ msgid "Your contact" #~ msgstr "Jūsų adresatas" #~ msgid "Not matching" #~ msgstr "Nesutampa" #~ msgid "Matching" #~ msgstr "Sutampa" #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "highlighted accordingly in the chat window." #~ msgstr "" #~ "Patikrinus, bet kokios būsimos žinutės, kurias %s išsiųs naudodamas šį " #~ "raktą, bus atitinkamai paryškintos pokalbių kambaryje." #~ msgid "" #~ "Stop accepting this key during communication with its associated contact." #~ msgstr "Nustoti priiminėti šį raktą bendravimo su susietu adresatu metu." #~ msgid "" #~ "Start accepting this key during communication with its associated contact" #~ msgstr "Pradėti priiminėti šį raktą bendravimo su susietu adresatu metu" #~ msgid "" #~ "This means it cannot be used by %s to receive messages, and any messages " #~ "sent by it will be ignored." #~ msgstr "" #~ "Tai reiškia, kad %s negali jo naudoti, kad gautų žinutes, o bet kurių " #~ "adresato siunčiamų žinučių bus nepaisoma." #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "ignored and none of your messages will be readable using this key." #~ msgstr "" #~ "Patvirtinus, bet kurių būsimų žinučių, kurias %s išsiųs naudodamas šį " #~ "raktą, bus nepaisoma ir jokių jūsų žinučių nebus įmanoma perskaityti " #~ "naudojant šį raktą." #~ msgid "" #~ "Once confirmed this key will be usable by %s to receive and send messages." #~ msgstr "" #~ "Patvirtinus, šis raktas taps tinkamu naudoti %s, kad gautų ir siųstų " #~ "žinutes." #~ msgid "" #~ "When this contact adds new encryption keys to their account, " #~ "automatically accept them." #~ msgstr "" #~ "Kai šis adresatas prie savo paskyros pridės naujus šifravimo raktus, " #~ "automatiškai juos priimti." #~ msgid "" #~ "When you add new encryption keys to your account, automatically accept " #~ "them." #~ msgstr "" #~ "Kai pridedate naujus šifravimo raktus prie savo paskyros, automatiškai " #~ "juos priimti." dino-0.3.0/plugins/omemo/po/nb.po0000644000000000000000000002657314202022370015320 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-10-10 10:49+0000\n" "Language-Team: Norwegian Bokmål \n" "Language: nb\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: Weblate 4.3-dev\n" #: plugins/omemo/src/ui/bad_messages_populator.vala:157 #, c-format msgid "" "%s has been using an untrusted device. You won't see messages from devices " "that you do not trust." msgstr "" "%s har brukt en ubetrodd enhet. Du vil ikke se meldinger fra enheter du ikke " "har tiltro til." #: plugins/omemo/src/ui/bad_messages_populator.vala:158 msgid "Manage devices" msgstr "Håndter enheter" #: plugins/omemo/src/ui/bad_messages_populator.vala:160 #, c-format msgid "" "%s does not trust this device. That means, you might be missing messages." msgstr "" "%s har ikke tiltro til denne enheten. Det betyr at du kan gå glipp av " "meldinger." #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Håndter nøkkel" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contact's device." msgstr "" "Sammenlign fingeravtrykket, tegn for tegn, med den vist på din kontakts " "enhet." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Fingerprints differ" msgstr "Fingeravtrykkene stemmer ikke overens" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Fingerprints match" msgstr "Fingeravtrykkene stemmer overens" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Avbryt" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Bekreft" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Bekreft nøkkel" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Future messages sent by %s from the device that uses this key will be " "highlighted accordingly in the chat window." msgstr "" "Fremtidige meldinger sendt av %s fra enheten som benytter denne nøkkelen vil " "framheves i så måte i sludrevinduet." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Fingeravtrykkene stemmer ikke overens" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Sjekk at du sammenligner riktig fingeravtrykk. Hvis de ikke stemmer overens, " "kan det hende %s sin konto har falt i gale hender, og du bør overveie å " "avvise denne nøkkelen." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Sjekk nøkkelfingeravtrykk" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "Sammenlign dette fingeravtrykket med fingeravtrykket vist på kontaktens " "enhet." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Avslå nøkkel" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Block encrypted communication with the contact's device that uses this key." msgstr "" "Blokker kryptert kommunikasjon med kontaktens enhet som benytter denne " "nøkkelen." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Godta nøkkel" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Allow encrypted communication with the contact's device that uses this key." msgstr "" "Tillat kryptert kommunikasjon med kontaktens enhet som benytter denne " "nøkkelen." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Denne nøkkelen er i øyeblikket %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "godtatt" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send encrypted messages." msgstr "" "Dette betyr at den kan brukes av %s til å motta og sende krypterte meldinger." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "bekreftet" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" "I tillegg har den blitt bekreftet å samsvare med nøkkelen på kontaktens " "enhet." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "avslått" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to decipher your messages, and you won't " "see messages encrypted with it." msgstr "" "Dette betyr den ikke kan brukes av %s for å fortolke meldingene dine, og du " "vil ikke kunne se meldinger kryptert med den." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "You won't see encrypted messages from the device of %s that uses this key. " "Conversely, that device won't be able to decipher your messages anymore." msgstr "" "Du vil ikke se krypterte meldinger fra %s sin enhet som bruker denne " "nøkkelen. Denne enheten vil heller ikke kunne fortolke dine meldinger lenger." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "You will be able to exchange encrypted messages with the device of %s that " "uses this key." msgstr "" "Du vil kunne utveksle krypterte meldinger med %s sin enhet som benytter " "denne nøkkelen." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Tilbake" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Behandle" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Denne kontakten har nye enheter" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Eget fingeravtrykk" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connection" msgstr "Vil bli generert ved første tilkobling" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "OMEMO tillitsbeslutning kreves" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "La du til en ny enhet for kontoen %s?" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Kryptering" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d OMEMO-enhet" msgstr[1] "%d OMEMO-enheter" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "OMEMO-nøkkelhåndtering" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Godta nye nøkler automatisk" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "New encryption keys from this contact will be accepted automatically." msgstr "Nye krypteringsnøkler fra denne kontakten vil godtas automatisk." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Egen nøkkel" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Nye nøkler" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Tilknyttede nøkler" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "Inaktive nøkler" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "New encryption keys from your other devices will be accepted automatically." msgstr "Nye krypteringsnøkler fra dine andre enheter vil godtas automatisk." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Godtatt" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Avslått" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Bekreftet" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Ubrukt" #~ msgid "Your contact" #~ msgstr "Din kontakt" #~ msgid "Not matching" #~ msgstr "Samsvarer ikke" #~ msgid "Matching" #~ msgstr "Samsvarer" #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "highlighted accordingly in the chat window." #~ msgstr "" #~ "Når først bekreftet, vil fremtidige meldinger sendt av %s ved bruk av " #~ "denne nøkkelen bli fremhevet tilsvarende i sludrevinduet." #~ msgid "" #~ "Stop accepting this key during communication with its associated contact." #~ msgstr "" #~ "Slutt å godta denne nøkkelen under kommunikasjon med dens tilknyttede " #~ "kontakt." #~ msgid "" #~ "Start accepting this key during communication with its associated contact" #~ msgstr "" #~ "Aksepter heretter denne nøkkelen i kommunikasjon med dens tilhørende " #~ "kontakt" #~ msgid "" #~ "This means it cannot be used by %s to receive messages, and any messages " #~ "sent by it will be ignored." #~ msgstr "" #~ "Dette betyr at den ikke kan brukes av %s til å motta meldinger, og enhver " #~ "melding sendt på vegne av den vil bli ignorert." #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "ignored and none of your messages will be readable using this key." #~ msgstr "" #~ "Etter bekreftelsen vil fremtidige meldinger sendt av %s der denne " #~ "nøkkelen brukes bli ignorert, og ingen av dine meldinger vil kunne leses " #~ "med den." #~ msgid "" #~ "Once confirmed this key will be usable by %s to receive and send messages." #~ msgstr "" #~ "Når først bekreftet vil denne nøkkelen kunne brukes av %s til å motta og " #~ "sende meldinger." #~ msgid "" #~ "When this contact adds new encryption keys to their account, " #~ "automatically accept them." #~ msgstr "" #~ "Når denne kontakten legger nye krypteringsnøkler til kontoen sin, godta " #~ "dem automatisk." #~ msgid "" #~ "When you add new encryption keys to your account, automatically accept " #~ "them." #~ msgstr "" #~ "Godta dem automatisk når du legger nye krypteringsnøkler til kontoen din." #~ msgid "" #~ "Start accepting this key during communication with its associated contact." #~ msgstr "" #~ "Begynn å godta denne nøkkelen under kommunikasjon med dens tilknyttede " #~ "kontakt." #~ msgid "Reject Key" #~ msgstr "Avslå nøkkel" #~ msgid "Accept Key" #~ msgstr "Godta nøkkel" #~ msgid "Unknown device (0x%.8x)" #~ msgstr "Ukjent enhet (0x%.8x)" #~ msgid "Other devices" #~ msgstr "Andre enheter" #~ msgid "- None -" #~ msgstr "- Ingen -" #~ msgid "OMEMO" #~ msgstr "OMEMO" dino-0.3.0/plugins/omemo/po/nl.po0000644000000000000000000002665414202022370015332 0ustar rootrootmsgid "" msgstr "" "Project-Id-Version: dino-omemo-0.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-10-10 10:49+0000\n" "Language-Team: Dutch \n" "Language: nl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 4.3-dev\n" #: plugins/omemo/src/ui/bad_messages_populator.vala:157 #, c-format msgid "" "%s has been using an untrusted device. You won't see messages from devices " "that you do not trust." msgstr "" "%s is een onvertrouwd apparaat. Berichten van onvertrouwde apparaten worden " "niet getoond." #: plugins/omemo/src/ui/bad_messages_populator.vala:158 msgid "Manage devices" msgstr "Apparaten beheren" #: plugins/omemo/src/ui/bad_messages_populator.vala:160 #, c-format msgid "" "%s does not trust this device. That means, you might be missing messages." msgstr "%s vertrouwt dit apparaat niet. Je mist hierdoor mogelijk berichten." #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Sleutel beheren" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contact's device." msgstr "" "Vergelijk de vingerafdruk, letter voor letter, met die op het apparaat van " "je contactpersoon." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Fingerprints differ" msgstr "Vingerafdrukken wijken af" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Fingerprints match" msgstr "Vingerafdrukken komen overeen" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Annuleren" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Bevestigen" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Sleutel verifiëren" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Future messages sent by %s from the device that uses this key will be " "highlighted accordingly in the chat window." msgstr "" "Toekomstige berichten van %s van het apparaat dat deze sleutel gebruikt, " "worden gemarkeerd in het gespreksvenster." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Vingerafdrukken komen niet overeen" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Zorg dat je de juiste vingerafdrukken vergelijkt. Als de vingerafdrukken " "niet overeenkomen, kan het zijn dat het account van %s gehackt is. Overweeg " "in dat geval deze sleutel te weigeren." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Vingerafdruk van sleutel verifiëren" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "Vergelijk de vingerafdruk van deze sleutel met de vingerafdruk die wordt " "getoond op het apparaat van je contactpersoon." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Sleutel weigeren" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Block encrypted communication with the contact's device that uses this key." msgstr "" "Blokkeer versleutelde communicatie met het apparaat van deze contactpersoon " "dat deze sleutel gebruikt." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Sleutel accepteren" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Allow encrypted communication with the contact's device that uses this key." msgstr "" "Sta versleutelde communicatie toe met het apparaat van deze contactpersoon " "dat deze sleutel gebruikt." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Deze sleutel is momenteel %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "geaccepteerd" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send encrypted messages." msgstr "" "Dit betekent dat %s deze kan gebruiken om berichten te ontvangen en " "versturen." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "geverifieerd" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" "Er is vastgesteld dat deze sleutel overeenkomt met de sleutel op het " "apparaat van de contactpersoon." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "geweigerd" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to decipher your messages, and you won't " "see messages encrypted with it." msgstr "" "De sleutel kan niet door %s worden gebruikt om berichten te ont- en " "versleutelen." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "You won't see encrypted messages from the device of %s that uses this key. " "Conversely, that device won't be able to decipher your messages anymore." msgstr "" "Je ziet geen versleutelde berichten van het apparaat van %s dat deze sleutel " "gebruikt. Daarnaast kan het apparaat jouw berichten niet meer ontsleutelen." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "You will be able to exchange encrypted messages with the device of %s that " "uses this key." msgstr "" "Je kunt versleutelde berichten uitwisselen met het apparaat van %s dat deze " "sleutel gebruikt." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Terug" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Beheren" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Deze contactpersoon heeft nieuwe apparaten" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Mijn vingerafdruk" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connection" msgstr "Wordt bij de eerste verbinding gegenereerd" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "OMEMO-vertrouwenskeuze vereist" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "Heb je een nieuw apparaat toegevoegd aan %s?" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Versleuteling" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d OMEMO-apparaat" msgstr[1] "%d OMEMO-apparaten" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "OMEMO-sleutelbeheer" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Nieuwe sleutels automatisch accepteren" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "New encryption keys from this contact will be accepted automatically." msgstr "" "Nieuwe sleutels van deze contactpersoon worden automatisch geaccepteerd." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Mijn sleutel" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Nieuwe sleutels" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Geassocieerde sleutels" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "Inactieve sleutels" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "New encryption keys from your other devices will be accepted automatically." msgstr "" "Nieuwe sleutels van je andere apparaten worden automatisch geaccepteerd." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Geaccepteerd" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Geweigerd" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Geverifieerd" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Ongebruikt" #~ msgid "Your contact" #~ msgstr "Je contactpersoon" #~ msgid "Not matching" #~ msgstr "Komen niet overeen" #~ msgid "Matching" #~ msgstr "Komen overeen" #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "highlighted accordingly in the chat window." #~ msgstr "" #~ "Zodra bevestigd, worden nieuwe berichten verstuurd door %s met deze " #~ "sleutel uitgelicht in het chatvenster." #~ msgid "" #~ "Stop accepting this key during communication with its associated contact." #~ msgstr "" #~ "Accepteer niet langer deze sleutel tijdens communicatie met het " #~ "bijbehorende contact." #~ msgid "" #~ "Start accepting this key during communication with its associated contact" #~ msgstr "" #~ "Accepteer deze sleutel tijdens communicatie met het bijbehorende contact" #~ msgid "" #~ "This means it cannot be used by %s to receive messages, and any messages " #~ "sent by it will be ignored." #~ msgstr "" #~ "Dit betekent dat het niet gebruikt kan worden door %s om berichten te " #~ "ontvangen. Berichten die hiermee verstuurd worden worden genegeerd." #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "ignored and none of your messages will be readable using this key." #~ msgstr "" #~ "Zodra bevestigd, worden toekomstige berichten die verstuurd worden door " #~ "%s met deze sleutel genegeerd en zullen geen van je berichten leesbaar " #~ "zijn met deze sleutel." #~ msgid "" #~ "Once confirmed this key will be usable by %s to receive and send messages." #~ msgstr "" #~ "Zodra bevestigd kan deze sleutel gebruikt worden door %s om berichten te " #~ "ontvangen en te versturen." #~ msgid "" #~ "When this contact adds new encryption keys to their account, " #~ "automatically accept them." #~ msgstr "" #~ "Indien dit contact nieuwe encryptiesleutels toevoegt aan hun account, " #~ "automatisch accepteren." #~ msgid "" #~ "When you add new encryption keys to your account, automatically accept " #~ "them." #~ msgstr "" #~ "Nieuwe encryptiesleutels die aan het account worden toegevoegd " #~ "automatisch accepteren." #, fuzzy #~ msgid "" #~ "Start accepting this key during communication with its associated contact." #~ msgstr "" #~ "Accepteer deze sleutel tijdens communicatie met het bijbehorende contact" #~ msgid "Reject Key" #~ msgstr "Sleutel Afwijzen" #~ msgid "Accept Key" #~ msgstr "Sleutel Aanvaarden" #~ msgid "Unknown device (0x%.8x)" #~ msgstr "Onbekend apparaat (0x%.8x)" #~ msgid "Other devices" #~ msgstr "Andere apparaten" #~ msgid "- None -" #~ msgstr "- Geen -" #~ msgid "OMEMO" #~ msgstr "OMEMO" #~ msgid "Database error" #~ msgstr "Databasefout" dino-0.3.0/plugins/omemo/po/nl_BE.po0000644000000000000000000002425014202022370015666 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2019-01-12 20:06+0000\n" "Language-Team: Flemish \n" "Language: nl_BE\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: Weblate 3.4-dev\n" #: plugins/omemo/src/ui/bad_messages_populator.vala:157 #, c-format msgid "" "%s has been using an untrusted device. You won't see messages from devices " "that you do not trust." msgstr "" #: plugins/omemo/src/ui/bad_messages_populator.vala:158 msgid "Manage devices" msgstr "" #: plugins/omemo/src/ui/bad_messages_populator.vala:160 #, c-format msgid "" "%s does not trust this device. That means, you might be missing messages." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Sleutel beheren" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contact's device." msgstr "" "Vergelijkt de vingerafdruk, teken per teken, met die op het apparaat van uw " "contact." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Fingerprints differ" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Fingerprints match" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Annuleren" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Bevestigen" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Sleutel verifiëren" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Future messages sent by %s from the device that uses this key will be " "highlighted accordingly in the chat window." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Vingerafdrukken komen niet overeen" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Zorgt ervoor dat ge de juiste vingerafdrukken vergelijkt. Indien dat de " "vingerafdrukken niet overeenkomen kan het zijn dat den account van %s " "gecompromitteerd is. Overweegt in dat geval van deze sleutel af te wijzen." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Vingerafdruk van sleutel verifiëren" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "Vergelijkt de vingerafdruk van deze sleutel met de vingerafdruk die dat " "wordt getoond op het apparaat van uw contact." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Sleutel afwijzen" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Block encrypted communication with the contact's device that uses this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Sleutel aanvaarden" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Allow encrypted communication with the contact's device that uses this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Deze sleutel is momenteel %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "aanvaard" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, fuzzy, c-format msgid "This means it can be used by %s to receive and send encrypted messages." msgstr "" "Dit betekent dat %s deze kan gebruiken voor berichten te ontvangen en " "versturen." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "geverifieerd" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" "Bovendien komt het geverifieerd overeen met de sleutel op het apparaat van " "het contact." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "afgewezen" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to decipher your messages, and you won't " "see messages encrypted with it." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "You won't see encrypted messages from the device of %s that uses this key. " "Conversely, that device won't be able to decipher your messages anymore." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "You will be able to exchange encrypted messages with the device of %s that " "uses this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Terug" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Beheren" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Dit contact heeft nieuwe apparaten" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Eigen vingerafdruk" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connection" msgstr "Wordt bij de eerste verbinding gegenereerd" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "OMEMO-vertrouwenskeuze vereist" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "Gebruikt ge een nieuw apparaat met account %s?" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Versleuteling" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d OMEMO-apparaat" msgstr[1] "%d OMEMO-apparaten" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "OMEMO-sleutelbeheer" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Nieuwe sleutels automatisch aanvaarden" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "New encryption keys from this contact will be accepted automatically." msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Eigen sleutel" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Nieuwe sleutels" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Geassocieerde sleutels" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "New encryption keys from your other devices will be accepted automatically." msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Aanvaard" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Afgewezen" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Geverifieerd" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Ongebruikt" #~ msgid "Not matching" #~ msgstr "Komen niet overeen" #~ msgid "Matching" #~ msgstr "Komen overeen" #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "highlighted accordingly in the chat window." #~ msgstr "" #~ "Zodra bevestigd, worden nieuwe berichten verstuurd door %s met deze " #~ "sleutel uitgelicht in ’t chatvenster." #~ msgid "" #~ "Stop accepting this key during communication with its associated contact." #~ msgstr "" #~ "Aanvaardt deze sleutel niet meer tijdens communicatie met het bijbehorend " #~ "contact." #~ msgid "" #~ "Start accepting this key during communication with its associated contact" #~ msgstr "" #~ "Aanvaardt deze sleutel tijdens communicatie met het bijbehorend contact" #~ msgid "" #~ "This means it cannot be used by %s to receive messages, and any messages " #~ "sent by it will be ignored." #~ msgstr "" #~ "Dit betekent dat hij niet kan gebruikt worden door %s voor berichten te " #~ "ontvangen. Berichten die dat hiermee worden verstuurd, worden genegeerd." #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "ignored and none of your messages will be readable using this key." #~ msgstr "" #~ "Zodra bevestigd, worden toekomstige berichten die dat worden verstuurd " #~ "door %s met deze sleutel genegeerd, en zullen geen van uw berichten met " #~ "deze sleutel leesbaar zijn." #~ msgid "" #~ "Once confirmed this key will be usable by %s to receive and send messages." #~ msgstr "" #~ "Zodra bevestigd kan deze sleutel gebruikt worden door %s voor berichten " #~ "te ontvangen en te versturen." #~ msgid "" #~ "When this contact adds new encryption keys to their account, " #~ "automatically accept them." #~ msgstr "" #~ "Indien dat dit contact nieuwe versleutelingssleutels toevoegt aan zijn/" #~ "haar account, deze automatisch aanvaarden." #~ msgid "" #~ "When you add new encryption keys to your account, automatically accept " #~ "them." #~ msgstr "" #~ "Nieuwe versleutelingssleutels die aan den account worden toegevoegd " #~ "automatisch aanvaarden." #~ msgid "Unknown device (0x%.8x)" #~ msgstr "Onbekend apparaat (0x%.8x)" #~ msgid "Other devices" #~ msgstr "Andere apparaten" #~ msgid "- None -" #~ msgstr "- Geen -" dino-0.3.0/plugins/omemo/po/oc.po0000644000000000000000000001557614202022370015323 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-11-03 11:26+0000\n" "Language-Team: none\n" "Language: oc\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: Weblate 4.3.2-dev\n" #: plugins/omemo/src/ui/bad_messages_populator.vala:157 #, c-format msgid "" "%s has been using an untrusted device. You won't see messages from devices " "that you do not trust." msgstr "" #: plugins/omemo/src/ui/bad_messages_populator.vala:158 msgid "Manage devices" msgstr "Gerir los periferics" #: plugins/omemo/src/ui/bad_messages_populator.vala:160 #, c-format msgid "" "%s does not trust this device. That means, you might be missing messages." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Gestion de las claus" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contact's device." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Fingerprints differ" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Fingerprints match" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Anullar" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Confirmar" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Future messages sent by %s from the device that uses this key will be " "highlighted accordingly in the chat window." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Block encrypted communication with the contact's device that uses this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Allow encrypted communication with the contact's device that uses this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "La clau es actualament %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "acceptada" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send encrypted messages." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "verificada" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "regetada" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to decipher your messages, and you won't " "see messages encrypted with it." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "You won't see encrypted messages from the device of %s that uses this key. " "Conversely, that device won't be able to decipher your messages anymore." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "You will be able to exchange encrypted messages with the device of %s that " "uses this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Tornar" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Gerir" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connection" msgstr "" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Chiframent" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "" msgstr[1] "" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "New encryption keys from this contact will be accepted automatically." msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "New encryption keys from your other devices will be accepted automatically." msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "" #~ msgid "Your contact" #~ msgstr "Vòstre contacte" dino-0.3.0/plugins/omemo/po/pl.po0000644000000000000000000002656514202022370015335 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2022-01-20 15:56+0000\n" "Language-Team: Polish \n" "Language: pl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " "|| n%100>=20) ? 1 : 2;\n" "X-Generator: Weblate 4.11-dev\n" #: plugins/omemo/src/ui/bad_messages_populator.vala:157 #, c-format msgid "" "%s has been using an untrusted device. You won't see messages from devices " "that you do not trust." msgstr "" "%s używa niezaufanego urządzenia. Nie będziesz widzieć wiadomości wysłanych " "z urządzeń, którym nie ufasz." #: plugins/omemo/src/ui/bad_messages_populator.vala:158 msgid "Manage devices" msgstr "Zarządzaj urządzeniami" #: plugins/omemo/src/ui/bad_messages_populator.vala:160 #, c-format msgid "" "%s does not trust this device. That means, you might be missing messages." msgstr "" "%s nie ufa temu urządzeniu. Oznacza to, że wiadomości mogą do ciebie nie " "docierać." #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Zarządzaj kluczem" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contact's device." msgstr "" "Porównaj odcisk klucza, znak po znaku, z tym pokazanym na urządzeniu twojego " "kontaktu." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Fingerprints differ" msgstr "Odcisk klucza różni się" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Fingerprints match" msgstr "Odcisk klucza pasuje" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Anuluj" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Potwierdź" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Zweryfikuj klucz" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Future messages sent by %s from the device that uses this key will be " "highlighted accordingly in the chat window." msgstr "" "Przyszłe wiadomości wysyłane przez %s z urządzenia używającego tego klucza " "zostaną odpowiednio podświetlone w oknie czatu." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Odcisk klucza nie zgadza się" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Proszę zweryfikować, że porównujesz prawidłowy odcisk klucza. Jeśli odciski " "nie zgadzają się, konto %s mogło być zaatakowane i należy rozważyć " "odrzucenie tego klucza." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Zweryfikuj odcisk klucza" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "Porównaj odcisk tego klucza z odciskiem wyświetlonym na urządzeniu kontaktu." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Odrzuć klucz" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Block encrypted communication with the contact's device that uses this key." msgstr "" "Blokuj szyfrowaną komunikację z urządzeniem kontaktu, które korzysta z tego " "klucza." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Zaakceptuj klucz" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Allow encrypted communication with the contact's device that uses this key." msgstr "" "Zezwól na szyfrowaną komunikację z urządzeniem kontaktu, które korzysta z " "tego klucza." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Ten klucz jest obecnie %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "zaakceptowany" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send encrypted messages." msgstr "" "To znaczy, że może być używany przez %s do odbierania i wysyłania " "szyfrowanych wiadomości." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "zweryfikowany" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" "Dodatkowo zostało sprawdzone, że zgadza się z kluczem na urządzeniu kontaktu." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "odrzucony" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to decipher your messages, and you won't " "see messages encrypted with it." msgstr "" "Oznacza to, że %s nie może go użyć do odszyfrowania wiadomości i nie " "zobaczysz wiadomości zaszyfrowanych przy jego pomocy." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "You won't see encrypted messages from the device of %s that uses this key. " "Conversely, that device won't be able to decipher your messages anymore." msgstr "" "Nie zobaczysz zaszyfrowanych wiadomości z urządzenia %s, które używa tego " "klucza. I odwrotnie, to urządzenie nie będzie już w stanie odszyfrować " "Twoich wiadomości." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "You will be able to exchange encrypted messages with the device of %s that " "uses this key." msgstr "" "Będziesz mógł wymieniać zaszyfrowane wiadomości z urządzeniem %s, które " "używa tego klucza." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Wstecz" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Zarządzaj" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Ten kontakt ma nowe urządzenia" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Własny odcisk klucza" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connection" msgstr "Zostanie wygenerowany przy pierwszym połączeniu" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "Decyzja o zaufaniu OMEMO jest potrzebna" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "Czy dodałeś nowe urządzenie dla konta %s?" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Szyfrowanie" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d urządzenie OMEMO" msgstr[1] "%d urządzenia OMEMO" msgstr[2] "%d urządzeń OMEMO" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "Zarządzanie kluczami OMEMO" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Automatycznie akceptuj nowe klucze" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "New encryption keys from this contact will be accepted automatically." msgstr "" "Nowe klucze szyfrowania od tego kontaktu będą akceptowane automatycznie." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Własny klucz" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Nowe klucze" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Pozostałe klucze" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "Nieaktywne klucze" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "New encryption keys from your other devices will be accepted automatically." msgstr "" "Nowe klucze szyfrowania z innych urządzeń będą akceptowane automatycznie." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Zaakceptowany" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Odrzucony" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Zweryfikowany" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Nieużywany" #~ msgid "Not matching" #~ msgstr "Odciski nie zgadzają się" #~ msgid "Matching" #~ msgstr "Odciski zgadzają się" #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "highlighted accordingly in the chat window." #~ msgstr "" #~ "Przyszłe wiadomości wysłane przez %s i szyfrowane tym kluczem będą " #~ "odpowiednio zaznaczone w oknie czatu." #~ msgid "" #~ "Stop accepting this key during communication with its associated contact." #~ msgstr "" #~ "Zablokuj szyfrowaną komunikację ze sprzętem kontaktu, który używa ten " #~ "klucz." #~ msgid "" #~ "Start accepting this key during communication with its associated contact" #~ msgstr "" #~ "Włącz szyfrowaną komunikację ze sprzętem kontaktu, który używa ten klucz." #~ msgid "" #~ "This means it cannot be used by %s to receive messages, and any messages " #~ "sent by it will be ignored." #~ msgstr "" #~ "To znaczy, że nie może być używany przez %s do rozszyfrowania twoich " #~ "wiadomości, a wiadomości szyfrowane nim nie będą u ciebie wyświetlane." #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "ignored and none of your messages will be readable using this key." #~ msgstr "" #~ "Wiadomości wysłane przez %s ze sprzętu używającego ten klucz nie będą " #~ "wyświetlane. Podobnie sprzęt tej osoby nie będzie mógł rozszyfrować " #~ "twoich wiadomości." #~ msgid "" #~ "Once confirmed this key will be usable by %s to receive and send messages." #~ msgstr "" #~ "To umożliwi wymianę szyfrowanych wiadomości ze sprzętem należącym do %s, " #~ "który używaja ten klucz." #~ msgid "" #~ "When this contact adds new encryption keys to their account, " #~ "automatically accept them." #~ msgstr "" #~ "Gdy ten kontakt doda nowe klucze szyfrowania do swojego konta, zaakceptuj " #~ "je automatycznie." #~ msgid "" #~ "When you add new encryption keys to your account, automatically accept " #~ "them." #~ msgstr "" #~ "Gdy dodam nowe klucze szyfrowania do mojego konta, zaakceptuj je " #~ "automatycznie." #~ msgid "Unknown device (0x%.8x)" #~ msgstr "Nieznane urządzenie (0x%.8x)" #~ msgid "Other devices" #~ msgstr "Pozostałe urządzenia" #~ msgid "- None -" #~ msgstr "- Wybierz -" dino-0.3.0/plugins/omemo/po/pt.po0000644000000000000000000002202314202022370015326 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2021-05-10 09:33+0000\n" "Language-Team: none\n" "Language: pt\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: Weblate 4.7-dev\n" #: plugins/omemo/src/ui/bad_messages_populator.vala:157 #, c-format msgid "" "%s has been using an untrusted device. You won't see messages from devices " "that you do not trust." msgstr "" "%s esteve a usar um aparelho não confiável. Não verá mensagens de aparelhos " "nos quais não confia." #: plugins/omemo/src/ui/bad_messages_populator.vala:158 msgid "Manage devices" msgstr "Gerir aparelhos" #: plugins/omemo/src/ui/bad_messages_populator.vala:160 #, c-format msgid "" "%s does not trust this device. That means, you might be missing messages." msgstr "" "%s não confia neste aparelho. Isso significa que pode estar a perder " "mensagens." #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Gerir Chave" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contact's device." msgstr "" "Compare a impressão digital, caractere a caractere, com aquela mostrada no " "aparelho de seu contato." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Fingerprints differ" msgstr "As impressões digitais não correspondem uma a outra" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Fingerprints match" msgstr "As impressões digitais correspondem uma a outra" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Cancelar" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Confirmar" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Verificar chave" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Future messages sent by %s from the device that uses this key will be " "highlighted accordingly in the chat window." msgstr "" "Mensagens futuras enviadas por %s do aparelho que usa essa chave serão " "destacadas no janela de bate papo." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Impressões digitais não correspondem" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Por favor verifique se está a comparar as impressões digitais corretas. Caso " "elas não correspondam, a conta do %s pode estar comprometida e deve " "considerar rejeitar essa chave." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Verificar impressão digital da chave" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "Compare a impressão digital dessa chave com a impressão digital exibida no " "aparelho do contato." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Rejeitar chave" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Block encrypted communication with the contact's device that uses this key." msgstr "" "Bloqueie comunicação criptografada com o aparelho do contato que usa essa " "chave." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Aceitar chave" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Allow encrypted communication with the contact's device that uses this key." msgstr "" "Permita comunicação criptografada com o aparelho do contato que usa essa " "chave." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Essa chave está atualmente %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "aceita" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send encrypted messages." msgstr "" "Isso significa que ela pode ser usada por %s para enviar e receber mensagens " "criptografadas." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "verificada" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" "Além disso, verificou-se que ela corresponde com a chave no aparelho do " "contato." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "rejeitada" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to decipher your messages, and you won't " "see messages encrypted with it." msgstr "" "Isso significa que ela não pode ser usada por %s para decifrar suas " "mensagens, e não irá visualizar mensagens criptografadas com ela." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "You won't see encrypted messages from the device of %s that uses this key. " "Conversely, that device won't be able to decipher your messages anymore." msgstr "" "Não irá visualizar mensagens criptografas pelo aparelho de %s que usa essa " "chave. Além disso, esse aparelho não será mais capaz de decifrar suas " "mensagens." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "You will be able to exchange encrypted messages with the device of %s that " "uses this key." msgstr "" "Poderá trocar mensagem criptografadas com o aparelho de %s que usa essa " "chave." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Voltar" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Gerir" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Esse contato possui novos aparelhos" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Impressão digital própria" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connection" msgstr "Será gerada na primeira conexão" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "Decisão de confiança OMEMO necessária" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "Adicionou um novo aparelho para a conta %s?" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Criptografia" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d aparelho OMEMO" msgstr[1] "%d aparelhos OMEMO" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "Gestão de chave OMEMO" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Aceitar novas chaves automaticamente" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "New encryption keys from this contact will be accepted automatically." msgstr "" "Novas chaves de criptografia desse contato serão aceitas automaticamente." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Chave própria" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Chaves novas" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Chaves associadas" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "Chaves inativas" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "New encryption keys from your other devices will be accepted automatically." msgstr "" "Novas chaves de criptografia de seus outros aparelhos serão aceitas " "automaticamente." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Aceita" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Rejeitada" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Verificada" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Não usada" #~ msgid "Your contact" #~ msgstr "O seu contacto" dino-0.3.0/plugins/omemo/po/pt_BR.po0000644000000000000000000002605114202022370015716 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-10-22 03:26+0000\n" "Language-Team: none\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: Weblate 4.3.1\n" #: plugins/omemo/src/ui/bad_messages_populator.vala:157 #, c-format msgid "" "%s has been using an untrusted device. You won't see messages from devices " "that you do not trust." msgstr "" "%s tem usado um dispositivo não confiável. Você não verá mensagens de " "dispositivos que você não confia." #: plugins/omemo/src/ui/bad_messages_populator.vala:158 msgid "Manage devices" msgstr "Gerenciar dispositivos" #: plugins/omemo/src/ui/bad_messages_populator.vala:160 #, c-format msgid "" "%s does not trust this device. That means, you might be missing messages." msgstr "" "%s não confia nesse dispositivo. Isso significa que você pode estar perdendo " "algumas mensagens." #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Gerenciar Chave" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contact's device." msgstr "" "Compare a impressão digital, caractere a caractere, com aquela mostrada no " "dispositivo de seu contato." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Fingerprints differ" msgstr "As impressões digitais não correspondem uma a outra" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Fingerprints match" msgstr "As impressões digitais correspondem uma a outra" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Cancelar" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Confirmar" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Verificar chave" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Future messages sent by %s from the device that uses this key will be " "highlighted accordingly in the chat window." msgstr "" "Mensagens futuras enviadas por %s do dispositivo que usa essa chave serão " "destacadas no janela de bate papo." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Impressões digitais não correspondem" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Por favor verifique se você está comparando as impressões digitais corretas. " "Caso elas não correspondam, a conta do %s pode estar comprometida e você " "deve considerar rejeitar essa chave." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Verificar impressão digital da chave" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "Compare a impressão digital dessa chave com a impressão digital exibida no " "dispositivo do contato." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Rejeitar chave" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Block encrypted communication with the contact's device that uses this key." msgstr "" "Bloqueie comunicação criptografada com o dispositivo do contato que usa essa " "chave." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Aceitar chave" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Allow encrypted communication with the contact's device that uses this key." msgstr "" "Permita comunicação criptografada com o dispositivo do contato que usa essa " "chave." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Essa chave está atualmente %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "aceita" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send encrypted messages." msgstr "" "Isso significa que ela pode ser usada por %s para enviar e receber mensagens " "criptografadas." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "verificada" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" "Além disso, verificou-se que ela corresponde com a chave no dispositivo do " "contato." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "rejeitada" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to decipher your messages, and you won't " "see messages encrypted with it." msgstr "" "Isso significa que ela não pode ser usada por %s para decifrar suas " "mensagens, e você não irá visualizar mensagens criptografadas com ela." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "You won't see encrypted messages from the device of %s that uses this key. " "Conversely, that device won't be able to decipher your messages anymore." msgstr "" "Você não irá visualizar mensagens criptografas pelo dispositivo de %s que " "usa essa chave. Além disso, esse dispositivo não será mais capaz de decifrar " "suas mensagens." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "You will be able to exchange encrypted messages with the device of %s that " "uses this key." msgstr "" "Você poderá trocar mensagem criptografadas com o dispositivo de %s que usa " "essa chave." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Voltar" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Gerenciar" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Esse contato possui novos dispositivos" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Impressão digital própria" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connection" msgstr "Será gerada na primeira conexão" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "Decisão de confiança OMEMO necessária" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "Você adicionou um novo dispositivo para a conta %s?" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Criptografia" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d dispositivo OMEMO" msgstr[1] "%d dispositivos OMEMO" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "Gerenciamento de Chave OMEMO" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Aceitar novas chaves automaticamente" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "New encryption keys from this contact will be accepted automatically." msgstr "" "Novas chaves de criptografia desse contato serão aceitas automaticamente." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Chave própria" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Chaves novas" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Chaves associadas" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "Chaves inativas" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "New encryption keys from your other devices will be accepted automatically." msgstr "" "Novas chaves de criptografia de seus outros dispositivos serão aceitas " "automaticamente." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Aceita" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Rejeitada" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Verificada" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Não usada" #~ msgid "Your contact" #~ msgstr "Seu contato" #~ msgid "Not matching" #~ msgstr "Não correspondem" #~ msgid "Matching" #~ msgstr "Correspondem" #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "highlighted accordingly in the chat window." #~ msgstr "" #~ "Uma vez confirmada, toda mensagem enviada por %s no futuro será destacada " #~ "na janela da conversa." #~ msgid "" #~ "Stop accepting this key during communication with its associated contact." #~ msgstr "Pare de aceitar essa chave ao se comunicar com o contato associado." #~ msgid "" #~ "Start accepting this key during communication with its associated contact" #~ msgstr "Aceite essa chave ao se comunicar com o contato associado" #~ msgid "" #~ "This means it cannot be used by %s to receive messages, and any messages " #~ "sent by it will be ignored." #~ msgstr "" #~ "Isso significa que ela não pode ser usada por %s para receber mensagens e " #~ "qualquer mensagem enviada com ela será ignorada." #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "ignored and none of your messages will be readable using this key." #~ msgstr "" #~ "Uma vez confirmada, toda mensagem enviada por %s usando essa chave será " #~ "ignorada e nenhuma de suas mensagens com ela serão legíveis." #~ msgid "" #~ "Once confirmed this key will be usable by %s to receive and send messages." #~ msgstr "" #~ "Uma vez confirmada, essa chave poderá ser usada por %s para receber e " #~ "enviar mensagens." #~ msgid "" #~ "When this contact adds new encryption keys to their account, " #~ "automatically accept them." #~ msgstr "" #~ "Quando esse contato adicionar novas chaves de criptografia à conta, " #~ "aceite-as automaticamente." #~ msgid "" #~ "When you add new encryption keys to your account, automatically accept " #~ "them." #~ msgstr "" #~ "Quando você adicionar novas chaves de criptografia para sua conta, aceite-" #~ "as automaticamente." dino-0.3.0/plugins/omemo/po/ro.po0000644000000000000000000002745614202022370015342 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-10-13 09:26+0000\n" "Language-Team: Romanian \n" "Language: ro\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==0 || (n%100 > 0 && n%100 < " "20)) ? 1 : 2;\n" "X-Generator: Weblate 4.3-dev\n" #: plugins/omemo/src/ui/bad_messages_populator.vala:157 #, c-format msgid "" "%s has been using an untrusted device. You won't see messages from devices " "that you do not trust." msgstr "" "%s a utilizat un dispozitiv care nu este de încredere. Nu veți vedea mesaje " "de pe dispozitive în care nu aveți încredere." #: plugins/omemo/src/ui/bad_messages_populator.vala:158 msgid "Manage devices" msgstr "Gestionare dispozitive" #: plugins/omemo/src/ui/bad_messages_populator.vala:160 #, c-format msgid "" "%s does not trust this device. That means, you might be missing messages." msgstr "" "%s nu are încredere în acest dispozitiv. Asta înseamnă că s-ar putea să-ți " "lipsească mesaje." #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Gestionare cheie" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contact's device." msgstr "" "Comparați amprenta, caracter cu caracter, cu amprenta afișată pe " "dispozitivul contactului." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Fingerprints differ" msgstr "Amprentele diferă" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Fingerprints match" msgstr "Amprentele se potrivesc" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Anulare" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Confirmare" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Verificare cheie" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Future messages sent by %s from the device that uses this key will be " "highlighted accordingly in the chat window." msgstr "" "Mesajele viitoare trimise de %s de pe dispozitivul care utilizează această " "cheie vor fi evidențiate în mod corespunzător în fereastra de chat." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Amprentele nu se potrivesc" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Vă rugäm să verificați dacă se compară amprentele corecte. Dacă nu se " "potrivesc amprentele s-ar putea ca %s să aibä un cont compromis și ar trebui " "să luați în considerare respingerea acestei chei." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Verificare amprentă cheie" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "Comparați amprenta cheii cu amprenta afișată pe dispozitivul contactului." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Respingere cheie" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Block encrypted communication with the contact's device that uses this key." msgstr "" "Blocați comunicarea criptată cu dispozitivul persoanei de contact care " "utilizează această cheie." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Acceptare cheie" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Allow encrypted communication with the contact's device that uses this key." msgstr "" "Permiteți comunicarea criptată cu dispozitivul persoanei de contact care " "utilizează această cheie." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Cheia este în prezent %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "acceptată" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send encrypted messages." msgstr "" "Aceasta înseamnă că poate fi utilizată de %s pentru a primi și a trimite " "mesaje criptate." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "verificată" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" "În plus, a fost verificată că se potrivește cu cheia de pe dispozitivul " "contactului." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "respinsă" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to decipher your messages, and you won't " "see messages encrypted with it." msgstr "" "Aceasta înseamnă că nu poate fi utilizată de %s pentru a vă descifra " "mesajele și nu veți vedea mesajele criptate cu aceasta." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "You won't see encrypted messages from the device of %s that uses this key. " "Conversely, that device won't be able to decipher your messages anymore." msgstr "" "Nu veți vedea mesaje criptate de la dispozitivul %s care utilizează această " "cheie. În schimb, dispozitivul respectiv nu va mai putea descifra mesajele " "dumneavoastră." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "You will be able to exchange encrypted messages with the device of %s that " "uses this key." msgstr "" "Veți putea schimba mesaje criptate cu dispozitivul %s care utilizează " "această cheie." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Înapoi" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Gestionare" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Acest contact are dispozitive noi" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Amprentă proprie" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connection" msgstr "Se va genera la prima conectare" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "Este necesara luarea unei decizii în privința OMEMO" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "Ați adăugat un nou dispozitiv pentru contul %s?" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Criptare" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d dispozitiv OMEMO" msgstr[1] "%d dispozitive OMEMO" msgstr[2] "%d de dispozitive OMEMO" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "Administrare chei OMEMO" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Acceptă automat chei noi" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "New encryption keys from this contact will be accepted automatically." msgstr "" "Noile chei de criptare de la această persoană de contact vor fi acceptate " "automat." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Cheie proprie" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Chei noi" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Chei asociate" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "Chei inactive" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "New encryption keys from your other devices will be accepted automatically." msgstr "" "Noile chei de criptare de pe celelalte dispozitive ale dumneavoastră vor fi " "acceptate automat." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Acceptată" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Respinsă" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Verificată" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Neutilizatä" #~ msgid "Your contact" #~ msgstr "Persoana de contact" #~ msgid "Not matching" #~ msgstr "Nu se potrivesc" #~ msgid "Matching" #~ msgstr "Se potrivesc" #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "highlighted accordingly in the chat window." #~ msgstr "" #~ "Odată confirmată, orice viitoare mesaje trimise de %s folosind această " #~ "cheie vor fi evidenţiate în consecință în fereastra de discuție." #~ msgid "" #~ "Stop accepting this key during communication with its associated contact." #~ msgstr "" #~ "Nu mai accepta această cheie în timpul comunicärii cu acest contact " #~ "asociat." #~ msgid "" #~ "Start accepting this key during communication with its associated contact" #~ msgstr "" #~ "Se va accepta această cheie în timpul comunicării cu acest contact asociat" #~ msgid "" #~ "This means it cannot be used by %s to receive messages, and any messages " #~ "sent by it will be ignored." #~ msgstr "" #~ "Aceasta înseamnă că nu poate fi folosită de %s ca să primească mesaje, " #~ "iar orice mesaj trimis cu ea v-a fi ignorat." #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "ignored and none of your messages will be readable using this key." #~ msgstr "" #~ "Odată confirmată, orice viitoare mesaje trimise de %s folosind această " #~ "cheie vor fi ignorate și nici un mesaj trimis de dumneavoastră nu se va " #~ "putea citii folosid aceastä cheie." #~ msgid "" #~ "Once confirmed this key will be usable by %s to receive and send messages." #~ msgstr "" #~ "Odată confirmată, aceastä cheie va fi utilizatä de %s pentru a primi și " #~ "trimite mesaje." #~ msgid "" #~ "When this contact adds new encryption keys to their account, " #~ "automatically accept them." #~ msgstr "" #~ "Când acest contact adäugă chei de criptare noi în contul lor, acestea vor " #~ "fi acceptate în mod automat." #~ msgid "" #~ "When you add new encryption keys to your account, automatically accept " #~ "them." #~ msgstr "" #~ "Când adäugați chei de criptare noi pentru acest cont, acestea vor fi " #~ "acceptate în mod automat." #~ msgid "" #~ "Start accepting this key during communication with its associated contact." #~ msgstr "" #~ "Acceptă această cheie în timpul comunicării cu acest contact asociat." #~ msgid "Reject Key" #~ msgstr "Respingere cheie" #~ msgid "Accept Key" #~ msgstr "Acceptare cheie" #~ msgid "Unknown device (0x%.8x)" #~ msgstr "Dispozitiv necunoscut (0x%.8x)" #~ msgid "Other devices" #~ msgstr "Alte dispozitive" #~ msgid "- None -" #~ msgstr "- Nici unul -" dino-0.3.0/plugins/omemo/po/ru.po0000644000000000000000000003274014202022370015340 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-10-10 10:49+0000\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" "X-Generator: Weblate 4.3-dev\n" #: plugins/omemo/src/ui/bad_messages_populator.vala:157 #, c-format msgid "" "%s has been using an untrusted device. You won't see messages from devices " "that you do not trust." msgstr "" "%s использовал(а) ненадёжное устройство. Вы не будете видеть сообщения от " "устройств, которым не доверяете." #: plugins/omemo/src/ui/bad_messages_populator.vala:158 msgid "Manage devices" msgstr "Управление устройствами" #: plugins/omemo/src/ui/bad_messages_populator.vala:160 #, c-format msgid "" "%s does not trust this device. That means, you might be missing messages." msgstr "" "%s не доверяет этому устройству. Это означает, что у вас могут отсутствовать " "сообщения." #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Управление ключом" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contact's device." msgstr "" "Сравните отпечаток, символ за символом, с тем, который показан на устройстве " "вашего контакта." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Fingerprints differ" msgstr "Отпечатки отличаются" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Fingerprints match" msgstr "Отпечатки схожи" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Отмена" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Подтвердить" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Подтвердить ключ" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Future messages sent by %s from the device that uses this key will be " "highlighted accordingly in the chat window." msgstr "" "Будущие сообщения, отправленные %s с устройства, использующего этот ключ, " "будут соответствующим образом выделены в окне чата." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Отпечатки не совпадают" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Пожалуйста, проверьте совпадение отпечатков. Если они не совпадают, аккаунт " "\"%s\" может быть скомпрометирован, и вам стоит отклонить этот ключ." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Подтвердить отпечаток" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "Сверить этот отпечаток с ключом, отображаемым у контакта." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Отклонить ключ" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Block encrypted communication with the contact's device that uses this key." msgstr "" "Заблокировать зашифрованную связь с устройством контакта, использующего этот " "ключ." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Принять ключ" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Allow encrypted communication with the contact's device that uses this key." msgstr "" "Разрешает зашифрованную связь с устройством контакта, использующего этот " "ключ." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Ключ был успешно %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "принят" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send encrypted messages." msgstr "" "Это означает, что %s теперь может отправлять и принимать зашифрованные " "сообщения." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "проверен" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "Также была проведена проверка совпадения ключа." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "отклонён" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to decipher your messages, and you won't " "see messages encrypted with it." msgstr "" "Это означает что они не смогут использоваться %s для расшифровки ваших " "сообщений, и вы также не увидите сообщений зашифрованных ими." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "You won't see encrypted messages from the device of %s that uses this key. " "Conversely, that device won't be able to decipher your messages anymore." msgstr "" "Вы не увидите зашифрованных сообщений от устройства %s, которое использует " "этот ключ. И наоборот, это устройство больше не сможет расшифровывать ваши " "сообщения." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "You will be able to exchange encrypted messages with the device of %s that " "uses this key." msgstr "" "Вы сможете обмениваться зашифрованными сообщениями с устройством %s, которое " "использует этот ключ." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Назад" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Настроить" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Этот контакт воспользовался новым устройством" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Отпечаток этого устройства" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connection" msgstr "Будет сгенерирован при первом подключении" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "Требуется решение OMEMO о доверии" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "Вы добавляли аккаунт %s на новое устройство?" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Шифрование" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d устройство OMEMO" msgstr[1] "%d устройства OMEMO" msgstr[2] "%d устройств OMEMO" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "Управление ключами OMEMO" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Автоматически принимать новые ключи" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "New encryption keys from this contact will be accepted automatically." msgstr "Новые ключи шифрования от этого контакта будут приняты автоматически." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Собственный ключ" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Новые ключи" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Связанные ключи" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "Неактивные ключи" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "New encryption keys from your other devices will be accepted automatically." msgstr "" "Новые ключи шифрования от других ваших устройств будут приняты автоматически." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Принят" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Отклонён" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Подтверждён" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Не используется" #~ msgid "Your contact" #~ msgstr "Ваш контакт" #~ msgid "Not matching" #~ msgstr "Не совпадает" #~ msgid "Matching" #~ msgstr "Совпадает" #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "highlighted accordingly in the chat window." #~ msgstr "" #~ "После подтверждения все последующие сообщения, отправленные с %s при " #~ "помощи этого ключа, будут соответственно подсвечены в окне чата." #~ msgid "" #~ "Stop accepting this key during communication with its associated contact." #~ msgstr "Не принимать ключ этого контакта." #~ msgid "" #~ "Start accepting this key during communication with its associated contact" #~ msgstr "Принять ключ этого контакта" #~ msgid "" #~ "This means it cannot be used by %s to receive messages, and any messages " #~ "sent by it will be ignored." #~ msgstr "" #~ "Это значит, что %s не может отправлять сообщения — все они будут " #~ "игнорироваться." #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "ignored and none of your messages will be readable using this key." #~ msgstr "" #~ "После подтверждения все будущие сообщения, отправленные %s с " #~ "использованием этого ключа, будут игнорироваться и ни одно из ваших " #~ "сообщений не сможет быть прочитано с использованием этого ключа." #~ msgid "" #~ "Once confirmed this key will be usable by %s to receive and send messages." #~ msgstr "" #~ "После подтверждения этот ключ будет использоваться %s для получения и " #~ "отправки сообщений." #~ msgid "" #~ "When this contact adds new encryption keys to their account, " #~ "automatically accept them." #~ msgstr "" #~ "Когда этот контакт добавляет новые ключи шифрования в свою учетную " #~ "запись, автоматически принимайте их." #~ msgid "" #~ "When you add new encryption keys to your account, automatically accept " #~ "them." #~ msgstr "" #~ "Когда вы добавляете новые ключи шифрования в свою учетную запись, " #~ "автоматически принимайте их." #~ msgid "Unknown device (0x%.8x)" #~ msgstr "Неизвестное устройство (0x%.8x)" #~ msgid "Other devices" #~ msgstr "Другие устройства" #~ msgid "- None -" #~ msgstr "- Нет -" dino-0.3.0/plugins/omemo/po/sq.po0000644000000000000000000002214714202022370015335 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2021-03-04 15:20+0000\n" "Language-Team: none\n" "Language: sq\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: Weblate 4.5.1-dev\n" #: plugins/omemo/src/ui/bad_messages_populator.vala:157 #, c-format msgid "" "%s has been using an untrusted device. You won't see messages from devices " "that you do not trust." msgstr "" "%s ka përdorur një pajisje jo të besuar. S’do të shihni mesazhe prej " "pajisjesh që nuk i besoni." #: plugins/omemo/src/ui/bad_messages_populator.vala:158 msgid "Manage devices" msgstr "Administroni pajisje" #: plugins/omemo/src/ui/bad_messages_populator.vala:160 #, c-format msgid "" "%s does not trust this device. That means, you might be missing messages." msgstr "" "%s nuk e beson këtë pajisje. Kjo do të thotë, mund të jeni duke humbur " "mesazhe." #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Administroni Kyç" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contact's device." msgstr "" "Krahasoni shenjën e gishtave, shenjë pas shenje, me atë të shfaqur në " "pajisjen e kontaktit tuaj." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Fingerprints differ" msgstr "Shenjat e gishtave janë të ndryshme" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Fingerprints match" msgstr "Shenjat e gishtave përputhen" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Anuloje" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Ripohojeni" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Verifikoni kyç" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Future messages sent by %s from the device that uses this key will be " "highlighted accordingly in the chat window." msgstr "" "Mesazhe të ardhshme dërguar nga %s prej pajisjes që përdor këtë kyç do të " "theksohen te dritarja e fjalosjes." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Shenjat e gishtave nuk përputhen" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Ju lutemi, verifikoni se po krahasoi shenjat e sakta të gishtave. Nëse " "shenjat e gishtave nuk përputhen, llogaria e %s mund të komprometohet dhe " "duhet të shihni mundësinë e hedhjes poshtë të këtij kyçi." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Verifikoni shenja gishtash kyçi" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "Krahasoni shenjën e gishtit të këtij kyçi me shenjën e gishtit të shfaqur në " "pajisjen e kontaktit." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Hidhe tej kyçin" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Block encrypted communication with the contact's device that uses this key." msgstr "" "Blloko komunikim të fshehtëzuar me pajisjen e kontaktit që përdor këtë kyç." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Pranoje kyçin" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Allow encrypted communication with the contact's device that uses this key." msgstr "" "Lejo komunikim të fshehtëzuar me pajisjen e kontaktit që përdor këtë kyç." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Ky kyç aktualisht është %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "i pranuar" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send encrypted messages." msgstr "" "Kjo do të thotë se mund të përdoret nga %s për të marrë dhe dërguar mesazhe " "të fshehtëzuara." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "i verifikuar" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" "Për më tepër, është verifikuar për t’u përputhur me kyçin në pajisjen e " "kontaktit." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "i hedhur poshtë" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to decipher your messages, and you won't " "see messages encrypted with it." msgstr "" "Kjo do të thotë se s’mund të përdoret nga %s për të deshifruar mesazhet " "tuaja, dhe s’do të shihni dot mesazhe të fshehtëzuara me të." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "You won't see encrypted messages from the device of %s that uses this key. " "Conversely, that device won't be able to decipher your messages anymore." msgstr "" "S’do të shihni dot mesazhe të fshehtëzuar prej pajisjes së %s që përdor këtë " "kyç. Më anë tjetër, ajo pajisje s’do të jetë në gjendje të deshifrojë më " "mesazhet tuaja." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "You will be able to exchange encrypted messages with the device of %s that " "uses this key." msgstr "" "Do të jeni në gjendje të shkëmbeni mesazhe të fshehtëzuar me pajisjen e %s " "që përdor këtë kyç." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Mbrapsht" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Administroni" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Ky kontakt ka pajisje të reja" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Shenjë e vet gishtash" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connection" msgstr "Do të prodhohet gjatë lidhjes së parë" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "Lypset vendim besimi OMEMO" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "Shtuat një pajisje të re për llogarinë %s?" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Fshehtëzim" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d pajisje OMEMO" msgstr[1] "%d pajisje OMEMO" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "Administrim Kyçesh OMEMO" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Prano automatikisht kyçe të rinj" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "New encryption keys from this contact will be accepted automatically." msgstr "Kyçe të rinj fshehtëzimi nga ky kontakt do të pranohen automatikisht." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Kyç i vet" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Kyçe të rinj" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Kyçe të përshoqëruar" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "Kyçe jo aktivë" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "New encryption keys from your other devices will be accepted automatically." msgstr "" "Kyçe të rinj fshehtëzimi prej pajisjesh tuaja të tjera do të pranohen " "automatikisht." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Të pranuar" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Të hedhur tej" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Të verifikuar" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Të papërdorur" dino-0.3.0/plugins/omemo/po/sv.po0000644000000000000000000002551314202022370015342 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2021-03-05 16:50+0000\n" "Language-Team: none\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" "X-Generator: Weblate 4.5.1-dev\n" #: plugins/omemo/src/ui/bad_messages_populator.vala:157 #, c-format msgid "" "%s has been using an untrusted device. You won't see messages from devices " "that you do not trust." msgstr "" "%s har använt en ej betrodd enhet. Du kommer inte se meddelanden från " "enheter du inte litar på." #: plugins/omemo/src/ui/bad_messages_populator.vala:158 msgid "Manage devices" msgstr "Hantera enheter" #: plugins/omemo/src/ui/bad_messages_populator.vala:160 #, c-format msgid "" "%s does not trust this device. That means, you might be missing messages." msgstr "%s litar inte på den här enheten. Du kanske inte ser alla meddelanden." #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Hantera nyckel" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contact's device." msgstr "" "Jämför fingeravtrycket, tecken för tecken, med det som visas i din kontakts " "enhet." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Fingerprints differ" msgstr "Fingeravtrycken matcher ej" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Fingerprints match" msgstr "Fingeravtrycken matchar" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Avbryt" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Bekräfta" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Verifiera nyckel" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Future messages sent by %s from the device that uses this key will be " "highlighted accordingly in the chat window." msgstr "" "Framtida meddelanden skickade av %s från enheten som använder denna nyckel " "kommer tydligt att markeras i chattfönstret." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Fingeravtrycken överensstämmer inte" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Kontrollera att du jämför rätt fingeravtryck. Om fingeravtrycken inte " "stämmer så kan %s konto vara underminerat och du bör överväga att avvisa " "denna nyckel." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Verifiera nyckelns fingeravtryck" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "Jämför nyckelns fingeravtryck med det fingeravtryck som visas i kontaktens " "enhet." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Avvisa nyckeln" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Block encrypted communication with the contact's device that uses this key." msgstr "" "Blockera krypterad kommunikation med kontaktens enhet som använder den här " "nyckeln." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Acceptera nyckeln" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Allow encrypted communication with the contact's device that uses this key." msgstr "" "Tillåt krypterad kommunikation med kontaktens enhet som använder den här " "nyckeln." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Denna nyckel är för närvarande %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "accepterad" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send encrypted messages." msgstr "" "Det betyder att den kan användas av %s för att skicka och ta emot krypterade " "meddelanden." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "verifierad" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" "Dessutom har den verifierats så att den överensstämmer med nyckeln på " "kontaktens enhet." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "avvisad" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to decipher your messages, and you won't " "see messages encrypted with it." msgstr "" "Det innebär att den inte kan användas av %s för att dechiffrera dina " "meddelanden och du ser inte meddelanden som krypterats med den." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "You won't see encrypted messages from the device of %s that uses this key. " "Conversely, that device won't be able to decipher your messages anymore." msgstr "" "Du ser inte krypterade meddelanden från enheten %s som använder den här " "nyckeln. Omvänt kan den enheten inte dechiffrera dina meddelanden längre." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "You will be able to exchange encrypted messages with the device of %s that " "uses this key." msgstr "" "Du kommer att kunna utbyta krypterade meddelanden med enheten %s som " "använder den här nyckeln." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Tillbaka" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Hantera" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Denna kontakt har nya enheter" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Eget fingeravtryck" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connection" msgstr "Genereras vid första anslutningstillfället" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "OMEMO-tillitsbeslut krävs" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "Har du lagt till en ny enhet för kontot %s?" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Kryptering" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d OMEMO-enhet" msgstr[1] "%d OMEMO-enheter" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "OMEMO-nyckelhantering" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Acceptera automatiskt nya nycklar" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "New encryption keys from this contact will be accepted automatically." msgstr "Nya krypteringsnycklar från den här kontakten accepteras automatiskt." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Egen nyckel" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Ny nyckel" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Kopplade nycklar" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "Inaktiva nycklar" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "New encryption keys from your other devices will be accepted automatically." msgstr "Nya krypteringsnycklar från dina andra enheter accepteras automatiskt." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Accepterad" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Avvisad" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Verifierad" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Oanvänd" #~ msgid "Your contact" #~ msgstr "Din kontakt" #~ msgid "Not matching" #~ msgstr "Stämmer ej" #~ msgid "Matching" #~ msgstr "Stämmer" #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "highlighted accordingly in the chat window." #~ msgstr "" #~ "Om du går vidare så kommer framtida meddelanden från %s med denna nyckel " #~ "att markeras i enlighet med nyckelns nya status." #~ msgid "" #~ "Stop accepting this key during communication with its associated contact." #~ msgstr "" #~ "Sluta acceptera denna nyckel vid kommunikation med den tillhörande " #~ "kontakten." #~ msgid "" #~ "Start accepting this key during communication with its associated contact" #~ msgstr "" #~ "Börja acceptera nyckeln för kommunikation med den tillhörande kontakten" #~ msgid "" #~ "This means it cannot be used by %s to receive messages, and any messages " #~ "sent by it will be ignored." #~ msgstr "" #~ "Det betyder att den inte kan användas av %s för att ta emot meddelanden, " #~ "och att alla skickade meddelanden kommer att ignoreras." #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "ignored and none of your messages will be readable using this key." #~ msgstr "" #~ "Om du går vidare så kommer framtida meddelanden från %s som använder " #~ "denna nyckel att ignoreras, och meddelanden från dig blir oläsbara med " #~ "denna nyckel." #~ msgid "" #~ "Once confirmed this key will be usable by %s to receive and send messages." #~ msgstr "" #~ "Om du går vidare så kommer nyckeln kunna användas av %s för att skicka " #~ "och ta emot meddelanden." #~ msgid "" #~ "When this contact adds new encryption keys to their account, " #~ "automatically accept them." #~ msgstr "Acceptera automatiskt nya nycklar som läggs till av denna kontakt." #~ msgid "" #~ "When you add new encryption keys to your account, automatically accept " #~ "them." #~ msgstr "Acceptera automatiskt nya nycklar som du läggs till ditt konto." dino-0.3.0/plugins/omemo/po/tr.po0000644000000000000000000002566014202022370015342 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2021-07-30 08:33+0000\n" "Language-Team: none\n" "Language: tr\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: Weblate 4.7.2-dev\n" #: plugins/omemo/src/ui/bad_messages_populator.vala:157 #, c-format msgid "" "%s has been using an untrusted device. You won't see messages from devices " "that you do not trust." msgstr "" "%s güvenilmeyen bir cihaz kullanıyor. Güvenmediğiniz cihazlardan gelen " "mesajları görmezsiniz." #: plugins/omemo/src/ui/bad_messages_populator.vala:158 msgid "Manage devices" msgstr "Cihazları yönet" #: plugins/omemo/src/ui/bad_messages_populator.vala:160 #, c-format msgid "" "%s does not trust this device. That means, you might be missing messages." msgstr "" "%s bu cihaza güvenmiyor. Bu, eksik mesajlarınız olabileceği anlamına gelir." #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Anahtarı Yönet" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contact's device." msgstr "" "Parmak izinin her bir karakterini, kişinizin cihazında gösterilen karakterle " "karşılaştırın." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Fingerprints differ" msgstr "Parmak izleri farklı" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Fingerprints match" msgstr "Parmak izleri eşleşiyor" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "İptal" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Onayla" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Anahtarı doğrula" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Future messages sent by %s from the device that uses this key will be " "highlighted accordingly in the chat window." msgstr "" "Bu anahtarı kullanan cihazdan %s tarafından gönderilen gelecekteki mesajlar, " "sohbet penceresinde vurgulanacaktır." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Parmak izleri uyuşmuyor" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Lütfen doğru parmak izini karşılaştırdığınızdan emin olun. Parmak izleri " "eşleşmezse, %s hesabının güvenliği ihlal edilmiş olabilir ve bu anahtarı " "reddetmenizi öneririz." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Anahtar parmak izini doğrulayın" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "Bu anahtarın parmak izini, kişinin cihazında görüntülenen parmak izi ile " "karşılaştırın." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Anahtarı reddet" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Block encrypted communication with the contact's device that uses this key." msgstr "Bu anahtarı kullanan kişinin cihazıyla şifreli iletişimi engelle." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Kabul" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Allow encrypted communication with the contact's device that uses this key." msgstr "Bu anahtarı kullanan kişinin cihazıyla şifreli iletişime izin ver." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Bu anahtar şu anda %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "kabul ediliyor" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send encrypted messages." msgstr "" "Bu, şifreli mesajlar almak ve göndermek için %s tarafından kullanılabileceği " "anlamına gelir." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "onaylı" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "Ayrıca, kişinin cihazındaki anahtarla eşleştiği doğrulandı." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "reddedildi" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to decipher your messages, and you won't " "see messages encrypted with it." msgstr "" "Bu, mesajlarınızın şifresini çözmek için %s tarafından kullanılamayacağı ve " "onunla şifrelenmiş mesajları göremeyeceğiniz anlamına gelir." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "You won't see encrypted messages from the device of %s that uses this key. " "Conversely, that device won't be able to decipher your messages anymore." msgstr "" "Bu anahtarı kullanan %s cihazından gelen şifreli mesajları görmeyeceksiniz. " "Diğer taraftan, o cihaz artık mesajlarınızın şifresini çözemeyecek." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "You will be able to exchange encrypted messages with the device of %s that " "uses this key." msgstr "" "Bu anahtarı kullanan %s cihazıyla şifreli mesaj alışverişi yapabileceksiniz." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Geri" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Yönet" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Bu kişinin yeni cihazları var" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Kendi parmak iziniz" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connection" msgstr "İlk bağlantıda oluşturulacak" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "OMEMO güven kararı gerekli" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "%s hesabı için yeni bir cihaz eklediniz mi?" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Şifreleme" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d OMEMO aygıtı" msgstr[1] "%d OMEMO aygıtı" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "OMEMO Anahtar Yönetimi" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Yeni anahtarları otomatik olarak kabul et" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "New encryption keys from this contact will be accepted automatically." msgstr "" "Bu kişiden gelen yeni şifreleme anahtarları otomatik olarak kabul edilecek." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Kendi anahtarınız" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Yeni anahtarlar" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "İlişkili anahtarlar" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "Etkin olmayan anahtarlar" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "New encryption keys from your other devices will be accepted automatically." msgstr "" "Diğer cihazlarınızdan gelen yeni şifreleme anahtarları otomatik olarak kabul " "edilecek." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Kabul edildi" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Reddedildi" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Onaylandı" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Kullanılmamış" #~ msgid "Your contact" #~ msgstr "Kişiniz" #~ msgid "Not matching" #~ msgstr "Eşleşmiyor" #~ msgid "Matching" #~ msgstr "Eşleşti" #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "highlighted accordingly in the chat window." #~ msgstr "" #~ "Onaylandıktan sonra, bu anahtarı kullanarak %s tarafından gönderilen " #~ "gelecek iletiler sohbet penceresinde buna göre vurgulanır." #~ msgid "" #~ "Stop accepting this key during communication with its associated contact." #~ msgstr "" #~ "İlişkili kişiyle iletişim kurarken bu anahtarı kabul etmeyi durdurun." #~ msgid "" #~ "Start accepting this key during communication with its associated contact" #~ msgstr "İlişkili kişiyle iletişim için bu anahtarı kabul edin" #~ msgid "" #~ "This means it cannot be used by %s to receive messages, and any messages " #~ "sent by it will be ignored." #~ msgstr "" #~ "Bu, %s tarafından ileti almak için kullanılamayacağı ve gönderdiği " #~ "iletilerin yok sayılacağı anlamına gelir." #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "ignored and none of your messages will be readable using this key." #~ msgstr "" #~ "Onaylandıktan sonra, bu anahtarı kullanarak %s tarafından gönderilen " #~ "gelecek iletiler yok sayılır ve iletilerinizden hiçbiri bu anahtar " #~ "kullanılarak okunmaz." #~ msgid "" #~ "Once confirmed this key will be usable by %s to receive and send messages." #~ msgstr "" #~ "Onaylandıktan sonra bu anahtar %s tarafından mesaj almak ve göndermek " #~ "için kullanılabilir." #~ msgid "" #~ "When this contact adds new encryption keys to their account, " #~ "automatically accept them." #~ msgstr "" #~ "Bu kişi hesaplarına yeni şifreleme anahtarları eklediğinde, otomatik " #~ "olarak kabul edilir." #~ msgid "" #~ "When you add new encryption keys to your account, automatically accept " #~ "them." #~ msgstr "" #~ "Hesabınıza yeni şifreleme anahtarları eklediğinizde, bunları otomatik " #~ "olarak kabul edin." dino-0.3.0/plugins/omemo/po/uk.po0000644000000000000000000001606114202022370015327 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-12-31 17:29+0000\n" "Language-Team: none\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=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" "X-Generator: Weblate 4.4.1-dev\n" #: plugins/omemo/src/ui/bad_messages_populator.vala:157 #, c-format msgid "" "%s has been using an untrusted device. You won't see messages from devices " "that you do not trust." msgstr "" #: plugins/omemo/src/ui/bad_messages_populator.vala:158 msgid "Manage devices" msgstr "Керування пристроями" #: plugins/omemo/src/ui/bad_messages_populator.vala:160 #, c-format msgid "" "%s does not trust this device. That means, you might be missing messages." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contact's device." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Fingerprints differ" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Fingerprints match" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Скасувати" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Підтвердити" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Підтвердити ключ" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Future messages sent by %s from the device that uses this key will be " "highlighted accordingly in the chat window." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Відбитки не збігаються" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Відхилити ключ" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Block encrypted communication with the contact's device that uses this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Allow encrypted communication with the contact's device that uses this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Ключ був успішно %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send encrypted messages." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to decipher your messages, and you won't " "see messages encrypted with it." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "You won't see encrypted messages from the device of %s that uses this key. " "Conversely, that device won't be able to decipher your messages anymore." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "You will be able to exchange encrypted messages with the device of %s that " "uses this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connection" msgstr "" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "" msgstr[1] "" msgstr[2] "" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "New encryption keys from this contact will be accepted automatically." msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "New encryption keys from your other devices will be accepted automatically." msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "" #~ msgid "Your contact" #~ msgstr "Ваш контакт" dino-0.3.0/plugins/omemo/po/zh_CN.po0000644000000000000000000002434714202022370015717 0ustar rootrootmsgid "" msgstr "" "Project-Id-Version: dino-omemo-0.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-10-26 14:26+0000\n" "Language-Team: Chinese (Simplified) \n" "Language: zh_CN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Weblate 4.3.2-dev\n" #: plugins/omemo/src/ui/bad_messages_populator.vala:157 #, c-format msgid "" "%s has been using an untrusted device. You won't see messages from devices " "that you do not trust." msgstr "%s 使用的是未受信任的设备。您将不会看到从您不信任的设备上发来的信息。" #: plugins/omemo/src/ui/bad_messages_populator.vala:158 msgid "Manage devices" msgstr "管理设备" #: plugins/omemo/src/ui/bad_messages_populator.vala:160 #, c-format msgid "" "%s does not trust this device. That means, you might be missing messages." msgstr "%s 不信任该设备。这意味着您可能会错过消息。" #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "管理密钥" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contact's device." msgstr "将指纹与联系人设备上显示的指纹按逐个字符进行比较。" #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Fingerprints differ" msgstr "指纹不匹配" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Fingerprints match" msgstr "指纹匹配" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "取消" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "确认" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "验证密钥" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Future messages sent by %s from the device that uses this key will be " "highlighted accordingly in the chat window." msgstr "之后由 %s 从使用该密钥的设备发送的消息将会相应地在聊天窗口中高亮。" #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "指纹不匹配" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "请确认您正在比较正确的指纹。如果指纹不匹配,可能会破坏%s的帐户,您应考虑拒绝" "使用此密钥。" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "验证密钥指纹" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "将此密钥的指纹与联系人设备上显示的指纹进行比较。" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "拒绝密钥" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Block encrypted communication with the contact's device that uses this key." msgstr "阻止与该联络人使用该密钥的设备的加密通信。" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "接受密钥" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Allow encrypted communication with the contact's device that uses this key." msgstr "允许与该联络人使用该密钥的设备的加密通信。" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "此密钥当前为%s。" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "接受" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send encrypted messages." msgstr "这意味着%s 可以使用它来接收和发送加密消息。" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "已验证" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "此外,已验证它与联系人设备上的密钥匹配。" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "拒绝" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to decipher your messages, and you won't " "see messages encrypted with it." msgstr "这意味着它不能被 %s 用于解密你的讯息,并且你将不会看到由它加密的讯息。" #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "You won't see encrypted messages from the device of %s that uses this key. " "Conversely, that device won't be able to decipher your messages anymore." msgstr "" "你将不会看到 %s 的使用该密钥的设备的加密讯息。反过来,那个设备再也不能解密你" "的讯息了。" #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "You will be able to exchange encrypted messages with the device of %s that " "uses this key." msgstr "你将能够与 %s 的使用该密钥的设备交换加密讯息。" #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "返回" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "管理" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "此联系人有新设备" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "自己的指纹" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connection" msgstr "将在第一次连接时生成" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "需要Omemo信任决策" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "是否为帐户%s添加了新设备?" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "加密" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d 个 OMEMO 设备" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "OMEMO 密钥管理" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "自动接受新密钥" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "New encryption keys from this contact will be accepted automatically." msgstr "来自该联络人的新加密密钥将被自动接受。" #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "自己的密钥" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "新密钥" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "关联密钥" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "不活动的密钥" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "New encryption keys from your other devices will be accepted automatically." msgstr "来自你其他设备的新加密密钥将被自动接受。" #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "接受的" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "拒绝的" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "验证的" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "不再使用" #~ msgid "Your contact" #~ msgstr "您的联系人" #~ msgid "Not matching" #~ msgstr "不匹配" #~ msgid "Matching" #~ msgstr "匹配" #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "highlighted accordingly in the chat window." #~ msgstr "" #~ "一旦确认,未来由%s使用此密钥发送的任何消息将在聊天窗口中相应突出显示。" #~ msgid "" #~ "Stop accepting this key during communication with its associated contact." #~ msgstr "在与其关联的联系人通信期间停止接受此密钥。" #~ msgid "" #~ "Start accepting this key during communication with its associated contact" #~ msgstr "在与其关联的联系人通信期间开始接受此密钥" #~ msgid "" #~ "This means it cannot be used by %s to receive messages, and any messages " #~ "sent by it will be ignored." #~ msgstr "这意味着%s不能使用它来接收消息, 它发送的任何消息都将被忽略。" #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "ignored and none of your messages will be readable using this key." #~ msgstr "" #~ "一旦确认,由%s使用此密钥发送的任何未来消息都将被忽略,并且使用此密钥将无法" #~ "读取任何消息。" #~ msgid "" #~ "Once confirmed this key will be usable by %s to receive and send messages." #~ msgstr "一旦确认,此密钥将被%s用于接收和发送消息。" #~ msgid "" #~ "When this contact adds new encryption keys to their account, " #~ "automatically accept them." #~ msgstr "当此联系人向其帐户添加新的加密密钥时,自动接受它们。" #~ msgid "" #~ "When you add new encryption keys to your account, automatically accept " #~ "them." #~ msgstr "当您向帐户添加新的加密密钥时,会自动接受它们。" #~ msgid "Unknown device (0x%.8x)" #~ msgstr "未知设备 (0x%.8x)" #~ msgid "Other devices" #~ msgstr "其他设备" #~ msgid "- None -" #~ msgstr "- 无 -" #~ msgid "Database error" #~ msgstr "数据库错误" dino-0.3.0/plugins/omemo/po/zh_TW.po0000644000000000000000000002441614202022370015746 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-11-24 05:28+0000\n" "Language-Team: Chinese (Traditional) \n" "Language: zh_TW\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Weblate 4.4-dev\n" #: plugins/omemo/src/ui/bad_messages_populator.vala:157 #, c-format msgid "" "%s has been using an untrusted device. You won't see messages from devices " "that you do not trust." msgstr "%s 使用了未信任的裝置。您不會看見來自未被您信任裝置的訊息。" #: plugins/omemo/src/ui/bad_messages_populator.vala:158 msgid "Manage devices" msgstr "管理裝置" #: plugins/omemo/src/ui/bad_messages_populator.vala:160 #, c-format msgid "" "%s does not trust this device. That means, you might be missing messages." msgstr "%s 不信任這部裝置。這意味著您可能會遺漏訊息。" #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "管理金鑰" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contact's device." msgstr "將指紋與顯示在聯絡人裝置上的指紋逐個字元地進行比較。" #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Fingerprints differ" msgstr "指紋不同" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Fingerprints match" msgstr "指紋吻合" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "取消" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "確定" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "核驗金鑰" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Future messages sent by %s from the device that uses this key will be " "highlighted accordingly in the chat window." msgstr "未來由 %s 從使用了此金鑰的裝置所傳送的訊息都將會在對話視窗內加亮顯示。" #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "指紋不吻合" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "請確定您正在比較正確的指紋。如果指紋不吻合,%s 的帳號可能不安全,同時您應該考" "虑拒絕此金鑰。" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "核驗金鑰指紋" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "將此金鑰的指紋與顯示在聯絡人裝置上的指紋進行比較。" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "拒絕金鑰" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Block encrypted communication with the contact's device that uses this key." msgstr "封鎖與該聯絡人使用此金鑰的裝置之間的加密通訊。" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "接收金鑰" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Allow encrypted communication with the contact's device that uses this key." msgstr "允許與該聯絡人使用此金鑰的裝置之間的加密通訊。" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "此金鑰目前%s。" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "已接收" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send encrypted messages." msgstr "這表示 %s 可以使用它來傳送及收受加密訊息。" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "已核驗" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "此外,已經核驗它與聯絡人裝置上的金鑰吻合。" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "已拒絕" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to decipher your messages, and you won't " "see messages encrypted with it." msgstr "這表示 %s 無法使用它來解密您的訊息,同時您不會看到使用它加密的訊息。" #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "You won't see encrypted messages from the device of %s that uses this key. " "Conversely, that device won't be able to decipher your messages anymore." msgstr "" "您不會看到從 %s 使用此金鑰的裝置所傳送的加密訊息。同時,該裝置亦無法再解密您" "的訊息。" #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "You will be able to exchange encrypted messages with the device of %s that " "uses this key." msgstr "您將能夠與 %s 所使用此金鑰的裝置互相傳送加密訊息。" #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "返回" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "管理" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "此連絡人有新的裝置" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "本裝置指紋" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connection" msgstr "將會在第一次連線時產生" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "需要 OMEMO 信任決策" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "您是否有爲帳號 %s 增加新裝置?" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "加密" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d 個 OMEMO 裝置" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "OMEMO 金鑰管理" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "自動接收新金鑰" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "New encryption keys from this contact will be accepted automatically." msgstr "新的來自此聯絡人的加密金鑰將會被自動接收。" #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "本裝置金鑰" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "新金鑰" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "其它裝置上的金鑰" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "長時間未用過的金鑰" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "New encryption keys from your other devices will be accepted automatically." msgstr "來自您其它裝置的新加密金鑰將會被自動接收。" #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "已接收" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "已拒絕" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "已核驗" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "不再使用" #~ msgid "Your contact" #~ msgstr "您的聯絡人" #~ msgid "Not matching" #~ msgstr "不吻合" #~ msgid "Matching" #~ msgstr "吻合" #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "highlighted accordingly in the chat window." #~ msgstr "" #~ "一經確定,所有未來由 %s 使用此金鑰傳送的訊息都會在對話視窗內被加亮。" #~ msgid "" #~ "Stop accepting this key during communication with its associated contact." #~ msgstr "在與其連結的聯絡人通訊期間停止接收此金鑰。" #~ msgid "" #~ "Start accepting this key during communication with its associated contact" #~ msgstr "在與其連結的聯絡人通訊期間開始接收此金鑰" #~ msgid "" #~ "This means it cannot be used by %s to receive messages, and any messages " #~ "sent by it will be ignored." #~ msgstr "這代表 %s 不能使用它來收受訊息,並且經它傳送的所有訊息都將被忽略。" #~ msgid "" #~ "Once confirmed, any future messages sent by %s using this key will be " #~ "ignored and none of your messages will be readable using this key." #~ msgstr "" #~ "一經確定,所有未來由 %s 使用此金鑰傳送的訊息都會被忽略,並且無法使用此金鑰" #~ "讀取您的任何訊息。" #~ msgid "" #~ "Once confirmed this key will be usable by %s to receive and send messages." #~ msgstr "一經確定,此金鑰將會被 %s 用於收受及傳送訊息。" #~ msgid "" #~ "When this contact adds new encryption keys to their account, " #~ "automatically accept them." #~ msgstr "當此聯絡人新增加密金鑰到其帳號時,自動接收它們。" #~ msgid "" #~ "When you add new encryption keys to your account, automatically accept " #~ "them." #~ msgstr "當您新增加密金鑰到您的帳號時,自動接收它們。" dino-0.3.0/plugins/omemo/src/0000755000000000000000000000000014202022370014515 5ustar rootrootdino-0.3.0/plugins/omemo/src/dtls_srtp_verification_draft.vala0000644000000000000000000003451414202022370023331 0ustar rootrootusing Signal; using Gee; using Xmpp; namespace Dino.Plugins.Omemo.DtlsSrtpVerificationDraft { public const string NS_URI = "http://gultsch.de/xmpp/drafts/omemo/dlts-srtp-verification"; public class StreamModule : XmppStreamModule { public static Xmpp.ModuleIdentity IDENTITY = new Xmpp.ModuleIdentity(NS_URI, "dtls_srtp_omemo_verification_draft"); private VerificationSendListener send_listener = new VerificationSendListener(); private HashMap device_id_by_jingle_sid = new HashMap(); private HashMap device_id_by_muji_member = new HashMap(); private HashMap> content_names_by_jingle_sid = new HashMap>(); private void on_preprocess_incoming_iq_set_get(XmppStream stream, Xmpp.Iq.Stanza iq) { if (iq.type_ != Iq.Stanza.TYPE_SET) return; Gee.List content_nodes = iq.stanza.get_deep_subnodes(Xep.Jingle.NS_URI + ":jingle", Xep.Jingle.NS_URI + ":content"); if (content_nodes.size == 0) return; string? jingle_sid = iq.stanza.get_deep_attribute(Xep.Jingle.NS_URI + ":jingle", "sid"); if (jingle_sid == null) return; Xep.Omemo.OmemoDecryptor decryptor = stream.get_module(Xep.Omemo.OmemoDecryptor.IDENTITY); foreach (StanzaNode content_node in content_nodes) { string? content_name = content_node.get_attribute("name"); if (content_name == null) continue; StanzaNode? transport_node = content_node.get_subnode("transport", Xep.JingleIceUdp.NS_URI); if (transport_node == null) continue; StanzaNode? fingerprint_node = transport_node.get_subnode("fingerprint", NS_URI); if (fingerprint_node == null) continue; StanzaNode? encrypted_node = fingerprint_node.get_subnode("encrypted", Omemo.NS_URI); if (encrypted_node == null) continue; Xep.Omemo.ParsedData? parsed_data = decryptor.parse_node(encrypted_node); if (parsed_data == null || parsed_data.ciphertext == null) continue; if (device_id_by_jingle_sid.has_key(jingle_sid) && device_id_by_jingle_sid[jingle_sid] != parsed_data.sid) { warning("Expected DTLS fingerprint to be OMEMO encrypted from %s %d, but it was from %d", iq.from.to_string(), device_id_by_jingle_sid[jingle_sid], parsed_data.sid); } foreach (Bytes encr_key in parsed_data.our_potential_encrypted_keys.keys) { parsed_data.is_prekey = parsed_data.our_potential_encrypted_keys[encr_key]; parsed_data.encrypted_key = encr_key.get_data(); try { uint8[] key = decryptor.decrypt_key(parsed_data, iq.from.bare_jid); string cleartext = decryptor.decrypt(parsed_data.ciphertext, key, parsed_data.iv); StanzaNode new_fingerprint_node = new StanzaNode.build("fingerprint", Xep.JingleIceUdp.DTLS_NS_URI).add_self_xmlns() .put_node(new StanzaNode.text(cleartext)); string? hash_attr = fingerprint_node.get_attribute("hash", NS_URI); string? setup_attr = fingerprint_node.get_attribute("setup", NS_URI); if (hash_attr != null) new_fingerprint_node.put_attribute("hash", hash_attr); if (setup_attr != null) new_fingerprint_node.put_attribute("setup", setup_attr); transport_node.put_node(new_fingerprint_node); device_id_by_jingle_sid[jingle_sid] = parsed_data.sid; if (!content_names_by_jingle_sid.has_key(content_name)) { content_names_by_jingle_sid[content_name] = new ArrayList(); } content_names_by_jingle_sid[content_name].add(content_name); stream.get_flag(Xep.Jingle.Flag.IDENTITY).get_session.begin(jingle_sid, (_, res) => { Xep.Jingle.Session? session = stream.get_flag(Xep.Jingle.Flag.IDENTITY).get_session.end(res); if (session == null || !session.contents_map.has_key(content_name)) return; var encryption = new OmemoContentEncryption(NS_URI, "OMEMO", iq.from.bare_jid, device_id_by_jingle_sid[jingle_sid]); session.contents_map[content_name].encryptions[NS_URI] = encryption; if (iq.stanza.get_deep_attribute(Xep.Jingle.NS_URI + ":jingle", "action") == "session-accept") { session.additional_content_add_incoming.connect(on_content_add_received); } }); break; } catch (Error e) { debug("Decrypting message from %s/%d failed: %s", iq.from.bare_jid.to_string(), parsed_data.sid, e.message); } } } } private void on_preprocess_outgoing_iq_set_get(XmppStream stream, Xmpp.Iq.Stanza iq) { if (iq.type_ != Iq.Stanza.TYPE_SET) return; StanzaNode? jingle_node = iq.stanza.get_subnode("jingle", Xep.Jingle.NS_URI); if (jingle_node == null) return; int device_id = -1; string? sid = jingle_node.get_attribute("sid", Xep.Jingle.NS_URI); if (sid != null && device_id_by_jingle_sid.has_key(sid)) { device_id = device_id_by_jingle_sid[sid]; } StanzaNode? muji_node = jingle_node.get_subnode("muji", Xep.Muji.NS_URI); if (muji_node != null) { string muji_room = muji_node.get_attribute("room"); try { Jid muji_jid = new Jid(muji_room); if (device_id_by_muji_member.has_key(@"$(muji_jid.bare_jid)/$(iq.to)")) { device_id = device_id_by_muji_member[@"$(muji_jid.bare_jid)/$(iq.to)"]; } } catch (InvalidJidError e) { // Ignore } } if (device_id == -1) return; Gee.List content_nodes = jingle_node.get_subnodes("content", Xep.Jingle.NS_URI); if (content_nodes.size == 0) return; foreach (StanzaNode content_node in content_nodes) { StanzaNode? transport_node = content_node.get_subnode("transport", Xep.JingleIceUdp.NS_URI); if (transport_node == null) continue; StanzaNode? fingerprint_node = transport_node.get_subnode("fingerprint", Xep.JingleIceUdp.DTLS_NS_URI); if (fingerprint_node == null) continue; string fingerprint = fingerprint_node.get_deep_string_content(); StanzaNode? encrypted_node = null; try { Xep.Omemo.OmemoEncryptor encryptor = stream.get_module(Xep.Omemo.OmemoEncryptor.IDENTITY); Xep.Omemo.EncryptionData enc_data = encryptor.encrypt_plaintext(fingerprint); encryptor.encrypt_key(enc_data, iq.to.bare_jid, device_id); encrypted_node = enc_data.get_encrypted_node(); } catch (Error e) { warning("Error while OMEMO-encrypting call keys: %s", e.message); return; } StanzaNode new_fingerprint_node = new StanzaNode.build("fingerprint", NS_URI).add_self_xmlns().put_node(encrypted_node); string? hash_attr = fingerprint_node.get_attribute("hash", Xep.JingleIceUdp.DTLS_NS_URI); string? setup_attr = fingerprint_node.get_attribute("setup", Xep.JingleIceUdp.DTLS_NS_URI); if (hash_attr != null) new_fingerprint_node.put_attribute("hash", hash_attr); if (setup_attr != null) new_fingerprint_node.put_attribute("setup", setup_attr); transport_node.put_node(new_fingerprint_node); transport_node.sub_nodes.remove(fingerprint_node); } } private void on_message_received(XmppStream stream, Xmpp.MessageStanza message) { StanzaNode? proceed_node = message.stanza.get_subnode("proceed", Xep.JingleMessageInitiation.NS_URI); if (proceed_node == null) return; string? jingle_sid = proceed_node.get_attribute("id"); if (jingle_sid == null) return; StanzaNode? device_node = proceed_node.get_subnode("device", NS_URI); if (device_node == null) return; int device_id = device_node.get_attribute_int("id", -1); if (device_id == -1) return; device_id_by_jingle_sid[jingle_sid] = device_id; } private void on_session_initiate_received(XmppStream stream, Xep.Jingle.Session session) { if (device_id_by_jingle_sid.has_key(session.sid)) { foreach (Xep.Jingle.Content content in session.contents) { on_content_add_received(stream, content); } } session.additional_content_add_incoming.connect(on_content_add_received); } private void on_content_add_received(XmppStream stream, Xep.Jingle.Content content) { if (!content_names_by_jingle_sid.has_key(content.session.sid) || content_names_by_jingle_sid[content.session.sid].contains(content.content_name)) { var encryption = new OmemoContentEncryption(NS_URI, "OMEMO", content.peer_full_jid.bare_jid, device_id_by_jingle_sid[content.session.sid]); content.encryptions[encryption.encryption_ns] = encryption; } } private void on_pre_send_presence_stanza(XmppStream stream, Presence.Stanza presence) { StanzaNode? muji_node = presence.stanza.get_subnode("muji", Xep.Muji.NS_URI); if (muji_node == null) return; StanzaNode device_node = new StanzaNode.build("device", NS_URI).add_self_xmlns() .put_attribute("id", stream.get_module(Omemo.StreamModule.IDENTITY).store.local_registration_id.to_string()); muji_node.put_node(device_node); } private void on_received_available(XmppStream stream, Presence.Stanza presence) { StanzaNode? muji_node = presence.stanza.get_subnode("muji", Xep.Muji.NS_URI); if (muji_node == null) return; StanzaNode? device_node = muji_node.get_subnode("device", NS_URI); if (device_node == null) return; int device_id = device_node.get_attribute_int("id", -1); if (device_id == -1) return; StanzaNode? muc_x_node = presence.stanza.get_subnode("x", "http://jabber.org/protocol/muc#user"); if (muc_x_node == null) return; StanzaNode? item_node = muc_x_node.get_subnode("item"); if (item_node == null) return; Jid? real_jid = null; try { string jid_attribute = item_node.get_attribute("jid"); if (jid_attribute == null) return; real_jid = new Jid(jid_attribute); } catch (InvalidJidError e) { // Ignore return; } device_id_by_muji_member[@"$(presence.from.bare_jid)/$(real_jid)"] = device_id; } public override void attach(XmppStream stream) { stream.get_module(Xmpp.MessageModule.IDENTITY).received_message.connect(on_message_received); stream.get_module(Xmpp.MessageModule.IDENTITY).send_pipeline.connect(send_listener); stream.get_module(Xmpp.Iq.Module.IDENTITY).preprocess_incoming_iq_set_get.connect(on_preprocess_incoming_iq_set_get); stream.get_module(Xmpp.Iq.Module.IDENTITY).preprocess_outgoing_iq_set_get.connect(on_preprocess_outgoing_iq_set_get); stream.get_module(Xep.Jingle.Module.IDENTITY).session_initiate_received.connect(on_session_initiate_received); stream.get_module(Xmpp.Presence.Module.IDENTITY).pre_send_presence_stanza.connect(on_pre_send_presence_stanza); stream.get_module(Xmpp.Presence.Module.IDENTITY).received_available.connect(on_received_available); } public override void detach(XmppStream stream) { stream.get_module(Xmpp.MessageModule.IDENTITY).received_message.disconnect(on_message_received); stream.get_module(Xmpp.MessageModule.IDENTITY).send_pipeline.disconnect(send_listener); stream.get_module(Xmpp.Iq.Module.IDENTITY).preprocess_incoming_iq_set_get.disconnect(on_preprocess_incoming_iq_set_get); stream.get_module(Xmpp.Iq.Module.IDENTITY).preprocess_outgoing_iq_set_get.disconnect(on_preprocess_outgoing_iq_set_get); stream.get_module(Xep.Jingle.Module.IDENTITY).session_initiate_received.disconnect(on_session_initiate_received); stream.get_module(Xmpp.Presence.Module.IDENTITY).received_available.disconnect(on_received_available); } public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } } public class VerificationSendListener : StanzaListener { private string[] after_actions_const = {}; public override string action_group { get { return "REWRITE_NODES"; } } public override string[] after_actions { get { return after_actions_const; } } public override async bool run(XmppStream stream, MessageStanza message) { StanzaNode? proceed_node = message.stanza.get_subnode("proceed", Xep.JingleMessageInitiation.NS_URI); if (proceed_node == null) return false; StanzaNode device_node = new StanzaNode.build("device", NS_URI).add_self_xmlns() .put_attribute("id", stream.get_module(Omemo.StreamModule.IDENTITY).store.local_registration_id.to_string()); proceed_node.put_node(device_node); return false; } } public class OmemoContentEncryption : Xep.Jingle.ContentEncryption { public Jid jid { get; set; } public int sid { get; set; } public OmemoContentEncryption(string encryption_ns, string encryption_name, Jid jid, int sid) { base(encryption_ns, encryption_name); this.jid = jid; this.sid = sid; } } } dino-0.3.0/plugins/omemo/src/file_transfer/0000755000000000000000000000000014202022370017340 5ustar rootrootdino-0.3.0/plugins/omemo/src/file_transfer/file_decryptor.vala0000644000000000000000000000727714202022370023234 0ustar rootrootusing Dino.Entities; using Crypto; using Signal; namespace Dino.Plugins.Omemo { public class OmemoHttpFileReceiveData : HttpFileReceiveData { public string original_url; } public class OmemoFileDecryptor : FileDecryptor, Object { private Regex url_regex = /^aesgcm:\/\/(.*)#(([A-Fa-f0-9]{2}){48}|([A-Fa-f0-9]{2}){44})$/; public Encryption get_encryption() { return Encryption.OMEMO; } public FileReceiveData prepare_get_meta_info(Conversation conversation, FileTransfer file_transfer, FileReceiveData receive_data) { HttpFileReceiveData? http_receive_data = receive_data as HttpFileReceiveData; if (http_receive_data == null) assert(false); if ((receive_data as OmemoHttpFileReceiveData) != null) return receive_data; var omemo_http_receive_data = new OmemoHttpFileReceiveData(); omemo_http_receive_data.url = aesgcm_to_https_link(http_receive_data.url); omemo_http_receive_data.original_url = http_receive_data.url; return omemo_http_receive_data; } public FileMeta prepare_download_file(Conversation conversation, FileTransfer file_transfer, FileReceiveData receive_data, FileMeta file_meta) { if (file_meta.file_name != null) { file_meta.file_name = file_meta.file_name.split("#")[0]; } return file_meta; } public bool can_decrypt_file(Conversation conversation, FileTransfer file_transfer, FileReceiveData receive_data) { HttpFileReceiveData? http_file_receive = receive_data as HttpFileReceiveData; if (http_file_receive == null) return false; return this.url_regex.match(http_file_receive.url) || (receive_data as OmemoHttpFileReceiveData) != null; } public async InputStream decrypt_file(InputStream encrypted_stream, Conversation conversation, FileTransfer file_transfer, FileReceiveData receive_data) throws FileReceiveError { const uint KEY_SIZE = 32; try { OmemoHttpFileReceiveData? omemo_http_receive_data = receive_data as OmemoHttpFileReceiveData; if (omemo_http_receive_data == null) assert(false); // Decode IV and key MatchInfo match_info; this.url_regex.match(omemo_http_receive_data.original_url, 0, out match_info); uint8[] iv_and_key = hex_to_bin(match_info.fetch(2).up()); uint8[] iv = iv_and_key[0:iv_and_key.length-KEY_SIZE]; uint8[] key = iv_and_key[iv_and_key.length-KEY_SIZE:iv_and_key.length]; file_transfer.encryption = Encryption.OMEMO; debug("Decrypting file %s from %s", file_transfer.file_name, file_transfer.server_file_name); SymmetricCipher cipher = new SymmetricCipher("AES-GCM"); cipher.set_key(key); cipher.set_iv(iv); return new ConverterInputStream(encrypted_stream, new SymmetricCipherDecrypter((owned) cipher, 16)); } catch (Crypto.Error e) { throw new FileReceiveError.DECRYPTION_FAILED("OMEMO file decryption error: %s".printf(e.message)); } catch (GLib.Error e) { throw new FileReceiveError.DECRYPTION_FAILED("OMEMO file decryption error: %s".printf(e.message)); } } private uint8[] hex_to_bin(string hex) { uint8[] bin = new uint8[hex.length / 2]; const string HEX = "0123456789ABCDEF"; for (int i = 0; i < hex.length / 2; i++) { bin[i] = (uint8) (HEX.index_of_char(hex[i*2]) << 4) | HEX.index_of_char(hex[i*2+1]); } return bin; } private string aesgcm_to_https_link(string aesgcm_link) { MatchInfo match_info; this.url_regex.match(aesgcm_link, 0, out match_info); return "https://" + match_info.fetch(1); } } } dino-0.3.0/plugins/omemo/src/file_transfer/file_encryptor.vala0000644000000000000000000000542514202022370023237 0ustar rootrootusing Gee; using Gtk; using Crypto; using Dino.Entities; using Xmpp; using Signal; namespace Dino.Plugins.Omemo { public class OmemoHttpFileMeta : HttpFileMeta { public uint8[] iv; public uint8[] key; } public class OmemoFileEncryptor : Dino.FileEncryptor, Object { public bool can_encrypt_file(Conversation conversation, FileTransfer file_transfer) { return file_transfer.encryption == Encryption.OMEMO; } public FileMeta encrypt_file(Conversation conversation, FileTransfer file_transfer) throws FileSendError { const uint KEY_SIZE = 32; const uint IV_SIZE = 12; var omemo_http_file_meta = new OmemoHttpFileMeta(); try { //Create a key and use it to encrypt the file uint8[] iv = new uint8[IV_SIZE]; Plugin.get_context().randomize(iv); uint8[] key = new uint8[KEY_SIZE]; Plugin.get_context().randomize(key); SymmetricCipher cipher = new SymmetricCipher("AES-GCM"); cipher.set_key(key); cipher.set_iv(iv); omemo_http_file_meta.iv = iv; omemo_http_file_meta.key = key; omemo_http_file_meta.size = file_transfer.size + 16; omemo_http_file_meta.mime_type = "omemo"; file_transfer.input_stream = new ConverterInputStream(file_transfer.input_stream, new SymmetricCipherEncrypter((owned) cipher, 16)); } catch (Crypto.Error error) { throw new FileSendError.ENCRYPTION_FAILED("OMEMO file encryption error: %s".printf(error.message)); } catch (GLib.Error error) { throw new FileSendError.ENCRYPTION_FAILED("OMEMO file encryption error: %s".printf(error.message)); } debug("Encrypting file %s as %s", file_transfer.file_name, file_transfer.server_file_name); return omemo_http_file_meta; } public FileSendData? preprocess_send_file(Conversation conversation, FileTransfer file_transfer, FileSendData file_send_data, FileMeta file_meta) { HttpFileSendData? send_data = file_send_data as HttpFileSendData; if (send_data == null) return null; OmemoHttpFileMeta? omemo_http_file_meta = file_meta as OmemoHttpFileMeta; if (omemo_http_file_meta == null) return null; // Convert iv and key to hex string iv_and_key = ""; foreach (uint8 byte in omemo_http_file_meta.iv) iv_and_key += byte.to_string("%02x"); foreach (uint8 byte in omemo_http_file_meta.key) iv_and_key += byte.to_string("%02x"); string aesgcm_link = send_data.url_down + "#" + iv_and_key; aesgcm_link = "aesgcm://" + aesgcm_link.substring(8); // replace https:// by aesgcm:// send_data.url_down = aesgcm_link; send_data.encrypt_message = true; return file_send_data; } } } dino-0.3.0/plugins/omemo/src/jingle/0000755000000000000000000000000014202022370015765 5ustar rootrootdino-0.3.0/plugins/omemo/src/jingle/jet_omemo.vala0000644000000000000000000001216214202022370020612 0ustar rootrootusing Crypto; using Dino; using Dino.Entities; using Gee; using Signal; using Xmpp; using Xmpp.Xep; namespace Dino.Plugins.JetOmemo { private const string NS_URI = "urn:xmpp:jingle:jet-omemo:0"; private const string AES_128_GCM_URI = "urn:xmpp:ciphers:aes-128-gcm-nopadding"; public class Module : XmppStreamModule, Jet.EnvelopEncoding { public static Xmpp.ModuleIdentity IDENTITY = new Xmpp.ModuleIdentity(NS_URI, "0396_jet_omemo"); const uint KEY_SIZE = 16; const uint IV_SIZE = 12; public override void attach(XmppStream stream) { if (stream.get_module(Jet.Module.IDENTITY) != null) { stream.get_module(ServiceDiscovery.Module.IDENTITY).add_feature(stream, NS_URI); stream.get_module(Jet.Module.IDENTITY).register_envelop_encoding(this); stream.get_module(Jet.Module.IDENTITY).register_cipher(new AesGcmCipher(KEY_SIZE, IV_SIZE, AES_128_GCM_URI)); } } public override void detach(XmppStream stream) { stream.get_module(ServiceDiscovery.Module.IDENTITY).remove_feature(stream, NS_URI); } public async bool is_available(XmppStream stream, Jid full_jid) { bool? has_feature = yield stream.get_module(ServiceDiscovery.Module.IDENTITY).has_entity_feature(stream, full_jid, NS_URI); if (has_feature == null || !(!)has_feature) { return false; } return yield stream.get_module(Xep.Jet.Module.IDENTITY).is_available(stream, full_jid); } public string get_type_uri() { return Omemo.NS_URI; } public Jet.TransportSecret decode_envolop(XmppStream stream, Jid local_full_jid, Jid peer_full_jid, StanzaNode security) throws Jingle.IqError { StanzaNode? encrypted = security.get_subnode("encrypted", Omemo.NS_URI); if (encrypted == null) throw new Jingle.IqError.BAD_REQUEST("Invalid JET-OMEMO envelop: missing encrypted element"); Xep.Omemo.OmemoDecryptor decryptor = stream.get_module(Xep.Omemo.OmemoDecryptor.IDENTITY); Xmpp.Xep.Omemo.ParsedData? data = decryptor.parse_node(encrypted); if (data == null) throw new Jingle.IqError.BAD_REQUEST("Invalid JET-OMEMO envelop: bad encrypted element"); foreach (Bytes encr_key in data.our_potential_encrypted_keys.keys) { data.is_prekey = data.our_potential_encrypted_keys[encr_key]; data.encrypted_key = encr_key.get_data(); try { uint8[] key = decryptor.decrypt_key(data, peer_full_jid.bare_jid); return new Jet.TransportSecret(key, data.iv); } catch (GLib.Error e) { debug("Decrypting JET key from %s/%d failed: %s", peer_full_jid.bare_jid.to_string(), data.sid, e.message); } } throw new Jingle.IqError.NOT_ACCEPTABLE("Not encrypted for targeted device"); } public void encode_envelop(XmppStream stream, Jid local_full_jid, Jid peer_full_jid, Jet.SecurityParameters security_params, StanzaNode security) { Store store = stream.get_module(Omemo.StreamModule.IDENTITY).store; var encryption_data = new Xep.Omemo.EncryptionData(store.local_registration_id); encryption_data.iv = security_params.secret.initialization_vector; encryption_data.keytag = security_params.secret.transport_key; Xep.Omemo.OmemoEncryptor encryptor = stream.get_module(Xep.Omemo.OmemoEncryptor.IDENTITY); encryptor.encrypt_key_to_recipient(stream, encryption_data, peer_full_jid.bare_jid); security.put_node(encryption_data.get_encrypted_node()); } public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } } public class AesGcmCipher : Jet.Cipher, Object { private uint key_size; private uint default_iv_size; private string uri; public AesGcmCipher(uint key_size, uint default_iv_size, string uri) { this.key_size = key_size; this.default_iv_size = default_iv_size; this.uri = uri; } public string get_cipher_uri() { return uri; } public Jet.TransportSecret generate_random_secret() { uint8[] iv = new uint8[default_iv_size]; Omemo.Plugin.get_context().randomize(iv); uint8[] key = new uint8[key_size]; Omemo.Plugin.get_context().randomize(key); return new Jet.TransportSecret(key, iv); } public InputStream wrap_input_stream(InputStream input, Jet.TransportSecret secret) requires (secret.transport_key.length == key_size) { SymmetricCipher cipher = new SymmetricCipher("AES-GCM"); cipher.set_key(secret.transport_key); cipher.set_iv(secret.initialization_vector); return new ConverterInputStream(input, new SymmetricCipherDecrypter((owned) cipher, 16)); } public OutputStream wrap_output_stream(OutputStream output, Jet.TransportSecret secret) requires (secret.transport_key.length == key_size) { Crypto.SymmetricCipher cipher = new SymmetricCipher("AES-GCM"); cipher.set_key(secret.transport_key); cipher.set_iv(secret.initialization_vector); return new ConverterOutputStream(output, new SymmetricCipherEncrypter((owned) cipher, 16)); } } } dino-0.3.0/plugins/omemo/src/jingle/jingle_helper.vala0000644000000000000000000000356114202022370021446 0ustar rootrootusing Dino.Entities; using Xmpp; namespace Dino.Plugins.JetOmemo { public class EncryptionHelper : JingleFileEncryptionHelper, Object { private StreamInteractor stream_interactor; public EncryptionHelper(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; } public bool can_transfer(Conversation conversation) { return true; } public async bool can_encrypt(Conversation conversation, FileTransfer file_transfer, Jid? full_jid) { XmppStream? stream = stream_interactor.get_stream(conversation.account); if (stream == null) return false; Gee.List? resources = stream.get_flag(Presence.Flag.IDENTITY).get_resources(conversation.counterpart); if (resources == null) return false; if (full_jid == null) { foreach (Jid test_jid in resources) { if (yield stream.get_module(Module.IDENTITY).is_available(stream, test_jid)) { return true; } } } else { if (yield stream.get_module(Module.IDENTITY).is_available(stream, full_jid)) { return true; } } return false; } public string? get_precondition_name(Conversation conversation, FileTransfer file_transfer) { return Xep.Jet.NS_URI; } public Object? get_precondition_options(Conversation conversation, FileTransfer file_transfer) { return new Xep.Jet.Options(Omemo.NS_URI, AES_128_GCM_URI); } public Encryption get_encryption(Xmpp.Xep.JingleFileTransfer.FileTransfer jingle_transfer) { Xep.Jet.SecurityParameters? security = jingle_transfer.security as Xep.Jet.SecurityParameters; if (security != null && security.encoding.get_type_uri() == Omemo.NS_URI) { return Encryption.OMEMO; } return Encryption.NONE; } } } dino-0.3.0/plugins/omemo/src/logic/0000755000000000000000000000000014202022370015612 5ustar rootrootdino-0.3.0/plugins/omemo/src/logic/database.vala0000644000000000000000000003361614202022370020234 0ustar rootrootusing Gee; using Qlite; using Dino.Entities; namespace Dino.Plugins.Omemo { public class Database : Qlite.Database { private const int VERSION = 5; public class IdentityMetaTable : Table { //Default to provide backwards compatability public Column identity_id = new Column.Integer("identity_id") { not_null = true, min_version = 2, default = "-1" }; public Column address_name = new Column.Text("address_name") { not_null = true }; public Column device_id = new Column.Integer("device_id") { not_null = true }; public Column identity_key_public_base64 = new Column.Text("identity_key_public_base64"); public Column trusted_identity = new Column.BoolInt("trusted_identity") { default = "0", max_version = 1 }; public Column trust_level = new Column.Integer("trust_level") { default = TrustLevel.UNKNOWN.to_string(), min_version = 2 }; public Column now_active = new Column.BoolInt("now_active") { default = "1" }; public Column last_active = new Column.Long("last_active"); public Column last_message_untrusted = new Column.Long("last_message_untrusted") { min_version = 5 }; public Column last_message_undecryptable = new Column.Long("last_message_undecryptable") { min_version = 5 }; internal IdentityMetaTable(Database db) { base(db, "identity_meta"); init({identity_id, address_name, device_id, identity_key_public_base64, trusted_identity, trust_level, now_active, last_active, last_message_untrusted, last_message_undecryptable}); index("identity_meta_idx", {identity_id, address_name, device_id}, true); index("identity_meta_list_idx", {identity_id, address_name}); } public QueryBuilder with_address(int identity_id, string address_name) { return select().with(this.identity_id, "=", identity_id).with(this.address_name, "=", address_name); } public QueryBuilder get_with_device_id(int identity_id, int device_id) { return select().with(this.identity_id, "=", identity_id).with(this.device_id, "=", device_id); } public void insert_device_list(int32 identity_id, string address_name, ArrayList devices) { update().with(this.identity_id, "=", identity_id).with(this.address_name, "=", address_name).set(now_active, false).perform(); foreach (int32 device_id in devices) { upsert() .value(this.identity_id, identity_id, true) .value(this.address_name, address_name, true) .value(this.device_id, device_id, true) .value(this.now_active, true) .value(this.last_active, (long) new DateTime.now_utc().to_unix()) .perform(); } } public int64 insert_device_bundle(int32 identity_id, string address_name, int device_id, Bundle bundle, TrustLevel trust) { if (bundle == null || bundle.identity_key == null) return -1; // Do not replace identity_key if it was known before, it should never change! string identity_key = Base64.encode(bundle.identity_key.serialize()); RowOption row = with_address(identity_id, address_name).with(this.device_id, "=", device_id).single().row(); if (row.is_present() && row[identity_key_public_base64] != null && row[identity_key_public_base64] != identity_key) { critical("Tried to change the identity key for a known device id. Likely an attack."); return -1; } return upsert() .value(this.identity_id, identity_id, true) .value(this.address_name, address_name, true) .value(this.device_id, device_id, true) .value(this.identity_key_public_base64, identity_key) .value(this.trust_level, trust).perform(); } public int64 insert_device_session(int32 identity_id, string address_name, int device_id, string identity_key, TrustLevel trust) { RowOption row = with_address(identity_id, address_name).with(this.device_id, "=", device_id).single().row(); if (row.is_present() && row[identity_key_public_base64] != null && row[identity_key_public_base64] != identity_key) { critical("Tried to change the identity key for a known device id. Likely an attack."); return -1; } return upsert() .value(this.identity_id, identity_id, true) .value(this.address_name, address_name, true) .value(this.device_id, device_id, true) .value(this.identity_key_public_base64, identity_key) .value(this.trust_level, trust).perform(); } public void update_last_message_untrusted(int identity_id, int device_id, DateTime? time) { var stmt = update() .with(this.identity_id, "=", identity_id) .with(this.device_id, "=", device_id); if (time != null) { stmt.set(last_message_untrusted, (long)time.to_unix()); } else { stmt.set_null(last_message_untrusted); } stmt.perform(); } public void update_last_message_undecryptable(int identity_id, int device_id, DateTime? time) { var stmt = update() .with(this.identity_id, "=", identity_id) .with(this.device_id, "=", device_id); if (time != null) { stmt.set(last_message_undecryptable, (long)time.to_unix()); } else { stmt.set_null(last_message_undecryptable); } stmt.perform(); } public QueryBuilder get_trusted_devices(int identity_id, string address_name) { return this.with_address(identity_id, address_name) .with(this.trust_level, "!=", TrustLevel.UNTRUSTED) .with(this.now_active, "=", true); } public QueryBuilder get_known_devices(int identity_id, string address_name) { return this.with_address(identity_id, address_name) .with(this.trust_level, "!=", TrustLevel.UNKNOWN) .without_null(this.identity_key_public_base64); } public QueryBuilder get_unknown_devices(int identity_id, string address_name) { return this.with_address(identity_id, address_name) .with_null(this.identity_key_public_base64); } public QueryBuilder get_new_devices(int identity_id, string address_name) { return this.with_address(identity_id, address_name) .with(this.trust_level, "=", TrustLevel.UNKNOWN) .without_null(this.identity_key_public_base64); } public Row? get_device(int identity_id, string address_name, int device_id) { return this.with_address(identity_id, address_name) .with(this.device_id, "=", device_id).single().row().inner; } } public class TrustTable : Table { public Column identity_id = new Column.Integer("identity_id") { not_null = true }; public Column address_name = new Column.Text("address_name"); public Column blind_trust = new Column.BoolInt("blind_trust") { default = "1" } ; internal TrustTable(Database db) { base(db, "trust"); init({identity_id, address_name, blind_trust}); index("trust_idx", {identity_id, address_name}, true); } public bool get_blind_trust(int32 identity_id, string address_name, bool def = false) { RowOption row = this.select().with(this.identity_id, "=", identity_id) .with(this.address_name, "=", address_name).single().row(); if (row.is_present()) return row[blind_trust]; return def; } } public class IdentityTable : Table { public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; public Column account_id = new Column.Integer("account_id") { unique = true, not_null = true }; public Column device_id = new Column.Integer("device_id") { not_null = true }; public Column identity_key_private_base64 = new Column.NonNullText("identity_key_private_base64"); public Column identity_key_public_base64 = new Column.NonNullText("identity_key_public_base64"); internal IdentityTable(Database db) { base(db, "identity"); init({id, account_id, device_id, identity_key_private_base64, identity_key_public_base64}); } public int get_id(int account_id) { int id = -1; Row? row = this.row_with(this.account_id, account_id).inner; if (row != null) id = ((!)row)[this.id]; return id; } } public class SignedPreKeyTable : Table { public Column identity_id = new Column.Integer("identity_id") { not_null = true }; public Column signed_pre_key_id = new Column.Integer("signed_pre_key_id") { not_null = true }; public Column record_base64 = new Column.NonNullText("record_base64"); internal SignedPreKeyTable(Database db) { base(db, "signed_pre_key"); init({identity_id, signed_pre_key_id, record_base64}); unique({identity_id, signed_pre_key_id}); index("signed_pre_key_idx", {identity_id, signed_pre_key_id}, true); } } public class PreKeyTable : Table { public Column identity_id = new Column.Integer("identity_id") { not_null = true }; public Column pre_key_id = new Column.Integer("pre_key_id") { not_null = true }; public Column record_base64 = new Column.NonNullText("record_base64"); internal PreKeyTable(Database db) { base(db, "pre_key"); init({identity_id, pre_key_id, record_base64}); unique({identity_id, pre_key_id}); index("pre_key_idx", {identity_id, pre_key_id}, true); } } public class SessionTable : Table { public Column identity_id = new Column.Integer("identity_id") { not_null = true }; public Column address_name = new Column.NonNullText("name"); public Column device_id = new Column.Integer("device_id") { not_null = true }; public Column record_base64 = new Column.NonNullText("record_base64"); internal SessionTable(Database db) { base(db, "session"); init({identity_id, address_name, device_id, record_base64}); unique({identity_id, address_name, device_id}); index("session_idx", {identity_id, address_name, device_id}, true); } } public class ContentItemMetaTable : Table { public Column content_item_id = new Column.Integer("message_id") { primary_key = true }; public Column identity_id = new Column.Integer("identity_id") { not_null = true }; public Column address_name = new Column.Text("address_name") { not_null = true }; public Column device_id = new Column.Integer("device_id") { not_null = true }; public Column trusted_when_received = new Column.BoolInt("trusted_when_received") { not_null = true, default = "1" }; internal ContentItemMetaTable(Database db) { base(db, "content_item_meta"); init({content_item_id, identity_id, address_name, device_id, trusted_when_received}); index("content_item_meta_device_idx", {identity_id, device_id, address_name}); } public RowOption with_content_item(ContentItem item) { return row_with(content_item_id, item.id); } public QueryBuilder with_device(int identity_id, string address_name, int device_id) { return select() .with(this.identity_id, "=", identity_id) .with(this.address_name, "=", address_name) .with(this.device_id, "=", device_id); } } public IdentityMetaTable identity_meta { get; private set; } public TrustTable trust { get; private set; } public IdentityTable identity { get; private set; } public SignedPreKeyTable signed_pre_key { get; private set; } public PreKeyTable pre_key { get; private set; } public SessionTable session { get; private set; } public ContentItemMetaTable content_item_meta { get; private set; } public Database(string fileName) { base(fileName, VERSION); identity_meta = new IdentityMetaTable(this); trust = new TrustTable(this); identity = new IdentityTable(this); signed_pre_key = new SignedPreKeyTable(this); pre_key = new PreKeyTable(this); session = new SessionTable(this); content_item_meta = new ContentItemMetaTable(this); init({identity_meta, trust, identity, signed_pre_key, pre_key, session, content_item_meta}); try { exec("PRAGMA journal_mode = WAL"); exec("PRAGMA synchronous = NORMAL"); exec("PRAGMA secure_delete = ON"); } catch (Error e) { error("Failed to set OMEMO database properties: %s", e.message); } } public override void migrate(long oldVersion) { if(oldVersion == 1) { try { exec("DROP INDEX identity_meta_idx"); exec("DROP INDEX identity_meta_list_idx"); exec("CREATE UNIQUE INDEX identity_meta_idx ON identity_meta (identity_id, address_name, device_id)"); exec("CREATE INDEX identity_meta_list_idx ON identity_meta (identity_id, address_name)"); } catch (Error e) { stderr.printf("Failed to migrate OMEMO database\n"); Process.exit(-1); } } } } } dino-0.3.0/plugins/omemo/src/logic/decrypt.vala0000644000000000000000000002456114202022370020141 0ustar rootrootusing Dino.Entities; using Qlite; using Gee; using Signal; using Xmpp; namespace Dino.Plugins.Omemo { public class OmemoDecryptor : Xep.Omemo.OmemoDecryptor { private Account account; private Store store; private Database db; private StreamInteractor stream_interactor; private TrustManager trust_manager; public override uint32 own_device_id { get { return store.local_registration_id; }} public OmemoDecryptor(Account account, StreamInteractor stream_interactor, TrustManager trust_manager, Database db, Store store) { this.account = account; this.stream_interactor = stream_interactor; this.trust_manager = trust_manager; this.db = db; this.store = store; } public bool decrypt_message(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) { StanzaNode? encrypted_node = stanza.stanza.get_subnode("encrypted", NS_URI); if (encrypted_node == null || MessageFlag.get_flag(stanza) != null || stanza.from == null) return false; if (message.body == null && Xep.ExplicitEncryption.get_encryption_tag(stanza) == NS_URI) { message.body = "[This message is OMEMO encrypted]"; // TODO temporary } if (!Plugin.ensure_context()) return false; int identity_id = db.identity.get_id(conversation.account.id); MessageFlag flag = new MessageFlag(); stanza.add_flag(flag); Xep.Omemo.ParsedData? data = parse_node(encrypted_node); if (data == null || data.ciphertext == null) return false; foreach (Bytes encr_key in data.our_potential_encrypted_keys.keys) { data.is_prekey = data.our_potential_encrypted_keys[encr_key]; data.encrypted_key = encr_key.get_data(); Gee.List possible_jids = get_potential_message_jids(message, data, identity_id); if (possible_jids.size == 0) { debug("Received message from unknown entity with device id %d", data.sid); } foreach (Jid possible_jid in possible_jids) { try { uint8[] key = decrypt_key(data, possible_jid); string cleartext = arr_to_str(aes_decrypt(Cipher.AES_GCM_NOPADDING, key, data.iv, data.ciphertext)); // If we figured out which real jid a message comes from due to decryption working, save it if (conversation.type_ == Conversation.Type.GROUPCHAT && message.real_jid == null) { message.real_jid = possible_jid; } message.body = cleartext; message.encryption = Encryption.OMEMO; trust_manager.message_device_id_map[message] = data.sid; return true; } catch (Error e) { debug("Decrypting message from %s/%d failed: %s", possible_jid.to_string(), data.sid, e.message); } } } if ( encrypted_node.get_deep_string_content("payload") != null && // Ratchet forwarding doesn't contain payload and might not include us, which is ok data.our_potential_encrypted_keys.size == 0 && // The message was not encrypted to us stream_interactor.module_manager.get_module(message.account, StreamModule.IDENTITY).store.local_registration_id != data.sid // Message from this device. Never encrypted to itself. ) { db.identity_meta.update_last_message_undecryptable(identity_id, data.sid, message.time); trust_manager.bad_message_state_updated(conversation.account, message.from, data.sid); } debug("Received OMEMO encryped message that could not be decrypted."); return false; } public Gee.List get_potential_message_jids(Entities.Message message, Xmpp.Xep.Omemo.ParsedData data, int identity_id) { Gee.List possible_jids = new ArrayList(); if (message.type_ == Message.Type.CHAT) { possible_jids.add(message.from.bare_jid); } else { if (message.real_jid != null) { possible_jids.add(message.real_jid.bare_jid); } else if (data.is_prekey) { // pre key messages do store the identity key, so we can use that to find the real jid PreKeySignalMessage msg = Plugin.get_context().deserialize_pre_key_signal_message(data.encrypted_key); string identity_key = Base64.encode(msg.identity_key.serialize()); foreach (Row row in db.identity_meta.get_with_device_id(identity_id, data.sid).with(db.identity_meta.identity_key_public_base64, "=", identity_key)) { try { possible_jids.add(new Jid(row[db.identity_meta.address_name])); } catch (InvalidJidError e) { warning("Ignoring invalid jid from database: %s", e.message); } } } else { // If we don't know the device name (MUC history w/o MAM), test decryption with all keys with fitting device id foreach (Row row in db.identity_meta.get_with_device_id(identity_id, data.sid)) { try { possible_jids.add(new Jid(row[db.identity_meta.address_name])); } catch (InvalidJidError e) { warning("Ignoring invalid jid from database: %s", e.message); } } } } return possible_jids; } public override uint8[] decrypt_key(Xmpp.Xep.Omemo.ParsedData data, Jid from_jid) throws GLib.Error { int sid = data.sid; uint8[] ciphertext = data.ciphertext; uint8[] encrypted_key = data.encrypted_key; Address address = new Address(from_jid.to_string(), sid); uint8[] key; if (data.is_prekey) { int identity_id = db.identity.get_id(account.id); PreKeySignalMessage msg = Plugin.get_context().deserialize_pre_key_signal_message(encrypted_key); string identity_key = Base64.encode(msg.identity_key.serialize()); bool ok = update_db_for_prekey(identity_id, identity_key, from_jid, sid); if (!ok) throw new GLib.Error(-1, 0, "Failed updating db for prekey"); debug("Starting new session for decryption with device from %s/%d", from_jid.to_string(), sid); SessionCipher cipher = store.create_session_cipher(address); key = cipher.decrypt_pre_key_signal_message(msg); // TODO: Finish session } else { debug("Continuing session for decryption with device from %s/%d", from_jid.to_string(), sid); SignalMessage msg = Plugin.get_context().deserialize_signal_message(encrypted_key); SessionCipher cipher = store.create_session_cipher(address); key = cipher.decrypt_signal_message(msg); } if (key.length >= 32) { int authtaglength = key.length - 16; uint8[] new_ciphertext = new uint8[ciphertext.length + authtaglength]; uint8[] new_key = new uint8[16]; Memory.copy(new_ciphertext, ciphertext, ciphertext.length); Memory.copy((uint8*)new_ciphertext + ciphertext.length, (uint8*)key + 16, authtaglength); Memory.copy(new_key, key, 16); data.ciphertext = new_ciphertext; key = new_key; } return key; } public override string decrypt(uint8[] ciphertext, uint8[] key, uint8[] iv) throws GLib.Error { return arr_to_str(aes_decrypt(Cipher.AES_GCM_NOPADDING, key, iv, ciphertext)); } private bool update_db_for_prekey(int identity_id, string identity_key, Jid from_jid, int sid) { Row? device = db.identity_meta.get_device(identity_id, from_jid.to_string(), sid); if (device != null && device[db.identity_meta.identity_key_public_base64] != null) { if (device[db.identity_meta.identity_key_public_base64] != identity_key) { critical("Tried to use a different identity key for a known device id."); return false; } } else { debug("Learn new device from incoming message from %s/%d", from_jid.to_string(), sid); bool blind_trust = db.trust.get_blind_trust(identity_id, from_jid.to_string(), true); if (db.identity_meta.insert_device_session(identity_id, from_jid.to_string(), sid, identity_key, blind_trust ? TrustLevel.TRUSTED : TrustLevel.UNKNOWN) < 0) { critical("Failed learning a device."); return false; } XmppStream? stream = stream_interactor.get_stream(account); if (device == null && stream != null) { stream.get_module(StreamModule.IDENTITY).request_user_devicelist.begin(stream, from_jid); } } return true; } private string arr_to_str(uint8[] arr) { // null-terminate the array uint8[] rarr = new uint8[arr.length+1]; Memory.copy(rarr, arr, arr.length); return (string)rarr; } } public class DecryptMessageListener : MessageListener { public string[] after_actions_const = new string[]{ }; public override string action_group { get { return "DECRYPT"; } } public override string[] after_actions { get { return after_actions_const; } } private HashMap decryptors; public DecryptMessageListener(HashMap decryptors) { this.decryptors = decryptors; } public override async bool run(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) { decryptors[message.account].decrypt_message(message, stanza, conversation); return false; } } } dino-0.3.0/plugins/omemo/src/logic/encrypt.vala0000644000000000000000000001322014202022370020141 0ustar rootrootusing Gee; using Signal; using Dino.Entities; using Xmpp; using Xmpp.Xep.Omemo; namespace Dino.Plugins.Omemo { public class OmemoEncryptor : Xep.Omemo.OmemoEncryptor { private Account account; private Store store; private TrustManager trust_manager; public override uint32 own_device_id { get { return store.local_registration_id; }} public OmemoEncryptor(Account account, TrustManager trust_manager, Store store) { this.account = account; this.trust_manager = trust_manager; this.store = store; } public override Xep.Omemo.EncryptionData encrypt_plaintext(string plaintext) throws GLib.Error { const uint KEY_SIZE = 16; const uint IV_SIZE = 12; //Create a key and use it to encrypt the message uint8[] key = new uint8[KEY_SIZE]; Plugin.get_context().randomize(key); uint8[] iv = new uint8[IV_SIZE]; Plugin.get_context().randomize(iv); uint8[] aes_encrypt_result = aes_encrypt(Cipher.AES_GCM_NOPADDING, key, iv, plaintext.data); uint8[] ciphertext = aes_encrypt_result[0:aes_encrypt_result.length - 16]; uint8[] tag = aes_encrypt_result[aes_encrypt_result.length - 16:aes_encrypt_result.length]; uint8[] keytag = new uint8[key.length + tag.length]; Memory.copy(keytag, key, key.length); Memory.copy((uint8*)keytag + key.length, tag, tag.length); var ret = new Xep.Omemo.EncryptionData(own_device_id); ret.ciphertext = ciphertext; ret.keytag = keytag; ret.iv = iv; return ret; } public EncryptState encrypt(MessageStanza message, Jid self_jid, Gee.List recipients, XmppStream stream) { EncryptState status = new EncryptState(); if (!Plugin.ensure_context()) return status; if (message.to == null) return status; try { EncryptionData enc_data = encrypt_plaintext(message.body); status = encrypt_key_to_recipients(enc_data, self_jid, recipients, stream); message.stanza.put_node(enc_data.get_encrypted_node()); Xep.ExplicitEncryption.add_encryption_tag_to_message(message, NS_URI, "OMEMO"); message.body = "[This message is OMEMO encrypted]"; status.encrypted = true; } catch (Error e) { warning(@"Signal error while encrypting message: $(e.message)\n"); message.body = "[OMEMO encryption failed]"; status.encrypted = false; } return status; } internal EncryptState encrypt_key_to_recipients(EncryptionData enc_data, Jid self_jid, Gee.List recipients, XmppStream stream) throws Error { EncryptState status = new EncryptState(); //Check we have the bundles and device lists needed to send the message if (!trust_manager.is_known_address(account, self_jid)) return status; status.own_list = true; status.own_devices = trust_manager.get_trusted_devices(account, self_jid).size; status.other_waiting_lists = 0; status.other_devices = 0; foreach (Jid recipient in recipients) { if (!trust_manager.is_known_address(account, recipient)) { status.other_waiting_lists++; } if (status.other_waiting_lists > 0) return status; status.other_devices += trust_manager.get_trusted_devices(account, recipient).size; } if (status.own_devices == 0 || status.other_devices == 0) return status; //Encrypt the key for each recipient's device individually foreach (Jid recipient in recipients) { EncryptionResult enc_res = encrypt_key_to_recipient(stream, enc_data, recipient); status.add_result(enc_res, false); } // Encrypt the key for each own device EncryptionResult enc_res = encrypt_key_to_recipient(stream, enc_data, self_jid); status.add_result(enc_res, true); return status; } public override EncryptionResult encrypt_key_to_recipient(XmppStream stream, Xep.Omemo.EncryptionData enc_data, Jid recipient) throws GLib.Error { var result = new EncryptionResult(); StreamModule module = stream.get_module(StreamModule.IDENTITY); foreach(int32 device_id in trust_manager.get_trusted_devices(account, recipient)) { if (module.is_ignored_device(recipient, device_id)) { result.lost++; continue; } try { encrypt_key(enc_data, recipient, device_id); result.success++; } catch (Error e) { if (e.code == ErrorCode.UNKNOWN) result.unknown++; else result.failure++; } } return result; } public override void encrypt_key(Xep.Omemo.EncryptionData encryption_data, Jid jid, int32 device_id) throws GLib.Error { Address address = new Address(jid.to_string(), device_id); SessionCipher cipher = store.create_session_cipher(address); CiphertextMessage device_key = cipher.encrypt(encryption_data.keytag); address.device_id = 0; debug("Created encrypted key for %s/%d", jid.to_string(), device_id); encryption_data.add_device_key(device_id, device_key.serialized, device_key.type == CiphertextType.PREKEY); } } }dino-0.3.0/plugins/omemo/src/logic/manager.vala0000644000000000000000000004777314202022370020113 0ustar rootrootusing Dino.Entities; using Signal; using Qlite; using Xmpp; using Gee; namespace Dino.Plugins.Omemo { public class Manager : StreamInteractionModule, Object { public static ModuleIdentity IDENTITY = new ModuleIdentity("omemo_manager"); public string id { get { return IDENTITY.id; } } private StreamInteractor stream_interactor; private Database db; private TrustManager trust_manager; private HashMap encryptors; private Map message_states = new HashMap(Entities.Message.hash_func, Entities.Message.equals_func); private class MessageState { public Entities.Message msg { get; private set; } public Xep.Omemo.EncryptState last_try { get; private set; } public int waiting_other_sessions { get; set; } public int waiting_own_sessions { get; set; } public bool waiting_own_devicelist { get; set; } public int waiting_other_devicelists { get; set; } public bool force_next_attempt { get; set; } public bool will_send_now { get; private set; } public bool active_send_attempt { get; set; } public MessageState(Entities.Message msg, Xep.Omemo.EncryptState last_try) { update_from_encrypt_status(msg, last_try); } public void update_from_encrypt_status(Entities.Message msg, Xep.Omemo.EncryptState new_try) { this.msg = msg; this.last_try = new_try; this.waiting_other_sessions = new_try.other_unknown; this.waiting_own_sessions = new_try.own_unknown; this.waiting_own_devicelist = !new_try.own_list; this.waiting_other_devicelists = new_try.other_waiting_lists; this.active_send_attempt = false; will_send_now = false; if (new_try.other_failure > 0 || (new_try.other_lost == new_try.other_devices && new_try.other_devices > 0)) { msg.marked = Entities.Message.Marked.WONTSEND; } else if (new_try.other_unknown > 0 || new_try.own_unknown > 0 || new_try.other_waiting_lists > 0 || !new_try.own_list || new_try.own_devices == 0) { msg.marked = Entities.Message.Marked.UNSENT; } else if (!new_try.encrypted) { msg.marked = Entities.Message.Marked.WONTSEND; } else { will_send_now = true; } } public bool should_retry_now() { return !waiting_own_devicelist && waiting_other_devicelists <= 0 && waiting_other_sessions <= 0 && waiting_own_sessions <= 0 && !active_send_attempt; } public string to_string() { return @"MessageState (msg=$(msg.stanza_id), send=$will_send_now, $last_try)"; } } private Manager(StreamInteractor stream_interactor, Database db, TrustManager trust_manager, HashMap encryptors) { this.stream_interactor = stream_interactor; this.db = db; this.trust_manager = trust_manager; this.encryptors = encryptors; stream_interactor.stream_negotiated.connect(on_stream_negotiated); stream_interactor.get_module(MessageProcessor.IDENTITY).pre_message_send.connect(on_pre_message_send); stream_interactor.get_module(RosterManager.IDENTITY).mutual_subscription.connect(on_mutual_subscription); } public void clear_device_list(Account account) { XmppStream? stream = stream_interactor.get_stream(account); if (stream == null) return; stream.get_module(StreamModule.IDENTITY).clear_device_list(stream); } private Gee.List get_occupants(Jid jid, Account account){ Gee.List occupants = new ArrayList(Jid.equals_bare_func); if(!stream_interactor.get_module(MucManager.IDENTITY).is_groupchat(jid, account)){ occupants.add(jid); } Gee.List? occupant_jids = stream_interactor.get_module(MucManager.IDENTITY).get_offline_members(jid, account); if(occupant_jids == null) { return occupants; } foreach (Jid occupant in occupant_jids) { if(!occupant.equals(account.bare_jid)){ occupants.add(occupant.bare_jid); } } return occupants; } private void on_pre_message_send(Entities.Message message, Xmpp.MessageStanza message_stanza, Conversation conversation) { if (message.encryption == Encryption.OMEMO) { if (message.type_ == Message.Type.GROUPCHAT_PM) { message.marked = Message.Marked.WONTSEND; return; } XmppStream? stream = stream_interactor.get_stream(conversation.account); if (stream == null) { message.marked = Entities.Message.Marked.UNSENT; return; } StreamModule? module_ = ((!)stream).get_module(StreamModule.IDENTITY); if (module_ == null) { message.marked = Entities.Message.Marked.UNSENT; return; } StreamModule module = (!)module_; //Get a list of everyone for whom the message should be encrypted Gee.List recipients; if (message_stanza.type_ == MessageStanza.TYPE_GROUPCHAT) { recipients = get_occupants((!)message.to.bare_jid, conversation.account); if (recipients.size == 0) { message.marked = Entities.Message.Marked.WONTSEND; return; } } else { recipients = new ArrayList(Jid.equals_bare_func); recipients.add(message_stanza.to); } //Attempt to encrypt the message Xep.Omemo.EncryptState enc_state = encryptors[conversation.account].encrypt(message_stanza, conversation.account.bare_jid, recipients, stream); MessageState state; lock (message_states) { if (message_states.has_key(message)) { state = message_states.get(message); state.update_from_encrypt_status(message, enc_state); if (state.will_send_now) { debug("sending message delayed: %s", state.to_string()); } } else { state = new MessageState(message, enc_state); message_states[message] = state; } if (state.will_send_now) { message_states.unset(message); } } //Encryption failed - need to fetch more information if (!state.will_send_now) { if (message.marked == Entities.Message.Marked.WONTSEND) { debug("retracting message %s", state.to_string()); message_states.unset(message); } else { debug("delaying message %s", state.to_string()); if (state.waiting_own_sessions > 0) { module.fetch_bundles((!)stream, conversation.account.bare_jid, trust_manager.get_trusted_devices(conversation.account, conversation.account.bare_jid)); } if (state.waiting_other_sessions > 0 && message.counterpart != null) { foreach(Jid jid in get_occupants(((!)message.counterpart).bare_jid, conversation.account)) { module.fetch_bundles((!)stream, jid, trust_manager.get_trusted_devices(conversation.account, jid)); } } if (state.waiting_other_devicelists > 0 && message.counterpart != null) { foreach(Jid jid in get_occupants(((!)message.counterpart).bare_jid, conversation.account)) { module.request_user_devicelist.begin((!)stream, jid); } } } } } } private void on_mutual_subscription(Account account, Jid jid) { XmppStream? stream = stream_interactor.get_stream(account); if(stream == null) return; stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).request_user_devicelist.begin((!)stream, jid); } private void on_stream_negotiated(Account account, XmppStream stream) { StreamModule module = stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY); if (module != null) { module.request_user_devicelist.begin(stream, account.bare_jid); module.device_list_loaded.connect((jid, devices) => on_device_list_loaded(account, jid, devices)); module.bundle_fetched.connect((jid, device_id, bundle) => on_bundle_fetched(account, jid, device_id, bundle)); module.bundle_fetch_failed.connect((jid) => continue_message_sending(account, jid)); } initialize_store.begin(account); } private void on_device_list_loaded(Account account, Jid jid, ArrayList device_list) { debug("received device list for %s from %s", account.bare_jid.to_string(), jid.to_string()); XmppStream? stream = stream_interactor.get_stream(account); if (stream == null) { return; } StreamModule? module = ((!)stream).get_module(StreamModule.IDENTITY); if (module == null) { return; } int identity_id = db.identity.get_id(account.id); if (identity_id < 0) return; //Update meta database db.identity_meta.insert_device_list(identity_id, jid.bare_jid.to_string(), device_list); //Fetch the bundle for each new device int inc = 0; foreach (Row row in db.identity_meta.get_unknown_devices(identity_id, jid.bare_jid.to_string())) { try { module.fetch_bundle(stream, new Jid(row[db.identity_meta.address_name]), row[db.identity_meta.device_id], false); inc++; } catch (InvalidJidError e) { warning("Ignoring device with invalid Jid: %s", e.message); } } if (inc > 0) { debug("new bundles %i/%i for %s", inc, device_list.size, jid.to_string()); } //Create an entry for the jid in the account table if one does not exist already if (db.trust.select().with(db.trust.identity_id, "=", identity_id).with(db.trust.address_name, "=", jid.bare_jid.to_string()).count() == 0) { db.trust.insert().value(db.trust.identity_id, identity_id).value(db.trust.address_name, jid.bare_jid.to_string()).value(db.trust.blind_trust, true).perform(); } //Get all messages that needed the devicelist and determine if we can now send them HashSet send_now = new HashSet(); lock (message_states) { foreach (Entities.Message msg in message_states.keys) { if (!msg.account.equals(account)) continue; Gee.List occupants = get_occupants(msg.counterpart.bare_jid, account); MessageState state = message_states[msg]; if (account.bare_jid.equals(jid)) { state.waiting_own_devicelist = false; } else if (msg.counterpart != null && occupants.contains(jid)) { state.waiting_other_devicelists--; } if (state.should_retry_now()) { send_now.add(msg); state.active_send_attempt = true; } } } foreach (Entities.Message msg in send_now) { if (msg.counterpart == null) continue; Entities.Conversation? conv = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(((!)msg.counterpart), account); if (conv == null) continue; stream_interactor.get_module(MessageProcessor.IDENTITY).send_xmpp_message(msg, (!)conv, true); } } private void on_bundle_fetched(Account account, Jid jid, int32 device_id, Bundle bundle) { int identity_id = db.identity.get_id(account.id); if (identity_id < 0) return; bool blind_trust = db.trust.get_blind_trust(identity_id, jid.bare_jid.to_string(), true); //If we don't blindly trust new devices and we haven't seen this key before then don't trust it bool untrust = !(blind_trust || db.identity_meta.with_address(identity_id, jid.bare_jid.to_string()) .with(db.identity_meta.device_id, "=", device_id) .with(db.identity_meta.identity_key_public_base64, "=", Base64.encode(bundle.identity_key.serialize())) .single().row().is_present()); //Get trust information from the database if the device id is known Row device = db.identity_meta.get_device(identity_id, jid.bare_jid.to_string(), device_id); TrustLevel trusted = TrustLevel.UNKNOWN; if (device != null) { trusted = (TrustLevel) device[db.identity_meta.trust_level]; } if(untrust) { trusted = TrustLevel.UNKNOWN; } else if (blind_trust && trusted == TrustLevel.UNKNOWN) { trusted = TrustLevel.TRUSTED; } //Update the database with the appropriate trust information db.identity_meta.insert_device_bundle(identity_id, jid.bare_jid.to_string(), device_id, bundle, trusted); if (should_start_session(account, jid)) { XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) { StreamModule? module = ((!)stream).get_module(StreamModule.IDENTITY); if (module != null) { module.start_session(stream, jid, device_id, bundle); } } } continue_message_sending(account, jid); } private bool should_start_session(Account account, Jid jid) { lock (message_states) { foreach (Entities.Message msg in message_states.keys) { if (!msg.account.equals(account)) continue; Gee.List occupants = get_occupants(msg.counterpart.bare_jid, account); if (account.bare_jid.equals(jid) || (msg.counterpart != null && (msg.counterpart.equals_bare(jid) || occupants.contains(jid)))) { return true; } } } return false; } private void continue_message_sending(Account account, Jid jid) { //Get all messages waiting and determine if they can now be sent HashSet send_now = new HashSet(); lock (message_states) { foreach (Entities.Message msg in message_states.keys) { if (!msg.account.equals(account)) continue; Gee.List occupants = get_occupants(msg.counterpart.bare_jid, account); MessageState state = message_states[msg]; if (account.bare_jid.equals(jid)) { state.waiting_own_sessions--; } else if (msg.counterpart != null && (msg.counterpart.equals_bare(jid) || occupants.contains(jid))) { state.waiting_other_sessions--; } if (state.should_retry_now()){ send_now.add(msg); state.active_send_attempt = true; } } } foreach (Entities.Message msg in send_now) { if (msg.counterpart == null) continue; Entities.Conversation? conv = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation((!)msg.counterpart, account); if (conv == null) continue; stream_interactor.get_module(MessageProcessor.IDENTITY).send_xmpp_message(msg, (!)conv, true); } } private async void initialize_store(Account account) { // If the account is not yet persisted, wait for that and then continue - without identity.account_id the entry isn't worth much. if (account.id == -1) { account.notify["id"].connect(() => initialize_store.callback()); yield; } StreamModule? module = stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY); if (module == null) return; Store store = module.store; Qlite.Row? row = db.identity.row_with(db.identity.account_id, account.id).inner; int identity_id = -1; bool publish_identity = false; if (row == null) { // OMEMO not yet initialized, starting with empty base publish_identity = true; try { store.identity_key_store.local_registration_id = Random.int_range(1, int32.MAX); Signal.ECKeyPair key_pair = Plugin.get_context().generate_key_pair(); store.identity_key_store.identity_key_private = new Bytes(key_pair.private.serialize()); store.identity_key_store.identity_key_public = new Bytes(key_pair.public.serialize()); identity_id = (int) db.identity.upsert() .value(db.identity.account_id, account.id, true) .value(db.identity.device_id, (int) store.local_registration_id) .value(db.identity.identity_key_private_base64, Base64.encode(store.identity_key_store.identity_key_private.get_data())) .value(db.identity.identity_key_public_base64, Base64.encode(store.identity_key_store.identity_key_public.get_data())) .perform(); } catch (Error e) { // Ignore error } } else { store.identity_key_store.local_registration_id = ((!)row)[db.identity.device_id]; store.identity_key_store.identity_key_private = new Bytes(Base64.decode(((!)row)[db.identity.identity_key_private_base64])); store.identity_key_store.identity_key_public = new Bytes(Base64.decode(((!)row)[db.identity.identity_key_public_base64])); identity_id = ((!)row)[db.identity.id]; } if (identity_id >= 0) { store.signed_pre_key_store = new BackedSignedPreKeyStore(db, identity_id); store.pre_key_store = new BackedPreKeyStore(db, identity_id); store.session_store = new BackedSessionStore(db, identity_id); } else { warning("store for %s is not persisted!", account.bare_jid.to_string()); } // Generated new device ID, ensure this gets added to the devicelist XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) { module.request_user_devicelist.begin((!)stream, account.bare_jid); } } public async bool ensure_get_keys_for_conversation(Conversation conversation) { if (stream_interactor.get_module(MucManager.IDENTITY).is_private_room(conversation.account, conversation.counterpart)) { foreach (Jid offline_member in stream_interactor.get_module(MucManager.IDENTITY).get_offline_members(conversation.counterpart, conversation.account)) { bool ok = yield ensure_get_keys_for_jid(conversation.account, offline_member); if (!ok) { return false; } } return true; } return yield ensure_get_keys_for_jid(conversation.account, conversation.counterpart.bare_jid); } public async bool ensure_get_keys_for_jid(Account account, Jid jid) { if (trust_manager.is_known_address(account, jid)) return true; XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) { var device_list = yield stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).request_user_devicelist(stream, jid); return device_list.size > 0; } return true; // TODO wait for stream? } public static void start(StreamInteractor stream_interactor, Database db, TrustManager trust_manager, HashMap encryptors) { Manager m = new Manager(stream_interactor, db, trust_manager, encryptors); stream_interactor.add_module(m); } } } dino-0.3.0/plugins/omemo/src/logic/pre_key_store.vala0000644000000000000000000000256114202022370021335 0ustar rootrootusing Signal; using Qlite; namespace Dino.Plugins.Omemo { private class BackedPreKeyStore : SimplePreKeyStore { private Database db; private int identity_id; public BackedPreKeyStore(Database db, int identity_id) { this.db = db; this.identity_id = identity_id; init(); } private void init() { try { foreach (Row row in db.pre_key.select().with(db.pre_key.identity_id, "=", identity_id)) { store_pre_key(row[db.pre_key.pre_key_id], Base64.decode(row[db.pre_key.record_base64])); } } catch (Error e) { warning("Error while initializing pre key store: %s", e.message); } pre_key_stored.connect(on_pre_key_stored); pre_key_deleted.connect(on_pre_key_deleted); } public void on_pre_key_stored(PreKeyStore.Key key) { db.pre_key.upsert() .value(db.pre_key.identity_id, identity_id, true) .value(db.pre_key.pre_key_id, (int) key.key_id, true) .value(db.pre_key.record_base64, Base64.encode(key.record)) .perform(); } public void on_pre_key_deleted(PreKeyStore.Key key) { db.pre_key.delete() .with(db.pre_key.identity_id, "=", identity_id) .with(db.pre_key.pre_key_id, "=", (int) key.key_id) .perform(); } } } dino-0.3.0/plugins/omemo/src/logic/session_store.vala0000644000000000000000000000320114202022370021352 0ustar rootrootusing Signal; using Qlite; namespace Dino.Plugins.Omemo { private class BackedSessionStore : SimpleSessionStore { private Database db; private int identity_id; public BackedSessionStore(Database db, int identity_id) { this.db = db; this.identity_id = identity_id; init(); } private void init() { try { foreach (Row row in db.session.select().with(db.session.identity_id, "=", identity_id)) { Address addr = new Address(row[db.session.address_name], row[db.session.device_id]); store_session(addr, Base64.decode(row[db.session.record_base64])); addr.device_id = 0; } } catch (Error e) { print("Error while initializing session store: %s", e.message); } session_stored.connect(on_session_stored); session_removed.connect(on_session_deleted); } public void on_session_stored(SessionStore.Session session) { db.session.upsert() .value(db.session.identity_id, identity_id, true) .value(db.session.address_name, session.name, true) .value(db.session.device_id, session.device_id, true) .value(db.session.record_base64, Base64.encode(session.record)) .perform(); } public void on_session_deleted(SessionStore.Session session) { db.session.delete() .with(db.session.identity_id, "=", identity_id) .with(db.session.address_name, "=", session.name) .with(db.session.device_id, "=", session.device_id) .perform(); } } } dino-0.3.0/plugins/omemo/src/logic/signed_pre_key_store.vala0000644000000000000000000000304714202022370022666 0ustar rootrootusing Qlite; using Signal; namespace Dino.Plugins.Omemo { private class BackedSignedPreKeyStore : SimpleSignedPreKeyStore { private Database db; private int identity_id; public BackedSignedPreKeyStore(Database db, int identity_id) { this.db = db; this.identity_id = identity_id; init(); } private void init() { try { foreach (Row row in db.signed_pre_key.select().with(db.signed_pre_key.identity_id, "=", identity_id)) { store_signed_pre_key(row[db.signed_pre_key.signed_pre_key_id], Base64.decode(row[db.signed_pre_key.record_base64])); } } catch (Error e) { print("Error while initializing signed pre key store: %s", e.message); } signed_pre_key_stored.connect(on_signed_pre_key_stored); signed_pre_key_deleted.connect(on_signed_pre_key_deleted); } public void on_signed_pre_key_stored(SignedPreKeyStore.Key key) { db.signed_pre_key.upsert() .value(db.signed_pre_key.identity_id, identity_id, true) .value(db.signed_pre_key.signed_pre_key_id, (int) key.key_id, true) .value(db.signed_pre_key.record_base64, Base64.encode(key.record)) .perform(); } public void on_signed_pre_key_deleted(SignedPreKeyStore.Key key) { db.signed_pre_key.delete() .with(db.signed_pre_key.identity_id, "=", identity_id) .with(db.signed_pre_key.signed_pre_key_id, "=", (int) key.key_id) .perform(); } } } dino-0.3.0/plugins/omemo/src/logic/trust_manager.vala0000644000000000000000000001471214202022370021337 0ustar rootrootusing Dino.Entities; using Gee; using Xmpp; using Signal; using Qlite; namespace Dino.Plugins.Omemo { public class TrustManager { public signal void bad_message_state_updated(Account account, Jid jid, int device_id); private StreamInteractor stream_interactor; private Database db; private TagMessageListener tag_message_listener; public HashMap message_device_id_map = new HashMap(Message.hash_func, Message.equals_func); public TrustManager(StreamInteractor stream_interactor, Database db) { this.stream_interactor = stream_interactor; this.db = db; tag_message_listener = new TagMessageListener(stream_interactor, this, db, message_device_id_map); stream_interactor.get_module(MessageProcessor.IDENTITY).received_pipeline.connect(tag_message_listener); } public void set_blind_trust(Account account, Jid jid, bool blind_trust) { int identity_id = db.identity.get_id(account.id); if (identity_id < 0) return; db.trust.update() .with(db.trust.identity_id, "=", identity_id) .with(db.trust.address_name, "=", jid.bare_jid.to_string()) .set(db.trust.blind_trust, blind_trust).perform(); } public void set_device_trust(Account account, Jid jid, int device_id, TrustLevel trust_level) { int identity_id = db.identity.get_id(account.id); db.identity_meta.update() .with(db.identity_meta.identity_id, "=", identity_id) .with(db.identity_meta.address_name, "=", jid.bare_jid.to_string()) .with(db.identity_meta.device_id, "=", device_id) .set(db.identity_meta.trust_level, trust_level).perform(); // Hide messages from untrusted or unknown devices string selection = null; string[] selection_args = {}; var app_db = Application.get_default().db; foreach (Row row in db.content_item_meta.with_device(identity_id, jid.bare_jid.to_string(), device_id).with(db.content_item_meta.trusted_when_received, "=", false)) { if (selection == null) { selection = @"$(app_db.content_item.id) = ?"; } else { selection += @" OR $(app_db.content_item.id) = ?"; } selection_args += row[db.content_item_meta.content_item_id].to_string(); } if (selection != null) { app_db.content_item.update() .set(app_db.content_item.hide, trust_level == TrustLevel.UNTRUSTED || trust_level == TrustLevel.UNKNOWN) .where(selection, selection_args) .perform(); } if (trust_level == TrustLevel.TRUSTED) { db.identity_meta.update_last_message_untrusted(identity_id, device_id, null); bad_message_state_updated(account, jid, device_id); } } public bool is_known_address(Account account, Jid jid) { int identity_id = db.identity.get_id(account.id); if (identity_id < 0) return false; return db.identity_meta.with_address(identity_id, jid.to_string()).with(db.identity_meta.last_active, ">", 0).count() > 0; } public Gee.List get_trusted_devices(Account account, Jid jid) { Gee.List devices = new ArrayList(); int identity_id = db.identity.get_id(account.id); if (identity_id < 0) return devices; foreach (Row device in db.identity_meta.get_trusted_devices(identity_id, jid.bare_jid.to_string())) { if(device[db.identity_meta.trust_level] != TrustLevel.UNKNOWN || device[db.identity_meta.identity_key_public_base64] == null) devices.add(device[db.identity_meta.device_id]); } return devices; } private class TagMessageListener : MessageListener { public string[] after_actions_const = new string[]{ "STORE" }; public override string action_group { get { return "DECRYPT_TAG"; } } public override string[] after_actions { get { return after_actions_const; } } private StreamInteractor stream_interactor; private TrustManager trust_manager; private Database db; private HashMap message_device_id_map; public TagMessageListener(StreamInteractor stream_interactor, TrustManager trust_manager, Database db, HashMap message_device_id_map) { this.stream_interactor = stream_interactor; this.trust_manager = trust_manager; this.db = db; this.message_device_id_map = message_device_id_map; } public override async bool run(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) { int device_id = 0; if (message_device_id_map.has_key(message)) { device_id = message_device_id_map[message]; message_device_id_map.unset(message); } // TODO: Handling of files ContentItem? content_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_item(conversation, 1, message.id); if (content_item != null && device_id != 0) { Jid jid = content_item.jid; if (conversation.type_ == Conversation.Type.GROUPCHAT) { jid = message.real_jid; } int identity_id = db.identity.get_id(conversation.account.id); TrustLevel trust_level = (TrustLevel) db.identity_meta.get_device(identity_id, jid.bare_jid.to_string(), device_id)[db.identity_meta.trust_level]; if (trust_level == TrustLevel.UNTRUSTED || trust_level == TrustLevel.UNKNOWN) { stream_interactor.get_module(ContentItemStore.IDENTITY).set_item_hide(content_item, true); db.identity_meta.update_last_message_untrusted(identity_id, device_id, message.time); trust_manager.bad_message_state_updated(conversation.account, jid, device_id); } db.content_item_meta.insert() .value(db.content_item_meta.content_item_id, content_item.id) .value(db.content_item_meta.identity_id, identity_id) .value(db.content_item_meta.address_name, jid.bare_jid.to_string()) .value(db.content_item_meta.device_id, device_id) .value(db.content_item_meta.trusted_when_received, trust_level != TrustLevel.UNTRUSTED) .perform(); } return false; } } } } dino-0.3.0/plugins/omemo/src/plugin.vala0000644000000000000000000001220214202022370016655 0ustar rootrootusing Gee; using Dino.Entities; extern const string GETTEXT_PACKAGE; extern const string LOCALE_INSTALL_DIR; namespace Dino.Plugins.Omemo { public class Plugin : RootInterface, Object { public const bool DEBUG = false; private static Signal.Context? _context; public static Signal.Context get_context() { assert(_context != null); return (!)_context; } public static bool ensure_context() { lock(_context) { try { if (_context == null) { _context = new Signal.Context(DEBUG); } return true; } catch (Error e) { warning("Error initializing Signal Context %s", e.message); return false; } } } public Dino.Application app; public Database db; public EncryptionListEntry list_entry; public AccountSettingsEntry settings_entry; public ContactDetailsProvider contact_details_provider; public DeviceNotificationPopulator device_notification_populator; public OwnNotifications own_notifications; public TrustManager trust_manager; public HashMap decryptors = new HashMap(Account.hash_func, Account.equals_func); public HashMap encryptors = new HashMap(Account.hash_func, Account.equals_func); public void registered(Dino.Application app) { ensure_context(); this.app = app; this.db = new Database(Path.build_filename(Application.get_storage_dir(), "omemo.db")); this.list_entry = new EncryptionListEntry(this); this.settings_entry = new AccountSettingsEntry(this); this.contact_details_provider = new ContactDetailsProvider(this); this.device_notification_populator = new DeviceNotificationPopulator(this, this.app.stream_interactor); this.trust_manager = new TrustManager(this.app.stream_interactor, this.db); this.app.plugin_registry.register_encryption_list_entry(list_entry); this.app.plugin_registry.register_account_settings_entry(settings_entry); this.app.plugin_registry.register_contact_details_entry(contact_details_provider); this.app.plugin_registry.register_notification_populator(device_notification_populator); this.app.plugin_registry.register_conversation_addition_populator(new BadMessagesPopulator(this.app.stream_interactor, this)); this.app.plugin_registry.register_call_entryption_entry(DtlsSrtpVerificationDraft.NS_URI, new CallEncryptionEntry(db)); this.app.stream_interactor.module_manager.initialize_account_modules.connect((account, list) => { Signal.Store signal_store = Plugin.get_context().create_store(); list.add(new StreamModule(signal_store)); decryptors[account] = new OmemoDecryptor(account, app.stream_interactor, trust_manager, db, signal_store); list.add(decryptors[account]); encryptors[account] = new OmemoEncryptor(account, trust_manager,signal_store); list.add(encryptors[account]); list.add(new JetOmemo.Module()); list.add(new DtlsSrtpVerificationDraft.StreamModule()); this.own_notifications = new OwnNotifications(this, this.app.stream_interactor, account); }); app.stream_interactor.get_module(MessageProcessor.IDENTITY).received_pipeline.connect(new DecryptMessageListener(decryptors)); app.stream_interactor.get_module(FileManager.IDENTITY).add_file_decryptor(new OmemoFileDecryptor()); app.stream_interactor.get_module(FileManager.IDENTITY).add_file_encryptor(new OmemoFileEncryptor()); JingleFileHelperRegistry.instance.add_encryption_helper(Encryption.OMEMO, new JetOmemo.EncryptionHelper(app.stream_interactor)); Manager.start(this.app.stream_interactor, db, trust_manager, encryptors); SimpleAction own_keys_action = new SimpleAction("own-keys", VariantType.INT32); own_keys_action.activate.connect((variant) => { foreach(Dino.Entities.Account account in this.app.stream_interactor.get_accounts()) { if(account.id == variant.get_int32()) { ContactDetailsDialog dialog = new ContactDetailsDialog(this, account, account.bare_jid); dialog.set_transient_for(((Gtk.Application) this.app).get_active_window()); dialog.present(); } } }); this.app.add_action(own_keys_action); string locales_dir; if (app.search_path_generator != null) { locales_dir = ((!)app.search_path_generator).get_locale_path(GETTEXT_PACKAGE, LOCALE_INSTALL_DIR); } else { locales_dir = LOCALE_INSTALL_DIR; } internationalize(GETTEXT_PACKAGE, locales_dir); } public void shutdown() { // Nothing to do } public bool has_new_devices(Account account, Xmpp.Jid jid) { int identity_id = db.identity.get_id(account.id); if (identity_id < 0) return false; return db.identity_meta.get_new_devices(identity_id, jid.bare_jid.to_string()).count() > 0; } } } dino-0.3.0/plugins/omemo/src/protocol/0000755000000000000000000000000014202022370016356 5ustar rootrootdino-0.3.0/plugins/omemo/src/protocol/bundle.vala0000644000000000000000000000515514202022370020502 0ustar rootrootusing Gee; using Signal; using Xmpp; namespace Dino.Plugins.Omemo { public class Bundle { public StanzaNode? node; public Bundle(StanzaNode? node) { this.node = node; assert(Plugin.ensure_context()); } public int32 signed_pre_key_id { owned get { if (node == null) return -1; string? id = ((!)node).get_deep_attribute("signedPreKeyPublic", "signedPreKeyId"); if (id == null) return -1; return int.parse((!)id); }} public ECPublicKey? signed_pre_key { owned get { if (node == null) return null; string? key = ((!)node).get_deep_string_content("signedPreKeyPublic"); if (key == null) return null; try { return Plugin.get_context().decode_public_key(Base64.decode((!)key)); } catch (Error e) { return null; } }} public uint8[]? signed_pre_key_signature { owned get { if (node == null) return null; string? sig = ((!)node).get_deep_string_content("signedPreKeySignature"); if (sig == null) return null; return Base64.decode((!)sig); }} public ECPublicKey? identity_key { owned get { if (node == null) return null; string? key = ((!)node).get_deep_string_content("identityKey"); if (key == null) return null; try { return Plugin.get_context().decode_public_key(Base64.decode((!)key)); } catch (Error e) { return null; } }} public ArrayList pre_keys { owned get { ArrayList list = new ArrayList(); if (node == null || ((!)node).get_subnode("prekeys") == null) return list; ((!)node).get_deep_subnodes("prekeys", "preKeyPublic") .filter((node) => ((!)node).get_attribute("preKeyId") != null) .map(PreKey.create) .foreach((key) => list.add(key)); return list; }} public class PreKey { private StanzaNode node; public static PreKey create(owned StanzaNode node) { return new PreKey(node); } public PreKey(StanzaNode node) { this.node = node; } public int32 key_id { owned get { return int.parse(node.get_attribute("preKeyId") ?? "-1"); }} public ECPublicKey? key { owned get { string? key = node.get_string_content(); if (key == null) return null; try { return Plugin.get_context().decode_public_key(Base64.decode((!)key)); } catch (Error e) { return null; } }} } } } dino-0.3.0/plugins/omemo/src/protocol/message_flag.vala0000644000000000000000000000066014202022370021642 0ustar rootrootusing Xmpp; namespace Dino.Plugins.Omemo { public class MessageFlag : Xmpp.MessageFlag { public const string id = "omemo"; public bool decrypted = false; public static MessageFlag? get_flag(MessageStanza message) { return (MessageFlag) message.get_flag(NS_URI, id); } public override string get_ns() { return NS_URI; } public override string get_id() { return id; } } }dino-0.3.0/plugins/omemo/src/protocol/stream_module.vala0000644000000000000000000003643514202022370022076 0ustar rootrootusing Gee; using Xmpp; using Xmpp.Xep; using Signal; namespace Dino.Plugins.Omemo { internal const string NS_URI = "eu.siacs.conversations.axolotl"; private const string NODE_DEVICELIST = NS_URI + ".devicelist"; private const string NODE_BUNDLES = NS_URI + ".bundles"; private const string NODE_VERIFICATION = NS_URI + ".verification"; private const int NUM_KEYS_TO_PUBLISH = 100; public class StreamModule : XmppStreamModule { public static Xmpp.ModuleIdentity IDENTITY = new Xmpp.ModuleIdentity(NS_URI, "omemo_module"); private static TimeSpan IGNORE_TIME = TimeSpan.MINUTE; public Store store { public get; private set; } private ConcurrentSet active_bundle_requests = new ConcurrentSet(); private HashMap>> active_devicelist_requests = new HashMap>>(Jid.hash_func, Jid.equals_func); private Map device_ignore_time = new HashMap(); public signal void device_list_loaded(Jid jid, ArrayList devices); public signal void bundle_fetched(Jid jid, int device_id, Bundle bundle); public signal void bundle_fetch_failed(Jid jid, int device_id); public StreamModule(Store store) { this.store = store; } public override void attach(XmppStream stream) { stream.get_module(Pubsub.Module.IDENTITY).add_filtered_notification(stream, NODE_DEVICELIST, true, (stream, jid, id, node) => parse_device_list(stream, jid, id, node), null); } public override void detach(XmppStream stream) { stream.get_module(Pubsub.Module.IDENTITY).remove_filtered_notification(stream, NODE_DEVICELIST); } public async ArrayList request_user_devicelist(XmppStream stream, Jid jid) { var future = active_devicelist_requests[jid]; if (future == null) { var promise = new Promise?>(); future = promise.future; active_devicelist_requests[jid] = future; stream.get_module(Pubsub.Module.IDENTITY).request(stream, jid, NODE_DEVICELIST, (stream, jid, id, node) => { ArrayList device_list = parse_device_list(stream, jid, id, node); promise.set_value(device_list); active_devicelist_requests.unset(jid); }); } try { ArrayList device_list = yield future.wait_async(); return device_list; } catch (FutureError error) { warning("Future error when waiting for device list: %s", error.message); return new ArrayList(); } } public ArrayList parse_device_list(XmppStream stream, Jid jid, string? id, StanzaNode? node_) { ArrayList device_list = new ArrayList(); StanzaNode node = node_ ?? new StanzaNode.build("list", NS_URI).add_self_xmlns(); Jid? my_jid = stream.get_flag(Bind.Flag.IDENTITY).my_jid; if (my_jid == null) return device_list; if (jid.equals_bare(my_jid) && store.local_registration_id != 0) { bool am_on_devicelist = false; foreach (StanzaNode device_node in node.get_subnodes("device")) { int device_id = device_node.get_attribute_int("id"); if (store.local_registration_id == device_id) { am_on_devicelist = true; } } if (!am_on_devicelist) { debug("Not on device list, adding id"); node.put_node(new StanzaNode.build("device", NS_URI).put_attribute("id", store.local_registration_id.to_string())); stream.get_module(Pubsub.Module.IDENTITY).publish.begin(stream, jid, NODE_DEVICELIST, id, node); } publish_bundles_if_needed(stream, jid); } foreach (StanzaNode device_node in node.get_subnodes("device")) { device_list.add(device_node.get_attribute_int("id")); } device_list_loaded(jid, device_list); return device_list; } public void fetch_bundles(XmppStream stream, Jid jid, Gee.List devices) { Address address = new Address(jid.bare_jid.to_string(), 0); foreach(int32 device_id in devices) { if (!is_ignored_device(jid, device_id)) { address.device_id = device_id; try { if (!store.contains_session(address)) { fetch_bundle(stream, jid, device_id); } } catch (Error e) { // Ignore } } } address.device_id = 0; // TODO: Hack to have address obj live longer } public void fetch_bundle(XmppStream stream, Jid jid, int device_id, bool ignore_if_non_present = true) { if (active_bundle_requests.add(jid.bare_jid.to_string() + @":$device_id")) { debug("Asking for bundle for %s/%d", jid.bare_jid.to_string(), device_id); stream.get_module(Pubsub.Module.IDENTITY).request(stream, jid.bare_jid, @"$NODE_BUNDLES:$device_id", (stream, jid, id, node) => { on_other_bundle_result(stream, jid, device_id, id, node, ignore_if_non_present); }); } } public void ignore_device(Jid jid, int32 device_id) { if (device_id <= 0) return; lock (device_ignore_time) { device_ignore_time[jid.bare_jid.to_string() + @":$device_id"] = new DateTime.now_utc(); } } public void unignore_device(Jid jid, int32 device_id) { if (device_id <= 0) return; lock (device_ignore_time) { device_ignore_time.unset(jid.bare_jid.to_string() + @":$device_id"); } } public bool is_ignored_device(Jid jid, int32 device_id) { if (device_id <= 0) return true; lock (device_ignore_time) { string id = jid.bare_jid.to_string() + @":$device_id"; if (device_ignore_time.has_key(id)) { return new DateTime.now_utc().difference(device_ignore_time[id]) < IGNORE_TIME; } } return false; } public void clear_device_list(XmppStream stream) { stream.get_module(Pubsub.Module.IDENTITY).delete_node(stream, null, NODE_DEVICELIST); } private void on_other_bundle_result(XmppStream stream, Jid jid, int device_id, string? id, StanzaNode? node, bool ignore_if_non_present) { if (node == null) { // Device not registered, shouldn't exist if (ignore_if_non_present) { debug("Ignoring device %s/%d: No bundle", jid.bare_jid.to_string(), device_id); stream.get_module(IDENTITY).ignore_device(jid, device_id); } bundle_fetch_failed(jid, device_id); } else { Bundle bundle = new Bundle(node); stream.get_module(IDENTITY).unignore_device(jid, device_id); debug("Received bundle for %s/%d: %s", jid.bare_jid.to_string(), device_id, Base64.encode(bundle.identity_key.serialize())); bundle_fetched(jid, device_id, bundle); } stream.get_module(IDENTITY).active_bundle_requests.remove(jid.bare_jid.to_string() + @":$device_id"); } public bool start_session(XmppStream stream, Jid jid, int32 device_id, Bundle bundle) { bool fail = false; int32 signed_pre_key_id = bundle.signed_pre_key_id; ECPublicKey? signed_pre_key = bundle.signed_pre_key; uint8[] signed_pre_key_signature = bundle.signed_pre_key_signature; ECPublicKey? identity_key = bundle.identity_key; ArrayList pre_keys = bundle.pre_keys; if (signed_pre_key_id < 0 || signed_pre_key == null || identity_key == null || pre_keys.size == 0) { fail = true; } else { int pre_key_idx = Random.int_range(0, pre_keys.size); int32 pre_key_id = pre_keys[pre_key_idx].key_id; ECPublicKey? pre_key = pre_keys[pre_key_idx].key; if (pre_key_id < 0 || pre_key == null) { fail = true; } else { Address address = new Address(jid.bare_jid.to_string(), device_id); try { if (store.contains_session(address)) { return false; } debug("Starting new session for encryption with %s/%d", jid.bare_jid.to_string(), device_id); SessionBuilder builder = store.create_session_builder(address); builder.process_pre_key_bundle(create_pre_key_bundle(device_id, device_id, pre_key_id, pre_key, signed_pre_key_id, signed_pre_key, signed_pre_key_signature, identity_key)); } catch (Error e) { debug("Can't create session with %s/%d: %s", jid.bare_jid.to_string(), device_id, e.message); fail = true; } address.device_id = 0; // TODO: Hack to have address obj live longer } } if (fail) { debug("Ignoring device %s/%d: Bad bundle: %s", jid.bare_jid.to_string(), device_id, bundle.node.to_string()); stream.get_module(IDENTITY).ignore_device(jid, device_id); } return true; } public void publish_bundles_if_needed(XmppStream stream, Jid jid) { if (active_bundle_requests.add(jid.bare_jid.to_string() + @":$(store.local_registration_id)")) { stream.get_module(Pubsub.Module.IDENTITY).request(stream, jid, @"$NODE_BUNDLES:$(store.local_registration_id)", on_self_bundle_result); } } private void on_self_bundle_result(XmppStream stream, Jid jid, string? id, StanzaNode? node) { if (!Plugin.ensure_context()) return; Map keys = new HashMap(); ECPublicKey? identity_key = null; int32 signed_pre_key_id = -1; ECPublicKey? signed_pre_key = null; SignedPreKeyRecord? signed_pre_key_record = null; bool changed = false; if (node == null) { identity_key = store.identity_key_pair.public; changed = true; } else { Bundle bundle = new Bundle(node); foreach (Bundle.PreKey prekey in bundle.pre_keys) { ECPublicKey? key = prekey.key; if (key != null) { keys[prekey.key_id] = (!)key; } } identity_key = bundle.identity_key; signed_pre_key_id = bundle.signed_pre_key_id; signed_pre_key = bundle.signed_pre_key; } try { // Validate IdentityKey if (identity_key == null || store.identity_key_pair.public.compare((!)identity_key) != 0) { changed = true; } IdentityKeyPair identity_key_pair = store.identity_key_pair; // Validate signedPreKeyRecord + ID if (signed_pre_key == null || signed_pre_key_id == -1 || !store.contains_signed_pre_key(signed_pre_key_id) || store.load_signed_pre_key(signed_pre_key_id).key_pair.public.compare((!)signed_pre_key) != 0) { signed_pre_key_id = Random.int_range(1, int32.MAX); // TODO: No random, use ordered number signed_pre_key_record = Plugin.get_context().generate_signed_pre_key(identity_key_pair, signed_pre_key_id); store.store_signed_pre_key((!)signed_pre_key_record); changed = true; } else { signed_pre_key_record = store.load_signed_pre_key(signed_pre_key_id); } // Validate PreKeys Set pre_key_records = new HashSet(); foreach (var entry in keys.entries) { if (store.contains_pre_key(entry.key)) { PreKeyRecord record = store.load_pre_key(entry.key); if (record.key_pair.public.compare(entry.value) == 0) { pre_key_records.add(record); } } } int new_keys = NUM_KEYS_TO_PUBLISH - pre_key_records.size; if (new_keys > 0) { int32 next_id = Random.int_range(1, int32.MAX); // TODO: No random, use ordered number Set new_records = Plugin.get_context().generate_pre_keys((uint)next_id, (uint)new_keys); pre_key_records.add_all(new_records); foreach (PreKeyRecord record in new_records) { store.store_pre_key(record); } changed = true; } if (changed) { publish_bundles.begin(stream, (!)signed_pre_key_record, identity_key_pair, pre_key_records, (int32) store.local_registration_id); } } catch (Error e) { warning(@"Unexpected error while publishing bundle: $(e.message)\n"); } stream.get_module(IDENTITY).active_bundle_requests.remove(jid.bare_jid.to_string() + @":$(store.local_registration_id)"); } public async void publish_bundles(XmppStream stream, SignedPreKeyRecord signed_pre_key_record, IdentityKeyPair identity_key_pair, Set pre_key_records, int32 device_id) throws Error { ECKeyPair tmp; StanzaNode bundle = new StanzaNode.build("bundle", NS_URI) .add_self_xmlns() .put_node(new StanzaNode.build("signedPreKeyPublic", NS_URI) .put_attribute("signedPreKeyId", signed_pre_key_record.id.to_string()) .put_node(new StanzaNode.text(Base64.encode((tmp = signed_pre_key_record.key_pair).public.serialize())))) .put_node(new StanzaNode.build("signedPreKeySignature", NS_URI) .put_node(new StanzaNode.text(Base64.encode(signed_pre_key_record.signature)))) .put_node(new StanzaNode.build("identityKey", NS_URI) .put_node(new StanzaNode.text(Base64.encode(identity_key_pair.public.serialize())))); StanzaNode prekeys = new StanzaNode.build("prekeys", NS_URI); foreach (PreKeyRecord pre_key_record in pre_key_records) { prekeys.put_node(new StanzaNode.build("preKeyPublic", NS_URI) .put_attribute("preKeyId", pre_key_record.id.to_string()) .put_node(new StanzaNode.text(Base64.encode(pre_key_record.key_pair.public.serialize())))); } bundle.put_node(prekeys); yield stream.get_module(Pubsub.Module.IDENTITY).publish(stream, null, @"$NODE_BUNDLES:$device_id", "1", bundle); yield try_make_bundle_public(stream, device_id); } private async void try_make_bundle_public(XmppStream stream, int32 device_id) { DataForms.DataForm? data_form = yield stream.get_module(Pubsub.Module.IDENTITY).request_node_config(stream, null, @"$NODE_BUNDLES:$device_id"); if (data_form == null) return; foreach (DataForms.DataForm.Field field in data_form.fields) { if (field.var == "pubsub#access_model" && field.get_value_string() != Pubsub.ACCESS_MODEL_OPEN) { field.set_value_string(Pubsub.ACCESS_MODEL_OPEN); yield stream.get_module(Pubsub.Module.IDENTITY).submit_node_config(stream, data_form, @"$NODE_BUNDLES:$device_id"); break; } } } public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } } } dino-0.3.0/plugins/omemo/src/register_plugin.vala0000644000000000000000000000013614202022370020564 0ustar rootrootpublic Type register_plugin(Module module) { return typeof (Dino.Plugins.Omemo.Plugin); } dino-0.3.0/plugins/omemo/src/trust_level.vala0000644000000000000000000000032414202022370017731 0ustar rootrootnamespace Dino.Plugins.Omemo { public enum TrustLevel { VERIFIED, TRUSTED, UNTRUSTED, UNKNOWN; public string to_string() { int val = this; return val.to_string(); } } } dino-0.3.0/plugins/omemo/src/ui/0000755000000000000000000000000014202022370015132 5ustar rootrootdino-0.3.0/plugins/omemo/src/ui/account_settings_entry.vala0000644000000000000000000000110514202022370022571 0ustar rootrootnamespace Dino.Plugins.Omemo { public class AccountSettingsEntry : Plugins.AccountSettingsEntry { private Plugin plugin; public AccountSettingsEntry(Plugin plugin) { this.plugin = plugin; } public override string id { get { return "omemo_identity_key"; }} public override string name { get { return "OMEMO"; }} public override Plugins.AccountSettingsWidget? get_widget(WidgetType type) { if (type == WidgetType.GTK) { return new AccountSettingWidget(plugin); } return null; } } }dino-0.3.0/plugins/omemo/src/ui/account_settings_widget.vala0000644000000000000000000000360614202022370022723 0ustar rootrootusing Gtk; using Dino.Entities; namespace Dino.Plugins.Omemo { public class AccountSettingWidget : Plugins.AccountSettingsWidget, Box { private Plugin plugin; private Label fingerprint; private Account account; private Button btn; public AccountSettingWidget(Plugin plugin) { this.plugin = plugin; fingerprint = new Label("..."); fingerprint.xalign = 0; Border border = new Button().get_style_context().get_padding(StateFlags.NORMAL); fingerprint.margin_top = border.top + 1; fingerprint.margin_start = border.left + 1; fingerprint.visible = true; pack_start(fingerprint); btn = new Button(); btn.image = new Image.from_icon_name("view-list-symbolic", IconSize.BUTTON); btn.relief = ReliefStyle.NONE; btn.visible = false; btn.valign = Align.CENTER; btn.clicked.connect(() => { activated(); ContactDetailsDialog dialog = new ContactDetailsDialog(plugin, account, account.bare_jid); dialog.set_transient_for((Window) get_toplevel()); dialog.present(); }); pack_start(btn, false); } public void set_account(Account account) { this.account = account; btn.visible = false; Qlite.Row? row = plugin.db.identity.row_with(plugin.db.identity.account_id, account.id).inner; if (row == null) { fingerprint.set_markup("%s\n%s".printf(_("Own fingerprint"), _("Will be generated on first connection"))); } else { string res = fingerprint_markup(fingerprint_from_base64(((!)row)[plugin.db.identity.identity_key_public_base64])); fingerprint.set_markup("%s\n%s".printf(_("Own fingerprint"), res)); btn.visible = true; } } public void deactivate() { } } } dino-0.3.0/plugins/omemo/src/ui/bad_messages_populator.vala0000644000000000000000000001636214202022370022531 0ustar rootrootusing Gee; using Gtk; using Qlite; using Dino.Entities; using Xmpp; namespace Dino.Plugins.Omemo { public enum BadnessType { UNTRUSTED, UNDECRYPTABLE } public class BadMessagesPopulator : Plugins.ConversationItemPopulator, Plugins.ConversationAdditionPopulator, Object { public string id { get { return "bad_omemo_messages"; } } private StreamInteractor stream_interactor; private Plugin plugin; private Database db; private Conversation? current_conversation; private Plugins.ConversationItemCollection? item_collection; private Gee.List bad_items = new ArrayList(); public BadMessagesPopulator(StreamInteractor stream_interactor, Plugin plugin) { this.stream_interactor = stream_interactor; this.plugin = plugin; this.db = plugin.db; plugin.trust_manager.bad_message_state_updated.connect((account, jid, device_id) => { clear_state(); init_state(); }); } private void init_state() { if (current_conversation == null) return; if (current_conversation.type_ == Conversation.Type.GROUPCHAT_PM) return; var qry = db.identity_meta.select() .join_with(db.identity, db.identity.id, db.identity_meta.identity_id) .with(db.identity.account_id, "=", current_conversation.account.id) .where("last_message_untrusted is not NULL OR last_message_undecryptable is not NULL"); switch (current_conversation.type_) { case Conversation.Type.CHAT: qry.with(db.identity_meta.address_name, "=", current_conversation.counterpart.to_string()); break; case Conversation.Type.GROUPCHAT: bool is_private = stream_interactor.get_module(MucManager.IDENTITY).is_private_room(current_conversation.account, current_conversation.counterpart); if (!is_private) return; var list = stream_interactor.get_module(MucManager.IDENTITY).get_offline_members(current_conversation.counterpart, current_conversation.account); if (list == null || list.is_empty) return; var selection = new StringBuilder(); string[] selection_args = {}; foreach (Jid jid in list) { if (selection.len == 0) { selection.append(@" ($(db.identity_meta.address_name) = ?"); } else { selection.append(@" OR $(db.identity_meta.address_name) = ?"); } selection_args += jid.to_string(); } selection.append(")"); qry.where(selection.str, selection_args); break; case Conversation.Type.GROUPCHAT_PM: break; } foreach (Row row in qry) { Jid jid = new Jid(row[db.identity_meta.address_name]); if (!db.identity_meta.last_message_untrusted.is_null(row)) { DateTime time = new DateTime.from_unix_utc(row[db.identity_meta.last_message_untrusted]); var item = new BadMessageItem(plugin, current_conversation, jid, time, BadnessType.UNTRUSTED); bad_items.add(item); item_collection.insert_item(item); } if (!db.identity_meta.last_message_undecryptable.is_null(row)) { DateTime time = new DateTime.from_unix_utc(row[db.identity_meta.last_message_undecryptable]); var item = new BadMessageItem(plugin, current_conversation, jid, time, BadnessType.UNDECRYPTABLE); bad_items.add(item); item_collection.insert_item(item); } } } private void clear_state() { foreach (BadMessageItem bad_item in bad_items) { item_collection.remove_item(bad_item); } } public void init(Conversation conversation, Plugins.ConversationItemCollection item_collection, Plugins.WidgetType type) { current_conversation = conversation; this.item_collection = item_collection; init_state(); } public void close(Conversation conversation) { } public void populate_timespan(Conversation conversation, DateTime after, DateTime before) { } } public class BadMessageItem : Plugins.MetaConversationItem { private Plugin plugin; private Conversation conversation; private Jid problem_jid; private BadnessType badness_type; public BadMessageItem(Plugin plugin, Conversation conversation, Jid jid, DateTime date, BadnessType badness_type) { this.plugin = plugin; this.conversation = conversation; this.problem_jid = jid; this.time = date; this.badness_type = badness_type; } public override Object? get_widget(Plugins.WidgetType widget_type) { return new BadMessagesWidget(plugin, conversation, problem_jid, badness_type); } public override Gee.List? get_item_actions(Plugins.WidgetType type) { return null; } } public class BadMessagesWidget : Box { public BadMessagesWidget(Plugin plugin, Conversation conversation, Jid jid, BadnessType badness_type) { Object(orientation:Orientation.HORIZONTAL, spacing:5); this.halign = Align.CENTER; this.visible = true; string who = ""; if (conversation.type_ == Conversation.Type.CHAT) { who = Dino.get_participant_display_name(plugin.app.stream_interactor, conversation, jid); } else if (conversation.type_ == Conversation.Type.GROUPCHAT) { who = jid.to_string(); // `jid` is a real JID. In MUCs, try to show nicks instead (given that the JID is currently online) var occupants = plugin.app.stream_interactor.get_module(MucManager.IDENTITY).get_occupants(conversation.counterpart, conversation.account); if (occupants == null) return; foreach (Jid occupant in occupants) { if (jid.equals_bare(plugin.app.stream_interactor.get_module(MucManager.IDENTITY).get_real_jid(occupant, conversation.account))) { who = occupant.resourcepart; } } } string warning_text = ""; if (badness_type == BadnessType.UNTRUSTED) { warning_text = _("%s has been using an untrusted device. You won't see messages from devices that you do not trust.").printf(who) + " %s".printf(_("Manage devices")); } else { warning_text += _("%s does not trust this device. That means, you might be missing messages.").printf(who); } Label label = new Label(warning_text) { margin_start=70, margin_end=70, justify=Justification.CENTER, use_markup=true, selectable=true, wrap=true, wrap_mode=Pango.WrapMode.WORD_CHAR, hexpand=true, visible=true }; label.get_style_context().add_class("dim-label"); this.add(label); label.activate_link.connect(() => { if (badness_type == BadnessType.UNTRUSTED) { ContactDetailsDialog dialog = new ContactDetailsDialog(plugin, conversation.account, jid); dialog.set_transient_for((Window) get_toplevel()); dialog.present(); } return false; }); } } } dino-0.3.0/plugins/omemo/src/ui/call_encryption_entry.vala0000644000000000000000000000345714202022370022416 0ustar rootrootusing Dino.Entities; using Gtk; using Qlite; using Xmpp; namespace Dino.Plugins.Omemo { public class CallEncryptionEntry : Plugins.CallEncryptionEntry, Object { private Database db; public CallEncryptionEntry(Database db) { this.db = db; } public Plugins.CallEncryptionWidget? get_widget(Account account, Xmpp.Xep.Jingle.ContentEncryption encryption) { DtlsSrtpVerificationDraft.OmemoContentEncryption? omemo_encryption = encryption as DtlsSrtpVerificationDraft.OmemoContentEncryption; if (omemo_encryption == null) return null; int identity_id = db.identity.get_id(account.id); Row? device = db.identity_meta.get_device(identity_id, omemo_encryption.jid.to_string(), omemo_encryption.sid); if (device == null) return null; TrustLevel trust = (TrustLevel) device[db.identity_meta.trust_level]; return new CallEncryptionWidget(trust); } } public class CallEncryptionWidget : Plugins.CallEncryptionWidget, Object { string? title = null; string? icon = null; bool should_show_keys = false; public CallEncryptionWidget(TrustLevel trust) { if (trust == TrustLevel.VERIFIED) { title = "This call is encrypted and verified with OMEMO."; icon = "dino-security-high-symbolic"; should_show_keys = false; } else { title = "This call is encrypted with OMEMO."; should_show_keys = true; } } public string? get_title() { return title; } public string? get_icon_name() { return icon; } public bool show_keys() { return should_show_keys; } } } dino-0.3.0/plugins/omemo/src/ui/contact_details_dialog.vala0000644000000000000000000003770714202022370022474 0ustar rootrootusing Gtk; using Xmpp; using Gee; using Qlite; using Dino.Entities; using Qrencode; using Gdk; namespace Dino.Plugins.Omemo { [GtkTemplate (ui = "/im/dino/Dino/omemo/contact_details_dialog.ui")] public class ContactDetailsDialog : Gtk.Dialog { private Plugin plugin; private Account account; private Jid jid; private bool own = false; private int own_id = 0; private int identity_id = 0; private Signal.Store store; private Set displayed_ids = new HashSet(); [GtkChild] private unowned Label automatically_accept_new_label; [GtkChild] private unowned Label automatically_accept_new_descr; [GtkChild] private unowned Label own_key_label; [GtkChild] private unowned Label new_keys_label; [GtkChild] private unowned Label associated_keys_label; [GtkChild] private unowned Label inactive_expander_label; [GtkChild] private unowned Box own_fingerprint_container; [GtkChild] private unowned Label own_fingerprint_label; [GtkChild] private unowned Box new_keys_container; [GtkChild] private unowned ListBox new_keys_listbox; [GtkChild] private unowned Box keys_container; [GtkChild] private unowned ListBox keys_listbox; [GtkChild] private unowned Expander inactive_keys_expander; [GtkChild] private unowned ListBox inactive_keys_listbox; [GtkChild] private unowned Switch auto_accept_switch; [GtkChild] private unowned Button copy_button; [GtkChild] private unowned Button show_qrcode_button; [GtkChild] private unowned Image qrcode_image; [GtkChild] private unowned Popover qrcode_popover; construct { // If we set the strings in the .ui file, they don't get translated title = _("OMEMO Key Management"); automatically_accept_new_label.label = _("Automatically accept new keys"); automatically_accept_new_descr.label = _("New encryption keys from this contact will be accepted automatically."); own_key_label.label = _("Own key"); new_keys_label.label = _("New keys"); associated_keys_label.label = _("Associated keys"); inactive_expander_label.label = _("Inactive keys"); } public ContactDetailsDialog(Plugin plugin, Account account, Jid jid) { Object(use_header_bar : Environment.get_variable("GTK_CSD") != "0" ? 1 : 0); this.plugin = plugin; this.account = account; this.jid = jid; if (Environment.get_variable("GTK_CSD") != "0") { ((HeaderBar) get_header_bar()).set_subtitle(jid.bare_jid.to_string()); } keys_listbox.row_activated.connect(on_key_entry_clicked); inactive_keys_listbox.row_activated.connect(on_key_entry_clicked); auto_accept_switch.state_set.connect(on_auto_accept_toggled); identity_id = plugin.db.identity.get_id(account.id); if (identity_id < 0) return; Dino.Application? app = Application.get_default() as Dino.Application; if (app != null) { store = app.stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).store; } auto_accept_switch.set_active(plugin.db.trust.get_blind_trust(identity_id, jid.bare_jid.to_string(), true)); // Dialog opened from the account settings menu // Show the fingerprint for this device separately with buttons for a qrcode and to copy if(jid.equals(account.bare_jid)) { own = true; own_id = plugin.db.identity.row_with(plugin.db.identity.account_id, account.id)[plugin.db.identity.device_id]; automatically_accept_new_descr.label = _("New encryption keys from your other devices will be accepted automatically."); own_fingerprint_container.visible = true; string own_b64 = plugin.db.identity.row_with(plugin.db.identity.account_id, account.id)[plugin.db.identity.identity_key_public_base64]; string fingerprint = fingerprint_from_base64(own_b64); own_fingerprint_label.set_markup(fingerprint_markup(fingerprint)); copy_button.clicked.connect(() => {Clipboard.get_default(get_display()).set_text(fingerprint, fingerprint.length);}); int sid = plugin.db.identity.row_with(plugin.db.identity.account_id, account.id)[plugin.db.identity.device_id]; Pixbuf qr_pixbuf = new QRcode(@"xmpp:$(account.bare_jid)?omemo-sid-$(sid)=$(fingerprint)", 2).to_pixbuf(); qr_pixbuf = qr_pixbuf.scale_simple(150, 150, InterpType.NEAREST); Pixbuf pixbuf = new Pixbuf( qr_pixbuf.colorspace, qr_pixbuf.has_alpha, qr_pixbuf.bits_per_sample, 170, 170 ); pixbuf.fill(uint32.MAX); qr_pixbuf.copy_area(0, 0, 150, 150, pixbuf, 10, 10); qrcode_image.set_from_pixbuf(pixbuf); show_qrcode_button.clicked.connect(qrcode_popover.popup); } new_keys_listbox.set_header_func(header_function); keys_listbox.set_header_func(header_function); //Show any new devices for which the user must decide whether to accept or reject foreach (Row device in plugin.db.identity_meta.get_new_devices(identity_id, jid.to_string())) { add_new_fingerprint(device); } //Show the normal devicelist foreach (Row device in plugin.db.identity_meta.get_known_devices(identity_id, jid.to_string())) { if(own && device[plugin.db.identity_meta.device_id] == own_id) { continue; } add_fingerprint(device, (TrustLevel) device[plugin.db.identity_meta.trust_level]); } // Check for unknown devices fetch_unknown_bundles(); } private void fetch_unknown_bundles() { Dino.Application app = Application.get_default() as Dino.Application; XmppStream? stream = app.stream_interactor.get_stream(account); if (stream == null) return; StreamModule? module = stream.get_module(StreamModule.IDENTITY); if (module == null) return; module.bundle_fetched.connect_after((bundle_jid, device_id, bundle) => { if (bundle_jid.equals(jid) && !displayed_ids.contains(device_id)) { Row? device = plugin.db.identity_meta.get_device(identity_id, jid.to_string(), device_id); if (device == null) return; if (auto_accept_switch.active) { add_fingerprint(device, (TrustLevel) device[plugin.db.identity_meta.trust_level]); } else { add_new_fingerprint(device); } } }); foreach (Row device in plugin.db.identity_meta.get_unknown_devices(identity_id, jid.to_string())) { try { module.fetch_bundle(stream, new Jid(device[plugin.db.identity_meta.address_name]), device[plugin.db.identity_meta.device_id], false); } catch (InvalidJidError e) { warning("Ignoring device with invalid Jid: %s", e.message); } } } private void header_function(ListBoxRow row, ListBoxRow? before) { if (row.get_header() == null && before != null) { row.set_header(new Separator(Orientation.HORIZONTAL)); } } private void add_fingerprint(Row device, TrustLevel trust) { string key_base64 = device[plugin.db.identity_meta.identity_key_public_base64]; bool key_active = device[plugin.db.identity_meta.now_active]; if (store != null) { try { Signal.Address address = new Signal.Address(jid.to_string(), device[plugin.db.identity_meta.device_id]); Signal.SessionRecord? session = null; if (store.contains_session(address)) { session = store.load_session(address); string session_key_base64 = Base64.encode(session.state.remote_identity_key.serialize()); if (key_base64 != session_key_base64) { critical("Session and database identity key mismatch!"); key_base64 = session_key_base64; } } } catch (Error e) { print("Error while reading session store: %s", e.message); } } FingerprintRow fingerprint_row = new FingerprintRow(device, key_base64, trust, key_active) { visible = true, activatable = true, hexpand = true }; if (device[plugin.db.identity_meta.now_active]) { keys_container.visible = true; keys_listbox.add(fingerprint_row); } else { inactive_keys_expander.visible=true; inactive_keys_listbox.add(fingerprint_row); } displayed_ids.add(device[plugin.db.identity_meta.device_id]); } private void on_key_entry_clicked(ListBoxRow widget) { FingerprintRow? fingerprint_row = widget as FingerprintRow; if (fingerprint_row == null) return; Row updated_device = plugin.db.identity_meta.get_device(fingerprint_row.row[plugin.db.identity_meta.identity_id], fingerprint_row.row[plugin.db.identity_meta.address_name], fingerprint_row.row[plugin.db.identity_meta.device_id]); ManageKeyDialog manage_dialog = new ManageKeyDialog(updated_device, plugin.db); manage_dialog.set_transient_for((Gtk.Window) get_toplevel()); manage_dialog.present(); manage_dialog.response.connect((response) => { fingerprint_row.update_trust_state(response, fingerprint_row.row[plugin.db.identity_meta.now_active]); update_stored_trust(response, fingerprint_row.row); }); } private bool on_auto_accept_toggled(bool active) { plugin.trust_manager.set_blind_trust(account, jid, active); if (active) { int identity_id = plugin.db.identity.get_id(account.id); if (identity_id < 0) return false; new_keys_container.visible = false; foreach (Row device in plugin.db.identity_meta.get_new_devices(identity_id, jid.to_string())) { plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], TrustLevel.TRUSTED); add_fingerprint(device, TrustLevel.TRUSTED); } } return false; } private void update_stored_trust(int response, Row device) { switch (response) { case TrustLevel.TRUSTED: plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], TrustLevel.TRUSTED); break; case TrustLevel.UNTRUSTED: plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], TrustLevel.UNTRUSTED); break; case TrustLevel.VERIFIED: plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], TrustLevel.VERIFIED); plugin.trust_manager.set_blind_trust(account, jid, false); auto_accept_switch.set_active(false); break; } } private void add_new_fingerprint(Row device) { new_keys_container.visible = true; ListBoxRow lbr = new ListBoxRow() { visible = true, activatable = false, hexpand = true }; Box box = new Box(Gtk.Orientation.HORIZONTAL, 40) { visible = true, margin_start = 20, margin_end = 20, margin_top = 14, margin_bottom = 14, hexpand = true }; Button accept_button = new Button() { visible = true, valign = Align.CENTER, hexpand = true }; accept_button.add(new Image.from_icon_name("emblem-ok-symbolic", IconSize.BUTTON) { visible=true }); // using .image = sets .image-button. Together with .suggested/destructive action that breaks the button Adwaita accept_button.get_style_context().add_class("suggested-action"); accept_button.tooltip_text = _("Accept key"); Button reject_button = new Button() { visible = true, valign = Align.CENTER, hexpand = true }; reject_button.add(new Image.from_icon_name("action-unavailable-symbolic", IconSize.BUTTON) { visible=true }); reject_button.get_style_context().add_class("destructive-action"); reject_button.tooltip_text = _("Reject key"); accept_button.clicked.connect(() => { plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], TrustLevel.TRUSTED); add_fingerprint(device, TrustLevel.TRUSTED); new_keys_listbox.remove(lbr); if (new_keys_listbox.get_children().length() < 1) new_keys_container.visible = false; }); reject_button.clicked.connect(() => { plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], TrustLevel.UNTRUSTED); add_fingerprint(device, TrustLevel.UNTRUSTED); new_keys_listbox.remove(lbr); if (new_keys_listbox.get_children().length() < 1) new_keys_container.visible = false; }); string res = fingerprint_markup(fingerprint_from_base64(device[plugin.db.identity_meta.identity_key_public_base64])); Label fingerprint_label = new Label(res) { use_markup=true, justify=Justification.RIGHT, visible=true, halign = Align.START, valign = Align.CENTER, hexpand = false }; box.add(fingerprint_label); Box control_box = new Box(Gtk.Orientation.HORIZONTAL, 0) { visible = true, hexpand = true }; control_box.add(accept_button); control_box.add(reject_button); control_box.get_style_context().add_class("linked"); // .linked: Visually link the accept / reject buttons box.add(control_box); lbr.add(box); new_keys_listbox.add(lbr); displayed_ids.add(device[plugin.db.identity_meta.device_id]); } } public class FingerprintRow : ListBoxRow { private Image trust_image = new Image() { visible = true, halign = Align.END, icon_size = IconSize.BUTTON }; private Label fingerprint_label = new Label("") { use_markup=true, justify=Justification.RIGHT, visible=true, halign = Align.START, valign = Align.CENTER, hexpand = false }; private Label trust_label = new Label(null) { visible = true, hexpand = true, xalign = 0 }; public Row row; construct { Box box = new Box(Gtk.Orientation.HORIZONTAL, 40) { visible = true, margin_start = 20, margin_end = 20, margin_top = 14, margin_bottom = 14, hexpand = true }; Box status_box = new Box(Gtk.Orientation.HORIZONTAL, 5) { visible = true, hexpand = true }; box.add(fingerprint_label); box.add(status_box); status_box.add(trust_label); status_box.add(trust_image); this.add(box); } public FingerprintRow(Row row, string key_base64, int trust, bool now_active) { this.row = row; fingerprint_label.label = fingerprint_markup(fingerprint_from_base64(key_base64)); update_trust_state(trust, now_active); } public void update_trust_state(int trust, bool now_active) { switch(trust) { case TrustLevel.TRUSTED: trust_image.icon_name = "emblem-ok-symbolic"; trust_label.set_markup("%s".printf(_("Accepted"))); fingerprint_label.get_style_context().remove_class("dim-label"); break; case TrustLevel.UNTRUSTED: trust_image.icon_name = "action-unavailable-symbolic"; trust_label.set_markup("%s".printf(_("Rejected"))); fingerprint_label.get_style_context().add_class("dim-label"); break; case TrustLevel.VERIFIED: trust_image.icon_name = "security-high-symbolic"; trust_label.set_markup("%s".printf(_("Verified"))); fingerprint_label.get_style_context().remove_class("dim-label"); break; } if (!now_active) { trust_image.icon_name = "appointment-missed-symbolic"; trust_label.set_markup("%s".printf(_("Unused"))); } } } } dino-0.3.0/plugins/omemo/src/ui/contact_details_provider.vala0000644000000000000000000000340114202022370023047 0ustar rootrootusing Gtk; using Gee; using Qlite; using Dino.Entities; namespace Dino.Plugins.Omemo { public class ContactDetailsProvider : Plugins.ContactDetailsProvider, Object { public string id { get { return "omemo_info"; } } private Plugin plugin; public ContactDetailsProvider(Plugin plugin) { this.plugin = plugin; } public void populate(Conversation conversation, Plugins.ContactDetails contact_details, WidgetType type) { if (conversation.type_ == Conversation.Type.CHAT && type == WidgetType.GTK) { int identity_id = plugin.db.identity.get_id(conversation.account.id); if (identity_id < 0) return; int i = 0; foreach (Row row in plugin.db.identity_meta.with_address(identity_id, conversation.counterpart.to_string())) { if (row[plugin.db.identity_meta.identity_key_public_base64] != null) { i++; } } if (i > 0) { Button btn = new Button.from_icon_name("view-list-symbolic") { visible = true, valign = Align.CENTER, relief = ReliefStyle.NONE }; btn.clicked.connect(() => { btn.activate(); ContactDetailsDialog dialog = new ContactDetailsDialog(plugin, conversation.account, conversation.counterpart); dialog.set_transient_for((Window) btn.get_toplevel()); dialog.response.connect((response_type) => { plugin.device_notification_populator.should_hide(); }); dialog.present(); }); contact_details.add(_("Encryption"), "OMEMO", n("%d OMEMO device", "%d OMEMO devices", i).printf(i), btn); } } } } } dino-0.3.0/plugins/omemo/src/ui/device_notification_populator.vala0000644000000000000000000000663414202022370024122 0ustar rootrootusing Dino.Entities; using Xmpp; using Gtk; namespace Dino.Plugins.Omemo { public class DeviceNotificationPopulator : NotificationPopulator, Object { public string id { get { return "device_notification"; } } private StreamInteractor? stream_interactor; private Plugin plugin; private Conversation? current_conversation; private NotificationCollection? notification_collection; private ConversationNotification notification; public DeviceNotificationPopulator(Plugin plugin, StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; this.plugin = plugin; stream_interactor.account_added.connect(on_account_added); } public void init(Conversation conversation, NotificationCollection notification_collection, Plugins.WidgetType type) { current_conversation = conversation; this.notification_collection = notification_collection; if (plugin.has_new_devices(conversation.account, conversation.counterpart) && conversation.type_ == Conversation.Type.CHAT) { display_notification(); } } public void close(Conversation conversation) { notification = null; } private void display_notification() { if (notification == null) { notification = new ConversationNotification(plugin, current_conversation.account, current_conversation.counterpart); notification.should_hide.connect(should_hide); notification_collection.add_meta_notification(notification); } } public void should_hide() { if (!plugin.has_new_devices(current_conversation.account, current_conversation.counterpart) && notification != null){ notification_collection.remove_meta_notification(notification); notification = null; } } private void on_account_added(Account account) { stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).bundle_fetched.connect_after((jid, device_id, bundle) => { if (current_conversation != null && jid.equals(current_conversation.counterpart) && plugin.has_new_devices(current_conversation.account, current_conversation.counterpart)) { display_notification(); } }); } } private class ConversationNotification : MetaConversationNotification { private Widget widget; private Plugin plugin; private Jid jid; private Account account; public signal void should_hide(); public ConversationNotification(Plugin plugin, Account account, Jid jid) { this.plugin = plugin; this.jid = jid; this.account = account; Box box = new Box(Orientation.HORIZONTAL, 5) { visible=true }; Button manage_button = new Button() { label=_("Manage"), visible=true }; manage_button.clicked.connect(() => { manage_button.activate(); ContactDetailsDialog dialog = new ContactDetailsDialog(plugin, account, jid); dialog.set_transient_for((Window) manage_button.get_toplevel()); dialog.response.connect((response_type) => { should_hide(); }); dialog.present(); }); box.add(new Label(_("This contact has new devices")) { margin_end=10, visible=true }); box.add(manage_button); widget = box; } public override Object? get_widget(WidgetType type) { return widget; } } } dino-0.3.0/plugins/omemo/src/ui/encryption_list_entry.vala0000644000000000000000000000632514202022370022453 0ustar rootrootusing Dino.Entities; using Gtk; using Qlite; using Xmpp; namespace Dino.Plugins.Omemo { public class EncryptionListEntry : Plugins.EncryptionListEntry, Object { private Plugin plugin; private Database db; public EncryptionListEntry(Plugin plugin) { this.plugin = plugin; this.db = plugin.db; } public Entities.Encryption encryption { get { return Entities.Encryption.OMEMO; }} public string name { get { return "OMEMO"; }} public static IconSize ICON_SIZE_HEADER = Gtk.icon_size_register("im.dino.Dino.HEADER_ICON2", 17, 12); public Object? get_encryption_icon(Entities.Conversation conversation, ContentItem content_item) { if (content_item.encryption != encryption) return null; RowOption row = db.content_item_meta.select( { db.identity_meta.trust_level } ).with(db.content_item_meta.content_item_id, "=", content_item.id) .join_on(db.identity_meta, @"$(db.identity_meta.address_name)=$(db.content_item_meta.address_name) AND $(db.identity_meta.device_id)=$(db.content_item_meta.device_id)") .single().row(); if (row.is_present() && (TrustLevel) row[db.identity_meta.trust_level] == TrustLevel.VERIFIED) { return new Image.from_icon_name("dino-security-high-symbolic", ICON_SIZE_HEADER) { opacity=0.4, visible = true }; } return null; } public void encryption_activated(Entities.Conversation conversation, Plugins.SetInputFieldStatus input_status_callback) { encryption_activated_async.begin(conversation, input_status_callback); } public async void encryption_activated_async(Entities.Conversation conversation, Plugins.SetInputFieldStatus input_status_callback) { if (conversation.type_ == Conversation.Type.GROUPCHAT_PM) { input_status_callback(new Plugins.InputFieldStatus("Can't use encryption in a groupchat private message.", Plugins.InputFieldStatus.MessageType.ERROR, Plugins.InputFieldStatus.InputState.NO_SEND)); return; } MucManager muc_manager = plugin.app.stream_interactor.get_module(MucManager.IDENTITY); Manager omemo_manager = plugin.app.stream_interactor.get_module(Manager.IDENTITY); if (muc_manager.is_private_room(conversation.account, conversation.counterpart)) { foreach (Jid offline_member in muc_manager.get_offline_members(conversation.counterpart, conversation.account)) { bool ok = yield omemo_manager.ensure_get_keys_for_jid(conversation.account, offline_member); if (!ok) { input_status_callback(new Plugins.InputFieldStatus("A member does not support OMEMO: %s".printf(offline_member.to_string()), Plugins.InputFieldStatus.MessageType.ERROR, Plugins.InputFieldStatus.InputState.NO_SEND)); return; } } return; } if (!(yield omemo_manager.ensure_get_keys_for_jid(conversation.account, conversation.counterpart.bare_jid))) { input_status_callback(new Plugins.InputFieldStatus("This contact does not support %s encryption".printf("OMEMO"), Plugins.InputFieldStatus.MessageType.ERROR, Plugins.InputFieldStatus.InputState.NO_SEND)); } } } } dino-0.3.0/plugins/omemo/src/ui/manage_key_dialog.vala0000644000000000000000000002105114202022370021415 0ustar rootrootusing Gtk; using Qlite; namespace Dino.Plugins.Omemo { [GtkTemplate (ui = "/im/dino/Dino/omemo/manage_key_dialog.ui")] public class ManageKeyDialog : Gtk.Dialog { [GtkChild] private unowned HeaderBar headerbar; [GtkChild] private unowned Stack manage_stack; [GtkChild] private unowned Button cancel_button; [GtkChild] private unowned Button ok_button; [GtkChild] private unowned Label main_desc_label; [GtkChild] private unowned ListBox main_action_list; [GtkChild] private unowned Image confirm_image; [GtkChild] private unowned Label confirm_title_label; [GtkChild] private unowned Label confirm_desc_label; [GtkChild] private unowned Label verify_label; [GtkChild] private unowned Label compare_fingerprint_label; [GtkChild] private unowned Button verify_yes_button; [GtkChild] private unowned Button verify_no_button; private Row device; private Database db; private bool return_to_main; private int current_response; construct { // If we set the strings in the .ui file, they don't get translated headerbar.title = _("Manage Key"); compare_fingerprint_label.label = _("Compare the fingerprint, character by character, with the one shown on your contact's device."); verify_no_button.label = _("Fingerprints differ"); verify_yes_button.label = _("Fingerprints match"); cancel_button.label = _("Cancel"); ok_button.label = _("Confirm"); } public ManageKeyDialog(Row device, Database db) { Object(use_header_bar : Environment.get_variable("GTK_CSD") != "0" ? 1 : 0); this.device = device; this.db = db; setup_main_screen(); setup_verify_screen(); cancel_button.clicked.connect(handle_cancel); ok_button.clicked.connect(() => { response(current_response); close(); }); verify_yes_button.clicked.connect(() => { confirm_image.set_from_icon_name("security-high-symbolic", IconSize.DIALOG); confirm_title_label.label = _("Verify key"); confirm_desc_label.set_markup(_("Future messages sent by %s from the device that uses this key will be highlighted accordingly in the chat window.").printf(@"$(device[db.identity_meta.address_name])")); manage_stack.set_visible_child_name("confirm"); ok_button.sensitive = true; return_to_main = false; current_response = TrustLevel.VERIFIED; }); verify_no_button.clicked.connect(() => { return_to_main = false; confirm_image.set_from_icon_name("dialog-warning-symbolic", IconSize.DIALOG); confirm_title_label.label = _("Fingerprints do not match"); confirm_desc_label.set_markup(_("Please verify that you are comparing the correct fingerprint. If fingerprints do not match, %s's account may be compromised and you should consider rejecting this key.").printf(@"$(device[db.identity_meta.address_name])")); manage_stack.set_visible_child_name("confirm"); }); } private void handle_cancel() { if (manage_stack.get_visible_child_name() == "main") close(); if (manage_stack.get_visible_child_name() == "verify") { manage_stack.set_visible_child_name("main"); cancel_button.label = _("Cancel"); } if (manage_stack.get_visible_child_name() == "confirm") { if (return_to_main) { manage_stack.set_visible_child_name("main"); cancel_button.label = _("Cancel"); } else { manage_stack.set_visible_child_name("verify"); } } ok_button.sensitive = false; } private Box make_action_box(string title, string desc){ Box box = new Box(Orientation.VERTICAL, 0) { visible = true, margin_start = 20, margin_end = 20, margin_top = 14, margin_bottom = 14 }; Label lbl_title = new Label(title) { visible = true, halign = Align.START }; Label lbl_desc = new Label(desc) { visible = true, xalign = 0, wrap = true, max_width_chars = 40 }; Pango.AttrList title_attrs = new Pango.AttrList(); title_attrs.insert(Pango.attr_scale_new(1.1)); lbl_title.attributes = title_attrs; Pango.AttrList desc_attrs = new Pango.AttrList(); desc_attrs.insert(Pango.attr_scale_new(0.8)); lbl_desc.attributes = desc_attrs; lbl_desc.get_style_context().add_class("dim-label"); box.add(lbl_title); box.add(lbl_desc); return box; } private void setup_main_screen() { main_action_list.set_header_func((row, before_row) => { if (row.get_header() == null && before_row != null) { row.set_header(new Separator(Orientation.HORIZONTAL)); } }); ListBoxRow verify_row = new ListBoxRow() { visible = true }; verify_row.add(make_action_box(_("Verify key fingerprint"), _("Compare this key's fingerprint with the fingerprint displayed on the contact's device."))); ListBoxRow reject_row = new ListBoxRow() { visible = true }; reject_row.add(make_action_box(_("Reject key"), _("Block encrypted communication with the contact's device that uses this key."))); ListBoxRow accept_row = new ListBoxRow() {visible = true }; accept_row.add(make_action_box(_("Accept key"), _("Allow encrypted communication with the contact's device that uses this key."))); switch((TrustLevel) device[db.identity_meta.trust_level]) { case TrustLevel.TRUSTED: main_desc_label.set_markup(_("This key is currently %s.").printf(""+_("accepted")+"")+" "+_("This means it can be used by %s to receive and send encrypted messages.").printf(@"$(device[db.identity_meta.address_name])")); main_action_list.add(verify_row); main_action_list.add(reject_row); break; case TrustLevel.VERIFIED: main_desc_label.set_markup(_("This key is currently %s.").printf(""+_("verified")+"")+" "+_("This means it can be used by %s to receive and send encrypted messages.").printf(@"$(device[db.identity_meta.address_name])") + " " + _("Additionally it has been verified to match the key on the contact's device.")); main_action_list.add(reject_row); break; case TrustLevel.UNTRUSTED: main_desc_label.set_markup(_("This key is currently %s.").printf(""+_("rejected")+"")+" "+_("This means it cannot be used by %s to decipher your messages, and you won't see messages encrypted with it.").printf(@"$(device[db.identity_meta.address_name])")); main_action_list.add(accept_row); break; } //Row clicked - go to appropriate screen main_action_list.row_activated.connect((row) => { if(row == verify_row) { manage_stack.set_visible_child_name("verify"); } else if (row == reject_row) { confirm_image.set_from_icon_name("action-unavailable-symbolic", IconSize.DIALOG); confirm_title_label.label = _("Reject key"); confirm_desc_label.set_markup(_("You won't see encrypted messages from the device of %s that uses this key. Conversely, that device won't be able to decipher your messages anymore.").printf(@"$(device[db.identity_meta.address_name])")); manage_stack.set_visible_child_name("confirm"); ok_button.sensitive = true; return_to_main = true; current_response = TrustLevel.UNTRUSTED; } else if (row == accept_row) { confirm_image.set_from_icon_name("emblem-ok-symbolic", IconSize.DIALOG); confirm_title_label.label = _("Accept key"); confirm_desc_label.set_markup(_("You will be able to exchange encrypted messages with the device of %s that uses this key.").printf(@"$(device[db.identity_meta.address_name])")); manage_stack.set_visible_child_name("confirm"); ok_button.sensitive = true; return_to_main = true; current_response = TrustLevel.TRUSTED; } cancel_button.label = _("Back"); }); manage_stack.set_visible_child_name("main"); } private void setup_verify_screen() { verify_label.set_markup(fingerprint_markup(fingerprint_from_base64(device[db.identity_meta.identity_key_public_base64]))); } } } dino-0.3.0/plugins/omemo/src/ui/own_notifications.vala0000644000000000000000000000244614202022370021541 0ustar rootrootusing Dino.Entities; using Xmpp; using Gtk; namespace Dino.Plugins.Omemo { public class OwnNotifications { private StreamInteractor stream_interactor; private Plugin plugin; private Account account; public OwnNotifications (Plugin plugin, StreamInteractor stream_interactor, Account account) { this.stream_interactor = (!)stream_interactor; this.plugin = plugin; this.account = account; stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).bundle_fetched.connect_after((jid, device_id, bundle) => { if (jid.equals(account.bare_jid) && plugin.has_new_devices(account, account.bare_jid)) { display_notification(); } }); if (plugin.has_new_devices(account, account.bare_jid)) { display_notification(); } } private void display_notification() { Notification notification = new Notification(_("OMEMO trust decision required")); notification.set_default_action_and_target_value("app.own-keys", new Variant.int32(account.id)); notification.set_body(_("Did you add a new device for account %s?").printf(@"$(account.bare_jid.to_string())")); plugin.app.send_notification(account.id.to_string()+"-new-device", notification); } } } dino-0.3.0/plugins/omemo/src/ui/util.vala0000644000000000000000000000346214202022370016761 0ustar rootrootusing Xmpp.Util; namespace Dino.Plugins.Omemo { public static string fingerprint_from_base64(string b64) { uint8[] arr = Base64.decode(b64); arr = arr[1:arr.length]; string s = ""; foreach (uint8 i in arr) { string tmp = i.to_string("%x"); if (tmp.length == 1) tmp = "0" + tmp; s = s + tmp; } return s; } public static string fingerprint_markup(string s) { string markup = ""; for (int i = 0; i < s.length; i += 4) { string four_chars = s.substring(i, 4).down(); int raw = (int) from_hex(four_chars); uint8[] bytes = {(uint8) ((raw >> 8) & 0xff - 128), (uint8) (raw & 0xff - 128)}; Checksum checksum = new Checksum(ChecksumType.SHA1); checksum.update(bytes, bytes.length); uint8[] digest = new uint8[20]; size_t len = 20; checksum.get_digest(digest, ref len); uint8 r = digest[0]; uint8 g = digest[1]; uint8 b = digest[2]; if (r == 0 && g == 0 && b == 0) r = g = b = 1; double brightness = 0.2126 * r + 0.7152 * g + 0.0722 * b; if (brightness < 80) { double factor = 80.0 / brightness; r = uint8.min(255, (uint8) (r * factor)); g = uint8.min(255, (uint8) (g * factor)); b = uint8.min(255, (uint8) (b * factor)); } else if (brightness > 180) { double factor = 180.0 / brightness; r = (uint8) (r * factor); g = (uint8) (g * factor); b = (uint8) (b * factor); } if (i % 32 == 0 && i != 0) markup += "\n"; markup += @"$four_chars"; if (i % 8 == 4 && i % 32 != 28) markup += " "; } return "" + markup + ""; } } dino-0.3.0/plugins/omemo/vapi/0000755000000000000000000000000014202022370014665 5ustar rootrootdino-0.3.0/plugins/omemo/vapi/libqrencode.vapi0000644000000000000000000000254314202022370020041 0ustar rootrootusing Gdk; [CCode (cheader_filename = "qrencode.h")] namespace Qrencode { [CCode (cname = "QRecLevel", cprefix = "QR_ECLEVEL_")] public enum ECLevel { L, M, Q, H } [CCode (cname = "QRencodeMode", cprefix = "QR_MODE_")] public enum EncodeMode { NUL, NUM, AN, [CCode (cname = "QR_MODE_8")] EIGHT_BIT, KANJI, STRUCTURE, ECI, FNC1FIRST, FNC1SECOND } [CCode (cname = "QRcode", free_function = "QRcode_free", has_type_id = false)] [Compact] public class QRcode { private int version; private int width; [CCode (array_length = false)] private uint8[] data; [CCode (cname = "QRcode_encodeString")] public QRcode (string str, int version = 0, ECLevel level = ECLevel.L, EncodeMode hint = EncodeMode.EIGHT_BIT, bool casesensitive = true); public Pixbuf to_pixbuf() { uint8[] bitmap = new uint8[3*width*width]; for (int i = 0; i < width*width; i++) { uint8 color = (data[i] & 1) == 1 ? 0 : 255; bitmap[i*3] = color; bitmap[i*3+1] = color; bitmap[i*3+2] = color; } return new Pixbuf.from_data(bitmap, Colorspace.RGB, false, 8, width, width, width*3); } } } dino-0.3.0/plugins/openpgp/0000755000000000000000000000000014202022370014262 5ustar rootrootdino-0.3.0/plugins/openpgp/CMakeLists.txt0000644000000000000000000000350514202022370017025 0ustar rootrootset(GETTEXT_PACKAGE "dino-openpgp") find_package(Gettext) include(${GETTEXT_USE_FILE}) gettext_compile(${GETTEXT_PACKAGE} SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/po TARGET_NAME ${GETTEXT_PACKAGE}-translations) find_packages(OPENPGP_PACKAGES REQUIRED Gee GLib GModule GObject GTK3 ) set(RESOURCE_LIST account_settings_item.ui ) compile_gresources( OPENPGP_GRESOURCES_TARGET OPENPGP_GRESOURCES_XML TARGET ${CMAKE_CURRENT_BINARY_DIR}/resources/resources.c TYPE EMBED_C RESOURCES ${RESOURCE_LIST} PREFIX /im/dino/Dino/openpgp SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/data ) vala_precompile(OPENPGP_VALA_C SOURCES src/file_transfer/file_decryptor.vala src/file_transfer/file_encryptor.vala src/account_settings_entry.vala src/account_settings_widget.vala src/contact_details_provider.vala src/database.vala src/encryption_list_entry.vala src/manager.vala src/plugin.vala src/register_plugin.vala src/stream_flag.vala src/stream_module.vala src/util.vala CUSTOM_VAPIS ${CMAKE_BINARY_DIR}/exports/gpgme.vapi ${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi ${CMAKE_BINARY_DIR}/exports/qlite.vapi ${CMAKE_BINARY_DIR}/exports/dino.vapi PACKAGES ${OPENPGP_PACKAGES} GRESOURCES ${OPENPGP_GRESOURCES_XML} ) add_definitions(${VALA_CFLAGS} -DG_LOG_DOMAIN="OpenPGP" -DGETTEXT_PACKAGE=\"${GETTEXT_PACKAGE}\" -DLOCALE_INSTALL_DIR=\"${LOCALE_INSTALL_DIR}\") add_library(openpgp SHARED ${OPENPGP_VALA_C} ${OPENPGP_GRESOURCES_TARGET}) add_dependencies(openpgp ${GETTEXT_PACKAGE}-translations) target_link_libraries(openpgp libdino gpgme-vala ${OPENPGP_PACKAGES}) set_target_properties(openpgp PROPERTIES PREFIX "") set_target_properties(openpgp PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/plugins/) install(TARGETS openpgp ${PLUGIN_INSTALL}) dino-0.3.0/plugins/openpgp/data/0000755000000000000000000000000014202022370015173 5ustar rootrootdino-0.3.0/plugins/openpgp/data/account_settings_item.ui0000644000000000000000000000220414202022370022122 0ustar rootroot dino-0.3.0/plugins/openpgp/po/0000755000000000000000000000000014202022370014700 5ustar rootrootdino-0.3.0/plugins/openpgp/po/LINGUAS0000644000000000000000000000016514202022370015727 0ustar rootrootar ca cs de el en eo es eu fa fi fr gl hu id ie it ja lb lt nb nl nl_BE oc pl pt pt_BR ro ru sq sv tr uk zh_CN zh_TW dino-0.3.0/plugins/openpgp/po/ar.po0000644000000000000000000000343214202022370015644 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2018-10-23 17:22+0000\n" "Language-Team: Arabic \n" "Language: ar\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " "&& n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n" "X-Generator: Weblate 3.3-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "نشر المفاتيح معطل" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "خطأ في GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "لا مفتاح متوفر. قم بتوليد واحد !" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "اختيار مفتاح" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loading…" msgstr "جارٍ التحميل…" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "استعلام GnuPG" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "المفتاح ليس في سلسلة المفاتيح" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "التشفير" dino-0.3.0/plugins/openpgp/po/ca.po0000644000000000000000000000301514202022370015622 0ustar rootroot# Catalan translation for dino-openpgp. # This file is distributed under the same license as the dino package. # Jordi Mallach , 2018. # msgid "" msgstr "" "Project-Id-Version: dino-openpgp 20180123\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2018-01-24 11:24+0100\n" "Language-Team: Catalan \n" "Language: ca\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Publicació de claus inhabilitada" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "S'ha produït un error al GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "No hi ha claus disponibles. Genereu una!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Seleccioneu una clau" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loading…" msgstr "S'està carregant…" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "S'està consultant al GnuPG" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "La clau no està al clauer" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Xifratge" dino-0.3.0/plugins/openpgp/po/cs.po0000644000000000000000000000316114202022370015646 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-08-21 05:36+0000\n" "Language-Team: none\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" "X-Generator: Weblate 4.2.1-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Publikování klíčů zakázáno" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "Chyba v GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "Nejsou k dispozici žádné klíče. Vygenerujte jeden!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Vybrat klíč" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loading…" msgstr "Načítání…" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "Dotazování GnuPG" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "Klíč není v klíčence" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Šifrování" dino-0.3.0/plugins/openpgp/po/de.po0000644000000000000000000000300614202022370015627 0ustar rootrootmsgid "" msgstr "" "Project-Id-Version: dino-openpgp-0.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-04-16 20:11+0000\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" "X-Generator: Weblate 4.0.1-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Schlüsselveröffentlichung deaktiviert" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "Fehler in GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "Keine Schlüssel vorhanden. Erzeuge einen!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Wähle einen Schlüssel" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loading…" msgstr "Lade…" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "Frage GnuPG ab" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "Schlüssel nicht im Schlüsselbund" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Verschlüsselung" #~ msgid "OpenPGP" #~ msgstr "OpenPGP" dino-0.3.0/plugins/openpgp/po/dino-openpgp.pot0000644000000000000000000000262214202022370020025 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loading…" msgstr "" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "" dino-0.3.0/plugins/openpgp/po/el.po0000644000000000000000000000340414202022370015641 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2022-02-04 09:55+0000\n" "Language-Team: none\n" "Language: el\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: Weblate 4.11-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Η δημοσίευση του κλειδιού είναι απενεργοποιημένη" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "Σφάλμα στο GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "Δεν υπάρχουν διαθέσιμα κλειδιά. Δημιουργήστε ένα!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Επιλέξτε κλειδί" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loading…" msgstr "Φόρτωση…" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "Αναζήτηση στο GnuPG" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "Το κλειδί δεν είναι στο keychain" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Κρυπτογράφηση" dino-0.3.0/plugins/openpgp/po/en.po0000644000000000000000000000211114202022370015635 0ustar rootrootmsgid "" msgstr "" "Project-Id-Version: dino-openpgp-0.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "Language: en\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loading…" msgstr "" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "" dino-0.3.0/plugins/openpgp/po/eo.po0000644000000000000000000000311214202022370015640 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2021-03-08 22:02+0000\n" "Language-Team: none\n" "Language: eo\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: Weblate 4.5.1\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Eldonado de ŝlosilo estas malŝaltita" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "Eraro pri GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "Neniu ŝlosilo estas havebla. Faru ŝlosilon!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Elekti ŝlosilon" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loading…" msgstr "Ŝargante…" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "Pridemandante GnuPG" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "Ŝlosilo ne en ŝlosilaro" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Ĉifrado" dino-0.3.0/plugins/openpgp/po/es.po0000644000000000000000000000322214202022370015646 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2018-11-19 08:07+0000\n" "Language-Team: Spanish \n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 3.3-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Publicación de claves desactivada" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "Error en GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "No hay claves disponibles. ¡Genera una!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Seleccionar clave" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loading…" msgstr "Cargando…" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "Consultando GnuPG" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "La clave no está en la cadena de claves" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Cifrado" dino-0.3.0/plugins/openpgp/po/eu.po0000644000000000000000000000320014202022370015644 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2018-04-03 15:27+0000\n" "Language-Team: Basque \n" "Language: eu\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: Weblate 2.20-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Gakoa argitaratzea ezgaituta" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "GnuPG akatsa" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "Ez dago gakorik eskuragarri. Sortu ezazu bat!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Gakoa hautatu" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loading…" msgstr "Kargatzen…" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "GnuPGri galdetzen" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "Gakorik ez giltzatakoan" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Enkriptazioa" dino-0.3.0/plugins/openpgp/po/fa.po0000644000000000000000000000327414202022370015634 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2021-04-26 15:32+0000\n" "Language-Team: none\n" "Language: fa\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: Weblate 4.7-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "انشار کلید غیرفعال‌شده" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "خطا در گنوپی‌جی" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "هیچ کلیدی در دسترس نیست. یکی تولید کنید!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "انتخاب کلید" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loading…" msgstr "در حال بارگذاری…" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "پرس‌و‌جوی گنوپی‌جی" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "کلید در دسته‌کلید نیست" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "رمزگذاری" dino-0.3.0/plugins/openpgp/po/fi.po0000644000000000000000000000263414202022370015643 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2021-09-02 00:45+0000\n" "Language-Team: none\n" "Language: fi\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: Weblate 4.8.1-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loading…" msgstr "" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "" dino-0.3.0/plugins/openpgp/po/fr.po0000644000000000000000000000325714202022370015656 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-11-12 17:21+0000\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: Weblate 4.4-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "La publication des clés est désactivée" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "Erreur dans GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "Aucune clé n’est disponible. Générez-en une !" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Choix d’une clé" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loading…" msgstr "Chargement…" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "Interrogation de GnuPG" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "La clé n’est pas dans le trousseau" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Chiffrement" dino-0.3.0/plugins/openpgp/po/gl.po0000644000000000000000000000321314202022370015641 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-02-07 10:50+0000\n" "Language-Team: Galician \n" "Language: gl\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: Weblate 3.11-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Desactivada a publicación de chaves" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "Fallo en GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "Sen chaves dispoñibles. Crear unha!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Escolle chave" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loading…" msgstr "Cargando…" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "Consultando a GnuPG" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "A chave non está no anel de chaves" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Cifrado" dino-0.3.0/plugins/openpgp/po/hu.po0000644000000000000000000000323514202022370015657 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2021-12-05 01:51+0000\n" "Language-Team: Hungarian \n" "Language: hu\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: Weblate 4.10-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Kulcsközzététel letiltva" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "Hiba a GnuPG-ben" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "Nincsenek elérhető kulcsok. Állítson elő egyet!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Kulcs kiválasztása" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loading…" msgstr "Betöltés…" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "A GnuPG lekérése" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "A kulcs nincs a kulcstartóban" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Titkosítás" dino-0.3.0/plugins/openpgp/po/id.po0000644000000000000000000000307414202022370015640 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2021-01-09 11:43+0000\n" "Language-Team: none\n" "Language: id\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Weblate 4.4.1-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Penerbitan kunci dinonaktifkan" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "Kesalahan pada GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "Kunci tidak tersedia. Buat satu!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Pilih kunci" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loading…" msgstr "Memuat…" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "Bertanya kepada GnuPG" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "Kunci tidak ada di gantungan kunci" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Enkripsi" dino-0.3.0/plugins/openpgp/po/ie.po0000644000000000000000000000310014202022370015627 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-02-04 16:50+0000\n" "Language-Team: none\n" "Language: ie\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: Weblate 3.11-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Publication de claves depermisset" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "Errore de GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "Null claves disponibil. Crea ún!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Selecter un clave" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loading…" msgstr "Cargante…" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "Contactante GnuPG" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "Clave ne es in li porta-clave" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Ciffration" dino-0.3.0/plugins/openpgp/po/it.po0000644000000000000000000000300714202022370015654 0ustar rootrootmsgid "" msgstr "" "Project-Id-Version: dino-openpgp-0.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2018-01-24 12:18+0000\n" "Language-Team: Italian \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 2.19-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Pubblicazione della chiave disabilitata" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "Errore in GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "Nessuna chiave disponibile. Generane una!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Seleziona una chiave" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loading…" msgstr "Caricamento…" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "Interrogando GnuPG" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "La chiave non è nel portachiavi" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Crittografia" #~ msgid "OpenPGP" #~ msgstr "OpenPGP" dino-0.3.0/plugins/openpgp/po/ja.po0000644000000000000000000000317214202022370015635 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-01-28 23:24+0000\n" "Language-Team: none\n" "Language: ja\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Weblate 3.11-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "キーの発行は無効" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "GnuPG でのエラー" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "キーがありません。生成しましょう!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "キーを選択" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loading…" msgstr "読み込んでいます…" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "GnuPG に問い合わせています" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "キーはキーチェーンにありません" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "暗号化" dino-0.3.0/plugins/openpgp/po/lb.po0000644000000000000000000000303014202022370015631 0ustar rootrootmsgid "" msgstr "" "Project-Id-Version: dino-openpgp-0.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2018-01-24 12:20+0000\n" "Language-Team: Luxembourgish \n" "Language: lb\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: Weblate 2.19-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Schlëssel Verëffentlechung ausgeschalt" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "Feeler am GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "Keng Schlëssele verfügbar. Generéier een!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Schlëssel auswielen" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loading…" msgstr "Lueden…" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "GnuPG gëtt ofgefrot" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "Schlësselen net am Schlësselbond" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Verschlësselung" #~ msgid "OpenPGP" #~ msgstr "OpenPGP" dino-0.3.0/plugins/openpgp/po/lt.po0000644000000000000000000000330314202022370015656 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-02-01 15:50+0000\n" "Language-Team: none\n" "Language: lt\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 || n % 100 > " "19)) ? 0 : ((n % 10 >= 2 && n % 10 <= 9 && (n % 100 < 11 || n % 100 > 19)) ? " "1 : 2);\n" "X-Generator: Weblate 3.11-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Rakto paskelbimas išjungtas" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "Klaida GnuPG programoje" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "Nėra prieinamų raktų. Sugeneruokite!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Pasirinkti raktą" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loading…" msgstr "Įkeliama…" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "Užklausiama GnuPG" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "Rakto nėra raktinėje" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Šifravimas" dino-0.3.0/plugins/openpgp/po/nb.po0000644000000000000000000000330014202022370015633 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2017-11-18 00:23+0000\n" "Language-Team: Norwegian Bokmål \n" "Language: nb\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: Weblate 2.18-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Nøkkelpublisering avskrudd" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "Feil i GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "Ingen nøkler tilgjengelige. Generer én." #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Velg nøkkel" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loading…" msgstr "Laster…" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "Sender spørring til GnuPG" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "Nøkkelen finnes ikke i nøkkelknippet" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Kryptering" #~ msgid "OpenPGP" #~ msgstr "OpenPGP" dino-0.3.0/plugins/openpgp/po/nl.po0000644000000000000000000000300714202022370015651 0ustar rootrootmsgid "" msgstr "" "Project-Id-Version: dino-openpgp-0.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-09-05 19:21+0000\n" "Language-Team: Dutch \n" "Language: nl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 4.3-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Sleutelpublicatie uitgeschakeld" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "Fout in GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "Geen sleutels beschikbaar. Genereer een sleutel!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Kies een sleutel" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loading…" msgstr "Bezig met laden…" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "Bezig met aanroepen van GnuPG" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "Sleutel niet in sleutelbos" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Versleuteling" #~ msgid "OpenPGP" #~ msgstr "OpenPGP" dino-0.3.0/plugins/openpgp/po/nl_BE.po0000644000000000000000000000322514202022370016221 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2018-12-26 15:08+0000\n" "Language-Team: Flemish \n" "Language: nl_BE\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: Weblate 3.4-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Publiceren van sleutels uitgeschakeld" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "Fout in GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "Geen sleutels beschikbaar. Genereert er enen!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Selecteert ne sleutel" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loading…" msgstr "Laden…" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "GnuPG inroepen" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "Sleutel niet in sleutelbos" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Versleuteling" dino-0.3.0/plugins/openpgp/po/oc.po0000644000000000000000000000311714202022370015643 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-06-22 10:41+0000\n" "Language-Team: none\n" "Language: oc\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: Weblate 4.2-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Publicacion de clau desactivada" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "Error dins gnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "Cap de clau pas disponibla. Generatz-ne una !" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Seleccionar una clau" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loading…" msgstr "Cargament…" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "Consultacion de GnuPG" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "La clau es pas al trocèl" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Chiframent" dino-0.3.0/plugins/openpgp/po/pl.po0000644000000000000000000000327514202022370015662 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-05-13 18:41+0000\n" "Language-Team: Polish \n" "Language: pl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " "|| n%100>=20) ? 1 : 2;\n" "X-Generator: Weblate 4.1-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Publikowanie kluczy wyłączone" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "Błąd w GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "Brak kluczy. Wygeneruj nowy!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Wybierz klucz" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loading…" msgstr "Ładowanie…" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "Odpytuję GnuPG" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "Klucz nie znajduje się w pęku kluczy" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Szyfrowanie" dino-0.3.0/plugins/openpgp/po/pt.po0000644000000000000000000000310414202022370015661 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-08-23 21:36+0000\n" "Language-Team: none\n" "Language: pt\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: Weblate 4.2.1-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Publicação da chave desativada" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "Erro no GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "Nenhuma chave disponível. Gere uma!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Selecionar chave" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loading…" msgstr "Carregando…" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "Consultando GnuPG" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "A chave não está no chaveiro" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Criptografia" dino-0.3.0/plugins/openpgp/po/pt_BR.po0000644000000000000000000000310614202022370016246 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-01-16 00:21+0000\n" "Language-Team: none\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: Weblate 3.11-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Publicação da chave desativada" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "Erro no GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "Nenhuma chave disponível. Gere uma!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Selecionar chave" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loading…" msgstr "Carregando…" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "Consultando GnuPG" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "A chave não está no chaveiro" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Criptografia" dino-0.3.0/plugins/openpgp/po/ro.po0000644000000000000000000000330414202022370015660 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2018-01-12 23:14+0000\n" "Language-Team: Romanian \n" "Language: ro\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==0 || (n%100 > 0 && n%100 < " "20)) ? 1 : 2;\n" "X-Generator: Weblate 2.19-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Cheie publică dezactivată" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "Eroare în GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "Nici o cheie disponibilă. Generați una!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Selectați cheia" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loading…" msgstr "Se încarcă…" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "Se interoghează GnuPG" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "Cheia nu este în șirul de chei" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Criptare" dino-0.3.0/plugins/openpgp/po/ru.po0000644000000000000000000000351714202022370015674 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-06-02 11:41+0000\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" "X-Generator: Weblate 4.1-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Публикация ключа отключена" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "Ошибка в GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "Нет доступных ключей. Стоило бы сгенерировать один!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Выбрать ключ" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loading…" msgstr "Загрузка…" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "Запрос GnuPG" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "Отсутствует ключ в связке" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Шифрование" dino-0.3.0/plugins/openpgp/po/sq.po0000644000000000000000000000311314202022370015661 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2021-03-04 15:20+0000\n" "Language-Team: none\n" "Language: sq\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: Weblate 4.5.1-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Publikim kyçesh i çaktivizuar" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "Gabim në GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "S’ka kyçe gati. Prodhoni një të tillë!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Përzgjidhni kyç" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loading…" msgstr "Po ngarkohet…" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "Po pyetet GnuPG-ja" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "Kyç jo në varg kyçesh" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Fshehtëzim" dino-0.3.0/plugins/openpgp/po/sv.po0000644000000000000000000000307014202022370015670 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2019-08-14 11:23+0000\n" "Language-Team: none\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" "X-Generator: Weblate 3.8-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Nyckelpublicering inaktiverad" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "Fel i GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "Inga nycklar tillgängliga. Generera en!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Välj nyckel" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loading…" msgstr "Laddar…" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "Frågar GnuPG" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "Nyckeln finns inte i nyckelknippan" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Kryptering" dino-0.3.0/plugins/openpgp/po/tr.po0000644000000000000000000000306614202022370015672 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-02-04 16:50+0000\n" "Language-Team: none\n" "Language: tr\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: Weblate 3.11-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Anahtar yayınlama devre dışı" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "GnuPG'de Hata" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "Anahtar yok. Bir tane oluşturun!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Anahtar seçin" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loading…" msgstr "Yükleniyor…" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "GnuPG sorgu" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "Anahtar anahtarlıkta değil" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Şifreleme" dino-0.3.0/plugins/openpgp/po/uk.po0000644000000000000000000000330314202022370015656 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-12-31 17:29+0000\n" "Language-Team: none\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=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" "X-Generator: Weblate 4.4.1-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 #, fuzzy msgid "Key publishing disabled" msgstr "Публікація ключа відключена" #: plugins/openpgp/src/account_settings_widget.vala:60 #, fuzzy msgid "Error in GnuPG" msgstr "Помилка у GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Вибрати ключ" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loading…" msgstr "Завантаження…" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "" #: plugins/openpgp/src/contact_details_provider.vala:28 #, fuzzy msgid "Key not in keychain" msgstr "Відсутній ключ у зв’язці" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Шифрування" dino-0.3.0/plugins/openpgp/po/zh_CN.po0000644000000000000000000000322714202022370016245 0ustar rootrootmsgid "" msgstr "" "Project-Id-Version: dino-omemo-0.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2018-05-08 09:42+0000\n" "Language-Team: Chinese (Simplified) \n" "Language: zh_Hans\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Weblate 3.0-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "已禁用密钥发布功能" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "GnuPG 错误" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "没有密钥可用。生成一个吧!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "选择密钥" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loading…" msgstr "载入中…" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "正在查询 GnuPG" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "密钥并没有在密钥串里" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "加密" #~ msgid "Own fingerprint" #~ msgstr "自己的指纹" #~ msgid "Will be generated on first connection" #~ msgstr "会在第一次连接时生成" #~ msgid "Database error" #~ msgstr "数据库错误" dino-0.3.0/plugins/openpgp/po/zh_TW.po0000644000000000000000000000320314202022370016271 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-02-12 22:06+0100\n" "PO-Revision-Date: 2020-11-24 05:28+0000\n" "Language-Team: Chinese (Traditional) \n" "Language: zh_TW\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Weblate 4.4-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "金鑰發佈已經被禁用" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "GnuPG 錯誤" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "沒有可用的金鑰。生成一個!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "選擇金鑰" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loading…" msgstr "載入中…" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "正在查詢 GnuPG" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "密鑰不在密鑰串中" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "加密" dino-0.3.0/plugins/openpgp/src/0000755000000000000000000000000014202022370015051 5ustar rootrootdino-0.3.0/plugins/openpgp/src/account_settings_entry.vala0000644000000000000000000000110714202022370022512 0ustar rootrootnamespace Dino.Plugins.OpenPgp { public class AccountSettingsEntry : Plugins.AccountSettingsEntry { private Plugin plugin; public AccountSettingsEntry(Plugin plugin) { this.plugin = plugin; } public override string id { get { return "pgp_key_picker"; }} public override string name { get { return "OpenPGP"; }} public override Plugins.AccountSettingsWidget? get_widget(WidgetType type) { if (type == WidgetType.GTK) { return new AccountSettingsWidget(plugin); } return null; } } }dino-0.3.0/plugins/openpgp/src/account_settings_widget.vala0000644000000000000000000001145314202022370022641 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; namespace Dino.Plugins.OpenPgp { [GtkTemplate (ui = "/im/dino/Dino/openpgp/account_settings_item.ui")] private class AccountSettingsWidget : Stack, Plugins.AccountSettingsWidget { [GtkChild] private unowned Label label; [GtkChild] private unowned Button button; [GtkChild] private unowned ComboBox combobox; private Plugin plugin; private Account current_account; private Gee.List keys = null; private Gtk.ListStore list_store = new Gtk.ListStore(2, typeof(string), typeof(string?)); public AccountSettingsWidget(Plugin plugin) { this.plugin = plugin; CellRendererText renderer = new CellRendererText(); renderer.set_padding(0, 0); combobox.pack_start(renderer, true); combobox.add_attribute(renderer, "markup", 0); combobox.set_model(list_store); button.clicked.connect(on_button_clicked); combobox.changed.connect(key_changed); } public void deactivate() { set_visible_child_name("label"); } public void set_account(Account account) { set_account_.begin(account); } private async void set_account_(Account account) { this.current_account = account; if (keys == null) { yield fetch_keys(); populate_list_store(); } activate_current_account(); } private void on_button_clicked() { activated(); set_visible_child_name("entry"); combobox.grab_focus(); combobox.popup(); } private void activate_current_account() { combobox.changed.disconnect(key_changed); if (keys == null) { label.set_markup(build_markup_string(_("Key publishing disabled"), _("Error in GnuPG"))); return; } if (keys.size == 0) { label.set_markup(build_markup_string(_("Key publishing disabled"), _("No keys available. Generate one!"))); return; } string? account_key = plugin.db.get_account_key(current_account); int activate_index = 0; for (int i = 0; i < keys.size; i++) { GPG.Key key = keys[i]; if (key.fpr == account_key) { activate_index = i + 1; } } combobox.active = activate_index; TreeIter selected; combobox.get_active_iter(out selected); set_label_active(selected); combobox.changed.connect(key_changed); } private void populate_list_store() { if (keys == null || keys.size == 0) { return; } list_store.clear(); TreeIter iter; list_store.append(out iter); list_store.set(iter, 0, build_markup_string(_("Key publishing disabled"), _("Select key") + " \n "), 1, ""); for (int i = 0; i < keys.size; i++) { list_store.append(out iter); list_store.set(iter, 0, @"$(Markup.escape_text(keys[i].uids[0].uid))\n$(markup_colorize_id(keys[i].fpr, true)) "); list_store.set(iter, 1, keys[i].fpr); if (keys[i].fpr == plugin.db.get_account_key(current_account)) { set_label_active(iter, i + 1); } } button.sensitive = true; } private async void fetch_keys() { label.set_markup(build_markup_string(_("Loading…"), _("Querying GnuPG"))); SourceFunc callback = fetch_keys.callback; new Thread (null, () => { // Querying GnuPG might take some time try { keys = GPGHelper.get_keylist(null, true); } catch (Error e) { } Idle.add((owned)callback); return null; }); yield; } private void set_label_active(TreeIter iter, int i = -1) { Value text; list_store.get_value(iter, 0, out text); label.set_markup((string) text); if (i != -1) combobox.active = i; } private void key_changed() { TreeIter selected; bool iter_valid = combobox.get_active_iter(out selected); if (iter_valid) { Value key_value; list_store.get_value(selected, 1, out key_value); string? key_id = key_value as string; if (key_id != null) { if (plugin.modules.has_key(current_account)) { plugin.modules[current_account].set_private_key_id(key_id); } plugin.db.set_account_key(current_account, key_id); } set_label_active(selected); deactivate(); } } private string build_markup_string(string primary, string secondary) { return @"$(Markup.escape_text(primary))\n$secondary"; } } } dino-0.3.0/plugins/openpgp/src/contact_details_provider.vala0000644000000000000000000000261514202022370022774 0ustar rootrootusing Gtk; using Dino.Entities; namespace Dino.Plugins.OpenPgp { public class ContactDetailsProvider : Plugins.ContactDetailsProvider, Object { public string id { get { return "pgp_info"; } } private StreamInteractor stream_interactor; public ContactDetailsProvider(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; } public void populate(Conversation conversation, Plugins.ContactDetails contact_details, WidgetType type) { if (conversation.type_ == Conversation.Type.CHAT && type == WidgetType.GTK) { string? key_id = stream_interactor.get_module(Manager.IDENTITY).get_key_id(conversation.account, conversation.counterpart); if (key_id != null) { Label label = new Label("") { use_markup=true, justify=Justification.RIGHT, selectable=true, visible=true }; Gee.List? keys = null; try { keys = GPGHelper.get_keylist(key_id); } catch (Error e) { } if (keys != null && keys.size > 0) { label.label = markup_colorize_id(keys[0].fpr, true); } else { label.label = _("Key not in keychain") + "\n" + markup_colorize_id(key_id, false); } contact_details.add(_("Encryption"), "OpenPGP", "", label); } } } } } dino-0.3.0/plugins/openpgp/src/database.vala0000644000000000000000000000473014202022370017466 0ustar rootrootusing Qlite; using Dino.Entities; using Xmpp; namespace Dino.Plugins.OpenPgp { public class Database : Qlite.Database { private const int VERSION = 0; public class AccountSetting : Table { public Column account_id = new Column.Integer("account_id") { primary_key = true }; public Column key = new Column.Text("key") { not_null = true }; internal AccountSetting(Database db) { base(db, "account_setting"); init({account_id, key}); } } public class ContactKey : Table { public Column jid = new Column.Text("jid") { primary_key = true }; public Column key = new Column.Text("key") { not_null = true }; internal ContactKey(Database db) { base(db, "contact_key"); init({jid, key}); } } public AccountSetting account_setting_table { get; private set; } public ContactKey contact_key_table { get; private set; } public Database(string filename) { base(filename, VERSION); this.account_setting_table = new AccountSetting(this); this.contact_key_table = new ContactKey(this); init({account_setting_table, contact_key_table}); try { exec("PRAGMA journal_mode = WAL"); exec("PRAGMA synchronous = NORMAL"); exec("PRAGMA secure_delete = ON"); } catch (Error e) { error("Failed to set OpenPGP database properties: %s", e.message); } } public void set_contact_key(Jid jid, string key) { contact_key_table.upsert() .value(contact_key_table.jid, jid.to_string(), true) .value(contact_key_table.key, key) .perform(); } public string? get_contact_key(Jid jid) { return contact_key_table.select({contact_key_table.key}) .with(contact_key_table.jid, "=", jid.to_string())[contact_key_table.key]; } public void set_account_key(Account account, string key) { account_setting_table.upsert() .value(account_setting_table.account_id, account.id, true) .value(account_setting_table.key, key) .perform(); } public string? get_account_key(Account account) { return account_setting_table.select({account_setting_table.key}) .with(account_setting_table.account_id, "=", account.id)[account_setting_table.key]; } public override void migrate(long oldVersion) { } } } dino-0.3.0/plugins/openpgp/src/encryption_list_entry.vala0000644000000000000000000000630314202022370022366 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; using Xmpp; namespace Dino.Plugins.OpenPgp { private class EncryptionListEntry : Plugins.EncryptionListEntry, Object { private StreamInteractor stream_interactor; private Database db; public EncryptionListEntry(StreamInteractor stream_interactor, Database db) { this.stream_interactor = stream_interactor; this.db = db; } public Entities.Encryption encryption { get { return Encryption.PGP; }} public string name { get { return "OpenPGP"; }} public static IconSize ICON_SIZE_HEADER = Gtk.icon_size_register("im.dino.Dino.HEADER_ICON3", 17, 12); public Object? get_encryption_icon(Entities.Conversation conversation, ContentItem content_item) { return null; } public void encryption_activated(Entities.Conversation conversation, Plugins.SetInputFieldStatus input_status_callback) { try { GPGHelper.get_public_key(db.get_account_key(conversation.account) ?? ""); } catch (Error e) { input_status_callback(new Plugins.InputFieldStatus("You didn't configure OpenPGP for this account. You can do that in the Accounts Dialog.", Plugins.InputFieldStatus.MessageType.ERROR, Plugins.InputFieldStatus.InputState.NO_SEND)); return; } if (conversation.type_ == Conversation.Type.CHAT) { string? key_id = stream_interactor.get_module(Manager.IDENTITY).get_key_id(conversation.account, conversation.counterpart); if (key_id == null) { input_status_callback(new Plugins.InputFieldStatus("This contact does not support %s encryption.".printf("OpenPGP"), Plugins.InputFieldStatus.MessageType.ERROR, Plugins.InputFieldStatus.InputState.NO_SEND)); return; } try { GPGHelper.get_keylist(key_id); } catch (Error e) { input_status_callback(new Plugins.InputFieldStatus("This contact's OpenPGP key is not in your keyring.", Plugins.InputFieldStatus.MessageType.ERROR, Plugins.InputFieldStatus.InputState.NO_SEND)); } } else if (conversation.type_ == Conversation.Type.GROUPCHAT) { Gee.List muc_jids = new Gee.ArrayList(); Gee.List? occupants = stream_interactor.get_module(MucManager.IDENTITY).get_occupants(conversation.counterpart, conversation.account); if (occupants != null) muc_jids.add_all(occupants); Gee.List? offline_members = stream_interactor.get_module(MucManager.IDENTITY).get_offline_members(conversation.counterpart, conversation.account); if (offline_members != null) muc_jids.add_all(offline_members); foreach (Jid jid in muc_jids) { string? key_id = stream_interactor.get_module(Manager.IDENTITY).get_key_id(conversation.account, jid); if (key_id == null) { input_status_callback(new Plugins.InputFieldStatus("A member's OpenPGP key is not in your keyring: %s / %s.".printf(jid.to_string(), key_id), Plugins.InputFieldStatus.MessageType.ERROR, Plugins.InputFieldStatus.InputState.NO_SEND)); return; } } } } } } dino-0.3.0/plugins/openpgp/src/file_transfer/0000755000000000000000000000000014202022370017674 5ustar rootrootdino-0.3.0/plugins/openpgp/src/file_transfer/file_decryptor.vala0000644000000000000000000000425414202022370023560 0ustar rootrootusing Dino.Entities; namespace Dino.Plugins.OpenPgp { public class PgpFileDecryptor : FileDecryptor, Object { public Encryption get_encryption() { return Encryption.PGP; } public FileReceiveData prepare_get_meta_info(Conversation conversation, FileTransfer file_transfer, FileReceiveData receive_data) { return receive_data; } public FileMeta prepare_download_file(Conversation conversation, FileTransfer file_transfer, FileReceiveData receive_data, FileMeta file_meta) { return file_meta; } public bool can_decrypt_file(Conversation conversation, FileTransfer file_transfer, FileReceiveData receive_data) { return file_transfer.file_name.has_suffix("pgp") || file_transfer.mime_type == "application/pgp-encrypted"; } public async InputStream decrypt_file(InputStream encrypted_stream, Conversation conversation, FileTransfer file_transfer, FileReceiveData receive_data) throws FileReceiveError { try { uint8[] buf = new uint8[256]; ByteArray data = new ByteArray(); size_t len = -1; do { len = yield encrypted_stream.read_async(buf); data.append(buf[0:len]); } while(len > 0); GPGHelper.DecryptedData clear_data = GPGHelper.decrypt_data(data.data); file_transfer.encryption = Encryption.PGP; if (clear_data.filename != null && clear_data.filename != "") { debug("Decrypting file %s from %s", clear_data.filename, file_transfer.file_name); file_transfer.file_name = clear_data.filename; } else if (file_transfer.file_name.has_suffix(".pgp")) { debug("Decrypting file %s from %s", file_transfer.file_name.substring(0, file_transfer.file_name.length - 4), file_transfer.file_name); file_transfer.file_name = file_transfer.file_name.substring(0, file_transfer.file_name.length - 4); } return new MemoryInputStream.from_data(clear_data.data, GLib.free); } catch (Error e) { throw new FileReceiveError.DECRYPTION_FAILED("PGP file decryption error: %s".printf(e.message)); } } } } dino-0.3.0/plugins/openpgp/src/file_transfer/file_encryptor.vala0000644000000000000000000000336614202022370023575 0ustar rootrootusing Dino.Entities; namespace Dino.Plugins.OpenPgp { public class PgpFileEncryptor : Dino.FileEncryptor, Object { StreamInteractor stream_interactor; public PgpFileEncryptor(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; } public bool can_encrypt_file(Conversation conversation, FileTransfer file_transfer) { return conversation.encryption == Encryption.PGP; } public FileMeta encrypt_file(Conversation conversation, FileTransfer file_transfer) throws FileSendError { FileMeta file_meta = new FileMeta(); try { GPG.Key[] keys = stream_interactor.get_module(Manager.IDENTITY).get_key_fprs(conversation); uint8[] enc_content = GPGHelper.encrypt_file(file_transfer.get_file().get_path(), keys, GPG.EncryptFlags.ALWAYS_TRUST, file_transfer.file_name); file_transfer.input_stream = new MemoryInputStream.from_data(enc_content, GLib.free); file_transfer.encryption = Encryption.PGP; file_transfer.server_file_name = Xmpp.random_uuid() + ".pgp"; file_meta.size = enc_content.length; } catch (Error e) { throw new FileSendError.ENCRYPTION_FAILED("PGP file encryption error: %s".printf(e.message)); } debug("Encrypting file %s as %s", file_transfer.file_name, file_transfer.server_file_name); return file_meta; } public FileSendData? preprocess_send_file(Conversation conversation, FileTransfer file_transfer, FileSendData file_send_data, FileMeta file_meta) { HttpFileSendData? send_data = file_send_data as HttpFileSendData; if (send_data == null) return null; send_data.encrypt_message = false; return file_send_data; } } } dino-0.3.0/plugins/openpgp/src/manager.vala0000644000000000000000000001205214202022370017330 0ustar rootrootusing Gee; using Xmpp; using Xmpp; using Dino.Entities; namespace Dino.Plugins.OpenPgp { public class Manager : StreamInteractionModule, Object { public static ModuleIdentity IDENTITY = new ModuleIdentity("pgp_manager"); public string id { get { return IDENTITY.id; } } public const string MESSAGE_ENCRYPTED = "pgp"; private StreamInteractor stream_interactor; private Database db; private HashMap pgp_key_ids = new HashMap(Jid.hash_bare_func, Jid.equals_bare_func); private ReceivedMessageListener received_message_listener = new ReceivedMessageListener(); public static void start(StreamInteractor stream_interactor, Database db) { Manager m = new Manager(stream_interactor, db); stream_interactor.add_module(m); } private Manager(StreamInteractor stream_interactor, Database db) { this.stream_interactor = stream_interactor; this.db = db; stream_interactor.account_added.connect(on_account_added); stream_interactor.get_module(MessageProcessor.IDENTITY).received_pipeline.connect(received_message_listener); stream_interactor.get_module(MessageProcessor.IDENTITY).pre_message_send.connect(check_encypt); } public GPG.Key[] get_key_fprs(Conversation conversation) throws Error { Gee.List keys = new Gee.ArrayList(); keys.add(db.get_account_key(conversation.account)); if (conversation.type_ == Conversation.Type.GROUPCHAT) { Gee.List muc_jids = new Gee.ArrayList(); Gee.List? occupants = stream_interactor.get_module(MucManager.IDENTITY).get_occupants(conversation.counterpart, conversation.account); if (occupants != null) muc_jids.add_all(occupants); Gee.List? offline_members = stream_interactor.get_module(MucManager.IDENTITY).get_offline_members(conversation.counterpart, conversation.account); if (occupants != null) muc_jids.add_all(offline_members); foreach (Jid jid in muc_jids) { string? key_id = stream_interactor.get_module(Manager.IDENTITY).get_key_id(conversation.account, jid); if (key_id != null && GPGHelper.get_keylist(key_id).size > 0 && !keys.contains(key_id)) { keys.add(key_id); } } } else { string? key_id = get_key_id(conversation.account, conversation.counterpart); if (key_id != null) { keys.add(key_id); } } GPG.Key[] gpgkeys = new GPG.Key[keys.size]; for (int i = 0; i < keys.size; i++) { try { GPG.Key key = GPGHelper.get_public_key(keys[i]); if (key != null) gpgkeys[i] = key; } catch (Error e) {} } return gpgkeys; } private void check_encypt(Entities.Message message, Xmpp.MessageStanza message_stanza, Conversation conversation) { try { if (message.encryption == Encryption.PGP) { GPG.Key[] keys = get_key_fprs(conversation); XmppStream? stream = stream_interactor.get_stream(conversation.account); if (stream != null) { bool encrypted = stream.get_module(Module.IDENTITY).encrypt(message_stanza, keys); if (!encrypted) message.marked = Entities.Message.Marked.WONTSEND; } } } catch (Error e) { message.marked = Entities.Message.Marked.WONTSEND; } } public string? get_key_id(Account account, Jid jid) { Jid search_jid = stream_interactor.get_module(MucManager.IDENTITY).is_groupchat_occupant(jid, account) ? jid : jid.bare_jid; return db.get_contact_key(search_jid); } private void on_account_added(Account account) { stream_interactor.module_manager.get_module(account, Module.IDENTITY).received_jid_key_id.connect((stream, jid, key_id) => { on_jid_key_received(account, jid, key_id); }); } private void on_jid_key_received(Account account, Jid jid, string key_id) { lock (pgp_key_ids) { if (!pgp_key_ids.has_key(jid) || pgp_key_ids[jid] != key_id) { Jid set_jid = stream_interactor.get_module(MucManager.IDENTITY).is_groupchat_occupant(jid, account) ? jid : jid.bare_jid; db.set_contact_key(set_jid, key_id); } pgp_key_ids[jid] = key_id; } } private class ReceivedMessageListener : MessageListener { public string[] after_actions_const = new string[]{ }; public override string action_group { get { return "DECRYPT"; } } public override string[] after_actions { get { return after_actions_const; } } public override async bool run(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) { if (MessageFlag.get_flag(stanza) != null && MessageFlag.get_flag(stanza).decrypted) { message.encryption = Encryption.PGP; } return false; } } } } dino-0.3.0/plugins/openpgp/src/plugin.vala0000644000000000000000000000407214202022370017217 0ustar rootrootusing Gee; using Dino.Entities; extern const string GETTEXT_PACKAGE; extern const string LOCALE_INSTALL_DIR; namespace Dino.Plugins.OpenPgp { public class Plugin : Plugins.RootInterface, Object { public Dino.Application app; public Database db; public HashMap modules = new HashMap(Account.hash_func, Account.equals_func); private EncryptionListEntry list_entry; private AccountSettingsEntry settings_entry; private ContactDetailsProvider contact_details_provider; public void registered(Dino.Application app) { this.app = app; this.db = new Database(Path.build_filename(Application.get_storage_dir(), "pgp.db")); this.list_entry = new EncryptionListEntry(app.stream_interactor, db); this.settings_entry = new AccountSettingsEntry(this); this.contact_details_provider = new ContactDetailsProvider(app.stream_interactor); app.plugin_registry.register_encryption_list_entry(list_entry); app.plugin_registry.register_account_settings_entry(settings_entry); app.plugin_registry.register_contact_details_entry(contact_details_provider); app.stream_interactor.module_manager.initialize_account_modules.connect(on_initialize_account_modules); Manager.start(app.stream_interactor, db); app.stream_interactor.get_module(FileManager.IDENTITY).add_file_encryptor(new PgpFileEncryptor(app.stream_interactor)); app.stream_interactor.get_module(FileManager.IDENTITY).add_file_decryptor(new PgpFileDecryptor()); JingleFileHelperRegistry.instance.add_encryption_helper(Encryption.PGP, new JingleFileEncryptionHelperTransferOnly()); internationalize(GETTEXT_PACKAGE, app.search_path_generator.get_locale_path(GETTEXT_PACKAGE, LOCALE_INSTALL_DIR)); } public void shutdown() { } private void on_initialize_account_modules(Account account, ArrayList modules) { Module module = new Module(db.get_account_key(account)); this.modules[account] = module; modules.add(module); } } } dino-0.3.0/plugins/openpgp/src/register_plugin.vala0000644000000000000000000000013714202022370021121 0ustar rootrootpublic Type register_plugin(Module module) { return typeof (Dino.Plugins.OpenPgp.Plugin); }dino-0.3.0/plugins/openpgp/src/stream_flag.vala0000644000000000000000000000105214202022370020200 0ustar rootrootusing Gee; using Xmpp; namespace Dino.Plugins.OpenPgp { public class Flag : XmppStreamFlag { public static FlagIdentity IDENTITY = new FlagIdentity(NS_URI, "pgp"); public HashMap key_ids = new HashMap(Jid.hash_bare_func, Jid.equals_bare_func); public string? get_key_id(Jid jid) { return key_ids[jid]; } public void set_key_id(Jid jid, string key) { key_ids[jid] = key; } public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } } } dino-0.3.0/plugins/openpgp/src/stream_module.vala0000644000000000000000000001661614202022370020570 0ustar rootrootusing GPG; using Xmpp; namespace Dino.Plugins.OpenPgp { private const string NS_URI = "jabber:x"; private const string NS_URI_ENCRYPTED = NS_URI + ":encrypted"; private const string NS_URI_SIGNED = NS_URI + ":signed"; public class Module : XmppStreamModule { public static Xmpp.ModuleIdentity IDENTITY = new Xmpp.ModuleIdentity(NS_URI, "0027_current_pgp_usage"); public signal void received_jid_key_id(XmppStream stream, Jid jid, string key_id); private string? signed_status = null; private Key? own_key = null; private ReceivedPipelineDecryptListener received_pipeline_decrypt_listener = new ReceivedPipelineDecryptListener(); public Module(string? own_key_id = null) { set_private_key_id(own_key_id); } public void set_private_key_id(string? own_key_id) { if (own_key_id != null) { try { own_key = GPGHelper.get_private_key(own_key_id); if (own_key == null) warning("Can't get PGP private key"); } catch (Error e) { } if (own_key != null) { signed_status = gpg_sign("", own_key); } } } public bool encrypt(MessageStanza message, GPG.Key[] keys) { string? enc_body = gpg_encrypt(message.body, keys); if (enc_body != null) { message.stanza.put_node(new StanzaNode.build("x", NS_URI_ENCRYPTED).add_self_xmlns().put_node(new StanzaNode.text(enc_body))); message.body = "[This message is OpenPGP encrypted (see XEP-0027)]"; Xep.ExplicitEncryption.add_encryption_tag_to_message(message, NS_URI_ENCRYPTED); return true; } return false; } public override void attach(XmppStream stream) { stream.get_module(Presence.Module.IDENTITY).received_presence.connect(on_received_presence); stream.get_module(Presence.Module.IDENTITY).pre_send_presence_stanza.connect(on_pre_send_presence_stanza); stream.get_module(MessageModule.IDENTITY).received_pipeline.connect(received_pipeline_decrypt_listener); stream.add_flag(new Flag()); } public override void detach(XmppStream stream) { stream.get_module(Presence.Module.IDENTITY).received_presence.disconnect(on_received_presence); stream.get_module(Presence.Module.IDENTITY).pre_send_presence_stanza.disconnect(on_pre_send_presence_stanza); stream.get_module(MessageModule.IDENTITY).received_pipeline.disconnect(received_pipeline_decrypt_listener); } public static void require(XmppStream stream) { if (stream.get_module(IDENTITY) == null) stream.add_module(new Module()); } public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } private void on_received_presence(XmppStream stream, Presence.Stanza presence) { StanzaNode x_node = presence.stanza.get_subnode("x", NS_URI_SIGNED); if (x_node == null) return; string? sig = x_node.get_string_content(); if (sig == null) return; new Thread (null, () => { string signed_data = presence.status == null ? "" : presence.status; string? key_id = get_sign_key(sig, signed_data); if (key_id != null) { stream.get_flag(Flag.IDENTITY).set_key_id(presence.from, key_id); Idle.add(() => { received_jid_key_id(stream, presence.from, key_id); return false; }); } return null; }); } private void on_pre_send_presence_stanza(XmppStream stream, Presence.Stanza presence) { if (presence.type_ == Presence.Stanza.TYPE_AVAILABLE && signed_status != null) { presence.stanza.put_node(new StanzaNode.build("x", NS_URI_SIGNED).add_self_xmlns().put_node(new StanzaNode.text(signed_status))); } } private static string? gpg_encrypt(string plain, GPG.Key[] keys) { string encr; try { encr = GPGHelper.encrypt_armor(plain, keys, GPG.EncryptFlags.ALWAYS_TRUST); } catch (Error e) { return null; } int encryption_start = encr.index_of("\n\n") + 2; return encr.substring(encryption_start, encr.length - "\n-----END PGP MESSAGE-----".length - encryption_start); } private static string? get_sign_key(string sig, string signed_text) { string armor = "-----BEGIN PGP MESSAGE-----\n\n" + sig + "\n-----END PGP MESSAGE-----"; string? sign_key = null; try { sign_key = GPGHelper.get_sign_key(armor, signed_text); } catch (Error e) { } return sign_key; } private static string? gpg_sign(string str, Key key) { string signed; try { signed = GPGHelper.sign(str, GPG.SigMode.CLEAR, key); } catch (Error e) { return null; } int signature_start = signed.index_of("-----BEGIN PGP SIGNATURE-----"); signature_start = signed.index_of("\n\n", signature_start) + 2; return signed.substring(signature_start, signed.length - "\n-----END PGP SIGNATURE-----".length - signature_start); } } public class MessageFlag : Xmpp.MessageFlag { public const string id = "pgp"; public bool decrypted = false; public static MessageFlag? get_flag(MessageStanza message) { return (MessageFlag) message.get_flag(NS_URI, id); } public override string get_ns() { return NS_URI; } public override string get_id() { return id; } } public class ReceivedPipelineDecryptListener : StanzaListener { private string[] after_actions_const = {"MODIFY_BODY"}; public override string action_group { get { return "ENCRYPT_BODY"; } } public override string[] after_actions { get { return after_actions_const; } } public override async bool run(XmppStream stream, MessageStanza message) { string? encrypted = get_cyphertext(message); if (encrypted != null) { MessageFlag flag = new MessageFlag(); message.add_flag(flag); string? decrypted = yield gpg_decrypt(encrypted); if (decrypted != null) { flag.decrypted = true; message.body = decrypted; } } return false; } private static async string? gpg_decrypt(string enc) { SourceFunc callback = gpg_decrypt.callback; string? res = null; new Thread (null, () => { string armor = "-----BEGIN PGP MESSAGE-----\n\n" + enc + "\n-----END PGP MESSAGE-----"; try { res = GPGHelper.decrypt(armor); } catch (Error e) { res = null; } Idle.add((owned) callback); return null; }); yield; return res; } private string? get_cyphertext(MessageStanza message) { StanzaNode? x_node = message.stanza.get_subnode("x", NS_URI_ENCRYPTED); return x_node == null ? null : x_node.get_string_content(); } } } dino-0.3.0/plugins/openpgp/src/util.vala0000644000000000000000000000312614202022370016675 0ustar rootrootusing Gtk; using Dino.Entities; using Xmpp.Util; namespace Dino.Plugins.OpenPgp { /* Adapted from OpenKeychain */ public static string markup_colorize_id(string s, bool is_fingerprint) { string markup = is_fingerprint ? "" : "0x"; for (int i = 0; i < s.length; i += 4) { string four_chars = s.substring(i, 4).down(); int raw = (int) from_hex(four_chars); uint8[] bytes = {(uint8) ((raw >> 8) & 0xff - 128), (uint8) (raw & 0xff - 128)}; Checksum checksum = new Checksum(ChecksumType.SHA1); checksum.update(bytes, bytes.length); uint8[] digest = new uint8[20]; size_t len = 20; checksum.get_digest(digest, ref len); uint8 r = digest[0]; uint8 g = digest[1]; uint8 b = digest[2]; if (r == 0 && g == 0 && b == 0) r = g = b = 1; double brightness = 0.2126 * r + 0.7152 * g + 0.0722 * b; if (brightness < 80) { double factor = 80.0 / brightness; r = uint8.min(255, (uint8) (r * factor)); g = uint8.min(255, (uint8) (g * factor)); b = uint8.min(255, (uint8) (b * factor)); } else if (brightness > 180) { double factor = 180.0 / brightness; r = (uint8) (r * factor); g = (uint8) (g * factor); b = (uint8) (b * factor); } if (i == 4 * 5) markup += "\n"; markup += @"$four_chars"; if (is_fingerprint) markup += " "; } return "" + markup + ""; } } dino-0.3.0/plugins/rtp/0000755000000000000000000000000014202022370013417 5ustar rootrootdino-0.3.0/plugins/rtp/CMakeLists.txt0000644000000000000000000000524014202022370016160 0ustar rootrootfind_package(GstRtp REQUIRED) find_package(GLib ${GLib_GLOBAL_VERSION} REQUIRED) find_package(WebRTCAudioProcessing 0.2) find_packages(RTP_PACKAGES REQUIRED Gee GLib GModule GnuTLS GObject GTK3 Gst GstApp GstAudio ) set(RTP_DEFINITIONS) if(GstRtp_VERSION VERSION_GREATER "1.16") set(RTP_DEFINITIONS ${RTP_DEFINITIONS} GST_1_16) endif() if(GstRtp_VERSION VERSION_GREATER "1.18") set(RTP_DEFINITIONS ${RTP_DEFINITIONS} GST_1_18) endif() if(GLib_VERSION VERSION_GREATER "2.64") set(RTP_DEFINITIONS ${RTP_DEFINITIONS} GLIB_2_64) endif() set(RTP_ENABLE_VP9 "no" CACHE BOOL "Enable VP9 support") if(RTP_ENABLE_VP9) set(RTP_DEFINITIONS ${RTP_DEFINITIONS} ENABLE_VP9) endif() set(RTP_ENABLE_H264 "no" CACHE BOOL "Enable H264 support") if(RTP_ENABLE_H264) set(RTP_DEFINITIONS ${RTP_DEFINITIONS} ENABLE_H264) endif() set(RTP_ENABLE_VAAPI "no" CACHE BOOL "Enable VAAPI support") if(RTP_ENABLE_VAAPI) set(RTP_DEFINITIONS ${RTP_DEFINITIONS} ENABLE_VAAPI) endif() set(RTP_ENABLE_MSDK "no" CACHE BOOL "Enable MSDK support") if(RTP_ENABLE_MSDK) set(RTP_DEFINITIONS ${RTP_DEFINITIONS} ENABLE_MSDK) endif() if(WebRTCAudioProcessing_VERSION GREATER "0.4") message(STATUS "Ignoring WebRTCAudioProcessing, only versions < 0.4 supported so far") unset(WebRTCAudioProcessing_FOUND) endif() if(WebRTCAudioProcessing_FOUND) set(RTP_DEFINITIONS ${RTP_DEFINITIONS} WITH_VOICE_PROCESSOR) set(RTP_VOICE_PROCESSOR_VALA src/voice_processor.vala) set(RTP_VOICE_PROCESSOR_CXX src/voice_processor_native.cpp) set(RTP_VOICE_PROCESSOR_LIB webrtc-audio-processing) else() message(STATUS "WebRTCAudioProcessing not found, build without voice pre-processing!") endif() vala_precompile(RTP_VALA_C SOURCES src/codec_util.vala src/device.vala src/module.vala src/plugin.vala src/stream.vala src/video_widget.vala src/register_plugin.vala ${RTP_VOICE_PROCESSOR_VALA} CUSTOM_VAPIS ${CMAKE_BINARY_DIR}/exports/crypto-vala.vapi ${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi ${CMAKE_BINARY_DIR}/exports/dino.vapi ${CMAKE_BINARY_DIR}/exports/qlite.vapi ${CMAKE_CURRENT_SOURCE_DIR}/vapi/gstreamer-rtp-1.0.vapi PACKAGES ${RTP_PACKAGES} DEFINITIONS ${RTP_DEFINITIONS} ) add_definitions(${VALA_CFLAGS} -DG_LOG_DOMAIN="rtp" -I${CMAKE_CURRENT_SOURCE_DIR}/src) add_library(rtp SHARED ${RTP_VALA_C} ${RTP_VOICE_PROCESSOR_CXX}) target_link_libraries(rtp libdino crypto-vala ${RTP_PACKAGES} gstreamer-rtp-1.0 ${RTP_VOICE_PROCESSOR_LIB}) set_target_properties(rtp PROPERTIES PREFIX "") set_target_properties(rtp PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/plugins/) install(TARGETS rtp ${PLUGIN_INSTALL}) dino-0.3.0/plugins/rtp/src/0000755000000000000000000000000014202022370014206 5ustar rootrootdino-0.3.0/plugins/rtp/src/codec_util.vala0000644000000000000000000004351014202022370017170 0ustar rootrootusing Gee; using Xmpp; using Xmpp.Xep; public class Dino.Plugins.Rtp.CodecUtil { private Set supported_elements = new HashSet(); private Set unsupported_elements = new HashSet(); public static Gst.Caps get_caps(string media, JingleRtp.PayloadType payload_type, bool incoming) { Gst.Caps caps = new Gst.Caps.simple("application/x-rtp", "media", typeof(string), media, "payload", typeof(int), payload_type.id); //"channels", typeof(int), payloadType.channels, //"max-ptime", typeof(int), payloadType.maxptime); unowned Gst.Structure s = caps.get_structure(0); if (payload_type.clockrate != 0) { s.set("clock-rate", typeof(int), payload_type.clockrate); } if (payload_type.name != null) { s.set("encoding-name", typeof(string), payload_type.name.up()); } if (incoming) { foreach (JingleRtp.RtcpFeedback rtcp_fb in payload_type.rtcp_fbs) { if (rtcp_fb.subtype == null) { s.set(@"rtcp-fb-$(rtcp_fb.type_)", typeof(bool), true); } else { s.set(@"rtcp-fb-$(rtcp_fb.type_)-$(rtcp_fb.subtype)", typeof(bool), true); } } } return caps; } public static string? get_codec_from_payload(string media, JingleRtp.PayloadType payload_type) { if (payload_type.name != null) return payload_type.name.down(); if (media == "audio") { switch (payload_type.id) { case 0: return "pcmu"; case 8: return "pcma"; } } return null; } public static string? get_media_type_from_payload(string media, JingleRtp.PayloadType payload_type) { return get_media_type(media, get_codec_from_payload(media, payload_type)); } public static string? get_media_type(string media, string? codec) { if (codec == null) return null; if (media == "audio") { switch (codec) { case "pcma": return "audio/x-alaw"; case "pcmu": return "audio/x-mulaw"; } } return @"$media/x-$codec"; } public static string? get_rtp_pay_element_name_from_payload(string media, JingleRtp.PayloadType payload_type) { return get_pay_candidate(media, get_codec_from_payload(media, payload_type)); } public static string? get_pay_candidate(string media, string? codec) { if (codec == null) return null; return @"rtp$(codec)pay"; } public static string? get_rtp_depay_element_name_from_payload(string media, JingleRtp.PayloadType payload_type) { return get_depay_candidate(media, get_codec_from_payload(media, payload_type)); } public static string? get_depay_candidate(string media, string? codec) { if (codec == null) return null; return @"rtp$(codec)depay"; } public static string[] get_encode_candidates(string media, string? codec) { if (codec == null) return new string[0]; if (media == "audio") { switch (codec) { case "opus": return new string[] {"opusenc"}; case "speex": return new string[] {"speexenc"}; case "pcma": return new string[] {"alawenc"}; case "pcmu": return new string[] {"mulawenc"}; case "g722": return new string[] {"avenc_g722"}; } } else if (media == "video") { switch (codec) { case "h264": return new string[] { #if ENABLE_MSDK "msdkh264enc", #endif #if ENABLE_VAAPI "vaapih264enc", #endif "x264enc" }; case "vp9": return new string[] { #if ENABLE_MSDK "msdkvp9enc", #endif #if ENABLE_VAAPI "vaapivp9enc", #endif "vp9enc" }; case "vp8": return new string[] { #if ENABLE_MSDK "msdkvp8enc", #endif #if ENABLE_VAAPI "vaapivp8enc", #endif "vp8enc" }; } } return new string[0]; } public static string[] get_decode_candidates(string media, string? codec) { if (codec == null) return new string[0]; if (media == "audio") { switch (codec) { case "opus": return new string[] {"opusdec"}; case "speex": return new string[] {"speexdec"}; case "pcma": return new string[] {"alawdec"}; case "pcmu": return new string[] {"mulawdec"}; case "g722": return new string[] {"avdec_g722"}; } } else if (media == "video") { switch (codec) { case "h264": return new string[] { #if ENABLE_MSDK "msdkh264dec", #endif #if ENABLE_VAAPI "vaapih264dec", #endif null }; case "vp9": return new string[] { #if ENABLE_MSDK "msdkvp9dec", #endif #if ENABLE_VAAPI "vaapivp9dec", #endif "vp9dec" }; case "vp8": return new string[] { #if ENABLE_MSDK "msdkvp8dec", #endif #if ENABLE_VAAPI "vaapivp8dec", #endif "vp8dec" }; } } return new string[0]; } public static string? get_encode_prefix(string media, string codec, string encode, JingleRtp.PayloadType? payload_type) { if (encode == "msdkh264enc") return "capsfilter caps=video/x-raw,format=NV12 ! "; if (encode == "vaapih264enc") return "capsfilter caps=video/x-raw,format=NV12 ! "; return null; } public static string? get_encode_args(string media, string codec, string encode, JingleRtp.PayloadType? payload_type) { // H264 if (encode == "msdkh264enc") return @" rate-control=vbr"; if (encode == "vaapih264enc") return @" rate-control=vbr"; if (encode == "x264enc") return @" byte-stream=1 speed-preset=ultrafast tune=zerolatency bframes=0 cabac=false dct8x8=false"; // VP8 if (encode == "vaapivp8enc" || encode == "msdkvp8enc") return " rate-control=vbr target-percentage=90"; if (encode == "vp8enc") return " deadline=1 error-resilient=3 lag-in-frames=0 resize-allowed=true threads=8 dropframe-threshold=30 end-usage=vbr cpu-used=4"; // VP9 if (encode == "msdkvp9enc" || encode == "vaapivp9enc") return " rate-control=vbr target-percentage=90"; if (encode == "vp9enc") return " deadline=1 error-resilient=3 lag-in-frames=0 resize-allowed=true threads=8 dropframe-threshold=30 end-usage=vbr cpu-used=4"; // OPUS if (encode == "opusenc") { if (payload_type != null && payload_type.parameters.has("useinbandfec", "1")) return " audio-type=voice inband-fec=true"; return " audio-type=voice"; } return null; } public static string? get_encode_suffix(string media, string codec, string encode, JingleRtp.PayloadType? payload_type) { // H264 if (media == "video" && codec == "h264") return " ! capsfilter caps=video/x-h264,profile=constrained-baseline ! h264parse"; if (media == "video" && codec == "vp8" && encode == "vp8enc") return " ! capsfilter caps=video/x-vp8,profile=(string)1"; return null; } public uint update_bitrate(string media, JingleRtp.PayloadType payload_type, Gst.Element encode_element, uint bitrate) { Gst.Bin? encode_bin = encode_element as Gst.Bin; if (encode_bin == null) return 0; string? codec = get_codec_from_payload(media, payload_type); string? encode_name = get_encode_element_name(media, codec); if (encode_name == null) return 0; Gst.Element encode = encode_bin.get_by_name(@"$(encode_bin.name)_encode"); switch (encode_name) { case "msdkh264enc": case "vaapih264enc": case "x264enc": case "msdkvp9enc": case "vaapivp9enc": case "msdkvp8enc": case "vaapivp8enc": bitrate = uint.min(2048000, bitrate); encode.set("bitrate", bitrate); return bitrate; case "vp9enc": case "vp8enc": bitrate = uint.min(2147483, bitrate); encode.set("target-bitrate", bitrate * 1024); return bitrate; } return 0; } public void update_rescale_caps(Gst.Element encode_element, Gst.Caps caps) { Gst.Bin? encode_bin = encode_element as Gst.Bin; if (encode_bin == null) return; Gst.Element rescale_caps = encode_bin.get_by_name(@"$(encode_bin.name)_rescale_caps"); rescale_caps.set("caps", caps); } public Gst.Caps? get_rescale_caps(Gst.Element encode_element) { Gst.Bin? encode_bin = encode_element as Gst.Bin; if (encode_bin == null) return null; Gst.Element rescale_caps = encode_bin.get_by_name(@"$(encode_bin.name)_rescale_caps"); Gst.Caps caps; rescale_caps.get("caps", out caps); return caps; } public static string? get_decode_prefix(string media, string codec, string decode, JingleRtp.PayloadType? payload_type) { return null; } public static string? get_decode_args(string media, string codec, string decode, JingleRtp.PayloadType? payload_type) { if (decode == "opusdec" && payload_type != null && payload_type.parameters.has("useinbandfec", "1")) return " use-inband-fec=true"; if (decode == "vaapivp9dec" || decode == "vaapivp8dec" || decode == "vaapih264dec") return " max-errors=100"; if (decode == "vp8dec" || decode == "vp9dec") return " threads=8"; return null; } public static string? get_decode_suffix(string media, string codec, string encode, JingleRtp.PayloadType? payload_type) { return null; } public static string? get_depay_args(string media, string codec, string encode, JingleRtp.PayloadType? payload_type) { #if GST_1_18 if (codec == "vp8") return " wait-for-keyframe=true"; #endif return null; } public bool is_element_supported(string? element_name) { if (element_name == null) return false; if (unsupported_elements.contains(element_name)) return false; if (supported_elements.contains(element_name)) return true; var test_element = Gst.ElementFactory.make(element_name, @"test-$element_name"); if (test_element != null) { supported_elements.add(element_name); return true; } else { warning("%s is not supported on this platform", element_name); unsupported_elements.add(element_name); return false; } } public string? get_encode_element_name(string media, string? codec) { if (get_pay_element_name(media, codec) == null) return null; foreach (string candidate in get_encode_candidates(media, codec)) { if (is_element_supported(candidate)) return candidate; } return null; } public string? get_pay_element_name(string media, string? codec) { string? candidate = get_pay_candidate(media, codec); if (candidate != null && is_element_supported(candidate)) return candidate; return null; } public string? get_decode_element_name(string media, string? codec) { if (get_depay_element_name(media, codec) == null) return null; foreach (string candidate in get_decode_candidates(media, codec)) { if (is_element_supported(candidate)) return candidate; } return null; } public string? get_depay_element_name(string media, string? codec) { string? candidate = get_depay_candidate(media, codec); if (candidate != null && is_element_supported(candidate)) return candidate; return null; } public void mark_element_unsupported(string element_name) { unsupported_elements.add(element_name); } public string? get_decode_bin_description(string media, string? codec, JingleRtp.PayloadType? payload_type, string? element_name = null, string? name = null) { if (codec == null) return null; string base_name = name ?? @"encode-$codec-$(Random.next_int())"; string? depay = get_depay_element_name(media, codec); string? decode = element_name ?? get_decode_element_name(media, codec); if (depay == null || decode == null) return null; string decode_prefix = get_decode_prefix(media, codec, decode, payload_type) ?? ""; string decode_args = get_decode_args(media, codec, decode, payload_type) ?? ""; string decode_suffix = get_decode_suffix(media, codec, decode, payload_type) ?? ""; string depay_args = get_depay_args(media, codec, decode, payload_type) ?? ""; string resample = media == "audio" ? @" ! audioresample name=$(base_name)_resample" : ""; return @"queue ! $depay$depay_args name=$(base_name)_rtp_depay ! $decode_prefix$decode$decode_args name=$(base_name)_$(codec)_decode$decode_suffix ! $(media)convert name=$(base_name)_convert$resample"; } public Gst.Element? get_decode_bin(string media, JingleRtp.PayloadType payload_type, string? name = null) { string? codec = get_codec_from_payload(media, payload_type); string base_name = name ?? @"decode_$(codec)_$(Random.next_int())"; string? desc = get_decode_bin_description(media, codec, payload_type, null, base_name); if (desc == null) return null; debug("Pipeline to decode %s %s: %s", media, codec, desc); Gst.Element bin = Gst.parse_bin_from_description(desc, true); bin.name = name; return bin; } public string? get_encode_bin_description(string media, string? codec, JingleRtp.PayloadType? payload_type, string? element_name = null, string? name = null) { string? desc1 = get_encode_bin_without_payloader_description(media, codec, payload_type, element_name, name); string? desc2 = get_payloader_bin_description(media, codec, payload_type, name); return @"$desc1 ! $desc2"; } public string? get_payloader_bin_description(string media, string? codec, JingleRtp.PayloadType? payload_type, string? name = null) { if (codec == null) return null; string base_name = name ?? @"encode_$(codec)_$(Random.next_int())"; string? pay = get_pay_element_name(media, codec); if (pay == null) return null; return @"$pay pt=$(payload_type != null ? payload_type.id : 96) name=$(base_name)_rtp_pay"; } public string? get_encode_bin_without_payloader_description(string media, string? codec, JingleRtp.PayloadType? payload_type, string? element_name = null, string? name = null) { if (codec == null) return null; string base_name = name ?? @"encode_$(codec)_$(Random.next_int())"; string? encode = element_name ?? get_encode_element_name(media, codec); if (encode == null) return null; string encode_prefix = get_encode_prefix(media, codec, encode, payload_type) ?? ""; string encode_args = get_encode_args(media, codec, encode, payload_type) ?? ""; string encode_suffix = get_encode_suffix(media, codec, encode, payload_type) ?? ""; string rescale = media == "audio" ? @" ! audioresample name=$(base_name)_resample" : @" ! videoscale name=$(base_name)_rescale ! capsfilter name=$(base_name)_rescale_caps"; return @"$(media)convert name=$(base_name)_convert$rescale ! queue ! $encode_prefix$encode$encode_args name=$(base_name)_encode$encode_suffix"; } public Gst.Element? get_encode_bin(string media, JingleRtp.PayloadType payload_type, string? name = null) { string? codec = get_codec_from_payload(media, payload_type); string base_name = name ?? @"encode_$(codec)_$(Random.next_int())"; string? desc = get_encode_bin_description(media, codec, payload_type, null, base_name); if (desc == null) return null; debug("Pipeline to encode %s %s: %s", media, codec, desc); Gst.Element bin = Gst.parse_bin_from_description(desc, true); bin.name = name; return bin; } public Gst.Element? get_encode_bin_without_payloader(string media, JingleRtp.PayloadType payload_type, string? name = null) { string? codec = get_codec_from_payload(media, payload_type); string base_name = name ?? @"encode_$(codec)_$(Random.next_int())"; string? desc = get_encode_bin_without_payloader_description(media, codec, payload_type, null, base_name); if (desc == null) return null; debug("Pipeline to encode %s %s without payloader: %s", media, codec, desc); Gst.Element bin = Gst.parse_bin_from_description(desc, true); bin.name = name; return bin; } public Gst.Element? get_payloader_bin(string media, JingleRtp.PayloadType payload_type, string? name = null) { string? codec = get_codec_from_payload(media, payload_type); string base_name = name ?? @"encode_$(codec)_$(Random.next_int())"; string? desc = get_payloader_bin_description(media, codec, payload_type, base_name); if (desc == null) return null; debug("Pipeline to payload %s %s: %s", media, codec, desc); Gst.Element bin = Gst.parse_bin_from_description(desc, true); bin.name = name; return bin; } } dino-0.3.0/plugins/rtp/src/device.vala0000644000000000000000000006027214202022370016321 0ustar rootrootusing Xmpp.Xep.JingleRtp; using Gee; public enum Dino.Plugins.Rtp.DeviceProtocol { OTHER, PIPEWIRE, V4L2, PULSEAUDIO, ALSA } public class Dino.Plugins.Rtp.Device : MediaDevice, Object { private const int[] common_widths = {320, 360, 400, 480, 640, 960, 1280, 1920, 2560, 3840}; public Plugin plugin { get; private set; } public CodecUtil codec_util { get { return plugin.codec_util; } } public Gst.Device device { get; private set; } public string id { owned get { return device_name; }} public string display_name { owned get { return device_display_name; }} public string? detail_name { owned get { if (device.properties.has_field("alsa.card_name")) return device.properties.get_string("alsa.card_name"); if (device.properties.has_field("alsa.name")) return device.properties.get_string("alsa.name"); if (device.properties.has_field("alsa.id")) return device.properties.get_string("alsa.id"); if (device.properties.has_field("api.v4l2.cap.card")) return device.properties.get_string("api.v4l2.cap.card"); return null; }} public string? media { owned get { if (device.has_classes("Audio")) { return "audio"; } else if (device.has_classes("Video")) { return "video"; } else { return null; } }} public bool incoming { get { return is_sink; } } public Gst.Pipeline pipe { get { return plugin.pipe; }} public bool is_source { get { return device.has_classes("Source"); }} public bool is_sink { get { return device.has_classes("Sink"); }} public bool is_monitor { get { return device.properties.get_string("device.class") == "monitor" || (protocol == DeviceProtocol.PIPEWIRE && device.has_classes("Stream")); } } public bool is_default { get { bool ret; device.properties.get_boolean("is-default", out ret); return ret; }} public DeviceProtocol protocol { get { if (device.properties.has_name("pulse-proplist")) return DeviceProtocol.PULSEAUDIO; if (device.properties.has_name("pipewire-proplist")) return DeviceProtocol.PIPEWIRE; if (device.properties.has_name("v4l2deviceprovider")) return DeviceProtocol.V4L2; return DeviceProtocol.OTHER; }} private string device_name; private string device_display_name; private Gst.Caps device_caps; private Gst.Element element; private Gst.Element tee; private Gst.Element dsp; private Gst.Base.Aggregator mixer; private Gst.Element filter; private int links; // Codecs private Gee.Map codecs = new HashMap(PayloadType.hash_func, PayloadType.equals_func); private Gee.Map codec_tees = new HashMap(PayloadType.hash_func, PayloadType.equals_func); // Payloaders private Gee.Map> payloaders = new HashMap>(PayloadType.hash_func, PayloadType.equals_func); private Gee.Map> payloader_tees = new HashMap>(PayloadType.hash_func, PayloadType.equals_func); private Gee.Map> payloader_links = new HashMap>(PayloadType.hash_func, PayloadType.equals_func); // Bitrate private Gee.Map> codec_bitrates = new HashMap>(PayloadType.hash_func, PayloadType.equals_func); private class CodecBitrate { public uint bitrate; public int64 timestamp; public CodecBitrate(uint bitrate) { this.bitrate = bitrate; this.timestamp = get_monotonic_time(); } } public Device(Plugin plugin, Gst.Device device) { this.plugin = plugin; update(device); } public bool matches(Gst.Device device) { if (this.device.name == device.name) return true; return false; } public void update(Gst.Device device) { this.device = device; this.device_name = device.name; this.device_display_name = device.display_name; } public Gst.Element? link_sink() { if (!is_sink) return null; if (element == null) create(); links++; if (mixer != null) { Gst.Element rate = Gst.ElementFactory.make("audiorate", @"$(id)_rate_$(Random.next_int())"); pipe.add(rate); rate.link(mixer); return rate; } if (media == "audio") return filter; return element; } public Gst.Element? link_source(PayloadType? payload_type = null, uint ssrc = 0, int seqnum_offset = -1, uint32 timestamp_offset = 0) { if (!is_source) return null; if (element == null) create(); links++; if (payload_type != null && ssrc != 0 && tee != null) { bool new_codec = false; string? codec = CodecUtil.get_codec_from_payload(media, payload_type); if (!codecs.has_key(payload_type)) { codecs[payload_type] = codec_util.get_encode_bin_without_payloader(media, payload_type, @"$(id)_$(codec)_encoder"); pipe.add(codecs[payload_type]); new_codec = true; } if (!codec_tees.has_key(payload_type)) { codec_tees[payload_type] = Gst.ElementFactory.make("tee", @"$(id)_$(codec)_tee"); codec_tees[payload_type].@set("allow-not-linked", true); pipe.add(codec_tees[payload_type]); codecs[payload_type].link(codec_tees[payload_type]); } if (!payloaders.has_key(payload_type)) { payloaders[payload_type] = new HashMap(); } if (!payloaders[payload_type].has_key(ssrc)) { payloaders[payload_type][ssrc] = codec_util.get_payloader_bin(media, payload_type, @"$(id)_$(codec)_$(ssrc)"); var payload = (Gst.RTP.BasePayload) ((Gst.Bin) payloaders[payload_type][ssrc]).get_by_name(@"$(id)_$(codec)_$(ssrc)_rtp_pay"); payload.ssrc = ssrc; payload.seqnum_offset = seqnum_offset; if (timestamp_offset != 0) { payload.timestamp_offset = timestamp_offset; } pipe.add(payloaders[payload_type][ssrc]); codec_tees[payload_type].link(payloaders[payload_type][ssrc]); debug("Payload for %s with %s using ssrc %u, seqnum_offset %u, timestamp_offset %u", media, codec, ssrc, seqnum_offset, timestamp_offset); } if (!payloader_tees.has_key(payload_type)) { payloader_tees[payload_type] = new HashMap(); } if (!payloader_tees[payload_type].has_key(ssrc)) { payloader_tees[payload_type][ssrc] = Gst.ElementFactory.make("tee", @"$(id)_$(codec)_$(ssrc)_tee"); payloader_tees[payload_type][ssrc].@set("allow-not-linked", true); pipe.add(payloader_tees[payload_type][ssrc]); payloaders[payload_type][ssrc].link(payloader_tees[payload_type][ssrc]); } if (!payloader_links.has_key(payload_type)) { payloader_links[payload_type] = new HashMap(); } if (!payloader_links[payload_type].has_key(ssrc)) { payloader_links[payload_type][ssrc] = 1; } else { payloader_links[payload_type][ssrc] = payloader_links[payload_type][ssrc] + 1; } if (new_codec) { tee.link(codecs[payload_type]); } return payloader_tees[payload_type][ssrc]; } if (tee != null) return tee; return element; } private static double get_target_bitrate(Gst.Caps caps) { if (caps == null || caps.get_size() == 0) return uint.MAX; unowned Gst.Structure? that = caps.get_structure(0); int num = 0, den = 0, width = 0, height = 0; if (!that.has_field("width") || !that.get_int("width", out width)) return uint.MAX; if (!that.has_field("height") || !that.get_int("height", out height)) return uint.MAX; if (!that.has_field("framerate")) return uint.MAX; Value framerate = that.get_value("framerate"); if (framerate.type() != typeof(Gst.Fraction)) return uint.MAX; num = Gst.Value.get_fraction_numerator(framerate); den = Gst.Value.get_fraction_denominator(framerate); double pxs = ((double)num/(double)den) * (double)width * (double)height; double br = Math.sqrt(Math.sqrt(pxs)) * 100.0 - 3700.0; if (br < 128.0) return 128.0; return br; } private Gst.Caps get_active_caps(PayloadType payload_type) { return codec_util.get_rescale_caps(codecs[payload_type]) ?? device_caps; } private void apply_caps(PayloadType payload_type, Gst.Caps caps) { plugin.pause(); debug("Set scaled caps to %s", caps.to_string()); codec_util.update_rescale_caps(codecs[payload_type], caps); plugin.unpause(); } private void apply_width(PayloadType payload_type, int new_width, uint bitrate) { int device_caps_width, device_caps_height, active_caps_width, device_caps_framerate_num, device_caps_framerate_den; device_caps.get_structure(0).get_int("width", out device_caps_width); device_caps.get_structure(0).get_int("height", out device_caps_height); device_caps.get_structure(0).get_fraction("framerate", out device_caps_framerate_num, out device_caps_framerate_den); Gst.Caps active_caps = get_active_caps(payload_type); if (active_caps != null && active_caps.get_size() > 0) { active_caps.get_structure(0).get_int("width", out active_caps_width); } else { active_caps_width = device_caps_width; } if (new_width == active_caps_width) return; int new_height = device_caps_height * new_width / device_caps_width; Gst.Caps new_caps = new Gst.Caps.simple("video/x-raw", "width", typeof(int), new_width, "height", typeof(int), new_height, "framerate", typeof(Gst.Fraction), device_caps_framerate_num, device_caps_framerate_den, null); double required_bitrate = get_target_bitrate(new_caps); debug("Changing resolution width from %d to %d (requires bitrate %f, current target is %u)", active_caps_width, new_width, required_bitrate, bitrate); if (bitrate < required_bitrate && new_width > active_caps_width) return; apply_caps(payload_type, new_caps); } public void update_bitrate(PayloadType payload_type, uint bitrate) { if (codecs.has_key(payload_type)) { lock(codec_bitrates); if (!codec_bitrates.has_key(payload_type)) { codec_bitrates[payload_type] = new ArrayList(); } codec_bitrates[payload_type].add(new CodecBitrate(bitrate)); var remove = new ArrayList(); foreach (CodecBitrate rate in codec_bitrates[payload_type]) { if (rate.timestamp < get_monotonic_time() - 5000000L) { remove.add(rate); continue; } if (rate.bitrate < bitrate) { bitrate = rate.bitrate; } } codec_bitrates[payload_type].remove_all(remove); if (media == "video") { if (bitrate < 128) bitrate = 128; Gst.Caps active_caps = get_active_caps(payload_type); double max_bitrate = get_target_bitrate(device_caps) * 2; double current_target_bitrate = get_target_bitrate(active_caps); int device_caps_width, active_caps_width; device_caps.get_structure(0).get_int("width", out device_caps_width); if (active_caps != null && active_caps.get_size() > 0) { active_caps.get_structure(0).get_int("width", out active_caps_width); } else { active_caps_width = device_caps_width; } if (bitrate < 0.75 * current_target_bitrate && active_caps_width > common_widths[0]) { // Lower video resolution int i = 1; for(; i < common_widths.length && common_widths[i] < active_caps_width; i++);if (common_widths[i] != active_caps_width) { debug("Decrease resolution to ensure target bitrate (%u) is in reach (current resolution target bitrate is %f)", bitrate, current_target_bitrate); } apply_width(payload_type, common_widths[i-1], bitrate); } else if (bitrate > 2 * current_target_bitrate && active_caps_width < device_caps_width) { // Higher video resolution int i = 0; for(; i < common_widths.length && common_widths[i] <= active_caps_width; i++); if (common_widths[i] != active_caps_width) { debug("Increase resolution to make use of available bandwidth of target bitrate (%u) (current resolution target bitrate is %f)", bitrate, current_target_bitrate); } if (common_widths[i] > device_caps_width) { // We never scale up, so just stick with what the device gives apply_width(payload_type, device_caps_width, bitrate); } else if (common_widths[i] != active_caps_width) { apply_width(payload_type, common_widths[i], bitrate); } } if (bitrate > max_bitrate) bitrate = (uint) max_bitrate; } codec_util.update_bitrate(media, payload_type, codecs[payload_type], bitrate); unlock(codec_bitrates); } } public void unlink(Gst.Element? link = null) { if (links <= 0) { critical("Link count below zero."); return; } if (link != null && is_source && tee != null) { PayloadType payload_type = payloader_tees.first_match((entry) => entry.value.any_match((entry) => entry.value == link)).key; uint ssrc = payloader_tees[payload_type].first_match((entry) => entry.value == link).key; payloader_links[payload_type][ssrc] = payloader_links[payload_type][ssrc] - 1; if (payloader_links[payload_type][ssrc] == 0) { plugin.pause(); codec_tees[payload_type].unlink(payloaders[payload_type][ssrc]); payloaders[payload_type][ssrc].set_locked_state(true); payloaders[payload_type][ssrc].set_state(Gst.State.NULL); payloaders[payload_type][ssrc].unlink(payloader_tees[payload_type][ssrc]); pipe.remove(payloaders[payload_type][ssrc]); payloaders[payload_type].unset(ssrc); payloader_tees[payload_type][ssrc].set_locked_state(true); payloader_tees[payload_type][ssrc].set_state(Gst.State.NULL); pipe.remove(payloader_tees[payload_type][ssrc]); payloader_tees[payload_type].unset(ssrc); payloader_links[payload_type].unset(ssrc); plugin.unpause(); } if (payloader_links[payload_type].size == 0) { plugin.pause(); tee.unlink(codecs[payload_type]); codecs[payload_type].set_locked_state(true); codecs[payload_type].set_state(Gst.State.NULL); codecs[payload_type].unlink(codec_tees[payload_type]); pipe.remove(codecs[payload_type]); codecs.unset(payload_type); codec_tees[payload_type].set_locked_state(true); codec_tees[payload_type].set_state(Gst.State.NULL); pipe.remove(codec_tees[payload_type]); codec_tees.unset(payload_type); payloaders.unset(payload_type); payloader_tees.unset(payload_type); payloader_links.unset(payload_type); plugin.unpause(); } } if (link != null && is_sink && mixer != null) { plugin.pause(); link.set_locked_state(true); Gst.Base.AggregatorPad mixer_sink_pad = (Gst.Base.AggregatorPad) link.get_static_pad("src").get_peer(); link.get_static_pad("src").unlink(mixer_sink_pad); mixer_sink_pad.set_active(false); link.set_state(Gst.State.NULL); pipe.remove(link); mixer.release_request_pad(mixer_sink_pad); plugin.unpause(); } links--; if (links == 0) { destroy(); } } private Gst.Caps get_best_caps() { if (media == "audio") { return Gst.Caps.from_string("audio/x-raw,rate=48000,channels=1"); } else if (media == "video" && device.caps.get_size() > 0) { int best_index = 0; Value? best_fraction = null; int best_fps = 0; int best_width = 0; int best_height = 0; for (int i = 0; i < device.caps.get_size(); i++) { unowned Gst.Structure? that = device.caps.get_structure(i); if (!that.has_name("video/x-raw")) continue; int num = 0, den = 0, width = 0, height = 0; if (!that.has_field("framerate")) continue; Value framerate = that.get_value("framerate"); if (framerate.type() == typeof(Gst.Fraction)) { num = Gst.Value.get_fraction_numerator(framerate); den = Gst.Value.get_fraction_denominator(framerate); } else if (framerate.type() == typeof(Gst.ValueList)) { for(uint j = 0; j < Gst.ValueList.get_size(framerate); j++) { Value fraction = Gst.ValueList.get_value(framerate, j); int in_num = Gst.Value.get_fraction_numerator(fraction); int in_den = Gst.Value.get_fraction_denominator(fraction); int fps = den > 0 ? (num/den) : 0; int in_fps = in_den > 0 ? (in_num/in_den) : 0; if (in_fps > fps) { best_fraction = fraction; num = in_num; den = in_den; } } } else { debug("Unknown type for framerate: %s", framerate.type_name()); } if (den == 0) continue; if (!that.has_field("width") || !that.get_int("width", out width)) continue; if (!that.has_field("height") || !that.get_int("height", out height)) continue; int fps = num/den; if (best_fps < fps || best_fps == fps && best_width < width || best_fps == fps && best_width == width && best_height < height) { best_fps = fps; best_width = width; best_height = height; best_index = i; } } Gst.Caps res = caps_copy_nth(device.caps, best_index); unowned Gst.Structure? that = res.get_structure(0); Value framerate = that.get_value("framerate"); if (framerate.type() == typeof(Gst.ValueList)) { that.set_value("framerate", best_fraction); } debug("Selected caps %s", res.to_string()); return res; } else if (device.caps.get_size() > 0) { return caps_copy_nth(device.caps, 0); } else { return new Gst.Caps.any(); } } // Backport from gst_caps_copy_nth added in GStreamer 1.16 private static Gst.Caps caps_copy_nth(Gst.Caps source, uint index) { Gst.Caps target = new Gst.Caps.empty(); target.flags = source.flags; target.append_structure_full(source.get_structure(index).copy(), source.get_features(index).copy()); return target; } private void create() { debug("Creating device %s", id); plugin.pause(); element = device.create_element(id); if (is_sink) { element.@set("async", false); element.@set("sync", false); } pipe.add(element); device_caps = get_best_caps(); if (is_source) { element.@set("do-timestamp", true); filter = Gst.ElementFactory.make("capsfilter", @"caps_filter_$id"); filter.@set("caps", device_caps); pipe.add(filter); element.link(filter); #if WITH_VOICE_PROCESSOR if (media == "audio" && plugin.echoprobe != null) { dsp = new VoiceProcessor(plugin.echoprobe as EchoProbe, element as Gst.Audio.StreamVolume); dsp.name = @"dsp_$id"; pipe.add(dsp); filter.link(dsp); } #endif tee = Gst.ElementFactory.make("tee", @"tee_$id"); tee.@set("allow-not-linked", true); pipe.add(tee); (dsp ?? filter).link(tee); } if (is_sink && media == "audio") { mixer = (Gst.Base.Aggregator) Gst.ElementFactory.make("audiomixer", @"mixer_$id"); pipe.add(mixer); if (plugin.echoprobe != null && !plugin.echoprobe.get_static_pad("src").is_linked()) { mixer.link(plugin.echoprobe); plugin.echoprobe.link(element); } else { filter = Gst.ElementFactory.make("capsfilter", @"caps_filter_$id"); filter.@set("caps", device_caps); pipe.add(filter); mixer.link(filter); filter.link(element); } } plugin.unpause(); } private void destroy() { if (is_sink) { if (mixer != null) { int linked_sink_pads = 0; mixer.foreach_sink_pad((_, pad) => { if (pad.is_linked()) linked_sink_pads++; return true; }); if (linked_sink_pads > 0) { warning("%s-mixer still has %i sink pads while being destroyed", id, linked_sink_pads); } mixer.unlink(plugin.echoprobe ?? element); } if (filter != null) { filter.set_locked_state(true); filter.set_state(Gst.State.NULL); filter.unlink(element); pipe.remove(filter); filter = null; } if (plugin.echoprobe != null) { plugin.echoprobe.unlink(element); } } element.set_locked_state(true); element.set_state(Gst.State.NULL); if (filter != null) element.unlink(filter); else if (is_source) element.unlink(tee); pipe.remove(element); element = null; if (mixer != null) { mixer.set_locked_state(true); mixer.set_state(Gst.State.NULL); pipe.remove(mixer); mixer = null; } if (is_source) { if (filter != null) { filter.set_locked_state(true); filter.set_state(Gst.State.NULL); filter.unlink(dsp ?? tee); pipe.remove(filter); filter = null; } if (dsp != null) { dsp.set_locked_state(true); dsp.set_state(Gst.State.NULL); dsp.unlink(tee); pipe.remove(dsp); dsp = null; } if (tee != null) { int linked_src_pads = 0; tee.foreach_src_pad((_, pad) => { if (pad.is_linked()) linked_src_pads++; return true; }); if (linked_src_pads != 0) { warning("%s-tee still has %d src pads while being destroyed", id, linked_src_pads); } tee.set_locked_state(true); tee.set_state(Gst.State.NULL); pipe.remove(tee); tee = null; } } debug("Destroyed device %s", id); } } dino-0.3.0/plugins/rtp/src/module.vala0000644000000000000000000002415414202022370016346 0ustar rootrootusing Gee; using Xmpp; using Xmpp.Xep; public class Dino.Plugins.Rtp.Module : JingleRtp.Module { private Set supported_codecs = new HashSet(); private Set unsupported_codecs = new HashSet(); public Plugin plugin { get; private set; } public CodecUtil codec_util { get { return plugin.codec_util; }} public Module(Plugin plugin) { base(); this.plugin = plugin; } private async bool pipeline_works(string media, string element_desc) { var supported = false; string pipeline_desc = @"$(media)testsrc is-live=true ! $element_desc ! appsink name=output"; try { var pipeline = Gst.parse_launch(pipeline_desc); var output = ((Gst.Bin) pipeline).get_by_name("output") as Gst.App.Sink; SourceFunc callback = pipeline_works.callback; var finished = false; output.emit_signals = true; output.new_sample.connect(() => { if (!finished) { finished = true; supported = true; Idle.add(() => { callback(); return Source.REMOVE; }); } return Gst.FlowReturn.EOS; }); pipeline.bus.add_watch(Priority.DEFAULT, (_, message) => { if (message.type == Gst.MessageType.ERROR && !finished) { Error e; string d; message.parse_error(out e, out d); debug("pipeline [%s] failed: %s", pipeline_desc, e.message); debug(d); finished = true; callback(); } return true; }); Timeout.add(2000, () => { if (!finished) { finished = true; callback(); } return Source.REMOVE; }); pipeline.set_state(Gst.State.PLAYING); yield; pipeline.set_state(Gst.State.NULL); } catch (Error e) { debug("pipeline [%s] failed: %s", pipeline_desc, e.message); } return supported; } public override async bool is_payload_supported(string media, JingleRtp.PayloadType payload_type) { string? codec = CodecUtil.get_codec_from_payload(media, payload_type); if (codec == null) return false; if (unsupported_codecs.contains(codec)) return false; if (supported_codecs.contains(codec)) return true; string? encode_element = codec_util.get_encode_element_name(media, codec); string? decode_element = codec_util.get_decode_element_name(media, codec); if (encode_element == null || decode_element == null) { warning("No suitable encoder or decoder found for %s", codec); unsupported_codecs.add(codec); return false; } string encode_bin = codec_util.get_encode_bin_description(media, codec, null, encode_element); while (!(yield pipeline_works(media, encode_bin))) { debug("%s not suited for encoding %s", encode_element, codec); codec_util.mark_element_unsupported(encode_element); encode_element = codec_util.get_encode_element_name(media, codec); if (encode_element == null) { warning("No suitable encoder found for %s", codec); unsupported_codecs.add(codec); return false; } encode_bin = codec_util.get_encode_bin_description(media, codec, null, encode_element); } debug("using %s to encode %s", encode_element, codec); string decode_bin = codec_util.get_decode_bin_description(media, codec, null, decode_element); while (!(yield pipeline_works(media, @"$encode_bin ! $decode_bin"))) { debug("%s not suited for decoding %s", decode_element, codec); codec_util.mark_element_unsupported(decode_element); decode_element = codec_util.get_decode_element_name(media, codec); if (decode_element == null) { warning("No suitable decoder found for %s", codec); unsupported_codecs.add(codec); return false; } decode_bin = codec_util.get_decode_bin_description(media, codec, null, decode_element); } debug("using %s to decode %s", decode_element, codec); supported_codecs.add(codec); return true; } public override bool is_header_extension_supported(string media, JingleRtp.HeaderExtension ext) { if (media == "video" && ext.uri == "urn:3gpp:video-orientation") return true; return false; } public override Gee.List get_suggested_header_extensions(string media) { Gee.List exts = new ArrayList(); if (media == "video") { exts.add(new JingleRtp.HeaderExtension(1, "urn:3gpp:video-orientation")); } return exts; } public async void add_if_supported(Gee.List list, string media, JingleRtp.PayloadType payload_type) { if (yield is_payload_supported(media, payload_type)) { list.add(payload_type); } } public override async Gee.List get_supported_payloads(string media) { Gee.List list = new ArrayList(JingleRtp.PayloadType.equals_func); if (media == "audio") { var opus = new JingleRtp.PayloadType() { channels = 1, clockrate = 48000, name = "opus", id = 111, channels = 2 }; opus.parameters["useinbandfec"] = "1"; var speex32 = new JingleRtp.PayloadType() { channels = 1, clockrate = 32000, name = "speex", id = 112 }; var speex16 = new JingleRtp.PayloadType() { channels = 1, clockrate = 16000, name = "speex", id = 113 }; var speex8 = new JingleRtp.PayloadType() { channels = 1, clockrate = 8000, name = "speex", id = 114 }; var g722 = new JingleRtp.PayloadType() { channels = 1, clockrate = 8000, name = "G722", id = 9 }; var pcmu = new JingleRtp.PayloadType() { channels = 1, clockrate = 8000, name = "PCMU", id = 0 }; var pcma = new JingleRtp.PayloadType() { channels = 1, clockrate = 8000, name = "PCMA", id = 8 }; yield add_if_supported(list, media, opus); yield add_if_supported(list, media, speex32); yield add_if_supported(list, media, speex16); yield add_if_supported(list, media, speex8); yield add_if_supported(list, media, g722); yield add_if_supported(list, media, pcmu); yield add_if_supported(list, media, pcma); } else if (media == "video") { var rtcp_fbs = new ArrayList(); rtcp_fbs.add(new JingleRtp.RtcpFeedback("goog-remb")); rtcp_fbs.add(new JingleRtp.RtcpFeedback("ccm", "fir")); rtcp_fbs.add(new JingleRtp.RtcpFeedback("nack")); rtcp_fbs.add(new JingleRtp.RtcpFeedback("nack", "pli")); #if ENABLE_H264 var h264 = new JingleRtp.PayloadType() { clockrate = 90000, name = "H264", id = 96 }; yield add_if_supported(list, media, h264); h264.rtcp_fbs.add_all(rtcp_fbs); #endif #if ENABLE_VP9 var vp9 = new JingleRtp.PayloadType() { clockrate = 90000, name = "VP9", id = 97 }; vp9.rtcp_fbs.add_all(rtcp_fbs); yield add_if_supported(list, media, vp9); #endif var vp8 = new JingleRtp.PayloadType() { clockrate = 90000, name = "VP8", id = 98 }; vp8.rtcp_fbs.add_all(rtcp_fbs); yield add_if_supported(list, media, vp8); } else { warning("Unsupported media type: %s", media); } return list; } public override async JingleRtp.PayloadType? pick_payload_type(string media, Gee.List payloads) { if (media == "audio" || media == "video") { foreach (JingleRtp.PayloadType type in payloads) { if (yield is_payload_supported(media, type)) return adjust_payload_type(media, type.clone()); } } else { warning("Unsupported media type: %s", media); } return null; } public JingleRtp.PayloadType adjust_payload_type(string media, JingleRtp.PayloadType type) { var iter = type.rtcp_fbs.iterator(); while (iter.next()) { var fb = iter.@get(); switch (fb.type_) { case "goog-remb": if (fb.subtype != null) iter.remove(); break; case "ccm": if (fb.subtype != "fir") iter.remove(); break; case "nack": if (fb.subtype != null && fb.subtype != "pli") iter.remove(); break; default: iter.remove(); break; } } return type; } public override JingleRtp.Stream create_stream(Jingle.Content content) { return plugin.open_stream(content); } public override void close_stream(JingleRtp.Stream stream) { var rtp_stream = stream as Rtp.Stream; plugin.close_stream(rtp_stream); } public override JingleRtp.Crypto? generate_local_crypto() { uint8[] key_and_salt = new uint8[30]; Crypto.randomize(key_and_salt); return JingleRtp.Crypto.create(JingleRtp.Crypto.AES_CM_128_HMAC_SHA1_80, key_and_salt); } public override JingleRtp.Crypto? pick_remote_crypto(Gee.List cryptos) { foreach (JingleRtp.Crypto crypto in cryptos) { if (crypto.is_valid) return crypto; } return null; } public override JingleRtp.Crypto? pick_local_crypto(JingleRtp.Crypto? remote) { if (remote == null || !remote.is_valid) return null; uint8[] key_and_salt = new uint8[30]; Crypto.randomize(key_and_salt); return remote.rekey(key_and_salt); } } dino-0.3.0/plugins/rtp/src/participant.vala0000644000000000000000000000204614202022370017373 0ustar rootrootusing Gee; using Xmpp; public class Dino.Plugins.Rtp.Participant { public Jid full_jid { get; private set; } protected Gst.Pipeline pipe; private Map ssrcs = new HashMap(); public Participant(Gst.Pipeline pipe, Jid full_jid) { this.pipe = pipe; this.full_jid = full_jid; } public uint32 get_ssrc(Stream stream) { if (ssrcs.has_key(stream)) { return ssrcs[stream]; } return 0; } public void set_ssrc(Stream stream, uint32 ssrc) { if (ssrcs.has_key(stream)) { warning("Learning ssrc %ul for %s in %s when it is already known as %ul", ssrc, full_jid.to_string(), stream.to_string(), ssrcs[stream]); } else { stream.on_destroy.connect(unset_ssrc); } ssrcs[stream] = ssrc; } public void unset_ssrc(Stream stream) { ssrcs.unset(stream); stream.on_destroy.disconnect(unset_ssrc); } public string to_string() { return @"participant $full_jid"; } }dino-0.3.0/plugins/rtp/src/plugin.vala0000644000000000000000000004216014202022370016354 0ustar rootrootusing Gee; using Xmpp; using Xmpp.Xep; public class Dino.Plugins.Rtp.Plugin : RootInterface, VideoCallPlugin, Object { public Dino.Application app { get; private set; } public CodecUtil codec_util { get; private set; } public Gst.DeviceMonitor? device_monitor { get; private set; } public Gst.Pipeline? pipe { get; private set; } public Gst.Bin? rtpbin { get; private set; } public Gst.Element? echoprobe { get; private set; } private Gee.List streams = new ArrayList(); private Gee.List devices = new ArrayList(); // private Gee.List participants = new ArrayList(); public void registered(Dino.Application app) { this.app = app; this.codec_util = new CodecUtil(); app.startup.connect(startup); app.add_option_group(Gst.init_get_option_group()); app.stream_interactor.module_manager.initialize_account_modules.connect((account, list) => { list.add(new Module(this)); }); app.plugin_registry.video_call_plugin = this; } private int pause_count = 0; public void pause() { // if (pause_count == 0) { // debug("Pausing pipe for modifications"); // pipe.set_state(Gst.State.PAUSED); // } pause_count++; } public void unpause() { pause_count--; if (pause_count == 0) { debug("Continue pipe after modifications"); pipe.set_state(Gst.State.PLAYING); } if (pause_count < 0) warning("Pause count below zero!"); } private void init_device_monitor() { if (device_monitor != null) return; device_monitor = new Gst.DeviceMonitor(); device_monitor.show_all = true; device_monitor.get_bus().add_watch(Priority.DEFAULT, on_device_monitor_message); device_monitor.start(); foreach (Gst.Device device in device_monitor.get_devices()) { if (device.properties.has_name("pipewire-proplist") && device.has_classes("Audio")) continue; if (device.properties.get_string("device.class") == "monitor") continue; if (devices.any_match((it) => it.matches(device))) continue; devices.add(new Device(this, device)); } } private void init_call_pipe() { if (pipe != null) return; pipe = new Gst.Pipeline(null); // RTP rtpbin = Gst.ElementFactory.make("rtpbin", null) as Gst.Bin; if (rtpbin == null) { warning("RTP not supported"); pipe = null; return; } rtpbin.pad_added.connect(on_rtp_pad_added); rtpbin.@set("latency", 100); rtpbin.@set("do-lost", true); // rtpbin.@set("do-sync-event", true); rtpbin.@set("drop-on-latency", true); rtpbin.connect("signal::request-pt-map", request_pt_map, this); pipe.add(rtpbin); #if WITH_VOICE_PROCESSOR // Audio echo probe echoprobe = new EchoProbe(); if (echoprobe != null) pipe.add(echoprobe); #endif // Pipeline pipe.auto_flush_bus = true; pipe.bus.add_watch(GLib.Priority.DEFAULT, (_, message) => { on_pipe_bus_message(message); return true; }); pipe.set_state(Gst.State.PLAYING); } private void destroy_call_pipe() { if (pipe == null) return; pipe.set_state(Gst.State.NULL); rtpbin = null; #if WITH_VOICE_PROCESSOR echoprobe = null; #endif pipe = null; } public void startup() { init_device_monitor(); } private static Gst.Caps? request_pt_map(Gst.Element rtpbin, uint session, uint pt, Plugin plugin) { debug("request-pt-map"); return null; } private void on_rtp_pad_added(Gst.Pad pad) { debug("pad added: %s", pad.name); if (pad.name.has_prefix("recv_rtp_src_")) { string[] split = pad.name.split("_"); uint8 rtpid = (uint8)int.parse(split[3]); foreach (Stream stream in streams) { if (stream.rtpid == rtpid) { stream.on_ssrc_pad_added((uint32) split[4].to_uint64(), pad); } } } if (pad.name.has_prefix("send_rtp_src_")) { string[] split = pad.name.split("_"); uint8 rtpid = (uint8)int.parse(split[3]); debug("pad %s for stream %hhu", pad.name, rtpid); foreach (Stream stream in streams) { if (stream.rtpid == rtpid) { stream.on_send_rtp_src_added(pad); } } } } private void on_pipe_bus_message(Gst.Message message) { switch (message.type) { case Gst.MessageType.ERROR: Error error; string str; message.parse_error(out error, out str); warning("Error in pipeline: %s", error.message); debug(str); break; case Gst.MessageType.WARNING: Error error; string str; message.parse_warning(out error, out str); warning("Warning in pipeline: %s", error.message); debug(str); break; case Gst.MessageType.CLOCK_LOST: debug("Clock lost. Restarting"); pipe.set_state(Gst.State.READY); pipe.set_state(Gst.State.PLAYING); break; case Gst.MessageType.STATE_CHANGED: // Ignore break; case Gst.MessageType.STREAM_STATUS: Gst.StreamStatusType status; Gst.Element owner; message.parse_stream_status(out status, out owner); if (owner != null) { debug("%s stream changed status to %s", owner.name, status.to_string()); } break; case Gst.MessageType.ELEMENT: unowned Gst.Structure struc = message.get_structure(); if (struc != null && message.src is Gst.Element) { debug("Message from %s in pipeline: %s", ((Gst.Element)message.src).name, struc.to_string()); } break; case Gst.MessageType.NEW_CLOCK: debug("New clock."); break; case Gst.MessageType.TAG: // Ignore break; case Gst.MessageType.QOS: // Ignore break; case Gst.MessageType.LATENCY: if (message.src != null && message.src.name != null && message.src is Gst.Element) { Gst.Query latency_query = new Gst.Query.latency(); if (((Gst.Element)message.src).query(latency_query)) { bool live; Gst.ClockTime min_latency, max_latency; latency_query.parse_latency(out live, out min_latency, out max_latency); debug("Latency message from %s: live=%s, min_latency=%s, max_latency=%s", message.src.name, live.to_string(), min_latency.to_string(), max_latency.to_string()); } } break; default: debug("Pipe bus message: %s", message.type.to_string()); break; } } private bool on_device_monitor_message(Gst.Bus bus, Gst.Message message) { Gst.Device? old_gst_device = null; Gst.Device? gst_device = null; Device? device = null; switch (message.type) { case Gst.MessageType.DEVICE_ADDED: message.parse_device_added(out gst_device); if (devices.any_match((it) => it.matches(gst_device))) return Source.CONTINUE; device = new Device(this, gst_device); devices.add(device); break; #if GST_1_16 case Gst.MessageType.DEVICE_CHANGED: message.parse_device_changed(out gst_device, out old_gst_device); device = devices.first_match((it) => it.matches(old_gst_device)); if (device != null) device.update(gst_device); break; #endif case Gst.MessageType.DEVICE_REMOVED: message.parse_device_removed(out gst_device); device = devices.first_match((it) => it.matches(gst_device)); if (device != null) devices.remove(device); break; default: break; } if (device != null) { devices_changed(device.media, device.is_sink); } return Source.CONTINUE; } public uint8 next_free_id() { uint8 rtpid = 0; while (streams.size < 100 && streams.any_match((stream) => stream.rtpid == rtpid)) { rtpid++; } return rtpid; } // public Participant get_participant(Jid full_jid, bool self) { // foreach (Participant participant in participants) { // if (participant.full_jid.equals(full_jid)) { // return participant; // } // } // Participant participant; // if (self) { // participant = new SelfParticipant(pipe, full_jid); // } else { // participant = new Participant(pipe, full_jid); // } // participants.add(participant); // return participant; // } public Stream open_stream(Xmpp.Xep.Jingle.Content content) { init_call_pipe(); var content_params = content.content_params as Xmpp.Xep.JingleRtp.Parameters; if (content_params == null) return null; Stream stream; if (content_params.media == "video") { stream = new VideoStream(this, content); } else { stream = new Stream(this, content); } streams.add(stream); return stream; } public void close_stream(Stream stream) { streams.remove(stream); stream.destroy(); } public void shutdown() { if (device_monitor != null) { device_monitor.stop(); } destroy_call_pipe(); Gst.deinit(); } public bool supports(string? media) { if (!codec_util.is_element_supported("rtpbin")) return false; if (media == "audio") { if (get_devices("audio", false).is_empty) return false; if (get_devices("audio", true).is_empty) return false; } if (media == "video") { if (!codec_util.is_element_supported("gtksink")) return false; if (get_devices("video", false).is_empty) return false; } return true; } public VideoCallWidget? create_widget(WidgetType type) { init_call_pipe(); if (type == WidgetType.GTK) { return new VideoWidget(this); } return null; } public Gee.List get_devices(string media, bool incoming) { Gee.List devices; if (media == "video" && !incoming) { devices = get_video_sources(); } else if (media == "audio") { devices = get_audio_devices(incoming); } else { devices = new ArrayList(); devices.add_all_iterator(this.devices.filter(it => it.media == media && (incoming && it.is_sink || !incoming && it.is_source) && !it.is_monitor)); } devices.sort((media_left, media_right) => { return strcmp(media_left.id, media_right.id); }); return devices; } public Gee.List get_audio_devices(bool incoming) { ArrayList pulse_devices = new ArrayList(); ArrayList other_devices = new ArrayList(); foreach (Device device in devices) { if (device.media != "audio") continue; if (incoming && !device.is_sink || !incoming && !device.is_source) continue; // Skip monitors if (device.is_monitor) continue; if (device.protocol == DeviceProtocol.PULSEAUDIO) { pulse_devices.add(device); } else { other_devices.add(device); } } // If we have any pulseaudio devices, present only those. Don't want duplicated devices from pipewire and pulseaudio. return pulse_devices.size > 0 ? pulse_devices : other_devices; } public Gee.List get_video_sources() { ArrayList pipewire_devices = new ArrayList(); ArrayList other_devices = new ArrayList(); foreach (Device device in devices) { if (device.media != "video") continue; if (device.is_sink) continue; // Skip monitors if (device.is_monitor) continue; bool is_color = false; for (int i = 0; i < device.device.caps.get_size(); i++) { unowned Gst.Structure structure = device.device.caps.get_structure(i); if (!structure.has_field("format")) continue; // "format" might be an array and get_string() will then return null. We just assume arrays to be fine. string? format = structure.get_string("format"); if (format == null || !format.has_prefix("GRAY")) { is_color = true; } } // Don't allow grey-scale devices if (!is_color) continue; if (device.protocol == DeviceProtocol.PIPEWIRE) { pipewire_devices.add(device); } else { other_devices.add(device); } } // If we have any pipewire devices, present only those. Don't want duplicated devices from pipewire and video for linux. return pipewire_devices.size > 0 ? pipewire_devices : other_devices; } private int get_max_fps(Device device) { int fps = 0; for (int i = 0; i < device.device.caps.get_size(); i++) { unowned Gst.Structure structure = device.device.caps.get_structure(i); int num = 0, den = 0; if (structure.has_field("framerate") && structure.get_fraction("framerate", out num, out den)) fps = int.max(fps, num / den); } return fps; } public MediaDevice? get_preferred_device(string media, bool incoming) { Gee.List devices = new ArrayList(); foreach (MediaDevice media_device in get_devices(media, incoming)) { if (media_device is Device) devices.add((Device)media_device); } if (devices.is_empty) { warning("No preferred device for %s %s. Media will not be processed.", incoming ? "incoming" : "outgoing", media); return null; } // Take default if present foreach (Device device in devices) { if (device.is_default) { debug("Using %s for %s %s as it's default", device.display_name, incoming ? "incoming" : "outgoing", media); return device; } } if (media == "video") { // Pick best FPS int max_fps = 0; Device? max_fps_device = null; foreach (Device device in devices) { int fps = get_max_fps(device); if (fps > max_fps) { max_fps = fps; max_fps_device = device; } } debug("Using %s for %s %s as it has max FPS (%d)", max_fps_device.display_name, incoming ? "incoming" : "outgoing", media, max_fps); return max_fps_device; } else { // Pick any Device? device = devices.first(); debug("Using %s for %s %s as it's first pick", device.display_name, incoming ? "incoming" : "outgoing", media); return device; } } public MediaDevice? get_device(Xmpp.Xep.JingleRtp.Stream? stream, bool incoming) { Stream? plugin_stream = stream as Stream?; if (plugin_stream == null) return null; MediaDevice? device = incoming ? plugin_stream.output_device : plugin_stream.input_device; return device ?? get_preferred_device(stream.media, incoming); } public void dump_dot() { if (pipe == null) return; string name = @"pipe-$(pipe.clock.get_time())-$(pipe.current_state)"; Gst.Debug.bin_to_dot_file(pipe, Gst.DebugGraphDetails.ALL, name); print(@"Stored pipe details as $name\n"); } public void set_pause(Xmpp.Xep.JingleRtp.Stream? stream, bool pause) { Stream? plugin_stream = stream as Stream?; if (plugin_stream == null) return; if (pause) { plugin_stream.pause(); } else { plugin_stream.unpause(); } } public void set_device(Xmpp.Xep.JingleRtp.Stream? stream, MediaDevice? device) { Device? real_device = device as Device?; Stream? plugin_stream = stream as Stream?; if (real_device == null || plugin_stream == null) return; if (real_device.is_source) { plugin_stream.input_device = real_device; } else if (real_device.is_sink) { plugin_stream.output_device = real_device; } } } dino-0.3.0/plugins/rtp/src/register_plugin.vala0000644000000000000000000000013414202022370020253 0ustar rootrootpublic Type register_plugin(Module module) { return typeof (Dino.Plugins.Rtp.Plugin); } dino-0.3.0/plugins/rtp/src/stream.vala0000644000000000000000000007474314202022370016365 0ustar rootrootusing Gee; using Xmpp; public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream { public uint8 rtpid { get; private set; } public Plugin plugin { get; private set; } public Gst.Pipeline pipe { get { return plugin.pipe; }} public Gst.Element rtpbin { get { return plugin.rtpbin; }} public CodecUtil codec_util { get { return plugin.codec_util; }} private Gst.App.Sink send_rtp; private Gst.App.Sink send_rtcp; private Gst.App.Src recv_rtp; private Gst.App.Src recv_rtcp; private Gst.Element decode; private Gst.RTP.BaseDepayload decode_depay; private Gst.Element input; private Gst.Pad input_pad; private Gst.Element output; private Gst.Element session; private Device _input_device; public Device input_device { get { return _input_device; } set { if (sending && !paused) { var input = this.input; set_input(value != null ? value.link_source(payload_type, our_ssrc, next_seqnum_offset, next_timestamp_offset) : null); if (this._input_device != null) this._input_device.unlink(input); } this._input_device = value; }} private Device _output_device; public Device output_device { get { return _output_device; } set { if (output != null) remove_output(output); if (value != null && receiving) add_output(value.link_sink()); this._output_device = value; }} public bool created { get; private set; default = false; } public bool paused { get; private set; default = false; } private bool push_recv_data = false; private uint our_ssrc = Random.next_int(); private int next_seqnum_offset = -1; private uint32 next_timestamp_offset_base = 0; private int64 next_timestamp_offset_stamp = 0; private uint32 next_timestamp_offset { get { if (next_timestamp_offset_base == 0) return 0; int64 monotonic_diff = get_monotonic_time() - next_timestamp_offset_stamp; return next_timestamp_offset_base + (uint32)((double)monotonic_diff / 1000000.0 * payload_type.clockrate); } } private uint32 participant_ssrc = 0; private Gst.Pad recv_rtcp_sink_pad; private Gst.Pad recv_rtp_sink_pad; private Gst.Pad recv_rtp_src_pad; private Gst.Pad send_rtcp_src_pad; private Gst.Pad send_rtp_sink_pad; private Gst.Pad send_rtp_src_pad; private Crypto.Srtp.Session? crypto_session = new Crypto.Srtp.Session(); public Stream(Plugin plugin, Xmpp.Xep.Jingle.Content content) { base(content); this.plugin = plugin; this.rtpid = plugin.next_free_id(); content.notify["senders"].connect_after(on_senders_changed); } public void on_senders_changed() { if (sending && input == null) { input_device = input_device; } if (receiving && output == null) { output_device = output_device; } } public override void create() { plugin.pause(); // Create i/o if needed if (input == null && sending) { input_device = input_device; } if (output == null && receiving && media == "audio") { output_device = output_device; } // Create app elements send_rtp = Gst.ElementFactory.make("appsink", @"rtp_sink_$rtpid") as Gst.App.Sink; send_rtp.async = false; send_rtp.caps = CodecUtil.get_caps(media, payload_type, false); send_rtp.emit_signals = true; send_rtp.sync = true; send_rtp.drop = true; send_rtp.wait_on_eos = false; send_rtp.new_sample.connect(on_new_sample); send_rtp.connect("signal::eos", on_eos_static, this); pipe.add(send_rtp); send_rtcp = Gst.ElementFactory.make("appsink", @"rtcp_sink_$rtpid") as Gst.App.Sink; send_rtcp.async = false; send_rtcp.caps = new Gst.Caps.empty_simple("application/x-rtcp"); send_rtcp.emit_signals = true; send_rtcp.sync = true; send_rtcp.drop = true; send_rtcp.wait_on_eos = false; send_rtcp.new_sample.connect(on_new_sample); send_rtcp.connect("signal::eos", on_eos_static, this); pipe.add(send_rtcp); recv_rtp = Gst.ElementFactory.make("appsrc", @"rtp_src_$rtpid") as Gst.App.Src; recv_rtp.caps = CodecUtil.get_caps(media, payload_type, true); recv_rtp.do_timestamp = true; recv_rtp.format = Gst.Format.TIME; recv_rtp.is_live = true; pipe.add(recv_rtp); recv_rtcp = Gst.ElementFactory.make("appsrc", @"rtcp_src_$rtpid") as Gst.App.Src; recv_rtcp.caps = new Gst.Caps.empty_simple("application/x-rtcp"); recv_rtcp.do_timestamp = true; recv_rtcp.format = Gst.Format.TIME; recv_rtcp.is_live = true; pipe.add(recv_rtcp); // Connect RTCP send_rtcp_src_pad = rtpbin.get_request_pad(@"send_rtcp_src_$rtpid"); send_rtcp_src_pad.link(send_rtcp.get_static_pad("sink")); recv_rtcp_sink_pad = rtpbin.get_request_pad(@"recv_rtcp_sink_$rtpid"); recv_rtcp.get_static_pad("src").link(recv_rtcp_sink_pad); // Connect input send_rtp_sink_pad = rtpbin.get_request_pad(@"send_rtp_sink_$rtpid"); if (input != null) { input_pad = input.get_request_pad(@"src_$rtpid"); input_pad.link(send_rtp_sink_pad); } // Connect output decode = codec_util.get_decode_bin(media, payload_type, @"decode_$rtpid"); decode_depay = (Gst.RTP.BaseDepayload)((Gst.Bin)decode).get_by_name(@"decode_$(rtpid)_rtp_depay"); pipe.add(decode); if (output != null) { decode.link(output); } // Connect RTP recv_rtp_sink_pad = rtpbin.get_request_pad(@"recv_rtp_sink_$rtpid"); recv_rtp.get_static_pad("src").link(recv_rtp_sink_pad); created = true; push_recv_data = true; plugin.unpause(); GLib.Signal.emit_by_name(rtpbin, "get-session", rtpid, out session); if (session != null && remb_enabled) { Object internal_session; session.@get("internal-session", out internal_session); if (internal_session != null) { internal_session.connect("signal::on-feedback-rtcp", on_feedback_rtcp, this); } Timeout.add(1000, () => remb_adjust()); } if (input_device != null && media == "video") { input_device.update_bitrate(payload_type, target_send_bitrate); } } private int last_packets_lost = -1; private uint64 last_packets_received = 0; private uint64 last_octets_received = 0; private uint max_target_receive_bitrate = 0; private int64 last_remb_time = 0; private bool remb_adjust() { unowned Gst.Structure? stats; if (session == null) { debug("Session for %u finished, turning off remb adjustment", rtpid); return Source.REMOVE; } session.get("stats", out stats); if (stats == null) { warning("No stats for session %u", rtpid); return Source.REMOVE; } unowned ValueArray? source_stats; stats.get("source-stats", typeof(ValueArray), out source_stats); if (source_stats == null) { warning("No source-stats for session %u", rtpid); return Source.REMOVE; } if (input_device == null) return Source.CONTINUE; foreach (Value value in source_stats.values) { unowned Gst.Structure source_stat = (Gst.Structure) value.get_boxed(); uint32 ssrc; if (!source_stat.get_uint("ssrc", out ssrc)) continue; if (ssrc == participant_ssrc) { int packets_lost; uint64 packets_received, octets_received; source_stat.get_int("packets-lost", out packets_lost); source_stat.get_uint64("packets-received", out packets_received); source_stat.get_uint64("octets-received", out octets_received); int new_lost = packets_lost - last_packets_lost; if (new_lost < 0) new_lost = 0; uint64 new_received = packets_received - last_packets_received; if (packets_received < last_packets_received) new_received = 0; uint64 new_octets = octets_received - last_octets_received; if (octets_received < last_octets_received) octets_received = 0; if (new_received == 0) continue; last_packets_lost = packets_lost; last_packets_received = packets_received; last_octets_received = octets_received; double loss_rate = (double)new_lost / (double)(new_lost + new_received); uint new_target_receive_bitrate; if (new_lost <= 0 || loss_rate < 0.02) { new_target_receive_bitrate = (uint)(1.08 * (double)target_receive_bitrate); } else if (loss_rate > 0.1) { new_target_receive_bitrate = (uint)((1.0 - 0.5 * loss_rate) * (double)target_receive_bitrate); } else { new_target_receive_bitrate = target_receive_bitrate; } if (last_remb_time == 0) { last_remb_time = get_monotonic_time(); } else { int64 time_now = get_monotonic_time(); int64 time_diff = time_now - last_remb_time; last_remb_time = time_now; uint actual_bitrate = (uint)(((double)new_octets * 8.0) * (double)time_diff / 1000.0 / 1000000.0); new_target_receive_bitrate = uint.max(new_target_receive_bitrate, (uint)(0.9 * (double)actual_bitrate)); max_target_receive_bitrate = uint.max((uint)(1.5 * (double)actual_bitrate), max_target_receive_bitrate); new_target_receive_bitrate = uint.min(new_target_receive_bitrate, max_target_receive_bitrate); } new_target_receive_bitrate = uint.max(16, new_target_receive_bitrate); // Never go below 16 if (new_target_receive_bitrate != target_receive_bitrate) { target_receive_bitrate = new_target_receive_bitrate; uint8[] data = new uint8[] { 143, 206, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 'R', 'E', 'M', 'B', 1, 0, 0, 0, 0, 0, 0, 0 }; data[4] = (uint8)((our_ssrc >> 24) & 0xff); data[5] = (uint8)((our_ssrc >> 16) & 0xff); data[6] = (uint8)((our_ssrc >> 8) & 0xff); data[7] = (uint8)(our_ssrc & 0xff); uint8 br_exp = 0; uint32 br_mant = target_receive_bitrate * 1000; uint8 bits = (uint8)Math.log2(br_mant); if (bits > 16) { br_exp = (uint8)bits - 16; br_mant = br_mant >> br_exp; } data[17] = (uint8)((br_exp << 2) | ((br_mant >> 16) & 0x3)); data[18] = (uint8)((br_mant >> 8) & 0xff); data[19] = (uint8)(br_mant & 0xff); data[20] = (uint8)((ssrc >> 24) & 0xff); data[21] = (uint8)((ssrc >> 16) & 0xff); data[22] = (uint8)((ssrc >> 8) & 0xff); data[23] = (uint8)(ssrc & 0xff); encrypt_and_send_rtcp(data); } } } return Source.CONTINUE; } private static void on_feedback_rtcp(Gst.Element session, uint type, uint fbtype, uint sender_ssrc, uint media_ssrc, Gst.Buffer? fci, Stream self) { if (self.input_device != null && self.media == "video" && type == 206 && fbtype == 15 && fci != null && sender_ssrc == self.participant_ssrc) { // https://tools.ietf.org/html/draft-alvestrand-rmcat-remb-03 uint8[] data; fci.extract_dup(0, fci.get_size(), out data); if (data[0] != 'R' || data[1] != 'E' || data[2] != 'M' || data[3] != 'B') return; uint8 br_exp = data[5] >> 2; uint32 br_mant = (((uint32)data[5] & 0x3) << 16) + ((uint32)data[6] << 8) + (uint32)data[7]; self.target_send_bitrate = (br_mant << br_exp) / 1000; self.input_device.update_bitrate(self.payload_type, self.target_send_bitrate); } } private void prepare_local_crypto() { if (local_crypto != null && local_crypto.is_valid && !crypto_session.has_encrypt) { crypto_session.set_encryption_key(local_crypto.crypto_suite, local_crypto.key, local_crypto.salt); debug("Setting up encryption with key params %s", local_crypto.key_params); } } private Gst.FlowReturn on_new_sample(Gst.App.Sink sink) { if (sink == null) { debug("Sink is null"); return Gst.FlowReturn.EOS; } if (sink != send_rtp && sink != send_rtcp) { warning("unknown sample"); return Gst.FlowReturn.NOT_SUPPORTED; } Gst.Sample sample = sink.pull_sample(); Gst.Buffer buffer = sample.get_buffer(); if (sink == send_rtp) { uint buffer_ssrc = 0, buffer_seq = 0; Gst.RTP.Buffer rtp_buffer; if (Gst.RTP.Buffer.map(buffer, Gst.MapFlags.READ, out rtp_buffer)) { buffer_ssrc = rtp_buffer.get_ssrc(); buffer_seq = rtp_buffer.get_seq(); next_seqnum_offset = rtp_buffer.get_seq() + 1; next_timestamp_offset_base = rtp_buffer.get_timestamp(); next_timestamp_offset_stamp = get_monotonic_time(); rtp_buffer.unmap(); } #if GLIB_2_64 if (our_ssrc != buffer_ssrc) { warning_once("Sending RTP %s buffer seq %u with SSRC %u when our ssrc is %u", media, buffer_seq, buffer_ssrc, our_ssrc); } #endif } prepare_local_crypto(); uint8[] data; buffer.extract_dup(0, buffer.get_size(), out data); if (sink == send_rtp) { encrypt_and_send_rtp((owned) data); } else if (sink == send_rtcp) { encrypt_and_send_rtcp((owned) data); } return Gst.FlowReturn.OK; } private void encrypt_and_send_rtp(owned uint8[] data) { Bytes bytes; if (crypto_session.has_encrypt) { bytes = new Bytes.take(crypto_session.encrypt_rtp(data)); } else { bytes = new Bytes.take(data); } on_send_rtp_data(bytes); } private void encrypt_and_send_rtcp(owned uint8[] data) { Bytes bytes; if (crypto_session.has_encrypt) { bytes = new Bytes.take(crypto_session.encrypt_rtcp(data)); } else { bytes = new Bytes.take(data); } if (rtcp_mux) { on_send_rtp_data(bytes); } else { on_send_rtcp_data(bytes); } } private static Gst.PadProbeReturn drop_probe() { return Gst.PadProbeReturn.DROP; } private static void on_eos_static(Gst.App.Sink sink, Stream self) { debug("EOS on %s", sink.name); if (sink == self.send_rtp) { Idle.add(() => { self.on_send_rtp_eos(); return Source.REMOVE; }); } else if (sink == self.send_rtcp) { Idle.add(() => { self.on_send_rtcp_eos(); return Source.REMOVE; }); } } private void on_send_rtp_eos() { if (send_rtp_src_pad != null) { send_rtp_src_pad.unlink(send_rtp.get_static_pad("sink")); send_rtp_src_pad = null; } send_rtp.set_locked_state(true); send_rtp.set_state(Gst.State.NULL); pipe.remove(send_rtp); send_rtp = null; debug("Stopped sending RTP for %u", rtpid); } private void on_send_rtcp_eos() { send_rtcp.set_locked_state(true); send_rtcp.set_state(Gst.State.NULL); pipe.remove(send_rtcp); send_rtcp = null; debug("Stopped sending RTCP for %u", rtpid); } public override void destroy() { // Stop network communication push_recv_data = false; if (recv_rtp != null) recv_rtp.end_of_stream(); if (recv_rtcp != null) recv_rtcp.end_of_stream(); if (send_rtp != null) send_rtp.new_sample.disconnect(on_new_sample); if (send_rtcp != null) send_rtcp.new_sample.disconnect(on_new_sample); // Disconnect input device if (input != null) { input_pad.unlink(send_rtp_sink_pad); input.release_request_pad(input_pad); input_pad = null; } if (this._input_device != null) { if (!paused) this._input_device.unlink(input); this._input_device = null; this.input = null; } // Inject EOS if (send_rtp_sink_pad != null) { send_rtp_sink_pad.send_event(new Gst.Event.eos()); } // Disconnect decode if (recv_rtp_src_pad != null) { recv_rtp_src_pad.add_probe(Gst.PadProbeType.BLOCK, drop_probe); recv_rtp_src_pad.unlink(decode.get_static_pad("sink")); } // Disconnect output if (output != null) { decode.get_static_pad("src").add_probe(Gst.PadProbeType.BLOCK, drop_probe); decode.unlink(output); } // Disconnect output device if (this._output_device != null) { this._output_device.unlink(output); this._output_device = null; } output = null; // Destroy decode if (decode != null) { decode.set_locked_state(true); decode.set_state(Gst.State.NULL); pipe.remove(decode); decode = null; decode_depay = null; } // Disconnect and remove RTP input if (recv_rtp != null) { recv_rtp.get_static_pad("src").unlink(recv_rtp_sink_pad); recv_rtp.set_locked_state(true); recv_rtp.set_state(Gst.State.NULL); pipe.remove(recv_rtp); recv_rtp = null; } // Disconnect and remove RTCP input if (recv_rtcp != null) { recv_rtcp.get_static_pad("src").unlink(recv_rtcp_sink_pad); recv_rtcp.set_locked_state(true); recv_rtcp.set_state(Gst.State.NULL); pipe.remove(recv_rtcp); recv_rtcp = null; } // Release rtp pads if (send_rtp_sink_pad != null) { rtpbin.release_request_pad(send_rtp_sink_pad); send_rtp_sink_pad = null; } if (recv_rtp_sink_pad != null) { rtpbin.release_request_pad(recv_rtp_sink_pad); recv_rtp_sink_pad = null; } if (send_rtcp_src_pad != null) { rtpbin.release_request_pad(send_rtcp_src_pad); send_rtcp_src_pad = null; } if (recv_rtcp_sink_pad != null) { rtpbin.release_request_pad(recv_rtcp_sink_pad); recv_rtcp_sink_pad = null; } } private void prepare_remote_crypto() { if (remote_crypto != null && remote_crypto.is_valid && !crypto_session.has_decrypt) { crypto_session.set_decryption_key(remote_crypto.crypto_suite, remote_crypto.key, remote_crypto.salt); debug("Setting up decryption with key params %s", remote_crypto.key_params); } } private uint16 previous_video_orientation_degree = uint16.MAX; public signal void video_orientation_changed(uint16 degree); public override void on_recv_rtp_data(Bytes bytes) { if (rtcp_mux && bytes.length >= 2 && bytes.get(1) >= 192 && bytes.get(1) < 224) { on_recv_rtcp_data(bytes); return; } #if GST_1_16 { Gst.Buffer buffer = new Gst.Buffer.wrapped_bytes(bytes); Gst.RTP.Buffer rtp_buffer; uint buffer_ssrc = 0, buffer_seq = 0; if (Gst.RTP.Buffer.map(buffer, Gst.MapFlags.READ, out rtp_buffer)) { buffer_ssrc = rtp_buffer.get_ssrc(); buffer_seq = rtp_buffer.get_seq(); rtp_buffer.unmap(); } } #endif if (push_recv_data) { prepare_remote_crypto(); Gst.Buffer buffer; if (crypto_session.has_decrypt) { try { buffer = new Gst.Buffer.wrapped(crypto_session.decrypt_rtp(bytes.get_data())); } catch (Error e) { warning("%s (%d)", e.message, e.code); return; } } else { #if GST_1_16 buffer = new Gst.Buffer.wrapped_bytes(bytes); #else buffer = new Gst.Buffer.wrapped(bytes.get_data()); #endif } Gst.RTP.Buffer rtp_buffer; if (Gst.RTP.Buffer.map(buffer, Gst.MapFlags.READ, out rtp_buffer)) { if (rtp_buffer.get_extension()) { Xmpp.Xep.JingleRtp.HeaderExtension? ext = header_extensions.first_match((it) => it.uri == "urn:3gpp:video-orientation"); if (ext != null) { unowned uint8[] extension_data; if (rtp_buffer.get_extension_onebyte_header(ext.id, 0, out extension_data) && extension_data.length == 1) { bool camera = (extension_data[0] & 0x8) > 0; bool flip = (extension_data[0] & 0x4) > 0; uint8 rotation = extension_data[0] & 0x3; uint16 rotation_degree = uint16.MAX; switch(rotation) { case 0: rotation_degree = 0; break; case 1: rotation_degree = 90; break; case 2: rotation_degree = 180; break; case 3: rotation_degree = 270; break; } if (rotation_degree != previous_video_orientation_degree) { video_orientation_changed(rotation_degree); previous_video_orientation_degree = rotation_degree; } } } } rtp_buffer.unmap(); } #if VALA_0_50 recv_rtp.push_buffer((owned) buffer); #else Gst.FlowReturn ret; GLib.Signal.emit_by_name(recv_rtp, "push-buffer", buffer, out ret); #endif } } public override void on_recv_rtcp_data(Bytes bytes) { if (push_recv_data) { prepare_remote_crypto(); Gst.Buffer buffer; if (crypto_session.has_decrypt) { try { buffer = new Gst.Buffer.wrapped(crypto_session.decrypt_rtcp(bytes.get_data())); } catch (Error e) { warning("%s (%d)", e.message, e.code); return; } } else { #if GST_1_16 buffer = new Gst.Buffer.wrapped_bytes(bytes); #else buffer = new Gst.Buffer.wrapped(bytes.get_data()); #endif } #if VALA_0_50 recv_rtcp.push_buffer((owned) buffer); #else Gst.FlowReturn ret; GLib.Signal.emit_by_name(recv_rtcp, "push-buffer", buffer, out ret); #endif } } public override void on_rtp_ready() { // If full frame has been sent before the connection was ready, the counterpart would only display our video after the next full frame. // Send a full frame to let the counterpart display our video asap rtpbin.send_event(new Gst.Event.custom( Gst.EventType.CUSTOM_UPSTREAM, new Gst.Structure("GstForceKeyUnit", "all-headers", typeof(bool), true, null)) ); } public override void on_rtcp_ready() { int rtp_session_id = (int) rtpid; uint64 max_delay = int.MAX; Object rtp_session; bool rtp_sent; GLib.Signal.emit_by_name(rtpbin, "get-internal-session", rtp_session_id, out rtp_session); GLib.Signal.emit_by_name(rtp_session, "send-rtcp-full", max_delay, out rtp_sent); debug("RTCP is ready, resending rtcp: %s", rtp_sent.to_string()); } public void on_ssrc_pad_added(uint32 ssrc, Gst.Pad pad) { debug("New ssrc %u with pad %s", ssrc, pad.name); if (participant_ssrc != 0 && participant_ssrc != ssrc) { warning("Got second ssrc on stream (old: %u, new: %u), ignoring", participant_ssrc, ssrc); return; } participant_ssrc = ssrc; recv_rtp_src_pad = pad; if (decode != null) { plugin.pause(); debug("Link %s to %s decode for %s", recv_rtp_src_pad.name, media, name); recv_rtp_src_pad.link(decode.get_static_pad("sink")); plugin.unpause(); } } public void on_send_rtp_src_added(Gst.Pad pad) { send_rtp_src_pad = pad; if (send_rtp != null) { plugin.pause(); debug("Link %s to %s send_rtp for %s", send_rtp_src_pad.name, media, name); send_rtp_src_pad.link(send_rtp.get_static_pad("sink")); plugin.unpause(); } } public void set_input(Gst.Element? input) { set_input_and_pause(input, paused); } private void set_input_and_pause(Gst.Element? input, bool paused) { if (created && this.input != null) { this.input_pad.unlink(send_rtp_sink_pad); this.input.release_request_pad(this.input_pad); this.input_pad = null; this.input = null; } this.input = input; this.paused = paused; if (created && sending && !paused && input != null) { plugin.pause(); input_pad = input.get_request_pad(@"src_$rtpid"); input_pad.link(send_rtp_sink_pad); plugin.unpause(); } } public void pause() { if (paused) return; var input = this.input; set_input_and_pause(null, true); if (input != null && input_device != null) input_device.unlink(input); } public void unpause() { if (!paused) return; set_input_and_pause(input_device != null ? input_device.link_source(payload_type, our_ssrc, next_seqnum_offset, next_timestamp_offset) : null, false); input_device.update_bitrate(payload_type, target_send_bitrate); } public uint get_participant_ssrc(Xmpp.Jid participant) { if (participant.equals(content.session.peer_full_jid)) { return participant_ssrc; } return 0; } ulong block_probe_handler_id = 0; public virtual void add_output(Gst.Element element, Xmpp.Jid? participant = null) { if (output != null) { critical("add_output() invoked more than once"); return; } if (participant != null) { critical("add_output() invoked with participant when not supported"); return; } this.output = element; if (created) { plugin.pause(); decode.link(element); if (block_probe_handler_id != 0) { decode.get_static_pad("src").remove_probe(block_probe_handler_id); } plugin.unpause(); } } public virtual void remove_output(Gst.Element element) { if (output != element) { critical("remove_output() invoked without prior add_output()"); return; } if (created) { block_probe_handler_id = decode.get_static_pad("src").add_probe(Gst.PadProbeType.BLOCK, drop_probe); decode.unlink(element); } if (this._output_device != null) { this._output_device.unlink(element); this._output_device = null; } this.output = null; } } public class Dino.Plugins.Rtp.VideoStream : Stream { private Gee.List outputs = new ArrayList(); private Gst.Element output_tee; private Gst.Element rotate; private ulong video_orientation_changed_handler; public VideoStream(Plugin plugin, Xmpp.Xep.Jingle.Content content) { base(plugin, content); if (media != "video") critical("VideoStream created for non-video media"); } public override void create() { video_orientation_changed_handler = video_orientation_changed.connect(on_video_orientation_changed); plugin.pause(); rotate = Gst.ElementFactory.make("videoflip", @"video_rotate_$rtpid"); pipe.add(rotate); output_tee = Gst.ElementFactory.make("tee", @"video_tee_$rtpid"); output_tee.@set("allow-not-linked", true); pipe.add(output_tee); rotate.link(output_tee); add_output(rotate); base.create(); foreach (Gst.Element output in outputs) { output_tee.link(output); } plugin.unpause(); } private void on_video_orientation_changed(uint16 degree) { if (rotate != null) { switch (degree) { case 0: rotate.@set("method", 0); break; case 90: rotate.@set("method", 1); break; case 180: rotate.@set("method", 2); break; case 270: rotate.@set("method", 3); break; } } } public override void destroy() { foreach (Gst.Element output in outputs) { output_tee.unlink(output); } base.destroy(); rotate.set_locked_state(true); rotate.set_state(Gst.State.NULL); rotate.unlink(output_tee); pipe.remove(rotate); rotate = null; output_tee.set_locked_state(true); output_tee.set_state(Gst.State.NULL); pipe.remove(output_tee); output_tee = null; disconnect(video_orientation_changed_handler); } public override void add_output(Gst.Element element, Xmpp.Jid? participant) { if (element == output_tee || element == rotate) { base.add_output(element); return; } outputs.add(element); if (output_tee != null) { output_tee.link(element); } } public override void remove_output(Gst.Element element) { if (element == output_tee || element == rotate) { base.remove_output(element); return; } outputs.remove(element); if (output_tee != null) { output_tee.unlink(element); } } } dino-0.3.0/plugins/rtp/src/video_widget.vala0000644000000000000000000001727514202022370017540 0ustar rootroot#if !VALA_0_52 [CCode (cheader_filename = "gst/gst.h")] private static extern void gst_value_set_fraction(ref GLib.Value value, int numerator, int denominator); #endif public class Dino.Plugins.Rtp.VideoWidget : Gtk.Bin, Dino.Plugins.VideoCallWidget { private const int RECAPS_AFTER_CHANGE = 5; private static uint last_id = 0; public uint id { get; private set; } public Gst.Base.Sink sink { get; private set; } public Gtk.Widget widget { get; private set; } public Plugin plugin { get; private set; } public Gst.Pipeline pipe { get { return plugin.pipe; }} private bool attached; private Device? connected_device; private Gst.Element? connected_device_element; private Stream? connected_stream; private Gst.Element prepare; private Gst.Caps last_input_caps; private Gst.Caps last_caps; private int recaps_since_change; public VideoWidget(Plugin plugin) { this.plugin = plugin; id = last_id++; sink = Gst.ElementFactory.make("gtksink", @"video_widget_$id") as Gst.Base.Sink; if (sink != null) { Gtk.Widget widget; sink.@get("widget", out widget); sink.@set("async", false); sink.@set("sync", true); sink.@set("ignore-alpha", false); this.widget = widget; this.widget.draw.connect_after(fix_caps_issues); add(widget); widget.visible = true; } else { warning("Could not create GTK video sink. Won't display videos."); } size_allocate.connect_after(after_size_allocate); } public void input_caps_changed(GLib.Object pad, ParamSpec spec) { Gst.Caps? caps = ((Gst.Pad)pad).caps; if (caps == null) { debug("Input: No caps"); return; } int width, height; caps.get_structure(0).get_int("width", out width); caps.get_structure(0).get_int("height", out height); debug("Input resolution changed: %ix%i", width, height); resolution_changed(width, height); last_input_caps = caps; } public void processed_input_caps_changed(GLib.Object pad, ParamSpec spec) { Gst.Caps? caps = ((Gst.Pad)pad).caps; if (caps == null) { debug("Processed input: No caps"); return; } int width, height; caps.get_structure(0).get_int("width", out width); caps.get_structure(0).get_int("height", out height); debug("Processed resolution changed: %ix%i", width, height); sink.set_caps(caps); last_caps = caps; recaps_since_change = 0; } public void after_size_allocate(Gtk.Allocation allocation) { if (prepare != null) { Gst.Element crop = ((Gst.Bin)prepare).get_by_name(@"video_widget_$(id)_crop"); if (crop != null) { int output_width = allocation.width; int output_height = allocation.height; int target_num, target_den; if (last_input_caps != null) { int input_width, input_height; last_input_caps.get_structure(0).get_int("width", out input_width); last_input_caps.get_structure(0).get_int("height", out input_height); double target_ratio = 3.0/2.0; double ratio = (double)(output_width*input_height)/(double)(input_width*output_height); if (ratio > target_ratio) { target_num = (int)((double)input_width * target_ratio); target_den = input_height; sink.@set("force-aspect-ratio", true); } else if (ratio < 1.0/target_ratio) { target_num = input_width; target_den = (int)((double)input_height * target_ratio);; sink.@set("force-aspect-ratio", true); } else { target_num = output_width; target_den = output_height; sink.@set("force-aspect-ratio", false); } } else { target_num = output_width; target_den = output_height; sink.@set("force-aspect-ratio", false); } Value ratio = Value(typeof(Gst.Fraction)); #if VALA_0_52 Gst.Value.set_fraction(ref ratio, target_num, target_den); #else gst_value_set_fraction(ref ratio, target_num, target_den); #endif crop.set_property("aspect-ratio", ratio); } } } public bool fix_caps_issues() { // FIXME: Detect if draw would fail and do something better if (last_caps != null && recaps_since_change++ < RECAPS_AFTER_CHANGE) { Gst.Caps? temp = last_caps.copy(); temp.set_simple("width", typeof(int), 1, "height", typeof(int), 1, null); sink.set_caps(temp); sink.set_caps(last_caps); } return false; } public void display_stream(Xmpp.Xep.JingleRtp.Stream? stream, Xmpp.Jid jid) { if (sink == null) return; detach(); if (stream.media != "video") return; connected_stream = stream as Stream?; if (connected_stream == null) return; plugin.pause(); pipe.add(sink); prepare = Gst.parse_bin_from_description(@"aspectratiocrop aspect-ratio=4/3 name=video_widget_$(id)_crop ! videoconvert name=video_widget_$(id)_convert", true); prepare.name = @"video_widget_$(id)_prepare"; prepare.get_static_pad("sink").notify["caps"].connect(input_caps_changed); prepare.get_static_pad("src").notify["caps"].connect(processed_input_caps_changed); pipe.add(prepare); connected_stream.add_output(prepare); prepare.link(sink); sink.set_locked_state(false); plugin.unpause(); attached = true; } public void display_device(MediaDevice media_device) { if (sink == null) return; detach(); connected_device = media_device as Device; if (connected_device == null) return; plugin.pause(); pipe.add(sink); prepare = Gst.parse_bin_from_description(@"aspectratiocrop aspect-ratio=4/3 name=video_widget_$(id)_crop ! videoflip method=horizontal-flip name=video_widget_$(id)_flip ! videoconvert name=video_widget_$(id)_convert", true); prepare.name = @"video_widget_$(id)_prepare"; prepare.get_static_pad("sink").notify["caps"].connect(input_caps_changed); pipe.add(prepare); connected_device_element = connected_device.link_source(); connected_device_element.link(prepare); prepare.link(sink); sink.set_locked_state(false); plugin.unpause(); attached = true; } public void detach() { if (sink == null) return; if (attached) { if (connected_stream != null) { connected_stream.remove_output(prepare); connected_stream = null; } if (connected_device != null) { connected_device_element.unlink(sink); connected_device_element = null; connected_device.unlink(); connected_device = null; } prepare.set_locked_state(true); prepare.set_state(Gst.State.NULL); pipe.remove(prepare); prepare = null; sink.set_locked_state(true); sink.set_state(Gst.State.NULL); pipe.remove(sink); attached = false; } } public override void dispose() { detach(); widget = null; sink = null; } } dino-0.3.0/plugins/rtp/src/voice_processor.vala0000644000000000000000000001571314202022370020266 0ustar rootrootusing Gst; namespace Dino.Plugins.Rtp { public static extern Buffer adjust_to_running_time(Base.Transform transform, Buffer buf); } public class Dino.Plugins.Rtp.EchoProbe : Audio.Filter { private static StaticPadTemplate sink_template = {"sink", PadDirection.SINK, PadPresence.ALWAYS, {null, "audio/x-raw,rate=48000,channels=1,layout=interleaved,format=S16LE"}}; private static StaticPadTemplate src_template = {"src", PadDirection.SRC, PadPresence.ALWAYS, {null, "audio/x-raw,rate=48000,channels=1,layout=interleaved,format=S16LE"}}; public Audio.Info audio_info { get; private set; } public signal void on_new_buffer(Buffer buffer); private uint period_samples; private uint period_size; private Base.Adapter adapter = new Base.Adapter(); static construct { add_static_pad_template(sink_template); add_static_pad_template(src_template); set_static_metadata("Acoustic Echo Canceller probe", "Generic/Audio", "Gathers playback buffers for echo cancellation", "Dino Team "); } construct { set_passthrough(true); } public override bool setup(Audio.Info info) { audio_info = info; period_samples = info.rate / 100; // 10ms buffers period_size = period_samples * info.bpf; return true; } public override FlowReturn transform_ip(Buffer buf) { lock (adapter) { adapter.push(adjust_to_running_time(this, buf)); while (adapter.available() > period_size) { on_new_buffer(adapter.take_buffer(period_size)); } } return FlowReturn.OK; } public override bool stop() { adapter.clear(); return true; } } public class Dino.Plugins.Rtp.VoiceProcessor : Audio.Filter { private static StaticPadTemplate sink_template = {"sink", PadDirection.SINK, PadPresence.ALWAYS, {null, "audio/x-raw,rate=48000,channels=1,layout=interleaved,format=S16LE"}}; private static StaticPadTemplate src_template = {"src", PadDirection.SRC, PadPresence.ALWAYS, {null, "audio/x-raw,rate=48000,channels=1,layout=interleaved,format=S16LE"}}; public Audio.Info audio_info { get; private set; } private ulong process_outgoing_buffer_handler_id; private uint adjust_delay_timeout_id; private uint period_samples; private uint period_size; private Base.Adapter adapter = new Base.Adapter(); private EchoProbe? echo_probe; private Audio.StreamVolume? stream_volume; private ClockTime last_reverse; private void* native; static construct { add_static_pad_template(sink_template); add_static_pad_template(src_template); set_static_metadata("Voice Processor (AGC, AEC, filters, etc.)", "Generic/Audio", "Pre-processes voice with WebRTC Audio Processing Library", "Dino Team "); } construct { set_passthrough(false); } public VoiceProcessor(EchoProbe? echo_probe = null, Audio.StreamVolume? stream_volume = null) { this.echo_probe = echo_probe; this.stream_volume = stream_volume; } private static extern void* init_native(int stream_delay); private static extern void setup_native(void* native); private static extern void destroy_native(void* native); private static extern void analyze_reverse_stream(void* native, Audio.Info info, Buffer buffer); private static extern void process_stream(void* native, Audio.Info info, Buffer buffer); private static extern void adjust_stream_delay(void* native); private static extern void notify_gain_level(void* native, int gain_level); private static extern int get_suggested_gain_level(void* native); private static extern bool get_stream_has_voice(void* native); public override bool setup(Audio.Info info) { debug("VoiceProcessor.setup(%s)", info.to_caps().to_string()); audio_info = info; period_samples = info.rate / 100; // 10ms buffers period_size = period_samples * info.bpf; adapter.clear(); setup_native(native); return true; } public override bool start() { native = init_native(150); if (process_outgoing_buffer_handler_id == 0 && echo_probe != null) { process_outgoing_buffer_handler_id = echo_probe.on_new_buffer.connect(process_outgoing_buffer); } if (stream_volume == null && sinkpad.get_peer() != null && sinkpad.get_peer().get_parent_element() is Audio.StreamVolume) { stream_volume = sinkpad.get_peer().get_parent_element() as Audio.StreamVolume; } return true; } private bool adjust_delay() { if (native != null) { adjust_stream_delay(native); return Source.CONTINUE; } else { adjust_delay_timeout_id = 0; return Source.REMOVE; } } private void process_outgoing_buffer(Buffer buffer) { if (buffer.pts != uint64.MAX) { last_reverse = buffer.pts; } analyze_reverse_stream(native, echo_probe.audio_info, buffer); if (adjust_delay_timeout_id == 0 && echo_probe != null) { adjust_delay_timeout_id = Timeout.add(1000, adjust_delay); } } public override FlowReturn submit_input_buffer(bool is_discont, Buffer input) { lock (adapter) { if (is_discont) { adapter.clear(); } adapter.push(adjust_to_running_time(this, input)); } return FlowReturn.OK; } public override FlowReturn generate_output(out Buffer output_buffer) { lock (adapter) { if (adapter.available() >= period_size) { output_buffer = (Gst.Buffer) adapter.take_buffer(period_size).make_writable(); int old_gain_level = 0; if (stream_volume != null) { old_gain_level = (int) (stream_volume.get_volume(Audio.StreamVolumeFormat.LINEAR) * 255.0); notify_gain_level(native, old_gain_level); } process_stream(native, audio_info, output_buffer); if (stream_volume != null) { int new_gain_level = get_suggested_gain_level(native); if (old_gain_level != new_gain_level) { debug("Gain: %i -> %i", old_gain_level, new_gain_level); stream_volume.set_volume(Audio.StreamVolumeFormat.LINEAR, ((double)new_gain_level) / 255.0); } } } } return FlowReturn.OK; } public override bool stop() { if (process_outgoing_buffer_handler_id != 0) { echo_probe.disconnect(process_outgoing_buffer_handler_id); process_outgoing_buffer_handler_id = 0; } if (adjust_delay_timeout_id != 0) { Source.remove(adjust_delay_timeout_id); adjust_delay_timeout_id = 0; } adapter.clear(); destroy_native(native); native = null; return true; } }dino-0.3.0/plugins/rtp/src/voice_processor_native.cpp0000644000000000000000000001552314202022370021472 0ustar rootroot#include #include #include #include #include #include #define SAMPLE_RATE 48000 #define SAMPLE_CHANNELS 1 struct _DinoPluginsRtpVoiceProcessorNative { webrtc::AudioProcessing *apm; gint stream_delay; gint last_median; gint last_poor_delays; }; extern "C" void *dino_plugins_rtp_adjust_to_running_time(GstBaseTransform *transform, GstBuffer *buffer) { GstBuffer *copy = gst_buffer_copy(buffer); GST_BUFFER_PTS(copy) = gst_segment_to_running_time(&transform->segment, GST_FORMAT_TIME, GST_BUFFER_PTS(buffer)); return copy; } extern "C" void *dino_plugins_rtp_voice_processor_init_native(gint stream_delay) { _DinoPluginsRtpVoiceProcessorNative *native = new _DinoPluginsRtpVoiceProcessorNative(); webrtc::Config config; config.Set(new webrtc::ExtendedFilter(true)); config.Set(new webrtc::ExperimentalAgc(true, 85)); native->apm = webrtc::AudioProcessing::Create(config); native->stream_delay = stream_delay; native->last_median = 0; native->last_poor_delays = 0; return native; } extern "C" void dino_plugins_rtp_voice_processor_setup_native(void *native_ptr) { _DinoPluginsRtpVoiceProcessorNative *native = (_DinoPluginsRtpVoiceProcessorNative *) native_ptr; webrtc::AudioProcessing *apm = native->apm; webrtc::ProcessingConfig pconfig; pconfig.streams[webrtc::ProcessingConfig::kInputStream] = webrtc::StreamConfig(SAMPLE_RATE, SAMPLE_CHANNELS, false); pconfig.streams[webrtc::ProcessingConfig::kOutputStream] = webrtc::StreamConfig(SAMPLE_RATE, SAMPLE_CHANNELS, false); pconfig.streams[webrtc::ProcessingConfig::kReverseInputStream] = webrtc::StreamConfig(SAMPLE_RATE, SAMPLE_CHANNELS, false); pconfig.streams[webrtc::ProcessingConfig::kReverseOutputStream] = webrtc::StreamConfig(SAMPLE_RATE, SAMPLE_CHANNELS, false); apm->Initialize(pconfig); apm->high_pass_filter()->Enable(true); apm->echo_cancellation()->enable_drift_compensation(false); apm->echo_cancellation()->set_suppression_level(webrtc::EchoCancellation::kModerateSuppression); apm->echo_cancellation()->enable_delay_logging(true); apm->echo_cancellation()->Enable(true); apm->noise_suppression()->set_level(webrtc::NoiseSuppression::kModerate); apm->noise_suppression()->Enable(true); apm->gain_control()->set_analog_level_limits(0, 255); apm->gain_control()->set_mode(webrtc::GainControl::kAdaptiveAnalog); apm->gain_control()->set_target_level_dbfs(3); apm->gain_control()->set_compression_gain_db(9); apm->gain_control()->enable_limiter(true); apm->gain_control()->Enable(true); apm->voice_detection()->set_likelihood(webrtc::VoiceDetection::Likelihood::kLowLikelihood); apm->voice_detection()->Enable(true); } extern "C" void dino_plugins_rtp_voice_processor_analyze_reverse_stream(void *native_ptr, GstAudioInfo *info, GstBuffer *buffer) { _DinoPluginsRtpVoiceProcessorNative *native = (_DinoPluginsRtpVoiceProcessorNative *) native_ptr; webrtc::StreamConfig config(SAMPLE_RATE, SAMPLE_CHANNELS, false); webrtc::AudioProcessing *apm = native->apm; GstMapInfo map; gst_buffer_map(buffer, &map, GST_MAP_READ); webrtc::AudioFrame frame; frame.num_channels_ = info->channels; frame.sample_rate_hz_ = info->rate; frame.samples_per_channel_ = gst_buffer_get_size(buffer) / info->bpf; memcpy(frame.data_, map.data, frame.samples_per_channel_ * info->bpf); int err = apm->AnalyzeReverseStream(&frame); if (err < 0) g_warning("voice_processor_native.cpp: ProcessReverseStream %i", err); gst_buffer_unmap(buffer, &map); } extern "C" void dino_plugins_rtp_voice_processor_notify_gain_level(void *native_ptr, gint gain_level) { _DinoPluginsRtpVoiceProcessorNative *native = (_DinoPluginsRtpVoiceProcessorNative *) native_ptr; webrtc::AudioProcessing *apm = native->apm; apm->gain_control()->set_stream_analog_level(gain_level); } extern "C" gint dino_plugins_rtp_voice_processor_get_suggested_gain_level(void *native_ptr) { _DinoPluginsRtpVoiceProcessorNative *native = (_DinoPluginsRtpVoiceProcessorNative *) native_ptr; webrtc::AudioProcessing *apm = native->apm; return apm->gain_control()->stream_analog_level(); } extern "C" bool dino_plugins_rtp_voice_processor_get_stream_has_voice(void *native_ptr) { _DinoPluginsRtpVoiceProcessorNative *native = (_DinoPluginsRtpVoiceProcessorNative *) native_ptr; webrtc::AudioProcessing *apm = native->apm; return apm->voice_detection()->stream_has_voice(); } extern "C" void dino_plugins_rtp_voice_processor_adjust_stream_delay(void *native_ptr) { _DinoPluginsRtpVoiceProcessorNative *native = (_DinoPluginsRtpVoiceProcessorNative *) native_ptr; webrtc::AudioProcessing *apm = native->apm; int median, std, poor_delays; float fraction_poor_delays; apm->echo_cancellation()->GetDelayMetrics(&median, &std, &fraction_poor_delays); poor_delays = (int)(fraction_poor_delays * 100.0); if (fraction_poor_delays < 0 || (native->last_median == median && native->last_poor_delays == poor_delays)) return; g_debug("voice_processor_native.cpp: Stream delay metrics: median=%i std=%i poor_delays=%i%%", median, std, poor_delays); native->last_median = median; native->last_poor_delays = poor_delays; if (poor_delays > 90) { native->stream_delay = std::min(std::max(0, native->stream_delay + std::min(48, std::max(median, -48))), 384); g_debug("voice_processor_native.cpp: set stream_delay=%i", native->stream_delay); } } extern "C" void dino_plugins_rtp_voice_processor_process_stream(void *native_ptr, GstAudioInfo *info, GstBuffer *buffer) { _DinoPluginsRtpVoiceProcessorNative *native = (_DinoPluginsRtpVoiceProcessorNative *) native_ptr; webrtc::StreamConfig config(SAMPLE_RATE, SAMPLE_CHANNELS, false); webrtc::AudioProcessing *apm = native->apm; GstMapInfo map; gst_buffer_map(buffer, &map, GST_MAP_READWRITE); webrtc::AudioFrame frame; frame.num_channels_ = info->channels; frame.sample_rate_hz_ = info->rate; frame.samples_per_channel_ = info->rate / 100; memcpy(frame.data_, map.data, frame.samples_per_channel_ * info->bpf); apm->set_stream_delay_ms(native->stream_delay); int err = apm->ProcessStream(&frame); if (err >= 0) memcpy(map.data, frame.data_, frame.samples_per_channel_ * info->bpf); if (err < 0) g_warning("voice_processor_native.cpp: ProcessStream %i", err); gst_buffer_unmap(buffer, &map); } extern "C" void dino_plugins_rtp_voice_processor_destroy_native(void *native_ptr) { _DinoPluginsRtpVoiceProcessorNative *native = (_DinoPluginsRtpVoiceProcessorNative *) native_ptr; delete native; }dino-0.3.0/plugins/rtp/vapi/0000755000000000000000000000000014202022370014356 5ustar rootrootdino-0.3.0/plugins/rtp/vapi/gstreamer-rtp-1.0.vapi0000644000000000000000000006772414202022370020347 0ustar rootroot// Fixme: This is fetched from development code of Vala upstream which fixed a few bugs. /* gstreamer-rtp-1.0.vapi generated by vapigen, do not modify. */ [CCode (cprefix = "Gst", gir_namespace = "GstRtp", gir_version = "1.0", lower_case_cprefix = "gst_")] namespace Gst { namespace RTCP { [CCode (cheader_filename = "gst/rtp/rtp.h", has_type_id = false)] [GIR (name = "RTCPBuffer")] public struct Buffer { public weak Gst.Buffer buffer; public bool add_packet (Gst.RTCP.Type type, Gst.RTCP.Packet packet); public bool get_first_packet (Gst.RTCP.Packet packet); public uint get_packet_count (); public static bool map (Gst.Buffer buffer, Gst.MapFlags flags, out Gst.RTCP.Buffer rtcp); public static Gst.Buffer @new (uint mtu); public static Gst.Buffer new_copy_data ([CCode (array_length_cname = "len", array_length_pos = 1.1, array_length_type = "guint")] uint8[] data); public static Gst.Buffer new_take_data ([CCode (array_length_cname = "len", array_length_pos = 1.1, array_length_type = "guint")] owned uint8[] data); public bool unmap (); public static bool validate (Gst.Buffer buffer); public static bool validate_data ([CCode (array_length_cname = "len", array_length_pos = 1.1, array_length_type = "guint")] uint8[] data); [Version (since = "1.6")] public static bool validate_data_reduced ([CCode (array_length_cname = "len", array_length_pos = 1.1, array_length_type = "guint")] uint8[] data); [Version (since = "1.6")] public static bool validate_reduced (Gst.Buffer buffer); } [CCode (cheader_filename = "gst/rtp/rtp.h", has_type_id = false)] [GIR (name = "RTCPPacket")] public struct Packet { public weak Gst.RTCP.Buffer? rtcp; public uint offset; [Version (since = "1.10")] public bool add_profile_specific_ext ([CCode (array_length_cname = "len", array_length_pos = 1.1, array_length_type = "guint")] uint8[] data); public bool add_rb (uint32 ssrc, uint8 fractionlost, int32 packetslost, uint32 exthighestseq, uint32 jitter, uint32 lsr, uint32 dlsr); [Version (since = "1.10")] public uint8 app_get_data (); [Version (since = "1.10")] public uint16 app_get_data_length (); [Version (since = "1.10")] public unowned string app_get_name (); [Version (since = "1.10")] public uint32 app_get_ssrc (); [Version (since = "1.10")] public uint8 app_get_subtype (); [Version (since = "1.10")] public bool app_set_data_length (uint16 wordlen); [Version (since = "1.10")] public void app_set_name (string name); [Version (since = "1.10")] public void app_set_ssrc (uint32 ssrc); [Version (since = "1.10")] public void app_set_subtype (uint8 subtype); public bool bye_add_ssrc (uint32 ssrc); public bool bye_add_ssrcs ([CCode (array_length_cname = "len", array_length_pos = 1.1, array_length_type = "guint")] uint32[] ssrc); public uint32 bye_get_nth_ssrc (uint nth); public string bye_get_reason (); public uint8 bye_get_reason_len (); public uint bye_get_ssrc_count (); public bool bye_set_reason (string reason); [Version (since = "1.10")] public bool copy_profile_specific_ext ([CCode (array_length_cname = "len", array_length_pos = 1.1, array_length_type = "guint")] out uint8[] data); public uint8 fb_get_fci (); public uint16 fb_get_fci_length (); public uint32 fb_get_media_ssrc (); public uint32 fb_get_sender_ssrc (); public Gst.RTCP.FBType fb_get_type (); public bool fb_set_fci_length (uint16 wordlen); public void fb_set_media_ssrc (uint32 ssrc); public void fb_set_sender_ssrc (uint32 ssrc); public void fb_set_type (Gst.RTCP.FBType type); public uint8 get_count (); public uint16 get_length (); public bool get_padding (); [Version (since = "1.10")] public bool get_profile_specific_ext ([CCode (array_length_cname = "len", array_length_pos = 1.1, array_length_type = "guint")] out unowned uint8[] data); [Version (since = "1.10")] public uint16 get_profile_specific_ext_length (); public void get_rb (uint nth, out uint32 ssrc, out uint8 fractionlost, out int32 packetslost, out uint32 exthighestseq, out uint32 jitter, out uint32 lsr, out uint32 dlsr); public uint get_rb_count (); public Gst.RTCP.Type get_type (); public bool move_to_next (); public bool remove (); public uint32 rr_get_ssrc (); public void rr_set_ssrc (uint32 ssrc); public bool sdes_add_entry (Gst.RTCP.SDESType type, [CCode (array_length_cname = "len", array_length_pos = 1.5, array_length_type = "guint8")] uint8[] data); public bool sdes_add_item (uint32 ssrc); public bool sdes_copy_entry (out Gst.RTCP.SDESType type, [CCode (array_length_cname = "len", array_length_pos = 1.5, array_length_type = "guint8")] out uint8[] data); public bool sdes_first_entry (); public bool sdes_first_item (); public bool sdes_get_entry (out Gst.RTCP.SDESType type, [CCode (array_length_cname = "len", array_length_pos = 1.5, array_length_type = "guint8")] out unowned uint8[] data); public uint sdes_get_item_count (); public uint32 sdes_get_ssrc (); public bool sdes_next_entry (); public bool sdes_next_item (); public void set_rb (uint nth, uint32 ssrc, uint8 fractionlost, int32 packetslost, uint32 exthighestseq, uint32 jitter, uint32 lsr, uint32 dlsr); public void sr_get_sender_info (out uint32 ssrc, out uint64 ntptime, out uint32 rtptime, out uint32 packet_count, out uint32 octet_count); public void sr_set_sender_info (uint32 ssrc, uint64 ntptime, uint32 rtptime, uint32 packet_count, uint32 octet_count); [Version (since = "1.16")] public bool xr_first_rb (); [Version (since = "1.16")] public uint16 xr_get_block_length (); [Version (since = "1.16")] public Gst.RTCP.XRType xr_get_block_type (); [Version (since = "1.16")] public bool xr_get_dlrr_block (uint nth, out uint32 ssrc, out uint32 last_rr, out uint32 delay); [Version (since = "1.16")] public bool xr_get_prt_by_seq (uint16 seq, out uint32 receipt_time); [Version (since = "1.16")] public bool xr_get_prt_info (out uint32 ssrc, out uint8 thinning, out uint16 begin_seq, out uint16 end_seq); [Version (since = "1.16")] public bool xr_get_rle_info (out uint32 ssrc, out uint8 thinning, out uint16 begin_seq, out uint16 end_seq, out uint32 chunk_count); [Version (since = "1.16")] public bool xr_get_rle_nth_chunk (uint nth, out uint16 chunk); [Version (since = "1.16")] public bool xr_get_rrt (out uint64 timestamp); [Version (since = "1.16")] public uint32 xr_get_ssrc (); [Version (since = "1.16")] public bool xr_get_summary_info (out uint32 ssrc, out uint16 begin_seq, out uint16 end_seq); [Version (since = "1.16")] public bool xr_get_summary_jitter (out uint32 min_jitter, out uint32 max_jitter, out uint32 mean_jitter, out uint32 dev_jitter); [Version (since = "1.16")] public bool xr_get_summary_pkt (out uint32 lost_packets, out uint32 dup_packets); [Version (since = "1.16")] public bool xr_get_summary_ttl (out bool is_ipv4, out uint8 min_ttl, out uint8 max_ttl, out uint8 mean_ttl, out uint8 dev_ttl); [Version (since = "1.16")] public bool xr_get_voip_burst_metrics (out uint8 burst_density, out uint8 gap_density, out uint16 burst_duration, out uint16 gap_duration); [Version (since = "1.16")] public bool xr_get_voip_configuration_params (out uint8 gmin, out uint8 rx_config); [Version (since = "1.16")] public bool xr_get_voip_delay_metrics (out uint16 roundtrip_delay, out uint16 end_system_delay); [Version (since = "1.16")] public bool xr_get_voip_jitter_buffer_params (out uint16 jb_nominal, out uint16 jb_maximum, out uint16 jb_abs_max); [Version (since = "1.16")] public bool xr_get_voip_metrics_ssrc (out uint32 ssrc); [Version (since = "1.16")] public bool xr_get_voip_packet_metrics (out uint8 loss_rate, out uint8 discard_rate); [Version (since = "1.16")] public bool xr_get_voip_quality_metrics (out uint8 r_factor, out uint8 ext_r_factor, out uint8 mos_lq, out uint8 mos_cq); [Version (since = "1.16")] public bool xr_get_voip_signal_metrics (out uint8 signal_level, out uint8 noise_level, out uint8 rerl, out uint8 gmin); [Version (since = "1.16")] public bool xr_next_rb (); } [CCode (cheader_filename = "gst/rtp/rtp.h", cprefix = "GST_RTCP_", type_id = "gst_rtcpfb_type_get_type ()")] [GIR (name = "RTCPFBType")] public enum FBType { FB_TYPE_INVALID, RTPFB_TYPE_NACK, RTPFB_TYPE_TMMBR, RTPFB_TYPE_TMMBN, RTPFB_TYPE_RTCP_SR_REQ, RTPFB_TYPE_TWCC, PSFB_TYPE_PLI, PSFB_TYPE_SLI, PSFB_TYPE_RPSI, PSFB_TYPE_AFB, PSFB_TYPE_FIR, PSFB_TYPE_TSTR, PSFB_TYPE_TSTN, PSFB_TYPE_VBCN } [CCode (cheader_filename = "gst/rtp/rtp.h", cprefix = "GST_RTCP_SDES_", type_id = "gst_rtcpsdes_type_get_type ()")] [GIR (name = "RTCPSDESType")] public enum SDESType { INVALID, END, CNAME, NAME, EMAIL, PHONE, LOC, TOOL, NOTE, PRIV; [CCode (cname = "gst_rtcp_sdes_name_to_type")] public static Gst.RTCP.SDESType from_string (string name); [CCode (cname = "gst_rtcp_sdes_type_to_name")] public unowned string to_string (); } [CCode (cheader_filename = "gst/rtp/rtp.h", cprefix = "GST_RTCP_TYPE_", type_id = "gst_rtcp_type_get_type ()")] [GIR (name = "RTCPType")] public enum Type { INVALID, SR, RR, SDES, BYE, APP, RTPFB, PSFB, XR } [CCode (cheader_filename = "gst/rtp/rtp.h", cprefix = "GST_RTCP_XR_TYPE_", type_id = "gst_rtcpxr_type_get_type ()")] [GIR (name = "RTCPXRType")] [Version (since = "1.16")] public enum XRType { INVALID, LRLE, DRLE, PRT, RRT, DLRR, SSUMM, VOIP_METRICS } [CCode (cheader_filename = "gst/rtp/rtp.h", cname = "GST_RTCP_MAX_BYE_SSRC_COUNT")] public const int MAX_BYE_SSRC_COUNT; [CCode (cheader_filename = "gst/rtp/rtp.h", cname = "GST_RTCP_MAX_RB_COUNT")] public const int MAX_RB_COUNT; [CCode (cheader_filename = "gst/rtp/rtp.h", cname = "GST_RTCP_MAX_SDES")] public const int MAX_SDES; [CCode (cheader_filename = "gst/rtp/rtp.h", cname = "GST_RTCP_MAX_SDES_ITEM_COUNT")] public const int MAX_SDES_ITEM_COUNT; [CCode (cheader_filename = "gst/rtp/rtp.h", cname = "GST_RTCP_REDUCED_SIZE_VALID_MASK")] public const int REDUCED_SIZE_VALID_MASK; [CCode (cheader_filename = "gst/rtp/rtp.h", cname = "GST_RTCP_VALID_MASK")] public const int VALID_MASK; [CCode (cheader_filename = "gst/rtp/rtp.h", cname = "GST_RTCP_VALID_VALUE")] public const int VALID_VALUE; [CCode (cheader_filename = "gst/rtp/rtp.h", cname = "GST_RTCP_VERSION")] public const int VERSION; [CCode (cheader_filename = "gst/rtp/rtp.h")] public static uint64 ntp_to_unix (uint64 ntptime); [CCode (cheader_filename = "gst/rtp/rtp.h")] public static uint64 unix_to_ntp (uint64 unixtime); } namespace RTP { [CCode (cheader_filename = "gst/rtp/rtp.h", type_id = "gst_rtp_base_audio_payload_get_type ()")] [GIR (name = "RTPBaseAudioPayload")] public class BaseAudioPayload : Gst.RTP.BasePayload { public Gst.ClockTime base_ts; public int frame_duration; public int frame_size; public int sample_size; [CCode (has_construct_function = false)] protected BaseAudioPayload (); public Gst.FlowReturn flush (uint payload_len, Gst.ClockTime timestamp); public Gst.Base.Adapter get_adapter (); public Gst.FlowReturn push ([CCode (array_length_cname = "payload_len", array_length_pos = 1.5, array_length_type = "guint")] uint8[] data, Gst.ClockTime timestamp); public void set_frame_based (); public void set_frame_options (int frame_duration, int frame_size); public void set_sample_based (); public void set_sample_options (int sample_size); public void set_samplebits_options (int sample_size); [NoAccessorMethod] public bool buffer_list { get; set; } } [CCode (cheader_filename = "gst/rtp/rtp.h", type_id = "gst_rtp_base_depayload_get_type ()")] [GIR (name = "RTPBaseDepayload")] public abstract class BaseDepayload : Gst.Element { public uint clock_rate; public bool need_newsegment; public weak Gst.Segment segment; public weak Gst.Pad sinkpad; public weak Gst.Pad srcpad; [CCode (has_construct_function = false)] protected BaseDepayload (); [NoWrapper] public virtual bool handle_event (Gst.Event event); [Version (since = "1.16")] public bool is_source_info_enabled (); [NoWrapper] public virtual bool packet_lost (Gst.Event event); [NoWrapper] public virtual Gst.Buffer process (Gst.Buffer @in); [NoWrapper] public virtual Gst.Buffer process_rtp_packet (Gst.RTP.Buffer rtp_buffer); public Gst.FlowReturn push (Gst.Buffer out_buf); public Gst.FlowReturn push_list (Gst.BufferList out_list); [NoWrapper] public virtual bool set_caps (Gst.Caps caps); [Version (since = "1.16")] public void set_source_info_enabled (bool enable); [NoAccessorMethod] [Version (since = "1.20")] public bool auto_header_extension { get; set; } [NoAccessorMethod] [Version (since = "1.18")] public int max_reorder { get; set; } [NoAccessorMethod] [Version (since = "1.16")] public bool source_info { get; set; } [NoAccessorMethod] public Gst.Structure stats { owned get; } [Version (since = "1.20")] public signal void add_extension (owned Gst.RTP.HeaderExtension ext); [Version (since = "1.20")] public signal void clear_extensions (); [Version (since = "1.20")] public signal Gst.RTP.HeaderExtension request_extension (uint ext_id, string? ext_uri); } [CCode (cheader_filename = "gst/rtp/rtp.h", type_id = "gst_rtp_base_payload_get_type ()")] [GIR (name = "RTPBasePayload")] public abstract class BasePayload : Gst.Element { [CCode (has_construct_function = false)] protected BasePayload (); [Version (since = "1.16")] public Gst.Buffer allocate_output_buffer (uint payload_len, uint8 pad_len, uint8 csrc_count); [NoWrapper] public virtual Gst.Caps get_caps (Gst.Pad pad, Gst.Caps filter); [Version (since = "1.16")] public uint get_source_count (Gst.Buffer buffer); [NoWrapper] public virtual Gst.FlowReturn handle_buffer (Gst.Buffer buffer); public bool is_filled (uint size, Gst.ClockTime duration); [Version (since = "1.16")] public bool is_source_info_enabled (); public Gst.FlowReturn push (Gst.Buffer buffer); public Gst.FlowReturn push_list (Gst.BufferList list); [NoWrapper] public virtual bool query (Gst.Pad pad, Gst.Query query); [NoWrapper] public virtual bool set_caps (Gst.Caps caps); public void set_options (string media, bool @dynamic, string encoding_name, uint32 clock_rate); [Version (since = "1.20")] public bool set_outcaps_structure (Gst.Structure? s); [Version (since = "1.16")] public void set_source_info_enabled (bool enable); [NoWrapper] public virtual bool sink_event (Gst.Event event); [NoWrapper] public virtual bool src_event (Gst.Event event); [NoAccessorMethod] [Version (since = "1.20")] public bool auto_header_extension { get; set; } [NoAccessorMethod] public int64 max_ptime { get; set; } [NoAccessorMethod] public int64 min_ptime { get; set; } [NoAccessorMethod] public uint mtu { get; set; } [NoAccessorMethod] [Version (since = "1.16")] public bool onvif_no_rate_control { get; set; } [NoAccessorMethod] public bool perfect_rtptime { get; set; } [NoAccessorMethod] public uint pt { get; set; } [NoAccessorMethod] public int64 ptime_multiple { get; set; } [NoAccessorMethod] [Version (since = "1.18")] public bool scale_rtptime { get; set; } [NoAccessorMethod] public uint seqnum { get; } [NoAccessorMethod] public int seqnum_offset { get; set; } [NoAccessorMethod] [Version (since = "1.16")] public bool source_info { get; set; } [NoAccessorMethod] public uint ssrc { get; set; } [NoAccessorMethod] public Gst.Structure stats { owned get; } [NoAccessorMethod] public uint timestamp { get; } [NoAccessorMethod] public uint timestamp_offset { get; set; } [Version (since = "1.20")] public signal void add_extension (owned Gst.RTP.HeaderExtension ext); [Version (since = "1.20")] public signal void clear_extensions (); [Version (since = "1.20")] public signal Gst.RTP.HeaderExtension request_extension (uint ext_id, string ext_uri); } [CCode (cheader_filename = "gst/rtp/rtp.h", type_id = "gst_rtp_header_extension_get_type ()")] [GIR (name = "RTPHeaderExtension")] [Version (since = "1.20")] public abstract class HeaderExtension : Gst.Element { public uint ext_id; [CCode (has_construct_function = false)] protected HeaderExtension (); public static Gst.RTP.HeaderExtension? create_from_uri (string uri); public uint get_id (); public virtual size_t get_max_size (Gst.Buffer input_meta); public string get_sdp_caps_field_name (); public virtual Gst.RTP.HeaderExtensionFlags get_supported_flags (); public unowned string get_uri (); public virtual bool read (Gst.RTP.HeaderExtensionFlags read_flags, [CCode (array_length_cname = "size", array_length_pos = 2.5, array_length_type = "gsize", type = "const guint8*")] uint8[] data, Gst.Buffer buffer); public virtual bool set_attributes_from_caps (Gst.Caps caps); public bool set_attributes_from_caps_simple_sdp (Gst.Caps caps); public virtual bool set_caps_from_attributes (Gst.Caps caps); public bool set_caps_from_attributes_simple_sdp (Gst.Caps caps); public void set_id (uint ext_id); public virtual bool set_non_rtp_sink_caps (Gst.Caps caps); [CCode (cname = "gst_rtp_header_extension_class_set_uri")] public class void set_uri (string uri); public void set_wants_update_non_rtp_src_caps (bool state); public virtual bool update_non_rtp_src_caps (Gst.Caps caps); public virtual size_t write (Gst.Buffer input_meta, Gst.RTP.HeaderExtensionFlags write_flags, Gst.Buffer output, [CCode (array_length_cname = "size", array_length_pos = 4.1, array_length_type = "gsize", type = "guint8*")] uint8[] data); } [CCode (cheader_filename = "gst/rtp/rtp.h", has_type_id = false)] [GIR (name = "RTPBuffer")] public struct Buffer { public weak Gst.Buffer buffer; public uint state; [CCode (array_length = false)] public weak void* data[4]; [CCode (array_length = false)] public weak size_t size[4]; public bool add_extension_onebyte_header (uint8 id, [CCode (array_length_cname = "size", array_length_pos = 2.1, array_length_type = "guint")] uint8[] data); public bool add_extension_twobytes_header (uint8 appbits, uint8 id, [CCode (array_length_cname = "size", array_length_pos = 3.1, array_length_type = "guint")] uint8[] data); [CCode (cname = "gst_buffer_add_rtp_source_meta")] [Version (since = "1.16")] public static unowned Gst.RTP.SourceMeta? add_rtp_source_meta (Gst.Buffer buffer, uint32? ssrc, uint32? csrc, uint csrc_count); public static void allocate_data (Gst.Buffer buffer, uint payload_len, uint8 pad_len, uint8 csrc_count); public static uint calc_header_len (uint8 csrc_count); public static uint calc_packet_len (uint payload_len, uint8 pad_len, uint8 csrc_count); public static uint calc_payload_len (uint packet_len, uint8 pad_len, uint8 csrc_count); public static int compare_seqnum (uint16 seqnum1, uint16 seqnum2); public static uint32 default_clock_rate (uint8 payload_type); public static uint64 ext_timestamp (ref uint64 exttimestamp, uint32 timestamp); public uint32 get_csrc (uint8 idx); public uint8 get_csrc_count (); public bool get_extension (); [Version (since = "1.2")] public GLib.Bytes get_extension_bytes (out uint16 bits); public bool get_extension_data (out uint16 bits, [CCode (array_length = false)] out unowned uint8[] data, out uint wordlen); public bool get_extension_onebyte_header (uint8 id, uint nth, [CCode (array_length_cname = "size", array_length_pos = 3.1, array_length_type = "guint")] out unowned uint8[] data); [Version (since = "1.18")] public static bool get_extension_onebyte_header_from_bytes (GLib.Bytes bytes, uint16 bit_pattern, uint8 id, uint nth, [CCode (array_length_cname = "size", array_length_pos = 5.1, array_length_type = "guint")] out unowned uint8[] data); public bool get_extension_twobytes_header (out uint8 appbits, uint8 id, uint nth, [CCode (array_length_cname = "size", array_length_pos = 4.1, array_length_type = "guint")] out unowned uint8[] data); public uint get_header_len (); public bool get_marker (); public uint get_packet_len (); public bool get_padding (); [CCode (array_length = false)] public unowned uint8[] get_payload (); public Gst.Buffer get_payload_buffer (); [Version (since = "1.2")] public GLib.Bytes get_payload_bytes (); public uint get_payload_len (); public Gst.Buffer get_payload_subbuffer (uint offset, uint len); public uint8 get_payload_type (); [CCode (cname = "gst_buffer_get_rtp_source_meta")] [Version (since = "1.16")] public static unowned Gst.RTP.SourceMeta? get_rtp_source_meta (Gst.Buffer buffer); public uint16 get_seq (); public uint32 get_ssrc (); public uint32 get_timestamp (); public uint8 get_version (); public static bool map (Gst.Buffer buffer, Gst.MapFlags flags, out Gst.RTP.Buffer rtp); public static Gst.Buffer new_allocate (uint payload_len, uint8 pad_len, uint8 csrc_count); public static Gst.Buffer new_allocate_len (uint packet_len, uint8 pad_len, uint8 csrc_count); public static Gst.Buffer new_copy_data ([CCode (array_length_cname = "len", array_length_pos = 1.1, array_length_type = "gsize")] uint8[] data); public static Gst.Buffer new_take_data ([CCode (array_length_cname = "len", array_length_pos = 1.1, array_length_type = "gsize")] owned uint8[] data); public void pad_to (uint len); public void set_csrc (uint8 idx, uint32 csrc); public void set_extension (bool extension); public bool set_extension_data (uint16 bits, uint16 length); public void set_marker (bool marker); public void set_packet_len (uint len); public void set_padding (bool padding); public void set_payload_type (uint8 payload_type); public void set_seq (uint16 seq); public void set_ssrc (uint32 ssrc); public void set_timestamp (uint32 timestamp); public void set_version (uint8 version); public void unmap (); } [CCode (cheader_filename = "gst/rtp/rtp.h", has_type_id = false)] [GIR (name = "RTPPayloadInfo")] public struct PayloadInfo { public uint8 payload_type; public weak string media; public weak string encoding_name; public uint clock_rate; public weak string encoding_parameters; public uint bitrate; } [CCode (cheader_filename = "gst/rtp/rtp.h", has_type_id = false)] [GIR (name = "RTPSourceMeta")] [Version (since = "1.16")] public struct SourceMeta { public Gst.Meta meta; public uint32 ssrc; public bool ssrc_valid; [CCode (array_length = false)] public weak uint32 csrc[15]; public uint csrc_count; public bool append_csrc ([CCode (array_length_cname = "csrc_count", array_length_pos = 1.1, array_length_type = "guint", type = "const guint32*")] uint32[] csrc); public uint get_source_count (); public bool set_ssrc (uint32? ssrc); } [CCode (cheader_filename = "gst/rtp/rtp.h", cprefix = "GST_RTP_BUFFER_FLAG_", type_id = "gst_rtp_buffer_flags_get_type ()")] [Flags] [GIR (name = "RTPBufferFlags")] [Version (since = "1.10")] public enum BufferFlags { RETRANSMISSION, REDUNDANT, LAST } [CCode (cheader_filename = "gst/rtp/rtp.h", cprefix = "GST_RTP_BUFFER_MAP_FLAG_", type_id = "gst_rtp_buffer_map_flags_get_type ()")] [Flags] [GIR (name = "RTPBufferMapFlags")] [Version (since = "1.6.1")] public enum BufferMapFlags { SKIP_PADDING, LAST } [CCode (cheader_filename = "gst/rtp/rtp.h", cprefix = "GST_RTP_HEADER_EXTENSION_", type_id = "gst_rtp_header_extension_flags_get_type ()")] [Flags] [GIR (name = "RTPHeaderExtensionFlags")] [Version (since = "1.20")] public enum HeaderExtensionFlags { ONE_BYTE, TWO_BYTE } [CCode (cheader_filename = "gst/rtp/rtp.h", cprefix = "GST_RTP_PAYLOAD_", type_id = "gst_rtp_payload_get_type ()")] [GIR (name = "RTPPayload")] public enum Payload { PCMU, @1016, G721, GSM, G723, DVI4_8000, DVI4_16000, LPC, PCMA, G722, L16_STEREO, L16_MONO, QCELP, CN, MPA, G728, DVI4_11025, DVI4_22050, G729, CELLB, JPEG, NV, H261, MPV, MP2T, H263; public const string @1016_STRING; public const string CELLB_STRING; public const string CN_STRING; public const string DVI4_11025_STRING; public const string DVI4_16000_STRING; public const string DVI4_22050_STRING; public const string DVI4_8000_STRING; public const string DYNAMIC_STRING; public const string G721_STRING; public const string G722_STRING; public const int G723_53; public const string G723_53_STRING; public const int G723_63; public const string G723_63_STRING; public const string G723_STRING; public const string G728_STRING; public const string G729_STRING; public const string GSM_STRING; public const string H261_STRING; public const string H263_STRING; public const string JPEG_STRING; public const string L16_MONO_STRING; public const string L16_STEREO_STRING; public const string LPC_STRING; public const string MP2T_STRING; public const string MPA_STRING; public const string MPV_STRING; public const string NV_STRING; public const string PCMA_STRING; public const string PCMU_STRING; public const string QCELP_STRING; public const int TS41; public const string TS41_STRING; public const int TS48; public const string TS48_STRING; } [CCode (cheader_filename = "gst/rtp/rtp.h", cprefix = "GST_RTP_PROFILE_", type_id = "gst_rtp_profile_get_type ()")] [GIR (name = "RTPProfile")] [Version (since = "1.6")] public enum Profile { UNKNOWN, AVP, SAVP, AVPF, SAVPF } [CCode (cheader_filename = "gst/rtp/rtp.h", cname = "GST_RTP_HDREXT_BASE")] public const string HDREXT_BASE; [CCode (cheader_filename = "gst/rtp/rtp.h", cname = "GST_RTP_HDREXT_ELEMENT_CLASS")] [Version (since = "1.20")] public const string HDREXT_ELEMENT_CLASS; [CCode (cheader_filename = "gst/rtp/rtp.h", cname = "GST_RTP_HDREXT_NTP_56")] public const string HDREXT_NTP_56; [CCode (cheader_filename = "gst/rtp/rtp.h", cname = "GST_RTP_HDREXT_NTP_56_SIZE")] public const int HDREXT_NTP_56_SIZE; [CCode (cheader_filename = "gst/rtp/rtp.h", cname = "GST_RTP_HDREXT_NTP_64")] public const string HDREXT_NTP_64; [CCode (cheader_filename = "gst/rtp/rtp.h", cname = "GST_RTP_HDREXT_NTP_64_SIZE")] public const int HDREXT_NTP_64_SIZE; [CCode (cheader_filename = "gst/rtp/rtp.h", cname = "GST_RTP_HEADER_EXTENSION_URI_METADATA_KEY")] [Version (since = "1.20")] public const string HEADER_EXTENSION_URI_METADATA_KEY; [CCode (cheader_filename = "gst/rtp/rtp.h", cname = "GST_RTP_SOURCE_META_MAX_CSRC_COUNT")] public const int SOURCE_META_MAX_CSRC_COUNT; [CCode (cheader_filename = "gst/rtp/rtp.h", cname = "GST_RTP_VERSION")] public const int VERSION; [CCode (cheader_filename = "gst/rtp/rtp.h")] [Version (since = "1.20")] public static GLib.List get_header_extension_list (); [CCode (cheader_filename = "gst/rtp/rtp.h")] public static bool hdrext_get_ntp_56 ([CCode (array_length_cname = "size", array_length_pos = 1.5, array_length_type = "guint")] uint8[] data, out uint64 ntptime); [CCode (cheader_filename = "gst/rtp/rtp.h")] public static bool hdrext_get_ntp_64 ([CCode (array_length_cname = "size", array_length_pos = 1.5, array_length_type = "guint")] uint8[] data, out uint64 ntptime); [CCode (cheader_filename = "gst/rtp/rtp.h")] public static bool hdrext_set_ntp_56 (void* data, uint size, uint64 ntptime); [CCode (cheader_filename = "gst/rtp/rtp.h")] public static bool hdrext_set_ntp_64 (void* data, uint size, uint64 ntptime); [CCode (cheader_filename = "gst/rtp/rtp.h")] public static unowned Gst.RTP.PayloadInfo? payload_info_for_name (string media, string encoding_name); [CCode (cheader_filename = "gst/rtp/rtp.h")] public static unowned Gst.RTP.PayloadInfo? payload_info_for_pt (uint8 payload_type); [CCode (cheader_filename = "gst/rtp/rtp.h")] public static GLib.Type source_meta_api_get_type (); [CCode (cheader_filename = "gst/rtp/rtp.h")] public static unowned Gst.MetaInfo? source_meta_get_info (); } } dino-0.3.0/plugins/signal-protocol/0000755000000000000000000000000014202022370015726 5ustar rootrootdino-0.3.0/plugins/signal-protocol/CMakeLists.txt0000644000000000000000000001124614202022370020472 0ustar rootrootfind_package(GCrypt REQUIRED) find_packages(SIGNAL_PROTOCOL_PACKAGES REQUIRED Gee GLib GObject ) vala_precompile(SIGNAL_PROTOCOL_VALA_C SOURCES "src/context.vala" "src/simple_iks.vala" "src/simple_ss.vala" "src/simple_pks.vala" "src/simple_spks.vala" "src/store.vala" "src/util.vala" CUSTOM_VAPIS ${CMAKE_CURRENT_SOURCE_DIR}/vapi/signal-protocol-public.vapi ${CMAKE_CURRENT_SOURCE_DIR}/vapi/signal-protocol-native.vapi PACKAGES ${SIGNAL_PROTOCOL_PACKAGES} GENERATE_VAPI signal-protocol-vala GENERATE_HEADER signal-protocol-vala ) set(C_HEADERS_SRC "") set(C_HEADERS_TARGET "") if(NOT BUILD_LIBSIGNAL_IN_TREE) # libsignal-protocol-c has a history of breaking compatibility on the patch level # we'll have to check compatibility for every new release # distro maintainers may update this dependency after compatibility tests find_package(SignalProtocol 2.3.2 REQUIRED) else() add_subdirectory(libsignal-protocol-c EXCLUDE_FROM_ALL) set_property(TARGET curve25519 PROPERTY POSITION_INDEPENDENT_CODE ON) set_property(TARGET protobuf-c PROPERTY POSITION_INDEPENDENT_CODE ON) set_property(TARGET signal-protocol-c PROPERTY POSITION_INDEPENDENT_CODE ON) set(SIGNAL_PROTOCOL_C_HEADERS signal_protocol.h signal_protocol_types.h curve.h hkdf.h ratchet.h protocol.h session_state.h session_record.h session_pre_key.h session_builder.h session_cipher.h key_helper.h sender_key.h sender_key_state.h sender_key_record.h group_session_builder.h group_cipher.h fingerprint.h device_consistency.h ) file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/exports/signal") foreach(f ${SIGNAL_PROTOCOL_C_HEADERS}) list(APPEND C_HEADERS_SRC "${CMAKE_CURRENT_SOURCE_DIR}/libsignal-protocol-c/src/${f}") list(APPEND C_HEADERS_TARGET "${CMAKE_BINARY_DIR}/exports/signal/${f}") add_custom_command(OUTPUT "${CMAKE_BINARY_DIR}/exports/signal/${f}" COMMAND cp "${CMAKE_CURRENT_SOURCE_DIR}/libsignal-protocol-c/src/${f}" "${CMAKE_BINARY_DIR}/exports/signal/${f}" DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/libsignal-protocol-c/src/${f}" COMMENT Copy header file signal/${f} ) endforeach(f) endif() list(APPEND C_HEADERS_SRC "${CMAKE_CURRENT_SOURCE_DIR}/src/signal_helper.h") list(APPEND C_HEADERS_TARGET "${CMAKE_BINARY_DIR}/exports/signal_helper.h") add_custom_command(OUTPUT "${CMAKE_BINARY_DIR}/exports/signal_helper.h" COMMAND cp "${CMAKE_CURRENT_SOURCE_DIR}/src/signal_helper.h" "${CMAKE_BINARY_DIR}/exports/signal_helper.h" DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/signal_helper.h" COMMENT Copy header file signal_helper.h ) add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/exports/signal-protocol.vapi COMMAND cat "${CMAKE_CURRENT_SOURCE_DIR}/vapi/signal-protocol-public.vapi" "${CMAKE_BINARY_DIR}/exports/signal-protocol-vala.vapi" > "${CMAKE_BINARY_DIR}/exports/signal-protocol.vapi" DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/vapi/signal-protocol-public.vapi ${CMAKE_BINARY_DIR}/exports/signal-protocol-vala.vapi ) add_custom_target(signal-protocol-vapi DEPENDS ${CMAKE_BINARY_DIR}/exports/signal-protocol.vapi ${CMAKE_BINARY_DIR}/exports/signal-protocol-vala.h ${C_HEADERS_TARGET} ) set(CFLAGS ${VALA_CFLAGS} -I${CMAKE_CURRENT_SOURCE_DIR}/libsignal-protocol-c/src -I${CMAKE_CURRENT_SOURCE_DIR}/src) add_definitions(${CFLAGS}) add_library(signal-protocol-vala STATIC ${SIGNAL_PROTOCOL_VALA_C} ${CMAKE_CURRENT_SOURCE_DIR}/src/signal_helper.c) add_dependencies(signal-protocol-vala signal-protocol-vapi) target_link_libraries(signal-protocol-vala ${SIGNAL_PROTOCOL_PACKAGES} gcrypt signal-protocol-c m) set_property(TARGET signal-protocol-vala PROPERTY POSITION_INDEPENDENT_CODE ON) if(BUILD_TESTS) vala_precompile(SIGNAL_TEST_VALA_C SOURCES "tests/common.vala" "tests/testcase.vala" "tests/curve25519.vala" "tests/hkdf.vala" "tests/session_builder.vala" CUSTOM_VAPIS ${CMAKE_BINARY_DIR}/exports/signal-protocol-vala_internal.vapi ${CMAKE_CURRENT_SOURCE_DIR}/vapi/signal-protocol-public.vapi ${CMAKE_CURRENT_SOURCE_DIR}/vapi/signal-protocol-native.vapi PACKAGES ${SIGNAL_PROTOCOL_PACKAGES} ) set(CFLAGS ${VALA_CFLAGS} -I${CMAKE_CURRENT_BINARY_DIR}/signal-protocol) add_executable(signal-protocol-vala-test ${SIGNAL_TEST_VALA_C}) add_dependencies(signal-protocol-vala-test signal-protocol-vala) target_link_libraries(signal-protocol-vala-test signal-protocol-vala ${SIGNAL_PROTOCOL_PACKAGES}) endif(BUILD_TESTS) dino-0.3.0/plugins/signal-protocol/libsignal-protocol-c/0000755000000000000000000000000014202022370021751 5ustar rootrootdino-0.3.0/plugins/signal-protocol/src/0000755000000000000000000000000014202022370016515 5ustar rootrootdino-0.3.0/plugins/signal-protocol/src/context.vala0000644000000000000000000000761514202022370021057 0ustar rootrootnamespace Signal { public class Context { internal NativeContext native_context; private RecMutex mutex = RecMutex(); static void locking_function_lock(void* user_data) { Context ctx = (Context) user_data; ctx.mutex.lock(); } static void locking_function_unlock(void* user_data) { Context ctx = (Context) user_data; ctx.mutex.unlock(); } static void stderr_log(LogLevel level, string message, size_t len, void* user_data) { printerr(@"$level: $message\n"); } public Context(bool log = false) throws Error { throw_by_code(NativeContext.create(out native_context, this), "Error initializing native context"); throw_by_code(native_context.set_locking_functions(locking_function_lock, locking_function_unlock), "Error initializing native locking functions"); if (log) native_context.set_log_function(stderr_log); setup_crypto_provider(native_context); } public Store create_store() { return new Store(this); } public void randomize(uint8[] data) throws Error { throw_by_code(Signal.native_random(data)); } public SignedPreKeyRecord generate_signed_pre_key(IdentityKeyPair identity_key_pair, int32 id, uint64 timestamp = 0) throws Error { if (timestamp == 0) timestamp = new DateTime.now_utc().to_unix(); SignedPreKeyRecord res; throw_by_code(Protocol.KeyHelper.generate_signed_pre_key(out res, identity_key_pair, id, timestamp, native_context)); return res; } public Gee.Set generate_pre_keys(uint start, uint count) throws Error { Gee.Set res = new Gee.HashSet(); for(uint i = start; i < start+count; i++) { ECKeyPair pair = generate_key_pair(); PreKeyRecord record; throw_by_code(PreKeyRecord.create(out record, i, pair)); res.add(record); } return res; } public ECPublicKey decode_public_key(uint8[] bytes) throws Error { ECPublicKey public_key; throw_by_code(curve_decode_point(out public_key, bytes, native_context), "Error decoding public key"); return public_key; } public ECPrivateKey decode_private_key(uint8[] bytes) throws Error { ECPrivateKey private_key; throw_by_code(curve_decode_private_point(out private_key, bytes, native_context), "Error decoding private key"); return private_key; } public ECKeyPair generate_key_pair() throws Error { ECKeyPair key_pair; throw_by_code(curve_generate_key_pair(native_context, out key_pair), "Error generating key pair"); return key_pair; } public uint8[] calculate_signature(ECPrivateKey signing_key, uint8[] message) throws Error { Buffer signature; throw_by_code(Curve.calculate_signature(native_context, out signature, signing_key, message), "Error calculating signature"); return signature.data; } public SignalMessage deserialize_signal_message(uint8[] data) throws Error { SignalMessage res; throw_by_code(signal_message_deserialize(out res, data, native_context)); return res; } public SignalMessage copy_signal_message(CiphertextMessage original) throws Error { SignalMessage res; throw_by_code(signal_message_copy(out res, (SignalMessage) original, native_context)); return res; } public PreKeySignalMessage deserialize_pre_key_signal_message(uint8[] data) throws Error { PreKeySignalMessage res; throw_by_code(pre_key_signal_message_deserialize(out res, data, native_context)); return res; } public PreKeySignalMessage copy_pre_key_signal_message(CiphertextMessage original) throws Error { PreKeySignalMessage res; throw_by_code(pre_key_signal_message_copy(out res, (PreKeySignalMessage) original, native_context)); return res; } } } dino-0.3.0/plugins/signal-protocol/src/signal_helper.c0000644000000000000000000002564114202022370021505 0ustar rootroot#include #include signal_type_base* signal_type_ref_vapi(void* instance) { g_return_val_if_fail(instance != NULL, NULL); signal_type_ref(instance); return instance; } signal_type_base* signal_type_unref_vapi(void* instance) { g_return_val_if_fail(instance != NULL, NULL); signal_type_unref(instance); return NULL; } signal_protocol_address* signal_protocol_address_new(const gchar* name, int32_t device_id) { g_return_val_if_fail(name != NULL, NULL); signal_protocol_address* address = malloc(sizeof(signal_protocol_address)); address->device_id = -1; address->name = NULL; signal_protocol_address_set_name(address, name); signal_protocol_address_set_device_id(address, device_id); return address; } void signal_protocol_address_free(signal_protocol_address* ptr) { g_return_if_fail(ptr != NULL); if (ptr->name) { g_free((void*)ptr->name); } return free(ptr); } void signal_protocol_address_set_name(signal_protocol_address* self, const gchar* name) { g_return_if_fail(self != NULL); g_return_if_fail(name != NULL); gchar* n = g_malloc(strlen(name)+1); memcpy(n, name, strlen(name)); n[strlen(name)] = 0; if (self->name) { g_free((void*)self->name); } self->name = n; self->name_len = strlen(n); } gchar* signal_protocol_address_get_name(signal_protocol_address* self) { g_return_val_if_fail(self != NULL, NULL); g_return_val_if_fail(self->name != NULL, 0); gchar* res = g_malloc(sizeof(char) * (self->name_len + 1)); memcpy(res, self->name, self->name_len); res[self->name_len] = 0; return res; } int32_t signal_protocol_address_get_device_id(signal_protocol_address* self) { g_return_val_if_fail(self != NULL, -1); return self->device_id; } void signal_protocol_address_set_device_id(signal_protocol_address* self, int32_t device_id) { g_return_if_fail(self != NULL); self->device_id = device_id; } int signal_vala_randomize(uint8_t *data, size_t len) { gcry_randomize(data, len, GCRY_STRONG_RANDOM); return SG_SUCCESS; } int signal_vala_random_generator(uint8_t *data, size_t len, void *user_data) { gcry_randomize(data, len, GCRY_STRONG_RANDOM); return SG_SUCCESS; } int signal_vala_hmac_sha256_init(void **hmac_context, const uint8_t *key, size_t key_len, void *user_data) { gcry_mac_hd_t* ctx = malloc(sizeof(gcry_mac_hd_t)); if (!ctx) return SG_ERR_NOMEM; if (gcry_mac_open(ctx, GCRY_MAC_HMAC_SHA256, 0, 0)) { free(ctx); return SG_ERR_UNKNOWN; } if (gcry_mac_setkey(*ctx, key, key_len)) { free(ctx); return SG_ERR_UNKNOWN; } *hmac_context = ctx; return SG_SUCCESS; } int signal_vala_hmac_sha256_update(void *hmac_context, const uint8_t *data, size_t data_len, void *user_data) { gcry_mac_hd_t* ctx = hmac_context; if (gcry_mac_write(*ctx, data, data_len)) return SG_ERR_UNKNOWN; return SG_SUCCESS; } int signal_vala_hmac_sha256_final(void *hmac_context, signal_buffer **output, void *user_data) { size_t len = gcry_mac_get_algo_maclen(GCRY_MAC_HMAC_SHA256); uint8_t md[len]; gcry_mac_hd_t* ctx = hmac_context; if (gcry_mac_read(*ctx, md, &len)) return SG_ERR_UNKNOWN; signal_buffer *output_buffer = signal_buffer_create(md, len); if (!output_buffer) return SG_ERR_NOMEM; *output = output_buffer; return SG_SUCCESS; } void signal_vala_hmac_sha256_cleanup(void *hmac_context, void *user_data) { gcry_mac_hd_t* ctx = hmac_context; if (ctx) { gcry_mac_close(*ctx); free(ctx); } } int signal_vala_sha512_digest_init(void **digest_context, void *user_data) { gcry_md_hd_t* ctx = malloc(sizeof(gcry_mac_hd_t)); if (!ctx) return SG_ERR_NOMEM; if (gcry_md_open(ctx, GCRY_MD_SHA512, 0)) { free(ctx); return SG_ERR_UNKNOWN; } *digest_context = ctx; return SG_SUCCESS; } int signal_vala_sha512_digest_update(void *digest_context, const uint8_t *data, size_t data_len, void *user_data) { gcry_md_hd_t* ctx = digest_context; gcry_md_write(*ctx, data, data_len); return SG_SUCCESS; } int signal_vala_sha512_digest_final(void *digest_context, signal_buffer **output, void *user_data) { size_t len = gcry_md_get_algo_dlen(GCRY_MD_SHA512); gcry_md_hd_t* ctx = digest_context; uint8_t* md = gcry_md_read(*ctx, GCRY_MD_SHA512); if (!md) return SG_ERR_UNKNOWN; gcry_md_reset(*ctx); signal_buffer *output_buffer = signal_buffer_create(md, len); free(md); if (!output_buffer) return SG_ERR_NOMEM; *output = output_buffer; return SG_SUCCESS; } void signal_vala_sha512_digest_cleanup(void *digest_context, void *user_data) { gcry_md_hd_t* ctx = digest_context; if (ctx) { gcry_md_close(*ctx); free(ctx); } } const int aes_cipher(int cipher, size_t key_len, int* algo, int* mode) { switch (key_len) { case 16: *algo = GCRY_CIPHER_AES128; break; case 24: *algo = GCRY_CIPHER_AES192; break; case 32: *algo = GCRY_CIPHER_AES256; break; default: return SG_ERR_UNKNOWN; } switch (cipher) { case SG_CIPHER_AES_CBC_PKCS5: *mode = GCRY_CIPHER_MODE_CBC; break; case SG_CIPHER_AES_CTR_NOPADDING: *mode = GCRY_CIPHER_MODE_CTR; break; case SG_CIPHER_AES_GCM_NOPADDING: *mode = GCRY_CIPHER_MODE_GCM; break; default: return SG_ERR_UNKNOWN; } return SG_SUCCESS; } int signal_vala_encrypt(signal_buffer **output, int cipher, const uint8_t *key, size_t key_len, const uint8_t *iv, size_t iv_len, const uint8_t *plaintext, size_t plaintext_len, void *user_data) { int algo, mode, error_code = SG_ERR_UNKNOWN; if (aes_cipher(cipher, key_len, &algo, &mode)) return SG_ERR_INVAL; gcry_cipher_hd_t ctx = {0}; if (gcry_cipher_open(&ctx, algo, mode, 0)) return SG_ERR_NOMEM; signal_buffer* padded = 0; signal_buffer* out_buf = 0; goto no_error; error: gcry_cipher_close(ctx); if (padded != 0) { signal_buffer_bzero_free(padded); } if (out_buf != 0) { signal_buffer_free(out_buf); } return error_code; no_error: if (gcry_cipher_setkey(ctx, key, key_len)) goto error; uint8_t tag_len = 0, pad_len = 0; switch (cipher) { case SG_CIPHER_AES_CBC_PKCS5: if (gcry_cipher_setiv(ctx, iv, iv_len)) goto error; pad_len = 16 - (plaintext_len % 16); if (pad_len == 0) pad_len = 16; break; case SG_CIPHER_AES_CTR_NOPADDING: if (gcry_cipher_setctr(ctx, iv, iv_len)) goto error; break; case SG_CIPHER_AES_GCM_NOPADDING: if (gcry_cipher_setiv(ctx, iv, iv_len)) goto error; tag_len = 16; break; default: return SG_ERR_UNKNOWN; } size_t padded_len = plaintext_len + pad_len; padded = signal_buffer_alloc(padded_len); if (padded == 0) { error_code = SG_ERR_NOMEM; goto error; } memset(signal_buffer_data(padded) + plaintext_len, pad_len, pad_len); memcpy(signal_buffer_data(padded), plaintext, plaintext_len); out_buf = signal_buffer_alloc(padded_len + tag_len); if (out_buf == 0) { error_code = SG_ERR_NOMEM; goto error; } if (gcry_cipher_encrypt(ctx, signal_buffer_data(out_buf), padded_len, signal_buffer_data(padded), padded_len)) goto error; if (tag_len > 0) { if (gcry_cipher_gettag(ctx, signal_buffer_data(out_buf) + padded_len, tag_len)) goto error; } *output = out_buf; out_buf = 0; signal_buffer_bzero_free(padded); padded = 0; gcry_cipher_close(ctx); return SG_SUCCESS; } int signal_vala_decrypt(signal_buffer **output, int cipher, const uint8_t *key, size_t key_len, const uint8_t *iv, size_t iv_len, const uint8_t *ciphertext, size_t ciphertext_len, void *user_data) { int algo, mode, error_code = SG_ERR_UNKNOWN; *output = 0; if (aes_cipher(cipher, key_len, &algo, &mode)) return SG_ERR_INVAL; if (ciphertext_len == 0) return SG_ERR_INVAL; gcry_cipher_hd_t ctx = {0}; if (gcry_cipher_open(&ctx, algo, mode, 0)) return SG_ERR_NOMEM; signal_buffer* out_buf = 0; goto no_error; error: gcry_cipher_close(ctx); if (out_buf != 0) { signal_buffer_bzero_free(out_buf); } return error_code; no_error: if (gcry_cipher_setkey(ctx, key, key_len)) goto error; uint8_t tag_len = 0, pkcs_pad = FALSE; switch (cipher) { case SG_CIPHER_AES_CBC_PKCS5: if (gcry_cipher_setiv(ctx, iv, iv_len)) goto error; pkcs_pad = TRUE; break; case SG_CIPHER_AES_CTR_NOPADDING: if (gcry_cipher_setctr(ctx, iv, iv_len)) goto error; break; case SG_CIPHER_AES_GCM_NOPADDING: if (gcry_cipher_setiv(ctx, iv, iv_len)) goto error; if (ciphertext_len < 16) goto error; tag_len = 16; break; default: goto error; } size_t padded_len = ciphertext_len - tag_len; out_buf = signal_buffer_alloc(padded_len); if (out_buf == 0) { error_code = SG_ERR_NOMEM; goto error; } if (gcry_cipher_decrypt(ctx, signal_buffer_data(out_buf), signal_buffer_len(out_buf), ciphertext, padded_len)) goto error; if (tag_len > 0) { if (gcry_cipher_checktag(ctx, ciphertext + padded_len, tag_len)) goto error; } if (pkcs_pad) { uint8_t pad_len = signal_buffer_data(out_buf)[padded_len - 1]; if (pad_len > 16 || pad_len > padded_len) goto error; *output = signal_buffer_create(signal_buffer_data(out_buf), padded_len - pad_len); signal_buffer_bzero_free(out_buf); out_buf = 0; } else { *output = out_buf; out_buf = 0; } gcry_cipher_close(ctx); return SG_SUCCESS; } void setup_signal_vala_crypto_provider(signal_context *context) { gcry_check_version(NULL); signal_crypto_provider provider = { .random_func = signal_vala_random_generator, .hmac_sha256_init_func = signal_vala_hmac_sha256_init, .hmac_sha256_update_func = signal_vala_hmac_sha256_update, .hmac_sha256_final_func = signal_vala_hmac_sha256_final, .hmac_sha256_cleanup_func = signal_vala_hmac_sha256_cleanup, .sha512_digest_init_func = signal_vala_sha512_digest_init, .sha512_digest_update_func = signal_vala_sha512_digest_update, .sha512_digest_final_func = signal_vala_sha512_digest_final, .sha512_digest_cleanup_func = signal_vala_sha512_digest_cleanup, .encrypt_func = signal_vala_encrypt, .decrypt_func = signal_vala_decrypt, .user_data = 0 }; signal_context_set_crypto_provider(context, &provider); } dino-0.3.0/plugins/signal-protocol/src/signal_helper.h0000644000000000000000000000426614202022370021512 0ustar rootroot#ifndef SIGNAL_PROTOCOL_VALA_HELPER #define SIGNAL_PROTOCOL_VALA_HELPER 1 #include #include #include #define SG_CIPHER_AES_GCM_NOPADDING 1000 signal_type_base* signal_type_ref_vapi(void* what); signal_type_base* signal_type_unref_vapi(void* what); signal_protocol_address* signal_protocol_address_new(const gchar* name, int32_t device_id); void signal_protocol_address_free(signal_protocol_address* ptr); void signal_protocol_address_set_name(signal_protocol_address* self, const gchar* name); gchar* signal_protocol_address_get_name(signal_protocol_address* self); void signal_protocol_address_set_device_id(signal_protocol_address* self, int32_t device_id); int32_t signal_protocol_address_get_device_id(signal_protocol_address* self); int signal_vala_randomize(uint8_t *data, size_t len); int signal_vala_random_generator(uint8_t *data, size_t len, void *user_data); int signal_vala_hmac_sha256_init(void **hmac_context, const uint8_t *key, size_t key_len, void *user_data); int signal_vala_hmac_sha256_update(void *hmac_context, const uint8_t *data, size_t data_len, void *user_data); int signal_vala_hmac_sha256_final(void *hmac_context, signal_buffer **output, void *user_data); void signal_vala_hmac_sha256_cleanup(void *hmac_context, void *user_data); int signal_vala_sha512_digest_init(void **digest_context, void *user_data); int signal_vala_sha512_digest_update(void *digest_context, const uint8_t *data, size_t data_len, void *user_data); int signal_vala_sha512_digest_final(void *digest_context, signal_buffer **output, void *user_data); void signal_vala_sha512_digest_cleanup(void *digest_context, void *user_data); int signal_vala_encrypt(signal_buffer **output, int cipher, const uint8_t *key, size_t key_len, const uint8_t *iv, size_t iv_len, const uint8_t *plaintext, size_t plaintext_len, void *user_data); int signal_vala_decrypt(signal_buffer **output, int cipher, const uint8_t *key, size_t key_len, const uint8_t *iv, size_t iv_len, const uint8_t *ciphertext, size_t ciphertext_len, void *user_data); void setup_signal_vala_crypto_provider(signal_context *context); #endif dino-0.3.0/plugins/signal-protocol/src/simple_iks.vala0000644000000000000000000000355214202022370021526 0ustar rootrootusing Gee; namespace Signal { public class SimpleIdentityKeyStore : IdentityKeyStore { public override Bytes identity_key_private { get; set; } public override Bytes identity_key_public { get; set; } public override uint32 local_registration_id { get; set; } private Map> trusted_identities = new HashMap>(); public override void save_identity(Address address, uint8[] key) throws Error { string name = address.name; if (trusted_identities.has_key(name)) { if (trusted_identities[name].has_key(address.device_id)) { trusted_identities[name][address.device_id].key = key; trusted_identity_updated(trusted_identities[name][address.device_id]); } else { trusted_identities[name][address.device_id] = new TrustedIdentity.by_address(address, key); trusted_identity_added(trusted_identities[name][address.device_id]); } } else { trusted_identities[name] = new HashMap(); trusted_identities[name][address.device_id] = new TrustedIdentity.by_address(address, key); trusted_identity_added(trusted_identities[name][address.device_id]); } } public override bool is_trusted_identity(Address address, uint8[] key) throws Error { if (!trusted_identities.has_key(address.name)) return true; if (!trusted_identities[address.name].has_key(address.device_id)) return true; uint8[] other_key = trusted_identities[address.name][address.device_id].key; if (other_key.length != key.length) return false; for (int i = 0; i < key.length; i++) { if (other_key[i] != key[i]) return false; } return true; } } } dino-0.3.0/plugins/signal-protocol/src/simple_pks.vala0000644000000000000000000000166514202022370021540 0ustar rootrootusing Gee; namespace Signal { public class SimplePreKeyStore : PreKeyStore { private Map pre_key_map = new HashMap(); public override uint8[]? load_pre_key(uint32 pre_key_id) throws Error { if (contains_pre_key(pre_key_id)) { return pre_key_map[pre_key_id].record; } return null; } public override void store_pre_key(uint32 pre_key_id, uint8[] record) throws Error { PreKeyStore.Key key = new Key(pre_key_id, record); pre_key_map[pre_key_id] = key; pre_key_stored(key); } public override bool contains_pre_key(uint32 pre_key_id) throws Error { return pre_key_map.has_key(pre_key_id); } public override void delete_pre_key(uint32 pre_key_id) throws Error { PreKeyStore.Key key; if (pre_key_map.unset(pre_key_id, out key)) { pre_key_deleted(key); } } } }dino-0.3.0/plugins/signal-protocol/src/simple_spks.vala0000644000000000000000000000201214202022370021706 0ustar rootrootusing Gee; namespace Signal { public class SimpleSignedPreKeyStore : SignedPreKeyStore { private Map pre_key_map = new HashMap(); public override uint8[]? load_signed_pre_key(uint32 pre_key_id) throws Error { if (contains_signed_pre_key(pre_key_id)) { return pre_key_map[pre_key_id].record; } return null; } public override void store_signed_pre_key(uint32 pre_key_id, uint8[] record) throws Error { SignedPreKeyStore.Key key = new Key(pre_key_id, record); pre_key_map[pre_key_id] = key; signed_pre_key_stored(key); } public override bool contains_signed_pre_key(uint32 pre_key_id) throws Error { return pre_key_map.has_key(pre_key_id); } public override void delete_signed_pre_key(uint32 pre_key_id) throws Error { SignedPreKeyStore.Key key; if (pre_key_map.unset(pre_key_id, out key)) { signed_pre_key_deleted(key); } } } }dino-0.3.0/plugins/signal-protocol/src/simple_ss.vala0000644000000000000000000000530014202022370021356 0ustar rootrootusing Gee; namespace Signal { public class SimpleSessionStore : SessionStore { private Map> session_map = new HashMap>(); public override uint8[]? load_session(Address address) throws Error { if (session_map.has_key(address.name)) { foreach (SessionStore.Session session in session_map[address.name]) { if (session.device_id == address.device_id) return session.record; } } return null; } public override IntList get_sub_device_sessions(string name) throws Error { IntList res = new IntList(); if (session_map.has_key(name)) { foreach (SessionStore.Session session in session_map[name]) { res.add(session.device_id); } } return res; } public override void store_session(Address address, uint8[] record) throws Error { if (contains_session(address)) { delete_session(address); } if (!session_map.has_key(address.name)) { session_map[address.name] = new ArrayList(); } SessionStore.Session session = new Session() { name = address.name, device_id = address.device_id, record = record }; session_map[address.name].add(session); session_stored(session); } public override bool contains_session(Address address) throws Error { if (!session_map.has_key(address.name)) return false; foreach (SessionStore.Session session in session_map[address.name]) { if (session.device_id == address.device_id) return true; } return false; } public override void delete_session(Address address) throws Error { if (!session_map.has_key(address.name)) throw_by_code(ErrorCode.UNKNOWN, "No session found"); foreach (SessionStore.Session session in session_map[address.name]) { if (session.device_id == address.device_id) { session_map[address.name].remove(session); if (session_map[address.name].size == 0) { session_map.unset(address.name); } session_removed(session); return; } } } public override void delete_all_sessions(string name) throws Error { if (session_map.has_key(name)) { foreach (SessionStore.Session session in session_map[name]) { session_map[name].remove(session); if (session_map[name].size == 0) { session_map.unset(name); } session_removed(session); } } } } }dino-0.3.0/plugins/signal-protocol/src/store.vala0000644000000000000000000003516114202022370020524 0ustar rootrootnamespace Signal { public abstract class IdentityKeyStore : Object { public abstract Bytes identity_key_private { get; set; } public abstract Bytes identity_key_public { get; set; } public abstract uint32 local_registration_id { get; set; } public signal void trusted_identity_added(TrustedIdentity id); public signal void trusted_identity_updated(TrustedIdentity id); public abstract void save_identity(Address address, uint8[] key) throws Error ; public abstract bool is_trusted_identity(Address address, uint8[] key) throws Error ; public class TrustedIdentity { public uint8[] key { get; set; } public string name { get; private set; } public int device_id { get; private set; } public TrustedIdentity(string name, int device_id, uint8[] key) { this.key = key; this.name = name; this.device_id = device_id; } public TrustedIdentity.by_address(Address address, uint8[] key) { this(address.name, address.device_id, key); } } } public abstract class SessionStore : Object { public signal void session_stored(Session session); public signal void session_removed(Session session); public abstract uint8[]? load_session(Address address) throws Error ; public abstract IntList get_sub_device_sessions(string name) throws Error ; public abstract void store_session(Address address, uint8[] record) throws Error ; public abstract bool contains_session(Address address) throws Error ; public abstract void delete_session(Address address) throws Error ; public abstract void delete_all_sessions(string name) throws Error ; public class Session { public string name; public int device_id; public uint8[] record; } } public abstract class PreKeyStore : Object { public signal void pre_key_stored(Key key); public signal void pre_key_deleted(Key key); public abstract uint8[]? load_pre_key(uint32 pre_key_id) throws Error ; public abstract void store_pre_key(uint32 pre_key_id, uint8[] record) throws Error ; public abstract bool contains_pre_key(uint32 pre_key_id) throws Error ; public abstract void delete_pre_key(uint32 pre_key_id) throws Error ; public class Key { public uint32 key_id { get; private set; } public uint8[] record { get; private set; } public Key(uint32 key_id, uint8[] record) { this.key_id = key_id; this.record = record; } } } public abstract class SignedPreKeyStore : Object { public signal void signed_pre_key_stored(Key key); public signal void signed_pre_key_deleted(Key key); public abstract uint8[]? load_signed_pre_key(uint32 pre_key_id) throws Error ; public abstract void store_signed_pre_key(uint32 pre_key_id, uint8[] record) throws Error ; public abstract bool contains_signed_pre_key(uint32 pre_key_id) throws Error ; public abstract void delete_signed_pre_key(uint32 pre_key_id) throws Error ; public class Key { public uint32 key_id { get; private set; } public uint8[] record { get; private set; } public Key(uint32 key_id, uint8[] record) { this.key_id = key_id; this.record = record; } } } public class Store : Object { public Context context { get; private set; } public IdentityKeyStore identity_key_store { get; set; default = new SimpleIdentityKeyStore(); } public SessionStore session_store { get; set; default = new SimpleSessionStore(); } public PreKeyStore pre_key_store { get; set; default = new SimplePreKeyStore(); } public SignedPreKeyStore signed_pre_key_store { get; set; default = new SimpleSignedPreKeyStore(); } public uint32 local_registration_id { get { return identity_key_store.local_registration_id; } } internal NativeStoreContext native_context {get { return native_store_context_; }} private NativeStoreContext native_store_context_; static int iks_get_identity_key_pair(out Buffer public_data, out Buffer private_data, void* user_data) { Store store = (Store) user_data; public_data = new Buffer.from(store.identity_key_store.identity_key_public.get_data()); private_data = new Buffer.from(store.identity_key_store.identity_key_private.get_data()); return 0; } static int iks_get_local_registration_id(void* user_data, out uint32 registration_id) { Store store = (Store) user_data; registration_id = store.identity_key_store.local_registration_id; return 0; } static int iks_save_identity(Address address, uint8[] key, void* user_data) { Store store = (Store) user_data; return catch_to_code(() => { store.identity_key_store.save_identity(address, key); return 0; }); } static int iks_is_trusted_identity(Address address, uint8[] key, void* user_data) { Store store = (Store) user_data; return catch_to_code(() => { return store.identity_key_store.is_trusted_identity(address, key) ? 1 : 0; }); } static void iks_destroy_func(void* user_data) { } static int ss_load_session_func(out Buffer? record, out Buffer? user_record, Address address, void* user_data) { Store store = (Store) user_data; user_record = null; // No support for user_record uint8[]? res = null; try { res = store.session_store.load_session(address); } catch (Error e) { record = null; return e.code; } if (res == null) { record = null; return 0; } record = new Buffer.from((!)res); if (record == null) return ErrorCode.NOMEM; return 1; } static int ss_get_sub_device_sessions_func(out IntList? sessions, char[] name, void* user_data) { Store store = (Store) user_data; try { sessions = store.session_store.get_sub_device_sessions(carr_to_string(name)); } catch (Error e) { sessions = null; return e.code; } return 0; } static int ss_store_session_func(Address address, uint8[] record, uint8[] user_record, void* user_data) { // Ignoring user_record Store store = (Store) user_data; return catch_to_code(() => { store.session_store.store_session(address, record); return 0; }); } static int ss_contains_session_func(Address address, void* user_data) { Store store = (Store) user_data; return catch_to_code(() => { return store.session_store.contains_session(address) ? 1 : 0; }); } static int ss_delete_session_func(Address address, void* user_data) { Store store = (Store) user_data; return catch_to_code(() => { store.session_store.delete_session(address); return 0; }); } static int ss_delete_all_sessions_func(char[] name, void* user_data) { Store store = (Store) user_data; return catch_to_code(() => { store.session_store.delete_all_sessions(carr_to_string(name)); return 0; }); } static void ss_destroy_func(void* user_data) { } static int pks_load_pre_key(out Buffer? record, uint32 pre_key_id, void* user_data) { Store store = (Store) user_data; uint8[]? res = null; try { res = store.pre_key_store.load_pre_key(pre_key_id); } catch (Error e) { record = null; return e.code; } if (res == null) { record = new Buffer(0); return 0; } record = new Buffer.from((!)res); if (record == null) return ErrorCode.NOMEM; return 1; } static int pks_store_pre_key(uint32 pre_key_id, uint8[] record, void* user_data) { Store store = (Store) user_data; return catch_to_code(() => { store.pre_key_store.store_pre_key(pre_key_id, record); return 0; }); } static int pks_contains_pre_key(uint32 pre_key_id, void* user_data) { Store store = (Store) user_data; return catch_to_code(() => { return store.pre_key_store.contains_pre_key(pre_key_id) ? 1 : 0; }); } static int pks_remove_pre_key(uint32 pre_key_id, void* user_data) { Store store = (Store) user_data; return catch_to_code(() => { store.pre_key_store.delete_pre_key(pre_key_id); return 0; }); } static void pks_destroy_func(void* user_data) { } static int spks_load_signed_pre_key(out Buffer? record, uint32 pre_key_id, void* user_data) { Store store = (Store) user_data; uint8[]? res = null; try { res = store.signed_pre_key_store.load_signed_pre_key(pre_key_id); } catch (Error e) { record = null; return e.code; } if (res == null) { record = new Buffer(0); return 0; } record = new Buffer.from((!)res); if (record == null) return ErrorCode.NOMEM; return 1; } static int spks_store_signed_pre_key(uint32 pre_key_id, uint8[] record, void* user_data) { Store store = (Store) user_data; return catch_to_code(() => { store.signed_pre_key_store.store_signed_pre_key(pre_key_id, record); return 0; }); } static int spks_contains_signed_pre_key(uint32 pre_key_id, void* user_data) { Store store = (Store) user_data; return catch_to_code(() => { return store.signed_pre_key_store.contains_signed_pre_key(pre_key_id) ? 1 : 0; }); } static int spks_remove_signed_pre_key(uint32 pre_key_id, void* user_data) { Store store = (Store) user_data; return catch_to_code(() => { store.signed_pre_key_store.delete_signed_pre_key(pre_key_id); return 0; }); } static void spks_destroy_func(void* user_data) { } internal Store(Context context) { this.context = context; NativeStoreContext.create(out native_store_context_, context.native_context); NativeIdentityKeyStore iks = NativeIdentityKeyStore() { get_identity_key_pair = iks_get_identity_key_pair, get_local_registration_id = iks_get_local_registration_id, save_identity = iks_save_identity, is_trusted_identity = iks_is_trusted_identity, destroy_func = iks_destroy_func, user_data = this }; native_context.set_identity_key_store(iks); NativeSessionStore ss = NativeSessionStore() { load_session_func = ss_load_session_func, get_sub_device_sessions_func = ss_get_sub_device_sessions_func, store_session_func = ss_store_session_func, contains_session_func = ss_contains_session_func, delete_session_func = ss_delete_session_func, delete_all_sessions_func = ss_delete_all_sessions_func, destroy_func = ss_destroy_func, user_data = this }; native_context.set_session_store(ss); NativePreKeyStore pks = NativePreKeyStore() { load_pre_key = pks_load_pre_key, store_pre_key = pks_store_pre_key, contains_pre_key = pks_contains_pre_key, remove_pre_key = pks_remove_pre_key, destroy_func = pks_destroy_func, user_data = this }; native_context.set_pre_key_store(pks); NativeSignedPreKeyStore spks = NativeSignedPreKeyStore() { load_signed_pre_key = spks_load_signed_pre_key, store_signed_pre_key = spks_store_signed_pre_key, contains_signed_pre_key = spks_contains_signed_pre_key, remove_signed_pre_key = spks_remove_signed_pre_key, destroy_func = spks_destroy_func, user_data = this }; native_context.set_signed_pre_key_store(spks); } public SessionBuilder create_session_builder(Address other) throws Error { SessionBuilder builder; throw_by_code(session_builder_create(out builder, native_context, other, context.native_context), "Error creating session builder"); return builder; } public SessionCipher create_session_cipher(Address other) throws Error { SessionCipher cipher; throw_by_code(session_cipher_create(out cipher, native_context, other, context.native_context)); return cipher; } public IdentityKeyPair identity_key_pair { owned get { IdentityKeyPair pair; Protocol.Identity.get_key_pair(native_context, out pair); return pair; } } public bool is_trusted_identity(Address address, ECPublicKey key) throws Error { return throw_by_code(Protocol.Identity.is_trusted_identity(native_context, address, key)) == 1; } public void save_identity(Address address, ECPublicKey key) throws Error { throw_by_code(Protocol.Identity.save_identity(native_context, address, key)); } public bool contains_session(Address other) throws Error { return throw_by_code(Protocol.Session.contains_session(native_context, other)) == 1; } public void delete_session(Address address) throws Error { throw_by_code(Protocol.Session.delete_session(native_context, address)); } public SessionRecord load_session(Address other) throws Error { SessionRecord record; throw_by_code(Protocol.Session.load_session(native_context, out record, other)); return record; } public bool contains_pre_key(uint32 pre_key_id) throws Error { return throw_by_code(Protocol.PreKey.contains_key(native_context, pre_key_id)) == 1; } public void store_pre_key(PreKeyRecord record) throws Error { throw_by_code(Protocol.PreKey.store_key(native_context, record)); } public PreKeyRecord load_pre_key(uint32 pre_key_id) throws Error { PreKeyRecord res; throw_by_code(Protocol.PreKey.load_key(native_context, out res, pre_key_id)); return res; } public bool contains_signed_pre_key(uint32 pre_key_id) throws Error { return throw_by_code(Protocol.SignedPreKey.contains_key(native_context, pre_key_id)) == 1; } public void store_signed_pre_key(SignedPreKeyRecord record) throws Error { throw_by_code(Protocol.SignedPreKey.store_key(native_context, record)); } public SignedPreKeyRecord load_signed_pre_key(uint32 pre_key_id) throws Error { SignedPreKeyRecord res; throw_by_code(Protocol.SignedPreKey.load_key(native_context, out res, pre_key_id)); return res; } } } dino-0.3.0/plugins/signal-protocol/src/util.vala0000644000000000000000000000313514202022370020341 0ustar rootrootnamespace Signal { public ECPublicKey generate_public_key(ECPrivateKey private_key) throws Error { ECPublicKey public_key; throw_by_code(ECPublicKey.generate(out public_key, private_key), "Error generating public key"); return public_key; } public uint8[] calculate_agreement(ECPublicKey public_key, ECPrivateKey private_key) throws Error { uint8[] res; int len = Curve.calculate_agreement(out res, public_key, private_key); throw_by_code(len, "Error calculating agreement"); res.length = len; return res; } public bool verify_signature(ECPublicKey signing_key, uint8[] message, uint8[] signature) throws Error { return throw_by_code(Curve.verify_signature(signing_key, message, signature)) == 1; } public PreKeyBundle create_pre_key_bundle(uint32 registration_id, int device_id, uint32 pre_key_id, ECPublicKey? pre_key_public, uint32 signed_pre_key_id, ECPublicKey? signed_pre_key_public, uint8[]? signed_pre_key_signature, ECPublicKey? identity_key) throws Error { PreKeyBundle res; throw_by_code(PreKeyBundle.create(out res, registration_id, device_id, pre_key_id, pre_key_public, signed_pre_key_id, signed_pre_key_public, signed_pre_key_signature, identity_key), "Error creating PreKeyBundle"); return res; } internal string carr_to_string(char[] carr) { char[] nu = new char[carr.length + 1]; Memory.copy(nu, carr, carr.length); return (string) nu; } internal delegate int CodeErroringFunc() throws Error; internal int catch_to_code(CodeErroringFunc func) { try { return func(); } catch (Error e) { return e.code; } } }dino-0.3.0/plugins/signal-protocol/tests/0000755000000000000000000000000014202022370017070 5ustar rootrootdino-0.3.0/plugins/signal-protocol/tests/common.vala0000644000000000000000000000551314202022370021231 0ustar rootrootnamespace Signal.Test { int main(string[] args) { GLib.Test.init(ref args); GLib.Test.set_nonfatal_assertions(); TestSuite.get_root().add_suite(new Curve25519().get_suite()); TestSuite.get_root().add_suite(new SessionBuilderTest().get_suite()); TestSuite.get_root().add_suite(new HKDF().get_suite()); return GLib.Test.run(); } Store setup_test_store_context(Context global_context) { Store store = global_context.create_store(); try { store.identity_key_store.local_registration_id = (Random.next_int() % 16380) + 1; ECKeyPair key_pair = global_context.generate_key_pair(); store.identity_key_store.identity_key_private = new Bytes(key_pair.private.serialize()); store.identity_key_store.identity_key_public = new Bytes(key_pair.public.serialize()); } catch (Error e) { fail_if_reached(); } return store; } ECPublicKey? create_test_ec_public_key(Context context) { try { return context.generate_key_pair().public; } catch (Error e) { fail_if_reached(); return null; } } bool fail_if(bool exp, string? reason = null) { if (exp) { if (reason != null) GLib.Test.message(reason); GLib.Test.fail(); return true; } return false; } void fail_if_reached(string? reason = null) { fail_if(true, reason); } delegate void ErrorFunc() throws Error; void fail_if_not_error_code(ErrorFunc func, int expectedCode, string? reason = null) { try { func(); fail_if_reached(@"$(reason + ": " ?? "")no error thrown"); } catch (Error e) { fail_if_not_eq_int(e.code, expectedCode, @"$(reason + ": " ?? "")caught unexpected error"); } } bool fail_if_not(bool exp, string? reason = null) { return fail_if(!exp, reason); } bool fail_if_eq_int(int left, int right, string? reason = null) { return fail_if(left == right, @"$(reason + ": " ?? "")$left == $right"); } bool fail_if_not_eq_int(int left, int right, string? reason = null) { return fail_if_not(left == right, @"$(reason + ": " ?? "")$left != $right"); } bool fail_if_not_eq_str(string left, string right, string? reason = null) { return fail_if_not(left == right, @"$(reason + ": " ?? "")$left != $right"); } bool fail_if_not_eq_uint8_arr(uint8[] left, uint8[] right, string? reason = null) { if (fail_if_not_eq_int(left.length, right.length, @"$(reason + ": " ?? "")array length not equal")) return true; return fail_if_not_eq_str(Base64.encode(left), Base64.encode(right), reason); } bool fail_if_not_zero_int(int zero, string? reason = null) { return fail_if_not_eq_int(zero, 0, reason); } bool fail_if_zero_int(int zero, string? reason = null) { return fail_if_eq_int(zero, 0, reason); } bool fail_if_null(void* what, string? reason = null) { return fail_if(what == null || (size_t)what == 0, reason); } } dino-0.3.0/plugins/signal-protocol/tests/curve25519.vala0000644000000000000000000002012314202022370021465 0ustar rootrootnamespace Signal.Test { class Curve25519 : Gee.TestCase { public Curve25519() { base("Curve25519"); add_test("agreement", test_curve25519_agreement); add_test("generate_public", test_curve25519_generate_public); add_test("random_agreements", test_curve25519_random_agreements); add_test("signature", test_curve25519_signature); } private Context global_context; public override void set_up() { try { global_context = new Context(); } catch (Error e) { fail_if_reached(); } } public override void tear_down() { global_context = null; } void test_curve25519_agreement() { try { uint8[] alicePublic = { 0x05, 0x1b, 0xb7, 0x59, 0x66, 0xf2, 0xe9, 0x3a, 0x36, 0x91, 0xdf, 0xff, 0x94, 0x2b, 0xb2, 0xa4, 0x66, 0xa1, 0xc0, 0x8b, 0x8d, 0x78, 0xca, 0x3f, 0x4d, 0x6d, 0xf8, 0xb8, 0xbf, 0xa2, 0xe4, 0xee, 0x28}; uint8[] alicePrivate = { 0xc8, 0x06, 0x43, 0x9d, 0xc9, 0xd2, 0xc4, 0x76, 0xff, 0xed, 0x8f, 0x25, 0x80, 0xc0, 0x88, 0x8d, 0x58, 0xab, 0x40, 0x6b, 0xf7, 0xae, 0x36, 0x98, 0x87, 0x90, 0x21, 0xb9, 0x6b, 0xb4, 0xbf, 0x59}; uint8[] bobPublic = { 0x05, 0x65, 0x36, 0x14, 0x99, 0x3d, 0x2b, 0x15, 0xee, 0x9e, 0x5f, 0xd3, 0xd8, 0x6c, 0xe7, 0x19, 0xef, 0x4e, 0xc1, 0xda, 0xae, 0x18, 0x86, 0xa8, 0x7b, 0x3f, 0x5f, 0xa9, 0x56, 0x5a, 0x27, 0xa2, 0x2f}; uint8[] bobPrivate = { 0xb0, 0x3b, 0x34, 0xc3, 0x3a, 0x1c, 0x44, 0xf2, 0x25, 0xb6, 0x62, 0xd2, 0xbf, 0x48, 0x59, 0xb8, 0x13, 0x54, 0x11, 0xfa, 0x7b, 0x03, 0x86, 0xd4, 0x5f, 0xb7, 0x5d, 0xc5, 0xb9, 0x1b, 0x44, 0x66}; uint8[] shared = { 0x32, 0x5f, 0x23, 0x93, 0x28, 0x94, 0x1c, 0xed, 0x6e, 0x67, 0x3b, 0x86, 0xba, 0x41, 0x01, 0x74, 0x48, 0xe9, 0x9b, 0x64, 0x9a, 0x9c, 0x38, 0x06, 0xc1, 0xdd, 0x7c, 0xa4, 0xc4, 0x77, 0xe6, 0x29}; ECPublicKey alice_public_key = global_context.decode_public_key(alicePublic); ECPrivateKey alice_private_key = global_context.decode_private_key(alicePrivate); ECPublicKey bob_public_key = global_context.decode_public_key(bobPublic); ECPrivateKey bob_private_key = global_context.decode_private_key(bobPrivate); uint8[] shared_one = calculate_agreement(alice_public_key, bob_private_key); uint8[] shared_two = calculate_agreement(bob_public_key, alice_private_key); fail_if_not_eq_int(shared_one.length, 32); fail_if_not_eq_int(shared_two.length, 32); fail_if_not_eq_uint8_arr(shared, shared_one); fail_if_not_eq_uint8_arr(shared_one, shared_two); } catch (Error e) { fail_if_reached(); } } void test_curve25519_generate_public() { try { uint8[] alicePublic = { 0x05, 0x1b, 0xb7, 0x59, 0x66, 0xf2, 0xe9, 0x3a, 0x36, 0x91, 0xdf, 0xff, 0x94, 0x2b, 0xb2, 0xa4, 0x66, 0xa1, 0xc0, 0x8b, 0x8d, 0x78, 0xca, 0x3f, 0x4d, 0x6d, 0xf8, 0xb8, 0xbf, 0xa2, 0xe4, 0xee, 0x28}; uint8[] alicePrivate = { 0xc8, 0x06, 0x43, 0x9d, 0xc9, 0xd2, 0xc4, 0x76, 0xff, 0xed, 0x8f, 0x25, 0x80, 0xc0, 0x88, 0x8d, 0x58, 0xab, 0x40, 0x6b, 0xf7, 0xae, 0x36, 0x98, 0x87, 0x90, 0x21, 0xb9, 0x6b, 0xb4, 0xbf, 0x59}; ECPrivateKey alice_private_key = global_context.decode_private_key(alicePrivate); ECPublicKey alice_expected_public_key = global_context.decode_public_key(alicePublic); ECPublicKey alice_public_key = generate_public_key(alice_private_key); fail_if_not_zero_int(alice_expected_public_key.compare(alice_public_key)); } catch (Error e) { fail_if_reached(); } } void test_curve25519_random_agreements() { try { ECKeyPair alice_key_pair = null; ECPublicKey alice_public_key = null; ECPrivateKey alice_private_key = null; ECKeyPair bob_key_pair = null; ECPublicKey bob_public_key = null; ECPrivateKey bob_private_key = null; uint8[] shared_alice = null; uint8[] shared_bob = null; for (int i = 0; i < 50; i++) { fail_if_null(alice_key_pair = global_context.generate_key_pair()); fail_if_null(alice_public_key = alice_key_pair.public); fail_if_null(alice_private_key = alice_key_pair.private); fail_if_null(bob_key_pair = global_context.generate_key_pair()); fail_if_null(bob_public_key = bob_key_pair.public); fail_if_null(bob_private_key = bob_key_pair.private); shared_alice = calculate_agreement(bob_public_key, alice_private_key); fail_if_not_eq_int(shared_alice.length, 32); shared_bob = calculate_agreement(alice_public_key, bob_private_key); fail_if_not_eq_int(shared_bob.length, 32); fail_if_not_eq_uint8_arr(shared_alice, shared_bob); } } catch (Error e) { fail_if_reached(); } } void test_curve25519_signature() { try { uint8[] aliceIdentityPrivate = { 0xc0, 0x97, 0x24, 0x84, 0x12, 0xe5, 0x8b, 0xf0, 0x5d, 0xf4, 0x87, 0x96, 0x82, 0x05, 0x13, 0x27, 0x94, 0x17, 0x8e, 0x36, 0x76, 0x37, 0xf5, 0x81, 0x8f, 0x81, 0xe0, 0xe6, 0xce, 0x73, 0xe8, 0x65}; uint8[] aliceIdentityPublic = { 0x05, 0xab, 0x7e, 0x71, 0x7d, 0x4a, 0x16, 0x3b, 0x7d, 0x9a, 0x1d, 0x80, 0x71, 0xdf, 0xe9, 0xdc, 0xf8, 0xcd, 0xcd, 0x1c, 0xea, 0x33, 0x39, 0xb6, 0x35, 0x6b, 0xe8, 0x4d, 0x88, 0x7e, 0x32, 0x2c, 0x64}; uint8[] aliceEphemeralPublic = { 0x05, 0xed, 0xce, 0x9d, 0x9c, 0x41, 0x5c, 0xa7, 0x8c, 0xb7, 0x25, 0x2e, 0x72, 0xc2, 0xc4, 0xa5, 0x54, 0xd3, 0xeb, 0x29, 0x48, 0x5a, 0x0e, 0x1d, 0x50, 0x31, 0x18, 0xd1, 0xa8, 0x2d, 0x99, 0xfb, 0x4a}; uint8[] aliceSignature = { 0x5d, 0xe8, 0x8c, 0xa9, 0xa8, 0x9b, 0x4a, 0x11, 0x5d, 0xa7, 0x91, 0x09, 0xc6, 0x7c, 0x9c, 0x74, 0x64, 0xa3, 0xe4, 0x18, 0x02, 0x74, 0xf1, 0xcb, 0x8c, 0x63, 0xc2, 0x98, 0x4e, 0x28, 0x6d, 0xfb, 0xed, 0xe8, 0x2d, 0xeb, 0x9d, 0xcd, 0x9f, 0xae, 0x0b, 0xfb, 0xb8, 0x21, 0x56, 0x9b, 0x3d, 0x90, 0x01, 0xbd, 0x81, 0x30, 0xcd, 0x11, 0xd4, 0x86, 0xce, 0xf0, 0x47, 0xbd, 0x60, 0xb8, 0x6e, 0x88}; global_context.decode_private_key(aliceIdentityPrivate); global_context.decode_public_key(aliceEphemeralPublic); ECPublicKey alice_public_key = global_context.decode_public_key(aliceIdentityPublic); fail_if(!verify_signature(alice_public_key, aliceEphemeralPublic, aliceSignature), "signature verification failed"); uint8[] modifiedSignature = new uint8[aliceSignature.length]; for (int i = 0; i < aliceSignature.length; i++) { Memory.copy(modifiedSignature, aliceSignature, aliceSignature.length); modifiedSignature[i] ^= 0x01; fail_if(verify_signature(alice_public_key, aliceEphemeralPublic, modifiedSignature), "invalid signature verification succeeded"); } } catch (Error e) { fail_if_reached(); } } } }dino-0.3.0/plugins/signal-protocol/tests/hkdf.vala0000644000000000000000000000326314202022370020655 0ustar rootrootnamespace Signal.Test { class HKDF : Gee.TestCase { public HKDF() { base("HKDF"); add_test("vector_v3", test_hkdf_vector_v3); } private Context global_context; public override void set_up() { try { global_context = new Context(); } catch (Error e) { fail_if_reached(); } } public override void tear_down() { global_context = null; } public void test_hkdf_vector_v3() { uint8[] ikm = { 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b}; uint8[] salt = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c}; uint8[] info = { 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9}; uint8[] okm = { 0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a, 0x90, 0x43, 0x4f, 0x64, 0xd0, 0x36, 0x2f, 0x2a, 0x2d, 0x2d, 0x0a, 0x90, 0xcf, 0x1a, 0x5a, 0x4c, 0x5d, 0xb0, 0x2d, 0x56, 0xec, 0xc4, 0xc5, 0xbf, 0x34, 0x00, 0x72, 0x08, 0xd5, 0xb8, 0x87, 0x18, 0x58, 0x65}; NativeHkdfContext context = null; fail_if_not_zero_int(NativeHkdfContext.create(out context, 3, global_context.native_context)); uint8[] output = null; int result = (int) context.derive_secrets(out output, ikm, salt, info, 42); fail_if_not_eq_int(result, okm.length); output.length = result; fail_if_not_eq_uint8_arr(output, okm); } } }dino-0.3.0/plugins/signal-protocol/tests/session_builder.vala0000644000000000000000000005070614202022370023136 0ustar rootrootnamespace Signal.Test { class SessionBuilderTest : Gee.TestCase { Address alice_address; Address bob_address; public SessionBuilderTest() { base("SessionBuilder"); add_test("basic_pre_key_v2", test_basic_pre_key_v2); add_test("basic_pre_key_v3", test_basic_pre_key_v3); add_test("bad_signed_pre_key_signature", test_bad_signed_pre_key_signature); add_test("repeat_bundle_message_v2", test_repeat_bundle_message_v2); } private Context global_context; public override void set_up() { try { global_context = new Context(); alice_address = new Address("+14151111111", 1); bob_address = new Address("+14152222222", 1); } catch (Error e) { fail_if_reached(@"Unexpected error: $(e.message)"); } } public override void tear_down() { global_context = null; alice_address = null; bob_address = null; } void test_basic_pre_key_v2() { try { /* Create Alice's data store and session builder */ Store alice_store = setup_test_store_context(global_context); SessionBuilder alice_session_builder = alice_store.create_session_builder(bob_address); /* Create Bob's data store and pre key bundle */ Store bob_store = setup_test_store_context(global_context); uint32 bob_local_registration_id = bob_store.local_registration_id; IdentityKeyPair bob_identity_key_pair = bob_store.identity_key_pair; ECKeyPair bob_pre_key_pair = global_context.generate_key_pair(); PreKeyBundle bob_pre_key = create_pre_key_bundle(bob_local_registration_id, 1, 31337, bob_pre_key_pair.public, 0, null, null, bob_identity_key_pair.public); /* * Have Alice process Bob's pre key bundle, which should fail due to a * missing unsigned pre key. */ fail_if_not_error_code(() => alice_session_builder.process_pre_key_bundle(bob_pre_key), ErrorCode.INVALID_KEY); } catch(Error e) { fail_if_reached(@"Unexpected error: $(e.message)"); } } void test_basic_pre_key_v3() { try { /* Create Alice's data store and session builder */ Store alice_store = setup_test_store_context(global_context); SessionBuilder alice_session_builder = alice_store.create_session_builder(bob_address); /* Create Bob's data store and pre key bundle */ Store bob_store = setup_test_store_context(global_context); uint32 bob_local_registration_id = bob_store.local_registration_id; ECKeyPair bob_pre_key_pair = global_context.generate_key_pair(); ECKeyPair bob_signed_pre_key_pair = global_context.generate_key_pair(); IdentityKeyPair bob_identity_key_pair = bob_store.identity_key_pair; uint8[] bob_signed_pre_key_signature = global_context.calculate_signature(bob_identity_key_pair.private, bob_signed_pre_key_pair.public.serialize()); PreKeyBundle bob_pre_key = create_pre_key_bundle(bob_local_registration_id, 1, 31337, bob_pre_key_pair.public, 22, bob_signed_pre_key_pair.public, bob_signed_pre_key_signature, bob_identity_key_pair.public); /* Have Alice process Bob's pre key bundle */ alice_session_builder.process_pre_key_bundle(bob_pre_key); /* Check that we can load the session state and verify its version */ fail_if_not(alice_store.contains_session(bob_address)); SessionRecord loaded_record = alice_store.load_session(bob_address); fail_if_not_eq_int((int)loaded_record.state.session_version, 3); /* Encrypt an outgoing message to send to Bob */ string original_message = "L'homme est condamné à être libre"; SessionCipher alice_session_cipher = alice_store.create_session_cipher(bob_address); CiphertextMessage outgoing_message = alice_session_cipher.encrypt(original_message.data); fail_if_not_eq_int(outgoing_message.type, CiphertextType.PREKEY); /* Convert to an incoming message for Bob */ PreKeySignalMessage incoming_message = global_context.deserialize_pre_key_signal_message(outgoing_message.serialized); /* Save the pre key and signed pre key in Bob's data store */ PreKeyRecord bob_pre_key_record; throw_by_code(PreKeyRecord.create(out bob_pre_key_record, bob_pre_key.pre_key_id, bob_pre_key_pair)); bob_store.store_pre_key(bob_pre_key_record); SignedPreKeyRecord bob_signed_pre_key_record; throw_by_code(SignedPreKeyRecord.create(out bob_signed_pre_key_record, 22, new DateTime.now_utc().to_unix(), bob_signed_pre_key_pair, bob_signed_pre_key_signature)); bob_store.store_signed_pre_key(bob_signed_pre_key_record); /* Create Bob's session cipher and decrypt the message from Alice */ SessionCipher bob_session_cipher = bob_store.create_session_cipher(alice_address); /* Prepare the data for the callback test */ //int callback_context = 1234; //bob_session_cipher.user_data = //bob_session_cipher.decryption_callback = uint8[] plaintext = bob_session_cipher.decrypt_pre_key_signal_message(incoming_message); /* Clean up callback data */ bob_session_cipher.user_data = null; bob_session_cipher.decryption_callback = null; /* Verify Bob's session state and the decrypted message */ fail_if_not(bob_store.contains_session(alice_address)); SessionRecord alice_recipient_session_record = bob_store.load_session(alice_address); SessionState alice_recipient_session_state = alice_recipient_session_record.state; fail_if_not_eq_int((int)alice_recipient_session_state.session_version, 3); fail_if_null(alice_recipient_session_state.alice_base_key); fail_if_not_eq_uint8_arr(original_message.data, plaintext); /* Have Bob send a reply to Alice */ CiphertextMessage bob_outgoing_message = bob_session_cipher.encrypt(original_message.data); fail_if_not_eq_int(bob_outgoing_message.type, CiphertextType.SIGNAL); /* Verify that Alice can decrypt it */ SignalMessage bob_outgoing_message_copy = global_context.copy_signal_message(bob_outgoing_message); uint8[] alice_plaintext = alice_session_cipher.decrypt_signal_message(bob_outgoing_message_copy); fail_if_not_eq_uint8_arr(original_message.data, alice_plaintext); GLib.Test.message("Pre-interaction tests complete"); /* Interaction tests */ run_interaction(alice_store, bob_store); /* Cleanup state from previous tests that we need to replace */ alice_store = null; bob_pre_key_pair = null; bob_signed_pre_key_pair = null; bob_identity_key_pair = null; bob_signed_pre_key_signature = null; bob_pre_key_record = null; bob_signed_pre_key_record = null; /* Create Alice's new session data */ alice_store = setup_test_store_context(global_context); alice_session_builder = alice_store.create_session_builder(bob_address); alice_session_cipher = alice_store.create_session_cipher(bob_address); /* Create Bob's new pre key bundle */ bob_pre_key_pair = global_context.generate_key_pair(); bob_signed_pre_key_pair = global_context.generate_key_pair(); bob_identity_key_pair = bob_store.identity_key_pair; bob_signed_pre_key_signature = global_context.calculate_signature(bob_identity_key_pair.private, bob_signed_pre_key_pair.public.serialize()); bob_pre_key = create_pre_key_bundle(bob_local_registration_id, 1, 31338, bob_pre_key_pair.public, 23, bob_signed_pre_key_pair.public, bob_signed_pre_key_signature, bob_identity_key_pair.public); /* Save the new pre key and signed pre key in Bob's data store */ throw_by_code(PreKeyRecord.create(out bob_pre_key_record, bob_pre_key.pre_key_id, bob_pre_key_pair)); bob_store.store_pre_key(bob_pre_key_record); throw_by_code(SignedPreKeyRecord.create(out bob_signed_pre_key_record, 23, new DateTime.now_utc().to_unix(), bob_signed_pre_key_pair, bob_signed_pre_key_signature)); bob_store.store_signed_pre_key(bob_signed_pre_key_record); /* Have Alice process Bob's pre key bundle */ alice_session_builder.process_pre_key_bundle(bob_pre_key); /* Have Alice encrypt a message for Bob */ outgoing_message = alice_session_cipher.encrypt(original_message.data); fail_if_not_eq_int(outgoing_message.type, CiphertextType.PREKEY); /* Have Bob try to decrypt the message */ PreKeySignalMessage outgoing_message_copy = global_context.copy_pre_key_signal_message(outgoing_message); /* The decrypt should fail with a specific error */ fail_if_not_error_code(() => bob_session_cipher.decrypt_pre_key_signal_message(outgoing_message_copy), ErrorCode.UNTRUSTED_IDENTITY); outgoing_message_copy = global_context.copy_pre_key_signal_message(outgoing_message); /* Save the identity key to Bob's store */ bob_store.save_identity(alice_address, outgoing_message_copy.identity_key); /* Try the decrypt again, this time it should succeed */ outgoing_message_copy = global_context.copy_pre_key_signal_message(outgoing_message); plaintext = bob_session_cipher.decrypt_pre_key_signal_message(outgoing_message_copy); fail_if_not_eq_uint8_arr(original_message.data, plaintext); /* Create a new pre key for Bob */ ECPublicKey test_public_key = create_test_ec_public_key(global_context); IdentityKeyPair alice_identity_key_pair = alice_store.identity_key_pair; bob_pre_key = create_pre_key_bundle(bob_local_registration_id, 1, 31337, test_public_key, 23, bob_signed_pre_key_pair.public, bob_signed_pre_key_signature, alice_identity_key_pair.public); /* Have Alice process Bob's new pre key bundle, which should fail */ fail_if_not_error_code(() => alice_session_builder.process_pre_key_bundle(bob_pre_key), ErrorCode.UNTRUSTED_IDENTITY); GLib.Test.message("Post-interaction tests complete"); } catch(Error e) { fail_if_reached(@"Unexpected error: $(e.message)"); } } void test_bad_signed_pre_key_signature() { try { /* Create Alice's data store and session builder */ Store alice_store = setup_test_store_context(global_context); SessionBuilder alice_session_builder = alice_store.create_session_builder(bob_address); /* Create Bob's data store */ Store bob_store = setup_test_store_context(global_context); /* Create Bob's regular and signed pre key pairs */ ECKeyPair bob_pre_key_pair = global_context.generate_key_pair(); ECKeyPair bob_signed_pre_key_pair = global_context.generate_key_pair(); /* Create Bob's signed pre key signature */ IdentityKeyPair bob_identity_key_pair = bob_store.identity_key_pair; uint8[] bob_signed_pre_key_signature = global_context.calculate_signature(bob_identity_key_pair.private, bob_signed_pre_key_pair.public.serialize()); for (int i = 0; i < bob_signed_pre_key_signature.length * 8; i++) { uint8[] modified_signature = bob_signed_pre_key_signature[0:bob_signed_pre_key_signature.length]; /* Intentionally corrupt the signature data */ modified_signature[i/8] ^= (1 << ((uint8)i % 8)); /* Create a pre key bundle */ PreKeyBundle bob_pre_key = create_pre_key_bundle(bob_store.local_registration_id,1,31137,bob_pre_key_pair.public,22,bob_signed_pre_key_pair.public,modified_signature,bob_identity_key_pair.public); /* Process the bundle and make sure we fail with an invalid key error */ fail_if_not_error_code(() => alice_session_builder.process_pre_key_bundle(bob_pre_key), ErrorCode.INVALID_KEY); } /* Create a correct pre key bundle */ PreKeyBundle bob_pre_key = create_pre_key_bundle(bob_store.local_registration_id,1,31137,bob_pre_key_pair.public,22,bob_signed_pre_key_pair.public,bob_signed_pre_key_signature,bob_identity_key_pair.public); /* Process the bundle and make sure we do not fail */ alice_session_builder.process_pre_key_bundle(bob_pre_key); } catch(Error e) { fail_if_reached(@"Unexpected error: $(e.message)"); } } void test_repeat_bundle_message_v2() { try { /* Create Alice's data store and session builder */ Store alice_store = setup_test_store_context(global_context); SessionBuilder alice_session_builder = alice_store.create_session_builder(bob_address); /* Create Bob's data store and pre key bundle */ Store bob_store = setup_test_store_context(global_context); ECKeyPair bob_pre_key_pair = global_context.generate_key_pair(); ECKeyPair bob_signed_pre_key_pair = global_context.generate_key_pair(); uint8[] bob_signed_pre_key_signature = global_context.calculate_signature(bob_store.identity_key_pair.private, bob_signed_pre_key_pair.public.serialize()); PreKeyBundle bob_pre_key = create_pre_key_bundle(bob_store.local_registration_id,1,31337,bob_pre_key_pair.public,0,null,null,bob_store.identity_key_pair.public); /* Add Bob's pre keys to Bob's data store */ PreKeyRecord bob_pre_key_record; throw_by_code(PreKeyRecord.create(out bob_pre_key_record, bob_pre_key.pre_key_id, bob_pre_key_pair)); bob_store.store_pre_key(bob_pre_key_record); SignedPreKeyRecord bob_signed_pre_key_record; throw_by_code(SignedPreKeyRecord.create(out bob_signed_pre_key_record, 22, new DateTime.now_utc().to_unix(), bob_signed_pre_key_pair, bob_signed_pre_key_signature)); bob_store.store_signed_pre_key(bob_signed_pre_key_record); /* * Have Alice process Bob's pre key bundle, which should fail due to a * missing signed pre key. */ fail_if_not_error_code(() => alice_session_builder.process_pre_key_bundle(bob_pre_key), ErrorCode.INVALID_KEY); } catch(Error e) { fail_if_reached(@"Unexpected error: $(e.message)"); } } class Holder { public uint8[] data { get; private set; } public Holder(uint8[] data) { this.data = data; } } void run_interaction(Store alice_store, Store bob_store) throws Error { /* Create the session ciphers */ SessionCipher alice_session_cipher = alice_store.create_session_cipher(bob_address); SessionCipher bob_session_cipher = bob_store.create_session_cipher(alice_address); /* Create a test message */ string original_message = "smert ze smert"; /* Simulate Alice sending a message to Bob */ CiphertextMessage alice_message = alice_session_cipher.encrypt(original_message.data); fail_if_not_eq_int(alice_message.type, CiphertextType.SIGNAL); SignalMessage alice_message_copy = global_context.copy_signal_message(alice_message); uint8[] plaintext = bob_session_cipher.decrypt_signal_message(alice_message_copy); fail_if_not_eq_uint8_arr(original_message.data, plaintext); GLib.Test.message("Interaction complete: Alice -> Bob"); /* Simulate Bob sending a message to Alice */ CiphertextMessage bob_message = bob_session_cipher.encrypt(original_message.data); fail_if_not_eq_int(alice_message.type, CiphertextType.SIGNAL); SignalMessage bob_message_copy = global_context.copy_signal_message(bob_message); plaintext = alice_session_cipher.decrypt_signal_message(bob_message_copy); fail_if_not_eq_uint8_arr(original_message.data, plaintext); GLib.Test.message("Interaction complete: Bob -> Alice"); /* Looping Alice -> Bob */ for (int i = 0; i < 10; i++) { uint8[] looping_message = create_looping_message(i); CiphertextMessage alice_looping_message = alice_session_cipher.encrypt(looping_message); SignalMessage alice_looping_message_copy = global_context.copy_signal_message(alice_looping_message); uint8[] looping_plaintext = bob_session_cipher.decrypt_signal_message(alice_looping_message_copy); fail_if_not_eq_uint8_arr(looping_message, looping_plaintext); } GLib.Test.message("Interaction complete: Alice -> Bob (looping)"); /* Looping Bob -> Alice */ for (int i = 0; i < 10; i++) { uint8[] looping_message = create_looping_message(i); CiphertextMessage bob_looping_message = bob_session_cipher.encrypt(looping_message); SignalMessage bob_looping_message_copy = global_context.copy_signal_message(bob_looping_message); uint8[] looping_plaintext = alice_session_cipher.decrypt_signal_message(bob_looping_message_copy); fail_if_not_eq_uint8_arr(looping_message, looping_plaintext); } GLib.Test.message("Interaction complete: Bob -> Alice (looping)"); /* Generate a shuffled list of encrypted messages for later use */ Holder[] alice_ooo_plaintext = new Holder[10]; Holder[] alice_ooo_ciphertext = new Holder[10]; for (int i = 0; i < 10; i++) { alice_ooo_plaintext[i] = new Holder(create_looping_message(i)); alice_ooo_ciphertext[i] = new Holder(alice_session_cipher.encrypt(alice_ooo_plaintext[i].data).serialized); } for (int i = 0; i < 10; i++) { uint32 s = Random.next_int() % 10; Holder tmp = alice_ooo_plaintext[s]; alice_ooo_plaintext[s] = alice_ooo_plaintext[i]; alice_ooo_plaintext[i] = tmp; tmp = alice_ooo_ciphertext[s]; alice_ooo_ciphertext[s] = alice_ooo_ciphertext[i]; alice_ooo_ciphertext[i] = tmp; } GLib.Test.message("Shuffled Alice->Bob messages created"); /* Looping Alice -> Bob (repeated) */ for (int i = 0; i < 10; i++) { uint8[] looping_message = create_looping_message(i); CiphertextMessage alice_looping_message = alice_session_cipher.encrypt(looping_message); SignalMessage alice_looping_message_copy = global_context.copy_signal_message(alice_looping_message); uint8[] looping_plaintext = bob_session_cipher.decrypt_signal_message(alice_looping_message_copy); fail_if_not_eq_uint8_arr(looping_message, looping_plaintext); } GLib.Test.message("Interaction complete: Alice -> Bob (looping, repeated)"); /* Looping Bob -> Alice (repeated) */ for (int i = 0; i < 10; i++) { uint8[] looping_message = create_looping_message(i); CiphertextMessage bob_looping_message = bob_session_cipher.encrypt(looping_message); SignalMessage bob_looping_message_copy = global_context.copy_signal_message(bob_looping_message); uint8[] looping_plaintext = alice_session_cipher.decrypt_signal_message(bob_looping_message_copy); fail_if_not_eq_uint8_arr(looping_message, looping_plaintext); } GLib.Test.message("Interaction complete: Bob -> Alice (looping, repeated)"); /* Shuffled Alice -> Bob */ for (int i = 0; i < 10; i++) { SignalMessage ooo_message_deserialized = global_context.deserialize_signal_message(alice_ooo_ciphertext[i].data); uint8[] ooo_plaintext = bob_session_cipher.decrypt_signal_message(ooo_message_deserialized); fail_if_not_eq_uint8_arr(alice_ooo_plaintext[i].data, ooo_plaintext); } GLib.Test.message("Interaction complete: Alice -> Bob (shuffled)"); } uint8[] create_looping_message(int index) { return (@"You can only desire based on what you know: $index").data; } /* uint8[] create_looping_message_short(int index) { return ("What do we mean by saying that existence precedes essence? " + "We mean that man first of all exists, encounters himself, " + @"surges up in the world--and defines himself aftward. $index").data; } */ } } dino-0.3.0/plugins/signal-protocol/tests/testcase.vala0000644000000000000000000000450214202022370021551 0ustar rootroot/* testcase.vala * * Copyright (C) 2009 Julien Peeters * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * This library 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 * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Author: * Julien Peeters */ public abstract class Gee.TestCase : Object { private GLib.TestSuite suite; private Adaptor[] adaptors = new Adaptor[0]; public delegate void TestMethod (); protected TestCase (string name) { this.suite = new GLib.TestSuite (name); } public void add_test (string name, owned TestMethod test) { var adaptor = new Adaptor (name, (owned)test, this); this.adaptors += adaptor; this.suite.add (new GLib.TestCase (adaptor.name, adaptor.set_up, adaptor.run, adaptor.tear_down )); } public virtual void set_up () { } public virtual void tear_down () { } public GLib.TestSuite get_suite () { return this.suite; } private class Adaptor { [CCode (notify = false)] public string name { get; private set; } private TestMethod test; private TestCase test_case; public Adaptor (string name, owned TestMethod test, TestCase test_case) { this.name = name; this.test = (owned)test; this.test_case = test_case; } public void set_up (void* fixture) { this.test_case.set_up (); } public void run (void* fixture) { this.test (); } public void tear_down (void* fixture) { this.test_case.tear_down (); } } }dino-0.3.0/plugins/signal-protocol/vapi/0000755000000000000000000000000014202022370016665 5ustar rootrootdino-0.3.0/plugins/signal-protocol/vapi/signal-protocol-native.vapi0000644000000000000000000004063014202022370024151 0ustar rootrootnamespace Signal { [Compact] [CCode (cname = "signal_context", cprefix="signal_context_", free_function="signal_context_destroy", cheader_filename = "signal/signal_protocol.h")] public class NativeContext { public static int create(out NativeContext context, void* user_data); public int set_crypto_provider(NativeCryptoProvider crypto_provider); public int set_locking_functions(LockingFunc lock, LockingFunc unlock); public int set_log_function(LogFunc log); } [CCode (has_target = false)] public delegate void LockingFunc(void* user_data); [CCode (has_target = false)] public delegate void LogFunc(LogLevel level, string message, size_t len, void* user_data); [Compact] [CCode (cname = "signal_crypto_provider", cheader_filename = "signal/signal_protocol.h")] public struct NativeCryptoProvider { public RandomFunc random_func; public HmacSha256Init hmac_sha256_init_func; public HmacSha256Update hmac_sha256_update_func; public HmacSha256Final hmac_sha256_final_func; public HmacSha256Cleanup hmac_sha256_cleanup_func; public Sha512DigestInit sha512_digest_init_func; public Sha512DigestUpdate sha512_digest_update_func; public Sha512DigestFinal sha512_digest_final_func; public Sha512DigestCleanup sha512_digest_cleanup_func; public CryptFunc encrypt_func; public CryptFunc decrypt_func; public void* user_data; } [CCode (has_target = false)] public delegate int RandomFunc(uint8[] data, void* user_data); [CCode (has_target = false)] public delegate int HmacSha256Init(out void* hmac_context, uint8[] key, void* user_data); [CCode (has_target = false)] public delegate int HmacSha256Update(void* hmac_context, uint8[] data, void* user_data); [CCode (has_target = false)] public delegate int HmacSha256Final(void* hmac_context, out Buffer buffer, void* user_data); [CCode (has_target = false)] public delegate int HmacSha256Cleanup(void* hmac_context, void* user_data); [CCode (has_target = false)] public delegate int Sha512DigestInit(out void* digest_context, void* user_data); [CCode (has_target = false)] public delegate int Sha512DigestUpdate(void* digest_context, uint8[] data, void* user_data); [CCode (has_target = false)] public delegate int Sha512DigestFinal(void* digest_context, out Buffer buffer, void* user_data); [CCode (has_target = false)] public delegate int Sha512DigestCleanup(void* digest_context, void* user_data); [CCode (has_target = false)] public delegate int CryptFunc(out Buffer output, Cipher cipher, uint8[] key, uint8[] iv, uint8[] content, void* user_data); [Compact] [CCode (cname = "signal_protocol_session_store", cheader_filename = "signal/signal_protocol.h")] public struct NativeSessionStore { public LoadSessionFunc load_session_func; public GetSubDeviceSessionsFunc get_sub_device_sessions_func; public StoreSessionFunc store_session_func; public ContainsSessionFunc contains_session_func; public DeleteSessionFunc delete_session_func; public DeleteAllSessionsFunc delete_all_sessions_func; public DestroyFunc destroy_func; public void* user_data; } [CCode (has_target = false)] public delegate int LoadSessionFunc(out Buffer record, out Buffer user_record, Address address, void* user_data); [CCode (has_target = false)] public delegate int GetSubDeviceSessionsFunc(out IntList sessions, [CCode (array_length_type = "size_t")] char[] name, void* user_data); [CCode (has_target = false)] public delegate int StoreSessionFunc(Address address, [CCode (array_length_type = "size_t")] uint8[] record, [CCode (array_length_type = "size_t")] uint8[] user_record, void* user_data); [CCode (has_target = false)] public delegate int ContainsSessionFunc(Address address, void* user_data); [CCode (has_target = false)] public delegate int DeleteSessionFunc(Address address, void* user_data); [CCode (has_target = false)] public delegate int DeleteAllSessionsFunc([CCode (array_length_type = "size_t")] char[] name, void* user_data); [Compact] [CCode (cname = "signal_protocol_identity_key_store", cheader_filename = "signal/signal_protocol.h")] public struct NativeIdentityKeyStore { GetIdentityKeyPairFunc get_identity_key_pair; GetLocalRegistrationIdFunc get_local_registration_id; SaveIdentityFunc save_identity; IsTrustedIdentityFunc is_trusted_identity; DestroyFunc destroy_func; void* user_data; } [CCode (has_target = false)] public delegate int GetIdentityKeyPairFunc(out Buffer public_data, out Buffer private_data, void* user_data); [CCode (has_target = false)] public delegate int GetLocalRegistrationIdFunc(void* user_data, out uint32 registration_id); [CCode (has_target = false)] public delegate int SaveIdentityFunc(Address address, [CCode (array_length_type = "size_t")] uint8[] key, void* user_data); [CCode (has_target = false)] public delegate int IsTrustedIdentityFunc(Address address, [CCode (array_length_type = "size_t")] uint8[] key, void* user_data); [Compact] [CCode (cname = "signal_protocol_pre_key_store", cheader_filename = "signal/signal_protocol.h")] public struct NativePreKeyStore { LoadPreKeyFunc load_pre_key; StorePreKeyFunc store_pre_key; ContainsPreKeyFunc contains_pre_key; RemovePreKeyFunc remove_pre_key; DestroyFunc destroy_func; void* user_data; } [CCode (has_target = false)] public delegate int LoadPreKeyFunc(out Buffer record, uint32 pre_key_id, void* user_data); [CCode (has_target = false)] public delegate int StorePreKeyFunc(uint32 pre_key_id, [CCode (array_length_type = "size_t")] uint8[] record, void* user_data); [CCode (has_target = false)] public delegate int ContainsPreKeyFunc(uint32 pre_key_id, void* user_data); [CCode (has_target = false)] public delegate int RemovePreKeyFunc(uint32 pre_key_id, void* user_data); [Compact] [CCode (cname = "signal_protocol_signed_pre_key_store", cheader_filename = "signal/signal_protocol.h")] public struct NativeSignedPreKeyStore { LoadPreKeyFunc load_signed_pre_key; StorePreKeyFunc store_signed_pre_key; ContainsPreKeyFunc contains_signed_pre_key; RemovePreKeyFunc remove_signed_pre_key; DestroyFunc destroy_func; void* user_data; } [Compact] [CCode (cname = "signal_protocol_sender_key_store")] public struct NativeSenderKeyStore { StoreSenderKeyFunc store_sender_key; LoadSenderKeyFunc load_sender_key; DestroyFunc destroy_func; void* user_data; } [CCode (has_target = false)] public delegate int StoreSenderKeyFunc(SenderKeyName sender_key_name, [CCode (array_length_type = "size_t")] uint8[] record, [CCode (array_length_type = "size_t")] uint8[] user_record, void* user_data); [CCode (has_target = false)] public delegate int LoadSenderKeyFunc(out Buffer record, out Buffer user_record, SenderKeyName sender_key_name, void* user_data); [CCode (has_target = false)] public delegate void DestroyFunc(void* user_data); [Compact] [CCode (cname = "signal_protocol_store_context", cprefix = "signal_protocol_store_context_", free_function="signal_protocol_store_context_destroy", cheader_filename = "signal/signal_protocol.h")] public class NativeStoreContext { public static int create(out NativeStoreContext context, NativeContext global_context); public int set_session_store(NativeSessionStore store); public int set_pre_key_store(NativePreKeyStore store); public int set_signed_pre_key_store(NativeSignedPreKeyStore store); public int set_identity_key_store(NativeIdentityKeyStore store); public int set_sender_key_store(NativeSenderKeyStore store); } [CCode (cheader_filename = "signal/signal_protocol.h")] namespace Protocol { /** * Interface to the pre-key store. * These functions will use the callbacks in the provided * signal_protocol_store_context instance and operate in terms of higher level * library data structures. */ [CCode (cprefix = "signal_protocol_pre_key_")] namespace PreKey { public int load_key(NativeStoreContext context, out PreKeyRecord pre_key, uint32 pre_key_id); public int store_key(NativeStoreContext context, PreKeyRecord pre_key); public int contains_key(NativeStoreContext context, uint32 pre_key_id); public int remove_key(NativeStoreContext context, uint32 pre_key_id); } [CCode (cprefix = "signal_protocol_signed_pre_key_")] namespace SignedPreKey { public int load_key(NativeStoreContext context, out SignedPreKeyRecord pre_key, uint32 pre_key_id); public int store_key(NativeStoreContext context, SignedPreKeyRecord pre_key); public int contains_key(NativeStoreContext context, uint32 pre_key_id); public int remove_key(NativeStoreContext context, uint32 pre_key_id); } /** * Interface to the session store. * These functions will use the callbacks in the provided * signal_protocol_store_context instance and operate in terms of higher level * library data structures. */ [CCode (cprefix = "signal_protocol_session_")] namespace Session { public int load_session(NativeStoreContext context, out SessionRecord record, Address address); public int get_sub_device_sessions(NativeStoreContext context, out IntList sessions, char[] name); public int store_session(NativeStoreContext context, Address address, SessionRecord record); public int contains_session(NativeStoreContext context, Address address); public int delete_session(NativeStoreContext context, Address address); public int delete_all_sessions(NativeStoreContext context, char[] name); } namespace Identity { public int get_key_pair(NativeStoreContext store_context, out IdentityKeyPair key_pair); public int get_local_registration_id(NativeStoreContext store_context, out uint32 registration_id); public int save_identity(NativeStoreContext store_context, Address address, ECPublicKey identity_key); public int is_trusted_identity(NativeStoreContext store_context, Address address, ECPublicKey identity_key); } [CCode (cheader_filename = "signal/key_helper.h", cprefix = "signal_protocol_key_helper_")] namespace KeyHelper { [Compact] [CCode (cname = "signal_protocol_key_helper_pre_key_list_node", cprefix = "signal_protocol_key_helper_key_list_", free_function="signal_protocol_key_helper_key_list_free")] public class PreKeyListNode { public PreKeyRecord element(); public PreKeyListNode next(); } public int generate_identity_key_pair(out IdentityKeyPair key_pair, NativeContext global_context); public int generate_registration_id(out int32 registration_id, int extended_range, NativeContext global_context); public int get_random_sequence(out int value, int max, NativeContext global_context); public int generate_pre_keys(out PreKeyListNode head, uint start, uint count, NativeContext global_context); public int generate_last_resort_pre_key(out PreKeyRecord pre_key, NativeContext global_context); public int generate_signed_pre_key(out SignedPreKeyRecord signed_pre_key, IdentityKeyPair identity_key_pair, uint32 signed_pre_key_id, uint64 timestamp, NativeContext global_context); public int generate_sender_signing_key(out ECKeyPair key_pair, NativeContext global_context); public int generate_sender_key(out Buffer key_buffer, NativeContext global_context); public int generate_sender_key_id(out int32 key_id, NativeContext global_context); } } [CCode (cheader_filename = "signal/curve.h")] namespace Curve { [CCode (cname = "curve_calculate_agreement")] public int calculate_agreement([CCode (array_length = false)] out uint8[] shared_key_data, ECPublicKey public_key, ECPrivateKey private_key); [CCode (cname = "curve_calculate_signature")] public int calculate_signature(NativeContext context, out Buffer signature, ECPrivateKey signing_key, uint8[] message); [CCode (cname = "curve_verify_signature")] public int verify_signature(ECPublicKey signing_key, uint8[] message, uint8[] signature); } [CCode (cname = "session_builder_create", cheader_filename = "signal/session_builder.h")] public static int session_builder_create(out SessionBuilder builder, NativeStoreContext store, Address remote_address, NativeContext global_context); [CCode (cname = "session_cipher_create", cheader_filename = "signal/session_cipher.h")] public static int session_cipher_create(out SessionCipher cipher, NativeStoreContext store, Address remote_address, NativeContext global_context); [CCode (cname = "pre_key_signal_message_deserialize", cheader_filename = "signal/protocol.h")] public static int pre_key_signal_message_deserialize(out PreKeySignalMessage message, uint8[] data, NativeContext global_context); [CCode (cname = "pre_key_signal_message_copy", cheader_filename = "signal/protocol.h")] public static int pre_key_signal_message_copy(out PreKeySignalMessage message, PreKeySignalMessage other_message, NativeContext global_context); [CCode (cname = "signal_message_create", cheader_filename = "signal/protocol.h")] public static int signal_message_create(out SignalMessage message, uint8 message_version, uint8[] mac_key, ECPublicKey sender_ratchet_key, uint32 counter, uint32 previous_counter, uint8[] ciphertext, ECPublicKey sender_identity_key, ECPublicKey receiver_identity_key, NativeContext global_context); [CCode (cname = "signal_message_deserialize", cheader_filename = "signal/protocol.h")] public static int signal_message_deserialize(out SignalMessage message, uint8[] data, NativeContext global_context); [CCode (cname = "signal_message_copy", cheader_filename = "signal/protocol.h")] public static int signal_message_copy(out SignalMessage message, SignalMessage other_message, NativeContext global_context); [CCode (cname = "curve_generate_key_pair", cheader_filename = "signal/curve.h")] public static int curve_generate_key_pair(NativeContext context, out ECKeyPair key_pair); [CCode (cname = "curve_decode_private_point", cheader_filename = "signal/curve.h")] public static int curve_decode_private_point(out ECPrivateKey public_key, uint8[] key, NativeContext global_context); [CCode (cname = "curve_decode_point", cheader_filename = "signal/curve.h")] public static int curve_decode_point(out ECPublicKey public_key, uint8[] key, NativeContext global_context); [CCode (cname = "curve_generate_private_key", cheader_filename = "signal/curve.h")] public static int curve_generate_private_key(NativeContext context, out ECPrivateKey private_key); [CCode (cname = "ratchet_identity_key_pair_deserialize", cheader_filename = "signal/ratchet.h")] public static int ratchet_identity_key_pair_deserialize(out IdentityKeyPair key_pair, uint8[] data, NativeContext global_context); [CCode (cname = "session_signed_pre_key_deserialize", cheader_filename = "signal/signed_pre_key.h")] public static int session_signed_pre_key_deserialize(out SignedPreKeyRecord pre_key, uint8[] data, NativeContext global_context); [Compact] [CCode (cname = "hkdf_context", cprefix = "hkdf_", free_function = "hkdf_destroy", cheader_filename = "signal/hkdf.h")] public class NativeHkdfContext { public static int create(out NativeHkdfContext context, int message_version, NativeContext global_context); public int compare(NativeHkdfContext other); public ssize_t derive_secrets([CCode (array_length = false)] out uint8[] output, uint8[] input_key_material, uint8[] salt, uint8[] info, size_t output_len); } [CCode (cname = "setup_signal_vala_crypto_provider", cheader_filename = "signal_helper.h")] public static void setup_crypto_provider(NativeContext context); [CCode (cname = "signal_vala_randomize", cheader_filename = "signal_helper.h")] public static int native_random(uint8[] data); } dino-0.3.0/plugins/signal-protocol/vapi/signal-protocol-public.vapi0000644000000000000000000004172214202022370024144 0ustar rootrootnamespace Signal { [CCode (cname = "int", cprefix = "SG_ERR_", cheader_filename = "signal/signal_protocol.h", has_type_id = false)] public enum ErrorCode { [CCode (cname = "SG_SUCCESS")] SUCCESS, NOMEM, INVAL, UNKNOWN, DUPLICATE_MESSAGE, INVALID_KEY, INVALID_KEY_ID, INVALID_MAC, INVALID_MESSAGE, INVALID_VERSION, LEGACY_MESSAGE, NO_SESSION, STALE_KEY_EXCHANGE, UNTRUSTED_IDENTITY, VRF_SIG_VERIF_FAILED, INVALID_PROTO_BUF, FP_VERSION_MISMATCH, FP_IDENT_MISMATCH; } [CCode (cname = "SG_ERR_MINIMUM", cheader_filename = "signal/signal_protocol.h")] public const int MIN_ERROR_CODE; [CCode (cname = "int", cprefix = "SG_LOG_", cheader_filename = "signal/signal_protocol.h", has_type_id = false)] public enum LogLevel { ERROR, WARNING, NOTICE, INFO, DEBUG } [CCode (cname = "signal_throw_gerror_by_code_", cheader_filename = "signal/signal_protocol.h")] private int throw_by_code(int code, string? message = null) throws GLib.Error { if (code < 0 && code > MIN_ERROR_CODE) { throw new GLib.Error(-1, code, "%s: %s", message ?? "Signal error", ((ErrorCode)code).to_string()); } return code; } [CCode (cname = "int", cprefix = "SG_CIPHER_", cheader_filename = "signal/signal_protocol.h", has_type_id = false)] public enum Cipher { AES_CTR_NOPADDING, AES_CBC_PKCS5, AES_GCM_NOPADDING } [Compact] [CCode (cname = "signal_type_base", ref_function="signal_type_ref_vapi", unref_function="signal_type_unref_vapi", cheader_filename="signal/signal_protocol_types.h,signal_helper.h")] public class TypeBase { } [Compact] [CCode (cname = "signal_buffer", cheader_filename = "signal/signal_protocol_types.h", free_function="signal_buffer_free")] public class Buffer { [CCode (cname = "signal_buffer_alloc")] public Buffer(size_t len); [CCode (cname = "signal_buffer_create")] public Buffer.from(uint8[] data); public Buffer copy(); public Buffer append(uint8[] data); public int compare(Buffer other); public uint8 get(int i) { return data[i]; } public void set(int i, uint8 val) { data[i] = val; } public uint8[] data { get { int x = (int)len(); unowned uint8[] res = _data(); res.length = x; return res; } } [CCode (array_length = false, cname = "signal_buffer_data")] private unowned uint8[] _data(); private size_t len(); } [Compact] [CCode (cname = "signal_int_list", cheader_filename = "signal/signal_protocol_types.h", free_function="signal_int_list_free")] public class IntList { [CCode (cname = "signal_int_list_alloc")] public IntList(); [CCode (cname = "signal_int_list_push_back")] public int add(int value); public uint size { [CCode (cname = "signal_int_list_size")] get; } [CCode (cname = "signal_int_list_at")] public int get(uint index); } [Compact] [CCode (cname = "session_builder", cprefix = "session_builder_", free_function="session_builder_free", cheader_filename = "signal/session_builder.h")] public class SessionBuilder { [CCode (cname = "session_builder_process_pre_key_bundle")] private int process_pre_key_bundle_(PreKeyBundle pre_key_bundle); [CCode (cname = "session_builder_process_pre_key_bundle_")] public void process_pre_key_bundle(PreKeyBundle pre_key_bundle) throws GLib.Error { throw_by_code(process_pre_key_bundle_(pre_key_bundle)); } } [Compact] [CCode (cname = "session_pre_key_bundle", cprefix = "session_pre_key_bundle_", cheader_filename = "signal/session_pre_key.h")] public class PreKeyBundle : TypeBase { public static int create(out PreKeyBundle bundle, uint32 registration_id, int device_id, uint32 pre_key_id, ECPublicKey? pre_key_public, uint32 signed_pre_key_id, ECPublicKey? signed_pre_key_public, uint8[]? signed_pre_key_signature, ECPublicKey? identity_key); public uint32 registration_id { get; } public int device_id { get; } public uint32 pre_key_id { get; } public ECPublicKey pre_key { owned get; } public uint32 signed_pre_key_id { get; } public ECPublicKey signed_pre_key { owned get; } public Buffer signed_pre_key_signature { owned get; } public ECPublicKey identity_key { owned get; } } [Compact] [CCode (cname = "session_pre_key", cprefix = "session_pre_key_", cheader_filename = "signal/session_pre_key.h,signal_helper.h")] public class PreKeyRecord : TypeBase { public static int create(out PreKeyRecord pre_key, uint32 id, ECKeyPair key_pair); //public static int deserialize(out PreKeyRecord pre_key, uint8[] data, NativeContext global_context); [CCode (instance_pos = 2)] public int serialze(out Buffer buffer); public uint32 id { get; } public ECKeyPair key_pair { get; } } [Compact] [CCode (cname = "session_record", cprefix = "session_record_", cheader_filename = "signal/signal_protocol_types.h")] public class SessionRecord : TypeBase { public SessionState state { get; } public Buffer user_record { get; } } [Compact] [CCode (cname = "session_state", cprefix = "session_state_", cheader_filename = "signal/session_state.h")] public class SessionState : TypeBase { //public static int create(out SessionState state, NativeContext context); //public static int deserialize(out SessionState state, uint8[] data, NativeContext context); //public static int copy(out SessionState state, SessionState other_state, NativeContext context); [CCode (instance_pos = 2)] public int serialze(out Buffer buffer); public uint32 session_version { get; set; } public ECPublicKey local_identity_key { get; set; } public ECPublicKey remote_identity_key { get; set; } //public Ratchet.RootKey root_key { get; set; } public uint32 previous_counter { get; set; } public ECPublicKey sender_ratchet_key { get; } public ECKeyPair sender_ratchet_key_pair { get; } //public Ratchet.ChainKey sender_chain_key { get; set; } public uint32 remote_registration_id { get; set; } public uint32 local_registration_id { get; set; } public int needs_refresh { get; set; } public ECPublicKey alice_base_key { get; set; } } [Compact] [CCode (cname = "session_signed_pre_key", cprefix = "session_signed_pre_key_", cheader_filename = "signal/session_pre_key.h")] public class SignedPreKeyRecord : TypeBase { public static int create(out SignedPreKeyRecord pre_key, uint32 id, uint64 timestamp, ECKeyPair key_pair, uint8[] signature); [CCode (instance_pos = 2)] public int serialze(out Buffer buffer); public uint32 id { get; } public uint64 timestamp { get; } public ECKeyPair key_pair { get; } public uint8[] signature { [CCode (cname = "session_signed_pre_key_get_signature_")] get { int x = (int)get_signature_len(); unowned uint8[] res = get_signature(); res.length = x; return res; } } [CCode (array_length = false, cname = "session_signed_pre_key_get_signature")] private unowned uint8[] get_signature(); private size_t get_signature_len(); } /** * Address of an Signal Protocol message recipient */ [Compact] [CCode (cname = "signal_protocol_address", cprefix = "signal_protocol_address_", cheader_filename = "signal/signal_protocol.h,signal_helper.h")] public class Address { public Address(string name, int32 device_id); public int32 device_id { get; set; } public string name { owned get; set; } } /** * A representation of a (group + sender + device) tuple */ [Compact] [CCode (cname = "signal_protocol_sender_key_name")] public class SenderKeyName { [CCode (cname = "group_id", array_length_cname="group_id_len")] private char* group_id_; private size_t group_id_len; public Address sender; } [Compact] [CCode (cname = "ec_public_key", cprefix = "ec_public_key_", cheader_filename = "signal/curve.h,signal_helper.h")] public class ECPublicKey : TypeBase { [CCode (cname = "curve_generate_public_key")] public static int generate(out ECPublicKey public_key, ECPrivateKey private_key); [CCode (instance_pos = 1, cname = "ec_public_key_serialize")] private int serialize_([CCode (pos = 0)] out Buffer buffer); [CCode (cname = "ec_public_key_serialize_")] public uint8[] serialize() { Buffer buffer; int code = serialize_(out buffer); if (code < 0 && code > MIN_ERROR_CODE) { // Can only throw for invalid arguments or out of memory. GLib.assert_not_reached(); } return buffer.data; } public int compare(ECPublicKey other); public int memcmp(ECPublicKey other); } [Compact] [CCode (cname = "ec_private_key", cprefix = "ec_private_key_", cheader_filename = "signal/curve.h,signal_helper.h")] public class ECPrivateKey : TypeBase { [CCode (instance_pos = 1, cname = "ec_private_key_serialize")] private int serialize_([CCode (pos = 0)] out Buffer buffer); [CCode (cname = "ec_private_key_serialize_")] public uint8[] serialize() throws GLib.Error { Buffer buffer; int code = serialize_(out buffer); if (code < 0 && code > MIN_ERROR_CODE) { // Can only throw for invalid arguments or out of memory. GLib.assert_not_reached(); } return buffer.data; } public int compare(ECPublicKey other); } [Compact] [CCode (cname = "ec_key_pair", cprefix="ec_key_pair_", cheader_filename = "signal/curve.h,signal_helper.h")] public class ECKeyPair : TypeBase { public static int create(out ECKeyPair key_pair, ECPublicKey public_key, ECPrivateKey private_key); public ECPublicKey public { get; } public ECPrivateKey private { get; } } [CCode (cname = "ratchet_message_keys", cheader_filename = "signal/ratchet.h")] public class MessageKeys { } [Compact] [CCode (cname = "ratchet_identity_key_pair", cprefix = "ratchet_identity_key_pair_", cheader_filename = "signal/ratchet.h,signal_helper.h")] public class IdentityKeyPair : TypeBase { public static int create(out IdentityKeyPair key_pair, ECPublicKey public_key, ECPrivateKey private_key); public int serialze(out Buffer buffer); public ECPublicKey public { get; } public ECPrivateKey private { get; } } [Compact] [CCode (cname = "ec_public_key_list")] public class PublicKeyList {} /** * The main entry point for Signal Protocol encrypt/decrypt operations. * * Once a session has been established with session_builder, * this class can be used for all encrypt/decrypt operations within * that session. */ [Compact] [CCode (cname = "session_cipher", cprefix = "session_cipher_", cheader_filename = "signal/session_cipher.h", free_function = "session_cipher_free")] public class SessionCipher { public void* user_data { get; set; } public DecryptionCallback decryption_callback { set; } [CCode (cname = "session_cipher_encrypt")] private int encrypt_(uint8[] padded_message, out CiphertextMessage encrypted_message); [CCode (cname = "session_cipher_encrypt_")] public CiphertextMessage encrypt(uint8[] padded_message) throws GLib.Error { CiphertextMessage res; throw_by_code(encrypt_(padded_message, out res)); return res; } [CCode (cname = "session_cipher_decrypt_pre_key_signal_message")] private int decrypt_pre_key_signal_message_(PreKeySignalMessage ciphertext, void* decrypt_context, out Buffer plaintext); [CCode (cname = "session_cipher_decrypt_pre_key_signal_message_")] public uint8[] decrypt_pre_key_signal_message(PreKeySignalMessage ciphertext, void* decrypt_context = null) throws GLib.Error { Buffer res; throw_by_code(decrypt_pre_key_signal_message_(ciphertext, decrypt_context, out res)); return res.data; } [CCode (cname = "session_cipher_decrypt_signal_message")] private int decrypt_signal_message_(SignalMessage ciphertext, void* decrypt_context, out Buffer plaintext); [CCode (cname = "session_cipher_decrypt_signal_message_")] public uint8[] decrypt_signal_message(SignalMessage ciphertext, void* decrypt_context = null) throws GLib.Error { Buffer res; throw_by_code(decrypt_signal_message_(ciphertext, decrypt_context, out res)); return res.data; } public int get_remote_registration_id(out uint32 remote_id); public int get_session_version(uint32 version); [CCode (has_target = false)] public delegate int DecryptionCallback(SessionCipher cipher, Buffer plaintext, void* decrypt_context); } [CCode (cname = "int", cheader_filename = "signal/protocol.h", has_type_id = false)] public enum CiphertextType { [CCode (cname = "CIPHERTEXT_SIGNAL_TYPE")] SIGNAL, [CCode (cname = "CIPHERTEXT_PREKEY_TYPE")] PREKEY, [CCode (cname = "CIPHERTEXT_SENDERKEY_TYPE")] SENDERKEY, [CCode (cname = "CIPHERTEXT_SENDERKEY_DISTRIBUTION_TYPE")] SENDERKEY_DISTRIBUTION } [Compact] [CCode (cname = "ciphertext_message", cprefix = "ciphertext_message_", cheader_filename = "signal/protocol.h,signal_helper.h")] public abstract class CiphertextMessage : TypeBase { public CiphertextType type { get; } [CCode (cname = "ciphertext_message_get_serialized")] private unowned Buffer get_serialized_(); public uint8[] serialized { [CCode (cname = "ciphertext_message_get_serialized_")] get { return get_serialized_().data; }} } [Compact] [CCode (cname = "signal_message", cprefix = "signal_message_", cheader_filename = "signal/protocol.h,signal_helper.h")] public class SignalMessage : CiphertextMessage { public ECPublicKey sender_ratchet_key { get; } public uint8 message_version { get; } public uint32 counter { get; } public Buffer body { get; } //public int verify_mac(uint8 message_version, ECPublicKey sender_identity_key, ECPublicKey receiver_identity_key, uint8[] mac, NativeContext global_context); public static int is_legacy(uint8[] data); } [Compact] [CCode (cname = "pre_key_signal_message", cprefix = "pre_key_signal_message_", cheader_filename = "signal/protocol.h,signal_helper.h")] public class PreKeySignalMessage : CiphertextMessage { public uint8 message_version { get; } public ECPublicKey identity_key { get; } public uint32 registration_id { get; } public uint32 pre_key_id { get; } public uint32 signed_pre_key_id { get; } public ECPublicKey base_key { get; } public SignalMessage signal_message { get; } } [Compact] [CCode (cname = "sender_key_message", cprefix = "sender_key_message_", cheader_filename = "signal/protocol.h,signal_helper.h")] public class SenderKeyMessage : CiphertextMessage { public uint32 key_id { get; } public uint32 iteration { get; } public Buffer ciphertext { get; } } [Compact] [CCode (cname = "sender_key_distribution_message", cprefix = "sender_key_distribution_message_", cheader_filename = "signal/protocol.h,signal_helper.h")] public class SenderKeyDistributionMessage : CiphertextMessage { public uint32 id { get; } public uint32 iteration { get; } public Buffer chain_key { get; } public ECPublicKey signature_key { get; } } [CCode (cname = "signal_vala_encrypt", cheader_filename = "signal_helper.h")] private static int aes_encrypt_(out Buffer output, int cipher, uint8[] key, uint8[] iv, uint8[] plaintext, void *user_data); [CCode (cname = "signal_vala_encrypt_")] public uint8[] aes_encrypt(int cipher, uint8[] key, uint8[] iv, uint8[] plaintext) throws GLib.Error { Buffer buf; throw_by_code(aes_encrypt_(out buf, cipher, key, iv, plaintext, null)); return buf.data; } [CCode (cname = "signal_vala_decrypt", cheader_filename = "signal_helper.h")] private static int aes_decrypt_(out Buffer output, int cipher, uint8[] key, uint8[] iv, uint8[] ciphertext, void *user_data); [CCode (cname = "signal_vala_decrypt_")] public uint8[] aes_decrypt(int cipher, uint8[] key, uint8[] iv, uint8[] ciphertext) throws GLib.Error { Buffer buf; throw_by_code(aes_decrypt_(out buf, cipher, key, iv, ciphertext, null)); return buf.data; } } dino-0.3.0/qlite/0000755000000000000000000000000014202022370012247 5ustar rootrootdino-0.3.0/qlite/CMakeLists.txt0000644000000000000000000000206514202022370015012 0ustar rootrootfind_packages(QLITE_PACKAGES REQUIRED Gee GLib GObject SQLite3>=3.24 ) vala_precompile(QLITE_VALA_C SOURCES "src/database.vala" "src/table.vala" "src/column.vala" "src/row.vala" "src/statement_builder.vala" "src/delete_builder.vala" "src/insert_builder.vala" "src/query_builder.vala" "src/update_builder.vala" "src/upsert_builder.vala" PACKAGES ${QLITE_PACKAGES} GENERATE_VAPI qlite GENERATE_HEADER qlite ) add_custom_target(qlite-vapi DEPENDS ${CMAKE_BINARY_DIR}/exports/qlite.vapi ${CMAKE_BINARY_DIR}/exports/qlite.deps ) add_definitions(${VALA_CFLAGS}) add_library(qlite SHARED ${QLITE_VALA_C}) add_dependencies(qlite qlite-vapi) target_link_libraries(qlite ${QLITE_PACKAGES}) set_target_properties(qlite PROPERTIES VERSION 0.1 SOVERSION 0) install(TARGETS qlite ${TARGET_INSTALL}) install(FILES ${CMAKE_BINARY_DIR}/exports/qlite.vapi ${CMAKE_BINARY_DIR}/exports/qlite.deps DESTINATION ${VAPI_INSTALL_DIR}) install(FILES ${CMAKE_BINARY_DIR}/exports/qlite.h DESTINATION ${INCLUDE_INSTALL_DIR}) dino-0.3.0/qlite/src/0000755000000000000000000000000014202022370013036 5ustar rootrootdino-0.3.0/qlite/src/column.vala0000644000000000000000000001413514202022370015204 0ustar rootrootusing Sqlite; namespace Qlite { public abstract class Column { public const string DEFAULT_TABLE_NAME = ""; public string name { get; private set; } public string? default { get; set; } public int sqlite_type { get; private set; } public bool primary_key { get; set; } public bool auto_increment { get; set; } public bool unique { get; set; } public virtual bool not_null { get; set; } public long min_version { get; set; default = -1; } public long max_version { get; set; default = long.MAX; } internal Table table { get; set; } public abstract T get(Row row, string? table_name = DEFAULT_TABLE_NAME); public virtual bool is_null(Row row, string? table_name = DEFAULT_TABLE_NAME) { return false; } internal abstract void bind(Statement stmt, int index, T value); public string to_string() { return table == null ? name : (table.name + "." + name); } public string to_column_definition() { string res = name; switch (sqlite_type) { case INTEGER: res += " INTEGER"; break; case FLOAT: res += " REAL"; break; case TEXT: res += " TEXT"; break; default: res += " UNKNOWN"; break; } if (primary_key) { res += " PRIMARY KEY"; if (auto_increment) res += " AUTOINCREMENT"; } if (not_null) res += " NOT NULL"; if (unique) res += " UNIQUE"; if (default != null) res += @" DEFAULT $((!) default)"; return res; } Column(string name, int type) { this.name = name; this.sqlite_type = type; } public class Integer : Column { public Integer(string name) { base(name, INTEGER); } public override int get(Row row, string? table_name = DEFAULT_TABLE_NAME) { return (int) row.get_integer(name, table_name == DEFAULT_TABLE_NAME ? table.name : table_name); } public override bool is_null(Row row, string? table_name = DEFAULT_TABLE_NAME) { return !row.has_integer(name, table_name == DEFAULT_TABLE_NAME ? table.name : table_name); } internal override void bind(Statement stmt, int index, int value) { stmt.bind_int(index, value); } } public class Long : Column { public Long(string name) { base(name, INTEGER); } public override long get(Row row, string? table_name = DEFAULT_TABLE_NAME) { return (long) row.get_integer(name, table_name == DEFAULT_TABLE_NAME ? table.name : table_name); } public override bool is_null(Row row, string? table_name = DEFAULT_TABLE_NAME) { return !row.has_integer(name, table_name == DEFAULT_TABLE_NAME ? table.name : table_name); } internal override void bind(Statement stmt, int index, long value) { stmt.bind_int64(index, value); } } public class NullableReal : Column { public NullableReal(string name) { base(name, FLOAT); } public override bool not_null { get { return false; } set {} } public override double? get(Row row, string? table_name = DEFAULT_TABLE_NAME) { return row.get_real(name, table_name == DEFAULT_TABLE_NAME ? table.name : table_name); } public override bool is_null(Row row, string? table_name = DEFAULT_TABLE_NAME) { return !row.has_real(name, table_name == DEFAULT_TABLE_NAME ? table.name : table_name); } internal override void bind(Statement stmt, int index, double? value) { stmt.bind_double(index, value); } } public class Text : Column { public Text(string name) { base(name, TEXT); } public override string? get(Row row, string? table_name = DEFAULT_TABLE_NAME) { return row.get_text(name, table_name == DEFAULT_TABLE_NAME ? table.name : table_name); } public override bool is_null(Row row, string? table_name = DEFAULT_TABLE_NAME) { return get(row, table_name == DEFAULT_TABLE_NAME ? table.name : table_name) == null; } internal override void bind(Statement stmt, int index, string? value) { if (value != null) { stmt.bind_text(index, (!) value); } else { stmt.bind_null(index); } } } public class NonNullText : Column { public NonNullText(string name) { base(name, TEXT); } public override bool not_null { get { return true; } set {} } public override string get(Row row, string? table_name = DEFAULT_TABLE_NAME) { return (!)row.get_text(name, table_name == DEFAULT_TABLE_NAME ? table.name : table_name); } public override bool is_null(Row row, string? table_name = DEFAULT_TABLE_NAME) { return false; } internal override void bind(Statement stmt, int index, string value) { stmt.bind_text(index, (!) value); } } public class BoolText : Column { public BoolText(string name) { base(name, TEXT); } public override bool get(Row row, string? table_name = DEFAULT_TABLE_NAME) { return row.get_text(name, table_name == DEFAULT_TABLE_NAME ? table.name : table_name) == "1"; } internal override void bind(Statement stmt, int index, bool value) { stmt.bind_text(index, value ? "1" : "0"); } } public class BoolInt : Column { public BoolInt(string name) { base(name, INTEGER); } public override bool get(Row row, string? table_name = DEFAULT_TABLE_NAME) { return row.get_integer(name, table_name == DEFAULT_TABLE_NAME ? table.name : table_name) == 1; } internal override void bind(Statement stmt, int index, bool value) { stmt.bind_int(index, value ? 1 : 0); } } } } dino-0.3.0/qlite/src/database.vala0000644000000000000000000001204614202022370015452 0ustar rootrootusing Sqlite; namespace Qlite { public class Database { private string file_name; private Sqlite.Database db; private long expected_version; private Table[]? tables; private Column meta_name = new Column.Text("name") { primary_key = true }; private Column meta_int_val = new Column.Long("int_val"); private Column meta_text_val = new Column.Text("text_val"); private Table meta_table; public bool debug = false; public Database(string file_name, long expected_version) { this.file_name = file_name; this.expected_version = expected_version; meta_table = new Table(this, "_meta"); meta_table.init({meta_name, meta_int_val, meta_text_val}); } public void init(Table[] tables) { Sqlite.config(Config.SERIALIZED); int ec = Sqlite.Database.open_v2(file_name, out db, OPEN_READWRITE | OPEN_CREATE | 0x00010000); if (ec != Sqlite.OK) { error(@"SQLite error: %d - %s", db.errcode(), db.errmsg()); } this.tables = tables; if (debug) db.trace((message) => print(@"Qlite trace: $message\n")); start_migration(); } public void ensure_init() { if (tables == null) error(@"Database $file_name was not initialized, call init()"); } private void start_migration() { try { exec("BEGIN TRANSACTION"); } catch (Error e) { error("SQLite error: %d - %s", db.errcode(), db.errmsg()); } meta_table.create_table_at_version(expected_version); long old_version = 0; old_version = meta_table.row_with(meta_name, "version")[meta_int_val, -1]; if (old_version == -1) { foreach (Table t in tables) { t.create_table_at_version(expected_version); } meta_table.insert().value(meta_name, "version").value(meta_int_val, expected_version).perform(); } else if (expected_version != old_version) { foreach (Table t in tables) { t.create_table_at_version(old_version); } foreach (Table t in tables) { t.add_columns_for_version(old_version, expected_version); } migrate(old_version); foreach (Table t in tables) { t.delete_columns_for_version(old_version, expected_version); } if (old_version == -1) { meta_table.insert().value(meta_name, "version").value(meta_int_val, expected_version).perform(); } else { meta_table.update().with(meta_name, "=", "version").set(meta_int_val, expected_version).perform(); } } foreach (Table t in tables) { t.post(); } try { exec("END TRANSACTION"); } catch (Error e) { error("SQLite error: %d - %s", db.errcode(), db.errmsg()); } } internal int errcode() { return db.errcode(); } internal string errmsg() { return db.errmsg(); } internal int64 last_insert_rowid() { return db.last_insert_rowid(); } // To be implemented by actual implementation if required // new table columns are added, outdated columns are still present and will be removed afterwards public virtual void migrate(long old_version) { } public QueryBuilder select(Column[]? columns = null) { ensure_init(); return new QueryBuilder(this).select(columns); } internal MatchQueryBuilder match_query(Table table) { ensure_init(); return new MatchQueryBuilder(this, table); } public InsertBuilder insert() { ensure_init(); return new InsertBuilder(this); } public UpdateBuilder update(Table table) { ensure_init(); return new UpdateBuilder(this, table); } public UpsertBuilder upsert(Table table) { ensure_init(); return new UpsertBuilder(this, table); } public UpdateBuilder update_named(string table) { ensure_init(); return new UpdateBuilder.for_name(this, table); } public DeleteBuilder delete() { ensure_init(); return new DeleteBuilder(this); } public RowIterator query_sql(string sql, string[]? args = null) { ensure_init(); return new RowIterator(this, sql, args); } internal Statement prepare(string sql) { ensure_init(); Sqlite.Statement statement; if (db.prepare_v2(sql, sql.length, out statement) != OK) { error("SQLite error: %d - %s: %s", db.errcode(), db.errmsg(), sql); } return statement; } public void exec(string sql) throws Error { ensure_init(); if (db.exec(sql) != OK) { throw new Error(-1, 0, "SQLite error: %d - %s", db.errcode(), db.errmsg()); } } public bool is_known_column(string table, string field) { ensure_init(); foreach (Table t in tables) { if (t.is_known_column(field)) return true; } return false; } } } dino-0.3.0/qlite/src/delete_builder.vala0000644000000000000000000000332014202022370016651 0ustar rootrootusing Sqlite; namespace Qlite { public class DeleteBuilder : StatementBuilder { // DELETE FROM [...] private Table? table; private string table_name; // WHERE [...] private string selection = "1"; private StatementBuilder.AbstractField[] selection_args = {}; internal DeleteBuilder(Database db) { base(db); } public DeleteBuilder from(Table table) { if (this.table != null) error("Qlite Error: ILLEGAL QUERY: cannot use from() multiple times."); this.table = table; this.table_name = table.name; return this; } public DeleteBuilder from_name(string table) { this.table_name = table; return this; } public DeleteBuilder where(string selection, string[]? selection_args = null) { if (this.selection != "1") error("selection was already done, but where() was called."); this.selection = selection; foreach (string arg in selection_args) { this.selection_args += new StatementBuilder.StringField(arg); } return this; } public DeleteBuilder with(Column column, string comp, T value) { selection_args += new Field(column, value); selection = @"($selection) AND $(column.name) $comp ?"; return this; } internal override Statement prepare() { Statement stmt = db.prepare(@"DELETE FROM $table_name WHERE $selection"); for (int i = 0; i < selection_args.length; i++) { selection_args[i].bind(stmt, i+1); } return stmt; } public void perform() { if (prepare().step() != DONE) { critical(@"SQLite error: %d - %s", db.errcode(), db.errmsg()); } } } } dino-0.3.0/qlite/src/insert_builder.vala0000644000000000000000000000415214202022370016717 0ustar rootrootusing Sqlite; namespace Qlite { public class InsertBuilder : StatementBuilder { // INSERT [OR ...] private bool replace_val; private string? or_val; // INTO [...] private Table table; private string table_name; // VALUES [...] private StatementBuilder.AbstractField[] fields = {}; internal InsertBuilder(Database db) { base(db); } public InsertBuilder replace() { this.replace_val = true; return this; } public InsertBuilder or(string or) { this.or_val = or; return this; } public InsertBuilder into(Table table) { this.table = table; this.table_name = table.name; return this; } public InsertBuilder into_name(string table) { this.table_name = table; return this; } public InsertBuilder value(Column column, T value) { fields += new Field(column, value); return this; } public InsertBuilder value_null(Column column) { if (column.not_null) error("Qlite Error: ILLEGAL QUERY: Can't set non-null column %s to null", column.name); fields += new NullField(column); return this; } internal override Statement prepare() { string fields_text = ""; string value_qs = ""; for (int i = 0; i < fields.length; i++) { if (i != 0) { value_qs += ", "; fields_text += ", "; } fields_text += ((!)fields[i].column).name; value_qs += "?"; } string sql = replace_val ? "REPLACE" : "INSERT"; if (!replace_val && or_val != null) sql += @" OR $((!)or_val)"; sql += @" INTO $table_name ( $fields_text ) VALUES ($value_qs)"; Statement stmt = db.prepare(sql); for (int i = 0; i < fields.length; i++) { fields[i].bind(stmt, i+1); } return stmt; } public int64 perform() { if (prepare().step() != DONE) { critical(@"SQLite error: %d - %s", db.errcode(), db.errmsg()); } return db.last_insert_rowid(); } } } dino-0.3.0/qlite/src/query_builder.vala0000644000000000000000000001632714202022370016567 0ustar rootrootusing Sqlite; namespace Qlite { public class QueryBuilder : StatementBuilder { private bool single_result; // SELECT [...] private string column_selector = "*"; private Column[] columns = {}; // FROM [...] protected Table? table; protected string? table_name; // JOIN [...] private string joins = ""; // WHERE [...] protected string selection = "1"; internal StatementBuilder.AbstractField[] selection_args = {}; // ORDER BY [...] private OrderingTerm[]? order_by_terms = {}; // GROUP BY [...] private string? group_by_term; // LIMIT [...] OFFSET [...] private int limit_val; private int offset_val; internal QueryBuilder(Database db) { base(db); } public QueryBuilder select(Column[] columns = {}) { this.columns = columns; if (columns.length != 0) { for (int i = 0; i < columns.length; i++) { if (column_selector == "*") { column_selector = columns[i].to_string(); } else { column_selector += ", " + columns[i].to_string(); } } } else { column_selector = "*"; } return this; } public QueryBuilder select_string(string column_selector) { this.columns = {}; this.column_selector = column_selector; return this; } public virtual QueryBuilder from(Table table) { if (this.table_name != null) error("cannot use from() multiple times."); this.table = table; this.table_name = table.name; return this; } public virtual QueryBuilder from_name(string table) { this.table_name = table; return this; } public QueryBuilder outer_join_with(Table table, Column lhs, Column rhs, string? as = null) { return outer_join_on(table, @"$lhs = $rhs", as); } public QueryBuilder outer_join_on(Table table, string on, string? as = null) { if (as == null) as = table.name; joins += @" LEFT OUTER JOIN $(table.name) AS $as ON $on"; return this; } public QueryBuilder join_with(Table table, Column lhs, Column rhs, string? as = null) { return join_on(table, @"$lhs = $rhs", as); } public QueryBuilder join_on(Table table, string on, string? as = null) { if (as == null) as = table.name; joins += @" JOIN $(table.name) AS $as ON $on"; return this; } internal QueryBuilder join_name(string table_name, string on) { joins += @" JOIN $table_name ON $on"; return this; } public QueryBuilder where(string selection, string[] selection_args = {}) { this.selection = @"($(this.selection)) AND ($selection)"; foreach (string arg in selection_args) { this.selection_args += new StatementBuilder.StringField(arg); } return this; } public QueryBuilder with(Column column, string comp, T value) { if ((column.unique || column.primary_key) && comp == "=") single_result = true; selection_args += new Field(column, value); selection = @"($selection) AND $column $comp ?"; return this; } public QueryBuilder with_null(Column column) { selection = @"($selection) AND $column ISNULL"; return this; } public QueryBuilder without_null(Column column) { selection = @"($selection) AND $column NOT NULL"; return this; } public QueryBuilder order_by(Column column, string dir = "ASC") { order_by_terms += new OrderingTerm(column, dir); return this; } public QueryBuilder order_by_name(string name, string dir) { order_by_terms += new OrderingTerm.by_name(name, dir); return this; } public QueryBuilder group_by(Column[] columns) { foreach(Column col in columns) { if (group_by_term == null) { group_by_term = col.to_string(); } else { group_by_term += @", $col"; } } return this; } public QueryBuilder limit(int limit) { if (this.limit_val != 0 && limit > this.limit_val) error("tried to increase an existing limit"); this.limit_val = limit; return this; } public QueryBuilder offset(int offset) { if (this.limit_val == 0) error("limit required before offset"); this.offset_val = offset; return this; } public QueryBuilder single() { this.single_result = true; return limit(1); } public int64 count() { this.column_selector = @"COUNT($column_selector) AS count"; this.single_result = true; return row().get_integer("count"); } private Row? row_() { if (!single_result) error("query is not suited to return a single row, but row() was called."); return iterator().get_next(); } public RowOption row() { return new RowOption(row_()); } public T get(Column field, T def = null) { return row().get(field, def); } internal override Statement prepare() { Statement stmt = db.prepare(@"SELECT $column_selector $(table_name == null ? "" : @"FROM $((!) table_name)") $joins WHERE $selection $(group_by_term == null ? "" : @"GROUP BY $group_by_term") $(OrderingTerm.all_to_string(order_by_terms)) $(limit_val > 0 ? @" LIMIT $limit_val OFFSET $offset_val" : "")"); for (int i = 0; i < selection_args.length; i++) { selection_args[i].bind(stmt, i+1); } return stmt; } public RowIterator iterator() { return new RowIterator.from_query_builder(db, this); } class OrderingTerm { Column? column; string column_name; string dir; public OrderingTerm(Column column, string dir) { this.column = column; this.column_name = column.to_string(); this.dir = dir; } public OrderingTerm.by_name(string column_name, string dir) { this.column_name = column_name; this.dir = dir; } public string to_string() { return @"$column_name $dir"; } public static string all_to_string(OrderingTerm[]? terms) { if (terms == null || terms.length == 0) return ""; string res = "ORDER BY "+terms[0].to_string(); for (int i = 1; i < terms.length; i++) { res += @", $(terms[i])"; } return res; } } } public class MatchQueryBuilder : QueryBuilder { internal MatchQueryBuilder(Database db, Table table) { base(db); if (table.fts_columns == null) error("MATCH query on non FTS table"); from(table); join_name(@"_fts_$table_name", @"_fts_$table_name.docid = $table_name.rowid"); } public MatchQueryBuilder match(Column column, string match) { if (table == null) error("MATCH must occur after FROM statement"); if (!(column in table.fts_columns)) error("MATCH selection on non FTS column"); selection_args += new StatementBuilder.StringField(match); selection = @"($selection) AND _fts_$table_name.$(column.name) MATCH ?"; return this; } } } dino-0.3.0/qlite/src/row.vala0000644000000000000000000001007014202022370014510 0ustar rootrootusing Gee; using Sqlite; namespace Qlite { public class Row { private Map text_map = new HashMap(); private Map int_map = new HashMap(); private Map real_map = new HashMap(); internal Row(Statement stmt) { for (int i = 0; i < stmt.column_count(); i++) { string column_name; if (stmt.column_origin_name(i) != null) { column_name = @"$(stmt.column_table_name(i)).$(stmt.column_origin_name(i))"; } else { column_name = stmt.column_name(i); } switch(stmt.column_type(i)) { case TEXT: text_map[column_name] = stmt.column_text(i); break; case INTEGER: int_map[column_name] = (long) stmt.column_int64(i); break; case FLOAT: real_map[column_name] = stmt.column_double(i); break; } } } public T get(Column field) { return field[this]; } private string field_name(string field, string? table) { if (table != null) { return @"$table.$field"; } else { return field; } } public string? get_text(string field, string? table = null) { if (text_map.has_key(field_name(field, table))) { return text_map[field_name(field, table)]; } return null; } public long get_integer(string field, string? table = null) { return int_map[field_name(field, table)]; } public bool has_integer(string field, string? table = null) { return int_map.has_key(field_name(field, table)); } public double get_real(string field, string? table = null, double def = 0) { return real_map[field_name(field, table)] ?? def; } public bool has_real(string field, string? table = null) { return real_map.has_key(field_name(field, table)) && real_map[field_name(field, table)] != null; } public string to_string() { string ret = "{"; foreach (string key in text_map.keys) { if (ret.length > 1) ret += ", "; ret = @"$ret$key: \"$(text_map[key])\""; } foreach (string key in int_map.keys) { if (ret.length > 1) ret += ", "; ret = @"$ret$key: $(int_map[key])"; } foreach (string key in real_map.keys) { if (ret.length > 1) ret += ", "; ret = @"$ret$key: $(real_map[key])"; } return ret + "}"; } } public class RowIterator { private Database db; private Statement stmt; public RowIterator.from_query_builder(Database db, QueryBuilder query) { this.db = db; this.stmt = query.prepare(); } public RowIterator(Database db, string sql, string[]? args = null) { this.db = db; this.stmt = db.prepare(sql); if (args != null) { for (int i = 0; i < args.length; i++) { stmt.bind_text(i, sql, sql.length); } } } public bool next() { int r = stmt.step(); if (r == Sqlite.ROW) return true; if (r == Sqlite.DONE) return false; warning(@"SQLite error: $(db.errcode()) - $(db.errmsg())"); return false; } public Row get() { return new Row(stmt); } public Row? get_next() { if (next()) return get(); return null; } } public class RowOption { public Row? inner { get; private set; } public RowOption(Row? row) { this.inner = row; } public bool is_present() { return inner != null; } public T get(Column field, T def = null) { if (inner == null || field.is_null((!)inner)) return def; return field[(!)inner]; } internal long get_integer(string field, long def = 0) { if (inner == null || !((!)inner).has_integer(field)) return def; return ((!)inner).get_integer(field); } } } dino-0.3.0/qlite/src/statement_builder.vala0000644000000000000000000000245314202022370017421 0ustar rootrootusing Sqlite; namespace Qlite { public abstract class StatementBuilder { protected Database db; internal StatementBuilder(Database db) { this.db = db; } internal abstract Statement prepare(); internal abstract class AbstractField { public T value; public Column? column; AbstractField(T value) { this.value = value; } internal abstract void bind(Statement stmt, int index); } internal class Field : AbstractField { public Field(Column column, T value) { base(value); this.column = column; } internal override void bind(Statement stmt, int index) { ((!)column).bind(stmt, index, value); } } internal class NullField : AbstractField { public NullField(Column column) { base(null); this.column = column; } internal override void bind(Statement stmt, int index) { stmt.bind_null(index); } } internal class StringField : AbstractField { public StringField(string value) { base(value); } internal override void bind(Statement stmt, int index) { stmt.bind_text(index, value); } } } } dino-0.3.0/qlite/src/table.vala0000644000000000000000000001646414202022370015005 0ustar rootrootusing Sqlite; namespace Qlite { public class Table { protected Database db; public string name { get; private set; } protected Column[]? columns; private string constraints = ""; private string[] post_statements = {}; private string[] create_statements = {}; internal Column[]? fts_columns; public Table(Database db, string name) { this.db = db; this.name = name; } public void init(Column[] columns, string constraints = "") { this.columns = columns; this.constraints = constraints; foreach(Column c in columns) { c.table = this; } } public void fts(Column[] columns) { if (fts_columns != null) error("Only one FTS index may be used per table."); fts_columns = columns; string cs = ""; string cnames = ""; string cnews = ""; foreach (Column c in columns) { cs += @", $(c.to_column_definition())"; cnames += @", $(c.name)"; cnews += @", new.$(c.name)"; } add_create_statement(@"CREATE VIRTUAL TABLE IF NOT EXISTS _fts_$name USING fts4(tokenize=unicode61, content=\"$name\"$cs)"); add_post_statement(@"CREATE TRIGGER IF NOT EXISTS _fts_bu_$(name) BEFORE UPDATE ON $name BEGIN DELETE FROM _fts_$name WHERE docid=old.rowid; END"); add_post_statement(@"CREATE TRIGGER IF NOT EXISTS _fts_bd_$(name) BEFORE DELETE ON $name BEGIN DELETE FROM _fts_$name WHERE docid=old.rowid; END"); add_post_statement(@"CREATE TRIGGER IF NOT EXISTS _fts_au_$(name) AFTER UPDATE ON $name BEGIN INSERT INTO _fts_$name(docid$cnames) VALUES(new.rowid$cnews); END"); add_post_statement(@"CREATE TRIGGER IF NOT EXISTS _fts_ai_$(name) AFTER INSERT ON $name BEGIN INSERT INTO _fts_$name(docid$cnames) VALUES(new.rowid$cnews); END"); } public void fts_rebuild() { if (fts_columns == null) error("FTS not available on this table."); try { db.exec(@"INSERT INTO _fts_$name(_fts_$name) VALUES('rebuild');"); } catch (Error e) { critical(@"Qlite Error: Rebuilding FTS index: $(e.message)"); } } public void unique(Column[] columns, string? on_conflict = null) { constraints += ", UNIQUE ("; bool first = true; foreach (Column c in columns) { if (!first) constraints += ", "; constraints += c.name; first = false; } constraints += ")"; if (on_conflict != null) { constraints += " ON CONFLICT " + (!)on_conflict; } } public void add_post_statement(string stmt) { post_statements += stmt; } public void add_create_statement(string stmt) { create_statements += stmt; } public void index(string index_name, Column[] columns, bool unique = false) { string stmt = @"CREATE $(unique ? "UNIQUE" : "") INDEX IF NOT EXISTS $index_name ON $name ("; bool first = true; foreach (Column c in columns) { if (!first) stmt += ", "; stmt += c.name; first = false; } stmt += ")"; add_post_statement(stmt); } private void ensure_init() { if (columns == null) error("Table %s was not initialized, call init()", name); } public QueryBuilder select(Column[]? columns = null) { ensure_init(); return db.select(columns).from(this); } private MatchQueryBuilder match_query() { ensure_init(); return db.match_query(this); } public MatchQueryBuilder match(Column column, string query) { return match_query().match(column, query); } public InsertBuilder insert() { ensure_init(); return db.insert().into(this); } public UpdateBuilder update() { ensure_init(); return db.update(this); } public UpsertBuilder upsert() { ensure_init(); return db.upsert(this); } public DeleteBuilder delete() { ensure_init(); return db.delete().from(this); } public RowOption row_with(Column column, T value) { ensure_init(); if (!column.unique && !column.primary_key) error("%s is not suited to identify a row, but used with row_with()", column.name); return select().with(column, "=", value).row(); } public bool is_known_column(string column) { ensure_init(); foreach (Column c in columns) { if (c.name == column) return true; } return false; } public void create_table_at_version(long version) { ensure_init(); string sql = @"CREATE TABLE IF NOT EXISTS $name ("; bool first = true; for (int i = 0; i < columns.length; i++) { Column c = columns[i]; if (c.min_version <= version && c.max_version >= version) { sql += @"$(!first ? "," : "") $(c.to_column_definition())"; first = false; } } sql += @"$constraints)"; try { db.exec(sql); } catch (Error e) { error(@"Qlite Error: Create table at version: $(e.message)"); } foreach (string stmt in create_statements) { try { db.exec(stmt); } catch (Error e) { error(@"Qlite Error: Create table at version: $(e.message)"); } } } public void add_columns_for_version(long old_version, long new_version) { ensure_init(); foreach (Column c in columns) { if (c.min_version <= new_version && c.max_version >= new_version && c.min_version > old_version) { try { db.exec(@"ALTER TABLE $name ADD COLUMN $(c.to_column_definition())"); } catch (Error e) { critical(@"Qlite Error: Add columns for version: $(e.message)"); } } } } public void delete_columns_for_version(long old_version, long new_version) { bool column_deletion_required = false; string column_list = ""; foreach (Column c in columns) { if (c.min_version <= new_version && c.max_version >= new_version) { if (column_list == "") { column_list = c.name; } else { column_list += ", " + c.name; } } if (!(c.min_version <= new_version && c.max_version >= new_version) && c.min_version <= old_version && c.max_version >= old_version) { column_deletion_required = true; } } if (column_deletion_required) { try { db.exec(@"ALTER TABLE $name RENAME TO _$(name)_$old_version"); create_table_at_version(new_version); db.exec(@"INSERT INTO $name ($column_list) SELECT $column_list FROM _$(name)_$old_version"); db.exec(@"DROP TABLE _$(name)_$old_version"); } catch (Error e) { error(@"Qlite Error: Delete columns for version change: $(e.message)"); } } } internal void post() { foreach (string stmt in post_statements) { try { db.exec(stmt); } catch (Error e) { error(@"Qlite Error: Post: $(e.message)"); } } } } } dino-0.3.0/qlite/src/update_builder.vala0000644000000000000000000000554714202022370016706 0ustar rootrootusing Sqlite; namespace Qlite { public class UpdateBuilder : StatementBuilder { // UPDATE [OR ...] private string? or_val; // [...] private Table? table; private string table_name; // SET [...] private StatementBuilder.AbstractField[] fields = {}; // WHERE [...] private string selection = "1"; private StatementBuilder.AbstractField[] selection_args = {}; internal UpdateBuilder(Database db, Table table) { base(db); this.table = table; this.table_name = table.name; } internal UpdateBuilder.for_name(Database db, string table) { base(db); this.table_name = table; } public UpdateBuilder or(string or) { this.or_val = or; return this; } public UpdateBuilder set(Column column, T value) { fields += new Field(column, value); return this; } public UpdateBuilder set_null(Column column) { if (column.not_null) error("Can't set non-null column %s to null", column.name); fields += new NullField(column); return this; } public UpdateBuilder where(string selection, string[] selection_args = {}) { if (this.selection != "1") error("selection was already done, but where() was called."); this.selection = selection; foreach (string arg in selection_args) { this.selection_args += new StatementBuilder.StringField(arg); } return this; } public UpdateBuilder with(Column column, string comp, T value) { selection_args += new Field(column, value); selection = @"($selection) AND $(column.name) $comp ?"; return this; } public UpdateBuilder with_null(Column column) { selection = @"($selection) AND $(column.name) ISNULL"; return this; } public UpdateBuilder without_null(Column column) { selection = @"($selection) AND $(column.name) NOT NULL"; return this; } internal override Statement prepare() { string sql = "UPDATE"; if (or_val != null) sql += @" OR $((!)or_val)"; sql += @" $table_name SET "; for (int i = 0; i < fields.length; i++) { if (i != 0) { sql += ", "; } sql += @"$(((!)fields[i].column).name) = ?"; } sql += @" WHERE $selection"; Statement stmt = db.prepare(sql); for (int i = 0; i < fields.length; i++) { fields[i].bind(stmt, i+1); } for (int i = 0; i < selection_args.length; i++) { selection_args[i].bind(stmt, i + fields.length + 1); } return stmt; } public void perform() { if (fields.length == 0) return; if (prepare().step() != DONE) { critical("SQLite error: %d - %s", db.errcode(), db.errmsg()); } } } } dino-0.3.0/qlite/src/upsert_builder.vala0000644000000000000000000000533214202022370016736 0ustar rootrootusing Sqlite; namespace Qlite { public class UpsertBuilder : StatementBuilder { // INTO [...] private Table table; private string table_name; // VALUES [...] private StatementBuilder.AbstractField[] keys = {}; private StatementBuilder.AbstractField[] fields = {}; internal UpsertBuilder(Database db, Table table) { base(db); this.table = table; this.table_name = table.name; } public UpsertBuilder value(Column column, T value, bool key = false) { if (key) { keys += new Field(column, value); } else { fields += new Field(column, value); } return this; } public UpsertBuilder value_null(Column column) { if (column.not_null) error("Can't set non-null column %s to null", column.name); fields += new NullField(column); return this; } internal override Statement prepare() { error("prepare() not available for upsert."); } internal Statement prepare_upsert() { var unique_fields = new StringBuilder(); var unique_values = new StringBuilder(); var update_fields = new StringBuilder(); var update_values = new StringBuilder(); var update_fields_vals = new StringBuilder(); for (int i = 0; i < keys.length; i++) { if (i != 0) { unique_fields.append(", "); unique_values.append(", "); } unique_fields.append(keys[i].column.name); unique_values.append("?"); } for (int i = 0; i < fields.length; i++) { if (i != 0) { update_fields.append(", "); update_values.append(", "); update_fields_vals.append(", "); } update_fields.append(fields[i].column.name); update_values.append("?"); update_fields_vals.append(fields[i].column.name).append("=excluded.").append(fields[i].column.name); } string sql = @"INSERT INTO $table_name ($(unique_fields.str), $(update_fields.str)) VALUES ($(unique_values.str), $(update_values.str)) " + @"ON CONFLICT ($(unique_fields.str)) DO UPDATE SET $(update_fields_vals.str)"; Statement stmt = db.prepare(sql); for (int i = 0; i < keys.length; i++) { keys[i].bind(stmt, i + 1); } for (int i = 0; i < fields.length; i++) { fields[i].bind(stmt, i + keys.length + 1); } return stmt; } public int64 perform() { if (prepare_upsert().step() != DONE) { critical(@"SQLite error: %d - %s", db.errcode(), db.errmsg()); } return db.last_insert_rowid(); } } } dino-0.3.0/xmpp-vala/0000755000000000000000000000000014202022370013036 5ustar rootrootdino-0.3.0/xmpp-vala/CMakeLists.txt0000644000000000000000000001534414202022370015605 0ustar rootrootfind_packages(ENGINE_PACKAGES REQUIRED GDKPixbuf2 Gee GIO GLib GObject ICU ) set(ENGINE_DEFINITIONS "") find_package(GIO) if(GIO_VERSION VERSION_GREATER "2.60") message(STATUS "ALPN support enabled") set(ENGINE_DEFINITIONS ALPN_SUPPORT) else() message(STATUS "No ALPN support, needs GIO >= 2.60") endif() set(ENGINE_EXTRA_OPTIONS ${MAIN_EXTRA_OPTIONS} --vapidir=${CMAKE_CURRENT_SOURCE_DIR}/vapi) vala_precompile(ENGINE_VALA_C SOURCES "src/core/direct_tls_xmpp_stream.vala" "src/core/io_xmpp_stream.vala" "src/core/module_flag.vala" "src/core/starttls_xmpp_stream.vala" "src/core/stream_connect.vala" "src/core/tls_xmpp_stream.vala" "src/core/xmpp_stream.vala" "src/core/namespace_state.vala" "src/core/stanza_attribute.vala" "src/core/stanza_node.vala" "src/core/stanza_reader.vala" "src/core/stanza_writer.vala" "src/core/xmpp_log.vala" "src/module/bind.vala" "src/module/bookmarks_provider.vala" "src/module/conference.vala" "src/module/iq/module.vala" "src/module/iq/stanza.vala" "src/module/jid.vala" "src/module/message/module.vala" "src/module/message/stanza.vala" "src/module/presence/flag.vala" "src/module/presence/module.vala" "src/module/presence/stanza.vala" "src/module/roster/flag.vala" "src/module/roster/item.vala" "src/module/roster/module.vala" "src/module/roster/versioning_module.vala" "src/module/sasl.vala" "src/module/session.vala" "src/module/stanza.vala" "src/module/stanza_error.vala" "src/module/stream_error.vala" "src/module/util.vala" "src/module/xep/0048_bookmarks.vala" "src/module/xep/0048_conference.vala" "src/module/xep/0402_bookmarks2.vala" "src/module/xep/0004_data_forms.vala" "src/module/xep/0030_service_discovery/flag.vala" "src/module/xep/0030_service_discovery/identity.vala" "src/module/xep/0030_service_discovery/info_result.vala" "src/module/xep/0030_service_discovery/item.vala" "src/module/xep/0030_service_discovery/items_result.vala" "src/module/xep/0030_service_discovery/module.vala" "src/module/xep/0045_muc/flag.vala" "src/module/xep/0045_muc/module.vala" "src/module/xep/0045_muc/status_code.vala" "src/module/xep/0047_in_band_bytestreams.vala" "src/module/xep/0049_private_xml_storage.vala" "src/module/xep/0054_vcard/module.vala" "src/module/xep/0060_pubsub.vala" "src/module/xep/0065_socks5_bytestreams.vala" "src/module/xep/0066_out_of_band_data.vala" "src/module/xep/0077_in_band_registration.vala" "src/module/xep/0082_date_time_profiles.vala" "src/module/xep/0084_user_avatars.vala" "src/module/xep/0085_chat_state_notifications.vala" "src/module/xep/0115_entity_capabilities.vala" "src/module/xep/0166_jingle/content.vala" "src/module/xep/0166_jingle/content_description.vala" "src/module/xep/0166_jingle/content_node.vala" "src/module/xep/0166_jingle/content_security.vala" "src/module/xep/0166_jingle/content_transport.vala" "src/module/xep/0166_jingle/component.vala" "src/module/xep/0166_jingle/jingle_flag.vala" "src/module/xep/0166_jingle/jingle_module.vala" "src/module/xep/0166_jingle/jingle_structs.vala" "src/module/xep/0166_jingle/reason_element.vala" "src/module/xep/0166_jingle/session.vala" "src/module/xep/0166_jingle/session_info.vala" "src/module/xep/0167_jingle_rtp/content_parameters.vala" "src/module/xep/0167_jingle_rtp/content_type.vala" "src/module/xep/0167_jingle_rtp/jingle_rtp_module.vala" "src/module/xep/0167_jingle_rtp/payload_type.vala" "src/module/xep/0167_jingle_rtp/session_info_type.vala" "src/module/xep/0167_jingle_rtp/stream.vala" "src/module/xep/0176_jingle_ice_udp/candidate.vala" "src/module/xep/0176_jingle_ice_udp/jingle_ice_udp_module.vala" "src/module/xep/0176_jingle_ice_udp/transport_parameters.vala" "src/module/xep/0177_jingle_raw_udp.vala" "src/module/xep/0384_omemo/omemo_encryptor.vala" "src/module/xep/0384_omemo/omemo_decryptor.vala" "src/module/xep/0184_message_delivery_receipts.vala" "src/module/xep/0191_blocking_command.vala" "src/module/xep/0198_stream_management.vala" "src/module/xep/0199_ping.vala" "src/module/xep/0203_delayed_delivery.vala" "src/module/xep/0215_external_service_discovery.vala" "src/module/xep/0234_jingle_file_transfer.vala" "src/module/xep/0249_direct_muc_invitations.vala" "src/module/xep/0260_jingle_socks5_bytestreams.vala" "src/module/xep/0261_jingle_in_band_bytestreams.vala" "src/module/xep/0272_muji.vala" "src/module/xep/0280_message_carbons.vala" "src/module/xep/0298_coin.vala" "src/module/xep/0308_last_message_correction.vala" "src/module/xep/0313_message_archive_management.vala" "src/module/xep/0333_chat_markers.vala" "src/module/xep/0334_message_processing_hints.vala" "src/module/xep/0353_jingle_message_initiation.vala" "src/module/xep/0353_call_invite_message.vala" "src/module/xep/0359_unique_stable_stanza_ids.vala" "src/module/xep/0363_http_file_upload.vala" "src/module/xep/0380_explicit_encryption.vala" "src/module/xep/0391_jingle_encrypted_transports.vala" "src/module/xep/0410_muc_self_ping.vala" "src/module/xep/pixbuf_storage.vala" "src/util.vala" PACKAGES ${ENGINE_PACKAGES} GENERATE_VAPI xmpp-vala GENERATE_HEADER xmpp-vala CUSTOM_VAPIS "${CMAKE_CURRENT_SOURCE_DIR}/src/glib_fixes.vapi" DEFINITIONS ${ENGINE_DEFINITIONS} OPTIONS ${ENGINE_EXTRA_OPTIONS} ) add_custom_target(xmpp-vala-vapi DEPENDS ${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi ${CMAKE_BINARY_DIR}/exports/xmpp-vala.deps ) add_definitions(${VALA_CFLAGS} -DG_LOG_DOMAIN="xmpp-vala") add_library(xmpp-vala SHARED ${ENGINE_VALA_C}) add_dependencies(xmpp-vala xmpp-vala-vapi) target_link_libraries(xmpp-vala ${ENGINE_PACKAGES}) set_target_properties(xmpp-vala PROPERTIES VERSION 0.1 SOVERSION 0) install(TARGETS xmpp-vala ${TARGET_INSTALL}) install(FILES ${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi ${CMAKE_BINARY_DIR}/exports/xmpp-vala.deps DESTINATION ${VAPI_INSTALL_DIR}) install(FILES ${CMAKE_BINARY_DIR}/exports/xmpp-vala.h DESTINATION ${INCLUDE_INSTALL_DIR}) if(BUILD_TESTS) vala_precompile(ENGINE_TEST_VALA_C SOURCES "tests/common.vala" "tests/testcase.vala" "tests/jid.vala" "tests/stanza.vala" "tests/util.vala" CUSTOM_VAPIS ${CMAKE_BINARY_DIR}/exports/xmpp-vala_internal.vapi PACKAGES ${ENGINE_PACKAGES} OPTIONS ${ENGINE_EXTRA_OPTIONS} ) add_definitions(${VALA_CFLAGS}) add_executable(xmpp-vala-test ${ENGINE_TEST_VALA_C}) target_link_libraries(xmpp-vala-test xmpp-vala ${SIGNAL_PROTOCOL_PACKAGES}) endif(BUILD_TESTS) dino-0.3.0/xmpp-vala/src/0000755000000000000000000000000014202022370013625 5ustar rootrootdino-0.3.0/xmpp-vala/src/core/0000755000000000000000000000000014202022370014555 5ustar rootrootdino-0.3.0/xmpp-vala/src/core/direct_tls_xmpp_stream.vala0000644000000000000000000000262314202022370022200 0ustar rootrootpublic class Xmpp.DirectTlsXmppStream : TlsXmppStream { const string[] ADVERTISED_PROTOCOLS = {"xmpp-client", null}; string host; uint16 port; TlsXmppStream.OnInvalidCertWrapper on_invalid_cert; public DirectTlsXmppStream(Jid remote_name, string host, uint16 port, TlsXmppStream.OnInvalidCertWrapper on_invalid_cert) { base(remote_name); this.host = host; this.port = port; this.on_invalid_cert = on_invalid_cert; } public override async void connect() throws IOStreamError { SocketClient client = new SocketClient(); try { debug("Connecting to %s:%i (tls)", host, port); IOStream? io_stream = yield client.connect_to_host_async(host, port); TlsConnection tls_connection = TlsClientConnection.new(io_stream, new NetworkAddress(remote_name.to_string(), port)); #if ALPN_SUPPORT tls_connection.set_advertised_protocols(ADVERTISED_PROTOCOLS); #endif tls_connection.accept_certificate.connect(on_invalid_certificate); tls_connection.accept_certificate.connect((cert, flags) => on_invalid_cert.func(cert, flags)); reset_stream(tls_connection); yield setup(); attach_negotation_modules(); } catch (Error e) { throw new IOStreamError.CONNECT("Failed connecting to %s:%i (tls): %s", host, port, e.message); } } } dino-0.3.0/xmpp-vala/src/core/io_xmpp_stream.vala0000644000000000000000000000720014202022370020447 0ustar rootrootusing Gee; public interface Xmpp.WriteNodeFunc : Object { public abstract async void write_stanza(XmppStream stream, StanzaNode node) throws IOStreamError; } public abstract class Xmpp.IoXmppStream : XmppStream { private IOStream? stream; internal StanzaReader? reader; internal StanzaWriter? writer; internal WriteNodeFunc? write_obj = null; protected IoXmppStream(Jid remote_name) { base(remote_name); } public override async void disconnect() throws IOStreamError, XmlError, IOError { disconnected = true; if (writer == null || reader == null || stream == null) { throw new IOStreamError.DISCONNECT("trying to disconnect, but no stream open"); } log.str("OUT", "", this); yield writer.write(""); reader.cancel(); yield stream.close_async(); } public void reset_stream(IOStream stream) { this.stream = stream; reader = new StanzaReader.for_stream(stream.input_stream); writer = new StanzaWriter.for_stream(stream.output_stream); writer.cancel.connect(reader.cancel); require_setup(); } public override async StanzaNode read() throws IOStreamError { StanzaReader? reader = this.reader; if (reader == null) throw new IOStreamError.READ("trying to read, but no stream open"); try { StanzaNode node = yield ((!)reader).read_node(); log.node("IN", node, this); return node; } catch (XmlError e) { throw new IOStreamError.READ(e.message); } } [Version (deprecated = true, deprecated_since = "0.1", replacement = "write_async")] public override void write(StanzaNode node) { write_async.begin(node, (obj, res) => { try { write_async.end(res); } catch (Error e) { } }); } public override async void write_async(StanzaNode node) throws IOStreamError { if (write_obj != null) { yield write_obj.write_stanza(this, node); } else { StanzaWriter? writer = this.writer; if (writer == null) throw new IOStreamError.WRITE("trying to write, but no stream open"); try { log.node("OUT", node, this); yield ((!)writer).write_node(node); } catch (XmlError e) { throw new IOStreamError.WRITE(e.message); } } } internal IOStream? get_stream() { return stream; } public override async void setup() throws IOStreamError { StanzaNode outs = new StanzaNode.build("stream", "http://etherx.jabber.org/streams") .put_attribute("to", remote_name.to_string()) .put_attribute("version", "1.0") .put_attribute("xmlns", "jabber:client") .put_attribute("stream", "http://etherx.jabber.org/streams", XMLNS_URI); outs.has_nodes = true; log.node("OUT ROOT", outs, this); write(outs); received_root_node(this, yield read_root()); setup_needed = false; } private async StanzaNode read_root() throws IOStreamError { StanzaReader? reader = this.reader; if (reader == null) throw new IOStreamError.READ("trying to read, but no stream open"); try { StanzaNode node = yield ((!)reader).read_root_node(); log.node("IN ROOT", node, this); return node; } catch (XmlError.TLS e) { throw new IOStreamError.TLS(e.message); } catch (Error e) { throw new IOStreamError.READ(e.message); } } }dino-0.3.0/xmpp-vala/src/core/module_flag.vala0000644000000000000000000000323514202022370017703 0ustar rootrootnamespace Xmpp { public class FlagIdentity : Object { public string ns { get; private set; } public string id { get; private set; } public FlagIdentity(string ns, string id) { this.ns = ns; this.id = id; } public T? cast(XmppStreamFlag flag) { return flag.get_type().is_a(typeof(T)) ? (T?) flag : null; } public bool matches(XmppStreamFlag module) { return module.get_ns() == ns && module.get_id() == id; } } public abstract class XmppStreamFlag : Object { public abstract string get_ns(); public abstract string get_id(); } public class ModuleIdentity : Object { public string ns { get; private set; } public string id { get; private set; } public ModuleIdentity(string ns, string id) { this.ns = ns; this.id = id; } public T? cast(XmppStreamModule module) { return module.get_type().is_a(typeof(T)) ? (T?) module : null; } public bool matches(XmppStreamModule module) { return module.get_ns() == ns && module.get_id() == id; } } public abstract class XmppStreamModule : Object { public abstract void attach(XmppStream stream); public abstract void detach(XmppStream stream); public abstract string get_ns(); public abstract string get_id(); } public abstract class XmppStreamNegotiationModule : XmppStreamModule { public abstract bool mandatory_outstanding(XmppStream stream); public abstract bool negotiation_active(XmppStream stream); } }dino-0.3.0/xmpp-vala/src/core/namespace_state.vala0000644000000000000000000000477114202022370020567 0ustar rootrootusing Gee; namespace Xmpp { public class NamespaceState { private HashMap uri_to_name = new HashMap (); private HashMap name_to_uri = new HashMap (); public string current_ns_uri; private NamespaceState parent; public NamespaceState() { add_assoc(XMLNS_URI, "xmlns"); add_assoc(XML_URI, "xml"); current_ns_uri = XML_URI; } public NamespaceState.for_stanza() { this(); add_assoc("http://etherx.jabber.org/streams", "stream"); current_ns_uri = "jabber:client"; } private NamespaceState.copy(NamespaceState old) { foreach (string key in old.uri_to_name.keys) { add_assoc(key, old.uri_to_name[key]); } set_current(old.current_ns_uri); } private NamespaceState.with_parent(NamespaceState parent) { this.copy(parent); this.parent = parent; } public NamespaceState.with_assoc(NamespaceState old, string ns_uri, string name) { this.copy(old); add_assoc(ns_uri, name); } public NamespaceState.with_current(NamespaceState old, string current_ns_uri) { this.copy(old); set_current(current_ns_uri); } public void add_assoc(string ns_uri, string name) { name_to_uri[name] = ns_uri; uri_to_name[ns_uri] = name; } public void set_current(string current_ns_uri) { this.current_ns_uri = current_ns_uri; } public string find_name(string ns_uri) throws XmlError { if (uri_to_name.has_key(ns_uri)) { return uri_to_name[ns_uri]; } throw new XmlError.NS_DICT_ERROR(@"NS URI $ns_uri not found."); } public string find_uri(string name) throws XmlError { if (name_to_uri.has_key(name)) { return name_to_uri[name]; } throw new XmlError.NS_DICT_ERROR(@"NS name $name not found."); } public NamespaceState push() { return new NamespaceState.with_parent(this); } public NamespaceState pop() { return parent; } public string to_string() { StringBuilder sb = new StringBuilder (); sb.append ("NamespaceState{"); foreach (string key in uri_to_name.keys) { sb.append(key); sb.append_c('='); sb.append(uri_to_name[key]); sb.append_c(','); } sb.append("current="); sb.append(current_ns_uri); sb.append_c('}'); return sb.str; } } } dino-0.3.0/xmpp-vala/src/core/stanza_attribute.vala0000644000000000000000000000534114202022370021010 0ustar rootrootnamespace Xmpp { public class StanzaAttribute : StanzaEntry { internal const string ATTRIBUTE_STRING_FORMAT = "{%s}:%s='%s'"; internal const string ATTRIBUTE_STRING_NO_NS_FORMAT = "%s='%s'"; internal const string ATTRIBUTE_STRING_ANSI_FORMAT = ANSI_COLOR_GRAY+"{%s}:"+ANSI_COLOR_END+"%s="+ANSI_COLOR_GREEN+"'%s'"+ANSI_COLOR_END; internal const string ATTRIBUTE_STRING_ANSI_NO_NS_FORMAT = "%s="+ANSI_COLOR_GREEN+"'%s'"+ANSI_COLOR_END; internal const string ATTRIBUTE_XML_FORMAT = "%s:%s='%s'"; internal const string ATTRIBUTE_XML_NO_NS_FORMAT = "%s='%s'"; internal const string ATTRIBUTE_XML_ANSI_FORMAT = "%s:%s="+ANSI_COLOR_GREEN+"'%s'"+ANSI_COLOR_END; internal const string ATTRIBUTE_XML_ANSI_NO_NS_FORMAT = "%s="+ANSI_COLOR_GREEN+"'%s'"+ANSI_COLOR_END; internal StanzaAttribute() { } public StanzaAttribute.build(string ns_uri, string name, string val) { this.ns_uri = ns_uri; this.name = name; this.val = val; } public bool equals(StanzaAttribute other) { if (other.ns_uri != ns_uri) return false; if (other.name != name) return false; if (other.val != val) return false; return true; } internal string printf(string fmt, bool no_ns = false, string? ns_name = null) { if (no_ns) { return fmt.printf(name, (!)encoded_val); } else { if (ns_name == null) { return fmt.printf((!)ns_uri, name, (!)encoded_val); } else { return fmt.printf((!)ns_name, name, (!)encoded_val); } } } public override string to_string(int i = 0) { return printf(ATTRIBUTE_STRING_FORMAT); } public string to_ansi_string(bool hide_ns = false) { if (hide_ns) { return printf(ATTRIBUTE_STRING_ANSI_NO_NS_FORMAT, true); } else { return printf(ATTRIBUTE_STRING_ANSI_FORMAT); } } public string to_xml(NamespaceState? state_ = null) throws XmlError { NamespaceState state = state_ ?? new NamespaceState(); if (ns_uri == state.current_ns_uri || (ns_uri == XMLNS_URI && name == "xmlns")) { return printf(ATTRIBUTE_XML_NO_NS_FORMAT, true); } else { return printf(ATTRIBUTE_XML_FORMAT, false, state.find_name((!)ns_uri)); } } public string to_ansi_xml(NamespaceState? state_ = null) throws XmlError { NamespaceState state = state_ ?? new NamespaceState(); if (ns_uri == state.current_ns_uri || (ns_uri == XMLNS_URI && name == "xmlns")) { return printf(ATTRIBUTE_XML_ANSI_NO_NS_FORMAT, true); } else { return printf(ATTRIBUTE_XML_ANSI_FORMAT, false, state.find_name((!)ns_uri)); } } } } dino-0.3.0/xmpp-vala/src/core/stanza_node.vala0000644000000000000000000003642214202022370017736 0ustar rootrootusing Gee; namespace Xmpp { public abstract class StanzaEntry { protected const string ANSI_COLOR_END = "\x1b[0m"; protected const string ANSI_COLOR_GREEN = "\x1b[32m"; protected const string ANSI_COLOR_YELLOW = "\x1b[33m"; protected const string ANSI_COLOR_GRAY = "\x1b[37m"; public string? ns_uri; public string name; public string? val; public string? encoded_val { owned get { if (val == null) return null; return ((!)val).replace("&", "&").replace("\"", """).replace("'", "'").replace("<", "<").replace(">", ">"); } set { if (value == null) { val = null; return; } string tmp = ((!)value).replace(">", ">").replace("<", "<").replace("'","'").replace(""","\""); while (tmp.contains("&#")) { int start = tmp.index_of("&#"); int end = tmp.index_of(";", start); if (end < start) break; unichar num = -1; if (tmp[start+2]=='x') { tmp.substring(start+3, start-end-3).scanf("%x", &num); } else { num = int.parse(tmp.substring(start+2, start-end-2)); } tmp = tmp.splice(start, end, num.to_string()); } val = tmp.replace("&", "&"); } } public virtual unowned string? get_string_content() { return val; } public virtual string to_string(int i = 0) { return get_string_content() ?? "(null)"; } } public class StanzaNode : StanzaEntry { public Gee.List sub_nodes = new ArrayList(); public Gee.List attributes = new ArrayList(); public bool has_nodes = false; public bool pseudo = false; internal StanzaNode() { } public StanzaNode.build(string name, string ns_uri = "jabber:client", ArrayList? nodes = null, ArrayList? attrs = null) { this.ns_uri = ns_uri; this.name = name; if (nodes != null) this.sub_nodes.add_all((!)nodes); if (attrs != null) this.attributes.add_all((!)attrs); } public StanzaNode.text(string text) { this.name = "#text"; this.val = text; } public StanzaNode.encoded_text(string text) { this.name = "#text"; this.encoded_val = text; } public StanzaNode add_self_xmlns() { if (ns_uri == null) return this; return put_attribute("xmlns", (!)ns_uri); } public unowned string? get_attribute(string name, string? ns_uri = null) { string _name = name; string? _ns_uri = ns_uri; if (_ns_uri == null) { if (_name.contains(":")) { var lastIndex = _name.last_index_of_char(':'); _ns_uri = _name.substring(0, lastIndex); _name = _name.substring(lastIndex + 1); } else { _ns_uri = this.ns_uri; } } foreach (var attr in attributes) { if (attr.ns_uri == (!)_ns_uri && attr.name == _name) return attr.val; } return null; } public int get_attribute_int(string name, int def = -1, string? ns_uri = null) { string? res = get_attribute(name, ns_uri); if (res == null) return def; return int.parse((!)res); } public uint get_attribute_uint(string name, uint def = 0, string? ns_uri = null) { string? res = get_attribute(name, ns_uri); if (res == null) return def; return (uint) long.parse((!)res); } public bool get_attribute_bool(string name, bool def = false, string? ns_uri = null) { string? res = get_attribute(name, ns_uri); if (res == null) return def; return ((!)res).down() == "true" || res == "1"; } public StanzaAttribute? get_attribute_raw(string name, string? ns_uri = null) { string _name = name; string? _ns_uri = ns_uri; if (_ns_uri == null) { if (_name.contains(":")) { var lastIndex = _name.last_index_of_char(':'); _ns_uri = _name.substring(0, lastIndex); _name = _name.substring(lastIndex + 1); } else { _ns_uri = this.ns_uri; } } foreach (var attr in attributes) { if (attr.ns_uri == _ns_uri && attr.name == _name) return attr; } return null; } public Gee.List get_attributes_by_ns_uri(string ns_uri) { ArrayList ret = new ArrayList (); foreach (var attr in attributes) { if (attr.ns_uri == ns_uri) ret.add(attr); } return ret; } public unowned string? get_deep_attribute(...) { va_list l = va_list(); StanzaAttribute? res = get_deep_attribute_(va_list.copy(l)); if (res == null) return null; return ((!)res).val; } public StanzaAttribute? get_deep_attribute_(va_list l) { StanzaNode node = this; string? attribute_name = l.arg(); if (attribute_name == null) return null; while (true) { string? s = l.arg(); if (s == null) break; StanzaNode? node_tmp = node.get_subnode((!)attribute_name); if (node_tmp == null) return null; node = (!)node_tmp; attribute_name = s; } return node.get_attribute_raw((!)attribute_name); } public StanzaNode? get_subnode(string name, string? ns_uri = null, bool recurse = false) { string _name = name; string? _ns_uri = ns_uri; if (ns_uri == null) { if (_name.contains(":")) { var lastIndex = _name.last_index_of_char(':'); _ns_uri = _name.substring(0, lastIndex); _name = _name.substring(lastIndex + 1); } else { _ns_uri = this.ns_uri; } } foreach (var node in sub_nodes) { if (node.ns_uri == _ns_uri && node.name == _name) return node; if (recurse) { var x = node.get_subnode(_name, _ns_uri, recurse); if (x != null) return x; } } return null; } public Gee.List get_subnodes(string name, string? ns_uri = null, bool recurse = false) { ArrayList ret = new ArrayList(); string _name = name; string? _ns_uri = ns_uri; if (ns_uri == null) { if (_name.contains(":")) { var lastIndex = _name.last_index_of_char(':'); _ns_uri = _name.substring(0, lastIndex); _name = _name.substring(lastIndex + 1); } else { _ns_uri = this.ns_uri; } } foreach (var node in sub_nodes) { if (node.ns_uri == _ns_uri && node.name == _name) ret.add(node); if (recurse) { ret.add_all(node.get_subnodes(_name, _ns_uri, recurse)); } } return ret; } public StanzaNode? get_deep_subnode(...) { va_list l = va_list(); return get_deep_subnode_(va_list.copy(l)); } public StanzaNode? get_deep_subnode_(va_list l) { StanzaNode node = this; while (true) { string? s = l.arg(); if (s == null) break; StanzaNode? node_tmp = node.get_subnode((!)s); if (node_tmp == null) return null; node = (!)node_tmp; } return node; } public Gee.List get_deep_subnodes(...) { va_list l = va_list(); return get_deep_subnodes_(va_list.copy(l)); } public Gee.List get_deep_subnodes_(va_list l) { StanzaNode node = this; string? subnode_name = l.arg(); if (subnode_name == null) return new ArrayList(); while (true) { string? s = l.arg(); if (s == null) break; StanzaNode? node_tmp = node.get_subnode((!)subnode_name); if (node_tmp == null) return new ArrayList(); node = (!)node_tmp; subnode_name = s; } return node.get_subnodes((!)subnode_name); } public Gee.List get_all_subnodes() { return sub_nodes; } public Gee.List get_deep_all_subnodes(...) { va_list l = va_list(); StanzaNode? node = get_deep_subnode_(va_list.copy(l)); if (node != null) return ((!)node).get_all_subnodes(); return new ArrayList(); } public void add_attribute(StanzaAttribute attr) { attributes.add(attr); } public override unowned string? get_string_content() { if (val != null) return val; if (sub_nodes.size == 1) return sub_nodes[0].get_string_content(); return null; } public unowned string? get_deep_string_content(...) { va_list l = va_list(); StanzaNode? node = get_deep_subnode_(va_list.copy(l)); if (node != null) return ((!)node).get_string_content(); return null; } public StanzaNode put_attribute(string name, string val, string? ns_uri = null) { string? _ns_uri = ns_uri; if (name == "xmlns") _ns_uri = XMLNS_URI; if (_ns_uri == null) _ns_uri = this.ns_uri; if (_ns_uri == null) return this; attributes.add(new StanzaAttribute.build((!)_ns_uri, name, val)); return this; } /** * Set only occurrence **/ public void set_attribute(string name, string val, string? ns_uri = null) { if (ns_uri == null) ns_uri = this.ns_uri; foreach (var attr in attributes) { if (attr.ns_uri == ns_uri && attr.name == name) { attr.val = val; return; } } put_attribute(name, val, ns_uri); } public StanzaNode put_node(StanzaNode node) { sub_nodes.add(node); return this; } public bool equals(StanzaNode other) { if (other.name != name) return false; if (other.val != val) return false; if (name == "#text") return true; if (other.ns_uri != ns_uri) return false; if (other.sub_nodes.size != sub_nodes.size) return false; for (int i = 0; i < sub_nodes.size; i++) { if (!other.sub_nodes[i].equals(sub_nodes[i])) return false; } if (other.attributes.size != attributes.size) return false; for (int i = 0; i < attributes.size; i++) { if (!other.attributes[i].equals(attributes[i])) return false; } return true; } private const string TAG_START_BEGIN_FORMAT = "%s<{%s}:%s"; private const string TAG_START_EMPTY_END = " />\n"; private const string TAG_START_CONTENT_END = ">\n"; private const string TAG_END_FORMAT = "%s\n"; private const string TAG_ANSI_START_BEGIN_FORMAT = "%s"+ANSI_COLOR_YELLOW+"<"+ANSI_COLOR_GRAY+"{%s}:"+ANSI_COLOR_YELLOW+"%s"+ANSI_COLOR_END; private const string TAG_ANSI_START_BEGIN_NO_NS_FORMAT = "%s"+ANSI_COLOR_YELLOW+"<%s"+ANSI_COLOR_END; private const string TAG_ANSI_START_EMPTY_END = ANSI_COLOR_YELLOW+" />"+ANSI_COLOR_END+"\n"; private const string TAG_ANSI_START_CONTENT_END = ANSI_COLOR_YELLOW+">"+ANSI_COLOR_END+"\n"; private const string TAG_ANSI_END_FORMAT = "%s"+ANSI_COLOR_YELLOW+""+ANSI_COLOR_END+"\n"; private const string TAG_ANSI_END_NO_NS_FORMAT = "%s"+ANSI_COLOR_YELLOW+""+ANSI_COLOR_END+"\n"; internal string printf(int i, string fmt_start_begin, string start_empty_end, string start_content_end, string fmt_end, string fmt_attr, bool no_ns = false) { string indent = string.nfill (i * 2, ' '); if (name == "#text") { if (((!)val).length > 1000) { return indent + "[... retracted for brevity ...]\n"; } return indent + ((!)val).replace("\n", indent + "\n") + "\n"; } var sb = new StringBuilder(); if (no_ns) { sb.append_printf(fmt_start_begin, indent, name); } else { sb.append_printf(fmt_start_begin, indent, (!)ns_uri, name); } foreach (StanzaAttribute attr in attributes) { sb.append_printf(" %s", attr.printf(fmt_attr, no_ns)); } if (!has_nodes && sub_nodes.size == 0) { sb.append(start_empty_end); } else { sb.append(start_content_end); if (sub_nodes.size != 0) { foreach (StanzaNode subnode in sub_nodes) { sb.append(subnode.printf(i+1, fmt_start_begin, start_empty_end, start_content_end, fmt_end, fmt_attr, no_ns)); } if (no_ns) { sb.append_printf(fmt_end, indent, name); } else { sb.append_printf(fmt_end, indent, (!)ns_uri, name); } } } return sb.str; } public override string to_string(int i = 0) { return printf(i, TAG_START_BEGIN_FORMAT, TAG_START_EMPTY_END, TAG_START_CONTENT_END, TAG_END_FORMAT, StanzaAttribute.ATTRIBUTE_STRING_FORMAT); } public string to_ansi_string(bool hide_ns = false, int i = 0) { if (hide_ns) { return printf(i, TAG_ANSI_START_BEGIN_NO_NS_FORMAT, TAG_ANSI_START_EMPTY_END, TAG_ANSI_START_CONTENT_END, TAG_ANSI_END_NO_NS_FORMAT, StanzaAttribute.ATTRIBUTE_STRING_ANSI_NO_NS_FORMAT, true); } else { return printf(i, TAG_ANSI_START_BEGIN_FORMAT, TAG_ANSI_START_EMPTY_END, TAG_ANSI_START_CONTENT_END, TAG_ANSI_END_FORMAT, StanzaAttribute.ATTRIBUTE_STRING_ANSI_FORMAT); } } public string to_xml(NamespaceState? state = null) throws XmlError { NamespaceState my_state = state ?? new NamespaceState.for_stanza(); if (name == "#text") return val == null ? "" : (!)encoded_val; my_state = my_state.push(); foreach (var xmlns in get_attributes_by_ns_uri (XMLNS_URI)) { if (xmlns.val == null) continue; if (xmlns.name == "xmlns") { my_state.set_current((!)xmlns.val); } else { my_state.add_assoc((!)xmlns.val, xmlns.name); } } var sb = new StringBuilder(); if (ns_uri == my_state.current_ns_uri) { sb.append_printf("<%s", name); } else { sb.append_printf("<%s:%s", my_state.find_name ((!)ns_uri), name); } var attr_ns_state = new NamespaceState.with_current(my_state, (!)ns_uri); foreach (StanzaAttribute attr in attributes) { sb.append_printf(" %s", attr.to_xml(attr_ns_state)); } if (!has_nodes && sub_nodes.size == 0) { sb.append("/>"); } else { sb.append(">"); if (sub_nodes.size != 0) { foreach (StanzaNode subnode in sub_nodes) { sb.append(subnode.to_xml(my_state)); } if (ns_uri == my_state.current_ns_uri) { sb.append(@""); } else { sb.append_printf("", my_state.find_name ((!)ns_uri), name); } } } my_state = my_state.pop(); return sb.str; } } } dino-0.3.0/xmpp-vala/src/core/stanza_reader.vala0000644000000000000000000002363214202022370020252 0ustar rootrootusing Gee; namespace Xmpp { public const string XMLNS_URI = "http://www.w3.org/2000/xmlns/"; public const string XML_URI = "http://www.w3.org/XML/1998/namespace"; public const string JABBER_URI = "jabber:client"; public errordomain XmlError { NS_DICT_ERROR, UNSUPPORTED, EOF, BAD_XML, IO, TLS } public class StanzaReader { private static int BUFFER_MAX = 4096; private InputStream? input; private uint8[] buffer; private int buffer_fill = 0; private int buffer_pos = 0; private Cancellable cancellable = new Cancellable(); private NamespaceState ns_state = new NamespaceState(); public StanzaReader.for_buffer(uint8[] buffer) { this.buffer = buffer; this.buffer_fill = buffer.length; } public StanzaReader.for_string(string s) { this.for_buffer(s.data); } public StanzaReader.for_stream(InputStream input) { this.input = input; buffer = new uint8[BUFFER_MAX]; } public void cancel() { cancellable.cancel(); } private async void update_buffer() throws XmlError { try { InputStream? input = this.input; if (input == null) throw new XmlError.EOF("No input stream specified and end of buffer reached."); if (cancellable.is_cancelled()) throw new XmlError.EOF("Input stream is canceled."); buffer_fill = (int) yield ((!)input).read_async(buffer, GLib.Priority.DEFAULT, cancellable); if (buffer_fill == 0) throw new XmlError.EOF("End of input stream reached."); buffer_pos = 0; } catch (GLib.IOError e) { throw new XmlError.IO("GLib.IOError: %s".printf(e.message)); } } private async char read_single() throws XmlError { if (buffer_pos >= buffer_fill) { yield update_buffer(); } return (char) buffer[buffer_pos++]; } private async char peek_single() throws XmlError { if (buffer_pos >= buffer_fill) { yield update_buffer(); } return (char) buffer[buffer_pos]; } private bool is_ws(uint8 what) { return what == ' ' || what == '\t' || what == '\r' || what == '\n'; } private void skip_single() { buffer_pos++; } private async void skip_until_non_ws() throws XmlError { if (buffer_pos >= buffer_fill) { yield update_buffer(); } while (is_ws(buffer[buffer_pos])) { buffer_pos++; if (buffer_pos >= buffer_fill) { yield update_buffer(); } } } private async string read_until_ws() throws XmlError { var res = new StringBuilder(); if (buffer_pos >= buffer_fill) { yield update_buffer(); } while (!is_ws(buffer[buffer_pos])) { res.append_c((char) buffer[buffer_pos++]); if (buffer_pos >= buffer_fill) { yield update_buffer(); } } return res.str; } private async string read_until_char_or_ws(char x, char y = 0) throws XmlError { var res = new StringBuilder(); if (buffer_pos >= buffer_fill) { yield update_buffer(); } while (buffer[buffer_pos] != x && buffer[buffer_pos] != y && !is_ws(buffer[buffer_pos])) { res.append_c((char) buffer[buffer_pos++]); if (buffer_pos >= buffer_fill) { yield update_buffer(); } } return res.str; } private async string read_until_char(char x) throws XmlError { var res = new StringBuilder(); if (buffer_pos >= buffer_fill) { yield update_buffer(); } while (buffer[buffer_pos] != x) { res.append_c((char) buffer[buffer_pos++]); if (buffer_pos >= buffer_fill) { yield update_buffer(); } } return res.str; } private async StanzaAttribute read_attribute() throws XmlError { var res = new StanzaAttribute(); res.name = yield read_until_char_or_ws('='); if ((yield read_single()) == '=') { var quot = yield peek_single(); if (quot == '\'' || quot == '"') { skip_single(); res.encoded_val = yield read_until_char(quot); skip_single(); } else { res.encoded_val = yield read_until_ws(); } } return res; } private void handle_entry_ns(StanzaEntry entry, string default_uri = ns_state.current_ns_uri) throws XmlError { if (entry.ns_uri != null) return; if (entry.name.contains(":")) { var split = entry.name.split(":"); entry.ns_uri = ns_state.find_uri(split[0]); entry.name = split[1]; } else { entry.ns_uri = default_uri; } } private void handle_stanza_ns(StanzaNode res) throws XmlError { foreach (StanzaAttribute attr in res.attributes) { if (attr.name == "xmlns" && attr.val != null) { attr.ns_uri = XMLNS_URI; ns_state.set_current((!)attr.val); } else if (attr.name.contains(":") && attr.val != null) { var split = attr.name.split(":"); if (split[0] == "xmlns") { attr.ns_uri = XMLNS_URI; attr.name = split[1]; ns_state.add_assoc((!)attr.val, attr.name); } } } handle_entry_ns(res); foreach (StanzaAttribute attr in res.attributes) { handle_entry_ns(attr, res.ns_uri ?? ns_state.current_ns_uri); } } public async StanzaNode read_node_start() throws XmlError { var res = new StanzaNode(); res.attributes = new ArrayList(); var eof = false; if ((yield peek_single()) == '<') skip_single(); if ((yield peek_single()) == '?') res.pseudo = true; if ((yield peek_single()) == '/') { eof = true; skip_single(); res.name = yield read_until_char_or_ws('>'); while ((yield peek_single()) != '>') { skip_single(); } skip_single(); res.has_nodes = false; res.pseudo = false; handle_stanza_ns(res); return res; } res.name = yield read_until_char_or_ws('>', '/'); yield skip_until_non_ws(); char next_char = yield peek_single(); while (next_char != '/' && next_char != '>' && next_char != '?') { res.attributes.add(yield read_attribute()); yield skip_until_non_ws(); next_char = yield peek_single(); } if ((yield read_single()) == '/' || res.pseudo) { res.has_nodes = false; skip_single(); } else { res.has_nodes = true; } handle_stanza_ns(res); return res; } public async StanzaNode read_text_node() throws XmlError { var res = new StanzaNode(); res.name = "#text"; res.ns_uri = ns_state.current_ns_uri; res.encoded_val = (yield read_until_char('<')); return res; } public async StanzaNode read_root_node() throws XmlError { yield skip_until_non_ws(); if ((yield peek_single()) == '<') { var res = yield read_node_start(); if (res.pseudo) { return yield read_root_node(); } return res; } else { throw new XmlError.BAD_XML("Content before root node"); } } public async StanzaNode read_stanza_node() throws XmlError { try { ns_state = ns_state.push(); var res = yield read_node_start(); if (res.has_nodes) { bool finish_node_seen = false; StanzaNode? text_node = null; do { text_node = yield read_text_node(); if ((yield peek_single()) == '<') { skip_single(); if ((yield peek_single()) == '/') { skip_single(); string desc = yield read_until_char('>'); skip_single(); if (desc.contains(":")) { var split = desc.split(":"); if (split[0] != ns_state.find_name((!)res.ns_uri)) throw new XmlError.BAD_XML(""); if (split[1] != res.name) throw new XmlError.BAD_XML(""); } else { if (ns_state.current_ns_uri != res.ns_uri) throw new XmlError.BAD_XML(""); if (desc != res.name) throw new XmlError.BAD_XML(""); } finish_node_seen = true; } else { res.sub_nodes.add(yield read_stanza_node()); } } } while (!finish_node_seen); if (res.sub_nodes.size == 0) { if (text_node == null || text_node.val.length == 0) { res.has_nodes = false; } else { res.sub_nodes.add(text_node); } } } ns_state = ns_state.pop(); return res; } catch (XmlError e) { uint8[] buffer_cpy = new uint8[buffer.length + 1]; Memory.copy(buffer_cpy, buffer, buffer.length); warning("XmlError at: %s".printf((string)buffer_cpy) + "\n"); throw e; } } public async StanzaNode read_node() throws XmlError { yield skip_until_non_ws(); if ((yield peek_single()) == '<') { return yield read_stanza_node(); } else { return yield read_text_node(); } } } } dino-0.3.0/xmpp-vala/src/core/stanza_writer.vala0000644000000000000000000000342514202022370020322 0ustar rootrootnamespace Xmpp { public class StanzaWriter { public signal void cancel(); private OutputStream output; private Queue queue = new Queue(); private bool running = false; public StanzaWriter.for_stream(OutputStream output) { this.output = output; } public async void write_node(StanzaNode node) throws XmlError { yield write_data(node.to_xml().data); } public async void write_nodes(StanzaNode node1, StanzaNode node2) throws XmlError { var data1 = node1.to_xml().data; var data2 = node2.to_xml().data; uint8[] concat = new uint8[data1.length + data2.length]; int i = 0; foreach (var datum in data1) { concat[i++] = datum; } foreach (var datum in data2) { concat[i++] = datum; } yield write_data(concat); } public async void write(string s) throws XmlError { yield write_data(s.data); } private async void write_data(owned uint8[] data) throws XmlError { if (running) { queue.push_tail(new SourceFuncWrapper(write_data.callback)); yield; } running = true; try { yield output.write_all_async(data, 0, null, null); } catch (GLib.Error e) { cancel(); throw new XmlError.IO(@"IOError in GLib: $(e.message)"); } finally { SourceFuncWrapper? sfw = queue.pop_head(); if (sfw != null) { sfw.sfun(); } else { running = false; } } } } public class SourceFuncWrapper : Object { public SourceFunc sfun; public SourceFuncWrapper(owned SourceFunc sfun) { this.sfun = (owned)sfun; } } } dino-0.3.0/xmpp-vala/src/core/starttls_xmpp_stream.vala0000644000000000000000000000404614202022370021725 0ustar rootrootpublic class Xmpp.StartTlsXmppStream : TlsXmppStream { private const string TLS_NS_URI = "urn:ietf:params:xml:ns:xmpp-tls"; string host; uint16 port; TlsXmppStream.OnInvalidCertWrapper on_invalid_cert; public StartTlsXmppStream(Jid remote, string host, uint16 port, TlsXmppStream.OnInvalidCertWrapper on_invalid_cert) { base(remote); this.host = host; this.port = port; this.on_invalid_cert = on_invalid_cert; } public override async void connect() throws IOStreamError { try { SocketClient client = new SocketClient(); debug("Connecting to %s:%i (starttls)", host, port); IOStream stream = yield client.connect_to_host_async(host, port); reset_stream(stream); yield setup(); StanzaNode node = yield read(); var starttls_node = node.get_subnode("starttls", TLS_NS_URI); if (starttls_node == null) { warning("%s does not offer starttls", remote_name.to_string()); } write(new StanzaNode.build("starttls", TLS_NS_URI).add_self_xmlns()); node = yield read(); if (node.ns_uri != TLS_NS_URI || node.name != "proceed") { warning("Server did not 'proceed' starttls request"); } try { var identity = new NetworkService("xmpp-client", "tcp", remote_name.to_string()); var conn = TlsClientConnection.new(get_stream(), identity); reset_stream(conn); conn.accept_certificate.connect(on_invalid_certificate); conn.accept_certificate.connect((cert, flags) => on_invalid_cert.func(cert, flags)); } catch (Error e) { stderr.printf("Failed to start TLS: %s\n", e.message); } yield setup(); attach_negotation_modules(); } catch (Error e) { throw new IOStreamError.CONNECT("Failed connecting to %s:%i (starttls): %s", host, port, e.message); } } } dino-0.3.0/xmpp-vala/src/core/stream_connect.vala0000644000000000000000000000770314202022370020435 0ustar rootrootnamespace Xmpp { private class SrvTargetInfo { public string host { get; set; } public uint16 port { get; set; } public string service { get; set; } public uint16 priority { get; set; } } public class XmppStreamResult { public TlsXmppStream? stream { get; set; } public TlsCertificateFlags? tls_errors { get; set; } public IOStreamError? io_error { get; set; } } public async XmppStreamResult establish_stream(Jid bare_jid, Gee.List modules, string? log_options, owned TlsXmppStream.OnInvalidCert on_invalid_cert) { Jid remote = bare_jid.domain_jid; TlsXmppStream.OnInvalidCertWrapper on_invalid_cert_wrapper = new TlsXmppStream.OnInvalidCertWrapper(on_invalid_cert); //Lookup xmpp-client and xmpps-client SRV records GLib.List? targets = new GLib.List(); GLibFixes.Resolver resolver = GLibFixes.Resolver.get_default(); try { GLib.List xmpp_services = yield resolver.lookup_service_async("xmpp-client", "tcp", remote.to_string(), null); foreach (SrvTarget service in xmpp_services) { targets.append(new SrvTargetInfo() { host=service.get_hostname(), port=service.get_port(), service="xmpp-client", priority=service.get_priority()}); } } catch (Error e) { debug("Got no xmpp-client DNS records for %s: %s", remote.to_string(), e.message); } try { GLib.List xmpp_services = yield resolver.lookup_service_async("xmpps-client", "tcp", remote.to_string(), null); foreach (SrvTarget service in xmpp_services) { targets.append(new SrvTargetInfo() { host=service.get_hostname(), port=service.get_port(), service="xmpps-client", priority=service.get_priority()}); } } catch (Error e) { debug("Got no xmpps-client DNS records for %s: %s", remote.to_string(), e.message); } targets.sort((a, b) => { return a.priority - b.priority; }); // Add fallback connection bool should_add_fallback = true; foreach (SrvTargetInfo target in targets) { if (target.service == "xmpp-client" && target.port == 5222 && target.host == remote.to_string()) { should_add_fallback = false; } } if (should_add_fallback) { targets.append(new SrvTargetInfo() { host=remote.to_string(), port=5222, service="xmpp-client", priority=uint16.MAX}); } // Try all connection options from lowest to highest priority TlsXmppStream? stream = null; TlsCertificateFlags? tls_errors = null; IOStreamError? io_error = null; foreach (SrvTargetInfo target in targets) { try { if (target.service == "xmpp-client") { stream = new StartTlsXmppStream(remote, target.host, target.port, on_invalid_cert_wrapper); } else { stream = new DirectTlsXmppStream(remote, target.host, target.port, on_invalid_cert_wrapper); } stream.log = new XmppLog(bare_jid.to_string(), log_options); foreach (XmppStreamModule module in modules) { stream.add_module(module); } yield stream.connect(); return new XmppStreamResult() { stream=stream }; } catch (IOStreamError e) { warning("Could not establish XMPP session with %s:%i: %s", target.host, target.port, e.message); if (stream != null) { if (stream.errors != null) { tls_errors = stream.errors; } io_error = e; stream.detach_modules(); } } } return new XmppStreamResult() { io_error=io_error, tls_errors=tls_errors }; } }dino-0.3.0/xmpp-vala/src/core/tls_xmpp_stream.vala0000644000000000000000000000226414202022370020647 0ustar rootrootpublic abstract class Xmpp.TlsXmppStream : IoXmppStream { public TlsCertificateFlags? errors; public delegate bool OnInvalidCert(GLib.TlsCertificate peer_cert, GLib.TlsCertificateFlags errors); public class OnInvalidCertWrapper { public OnInvalidCert func; public OnInvalidCertWrapper(owned OnInvalidCert func) { this.func = (owned) func; } } protected TlsXmppStream(Jid remote_name) { base(remote_name); } protected bool on_invalid_certificate(TlsCertificate peer_cert, TlsCertificateFlags errors) { this.errors = errors; string error_str = ""; foreach (var f in new TlsCertificateFlags[]{TlsCertificateFlags.UNKNOWN_CA, TlsCertificateFlags.BAD_IDENTITY, TlsCertificateFlags.NOT_ACTIVATED, TlsCertificateFlags.EXPIRED, TlsCertificateFlags.REVOKED, TlsCertificateFlags.INSECURE, TlsCertificateFlags.GENERIC_ERROR, TlsCertificateFlags.VALIDATE_ALL}) { if (f in errors) { error_str += @"$(f), "; } } warning(@"[%p, %s] Tls Certificate Errors: %s", this, this.remote_name.to_string(), error_str); return false; } }dino-0.3.0/xmpp-vala/src/core/xmpp_log.vala0000644000000000000000000001144314202022370017252 0ustar rootrootusing Gee; namespace Xmpp { public class XmppLog { protected const string ANSI_COLOR_END = "\x1b[0m"; protected const string ANSI_COLOR_WHITE = "\x1b[37;1m"; class NodeLogDesc { public string? name; private string? ns_uri; private string? val; private Map attrs = new HashMap(); private NodeLogDesc? inner; public NodeLogDesc(string desc) { string d = desc; if (d.contains("[")) { int start = d.index_of("["); int end = d.index_of("]"); string attrs = d.substring(start + 1, end - start - 1); d = d.substring(0, start) + d.substring(end + 1); foreach (string attr in attrs.split(",")) { if (attr.contains("=")) { string key = attr.substring(0, attr.index_of("=")); string val = attr.substring(attr.index_of("=") + 1); this.attrs[key] = val; } else { this.attrs[attr] = null; } } } if (d.contains(":") && d.index_of("{") == 0 && d.index_of("}") != -1) { int end = d.index_of("}"); this.ns_uri = d.substring(1, end - 1); d = d.substring(end + 2); } if (d.contains(".")) { inner = new NodeLogDesc(d.substring(d.index_of(".") + 1)); d = d.substring(0, d.index_of(".")); } else if (d.contains("=")) { this.val = d.substring(d.index_of("=")); d = d.substring(0, d.index_of("=")); } if (d != "") this.name = d; } public bool matches(StanzaNode node) { if (name != null && node.name != name) return false; if (ns_uri != null && node.ns_uri != ns_uri) return false; if (val != null && node.val != val) return false; foreach (var pair in attrs.entries) { if (pair.value == null && node.get_attribute(pair.key) == null) return false; else if (pair.value != null && pair.value != node.get_attribute(pair.key)) return false; } if (inner == null) return true; foreach (StanzaNode snode in node.get_all_subnodes()) { if (((!)inner).matches(snode)) return true; } return false; } } private bool use_ansi; private bool hide_ns = true; private string ident; private string desc; private Gee.List descs = new ArrayList(); public XmppLog(string? ident = null, string? desc = null) { this.ident = ident ?? ""; this.desc = desc ?? ""; this.use_ansi = is_atty(stderr.fileno()); while (this.desc.contains(";")) { string opt = this.desc.substring(0, this.desc.index_of(";")); this.desc = this.desc.substring(opt.length + 1); switch (opt) { case "ansi": use_ansi = true; break; case "no-ansi": use_ansi = false; break; case "hide-ns": hide_ns = true; break; case "show-ns": hide_ns = false; break; } } if (desc != "") { foreach (string d in this.desc.split("|")) { descs.add(new NodeLogDesc(d)); } } } public virtual bool should_log_node(StanzaNode node) { if (ident == "" || desc == "") return false; if (desc == "all") return true; foreach (var desc in descs) { if (desc.matches(node)) return true; } return false; } public virtual bool should_log_str(string str) { if (ident == "" || desc == "") return false; if (desc == "all") return true; foreach (var desc in descs) { if (desc.name == "#text") return true; } return false; } public void node(string what, StanzaNode node, XmppStream stream) { if (should_log_node(node)) { stderr.printf("%sXMPP %s [%s stream:%p thread:%p %s]%s\n%s\n", use_ansi ? ANSI_COLOR_WHITE : "", what, ident, stream, Thread.self(), new DateTime.now_local().to_string(), use_ansi ? ANSI_COLOR_END : "", use_ansi ? node.to_ansi_string(hide_ns) : node.to_string()); } } public void str(string what, string str, XmppStream stream) { if (should_log_str(str)) { stderr.printf("%sXMPP %s [%s stream:%p thread:%p %s]%s\n%s\n", use_ansi ? ANSI_COLOR_WHITE : "", what, ident, stream, Thread.self(), new DateTime.now_local().to_string(), use_ansi ? ANSI_COLOR_END : "", str); } } [CCode (cname = "isatty")] private static extern bool is_atty(int fd); } } dino-0.3.0/xmpp-vala/src/core/xmpp_stream.vala0000644000000000000000000001523714202022370017771 0ustar rootrootusing Gee; public errordomain Xmpp.IOStreamError { READ, WRITE, CONNECT, DISCONNECT, TLS } public abstract class Xmpp.XmppStream { public signal void received_node(XmppStream stream, StanzaNode node); public signal void received_root_node(XmppStream stream, StanzaNode node); public signal void received_features_node(XmppStream stream); public signal void received_message_stanza(XmppStream stream, StanzaNode node); public signal void received_presence_stanza(XmppStream stream, StanzaNode node); public signal void received_iq_stanza(XmppStream stream, StanzaNode node); public signal void received_nonza(XmppStream stream, StanzaNode node); public signal void stream_negotiated(XmppStream stream); public signal void attached_modules(XmppStream stream); public const string NS_URI = "http://etherx.jabber.org/streams"; public Gee.List flags { get; private set; default=new ArrayList(); } public Gee.List modules { get; private set; default=new ArrayList(); } public StanzaNode? features { get; private set; default = new StanzaNode.build("features", NS_URI); } public Jid remote_name; public XmppLog log = new XmppLog(); public bool negotiation_complete { get; set; default=false; } protected bool non_negotiation_modules_attached = false; protected bool setup_needed = false; protected bool disconnected = false; protected XmppStream(Jid remote_name) { this.remote_name = remote_name; } public abstract async void connect() throws IOStreamError; public abstract async void disconnect() throws IOStreamError, XmlError, IOError; public abstract async StanzaNode read() throws IOStreamError; [Version (deprecated = true, deprecated_since = "0.1", replacement = "write_async")] public abstract void write(StanzaNode node); public abstract async void write_async(StanzaNode node) throws IOStreamError; public abstract async void setup() throws IOStreamError; public void require_setup() { setup_needed = true; } public bool is_setup_needed() { return setup_needed; } public void add_flag(XmppStreamFlag flag) { flags.add(flag); } public bool has_flag(FlagIdentity? identity) { return get_flag(identity) != null; } public T? get_flag(FlagIdentity? identity) { if (identity == null) return null; foreach (var flag in flags) { if (((!)identity).matches(flag)) return ((!)identity).cast(flag); } return null; } public void remove_flag(XmppStreamFlag flag) { flags.remove(flag); } public XmppStream add_module(XmppStreamModule module) { foreach (XmppStreamModule m in modules) { if (m.get_ns() == module.get_ns() && m.get_id() == module.get_id()) { warning("[%p] Adding already added module: %s\n", this, module.get_id()); return this; } } modules.add(module); if (negotiation_complete) module.attach(this); return this; } public void detach_modules() { foreach (XmppStreamModule module in modules) { module.detach(this); } } public T? get_module(ModuleIdentity? identity) { if (identity == null) return null; foreach (var module in modules) { if (((!)identity).matches(module)) return ((!)identity).cast(module); } return null; } public async void loop() throws IOStreamError { while (true) { if (setup_needed) { yield setup(); } StanzaNode node = yield read(); Idle.add(loop.callback); yield; if (disconnected) break; yield handle_stanza(node); if (!non_negotiation_modules_attached && negotiation_modules_done()) { attach_non_negotation_modules(); non_negotiation_modules_attached = true; if (!negotiation_complete) { stream_negotiated(this); negotiation_complete = true; } } } } private async void handle_stanza(StanzaNode node) { received_node(this, node); if (node.ns_uri == NS_URI && node.name == "features") { features = node; received_features_node(this); } else if (node.ns_uri == NS_URI && node.name == "stream" && node.pseudo) { debug("[%p] Server closed stream", this); try { yield disconnect(); } catch (Error e) {} return; } else if (node.ns_uri == JABBER_URI) { if (node.name == "message") { received_message_stanza(this, node); } else if (node.name == "presence") { received_presence_stanza(this, node); } else if (node.name == "iq") { received_iq_stanza(this, node); } else { received_nonza(this, node); } } else { received_nonza(this, node); } } public bool is_negotiation_active() { foreach (XmppStreamModule module in modules) { if (module is XmppStreamNegotiationModule) { XmppStreamNegotiationModule negotiation_module = (XmppStreamNegotiationModule) module; if (negotiation_module.negotiation_active(this)) return true; } } return false; } private bool negotiation_modules_done() throws IOStreamError { if (setup_needed) return false; if (is_negotiation_active()) return false; foreach (XmppStreamModule module in modules) { if (module is XmppStreamNegotiationModule) { XmppStreamNegotiationModule negotiation_module = (XmppStreamNegotiationModule) module; if (negotiation_module.mandatory_outstanding(this)) { throw new IOStreamError.CONNECT("mandatory-to-negotiate feature not negotiated: " + negotiation_module.get_id()); } } } return true; } private void attach_non_negotation_modules() { foreach (XmppStreamModule module in modules) { if (module as XmppStreamNegotiationModule == null) { module.attach(this); } } attached_modules(this); } public void attach_negotation_modules() { foreach (XmppStreamModule module in modules) { if (module as XmppStreamNegotiationModule != null) { module.attach(this); } } } }dino-0.3.0/xmpp-vala/src/glib_fixes.vapi0000644000000000000000000000414714202022370016627 0ustar rootroot[CCode (cprefix = "G", gir_namespace = "Gio", gir_version = "2.0", lower_case_cprefix = "g_")] namespace GLibFixes { [CCode (cheader_filename = "gio/gio.h", type_id = "g_resolver_get_type ()")] public class Resolver : GLib.Object { [CCode (has_construct_function = false)] protected Resolver(); [Version (since = "2.22")] public static Resolver get_default(); [Version (since = "2.22")] public virtual string lookup_by_address(GLib.InetAddress address, GLib.Cancellable? cancellable = null) throws GLib.Error ; [Version (since = "2.22")] public virtual async string lookup_by_address_async(GLib.InetAddress address, GLib.Cancellable? cancellable = null) throws GLib.Error ; [Version (since = "2.22")] public virtual GLib.List lookup_by_name(string hostname, GLib.Cancellable? cancellable = null) throws GLib.Error ; [Version (since = "2.22")] public virtual async GLib.List lookup_by_name_async(string hostname, GLib.Cancellable? cancellable = null) throws GLib.Error ; [Version (since = "2.34")] public virtual GLib.List lookup_records(string rrname, GLib.ResolverRecordType record_type, GLib.Cancellable? cancellable = null) throws GLib.Error ; [Version (since = "2.34")] public virtual async GLib.List lookup_records_async(string rrname, GLib.ResolverRecordType record_type, GLib.Cancellable? cancellable = null) throws GLib.Error ; [Version (since = "2.22")] public virtual GLib.List lookup_service(string service, string protocol, string domain, GLib.Cancellable? cancellable = null) throws GLib.Error ; [CCode (finish_vfunc_name = "lookup_service_finish", vfunc_name = "lookup_service_async")] public async GLib.List lookup_service_async (string service, string protocol, string domain, GLib.Cancellable? cancellable = null) throws GLib.Error; [Version (since = "2.22")] public void set_default(); public virtual signal void reload (); } } dino-0.3.0/xmpp-vala/src/module/0000755000000000000000000000000014202022370015112 5ustar rootrootdino-0.3.0/xmpp-vala/src/module/bind.vala0000644000000000000000000000633014202022370016675 0ustar rootrootnamespace Xmpp.Bind { private const string NS_URI = "urn:ietf:params:xml:ns:xmpp-bind"; /** The parties to a stream MUST consider resource binding as mandatory-to-negotiate. (RFC6120 7.3.1) */ public class Module : XmppStreamNegotiationModule { public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "bind_module"); public string? requested_resource { get; set; } public signal void bound_to_resource(XmppStream stream, Jid my_jid); public Module(string? requested_resource) { this.requested_resource = requested_resource; } public void iq_response_stanza(XmppStream stream, Iq.Stanza iq) { var flag = stream.get_flag(Flag.IDENTITY); if (flag == null || flag.finished) return; if (iq.type_ == Iq.Stanza.TYPE_RESULT) { try { flag.my_jid = new Jid(iq.stanza.get_subnode("jid", NS_URI, true).get_string_content()); flag.finished = true; bound_to_resource(stream, flag.my_jid); } catch (InvalidJidError e) { warning("Received invalid Jid when binding: %s", e.message); } } } public void received_features_node(XmppStream stream) { if (stream.is_setup_needed()) return; if (stream.is_negotiation_active()) return; var bind = stream.features.get_subnode("bind", NS_URI); if (bind != null) { var flag = new Flag(); StanzaNode bind_node = new StanzaNode.build("bind", NS_URI).add_self_xmlns(); if (requested_resource != null) { bind_node.put_node(new StanzaNode.build("resource", NS_URI).put_node(new StanzaNode.text(requested_resource))); } stream.get_module(Iq.Module.IDENTITY).send_iq(stream, new Iq.Stanza.set(bind_node), iq_response_stanza); stream.add_flag(flag); } } public override void attach(XmppStream stream) { stream.received_features_node.connect(this.received_features_node); } public override void detach(XmppStream stream) { stream.received_features_node.disconnect(this.received_features_node); } public override bool mandatory_outstanding(XmppStream stream) { return !stream.has_flag(Flag.IDENTITY) || !stream.get_flag(Flag.IDENTITY).finished; } public override bool negotiation_active(XmppStream stream) { return stream.has_flag(Flag.IDENTITY) && !stream.get_flag(Flag.IDENTITY).finished; } public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } } public class Flag : XmppStreamFlag { public static FlagIdentity IDENTITY = new FlagIdentity(NS_URI, "bind"); public Jid? my_jid; public bool finished = false; public static Jid? get_my_jid(XmppStream stream) { return stream.get_flag(IDENTITY).my_jid; } public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } } } dino-0.3.0/xmpp-vala/src/module/bookmarks_provider.vala0000644000000000000000000000143514202022370021664 0ustar rootrootusing Gee; namespace Xmpp { public interface BookmarksProvider : Object { public signal void conference_added(XmppStream stream, Conference conferences); public signal void conference_removed(XmppStream stream, Jid jid); public signal void conference_changed(XmppStream stream, Conference conferences); public signal void received_conferences(XmppStream stream, Set conferences); public async abstract async Set? get_conferences(XmppStream stream); public async abstract void add_conference(XmppStream stream, Conference conference); public async abstract void remove_conference(XmppStream stream, Conference conference); public async abstract void replace_conference(XmppStream stream, Jid muc_jid, Conference modified_conference); } } dino-0.3.0/xmpp-vala/src/module/conference.vala0000644000000000000000000000110714202022370020065 0ustar rootrootnamespace Xmpp { public class Conference : Object { public virtual Jid? jid { get; set; } public virtual bool autojoin { get; set; } public virtual string? nick { get; set; } public virtual string? name { get; set; } public virtual string? password { get; set; } public bool equals(Conference c) { return equals_func(this, c); } public static bool equals_func(Conference a, Conference b) { return Jid.equals_func(a.jid, b.jid); } public static uint hash_func(Conference a) { return Jid.hash_func(a.jid); } } } dino-0.3.0/xmpp-vala/src/module/iq/0000755000000000000000000000000014202022370015523 5ustar rootrootdino-0.3.0/xmpp-vala/src/module/iq/module.vala0000644000000000000000000001214314202022370017656 0ustar rootrootusing Gee; namespace Xmpp.Iq { private const string NS_URI = "jabber:client"; public class Module : XmppStreamNegotiationModule { public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "iq_module"); public signal void preprocess_incoming_iq_set_get(XmppStream stream, Stanza iq_stanza); public signal void preprocess_outgoing_iq_set_get(XmppStream stream, Stanza iq_stanza); private HashMap responseListeners = new HashMap(); private HashMap> namespaceRegistrants = new HashMap>(); public async Iq.Stanza send_iq_async(XmppStream stream, Iq.Stanza iq) { assert(iq.type_ == Iq.Stanza.TYPE_GET || iq.type_ == Iq.Stanza.TYPE_SET); Iq.Stanza? return_stanza = null; send_iq(stream, iq, (_, result_iq) => { return_stanza = result_iq; Idle.add(send_iq_async.callback); }); yield; return return_stanza; } public delegate void OnResult(XmppStream stream, Iq.Stanza iq); public void send_iq(XmppStream stream, Iq.Stanza iq, owned OnResult? listener = null) { preprocess_outgoing_iq_set_get(stream, iq); stream.write(iq.stanza); if (listener != null) { responseListeners[iq.id] = new ResponseListener((owned) listener); } } public void register_for_namespace(string namespace, Handler module) { if (!namespaceRegistrants.has_key(namespace)) { namespaceRegistrants.set(namespace, new ArrayList()); } namespaceRegistrants[namespace].add(module); } public void unregister_from_namespace(string namespace, Handler module) { ArrayList? handlers = namespaceRegistrants[namespace]; if (handlers != null) handlers.remove(module); } public override void attach(XmppStream stream) { stream.received_iq_stanza.connect(on_received_iq_stanza); } public override void detach(XmppStream stream) { stream.received_iq_stanza.disconnect(on_received_iq_stanza); } public override bool mandatory_outstanding(XmppStream stream) { return false; } public override bool negotiation_active(XmppStream stream) { return false; } public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } private async void on_received_iq_stanza(XmppStream stream, StanzaNode node) { Iq.Stanza iq = new Iq.Stanza.from_stanza(node, stream.has_flag(Bind.Flag.IDENTITY) ? stream.get_flag(Bind.Flag.IDENTITY).my_jid : null); if (iq.type_ == Iq.Stanza.TYPE_RESULT || iq.is_error()) { if (responseListeners.has_key(iq.id)) { ResponseListener? listener = responseListeners.get(iq.id); if (listener != null) { listener.on_result(stream, iq); } responseListeners.unset(iq.id); } } else { Gee.List children = node.get_all_subnodes(); if (children.size == 1 && namespaceRegistrants.has_key(children[0].ns_uri)) { preprocess_incoming_iq_set_get(stream, iq); Gee.List handlers = namespaceRegistrants[children[0].ns_uri]; foreach (Handler handler in handlers) { if (iq.type_ == Iq.Stanza.TYPE_GET) { yield handler.on_iq_get(stream, iq); } else if (iq.type_ == Iq.Stanza.TYPE_SET) { yield handler.on_iq_set(stream, iq); } } } else { // Send error if we don't handle the NS of the IQ get/set payload (RFC6120 10.3.3 (2)) Iq.Stanza unavailable_error = new Iq.Stanza.error(iq, new ErrorStanza.service_unavailable()) { to=iq.from }; send_iq(stream, unavailable_error); } } } private class ResponseListener { public OnResult on_result { get; private owned set; } public ResponseListener(owned OnResult on_result) { this.on_result = (owned) on_result; } } } public interface Handler : Object { public async virtual void on_iq_get(XmppStream stream, Iq.Stanza iq) { Iq.Stanza bad_request = new Iq.Stanza.error(iq, new ErrorStanza.bad_request("unexpected IQ get for this namespace")); stream.get_module(Module.IDENTITY).send_iq(stream, bad_request); } public async virtual void on_iq_set(XmppStream stream, Iq.Stanza iq) { Iq.Stanza bad_request = new Iq.Stanza.error(iq, new ErrorStanza.bad_request("unexpected IQ set for this namespace")); stream.get_module(Module.IDENTITY).send_iq(stream, bad_request); } } } dino-0.3.0/xmpp-vala/src/module/iq/stanza.vala0000644000000000000000000000231614202022370017672 0ustar rootrootusing Gee; namespace Xmpp.Iq { public class Stanza : Xmpp.Stanza { public const string TYPE_GET = "get"; public const string TYPE_RESULT = "result"; public const string TYPE_SET = "set"; private Stanza(string? id = null) { base.outgoing(new StanzaNode.build("iq")); this.id = id ?? random_uuid(); } public Stanza.get(StanzaNode stanza_node, string? id = null) { this(id); this.type_ = TYPE_GET; stanza.put_node(stanza_node); } public Stanza.result(Stanza request, StanzaNode? stanza_node = null) { this(request.id); this.to = request.from; this.type_ = TYPE_RESULT; if (stanza_node != null) { stanza.put_node(stanza_node); } } public Stanza.set(StanzaNode stanza_node, string? id = null) { this(id); this.type_ = TYPE_SET; stanza.put_node(stanza_node); } public Stanza.error(Stanza request, ErrorStanza error_stanza) { this(request.id); this.type_ = TYPE_ERROR; stanza.put_node(error_stanza.error_node); } public Stanza.from_stanza(StanzaNode stanza_node, Jid? my_jid) { base.incoming(stanza_node, my_jid); } } } dino-0.3.0/xmpp-vala/src/module/jid.vala0000644000000000000000000001574414202022370016540 0ustar rootrootnamespace Xmpp { public class Jid { public string? localpart; public string domainpart; public string? resourcepart; public Jid bare_jid { owned get { return is_bare() ? this : new Jid.intern(null, localpart, domainpart, null); } } public Jid domain_jid { owned get { return is_domain() ? this : new Jid.intern(domainpart, null, domainpart, null); } } private string jid; public Jid(string jid) throws InvalidJidError { int slash_index = jid.index_of("/"); int at_index = jid.index_of("@"); if (at_index > slash_index && slash_index != -1) at_index = -1; string resourcepart = slash_index < 0 ? null : jid.slice(slash_index + 1, jid.length); string localpart = at_index < 0 ? null : jid.slice(0, at_index); string domainpart; if (at_index < 0) { if (slash_index < 0) { domainpart = jid; } else { domainpart = jid.slice(0, slash_index); } } else { if (slash_index < 0) { domainpart = jid.slice(at_index + 1, jid.length); } else { domainpart = jid.slice(at_index + 1, slash_index); } } this.components(localpart, domainpart, resourcepart); } private Jid.intern(owned string? jid, owned string? localpart, owned string domainpart, owned string? resourcepart) { this.jid = (owned) jid; this.localpart = (owned) localpart; this.domainpart = (owned) domainpart; this.resourcepart = (owned) resourcepart; } public Jid.components(string? localpart, string domainpart, string? resourcepart) throws InvalidJidError { // TODO verify and normalize all parts if (domainpart.length == 0) throw new InvalidJidError.EMPTY_DOMAIN("Domain is empty"); if (localpart != null && localpart.length == 0) throw new InvalidJidError.EMPTY_LOCAL("Localpart is empty but non-null"); if (resourcepart != null && resourcepart.length == 0) throw new InvalidJidError.EMPTY_RESOURCE("Resource is empty but non-null"); string domain = domainpart[domainpart.length - 1] == '.' ? domainpart.substring(0, domainpart.length - 1) : domainpart; if (domain.contains("xn--")) { domain = idna_decode(domain); } this.localpart = prepare(localpart, ICU.PrepType.RFC3920_NODEPREP); this.domainpart = prepare(domain, ICU.PrepType.RFC3491_NAMEPREP); this.resourcepart = prepare(resourcepart, ICU.PrepType.RFC3920_RESOURCEPREP); idna_verify(this.domainpart); } private static string idna_decode(string src) throws InvalidJidError { ICU.ErrorCode status = ICU.ErrorCode.ZERO_ERROR; ICU.IDNAInfo info; char[] dest = new char[src.length * 2]; ICU.IDNA.openUTS46(ICU.IDNAOptions.DEFAULT, ref status).nameToUnicodeUTF8(src, -1, dest, out info, ref status); if (status == ICU.ErrorCode.INVALID_CHAR_FOUND) { throw new InvalidJidError.INVALID_CHAR("Found invalid character"); } else if (status.is_failure() || info.errors > 0) { throw new InvalidJidError.UNKNOWN(@"Unknown error: $(status.errorName())"); } return (string) dest; } private static void idna_verify(string src) throws InvalidJidError { ICU.ErrorCode status = ICU.ErrorCode.ZERO_ERROR; ICU.IDNAInfo info; char[] dest = new char[src.length * 2]; ICU.IDNA.openUTS46(ICU.IDNAOptions.DEFAULT, ref status).nameToASCII_UTF8(src, -1, dest, out info, ref status); if (status == ICU.ErrorCode.INVALID_CHAR_FOUND) { throw new InvalidJidError.INVALID_CHAR("Found invalid character"); } else if (status.is_failure() || info.errors > 0) { throw new InvalidJidError.UNKNOWN(@"Unknown error: $(status.errorName())"); } } private static string? prepare(string? src, ICU.PrepType type, bool strict = false) throws InvalidJidError { if (src == null) return src; try { ICU.ParseError error; ICU.ErrorCode status = ICU.ErrorCode.ZERO_ERROR; ICU.PrepProfile profile = ICU.PrepProfile.openByType(type, ref status); ICU.String src16 = ICU.String.from_string(src); int32 dest16_capacity = src16.len() * 2 + 1; ICU.String dest16 = ICU.String.alloc(dest16_capacity); long dest16_length = profile.prepare(src16, src16.len(), dest16, dest16_capacity, strict ? ICU.PrepOptions.DEFAULT : ICU.PrepOptions.ALLOW_UNASSIGNED, out error, ref status); if (status == ICU.ErrorCode.INVALID_CHAR_FOUND) { throw new InvalidJidError.INVALID_CHAR("Found invalid character"); } else if (status == ICU.ErrorCode.STRINGPREP_PROHIBITED_ERROR) { throw new InvalidJidError.INVALID_CHAR("Found prohibited character"); } else if (status != ICU.ErrorCode.ZERO_ERROR) { throw new InvalidJidError.UNKNOWN(@"Unknown error: $(status.errorName())"); } else if (dest16_length < 0) { throw new InvalidJidError.UNKNOWN("Unknown error"); } return dest16.to_string(); } catch (ConvertError e) { throw new InvalidJidError.INVALID_CHAR(@"Conversion error: $(e.message)"); } } public Jid with_resource(string? resourcepart) throws InvalidJidError { return new Jid.components(localpart, domainpart, resourcepart); } public bool is_domain() { return localpart == null && resourcepart == null; } public bool is_bare() { return resourcepart == null; } public bool is_full() { return localpart != null && resourcepart != null; } public string to_string() { if (jid == null) { if (localpart != null && resourcepart != null) { jid = @"$localpart@$domainpart/$resourcepart"; } else if (localpart != null) { jid = @"$localpart@$domainpart"; } else if (resourcepart != null) { jid = @"$domainpart/$resourcepart"; } else { jid = domainpart; } } return jid; } public bool equals_bare(Jid? jid) { return jid != null && equals_bare_func(this, jid); } public bool equals(Jid? jid) { return jid != null && equals_func(this, jid); } public static new bool equals_bare_func(Jid jid1, Jid jid2) { return jid1.localpart == jid2.localpart && jid1.domainpart == jid2.domainpart; } public static bool equals_func(Jid jid1, Jid jid2) { return equals_bare_func(jid1, jid2) && jid1.resourcepart == jid2.resourcepart; } public static new uint hash_bare_func(Jid jid) { return jid.bare_jid.to_string().hash(); } public static new uint hash_func(Jid jid) { return jid.to_string().hash(); } } public errordomain InvalidJidError { EMPTY_DOMAIN, EMPTY_RESOURCE, EMPTY_LOCAL, INVALID_CHAR, UNKNOWN } } dino-0.3.0/xmpp-vala/src/module/message/0000755000000000000000000000000014202022370016536 5ustar rootrootdino-0.3.0/xmpp-vala/src/module/message/module.vala0000644000000000000000000000434514202022370020676 0ustar rootrootusing Gee; namespace Xmpp { private const string NS_URI = "jabber:client"; public class MessageModule : XmppStreamModule { public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "message_module"); public StanzaListenerHolder received_pipeline = new StanzaListenerHolder(); public StanzaListenerHolder send_pipeline = new StanzaListenerHolder(); public signal void received_message(XmppStream stream, MessageStanza message); public signal void received_error(XmppStream stream, MessageStanza message, ErrorStanza error); public signal void received_message_unprocessed(XmppStream stream, MessageStanza message); public async void send_message(XmppStream stream, MessageStanza message) throws IOStreamError { yield send_pipeline.run(stream, message); yield stream.write_async(message.stanza); } public async void received_message_stanza_async(XmppStream stream, StanzaNode node) { MessageStanza message = new MessageStanza.from_stanza(node, stream.get_flag(Bind.Flag.IDENTITY).my_jid); received_message_unprocessed(stream, message); if (message.is_error()) { ErrorStanza? error_stanza = message.get_error(); if (error_stanza == null) return; received_error(stream, message, error_stanza); } else { bool abort = yield received_pipeline.run(stream, message); if (abort) return; received_message(stream, message); } } private void received_message_stanza(XmppStream stream, StanzaNode node) { received_message_stanza_async.begin(stream, node); } public override void attach(XmppStream stream) { stream.received_message_stanza.connect(received_message_stanza); } public override void detach(XmppStream stream) { stream.received_message_stanza.disconnect(received_message_stanza); } public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } } } dino-0.3.0/xmpp-vala/src/module/message/stanza.vala0000644000000000000000000000363014202022370020705 0ustar rootrootusing Gee; namespace Xmpp { public class MessageStanza : Xmpp.Stanza { public const string NODE_BODY = "body"; public const string NODE_SUBJECT = "subject"; public const string NODE_THREAD = "thread"; public const string TYPE_CHAT = "chat"; public const string TYPE_GROUPCHAT = "groupchat"; public const string TYPE_HEADLINE = "headline"; public const string TYPE_NORMAL = "normal"; public bool rerun_parsing = false; private ArrayList flags = new ArrayList(); public string body { get { StanzaNode? body_node = stanza.get_subnode(NODE_BODY); return body_node == null ? null : body_node.get_string_content(); } set { StanzaNode? body_node = stanza.get_subnode(NODE_BODY); if (body_node == null) { body_node = new StanzaNode.build(NODE_BODY); stanza.put_node(body_node); } body_node.sub_nodes.clear(); body_node.put_node(new StanzaNode.text(value)); } } public override string? type_ { get { return base.type_ ?? TYPE_NORMAL; } set { base.type_ = value; } } public MessageStanza(string? id = null) { base.outgoing(new StanzaNode.build("message")); stanza.set_attribute(ATTRIBUTE_ID, id ?? random_uuid()); } public MessageStanza.from_stanza(StanzaNode stanza_node, Jid my_jid) { base.incoming(stanza_node, my_jid); } public void add_flag(MessageFlag flag) { flags.add(flag); } public MessageFlag? get_flag(string ns, string id) { foreach (MessageFlag flag in flags) { if (flag.get_ns() == ns && flag.get_id() == id) return flag; } return null; } } public abstract class MessageFlag : Object { public abstract string get_ns(); public abstract string get_id(); } } dino-0.3.0/xmpp-vala/src/module/presence/0000755000000000000000000000000014202022370016716 5ustar rootrootdino-0.3.0/xmpp-vala/src/module/presence/flag.vala0000644000000000000000000000434214202022370020477 0ustar rootrootusing Gee; namespace Xmpp.Presence { public class Flag : XmppStreamFlag { public static FlagIdentity IDENTITY = new FlagIdentity(NS_URI, "presence"); private HashMap> resources = new HashMap>(Jid.hash_bare_func, Jid.equals_bare_func); private HashMap presences = new HashMap(Jid.hash_func, Jid.equals_func); public Set get_available_jids() { return resources.keys; } public Gee.List? get_resources(Jid jid) { if (!resources.has_key(jid)) return null; ArrayList ret = new ArrayList(Jid.equals_func); ret.add_all(resources[jid]); return ret; } public Presence.Stanza? get_presence(Jid full_jid) { return presences[full_jid]; } public Gee.List get_presences(Jid jid) { Gee.List ret = new ArrayList(); Gee.List? jid_res = resources[jid]; if (jid_res == null) return ret; foreach (Jid full_jid in jid_res) { ret.add(presences[full_jid]); } return ret; } public void add_presence(Presence.Stanza presence) { if (!resources.has_key(presence.from)) { resources[presence.from] = new ArrayList(Jid.equals_func); } if (resources[presence.from].contains(presence.from)) { resources[presence.from].remove(presence.from); } resources[presence.from].add(presence.from); presences[presence.from] = presence; } public void remove_presence(Jid jid) { if (resources.has_key(jid)) { if (jid.is_bare()) { foreach (Jid full_jid in resources[jid]) { presences.unset(full_jid); } resources.unset(jid); } else { resources[jid].remove(jid); if (resources[jid].size == 0) { resources.unset(jid); } presences.unset(jid); } } } public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } } } dino-0.3.0/xmpp-vala/src/module/presence/module.vala0000644000000000000000000001156314202022370021056 0ustar rootrootusing Gee; namespace Xmpp.Presence { private const string NS_URI = "jabber:client"; public class Module : XmppStreamModule { public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "presence_module"); public signal void received_presence(XmppStream stream, Presence.Stanza presence); public signal void pre_send_presence_stanza(XmppStream stream, Presence.Stanza presence); public signal void initial_presence_sent(XmppStream stream, Presence.Stanza presence); public signal void received_available(XmppStream stream, Presence.Stanza presence); public signal void received_available_show(XmppStream stream, Jid jid, string show); public signal void received_unavailable(XmppStream stream, Presence.Stanza presence); public signal void received_subscription_request(XmppStream stream, Jid jid); public signal void received_subscription_approval(XmppStream stream, Jid jid); public signal void received_unsubscription(XmppStream stream, Jid jid); public bool available_resource = true; public void request_subscription(XmppStream stream, Jid bare_jid) { Presence.Stanza presence = new Presence.Stanza(); presence.to = bare_jid; presence.type_ = Presence.Stanza.TYPE_SUBSCRIBE; send_presence(stream, presence); } public void approve_subscription(XmppStream stream, Jid bare_jid) { Presence.Stanza presence = new Presence.Stanza(); presence.to = bare_jid; presence.type_ = Presence.Stanza.TYPE_SUBSCRIBED; send_presence(stream, presence); } public void deny_subscription(XmppStream stream, Jid bare_jid) { cancel_subscription(stream, bare_jid); } public void cancel_subscription(XmppStream stream, Jid bare_jid) { Presence.Stanza presence = new Presence.Stanza(); presence.to = bare_jid; presence.type_ = Presence.Stanza.TYPE_UNSUBSCRIBED; send_presence(stream, presence); } public void unsubscribe(XmppStream stream, Jid bare_jid) { Presence.Stanza presence = new Presence.Stanza(); presence.to = bare_jid; presence.type_ = Presence.Stanza.TYPE_UNSUBSCRIBE; send_presence(stream, presence); } public void send_presence(XmppStream stream, Presence.Stanza presence) { pre_send_presence_stanza(stream, presence); stream.write(presence.stanza); } public override void attach(XmppStream stream) { stream.add_flag(new Flag()); stream.received_presence_stanza.connect(on_received_presence_stanza); stream.stream_negotiated.connect(on_stream_negotiated); } public override void detach(XmppStream stream) { stream.received_presence_stanza.disconnect(on_received_presence_stanza); stream.stream_negotiated.disconnect(on_stream_negotiated); } private void on_received_presence_stanza(XmppStream stream, StanzaNode node) { Presence.Stanza presence = new Presence.Stanza.from_stanza(node, stream.get_flag(Bind.Flag.IDENTITY).my_jid); received_presence(stream, presence); switch (presence.type_) { case Presence.Stanza.TYPE_AVAILABLE: stream.get_flag(Flag.IDENTITY).add_presence(presence); received_available(stream, presence); received_available_show(stream, presence.from, presence.show); break; case Presence.Stanza.TYPE_UNAVAILABLE: stream.get_flag(Flag.IDENTITY).remove_presence(presence.from); received_unavailable(stream, presence); break; case Presence.Stanza.TYPE_SUBSCRIBE: received_subscription_request(stream, presence.from); break; case Presence.Stanza.TYPE_SUBSCRIBED: received_subscription_approval(stream, presence.from); break; case Presence.Stanza.TYPE_UNSUBSCRIBE: stream.get_flag(Flag.IDENTITY).remove_presence(presence.from); received_unsubscription(stream, presence.from); break; case Presence.Stanza.TYPE_UNSUBSCRIBED: break; } } private void on_stream_negotiated(XmppStream stream) { if (available_resource) { Presence.Stanza presence = new Presence.Stanza(); send_presence(stream, presence); initial_presence_sent(stream, presence); } } public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } } } dino-0.3.0/xmpp-vala/src/module/presence/stanza.vala0000644000000000000000000000566414202022370021076 0ustar rootrootnamespace Xmpp.Presence { public class Stanza : Xmpp.Stanza { public const string NODE_PRIORITY = "priority"; public const string NODE_STATUS = "status"; public const string NODE_SHOW = "show"; public const string SHOW_ONLINE = "online"; public const string SHOW_AWAY = "away"; public const string SHOW_CHAT = "chat"; public const string SHOW_DND = "dnd"; public const string SHOW_XA = "xa"; public const string TYPE_AVAILABLE = "available"; public const string TYPE_PROBE = "probe"; public const string TYPE_SUBSCRIBE = "subscribe"; public const string TYPE_SUBSCRIBED = "subscribed"; public const string TYPE_UNAVAILABLE = "unavailable"; public const string TYPE_UNSUBSCRIBE = "unsubscribe"; public const string TYPE_UNSUBSCRIBED = "unsubscribed"; public int priority { get { StanzaNode? priority_node = stanza.get_subnode(NODE_PRIORITY); if (priority_node == null) { return 0; } else { return int.parse(priority_node.get_string_content()); } } set { StanzaNode? priority_node = stanza.get_subnode(NODE_PRIORITY); if (priority_node == null) { priority_node = new StanzaNode.build(NODE_PRIORITY); stanza.put_node(priority_node); } priority_node.val = value.to_string(); } } public string? status { get { StanzaNode? status_node = stanza.get_subnode(NODE_STATUS); return status_node != null ? status_node.get_string_content() : null; } set { StanzaNode? status_node = stanza.get_subnode(NODE_STATUS); if (status_node == null) { status_node = new StanzaNode.build(NODE_STATUS); stanza.put_node(status_node); } status_node.val = value; } } public string show { get { StanzaNode? show_node = stanza.get_subnode(NODE_SHOW); if (show_node == null) { return SHOW_ONLINE; } return show_node.get_string_content() ?? SHOW_ONLINE; } set { if (value != SHOW_ONLINE) { StanzaNode? show_node = stanza.get_subnode(NODE_SHOW); if (show_node == null) { show_node = new StanzaNode.build(NODE_SHOW); stanza.put_node(show_node); } show_node.val = value; } } } public override string? type_ { get { return base.type_ ?? TYPE_AVAILABLE; } set { base.type_ = value; } } public Stanza(string? id = null) { stanza = new StanzaNode.build("presence"); this.id = id ?? random_uuid(); } public Stanza.from_stanza(StanzaNode stanza_node, Jid my_jid) { base.incoming(stanza_node, my_jid); } } } dino-0.3.0/xmpp-vala/src/module/roster/0000755000000000000000000000000014202022370016430 5ustar rootrootdino-0.3.0/xmpp-vala/src/module/roster/flag.vala0000644000000000000000000000110414202022370020202 0ustar rootrootusing Gee; namespace Xmpp.Roster { public class Flag : XmppStreamFlag { public const string ID = "roster"; public static FlagIdentity IDENTITY = new FlagIdentity(NS_URI, ID); public HashMap roster_items = new HashMap(); public string? iq_id; public Collection get_roster() { return roster_items.values; } public Item? get_item(Jid jid) { return roster_items[jid]; } public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } } }dino-0.3.0/xmpp-vala/src/module/roster/item.vala0000644000000000000000000000271714202022370020242 0ustar rootrootusing Gee; namespace Xmpp.Roster { public class Item { public const string NODE_JID = "jid"; public const string NODE_NAME = "name"; public const string NODE_SUBSCRIPTION = "subscription"; public const string SUBSCRIPTION_NONE = "none"; public const string SUBSCRIPTION_TO = "to"; public const string SUBSCRIPTION_FROM = "from"; public const string SUBSCRIPTION_BOTH = "both"; public const string SUBSCRIPTION_REMOVE = "remove"; public StanzaNode stanza_node; private Jid jid_; public Jid? jid { get { try { return jid_ ?? (jid_ = new Jid(stanza_node.get_attribute(NODE_JID))); } catch (InvalidJidError e) { warning("Ignoring invalid Jid in roster entry: %s", e.message); return null; } } set { stanza_node.set_attribute(NODE_JID, value.to_string()); } } public string? name { get { return stanza_node.get_attribute(NODE_NAME); } set { if (value != null) stanza_node.set_attribute(NODE_NAME, value); } } public string? subscription { get { return stanza_node.get_attribute(NODE_SUBSCRIPTION); } set { if (value != null) stanza_node.set_attribute(NODE_SUBSCRIPTION, value); } } public Item() { stanza_node = new StanzaNode.build("item", NS_URI); } public Item.from_stanza_node(StanzaNode stanza_node) { this.stanza_node = stanza_node; } } } dino-0.3.0/xmpp-vala/src/module/roster/module.vala0000644000000000000000000001124214202022370020562 0ustar rootrootusing Gee; namespace Xmpp.Roster { private const string NS_URI = "jabber:iq:roster"; public class Module : XmppStreamModule, Iq.Handler { public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "roster_module"); public signal void received_roster(XmppStream stream, Collection roster, Iq.Stanza stanza); public signal void pre_get_roster(XmppStream stream, Iq.Stanza iq); public signal void item_removed(XmppStream stream, Item item, Iq.Stanza iq); public signal void item_updated(XmppStream stream, Item item, Iq.Stanza iq); public signal void mutual_subscription(XmppStream stream, Jid jid); public bool interested_resource = true; public void add_jid(XmppStream stream, Jid jid, string? handle = null) { Item roster_item = new Item(); roster_item.jid = jid; if (handle != null) { roster_item.name = handle; } roster_set(stream, roster_item); } public void remove_jid(XmppStream stream, Jid jid) { Item roster_item = new Item(); roster_item.jid = jid; roster_item.subscription = Item.SUBSCRIPTION_REMOVE; roster_set(stream, roster_item); } /** * Set a handle for a jid * @param handle Handle to be set. If null, any handle will be removed. */ public void set_jid_handle(XmppStream stream, Jid jid, string? handle) { Flag flag = stream.get_flag(Flag.IDENTITY); Item item = flag.get_item(jid) ?? new Item() { jid=jid }; item.name = handle != null ? handle : ""; roster_set(stream, item); } public async void on_iq_set(XmppStream stream, Iq.Stanza iq) { StanzaNode? query_node = iq.stanza.get_subnode("query", NS_URI); if (query_node == null) return; if (!iq.from.equals(stream.get_flag(Bind.Flag.IDENTITY).my_jid.bare_jid)) { warning("Received alleged roster push from %s, ignoring", iq.from.to_string()); return; } Flag flag = stream.get_flag(Flag.IDENTITY); Item item = new Item.from_stanza_node(query_node.get_subnode("item", NS_URI)); switch (item.subscription) { case Item.SUBSCRIPTION_REMOVE: flag.roster_items.unset(item.jid); item_removed(stream, item, iq); break; default: bool is_new = false; Item old = flag.get_item(item.jid); is_new = item.subscription == Item.SUBSCRIPTION_BOTH && (old == null || old.subscription == Item.SUBSCRIPTION_BOTH); flag.roster_items[item.jid] = item; item_updated(stream, item, iq); if(is_new) mutual_subscription(stream, item.jid); break; } } public override void attach(XmppStream stream) { stream.get_module(Iq.Module.IDENTITY).register_for_namespace(NS_URI, this); stream.get_module(Presence.Module.IDENTITY).initial_presence_sent.connect(roster_get); stream.add_flag(new Flag()); } public override void detach(XmppStream stream) { stream.get_module(Presence.Module.IDENTITY).initial_presence_sent.disconnect(roster_get); } internal override string get_ns() { return NS_URI; } internal override string get_id() { return IDENTITY.id; } private void roster_get(XmppStream stream) { stream.get_flag(Flag.IDENTITY).iq_id = random_uuid(); StanzaNode query_node = new StanzaNode.build("query", NS_URI).add_self_xmlns(); Iq.Stanza iq = new Iq.Stanza.get(query_node, stream.get_flag(Flag.IDENTITY).iq_id); pre_get_roster(stream, iq); stream.get_module(Iq.Module.IDENTITY).send_iq(stream, iq, on_roster_get_received); } private static void on_roster_get_received(XmppStream stream, Iq.Stanza iq) { Flag flag = stream.get_flag(Flag.IDENTITY); if (iq.id == flag.iq_id) { StanzaNode? query_node = iq.stanza.get_subnode("query", NS_URI); if (query_node != null) { foreach (StanzaNode item_node in query_node.sub_nodes) { Item item = new Item.from_stanza_node(item_node); flag.roster_items[item.jid] = item; } } stream.get_module(Module.IDENTITY).received_roster(stream, flag.roster_items.values, iq); } } private void roster_set(XmppStream stream, Item roster_item) { StanzaNode query_node = new StanzaNode.build("query", NS_URI).add_self_xmlns() .put_node(roster_item.stanza_node); Iq.Stanza iq = new Iq.Stanza.set(query_node); stream.get_module(Iq.Module.IDENTITY).send_iq(stream, iq); } } } dino-0.3.0/xmpp-vala/src/module/roster/versioning_module.vala0000644000000000000000000000544214202022370023032 0ustar rootrootusing Gee; namespace Xmpp.Roster { public class VersioningModule : XmppStreamModule { private const string NS_URI_FEATURE = "urn:xmpp:features:rosterver"; public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "roster_versioning"); private Storage storage; public VersioningModule(Storage storage) { this.storage = storage; } public override void attach(XmppStream stream) { stream.get_module(Module.IDENTITY).pre_get_roster.connect(on_pre_get_roster); stream.get_module(Module.IDENTITY).received_roster.connect(on_received_roster); stream.get_module(Module.IDENTITY).item_updated.connect(on_item_updated); stream.get_module(Module.IDENTITY).item_removed.connect(on_item_removed); } public override void detach(XmppStream stream) { stream.get_module(Module.IDENTITY).pre_get_roster.disconnect(on_pre_get_roster); } internal override string get_ns() { return NS_URI; } internal override string get_id() { return IDENTITY.id; } private void on_pre_get_roster(XmppStream stream, Iq.Stanza iq) { StanzaNode? ver_feature = stream.features.get_subnode("ver", NS_URI_FEATURE); if (ver_feature != null) { iq.stanza.get_subnode("query", NS_URI).set_attribute("ver", storage.get_roster_version() ?? ""); } } private void on_received_roster(XmppStream stream, Collection roster, Iq.Stanza iq) { string? ver = iq.stanza.get_deep_attribute(NS_URI + ":query", NS_URI + ":ver"); if (ver != null) storage.set_roster_version(ver); if (iq.stanza.get_subnode("query", NS_URI) != null) { storage.set_roster(roster); } else { Flag flag = stream.get_flag(Flag.IDENTITY); foreach (Item item in storage.get_roster()) { flag.roster_items[item.jid] = item; } } } private void on_item_updated(XmppStream stream, Item item, Iq.Stanza iq) { string? ver = iq.stanza.get_deep_attribute(NS_URI + ":query", NS_URI + ":ver"); if (ver != null) storage.set_roster_version(ver); storage.set_item(item); } private void on_item_removed(XmppStream stream, Item item, Iq.Stanza iq) { string? ver = iq.stanza.get_deep_attribute(NS_URI + ":query", NS_URI + ":ver"); if (ver != null) storage.set_roster_version(ver); storage.remove_item(item); } } public interface Storage : Object { public abstract string? get_roster_version(); public abstract Collection get_roster(); public abstract void set_roster_version(string version); public abstract void set_roster(Collection items); public abstract void set_item(Roster.Item item); public abstract void remove_item(Roster.Item item); } } dino-0.3.0/xmpp-vala/src/module/sasl.vala0000644000000000000000000002564114202022370016731 0ustar rootrootnamespace Xmpp.Sasl { private const string NS_URI = "urn:ietf:params:xml:ns:xmpp-sasl"; public class Flag : XmppStreamFlag { public static FlagIdentity IDENTITY = new FlagIdentity(NS_URI, "sasl"); public string mechanism; public string name; public string password; public string client_nonce; public uint8[] server_signature; public bool finished = false; public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } } namespace Mechanism { public const string PLAIN = "PLAIN"; public const string SCRAM_SHA_1 = "SCRAM-SHA-1"; public const string SCRAM_SHA_1_PLUS = "SCRAM-SHA-1-PLUS"; } public class Module : XmppStreamNegotiationModule { public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "sasl"); public string name { get; set; } public string password { get; set; } public bool use_full_name = false; public signal void received_auth_failure(XmppStream stream, StanzaNode node); public Module(string name, string password) { this.name = name; this.password = password; } public override void attach(XmppStream stream) { stream.received_features_node.connect(this.received_features_node); stream.received_nonza.connect(this.received_nonza); } public override void detach(XmppStream stream) { stream.received_features_node.disconnect(this.received_features_node); stream.received_nonza.disconnect(this.received_nonza); } private static size_t SHA1_SIZE = 20; private static uint8[] sha1(uint8[] data) { Checksum checksum = new Checksum(ChecksumType.SHA1); checksum.update(data, data.length); uint8[] res = new uint8[SHA1_SIZE]; checksum.get_digest(res, ref SHA1_SIZE); return res; } private static uint8[] hmac_sha1(uint8[] key, uint8[] data) { Hmac hmac = new Hmac(ChecksumType.SHA1, key); hmac.update(data); uint8[] res = new uint8[SHA1_SIZE]; hmac.get_digest(res, ref SHA1_SIZE); return res; } private static uint8[] pbkdf2_sha1(string password, uint8[] salt, uint iterations) { uint8[] res = new uint8[SHA1_SIZE]; uint8[] last = new uint8[salt.length + 4]; for(int i = 0; i < salt.length; i++) { last[i] = salt[i]; } last[salt.length + 3] = 1; for(int i = 0; i < iterations; i++) { last = hmac_sha1((uint8[]) password.to_utf8(), last); xor_inplace(res, last); } return res; } private static void xor_inplace(uint8[] mix, uint8[] a2) { for(int i = 0; i < mix.length; i++) { mix[i] = mix[i] ^ a2[i]; } } private static uint8[] xor(uint8[] a1, uint8[] a2) { uint8[] mix = new uint8[a1.length]; for(int i = 0; i < a1.length; i++) { mix[i] = a1[i] ^ a2[i]; } return mix; } public void received_nonza(XmppStream stream, StanzaNode node) { if (node.ns_uri == NS_URI) { if (node.name == "success") { Flag flag = stream.get_flag(Flag.IDENTITY); if (flag.mechanism == Mechanism.SCRAM_SHA_1) { string confirm = (string) Base64.decode(node.get_string_content()); uint8[] server_signature = null; foreach(string c in confirm.split(",")) { string[] split = c.split("=", 2); if (split.length != 2) continue; switch(split[0]) { case "v": server_signature = Base64.decode(split[1]); break; } } if (server_signature == null) return; if (server_signature.length != flag.server_signature.length) return; for(int i = 0; i < server_signature.length; i++) { if (server_signature[i] != flag.server_signature[i]) return; } } stream.require_setup(); flag.password = null; // Remove password from memory flag.finished = true; } else if (node.name == "failure") { stream.remove_flag(stream.get_flag(Flag.IDENTITY)); received_auth_failure(stream, node); } else if (node.name == "challenge" && stream.has_flag(Flag.IDENTITY)) { Flag flag = stream.get_flag(Flag.IDENTITY); if (flag.mechanism == Mechanism.SCRAM_SHA_1) { string challenge = (string) Base64.decode(node.get_string_content()); string? server_nonce = null; uint8[] salt = null; uint iterations = 0; foreach(string c in challenge.split(",")) { string[] split = c.split("=", 2); if (split.length != 2) continue; switch(split[0]) { case "r": server_nonce = split[1]; break; case "s": salt = Base64.decode(split[1]); break; case "i": iterations = int.parse(split[1]); break; } } if (server_nonce == null || salt == null || iterations == 0) return; if (!server_nonce.has_prefix(flag.client_nonce)) return; string client_final_message_bare = @"c=biws,r=$server_nonce"; uint8[] salted_password = pbkdf2_sha1(flag.password, salt, iterations); uint8[] client_key = hmac_sha1(salted_password, (uint8[]) "Client Key".to_utf8()); uint8[] stored_key = sha1(client_key); string auth_message = @"n=$(flag.name),r=$(flag.client_nonce),$challenge,$client_final_message_bare"; uint8[] client_signature = hmac_sha1(stored_key, (uint8[]) auth_message.to_utf8()); uint8[] client_proof = xor(client_key, client_signature); uint8[] server_key = hmac_sha1(salted_password, (uint8[]) "Server Key".to_utf8()); flag.server_signature = hmac_sha1(server_key, (uint8[]) auth_message.to_utf8()); string client_final_message = @"$client_final_message_bare,p=$(Base64.encode(client_proof))"; stream.write(new StanzaNode.build("response", NS_URI).add_self_xmlns() .put_node(new StanzaNode.text(Base64.encode((uchar[]) (client_final_message).to_utf8())))); } } } } public void received_features_node(XmppStream stream) { if (stream.has_flag(Flag.IDENTITY)) return; if (stream.is_setup_needed()) return; var mechanisms = stream.features.get_subnode("mechanisms", NS_URI); string[] supported_mechanisms = {}; foreach (var mechanism in mechanisms.sub_nodes) { if (mechanism.name != "mechanism" || mechanism.ns_uri != NS_URI) continue; supported_mechanisms += mechanism.get_string_content(); } if (!name.contains("@")) { name = "%s@%s".printf(name, stream.remote_name.to_string()); } if (!use_full_name && name.contains("@")) { var split = name.split("@"); if (split[1] == stream.remote_name.to_string()) { name = split[0]; } else { use_full_name = true; } } string name = this.name; if (!use_full_name && name.contains("@")) { var split = name.split("@"); if (split[1] == stream.remote_name.to_string()) { name = split[0]; } } if (Mechanism.SCRAM_SHA_1 in supported_mechanisms) { string normalized_password = password.normalize(-1, NormalizeMode.NFKC); string client_nonce = Random.next_int().to_string("%.8x") + Random.next_int().to_string("%.8x") + Random.next_int().to_string("%.8x"); string initial_message = @"n=$name,r=$client_nonce"; stream.write(new StanzaNode.build("auth", NS_URI).add_self_xmlns() .put_attribute("mechanism", Mechanism.SCRAM_SHA_1) .put_node(new StanzaNode.text(Base64.encode((uchar[]) ("n,,"+initial_message).to_utf8())))); var flag = new Flag(); flag.mechanism = Mechanism.SCRAM_SHA_1; flag.name = name; flag.password = normalized_password; flag.client_nonce = client_nonce; stream.add_flag(flag); } else if (Mechanism.PLAIN in supported_mechanisms) { stream.write(new StanzaNode.build("auth", NS_URI).add_self_xmlns() .put_attribute("mechanism", Mechanism.PLAIN) .put_node(new StanzaNode.text(Base64.encode(get_plain_bytes(name, password))))); var flag = new Flag(); flag.mechanism = Mechanism.PLAIN; flag.name = name; stream.add_flag(flag); } else { stderr.printf("No supported mechanism provided by server at %s\n", stream.remote_name.to_string()); return; } } private static uchar[] get_plain_bytes(string name_s, string password_s) { var name = name_s.to_utf8(); var password = password_s.to_utf8(); uchar[] res = new uchar[name.length + password.length + 2]; res[0] = 0; res[name.length + 1] = 0; for(int i = 0; i < name.length; i++) { res[i + 1] = (uchar) name[i]; } for(int i = 0; i < password.length; i++) { res[i + name.length + 2] = (uchar) password[i]; } return res; } public override bool mandatory_outstanding(XmppStream stream) { return !stream.has_flag(Flag.IDENTITY) || !stream.get_flag(Flag.IDENTITY).finished; } public override bool negotiation_active(XmppStream stream) { return stream.has_flag(Flag.IDENTITY) && !stream.get_flag(Flag.IDENTITY).finished; } public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } } } dino-0.3.0/xmpp-vala/src/module/session.vala0000644000000000000000000000354514202022370017451 0ustar rootroot/* Legacy. RFC 3921 3*/ namespace Xmpp.Session { private const string NS_URI = "urn:ietf:params:xml:ns:xmpp-session"; public class Module : XmppStreamNegotiationModule { public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "session"); public override void attach(XmppStream stream) { stream.get_module(Bind.Module.IDENTITY).bound_to_resource.connect(on_bound_resource); } public override void detach(XmppStream stream) { stream.get_module(Bind.Module.IDENTITY).bound_to_resource.disconnect(on_bound_resource); } public override bool mandatory_outstanding(XmppStream stream) { return false; } public override bool negotiation_active(XmppStream stream) { return stream.has_flag(Flag.IDENTITY) && !stream.get_flag(Flag.IDENTITY).finished; } public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } private async void on_bound_resource(XmppStream stream, Jid my_jid) { StanzaNode? session_node = stream.features.get_subnode("session", NS_URI); if (session_node != null && session_node.get_subnode("optional", NS_URI) == null) { stream.add_flag(new Flag()); Iq.Stanza iq = new Iq.Stanza.set(new StanzaNode.build("session", NS_URI).add_self_xmlns()) { to=stream.remote_name }; Iq.Stanza result_iq = yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq); if (!result_iq.is_error()) { stream.get_flag(Flag.IDENTITY).finished = true; } } } } public class Flag : XmppStreamFlag { public static FlagIdentity IDENTITY = new FlagIdentity(NS_URI, "session"); public bool finished = false; public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } } } dino-0.3.0/xmpp-vala/src/module/stanza.vala0000644000000000000000000000477714202022370017276 0ustar rootrootnamespace Xmpp { public class Stanza : Object { public const string ATTRIBUTE_FROM = "from"; public const string ATTRIBUTE_ID = "id"; public const string ATTRIBUTE_TO = "to"; public const string ATTRIBUTE_TYPE = "type"; public const string TYPE_ERROR = "error"; private Jid? my_jid; private Jid? from_; private Jid? to_; public virtual Jid? from { owned get { string? from_attribute = stanza.get_attribute(ATTRIBUTE_FROM); // "when a client receives a stanza that does not include a 'from' attribute, it MUST assume that the stanza // is from the user's account on the server." (RFC6120 8.1.2.1) if (from_attribute != null) { try { return from_ = new Jid(from_attribute); } catch (InvalidJidError e) { warning("Ignoring invalid from Jid: %s", e.message); } } if (my_jid != null) { return my_jid.bare_jid; } return null; } set { stanza.set_attribute(ATTRIBUTE_FROM, value.to_string()); } } public virtual string? id { get { return stanza.get_attribute(ATTRIBUTE_ID); } set { stanza.set_attribute(ATTRIBUTE_ID, value); } } public virtual Jid? to { owned get { string? to_attribute = stanza.get_attribute(ATTRIBUTE_TO); // "if the stanza does not include a 'to' address then the client MUST treat it as if the 'to' address were // included with a value of the client's full JID." (RFC6120 8.1.1.1) try { return to_attribute == null ? my_jid : to_ = new Jid(to_attribute); } catch (InvalidJidError e) { warning("Ignoring invalid to Jid: %s", e.message); } return my_jid; } set { stanza.set_attribute(ATTRIBUTE_TO, value.to_string()); } } public virtual string? type_ { get { return stanza.get_attribute(ATTRIBUTE_TYPE); } set { stanza.set_attribute(ATTRIBUTE_TYPE, value); } } public StanzaNode stanza; public Stanza.incoming(StanzaNode stanza, Jid? my_jid) { this.stanza = stanza; this.my_jid = my_jid; } public Stanza.outgoing(StanzaNode stanza) { this.stanza = stanza; } public bool is_error() { return type_ == TYPE_ERROR; } public ErrorStanza? get_error() { return ErrorStanza.from_stanza(this.stanza); } } }dino-0.3.0/xmpp-vala/src/module/stanza_error.vala0000644000000000000000000001203214202022370020466 0ustar rootrootusing Gee; namespace Xmpp { private const string ERROR_NS_URI = "urn:ietf:params:xml:ns:xmpp-stanzas"; public class ErrorStanza { public const string CONDITION_BAD_REQUEST = "bad-request"; public const string CONDITION_CONFLICT = "conflict"; public const string CONDITION_FEATURE_NOT_IMPLEMENTED = "feature-not-implemented"; public const string CONDITION_FORBIDDEN = "forbidden"; public const string CONDITION_GONE = "gone"; public const string CONDITION_INTERNAL_SERVER_ERROR = "internal-server-error"; public const string CONDITION_ITEM_NOT_FOUND = "item-not-found"; public const string CONDITION_JID_MALFORMED = "jid-malformed"; public const string CONDITION_NOT_ACCEPTABLE = "not-acceptable"; public const string CONDITION_NOT_ALLOWED = "not-allowed"; public const string CONDITION_NOT_AUTHORIZED = "not-authorized"; public const string CONDITION_POLICY_VIOLATION = "policy-violation"; public const string CONDITION_RECIPIENT_UNAVAILABLE = "recipient-unavailable"; public const string CONDITION_REDIRECT = "redirect"; public const string CONDITION_REGISTRATION_REQUIRED = "registration-required"; public const string CONDITION_REMOTE_SERVER_NOT_FOUND = "remote-server-not-found"; public const string CONDITION_REMOTE_SERVER_TIMEOUT = "remote-server-timeout"; public const string CONDITION_RESOURCE_CONSTRAINT = "resource-constraint"; public const string CONDITION_SERVICE_UNAVAILABLE = "service-unavailable"; public const string CONDITION_SUBSCRIPTION_REQUIRED = "subscription-required"; public const string CONDITION_UNDEFINED_CONDITION = "undefined-condition"; public const string CONDITION_UNEXPECTED_REQUEST = "unexpected-request"; public const string TYPE_AUTH = "auth"; public const string TYPE_CANCEL = "cancel"; public const string TYPE_CONTINUE = "continue"; public const string TYPE_MODIFY = "modify"; public const string TYPE_WAIT = "wait"; public string? by { get { return error_node.get_attribute("by"); } } public string? text { get { return error_node.get_deep_string_content(ERROR_NS_URI + ":text"); } } public string condition { get { Gee.List subnodes = error_node.sub_nodes; foreach (StanzaNode subnode in subnodes) { // TODO get subnode by ns if (subnode.ns_uri == ERROR_NS_URI) { return subnode.name; } } return CONDITION_UNDEFINED_CONDITION; // TODO hm! } } public string type_ { get { return error_node.get_attribute("type"); } } public StanzaNode error_node; public static ErrorStanza? from_stanza(StanzaNode stanza) { var error_stanza = new ErrorStanza(); error_stanza.error_node = stanza.get_subnode("error"); if (error_stanza.error_node == null) return null; return error_stanza; } public ErrorStanza.build(string type, string condition, string? human_readable, StanzaNode? application_condition) { error_node = new StanzaNode.build("error") .put_attribute("type", type) .put_node(new StanzaNode.build(condition, ERROR_NS_URI).add_self_xmlns()); if (application_condition != null) { error_node.put_node(application_condition); } if (human_readable != null) { error_node.put_node(new StanzaNode.build("text", ERROR_NS_URI) .add_self_xmlns() .put_attribute("xml:lang", "en") .put_node(new StanzaNode.text(human_readable)) ); } } public ErrorStanza.bad_request(string? human_readable = null) { this.build(TYPE_MODIFY, CONDITION_BAD_REQUEST, human_readable, null); } public ErrorStanza.feature_not_implemented(string? human_readable = null) { this.build(TYPE_MODIFY, CONDITION_FEATURE_NOT_IMPLEMENTED, human_readable, null); } public ErrorStanza.item_not_found(StanzaNode? application_condition = null) { this.build(TYPE_CANCEL, CONDITION_ITEM_NOT_FOUND, null, application_condition); } public ErrorStanza.not_acceptable(string? human_readable = null) { this.build(TYPE_MODIFY, CONDITION_NOT_ACCEPTABLE, human_readable, null); } public ErrorStanza.not_allowed(string? human_readable = null) { this.build(TYPE_CANCEL, CONDITION_NOT_ALLOWED, human_readable, null); } public ErrorStanza.resource_constraint(string? human_readable = null) { this.build(TYPE_WAIT, CONDITION_RESOURCE_CONSTRAINT, human_readable, null); } public ErrorStanza.service_unavailable() { this.build(TYPE_CANCEL, CONDITION_SERVICE_UNAVAILABLE, null, null); } } } dino-0.3.0/xmpp-vala/src/module/stream_error.vala0000644000000000000000000001013214202022370020460 0ustar rootrootusing Gee; namespace Xmpp.StreamError { private const string NS_URI = "jabber:client"; private const string NS_ERROR = "urn:ietf:params:xml:ns:xmpp-streams"; public class Module : XmppStreamModule { public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "stream_error_module"); public override void attach(XmppStream stream) { stream.received_nonza.connect(on_received_nonstanza); } public override void detach(XmppStream stream) { stream.received_nonza.disconnect(on_received_nonstanza); } public static void require(XmppStream stream) { if (stream.get_module(IDENTITY) == null) stream.add_module(new Module()); } public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } private void on_received_nonstanza(XmppStream stream, StanzaNode node) { if (node.name == "error" && node.ns_uri == "http://etherx.jabber.org/streams") { stream.add_flag(generate_error_flag(node)); } } private Flag generate_error_flag(StanzaNode node) { string? subnode_name = null; Gee.List subnodes = node.sub_nodes; foreach (StanzaNode subnode in subnodes) { // TODO get subnode by ns if (subnode.ns_uri == "urn:ietf:params:xml:ns:xmpp-streams" && subnode.name != "text") { subnode_name = subnode.name; } } Flag flag = new StreamError.Flag(); flag.error_type = subnode_name; switch (subnode_name) { case "bad-format": case "conflict": case "connection-timeout": case "bad-namespace-prefix": flag.reconnection_recomendation = StreamError.Flag.Reconnect.NOW; break; case "host-gone": case "host-unknown": flag.reconnection_recomendation = StreamError.Flag.Reconnect.LATER; break; case "improper-addressing": case "internal-server-error": case "invalid-from": case "invalid-namespace": case "invalid-xml": case "not-authorized": case "not-well-formed": case "policy-violation": case "remote-connection-failed": case "reset": flag.reconnection_recomendation = StreamError.Flag.Reconnect.NOW; break; case "resource-constraint": flag.reconnection_recomendation = StreamError.Flag.Reconnect.LATER; break; case "restricted-xml": flag.reconnection_recomendation = StreamError.Flag.Reconnect.NOW; break; case "see-other-host": case "system-shutdown": flag.reconnection_recomendation = StreamError.Flag.Reconnect.LATER; break; case "undefined-condition": case "unsupported-encoding": case "unsupported-feature": case "unsupported-stanza-type": case "unsupported-version": flag.reconnection_recomendation = StreamError.Flag.Reconnect.NOW; break; } if (subnode_name == "conflict") flag.resource_rejected = true; return flag; } } public class Flag : XmppStreamFlag { public static FlagIdentity IDENTITY = new FlagIdentity(NS_URI, "stream_error"); public enum Reconnect { UNKNOWN, NOW, LATER, NEVER } public string? error_type; public Reconnect reconnection_recomendation = Reconnect.UNKNOWN; public bool resource_rejected = false; public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } } } dino-0.3.0/xmpp-vala/src/module/tls.vala0000644000000000000000000001051614202022370016564 0ustar rootrootnamespace Xmpp.Tls { private const string NS_URI = "urn:ietf:params:xml:ns:xmpp-tls"; public class Module : XmppStreamNegotiationModule { public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "tls_module"); public signal void invalid_certificate(TlsCertificate peer_cert, TlsCertificateFlags errors); public bool require { get; set; default = true; } public bool server_supports_tls = false; public bool server_requires_tls = false; public SocketConnectable? identity = null; public override void attach(XmppStream stream) { stream.received_features_node.connect(this.received_features_node); stream.received_nonza.connect(this.received_nonza); } public override void detach(XmppStream stream) { stream.received_features_node.disconnect(this.received_features_node); stream.received_nonza.disconnect(this.received_nonza); } private void received_nonza(XmppStream stream, StanzaNode node) { if (node.ns_uri == NS_URI && node.name == "proceed") { try { StartTlsXmppStream? tls_xmpp_stream = stream as StartTlsXmppStream; var io_stream = tls_xmpp_stream.get_stream(); if (io_stream == null) return; var conn = TlsClientConnection.new(io_stream, identity); tls_xmpp_stream.reset_stream(conn); conn.accept_certificate.connect(on_invalid_certificate); var flag = stream.get_flag(Flag.IDENTITY); flag.peer_certificate = conn.get_peer_certificate(); flag.finished = true; } catch (Error e) { stderr.printf("Failed to start TLS: %s\n", e.message); } } } private void received_features_node(XmppStream stream) { if (stream.has_flag(Flag.IDENTITY)) return; if (stream.is_setup_needed()) return; var starttls = stream.features.get_subnode("starttls", NS_URI); if (starttls != null) { server_supports_tls = true; if (starttls.get_subnode("required") != null || stream.features.get_all_subnodes().size == 1) { server_requires_tls = true; } if (server_requires_tls || require) { stream.write(new StanzaNode.build("starttls", NS_URI).add_self_xmlns()); } if (identity == null) { identity = new NetworkService("xmpp-client", "tcp", stream.remote_name.to_string()); } stream.add_flag(new Flag()); } } public bool on_invalid_certificate(TlsCertificate peer_cert, TlsCertificateFlags errors) { string error_str = ""; foreach (var f in new TlsCertificateFlags[]{TlsCertificateFlags.UNKNOWN_CA, TlsCertificateFlags.BAD_IDENTITY, TlsCertificateFlags.NOT_ACTIVATED, TlsCertificateFlags.EXPIRED, TlsCertificateFlags.REVOKED, TlsCertificateFlags.INSECURE, TlsCertificateFlags.GENERIC_ERROR, TlsCertificateFlags.VALIDATE_ALL}) { if (f in errors) { error_str += @"$(f), "; } } warning(@"Tls Certificate Errors: $(error_str)"); invalid_certificate(peer_cert, errors); return false; } public override bool mandatory_outstanding(XmppStream stream) { return require && (!stream.has_flag(Flag.IDENTITY) || !stream.get_flag(Flag.IDENTITY).finished); } public override bool negotiation_active(XmppStream stream) { return stream.has_flag(Flag.IDENTITY) && !stream.get_flag(Flag.IDENTITY).finished; } public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } } public class Flag : XmppStreamFlag { public static FlagIdentity IDENTITY = new FlagIdentity(NS_URI, "tls"); public TlsCertificate? peer_certificate; public bool finished { get; set; default=false; } public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } } } dino-0.3.0/xmpp-vala/src/module/util.vala0000644000000000000000000000523714202022370016743 0ustar rootrootusing Gee; namespace Xmpp { public string random_uuid() { uint32 b1 = Random.next_int(); uint16 b2 = (uint16)Random.next_int(); uint16 b3 = (uint16)(Random.next_int() | 0x4000u) & ~0xb000u; uint16 b4 = (uint16)(Random.next_int() | 0x8000u) & ~0x4000u; uint16 b5_1 = (uint16)Random.next_int(); uint32 b5_2 = Random.next_int(); return "%08x-%04x-%04x-%04x-%04x%08x".printf(b1, b2, b3, b4, b5_1, b5_2); } public abstract class StanzaListener : OrderedListener { public abstract async bool run(XmppStream stream, T stanza); } public class StanzaListenerHolder : ListenerHolder { public async bool run(XmppStream stream, T stanza) { // listeners can change e.g. when switching to another stream ArrayList listeners_copy = new ArrayList(); listeners_copy.add_all(listeners); foreach (OrderedListener ol in listeners_copy) { StanzaListener l = ol as StanzaListener; bool stop = yield l.run(stream, stanza); if (stop) return true; } return false; } } public abstract class OrderedListener : Object { public abstract string action_group { get; } public abstract string[] after_actions { get; } } public abstract class ListenerHolder : Object { protected ArrayList listeners = new ArrayList(); public new void connect(OrderedListener listener) { listeners.add(listener); resort_list(); } public new void disconnect(OrderedListener listener) { listeners.remove(listener); resort_list(); } private bool set_contains_action(Gee.List s, string[] actions) { foreach(OrderedListener l in s) { if (l.action_group in actions) { return true; } } return false; } private void resort_list() { ArrayList new_list = new ArrayList(); ArrayList remaining = new ArrayList(); remaining.add_all(listeners); while (remaining.size > 0) { bool changed = false; Gee.Iterator iter = remaining.iterator(); while (iter.has_next()) { iter.next(); OrderedListener l = iter.get(); if (!set_contains_action(remaining, l.after_actions)) { new_list.add(l); iter.remove(); changed = true; } } if (!changed) error("Can't sort listeners"); } listeners = new_list; } } } dino-0.3.0/xmpp-vala/src/module/xep/0000755000000000000000000000000014202022370015706 5ustar rootrootdino-0.3.0/xmpp-vala/src/module/xep/0004_data_forms.vala0000644000000000000000000001734314202022370021345 0ustar rootrootusing Gee; namespace Xmpp.Xep.DataForms { public const string NS_URI = "jabber:x:data"; public class DataForm { public StanzaNode stanza_node { get; set; } public Gee.List fields = new ArrayList(); public string? form_type = null; public string? instructions = null; public string? title = null; public StanzaNode get_submit_node() { stanza_node.set_attribute("type", "submit"); return stanza_node; } public enum Type { BOOLEAN, FIXED, HIDDEN, JID_MULTI, LIST_SINGLE, LIST_MULTI, TEXT_PRIVATE, TEXT_SINGLE, } public class Option { public string label { get; set; } public string value { get; set; } public Option(string label, string value) { this.label = label; this.value = value; } } public class Field { public StanzaNode node { get; set; } public string? label { get { return node.get_attribute("label", NS_URI); } set { node.set_attribute("label", value); } } public virtual Type? type_ { get; internal set; default=null; } public string? var { get { return node.get_attribute("var", NS_URI); } set { node.set_attribute("var", value); } } public Field() { this.node = new StanzaNode.build("field", NS_URI); } public Field.from_node(StanzaNode node) { this.node = node; } internal Gee.List get_values() { Gee.List ret = new ArrayList(); Gee.List value_nodes = node.get_subnodes("value", NS_URI); foreach (StanzaNode node in value_nodes) { ret.add(node.get_string_content()); } return ret; } public string get_value_string() { Gee.List values = get_values(); return values.size > 0 ? values[0] : ""; } public void set_value_string(string val) { StanzaNode? value_node = node.get_subnode("value", NS_URI); if (value_node == null) { value_node = new StanzaNode.build("value", NS_URI); node.put_node(value_node); } value_node.sub_nodes.clear(); value_node.put_node(new StanzaNode.text(val)); } internal void add_value_string(string val) { StanzaNode node = new StanzaNode.build("value"); node.put_node(new StanzaNode.text(val)); } internal Gee.List