diff --git a/LICENSES/BSD-3-Clause.txt b/LICENSES/BSD-3-Clause.txt new file mode 100644 index 0000000..0741db7 --- /dev/null +++ b/LICENSES/BSD-3-Clause.txt @@ -0,0 +1,26 @@ +Copyright (c) . All rights reserved. + +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. + +3. Neither the name of the copyright holder nor the names of its contributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT HOLDER 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. diff --git a/LICENSES/LGPL-2.0-only.txt b/LICENSES/LGPL-2.0-only.txt new file mode 100644 index 0000000..5c96471 --- /dev/null +++ b/LICENSES/LGPL-2.0-only.txt @@ -0,0 +1,446 @@ +GNU LIBRARY GENERAL PUBLIC LICENSE + +Version 2, June 1991 Copyright (C) 1991 Free Software Foundation, Inc. + +51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is numbered 2 because +it goes with version 2 of the ordinary GPL.] + +Preamble + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public Licenses are intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. + +This license, the Library General Public License, applies to some specially +designated Free Software Foundation software, and to any other libraries whose +authors decide to use it. You can use it for your libraries, 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 this service 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 make restrictions that forbid anyone to +deny you these rights or to ask you to surrender the rights. These restrictions +translate to certain responsibilities for you if you distribute copies of +the library, or if you modify it. + +For example, if you distribute copies of the library, whether gratis or for +a fee, you must give the recipients all the rights that we gave you. You must +make sure that they, too, receive or can get the source code. If you link +a program with the library, you must provide complete object files to the +recipients so that they can relink them with the library, after making changes +to the library and recompiling it. And you must show them these terms so they +know their rights. + +Our method of protecting your rights has two steps: (1) copyright the library, +and (2) offer you this license which gives you legal permission to copy, distribute +and/or modify the library. + +Also, for each distributor's protection, we want to make certain that everyone +understands that there is no warranty for this free library. If the library +is modified by someone else and passed on, we want its recipients to know +that what they have is not the original version, so that any problems introduced +by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We +wish to avoid the danger that companies distributing free software will individually +obtain patent licenses, thus in effect transforming the program into proprietary +software. To prevent this, we have made it clear that any patent must be licensed +for everyone's free use or not licensed at all. + +Most GNU software, including some libraries, is covered by the ordinary GNU +General Public License, which was designed for utility programs. This license, +the GNU Library General Public License, applies to certain designated libraries. +This license is quite different from the ordinary one; be sure to read it +in full, and don't assume that anything in it is the same as in the ordinary +license. + +The reason we have a separate public license for some libraries is that they +blur the distinction we usually make between modifying or adding to a program +and simply using it. Linking a program with a library, without changing the +library, is in some sense simply using the library, and is analogous to running +a utility program or application program. However, in a textual and legal +sense, the linked executable is a combined work, a derivative of the original +library, and the ordinary General Public License treats it as such. + +Because of this blurred distinction, using the ordinary General Public License +for libraries did not effectively promote software sharing, because most developers +did not use the libraries. We concluded that weaker conditions might promote +sharing better. + +However, unrestricted linking of non-free programs would deprive the users +of those programs of all benefit from the free status of the libraries themselves. +This Library General Public License is intended to permit developers of non-free +programs to use free libraries, while preserving your freedom as a user of +such programs to change the free libraries that are incorporated in them. +(We have not seen how to achieve this as regards changes in header files, +but we have achieved it as regards changes in the actual functions of the +Library.) The hope is that this will lead to faster development of free libraries. + +The precise terms and conditions for copying, distribution and modification +follow. Pay close attention to the difference between a "work based on the +library" and a "work that uses the library". The former contains code derived +from the library, while the latter only works together with the library. + +Note that it is possible for a library to be covered by the ordinary General +Public License rather than by this special one. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License Agreement applies to any software library which contains a +notice placed by the copyright holder or other authorized party saying it +may be distributed under the terms of this Library General Public License +(also called "this License"). Each licensee is addressed as "you". + +A "library" means a collection of software functions and/or data prepared +so as to be conveniently linked with application programs (which use some +of those functions and data) to form executables. + +The "Library", below, refers to any such software library or work which has +been distributed under these terms. A "work based on the Library" means either +the Library or any derivative work under copyright law: that is to say, a +work containing the Library or a portion of it, either verbatim or with modifications +and/or translated straightforwardly into another language. (Hereinafter, translation +is included without limitation in the term "modification".) + +"Source code" for a work means the preferred form of the work for making modifications +to it. For a library, complete source code means all the source code for all +modules it contains, plus any associated interface definition files, plus +the scripts used to control compilation and installation of the library. + +Activities other than copying, distribution and modification are not covered +by this License; they are outside its scope. The act of running a program +using the Library is not restricted, and output from such a program is covered +only if its contents constitute a work based on the Library (independent of +the use of the Library in a tool for writing it). Whether that is true depends +on what the Library does and what the program that uses the Library does. + +1. You may copy and distribute verbatim copies of the Library's complete source +code as you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and disclaimer +of warranty; keep intact all the notices that refer to this License and to +the absence of any warranty; and distribute a copy of this License along with +the Library. + +You may charge a fee for the physical act of transferring a copy, and you +may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Library or any portion of it, +thus forming a work based on the Library, and copy and distribute such modifications +or work under the terms of Section 1 above, provided that you also meet all +of these conditions: + + a) The modified work must itself be a software library. + +b) You must cause the files modified to carry prominent notices stating that +you changed the files and the date of any change. + +c) You must cause the whole of the work to be licensed at no charge to all +third parties under the terms of this License. + +d) If a facility in the modified Library refers to a function or a table of +data to be supplied by an application program that uses the facility, other +than as an argument passed when the facility is invoked, then you must make +a good faith effort to ensure that, in the event an application does not supply +such function or table, the facility still operates, and performs whatever +part of its purpose remains meaningful. + +(For example, a function in a library to compute square roots has a purpose +that is entirely well-defined independent of the application. Therefore, Subsection +2d requires that any application-supplied function or table used by this function +must be optional: if the application does not supply it, the square root function +must still compute square roots.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Library, and can be reasonably +considered independent and separate works in themselves, then this License, +and its terms, do not apply to those sections when you distribute them as +separate works. But when you distribute the same sections as part of a whole +which is a work based on the Library, the distribution of the whole must be +on the terms of this License, whose permissions for other licensees extend +to the entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest your +rights to work written entirely by you; rather, the intent is to exercise +the right to control the distribution of derivative or collective works based +on the Library. + +In addition, mere aggregation of another work not based on the Library with +the Library (or with a work based on the Library) on a volume of a storage +or distribution medium does not bring the other work under the scope of this +License. + +3. You may opt to apply the terms of the ordinary GNU General Public License +instead of this License to a given copy of the Library. To do this, you must +alter all the notices that refer to this License, so that they refer to the +ordinary GNU General Public License, version 2, instead of to this License. +(If a newer version than version 2 of the ordinary GNU General Public License +has appeared, then you can specify that version instead if you wish.) Do not +make any other change in these notices. + +Once this change is made in a given copy, it is irreversible for that copy, +so the ordinary GNU General Public License applies to all subsequent copies +and derivative works made from that copy. + +This option is useful when you wish to copy part of the code of the Library +into a program that is not a library. + +4. You may copy and distribute the Library (or a portion or derivative of +it, under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you accompany it with the complete corresponding +machine-readable source code, which must be distributed under the terms of +Sections 1 and 2 above on a medium customarily used for software interchange. + +If distribution of object code is made by offering access to copy from a designated +place, then offering equivalent access to copy the source code from the same +place satisfies the requirement to distribute the source code, even though +third parties are not compelled to copy the source along with the object code. + +5. A program that contains no derivative of any portion of the Library, but +is designed to work with the Library by being compiled or linked with it, +is called a "work that uses the Library". Such a work, in isolation, is not +a derivative work of the Library, and therefore falls outside the scope of +this License. + +However, linking a "work that uses the Library" with the Library creates an +executable that is a derivative of the Library (because it contains portions +of the Library), rather than a "work that uses the library". The executable +is therefore covered by this License. Section 6 states terms for distribution +of such executables. + +When a "work that uses the Library" uses material from a header file that +is part of the Library, the object code for the work may be a derivative work +of the Library even though the source code is not. Whether this is true is +especially significant if the work can be linked without the Library, or if +the work is itself a library. The threshold for this to be true is not precisely +defined by law. + +If such an object file uses only numerical parameters, data structure layouts +and accessors, and small macros and small inline functions (ten lines or less +in length), then the use of the object file is unrestricted, regardless of +whether it is legally a derivative work. (Executables containing this object +code plus portions of the Library will still fall under Section 6.) + +Otherwise, if the work is a derivative of the Library, you may distribute +the object code for the work under the terms of Section 6. Any executables +containing that work also fall under Section 6, whether or not they are linked +directly with the Library itself. + +6. As an exception to the Sections above, you may also compile or link a "work +that uses the Library" with the Library to produce a work containing portions +of the Library, and distribute that work under terms of your choice, provided +that the terms permit modification of the work for the customer's own use +and reverse engineering for debugging such modifications. + +You must give prominent notice with each copy of the work that the Library +is used in it and that the Library and its use are covered by this License. +You must supply a copy of this License. If the work during execution displays +copyright notices, you must include the copyright notice for the Library among +them, as well as a reference directing the user to the copy of this License. +Also, you must do one of these things: + +a) Accompany the work with the complete corresponding machine-readable source +code for the Library including whatever changes were used in the work (which +must be distributed under Sections 1 and 2 above); and, if the work is an +executable linked with the Library, with the complete machine-readable "work +that uses the Library", as object code and/or source code, so that the user +can modify the Library and then relink to produce a modified executable containing +the modified Library. (It is understood that the user who changes the contents +of definitions files in the Library will not necessarily be able to recompile +the application to use the modified definitions.) + +b) Accompany the work with a written offer, valid for at least three years, +to give the same user the materials specified in Subsection 6a, above, for +a charge no more than the cost of performing this distribution. + +c) If distribution of the work is made by offering access to copy from a designated +place, offer equivalent access to copy the above specified materials from +the same place. + +d) Verify that the user has already received a copy of these materials or +that you have already sent this user a copy. + +For an executable, the required form of the "work that uses the Library" must +include any data and utility programs needed for reproducing the executable +from it. However, as a special exception, the source code distributed need +not include anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the operating +system on which the executable runs, unless that component itself accompanies +the executable. + +It may happen that this requirement contradicts the license restrictions of +other proprietary libraries that do not normally accompany the operating system. +Such a contradiction means you cannot use both them and the Library together +in an executable that you distribute. + +7. You may place library facilities that are a work based on the Library side-by-side +in a single library together with other library facilities not covered by +this License, and distribute such a combined library, provided that the separate +distribution of the work based on the Library and of the other library facilities +is otherwise permitted, and provided that you do these two things: + +a) Accompany the combined library with a copy of the same work based on the +Library, uncombined with any other library facilities. This must be distributed +under the terms of the Sections above. + +b) Give prominent notice with the combined library of the fact that part of +it is a work based on the Library, and explaining where to find the accompanying +uncombined form of the same work. + +8. You may not copy, modify, sublicense, link with, or distribute the Library +except as expressly provided under this License. Any attempt otherwise to +copy, modify, sublicense, link with, or distribute the Library is void, and +will automatically terminate your rights under this License. However, parties +who have received copies, or rights, from you under this License will not +have their licenses terminated so long as such parties remain in full compliance. + +9. You are not required to accept this License, since you have not signed +it. However, nothing else grants you permission to modify or distribute the +Library or its derivative works. These actions are prohibited by law if you +do not accept this License. Therefore, by modifying or distributing the Library +(or any work based on the Library), you indicate your acceptance of this License +to do so, and all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + +10. Each time you redistribute the Library (or any work based on the Library), +the recipient automatically receives a license from the original licensor +to copy, distribute, link with or modify the Library subject to these terms +and conditions. You may not impose any further restrictions on the recipients' +exercise of the rights granted herein. You are not responsible for enforcing +compliance by third parties to this License. + +11. If, as a consequence of a court judgment or allegation of patent infringement +or for any other reason (not limited to patent issues), 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 distribute so as to satisfy simultaneously your +obligations under this License and any other pertinent obligations, then as +a consequence you may not distribute the Library at all. For example, if a +patent license would not permit royalty-free redistribution of the Library +by all those who receive copies directly or indirectly through you, then the +only way you could satisfy both it and this License would be to refrain entirely +from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents +or other property right claims or to contest validity of any such claims; +this section has the sole purpose of protecting the integrity of the free +software distribution system which is implemented by public license practices. +Many people have made generous contributions to the wide range of software +distributed through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing to +distribute software through any other system and a licensee cannot impose +that choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +12. If the distribution and/or use of the Library is restricted in certain +countries either by patents or by copyrighted interfaces, the original copyright +holder who places the Library under this License may add an explicit geographical +distribution limitation excluding those countries, so that distribution is +permitted only in or among countries not thus excluded. In such case, this +License incorporates the limitation as if written in the body of this License. + +13. The Free Software Foundation may publish revised and/or new versions of +the Library 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 Library specifies +a version number of this License which applies to it and "any later version", +you have the option of following the terms and conditions either of that version +or of any later version published by the Free Software Foundation. If the +Library does not specify a license version number, you may choose any version +ever published by the Free Software Foundation. + +14. If you wish to incorporate parts of the Library into other free programs +whose distribution conditions are incompatible with these, write to the author +to ask for permission. For software which is copyrighted by the Free Software +Foundation, write to the Free Software Foundation; we sometimes make exceptions +for this. Our decision will be guided by the two goals of preserving the free +status of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + +15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR +THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE +STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY +"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 LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE +THE LIBRARY 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 LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH +HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Libraries + +If you develop a new library, and you want it to be of the greatest possible +use to the public, we recommend making it free software that everyone can +redistribute and change. You can do so by permitting redistribution under +these terms (or, alternatively, under the terms of the ordinary General Public +License). + +To apply these terms, attach the following notices to the library. It is safest +to attach them to the start of each source file to most effectively convey +the exclusion of warranty; and each file should have at least the "copyright" +line and a pointer to where the full notice is found. + +one line to give the library's name and an idea of what it does. + +Copyright (C) year name of author + +This library is free software; you can redistribute it and/or modify it under +the terms of the GNU Library 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 Library General Public License for more +details. + +You should have received a copy of the GNU Library General Public License +along with this library; if not, write to the Free Software Foundation, Inc., +51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your school, +if any, to sign a "copyright disclaimer" for the library, if necessary. Here +is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in + +the library `Frob' (a library for tweaking knobs) written + +by James Random Hacker. + +signature of Ty Coon, 1 April 1990 + +Ty Coon, President of Vice + +That's all there is to it! diff --git a/LICENSES/LGPL-2.0-or-later.txt b/LICENSES/LGPL-2.0-or-later.txt new file mode 100644 index 0000000..5c96471 --- /dev/null +++ b/LICENSES/LGPL-2.0-or-later.txt @@ -0,0 +1,446 @@ +GNU LIBRARY GENERAL PUBLIC LICENSE + +Version 2, June 1991 Copyright (C) 1991 Free Software Foundation, Inc. + +51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is numbered 2 because +it goes with version 2 of the ordinary GPL.] + +Preamble + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public Licenses are intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. + +This license, the Library General Public License, applies to some specially +designated Free Software Foundation software, and to any other libraries whose +authors decide to use it. You can use it for your libraries, 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 this service 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 make restrictions that forbid anyone to +deny you these rights or to ask you to surrender the rights. These restrictions +translate to certain responsibilities for you if you distribute copies of +the library, or if you modify it. + +For example, if you distribute copies of the library, whether gratis or for +a fee, you must give the recipients all the rights that we gave you. You must +make sure that they, too, receive or can get the source code. If you link +a program with the library, you must provide complete object files to the +recipients so that they can relink them with the library, after making changes +to the library and recompiling it. And you must show them these terms so they +know their rights. + +Our method of protecting your rights has two steps: (1) copyright the library, +and (2) offer you this license which gives you legal permission to copy, distribute +and/or modify the library. + +Also, for each distributor's protection, we want to make certain that everyone +understands that there is no warranty for this free library. If the library +is modified by someone else and passed on, we want its recipients to know +that what they have is not the original version, so that any problems introduced +by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We +wish to avoid the danger that companies distributing free software will individually +obtain patent licenses, thus in effect transforming the program into proprietary +software. To prevent this, we have made it clear that any patent must be licensed +for everyone's free use or not licensed at all. + +Most GNU software, including some libraries, is covered by the ordinary GNU +General Public License, which was designed for utility programs. This license, +the GNU Library General Public License, applies to certain designated libraries. +This license is quite different from the ordinary one; be sure to read it +in full, and don't assume that anything in it is the same as in the ordinary +license. + +The reason we have a separate public license for some libraries is that they +blur the distinction we usually make between modifying or adding to a program +and simply using it. Linking a program with a library, without changing the +library, is in some sense simply using the library, and is analogous to running +a utility program or application program. However, in a textual and legal +sense, the linked executable is a combined work, a derivative of the original +library, and the ordinary General Public License treats it as such. + +Because of this blurred distinction, using the ordinary General Public License +for libraries did not effectively promote software sharing, because most developers +did not use the libraries. We concluded that weaker conditions might promote +sharing better. + +However, unrestricted linking of non-free programs would deprive the users +of those programs of all benefit from the free status of the libraries themselves. +This Library General Public License is intended to permit developers of non-free +programs to use free libraries, while preserving your freedom as a user of +such programs to change the free libraries that are incorporated in them. +(We have not seen how to achieve this as regards changes in header files, +but we have achieved it as regards changes in the actual functions of the +Library.) The hope is that this will lead to faster development of free libraries. + +The precise terms and conditions for copying, distribution and modification +follow. Pay close attention to the difference between a "work based on the +library" and a "work that uses the library". The former contains code derived +from the library, while the latter only works together with the library. + +Note that it is possible for a library to be covered by the ordinary General +Public License rather than by this special one. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License Agreement applies to any software library which contains a +notice placed by the copyright holder or other authorized party saying it +may be distributed under the terms of this Library General Public License +(also called "this License"). Each licensee is addressed as "you". + +A "library" means a collection of software functions and/or data prepared +so as to be conveniently linked with application programs (which use some +of those functions and data) to form executables. + +The "Library", below, refers to any such software library or work which has +been distributed under these terms. A "work based on the Library" means either +the Library or any derivative work under copyright law: that is to say, a +work containing the Library or a portion of it, either verbatim or with modifications +and/or translated straightforwardly into another language. (Hereinafter, translation +is included without limitation in the term "modification".) + +"Source code" for a work means the preferred form of the work for making modifications +to it. For a library, complete source code means all the source code for all +modules it contains, plus any associated interface definition files, plus +the scripts used to control compilation and installation of the library. + +Activities other than copying, distribution and modification are not covered +by this License; they are outside its scope. The act of running a program +using the Library is not restricted, and output from such a program is covered +only if its contents constitute a work based on the Library (independent of +the use of the Library in a tool for writing it). Whether that is true depends +on what the Library does and what the program that uses the Library does. + +1. You may copy and distribute verbatim copies of the Library's complete source +code as you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and disclaimer +of warranty; keep intact all the notices that refer to this License and to +the absence of any warranty; and distribute a copy of this License along with +the Library. + +You may charge a fee for the physical act of transferring a copy, and you +may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Library or any portion of it, +thus forming a work based on the Library, and copy and distribute such modifications +or work under the terms of Section 1 above, provided that you also meet all +of these conditions: + + a) The modified work must itself be a software library. + +b) You must cause the files modified to carry prominent notices stating that +you changed the files and the date of any change. + +c) You must cause the whole of the work to be licensed at no charge to all +third parties under the terms of this License. + +d) If a facility in the modified Library refers to a function or a table of +data to be supplied by an application program that uses the facility, other +than as an argument passed when the facility is invoked, then you must make +a good faith effort to ensure that, in the event an application does not supply +such function or table, the facility still operates, and performs whatever +part of its purpose remains meaningful. + +(For example, a function in a library to compute square roots has a purpose +that is entirely well-defined independent of the application. Therefore, Subsection +2d requires that any application-supplied function or table used by this function +must be optional: if the application does not supply it, the square root function +must still compute square roots.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Library, and can be reasonably +considered independent and separate works in themselves, then this License, +and its terms, do not apply to those sections when you distribute them as +separate works. But when you distribute the same sections as part of a whole +which is a work based on the Library, the distribution of the whole must be +on the terms of this License, whose permissions for other licensees extend +to the entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest your +rights to work written entirely by you; rather, the intent is to exercise +the right to control the distribution of derivative or collective works based +on the Library. + +In addition, mere aggregation of another work not based on the Library with +the Library (or with a work based on the Library) on a volume of a storage +or distribution medium does not bring the other work under the scope of this +License. + +3. You may opt to apply the terms of the ordinary GNU General Public License +instead of this License to a given copy of the Library. To do this, you must +alter all the notices that refer to this License, so that they refer to the +ordinary GNU General Public License, version 2, instead of to this License. +(If a newer version than version 2 of the ordinary GNU General Public License +has appeared, then you can specify that version instead if you wish.) Do not +make any other change in these notices. + +Once this change is made in a given copy, it is irreversible for that copy, +so the ordinary GNU General Public License applies to all subsequent copies +and derivative works made from that copy. + +This option is useful when you wish to copy part of the code of the Library +into a program that is not a library. + +4. You may copy and distribute the Library (or a portion or derivative of +it, under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you accompany it with the complete corresponding +machine-readable source code, which must be distributed under the terms of +Sections 1 and 2 above on a medium customarily used for software interchange. + +If distribution of object code is made by offering access to copy from a designated +place, then offering equivalent access to copy the source code from the same +place satisfies the requirement to distribute the source code, even though +third parties are not compelled to copy the source along with the object code. + +5. A program that contains no derivative of any portion of the Library, but +is designed to work with the Library by being compiled or linked with it, +is called a "work that uses the Library". Such a work, in isolation, is not +a derivative work of the Library, and therefore falls outside the scope of +this License. + +However, linking a "work that uses the Library" with the Library creates an +executable that is a derivative of the Library (because it contains portions +of the Library), rather than a "work that uses the library". The executable +is therefore covered by this License. Section 6 states terms for distribution +of such executables. + +When a "work that uses the Library" uses material from a header file that +is part of the Library, the object code for the work may be a derivative work +of the Library even though the source code is not. Whether this is true is +especially significant if the work can be linked without the Library, or if +the work is itself a library. The threshold for this to be true is not precisely +defined by law. + +If such an object file uses only numerical parameters, data structure layouts +and accessors, and small macros and small inline functions (ten lines or less +in length), then the use of the object file is unrestricted, regardless of +whether it is legally a derivative work. (Executables containing this object +code plus portions of the Library will still fall under Section 6.) + +Otherwise, if the work is a derivative of the Library, you may distribute +the object code for the work under the terms of Section 6. Any executables +containing that work also fall under Section 6, whether or not they are linked +directly with the Library itself. + +6. As an exception to the Sections above, you may also compile or link a "work +that uses the Library" with the Library to produce a work containing portions +of the Library, and distribute that work under terms of your choice, provided +that the terms permit modification of the work for the customer's own use +and reverse engineering for debugging such modifications. + +You must give prominent notice with each copy of the work that the Library +is used in it and that the Library and its use are covered by this License. +You must supply a copy of this License. If the work during execution displays +copyright notices, you must include the copyright notice for the Library among +them, as well as a reference directing the user to the copy of this License. +Also, you must do one of these things: + +a) Accompany the work with the complete corresponding machine-readable source +code for the Library including whatever changes were used in the work (which +must be distributed under Sections 1 and 2 above); and, if the work is an +executable linked with the Library, with the complete machine-readable "work +that uses the Library", as object code and/or source code, so that the user +can modify the Library and then relink to produce a modified executable containing +the modified Library. (It is understood that the user who changes the contents +of definitions files in the Library will not necessarily be able to recompile +the application to use the modified definitions.) + +b) Accompany the work with a written offer, valid for at least three years, +to give the same user the materials specified in Subsection 6a, above, for +a charge no more than the cost of performing this distribution. + +c) If distribution of the work is made by offering access to copy from a designated +place, offer equivalent access to copy the above specified materials from +the same place. + +d) Verify that the user has already received a copy of these materials or +that you have already sent this user a copy. + +For an executable, the required form of the "work that uses the Library" must +include any data and utility programs needed for reproducing the executable +from it. However, as a special exception, the source code distributed need +not include anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the operating +system on which the executable runs, unless that component itself accompanies +the executable. + +It may happen that this requirement contradicts the license restrictions of +other proprietary libraries that do not normally accompany the operating system. +Such a contradiction means you cannot use both them and the Library together +in an executable that you distribute. + +7. You may place library facilities that are a work based on the Library side-by-side +in a single library together with other library facilities not covered by +this License, and distribute such a combined library, provided that the separate +distribution of the work based on the Library and of the other library facilities +is otherwise permitted, and provided that you do these two things: + +a) Accompany the combined library with a copy of the same work based on the +Library, uncombined with any other library facilities. This must be distributed +under the terms of the Sections above. + +b) Give prominent notice with the combined library of the fact that part of +it is a work based on the Library, and explaining where to find the accompanying +uncombined form of the same work. + +8. You may not copy, modify, sublicense, link with, or distribute the Library +except as expressly provided under this License. Any attempt otherwise to +copy, modify, sublicense, link with, or distribute the Library is void, and +will automatically terminate your rights under this License. However, parties +who have received copies, or rights, from you under this License will not +have their licenses terminated so long as such parties remain in full compliance. + +9. You are not required to accept this License, since you have not signed +it. However, nothing else grants you permission to modify or distribute the +Library or its derivative works. These actions are prohibited by law if you +do not accept this License. Therefore, by modifying or distributing the Library +(or any work based on the Library), you indicate your acceptance of this License +to do so, and all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + +10. Each time you redistribute the Library (or any work based on the Library), +the recipient automatically receives a license from the original licensor +to copy, distribute, link with or modify the Library subject to these terms +and conditions. You may not impose any further restrictions on the recipients' +exercise of the rights granted herein. You are not responsible for enforcing +compliance by third parties to this License. + +11. If, as a consequence of a court judgment or allegation of patent infringement +or for any other reason (not limited to patent issues), 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 distribute so as to satisfy simultaneously your +obligations under this License and any other pertinent obligations, then as +a consequence you may not distribute the Library at all. For example, if a +patent license would not permit royalty-free redistribution of the Library +by all those who receive copies directly or indirectly through you, then the +only way you could satisfy both it and this License would be to refrain entirely +from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents +or other property right claims or to contest validity of any such claims; +this section has the sole purpose of protecting the integrity of the free +software distribution system which is implemented by public license practices. +Many people have made generous contributions to the wide range of software +distributed through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing to +distribute software through any other system and a licensee cannot impose +that choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +12. If the distribution and/or use of the Library is restricted in certain +countries either by patents or by copyrighted interfaces, the original copyright +holder who places the Library under this License may add an explicit geographical +distribution limitation excluding those countries, so that distribution is +permitted only in or among countries not thus excluded. In such case, this +License incorporates the limitation as if written in the body of this License. + +13. The Free Software Foundation may publish revised and/or new versions of +the Library 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 Library specifies +a version number of this License which applies to it and "any later version", +you have the option of following the terms and conditions either of that version +or of any later version published by the Free Software Foundation. If the +Library does not specify a license version number, you may choose any version +ever published by the Free Software Foundation. + +14. If you wish to incorporate parts of the Library into other free programs +whose distribution conditions are incompatible with these, write to the author +to ask for permission. For software which is copyrighted by the Free Software +Foundation, write to the Free Software Foundation; we sometimes make exceptions +for this. Our decision will be guided by the two goals of preserving the free +status of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + +15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR +THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE +STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY +"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 LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE +THE LIBRARY 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 LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH +HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Libraries + +If you develop a new library, and you want it to be of the greatest possible +use to the public, we recommend making it free software that everyone can +redistribute and change. You can do so by permitting redistribution under +these terms (or, alternatively, under the terms of the ordinary General Public +License). + +To apply these terms, attach the following notices to the library. It is safest +to attach them to the start of each source file to most effectively convey +the exclusion of warranty; and each file should have at least the "copyright" +line and a pointer to where the full notice is found. + +one line to give the library's name and an idea of what it does. + +Copyright (C) year name of author + +This library is free software; you can redistribute it and/or modify it under +the terms of the GNU Library 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 Library General Public License for more +details. + +You should have received a copy of the GNU Library General Public License +along with this library; if not, write to the Free Software Foundation, Inc., +51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your school, +if any, to sign a "copyright disclaimer" for the library, if necessary. Here +is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in + +the library `Frob' (a library for tweaking knobs) written + +by James Random Hacker. + +signature of Ty Coon, 1 April 1990 + +Ty Coon, President of Vice + +That's all there is to it! diff --git a/LICENSES/LGPL-2.1-only.txt b/LICENSES/LGPL-2.1-only.txt new file mode 100644 index 0000000..130dffb --- /dev/null +++ b/LICENSES/LGPL-2.1-only.txt @@ -0,0 +1,467 @@ +GNU LESSER GENERAL PUBLIC LICENSE + +Version 2.1, February 1999 + +Copyright (C) 1991, 1999 Free Software Foundation, Inc. + +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts as the +successor of the GNU Library Public License, version 2, hence the version +number 2.1.] + +Preamble + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public Licenses are intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. + +This license, the Lesser General Public License, applies to some specially +designated software packages--typically libraries--of the Free Software Foundation +and other authors who decide to use it. You can use it too, but we suggest +you first think carefully about whether this license or the ordinary General +Public License is the better strategy to use in any particular case, based +on the explanations below. + +When we speak of free software, we are referring to freedom of use, 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 this service if you +wish); that you receive source code or can get it if you want it; that you +can change the software and use pieces of it in new free programs; and that +you are informed that you can do these things. + +To protect your rights, we need to make restrictions that forbid distributors +to deny you these rights or to ask you to surrender these rights. These restrictions +translate to certain responsibilities for you if you distribute copies of +the library or if you modify it. + +For example, if you distribute copies of the library, whether gratis or for +a fee, you must give the recipients all the rights that we gave you. You must +make sure that they, too, receive or can get the source code. If you link +other code with the library, you must provide complete object files to the +recipients, so that they can relink them with the library after making changes +to the library and recompiling it. And you must show them these terms so they +know their rights. + +We protect your rights with a two-step method: (1) we copyright the library, +and (2) we offer you this license, which gives you legal permission to copy, +distribute and/or modify the library. + +To protect each distributor, we want to make it very clear that there is no +warranty for the free library. Also, if the library is modified by someone +else and passed on, the recipients should know that what they have is not +the original version, so that the original author's reputation will not be +affected by problems that might be introduced by others. + +Finally, software patents pose a constant threat to the existence of any free +program. We wish to make sure that a company cannot effectively restrict the +users of a free program by obtaining a restrictive license from a patent holder. +Therefore, we insist that any patent license obtained for a version of the +library must be consistent with the full freedom of use specified in this +license. + +Most GNU software, including some libraries, is covered by the ordinary GNU +General Public License. This license, the GNU Lesser General Public License, +applies to certain designated libraries, and is quite different from the ordinary +General Public License. We use this license for certain libraries in order +to permit linking those libraries into non-free programs. + +When a program is linked with a library, whether statically or using a shared +library, the combination of the two is legally speaking a combined work, a +derivative of the original library. The ordinary General Public License therefore +permits such linking only if the entire combination fits its criteria of freedom. +The Lesser General Public License permits more lax criteria for linking other +code with the library. + +We call this license the "Lesser" General Public License because it does Less +to protect the user's freedom than the ordinary General Public License. It +also provides other free software developers Less of an advantage over competing +non-free programs. These disadvantages are the reason we use the ordinary +General Public License for many libraries. However, the Lesser license provides +advantages in certain special circumstances. + +For example, on rare occasions, there may be a special need to encourage the +widest possible use of a certain library, so that it becomes a de-facto standard. +To achieve this, non-free programs must be allowed to use the library. A more +frequent case is that a free library does the same job as widely used non-free +libraries. In this case, there is little to gain by limiting the free library +to free software only, so we use the Lesser General Public License. + +In other cases, permission to use a particular library in non-free programs +enables a greater number of people to use a large body of free software. For +example, permission to use the GNU C Library in non-free programs enables +many more people to use the whole GNU operating system, as well as its variant, +the GNU/Linux operating system. + +Although the Lesser General Public License is Less protective of the users' +freedom, it does ensure that the user of a program that is linked with the +Library has the freedom and the wherewithal to run that program using a modified +version of the Library. + +The precise terms and conditions for copying, distribution and modification +follow. Pay close attention to the difference between a "work based on the +library" and a "work that uses the library". The former contains code derived +from the library, whereas the latter must be combined with the library in +order to run. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License Agreement applies to any software library or other program +which contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Lesser General +Public License (also called "this License"). Each licensee is addressed as +"you". + +A "library" means a collection of software functions and/or data prepared +so as to be conveniently linked with application programs (which use some +of those functions and data) to form executables. + +The "Library", below, refers to any such software library or work which has +been distributed under these terms. A "work based on the Library" means either +the Library or any derivative work under copyright law: that is to say, a +work containing the Library or a portion of it, either verbatim or with modifications +and/or translated straightforwardly into another language. (Hereinafter, translation +is included without limitation in the term "modification".) + +"Source code" for a work means the preferred form of the work for making modifications +to it. For a library, complete source code means all the source code for all +modules it contains, plus any associated interface definition files, plus +the scripts used to control compilation and installation of the library. + +Activities other than copying, distribution and modification are not covered +by this License; they are outside its scope. The act of running a program +using the Library is not restricted, and output from such a program is covered +only if its contents constitute a work based on the Library (independent of +the use of the Library in a tool for writing it). Whether that is true depends +on what the Library does and what the program that uses the Library does. + +1. You may copy and distribute verbatim copies of the Library's complete source +code as you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and disclaimer +of warranty; keep intact all the notices that refer to this License and to +the absence of any warranty; and distribute a copy of this License along with +the Library. + +You may charge a fee for the physical act of transferring a copy, and you +may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Library or any portion of it, +thus forming a work based on the Library, and copy and distribute such modifications +or work under the terms of Section 1 above, provided that you also meet all +of these conditions: + + a) The modified work must itself be a software library. + +b) You must cause the files modified to carry prominent notices stating that +you changed the files and the date of any change. + +c) You must cause the whole of the work to be licensed at no charge to all +third parties under the terms of this License. + +d) If a facility in the modified Library refers to a function or a table of +data to be supplied by an application program that uses the facility, other +than as an argument passed when the facility is invoked, then you must make +a good faith effort to ensure that, in the event an application does not supply +such function or table, the facility still operates, and performs whatever +part of its purpose remains meaningful. + +(For example, a function in a library to compute square roots has a purpose +that is entirely well-defined independent of the application. Therefore, Subsection +2d requires that any application-supplied function or table used by this function +must be optional: if the application does not supply it, the square root function +must still compute square roots.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Library, and can be reasonably +considered independent and separate works in themselves, then this License, +and its terms, do not apply to those sections when you distribute them as +separate works. But when you distribute the same sections as part of a whole +which is a work based on the Library, the distribution of the whole must be +on the terms of this License, whose permissions for other licensees extend +to the entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest your +rights to work written entirely by you; rather, the intent is to exercise +the right to control the distribution of derivative or collective works based +on the Library. + +In addition, mere aggregation of another work not based on the Library with +the Library (or with a work based on the Library) on a volume of a storage +or distribution medium does not bring the other work under the scope of this +License. + +3. You may opt to apply the terms of the ordinary GNU General Public License +instead of this License to a given copy of the Library. To do this, you must +alter all the notices that refer to this License, so that they refer to the +ordinary GNU General Public License, version 2, instead of to this License. +(If a newer version than version 2 of the ordinary GNU General Public License +has appeared, then you can specify that version instead if you wish.) Do not +make any other change in these notices. + +Once this change is made in a given copy, it is irreversible for that copy, +so the ordinary GNU General Public License applies to all subsequent copies +and derivative works made from that copy. + +This option is useful when you wish to copy part of the code of the Library +into a program that is not a library. + +4. You may copy and distribute the Library (or a portion or derivative of +it, under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you accompany it with the complete corresponding +machine-readable source code, which must be distributed under the terms of +Sections 1 and 2 above on a medium customarily used for software interchange. + +If distribution of object code is made by offering access to copy from a designated +place, then offering equivalent access to copy the source code from the same +place satisfies the requirement to distribute the source code, even though +third parties are not compelled to copy the source along with the object code. + +5. A program that contains no derivative of any portion of the Library, but +is designed to work with the Library by being compiled or linked with it, +is called a "work that uses the Library". Such a work, in isolation, is not +a derivative work of the Library, and therefore falls outside the scope of +this License. + +However, linking a "work that uses the Library" with the Library creates an +executable that is a derivative of the Library (because it contains portions +of the Library), rather than a "work that uses the library". The executable +is therefore covered by this License. Section 6 states terms for distribution +of such executables. + +When a "work that uses the Library" uses material from a header file that +is part of the Library, the object code for the work may be a derivative work +of the Library even though the source code is not. Whether this is true is +especially significant if the work can be linked without the Library, or if +the work is itself a library. The threshold for this to be true is not precisely +defined by law. + +If such an object file uses only numerical parameters, data structure layouts +and accessors, and small macros and small inline functions (ten lines or less +in length), then the use of the object file is unrestricted, regardless of +whether it is legally a derivative work. (Executables containing this object +code plus portions of the Library will still fall under Section 6.) + +Otherwise, if the work is a derivative of the Library, you may distribute +the object code for the work under the terms of Section 6. Any executables +containing that work also fall under Section 6, whether or not they are linked +directly with the Library itself. + +6. As an exception to the Sections above, you may also combine or link a "work +that uses the Library" with the Library to produce a work containing portions +of the Library, and distribute that work under terms of your choice, provided +that the terms permit modification of the work for the customer's own use +and reverse engineering for debugging such modifications. + +You must give prominent notice with each copy of the work that the Library +is used in it and that the Library and its use are covered by this License. +You must supply a copy of this License. If the work during execution displays +copyright notices, you must include the copyright notice for the Library among +them, as well as a reference directing the user to the copy of this License. +Also, you must do one of these things: + +a) Accompany the work with the complete corresponding machine-readable source +code for the Library including whatever changes were used in the work (which +must be distributed under Sections 1 and 2 above); and, if the work is an +executable linked with the Library, with the complete machine-readable "work +that uses the Library", as object code and/or source code, so that the user +can modify the Library and then relink to produce a modified executable containing +the modified Library. (It is understood that the user who changes the contents +of definitions files in the Library will not necessarily be able to recompile +the application to use the modified definitions.) + +b) Use a suitable shared library mechanism for linking with the Library. A +suitable mechanism is one that (1) uses at run time a copy of the library +already present on the user's computer system, rather than copying library +functions into the executable, and (2) will operate properly with a modified +version of the library, if the user installs one, as long as the modified +version is interface-compatible with the version that the work was made with. + +c) Accompany the work with a written offer, valid for at least three years, +to give the same user the materials specified in Subsection 6a, above, for +a charge no more than the cost of performing this distribution. + +d) If distribution of the work is made by offering access to copy from a designated +place, offer equivalent access to copy the above specified materials from +the same place. + +e) Verify that the user has already received a copy of these materials or +that you have already sent this user a copy. + +For an executable, the required form of the "work that uses the Library" must +include any data and utility programs needed for reproducing the executable +from it. However, as a special exception, the materials to be distributed +need not include anything that is normally distributed (in either source or +binary form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component itself +accompanies the executable. + +It may happen that this requirement contradicts the license restrictions of +other proprietary libraries that do not normally accompany the operating system. +Such a contradiction means you cannot use both them and the Library together +in an executable that you distribute. + +7. You may place library facilities that are a work based on the Library side-by-side +in a single library together with other library facilities not covered by +this License, and distribute such a combined library, provided that the separate +distribution of the work based on the Library and of the other library facilities +is otherwise permitted, and provided that you do these two things: + +a) Accompany the combined library with a copy of the same work based on the +Library, uncombined with any other library facilities. This must be distributed +under the terms of the Sections above. + +b) Give prominent notice with the combined library of the fact that part of +it is a work based on the Library, and explaining where to find the accompanying +uncombined form of the same work. + +8. You may not copy, modify, sublicense, link with, or distribute the Library +except as expressly provided under this License. Any attempt otherwise to +copy, modify, sublicense, link with, or distribute the Library is void, and +will automatically terminate your rights under this License. However, parties +who have received copies, or rights, from you under this License will not +have their licenses terminated so long as such parties remain in full compliance. + +9. You are not required to accept this License, since you have not signed +it. However, nothing else grants you permission to modify or distribute the +Library or its derivative works. These actions are prohibited by law if you +do not accept this License. Therefore, by modifying or distributing the Library +(or any work based on the Library), you indicate your acceptance of this License +to do so, and all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + +10. Each time you redistribute the Library (or any work based on the Library), +the recipient automatically receives a license from the original licensor +to copy, distribute, link with or modify the Library subject to these terms +and conditions. You may not impose any further restrictions on the recipients' +exercise of the rights granted herein. You are not responsible for enforcing +compliance by third parties with this License. + +11. If, as a consequence of a court judgment or allegation of patent infringement +or for any other reason (not limited to patent issues), 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 distribute so as to satisfy simultaneously your +obligations under this License and any other pertinent obligations, then as +a consequence you may not distribute the Library at all. For example, if a +patent license would not permit royalty-free redistribution of the Library +by all those who receive copies directly or indirectly through you, then the +only way you could satisfy both it and this License would be to refrain entirely +from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents +or other property right claims or to contest validity of any such claims; +this section has the sole purpose of protecting the integrity of the free +software distribution system which is implemented by public license practices. +Many people have made generous contributions to the wide range of software +distributed through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing to +distribute software through any other system and a licensee cannot impose +that choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +12. If the distribution and/or use of the Library is restricted in certain +countries either by patents or by copyrighted interfaces, the original copyright +holder who places the Library under this License may add an explicit geographical +distribution limitation excluding those countries, so that distribution is +permitted only in or among countries not thus excluded. In such case, this +License incorporates the limitation as if written in the body of this License. + +13. The Free Software Foundation may publish revised and/or new versions of +the Lesser 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 Library specifies +a version number of this License which applies to it and "any later version", +you have the option of following the terms and conditions either of that version +or of any later version published by the Free Software Foundation. If the +Library does not specify a license version number, you may choose any version +ever published by the Free Software Foundation. + +14. If you wish to incorporate parts of the Library into other free programs +whose distribution conditions are incompatible with these, write to the author +to ask for permission. For software which is copyrighted by the Free Software +Foundation, write to the Free Software Foundation; we sometimes make exceptions +for this. Our decision will be guided by the two goals of preserving the free +status of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + +15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR +THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE +STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY +"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 LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE +THE LIBRARY 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 LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH +HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Libraries + +If you develop a new library, and you want it to be of the greatest possible +use to the public, we recommend making it free software that everyone can +redistribute and change. You can do so by permitting redistribution under +these terms (or, alternatively, under the terms of the ordinary General Public +License). + +To apply these terms, attach the following notices to the library. It is safest +to attach them to the start of each source file to most effectively convey +the exclusion of warranty; and each file should have at least the "copyright" +line and a pointer to where the full notice is found. + +< one line to give the library's name and an idea of what it does. > + +Copyright (C) < year > < name of author > + +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 Also add information +on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your school, +if any, to sign a "copyright disclaimer" for the library, if necessary. Here +is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in + +the library `Frob' (a library for tweaking knobs) written + +by James Random Hacker. + +< signature of Ty Coon > , 1 April 1990 + +Ty Coon, President of Vice + +That's all there is to it! diff --git a/LICENSES/LGPL-3.0-only.txt b/LICENSES/LGPL-3.0-only.txt new file mode 100644 index 0000000..bd405af --- /dev/null +++ b/LICENSES/LGPL-3.0-only.txt @@ -0,0 +1,163 @@ +GNU LESSER 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. + +This version of the GNU Lesser General Public License incorporates the terms +and conditions of version 3 of the GNU General Public License, supplemented +by the additional permissions listed below. + + 0. Additional Definitions. + + + +As used herein, "this License" refers to version 3 of the GNU Lesser General +Public License, and the "GNU GPL" refers to version 3 of the GNU General Public +License. + + + +"The Library" refers to a covered work governed by this License, other than +an Application or a Combined Work as defined below. + + + +An "Application" is any work that makes use of an interface provided by the +Library, but which is not otherwise based on the Library. Defining a subclass +of a class defined by the Library is deemed a mode of using an interface provided +by the Library. + + + +A "Combined Work" is a work produced by combining or linking an Application +with the Library. The particular version of the Library with which the Combined +Work was made is also called the "Linked Version". + + + +The "Minimal Corresponding Source" for a Combined Work means the Corresponding +Source for the Combined Work, excluding any source code for portions of the +Combined Work that, considered in isolation, are based on the Application, +and not on the Linked Version. + + + +The "Corresponding Application Code" for a Combined Work means the object +code and/or source code for the Application, including any data and utility +programs needed for reproducing the Combined Work from the Application, but +excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + +You may convey a covered work under sections 3 and 4 of this License without +being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + +If you modify a copy of the Library, and, in your modifications, a facility +refers to a function or data to be supplied by an Application that uses the +facility (other than as an argument passed when the facility is invoked), +then you may convey a copy of the modified version: + +a) under this License, provided that you make a good faith effort to ensure +that, in the event an Application does not supply the function or data, the +facility still operates, and performs whatever part of its purpose remains +meaningful, or + +b) under the GNU GPL, with none of the additional permissions of this License +applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + +The object code form of an Application may incorporate material from a header +file that is part of the Library. You may convey such object code under terms +of your choice, provided that, if the incorporated material is not limited +to numerical parameters, data structure layouts and accessors, or small macros, +inline functions and templates (ten or fewer lines in length), you do both +of the following: + +a) Give prominent notice with each copy of the object code that the Library +is used in it and that the Library and its use are covered by this License. + +b) Accompany the object code with a copy of the GNU GPL and this license document. + + 4. Combined Works. + +You may convey a Combined Work under terms of your choice that, taken together, +effectively do not restrict modification of the portions of the Library contained +in the Combined Work and reverse engineering for debugging such modifications, +if you also do each of the following: + +a) Give prominent notice with each copy of the Combined Work that the Library +is used in it and that the Library and its use are covered by this License. + +b) Accompany the Combined Work with a copy of the GNU GPL and this license +document. + +c) For a Combined Work that displays copyright notices during execution, include +the copyright notice for the Library among these notices, as well as a reference +directing the user to the copies of the GNU GPL and this license document. + + d) Do one of the following: + +0) Convey the Minimal Corresponding Source under the terms of this License, +and the Corresponding Application Code in a form suitable for, and under terms +that permit, the user to recombine or relink the Application with a modified +version of the Linked Version to produce a modified Combined Work, in the +manner specified by section 6 of the GNU GPL for conveying Corresponding Source. + +1) Use a suitable shared library mechanism for linking with the Library. A +suitable mechanism is one that (a) uses at run time a copy of the Library +already present on the user's computer system, and (b) will operate properly +with a modified version of the Library that is interface-compatible with the +Linked Version. + +e) Provide Installation Information, but only if you would otherwise be required +to provide such information under section 6 of the GNU GPL, and only to the +extent that such information is necessary to install and execute a modified +version of the Combined Work produced by recombining or relinking the Application +with a modified version of the Linked Version. (If you use option 4d0, the +Installation Information must accompany the Minimal Corresponding Source and +Corresponding Application Code. If you use option 4d1, you must provide the +Installation Information in the manner specified by section 6 of the GNU GPL +for conveying Corresponding Source.) + + 5. Combined Libraries. + +You may place library facilities that are a work based on the Library side +by side in a single library together with other library facilities that are +not Applications and are not covered by this License, and convey such a combined +library under terms of your choice, if you do both of the following: + +a) Accompany the combined library with a copy of the same work based on the +Library, uncombined with any other library facilities, conveyed under the +terms of this License. + +b) Give prominent notice with the combined library that part of it is a work +based on the Library, and explaining where to find the accompanying uncombined +form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + +The Free Software Foundation may publish revised and/or new versions of the +GNU Lesser 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 Library as you +received it specifies that a certain numbered version of the GNU Lesser General +Public License "or any later version" applies to it, you have the option of +following the terms and conditions either of that published version or of +any later version published by the Free Software Foundation. If the Library +as you received it does not specify a version number of the GNU Lesser General +Public License, you may choose any version of the GNU Lesser General Public +License ever published by the Free Software Foundation. + +If the Library as you received it specifies that a proxy can decide whether +future versions of the GNU Lesser General Public License shall apply, that +proxy's public statement of acceptance of any version is permanent authorization +for you to choose that version for the Library. diff --git a/LICENSES/LicenseRef-KDE-Accepted-LGPL.txt b/LICENSES/LicenseRef-KDE-Accepted-LGPL.txt new file mode 100644 index 0000000..232b3c5 --- /dev/null +++ b/LICENSES/LicenseRef-KDE-Accepted-LGPL.txt @@ -0,0 +1,12 @@ +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 3 of the license or (at your option) any later version +that is accepted by the membership of KDE e.V. (or its successor +approved by the membership of KDE e.V.), which shall act as a +proxy as defined in Section 6 of version 3 of the license. + +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. diff --git a/autotests/ki18ndeclarativetest.cpp b/autotests/ki18ndeclarativetest.cpp index 2d9c363..51d4af3 100644 --- a/autotests/ki18ndeclarativetest.cpp +++ b/autotests/ki18ndeclarativetest.cpp @@ -1,66 +1,53 @@ /* This file is part of the KDE libraries - Copyright (C) 2006 Chusslove Illich + SPDX-FileCopyrightText: 2006 Chusslove Illich - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library 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 - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-License-Identifier: LGPL-2.0-or-later */ #include #include #include #include #include #include class KI18nDeclarativeTest : public QObject { Q_OBJECT private Q_SLOTS: void testLocalizedContext_data() { QTest::addColumn("propertyName"); QTest::addColumn("value"); QTest::newRow("translation") << "testString" << QStringLiteral("Awesome"); QTest::newRow("singular translation") << "testStringSingular" << QStringLiteral("and 1 other window"); QTest::newRow("plural translation") << "testStringPlural" << QStringLiteral("and 3 other windows"); QTest::newRow("plural translation with domain") << "testStringPluralWithDomain" << QStringLiteral("in 3 seconds"); } void testLocalizedContext() { QFETCH(QString, propertyName); QFETCH(QString, value); KLocalizedContext ctx; QUrl input = QUrl::fromLocalFile(QFINDTESTDATA("test.qml")); QQmlEngine engine; engine.rootContext()->setContextObject(&ctx); QQmlComponent component(&engine, input, QQmlComponent::PreferSynchronous); QObject *object = component.create(); if (!object) { qDebug() << "errors:" << component.errors(); } QVERIFY(object); QVERIFY(!component.isLoading()); QCOMPARE(object->property(propertyName.toUtf8().constData()).toString(), value); } }; QTEST_MAIN(KI18nDeclarativeTest) #include "ki18ndeclarativetest.moc" diff --git a/autotests/klocalizedstringtest.cpp b/autotests/klocalizedstringtest.cpp index 46b99a1..b3790c0 100644 --- a/autotests/klocalizedstringtest.cpp +++ b/autotests/klocalizedstringtest.cpp @@ -1,654 +1,641 @@ // krazy:excludeall=i18ncheckarg /* This file is part of the KDE libraries - Copyright (C) 2006 Chusslove Illich - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library 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 - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-FileCopyrightText: 2006 Chusslove Illich + + SPDX-License-Identifier: LGPL-2.0-or-later */ // Tests explicitly use their own test catalogs. #undef TRANSLATION_DOMAIN #include "klocalizedstringtest.h" #include "klocalizedtranslator.h" #include #include #include #include #include #include #include #include #include #include #include void KLocalizedStringTest::initTestCase() { KLocalizedString::setApplicationDomain("ki18n-test"); m_hasFrench = true; m_hasCatalan = true; setlocale(LC_ALL, "ca_ES.utf8"); if (setlocale(LC_ALL, nullptr) != QByteArray("ca_ES.utf8")) { qDebug() << "Failed to set locale to ca_ES.utf8."; m_hasCatalan = false; } if (m_hasFrench) { setlocale(LC_ALL, "fr_FR.utf8"); if (setlocale(LC_ALL, nullptr) != QByteArray("fr_FR.utf8")) { qDebug() << "Failed to set locale to fr_FR.utf8."; m_hasFrench = false; } else { QLocale::setDefault(QLocale("fr_FR")); // the setlocale is "too late" for Qt that already has created the default QLocale, so set it manually } } if (m_hasFrench) { if (!m_tempDir.isValid()) { qDebug() << "Failed to create temporary directory for test data."; m_hasFrench = false; } } QDir dataDir(m_tempDir.path()); if (m_hasFrench) { m_hasFrench = compileCatalogs({QFINDTESTDATA("po/fr/ki18n-test.po"), QFINDTESTDATA("po/fr/ki18n-test-qt.po")}, dataDir, "fr"); } if (m_hasCatalan) { m_hasCatalan = compileCatalogs({QFINDTESTDATA("po/ca/ki18n-test.po")}, dataDir, "ca"); } if (m_hasFrench) { qputenv("XDG_DATA_DIRS", qgetenv("XDG_DATA_DIRS") + ":" + QFile::encodeName(dataDir.path())); // bind... dataDir.path() QStringList languages; languages.append("fr"); KLocalizedString::setLanguages(languages); } #if 0 // until locale system is ready if (m_hasFrench) { KLocale::global()->setLanguage(QStringList() << "fr" << "en_US"); } KLocale::global()->setThousandsSeparator(QLatin1String(",")); KLocale::global()->setDecimalSymbol(QLatin1String(".")); #endif } bool KLocalizedStringTest::compileCatalogs(const QStringList &testPoPaths, const QDir &dataDir, const QString &lang) { const QString lcMessages = QString("locale/%1/LC_MESSAGES").arg(lang); if (!dataDir.mkpath(lcMessages)) { qDebug() << "Failed to create locale subdirectory " "inside temporary directory."; return false; } QString msgfmt = QStandardPaths::findExecutable(QLatin1String("msgfmt")); if (msgfmt.isEmpty()) { qDebug() << "msgfmt(1) not found in path."; return false; } for (const QString &testPoPath : testPoPaths) { int pos_1 = testPoPath.lastIndexOf(QLatin1Char('/')); int pos_2 = testPoPath.lastIndexOf(QLatin1Char('.')); QString domain = testPoPath.mid(pos_1 + 1, pos_2 - pos_1 - 1); QString testMoPath; testMoPath = QString::fromLatin1("%1/%3/%2.mo") .arg(dataDir.path(), domain, lcMessages); QProcess process; QStringList arguments; arguments << testPoPath << QLatin1String("-o") << testMoPath; process.start(msgfmt, arguments); process.waitForFinished(10000); if (process.exitCode() != 0) { qDebug() << QString::fromLatin1("msgfmt(1) could not compile %1.") .arg(testPoPath); return false; } } return true; } void KLocalizedStringTest::correctSubs() { if (!m_hasFrench) { QSKIP("French test files not usable."); } // Warm up. QCOMPARE(i18n("Daisies, daisies"), QString("Daisies, daisies")); // Placeholder in the middle. QCOMPARE(i18n("Fault in %1 unit", QString("AE35")), QString("Fault in AE35 unit")); // Placeholder at the start. QCOMPARE(i18n("%1, Tycho Magnetic Anomaly 1", QString("TMA-1")), QString("TMA-1, Tycho Magnetic Anomaly 1")); // Placeholder at the end. QCOMPARE(i18n("...odd things happening at %1", QString("Clavius")), QString("...odd things happening at Clavius")); QCOMPARE(i18n("Group %1", 1), QString("Group 1")); // Two placeholders. QCOMPARE(i18n("%1 and %2", QString("Bowman"), QString("Poole")), QString("Bowman and Poole")); // Two placeholders in inverted order. QCOMPARE(i18n("%2 and %1", QString("Poole"), QString("Bowman")), QString("Bowman and Poole")); // % which is not of placeholder. QCOMPARE(i18n("It's going to go %1% failure in 72 hours.", 100), QString("It's going to go 100% failure in 72 hours.")); // Usual plural. QCOMPARE(i18np("%1 pod", "%1 pods", 1), QString("1 pod")); QCOMPARE(i18np("%1 pod", "%1 pods", 10), QString("10 pods")); // No plural-number in singular. QCOMPARE(i18np("A pod", "%1 pods", 1), QString("A pod")); QCOMPARE(i18np("A pod", "%1 pods", 10), QString("10 pods")); // No plural-number in singular or plural. QCOMPARE(i18np("A pod", "Few pods", 1), QString("A pod")); QCOMPARE(i18np("A pod", "Few pods", 10), QString("Few pods")); // First of two arguments as plural-number. QCOMPARE(i18np("A pod left on %2", "%1 pods left on %2", 1, QString("Discovery")), QString("A pod left on Discovery")); QCOMPARE(i18np("A pod left on %2", "%1 pods left on %2", 2, QString("Discovery")), QString("2 pods left on Discovery")); // Second of two arguments as plural-number. QCOMPARE(i18np("%1 has a pod left", "%1 has %2 pods left", QString("Discovery"), 1), QString("Discovery has a pod left")); QCOMPARE(i18np("%1 has a pod left", "%1 has %2 pods left", QString("Discovery"), 2), QString("Discovery has 2 pods left")); // No plural-number in singular or plural, but another argument present. QCOMPARE(i18np("A pod left on %2", "Some pods left on %2", 1, QString("Discovery")), QString("A pod left on Discovery")); QCOMPARE(i18np("A pod left on %2", "Some pods left on %2", 2, QString("Discovery")), QString("Some pods left on Discovery")); // Visual formatting. // FIXME: Needs much more tests. QCOMPARE(xi18n("E = mc^2"), QString("E = mc^2")); QCOMPARE(xi18n("E < mc^2"), QString("E < mc^2")); QCOMPARE(xi18n("E ? mc^2"), QString("E ? *mc^2*")); QCOMPARE(xi18n("E < mc^2"), QString("E < *mc^2*")); QCOMPARE(xi18nc("@label", "E < mc^2"), QString("E < *mc^2*")); QCOMPARE(xi18nc("@info", "E < mc^2"), QString("E < mc^2")); QCOMPARE(xi18nc("@info:status", "E < mc^2"), QString("E < *mc^2*")); QCOMPARE(xi18nc("@info:progress", "E < mc^2"), QString("E < *mc^2*")); QCOMPARE(xi18nc("@info:tooltip", "E < mc^2"), QString("E < mc^2")); QCOMPARE(xi18nc("@info:shell", "E < mc^2"), QString("E < *mc^2*")); QCOMPARE(xi18n("E = mc^2"), QString("E = mc^2")); QCOMPARE(xi18n("E = mc^2"), QString("E = mc^2")); // with additional whitespace QCOMPARE(xi18nc(" @info:progress ", "E < mc^2"), QString("E < *mc^2*")); QCOMPARE(xi18nc(" @info:tooltip ", "E < mc^2"), QString("E < mc^2")); QCOMPARE(xi18nc(" @info: progress ", "E < mc^2"), // not parsed as a cue QString("E < mc^2")); QCOMPARE(xi18nc(" @info: tooltip ", "E < mc^2"), // not parsed as a cue QString("E < mc^2")); QTest::ignoreMessage(QtWarningMsg, "\"Unknown subcue ':doesnotexist' in UI marker in context {@info:doesnotexist}.\""); QCOMPARE(xi18nc("@info:doesnotexist", "E < mc^2"), QString("E < mc^2")); // Number formatting. QCOMPARE(ki18n("%1").subs(42).toString(), QString("42")); QCOMPARE(ki18n("%1").subs(42, 5).toString(), QString(" 42")); QCOMPARE(ki18n("%1").subs(42, -5, 10, QChar('_')).toString(), QString("42___")); QCOMPARE(ki18n("%1").subs(4.2, 5, 'f', 2).toString(), QString(" 4,20")); } void KLocalizedStringTest::wrongSubs() { #ifndef NDEBUG // Too many arguments. QVERIFY(i18n("Europa", 1) != QString("Europa")); // Too few arguments. QVERIFY(i18n("%1, %2 and %3", QString("Hunter"), QString("Kimball")) != QString("Hunter, Kimball and %3")); // Gaps in placheholder numbering. QVERIFY(ki18n("Beyond the %2").subs("infinity").toString() != QString("Beyond the infinity")); // Plural argument not supplied. QVERIFY(ki18np("1 pod", "%1 pods").toString() != QString("1 pod")); QVERIFY(ki18np("1 pod", "%1 pods").toString() != QString("%1 pods")); #endif } void KLocalizedStringTest::semanticTags() { KLocalizedString::setLanguages({"en"}); // QCOMPARE(xi18nc("@action:inmenu", "Open with %1", "Okteta"), QString("Open with Okteta")); QCOMPARE(xi18nc("@info", "Open with %1", "Okteta"), QString("Open with Okteta")); // QCOMPARE(xi18nc("@info:whatsthis", "You can try the following snippet:" "\\begin{equation}\n" " C_{x_i} = \\frac{C_z^2}{e \\pi \\lambda}\n" "\\end{equation}" ""), QString("You can try the following snippet:\n\n
"
                     "\\begin{equation}\n"
                     "  C_{x_i} = \\frac{C_z^2}{e \\pi \\lambda}\n"
                     "\\end{equation}"
                     "
")); // QCOMPARE(xi18nc("@info", "This will call %1 internally.", "true"), QString("This will call true internally.")); QCOMPARE(xi18nc("@info", "Consult man entry for %1", "true", 1), QString("Consult man entry for true(1)")); // QCOMPARE(xi18nc("@info", "Send bug reports to %1.", "konqi@kde.org"), QString("Send bug reports to <konqi@kde.org>.")); QCOMPARE(xi18nc("@info", "Send praises to %2.", "konqi@kde.org", "Konqi"), QString("Send praises to Konqi.")); // QCOMPARE(xi18nc("@info:progress", "Checking feedback circuits..."), QString("Checking *feedback* circuits...")); QCOMPARE(xi18nc("@info:progress", "Checking feedback circuits..."), QString("Checking **feedback** circuits...")); // QCOMPARE(xi18nc("@info", "Assure that your PATH is properly set."), QString("Assure that your $PATH is properly set.")); // QCOMPARE(xi18nc("@info", "Cannot read %1.", "data.dat"), QString("Cannot read data.dat.")); // TODO: is nested really wanted? #ifndef Q_OS_WIN QString homeFooRc("$HOME/.foorc does not exist."); #else //TODO $HOME -> %HOME% ? QString homeFooRc("$HOME\\.foorc does not exist."); #endif QCOMPARE(xi18nc("@info", "HOME/.foorc does not exist."), homeFooRc); // QCOMPARE(xi18nc("@info:tooltip", "Execute svn merge on selected revisions."), QString("Execute svn merge on selected revisions.")); // QCOMPARE(xi18nc("@info:whatsthis", "If you make a mistake, click Reset to start again."), QString("If you make a mistake, click Reset to start again.")); QCOMPARE(xi18nc("@info:whatsthis", "The line colors can be changed under Settings->Visuals."), QString("The line colors can be changed under Settings->Visuals.")); // QCOMPARE(xi18nc("@info:tooltip", "Go to %1 website.", "http://kde.org/"), QString("Go to http://kde.org/ website.")); QCOMPARE(xi18nc("@info:tooltip", "Go to %2.", "http://kde.org/", "the KDE website"), QString("Go to the KDE website.")); // QCOMPARE(xi18nc("@info", "The fortune cookie says: %1", "Nothing"), QString("The fortune cookie says: Nothing")); // #ifndef Q_OS_WIN QString deleteEtcPasswd("Do you really want to delete:
/etc/passwd"); #else QString deleteEtcPasswd("Do you really want to delete:
\\etc\\passwd"); #endif QCOMPARE(xi18nc("@info", "Do you really want to delete:%1", "/etc/passwd"), deleteEtcPasswd); //check within filename doesn't break (Windows path separators) #ifndef Q_OS_WIN QString filenameWithNewline("/filename/with
/newline
"); #else QString filenameWithNewline("\\filename\\with
\\newline
"); #endif QCOMPARE(xi18nc("@info", "/filename/with/newline"), filenameWithNewline); // QEXPECT_FAIL("", "what happened to ? TODO.", Continue); QCOMPARE(xi18nc("@info:progress", "Connecting to %1...", 22), QString("Connecting to 22")); QCOMPARE(xi18nc("@info", "Replace name with your name."), QString("Replace <name> with your name.")); QCOMPARE(xi18nc("@item:inlistbox", "All images"), QString("")); // QCOMPARE(xi18nc("@info", "Apply color scheme %1?", "XXX"), QString("Apply color scheme “XXX”?")); QCOMPARE(xi18nc("@info:whatsthis", "Cycle through layouts using Alt+Space."), QString("Cycle through layouts using Alt+Space.")); // QCOMPARE(xi18nc("@info", "Probably the best known of all duck species is the Mallard. " "It breeds throughout the temperate areas around the world. " "Most domestic ducks are derived from Mallard."), QString("Probably the best known of all duck species is the Mallard. " "It breeds throughout the temperate areas around the world. " "Note: Most domestic ducks are derived from Mallard.")); QCOMPARE(xi18nc("@info", "Most domestic ducks are derived from Mallard."), QString("Trivia: Most domestic ducks are derived from Mallard.")); // QCOMPARE(xi18nc("@info", "Really delete this key?" "This cannot be undone."), QString("Really delete this key?" "Warning: This cannot be undone.")); QCOMPARE(xi18nc("@info", "This cannot be undone."), QString("Danger: This cannot be undone.")); } void KLocalizedStringTest::setFormatForMarker() { KLocalizedString::setLanguages({"en"}); QCOMPARE(xi18nc("@info:tooltip", "Hello world"), QString("Hello world")); KuitSetup &setup = Kuit::setupForDomain(KLocalizedString::applicationDomain()); setup.setFormatForMarker("@info:tooltip", Kuit::PlainText); QCOMPARE(xi18nc("@info:tooltip", "Hello world"), QString("Hello world")); } void KLocalizedStringTest::removeAcceleratorMarker() { // No accelerator marker. QCOMPARE(KLocalizedString::removeAcceleratorMarker(QString()), QString()); QCOMPARE(KLocalizedString::removeAcceleratorMarker("Foo bar"), QString("Foo bar")); // Run of the mill. QCOMPARE(KLocalizedString::removeAcceleratorMarker("&Foo bar"), QString("Foo bar")); QCOMPARE(KLocalizedString::removeAcceleratorMarker("Foo &bar"), QString("Foo bar")); QCOMPARE(KLocalizedString::removeAcceleratorMarker("Foo b&ar"), QString("Foo bar")); // - presence of escaped ampersands QCOMPARE(KLocalizedString::removeAcceleratorMarker("Foo && Bar"), QString("Foo & Bar")); QCOMPARE(KLocalizedString::removeAcceleratorMarker("Foo && &Bar"), QString("Foo & Bar")); QCOMPARE(KLocalizedString::removeAcceleratorMarker("&Foo && Bar"), QString("Foo & Bar")); // CJK-style markers. QCOMPARE(KLocalizedString::removeAcceleratorMarker("Foo bar (&F)"), QString("Foo bar")); QCOMPARE(KLocalizedString::removeAcceleratorMarker("(&F) Foo bar"), QString("Foo bar")); // - interpunction after/before parenthesis still qualifies CJK marker QCOMPARE(KLocalizedString::removeAcceleratorMarker("Foo bar (&F):"), QString("Foo bar:")); QCOMPARE(KLocalizedString::removeAcceleratorMarker("Foo bar (&F)..."), QString("Foo bar...")); QCOMPARE(KLocalizedString::removeAcceleratorMarker("...(&F) foo bar"), QString("...foo bar")); // - alphanumerics around parenthesis disqualify CJK marker QCOMPARE(KLocalizedString::removeAcceleratorMarker("Foo (&F) bar"), QString("Foo (F) bar")); // - something removed raw ampersands, leaving dangling reduced CJK markers. // Remove reduced markers only if CJK characters are found in the string. QCOMPARE(KLocalizedString::removeAcceleratorMarker(QString::fromUtf8("Foo bar (F)")), QString::fromUtf8("Foo bar (F)")); QCOMPARE(KLocalizedString::removeAcceleratorMarker(QString::fromUtf8("印刷(P)...")), QString::fromUtf8("印刷...")); // Shady cases, where ampersand is obviously not a marker // and should have been escaped, but it was not. QCOMPARE(KLocalizedString::removeAcceleratorMarker("&"), QString("&")); QCOMPARE(KLocalizedString::removeAcceleratorMarker("Foo bar &"), QString("Foo bar &")); QCOMPARE(KLocalizedString::removeAcceleratorMarker("Foo & Bar"), QString("Foo & Bar")); } void KLocalizedStringTest::miscMethods() { KLocalizedString k; QVERIFY(k.isEmpty()); if (m_hasFrench) { QSet availableLanguages; availableLanguages.insert("fr"); availableLanguages.insert("en_US"); if (m_hasCatalan) { availableLanguages.insert("ca"); } QCOMPARE(KLocalizedString::availableApplicationTranslations(), availableLanguages); } } // Same as translateToFrench, but using libintl directly (bindtextdomain+dgettext). // Useful for debugging. This changes global state, though, so it's skipped by default. void KLocalizedStringTest::translateToFrenchLowlevel() { if (!m_hasFrench) { QSKIP("French test files not usable."); } QSKIP("Skipped by default to avoid changing global state."); // fr_FR locale was set by initTestCase already. if (QFile::exists("/usr/share/locale/fr/LC_MESSAGES/ki18n-test.mo")) { bindtextdomain("ki18n-test", "/usr/share/locale"); QCOMPARE(QString::fromUtf8(dgettext("ki18n-test", "Loadable modules")), QString::fromUtf8("Modules chargeables")); } } void KLocalizedStringTest::translateToFrench() { if (!m_hasFrench) { QSKIP("French test files not usable."); } QCOMPARE(i18n("Loadable modules"), QString::fromUtf8("Modules chargeables")); QCOMPARE(i18n("Job"), QString::fromUtf8("Tâche")); } #if KI18N_ENABLE_DEPRECATED_SINCE(5, 0) void KLocalizedStringTest::translateQt() { KLocalizedString::insertQtDomain("ki18n-test-qt"); QString result = KLocalizedString::translateQt("QPrintPreviewDialog", "Landscape", nullptr, 0); // When we use the default language, translateQt returns an empty string. QString expected = m_hasFrench ? QString("Paysage") : QString(); QCOMPARE(result, expected); #if 0 // KLocalizedString no longer does anything with QTranslator, this needed? result = QCoreApplication::translate("QPrintPreviewDialog", "Landscape"); QString expected2 = m_hasFrench ? QString("Paysage") : QString("Landscape"); QCOMPARE(result, expected2); #endif #if 0 // translateRaw no longer public, this needed? // So let's use translateRaw instead for the threaded test QString lang; KLocale::global()->translateRaw("Landscape", &lang, &result); QCOMPARE(lang, m_hasFrench ? QString("fr") : QString("en_US")); QCOMPARE(result, m_hasFrench ? QString("Paysage") : QString("Landscape")); #endif KLocalizedString::removeQtDomain("ki18n-test-qt"); } #endif void KLocalizedStringTest::testLocalizedTranslator() { if (!m_hasFrench) { QSKIP("French test files not usable."); } QScopedPointer translator(new KLocalizedTranslator()); QCoreApplication *app = QCoreApplication::instance(); app->installTranslator(translator.data()); // no translation domain and no context QCOMPARE(app->translate("foo", "Job"), QStringLiteral("Job")); // adding the translation domain still lacks the context translator->setTranslationDomain(QStringLiteral("ki18n-test")); QCOMPARE(app->translate("foo", "Job"), QStringLiteral("Job")); translator->addContextToMonitor(QStringLiteral("foo")); // now it should translate QCOMPARE(app->translate("foo", "Job"), QStringLiteral("Tâche")); // other context shouldn't translate QCOMPARE(app->translate("bar", "Job"), QStringLiteral("Job")); // with a mismatching disambiguation it shouldn't translate QCOMPARE(app->translate("foo", "Job", "bar"), QStringLiteral("Job")); } void KLocalizedStringTest::addCustomDomainPath() { if (!m_hasFrench) { QSKIP("French test files not usable."); } QTemporaryDir dir; compileCatalogs({QFINDTESTDATA("po/fr/ki18n-test2.po")}, dir.path(), "fr"); KLocalizedString::addDomainLocaleDir("ki18n-test2", dir.path() + "/locale"); QSet expectedAvailableTranslations({"en_US", "fr"}); QCOMPARE(KLocalizedString::availableDomainTranslations("ki18n-test2"), expectedAvailableTranslations); QCOMPARE(i18nd("ki18n-test2", "Cheese"), QString::fromUtf8("Fromage")); } void KLocalizedStringTest::multipleLanguages() { if (!m_hasFrench || !m_hasCatalan) { QSKIP("French or Catalan test files not usable."); } KLocalizedString::setLanguages({"ca"}); QCOMPARE(i18n("Job"), QString::fromUtf8("Job")); // This is not the actual catalan translation but who cares KLocalizedString::setLanguages({"fr"}); QCOMPARE(i18n("Job"), QString::fromUtf8("Tâche")); KLocalizedString::setLanguages({"ca", "fr"}); QCOMPARE(i18n("Job"), QString::fromUtf8("Job")); // This is not the actual catalan translation but who cares KLocalizedString::setLanguages({"ca"}); QCOMPARE(i18n("Loadable modules"), QString::fromUtf8("Loadable modules")); // The po doesn't have a translation so we get the English text KLocalizedString::setLanguages({"fr"}); QCOMPARE(i18n("Loadable modules"), QString::fromUtf8("Modules chargeables")); KLocalizedString::setLanguages({"ca", "fr"}); QCOMPARE(i18n("Loadable modules"), QString::fromUtf8("Modules chargeables")); // The Catalan po doesn't have a translation so we get the English text } void KLocalizedStringTest::untranslatedText() { if (!m_hasFrench) { QSKIP("French test files not usable."); } KLocalizedString s = ki18n("Job"); KLocalizedString::setLanguages({"fr"}); QCOMPARE(s.untranslatedText(), "Job"); QCOMPARE(s.toString(), QString::fromUtf8("Tâche")); QCOMPARE(s.untranslatedText(), "Job"); } void KLocalizedStringTest::brokenTags() { QTest::ignoreMessage(QtWarningMsg, QRegularExpression("Markup error in message {.*}: Opening and ending tag mismatch.. Last tag parsed: email. Complete message follows")); QCOMPARE(xi18nc("@info", "Send bug reports to %1.", "konqi@kde.org"), // notice the missing '/' before "email" QString("Send bug reports to konqi@kde.org.")); } #include #include #include void KLocalizedStringTest::testThreads() { QThreadPool::globalInstance()->setMaxThreadCount(10); QFutureSynchronizer sync; sync.addFuture(QtConcurrent::run(this, &KLocalizedStringTest::correctSubs)); sync.addFuture(QtConcurrent::run(this, &KLocalizedStringTest::correctSubs)); sync.addFuture(QtConcurrent::run(this, &KLocalizedStringTest::correctSubs)); #if KI18N_ENABLE_DEPRECATED_SINCE(5, 0) sync.addFuture(QtConcurrent::run(this, &KLocalizedStringTest::translateQt)); sync.addFuture(QtConcurrent::run(this, &KLocalizedStringTest::translateQt)); #endif sync.addFuture(QtConcurrent::run(this, &KLocalizedStringTest::translateToFrench)); sync.waitForFinished(); QThreadPool::globalInstance()->setMaxThreadCount(1); // delete those threads } QTEST_MAIN(KLocalizedStringTest) diff --git a/autotests/klocalizedstringtest.h b/autotests/klocalizedstringtest.h index e24a861..bcd0c6c 100644 --- a/autotests/klocalizedstringtest.h +++ b/autotests/klocalizedstringtest.h @@ -1,58 +1,46 @@ /* This file is part of the KDE libraries - Copyright (c) 2005 Thomas Braxton + SPDX-FileCopyrightText: 2005 Thomas Braxton - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - 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 - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-License-Identifier: LGPL-2.0-only */ #ifndef KLOCALIZEDSTRINGTEST_H #define KLOCALIZEDSTRINGTEST_H #include #include #include class KLocalizedStringTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void correctSubs(); void wrongSubs(); void removeAcceleratorMarker(); void miscMethods(); void translateToFrenchLowlevel(); void translateToFrench(); #if KI18N_ENABLE_DEPRECATED_SINCE(5, 0) void translateQt(); #endif void addCustomDomainPath(); void testThreads(); void testLocalizedTranslator(); void semanticTags(); void setFormatForMarker(); void multipleLanguages(); void untranslatedText(); void brokenTags(); private: bool m_hasFrench; bool m_hasCatalan; QTemporaryDir m_tempDir; bool compileCatalogs(const QStringList &catalogs, const QDir &dataDir, const QString &language); }; #endif // KLOCALIZEDSTRINGTEST_H diff --git a/autotests/ktranscriptcleantest.cpp b/autotests/ktranscriptcleantest.cpp index 73288c3..32f8883 100644 --- a/autotests/ktranscriptcleantest.cpp +++ b/autotests/ktranscriptcleantest.cpp @@ -1,99 +1,85 @@ /* -Copyright 2014 Kevin Krammer - -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) version 3, or any -later version accepted by the membership of KDE e.V. (or its -successor approved by the membership of KDE e.V.), which shall -act as a proxy defined in Section 6 of version 3 of the license. - -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 . + SPDX-FileCopyrightText: 2014 Kevin Krammer + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #include #include #include #include QTEST_MAIN(KTranscriptCleanTest) KTranscriptCleanTest::KTranscriptCleanTest() : m_transcript(nullptr) { } void KTranscriptCleanTest::init() { m_transcript = autotestCreateKTranscriptImp(); } void KTranscriptCleanTest::cleanup() { autotestDestroyKTranscriptImp(); m_transcript = nullptr; } void KTranscriptCleanTest::test_data() { QTest::addColumn("argv"); QTest::addColumn("fallsBack"); QTest::addColumn("expected"); // Example test case, replace with first clean-slate test QTest::newRow("test_basic") << (QVariantList() << "test_basic" << "foo") << false << "foo bar"; } void KTranscriptCleanTest::test() { QFETCH(QVariantList, argv); QFETCH(bool, fallsBack); QFETCH(QString, expected); QString language = "fr"; QString country = "fr"; QString msgctxt = "a-context"; QHash dynamicContext; dynamicContext.insert("origin", "neverwhere"); QString msgid = "source-text"; QStringList subs; subs << "10" << "qwyx"; QList values; values << 10 << "qwyx"; QString ordinaryTranslation = "translated-text"; QString testJs = QFINDTESTDATA("test.js"); QList modules; modules << (QStringList() << testJs << language); QString error; bool fallback; QString result = m_transcript->eval( argv, language, country, msgctxt, dynamicContext, msgid, subs, values, ordinaryTranslation, modules, error, fallback); if (!error.isEmpty()) { QFAIL(qPrintable(error)); } if (!fallsBack) { QVERIFY(!fallback); QCOMPARE(result, expected); } else { QVERIFY(fallback); } } diff --git a/autotests/ktranscriptcleantest.h b/autotests/ktranscriptcleantest.h index 22e466e..96c20ad 100644 --- a/autotests/ktranscriptcleantest.h +++ b/autotests/ktranscriptcleantest.h @@ -1,60 +1,46 @@ /* -Copyright 2014 Kevin Krammer - -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) version 3, or any -later version accepted by the membership of KDE e.V. (or its -successor approved by the membership of KDE e.V.), which shall -act as a proxy defined in Section 6 of version 3 of the license. - -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 . + SPDX-FileCopyrightText: 2014 Kevin Krammer + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #ifndef KTRANSCRIPTCLEANTEST_H #define KTRANSCRIPTCLEANTEST_H #include #include class KTranscript; /** * @brief Test the KTranscript implementation class * * Runs tests on the KTranscriptImp scripting facility. * * The main difference to the test ktranscripttest is that it * creates a new instance of KTranscriptImp for each test while * the main test re-uses one instance due to internal use of * Q_GLOBAL_STATIC * * Test that require a "clean slate" can be added here, tests that do not * should be added to both. */ class KTranscriptCleanTest : public QObject { Q_OBJECT public: KTranscriptCleanTest(); private Q_SLOTS: void init(); void cleanup(); void test_data(); void test(); private: QLibrary m_library; KTranscript *m_transcript; }; #endif /* KTRANSCRIPTCLEANTEST_H */ diff --git a/autotests/ktranscripttest.cpp b/autotests/ktranscripttest.cpp index badd118..2098137 100644 --- a/autotests/ktranscripttest.cpp +++ b/autotests/ktranscripttest.cpp @@ -1,234 +1,220 @@ /* -Copyright 2013 Aurélien Gâteau -Copyright (C) 2014 Chusslove Illich -Copyright (C) 2014 Kevin Krammer - -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) version 3, or any -later version accepted by the membership of KDE e.V. (or its -successor approved by the membership of KDE e.V.), which shall -act as a proxy defined in Section 6 of version 3 of the license. - -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 . + SPDX-FileCopyrightText: 2013 Aurélien Gâteau + SPDX-FileCopyrightText: 2014 Chusslove Illich + SPDX-FileCopyrightText: 2014 Kevin Krammer + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #include "ktranscripttest.h" #include "testhelpers.h" #include #include #include QTEST_MAIN(KTranscriptTest) extern "C" { typedef KTranscript *(*InitFunc)(); } KTranscriptTest::KTranscriptTest() : m_transcript(nullptr) { } void KTranscriptTest::initTestCase() { QVERIFY2(deployTestConfig(), "Could not deploy test ktranscript.ini"); QString pluginPath = QStringLiteral(KTRANSCRIPT_PATH); QVERIFY2(QFile::exists(pluginPath), "Could not find ktranscript plugin"); m_library.setFileName(pluginPath); QVERIFY(m_library.load()); InitFunc initf = (InitFunc) m_library.resolve("load_transcript"); QVERIFY(initf); m_transcript = initf(); QVERIFY(m_transcript); } void KTranscriptTest::cleanupTestCase() { QVERIFY2(removeTestConfig(), "Could not remove test ktranscript.ini"); } void KTranscriptTest::test_data() { QTest::addColumn("argv"); QTest::addColumn("fallsBack"); QTest::addColumn("expected"); QTest::newRow("test_basic") << (QVariantList() << "test_basic" << "foo") << false << "foo bar"; QTest::newRow("test_unicode") << (QVariantList() << "test_unicode" << "čгσィ九") << false << "čгσィ九 фу"; QTest::newRow("test_hascall") << (QVariantList() << "test_hascall" << "test_basic") << false << "yes"; QTest::newRow("test_acall") << (QVariantList() << "test_acall" << "test_basic" << "qwyx") << false << "qwyx bar"; QTest::newRow("test_load") << (QVariantList() << "test_load") << false << "foo blurb"; QTest::newRow("test_fallback") << (QVariantList() << "test_fallback") << true << ""; QTest::newRow("test_msgid") << (QVariantList() << "test_msgid") << false << "source-text"; QTest::newRow("test_msgtrf") << (QVariantList() << "test_msgtrf") << false << "translated-text"; QTest::newRow("test_msgctxt") << (QVariantList() << "test_msgctxt") << false << "a-context"; QTest::newRow("test_msgkey") << (QVariantList() << "test_msgkey") << false << "a-context|source-text"; QTest::newRow("test_nsubs") << (QVariantList() << "test_nsubs") << false << "2"; QTest::newRow("test_subs") << (QVariantList() << "test_subs" << 1) << false << "qwyx"; QTest::newRow("test_vals") << (QVariantList() << "test_vals" << 0 << 5) << false << "50"; QTest::newRow("test_dynctxt") << (QVariantList() << "test_dynctxt" << "origin") << false << "neverwhere"; QTest::newRow("test_dbgputs") << (QVariantList() << "test_dbgputs") << false << "debugged"; QTest::newRow("test_warnputs") << (QVariantList() << "test_warnputs") << false << "warned"; QTest::newRow("test_setcallForall") << (QVariantList() << "test_setcallForall") << false << "done"; QTest::newRow("test_toUpperFirst") << (QVariantList() << "test_toUpperFirst" << "...123 foo") << false << "...123 Foo"; QTest::newRow("test_toUpperFirst_unicode") << (QVariantList() << "test_toUpperFirst" << "...123 фу") << false << "...123 Фу"; QTest::newRow("test_toLowerFirst") << (QVariantList() << "test_toLowerFirst" << "...123 FOO") << false << "...123 fOO"; QTest::newRow("test_loadProps") << (QVariantList() << "test_loadProps" << "cities") << false << "loaded"; QTest::newRow("test_getProp") << (QVariantList() << "test_getProp" << "cities" << "Athens" << "gen") << false << "Atine"; QTest::newRow("test_setProp") << (QVariantList() << "test_setProp" << "Oslo" << "dat" << "Oslou") << false << "Oslou"; QTest::newRow("test_normKey") << (QVariantList() << "test_normKey" << "Some &Thing") << false << "something"; QTest::newRow("test_getConfString") << (QVariantList() << "test_getConfString" << "StringKey") << false << "StringValue"; QTest::newRow("test_getConfStringWithDefault") << (QVariantList() << "test_getConfStringWithDefault" << "NoSuchKey" << "DefaultValue") << false << "DefaultValue"; QTest::newRow("test_getConfBool") << (QVariantList() << "test_getConfBool" << "BoolKey") << false << "true"; QTest::newRow("test_getConfBoolWithDefault") << (QVariantList() << "test_getConfBoolWithDefault" << "NoSuchKey" << true) << false << "true"; QTest::newRow("test_getConfNumber") << (QVariantList() << "test_getConfNumber" << "NumberKey") << false << "12345"; QTest::newRow("test_getConfNumberWithDefault") << (QVariantList() << "test_getConfNumberWithDefault" << "NoSuchKey" << 54321) << false << "54321"; } void KTranscriptTest::test() { QFETCH(QVariantList, argv); QFETCH(bool, fallsBack); QFETCH(QString, expected); QString language = "fr"; QString country = "fr"; QString msgctxt = "a-context"; QHash dynamicContext; dynamicContext.insert("origin", "neverwhere"); QString msgid = "source-text"; QStringList subs; subs << "10" << "qwyx"; QList values; values << 10 << "qwyx"; QString ordinaryTranslation = "translated-text"; QString testJs = QFINDTESTDATA("test.js"); QList modules; modules << (QStringList() << testJs << language); QString error; bool fallback; QString result = m_transcript->eval( argv, language, country, msgctxt, dynamicContext, msgid, subs, values, ordinaryTranslation, modules, error, fallback); if (!error.isEmpty()) { QFAIL(qPrintable(error)); } if (!fallsBack) { QVERIFY(!fallback); QCOMPARE(result, expected); } else { QVERIFY(fallback); } } diff --git a/autotests/ktranscripttest.h b/autotests/ktranscripttest.h index 68cb820..59b1b2e 100644 --- a/autotests/ktranscripttest.h +++ b/autotests/ktranscripttest.h @@ -1,58 +1,44 @@ /* -Copyright 2013 Aurélien Gâteau - -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) version 3, or any -later version accepted by the membership of KDE e.V. (or its -successor approved by the membership of KDE e.V.), which shall -act as a proxy defined in Section 6 of version 3 of the license. - -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 . + SPDX-FileCopyrightText: 2013 Aurélien Gâteau + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #ifndef KTRANSCRIPTTEST_H #define KTRANSCRIPTTEST_H #include #include class KTranscript; /** * @brief Test the KTranscript plugin * * Loads the KTranscript plugin and runs tests on its scripting capabilities. * * The main difference to the test ktranscriptcleantest is that it does so using * a single instance of the KTranscript implementation due to the plugin * using Q_GLOBAL_STATIC * * ktranscriptcleantest on the other hand creates and destroys the instance between * tests. Test that require a "clean slate" can be added there. */ class KTranscriptTest : public QObject { Q_OBJECT public: KTranscriptTest(); private Q_SLOTS: void initTestCase(); void cleanupTestCase(); void test_data(); void test(); private: QLibrary m_library; KTranscript *m_transcript; }; #endif /* KTRANSCRIPTTEST_H */ diff --git a/autotests/testhelpers.cpp b/autotests/testhelpers.cpp index dbb1afc..de3d145 100644 --- a/autotests/testhelpers.cpp +++ b/autotests/testhelpers.cpp @@ -1,75 +1,62 @@ /* This file is part of the KI18N Framework - Copyright (C) 2014 Kevin Krammer + SPDX-FileCopyrightText: 2014 Kevin Krammer - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library 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 - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-License-Identifier: LGPL-2.0-or-later */ #include "testhelpers.h" #include #include #include #include #include #include static const QString targetFileName = QStringLiteral("ktranscript.ini"); bool deployTestConfig() { QStandardPaths::setTestModeEnabled(true); QDir configDir = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); const QFileInfo targetFile = QFileInfo(configDir, targetFileName); if (!configDir.exists()) { if (!QDir::current().mkpath(configDir.absolutePath())) { qWarning() << "Failed to create config dir" << configDir.absolutePath(); return false; } } else if (targetFile.exists()) { if (!configDir.remove(targetFileName)) { qWarning() << "Failed to remove existing test config file" << targetFile.absoluteFilePath(); return false; } } QFile sourceFile(QFINDTESTDATA("ktranscript-test.ini")); if (!sourceFile.exists()) { qWarning() << "Could not locate test data file" << sourceFile.fileName(); return false; } if (!sourceFile.copy(targetFile.absoluteFilePath())) { qWarning() << "Failed to copy test config file" << sourceFile.fileName() << "to target location" << targetFile.absoluteFilePath(); return false; } return true; } bool removeTestConfig() { Q_ASSERT(QStandardPaths::isTestModeEnabled()); QDir configDir = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); if (!configDir.exists()) { return true; } return configDir.remove(targetFileName); } diff --git a/autotests/testhelpers.h b/autotests/testhelpers.h index 06c7b04..7f27476 100644 --- a/autotests/testhelpers.h +++ b/autotests/testhelpers.h @@ -1,28 +1,15 @@ /* This file is part of the KI18N Framework - Copyright (C) 2014 Kevin Krammer + SPDX-FileCopyrightText: 2014 Kevin Krammer - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library 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 - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-License-Identifier: LGPL-2.0-or-later */ #ifndef TESTHELPERS_H #define TESTHELPERS_H #include bool deployTestConfig(); bool removeTestConfig(); #endif // TESTHELPERS_H diff --git a/cmake/FindLibIntl.cmake b/cmake/FindLibIntl.cmake index e669fe1..a7f9167 100644 --- a/cmake/FindLibIntl.cmake +++ b/cmake/FindLibIntl.cmake @@ -1,71 +1,49 @@ #.rst: # FindLibIntl # --------- # # Find libintl # # Find the libintl headers and libraries. On platforms that use glibc this is not required # and LibIntl_LIBRARIES will be empty # # :: # # LibIntl_INCLUDE_DIRS - where to find libintl.h # LibIntl_LIBRARIES - The libintl library if the current platform does not use glibc. # LibIntl_FOUND - True if libintl was found. #============================================================================= -# Copyright 2014 Alex Richardson +# SPDX-FileCopyrightText: 2014 Alex Richardson # -# 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. -# 3. Neither the name of the University nor the names of its contributors -# may be used to endorse or promote products derived from this software -# without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 THE REGENTS 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. +# SPDX-License-Identifier: BSD-3-Clause #============================================================================= find_path(LibIntl_INCLUDE_DIRS NAMES libintl.h) find_library(LibIntl_LIBRARIES NAMES intl libintl) include(CheckCXXSymbolExists) include(CMakePushCheckState) check_cxx_symbol_exists(dngettext libintl.h LibIntl_SYMBOL_FOUND) include(FindPackageHandleStandardArgs) if(LibIntl_SYMBOL_FOUND) message(STATUS "libintl is part of libc, no extra library is required.") set(LibIntl_LIBRARIES "") if(LibIntl_INCLUDE_DIRS) find_package_handle_standard_args(LibIntl REQUIRED_VARS LibIntl_INCLUDE_DIRS) else() # in the default search path but not found by find_path, e.g. host glibc when cross-compiling set(LibIntl_INCLUDE_DIRS "") set(LibIntl_FOUND TRUE) endif() else() message(STATUS "libintl is a separate library.") find_package_handle_standard_args(LibIntl REQUIRED_VARS LibIntl_INCLUDE_DIRS LibIntl_LIBRARIES) endif() # make sure we have -Wl,--no-undefined here, otherwise this test will always pass cmake_push_check_state() set(CMAKE_REQUIRED_LIBRARIES ${LibIntl_LIBRARIES} ${CMAKE_SHARED_LINKER_FLAGS}) check_cxx_source_compiles("extern \"C\" int _nl_msg_cat_cntr; int main(void) { ++_nl_msg_cat_cntr; return 0; }" HAVE_NL_MSG_CAT_CNTR) cmake_pop_check_state() diff --git a/cmake/KF5I18nMacros.cmake.in b/cmake/KF5I18nMacros.cmake.in index 1b794a8..5ec5ffa 100644 --- a/cmake/KF5I18nMacros.cmake.in +++ b/cmake/KF5I18nMacros.cmake.in @@ -1,219 +1,196 @@ - -# Copyright (c) 2006, Alexander Neundorf, -# -# 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. -# 3. Neither the name of the University nor the names of its contributors -# may be used to endorse or promote products derived from this software -# without specific prior written permission. +# SPDX-FileCopyrightText: 2006 Alexander Neundorf # -# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 THE REGENTS 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. +# SPDX-License-Identifier: BSD-3-Clause find_package(Gettext REQUIRED) # The Python executable used for building ki18n will be used as a fallback # solution if it cannot be found in $PATH when building applications. set(_KI18N_PYTHON_EXECUTABLE "@PYTHON_EXECUTABLE@") find_program(KI18N_PYTHON_EXECUTABLE NAMES python3 python2 python) if(NOT KI18N_PYTHON_EXECUTABLE) set(KI18N_PYTHON_EXECUTABLE "${_KI18N_PYTHON_EXECUTABLE}") endif() set(_ki18n_pmap_compile_script ${CMAKE_CURRENT_LIST_DIR}/ts-pmap-compile.py) set(_ki18n_uic_script ${CMAKE_CURRENT_LIST_DIR}/kf5i18nuic.cmake) set(_ki18n_build_pofiles_script ${CMAKE_CURRENT_LIST_DIR}/build-pofiles.cmake) set(_ki18n_build_tsfiles_script ${CMAKE_CURRENT_LIST_DIR}/build-tsfiles.cmake) #create the implementation files from the ui files and add them to the list of sources #usage: KI18N_WRAP_UI(foo_SRCS ${ui_files}) or KI18N_WRAP_UI(TARGET ${ui_files}) macro (KI18N_WRAP_UI _sources ) if(NOT TARGET Qt5::uic) message(FATAL_ERROR "Qt5Widgets should be found before calling ki18n_wrap_ui(). Please add find_package(Qt5Widgets ...)") endif() foreach (_current_FILE ${ARGN}) get_filename_component(_tmp_FILE ${_current_FILE} ABSOLUTE) if(NOT EXISTS ${_tmp_FILE}) message(SEND_ERROR " Cannot find ui file:\n \n" " ${_current_FILE}\n") endif() get_filename_component(_basename ${_tmp_FILE} NAME_WE) set(_header ${CMAKE_CURRENT_BINARY_DIR}/ui_${_basename}.h) get_target_property(QT_UIC_EXECUTABLE Qt5::uic LOCATION) # we need to run uic and replace some things in the generated file # this is done by executing the cmake script kf5i18nuic.cmake add_custom_command(OUTPUT ${_header} COMMAND ${CMAKE_COMMAND} ARGS -DKDE_UIC_EXECUTABLE:FILEPATH=${QT_UIC_EXECUTABLE} -DKDE_UIC_FILE:FILEPATH=${_tmp_FILE} -DKDE_UIC_H_FILE:FILEPATH=${_header} -DKDE_UIC_BASENAME:STRING=${_basename} -P ${_ki18n_uic_script} MAIN_DEPENDENCY ${_tmp_FILE} ) set_source_files_properties(${_header} PROPERTIES SKIP_AUTOMOC ON) if(TARGET ${_sources}) target_sources(${_sources} PRIVATE ${_header}) else() list(APPEND ${_sources} ${_header}) endif() endforeach (_current_FILE) endmacro (KI18N_WRAP_UI) # KI18N_INSTALL(podir) # Search for .po files and scripting modules and install them to the standard # location. # # This is a convenience function which relies on the following directory # structure: # # / # / # scripts/ # / # *.js # *.po # # .po files are passed to build-pofiles.cmake # # .js files are installed using build-tsfiles.cmake # # For example, given the following directory structure: # # po/ # fr/ # scripts/ # kfoo/ # kfoo.js # kfoo.po # # KI18N_INSTALL(po) does the following: # - Compiles kfoo.po into kfoo.mo and installs it in # ${LOCALE_INSTALL_DIR}/fr/LC_MESSAGES or share/locale/fr/LC_MESSAGES if # ${LOCALE_INSTALL_DIR} is not set. # - Installs kfoo.js in ${LOCALE_INSTALL_DIR}/fr/LC_SCRIPTS/kfoo # # KI18N_INSTALL_TS_FILES() is deprecated, use KI18N_INSTALL() # function(KI18N_INSTALL podir) if (NOT LOCALE_INSTALL_DIR) set(LOCALE_INSTALL_DIR share/locale) endif() # First try to find the po directory in the source directory, where the release scripts copy them before making the tarballs get_filename_component(absolute_podir ${podir} ABSOLUTE) # we try to find the po directory in the binary directory, in case it was downloaded # using ECM if (NOT (EXISTS "${absolute_podir}" AND IS_DIRECTORY "${absolute_podir}")) get_filename_component(absolute_podir ${CMAKE_CURRENT_BINARY_DIR}/${podir} ABSOLUTE) endif() if (NOT (EXISTS "${absolute_podir}" AND IS_DIRECTORY "${absolute_podir}")) # Nothing to do if there's no podir and it would create an empty # LOCALE_INSTALL_DIR in that case. return() endif() get_filename_component(dirname ${LOCALE_INSTALL_DIR} NAME) get_filename_component(destname ${LOCALE_INSTALL_DIR} DIRECTORY) string(MD5 pathmd5 ${absolute_podir}) add_custom_target(pofiles-${pathmd5} ALL COMMENT "Generating mo..." COMMAND ${CMAKE_COMMAND} -DGETTEXT_MSGFMT_EXECUTABLE=${GETTEXT_MSGFMT_EXECUTABLE} -DCOPY_TO=${CMAKE_CURRENT_BINARY_DIR}/${dirname} -DPO_DIR=${absolute_podir} -P ${_ki18n_build_pofiles_script} ) add_custom_target(tsfiles-${pathmd5} ALL COMMENT "Generating ts..." COMMAND ${CMAKE_COMMAND} -DPYTHON_EXECUTABLE=${KI18N_PYTHON_EXECUTABLE} -D_ki18n_pmap_compile_script=${_ki18n_pmap_compile_script} -DCOPY_TO=${CMAKE_CURRENT_BINARY_DIR}/${dirname} -DPO_DIR=${absolute_podir} -P ${_ki18n_build_tsfiles_script} ) if (NOT TARGET pofiles) add_custom_target(pofiles) endif() if (NOT TARGET tsfiles) add_custom_target(tsfiles) endif() add_dependencies(pofiles pofiles-${pathmd5}) add_dependencies(tsfiles tsfiles-${pathmd5}) file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${dirname}) install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${dirname} DESTINATION ${destname}) endfunction() #install the scripts for a given language in the target folder #usage: KI18N_INSTALL_TS_FILES("ja" ${scripts_dir}) function(KI18N_INSTALL_TS_FILES lang scripts_dir) file(GLOB_RECURSE ts_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ${scripts_dir}/*) set(pmapc_files) foreach(ts_file ${ts_files}) string(REGEX MATCH "\\.svn/" in_svn ${ts_file}) if(NOT in_svn) # If ts_file is "path/to/foo/bar.js" # We want subpath to contain "foo" get_filename_component(subpath ${ts_file} DIRECTORY) get_filename_component(subpath ${subpath} NAME) install(FILES ${ts_file} DESTINATION ${LOCALE_INSTALL_DIR}/${lang}/LC_SCRIPTS/${subpath}) # If current file is a pmap, also install the compiled version. get_filename_component(ts_ext ${ts_file} EXT) if(ts_ext STREQUAL ".pmap") set(pmap_file ${ts_file}) get_filename_component(pmap_basename ${ts_file} NAME) set(pmapc_basename "${pmap_basename}c") set(pmapc_file "${lang}-${subpath}-${pmapc_basename}") add_custom_command(OUTPUT ${pmapc_file} COMMAND ${KI18N_PYTHON_EXECUTABLE} ARGS -B ${_ki18n_pmap_compile_script} ${CMAKE_CURRENT_SOURCE_DIR}/${pmap_file} ${pmapc_file} DEPENDS ${pmap_file}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${pmapc_file} DESTINATION ${LOCALE_INSTALL_DIR}/${lang}/LC_SCRIPTS/${subpath} RENAME ${pmapc_basename}) list(APPEND pmapc_files ${pmapc_file}) endif() endif() endforeach() if(pmapc_files) if(NOT TARGET pmapfiles) add_custom_target(pmapfiles) endif() set(pmapc_target "pmapfiles-${lang}") string(REPLACE "@" "_" pmapc_target ${pmapc_target}) add_custom_target(${pmapc_target} ALL DEPENDS ${pmapc_files}) add_dependencies(pmapfiles ${pmapc_target}) endif() endfunction() diff --git a/cmake/build-pofiles.cmake b/cmake/build-pofiles.cmake index b39be31..e88272c 100644 --- a/cmake/build-pofiles.cmake +++ b/cmake/build-pofiles.cmake @@ -1,69 +1,47 @@ -# Copyright (c) 2017 Aleix Pol Gonzalez -# Copyright (c) 2017 Harald Sitter +# SPDX-FileCopyrightText: 2017 Aleix Pol Gonzalez +# SPDX-FileCopyrightText: 2017 Harald Sitter # -# 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. -# 3. Neither the name of the University nor the names of its contributors -# may be used to endorse or promote products derived from this software -# without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 THE REGENTS 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. +# SPDX-License-Identifier: BSD-3-Clause file(GLOB_RECURSE pofiles RELATIVE "${PO_DIR}" "${PO_DIR}/**.po") include(ProcessorCount) ProcessorCount(numberOfProcesses) set(i 0) set(commands) function(_processCommands) if(NOT commands) return() endif() execute_process( ${commands} RESULT_VARIABLE code ) if(code) message(FATAL_ERROR "failed generating ${PO_DIR}") endif() endfunction() foreach(pofile IN LISTS pofiles) get_filename_component(name ${pofile} NAME) # Regex the basename, cmake only allows stripping the longest extension, we # need the shortest or we'll screw up "org.kde.plasma.kittens.po" # https://bugs.kde.org/show_bug.cgi?id=379116 string(REGEX REPLACE "^(.+)(\\.[^.]+)$" "\\1" name ${name}) get_filename_component(langdir ${pofile} DIRECTORY) set(dest ${COPY_TO}/${langdir}/LC_MESSAGES) file(MAKE_DIRECTORY ${dest}) list(APPEND commands COMMAND ${GETTEXT_MSGFMT_EXECUTABLE} -o ${dest}/${name}.mo ${PO_DIR}/${pofile}) math(EXPR i "${i}+1") if(i EQUAL ${numberOfProcesses}) _processCommands() set(i 0) set(commands) endif() endforeach() _processCommands() diff --git a/cmake/build-tsfiles.cmake b/cmake/build-tsfiles.cmake index e91fe81..77d3048 100644 --- a/cmake/build-tsfiles.cmake +++ b/cmake/build-tsfiles.cmake @@ -1,83 +1,61 @@ -# Copyright (c) 2017 Aleix Pol Gonzalez +# SPDX-FileCopyrightText: 2017 Aleix Pol Gonzalez # -# 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. -# 3. Neither the name of the University nor the names of its contributors -# may be used to endorse or promote products derived from this software -# without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 THE REGENTS 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. +# SPDX-License-Identifier: BSD-3-Clause file(GLOB_RECURSE ts_files RELATIVE ${PO_DIR} ${PO_DIR}/**/scripts/*) foreach(ts_file ${ts_files}) if(ts_file MATCHES "\\.svn") continue() endif() get_filename_component(subpath ${ts_file} DIRECTORY) string(REPLACE "scripts" "LC_SCRIPTS" subpath ${subpath}) message(STATUS "copying... ${PO_DIR}/${ts_file} DESTINATION ${COPY_TO}/${subpath}" ) file(COPY ${PO_DIR}/${ts_file} DESTINATION ${COPY_TO}/${subpath}) endforeach() include(ProcessorCount) ProcessorCount(numberOfProcesses) set(i 0) set(commands) function(_processCommands) if(NOT commands) return() endif() execute_process( ${commands} RESULT_VARIABLE code ) if(code) message(FATAL_ERROR "failed generating: ${PO_DIR}") endif() endfunction() file(GLOB_RECURSE pmap_files RELATIVE ${PO_DIR} "${PO_DIR}/**.pmap") foreach(pmap_file ${pmap_files}) get_filename_component(pmap_basename ${pmap_file} NAME) get_filename_component(subpath ${pmap_file} DIRECTORY) string(REPLACE "scripts" "LC_SCRIPTS" subpath ${subpath}) set(pmapc_file "${COPY_TO}/${subpath}/${pmap_basename}c") message(STATUS "building... ${pmap_file} to ${pmapc_file}" ) list(APPEND commands COMMAND ${PYTHON_EXECUTABLE} -B ${_ki18n_pmap_compile_script} ${PO_DIR}/${pmap_file} ${pmapc_file} ) math(EXPR i "${i}+1") if (i EQUAL ${numberOfProcesses}) _processCommands() set(i 0) endif() endforeach() _processCommands() diff --git a/cmake/kf5i18nuic.cmake b/cmake/kf5i18nuic.cmake index f7ce3ff..befca06 100644 --- a/cmake/kf5i18nuic.cmake +++ b/cmake/kf5i18nuic.cmake @@ -1,49 +1,26 @@ - -# Copyright (c) 2006, Alexander Neundorf, -# -# 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. -# 3. Neither the name of the University nor the names of its contributors -# may be used to endorse or promote products derived from this software -# without specific prior written permission. +# SPDX-FileCopyrightText: 2006 Alexander Neundorf # -# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 THE REGENTS 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. +# SPDX-License-Identifier: BSD-3-Clause EXECUTE_PROCESS(COMMAND ${KDE_UIC_EXECUTABLE} -tr tr2i18n ${KDE_UIC_FILE} OUTPUT_VARIABLE _uic_CONTENTS ) set(KDE_UIC_CPP_FILE ${KDE_UIC_H_FILE}) IF (_uic_CONTENTS) #replace tr218n("") with QString() to avoid warning from KLocale STRING(REGEX REPLACE "tr2i18n\\(\"\"\\)" "QString\(\)" _uic_CONTENTS "${_uic_CONTENTS}" ) STRING(REGEX REPLACE "tr2i18n\\(\"\", \"\"\\)" "QString\(\)" _uic_CONTENTS "${_uic_CONTENTS}" ) #replace image15_data with img15_filename to make enable_final work STRING(REGEX REPLACE "image([0-9]+)_data" "img\\1_${KDE_UIC_BASENAME}" _uic_CONTENTS "${_uic_CONTENTS}") #fixup include guard STRING(REGEX REPLACE "#ifndef " "#ifndef UI_" _uic_CONTENTS "${_uic_CONTENTS}") STRING(REGEX REPLACE "#define " "#define UI_" _uic_CONTENTS "${_uic_CONTENTS}") FILE(WRITE ${KDE_UIC_CPP_FILE} "#include \n\n${_uic_CONTENTS}\n") ENDIF() diff --git a/cmake/rules_PyKF5.py b/cmake/rules_PyKF5.py index 34d3556..6b10de2 100644 --- a/cmake/rules_PyKF5.py +++ b/cmake/rules_PyKF5.py @@ -1,201 +1,180 @@ #============================================================================= -# Copyright 2016 by Shaheed Haque (srhaque@theiet.org) -# Copyright 2016 Stephen Kelly +# SPDX-FileCopyrightText: 2016 by Shaheed Haque (srhaque@theiet.org) +# SPDX-FileCopyrightText: 2016 Stephen Kelly # -# 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 copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# 3. The name of the author may not be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 THE AUTHOR 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. +# SPDX-License-Identifier: BSD-3-Clause #============================================================================= import os, sys import rules_engine sys.path.append(os.path.dirname(os.path.dirname(rules_engine.__file__))) import Qt5Ruleset from clang.cindex import CursorKind def i18n_ellipsis(container, function, sip, matcher): if len(sip["template_parameters"]) == 0: sip["parameters"] += ["..."] else: sip["name"] = "" def i18np_ellipsis(container, function, sip, matcher): if len(sip["template_parameters"]) == 1: sip["parameters"][-1] = "..." sip["template_parameters"] = "" else: sip["name"] = "" def local_function_rules(): return [ ["KLocalizedString", "subs", ".*", ".*", ".*unsigned int.*", rules_engine.function_discard], ["KLocalizedString", "subs", ".*", ".*", ".*long.*", rules_engine.function_discard], ["KLocalizedString", "subs", ".*", ".*", ".*unsigned long.*", rules_engine.function_discard], ["KLocalizedString", "subs", ".*", ".*", ".*unsigned long long.*", rules_engine.function_discard], ["KLocalizedString", "subs", ".*", ".*", ".*QChar.*", rules_engine.function_discard], ["Kuit", "setupForDomain", ".*", ".*", ".*", rules_engine.function_discard], ["KuitSetup", "setTagPattern", ".*", ".*", ".*", rules_engine.function_discard], ["klocalizedstring.h", "i18n", ".*", ".*", ".*", i18n_ellipsis], ["klocalizedstring.h", "i18nc", ".*", ".*", ".*", i18n_ellipsis], ["klocalizedstring.h", "i18np", ".*", ".*", ".*", i18np_ellipsis], ["klocalizedstring.h", "i18ncp", ".*", ".*", ".*", i18np_ellipsis], ] def local_typedef_rules(): return [ [".*", "TagFormatter", rules_engine.typedef_discard], ] def _klocalizedstring_add_template_code(filename, sip, entry): sip["code"] = """ %ModuleCode QString klocalizedstring_i18n_template(KLocalizedString base, PyObject *list,int *sipIsErr) { KLocalizedString result = base; QString *arg; long long_arg; double double_arg; int iserr = 0; for (int i=0; i < PyTuple_Size(list); i++) { PyObject *pyarg = PyTuple_GET_ITEM (list, i); #if PY_MAJOR_VERSION >= 3 if (PyLong_Check(pyarg)) { long_arg = PyLong_AsLong(pyarg); #else if (PyInt_Check(pyarg)) { long_arg = PyInt_AsLong(pyarg); #endif if (long_arg==-1 && PyErr_Occurred()) { *sipIsErr = 1; return QString(); } result = result.subs(long_arg); #if PY_MAJOR_VERSION >= 3 } else if (PyNumber_Check(pyarg)) { PyObject *long_py = PyNumber_Long(pyarg); long_arg = PyLong_AsLong(long_py); Py_DECREF(long_py); #else } else if (PyLong_Check(pyarg)) { long_arg = PyLong_AsLong(pyarg); #endif if (long_arg==-1 && PyErr_Occurred()) { *sipIsErr = 1; return QString(); } result = result.subs(long_arg); } else if (PyFloat_Check(pyarg)) { double_arg = PyFloat_AsDouble(pyarg); result = result.subs(double_arg); } else { int state = 0; arg = (QString *)sipForceConvertToType(pyarg, sipType_QString, NULL, SIP_NOT_NONE, &state, &iserr); if (iserr) { *sipIsErr = 1; return QString(); } result = result.subs(*arg); sipReleaseType(arg,sipType_QString,state); arg = 0; } } return result.toString(); } %End\n """ class RuleSet(Qt5Ruleset.RuleSet): """ SIP file generator rules. This is a set of (short, non-public) functions and regular expression-based matching rules. """ def __init__(self): Qt5Ruleset.RuleSet.__init__(self) self._fn_db = rules_engine.FunctionRuleDb(lambda: local_function_rules() + Qt5Ruleset.function_rules()) self._typedef_db = rules_engine.TypedefRuleDb(lambda: local_typedef_rules()) self._methodcode = rules_engine.MethodCodeDb({ "klocalizedstring.h": { "i18n": { "code": """ %MethodCode QString result = klocalizedstring_i18n_template(ki18n(a0),a1,&sipIsErr); if (!sipIsErr) { sipRes = new QString(result); } %End """ }, "i18nc": { "code": """ %MethodCode QString result = klocalizedstring_i18n_template(ki18nc(a0,a1),a2,&sipIsErr); if (!sipIsErr) { sipRes = new QString(result); } %End """ }, "i18np": { "code": """ %MethodCode QString result = klocalizedstring_i18n_template(ki18np(a0,a1),a2,&sipIsErr); if (!sipIsErr) { sipRes = new QString(result); } %End """ }, "i18ncp": { "code": """ %MethodCode QString result = klocalizedstring_i18n_template(ki18ncp(a0,a1,a2),a3,&sipIsErr); if (!sipIsErr) { sipRes = new QString(result); } %End """ }, } }) self._modulecode = rules_engine.ModuleCodeDb({ "klocalizedstring.h": { "code": _klocalizedstring_add_template_code, }, }) diff --git a/src/common_helpers.cpp b/src/common_helpers.cpp index 66d1ca8..ced5c1f 100644 --- a/src/common_helpers.cpp +++ b/src/common_helpers.cpp @@ -1,105 +1,92 @@ /* This file is part of the KDE libraries - Copyright (C) 2008 Chusslove Illich + SPDX-FileCopyrightText: 2008 Chusslove Illich - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library 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 - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-License-Identifier: LGPL-2.0-or-later */ #include // If pos points to alphanumeric X in "...(X)...", which is preceded or // followed only by non-alphanumerics, then "(X)" gets removed. static QString removeReducedCJKAccMark(const QString &label, int pos) { if (pos > 0 && pos + 1 < label.length() && label[pos - 1] == QLatin1Char('(') && label[pos + 1] == QLatin1Char(')') && label[pos].isLetterOrNumber()) { // Check if at start or end, ignoring non-alphanumerics. int len = label.length(); int p1 = pos - 2; while (p1 >= 0 && !label[p1].isLetterOrNumber()) { --p1; } ++p1; int p2 = pos + 2; while (p2 < len && !label[p2].isLetterOrNumber()) { ++p2; } --p2; if (p1 == 0) { return label.leftRef(pos - 1) + label.midRef(p2 + 1); } else if (p2 + 1 == len) { return label.leftRef(p1) + label.midRef(pos + 2); } } return label; } QString removeAcceleratorMarker(const QString &label_) { QString label = label_; int p = 0; bool accmarkRemoved = false; while (true) { p = label.indexOf(QLatin1Char('&'), p); if (p < 0 || p + 1 == label.length()) { break; } if (label[p + 1].isLetterOrNumber()) { // Valid accelerator. label = QString(label.leftRef(p) + label.midRef(p + 1)); // May have been an accelerator in CJK-style "(&X)" // at the start or end of text. label = removeReducedCJKAccMark(label, p); accmarkRemoved = true; } else if (label[p + 1] == QLatin1Char('&')) { // Escaped accelerator marker. label = QString(label.leftRef(p) + label.midRef(p + 1)); } ++p; } // If no marker was removed, and there are CJK characters in the label, // also try to remove reduced CJK marker -- something may have removed // ampersand beforehand. if (!accmarkRemoved) { bool hasCJK = false; for (const QChar c : label) { if (c.unicode() >= 0x2e00) { // rough, but should be sufficient hasCJK = true; break; } } if (hasCJK) { p = 0; while (true) { p = label.indexOf(QLatin1Char('('), p); if (p < 0) { break; } label = removeReducedCJKAccMark(label, p + 1); ++p; } } } return label; } diff --git a/src/common_helpers_p.h b/src/common_helpers_p.h index c531dcd..b68d39b 100644 --- a/src/common_helpers_p.h +++ b/src/common_helpers_p.h @@ -1,46 +1,33 @@ /* This file is part of the KDE libraries - Copyright (C) 2008 Chusslove Illich + SPDX-FileCopyrightText: 2008 Chusslove Illich - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library 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 - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-License-Identifier: LGPL-2.0-or-later */ #ifndef COMMON_HELPERS_P_H #define COMMON_HELPERS_P_H #include // Standalone (pure Qt) functionality needed internally in more than // one source file on localization. /** * @internal * * Removes accelerator marker from a UI text label. * * Accelerator marker is not always a plain ampersand (&), * so it is not enough to just remove it by @c QString::remove(). * The label may contain escaped markers ("&&") which must be resolved * and skipped, as well as CJK-style markers ("Foo (&F)") where * the whole parenthesis construct should be removed. * Therefore always use this function to remove accelerator marker * from UI labels. * * @param label UI label which may contain an accelerator marker * @return label without the accelerator marker */ QString removeAcceleratorMarker(const QString &label); #endif diff --git a/src/config.h.in b/src/config.h.in index 7f6b26f..c26de3b 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -1,25 +1,12 @@ -/* This file is part of the KDE libraries - Copyright (c) 2016 A. Wilcox +/* This file is part of the KDE libraries + SPDX-FileCopyrightText: 2016 A. Wilcox - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library 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 - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-License-Identifier: LGPL-2.0-or-later */ #ifndef KI18N_CONFIG_H #define KI18N_CONFIG_H #cmakedefine HAVE_NL_MSG_CAT_CNTR #endif diff --git a/src/gettext.h b/src/gettext.h index 41e2c73..b0f878c 100644 --- a/src/gettext.h +++ b/src/gettext.h @@ -1,198 +1,186 @@ -/* Convenience header for conditional use of GNU . - Copyright (C) 1995-1998, 2000-2002, 2004-2006 Free Software Foundation, Inc. - - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU Library General Public License as published - by the Free Software Foundation; either version 2, 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 - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public - License along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - USA. */ +/* Convenience header for conditional use of GNU . + SPDX-FileCopyrightText: 1995-1998, 2000-2002, 2004-2006 Free Software Foundation, Inc. + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ #ifndef _LIBGETTEXT_H #define _LIBGETTEXT_H 1 /* Get declarations of GNU message catalog functions. */ # include // libintl.h redefines inline which causes MSVC to abort compilation with the message // fatal error C1189: #error : The C++ Standard Library forbids macroizing keywords #undef inline /* You can set the DEFAULT_TEXT_DOMAIN macro to specify the domain used by the gettext() and ngettext() macros. This is an alternative to calling textdomain(), and is useful for libraries. */ # ifdef DEFAULT_TEXT_DOMAIN # undef gettext # define gettext(Msgid) \ dgettext (DEFAULT_TEXT_DOMAIN, Msgid) # undef ngettext # define ngettext(Msgid1, Msgid2, N) \ dngettext (DEFAULT_TEXT_DOMAIN, Msgid1, Msgid2, N) # endif /* The separator between msgctxt and msgid in a .mo file. */ #define GETTEXT_CONTEXT_GLUE "\004" /* Pseudo function calls, taking a MSGCTXT and a MSGID instead of just a MSGID. MSGCTXT and MSGID must be string literals. MSGCTXT should be short and rarely need to change. The letter 'p' stands for 'particular' or 'special'. */ #ifdef DEFAULT_TEXT_DOMAIN # define pgettext(Msgctxt, Msgid) \ pgettext_aux (DEFAULT_TEXT_DOMAIN, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid) #else # define pgettext(Msgctxt, Msgid) \ pgettext_aux (NULL, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid) #endif #define dpgettext(Domainname, Msgctxt, Msgid) \ pgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid) #ifdef __GNUC__ __inline #else #ifdef __cplusplus inline #endif #endif static const char * pgettext_aux(const char *domain, const char *msg_ctxt_id, const char *msgid) { const char *translation = dgettext(domain, msg_ctxt_id); if (translation == msg_ctxt_id) { return msgid; } else { return translation; } } /* The same thing extended for non-constant arguments. Here MSGCTXT and MSGID can be arbitrary expressions. But for string literals these macros are less efficient than those above. */ #include #ifndef __STRICT_ANSI__ #define __STRICT_ANSI__ #define __STRICT_ANSI_FORCED__ #endif #define _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS \ (__STRICT_ANSI__ - 0 == 0) && (__GNUC__ >= 3 || __GNUG__ >= 2 /* || __STDC_VERSION__ >= 199901L */ ) #if !_LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS #include #endif #define pgettext_expr(Msgctxt, Msgid) \ dpgettext_expr (NULL, Msgctxt, Msgid) #define dpgettext_expr(Domainname, Msgctxt, Msgid) \ dpgettext_expr (Domainname, Msgctxt, Msgid) #ifdef __GNUC__ __inline #else #ifdef __cplusplus inline #endif #endif static const char * dpgettext_expr(const char *domain, const char *msgctxt, const char *msgid) { size_t msgctxt_len = strlen(msgctxt) + 1; size_t msgid_len = strlen(msgid) + 1; const char *translation; int translation_found; #if _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS char msg_ctxt_id[msgctxt_len + msgid_len]; #else char buf[1024]; char *msg_ctxt_id = (msgctxt_len + msgid_len <= sizeof(buf) ? buf : (char *) malloc(msgctxt_len + msgid_len)); if (msg_ctxt_id != nullptr) #endif { memcpy(msg_ctxt_id, msgctxt, msgctxt_len - 1); msg_ctxt_id[msgctxt_len - 1] = '\004'; memcpy(msg_ctxt_id + msgctxt_len, msgid, msgid_len); translation = dgettext(domain, msg_ctxt_id); /* Test must occur before msg_ctxt_id freed */ translation_found = translation != msg_ctxt_id; #if !_LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS if (msg_ctxt_id != buf) { free(msg_ctxt_id); } #endif if (translation_found) { return translation; } } return msgid; } #define npgettext_expr(Msgctxt, Msgid, MsgidPlural, N) \ dnpgettext_expr (NULL, Msgctxt, Msgid, MsgidPlural, N) #define dnpgettext_expr(Domainname, Msgctxt, Msgid, MsgidPlural, N) \ dnpgettext_expr (Domainname, Msgctxt, Msgid, MsgidPlural, N) #ifdef __GNUC__ __inline #else #ifdef __cplusplus inline #endif #endif static const char * dnpgettext_expr(const char *domain, const char *msgctxt, const char *msgid, const char *msgid_plural, unsigned long int n) { size_t msgctxt_len = strlen(msgctxt) + 1; size_t msgid_len = strlen(msgid) + 1; const char *translation; int translation_found; #if _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS char msg_ctxt_id[msgctxt_len + msgid_len]; #else char buf[1024]; char *msg_ctxt_id = (msgctxt_len + msgid_len <= sizeof(buf) ? buf : (char *) malloc(msgctxt_len + msgid_len)); if (msg_ctxt_id != nullptr) #endif { memcpy(msg_ctxt_id, msgctxt, msgctxt_len - 1); msg_ctxt_id[msgctxt_len - 1] = '\004'; memcpy(msg_ctxt_id + msgctxt_len, msgid, msgid_len); translation = dngettext(domain, msg_ctxt_id, msgid_plural, n); /* Test must occur before msg_ctxt_id freed */ translation_found = !(translation == msg_ctxt_id || translation == msgid_plural); #if !_LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS if (msg_ctxt_id != buf) { free(msg_ctxt_id); } #endif if (translation_found) { return translation; } } return (n == 1 ? msgid : msgid_plural); } #ifdef __STRICT_ANSI_FORCED__ #undef __STRICT_ANSI__ #undef __STRICT_ANSI_FORCED__ #endif #endif /* _LIBGETTEXT_H */ diff --git a/src/kcatalog.cpp b/src/kcatalog.cpp index a592e02..e4e0dfb 100644 --- a/src/kcatalog.cpp +++ b/src/kcatalog.cpp @@ -1,345 +1,332 @@ -/* This file is part of the KDE libraries - Copyright (c) 2001 Hans Petter Bieker - Copyright (c) 2012, 2013 Chusslove Illich - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library 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 - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. +/* This file is part of the KDE libraries + SPDX-FileCopyrightText: 2001 Hans Petter Bieker + SPDX-FileCopyrightText: 2012, 2013 Chusslove Illich + + SPDX-License-Identifier: LGPL-2.0-or-later */ #include #include #include "gettext.h" #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include "ki18n_logging.h" // not defined on win32 :( #ifdef _WIN32 #ifndef LC_MESSAGES #define LC_MESSAGES 42 #endif #endif #if defined(HAVE_NL_MSG_CAT_CNTR) extern "C" int Q_DECL_IMPORT _nl_msg_cat_cntr; #endif static char *langenv = nullptr; static const int langenvMaxlen = 42; // = "LANGUAGE=" + 32 chars for language code + terminating zero class KCatalogStaticData { public: KCatalogStaticData() {} QHash customCatalogDirs; QMutex mutex; }; Q_GLOBAL_STATIC(KCatalogStaticData, catalogStaticData) class KCatalogPrivate { public: KCatalogPrivate(); QByteArray domain; QByteArray language; QByteArray localeDir; QByteArray systemLanguage; bool bindDone; static QByteArray currentLanguage; void setupGettextEnv(); void resetSystemLanguage(); }; KCatalogPrivate::KCatalogPrivate() : bindDone(false) {} QByteArray KCatalogPrivate::currentLanguage; KCatalog::KCatalog(const QByteArray &domain, const QString &language_) : d(new KCatalogPrivate) { d->domain = domain; d->language = QFile::encodeName(language_); d->localeDir = QFile::encodeName(catalogLocaleDir(domain, language_)); if (!d->localeDir.isEmpty()) { // Always get translations in UTF-8, regardless of user's environment. bind_textdomain_codeset(d->domain, "UTF-8"); // Invalidate current language, to trigger binding at next translate call. KCatalogPrivate::currentLanguage.clear(); if (!langenv) { // Call putenv only here, to initialize LANGUAGE variable. // Later only change langenv to what is currently needed. langenv = new char[langenvMaxlen]; QByteArray baselang = qgetenv("LANGUAGE"); qsnprintf(langenv, langenvMaxlen, "LANGUAGE=%s", baselang.constData()); putenv(langenv); } } } KCatalog::~KCatalog() { delete d; } #if defined(Q_OS_ANDROID) && QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) static QString androidUnpackCatalog(const QString &relpath) { // the catalog files are no longer extracted to the local file system // by androiddeployqt starting with Qt 5.14, libintl however needs // local files rather than qrc: or asset: URLs, so we unpack the .mo // files on demand to the local cache folder const QString cachePath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QLatin1String("/org.kde.ki18n/") + relpath; QFileInfo cacheFile(cachePath); if (cacheFile.exists()) { return cachePath; } const QString assetPath = QLatin1String("assets:/share/locale/") + relpath; if (!QFileInfo::exists(assetPath)) { return {}; } QDir().mkpath(cacheFile.absolutePath()); QFile f(assetPath); if (!f.copy(cachePath)) { qCWarning(KI18N) << "Failed to copy catalog:" << f.errorString() << assetPath << cachePath; return {}; } return cachePath; } #endif QString KCatalog::catalogLocaleDir(const QByteArray &domain, const QString &language) { QString relpath = QStringLiteral("%1/LC_MESSAGES/%2.mo") .arg(language, QFile::decodeName(domain)); { QMutexLocker lock(&catalogStaticData->mutex); const QString customLocaleDir = catalogStaticData->customCatalogDirs.value(domain); const QString filename = customLocaleDir + QLatin1Char('/') + relpath; if (!customLocaleDir.isEmpty() && QFileInfo::exists(filename)) { #if defined(Q_OS_ANDROID) // The exact file name must be returned on Android because libintl-lite loads a catalog by filename with bindtextdomain() return filename; #else return customLocaleDir; #endif } } #if defined(Q_OS_ANDROID) #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) // The exact file name must be returned on Android because libintl-lite loads a catalog by filename with bindtextdomain() QString file = QDir::homePath()+QStringLiteral("/../qt-reserved-files/share/locale/") + relpath; if (!QFile::exists(file)) { file.clear(); } return file; #else return androidUnpackCatalog(relpath); #endif #else const QString file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("locale/") + relpath); QString localeDir; if (!file.isEmpty()) { // Path of the locale/ directory must be returned. localeDir = QFileInfo(file.left(file.size() - relpath.size())).absolutePath(); } return localeDir; #endif } QSet KCatalog::availableCatalogLanguages(const QByteArray &domain_) { QString domain = QFile::decodeName(domain_); QStringList localeDirPaths = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("locale"), QStandardPaths::LocateDirectory); { QMutexLocker lock(&catalogStaticData->mutex); auto it = catalogStaticData->customCatalogDirs.constFind(domain_); if (it != catalogStaticData->customCatalogDirs.constEnd()) { localeDirPaths.prepend(*it); } } QSet availableLanguages; for (const QString &localDirPath : qAsConst(localeDirPaths)) { QDir localeDir(localDirPath); const QStringList languages = localeDir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot); for (const QString &language : languages) { QString relPath = QStringLiteral("%1/LC_MESSAGES/%2.mo") .arg(language, domain); if (localeDir.exists(relPath)) { availableLanguages.insert(language); } } } return availableLanguages; } void KCatalogPrivate::setupGettextEnv() { // Point Gettext to current language, recording system value for recovery. systemLanguage = qgetenv("LANGUAGE"); if (systemLanguage != language) { // putenv has been called in the constructor, // it is enough to change the string set there. qsnprintf(langenv, langenvMaxlen, "LANGUAGE=%s", language.constData()); } // Rebind text domain if language actually changed from the last time, // as locale directories may differ for different languages of same catalog. if (language != currentLanguage || !bindDone) { Q_ASSERT_X(QCoreApplication::instance(), "KCatalogPrivate::setupGettextEnv", "You need to instantiate a Q*Application before using KCatalog"); if (!QCoreApplication::instance()) { qCWarning(KI18N) << "KCatalog being used without a Q*Application instance. Some translations won't work"; } currentLanguage = language; bindDone = true; //qDebug() << "bindtextdomain" << domain << localeDir; bindtextdomain(domain, localeDir); #if defined(HAVE_NL_MSG_CAT_CNTR) // Magic to make sure GNU Gettext doesn't use stale cached translation // from previous language. ++_nl_msg_cat_cntr; #endif } } void KCatalogPrivate::resetSystemLanguage() { if (language != systemLanguage) { qsnprintf(langenv, langenvMaxlen, "LANGUAGE=%s", systemLanguage.constData()); } } QString KCatalog::translate(const QByteArray &msgid) const { if (!d->localeDir.isEmpty()) { QMutexLocker locker(&catalogStaticData()->mutex); d->setupGettextEnv(); const char *msgid_char = msgid.constData(); const char *msgstr = dgettext(d->domain.constData(), msgid_char); d->resetSystemLanguage(); return msgstr != msgid_char // Yes we want pointer comparison ? QString::fromUtf8(msgstr) : QString(); } else { return QString(); } } QString KCatalog::translate(const QByteArray &msgctxt, const QByteArray &msgid) const { if (!d->localeDir.isEmpty()) { QMutexLocker locker(&catalogStaticData()->mutex); d->setupGettextEnv(); const char *msgid_char = msgid.constData(); const char *msgstr = dpgettext_expr(d->domain.constData(), msgctxt.constData(), msgid_char); d->resetSystemLanguage(); return msgstr != msgid_char // Yes we want pointer comparison ? QString::fromUtf8(msgstr) : QString(); } else { return QString(); } } QString KCatalog::translate(const QByteArray &msgid, const QByteArray &msgid_plural, qulonglong n) const { if (!d->localeDir.isEmpty()) { QMutexLocker locker(&catalogStaticData()->mutex); d->setupGettextEnv(); const char *msgid_char = msgid.constData(); const char *msgid_plural_char = msgid_plural.constData(); const char *msgstr = dngettext(d->domain.constData(), msgid_char, msgid_plural_char, n); d->resetSystemLanguage(); // If original and translation are same, dngettext will return // the original pointer, which is generally fine, except in // the corner cases where e.g. msgstr[1] is same as msgid. // Therefore check for pointer difference only with msgid or // only with msgid_plural, and not with both. return (n == 1 && msgstr != msgid_char) || (n != 1 && msgstr != msgid_plural_char) ? QString::fromUtf8(msgstr) : QString(); } else { return QString(); } } QString KCatalog::translate(const QByteArray &msgctxt, const QByteArray &msgid, const QByteArray &msgid_plural, qulonglong n) const { if (!d->localeDir.isEmpty()) { QMutexLocker locker(&catalogStaticData()->mutex); d->setupGettextEnv(); const char *msgid_char = msgid.constData(); const char *msgid_plural_char = msgid_plural.constData(); const char *msgstr = dnpgettext_expr(d->domain.constData(), msgctxt.constData(), msgid_char, msgid_plural_char, n); d->resetSystemLanguage(); return (n == 1 && msgstr != msgid_char) || (n != 1 && msgstr != msgid_plural_char) ? QString::fromUtf8(msgstr) : QString(); } else { return QString(); } } void KCatalog::addDomainLocaleDir(const QByteArray &domain, const QString &path) { QMutexLocker(&catalogStaticData()->mutex); catalogStaticData()->customCatalogDirs.insert(domain, path); } diff --git a/src/kcatalog_p.h b/src/kcatalog_p.h index b05eccc..04c58e0 100644 --- a/src/kcatalog_p.h +++ b/src/kcatalog_p.h @@ -1,134 +1,121 @@ -/* This file is part of the KDE libraries - Copyright (c) 2001 Hans Petter Bieker - Copyright (c) 2012, 2013 Chusslove Illich - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library 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 - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. +/* This file is part of the KDE libraries + SPDX-FileCopyrightText: 2001 Hans Petter Bieker + SPDX-FileCopyrightText: 2012, 2013 Chusslove Illich + + SPDX-License-Identifier: LGPL-2.0-or-later */ #ifndef KCATALOG_H #define KCATALOG_H #include #include #include class KCatalogPrivate; /** * This class abstracts a Gettext message catalog. * It takes care of needed Gettext bindings. * * @see KLocalizedString * @internal */ class KCatalog { public: /** * Constructor. * * @param name translation domain * @param language translation language */ KCatalog(const QByteArray &domain, const QString &language); /** * Destructor. */ ~KCatalog(); /** * Get translation of the given message text. * * Do not pass empty message text. * * @param msgid message text * * @return translated message if found, QString() otherwise */ QString translate(const QByteArray &msgid) const; /** * Get translation of the given message text with given context. * * Do not pass empty message text. * * @param msgctxt message context * @param msgid message text * * @return translated message if found, QString() otherwise */ QString translate(const QByteArray &msgctxt, const QByteArray &msgid) const; /** * Get translation of given message with plural forms. * * Do not pass empty message text. * * @param msgid singular message text * @param msgid_plural plural message text * @param n number for which the plural form is needed * * @return translated message if found, QString() otherwise */ QString translate(const QByteArray &msgid, const QByteArray &msgid_plural, qulonglong n) const; /** * Get translation of given message with plural forms with given context. * * Do not pass empty message text. * * @param msgctxt message context * @param msgid singular message text * @param msgid_plural plural message text * @param n number for which the plural form is needed * * @return translated message if found, QString() otherwise */ QString translate(const QByteArray &msgctxt, const QByteArray &msgid, const QByteArray &msgid_plural, qulonglong n) const; /** * Find the locale directory for the given domain in the given language. * * @param domain translation domain * @param language language of the catalog * * @return the locale directory if found, QByteArray() otherwise. */ static QString catalogLocaleDir(const QByteArray &domain, const QString &language); /** * Find the all languages for which the translation catalog * of given domain exists. * * @param domain translation domain * * @return set of language codes */ static QSet availableCatalogLanguages(const QByteArray &domain); static void addDomainLocaleDir(const QByteArray &domain, const QString &path); private: Q_DISABLE_COPY(KCatalog) KCatalogPrivate *const d; }; #endif diff --git a/src/klocalizedcontext.cpp b/src/klocalizedcontext.cpp index 6ceffbd..6353f03 100644 --- a/src/klocalizedcontext.cpp +++ b/src/klocalizedcontext.cpp @@ -1,441 +1,428 @@ /* - * Copyright 2013 Marco Martin - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2, 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 Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ + SPDX-FileCopyrightText: 2013 Marco Martin + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ // Undefine this because we don't want our i18n*() method names to be turned into i18nd*() #undef TRANSLATION_DOMAIN #include "klocalizedcontext.h" #include #include "ki18n_logging.h" class KLocalizedContextPrivate { public: QString m_translationDomain; }; KLocalizedContext::KLocalizedContext(QObject *parent) : QObject(parent) , d_ptr(new KLocalizedContextPrivate) { } KLocalizedContext::~KLocalizedContext() { delete d_ptr; } QString KLocalizedContext::translationDomain() const { Q_D(const KLocalizedContext); return d->m_translationDomain; } void KLocalizedContext::setTranslationDomain(const QString &domain) { Q_D(KLocalizedContext); if (domain != d->m_translationDomain) { d->m_translationDomain = domain; Q_EMIT translationDomainChanged(domain); } } static void subsVariant(KLocalizedString &trMessage, const QVariant &value) { switch(value.type()) { case QVariant::String: trMessage = trMessage.subs(value.toString()); break; case QVariant::Int: trMessage = trMessage.subs(value.toInt()); break; case QVariant::Double: trMessage = trMessage.subs(value.toDouble()); break; case QVariant::Char: trMessage = trMessage.subs(value.toChar()); break; default: if (value.canConvert(QVariant::String)) { trMessage = trMessage.subs(value.toString()); } else { trMessage = trMessage.subs(QStringLiteral("???")); qCWarning(KI18N) << "couldn't convert" << value << "to translate"; } } } static void resolveMessage(KLocalizedString &trMessage, const QVariant ¶m1, const QVariant ¶m2, const QVariant ¶m3, const QVariant ¶m4, const QVariant ¶m5, const QVariant ¶m6, const QVariant ¶m7, const QVariant ¶m8, const QVariant ¶m9, const QVariant ¶m10 = QString()) { if (!param1.isNull()) { subsVariant(trMessage, param1); } if (!param2.isNull()) { subsVariant(trMessage, param2); } if (!param3.isNull()) { subsVariant(trMessage, param3); } if (!param4.isNull()) { subsVariant(trMessage, param4); } if (!param5.isNull()) { subsVariant(trMessage, param5); } if (!param6.isNull()) { subsVariant(trMessage, param6); } if (!param7.isNull()) { subsVariant(trMessage, param7); } if (!param8.isNull()) { subsVariant(trMessage, param8); } if (!param9.isNull()) { subsVariant(trMessage, param9); } if (!param10.isNull()) { subsVariant(trMessage, param10); } } static void resolvePlural(KLocalizedString &trMessage, const QVariant ¶m) { trMessage = trMessage.subs(param.toInt()); } QString KLocalizedContext::i18n(const QString &message, const QVariant ¶m1, const QVariant ¶m2, const QVariant ¶m3, const QVariant ¶m4, const QVariant ¶m5, const QVariant ¶m6, const QVariant ¶m7, const QVariant ¶m8, const QVariant ¶m9, const QVariant ¶m10) const { if (message.isEmpty()) { qCWarning(KI18N) << "i18n() needs at least one parameter"; return QString(); } Q_D(const KLocalizedContext); KLocalizedString trMessage; if (!d->m_translationDomain.isEmpty()) { trMessage = ki18nd(d->m_translationDomain.toUtf8().constData(), message.toUtf8().constData()); } else { trMessage = ki18n(message.toUtf8().constData()); } resolveMessage(trMessage, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10); return trMessage.toString(); } QString KLocalizedContext::i18nc(const QString &context, const QString &message, const QVariant ¶m1, const QVariant ¶m2, const QVariant ¶m3, const QVariant ¶m4, const QVariant ¶m5, const QVariant ¶m6, const QVariant ¶m7, const QVariant ¶m8, const QVariant ¶m9, const QVariant ¶m10) const { if (context.isEmpty() || message.isEmpty()) { qCWarning(KI18N) << "i18nc() needs at least two arguments"; return QString(); } Q_D(const KLocalizedContext); KLocalizedString trMessage; if (!d->m_translationDomain.isEmpty()) { trMessage = ki18ndc(d->m_translationDomain.toUtf8().constData(), context.toUtf8().constData(), message.toUtf8().constData()); } else { trMessage = ki18nc(context.toUtf8().constData(), message.toUtf8().constData()); } resolveMessage(trMessage, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10); return trMessage.toString(); } QString KLocalizedContext::i18np(const QString &singular, const QString &plural, const QVariant ¶m1, const QVariant ¶m2, const QVariant ¶m3, const QVariant ¶m4, const QVariant ¶m5, const QVariant ¶m6, const QVariant ¶m7, const QVariant ¶m8, const QVariant ¶m9, const QVariant ¶m10) const { if (singular.isEmpty() || plural.isEmpty()) { qCWarning(KI18N) << "i18np() needs at least two arguments"; return QString(); } Q_D(const KLocalizedContext); KLocalizedString trMessage; if (!d->m_translationDomain.isEmpty()) { trMessage = ki18ndp(d->m_translationDomain.toUtf8().constData(), singular.toUtf8().constData(), plural.toUtf8().constData()); } else { trMessage = ki18np(singular.toUtf8().constData(), plural.toUtf8().constData()); } resolvePlural(trMessage, param1); resolveMessage(trMessage, param2, param3, param4, param5, param6, param7, param8, param9, param10); return trMessage.toString(); } QString KLocalizedContext::i18ncp(const QString &context, const QString &singular, const QString &plural, const QVariant ¶m1, const QVariant ¶m2, const QVariant ¶m3, const QVariant ¶m4, const QVariant ¶m5, const QVariant ¶m6, const QVariant ¶m7, const QVariant ¶m8, const QVariant ¶m9, const QVariant ¶m10) const { if (context.isEmpty() || singular.isEmpty() || plural.isEmpty()) { qCWarning(KI18N) << "i18ncp() needs at least three arguments"; return QString(); } Q_D(const KLocalizedContext); KLocalizedString trMessage; if (!d->m_translationDomain.isEmpty()) { trMessage = ki18ndcp(d->m_translationDomain.toUtf8().constData(), context.toUtf8().constData(), singular.toUtf8().constData(), plural.toUtf8().constData()); } else { trMessage = ki18ncp(context.toUtf8().constData(), singular.toUtf8().constData(), plural.toUtf8().constData()); } resolvePlural(trMessage, param1); resolveMessage(trMessage, param2, param3, param4, param5, param6, param7, param8, param9, param10); return trMessage.toString(); } QString KLocalizedContext::i18nd(const QString &domain, const QString &message, const QVariant ¶m1, const QVariant ¶m2, const QVariant ¶m3, const QVariant ¶m4, const QVariant ¶m5, const QVariant ¶m6, const QVariant ¶m7, const QVariant ¶m8, const QVariant ¶m9, const QVariant ¶m10) const { if (domain.isEmpty() || message.isEmpty()) { qCWarning(KI18N) << "i18nd() needs at least two parameters"; return QString(); } KLocalizedString trMessage = ki18nd(domain.toUtf8().constData(), message.toUtf8().constData()); resolveMessage(trMessage, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10); return trMessage.toString(); } QString KLocalizedContext::i18ndc(const QString &domain, const QString &context, const QString &message, const QVariant ¶m1, const QVariant ¶m2, const QVariant ¶m3, const QVariant ¶m4, const QVariant ¶m5, const QVariant ¶m6, const QVariant ¶m7, const QVariant ¶m8, const QVariant ¶m9, const QVariant ¶m10) const { if (domain.isEmpty() || context.isEmpty() || message.isEmpty()) { qCWarning(KI18N) << "i18ndc() needs at least three arguments"; return QString(); } KLocalizedString trMessage = ki18ndc(domain.toUtf8().constData(), context.toUtf8().constData(), message.toUtf8().constData()); resolveMessage(trMessage, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10); return trMessage.toString(); } QString KLocalizedContext::i18ndp(const QString &domain, const QString &singular, const QString &plural, const QVariant ¶m1, const QVariant ¶m2, const QVariant ¶m3, const QVariant ¶m4, const QVariant ¶m5, const QVariant ¶m6, const QVariant ¶m7, const QVariant ¶m8, const QVariant ¶m9, const QVariant ¶m10) const { if (domain.isEmpty() || singular.isEmpty() || plural.isEmpty()) { qCWarning(KI18N) << "i18ndp() needs at least three arguments"; return QString(); } KLocalizedString trMessage = ki18ndp(domain.toUtf8().constData(), singular.toUtf8().constData(), plural.toUtf8().constData()); resolvePlural(trMessage, param1); resolveMessage(trMessage, param2, param3, param4, param5, param6, param7, param8, param9, param10); return trMessage.toString(); } QString KLocalizedContext::i18ndcp(const QString &domain, const QString &context, const QString &singular, const QString &plural, const QVariant ¶m1, const QVariant ¶m2, const QVariant ¶m3, const QVariant ¶m4, const QVariant ¶m5, const QVariant ¶m6, const QVariant ¶m7, const QVariant ¶m8, const QVariant ¶m9, const QVariant ¶m10) const { if (domain.isEmpty() || context.isEmpty() || singular.isEmpty() || plural.isEmpty()) { qCWarning(KI18N) << "i18ndcp() needs at least four arguments"; return QString(); } KLocalizedString trMessage = ki18ndcp(domain.toUtf8().constData(), context.toUtf8().constData(), singular.toUtf8().constData(), plural.toUtf8().constData()); resolvePlural(trMessage, param1); resolveMessage(trMessage, param2, param3, param4, param5, param6, param7, param8, param9, param10); return trMessage.toString(); } ///////////////////////// QString KLocalizedContext::xi18n(const QString &message, const QVariant ¶m1, const QVariant ¶m2, const QVariant ¶m3, const QVariant ¶m4, const QVariant ¶m5, const QVariant ¶m6, const QVariant ¶m7, const QVariant ¶m8, const QVariant ¶m9, const QVariant ¶m10) const { if (message.isEmpty()) { qCWarning(KI18N) << "xi18n() needs at least one parameter"; return QString(); } Q_D(const KLocalizedContext); KLocalizedString trMessage; if (!d->m_translationDomain.isEmpty()) { trMessage = kxi18nd(d->m_translationDomain.toUtf8().constData(), message.toUtf8().constData()); } else { trMessage = kxi18n(message.toUtf8().constData()); } resolveMessage(trMessage, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10); return trMessage.toString(); } QString KLocalizedContext::xi18nc(const QString &context, const QString &message, const QVariant ¶m1, const QVariant ¶m2, const QVariant ¶m3, const QVariant ¶m4, const QVariant ¶m5, const QVariant ¶m6, const QVariant ¶m7, const QVariant ¶m8, const QVariant ¶m9, const QVariant ¶m10) const { if (context.isEmpty() || message.isEmpty()) { qCWarning(KI18N) << "xi18nc() needs at least two arguments"; return QString(); } Q_D(const KLocalizedContext); KLocalizedString trMessage; if (!d->m_translationDomain.isEmpty()) { trMessage = kxi18ndc(d->m_translationDomain.toUtf8().constData(), context.toUtf8().constData(), message.toUtf8().constData()); } else { trMessage = kxi18nc(context.toUtf8().constData(), message.toUtf8().constData()); } resolveMessage(trMessage, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10); return trMessage.toString(); } QString KLocalizedContext::xi18np(const QString &singular, const QString &plural, const QVariant ¶m1, const QVariant ¶m2, const QVariant ¶m3, const QVariant ¶m4, const QVariant ¶m5, const QVariant ¶m6, const QVariant ¶m7, const QVariant ¶m8, const QVariant ¶m9, const QVariant ¶m10) const { if (singular.isEmpty() || plural.isEmpty()) { qCWarning(KI18N) << "xi18np() needs at least two arguments"; return QString(); } Q_D(const KLocalizedContext); KLocalizedString trMessage; if (!d->m_translationDomain.isEmpty()) { trMessage = kxi18ndp(d->m_translationDomain.toUtf8().constData(), singular.toUtf8().constData(), plural.toUtf8().constData()); } else { trMessage = kxi18np(singular.toUtf8().constData(), plural.toUtf8().constData()); } resolvePlural(trMessage, param1); resolveMessage(trMessage, param2, param3, param4, param5, param6, param7, param8, param9, param10); return trMessage.toString(); } QString KLocalizedContext::xi18ncp(const QString &context, const QString &singular, const QString &plural, const QVariant ¶m1, const QVariant ¶m2, const QVariant ¶m3, const QVariant ¶m4, const QVariant ¶m5, const QVariant ¶m6, const QVariant ¶m7, const QVariant ¶m8, const QVariant ¶m9, const QVariant ¶m10) const { if (context.isEmpty() || singular.isEmpty() || plural.isEmpty()) { qCWarning(KI18N) << "xi18ncp() needs at least three arguments"; return QString(); } Q_D(const KLocalizedContext); KLocalizedString trMessage; if (!d->m_translationDomain.isEmpty()) { trMessage = kxi18ndcp(d->m_translationDomain.toUtf8().constData(), context.toUtf8().constData(), singular.toUtf8().constData(), plural.toUtf8().constData()); } else { trMessage = kxi18ncp(context.toUtf8().constData(), singular.toUtf8().constData(), plural.toUtf8().constData()); } resolvePlural(trMessage, param1); resolveMessage(trMessage, param2, param3, param4, param5, param6, param7, param8, param9, param10); return trMessage.toString(); } QString KLocalizedContext::xi18nd(const QString &domain, const QString &message, const QVariant ¶m1, const QVariant ¶m2, const QVariant ¶m3, const QVariant ¶m4, const QVariant ¶m5, const QVariant ¶m6, const QVariant ¶m7, const QVariant ¶m8, const QVariant ¶m9, const QVariant ¶m10) const { if (domain.isEmpty() || message.isEmpty()) { qCWarning(KI18N) << "xi18nd() needs at least two parameters"; return QString(); } KLocalizedString trMessage = kxi18nd(domain.toUtf8().constData(), message.toUtf8().constData()); resolveMessage(trMessage, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10); return trMessage.toString(); } QString KLocalizedContext::xi18ndc(const QString &domain, const QString &context, const QString &message, const QVariant ¶m1, const QVariant ¶m2, const QVariant ¶m3, const QVariant ¶m4, const QVariant ¶m5, const QVariant ¶m6, const QVariant ¶m7, const QVariant ¶m8, const QVariant ¶m9, const QVariant ¶m10) const { if (domain.isEmpty() || context.isEmpty() || message.isEmpty()) { qCWarning(KI18N) << "x18ndc() needs at least three arguments"; return QString(); } KLocalizedString trMessage = ki18ndc(domain.toUtf8().constData(), context.toUtf8().constData(), message.toUtf8().constData()); resolveMessage(trMessage, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10); return trMessage.toString(); } QString KLocalizedContext::xi18ndp(const QString &domain, const QString &singular, const QString &plural, const QVariant ¶m1, const QVariant ¶m2, const QVariant ¶m3, const QVariant ¶m4, const QVariant ¶m5, const QVariant ¶m6, const QVariant ¶m7, const QVariant ¶m8, const QVariant ¶m9, const QVariant ¶m10) const { if (domain.isEmpty() || singular.isEmpty() || plural.isEmpty()) { qCWarning(KI18N) << "xi18ndp() needs at least three arguments"; return QString(); } KLocalizedString trMessage = ki18ndp(domain.toUtf8().constData(), singular.toUtf8().constData(), plural.toUtf8().constData()); resolvePlural(trMessage, param1); resolveMessage(trMessage, param2, param3, param4, param5, param6, param7, param8, param9, param10); return trMessage.toString(); } QString KLocalizedContext::xi18ndcp(const QString &domain, const QString &context, const QString &singular, const QString &plural, const QVariant ¶m1, const QVariant ¶m2, const QVariant ¶m3, const QVariant ¶m4, const QVariant ¶m5, const QVariant ¶m6, const QVariant ¶m7, const QVariant ¶m8, const QVariant ¶m9, const QVariant ¶m10) const { if (domain.isEmpty() || context.isEmpty() || singular.isEmpty() || plural.isEmpty()) { qCWarning(KI18N) << "xi18ndcp() needs at least four arguments"; return QString(); } KLocalizedString trMessage = kxi18ndcp(domain.toUtf8().constData(), context.toUtf8().constData(), singular.toUtf8().constData(), plural.toUtf8().constData()); resolvePlural(trMessage, param1); resolveMessage(trMessage, param2, param3, param4, param5, param6, param7, param8, param9, param10); return trMessage.toString(); } diff --git a/src/klocalizedcontext.h b/src/klocalizedcontext.h index 27ebcbd..e11722d 100644 --- a/src/klocalizedcontext.h +++ b/src/klocalizedcontext.h @@ -1,142 +1,129 @@ /* - * Copyright 2011 Marco Martin - * Copyright 2015 Aleix Pol Gonzalez - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2, 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 Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ + SPDX-FileCopyrightText: 2011 Marco Martin + SPDX-FileCopyrightText: 2015 Aleix Pol Gonzalez + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ #ifndef KLOCALIZEDCONTEXT_H #define KLOCALIZEDCONTEXT_H #include #include #include /** * @class KLocalizedContext klocalizedcontext.h * * This class is meant to be used to simplify integration of the KI18n framework * in QML. * * The way to do so, is by creating this object and setting it as a context * object: * * @code * QQuickView* view = new QQuickView; * view.engine()->rootContext()->setContextObject(new KLocalizedContext(view)); * @endcode * * Then i18n*() and xi18n*() functions should be available for use from the code * loaded in the engine, for the view. * * @note Plural functions differ from the C/C++ version. On QML/JS we can get a * real value easily. To solve warnings on those cases we'll cast the first argument * to make sure it's taken into account for the plural. * * @since 5.17 */ class KI18N_EXPORT KLocalizedContext : public QObject { Q_OBJECT /** * This property only needs to be specified if the context is being run on a library. * in an application there is no need to set the translation domain as the application's * domain can be used. */ Q_PROPERTY(QString translationDomain READ translationDomain WRITE setTranslationDomain NOTIFY translationDomainChanged) public: explicit KLocalizedContext(QObject *parent = nullptr); ~KLocalizedContext() override; QString translationDomain() const; void setTranslationDomain(const QString &domain); Q_INVOKABLE QString i18n(const QString &message, const QVariant &p1 = QVariant(), const QVariant &p2 = QVariant(), const QVariant &p3 = QVariant(), const QVariant &p4 = QVariant(), const QVariant &p5 = QVariant(), const QVariant &p6 = QVariant(), const QVariant &p7 = QVariant(), const QVariant &p8 = QVariant(), const QVariant &p9 = QVariant(), const QVariant &p10 = QVariant()) const; Q_INVOKABLE QString i18nc(const QString &context, const QString &message, const QVariant &p1 = QVariant(), const QVariant &p2 = QVariant(), const QVariant &p3 = QVariant(), const QVariant &p4 = QVariant(), const QVariant &p5 = QVariant(), const QVariant &p6 = QVariant(), const QVariant &p7 = QVariant(), const QVariant &p8 = QVariant(), const QVariant &p9 = QVariant(), const QVariant &p10 = QVariant()) const; Q_INVOKABLE QString i18np(const QString &singular, const QString &plural, const QVariant &p1 = QVariant(), const QVariant &p2 = QVariant(), const QVariant &p3 = QVariant(), const QVariant &p4 = QVariant(), const QVariant &p5 = QVariant(), const QVariant &p6 = QVariant(), const QVariant &p7 = QVariant(), const QVariant &p8 = QVariant(), const QVariant &p9 = QVariant(), const QVariant &p10 = QVariant()) const; Q_INVOKABLE QString i18ncp(const QString &context, const QString &singular, const QString &plural, const QVariant &p1 = QVariant(), const QVariant &p2 = QVariant(), const QVariant &p3 = QVariant(), const QVariant &p4 = QVariant(), const QVariant &p5 = QVariant(), const QVariant &p6 = QVariant(), const QVariant &p7 = QVariant(), const QVariant &p8 = QVariant(), const QVariant &p9 = QVariant(), const QVariant &p10 = QVariant()) const; Q_INVOKABLE QString i18nd(const QString &domain, const QString &message, const QVariant &p1 = QVariant(), const QVariant &p2 = QVariant(), const QVariant &p3 = QVariant(), const QVariant &p4 = QVariant(), const QVariant &p5 = QVariant(), const QVariant &p6 = QVariant(), const QVariant &p7 = QVariant(), const QVariant &p8 = QVariant(), const QVariant &p9 = QVariant(), const QVariant &p10 = QVariant()) const; Q_INVOKABLE QString i18ndc(const QString &domain, const QString &context, const QString &message, const QVariant &p1 = QVariant(), const QVariant &p2 = QVariant(), const QVariant &p3 = QVariant(), const QVariant &p4 = QVariant(), const QVariant &p5 = QVariant(), const QVariant &p6 = QVariant(), const QVariant &p7 = QVariant(), const QVariant &p8 = QVariant(), const QVariant &p9 = QVariant(), const QVariant &p10 = QVariant()) const; Q_INVOKABLE QString i18ndp(const QString &domain, const QString &singular, const QString &plural, const QVariant &p1 = QVariant(), const QVariant &p2 = QVariant(), const QVariant &p3 = QVariant(), const QVariant &p4 = QVariant(), const QVariant &p5 = QVariant(), const QVariant &p6 = QVariant(), const QVariant &p7 = QVariant(), const QVariant &p8 = QVariant(), const QVariant &p9 = QVariant(), const QVariant &p10 = QVariant()) const; Q_INVOKABLE QString i18ndcp(const QString &domain, const QString &context, const QString &singular, const QString &plural, const QVariant &p1 = QVariant(), const QVariant &p2 = QVariant(), const QVariant &p3 = QVariant(), const QVariant &p4 = QVariant(), const QVariant &p5 = QVariant(), const QVariant &p6 = QVariant(), const QVariant &p7 = QVariant(), const QVariant &p8 = QVariant(), const QVariant &p9 = QVariant(), const QVariant &p10 = QVariant()) const; Q_INVOKABLE QString xi18n(const QString &message, const QVariant &p1 = QVariant(), const QVariant &p2 = QVariant(), const QVariant &p3 = QVariant(), const QVariant &p4 = QVariant(), const QVariant &p5 = QVariant(), const QVariant &p6 = QVariant(), const QVariant &p7 = QVariant(), const QVariant &p8 = QVariant(), const QVariant &p9 = QVariant(), const QVariant &p10 = QVariant()) const; Q_INVOKABLE QString xi18nc(const QString &context, const QString &message, const QVariant &p1 = QVariant(), const QVariant &p2 = QVariant(), const QVariant &p3 = QVariant(), const QVariant &p4 = QVariant(), const QVariant &p5 = QVariant(), const QVariant &p6 = QVariant(), const QVariant &p7 = QVariant(), const QVariant &p8 = QVariant(), const QVariant &p9 = QVariant(), const QVariant &p10 = QVariant()) const; Q_INVOKABLE QString xi18np(const QString &singular, const QString &plural, const QVariant &p1 = QVariant(), const QVariant &p2 = QVariant(), const QVariant &p3 = QVariant(), const QVariant &p4 = QVariant(), const QVariant &p5 = QVariant(), const QVariant &p6 = QVariant(), const QVariant &p7 = QVariant(), const QVariant &p8 = QVariant(), const QVariant &p9 = QVariant(), const QVariant &p10 = QVariant()) const; Q_INVOKABLE QString xi18ncp(const QString &context, const QString &singular, const QString &plural, const QVariant &p1 = QVariant(), const QVariant &p2 = QVariant(), const QVariant &p3 = QVariant(), const QVariant &p4 = QVariant(), const QVariant &p5 = QVariant(), const QVariant &p6 = QVariant(), const QVariant &p7 = QVariant(), const QVariant &p8 = QVariant(), const QVariant &p9 = QVariant(), const QVariant &p10 = QVariant()) const; Q_INVOKABLE QString xi18nd(const QString &domain, const QString &message, const QVariant &p1 = QVariant(), const QVariant &p2 = QVariant(), const QVariant &p3 = QVariant(), const QVariant &p4 = QVariant(), const QVariant &p5 = QVariant(), const QVariant &p6 = QVariant(), const QVariant &p7 = QVariant(), const QVariant &p8 = QVariant(), const QVariant &p9 = QVariant(), const QVariant &p10 = QVariant()) const; Q_INVOKABLE QString xi18ndc(const QString &domain, const QString &context, const QString &message, const QVariant &p1 = QVariant(), const QVariant &p2 = QVariant(), const QVariant &p3 = QVariant(), const QVariant &p4 = QVariant(), const QVariant &p5 = QVariant(), const QVariant &p6 = QVariant(), const QVariant &p7 = QVariant(), const QVariant &p8 = QVariant(), const QVariant &p9 = QVariant(), const QVariant &p10 = QVariant()) const; Q_INVOKABLE QString xi18ndp(const QString &domain, const QString &singular, const QString &plural, const QVariant &p1 = QVariant(), const QVariant &p2 = QVariant(), const QVariant &p3 = QVariant(), const QVariant &p4 = QVariant(), const QVariant &p5 = QVariant(), const QVariant &p6 = QVariant(), const QVariant &p7 = QVariant(), const QVariant &p8 = QVariant(), const QVariant &p9 = QVariant(), const QVariant &p10 = QVariant()) const; Q_INVOKABLE QString xi18ndcp(const QString &domain, const QString &context, const QString &singular, const QString &plural, const QVariant &p1 = QVariant(), const QVariant &p2 = QVariant(), const QVariant &p3 = QVariant(), const QVariant &p4 = QVariant(), const QVariant &p5 = QVariant(), const QVariant &p6 = QVariant(), const QVariant &p7 = QVariant(), const QVariant &p8 = QVariant(), const QVariant &p9 = QVariant(), const QVariant &p10 = QVariant()) const; Q_SIGNALS: void translationDomainChanged(const QString& translationDomain); private: class KLocalizedContextPrivate * const d_ptr; Q_DECLARE_PRIVATE(KLocalizedContext) }; #endif diff --git a/src/klocalizedstring.cpp b/src/klocalizedstring.cpp index ffb5aed..1609fec 100644 --- a/src/klocalizedstring.cpp +++ b/src/klocalizedstring.cpp @@ -1,1666 +1,1653 @@ /* This file is part of the KDE libraries - Copyright (C) 2006, 2013 Chusslove Illich - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library 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 - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-FileCopyrightText: 2006, 2013 Chusslove Illich + + SPDX-License-Identifier: LGPL-2.0-or-later */ // We don't want i18n to be expanded to i18nd here #undef TRANSLATION_DOMAIN #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ki18n_logging.h" // Truncate string, for output of long messages. static QString shortenMessage(const QString &str) { const int maxlen = 20; if (str.length() <= maxlen) { return str; } else { return str.leftRef(maxlen) + QLatin1String("..."); } } static void splitLocale(const QString &aLocale, QString &language, QString &country, QString &modifier, QString &charset) { QString locale = aLocale; language.clear(); country.clear(); modifier.clear(); charset.clear(); // In case there are several concatenated locale specifications, // truncate all but first. int f = locale.indexOf(QLatin1Char(':')); if (f >= 0) { locale.truncate(f); } // now decompose into [language[_territory][.codeset][@modifier]] f = locale.indexOf(QLatin1Char('@')); if (f >= 0) { modifier = locale.mid(f + 1); locale.truncate(f); } f = locale.indexOf(QLatin1Char('.')); if (f >= 0) { charset = locale.mid(f + 1); locale.truncate(f); } f = locale.indexOf(QLatin1Char('_')); if (f >= 0) { country = locale.mid(f + 1); locale.truncate(f); } language = locale; } static void appendLocaleString(QStringList &languages, const QString &value) { // Process the value to create possible combinations. QString language, country, modifier, charset; splitLocale(value, language, country, modifier, charset); if (language.isEmpty()) { return; } if (!country.isEmpty() && !modifier.isEmpty()) { languages += language + QLatin1Char('_') + country + QLatin1Char('@') + modifier; } // NOTE: Priority is unclear in case both the country and // the modifier are present. Should really language@modifier be of // higher priority than language_country? // In at least one case (Serbian language), it is better this way. if (!modifier.isEmpty()) { languages += language + QLatin1Char('@') + modifier; } if (!country.isEmpty()) { languages += language + QLatin1Char('_') + country; } languages += language; } static void appendLanguagesFromVariable(QStringList &languages, const char *envar, bool isList = false) { QByteArray qenvar(qgetenv(envar)); if (!qenvar.isEmpty()) { QString value = QFile::decodeName(qenvar); if (isList) { #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) const auto listLanguages = value.split(QLatin1Char(':'), QString::SkipEmptyParts); #else const auto listLanguages = value.split(QLatin1Char(':'), Qt::SkipEmptyParts); #endif for (const QString &v : listLanguages) { appendLocaleString(languages, v); } } else { appendLocaleString(languages, value); } } } #if !defined(Q_OS_UNIX) || defined(Q_OS_ANDROID) static void appendLanguagesFromQLocale(QStringList &languages, const QLocale &locale) { const QStringList uiLangs = locale.uiLanguages(); for (QString value : uiLangs) { // no const ref because of replace() below appendLocaleString(languages, value.replace(QLatin1Char('-'), QLatin1Char('_'))); } } #endif // Extract the first country code from a list of language_COUNTRY strings. // Country code is converted to all lower case letters. static QString extractCountry(const QStringList &languages) { QString country; for (const QString &language : languages) { int pos1 = language.indexOf(QLatin1Char('_')); if (pos1 >= 0) { ++pos1; int pos2 = pos1; while (pos2 < language.length() && language[pos2].isLetter()) { ++pos2; } country = language.mid(pos1, pos2 - pos1); break; } } country = country.toLower(); return country; } typedef qulonglong pluraln; typedef qlonglong intn; typedef qulonglong uintn; typedef double realn; class KLocalizedStringPrivate { friend class KLocalizedString; QByteArray domain; QStringList languages; Kuit::VisualFormat format; QByteArray context; QByteArray text; QByteArray plural; QStringList arguments; QList values; QHash klsArguments; QHash klsArgumentFieldWidths; QHash klsArgumentFillChars; bool numberSet; pluraln number; int numberOrdinal; QHash dynamicContext; bool markupAware; bool relaxedSubs; KLocalizedStringPrivate() : format() , numberSet(false) , markupAware(false) , relaxedSubs(false) { } static void translateRaw(const QByteArray &domain, const QStringList &languages, const QByteArray &msgctxt, const QByteArray &msgid, const QByteArray &msgid_plural, qulonglong n, QString &language, QString &translation); QString toString(const QByteArray &domain, const QStringList &languages, Kuit::VisualFormat format, bool isArgument = false) const; QString substituteSimple(const QString &translation, const QStringList &arguments, QChar plchar = QLatin1Char('%'), bool isPartial = false) const; QString formatMarkup(const QByteArray &domain, const QString &language, const QString &context, const QString &text, Kuit::VisualFormat format) const; QString substituteTranscript(const QString &scriptedTranslation, const QString &language, const QString &country, const QString &ordinaryTranslation, const QStringList &arguments, const QList &values, bool &fallback) const; int resolveInterpolation(const QString &scriptedTranslation, int pos, const QString &language, const QString &country, const QString &ordinaryTranslation, const QStringList &arguments, const QList &values, QString &result, bool &fallback) const; QVariant segmentToValue(const QString &segment) const; QString postTranscript(const QString &pcall, const QString &language, const QString &country, const QString &finalTranslation, const QStringList &arguments, const QList &values) const; static const KCatalog &getCatalog(const QByteArray &domain, const QString &language); static void locateScriptingModule(const QByteArray &domain, const QString &language); static void loadTranscript(); void checkNumber(pluraln a) { if (!plural.isEmpty() && !numberSet) { number = a; numberSet = true; numberOrdinal = arguments.size(); } } }; typedef QHash KCatalogPtrHash; class KLocalizedStringPrivateStatics { public: QHash catalogs; QStringList languages; QByteArray ourDomain; QByteArray applicationDomain; QString codeLanguage; QStringList localeLanguages; const QString theFence; const QString startInterp; const QString endInterp; const QChar scriptPlchar; const QChar scriptVachar; const QString scriptDir; QHash > scriptModules; QList scriptModulesToLoad; bool loadTranscriptCalled; KTranscript *ktrs; QHash formatters; QList qtDomains; QList qtDomainInsertCount; QMutex klspMutex; KLocalizedStringPrivateStatics(); ~KLocalizedStringPrivateStatics(); void initializeLocaleLanguages(); }; KLocalizedStringPrivateStatics::KLocalizedStringPrivateStatics() : catalogs() , languages() , ourDomain(QByteArrayLiteral("ki18n5")) , applicationDomain() , codeLanguage(QStringLiteral("en_US")) , localeLanguages() , theFence(QStringLiteral("|/|")) , startInterp(QStringLiteral("$[")) , endInterp(QStringLiteral("]")) , scriptPlchar(QLatin1Char('%')) , scriptVachar(QLatin1Char('^')) , scriptDir(QStringLiteral("LC_SCRIPTS")) , scriptModules() , scriptModulesToLoad() , loadTranscriptCalled(false) , ktrs(nullptr) , formatters() , qtDomains() , qtDomainInsertCount() , klspMutex(QMutex::Recursive) { initializeLocaleLanguages(); languages = localeLanguages; } KLocalizedStringPrivateStatics::~KLocalizedStringPrivateStatics() { for (const KCatalogPtrHash &languageCatalogs : qAsConst(catalogs)) { qDeleteAll(languageCatalogs); } // ktrs is handled by QLibrary. //delete ktrs; qDeleteAll(formatters); } Q_GLOBAL_STATIC(KLocalizedStringPrivateStatics, staticsKLSP) void KLocalizedStringPrivateStatics::initializeLocaleLanguages() { QMutexLocker lock(&klspMutex); // Collect languages by same order of priority as for gettext(3). // LANGUAGE contains list of language codes, not locale string. appendLanguagesFromVariable(localeLanguages, "LANGUAGE", true); appendLanguagesFromVariable(localeLanguages, "LC_ALL"); appendLanguagesFromVariable(localeLanguages, "LC_MESSAGES"); appendLanguagesFromVariable(localeLanguages, "LANG"); #if !defined(Q_OS_UNIX) || defined(Q_OS_ANDROID) // For non UNIX platforms the environment variables might not // suffice so we add system locale UI languages, too. appendLanguagesFromQLocale(localeLanguages, QLocale::system()); #endif } KLocalizedString::KLocalizedString() : d(new KLocalizedStringPrivate) { } KLocalizedString::KLocalizedString(const char *domain, const char *context, const char *text, const char *plural, bool markupAware) : d(new KLocalizedStringPrivate) { d->domain = domain; d->languages.clear(); d->format = Kuit::UndefinedFormat; d->context = context; d->text = text; d->plural = plural; d->numberSet = false; d->number = 0; d->numberOrdinal = 0; d->markupAware = markupAware; d->relaxedSubs = false; } KLocalizedString::KLocalizedString(const KLocalizedString &rhs) : d(new KLocalizedStringPrivate(*rhs.d)) { } KLocalizedString &KLocalizedString::operator=(const KLocalizedString &rhs) { if (&rhs != this) { *d = *rhs.d; } return *this; } KLocalizedString::~KLocalizedString() { delete d; } bool KLocalizedString::isEmpty() const { return d->text.isEmpty(); } void KLocalizedStringPrivate::translateRaw(const QByteArray &domain, const QStringList &languages, const QByteArray &msgctxt, const QByteArray &msgid, const QByteArray &msgid_plural, qulonglong n, QString &language, QString &msgstr) { KLocalizedStringPrivateStatics *s = staticsKLSP(); // Empty msgid would result in returning the catalog header, // which is never intended, so warn and return empty translation. if (msgid.isNull() || msgid.isEmpty()) { qCWarning(KI18N) << "KLocalizedString: " "Trying to look up translation of \"\", fix the code."; language.clear(); msgstr.clear(); return; } // Gettext semantics allows empty context, but it is pointless, so warn. if (!msgctxt.isNull() && msgctxt.isEmpty()) { qCWarning(KI18N) << "KLocalizedString: " "Using \"\" as context, fix the code."; } // Gettext semantics allows empty plural, but it is pointless, so warn. if (!msgid_plural.isNull() && msgid_plural.isEmpty()) { qCWarning(KI18N) << "KLocalizedString: " "Using \"\" as plural text, fix the code."; } // Set translation to text in code language, in case no translation found. msgstr = msgid_plural.isNull() || n == 1 ? QString::fromUtf8(msgid) : QString::fromUtf8(msgid_plural); language = s->codeLanguage; if (domain.isEmpty()) { return; } // Languages are ordered from highest to lowest priority. for (const QString &testLanguage : languages) { // If code language reached, no catalog lookup is needed. if (testLanguage == s->codeLanguage) { return; } const KCatalog &catalog = getCatalog(domain, testLanguage); QString testMsgstr; if (!msgctxt.isNull() && !msgid_plural.isNull()) { testMsgstr = catalog.translate(msgctxt, msgid, msgid_plural, n); } else if (!msgid_plural.isNull()) { testMsgstr = catalog.translate(msgid, msgid_plural, n); } else if (!msgctxt.isNull()) { testMsgstr = catalog.translate(msgctxt, msgid); } else { testMsgstr = catalog.translate(msgid); } if (!testMsgstr.isEmpty()) { // Translation found. language = testLanguage; msgstr = testMsgstr; return; } } } QString KLocalizedString::toString() const { return d->toString(d->domain, d->languages, d->format); } QString KLocalizedString::toString(const char *domain) const { return d->toString(domain, d->languages, d->format); } QString KLocalizedString::toString(const QStringList &languages) const { return d->toString(d->domain, languages, d->format); } QString KLocalizedString::toString(Kuit::VisualFormat format) const { return d->toString(d->domain, d->languages, format); } QString KLocalizedStringPrivate::toString(const QByteArray &domain, const QStringList &languages, Kuit::VisualFormat format, bool isArgument) const { KLocalizedStringPrivateStatics *s = staticsKLSP(); QMutexLocker lock(&s->klspMutex); // Assure the message has been supplied. if (text.isEmpty()) { qCWarning(KI18N) << "Trying to convert empty KLocalizedString to QString."; #ifndef NDEBUG return QStringLiteral("(I18N_EMPTY_MESSAGE)"); #else return QString(); #endif } // Check whether plural argument has been supplied, if message has plural. if (!plural.isEmpty() && !numberSet) { qCWarning(KI18N) << QStringLiteral( "Plural argument to message {%1} not supplied before conversion.") .arg(shortenMessage(QString::fromUtf8(text))); } // Resolve inputs. QByteArray resolvedDomain = domain; if (resolvedDomain.isEmpty()) { resolvedDomain = s->applicationDomain; } QStringList resolvedLanguages = languages; if (resolvedLanguages.isEmpty()) { resolvedLanguages = s->languages; } Kuit::VisualFormat resolvedFormat = format; // Get raw translation. QString language, rawTranslation; translateRaw(resolvedDomain, resolvedLanguages, context, text, plural, number, language, rawTranslation); QString country = extractCountry(resolvedLanguages); // Set ordinary translation and possibly scripted translation. QString translation, scriptedTranslation; int fencePos = rawTranslation.indexOf(s->theFence); if (fencePos > 0) { // Script fence has been found, strip the scripted from the // ordinary translation. translation = rawTranslation.left(fencePos); // Scripted translation. scriptedTranslation = rawTranslation.mid(fencePos + s->theFence.length()); // Try to initialize Transcript if not initialized and script not empty. // FIXME: And also if Transcript not disabled: where to configure this? if (!s->loadTranscriptCalled && !scriptedTranslation.isEmpty()) { loadTranscript(); // Definitions from this library's scripting module // must be available to all other modules. // So force creation of this library's catalog here, // to make sure the scripting module is loaded. getCatalog(s->ourDomain, language); } } else if (fencePos < 0) { // No script fence, use translation as is. translation = rawTranslation; } else { // fencePos == 0 // The msgstr starts with the script fence, no ordinary translation. // This is not allowed, consider message not translated. qCWarning(KI18N) << QStringLiteral( "Scripted message {%1} without ordinary translation, discarded.") .arg(shortenMessage(translation)); translation = plural.isEmpty() || number == 1 ? QString::fromUtf8(text) : QString::fromUtf8(plural); } // Resolve substituted KLocalizedString arguments. QStringList resolvedArguments; QList resolvedValues; for (int i = 0; i < arguments.size(); i++) { if (klsArguments.contains(i)) { const KLocalizedString &kls = klsArguments.value(i); int fieldWidth = klsArgumentFieldWidths.value(i); QChar fillChar = klsArgumentFillChars.value(i); // Override argument's languages and format, but not domain. bool isArgumentSub = true; QString resdArg = kls.d->toString(kls.d->domain, resolvedLanguages, resolvedFormat, isArgumentSub); resolvedValues.append(resdArg); if (markupAware && !kls.d->markupAware) { resdArg = Kuit::escape(resdArg); } resdArg = QStringLiteral("%1").arg(resdArg, fieldWidth, fillChar); resolvedArguments.append(resdArg); } else { QString resdArg = arguments[i]; if (markupAware) { resdArg = Kuit::escape(resdArg); } resolvedArguments.append(resdArg); resolvedValues.append(values[i]); } } // Substitute placeholders in ordinary translation. QString finalTranslation = substituteSimple(translation, resolvedArguments); if (markupAware && !isArgument) { // Resolve markup in ordinary translation. finalTranslation = formatMarkup(resolvedDomain, language, QString::fromUtf8(context), finalTranslation, resolvedFormat); } // If there is also a scripted translation. if (!scriptedTranslation.isEmpty()) { // Evaluate scripted translation. bool fallback; scriptedTranslation = substituteTranscript(scriptedTranslation, language, country, finalTranslation, resolvedArguments, resolvedValues, fallback); // If any translation produced and no fallback requested. if (!scriptedTranslation.isEmpty() && !fallback) { if (markupAware && !isArgument) { // Resolve markup in scripted translation. scriptedTranslation = formatMarkup(resolvedDomain, language, QString::fromUtf8(context), scriptedTranslation, resolvedFormat); } finalTranslation = scriptedTranslation; } } // Execute any scripted post calls; they cannot modify the final result, // but are used to set states. if (s->ktrs != nullptr) { const QStringList pcalls = s->ktrs->postCalls(language); for (const QString &pcall : pcalls) { postTranscript(pcall, language, country, finalTranslation, resolvedArguments, resolvedValues); } } return finalTranslation; } QString KLocalizedStringPrivate::substituteSimple(const QString &translation, const QStringList &arguments, QChar plchar, bool isPartial) const { #ifdef NDEBUG Q_UNUSED(isPartial); #endif QStringList tsegs; // text segments per placeholder occurrence QList plords; // ordinal numbers per placeholder occurrence #ifndef NDEBUG QVector ords; // indicates which placeholders are present #endif int slen = translation.length(); int spos = 0; int tpos = translation.indexOf(plchar); while (tpos >= 0) { int ctpos = tpos; ++tpos; if (tpos == slen) { break; } if (translation[tpos].digitValue() > 0) { // NOTE: %0 is not considered a placeholder. // Get the placeholder ordinal. int plord = 0; while (tpos < slen && translation[tpos].digitValue() >= 0) { plord = 10 * plord + translation[tpos].digitValue(); ++tpos; } --plord; // ordinals are zero based #ifndef NDEBUG // Perhaps enlarge storage for indicators. // Note that QVector will initialize new elements to 0, // as they are supposed to be. if (plord >= ords.size()) { ords.resize(plord + 1); } // Indicate that placeholder with computed ordinal is present. ords[plord] = 1; #endif // Store text segment prior to placeholder and placeholder number. tsegs.append(translation.mid(spos, ctpos - spos)); plords.append(plord); // Position of next text segment. spos = tpos; } tpos = translation.indexOf(plchar, tpos); } // Store last text segment. tsegs.append(translation.mid(spos)); #ifndef NDEBUG // Perhaps enlarge storage for plural-number ordinal. if (!plural.isEmpty() && numberOrdinal >= ords.size()) { ords.resize(numberOrdinal + 1); } // Message might have plural but without plural placeholder, which is an // allowed state. To ease further logic, indicate that plural placeholder // is present anyway if message has plural. if (!plural.isEmpty()) { ords[numberOrdinal] = 1; } #endif // Assemble the final string from text segments and arguments. QString finalTranslation; for (int i = 0; i < plords.size(); i++) { finalTranslation.append(tsegs.at(i)); if (plords.at(i) >= arguments.size()) { // too little arguments // put back the placeholder finalTranslation.append(QLatin1Char('%') + QString::number(plords.at(i) + 1)); #ifndef NDEBUG if (!isPartial) { // spoof the message finalTranslation.append(QStringLiteral("(I18N_ARGUMENT_MISSING)")); } #endif } else { // just fine finalTranslation.append(arguments.at(plords.at(i))); } } finalTranslation.append(tsegs.last()); #ifndef NDEBUG if (!isPartial && !relaxedSubs) { // Check that there are no gaps in numbering sequence of placeholders. bool gaps = false; for (int i = 0; i < ords.size(); i++) { if (!ords.at(i)) { gaps = true; qCWarning(KI18N) << QStringLiteral( "Placeholder %%1 skipped in message {%2}.") .arg(QString::number(i + 1), shortenMessage(translation)); } } // If no gaps, check for mismatch between the number of // unique placeholders and actually supplied arguments. if (!gaps && ords.size() != arguments.size()) { qCWarning(KI18N) << QString::fromLatin1( "%1 instead of %2 arguments to message {%3} " "supplied before conversion.") .arg(arguments.size()).arg(ords.size()) .arg(shortenMessage(translation)); } // Some spoofs. if (gaps) { finalTranslation.append(QStringLiteral("(I18N_GAPS_IN_PLACEHOLDER_SEQUENCE)")); } if (ords.size() < arguments.size()) { finalTranslation.append(QStringLiteral("(I18N_EXCESS_ARGUMENTS_SUPPLIED)")); } } if (!isPartial) { if (!plural.isEmpty() && !numberSet) { finalTranslation.append(QStringLiteral("(I18N_PLURAL_ARGUMENT_MISSING)")); } } #endif return finalTranslation; } QString KLocalizedStringPrivate::formatMarkup(const QByteArray &domain, const QString &language, const QString &context, const QString &text, Kuit::VisualFormat format) const { KLocalizedStringPrivateStatics *s = staticsKLSP(); QHash::iterator formatter = s->formatters.find(language); if (formatter == s->formatters.end()) { formatter = s->formatters.insert(language, new KuitFormatter(language)); } return (*formatter)->format(domain, context, text, format); } QString KLocalizedStringPrivate::substituteTranscript(const QString &scriptedTranslation, const QString &language, const QString &country, const QString &ordinaryTranslation, const QStringList &arguments, const QList &values, bool &fallback) const { KLocalizedStringPrivateStatics *s = staticsKLSP(); if (s->ktrs == nullptr) { // Scripting engine not available. return QString(); } // Iterate by interpolations. QString finalTranslation; fallback = false; int ppos = 0; int tpos = scriptedTranslation.indexOf(s->startInterp); while (tpos >= 0) { // Resolve substitutions in preceding text. QString ptext = substituteSimple(scriptedTranslation.mid(ppos, tpos - ppos), arguments, s->scriptPlchar, true); finalTranslation.append(ptext); // Resolve interpolation. QString result; bool fallbackLocal; tpos = resolveInterpolation(scriptedTranslation, tpos, language, country, ordinaryTranslation, arguments, values, result, fallbackLocal); // If there was a problem in parsing the interpolation, cannot proceed // (debug info already reported while parsing). if (tpos < 0) { return QString(); } // If fallback has been explicitly requested, indicate global fallback // but proceed with evaluations (other interpolations may set states). if (fallbackLocal) { fallback = true; } // Add evaluated interpolation to the text. finalTranslation.append(result); // On to next interpolation. ppos = tpos; tpos = scriptedTranslation.indexOf(s->startInterp, tpos); } // Last text segment. finalTranslation.append(substituteSimple(scriptedTranslation.mid(ppos), arguments, s->scriptPlchar, true)); // Return empty string if fallback was requested. return fallback ? QString() : finalTranslation; } int KLocalizedStringPrivate::resolveInterpolation(const QString &scriptedTranslation, int pos, const QString &language, const QString &country, const QString &ordinaryTranslation, const QStringList &arguments, const QList &values, QString &result, bool &fallback) const { // pos is the position of opening character sequence. // Returns the position of first character after closing sequence, // or -1 in case of parsing error. // result is set to result of Transcript evaluation. // fallback is set to true if Transcript evaluation requested so. KLocalizedStringPrivateStatics *s = staticsKLSP(); result.clear(); fallback = false; // Split interpolation into arguments. QList iargs; int slen = scriptedTranslation.length(); int islen = s->startInterp.length(); int ielen = s->endInterp.length(); int tpos = pos + s->startInterp.length(); while (1) { // Skip whitespace. while (tpos < slen && scriptedTranslation[tpos].isSpace()) { ++tpos; } if (tpos == slen) { qCWarning(KI18N) << QStringLiteral( "Unclosed interpolation {%1} in message {%2}.") .arg(scriptedTranslation.mid(pos, tpos - pos), shortenMessage(scriptedTranslation)); return -1; } if (scriptedTranslation.midRef(tpos, ielen) == s->endInterp) { break; // no more arguments } // Parse argument: may be concatenated from free and quoted text, // and sub-interpolations. // Free and quoted segments may contain placeholders, substitute them; // recurse into sub-interpolations. // Free segments may be value references, parse and record for // consideration at the end. // Mind backslash escapes throughout. QStringList segs; QVariant vref; while (!scriptedTranslation[tpos].isSpace() && scriptedTranslation.mid(tpos, ielen) != s->endInterp) { if (scriptedTranslation[tpos] == QLatin1Char('\'')) { // quoted segment QString seg; ++tpos; // skip opening quote // Find closing quote. while (tpos < slen && scriptedTranslation[tpos] != QLatin1Char('\'')) { if (scriptedTranslation[tpos] == QLatin1Char('\\')) { ++tpos; // escape next character } seg.append(scriptedTranslation[tpos]); ++tpos; } if (tpos == slen) { qCWarning(KI18N) << QStringLiteral( "Unclosed quote in interpolation {%1} in message {%2}.") .arg(scriptedTranslation.mid(pos, tpos - pos), shortenMessage(scriptedTranslation)); return -1; } // Append to list of segments, resolving placeholders. segs.append(substituteSimple(seg, arguments, s->scriptPlchar, true)); ++tpos; // skip closing quote } else if (scriptedTranslation.mid(tpos, islen) == s->startInterp) { // sub-interpolation QString resultLocal; bool fallbackLocal; tpos = resolveInterpolation(scriptedTranslation, tpos, language, country, ordinaryTranslation, arguments, values, resultLocal, fallbackLocal); if (tpos < 0) { // unrecoverable problem in sub-interpolation // Error reported in the subcall. return tpos; } if (fallbackLocal) { // sub-interpolation requested fallback fallback = true; } segs.append(resultLocal); } else { // free segment QString seg; // Find whitespace, quote, opening or closing sequence. while (tpos < slen && !scriptedTranslation[tpos].isSpace() && scriptedTranslation[tpos] != QLatin1Char('\'') && scriptedTranslation.mid(tpos, islen) != s->startInterp && scriptedTranslation.mid(tpos, ielen) != s->endInterp) { if (scriptedTranslation[tpos] == QLatin1Char('\\')) { ++tpos; // escape next character } seg.append(scriptedTranslation[tpos]); ++tpos; } if (tpos == slen) { qCWarning(KI18N) << QStringLiteral( "Non-terminated interpolation {%1} in message {%2}.") .arg(scriptedTranslation.mid(pos, tpos - pos), shortenMessage(scriptedTranslation)); return -1; } // The free segment may look like a value reference; // in that case, record which value it would reference, // and add verbatim to the segment list. // Otherwise, do a normal substitution on the segment. vref = segmentToValue(seg); if (vref.isValid()) { segs.append(seg); } else { segs.append(substituteSimple(seg, arguments, s->scriptPlchar, true)); } } } // Append this argument to rest of the arguments. // If the there was a single text segment and it was a proper value // reference, add it instead of the joined segments. // Otherwise, add the joined segments. if (segs.size() == 1 && vref.isValid()) { iargs.append(vref); } else { iargs.append(segs.join(QString())); } } tpos += ielen; // skip to first character after closing sequence // NOTE: Why not substitute placeholders (via substituteSimple) in one // global pass, then handle interpolations in second pass? Because then // there is the danger of substituted text or sub-interpolations producing // quotes and escapes themselves, which would mess up the parsing. // Evaluate interpolation. QString msgctxt = QString::fromUtf8(context); QString msgid = QString::fromUtf8(text); QString scriptError; bool fallbackLocal; result = s->ktrs->eval(iargs, language, country, msgctxt, dynamicContext, msgid, arguments, values, ordinaryTranslation, s->scriptModulesToLoad, scriptError, fallbackLocal); // s->scriptModulesToLoad will be cleared during the call. if (fallbackLocal) { // evaluation requested fallback fallback = true; } if (!scriptError.isEmpty()) { // problem with evaluation fallback = true; // also signal fallback if (!scriptError.isEmpty()) { qCWarning(KI18N) << QStringLiteral( "Interpolation {%1} in {%2} failed: %3") .arg(scriptedTranslation.mid(pos, tpos - pos), shortenMessage(scriptedTranslation), scriptError); } } return tpos; } QVariant KLocalizedStringPrivate::segmentToValue(const QString &segment) const { KLocalizedStringPrivateStatics *s = staticsKLSP(); // Return invalid variant if segment is either not a proper // value reference, or the reference is out of bounds. // Value reference must start with a special character. if (!segment.startsWith(s->scriptVachar)) { return QVariant(); } // Reference number must start with 1-9. // (If numstr is empty, toInt() will return 0.) QString numstr = segment.mid(1); if (numstr.leftRef(1).toInt() < 1) { return QVariant(); } // Number must be valid and in bounds. bool ok; int index = numstr.toInt(&ok) - 1; if (!ok || index >= values.size()) { return QVariant(); } // Passed all hoops. return values.at(index); } QString KLocalizedStringPrivate::postTranscript(const QString &pcall, const QString &language, const QString &country, const QString &finalTranslation, const QStringList &arguments, const QList &values) const { KLocalizedStringPrivateStatics *s = staticsKLSP(); if (s->ktrs == nullptr) { // Scripting engine not available. // (Though this cannot happen, we wouldn't be here then.) return QString(); } // Resolve the post call. QList iargs; iargs.append(pcall); QString msgctxt = QString::fromUtf8(context); QString msgid = QString::fromUtf8(text); QString scriptError; bool fallback; QString dummy = s->ktrs->eval(iargs, language, country, msgctxt, dynamicContext, msgid, arguments, values, finalTranslation, s->scriptModulesToLoad, scriptError, fallback); // s->scriptModulesToLoad will be cleared during the call. // If the evaluation went wrong. if (!scriptError.isEmpty()) { qCWarning(KI18N) << QStringLiteral( "Post call {%1} for message {%2} failed: %3") .arg(pcall, shortenMessage(msgid), scriptError); return QString(); } return finalTranslation; } KLocalizedString KLocalizedString::withLanguages(const QStringList &languages) const { KLocalizedString kls(*this); kls.d->languages = languages; return kls; } KLocalizedString KLocalizedString::withDomain(const char *domain) const { KLocalizedString kls(*this); kls.d->domain = domain; return kls; } KLocalizedString KLocalizedString::withFormat(Kuit::VisualFormat format) const { KLocalizedString kls(*this); kls.d->format = format; return kls; } KLocalizedString KLocalizedString::subs(int a, int fieldWidth, int base, QChar fillChar) const { KLocalizedString kls(*this); kls.d->checkNumber(std::abs(a)); kls.d->arguments.append(QStringLiteral("%L1").arg(a, fieldWidth, base, fillChar)); kls.d->values.append(static_cast(a)); return kls; } KLocalizedString KLocalizedString::subs(uint a, int fieldWidth, int base, QChar fillChar) const { KLocalizedString kls(*this); kls.d->checkNumber(a); kls.d->arguments.append(QStringLiteral("%L1").arg(a, fieldWidth, base, fillChar)); kls.d->values.append(static_cast(a)); return kls; } KLocalizedString KLocalizedString::subs(long a, int fieldWidth, int base, QChar fillChar) const { KLocalizedString kls(*this); kls.d->checkNumber(std::abs(a)); kls.d->arguments.append(QStringLiteral("%L1").arg(a, fieldWidth, base, fillChar)); kls.d->values.append(static_cast(a)); return kls; } KLocalizedString KLocalizedString::subs(ulong a, int fieldWidth, int base, QChar fillChar) const { KLocalizedString kls(*this); kls.d->checkNumber(a); kls.d->arguments.append(QStringLiteral("%L1").arg(a, fieldWidth, base, fillChar)); kls.d->values.append(static_cast(a)); return kls; } KLocalizedString KLocalizedString::subs(qlonglong a, int fieldWidth, int base, QChar fillChar) const { KLocalizedString kls(*this); kls.d->checkNumber(qAbs(a)); kls.d->arguments.append(QStringLiteral("%L1").arg(a, fieldWidth, base, fillChar)); kls.d->values.append(static_cast(a)); return kls; } KLocalizedString KLocalizedString::subs(qulonglong a, int fieldWidth, int base, QChar fillChar) const { KLocalizedString kls(*this); kls.d->checkNumber(a); kls.d->arguments.append(QStringLiteral("%L1").arg(a, fieldWidth, base, fillChar)); kls.d->values.append(static_cast(a)); return kls; } KLocalizedString KLocalizedString::subs(double a, int fieldWidth, char format, int precision, QChar fillChar) const { KLocalizedString kls(*this); kls.d->arguments.append(QStringLiteral("%L1").arg(a, fieldWidth, format, precision, fillChar)); kls.d->values.append(static_cast(a)); return kls; } KLocalizedString KLocalizedString::subs(QChar a, int fieldWidth, QChar fillChar) const { KLocalizedString kls(*this); QString baseArg = QString(a); QString fmtdArg = QStringLiteral("%1").arg(a, fieldWidth, fillChar); kls.d->arguments.append(fmtdArg); kls.d->values.append(baseArg); return kls; } KLocalizedString KLocalizedString::subs(const QString &a, int fieldWidth, QChar fillChar) const { KLocalizedString kls(*this); QString baseArg = a; QString fmtdArg = QStringLiteral("%1").arg(a, fieldWidth, fillChar); kls.d->arguments.append(fmtdArg); kls.d->values.append(baseArg); return kls; } KLocalizedString KLocalizedString::subs(const KLocalizedString &a, int fieldWidth, QChar fillChar) const { KLocalizedString kls(*this); // KLocalizedString arguments must be resolved inside toString // when the domain, language, visual format, etc. become known. int i = kls.d->arguments.size(); kls.d->klsArguments[i] = a; kls.d->klsArgumentFieldWidths[i] = fieldWidth; kls.d->klsArgumentFillChars[i] = fillChar; kls.d->arguments.append(QString()); kls.d->values.append(0); return kls; } KLocalizedString KLocalizedString::inContext(const QString &key, const QString &value) const { KLocalizedString kls(*this); kls.d->dynamicContext[key] = value; return kls; } KLocalizedString KLocalizedString::relaxSubs() const { KLocalizedString kls(*this); kls.d->relaxedSubs = true; return kls; } KLocalizedString KLocalizedString::ignoreMarkup() const { KLocalizedString kls(*this); kls.d->markupAware = false; return kls; } QByteArray KLocalizedString::untranslatedText() const { return d->text; } void KLocalizedString::setApplicationDomain(const char *domain) { KLocalizedStringPrivateStatics *s = staticsKLSP(); QMutexLocker lock(&s->klspMutex); s->applicationDomain = domain; } QByteArray KLocalizedString::applicationDomain() { KLocalizedStringPrivateStatics *s = staticsKLSP(); return s->applicationDomain; } QStringList KLocalizedString::languages() { KLocalizedStringPrivateStatics *s = staticsKLSP(); return s->languages; } void KLocalizedString::setLanguages(const QStringList &languages) { KLocalizedStringPrivateStatics *s = staticsKLSP(); QMutexLocker lock(&s->klspMutex); s->languages = languages; } void KLocalizedString::clearLanguages() { KLocalizedStringPrivateStatics *s = staticsKLSP(); QMutexLocker lock(&s->klspMutex); s->languages = s->localeLanguages; } bool KLocalizedString::isApplicationTranslatedInto(const QString &language) { KLocalizedStringPrivateStatics *s = staticsKLSP(); return language == s->codeLanguage || !KCatalog::catalogLocaleDir(s->applicationDomain, language).isEmpty(); } QSet KLocalizedString::availableApplicationTranslations() { return availableDomainTranslations(staticsKLSP()->applicationDomain); } QSet KLocalizedString::availableDomainTranslations(const QByteArray &domain) { QSet availableLanguages; if (!domain.isEmpty()) { availableLanguages = KCatalog::availableCatalogLanguages(domain); availableLanguages.insert(staticsKLSP()->codeLanguage); } return availableLanguages; } const KCatalog &KLocalizedStringPrivate::getCatalog(const QByteArray &domain, const QString &language) { KLocalizedStringPrivateStatics *s = staticsKLSP(); QMutexLocker lock(&s->klspMutex); QHash::iterator languageCatalogs = s->catalogs.find(domain); if (languageCatalogs == s->catalogs.end()) { languageCatalogs = s->catalogs.insert(domain, KCatalogPtrHash()); } KCatalogPtrHash::iterator catalog = languageCatalogs->find(language); if (catalog == languageCatalogs->end()) { catalog = languageCatalogs->insert(language, new KCatalog(domain, language)); locateScriptingModule(domain, language); } return **catalog; } void KLocalizedStringPrivate::locateScriptingModule(const QByteArray &domain, const QString &language) { KLocalizedStringPrivateStatics *s = staticsKLSP(); QMutexLocker lock(&s->klspMutex); // Assemble module's relative path. QString modrpath = language + QLatin1Char('/') + s->scriptDir + QLatin1Char('/') + QString::fromLatin1(domain) + QLatin1Char('/') + QString::fromLatin1(domain) + QLatin1String(".js"); // Try to find this module. QString modapath = QStandardPaths::locate( QStandardPaths::GenericDataLocation, QLatin1String("locale") + QLatin1Char('/') + modrpath); // If the module exists and hasn't been already included. if (!modapath.isEmpty() && !s->scriptModules[language].contains(domain)) { // Indicate that the module has been considered. s->scriptModules[language].append(domain); // Store the absolute path and language of the module, // to load on next script evaluation. QStringList module; module.append(modapath); module.append(language); s->scriptModulesToLoad.append(module); } } extern "C" { typedef KTranscript *(*InitFunc)(); } void KLocalizedStringPrivate::loadTranscript() { KLocalizedStringPrivateStatics *s = staticsKLSP(); QMutexLocker lock(&s->klspMutex); s->loadTranscriptCalled = true; s->ktrs = nullptr; // null indicates that Transcript is not available // QPluginLoader is just used to find the plugin QPluginLoader loader(QStringLiteral("kf5/ktranscript")); if (loader.fileName().isEmpty()) { qCWarning(KI18N) << "Cannot find Transcript plugin."; return; } QLibrary lib(loader.fileName()); if (!lib.load()) { qCWarning(KI18N) << "Cannot load Transcript plugin:" << lib.errorString(); return; } InitFunc initf = (InitFunc) lib.resolve("load_transcript"); if (!initf) { lib.unload(); qCWarning(KI18N) << "Cannot find function load_transcript in Transcript plugin."; return; } s->ktrs = initf(); } QString KLocalizedString::localizedFilePath(const QString &filePath) { KLocalizedStringPrivateStatics *s = staticsKLSP(); // Check if l10n subdirectory is present, stop if not. QFileInfo fileInfo(filePath); QString locDirPath = fileInfo.path() + QLatin1Char('/') + QLatin1String("l10n"); QFileInfo locDirInfo(locDirPath); if (!locDirInfo.isDir()) { return filePath; } // Go through possible localized paths by priority of languages, // return first that exists. QString fileName = fileInfo.fileName(); for (const QString &lang : qAsConst(s->languages)) { QString locFilePath = locDirPath + QLatin1Char('/') + lang + QLatin1Char('/') + fileName; QFileInfo locFileInfo(locFilePath); if (locFileInfo.isFile() && locFileInfo.isReadable()) { return locFilePath; } } return filePath; } QString KLocalizedString::removeAcceleratorMarker(const QString &label) { return ::removeAcceleratorMarker(label); } #if KI18N_BUILD_DEPRECATED_SINCE(5, 0) QString KLocalizedString::translateQt(const char *context, const char *sourceText, const char *comment, int n) { // NOTE: Qt message semantics. // // Qt's context is normally the name of the class of the method which makes // the tr(sourceText) call. However, it can also be manually supplied via // translate(context, sourceText) call. // // Qt's sourceText is the actual message displayed to the user. // // Qt's comment is an optional argument of tr() and translate(), like // tr(sourceText, comment) and translate(context, sourceText, comment). // // We handle this in the following way: // // If the comment is given, then it is considered gettext's msgctxt, so a // context call is made. // // If the comment is not given, but context is given, then we treat it as // msgctxt only if it was manually supplied (the one in translate()) -- but // we don't know this, so we first try a context call, and if translation // is not found, we fallback to ordinary call. // // If neither comment nor context are given, it's just an ordinary call // on sourceText. Q_UNUSED(n); KLocalizedStringPrivateStatics *s = staticsKLSP(); QMutexLocker lock(&s->klspMutex); if (!sourceText || !sourceText[0]) { qCWarning(KI18N) << "KLocalizedString::translateQt: " "Trying to look up translation of \"\", fix the code."; return QString(); } // NOTE: Condition (language != s->codeLanguage) means that translation // was found, otherwise the original text was returned as translation. QString translation; QString language; for (const QByteArray &domain : qAsConst(s->qtDomains)) { if (comment && comment[0]) { // Comment given, go for context call. KLocalizedStringPrivate::translateRaw(domain, s->languages, comment, sourceText, nullptr, 0, language, translation); } else { // Comment not given, go for try-fallback with context. if (context && context[0]) { KLocalizedStringPrivate::translateRaw(domain, s->languages, context, sourceText, nullptr, 0, language, translation); } if (language.isEmpty() || language == s->codeLanguage) { KLocalizedStringPrivate::translateRaw(domain, s->languages, nullptr, sourceText, nullptr, 0, language, translation); } } if (language != s->codeLanguage) { return translation; } } // No proper translation found, return empty according to Qt semantics. return QString(); } #endif #if KI18N_BUILD_DEPRECATED_SINCE(5, 0) void KLocalizedString::insertQtDomain(const char *domain) { KLocalizedStringPrivateStatics *s = staticsKLSP(); QMutexLocker lock(&s->klspMutex); int pos = s->qtDomains.indexOf(domain); if (pos < 0) { // Domain priority is undefined, but to minimize damage // due to message conflicts, put later inserted catalogs at front. s->qtDomains.prepend(domain); s->qtDomainInsertCount.prepend(1); } else { ++s->qtDomainInsertCount[pos]; } } #endif #if KI18N_BUILD_DEPRECATED_SINCE(5, 0) void KLocalizedString::removeQtDomain(const char *domain) { KLocalizedStringPrivateStatics *s = staticsKLSP(); QMutexLocker lock(&s->klspMutex); int pos = s->qtDomains.indexOf(domain); if (pos >= 0 && --s->qtDomainInsertCount[pos] == 0) { s->qtDomains.removeAt(pos); s->qtDomainInsertCount.removeAt(pos); } } #endif void KLocalizedString::addDomainLocaleDir(const QByteArray &domain, const QString &path) { KCatalog::addDomainLocaleDir(domain, path); } KLocalizedString ki18n(const char *text) { return KLocalizedString(nullptr, nullptr, text, nullptr, false); } KLocalizedString ki18nc(const char *context, const char *text) { return KLocalizedString(nullptr, context, text, nullptr, false); } KLocalizedString ki18np(const char *singular, const char *plural) { return KLocalizedString(nullptr, nullptr, singular, plural, false); } KLocalizedString ki18ncp(const char *context, const char *singular, const char *plural) { return KLocalizedString(nullptr, context, singular, plural, false); } KLocalizedString ki18nd(const char *domain, const char *text) { return KLocalizedString(domain, nullptr, text, nullptr, false); } KLocalizedString ki18ndc(const char *domain, const char *context, const char *text) { return KLocalizedString(domain, context, text, nullptr, false); } KLocalizedString ki18ndp(const char *domain, const char *singular, const char *plural) { return KLocalizedString(domain, nullptr, singular, plural, false); } KLocalizedString ki18ndcp(const char *domain, const char *context, const char *singular, const char *plural) { return KLocalizedString(domain, context, singular, plural, false); } KLocalizedString kxi18n(const char *text) { return KLocalizedString(nullptr, nullptr, text, nullptr, true); } KLocalizedString kxi18nc(const char *context, const char *text) { return KLocalizedString(nullptr, context, text, nullptr, true); } KLocalizedString kxi18np(const char *singular, const char *plural) { return KLocalizedString(nullptr, nullptr, singular, plural, true); } KLocalizedString kxi18ncp(const char *context, const char *singular, const char *plural) { return KLocalizedString(nullptr, context, singular, plural, true); } KLocalizedString kxi18nd(const char *domain, const char *text) { return KLocalizedString(domain, nullptr, text, nullptr, true); } KLocalizedString kxi18ndc(const char *domain, const char *context, const char *text) { return KLocalizedString(domain, context, text, nullptr, true); } KLocalizedString kxi18ndp(const char *domain, const char *singular, const char *plural) { return KLocalizedString(domain, nullptr, singular, plural, true); } KLocalizedString kxi18ndcp(const char *domain, const char *context, const char *singular, const char *plural) { return KLocalizedString(domain, context, singular, plural, true); } diff --git a/src/klocalizedstring.h b/src/klocalizedstring.h index 72bf25c..cd87710 100644 --- a/src/klocalizedstring.h +++ b/src/klocalizedstring.h @@ -1,2046 +1,2033 @@ /* This file is part of the KDE libraries - Copyright (C) 2006, 2013 Chusslove Illich - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library 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 - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-FileCopyrightText: 2006, 2013 Chusslove Illich + + SPDX-License-Identifier: LGPL-2.0-or-later */ #ifndef KLOCALIZEDSTRING_H #define KLOCALIZEDSTRING_H #include #include #include #include #include #include #include class KLocalizedStringPrivate; /** * \file klocalizedstring.h */ #ifndef I18N_NOOP /** * Wrap string for extraction. * * See \ref i18n_noop for use cases. */ #define I18N_NOOP(text) text #endif #ifndef I18NC_NOOP /** * Wrap string with context for extraction. * * See \ref i18n_noop for use cases. */ #define I18NC_NOOP(context, text) context, text #endif #ifndef I18N_NOOP2 /** * Wrap string with context for extraction, discarding context. * WARNING: this means you'll need to pass the exact same context when calling i18nc() later on. * Do not make typos... * The preferred solution is to use I18NC_NOOP and store both @p context and @p text. * I18NC_NOOP2 exists for cases where storing the context is not possible. * * \deprecated between 5.0 and 5.64, re-enabled in 5.65 */ #define I18N_NOOP2(context, text) text #endif #if KI18N_ENABLE_DEPRECATED_SINCE(5, 0) #ifndef I18N_NOOP2_NOSTRIP /** * Wrap string with context for extraction. * * \deprecated Since 5.0, use \c I18NC_NOOP. */ #define I18N_NOOP2_NOSTRIP(context, text) context, text #endif #endif // KI18N_ENABLE_DEPRECATED_SINCE(5, 0) /** * @class KLocalizedString klocalizedstring.h * * \short Class for producing and handling localized messages * * \c KLocalizedString handles translation and * argument substitution and formatting of user-visible text. * * \c KLocalizedString instances are usually not constructed directly, * but through one of the wrapper \c \*i18n\* calls. * * For detailed information on how to use KI18n functions please refer * to \ref prg_guide. */ class KI18N_EXPORT KLocalizedString { friend class KLocalizedStringPrivate; friend KLocalizedString KI18N_EXPORT ki18n(const char *text); friend KLocalizedString KI18N_EXPORT ki18nc(const char *context, const char *text); friend KLocalizedString KI18N_EXPORT ki18np(const char *singular, const char *plural); friend KLocalizedString KI18N_EXPORT ki18ncp(const char *context, const char *singular, const char *plural); friend KLocalizedString KI18N_EXPORT ki18nd(const char *domain, const char *text); friend KLocalizedString KI18N_EXPORT ki18ndc(const char *domain, const char *context, const char *text); friend KLocalizedString KI18N_EXPORT ki18ndp(const char *domain, const char *singular, const char *plural); friend KLocalizedString KI18N_EXPORT ki18ndcp(const char *domain, const char *context, const char *singular, const char *plural); friend KLocalizedString KI18N_EXPORT kxi18n(const char *text); friend KLocalizedString KI18N_EXPORT kxi18nc(const char *context, const char *text); friend KLocalizedString KI18N_EXPORT kxi18np(const char *singular, const char *plural); friend KLocalizedString KI18N_EXPORT kxi18ncp(const char *context, const char *singular, const char *plural); friend KLocalizedString KI18N_EXPORT kxi18nd(const char *domain, const char *text); friend KLocalizedString KI18N_EXPORT kxi18ndc(const char *domain, const char *context, const char *text); friend KLocalizedString KI18N_EXPORT kxi18ndp(const char *domain, const char *singular, const char *plural); friend KLocalizedString KI18N_EXPORT kxi18ndcp(const char *domain, const char *context, const char *singular, const char *plural); public: /** * Construct an empty message. * * Direct construction is used when another \c KLocalizedString instance, * obtained by one of \c ki18n\* calls, should later be assigned * to directly constructed instance. * Before the assignment happens, directly constructed instance * is not valid for finalization by \c toString methods. * * \see isEmpty */ explicit KLocalizedString(); /** * Copy constructor. */ KLocalizedString(const KLocalizedString &rhs); /** * Assignment operator. */ KLocalizedString &operator=(const KLocalizedString &rhs); /** * Destructor. */ ~KLocalizedString(); /** * Check whether the message is empty. * * The message is considered empty if the object was constructed * via the default constructor. * * Empty messages are not valid for finalization. * The behavior of calling \c toString on them is undefined. * In debug mode, an error mark may appear in the returned string. * * \return \c true if the message is empty, \c false otherwise */ bool isEmpty() const; /** * Finalize the translation. * * Creates translated \c QString, with placeholders substituted * by arguments given by \c KLocalizedString::subs methods. * Translated text is searched for and arguments are formatted * based on the global locale. * * If there was any mismatch between placeholders and arguments, * in debug mode the returned string may contain error marks. * * \return finalized translation */ Q_REQUIRED_RESULT QString toString() const; /** * Like \c toString, but look for translation only in given languages. * * Given languages override languages defined by the global locale, * and any languages set earlier using \c withLanguages. * If \p languages is empty, original message is returned. * * \param languages list of language codes (by decreasing priority) * \return finalized translation */ Q_REQUIRED_RESULT QString toString(const QStringList &languages) const; #if 0 // until locale system is ready /** * Like \c toString, but look for translation based on given locale. * * Given locale overrides any set earlier using \c withLocale. * If \p locale is \c NULL, original message is returned. * * \param locale the locale for which translations are made * \return finalized translation */ QString toString(const KLocale *locale) const; #endif /** * Like \c toString, but look for translation in the given domain. * * Given domain overrides any set earlier using \c withDomain. * * \param domain the translation domain * \return finalized translation */ Q_REQUIRED_RESULT QString toString(const char *domain) const; /** * Like \c toString, but resolve KUIT markup into given visual format. * * Given visual format overrides that implied by the context UI marker * or set earlier using \c withFormat. * If the message is not markup-aware, * this is same as \c toString without arguments. * * \param format the target visual format * \return finalized translation */ Q_REQUIRED_RESULT QString toString(Kuit::VisualFormat format) const; /** * Indicate to look for translation only in given languages. * * \param languages list of language codes (by decreasing priority) * \return updated \c KLocalizedString */ Q_REQUIRED_RESULT KLocalizedString withLanguages(const QStringList &languages) const; #if 0 // until locale system is ready /** * Indicate to look for translation based on given locale. * * \param locale the locale for which translations are made * \return updated \c KLocalizedString */ KLocalizedString withLocale(const KLocale *locale) const; #endif /** * Indicate to look for translation in the given domain. * * \param domain the translation domain * \return updated \c KLocalizedString */ Q_REQUIRED_RESULT KLocalizedString withDomain(const char *domain) const; /** * Indicate to resolve KUIT markup into given visual format. * * If the message is not markup-aware, this has no effect. * * \param format the target visual format * \return updated \c KLocalizedString */ Q_REQUIRED_RESULT KLocalizedString withFormat(Kuit::VisualFormat format) const; /** * Substitute an int argument into the message. * * \param a the argument * \param fieldWidth width of the formatted field, padded by spaces. * Positive value aligns right, negative aligns left * \param base the radix used to represent the number as a string. * Valid values range from 2 to 36 * \param fillChar the character used to fill up the empty places when * field width is greater than argument width * \return updated \c KLocalizedString */ Q_REQUIRED_RESULT KLocalizedString subs(int a, int fieldWidth = 0, int base = 10, QChar fillChar = QLatin1Char(' ')) const; /** * Substitute an unsigned int argument into the message. * * \param a the argument * \param fieldWidth width of the formatted field, padded by spaces. * Positive value aligns right, negative aligns left * \param base the radix used to represent the number as a string. * Valid values range from 2 to 36 * \param fillChar the character used to fill up the empty places when * field width is greater than argument width * \return updated \c KLocalizedString */ Q_REQUIRED_RESULT KLocalizedString subs(uint a, int fieldWidth = 0, int base = 10, QChar fillChar = QLatin1Char(' ')) const; /** * Substitute a long argument into the message. * * \param a the argument * \param fieldWidth width of the formatted field, padded by spaces. * Positive value aligns right, negative aligns left * \param base the radix used to represent the number as a string. * Valid values range from 2 to 36 * \param fillChar the character used to fill up the empty places when * field width is greater than argument width * \return updated \c KLocalizedString */ Q_REQUIRED_RESULT KLocalizedString subs(long a, int fieldWidth = 0, int base = 10, QChar fillChar = QLatin1Char(' ')) const; /** * Substitute an unsigned long argument into the message. * * \param a the argument * \param fieldWidth width of the formatted field, padded by spaces. * Positive value aligns right, negative aligns left * \param base the radix used to represent the number as a string. * Valid values range from 2 to 36 * \param fillChar the character used to fill up the empty places when * field width is greater than argument width * \return updated \c KLocalizedString */ Q_REQUIRED_RESULT KLocalizedString subs(ulong a, int fieldWidth = 0, int base = 10, QChar fillChar = QLatin1Char(' ')) const; /** * Substitute a long long argument into the message. * * \param a the argument * \param fieldWidth width of the formatted field, padded by spaces. * Positive value aligns right, negative aligns left * \param base the radix used to represent the number as a string. * Valid values range from 2 to 36 * \param fillChar the character used to fill up the empty places when * field width is greater than argument width * \return updated \c KLocalizedString */ Q_REQUIRED_RESULT KLocalizedString subs(qlonglong a, int fieldWidth = 0, int base = 10, QChar fillChar = QLatin1Char(' ')) const; /** * Substitute an unsigned long long argument into the message. * * \param a the argument * \param fieldWidth width of the formatted field, padded by spaces. * Positive value aligns right, negative aligns left * \param base the radix used to represent the number as a string. * Valid values range from 2 to 36 * \param fillChar the character used to fill up the empty places when * field width is greater than argument width * \return updated \c KLocalizedString */ Q_REQUIRED_RESULT KLocalizedString subs(qulonglong a, int fieldWidth = 0, int base = 10, QChar fillChar = QLatin1Char(' ')) const; /** * Substitute a double argument into the message. * * \param a the argument * \param fieldWidth width of the formatted field, padded by spaces. * Positive value aligns right, negative aligns left * \param format type of floating point formating, like in QString::arg * \param precision number of digits after the decimal separator * \param fillChar the character used to fill up the empty places when * field width is greater than argument width * \return updated \c KLocalizedString */ Q_REQUIRED_RESULT KLocalizedString subs(double a, int fieldWidth = 0, char format = 'g', int precision = -1, QChar fillChar = QLatin1Char(' ')) const; /** * Substitute a \c QChar argument into the message. * * \param a the argument * \param fieldWidth width of the formatted field, padded by spaces. * Positive value aligns right, negative aligns left * \param fillChar the character used to fill up the empty places when * field width is greater than argument width * \return updated \c KLocalizedString */ Q_REQUIRED_RESULT KLocalizedString subs(QChar a, int fieldWidth = 0, QChar fillChar = QLatin1Char(' ')) const; /** * Substitute a \c QString argument into the message. * * \param a the argument * \param fieldWidth width of the formatted field, padded by spaces. * Positive value aligns right, negative aligns left * \param fillChar the character used to fill up the empty places when * field width is greater than argument width * \return updated \c KLocalizedString */ Q_REQUIRED_RESULT KLocalizedString subs(const QString &a, int fieldWidth = 0, QChar fillChar = QLatin1Char(' ')) const; /** * Substitute another \c KLocalizedString into the message. * * \param a the argument * \param fieldWidth width of the formatted field, padded by spaces. * Positive value aligns right, negative aligns left * \param fillChar the character used to fill up the empty places when * field width is greater than argument width * \return updated \c KLocalizedString */ Q_REQUIRED_RESULT KLocalizedString subs(const KLocalizedString &a, int fieldWidth = 0, QChar fillChar = QLatin1Char(' ')) const; /** * Add dynamic context to the message. * * See \ref dyn_ctxt for use cases. * * \param key context key * \param value context value * \return updated \c KLocalizedString */ Q_REQUIRED_RESULT KLocalizedString inContext(const QString &key, const QString &value) const; /** * Relax matching between placeholders and arguments. * * Normally the placeholders should start from %1 and have no gaps, * and on finalization there must be exactly as many arguments * supplied through \c subs methods as there are unique plaecholders. * If this is not satisfied, in debug mode warnings are printed * and the finalized string may contain error marks. * * This method relaxes the placeholder-argument matching, * such that there must only be an argument available for * every present unique placeholder (taking placeholder numbers * to be 1-based indices into the argument list). * This can come useful in some situations. * * \return updated \c KLocalizedString */ Q_REQUIRED_RESULT KLocalizedString relaxSubs() const; /** * Do not resolve KUIT markup. * * If the message is markup-aware * (constructed by one of \c \*xi18n\* calls), * this function can be used to make it non-markup-aware. * This may be useful for debugging markup. * * \return updated \c KLocalizedString */ Q_REQUIRED_RESULT KLocalizedString ignoreMarkup() const; /** * Returns the untranslated text. * * \since 5.64 */ Q_REQUIRED_RESULT QByteArray untranslatedText() const; /** * Set the given domain as application's main domain. * * This function must be called in applications, in order to have * any translations at all. It should never be called in libraries. * This allows to check whether the application is translated * into a given language, so that if it is not, translations from * underlying libraries will not appear even if they are translated. * This prevents mixing of translated and untranslated text * in the user interface. * * This function should be called right after creating the instance * of QCoreApplication or one of its subclasses. At that time the locale * setup has been made, including what is hooked into the * QCoreApplication startup, like KXMLGUI's language switching support. * So the initialisation done by this function sees all the data it should. * * \param domain the translation domain of the application */ static void setApplicationDomain(const char *domain); /** * Get the application's main translation domain. * * Returns the domain set by \c setApplicationDomain. */ static QByteArray applicationDomain(); #if 0 // until locale system is ready /** * Set the locale for which translations will be made. * * Locale determines from which languages (and in which order) * to draw translations, formatting of number arguments, etc. * * \param locale the locale * \see setLanguages */ static void setLocale(const KLocale &locale); #endif /** * Get the languages for which translations will be made. * * Returned languages are ordered with decreasing priority. * * \return languages ordered list of language codes * \see setLanguages * \see clearLanguages * * \since 5.20.0 */ static QStringList languages(); /** * Set the languages for which translations will be made. * * This overrides the languages provided by the locale. * Languages should be ordered with decreasing priority. * * \param languages ordered list of language codes * \see setLocale * \see clearLanguages * \see languages */ static void setLanguages(const QStringList &languages); /** * Clear override languages. * * This clears the override languages, going back to those * provided by the locale. * * \see setLanguages * \see languages */ static void clearLanguages(); /** * Check whether the translation catalog file in the given language * for the set application translation domain exists. * * \param language the language code to check * \return \c true if the translation catalog for \p language exits, * \c false otherwise * \see setApplicationDomain */ static bool isApplicationTranslatedInto(const QString &language); /** * @since 5.0 * * Get the languages for which there exists the translation catalog file * for the set application translation domain. * * The application domain is set by \c setApplicationDomain. * If the application domain was not set, empty set is returned. * If the application domain was set, the language set will always * contain at least the source code language (en_US). * * \return set of language codes for existing translation catalogs * \see setApplicationDomain */ static QSet availableApplicationTranslations(); /** * @since 5.0 * * Get the languages for which a translation catalog file * for the passed translation domain exists. * * If the translation domain was not specified in the * domain parameter an empty set is returned. * * If the application domain was set, the language set will always * contain at least the source code language (en_US). * * \param domain query for translations of a specific domain, if an empty * QByteArray is passed, an empty set will be returned * * \return set of language codes for existing translation catalogs * \see setApplicationDomain * \see availableApplicationTranslations */ static QSet availableDomainTranslations(const QByteArray &domain); /* * Load locales for a domain from a specific location * This is useful for resources which have their translation files * outside of the usual $XDG_DATA_DIRS/locales location * * \param the domain to load resources from * \path the full file path to the locale directory */ static void addDomainLocaleDir(const QByteArray &domain, const QString &path); /** * Find a path to the localized file for the given original path. * * This is intended mainly for non-text resources (images, sounds, etc). * Text resources should be handled in more specific ways. * * Possible localized paths are checked in turn by priority of set * languages, in form of \/l10n/\/\, * where \ and \ are those of * the original path, and \ is the language code. * * \param filePath path to the original file * * \return path to the localized file if found, original path otherwise */ Q_REQUIRED_RESULT static QString localizedFilePath(const QString &filePath); /** * Remove accelerator marker from a UI text label. * * Accelerator marker is not always a plain ampersand (&), * so it is not enough to just remove it by \c QString::remove. * The label may contain escaped markers ("&&") which must be resolved * and skipped, as well as CJK-style markers ("Foo (&F)") where * the whole parenthesis construct should be removed. * Therefore always use this function to remove accelerator marker * from UI labels. * * \param label UI label which may contain an accelerator marker * \return label without the accelerator marker */ Q_REQUIRED_RESULT static QString removeAcceleratorMarker(const QString &label); #if KI18N_ENABLE_DEPRECATED_SINCE(5, 0) /** * Translate a message with Qt semantics. * * This functions provides a capability to derive a Qt translator from * \c QTranslator and draw translations from PO catalogs of given domain. * All domains added with \c insertQtDomain are checked for translation, * in undefined order. * No Ki18n-specific processing is performed (formatting, scripting, etc). * * \see QTranslator * * \deprecated Use Qt's native i18n system, Qt Linguist, * with roundtrip TS->PO->TS through * Qt's \c lupdate and \c lconvert commands. */ KI18N_DEPRECATED_VERSION(5, 0, "See API docs") Q_REQUIRED_RESULT static QString translateQt(const char *context, const char *text, const char *comment, int n); #endif #if KI18N_ENABLE_DEPRECATED_SINCE(5, 0) /** * Add another domain to search for Qt translations. * * \param domain the translation domain to add * * \see translateQt * \see removeQtDomain * * \deprecated Since 5.0 */ KI18N_DEPRECATED_VERSION(5, 0, "See API docs") static void insertQtDomain(const char *domain); #endif #if KI18N_ENABLE_DEPRECATED_SINCE(5, 0) /** * Remove a domain from Qt translation lookup. * * To really remove the domain, this function must be invoked * at least as many times as \c insertQtDomain was invoked to add * this domain. This makes it safe to always use paired * insertion/removal calls, without pulling out a domain * underneath an unrelated piece of code that uses it as well. * * \param domain the translation domain to remove * * \see translateQt * \see insertQtDomain * * \deprecated Since 5.0 */ KI18N_DEPRECATED_VERSION(5, 0, "See API docs") static void removeQtDomain(const char *domain); #endif private: KLocalizedString(const char *domain, const char *context, const char *text, const char *plural, bool markupAware); KLocalizedStringPrivate *const d; }; // Do not document every multi-argument i18n* call separately, // but provide special quasi-calls that only Doxygen sees. // Placed in front of ki18n* calls, because i18n* are more basic. #ifdef K_DOXYGEN /** * Translate a string and substitute any arguments. * * \param text string to translate * \param arg arguments to insert (0 to 9), * admissible types according to \c KLocalizedString::subs methods * \return translated string */ QString i18n(const char *text, const TYPE &arg...); /** * Translate a string with context and substitute any arguments. * * \param context context of the string * \param text string to translate * \param arg arguments to insert (0 to 9), * admissible types according to \c KLocalizedString::subs methods * \return translated string */ QString i18nc(const char *context, const char *text, const TYPE &arg...); /** * Translate a string with plural and substitute any arguments. * * \param singular singular form of the string to translate * \param plural plural form of the string to translate * \param arg arguments to insert (0 to 9), * admissible types according to \c KLocalizedString::subs methods * \return translated string */ QString i18np(const char *singular, const char *plural, const TYPE &arg...); /** * Translate a string with context and plural and substitute any arguments. * * \param context context of the string * \param singular singular form of the string to translate * \param plural plural form of the string to translate * \param arg arguments to insert (0 to 9), * admissible types according to \c KLocalizedString::subs methods * \return translated string */ QString i18ncp(const char *context, const char *singular, const char *plural, const TYPE &arg...); /** * Translate a string from domain and substitute any arguments. * * \param domain domain in which to look for translations * \param text string to translate * \param arg arguments to insert (0 to 9), * admissible types according to \c KLocalizedString::subs methods * \return translated string */ QString i18nd(const char *domain, const char *text, const TYPE &arg...); /** * Translate a string from domain with context and substitute any arguments. * * \param domain domain in which to look for translations * \param context context of the string * \param text string to translate * \param arg arguments to insert (0 to 9), * admissible types according to \c KLocalizedString::subs methods * \return translated string */ QString i18ndc(const char *domain, const char *context, const char *text, const TYPE &arg...); /** * Translate a string from domain with plural and substitute any arguments. * * \param domain domain in which to look for translations * \param singular singular form of the string to translate * \param plural plural form of the string to translate * \param arg arguments to insert (0 to 9), * admissible types according to \c KLocalizedString::subs methods * \return translated string */ QString i18ndp(const char *domain, const char *singular, const char *plural, const TYPE &arg...); /** * Translate a string from domain with context and plural * and substitute any arguments. * * \param domain domain in which to look for translations * \param context context of the string * \param singular singular form of the string to translate * \param plural plural form of the string to translate * \param arg arguments to insert (0 to 9), * admissible types according to \c KLocalizedString::subs methods * \return translated string */ QString i18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const TYPE &arg...); /** * Translate a markup-aware string and substitute any arguments. * * \param text string to translate * \param arg arguments to insert (0 to 9), * admissible types according to \c KLocalizedString::subs methods * \return translated string */ QString xi18n(const char *text, const TYPE &arg...); /** * Translate a markup-aware string with context and substitute any arguments. * * \param context context of the string * \param text string to translate * \param arg arguments to insert (0 to 9), * admissible types according to \c KLocalizedString::subs methods * \return translated string */ QString xi18nc(const char *context, const char *text, const TYPE &arg...); /** * Translate a markup-aware string with plural and substitute any arguments. * * \param singular singular form of the string to translate * \param plural plural form of the string to translate * \param arg arguments to insert (0 to 9), * admissible types according to \c KLocalizedString::subs methods * \return translated string */ QString xi18np(const char *singular, const char *plural, const TYPE &arg...); /** * Translate a markup-aware string with context and plural * and substitute any arguments. * * \param context context of the string * \param singular singular form of the string to translate * \param plural plural form of the string to translate * \param arg arguments to insert (0 to 9), * admissible types according to \c KLocalizedString::subs methods * \return translated string */ QString xi18ncp(const char *context, const char *singular, const char *plural, const TYPE &arg...); /** * Translate a markup-aware string from domain and substitute any arguments. * * \param domain domain in which to look for translations * \param text string to translate * \param arg arguments to insert (0 to 9), * admissible types according to \c KLocalizedString::subs methods * \return translated string */ QString xi18nd(const char *domain, const char *text, const TYPE &arg...); /** * Translate a markup-aware string from domain with context * and substitute any arguments. * * \param domain domain in which to look for translations * \param context context of the string * \param text string to translate * \param arg arguments to insert (0 to 9), * admissible types according to \c KLocalizedString::subs methods * \return translated string */ QString xi18ndc(const char *domain, const char *context, const char *text, const TYPE &arg...); /** * Translate a markup-aware string from domain with plural * and substitute any arguments. * * \param domain domain in which to look for translations * \param singular singular form of the string to translate * \param plural plural form of the string to translate * \param arg arguments to insert (0 to 9), * admissible types according to \c KLocalizedString::subs methods * \return translated string */ QString xi18ndp(const char *domain, const char *singular, const char *plural, const TYPE &arg...); /** * Translate a markup-aware string from domain with context and plural * and substitute any arguments. * * \param domain domain in which to look for translations * \param context context of the string * \param singular singular form of the string to translate * \param plural plural form of the string to translate * \param arg arguments to insert (0 to 9), * admissible types according to \c KLocalizedString::subs methods * \return translated string */ QString xi18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const TYPE &arg...); #endif // K_DOXYGEN /** * Create non-finalized translated string. * * \param text string to translate * \return non-finalized translated string */ KLocalizedString KI18N_EXPORT ki18n(const char *text); /** * Create non-finalized translated string with context. * * \param context context of the string * \param text string to translate * \return non-finalized translated string */ KLocalizedString KI18N_EXPORT ki18nc(const char *context, const char *text); /** * Create non-finalized translated string with plural. * * \param singular singular form of the string to translate * \param plural plural form of the string to translate * \return non-finalized translated string */ KLocalizedString KI18N_EXPORT ki18np(const char *singular, const char *plural); /** * Create non-finalized translated string with context and plural. * * \param context context of the string * \param singular singular form of the string to translate * \param plural plural form of the string to translate * \return non-finalized translated string */ KLocalizedString KI18N_EXPORT ki18ncp(const char *context, const char *singular, const char *plural); /** * Create non-finalized translated string from domain. * * \param domain domain in which to look for translations * \param text string to translate * \return non-finalized translated string */ KLocalizedString KI18N_EXPORT ki18nd(const char *domain, const char *text); /** * Create non-finalized translated string from domain with context. * * \param domain domain in which to look for translations * \param context context of the string * \param text string to translate * \return non-finalized translated string */ KLocalizedString KI18N_EXPORT ki18ndc(const char *domain, const char *context, const char *text); /** * Create non-finalized translated string from domain with plural. * * \param domain domain in which to look for translations * \param singular singular form of the string to translate * \param plural plural form of the string to translate * \return non-finalized translated string */ KLocalizedString KI18N_EXPORT ki18ndp(const char *domain, const char *singular, const char *plural); /** * Create non-finalized translated string from domain with context and plural. * * \param domain domain in which to look for translations * \param context context of the string * \param singular singular form of the string to translate * \param plural plural form of the string to translate * \return non-finalized translated string */ KLocalizedString KI18N_EXPORT ki18ndcp(const char *domain, const char *context, const char *singular, const char *plural); /** * Create non-finalized markup-aware translated string. * * \param text string to translate * \return non-finalized translated string */ KLocalizedString KI18N_EXPORT kxi18n(const char *text); /** * Create non-finalized markup-aware translated string with context. * * \param context context of the string * \param text string to translate * \return non-finalized translated string */ KLocalizedString KI18N_EXPORT kxi18nc(const char *context, const char *text); /** * Create non-finalized markup-aware translated string with plural. * * \param singular singular form of the string to translate * \param plural plural form of the string to translate * \return non-finalized translated string */ KLocalizedString KI18N_EXPORT kxi18np(const char *singular, const char *plural); /** * Create non-finalized markup-aware translated string. * with context and plural. * * \param context context of the string * \param singular singular form of the string to translate * \param plural plural form of the string to translate * \return non-finalized translated string */ KLocalizedString KI18N_EXPORT kxi18ncp(const char *context, const char *singular, const char *plural); /** * Create non-finalized markup-aware translated string from domain. * * \param domain domain in which to look for translations * \param text string to translate * \return non-finalized translated string */ KLocalizedString KI18N_EXPORT kxi18nd(const char *domain, const char *text); /** * Create non-finalized markup-aware translated string from domain with context. * * \param domain domain in which to look for translations * \param context context of the string * \param text string to translate * \return non-finalized translated string */ KLocalizedString KI18N_EXPORT kxi18ndc(const char *domain, const char *context, const char *text); /** * Create non-finalized markup-aware translated string from domain with plural. * * \param domain domain in which to look for translations * \param singular singular form of the string to translate * \param plural plural form of the string to translate * \return non-finalized translated string */ KLocalizedString KI18N_EXPORT kxi18ndp(const char *domain, const char *singular, const char *plural); /** * Create non-finalized markup-aware translated string from domain * with context and plural. * * \param domain domain in which to look for translations * \param context context of the string * \param singular singular form of the string to translate * \param plural plural form of the string to translate * \return non-finalized translated string */ KLocalizedString KI18N_EXPORT kxi18ndcp(const char *domain, const char *context, const char *singular, const char *plural); /** * Redirect Qt's uic-generated translation calls to Ki18n. * * Use -tr tr2i18n option to \c uic to have it redirect calls. * * \param text string to translate * \param comment Qt equivalent of disambiguation context * \return translated string */ inline QString tr2i18n(const char *text, const char *comment = nullptr) { if (comment && comment[0] && text && text[0]) { return ki18nc(comment, text).toString(); } else if (text && text[0]) { return ki18n(text).toString(); } else { return QString(); } } /** * Like \c tr2i18n, but look for translation in a specific domain. * * Use -tr tr2i18nd option to \c uic to have it redirect calls. * * \param domain domain in which to look for translations * \param text string to translate * \param comment Qt equivalent of disambiguation context * \return translated string */ inline QString tr2i18nd(const char *domain, const char *text, const char *comment = nullptr) { if (comment && comment[0] && text && text[0]) { return ki18ndc(domain, comment, text).toString(); } else if (text && text[0]) { return ki18nd(domain, text).toString(); } else { return QString(); } } /** * Like \c tr2i18n, but when UI strings are KUIT markup-aware. * * Use -tr tr2xi18n option to \c uic to have it redirect calls. * * \param text markup-aware string to translate * \param comment Qt equivalent of disambiguation context * \return translated string */ inline QString tr2xi18n(const char *text, const char *comment = nullptr) { if (comment && comment[0] && text && text[0]) { return kxi18nc(comment, text).toString(); } else if (text && text[0]) { return kxi18n(text).toString(); } else { return QString(); } } /** * Like \c tr2xi18n, but look for translation in a specific domain. * * Use -tr tr2xi18nd option to \c uic to have it redirect calls. * * \param domain domain in which to look for translations * \param text markup-aware string to translate * \param comment Qt equivalent of disambiguation context * \return translated string */ inline QString tr2xi18nd(const char *domain, const char *text, const char *comment = nullptr) { if (comment && comment[0] && text && text[0]) { return kxi18ndc(domain, comment, text).toString(); } else if (text && text[0]) { return kxi18nd(domain, text).toString(); } else { return QString(); } } #ifndef K_DOXYGEN #ifndef NDEBUG #define I18N_ERR_MSG String_literal_as_second_argument_to_i18n___Perhaps_you_need_i18nc_or_i18np template class I18nTypeCheck { public: static void I18N_ERR_MSG() {} }; template class I18nTypeCheck {}; #define STATIC_ASSERT_NOT_LITERAL_STRING(T) I18nTypeCheck::I18N_ERR_MSG(); #else #define STATIC_ASSERT_NOT_LITERAL_STRING(T) #endif // >>>>> Basic calls // Autogenerated; contact maintainer for batch changes. inline QString i18n(const char *text) { return ki18n(text).toString(); } template inline QString i18n(const char *text, const A1 &a1) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return ki18n(text).subs(a1).toString(); } template inline QString i18n(const char *text, const A1 &a1, const A2 &a2) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return ki18n(text).subs(a1).subs(a2).toString(); } template inline QString i18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return ki18n(text).subs(a1).subs(a2).subs(a3).toString(); } template inline QString i18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return ki18n(text).subs(a1).subs(a2).subs(a3).subs(a4).toString(); } template inline QString i18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return ki18n(text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).toString(); } template inline QString i18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return ki18n(text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).toString(); } template inline QString i18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return ki18n(text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).toString(); } template inline QString i18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return ki18n(text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).toString(); } template inline QString i18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return ki18n(text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).subs(a9).toString(); } // <<<<<<< End of basic calls // >>>>> Context calls // Autogenerated; contact maintainer for batch changes. inline QString i18nc(const char *context, const char *text) { return ki18nc(context, text).toString(); } template inline QString i18nc(const char *context, const char *text, const A1 &a1) { return ki18nc(context, text).subs(a1).toString(); } template inline QString i18nc(const char *context, const char *text, const A1 &a1, const A2 &a2) { return ki18nc(context, text).subs(a1).subs(a2).toString(); } template inline QString i18nc(const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3) { return ki18nc(context, text).subs(a1).subs(a2).subs(a3).toString(); } template inline QString i18nc(const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { return ki18nc(context, text).subs(a1).subs(a2).subs(a3).subs(a4).toString(); } template inline QString i18nc(const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5) { return ki18nc(context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).toString(); } template inline QString i18nc(const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6) { return ki18nc(context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).toString(); } template inline QString i18nc(const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7) { return ki18nc(context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).toString(); } template inline QString i18nc(const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8) { return ki18nc(context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).toString(); } template inline QString i18nc(const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9) { return ki18nc(context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).subs(a9).toString(); } // <<<<< End of context calls // >>>>> Plural calls // Autogenerated; contact maintainer for batch changes. template inline QString i18np(const char *singular, const char *plural, const A1 &a1) { return ki18np(singular, plural).subs(a1).toString(); } template inline QString i18np(const char *singular, const char *plural, const A1 &a1, const A2 &a2) { return ki18np(singular, plural).subs(a1).subs(a2).toString(); } template inline QString i18np(const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3) { return ki18np(singular, plural).subs(a1).subs(a2).subs(a3).toString(); } template inline QString i18np(const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { return ki18np(singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).toString(); } template inline QString i18np(const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5) { return ki18np(singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).toString(); } template inline QString i18np(const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6) { return ki18np(singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).toString(); } template inline QString i18np(const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7) { return ki18np(singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).toString(); } template inline QString i18np(const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8) { return ki18np(singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).toString(); } template inline QString i18np(const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9) { return ki18np(singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).subs(a9).toString(); } // <<<<< End of plural calls // >>>>> Context-plural calls // Autogenerated; contact maintainer for batch changes. template inline QString i18ncp(const char *context, const char *singular, const char *plural, const A1 &a1) { return ki18ncp(context, singular, plural).subs(a1).toString(); } template inline QString i18ncp(const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2) { return ki18ncp(context, singular, plural).subs(a1).subs(a2).toString(); } template inline QString i18ncp(const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3) { return ki18ncp(context, singular, plural).subs(a1).subs(a2).subs(a3).toString(); } template inline QString i18ncp(const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { return ki18ncp(context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).toString(); } template inline QString i18ncp(const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5) { return ki18ncp(context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).toString(); } template inline QString i18ncp(const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6) { return ki18ncp(context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).toString(); } template inline QString i18ncp(const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7) { return ki18ncp(context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).toString(); } template inline QString i18ncp(const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8) { return ki18ncp(context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).toString(); } template inline QString i18ncp(const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9) { return ki18ncp(context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).subs(a9).toString(); } // <<<<< End of context-plural calls // >>>>> Basic calls with domain // Autogenerated; contact maintainer for batch changes. inline QString i18nd(const char *domain, const char *text) { return ki18nd(domain, text).toString(); } template inline QString i18nd(const char *domain, const char *text, const A1 &a1) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return ki18nd(domain, text).subs(a1).toString(); } template inline QString i18nd(const char *domain, const char *text, const A1 &a1, const A2 &a2) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return ki18nd(domain, text).subs(a1).subs(a2).toString(); } template inline QString i18nd(const char *domain, const char *text, const A1 &a1, const A2 &a2, const A3 &a3) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return ki18nd(domain, text).subs(a1).subs(a2).subs(a3).toString(); } template inline QString i18nd(const char *domain, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return ki18nd(domain, text).subs(a1).subs(a2).subs(a3).subs(a4).toString(); } template inline QString i18nd(const char *domain, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return ki18nd(domain, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).toString(); } template inline QString i18nd(const char *domain, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return ki18nd(domain, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).toString(); } template inline QString i18nd(const char *domain, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return ki18nd(domain, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).toString(); } template inline QString i18nd(const char *domain, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return ki18nd(domain, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).toString(); } template inline QString i18nd(const char *domain, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return ki18nd(domain, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).subs(a9).toString(); } // <<<<<<< End of basic calls with domain // >>>>> Context calls with domain // Autogenerated; contact maintainer for batch changes. inline QString i18ndc(const char *domain, const char *context, const char *text) { return ki18ndc(domain, context, text).toString(); } template inline QString i18ndc(const char *domain, const char *context, const char *text, const A1 &a1) { return ki18ndc(domain, context, text).subs(a1).toString(); } template inline QString i18ndc(const char *domain, const char *context, const char *text, const A1 &a1, const A2 &a2) { return ki18ndc(domain, context, text).subs(a1).subs(a2).toString(); } template inline QString i18ndc(const char *domain, const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3) { return ki18ndc(domain, context, text).subs(a1).subs(a2).subs(a3).toString(); } template inline QString i18ndc(const char *domain, const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { return ki18ndc(domain, context, text).subs(a1).subs(a2).subs(a3).subs(a4).toString(); } template inline QString i18ndc(const char *domain, const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5) { return ki18ndc(domain, context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).toString(); } template inline QString i18ndc(const char *domain, const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6) { return ki18ndc(domain, context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).toString(); } template inline QString i18ndc(const char *domain, const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7) { return ki18ndc(domain, context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).toString(); } template inline QString i18ndc(const char *domain, const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8) { return ki18ndc(domain, context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).toString(); } template inline QString i18ndc(const char *domain, const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9) { return ki18ndc(domain, context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).subs(a9).toString(); } // <<<<< End of context calls with domain // >>>>> Plural calls with domain // Autogenerated; contact maintainer for batch changes. template inline QString i18ndp(const char *domain, const char *singular, const char *plural, const A1 &a1) { return ki18ndp(domain, singular, plural).subs(a1).toString(); } template inline QString i18ndp(const char *domain, const char *singular, const char *plural, const A1 &a1, const A2 &a2) { return ki18ndp(domain, singular, plural).subs(a1).subs(a2).toString(); } template inline QString i18ndp(const char *domain, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3) { return ki18ndp(domain, singular, plural).subs(a1).subs(a2).subs(a3).toString(); } template inline QString i18ndp(const char *domain, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { return ki18ndp(domain, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).toString(); } template inline QString i18ndp(const char *domain, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5) { return ki18ndp(domain, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).toString(); } template inline QString i18ndp(const char *domain, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6) { return ki18ndp(domain, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).toString(); } template inline QString i18ndp(const char *domain, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7) { return ki18ndp(domain, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).toString(); } template inline QString i18ndp(const char *domain, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8) { return ki18ndp(domain, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).toString(); } template inline QString i18ndp(const char *domain, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9) { return ki18ndp(domain, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).subs(a9).toString(); } // <<<<< End of plural calls with domain // >>>>> Context-plural calls with domain // Autogenerated; contact maintainer for batch changes. template inline QString i18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const A1 &a1) { return ki18ndcp(domain, context, singular, plural).subs(a1).toString(); } template inline QString i18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2) { return ki18ndcp(domain, context, singular, plural).subs(a1).subs(a2).toString(); } template inline QString i18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3) { return ki18ndcp(domain, context, singular, plural).subs(a1).subs(a2).subs(a3).toString(); } template inline QString i18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { return ki18ndcp(domain, context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).toString(); } template inline QString i18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5) { return ki18ndcp(domain, context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).toString(); } template inline QString i18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6) { return ki18ndcp(domain, context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).toString(); } template inline QString i18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7) { return ki18ndcp(domain, context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).toString(); } template inline QString i18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8) { return ki18ndcp(domain, context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).toString(); } template inline QString i18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9) { return ki18ndcp(domain, context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).subs(a9).toString(); } // <<<<< End of context-plural calls with domain // >>>>> Markup-aware basic calls // Autogenerated; contact maintainer for batch changes. inline QString xi18n(const char *text) { return kxi18n(text).toString(); } template inline QString xi18n(const char *text, const A1 &a1) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return kxi18n(text).subs(a1).toString(); } template inline QString xi18n(const char *text, const A1 &a1, const A2 &a2) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return kxi18n(text).subs(a1).subs(a2).toString(); } template inline QString xi18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return kxi18n(text).subs(a1).subs(a2).subs(a3).toString(); } template inline QString xi18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return kxi18n(text).subs(a1).subs(a2).subs(a3).subs(a4).toString(); } template inline QString xi18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return kxi18n(text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).toString(); } template inline QString xi18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return kxi18n(text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).toString(); } template inline QString xi18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return kxi18n(text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).toString(); } template inline QString xi18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return kxi18n(text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).toString(); } template inline QString xi18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return kxi18n(text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).subs(a9).toString(); } // <<<<<<< End of markup-aware basic calls // >>>>> Markup-aware context calls // Autogenerated; contact maintainer for batch changes. inline QString xi18nc(const char *context, const char *text) { return kxi18nc(context, text).toString(); } template inline QString xi18nc(const char *context, const char *text, const A1 &a1) { return kxi18nc(context, text).subs(a1).toString(); } template inline QString xi18nc(const char *context, const char *text, const A1 &a1, const A2 &a2) { return kxi18nc(context, text).subs(a1).subs(a2).toString(); } template inline QString xi18nc(const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3) { return kxi18nc(context, text).subs(a1).subs(a2).subs(a3).toString(); } template inline QString xi18nc(const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { return kxi18nc(context, text).subs(a1).subs(a2).subs(a3).subs(a4).toString(); } template inline QString xi18nc(const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5) { return kxi18nc(context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).toString(); } template inline QString xi18nc(const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6) { return kxi18nc(context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).toString(); } template inline QString xi18nc(const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7) { return kxi18nc(context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).toString(); } template inline QString xi18nc(const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8) { return kxi18nc(context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).toString(); } template inline QString xi18nc(const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9) { return kxi18nc(context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).subs(a9).toString(); } // <<<<< End of markup-aware context calls // >>>>> Markup-aware plural calls // Autogenerated; contact maintainer for batch changes. template inline QString xi18np(const char *singular, const char *plural, const A1 &a1) { return kxi18np(singular, plural).subs(a1).toString(); } template inline QString xi18np(const char *singular, const char *plural, const A1 &a1, const A2 &a2) { return kxi18np(singular, plural).subs(a1).subs(a2).toString(); } template inline QString xi18np(const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3) { return kxi18np(singular, plural).subs(a1).subs(a2).subs(a3).toString(); } template inline QString xi18np(const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { return kxi18np(singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).toString(); } template inline QString xi18np(const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5) { return kxi18np(singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).toString(); } template inline QString xi18np(const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6) { return kxi18np(singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).toString(); } template inline QString xi18np(const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7) { return kxi18np(singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).toString(); } template inline QString xi18np(const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8) { return kxi18np(singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).toString(); } template inline QString xi18np(const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9) { return kxi18np(singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).subs(a9).toString(); } // <<<<< End of markup-aware plural calls // >>>>> Markup-aware context-plural calls // Autogenerated; contact maintainer for batch changes. template inline QString xi18ncp(const char *context, const char *singular, const char *plural, const A1 &a1) { return kxi18ncp(context, singular, plural).subs(a1).toString(); } template inline QString xi18ncp(const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2) { return kxi18ncp(context, singular, plural).subs(a1).subs(a2).toString(); } template inline QString xi18ncp(const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3) { return kxi18ncp(context, singular, plural).subs(a1).subs(a2).subs(a3).toString(); } template inline QString xi18ncp(const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { return kxi18ncp(context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).toString(); } template inline QString xi18ncp(const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5) { return kxi18ncp(context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).toString(); } template inline QString xi18ncp(const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6) { return kxi18ncp(context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).toString(); } template inline QString xi18ncp(const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7) { return kxi18ncp(context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).toString(); } template inline QString xi18ncp(const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8) { return kxi18ncp(context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).toString(); } template inline QString xi18ncp(const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9) { return kxi18ncp(context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).subs(a9).toString(); } // <<<<< End of markup-aware context-plural calls // >>>>> Markup-aware basic calls with domain // Autogenerated; contact maintainer for batch changes. inline QString xi18nd(const char *domain, const char *text) { return kxi18nd(domain, text).toString(); } template inline QString xi18nd(const char *domain, const char *text, const A1 &a1) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return kxi18nd(domain, text).subs(a1).toString(); } template inline QString xi18nd(const char *domain, const char *text, const A1 &a1, const A2 &a2) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return kxi18nd(domain, text).subs(a1).subs(a2).toString(); } template inline QString xi18nd(const char *domain, const char *text, const A1 &a1, const A2 &a2, const A3 &a3) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return kxi18nd(domain, text).subs(a1).subs(a2).subs(a3).toString(); } template inline QString xi18nd(const char *domain, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return kxi18nd(domain, text).subs(a1).subs(a2).subs(a3).subs(a4).toString(); } template inline QString xi18nd(const char *domain, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return kxi18nd(domain, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).toString(); } template inline QString xi18nd(const char *domain, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return kxi18nd(domain, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).toString(); } template inline QString xi18nd(const char *domain, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return kxi18nd(domain, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).toString(); } template inline QString xi18nd(const char *domain, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return kxi18nd(domain, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).toString(); } template inline QString xi18nd(const char *domain, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return kxi18nd(domain, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).subs(a9).toString(); } // <<<<<<< End of markup-aware basic calls with domain // >>>>> Markup-aware context calls with domain // Autogenerated; contact maintainer for batch changes. inline QString xi18ndc(const char *domain, const char *context, const char *text) { return kxi18ndc(domain, context, text).toString(); } template inline QString xi18ndc(const char *domain, const char *context, const char *text, const A1 &a1) { return kxi18ndc(domain, context, text).subs(a1).toString(); } template inline QString xi18ndc(const char *domain, const char *context, const char *text, const A1 &a1, const A2 &a2) { return kxi18ndc(domain, context, text).subs(a1).subs(a2).toString(); } template inline QString xi18ndc(const char *domain, const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3) { return kxi18ndc(domain, context, text).subs(a1).subs(a2).subs(a3).toString(); } template inline QString xi18ndc(const char *domain, const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { return kxi18ndc(domain, context, text).subs(a1).subs(a2).subs(a3).subs(a4).toString(); } template inline QString xi18ndc(const char *domain, const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5) { return kxi18ndc(domain, context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).toString(); } template inline QString xi18ndc(const char *domain, const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6) { return kxi18ndc(domain, context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).toString(); } template inline QString xi18ndc(const char *domain, const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7) { return kxi18ndc(domain, context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).toString(); } template inline QString xi18ndc(const char *domain, const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8) { return kxi18ndc(domain, context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).toString(); } template inline QString xi18ndc(const char *domain, const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9) { return kxi18ndc(domain, context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).subs(a9).toString(); } // <<<<< End of markup-aware context calls with domain // >>>>> Markup-aware plural calls with domain // Autogenerated; contact maintainer for batch changes. template inline QString xi18ndp(const char *domain, const char *singular, const char *plural, const A1 &a1) { return kxi18ndp(domain, singular, plural).subs(a1).toString(); } template inline QString xi18ndp(const char *domain, const char *singular, const char *plural, const A1 &a1, const A2 &a2) { return kxi18ndp(domain, singular, plural).subs(a1).subs(a2).toString(); } template inline QString xi18ndp(const char *domain, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3) { return kxi18ndp(domain, singular, plural).subs(a1).subs(a2).subs(a3).toString(); } template inline QString xi18ndp(const char *domain, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { return kxi18ndp(domain, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).toString(); } template inline QString xi18ndp(const char *domain, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5) { return kxi18ndp(domain, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).toString(); } template inline QString xi18ndp(const char *domain, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6) { return kxi18ndp(domain, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).toString(); } template inline QString xi18ndp(const char *domain, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7) { return kxi18ndp(domain, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).toString(); } template inline QString xi18ndp(const char *domain, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8) { return kxi18ndp(domain, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).toString(); } template inline QString xi18ndp(const char *domain, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9) { return kxi18ndp(domain, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).subs(a9).toString(); } // <<<<< End of markup-aware plural calls with domain // >>>>> Markup-aware context-plural calls with domain // Autogenerated; contact maintainer for batch changes. template inline QString xi18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const A1 &a1) { return kxi18ndcp(domain, context, singular, plural).subs(a1).toString(); } template inline QString xi18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2) { return kxi18ndcp(domain, context, singular, plural).subs(a1).subs(a2).toString(); } template inline QString xi18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3) { return kxi18ndcp(domain, context, singular, plural).subs(a1).subs(a2).subs(a3).toString(); } template inline QString xi18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { return kxi18ndcp(domain, context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).toString(); } template inline QString xi18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5) { return kxi18ndcp(domain, context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).toString(); } template inline QString xi18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6) { return kxi18ndcp(domain, context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).toString(); } template inline QString xi18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7) { return kxi18ndcp(domain, context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).toString(); } template inline QString xi18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8) { return kxi18ndcp(domain, context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).toString(); } template inline QString xi18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9) { return kxi18ndcp(domain, context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).subs(a9).toString(); } // <<<<< End of markup-aware context-plural calls with domain #endif // K_DOXYGEN #endif // KLOCALIZEDSTRING_H #ifndef K_DOXYGEN // Outside of include guards, to be able to map and unmap domains // by successive inclusions of this header // preceded with different definitions of TRANSLATION_DOMAIN. #ifdef TRANSLATION_DOMAIN #define i18n(...) i18nd(TRANSLATION_DOMAIN, __VA_ARGS__) #define i18nc(...) i18ndc(TRANSLATION_DOMAIN, __VA_ARGS__) #define i18np(...) i18ndp(TRANSLATION_DOMAIN, __VA_ARGS__) #define i18ncp(...) i18ndcp(TRANSLATION_DOMAIN, __VA_ARGS__) #define ki18n(...) ki18nd(TRANSLATION_DOMAIN, __VA_ARGS__) #define ki18nc(...) ki18ndc(TRANSLATION_DOMAIN, __VA_ARGS__) #define ki18np(...) ki18ndp(TRANSLATION_DOMAIN, __VA_ARGS__) #define ki18ncp(...) ki18ndcp(TRANSLATION_DOMAIN, __VA_ARGS__) #define tr2i18n(...) tr2i18nd(TRANSLATION_DOMAIN, __VA_ARGS__) #define xi18n(...) xi18nd(TRANSLATION_DOMAIN, __VA_ARGS__) #define xi18nc(...) xi18ndc(TRANSLATION_DOMAIN, __VA_ARGS__) #define xi18np(...) xi18ndp(TRANSLATION_DOMAIN, __VA_ARGS__) #define xi18ncp(...) xi18ndcp(TRANSLATION_DOMAIN, __VA_ARGS__) #define kxi18n(...) kxi18nd(TRANSLATION_DOMAIN, __VA_ARGS__) #define kxi18nc(...) kxi18ndc(TRANSLATION_DOMAIN, __VA_ARGS__) #define kxi18np(...) kxi18ndp(TRANSLATION_DOMAIN, __VA_ARGS__) #define kxi18ncp(...) kxi18ndcp(TRANSLATION_DOMAIN, __VA_ARGS__) #define tr2xi18n(...) tr2xi18nd(TRANSLATION_DOMAIN, __VA_ARGS__) #else #undef i18n #undef i18nc #undef i18np #undef i18ncp #undef ki18n #undef ki18nc #undef ki18np #undef ki18ncp #undef tr2i18n #undef xi18n #undef xi18nc #undef xi18np #undef xi18ncp #undef kxi18n #undef kxi18nc #undef kxi18np #undef kxi18ncp #undef tr2xi18n #endif #endif // K_DOXYGEN diff --git a/src/klocalizedtranslator.cpp b/src/klocalizedtranslator.cpp index b1b256f..276805f 100644 --- a/src/klocalizedtranslator.cpp +++ b/src/klocalizedtranslator.cpp @@ -1,69 +1,55 @@ /* - * Copyright 2014 Martin Gräßlin - * - * 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) version 3, or any - * later version accepted by the membership of KDE e.V. (or its - * successor approved by the membership of KDE e.V.), which shall - * act as a proxy defined in Section 6 of version 3 of the license. - * - * 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 . + SPDX-FileCopyrightText: 2014 Martin Gräßlin + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #include "klocalizedtranslator.h" #include "klocalizedstring.h" // Qt #include #include class KLocalizedTranslatorPrivate { public: QString translationDomain; QSet monitoredContexts; }; KLocalizedTranslator::KLocalizedTranslator(QObject *parent) : QTranslator(parent) , d(new KLocalizedTranslatorPrivate) { } KLocalizedTranslator::~KLocalizedTranslator() { } void KLocalizedTranslator::setTranslationDomain(const QString &translationDomain) { d->translationDomain = translationDomain; } void KLocalizedTranslator::addContextToMonitor(const QString &context) { d->monitoredContexts.insert(context); } void KLocalizedTranslator::removeContextToMonitor(const QString &context) { d->monitoredContexts.remove(context); } QString KLocalizedTranslator::translate(const char *context, const char *sourceText, const char *disambiguation, int n) const { if (d->translationDomain.isEmpty() || !d->monitoredContexts.contains(QString::fromUtf8(context))) { return QTranslator::translate(context, sourceText, disambiguation, n); } if (qstrlen(disambiguation) == 0) { return ki18nd(d->translationDomain.toUtf8().constData(), sourceText).toString(); } else { return ki18ndc(d->translationDomain.toUtf8().constData(), disambiguation, sourceText).toString(); } } diff --git a/src/klocalizedtranslator.h b/src/klocalizedtranslator.h index 64060c5..00dbe78 100644 --- a/src/klocalizedtranslator.h +++ b/src/klocalizedtranslator.h @@ -1,110 +1,96 @@ /* - * Copyright 2014 Martin Gräßlin - * - * 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) version 3, or any - * later version accepted by the membership of KDE e.V. (or its - * successor approved by the membership of KDE e.V.), which shall - * act as a proxy defined in Section 6 of version 3 of the license. - * - * 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 . + SPDX-FileCopyrightText: 2014 Martin Gräßlin + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #ifndef KLOCALIZEDTRANSLATOR_H #define KLOCALIZEDTRANSLATOR_H #include #include class KLocalizedTranslatorPrivate; /** * @class KLocalizedTranslator klocalizedtranslator.h * * @brief A QTranslator using KLocalizedString for translations. * * This class allows to translate strings in Qt's translation system with KLocalizedString. * An example is the translation of a dynamically loaded user interface through QUILoader. * * To use this Translator install it in the QCoreApplication and provide the translation domain * to be used. The Translator can operate for multiple contexts, those needs to be specified. * * Example for translating a UI loaded through QUILoader: * @code * // create translator and install in QCoreApplication * KLocalizedTranslator *translator = new KLocalizedTranslator(this); * QCoreApplication::instance()->installTranslator(translator); * translator->setTranslationDomain(QStringLiteral("MyAppsDomain")); * * // create the QUILoader * QUiLoader *loader = new QUiLoader(this); * loader->setLanguageChangeEnabled(true); * * // load the UI * QFile uiFile(QStringLiteral("/path/to/userInterface.ui")); * uiFile.open(QFile::ReadOnly); * QWidget *loadedWidget = loader->load(&uiFile, this); * uiFile.close(); * * // the object name of the loaded UI is the context in this case * translator->addContextToMonitor(loadedWidget->objectName()); * * // send a LanguageChange event, this will re-translate using our translator * QEvent le(QEvent::LanguageChange); * QCoreApplication::sendEvent(loadedWidget, &le); * @endcode * * @since 5.0 **/ class KI18N_EXPORT KLocalizedTranslator : public QTranslator { Q_OBJECT public: explicit KLocalizedTranslator(QObject *parent = nullptr); virtual ~KLocalizedTranslator(); QString translate(const char *context, const char *sourceText, const char *disambiguation = nullptr, int n = -1) const override; /** * Sets the @p translationDomain to be used. * * The translation domain is required. Without the translation domain any invocation of * translate() will be delegated to the base class. * * @param translationDomain The translation domain to be used. **/ void setTranslationDomain(const QString &translationDomain); /** * Adds a @p context for which this Translator should be active. * * The Translator only translates texts with a context matching one of the monitored contexts. * If the context is not monitored, the translate() method delegates to the base class. * * @param context The context for which the Translator should be active * * @see removeContextToMonitor **/ void addContextToMonitor(const QString &context); /** * Stop translating for the given @p context. * * @param context The context for which the Translator should no longer be active * * @see addContextToMonitor **/ void removeContextToMonitor(const QString &context); private: const QScopedPointer d; }; #endif //KLOCALIZEDTRANSLATOR_H diff --git a/src/ktranscript.cpp b/src/ktranscript.cpp index 87b43b4..ba2628d 100644 --- a/src/ktranscript.cpp +++ b/src/ktranscript.cpp @@ -1,1617 +1,1604 @@ /* This file is part of the KDE libraries - Copyright (C) 2007 Chusslove Illich - Copyright (C) 2014 Kevin Krammer - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library 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 - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-FileCopyrightText: 2007 Chusslove Illich + SPDX-FileCopyrightText: 2014 Kevin Krammer + + SPDX-License-Identifier: LGPL-2.0-or-later */ #include #include #include //#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class KTranscriptImp; class Scriptface; typedef QHash TsConfigGroup; typedef QHash TsConfig; // Transcript implementation (used as singleton). class KTranscriptImp : public KTranscript { public: KTranscriptImp(); ~KTranscriptImp() override; QString eval(const QList &argv, const QString &lang, const QString &ctry, const QString &msgctxt, const QHash &dynctxt, const QString &msgid, const QStringList &subs, const QList &vals, const QString &ftrans, QList &mods, QString &error, bool &fallback) override; QStringList postCalls(const QString &lang) override; // Lexical path of the module for the executing code. QString currentModulePath; private: void loadModules(const QList &mods, QString &error); void setupInterpreter(const QString &lang); TsConfig config; QHash m_sface; }; // Script-side transcript interface. class Scriptface : public QObject { Q_OBJECT public: explicit Scriptface(const TsConfigGroup &config, QObject *parent = nullptr); ~Scriptface(); // Interface functions. Q_INVOKABLE QJSValue load(const QString &name); Q_INVOKABLE QJSValue setcall(const QJSValue &name, const QJSValue &func, const QJSValue &fval = QJSValue::NullValue); Q_INVOKABLE QJSValue hascall(const QString &name); Q_INVOKABLE QJSValue acallInternal(const QJSValue &args); Q_INVOKABLE QJSValue setcallForall(const QJSValue &name, const QJSValue &func, const QJSValue &fval = QJSValue::NullValue); Q_INVOKABLE QJSValue fallback(); Q_INVOKABLE QJSValue nsubs(); Q_INVOKABLE QJSValue subs(const QJSValue &index); Q_INVOKABLE QJSValue vals(const QJSValue &index); Q_INVOKABLE QJSValue msgctxt(); Q_INVOKABLE QJSValue dynctxt(const QString &key); Q_INVOKABLE QJSValue msgid(); Q_INVOKABLE QJSValue msgkey(); Q_INVOKABLE QJSValue msgstrf(); Q_INVOKABLE void dbgputs(const QString &str); Q_INVOKABLE void warnputs(const QString &str); Q_INVOKABLE QJSValue localeCountry(); Q_INVOKABLE QJSValue normKey(const QJSValue &phrase); Q_INVOKABLE QJSValue loadProps(const QString &name); Q_INVOKABLE QJSValue getProp(const QJSValue &phrase, const QJSValue &prop); Q_INVOKABLE QJSValue setProp(const QJSValue &phrase, const QJSValue &prop, const QJSValue &value); Q_INVOKABLE QJSValue toUpperFirst(const QJSValue &str, const QJSValue &nalt = QJSValue::NullValue); Q_INVOKABLE QJSValue toLowerFirst(const QJSValue &str, const QJSValue &nalt = QJSValue::NullValue); Q_INVOKABLE QJSValue getConfString(const QJSValue &key, const QJSValue &dval = QJSValue::NullValue); Q_INVOKABLE QJSValue getConfBool(const QJSValue &key, const QJSValue &dval = QJSValue::NullValue); Q_INVOKABLE QJSValue getConfNumber(const QJSValue &key, const QJSValue &dval = QJSValue::NullValue); // Helper methods to interface functions. QJSValue load(const QJSValueList &names); QJSValue loadProps(const QJSValueList &names); QString loadProps_text(const QString &fpath); QString loadProps_bin(const QString &fpath); QString loadProps_bin_00(const QString &fpath); QString loadProps_bin_01(const QString &fpath); void put(const QString &propertyName, const QJSValue &value); // Link to its script engine QJSEngine *const scriptEngine; // Current message data. const QString *msgcontext; const QHash *dyncontext; const QString *msgId; const QStringList *subList; const QList *valList; const QString *ftrans; const QString *ctry; // Fallback request handle. bool *fallbackRequest; // Function register. QHash funcs; QHash fvals; QHash fpaths; // Ordering of those functions which execute for all messages. QList nameForalls; // Property values per phrase (used by *Prop interface calls). // Not QStrings, in order to avoid conversion from UTF-8 when // loading compiled maps (less latency on startup). QHash > phraseProps; // Unresolved property values per phrase, // containing the pointer to compiled pmap file handle and offset in it. QHash > phraseUnparsedProps; QHash resolveUnparsedProps(const QByteArray &phrase); // Set of loaded pmap files by paths and file handle pointers. QSet loadedPmapPaths; QSet loadedPmapHandles; // User config. TsConfigGroup config; }; // ---------------------------------------------------------------------- // Custom debug and warning output (kdebug not available) #define DBGP "KTranscript: " void dbgout(const char *str) { #ifndef NDEBUG fprintf(stderr, DBGP"%s\n", str); #else Q_UNUSED(str); #endif } template void dbgout(const char *str, const T1 &a1) { #ifndef NDEBUG fprintf(stderr, DBGP"%s\n", QString::fromUtf8(str).arg(a1).toLocal8Bit().data()); #else Q_UNUSED(str); Q_UNUSED(a1); #endif } template void dbgout(const char *str, const T1 &a1, const T2 &a2) { #ifndef NDEBUG fprintf(stderr, DBGP"%s\n", QString::fromUtf8(str).arg(a1).arg(a2).toLocal8Bit().data()); #else Q_UNUSED(str); Q_UNUSED(a1); Q_UNUSED(a2); #endif } template void dbgout(const char *str, const T1 &a1, const T2 &a2, const T3 &a3) { #ifndef NDEBUG fprintf(stderr, DBGP"%s\n", QString::fromUtf8(str).arg(a1).arg(a2).arg(a3).toLocal8Bit().data()); #else Q_UNUSED(str); Q_UNUSED(a1); Q_UNUSED(a2); Q_UNUSED(a3); #endif } #define WARNP "KTranscript: " void warnout(const char *str) { fprintf(stderr, WARNP"%s\n", str); } template void warnout(const char *str, const T1 &a1) { fprintf(stderr, WARNP"%s\n", QString::fromUtf8(str).arg(a1).toLocal8Bit().data()); } // ---------------------------------------------------------------------- // Produces a string out of a script exception. QString expt2str(const QJSValue &expt) { if (expt.isError()) { const QJSValue message = expt.property(QStringLiteral("message")); if (!message.isUndefined()) { return QStringLiteral("Error: %1").arg(message.toString()); } } QString strexpt = expt.toString(); return QStringLiteral("Caught exception: %1").arg(strexpt); } // ---------------------------------------------------------------------- // Count number of lines in the string, // up to and excluding the requested position. int countLines(const QString &s, int p) { int n = 1; int len = s.length(); for (int i = 0; i < p && i < len; ++i) { if (s[i] == QLatin1Char('\n')) { ++n; } } return n; } // ---------------------------------------------------------------------- // Normalize string key for hash lookups, QByteArray normKeystr(const QString &raw, bool mayHaveAcc = true) { // NOTE: Regexes should not be used here for performance reasons. // This function may potentially be called thousands of times // on application startup. QString key = raw; // Strip all whitespace. int len = key.length(); QString nkey; for (int i = 0; i < len; ++i) { QChar c = key[i]; if (!c.isSpace()) { nkey.append(c); } } key = nkey; // Strip accelerator marker. if (mayHaveAcc) { key = removeAcceleratorMarker(key); } // Convert to lower case. key = key.toLower(); return key.toUtf8(); } // ---------------------------------------------------------------------- // Trim multiline string in a "smart" way: // Remove leading and trailing whitespace up to and including first // newline from that side, if there is one; otherwise, don't touch. QString trimSmart(const QString &raw) { // NOTE: This could be done by a single regex, but is not due to // performance reasons. // This function may potentially be called thousands of times // on application startup. int len = raw.length(); int is = 0; while (is < len && raw[is].isSpace() && raw[is] != QLatin1Char('\n')) { ++is; } if (is >= len || raw[is] != QLatin1Char('\n')) { is = -1; } int ie = len - 1; while (ie >= 0 && raw[ie].isSpace() && raw[ie] != QLatin1Char('\n')) { --ie; } if (ie < 0 || raw[ie] != QLatin1Char('\n')) { ie = len; } return raw.mid(is + 1, ie - is - 1); } // ---------------------------------------------------------------------- // Produce a JavaScript object out of Qt variant. QJSValue variantToJsValue(const QVariant &val) { QVariant::Type vtype = val.type(); if (vtype == QVariant::String) { return QJSValue(val.toString()); } else if (vtype == QVariant::Bool) { return QJSValue(val.toBool()); } else if (vtype == QVariant::Double || vtype == QVariant::Int || vtype == QVariant::UInt || vtype == QVariant::LongLong || vtype == QVariant::ULongLong) { return QJSValue(val.toDouble()); } else { return QJSValue::UndefinedValue; } } // ---------------------------------------------------------------------- // Parse ini-style config file, // returning content as hash of hashes by group and key. // Parsing is not fussy, it will read what it can. TsConfig readConfig(const QString &fname) { TsConfig config; // Add empty group. TsConfig::iterator configGroup; configGroup = config.insert(QString(), TsConfigGroup()); QFile file(fname); if (!file.open(QIODevice::ReadOnly)) { return config; } QTextStream stream(&file); stream.setCodec("UTF-8"); while (!stream.atEnd()) { QString line = stream.readLine(); int p1, p2; // Remove comment from the line. p1 = line.indexOf(QLatin1Char('#')); if (p1 >= 0) { line.truncate(p1); } line = line.trimmed(); if (line.isEmpty()) { continue; } if (line[0] == QLatin1Char('[')) { // Group switch. p1 = 0; p2 = line.indexOf(QLatin1Char(']'), p1 + 1); if (p2 < 0) { continue; } QString group = line.mid(p1 + 1, p2 - p1 - 1).trimmed(); configGroup = config.find(group); if (configGroup == config.end()) { // Add new group. configGroup = config.insert(group, TsConfigGroup()); } } else { // Field. p1 = line.indexOf(QLatin1Char('=')); if (p1 < 0) { continue; } QStringRef field = line.leftRef(p1).trimmed(); QStringRef value = line.midRef(p1 + 1).trimmed(); if (!field.isEmpty()) { (*configGroup)[field.toString()] = value.toString(); } } } file.close(); return config; } // ---------------------------------------------------------------------- // throw or log error, depending on context availability static QJSValue throwError(QJSEngine *engine, const QString &message) { if (engine) { return engine->evaluate(QStringLiteral("new Error(%1)").arg(message)); } qCritical() << "Script error" << message; return QJSValue::UndefinedValue; } #ifdef KTRANSCRIPT_TESTBUILD // ---------------------------------------------------------------------- // Test build creation/destruction hooks static KTranscriptImp *s_transcriptInstance = nullptr; KTranscriptImp *globalKTI() { return s_transcriptInstance; } KTranscript *autotestCreateKTranscriptImp() { Q_ASSERT(s_transcriptInstance == nullptr); s_transcriptInstance = new KTranscriptImp; return s_transcriptInstance; } void autotestDestroyKTranscriptImp() { Q_ASSERT(s_transcriptInstance != nullptr); delete s_transcriptInstance; s_transcriptInstance = nullptr; } #else // ---------------------------------------------------------------------- // Dynamic loading. Q_GLOBAL_STATIC(KTranscriptImp, globalKTI) extern "C" { KTRANSCRIPT_EXPORT KTranscript *load_transcript() { return globalKTI(); } } #endif // ---------------------------------------------------------------------- // KTranscript definitions. KTranscriptImp::KTranscriptImp() { // Load user configuration. QString tsConfigPath = QStandardPaths::locate(QStandardPaths::ConfigLocation, QStringLiteral("ktranscript.ini")); if (tsConfigPath.isEmpty()) { tsConfigPath = QDir::homePath() + QLatin1Char('/') + QLatin1String(".transcriptrc"); } config = readConfig(tsConfigPath); } KTranscriptImp::~KTranscriptImp() { qDeleteAll(m_sface); } QString KTranscriptImp::eval(const QList &argv, const QString &lang, const QString &ctry, const QString &msgctxt, const QHash &dynctxt, const QString &msgid, const QStringList &subs, const QList &vals, const QString &ftrans, QList &mods, QString &error, bool &fallback) { //error = "debug"; return QString(); error.clear(); // empty error message means successful evaluation fallback = false; // fallback not requested #if 0 // FIXME: Maybe not needed, as QJSEngine has no native outside access? // Unportable (needs unistd.h)? // If effective user id is root and real user id is not root. if (geteuid() == 0 && getuid() != 0) { // Since scripts are user input, and the program is running with // root permissions while real user is not root, do not invoke // scripting at all, to prevent exploits. error = "Security block: trying to execute a script in suid environment."; return QString(); } #endif // Load any new modules and clear the list. if (!mods.isEmpty()) { loadModules(mods, error); mods.clear(); if (!error.isEmpty()) { return QString(); } } // Add interpreters for new languages. // (though it should never happen here, but earlier when loading modules; // this also means there are no calls set, so the unregistered call error // below will be reported). if (!m_sface.contains(lang)) { setupInterpreter(lang); } // Shortcuts. Scriptface *sface = m_sface[lang]; QJSEngine *engine = sface->scriptEngine; QJSValue gobj = engine->globalObject(); // Link current message data for script-side interface. sface->msgcontext = &msgctxt; sface->dyncontext = &dynctxt; sface->msgId = &msgid; sface->subList = &subs; sface->valList = &vals; sface->ftrans = &ftrans; sface->fallbackRequest = &fallback; sface->ctry = &ctry; // Find corresponding JS function. int argc = argv.size(); if (argc < 1) { //error = "At least the call name must be supplied."; // Empty interpolation is OK, possibly used just to initialize // at a given point (e.g. for Ts.setForall() to start having effect). return QString(); } QString funcName = argv[0].toString(); if (!sface->funcs.contains(funcName)) { error = QStringLiteral("Unregistered call to '%1'.").arg(funcName); return QString(); } QJSValue func = sface->funcs[funcName]; QJSValue fval = sface->fvals[funcName]; // Recover module path from the time of definition of this call, // for possible load calls. currentModulePath = sface->fpaths[funcName]; // Execute function. QJSValueList arglist; arglist.reserve(argc-1); for (int i = 1; i < argc; ++i) { arglist.append(engine->toScriptValue(argv[i])); } QJSValue val; if (fval.isObject()) { val = func.callWithInstance(fval, arglist); } else { // no object associated to this function, use global val = func.callWithInstance(gobj, arglist); } if (fallback) { // Fallback to ordinary translation requested. return QString(); } else if (!val.isError()) { // Evaluation successful. if (val.isString()) { // Good to go. return val.toString(); } else { // Accept only strings. QString strval = val.toString(); error = QStringLiteral("Non-string return value: %1").arg(strval); return QString(); } } else { // Exception raised. error = expt2str(val); return QString(); } } QStringList KTranscriptImp::postCalls(const QString &lang) { // Return no calls if scripting was not already set up for this language. // NOTE: This shouldn't happen, as postCalls cannot be called in such case. if (!m_sface.contains(lang)) { return QStringList(); } // Shortcuts. Scriptface *sface = m_sface[lang]; return sface->nameForalls; } void KTranscriptImp::loadModules(const QList &mods, QString &error) { QList modErrors; for (const QStringList &mod : mods) { QString mpath = mod[0]; QString mlang = mod[1]; // Add interpreters for new languages. if (!m_sface.contains(mlang)) { setupInterpreter(mlang); } // Setup current module path for loading submodules. // (sort of closure over invocations of loadf) int posls = mpath.lastIndexOf(QLatin1Char('/')); if (posls < 1) { modErrors.append(QStringLiteral( "Funny module path '%1', skipping.").arg(mpath)); continue; } currentModulePath = mpath.left(posls); QString fname = mpath.mid(posls + 1); // Scriptface::loadf() wants no extension on the filename fname = fname.left(fname.lastIndexOf(QLatin1Char('.'))); // Load the module. QJSValueList alist; alist.append(QJSValue(fname)); m_sface[mlang]->load(alist); } // Unset module path. currentModulePath.clear(); for (const QString &merr : qAsConst(modErrors)) { error.append(merr + QLatin1Char('\n')); } } #define SFNAME "Ts" void KTranscriptImp::setupInterpreter(const QString &lang) { // Add scripting interface // Creates its own script engine and registers with it // NOTE: Config may not contain an entry for the language, in which case // it is automatically constructed as an empty hash. This is intended. Scriptface *sface = new Scriptface(config[lang]); // Store scriptface m_sface[lang] = sface; //dbgout("=====> Created interpreter for '%1'", lang); } Scriptface::Scriptface(const TsConfigGroup &config_, QObject *parent) : QObject(parent), scriptEngine(new QJSEngine), fallbackRequest(nullptr), config(config_) { QJSValue object = scriptEngine->newQObject(this); scriptEngine->globalObject().setProperty(QStringLiteral(SFNAME), object); scriptEngine->evaluate(QStringLiteral("Ts.acall = function() { return Ts.acallInternal(Array.prototype.slice.call(arguments)); };")); } Scriptface::~Scriptface() { qDeleteAll(loadedPmapHandles); scriptEngine->deleteLater(); } void Scriptface::put(const QString &propertyName, const QJSValue &value) { QJSValue internalObject = scriptEngine->globalObject().property(QStringLiteral("ScriptfaceInternal")); if (internalObject.isUndefined()) { internalObject = scriptEngine->newObject(); scriptEngine->globalObject().setProperty(QStringLiteral("ScriptfaceInternal"), internalObject); } internalObject.setProperty(propertyName, value); } // ---------------------------------------------------------------------- // Scriptface interface functions. #ifdef _MSC_VER // Work around bizarre MSVC (2013) bug preventing use of QStringLiteral for concatenated string literals # define SPREF(X) QString::fromLatin1(SFNAME "." X) #else # define SPREF(X) QStringLiteral(SFNAME "." X) #endif QJSValue Scriptface::load(const QString &name) { QJSValueList fnames; fnames << name; return load(fnames); } QJSValue Scriptface::setcall(const QJSValue &name, const QJSValue &func, const QJSValue &fval) { if (!name.isString()) { return throwError(scriptEngine, SPREF("setcall: expected string as first argument")); } if (!func.isCallable()) { return throwError(scriptEngine, SPREF("setcall: expected function as second argument")); } if (!(fval.isObject() || fval.isNull())) { return throwError(scriptEngine, SPREF("setcall: expected object or null as third argument")); } QString qname = name.toString(); funcs[qname] = func; fvals[qname] = fval; // Register values to keep GC from collecting them. Is this needed? put(QStringLiteral("#:f<%1>").arg(qname), func); put(QStringLiteral("#:o<%1>").arg(qname), fval); // Set current module path as module path for this call, // in case it contains load subcalls. fpaths[qname] = globalKTI()->currentModulePath; return QJSValue::UndefinedValue; } QJSValue Scriptface::hascall(const QString &qname) { return QJSValue(funcs.contains(qname)); } QJSValue Scriptface::acallInternal(const QJSValue &args) { QJSValueIterator it(args); if (!it.next()) { return throwError(scriptEngine, SPREF("acall: expected at least one argument (call name)")); } if (!it.value().isString()) { return throwError(scriptEngine, SPREF("acall: expected string as first argument (call name)")); } // Get the function and its context object. QString callname = it.value().toString(); if (!funcs.contains(callname)) { return throwError(scriptEngine, SPREF("acall: unregistered call to '%1'").arg(callname)); } QJSValue func = funcs[callname]; QJSValue fval = fvals[callname]; // Recover module path from the time of definition of this call, // for possible load calls. globalKTI()->currentModulePath = fpaths[callname]; // Execute function. QJSValueList arglist; while (it.next()) arglist.append(it.value()); QJSValue val; if (fval.isObject()) { // Call function with the context object. val = func.callWithInstance(fval, arglist); } else { // No context object associated to this function, use global. val = func.callWithInstance(scriptEngine->globalObject(), arglist); } return val; } QJSValue Scriptface::setcallForall(const QJSValue &name, const QJSValue &func, const QJSValue &fval) { if (!name.isString()) { return throwError(scriptEngine, SPREF("setcallForall: expected string as first argument")); } if (!func.isCallable()) { return throwError(scriptEngine, SPREF("setcallForall: expected function as second argument")); } if (!(fval.isObject() || fval.isNull())) { return throwError(scriptEngine, SPREF("setcallForall: expected object or null as third argument")); } QString qname = name.toString(); funcs[qname] = func; fvals[qname] = fval; // Register values to keep GC from collecting them. Is this needed? put(QStringLiteral("#:fall<%1>").arg(qname), func); put(QStringLiteral("#:oall<%1>").arg(qname), fval); // Set current module path as module path for this call, // in case it contains load subcalls. fpaths[qname] = globalKTI()->currentModulePath; // Put in the queue order for execution on all messages. nameForalls.append(qname); return QJSValue::UndefinedValue; } QJSValue Scriptface::fallback() { if (fallbackRequest) { *fallbackRequest = true; } return QJSValue::UndefinedValue; } QJSValue Scriptface::nsubs() { return QJSValue(subList->size()); } QJSValue Scriptface::subs(const QJSValue &index) { if (!index.isNumber()) { return throwError(scriptEngine, SPREF("subs: expected number as first argument")); } int i = qRound(index.toNumber()); if (i < 0 || i >= subList->size()) { return throwError(scriptEngine, SPREF("subs: index out of range")); } return QJSValue(subList->at(i)); } QJSValue Scriptface::vals(const QJSValue &index) { if (!index.isNumber()) { return throwError(scriptEngine, SPREF("vals: expected number as first argument")); } int i = qRound(index.toNumber()); if (i < 0 || i >= valList->size()) { return throwError(scriptEngine, SPREF("vals: index out of range")); } return scriptEngine->toScriptValue(valList->at(i)); // return variantToJsValue(valList->at(i)); } QJSValue Scriptface::msgctxt() { return QJSValue(*msgcontext); } QJSValue Scriptface::dynctxt(const QString &qkey) { if (dyncontext->contains(qkey)) { return QJSValue(dyncontext->value(qkey)); } return QJSValue::UndefinedValue; } QJSValue Scriptface::msgid() { return QJSValue(*msgId); } QJSValue Scriptface::msgkey() { return QJSValue(QString(*msgcontext + QLatin1Char('|') + *msgId)); } QJSValue Scriptface::msgstrf() { return QJSValue(*ftrans); } void Scriptface::dbgputs(const QString &qstr) { dbgout("[JS-debug] %1", qstr); } void Scriptface::warnputs(const QString &qstr) { warnout("[JS-warning] %1", qstr); } QJSValue Scriptface::localeCountry() { return QJSValue(*ctry); } QJSValue Scriptface::normKey(const QJSValue &phrase) { if (!phrase.isString()) { return throwError(scriptEngine, SPREF("normKey: expected string as argument")); } QByteArray nqphrase = normKeystr(phrase.toString()); return QJSValue(QString::fromUtf8(nqphrase)); } QJSValue Scriptface::loadProps(const QString &name) { QJSValueList fnames; fnames << name; return loadProps(fnames); } QJSValue Scriptface::loadProps(const QJSValueList &fnames) { if (globalKTI()->currentModulePath.isEmpty()) { return throwError(scriptEngine, SPREF("loadProps: no current module path, aiiie...")); } for (int i = 0; i < fnames.size(); ++i) { if (!fnames[i].isString()) { return throwError(scriptEngine, SPREF("loadProps: expected string as file name")); } } for (int i = 0; i < fnames.size(); ++i) { QString qfname = fnames[i].toString(); QString qfpath_base = globalKTI()->currentModulePath + QLatin1Char('/') + qfname; // Determine which kind of map is available. // Give preference to compiled map. QString qfpath = qfpath_base + QLatin1String(".pmapc"); bool haveCompiled = true; QFile file_check(qfpath); if (!file_check.open(QIODevice::ReadOnly)) { haveCompiled = false; qfpath = qfpath_base + QLatin1String(".pmap"); QFile file_check(qfpath); if (!file_check.open(QIODevice::ReadOnly)) { return throwError(scriptEngine, SPREF("loadProps: cannot read map '%1'") .arg(qfpath)); } } file_check.close(); // Load from appropriate type of map. if (!loadedPmapPaths.contains(qfpath)) { QString errorString; if (haveCompiled) { errorString = loadProps_bin(qfpath); } else { errorString = loadProps_text(qfpath); } if (!errorString.isEmpty()) { return throwError(scriptEngine, errorString); } dbgout("Loaded property map: %1", qfpath); loadedPmapPaths.insert(qfpath); } } return QJSValue::UndefinedValue; } QJSValue Scriptface::getProp(const QJSValue &phrase, const QJSValue &prop) { if (!phrase.isString()) { return throwError(scriptEngine, SPREF("getProp: expected string as first argument")); } if (!prop.isString()) { return throwError(scriptEngine, SPREF("getProp: expected string as second argument")); } QByteArray qphrase = normKeystr(phrase.toString()); QHash props = phraseProps.value(qphrase); if (props.isEmpty()) { props = resolveUnparsedProps(qphrase); } if (!props.isEmpty()) { QByteArray qprop = normKeystr(prop.toString()); QByteArray qval = props.value(qprop); if (!qval.isEmpty()) { return QJSValue(QString::fromUtf8(qval)); } } return QJSValue::UndefinedValue; } QJSValue Scriptface::setProp(const QJSValue &phrase, const QJSValue &prop, const QJSValue &value) { if (!phrase.isString()) { return throwError(scriptEngine, SPREF("setProp: expected string as first argument")); } if (!prop.isString()) { return throwError(scriptEngine, SPREF("setProp: expected string as second argument")); } if (!value.isString()) { return throwError(scriptEngine, SPREF("setProp: expected string as third argument")); } QByteArray qphrase = normKeystr(phrase.toString()); QByteArray qprop = normKeystr(prop.toString()); QByteArray qvalue = value.toString().toUtf8(); // Any non-existent key in first or second-level hash will be created. phraseProps[qphrase][qprop] = qvalue; return QJSValue::UndefinedValue; } static QString toCaseFirst(const QString &qstr, int qnalt, bool toupper) { static const QLatin1String head("~@"); static const int hlen = 2; //head.length() // If the first letter is found within an alternatives directive, // change case of the first letter in each of the alternatives. QString qstrcc = qstr; int len = qstr.length(); QChar altSep; int remainingAlts = 0; bool checkCase = true; int numChcased = 0; int i = 0; while (i < len) { QChar c = qstr[i]; if (qnalt && !remainingAlts && qstr.midRef(i, hlen) == head) { // An alternatives directive is just starting. i += 2; if (i >= len) { break; // malformed directive, bail out } // Record alternatives separator, set number of remaining // alternatives, reactivate case checking. altSep = qstrcc[i]; remainingAlts = qnalt; checkCase = true; } else if (remainingAlts && c == altSep) { // Alternative separator found, reduce number of remaining // alternatives and reactivate case checking. --remainingAlts; checkCase = true; } else if (checkCase && c.isLetter()) { // Case check is active and the character is a letter; change case. if (toupper) { qstrcc[i] = c.toUpper(); } else { qstrcc[i] = c.toLower(); } ++numChcased; // No more case checks until next alternatives separator. checkCase = false; } // If any letter has been changed, and there are no more alternatives // to be processed, we're done. if (numChcased > 0 && remainingAlts == 0) { break; } // Go to next character. ++i; } return qstrcc; } QJSValue Scriptface::toUpperFirst(const QJSValue &str, const QJSValue &nalt) { if (!str.isString()) { return throwError(scriptEngine, SPREF("toUpperFirst: expected string as first argument")); } if (!(nalt.isNumber() || nalt.isNull())) { return throwError(scriptEngine, SPREF("toUpperFirst: expected number as second argument")); } QString qstr = str.toString(); int qnalt = nalt.isNull() ? 0 : nalt.toInt(); QString qstruc = toCaseFirst(qstr, qnalt, true); return QJSValue(qstruc); } QJSValue Scriptface::toLowerFirst(const QJSValue &str, const QJSValue &nalt) { if (!str.isString()) { return throwError(scriptEngine, SPREF("toLowerFirst: expected string as first argument")); } if (!(nalt.isNumber() || nalt.isNull())) { return throwError(scriptEngine, SPREF("toLowerFirst: expected number as second argument")); } QString qstr = str.toString(); int qnalt = nalt.isNull() ? 0 : nalt.toInt(); QString qstrlc = toCaseFirst(qstr, qnalt, false); return QJSValue(qstrlc); } QJSValue Scriptface::getConfString(const QJSValue &key, const QJSValue &dval) { if (!key.isString()) { return throwError(scriptEngine, QStringLiteral("getConfString: expected string as first argument")); } if (!(dval.isString() || dval.isNull())) { return throwError(scriptEngine, SPREF("getConfString: expected string as second argument (when given)")); } QString qkey = key.toString(); if (config.contains(qkey)) { return QJSValue(config.value(qkey)); } return dval.isNull() ? QJSValue::UndefinedValue : dval; } QJSValue Scriptface::getConfBool(const QJSValue &key, const QJSValue &dval) { if (!key.isString()) { return throwError(scriptEngine, SPREF("getConfBool: expected string as first argument")); } if (!(dval.isBool() || dval.isNull())) { return throwError(scriptEngine, SPREF("getConfBool: expected boolean as second argument (when given)")); } static QStringList falsities; if (falsities.isEmpty()) { falsities.append(QString(QLatin1Char('0'))); falsities.append(QStringLiteral("no")); falsities.append(QStringLiteral("false")); } QString qkey = key.toString(); if (config.contains(qkey)) { QString qval = config.value(qkey).toLower(); return QJSValue(!falsities.contains(qval)); } return dval.isNull() ? QJSValue::UndefinedValue : dval; } QJSValue Scriptface::getConfNumber(const QJSValue &key, const QJSValue &dval) { if (!key.isString()) { return throwError(scriptEngine, SPREF("getConfNumber: expected string " "as first argument")); } if (!(dval.isNumber() || dval.isNull())) { return throwError(scriptEngine, SPREF("getConfNumber: expected number " "as second argument (when given)")); } QString qkey = key.toString(); if (config.contains(qkey)) { QString qval = config.value(qkey); bool convOk; double qnum = qval.toDouble(&convOk); if (convOk) { return QJSValue(qnum); } } return dval.isNull() ? QJSValue::UndefinedValue : dval; } // ---------------------------------------------------------------------- // Scriptface helpers to interface functions. QJSValue Scriptface::load(const QJSValueList &fnames) { if (globalKTI()->currentModulePath.isEmpty()) { return throwError(scriptEngine, SPREF("load: no current module path, aiiie...")); } for (int i = 0; i < fnames.size(); ++i) { if (!fnames[i].isString()) { return throwError(scriptEngine, SPREF("load: expected string as file name")); } } for (int i = 0; i < fnames.size(); ++i) { QString qfname = fnames[i].toString(); QString qfpath = globalKTI()->currentModulePath + QLatin1Char('/') + qfname + QLatin1String(".js"); QFile file(qfpath); if (!file.open(QIODevice::ReadOnly)) { return throwError(scriptEngine, SPREF("load: cannot read file '%1'") \ .arg(qfpath)); } QTextStream stream(&file); stream.setCodec("UTF-8"); QString source = stream.readAll(); file.close(); QJSValue comp = scriptEngine->evaluate(source, qfpath, 0); if (comp.isError()) { QString msg = comp.toString(); QString line; if (comp.isObject()) { QJSValue lval = comp.property(QStringLiteral("line")); if (lval.isNumber()) { line = QString::number(lval.toInt()); } } return throwError(scriptEngine, QStringLiteral("at %1:%2: %3") .arg(qfpath, line, msg)); } dbgout("Loaded module: %1", qfpath); } return QJSValue::UndefinedValue; } QString Scriptface::loadProps_text(const QString &fpath) { QFile file(fpath); if (!file.open(QIODevice::ReadOnly)) { return SPREF("loadProps_text: cannot read file '%1'") .arg(fpath); } QTextStream stream(&file); stream.setCodec("UTF-8"); QString s = stream.readAll(); file.close(); // Parse the map. // Should care about performance: possibly executed on each KDE // app startup and reading houndreds of thousands of characters. enum {s_nextEntry, s_nextKey, s_nextValue}; QList ekeys; // holds keys for current entry QHash props; // holds properties for current entry int slen = s.length(); int state = s_nextEntry; QByteArray pkey; QChar prop_sep, key_sep; int i = 0; while (1) { int i_checkpoint = i; if (state == s_nextEntry) { while (s[i].isSpace()) { ++i; if (i >= slen) { goto END_PROP_PARSE; } } if (i + 1 >= slen) { return SPREF("loadProps_text: unexpected end " "of file in %1").arg(fpath); } if (s[i] != QLatin1Char('#')) { // Separator characters for this entry. key_sep = s[i]; prop_sep = s[i + 1]; if (key_sep.isLetter() || prop_sep.isLetter()) { return SPREF("loadProps_text: separator " "characters must not be letters at %1:%2") .arg(fpath).arg(countLines(s, i)); } // Reset all data for current entry. ekeys.clear(); props.clear(); pkey.clear(); i += 2; state = s_nextKey; } else { // This is a comment, skip to EOL, don't change state. while (s[i] != QLatin1Char('\n')) { ++i; if (i >= slen) { goto END_PROP_PARSE; } } } } else if (state == s_nextKey) { int ip = i; // Proceed up to next key or property separator. while (s[i] != key_sep && s[i] != prop_sep) { ++i; if (i >= slen) { goto END_PROP_PARSE; } } if (s[i] == key_sep) { // This is a property key, // record for when the value gets parsed. pkey = normKeystr(s.mid(ip, i - ip), false); i += 1; state = s_nextValue; } else { // if (s[i] == prop_sep) { // This is an entry key, or end of entry. QByteArray ekey = normKeystr(s.mid(ip, i - ip), false); if (!ekey.isEmpty()) { // An entry key. ekeys.append(ekey); i += 1; state = s_nextKey; } else { // End of entry. if (ekeys.size() < 1) { return SPREF("loadProps_text: no entry key " "for entry ending at %1:%2") .arg(fpath).arg(countLines(s, i)); } // Add collected entry into global store, // once for each entry key (QHash implicitly shared). for (const QByteArray &ekey : qAsConst(ekeys)) { phraseProps[ekey] = props; } i += 1; state = s_nextEntry; } } } else if (state == s_nextValue) { int ip = i; // Proceed up to next property separator. while (s[i] != prop_sep) { ++i; if (i >= slen) { goto END_PROP_PARSE; } if (s[i] == key_sep) { return SPREF("loadProps_text: property separator " "inside property value at %1:%2") .arg(fpath).arg(countLines(s, i)); } } // Extract the property value and store the property. QByteArray pval = trimSmart(s.mid(ip, i - ip)).toUtf8(); props[pkey] = pval; i += 1; state = s_nextKey; } else { return SPREF("loadProps: internal error 10 at %1:%2") .arg(fpath).arg(countLines(s, i)); } // To avoid infinite looping and stepping out. if (i == i_checkpoint || i >= slen) { return SPREF("loadProps: internal error 20 at %1:%2") .arg(fpath).arg(countLines(s, i)); } } END_PROP_PARSE: if (state != s_nextEntry) { return SPREF("loadProps: unexpected end of file in %1") .arg(fpath); } return QString(); } // Read big-endian integer of nbytes length at position pos // in character array fc of length len. // Update position to point after the number. // In case of error, pos is set to -1. template static int bin_read_int_nbytes(const char *fc, qlonglong len, qlonglong &pos, int nbytes) { if (pos + nbytes > len) { pos = -1; return 0; } T num = qFromBigEndian((uchar *) fc + pos); pos += nbytes; return num; } // Read 64-bit big-endian integer. static quint64 bin_read_int64(const char *fc, qlonglong len, qlonglong &pos) { return bin_read_int_nbytes(fc, len, pos, 8); } // Read 32-bit big-endian integer. static quint32 bin_read_int(const char *fc, qlonglong len, qlonglong &pos) { return bin_read_int_nbytes(fc, len, pos, 4); } // Read string at position pos of character array fc of length n. // String is represented as 32-bit big-endian byte length followed by bytes. // Update position to point after the string. // In case of error, pos is set to -1. static QByteArray bin_read_string(const char *fc, qlonglong len, qlonglong &pos) { // Binary format stores strings as length followed by byte sequence. // No null-termination. int nbytes = bin_read_int(fc, len, pos); if (pos < 0) { return QByteArray(); } if (nbytes < 0 || pos + nbytes > len) { pos = -1; return QByteArray(); } QByteArray s(fc + pos, nbytes); pos += nbytes; return s; } QString Scriptface::loadProps_bin(const QString &fpath) { QFile file(fpath); if (!file.open(QIODevice::ReadOnly)) { return SPREF("loadProps: cannot read file '%1'") .arg(fpath); } // Collect header. QByteArray head(8, '0'); file.read(head.data(), head.size()); file.close(); // Choose pmap loader based on header. if (head == "TSPMAP00") { return loadProps_bin_00(fpath); } else if (head == "TSPMAP01") { return loadProps_bin_01(fpath); } else { return SPREF("loadProps: unknown version of compiled map '%1'") .arg(fpath); } } QString Scriptface::loadProps_bin_00(const QString &fpath) { QFile file(fpath); if (!file.open(QIODevice::ReadOnly)) { return SPREF("loadProps: cannot read file '%1'") .arg(fpath); } QByteArray fctmp = file.readAll(); file.close(); const char *fc = fctmp.data(); const int fclen = fctmp.size(); // Indicates stream state. qlonglong pos = 0; // Match header. QByteArray head(fc, 8); pos += 8; if (head != "TSPMAP00") { goto END_PROP_PARSE; } // Read total number of entries. int nentries; nentries = bin_read_int(fc, fclen, pos); if (pos < 0) { goto END_PROP_PARSE; } // Read all entries. for (int i = 0; i < nentries; ++i) { // Read number of entry keys and all entry keys. QList ekeys; int nekeys = bin_read_int(fc, fclen, pos); if (pos < 0) { goto END_PROP_PARSE; } ekeys.reserve(nekeys); //nekeys are appended if data is not corrupted for (int j = 0; j < nekeys; ++j) { QByteArray ekey = bin_read_string(fc, fclen, pos); if (pos < 0) { goto END_PROP_PARSE; } ekeys.append(ekey); } //dbgout("--------> ekey[0]={%1}", QString::fromUtf8(ekeys[0])); // Read number of properties and all properties. QHash props; int nprops = bin_read_int(fc, fclen, pos); if (pos < 0) { goto END_PROP_PARSE; } for (int j = 0; j < nprops; ++j) { QByteArray pkey = bin_read_string(fc, fclen, pos); if (pos < 0) { goto END_PROP_PARSE; } QByteArray pval = bin_read_string(fc, fclen, pos); if (pos < 0) { goto END_PROP_PARSE; } props[pkey] = pval; } // Add collected entry into global store, // once for each entry key (QHash implicitly shared). for (const QByteArray &ekey : qAsConst(ekeys)) { phraseProps[ekey] = props; } } END_PROP_PARSE: if (pos < 0) { return SPREF("loadProps: corrupt compiled map '%1'") .arg(fpath); } return QString(); } QString Scriptface::loadProps_bin_01(const QString &fpath) { QFile *file = new QFile(fpath); if (!file->open(QIODevice::ReadOnly)) { return SPREF("loadProps: cannot read file '%1'") .arg(fpath); } QByteArray fstr; qlonglong pos; // Read the header and number and length of entry keys. fstr = file->read(8 + 4 + 8); pos = 0; QByteArray head = fstr.left(8); pos += 8; if (head != "TSPMAP01") { return SPREF("loadProps: corrupt compiled map '%1'") .arg(fpath); } quint32 numekeys = bin_read_int(fstr, fstr.size(), pos); quint64 lenekeys = bin_read_int64(fstr, fstr.size(), pos); // Read entry keys. fstr = file->read(lenekeys); pos = 0; for (quint32 i = 0; i < numekeys; ++i) { QByteArray ekey = bin_read_string(fstr, lenekeys, pos); quint64 offset = bin_read_int64(fstr, lenekeys, pos); phraseUnparsedProps[ekey] = QPair(file, offset); } // // Read property keys. // ...when it becomes necessary loadedPmapHandles.insert(file); return QString(); } QHash Scriptface::resolveUnparsedProps(const QByteArray &phrase) { QPair ref = phraseUnparsedProps.value(phrase); QFile *file = ref.first; quint64 offset = ref.second; QHash props; if (file && file->seek(offset)) { QByteArray fstr = file->read(4 + 4); qlonglong pos = 0; quint32 numpkeys = bin_read_int(fstr, fstr.size(), pos); quint32 lenpkeys = bin_read_int(fstr, fstr.size(), pos); fstr = file->read(lenpkeys); pos = 0; for (quint32 i = 0; i < numpkeys; ++i) { QByteArray pkey = bin_read_string(fstr, lenpkeys, pos); QByteArray pval = bin_read_string(fstr, lenpkeys, pos); props[pkey] = pval; } phraseProps[phrase] = props; phraseUnparsedProps.remove(phrase); } return props; } #include "ktranscript.moc" diff --git a/src/ktranscript_p.h b/src/ktranscript_p.h index cfbf2dc..2230fa4 100644 --- a/src/ktranscript_p.h +++ b/src/ktranscript_p.h @@ -1,95 +1,82 @@ /* This file is part of the KDE libraries - Copyright (C) 2007 Chusslove Illich + SPDX-FileCopyrightText: 2007 Chusslove Illich - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library 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 - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-License-Identifier: LGPL-2.0-or-later */ #ifndef KTRANSCRIPT_P_H #define KTRANSCRIPT_P_H #include #include #include #include /** * @internal * (used by KLocalizedString) * * @c KTranscript provides support for programmable translations. * The class is abstract in order to facilitate dynamic loading. * * @author Chusslove Illich * @short class for supporting programmable translations */ class KTranscript { public: /** * Evaluates interpolation. * * @param argv list of interpolation tokens * @param lang language of the translation * @param ctry locale country * @param msgctxt message context * @param dynctxt dynamic context * @param msgid original message * @param subs substitutions for message placeholders * @param vals values that were formatted to substitutions * @param ftrans finalized ordinary translation * @param mods scripting modules to load; the list is cleared after loading * @param error set to the message detailing the problem, if the script failed; set to empty otherwise * @param fallback set to true if the script requested fallback to ordinary translation; set to false otherwise * @return resolved interpolation if evaluation succeeded, * empty string otherwise */ virtual QString eval(const QList &argv, const QString &lang, const QString &ctry, const QString &msgctxt, const QHash &dynctxt, const QString &msgid, const QStringList &subs, const QList &vals, const QString &ftrans, QList &mods, QString &error, bool &fallback) = 0; /** * Returns the list of calls to execute an all messages after the * interpolations are done, as evaluations with no parameters. * * @param lang language of the translation * @return list of post calls */ virtual QStringList postCalls(const QString &lang) = 0; /** * Destructor. */ virtual ~KTranscript() {} }; #ifdef KTRANSCRIPT_TESTBUILD KTranscript *autotestCreateKTranscriptImp(); void autotestDestroyKTranscriptImp(); #endif #endif diff --git a/src/kuitmarkup.cpp b/src/kuitmarkup.cpp index 187682e..f2ab78a 100644 --- a/src/kuitmarkup.cpp +++ b/src/kuitmarkup.cpp @@ -1,1687 +1,1674 @@ /* This file is part of the KDE libraries - Copyright (C) 2007, 2013 Chusslove Illich - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library 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 - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-FileCopyrightText: 2007, 2013 Chusslove Illich + + SPDX-License-Identifier: LGPL-2.0-or-later */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ki18n_logging_kuit.h" #define QL1S(x) QLatin1String(x) #define QSL(x) QStringLiteral(x) #define QL1C(x) QLatin1Char(x) QString Kuit::escape(const QString &text) { int tlen = text.length(); QString ntext; ntext.reserve(tlen); for (int i = 0; i < tlen; ++i) { QChar c = text[i]; if (c == QL1C('&')) { ntext += QStringLiteral("&"); } else if (c == QL1C('<')) { ntext += QStringLiteral("<"); } else if (c == QL1C('>')) { ntext += QStringLiteral(">"); } else if (c == QL1C('\'')) { ntext += QStringLiteral("'"); } else if (c == QL1C('"')) { ntext += QStringLiteral("""); } else { ntext += c; } } return ntext; } // Truncates the string, for output of long messages. // (But don't truncate too much otherwise it's impossible to determine // which message is faulty if many messages have the same beginning). static QString shorten(const QString &str) { const int maxlen = 80; if (str.length() <= maxlen) { return str; } else { return str.leftRef(maxlen) + QSL("..."); } } static void parseUiMarker(const QString &context_, QString &roleName, QString &cueName, QString &formatName) { // UI marker is in the form @role:cue/format, // and must start just after any leading whitespace in the context string. // Note that names remain untouched if the marker is not found. // Normalize the whole string, all lowercase. QString context = context_.trimmed().toLower(); if (context.startsWith(QL1C('@'))) { // found UI marker static const QRegularExpression wsRx(QStringLiteral("\\s")); context = context.mid(1, wsRx.match(context).capturedStart(0) - 1); // Possible format. int pfmt = context.indexOf(QL1C('/')); if (pfmt >= 0) { formatName = context.mid(pfmt + 1); context.truncate(pfmt); } // Possible subcue. int pcue = context.indexOf(QL1C(':')); if (pcue >= 0) { cueName = context.mid(pcue + 1); context.truncate(pcue); } // Role. roleName = context; } } // Custom entity resolver for QXmlStreamReader. class KuitEntityResolver : public QXmlStreamEntityResolver { public: void setEntities(const QHash &entities) { entityMap = entities; } QString resolveUndeclaredEntity(const QString &name) override { QString value = entityMap.value(name); // This will return empty string if the entity name is not known, // which will make QXmlStreamReader signal unknown entity error. return value; } private: QHash entityMap; }; namespace Kuit { enum Role { // UI marker roles UndefinedRole, ActionRole, TitleRole, OptionRole, LabelRole, ItemRole, InfoRole }; enum Cue { // UI marker subcues UndefinedCue, ButtonCue, InmenuCue, IntoolbarCue, WindowCue, MenuCue, TabCue, GroupCue, ColumnCue, RowCue, SliderCue, SpinboxCue, ListboxCue, TextboxCue, ChooserCue, CheckCue, RadioCue, InlistboxCue, IntableCue, InrangeCue, IntextCue, ValuesuffixCue, TooltipCue, WhatsthisCue, PlaceholderCue, StatusCue, ProgressCue, TipofthedayCue, CreditCue, ShellCue }; } class KuitStaticData { public: QHash xmlEntities; QHash xmlEntitiesInverse; KuitEntityResolver xmlEntityResolver; QHash rolesByName; QHash cuesByName; QHash formatsByName; QHash namesByFormat; QHash > knownRoleCues; QHash comboKeyDelim; QHash guiPathDelim; QHash keyNames; QHash domainSetups; KuitStaticData(); ~KuitStaticData(); KuitStaticData(const KuitStaticData &) = delete; KuitStaticData &operator=(const KuitStaticData &) = delete; void setXmlEntityData(); void setUiMarkerData(); void setTextTransformData(); QString toKeyCombo(const QStringList &languages, const QString &shstr, Kuit::VisualFormat format); QString toInterfacePath(const QStringList &languages, const QString &inpstr, Kuit::VisualFormat format); }; KuitStaticData::KuitStaticData() { setXmlEntityData(); setUiMarkerData(); setTextTransformData(); } KuitStaticData::~KuitStaticData() { qDeleteAll(domainSetups); } void KuitStaticData::setXmlEntityData() { QString LT = QStringLiteral("lt"); QString GT = QStringLiteral("gt"); QString AMP = QStringLiteral("amp"); QString APOS = QStringLiteral("apos"); QString QUOT = QStringLiteral("quot"); // Default XML entities, direct and inverse mapping. xmlEntities[LT] = QString(QL1C('<')); xmlEntities[GT] = QString(QL1C('>')); xmlEntities[AMP] = QString(QL1C('&')); xmlEntities[APOS] = QString(QL1C('\'')); xmlEntities[QUOT] = QString(QL1C('"')); xmlEntitiesInverse[QString(QL1C('<'))] = LT; xmlEntitiesInverse[QString(QL1C('>'))] = GT; xmlEntitiesInverse[QString(QL1C('&'))] = AMP; xmlEntitiesInverse[QString(QL1C('\''))] = APOS; xmlEntitiesInverse[QString(QL1C('"'))] = QUOT; // Custom XML entities. xmlEntities[QStringLiteral("nbsp")] = QString(QChar(0xa0)); xmlEntityResolver.setEntities(xmlEntities); } void KuitStaticData::setUiMarkerData() { using namespace Kuit; // Role names and their available subcues. #undef SET_ROLE #define SET_ROLE(role, name, cues) do { \ rolesByName[name] = role; \ knownRoleCues[role] << cues; \ } while (0) SET_ROLE(ActionRole, QStringLiteral("action"), ButtonCue << InmenuCue << IntoolbarCue); SET_ROLE(TitleRole, QStringLiteral("title"), WindowCue << MenuCue << TabCue << GroupCue << ColumnCue << RowCue); SET_ROLE(LabelRole, QStringLiteral("label"), SliderCue << SpinboxCue << ListboxCue << TextboxCue << ChooserCue); SET_ROLE(OptionRole, QStringLiteral("option"), CheckCue << RadioCue); SET_ROLE(ItemRole, QStringLiteral("item"), InmenuCue << InlistboxCue << IntableCue << InrangeCue << IntextCue << ValuesuffixCue); SET_ROLE(InfoRole, QStringLiteral("info"), TooltipCue << WhatsthisCue << PlaceholderCue << StatusCue << ProgressCue << TipofthedayCue << CreditCue << ShellCue); // Cue names. #undef SET_CUE #define SET_CUE(cue, name) do { \ cuesByName[name] = cue; \ } while (0) SET_CUE(ButtonCue, QStringLiteral("button")); SET_CUE(InmenuCue, QStringLiteral("inmenu")); SET_CUE(IntoolbarCue, QStringLiteral("intoolbar")); SET_CUE(WindowCue, QStringLiteral("window")); SET_CUE(MenuCue, QStringLiteral("menu")); SET_CUE(TabCue, QStringLiteral("tab")); SET_CUE(GroupCue, QStringLiteral("group")); SET_CUE(ColumnCue, QStringLiteral("column")); SET_CUE(RowCue, QStringLiteral("row")); SET_CUE(SliderCue, QStringLiteral("slider")); SET_CUE(SpinboxCue, QStringLiteral("spinbox")); SET_CUE(ListboxCue, QStringLiteral("listbox")); SET_CUE(TextboxCue, QStringLiteral("textbox")); SET_CUE(ChooserCue, QStringLiteral("chooser")); SET_CUE(CheckCue, QStringLiteral("check")); SET_CUE(RadioCue, QStringLiteral("radio")); SET_CUE(InlistboxCue, QStringLiteral("inlistbox")); SET_CUE(IntableCue, QStringLiteral("intable")); SET_CUE(InrangeCue, QStringLiteral("inrange")); SET_CUE(IntextCue, QStringLiteral("intext")); SET_CUE(ValuesuffixCue, QStringLiteral("valuesuffix")); SET_CUE(TooltipCue, QStringLiteral("tooltip")); SET_CUE(WhatsthisCue, QStringLiteral("whatsthis")); SET_CUE(PlaceholderCue, QStringLiteral("placeholder")); SET_CUE(StatusCue, QStringLiteral("status")); SET_CUE(ProgressCue, QStringLiteral("progress")); SET_CUE(TipofthedayCue, QStringLiteral("tipoftheday")); SET_CUE(CreditCue, QStringLiteral("credit")); SET_CUE(ShellCue, QStringLiteral("shell")); // Format names. #undef SET_FORMAT #define SET_FORMAT(format, name) do { \ formatsByName[name] = format; \ namesByFormat[format] = name; \ } while (0) SET_FORMAT(UndefinedFormat, QStringLiteral("undefined")); SET_FORMAT(PlainText, QStringLiteral("plain")); SET_FORMAT(RichText, QStringLiteral("rich")); SET_FORMAT(TermText, QStringLiteral("term")); } void KuitStaticData::setTextTransformData() { // i18n: Decide which string is used to delimit keys in a keyboard // shortcut (e.g. + in Ctrl+Alt+Tab) in plain text. comboKeyDelim[Kuit::PlainText] = ki18nc("shortcut-key-delimiter/plain", "+"); comboKeyDelim[Kuit::TermText] = comboKeyDelim[Kuit::PlainText]; // i18n: Decide which string is used to delimit keys in a keyboard // shortcut (e.g. + in Ctrl+Alt+Tab) in rich text. comboKeyDelim[Kuit::RichText] = ki18nc("shortcut-key-delimiter/rich", "+"); // i18n: Decide which string is used to delimit elements in a GUI path // (e.g. -> in "Go to Settings->Advanced->Core tab.") in plain text. guiPathDelim[Kuit::PlainText] = ki18nc("gui-path-delimiter/plain", "→"); guiPathDelim[Kuit::TermText] = guiPathDelim[Kuit::PlainText]; // i18n: Decide which string is used to delimit elements in a GUI path // (e.g. -> in "Go to Settings->Advanced->Core tab.") in rich text. guiPathDelim[Kuit::RichText] = ki18nc("gui-path-delimiter/rich", "→"); // NOTE: The '→' glyph seems to be available in all widespread fonts. // Collect keyboard key names. #undef SET_KEYNAME #define SET_KEYNAME(rawname) do { \ /* Normalize key, trim and all lower-case. */ \ QString normname = QStringLiteral(rawname).trimmed().toLower(); \ keyNames[normname] = ki18nc("keyboard-key-name", rawname); \ } while (0) // Now we need I18NC_NOOP that does remove context. #undef I18NC_NOOP #define I18NC_NOOP(ctxt, msg) msg SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Alt")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "AltGr")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Backspace")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "CapsLock")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Control")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Ctrl")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Del")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Delete")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Down")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "End")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Enter")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Esc")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Escape")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Home")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Hyper")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Ins")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Insert")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Left")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Menu")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Meta")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "NumLock")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "PageDown")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "PageUp")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "PgDown")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "PgUp")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "PauseBreak")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "PrintScreen")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "PrtScr")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Return")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Right")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "ScrollLock")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Shift")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Space")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Super")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "SysReq")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Tab")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Up")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Win")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "F1")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "F2")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "F3")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "F4")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "F5")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "F6")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "F7")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "F8")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "F9")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "F10")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "F11")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "F12")); // TODO: Add rest of the key names? } QString KuitStaticData::toKeyCombo(const QStringList &languages, const QString &shstr, Kuit::VisualFormat format) { // Take '+' or '-' as input shortcut delimiter, // whichever is first encountered. static const QRegularExpression delimRx(QStringLiteral("[+-]")); const QRegularExpressionMatch match = delimRx.match(shstr); QStringList keys; if (match.hasMatch()) { // delimiter found, multi-key shortcut const QString oldDelim = match.captured(0); #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) keys = shstr.split(oldDelim, QString::SkipEmptyParts); #else keys = shstr.split(oldDelim, Qt::SkipEmptyParts); #endif } else { // single-key shortcut, no delimiter found keys.append(shstr); } for (int i = 0; i < keys.size(); ++i) { // Normalize key, trim and all lower-case. const QString nkey = keys.at(i).trimmed().toLower(); keys[i] = keyNames.contains(nkey) ? keyNames[nkey].toString(languages) : keys.at(i).trimmed(); } const QString delim = comboKeyDelim.value(format).toString(languages); return keys.join(delim); } QString KuitStaticData::toInterfacePath(const QStringList &languages, const QString &inpstr, Kuit::VisualFormat format) { // Take '/', '|' or "->" as input path delimiter, // whichever is first encountered. static const QRegularExpression delimRx(QStringLiteral("\\||->")); const QRegularExpressionMatch match = delimRx.match(inpstr); if (match.hasMatch()) { // multi-element path const QString oldDelim = match.captured(0); #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) QStringList guiels = inpstr.split(oldDelim, QString::SkipEmptyParts); #else QStringList guiels = inpstr.split(oldDelim, Qt::SkipEmptyParts); #endif const QString delim = guiPathDelim.value(format).toString(languages); return guiels.join(delim); } // single-element path, no delimiter found return inpstr; } Q_GLOBAL_STATIC(KuitStaticData, staticData) static QString attributeSetKey(const QStringList &attribNames_) { QStringList attribNames = attribNames_; std::sort(attribNames.begin(), attribNames.end()); QString key = QL1C('[') + attribNames.join(QL1C(' ')) + QL1C(']'); return key; } class KuitTag { public: QString name; Kuit::TagClass type; QSet knownAttribs; QHash > attributeOrders; QHash > patterns; QHash > formatters; int leadingNewlines; QString format(const QStringList &languages, const QHash &attributes, const QString &text, const QStringList &tagPath, Kuit::VisualFormat format) const; }; QString KuitTag::format(const QStringList &languages, const QHash &attributes, const QString &text, const QStringList &tagPath, Kuit::VisualFormat format) const { KuitStaticData *s = staticData(); QString formattedText = text; QString attribKey = attributeSetKey(attributes.keys()); const QHash pattern = patterns.value(attribKey); if (pattern.contains(format)) { QString modText; Kuit::TagFormatter formatter = formatters.value(attribKey).value(format); if (formatter != nullptr) { modText = formatter(languages, name, attributes, text, tagPath, format); } else { modText = text; } KLocalizedString aggText = pattern.value(format); // line below is first-aid fix.for e.g. . // TODO: proper handling of boolean attributes still needed aggText = aggText.relaxSubs(); if (!aggText.isEmpty()) { aggText = aggText.subs(modText); const QStringList attributeOrder = attributeOrders.value(attribKey).value(format); for (const QString &attribName : attributeOrder) { aggText = aggText.subs(attributes.value(attribName)); } formattedText = aggText.ignoreMarkup().toString(languages); } else { formattedText = modText; } } else if (patterns.contains(attribKey)) { qCWarning(KI18N_KUIT) << QStringLiteral( "Undefined visual format for tag <%1> and attribute combination %2: %3.") .arg(name, attribKey, s->namesByFormat.value(format)); } else { qCWarning(KI18N_KUIT) << QStringLiteral( "Undefined attribute combination for tag <%1>: %2.") .arg(name, attribKey); } return formattedText; } KuitSetup &Kuit::setupForDomain(const QByteArray& domain) { KuitStaticData *s = staticData(); KuitSetup *setup = s->domainSetups.value(domain); if (!setup) { setup = new KuitSetup(domain); s->domainSetups.insert(domain, setup); } return *setup; } KuitSetup &Kuit::setupForDomain(const char *domain) { return setupForDomain(QByteArray(domain)); } class KuitSetupPrivate { public: void setTagPattern(const QString &tagName, const QStringList &attribNames, Kuit::VisualFormat format, const KLocalizedString &pattern, Kuit::TagFormatter formatter, int leadingNewlines); void setTagClass(const QString &tagName, Kuit::TagClass aClass); void setFormatForMarker(const QString &marker, Kuit::VisualFormat format); void setDefaultMarkup(); void setDefaultFormats(); QByteArray domain; QHash knownTags; QHash > formatsByRoleCue; }; void KuitSetupPrivate::setTagPattern(const QString &tagName, const QStringList &attribNames_, Kuit::VisualFormat format, const KLocalizedString &pattern, Kuit::TagFormatter formatter, int leadingNewlines_) { bool isNewTag = knownTags.contains(tagName); KuitTag &tag = knownTags[tagName]; if (isNewTag) { tag.name = tagName; tag.type = Kuit::PhraseTag; } QStringList attribNames = attribNames_; attribNames.removeAll(QString()); for (const QString &attribName : qAsConst(attribNames)) { tag.knownAttribs.insert(attribName); } QString attribKey = attributeSetKey(attribNames); tag.attributeOrders[attribKey][format] = attribNames; tag.patterns[attribKey][format] = pattern; tag.formatters[attribKey][format] = formatter; tag.leadingNewlines = leadingNewlines_; } void KuitSetupPrivate::setTagClass(const QString &tagName, Kuit::TagClass aClass) { bool isNewTag = knownTags.contains(tagName); KuitTag &tag = knownTags[tagName]; if (isNewTag) { tag.name = tagName; } tag.type = aClass; } void KuitSetupPrivate::setFormatForMarker(const QString &marker, Kuit::VisualFormat format) { KuitStaticData *s = staticData(); QString roleName, cueName, formatName; parseUiMarker(marker, roleName, cueName, formatName); Kuit::Role role; if (s->rolesByName.contains(roleName)) { role = s->rolesByName.value(roleName); } else if (!roleName.isEmpty()) { qCWarning(KI18N_KUIT) << QStringLiteral( "Unknown role '@%1' in UI marker {%2}, visual format not set.") .arg(roleName, marker); return; } else { qCWarning(KI18N_KUIT) << QStringLiteral( "Empty role in UI marker {%1}, visual format not set.") .arg(marker); return; } Kuit::Cue cue; if (s->cuesByName.contains(cueName)) { cue = s->cuesByName.value(cueName); if (!s->knownRoleCues.value(role).contains(cue)) { qCWarning(KI18N_KUIT) << QStringLiteral( "Subcue ':%1' does not belong to role '@%2' in UI marker {%3}, visual format not set.") .arg(cueName, roleName, marker); return; } } else if (!cueName.isEmpty()) { qCWarning(KI18N_KUIT) << QStringLiteral( "Unknown subcue ':%1' in UI marker {%2}, visual format not set.") .arg(cueName, marker); return; } else { cue = Kuit::UndefinedCue; } formatsByRoleCue[role][cue] = format; } #define TAG_FORMATTER_ARGS \ const QStringList &languages, \ const QString &tagName, \ const QHash &attributes, \ const QString &text, \ const QStringList &tagPath, \ Kuit::VisualFormat format static QString tagFormatterFilename(TAG_FORMATTER_ARGS) { Q_UNUSED(languages); Q_UNUSED(tagName); Q_UNUSED(attributes); Q_UNUSED(tagPath); #ifdef Q_OS_WIN // with rich text the path can include ... which will be replaced by ...<\foo> on Windows! // the same problem also happens for tags such as
-> if (format == Kuit::RichText) { // replace all occurrences of "" to make sure toNativeSeparators() doesn't destroy XML markup const auto KUIT_CLOSE_XML_REPLACEMENT = QStringLiteral("__kuit_close_xml_tag__"); const auto KUIT_NOTEXT_XML_REPLACEMENT = QStringLiteral("__kuit_notext_xml_tag__"); QString result = text; result.replace(QStringLiteral(""), KUIT_NOTEXT_XML_REPLACEMENT); result = QDir::toNativeSeparators(result); result.replace(KUIT_CLOSE_XML_REPLACEMENT, QStringLiteral("")); return result; } #else Q_UNUSED(format); #endif return QDir::toNativeSeparators(text); } static QString tagFormatterShortcut(TAG_FORMATTER_ARGS) { Q_UNUSED(tagName); Q_UNUSED(attributes); Q_UNUSED(tagPath); KuitStaticData *s = staticData(); return s->toKeyCombo(languages, text, format); } static QString tagFormatterInterface(TAG_FORMATTER_ARGS) { Q_UNUSED(tagName); Q_UNUSED(attributes); Q_UNUSED(tagPath); KuitStaticData *s = staticData(); return s->toInterfacePath(languages, text, format); } void KuitSetupPrivate::setDefaultMarkup() { using namespace Kuit; const QString INTERNAL_TOP_TAG_NAME = QStringLiteral("__kuit_internal_top__"); const QString TITLE = QStringLiteral("title"); const QString EMPHASIS = QStringLiteral("emphasis"); const QString COMMAND = QStringLiteral("command"); const QString WARNING = QStringLiteral("warning"); const QString LINK = QStringLiteral("link"); const QString NOTE = QStringLiteral("note"); // Macro to hide message from extraction. #define HI18NC ki18nc // Macro to expedite setting the patterns. #undef SET_PATTERN #define SET_PATTERN(tagName, attribNames_, format, pattern, formatter, leadNl) \ do { \ QStringList attribNames; \ attribNames << attribNames_; \ setTagPattern(tagName, attribNames, format, pattern, formatter, leadNl); \ /* Make TermText pattern same as PlainText if not explicitly given. */ \ KuitTag &tag = knownTags[tagName]; \ QString attribKey = attributeSetKey(attribNames); \ if (format == PlainText && !tag.patterns[attribKey].contains(TermText)) { \ setTagPattern(tagName, attribNames, TermText, pattern, formatter, leadNl); \ } \ } while (0) // NOTE: The following "i18n:" comments are oddly placed in order that // xgettext extracts them properly. // -------> Internal top tag setTagClass(INTERNAL_TOP_TAG_NAME, StructTag); setTagClass(INTERNAL_TOP_TAG_NAME, StructTag); SET_PATTERN(INTERNAL_TOP_TAG_NAME, QString(), PlainText, HI18NC("tag-format-pattern <> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "%1"), nullptr, 0); SET_PATTERN(INTERNAL_TOP_TAG_NAME, QString(), RichText, HI18NC("tag-format-pattern <> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "%1"), nullptr, 0); // -------> Title setTagClass(TITLE, StructTag); SET_PATTERN(TITLE, QString(), PlainText, ki18nc("tag-format-pattern plain", // i18n: The messages with context "tag-format-pattern <tag ...> format" // are KUIT patterns for formatting the text found inside KUIT tags. // The format is either "plain" or "rich", and tells if the pattern // is used for plain text or rich text (which can use HTML tags). // You may be in general satisfied with the patterns as they are in the // original. Some things you may consider changing: // - the proper quotes, those used in msgid are English-standard // - the <i> and <b> tags, does your language script work well with them? "== %1 =="), nullptr, 2); SET_PATTERN(TITLE, QString(), RichText, ki18nc("tag-format-pattern <title> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<h2>%1</h2>"), nullptr, 2); // -------> Subtitle setTagClass(QSL("subtitle"), StructTag); SET_PATTERN(QSL("subtitle"), QString(), PlainText, ki18nc("tag-format-pattern <subtitle> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "~ %1 ~"), nullptr, 2); SET_PATTERN(QSL("subtitle"), QString(), RichText, ki18nc("tag-format-pattern <subtitle> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<h3>%1</h3>"), nullptr, 2); // -------> Para setTagClass(QSL("para"), StructTag); SET_PATTERN(QSL("para"), QString(), PlainText, ki18nc("tag-format-pattern <para> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "%1"), nullptr, 2); SET_PATTERN(QSL("para"), QString(), RichText, ki18nc("tag-format-pattern <para> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<p>%1</p>"), nullptr, 2); // -------> List setTagClass(QSL("list"), StructTag); SET_PATTERN(QSL("list"), QString(), PlainText, ki18nc("tag-format-pattern <list> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "%1"), nullptr, 1); SET_PATTERN(QSL("list"), QString(), RichText, ki18nc("tag-format-pattern <list> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<ul>%1</ul>"), nullptr, 1); // -------> Item setTagClass(QSL("item"), StructTag); SET_PATTERN(QSL("item"), QString(), PlainText, ki18nc("tag-format-pattern <item> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. " * %1"), nullptr, 1); SET_PATTERN(QSL("item"), QString(), RichText, ki18nc("tag-format-pattern <item> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<li>%1</li>"), nullptr, 1); // -------> Note SET_PATTERN(NOTE, QString(), PlainText, ki18nc("tag-format-pattern <note> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "Note: %1"), nullptr, 0); SET_PATTERN(NOTE, QString(), RichText, ki18nc("tag-format-pattern <note> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<i>Note</i>: %1"), nullptr, 0); SET_PATTERN(NOTE, QSL("label"), PlainText, ki18nc("tag-format-pattern <note label=> plain\n" "%1 is the text, %2 is the note label", // i18n: KUIT pattern, see the comment to the first of these entries above. "%2: %1"), nullptr, 0); SET_PATTERN(NOTE, QSL("label"), RichText, ki18nc("tag-format-pattern <note label=> rich\n" "%1 is the text, %2 is the note label", // i18n: KUIT pattern, see the comment to the first of these entries above. "<i>%2</i>: %1"), nullptr, 0); // -------> Warning SET_PATTERN(WARNING, QString(), PlainText, ki18nc("tag-format-pattern <warning> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "WARNING: %1"), nullptr, 0); SET_PATTERN(WARNING, QString(), RichText, ki18nc("tag-format-pattern <warning> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<b>Warning</b>: %1"), nullptr, 0); SET_PATTERN(WARNING, QSL("label"), PlainText, ki18nc("tag-format-pattern <warning label=> plain\n" "%1 is the text, %2 is the warning label", // i18n: KUIT pattern, see the comment to the first of these entries above. "%2: %1"), nullptr, 0); SET_PATTERN(WARNING, QSL("label"), RichText, ki18nc("tag-format-pattern <warning label=> rich\n" "%1 is the text, %2 is the warning label", // i18n: KUIT pattern, see the comment to the first of these entries above. "<b>%2</b>: %1"), nullptr, 0); // -------> Link SET_PATTERN(LINK, QString(), PlainText, ki18nc("tag-format-pattern <link> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "%1"), nullptr, 0); SET_PATTERN(LINK, QString(), RichText, ki18nc("tag-format-pattern <link> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<a href=\"%1\">%1</a>"), nullptr, 0); SET_PATTERN(LINK, QSL("url"), PlainText, ki18nc("tag-format-pattern <link url=> plain\n" "%1 is the descriptive text, %2 is the URL", // i18n: KUIT pattern, see the comment to the first of these entries above. "%1 (%2)"), nullptr, 0); SET_PATTERN(LINK, QSL("url"), RichText, ki18nc("tag-format-pattern <link url=> rich\n" "%1 is the descriptive text, %2 is the URL", // i18n: KUIT pattern, see the comment to the first of these entries above. "<a href=\"%2\">%1</a>"), nullptr, 0); // -------> Filename SET_PATTERN(QSL("filename"), QString(), PlainText, ki18nc("tag-format-pattern <filename> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "‘%1’"), tagFormatterFilename, 0); SET_PATTERN(QSL("filename"), QString(), RichText, ki18nc("tag-format-pattern <filename> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<tt>%1</tt>"), tagFormatterFilename, 0); // -------> Application SET_PATTERN(QSL("application"), QString(), PlainText, ki18nc("tag-format-pattern <application> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "%1"), nullptr, 0); SET_PATTERN(QSL("application"), QString(), RichText, ki18nc("tag-format-pattern <application> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "%1"), nullptr, 0); // -------> Command SET_PATTERN(COMMAND, QString(), PlainText, ki18nc("tag-format-pattern <command> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "%1"), nullptr, 0); SET_PATTERN(COMMAND, QString(), RichText, ki18nc("tag-format-pattern <command> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<tt>%1</tt>"), nullptr, 0); SET_PATTERN(COMMAND, QSL("section"), PlainText, ki18nc("tag-format-pattern <command section=> plain\n" "%1 is the command name, %2 is its man section", // i18n: KUIT pattern, see the comment to the first of these entries above. "%1(%2)"), nullptr, 0); SET_PATTERN(COMMAND, QSL("section"), RichText, ki18nc("tag-format-pattern <command section=> rich\n" "%1 is the command name, %2 is its man section", // i18n: KUIT pattern, see the comment to the first of these entries above. "<tt>%1(%2)</tt>"), nullptr, 0); // -------> Resource SET_PATTERN(QSL("resource"), QString(), PlainText, ki18nc("tag-format-pattern <resource> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "“%1”"), nullptr, 0); SET_PATTERN(QSL("resource"), QString(), RichText, ki18nc("tag-format-pattern <resource> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "“%1”"), nullptr, 0); // -------> Icode SET_PATTERN(QSL("icode"), QString(), PlainText, ki18nc("tag-format-pattern <icode> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "“%1”"), nullptr, 0); SET_PATTERN(QSL("icode"), QString(), RichText, ki18nc("tag-format-pattern <icode> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<tt>%1</tt>"), nullptr, 0); // -------> Bcode SET_PATTERN(QSL("bcode"), QString(), PlainText, ki18nc("tag-format-pattern <bcode> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "\n%1\n"), nullptr, 2); SET_PATTERN(QSL("bcode"), QString(), RichText, ki18nc("tag-format-pattern <bcode> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<pre>%1</pre>"), nullptr, 2); // -------> Shortcut SET_PATTERN(QSL("shortcut"), QString(), PlainText, ki18nc("tag-format-pattern <shortcut> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "%1"), tagFormatterShortcut, 0); SET_PATTERN(QSL("shortcut"), QString(), RichText, ki18nc("tag-format-pattern <shortcut> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<b>%1</b>"), tagFormatterShortcut, 0); // -------> Interface SET_PATTERN(QSL("interface"), QString(), PlainText, ki18nc("tag-format-pattern <interface> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "|%1|"), tagFormatterInterface, 0); SET_PATTERN(QSL("interface"), QString(), RichText, ki18nc("tag-format-pattern <interface> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<i>%1</i>"), tagFormatterInterface, 0); // -------> Emphasis SET_PATTERN(EMPHASIS, QString(), PlainText, ki18nc("tag-format-pattern <emphasis> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "*%1*"), nullptr, 0); SET_PATTERN(EMPHASIS, QString(), RichText, ki18nc("tag-format-pattern <emphasis> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<i>%1</i>"), nullptr, 0); SET_PATTERN(EMPHASIS, QSL("strong"), PlainText, ki18nc("tag-format-pattern <emphasis-strong> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "**%1**"), nullptr, 0); SET_PATTERN(EMPHASIS, QSL("strong"), RichText, ki18nc("tag-format-pattern <emphasis-strong> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<b>%1</b>"), nullptr, 0); // -------> Placeholder SET_PATTERN(QSL("placeholder"), QString(), PlainText, ki18nc("tag-format-pattern <placeholder> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "<%1>"), nullptr, 0); SET_PATTERN(QSL("placeholder"), QString(), RichText, ki18nc("tag-format-pattern <placeholder> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<<i>%1</i>>"), nullptr, 0); // -------> Email SET_PATTERN(QSL("email"), QString(), PlainText, ki18nc("tag-format-pattern <email> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "<%1>"), nullptr, 0); SET_PATTERN(QSL("email"), QString(), RichText, ki18nc("tag-format-pattern <email> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<<a href=\"mailto:%1\">%1</a>>"), nullptr, 0); SET_PATTERN(QSL("email"), QSL("address"), PlainText, ki18nc("tag-format-pattern <email address=> plain\n" "%1 is name, %2 is address", // i18n: KUIT pattern, see the comment to the first of these entries above. "%1 <%2>"), nullptr, 0); SET_PATTERN(QSL("email"), QSL("address"), RichText, ki18nc("tag-format-pattern <email address=> rich\n" "%1 is name, %2 is address", // i18n: KUIT pattern, see the comment to the first of these entries above. "<a href=\"mailto:%2\">%1</a>"), nullptr, 0); // -------> Envar SET_PATTERN(QSL("envar"), QString(), PlainText, ki18nc("tag-format-pattern <envar> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "$%1"), nullptr, 0); SET_PATTERN(QSL("envar"), QString(), RichText, ki18nc("tag-format-pattern <envar> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<tt>$%1</tt>"), nullptr, 0); // -------> Message SET_PATTERN(QSL("message"), QString(), PlainText, ki18nc("tag-format-pattern <message> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "/%1/"), nullptr, 0); SET_PATTERN(QSL("message"), QString(), RichText, ki18nc("tag-format-pattern <message> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<i>%1</i>"), nullptr, 0); // -------> Nl SET_PATTERN(QSL("nl"), QString(), PlainText, ki18nc("tag-format-pattern <nl> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "%1\n"), nullptr, 0); SET_PATTERN(QSL("nl"), QString(), RichText, ki18nc("tag-format-pattern <nl> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "%1<br/>"), nullptr, 0); } void KuitSetupPrivate::setDefaultFormats() { using namespace Kuit; // Setup formats by role. formatsByRoleCue[ActionRole][UndefinedCue] = PlainText; formatsByRoleCue[TitleRole][UndefinedCue] = PlainText; formatsByRoleCue[LabelRole][UndefinedCue] = PlainText; formatsByRoleCue[OptionRole][UndefinedCue] = PlainText; formatsByRoleCue[ItemRole][UndefinedCue] = PlainText; formatsByRoleCue[InfoRole][UndefinedCue] = RichText; // Setup override formats by subcue. formatsByRoleCue[InfoRole][StatusCue] = PlainText; formatsByRoleCue[InfoRole][ProgressCue] = PlainText; formatsByRoleCue[InfoRole][CreditCue] = PlainText; formatsByRoleCue[InfoRole][ShellCue] = TermText; } KuitSetup::KuitSetup(const QByteArray &domain) : d(new KuitSetupPrivate) { d->domain = domain; d->setDefaultMarkup(); d->setDefaultFormats(); } KuitSetup::~KuitSetup() { delete d; } void KuitSetup::setTagPattern(const QString &tagName, const QStringList &attribNames, Kuit::VisualFormat format, const KLocalizedString &pattern, Kuit::TagFormatter formatter, int leadingNewlines) { d->setTagPattern(tagName, attribNames, format, pattern, formatter, leadingNewlines); } void KuitSetup::setTagClass(const QString &tagName, Kuit::TagClass aClass) { d->setTagClass(tagName, aClass); } void KuitSetup::setFormatForMarker(const QString &marker, Kuit::VisualFormat format) { d->setFormatForMarker(marker, format); } class KuitFormatterPrivate { public: KuitFormatterPrivate(const QString &language); QString format(const QByteArray &domain, const QString &context, const QString &text, Kuit::VisualFormat format) const; // Get metatranslation (formatting patterns, etc.) QString metaTr(const char *context, const char *text) const; // Set visual formatting patterns for text within tags. void setFormattingPatterns(); // Set data used in transformation of text within tags. void setTextTransformData(); // Determine visual format by parsing the UI marker in the context. static Kuit::VisualFormat formatFromUiMarker(const QString &context, const KuitSetup &setup); // Determine if text has block structure (multiple paragraphs, etc). static bool determineIsStructured(const QString &text, const KuitSetup &setup); // Format KUIT text into visual text. QString toVisualText(const QString &text, Kuit::VisualFormat format, const KuitSetup &setup) const; // Final touches to the formatted text. QString finalizeVisualText(const QString &ftext, Kuit::VisualFormat format) const; // In case of markup errors, try to make result not look too bad. QString salvageMarkup(const QString &text, Kuit::VisualFormat format, const KuitSetup &setup) const; // Data for XML parsing state. class OpenEl { public: enum Handling { Proper, Ignored, Dropout }; KuitTag tag; QString name; QHash<QString, QString> attributes; QString attribStr; Handling handling; QString formattedText; QStringList tagPath; }; // Gather data about current element for the parse state. KuitFormatterPrivate::OpenEl parseOpenEl(const QXmlStreamReader &xml, const OpenEl &enclosingOel, const QString &text, const KuitSetup &setup) const; // Format text of the element. QString formatSubText(const QString &ptext, const OpenEl &oel, Kuit::VisualFormat format, const KuitSetup &setup) const; // Count number of newlines at start and at end of text. static void countWrappingNewlines(const QString &ptext, int &numle, int &numtr); private: QString language; QStringList languageAsList; QHash<Kuit::VisualFormat, QString> comboKeyDelim; QHash<Kuit::VisualFormat, QString> guiPathDelim; QHash<QString, QString> keyNames; }; KuitFormatterPrivate::KuitFormatterPrivate(const QString &language_) : language(language_) { } QString KuitFormatterPrivate::format(const QByteArray &domain, const QString &context, const QString &text, Kuit::VisualFormat format) const { const KuitSetup &setup = Kuit::setupForDomain(domain); // If format is undefined, determine it based on UI marker inside context. Kuit::VisualFormat resolvedFormat = format; if (resolvedFormat == Kuit::UndefinedFormat) { resolvedFormat = formatFromUiMarker(context, setup); } // Quick check: are there any tags at all? QString ftext; if (text.indexOf(QL1C('<')) < 0) { ftext = finalizeVisualText(text, resolvedFormat); } else { // Format the text. ftext = toVisualText(text, resolvedFormat, setup); if (ftext.isEmpty()) { // error while processing markup ftext = salvageMarkup(text, resolvedFormat, setup); } } return ftext; } Kuit::VisualFormat KuitFormatterPrivate::formatFromUiMarker(const QString &context, const KuitSetup &setup) { KuitStaticData *s = staticData(); QString roleName, cueName, formatName; parseUiMarker(context, roleName, cueName, formatName); // Set role from name. Kuit::Role role = s->rolesByName.value(roleName, Kuit::UndefinedRole); if (role == Kuit::UndefinedRole) { // unknown role if (!roleName.isEmpty()) { qCWarning(KI18N_KUIT) << QStringLiteral( "Unknown role '@%1' in UI marker in context {%2}.") .arg(roleName, shorten(context)); } } // Set subcue from name. Kuit::Cue cue; if (role != Kuit::UndefinedRole) { cue = s->cuesByName.value(cueName, Kuit::UndefinedCue); if (cue != Kuit::UndefinedCue) { // known subcue if (!s->knownRoleCues.value(role).contains(cue)) { cue = Kuit::UndefinedCue; qCWarning(KI18N_KUIT) << QStringLiteral( "Subcue ':%1' does not belong to role '@%2' in UI marker in context {%3}.") .arg(cueName, roleName, shorten(context)); } } else { // unknown or not given subcue if (!cueName.isEmpty()) { qCWarning(KI18N_KUIT) << QStringLiteral( "Unknown subcue ':%1' in UI marker in context {%2}.") .arg(cueName, shorten(context)); } } } else { // Bad role, silently ignore the cue. cue = Kuit::UndefinedCue; } // Set format from name, or by derivation from contex/subcue. Kuit::VisualFormat format = s->formatsByName.value(formatName, Kuit::UndefinedFormat); if (format == Kuit::UndefinedFormat) { // unknown or not given format // Check first if there is a format defined for role/subcue // combination, then for role only, otherwise default to undefined. if (setup.d->formatsByRoleCue.contains(role)) { if (setup.d->formatsByRoleCue.value(role).contains(cue)) { format = setup.d->formatsByRoleCue.value(role).value(cue); } else { format = setup.d->formatsByRoleCue.value(role).value(Kuit::UndefinedCue); } } if (!formatName.isEmpty()) { qCWarning(KI18N_KUIT) << QStringLiteral( "Unknown format '/%1' in UI marker for message {%2}.") .arg(formatName, shorten(context)); } } if (format == Kuit::UndefinedFormat) { format = Kuit::PlainText; } return format; } bool KuitFormatterPrivate::determineIsStructured(const QString &text, const KuitSetup &setup) { // If the text opens with a structuring tag, then it is structured, // otherwise not. Leading whitespace is ignored for this purpose. static const QRegularExpression opensWithTagRx(QStringLiteral("^\\s*<\\s*(\\w+)[^>]*>")); bool isStructured = false; const QRegularExpressionMatch match = opensWithTagRx.match(text); if (match.hasMatch()) { const QString tagName = match.captured(1).toLower(); if (setup.d->knownTags.contains(tagName)) { const KuitTag &tag = setup.d->knownTags.value(tagName); isStructured = (tag.type == Kuit::StructTag); } } return isStructured; } static const char s_entitySubRx[] = "[a-z]+|#[0-9]+|#x[0-9a-fA-F]+"; QString KuitFormatterPrivate::toVisualText(const QString &text_, Kuit::VisualFormat format, const KuitSetup &setup) const { KuitStaticData *s = staticData(); // Replace &-shortcut marker with "&", not to confuse the parser; // but do not touch & which forms an XML entity as it is. QString original = text_; // Regex is (see s_entitySubRx var): ^([a-z]+|#[0-9]+|#x[0-9a-fA-F]+); static const QRegularExpression restRx(QLatin1String("^(") + QLatin1String(s_entitySubRx) + QLatin1String(");")); QString text; int p = original.indexOf(QL1C('&')); while (p >= 0) { text.append(original.midRef(0, p + 1)); original.remove(0, p + 1); if (original.indexOf(restRx) != 0) { // not an entity text.append(QSL("amp;")); } p = original.indexOf(QL1C('&')); } text.append(original); // FIXME: Do this and then check proper use of structuring and phrase tags. #if 0 // Determine whether this is block-structured text. bool isStructured = determineIsStructured(text, setup); #endif const QString INTERNAL_TOP_TAG_NAME = QStringLiteral("__kuit_internal_top__"); // Add top tag, not to confuse the parser. text = QStringLiteral("<%2>%1</%2>").arg(text, INTERNAL_TOP_TAG_NAME); QStack<OpenEl> openEls; QXmlStreamReader xml(text); xml.setEntityResolver(&s->xmlEntityResolver); QStringRef lastElementName; while (!xml.atEnd()) { xml.readNext(); if (xml.isStartElement()) { lastElementName = xml.name(); // Find first proper enclosing element. OpenEl enclosingOel; for (int i = openEls.size() - 1; i >= 0; --i) { if (openEls[i].handling == OpenEl::Proper) { enclosingOel = openEls[i]; break; } } // Collect data about this element. OpenEl oel = parseOpenEl(xml, enclosingOel, text, setup); // Record the new element on the parse stack. openEls.push(oel); } else if (xml.isEndElement()) { // Get closed element data. OpenEl oel = openEls.pop(); // If this was closing of the top element, we're done. if (openEls.isEmpty()) { // Return with final touches applied. return finalizeVisualText(oel.formattedText, format); } // Append formatted text segment. QString ptext = openEls.top().formattedText; // preceding text openEls.top().formattedText += formatSubText(ptext, oel, format, setup); } else if (xml.isCharacters()) { // Stream reader will automatically resolve default XML entities, // which is not desired in this case, as the entities are to be // resolved in finalizeVisualText. Convert back into entities. const QString ctext = xml.text().toString(); QString nctext; for (const QChar c : ctext) { if (s->xmlEntitiesInverse.contains(c)) { const QString entName = s->xmlEntitiesInverse[c]; nctext += QL1C('&') + entName + QL1C(';'); } else { nctext += c; } } openEls.top().formattedText += nctext; } } if (xml.hasError()) { qCWarning(KI18N_KUIT) << QStringLiteral( "Markup error in message {%1}: %2. Last tag parsed: %3. Complete message follows:\n%4") .arg(shorten(text), xml.errorString(), lastElementName.toString(), text); return QString(); } // Cannot reach here. return text; } KuitFormatterPrivate::OpenEl KuitFormatterPrivate::parseOpenEl(const QXmlStreamReader &xml, const OpenEl &enclosingOel, const QString &text, const KuitSetup &setup) const { OpenEl oel; oel.name = xml.name().toString().toLower(); // Collect attribute names and values, and format attribute string. QStringList attribNames, attribValues; const auto listAttributes = xml.attributes(); for (const QXmlStreamAttribute &xatt : listAttributes) { attribNames += xatt.name().toString().toLower(); attribValues += xatt.value().toString(); QChar qc = attribValues.last().indexOf(QL1C('\'')) < 0 ? QL1C('\'') : QL1C('"'); oel.attribStr += QL1C(' ') + attribNames.last() + QL1C('=') + qc + attribValues.last() + qc; } if (setup.d->knownTags.contains(oel.name)) { // known KUIT element const KuitTag &tag = setup.d->knownTags.value(oel.name); const KuitTag &etag = setup.d->knownTags.value(enclosingOel.name); // If this element can be contained within enclosing element, // mark it proper, otherwise mark it for removal. if (tag.name.isEmpty() || tag.type == Kuit::PhraseTag || etag.type == Kuit::StructTag) { oel.handling = OpenEl::Proper; } else { oel.handling = OpenEl::Dropout; qCWarning(KI18N_KUIT) << QStringLiteral( "Structuring tag ('%1') cannot be subtag of phrase tag ('%2') in message {%3}.") .arg(tag.name, etag.name, shorten(text)); } // Resolve attributes and compute attribute set key. QSet<QString> attset; for (int i = 0; i < attribNames.size(); ++i) { QString att = attribNames[i]; if (tag.knownAttribs.contains(att)) { attset << att; oel.attributes[att] = attribValues[i]; } else { qCWarning(KI18N_KUIT) << QStringLiteral( "Attribute '%1' not defined for tag '%2' in message {%3}.") .arg(att, tag.name, shorten(text)); } } // Continue tag path. oel.tagPath = enclosingOel.tagPath; oel.tagPath.prepend(enclosingOel.name); } else { // unknown element, leave it in verbatim oel.handling = OpenEl::Ignored; qCWarning(KI18N_KUIT) << QStringLiteral( "Tag '%1' is not defined in message {%2}.") .arg(oel.name, shorten(text)); } return oel; } QString KuitFormatterPrivate::formatSubText(const QString &ptext, const OpenEl &oel, Kuit::VisualFormat format, const KuitSetup &setup) const { if (oel.handling == OpenEl::Proper) { const KuitTag &tag = setup.d->knownTags.value(oel.name); QString ftext = tag.format(languageAsList, oel.attributes, oel.formattedText, oel.tagPath, format); // Handle leading newlines, if this is not start of the text // (ptext is the preceding text). if (!ptext.isEmpty() && tag.leadingNewlines > 0) { // Count number of present newlines. int pnumle, pnumtr, fnumle, fnumtr; countWrappingNewlines(ptext, pnumle, pnumtr); countWrappingNewlines(ftext, fnumle, fnumtr); // Number of leading newlines already present. int numle = pnumtr + fnumle; // The required extra newlines. QString strle; if (numle < tag.leadingNewlines) { strle = QString(tag.leadingNewlines - numle, QL1C('\n')); } ftext = strle + ftext; } return ftext; } else if (oel.handling == OpenEl::Ignored) { return QL1C('<') + oel.name + oel.attribStr + QL1C('>') + oel.formattedText + QSL("</") + oel.name + QL1C('>'); } else { // oel.handling == OpenEl::Dropout return oel.formattedText; } } void KuitFormatterPrivate::countWrappingNewlines(const QString &text, int &numle, int &numtr) { int len = text.length(); // Number of newlines at start of text. numle = 0; while (numle < len && text[numle] == QL1C('\n')) { ++numle; } // Number of newlines at end of text. numtr = 0; while (numtr < len && text[len - numtr - 1] == QL1C('\n')) { ++numtr; } } QString KuitFormatterPrivate::finalizeVisualText(const QString &text_, Kuit::VisualFormat format) const { KuitStaticData *s = staticData(); QString text = text_; // Resolve XML entities. if (format != Kuit::RichText) { // regex is (see s_entitySubRx var): (&([a-z]+|#[0-9]+|#x[0-9a-fA-F]+);) static const QRegularExpression entRx(QLatin1String("(&(") + QLatin1String(s_entitySubRx) + QLatin1String(");)")); QRegularExpressionMatch match; QString plain; while ((match = entRx.match(text)).hasMatch()) { const QString ent = match.captured(2); plain.append(text.midRef(0, match.capturedStart(0))); text.remove(0, match.capturedEnd(0)); if (ent.startsWith(QL1C('#'))) { // numeric character entity bool ok; const QChar c = ent.at(1) == QL1C('x') ? QChar(ent.midRef(2).toInt(&ok, 16)) : QChar(ent.midRef(1).toInt(&ok, 10)); if (ok) { plain.append(c); } else { // unknown Unicode point, leave as is plain.append(match.captured(0)); } } else if (s->xmlEntities.contains(ent)) { // known entity plain.append(s->xmlEntities[ent]); } else { // unknown entity, just leave as is plain.append(match.captured(0)); } } plain.append(text); text = plain; } // Add top tag. if (format == Kuit::RichText) { text = QLatin1String("<html>") + text + QLatin1String("</html>"); } return text; } QString KuitFormatterPrivate::salvageMarkup(const QString &text_, Kuit::VisualFormat format, const KuitSetup &setup) const { QString text = text_; QString ntext; // Resolve tags simple-mindedly. // - tags with content static const QRegularExpression wrapRx(QStringLiteral("(<\\s*(\\w+)\\b([^>]*)>)(.*)(<\\s*/\\s*\\2\\s*>)"), QRegularExpression::InvertedGreedinessOption); QRegularExpressionMatchIterator iter = wrapRx.globalMatch(text); QRegularExpressionMatch match; int pos = 0; while (iter.hasNext()) { match = iter.next(); ntext += text.midRef(pos, match.capturedStart(0) - pos); const QString tagname = match.captured(2).toLower(); const QString content = salvageMarkup(match.captured(4), format, setup); if (setup.d->knownTags.contains(tagname)) { const KuitTag &tag = setup.d->knownTags.value(tagname); QHash<QString, QString> attributes; // TODO: Do not ignore attributes (in match.captured(3)). ntext += tag.format(languageAsList, attributes, content, QStringList(), format); } else { ntext += match.captured(1) + content + match.captured(5); } pos = match.capturedEnd(0); } // get the remaining part after the last match in "text" ntext += text.midRef(pos); text = ntext; // - tags without content static const QRegularExpression nowrRx(QStringLiteral("<\\s*(\\w+)\\b([^>]*)/\\s*>"), QRegularExpression::InvertedGreedinessOption); iter = nowrRx.globalMatch(text); pos = 0; ntext.clear(); while (iter.hasNext()) { match = iter.next(); ntext += text.midRef(pos, match.capturedStart(0) - pos); const QString tagname = match.captured(1).toLower(); if (setup.d->knownTags.contains(tagname)) { const KuitTag &tag = setup.d->knownTags.value(tagname); ntext += tag.format(languageAsList, QHash<QString, QString>(), QString(), QStringList(), format); } else { ntext += match.captured(0); } pos = match.capturedEnd(0); } // get the remaining part after the last match in "text" ntext += text.midRef(pos); text = ntext; // Add top tag. if (format == Kuit::RichText) { text = QStringLiteral("<html>") + text + QStringLiteral("</html>"); } return text; } KuitFormatter::KuitFormatter(const QString &language) : d(new KuitFormatterPrivate(language)) { } KuitFormatter::~KuitFormatter() { delete d; } QString KuitFormatter::format(const QByteArray &domain, const QString &context, const QString &text, Kuit::VisualFormat format) const { return d->format(domain, context, text, format); } diff --git a/src/kuitmarkup.h b/src/kuitmarkup.h index e838356..b9225e2 100644 --- a/src/kuitmarkup.h +++ b/src/kuitmarkup.h @@ -1,201 +1,188 @@ /* This file is part of the KDE libraries - Copyright (C) 2013 Chusslove Illich <caslav.ilic@gmx.net> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library 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 - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-FileCopyrightText: 2013 Chusslove Illich <caslav.ilic@gmx.net> + + SPDX-License-Identifier: LGPL-2.0-or-later */ #ifndef KUITMARKUP_H #define KUITMARKUP_H #include <ki18n_export.h> #include <QString> #include <QStringList> #include <QHash> class KuitSetup; /** * Global constants and functions related to KUIT markup. */ namespace Kuit { /** * Visual formats into which KUIT markup can be resolved. */ enum VisualFormat { /** * Visual format not defined. * This value can be explicitly set * (e.g. through \c KLocalizedString::withFormat) * to indicate that the format should be decided * by another mechanism (e.g. context UI marker). */ UndefinedFormat = 0, /** * Plain text. */ PlainText = 10, /** * Qt rich text (HTML subset). */ RichText = 20, /** * Terminal escape sequences. */ TermText = 30 }; /** * Classification of KUIT tags. */ enum TagClass { /** * Tags wrapping text inserted into running text. */ PhraseTag = 0, /** * Tags spliting text into paragraph-level blocks. */ StructTag = 1 }; /** * Functions accepted by tag formatting functions. * * \param languages the target languages (by decreasing priority) * \param tagName the wrapping tag name * \param attributes the attribute name-value pairs in the tag * \param text the wrapped text * \param tagPath the ordered list of ancestor tag names, parent first * \param format the target visual format * \return formatted text */ typedef QString(*TagFormatter)(const QStringList &languages, const QString &tagName, const QHash<QString, QString> &attributes, const QString &text, const QStringList &tagPath, Kuit::VisualFormat format); /** * Get hold of the KUIT setup object for a given domain. * * \param domain the translation domain * \return pointer to KUIT setup object */ KI18N_EXPORT KuitSetup &setupForDomain(const char *domain); // KF6 TODO: remove, the QByteArray overload is enough KI18N_EXPORT KuitSetup &setupForDomain(const QByteArray& domain); } class KLocalizedString; class KuitSetupPrivate; class KuitFormatterPrivate; /** * @class KuitSetup kuitmarkup.h <KuitSetup> * * Class for modifying KUIT markup in a given domain. * * Not directly constructed, but obtained through \c Kuit::setupForDomain. */ class KI18N_EXPORT KuitSetup { friend KuitSetup &Kuit::setupForDomain(const QByteArray& domain); friend class KuitFormatterPrivate; public: /** * Destructor. */ ~KuitSetup(); /** * Set the formatting string for a tag with attributes combination. * * If a new tag name is given, this effectively defines a new tag. * The same holds for attribute names. * * The pattern string \p pattern should contain placeholders * for inserting the text and the attribute values. * %1 will be replaced with the wrapped text, and %2 and upwards * with attribute values in the order given by \p attrNames. * Non markup-aware translation call with context (\c ki18nc) * should be used to create the pattern string. * * In addition to the pattern, a formatting function * of the type \c TagFormatter can be given. * This function receives the full markup parsing context, * so that it can do whatever is necessary with the wrapped text. * The result of this function is then substituted into the pattern. * You can also give an empty pattern (as <tt>KLocalizedString()</tt>) * together with the formatting function, in which case the function * is assumed to do everything and no substitution is performed. * * \param tagName the name of the tag * \param attribNames the names of the attributes (empty names are ignored) * \param format the target visual format * \param pattern the pattern string * \param leadingNewlines the number of new lines (\\n) to be maintained * between any preceding text and the text wrapped * with this tag (for formats where it matters) */ void setTagPattern(const QString &tagName, const QStringList &attribNames, Kuit::VisualFormat format, const KLocalizedString &pattern, Kuit::TagFormatter formatter = nullptr, int leadingNewlines = 0); /** * Set the KUIT class of the tag. * * \param tagName the name of the tag * \param aClass the KUIT tag class */ void setTagClass(const QString &tagName, Kuit::TagClass aClass); /** * Set the default visual format for a given UI marker. * * Giving <tt>"@<major>"</tt> for \p marker means to set the format * only for standalone <tt>\@\<major\></tt> marker, * while <tt>"@<major>:"</tt> (with trailing colon) means to set * the same format for all <tt>\@\<major\>:\<minor\></tt> combinations. * * Defined UI marker major/minor combinations are listed in the section * \ref uimark_ctxt. If an UI marker combination outside of the defined * is given as \p marker, it will be ignored. * * Setting \c Kuit::UndefinedFormat as \p format * means to fall back to default format for the given UI marker. * * \param marker the UI marker * \param format the visual format */ void setFormatForMarker(const QString &marker, Kuit::VisualFormat format); private: KuitSetup(const QByteArray &domain); Q_DISABLE_COPY(KuitSetup) KuitSetupPrivate *const d; }; #endif // KUITMARKUP_H diff --git a/src/kuitmarkup_p.h b/src/kuitmarkup_p.h index ca07ee1..dd4571a 100644 --- a/src/kuitmarkup_p.h +++ b/src/kuitmarkup_p.h @@ -1,86 +1,73 @@ /* This file is part of the KDE libraries - Copyright (C) 2007, 2013 Chusslove Illich <caslav.ilic@gmx.net> + SPDX-FileCopyrightText: 2007, 2013 Chusslove Illich <caslav.ilic@gmx.net> - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library 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 - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-License-Identifier: LGPL-2.0-or-later */ #ifndef KUITMARKUP_P_H #define KUITMARKUP_P_H #include <QString> class KuitFormatter; class KuitFormatterPrivate; namespace Kuit { /** * Convert &, ", ', <, > characters into XML entities * &, <, >, ', ", respectively. */ QString escape(const QString &text); } /** * @internal * (used by KLocalizedString) * * KuitFormatter resolves KUIT markup in user interface text * into appropriate visual formatting. * * @author Chusslove Illich <caslav.ilic@gmx.net> * @short class for formatting KUIT markup in UI messages */ class KuitFormatter { public: /** * Constructor. * * @param language language to create the formatter for */ KuitFormatter(const QString &language); /** * Transforms KUIT markup in the given text into visual formatting. * The appropriate visual formatting is decided based on * the context marker provided in the context string. * * @param domain translation domain from which the text was fetched * @param context context of the text (used if \p format == UndefinedFormat) * @param text text containing the KUIT markup * @param format target visual format * @param isArgument whether this text is inserted into an outer text */ QString format(const QByteArray &domain, const QString &context, const QString &text, Kuit::VisualFormat format) const; /** * Destructor. */ ~KuitFormatter(); private: KuitFormatter(const KuitFormatter &t); KuitFormatter &operator=(const KuitFormatter &t); KuitFormatterPrivate *d; }; #endif diff --git a/src/kuitsetup.h b/src/kuitsetup.h index 58f543d..f0a5d77 100644 --- a/src/kuitsetup.h +++ b/src/kuitsetup.h @@ -1,25 +1,12 @@ /* This file is part of the KDE libraries - Copyright (C) 2013 Chusslove Illich <caslav.ilic@gmx.net> + SPDX-FileCopyrightText: 2013 Chusslove Illich <caslav.ilic@gmx.net> - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library 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 - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-License-Identifier: LGPL-2.0-or-later */ #ifndef KUITSETUP_H #define KUITSETUP_H #include <kuitmarkup.h> #endif // KUITSETUP_H diff --git a/src/main.cpp b/src/main.cpp index 6b46f41..980b0a5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,53 +1,40 @@ /* This file is part of the KDE libraries - Copyright (C) 2015 Lukáš Tinkl <ltinkl@redhat.com> + SPDX-FileCopyrightText: 2015 Lukáš Tinkl <ltinkl@redhat.com> - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library 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 - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. + SPDX-License-Identifier: LGPL-2.0-or-later */ #include <QCoreApplication> #include <QLocale> #include <QLibraryInfo> #include <QTranslator> // load global Qt translation, needed in KDE e.g. by lots of builtin dialogs (QColorDialog, QFontDialog) that we use static bool loadTranslation(const QString &localeName) { QTranslator *translator = new QTranslator(QCoreApplication::instance()); if (!translator->load(QLocale(localeName), QStringLiteral("qt_"), QString(), QLibraryInfo::location(QLibraryInfo::TranslationsPath))) { delete translator; return false; } QCoreApplication::instance()->installTranslator(translator); return true; } static void load() { // The way Qt translation system handles plural forms makes it necessary to // have a translation file which contains only plural forms for `en`. That's // why we load the `en` translation unconditionally, then load the // translation for the current locale to overload it. loadTranslation(QStringLiteral("en")); QLocale locale = QLocale::system(); if (locale.name() != QStringLiteral("en")) { if (!loadTranslation(locale.name())) { loadTranslation(locale.bcp47Name()); } } } Q_COREAPP_STARTUP_FUNCTION(load)