├── .circleci └── config.yml ├── .coveragerc ├── .gitattributes ├── .github └── workflows │ ├── ci.yml │ └── codeql-analysis.yml ├── .gitignore ├── LICENSE ├── MANIFEST.in ├── Makefile ├── PULL_REQUEST_TEMPLATE.md ├── README.rst ├── bin ├── __init__.py └── create_files.py ├── docker-compose.yml ├── docker └── Dockerfile ├── docs ├── Changelog.rst ├── Makefile ├── conf.py ├── contributing.rst ├── getting-started.rst ├── images │ ├── openformats-testbed-error.gif │ └── openformats-testbed-screencast.gif ├── index.rst ├── introduction.rst ├── testbed.rst ├── testing.rst └── utils.rst ├── manage.py ├── openformats ├── __init__.py ├── _version.py ├── exceptions.py ├── formats │ ├── __init__.py │ ├── android.py │ ├── android_unescaped.py │ ├── beta_android.py │ ├── customizable_xml.py │ ├── docx.py │ ├── github_markdown.py │ ├── github_markdown_v2.py │ ├── indesign.py │ ├── json.py │ ├── office_open_xml │ │ ├── __init__.py │ │ └── parser.py │ ├── plaintext.py │ ├── po.py │ ├── pptx.py │ ├── srt.py │ ├── stringsdict.py │ ├── vtt.py │ ├── xlsx_unstructured.py │ └── yaml │ │ ├── __init__.py │ │ ├── constants.py │ │ ├── utils.py │ │ ├── yaml.py │ │ ├── yaml_i18n.py │ │ ├── yaml_representee_classes.py │ │ └── yaml_representers.py ├── handlers.py ├── strings.py ├── tests │ ├── __init__.py │ ├── formats │ │ ├── __init__.py │ │ ├── android │ │ │ ├── __init__.py │ │ │ ├── files │ │ │ │ ├── 1_el.xml │ │ │ │ ├── 1_en.xml │ │ │ │ └── 1_tpl.xml │ │ │ ├── test_android.py │ │ │ └── test_android_unescaped.py │ │ ├── arb │ │ │ ├── __init__.py │ │ │ ├── files │ │ │ │ ├── 1_el.arb │ │ │ │ ├── 1_en.arb │ │ │ │ ├── 1_en_exported.arb │ │ │ │ └── 1_tpl.arb │ │ │ └── test_arb.py │ │ ├── beta_android │ │ │ ├── __init__.py │ │ │ ├── files │ │ │ │ ├── 1_el.xml │ │ │ │ ├── 1_en.xml │ │ │ │ └── 1_tpl.xml │ │ │ └── test_android.py │ │ ├── chrome_i18n │ │ │ ├── __init__.py │ │ │ ├── files │ │ │ │ ├── 1_el.json │ │ │ │ ├── 1_en.json │ │ │ │ └── 1_tpl.json │ │ │ └── test_chrome_i18n.py │ │ ├── chromev3 │ │ │ ├── __init__.py │ │ │ ├── files │ │ │ │ ├── 1_el.json │ │ │ │ ├── 1_en.json │ │ │ │ └── 1_tpl.json │ │ │ └── test_chrome_v3.py │ │ ├── common │ │ │ └── __init__.py │ │ ├── customizable_xml │ │ │ ├── __init__.py │ │ │ ├── files │ │ │ │ ├── 1_el.xml │ │ │ │ ├── 1_en.xml │ │ │ │ └── 1_tpl.xml │ │ │ └── test_customizable_xml.py │ │ ├── docx │ │ │ ├── __init__.py │ │ │ ├── files │ │ │ │ ├── complex.docx │ │ │ │ ├── hello_world.docx │ │ │ │ ├── hello_world_no_ppr.docx │ │ │ │ ├── missing_wr_parent.docx │ │ │ │ ├── special_cases.docx │ │ │ │ ├── special_cases_2.docx │ │ │ │ ├── special_cases_3.docx │ │ │ │ ├── test_no_parent.docx │ │ │ │ ├── two_text_elements.docx │ │ │ │ ├── with_ampersand.docx │ │ │ │ └── with_lt.docx │ │ │ └── test_docx.py │ │ ├── github_markdown │ │ │ ├── __init__.py │ │ │ ├── files │ │ │ │ ├── 1_el.md │ │ │ │ ├── 1_en.md │ │ │ │ └── 1_tpl.md │ │ │ └── test_github_markdown.py │ │ ├── github_markdown_v2 │ │ │ ├── __init__.py │ │ │ ├── files │ │ │ │ ├── 1_el.md │ │ │ │ ├── 1_en.md │ │ │ │ ├── 1_en_export.md │ │ │ │ └── 1_tpl.md │ │ │ └── test_github_markdown.py │ │ ├── indesign │ │ │ ├── __init__.py │ │ │ ├── files │ │ │ │ └── sample.idml │ │ │ └── test_indesign.py │ │ ├── keyvaluejson │ │ │ ├── __init__.py │ │ │ ├── files │ │ │ │ ├── 1_el.json │ │ │ │ ├── 1_en.json │ │ │ │ └── 1_tpl.json │ │ │ └── test_keyvaluejson.py │ │ ├── plaintext │ │ │ ├── __init__.py │ │ │ ├── files │ │ │ │ ├── 1_el.txt │ │ │ │ ├── 1_en.txt │ │ │ │ └── 1_tpl.txt │ │ │ └── test_plaintext.py │ │ ├── po │ │ │ ├── __init__.py │ │ │ ├── files │ │ │ │ ├── 1_el.po │ │ │ │ ├── 1_en.po │ │ │ │ └── 1_tpl.po │ │ │ └── test_po.py │ │ ├── pptx │ │ │ ├── __init__.py │ │ │ ├── files │ │ │ │ ├── autofield.pptx │ │ │ │ ├── complex.pptx │ │ │ │ ├── graphicFrame_with_text.pptx │ │ │ │ ├── hello_world.pptx │ │ │ │ ├── multi.pptx │ │ │ │ ├── multi_with_notes.pptx │ │ │ │ ├── notes.pptx │ │ │ │ └── rtl.pptx │ │ │ └── test_pptx.py │ │ ├── srt │ │ │ ├── __init__.py │ │ │ ├── files │ │ │ │ ├── 1_el.srt │ │ │ │ ├── 1_en.srt │ │ │ │ └── 1_tpl.srt │ │ │ └── test_srt.py │ │ ├── stringsdict │ │ │ ├── __init__.py │ │ │ ├── files │ │ │ │ ├── 1_el.stringsdict │ │ │ │ ├── 1_en.stringsdict │ │ │ │ └── 1_tpl.stringsdict │ │ │ └── test_stringsdict.py │ │ ├── structuredkeyvaluejson │ │ │ ├── __init__.py │ │ │ ├── files │ │ │ │ ├── 1_el.json │ │ │ │ ├── 1_en.json │ │ │ │ └── 1_tpl.json │ │ │ └── test_keyvaluejson.py │ │ ├── vtt │ │ │ ├── __init__.py │ │ │ ├── files │ │ │ │ ├── 1_el.vtt │ │ │ │ ├── 1_en.vtt │ │ │ │ └── 1_tpl.vtt │ │ │ └── test_vtt.py │ │ ├── xlsx_unstructured │ │ │ ├── __init__.py │ │ │ ├── files │ │ │ │ └── example.xlsx │ │ │ └── test_xlsx_unstructured.py │ │ ├── yaml │ │ │ ├── __init__.py │ │ │ ├── files │ │ │ │ ├── 1_el.yml │ │ │ │ ├── 1_en.yml │ │ │ │ ├── 1_en_exported.yml │ │ │ │ ├── 1_en_exported_without_template.yml │ │ │ │ └── 1_tpl.yml │ │ │ ├── test_utils.py │ │ │ └── test_yaml.py │ │ └── yamlinternationalization │ │ │ ├── __init__.py │ │ │ ├── files │ │ │ ├── 1_el.yml │ │ │ ├── 1_en.yml │ │ │ ├── 1_en_exported.yml │ │ │ ├── 1_en_exported_without_template.yml │ │ │ └── 1_tpl.yml │ │ │ └── test_i18n_yaml.py │ ├── run_tests.py │ ├── test_handlers.py │ ├── test_strings.py │ ├── util_tests │ │ ├── __init__.py │ │ ├── test_icu.py │ │ ├── test_json.py │ │ └── test_xml.py │ └── utils │ │ ├── __init__.py │ │ ├── dictionary.csv │ │ ├── dictionary.py │ │ └── strings.py ├── transcribers.py └── utils │ ├── __init__.py │ ├── compat.py │ ├── compilers.py │ ├── icu.py │ ├── json.py │ ├── newlines.py │ ├── xml.py │ └── xmlutils.py ├── requirements.txt ├── setup.cfg ├── setup.py ├── testbed ├── __init__.py ├── main │ ├── __init__.py │ ├── models.py │ ├── urls.py │ └── views.py ├── settings.py ├── static │ ├── css │ │ └── home.css │ ├── js │ │ ├── globals.js │ │ ├── main.js │ │ ├── namespace.js │ │ └── views.js │ └── libraries │ │ ├── css │ │ ├── bootstrap.css │ │ └── bootstrap.css.map │ │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ │ └── js │ │ ├── backbone.js │ │ ├── bootstrap.js │ │ ├── jquery.js │ │ └── underscore.js ├── templates │ └── main │ │ └── home.html ├── testbed.sqlite ├── urls.py └── wsgi.py ├── tox.ini └── versioneer.py /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Python CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-python/ for more details 4 | # 5 | version: 2 6 | jobs: 7 | build: 8 | docker: 9 | - image: python:3.9 10 | steps: 11 | - checkout 12 | - run: 13 | name: Install python-dev 14 | command: | 15 | apt-get update; 16 | - run: 17 | name: Install Tox 18 | command: pip install tox 19 | - run: 20 | name: Run tests 21 | command: tox -r 22 | 23 | workflows: 24 | version: 2 25 | run_tests: 26 | jobs: 27 | - build 28 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = 3 | */python?.?/* 4 | */site-packages/* 5 | *.egg/* 6 | openformats/tests/* -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | openformats/_version.py export-subst 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Run Tests 3 | 4 | on: 5 | pull_request: 6 | 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v3 13 | 14 | - name: Use Python version 15 | uses: actions/setup-python@v3 16 | with: 17 | python-version: 3.9 18 | 19 | - name: Install dependencies 20 | run: | 21 | python -m pip install --upgrade pip 22 | pip install . 23 | pip install six mock coverage 24 | 25 | - name: Run Tests 26 | run: | 27 | coverage run -m unittest 28 | coverage report 29 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "devel", master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "devel" ] 20 | schedule: 21 | - cron: '21 18 * * 3' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'python' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 73 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | 59 | # Editor temp files 60 | .*.swp 61 | 62 | .idea 63 | *.iml -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include versioneer.py 2 | include openformats/_version.py 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | help: 2 | cat Makefile 3 | 4 | run: 5 | docker-compose up 6 | 7 | test: 8 | docker-compose run --rm --entrypoint='python /app/setup.py' app test 9 | 10 | shell: 11 | docker-compose run --rm app shell 12 | 13 | migrate: 14 | docker-compose run --rm app migrate 15 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Problem and/or solution 2 | ----------------------- 3 | 4 | How to test 5 | ----------- 6 | 7 | Reviewer checklist 8 | ------------------ 9 | 10 | Code: 11 | * [ ] Change is covered by unit-tests 12 | * [ ] Code is well documented, well styled and is following [best practices](https://tem.transifex.com) 13 | * [ ] Performance issues have been taken under consideration 14 | * [ ] Errors and other edge-cases are handled properly 15 | 16 | PR: 17 | * [ ] Problem and/or solution are well-explained 18 | * [ ] Commits have been squashed so that each one has a clear purpose 19 | * [ ] Commits have a proper commit message [according to TEM](https://tem.transifex.com/github-guide.html#working-on-a-feature) 20 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | OpenFormats 4 | =========== 5 | 6 | |build-status| |coverage-status| |docs-status| 7 | 8 | 9 | OpenFormats is a localization file format library, written in Python_. 10 | 11 | * Read and write to various file formats such as `.po`, `.xliff` or even ones 12 | which are not localization formats, such as `.srt` and `.txt`. 13 | * Plural support for the formats which do support it. 14 | * Built-in web-based test app, to help you develop your own format handlers. 15 | 16 | OpenFormats' primary use is to work as a file format backend to Transifex_. 17 | 18 | Check out `OpenFormats documentation`_ for more information. 19 | 20 | 21 | How to get help, contribute, or provide feedback 22 | ------------------------------------------------ 23 | 24 | See our `contribution submission and feedback guidelines`_. 25 | 26 | You can run tests for the formats by doing the following:: 27 | 28 | python setup.py test 29 | 30 | 31 | Source code 32 | ----------- 33 | 34 | The source code for OpenFormats is `hosted on GitHub`_. 35 | 36 | 37 | The testbed 38 | ----------- 39 | 40 | To run the testbed:: 41 | 42 | ./manage.py syncdb --noinput # optional 43 | ./manage.py runserver 44 | 45 | Then point your browser to http://localhost:8000/. 46 | 47 | The `syncdb` step is optional and is used if you wish to save certain tests by 48 | their URL The tests are saved to an sqlite database. This is most likely to be 49 | useful in the live version of the testbed. 50 | 51 | Having fired up the testbed, you can select a format handler, paste some text 52 | and try to parse it. The testbed will show you the stringset that was extracted 53 | from the source text and the template in kept from it. Then, you can try 54 | compiling the template against the stringset, or you can modify it first. 55 | 56 | 57 | .. Links 58 | 59 | .. _Python: http://www.python.org/ 60 | .. _Transifex: http://www.transifex.com/ 61 | .. _`contribution submission and feedback guidelines`: http://openformats.readthedocs.org/en/latest/contributing.html 62 | .. _`OpenFormats documentation`: http://openformats.readthedocs.org/ 63 | .. _`hosted on GitHub`: https://github.com/transifex/openformats 64 | 65 | 66 | .. |build-status| image:: https://img.shields.io/circleci/project/transifex/openformats.svg 67 | :target: https://circleci.com/gh/transifex/openformats 68 | :alt: Circle.ci: continuous integration status 69 | .. |coverage-status| image:: https://img.shields.io/coveralls/transifex/openformats.svg 70 | :target: https://coveralls.io/r/transifex/openformats 71 | :alt: Coveralls: code coverage status 72 | .. |docs-status| image:: https://readthedocs.org/projects/openformats/badge/?version=latest 73 | :target: https://readthedocs.org/projects/openformats/?badge=latest 74 | :alt: Documentation Status 75 | -------------------------------------------------------------------------------- /bin/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/bin/__init__.py -------------------------------------------------------------------------------- /bin/create_files.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Create template files from source files using the respective handlers. 5 | 6 | Example: 7 | $ ./bin/create_files.py openformats/tests/srt/files/1_en.srt 8 | """ 9 | 10 | from __future__ import absolute_import 11 | 12 | import argparse 13 | import os 14 | import sys 15 | from io import open 16 | 17 | from openformats.formats import (android, github_markdown_v2, json, plaintext, 18 | po, srt, vtt) 19 | from openformats.tests.utils import translate_stringset 20 | 21 | sys.path.append(os.path.join(os.path.dirname(__file__), "..")) 22 | 23 | 24 | args = argparse.ArgumentParser 25 | 26 | 27 | def get_handler(ext): 28 | """Return the right format handler based on the file extension.""" 29 | return { 30 | 'txt': plaintext.PlaintextHandler(), 31 | 'srt': srt.SrtHandler(), 32 | 'vtt': vtt.VttHandler(), 33 | 'xml': android.AndroidHandler(), 34 | 'json': json.JsonHandler(), 35 | 'arb': json.ArbHandler(), 36 | 'po': po.PoHandler(), 37 | 'md': github_markdown_v2.GithubMarkdownHandlerV2(), 38 | }[ext] 39 | 40 | 41 | def run(): 42 | # Choose correct handler based on the file extension 43 | file_extension = os.path.splitext(args.inputfile)[1][1:] 44 | handler = get_handler(file_extension) 45 | 46 | with open(args.inputfile, mode='rU', encoding='utf-8') as f: 47 | source_contents = f.read() 48 | 49 | # Save template test file, eg. 1_tpl.foo 50 | template, stringset = handler.parse(source_contents) 51 | tpl_fname = args.inputfile.replace("_en", "_tpl") 52 | with open(tpl_fname, 'w+', encoding='utf-8') as tpl_file: 53 | if args.debug: 54 | print("Writing {}".format(tpl_fname)) 55 | if args.execute: 56 | tpl_file.write(template) 57 | tpl_file.close() 58 | 59 | translated_stringset = translate_stringset(stringset, debug=True) 60 | 61 | # Save translated file 62 | compiled = handler.compile(template, translated_stringset) 63 | fname = args.inputfile.replace("_en", "_el") 64 | with open(fname, 'w+', encoding='utf-8') as f: 65 | if args.debug: 66 | print("Writing {}".format(fname)) 67 | f.write(compiled) 68 | f.close() 69 | 70 | 71 | def main(argv): 72 | parser = argparse.ArgumentParser( 73 | add_help=True, 74 | description='Generate right test files from an English source file.' 75 | ) 76 | parser.add_argument('inputfile', 77 | help="Source file to convert") 78 | parser.add_argument('-d', '--debug', action='store_true', default=True, 79 | help='Print debug information') 80 | parser.add_argument('-x', '--execute', action='store_true', default=True, 81 | help="Actually execute. Otherwise, don't do anything.") 82 | global args # Help us access this variable from inside the other methods. 83 | args = parser.parse_args() 84 | run() 85 | 86 | 87 | if __name__ == "__main__": 88 | main(sys.argv[1:]) 89 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.2" 2 | services: 3 | app: 4 | build: 5 | context: . 6 | dockerfile: docker/Dockerfile 7 | ports: 8 | - target: 8010 9 | published: 8010 10 | protocol: tcp 11 | mode: host 12 | volumes: 13 | - ./:/app 14 | stdin_open: true 15 | tty: true 16 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax = docker/dockerfile:1.0.2-experimental 2 | ARG PY_VERSION=3.9 3 | 4 | FROM python:${PY_VERSION}-slim-buster as builder 5 | 6 | 7 | ENV PKGS='\ 8 | # Packages required by pip 9 | git openssh-client' 10 | 11 | COPY ./setup.cfg ./app/setup.cfg 12 | COPY ./setup.py ./app/setup.py 13 | COPY ./versioneer.py ./app/versioneer.py 14 | 15 | RUN apt-get update && apt-get install -y --no-install-recommends $PKGS && \ 16 | python ./app/setup.py egg_info && \ 17 | pip install --no-cache-dir -r openformats.egg-info/requires.txt && \ 18 | rm -rf /var/lib/apt/lists/* 19 | 20 | ENV PYTHONDONTWRITEBYTECODE=1 21 | 22 | COPY requirements.txt /requirements.txt 23 | RUN pip install --upgrade pip 24 | RUN pip install -r /requirements.txt 25 | 26 | WORKDIR /app 27 | 28 | ENTRYPOINT ["python", "/app/manage.py"] 29 | CMD ["runserver", "0.0.0.0:8010"] 30 | -------------------------------------------------------------------------------- /docs/Changelog.rst: -------------------------------------------------------------------------------- 1 | .. _changelog: 2 | 3 | Changelog 4 | ========= 5 | 6 | 0.1 (2015-05-15) 7 | ------------------ 8 | 9 | Initial release. 10 | -------------------------------------------------------------------------------- /docs/getting-started.rst: -------------------------------------------------------------------------------- 1 | .. _getting-started: 2 | 3 | 4 | Getting Started Guide 5 | ##################### 6 | 7 | Here are some quick steps to get you started with OpenFormats. 8 | 9 | 10 | Installation 11 | ============ 12 | 13 | To use OpenFormats as a Python library, simply install it with ``pip``, 14 | prefixing with ``sudo`` if permissions warrant:: 15 | 16 | pip install openformats 17 | 18 | If you plan to tweak the codebase or add your own format handler, grab a copy 19 | of the whole repository from GitHub:: 20 | 21 | git clone https://github.com/transifex/openformats.git 22 | cd openformats 23 | 24 | 25 | Creating your own handler 26 | ========================= 27 | 28 | OpenFormats supports a variety of file formats, including plaintext (``.txt``), 29 | subtitles (``.srt``) and others. Here are the steps to create your own handler. 30 | 31 | 32 | 1. Subclass the base `Handler` 33 | ============================== 34 | 35 | .. py:module:: openformats.handlers 36 | 37 | .. autoclass:: Handler 38 | :members: 39 | 40 | The following are some classes that will help you with this process: 41 | 42 | 43 | 2. The `OpenString` class 44 | ========================= 45 | 46 | .. py:module:: openformats.strings 47 | 48 | .. autoclass:: OpenString 49 | :members: 50 | 51 | 52 | 3. The `Transcriber` 53 | ==================== 54 | 55 | .. py:module:: openformats.transcribers 56 | 57 | .. autoclass:: Transcriber 58 | :members: 59 | 60 | 61 | 62 | Continue reading the other documentation sections for further details. 63 | -------------------------------------------------------------------------------- /docs/images/openformats-testbed-error.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/docs/images/openformats-testbed-error.gif -------------------------------------------------------------------------------- /docs/images/openformats-testbed-screencast.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/docs/images/openformats-testbed-screencast.gif -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | 2 | .. include:: ../README.rst 3 | 4 | 5 | Contents 6 | -------- 7 | .. toctree:: 8 | :maxdepth: 2 9 | 10 | introduction 11 | getting-started 12 | testing 13 | testbed 14 | utils 15 | contributing 16 | Changelog 17 | 18 | 19 | Indices and tables 20 | ------------------ 21 | * :ref:`genindex` 22 | * :ref:`modindex` 23 | * :ref:`search` 24 | -------------------------------------------------------------------------------- /docs/testbed.rst: -------------------------------------------------------------------------------- 1 | .. _testbed: 2 | 3 | 4 | The Testbed 5 | ########### 6 | 7 | The testbed is a real-time `django`_ web application included with the openformats 8 | library to help you develop, test and debug format handlers. To start it, 9 | simply run:: 10 | 11 | ./manage.py runserver 12 | 13 | and point your browser to ``http://localhost:8000`` 14 | 15 | The interface consists of 3 columns, one for each state of the handler's 16 | lifetime. 17 | 18 | .. image:: images/openformats-testbed-screencast.gif 19 | 20 | .. _django: https://www.djangoproject.com/ 21 | 22 | 23 | The source column 24 | ================= 25 | 26 | From here you choose which file format you want to play around with. The list 27 | is automatically populated by all sublasses of `Handler` defined in all modules 28 | in ``openformats/formats``. 29 | 30 | Once you select one, you can type or paste some content in the textarea. If you 31 | have a sample file in 32 | ``openformats/tests/formats//files/1_en.``, it will be 33 | picked up by the testbed and put in the textarea automatically once you select 34 | a format. You can of course edit or replace it if you want. 35 | 36 | Finally, press 'parse' to, well, parse the source content. The actual handler 37 | will be used to display the outcome in the next column: 38 | 39 | The stringset-template column 40 | ============================= 41 | 42 | This column shows the outcome of the previous operation: the stringset and 43 | template extracted from the source. You can inspect entries of the stringset, 44 | edit their content or even delete them. Once you're ready, you can press on 45 | compile to have the handler create a language file out of the template and the, 46 | potentially edited, stringset. 47 | 48 | The compiled column 49 | =================== 50 | 51 | This shows the outcome of the previous operation. There is also a message that 52 | tells you if the compiled text matches the source, in case you didn't edit the 53 | stringset and this is what you had expected. 54 | 55 | Errors 56 | ====== 57 | 58 | If there's an error during the parsing or compiling operation, a full traceback 59 | will be printed on the relevant column. This is helpful for both debugging and 60 | making sure that the error messages displayed to the user when there is a 61 | mistake in the source file is accurate and helpful. 62 | 63 | .. image:: images/openformats-testbed-error.gif 64 | 65 | Saving tests 66 | ============ 67 | 68 | If you run the following command:: 69 | 70 | ./manage.py syncdb --noinput 71 | 72 | The testbed will be able to save your current test state (chosen format, 73 | soruce, stringset, template, compiled file) in an sqlite database and allow you 74 | to play it back any time. This saved test can be accessed from the URL in your 75 | browser right after you've pressed the save button. 76 | 77 | You will probably not need to do that yourself; this is a feature intended for 78 | the `public hosted version`_ of the testbed, so that users can provide 79 | Transifex support or openformats contributors with test cases that reproduce a 80 | bug. 81 | 82 | .. _public hosted version: https://formats.transifex.com 83 | -------------------------------------------------------------------------------- /docs/testing.rst: -------------------------------------------------------------------------------- 1 | .. _testing: 2 | 3 | 4 | Testing 5 | ####### 6 | 7 | 8 | 1. Get yourself a sample file 9 | ============================= 10 | 11 | Its a very good idea to start developing by getting a sample source file for 12 | two reasons: 13 | 14 | 1. It will get picked up by the :ref:`testbed` so you will be able to get 15 | instant feedback as you work on your handler. 16 | 17 | 2. You will get a lot of tests for free. These tests will parse the sample file 18 | into a template and stringset, compile them back in your source file and 19 | check whether the template matches the expected one and that the resulted 20 | file matches the source. It will also try to translate the strings based on 21 | some common ones found in a dictionary and check that it can compile a 22 | language file that matches the expected one. 23 | 24 | Put your sample file in 25 | ``openformats/tests/formats//files/1_en.``. For 26 | example, our sample SRT file goes to 27 | ``OpenFormats/tests/formats/srt/files/1_en.srt``. 28 | 29 | 30 | 2. Generate expected template and language files 31 | ================================================ 32 | 33 | In order to generate the expected template and language files mentioned above, 34 | you can use the `bin/create_files.py` script once you have a working handler:: 35 | 36 | ./bin/create_files.py openformats/tests/formats/srt/files/1_en.srt 37 | 38 | In order to get the tests we mentioned for free, make sure your test class 39 | inherits from the: 40 | 41 | .. py:module:: openformats.tests.formats.common 42 | 43 | .. autoclass:: CommonFormatTestMixin() 44 | 45 | You might have noticed that by using a working handler to make the expected 46 | sample files and then testing against them seems pointless. Well, you're right, 47 | they are, initially. The point of for them to serve as regression tests, as you 48 | later make changes to your handler. 49 | 50 | 51 | 3. Add your own tests 52 | ===================== 53 | 54 | Testing that a handler works correctly against a valid source file is good, but 55 | you will want to also test more things, like: 56 | 57 | * The hashes produced take the correct information into account 58 | * The metadata of the extracted strings is what you want 59 | * `ParseErrors` are raised when appropriate and they produce a helpful error 60 | message 61 | * Sections of the compiled files are removed when the relevant strings are 62 | missing from the stringset given as input 63 | * Anything to get your coverage higher 64 | 65 | 66 | 4. Utilities 67 | ============ 68 | 69 | .. py:module:: openformats.tests.utils 70 | 71 | .. autofunction:: generate_random_string 72 | .. autofunction:: strip_leading_spaces 73 | 74 | .. automethod:: openformats.tests.formats.common.CommonFormatTestMixin._test_parse_error 75 | 76 | 77 | 5. Run the test suite 78 | ===================== 79 | :: 80 | 81 | python setup.py test 82 | -------------------------------------------------------------------------------- /docs/utils.rst: -------------------------------------------------------------------------------- 1 | .. _utils: 2 | 3 | Utils 4 | ===== 5 | 6 | Compilers 7 | --------- 8 | 9 | .. py:module:: openformats.utils.compilers 10 | 11 | .. autoclass:: OrderedCompilerMixin 12 | :members: 13 | :show-inheritance: 14 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testbed.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /openformats/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from ._version import get_versions 3 | __version__ = get_versions()['version'] 4 | del get_versions 5 | -------------------------------------------------------------------------------- /openformats/exceptions.py: -------------------------------------------------------------------------------- 1 | class OpenformatsError(Exception): 2 | pass 3 | 4 | 5 | class ParseError(OpenformatsError): 6 | pass 7 | 8 | 9 | class RuleError(OpenformatsError): 10 | pass 11 | 12 | 13 | class MissingParentError(OpenformatsError): 14 | pass 15 | -------------------------------------------------------------------------------- /openformats/formats/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/formats/__init__.py -------------------------------------------------------------------------------- /openformats/formats/office_open_xml/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/formats/office_open_xml/__init__.py -------------------------------------------------------------------------------- /openformats/formats/plaintext.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | import six 4 | 5 | from openformats.strings import OpenString 6 | 7 | from ..handlers import Handler 8 | from ..utils.compilers import OrderedCompilerMixin 9 | 10 | 11 | class PlaintextHandler(OrderedCompilerMixin, Handler): 12 | name = "Plaintext" 13 | extension = "txt" 14 | EXTRACTS_RAW = False 15 | 16 | def parse(self, content, **kwargs): 17 | stringset = [] 18 | # find out whether we're using UNIX or DOS newlines 19 | try: 20 | position = content.index('\n') 21 | except ValueError: 22 | # No newlines present 23 | newline_sequence = "" 24 | lines = (content, ) 25 | else: 26 | if position == 0 or content[position - 1] != '\r': 27 | newline_sequence = "\n" 28 | else: 29 | newline_sequence = "\r\n" 30 | lines = content.split(newline_sequence) 31 | 32 | template = "" 33 | order = 0 34 | for line in lines: 35 | stripped_line = line.strip() 36 | if stripped_line: 37 | string = OpenString(six.text_type(order), 38 | stripped_line, 39 | order=order) 40 | order += 1 41 | stringset.append(string) 42 | 43 | template_line = line.replace(stripped_line, 44 | string.template_replacement) 45 | template += newline_sequence 46 | if stripped_line: 47 | template += template_line 48 | else: 49 | template += line 50 | 51 | # remove newline_sequence added to the start of the template 52 | template = template[len(newline_sequence):] 53 | return template, stringset 54 | -------------------------------------------------------------------------------- /openformats/formats/yaml/__init__.py: -------------------------------------------------------------------------------- 1 | from .yaml import YamlHandler 2 | from .yaml_i18n import I18nYamlHandler 3 | -------------------------------------------------------------------------------- /openformats/formats/yaml/constants.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | YAML_STRING_ID = u'tag:yaml.org,2002:str' 4 | YAML_LIST_ID = u'tag:yaml.org,2002:seq' 5 | YAML_DICT_ID = u'tag:yaml.org,2002:map' 6 | YAML_BINARY_ID = u'tag:yaml.org,2002:binary' 7 | -------------------------------------------------------------------------------- /openformats/formats/yaml/yaml_representee_classes.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Wrapper classes to represent various styled objects 5 | in a dictionary for generating YAML content 6 | """ 7 | 8 | from collections import OrderedDict 9 | 10 | import six 11 | 12 | from openformats.formats.yaml.constants import YAML_STRING_ID 13 | 14 | 15 | class plain_unicode(six.text_type): # noqa: N801 16 | 17 | def __new__(self, value, tag=None): 18 | self = super(plain_unicode, self).__new__(self, value) 19 | self.tag = tag or YAML_STRING_ID 20 | return self 21 | 22 | pass 23 | 24 | 25 | class folded_unicode(plain_unicode): # noqa: N801 26 | pass 27 | 28 | 29 | class literal_unicode(plain_unicode): # noqa: N801 30 | pass 31 | 32 | 33 | class double_quoted_unicode(plain_unicode): # noqa: N801 34 | pass 35 | 36 | 37 | class single_quoted_unicode(plain_unicode): # noqa: N801 38 | pass 39 | 40 | 41 | class BlockList(list): 42 | pass 43 | 44 | 45 | class FlowList(list): 46 | pass 47 | 48 | 49 | class BlockStyleOrderedDict(OrderedDict): 50 | pass 51 | 52 | 53 | class FlowStyleOrderedDict(OrderedDict): 54 | pass 55 | -------------------------------------------------------------------------------- /openformats/formats/yaml/yaml_representers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import 3 | 4 | import six 5 | import yaml 6 | 7 | from openformats.formats.yaml.constants import (YAML_DICT_ID, YAML_LIST_ID, 8 | YAML_STRING_ID) 9 | 10 | 11 | """ 12 | YAML representers 13 | """ 14 | 15 | 16 | def unicode_representer(dumper, data): 17 | tag = getattr(data, 'tag', YAML_STRING_ID) 18 | return yaml.ScalarNode(tag=tag, value=data) 19 | 20 | 21 | def folded_unicode_representer(dumper, data): 22 | return dumper.represent_scalar(YAML_STRING_ID, data, style='>') 23 | 24 | 25 | def literal_unicode_representer(dumper, data): 26 | return dumper.represent_scalar(YAML_STRING_ID, data, style='|') 27 | 28 | 29 | def double_quoted_unicode_representer(dumper, data): 30 | tag = getattr(data, 'tag', YAML_STRING_ID) 31 | return dumper.represent_scalar(tag, data, style='"') 32 | 33 | 34 | def single_quoted_unicode_representer(dumper, data): 35 | tag = getattr(data, 'tag', YAML_STRING_ID) 36 | return dumper.represent_scalar(tag, data, style="'") 37 | 38 | 39 | def block_list_representer(dumper, data): 40 | return dumper.represent_sequence(YAML_LIST_ID, data, flow_style=False) 41 | 42 | 43 | def flow_list_representer(dumper, data): 44 | return dumper.represent_sequence(YAML_LIST_ID, data, flow_style=True) 45 | 46 | 47 | def ordered_dict_representer(dumper, data): 48 | return dumper.represent_dict(six.iteritems(data)) 49 | 50 | 51 | def block_style_ordered_dict_representer(dumper, data): 52 | return dumper.represent_mapping(YAML_DICT_ID, six.iteritems(data), 53 | flow_style=False) 54 | 55 | 56 | def flow_style_ordered_dict_representer(dumper, data): 57 | return dumper.represent_mapping(YAML_DICT_ID, six.iteritems(data), 58 | flow_style=True) 59 | -------------------------------------------------------------------------------- /openformats/handlers.py: -------------------------------------------------------------------------------- 1 | import six 2 | 3 | from openformats.exceptions import RuleError 4 | 5 | 6 | class Handler(object): 7 | """ 8 | This class defines the interface you need to implement in order to create a 9 | handler. Both the `parse` and `compile` methods must be implemented. 10 | """ 11 | 12 | name = None 13 | extension = None 14 | 15 | _RULES_ATOI = { 16 | 'zero': 0, 17 | 'one': 1, 18 | 'two': 2, 19 | 'few': 3, 20 | 'many': 4, 21 | 'other': 5 22 | } 23 | 24 | EXTRACTS_RAW = True 25 | 26 | # Use this flag for handlers whose the content should be processed as 27 | # binary and not be converted to unicode 28 | PROCESSES_BINARY = False 29 | 30 | _RULES_ITOA = {value: key for key, value in six.iteritems(_RULES_ATOI)} 31 | 32 | _RULE_ERROR_MSG = ( 33 | '{attempted} is not a valid rule value. Valid choices are {valid}' 34 | ) 35 | 36 | @classmethod 37 | def get_rule_number(cls, string_value): 38 | try: 39 | return cls._RULES_ATOI[string_value] 40 | except KeyError: 41 | msg = cls._RULE_ERROR_MSG.format( 42 | attempted=string_value, 43 | valid=list(six.iterkeys(cls._RULES_ATOI)) 44 | ) 45 | raise RuleError(msg) 46 | 47 | @classmethod 48 | def get_rule_string(cls, number_value): 49 | try: 50 | return cls._RULES_ITOA[number_value] 51 | except KeyError: 52 | msg = cls._RULE_ERROR_MSG.format( 53 | attempted=number_value, 54 | valid=list(six.iterkeys(cls._RULES_ITOA)) 55 | ) 56 | raise RuleError(msg) 57 | 58 | def parse(self, content, is_source=False): 59 | """ 60 | Parses the content, extracts translatable strings into a stringset, 61 | replaces them with hashes and returns a tuple of the template with the 62 | stringset 63 | 64 | Typically this is done in the following way: 65 | 66 | * Use a library or your own code to segment (deserialize) the content 67 | into translatable entities. 68 | * Choose a key to uniquely identify the entity. 69 | * Create an ``OpenString`` object representing the entity. 70 | * Create a hash to replace the original content with. 71 | * Create a stringset with the content. 72 | * Use library or own code to serialize stringset back into a template. 73 | """ 74 | 75 | raise NotImplementedError('Abstract method') # pragma: no cover 76 | 77 | def compile(self, template, stringset, **kwargs): 78 | """ 79 | Parses the template, finds the hashes, replaces them with strings from 80 | the stringset and returns the compiled file. If a hash in the template 81 | isn't found in the stringset, it's a good practice to remove the whole 82 | string section surrounding it 83 | 84 | Typically this is done in the following way: 85 | 86 | * Use a library or own code to segment (deserialize) the template into 87 | translatable entities, as if assuming that the hashes are the 88 | translatable entities. 89 | * Make sure the hash matches the first string in the stringset. 90 | * Replace the hash with the string. 91 | * Use library or own code to serialize stringset back into a compiled 92 | file. 93 | 94 | You can safely assume that the stringset will have strings in the 95 | correct order for the above process and thus you will probably be able 96 | to perform the whole compilation in a single pass. 97 | """ 98 | 99 | raise NotImplementedError('Abstract method') # pragma: no cover 100 | -------------------------------------------------------------------------------- /openformats/strings.py: -------------------------------------------------------------------------------- 1 | from hashlib import md5 2 | 3 | import six 4 | 5 | 6 | class OpenString(object): 7 | """ 8 | This class will abstract away the business of generating hashes out of your 9 | strings and will serve as a place to get translations from when compiling. 10 | Several OpenStrings in our process define a *Stringset*, which is simply a 11 | python list of OpenStrings. To create an OpenString, you need 2 arguments: 12 | 13 | * The 'key' 14 | 15 | Something in your source file that uniquely identifies the section that 16 | the source string originated from. It might be helpful for your 17 | compiler to use something that appears in the same form in language 18 | files as well. 19 | 20 | * The 'string' or 'plural forms of the string': 21 | 22 | If the file format you're working with does not support plural forms, 23 | or if the string in question is not pluralized, you can just supply the 24 | string itself as the second argument. If you string is pluralized 25 | however, you have to supply all plural forms in a dictionary with the 26 | rule numbers as keys. For example:: 27 | 28 | OpenString("UNREAD MESSAGES", 29 | {1: "You have %s unread message", 30 | 5: "You have %s unread messages"}) 31 | 32 | * There are a number of optional keyword arguments to `OpenString`: 33 | 34 | context, order, character_limit, occurrences, developer_comment, flags, 35 | fuzzy, obsolete 36 | 37 | Their main purpose is to provide context to the translators so that they 38 | can achieve higher quality. Two of them however, though optional, are 39 | highly recommended: 40 | 41 | * Context 42 | 43 | This is also taken into account when producing the hash, so if you 44 | can't ensure that your keys aren't unique within the source file, you 45 | can still get away with ensuring that the `(key, context)` pair is. 46 | 47 | * Order 48 | 49 | If you provide an order (integer), Transifex will save it in the 50 | database and then, when you try to compile a template against a 51 | stringset fetched from Transifex, it will already be ordered, even if 52 | it contains translations. This can allow you to optimize the 53 | compilation process as the order that the hashes appear in the template 54 | will be the same as the order of strings in the stringset. 55 | 56 | Another valuable outcome is that the order will be preserved when the 57 | strings are shown to translators which can provide context and thus 58 | improve translation quality. 59 | 60 | Once you have created an OpenString, you can get it's hash using the 61 | `template_replacement` property 62 | """ 63 | 64 | DEFAULTS = { 65 | "context": "", 66 | "order": None, 67 | "character_limit": None, 68 | "occurrences": None, 69 | "developer_comment": "", 70 | "flags": "", 71 | "fuzzy": False, 72 | "obsolete": False, 73 | "tags": None, 74 | } 75 | 76 | def __init__(self, key, string_or_strings, **kwargs): 77 | self.key = key 78 | if isinstance(string_or_strings, dict): 79 | self.pluralized = len(string_or_strings) > 1 80 | self._strings = { 81 | key: value for key, value in six.iteritems(string_or_strings) 82 | } 83 | else: 84 | self.pluralized = False 85 | self._strings = {5: string_or_strings} 86 | 87 | for key, value in six.iteritems(self.DEFAULTS): 88 | setattr(self, key, kwargs.get(key, value)) 89 | 90 | if "pluralized" in kwargs: 91 | self.pluralized = kwargs["pluralized"] 92 | 93 | self._string_hash = None 94 | 95 | def __hash__(self): 96 | return hash((self.key, self.context, self._strings[5])) 97 | 98 | def __repr__(self): 99 | return next( 100 | ( 101 | self._strings.get(i) 102 | for i in six.moves.xrange(5, -1, -1) 103 | if self._strings.get(i) 104 | ), 105 | "Invalid string", 106 | ).encode("utf-8") 107 | 108 | @property 109 | def string(self): 110 | if self.pluralized: 111 | return self._strings 112 | else: 113 | return self._strings[5] 114 | 115 | @property 116 | def strings(self): 117 | return self._strings 118 | 119 | def _get_string_hash(self): 120 | keys = [self.key, self.context or ""] 121 | return md5(":".join(keys).encode("utf-8")).hexdigest() 122 | 123 | @property 124 | def string_hash(self): 125 | if self._string_hash is None: 126 | self._string_hash = self._get_string_hash() 127 | return self._string_hash 128 | 129 | @property 130 | def template_replacement(self): 131 | suffix = "pl" if self.pluralized else "tr" 132 | return "{hash}_{suffix}".format(hash=self.string_hash, suffix=suffix) 133 | 134 | def __str__(self): 135 | return "{} - {}".format(self.string, self.string_hash) 136 | -------------------------------------------------------------------------------- /openformats/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/__init__.py -------------------------------------------------------------------------------- /openformats/tests/formats/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'lolletsoc' 2 | -------------------------------------------------------------------------------- /openformats/tests/formats/android/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/android/__init__.py -------------------------------------------------------------------------------- /openformats/tests/formats/android/files/1_el.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | el:Simple string 5 | 6 | 7 | el:Simple string & \"with\" \'special characters\' 8 | 9 | 10 | el:Simple string with product 11 | 12 | 13 | 14 | el:First item of string array 15 | el:Second item of string array 16 | 17 | 18 | 19 | 20 | el:First item of string array with product 21 | el:Second item of string array with product 22 | 23 | 24 | 25 | 26 | el:Singular form of plural string 27 | el:Plural form of plural string 28 | 29 | 30 | 31 | 32 | el:Singular form of plural string with product 33 | el:Plural form of plural string with product 34 | 35 | 36 | -------------------------------------------------------------------------------- /openformats/tests/formats/android/files/1_en.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Simple string 5 | 6 | 7 | Simple string & \"with\" \'special characters\' 8 | 9 | 10 | Simple string with product 11 | 12 | 13 | 14 | First item of string array 15 | Second item of string array 16 | 17 | 18 | 19 | 20 | First item of string array with product 21 | Second item of string array with product 22 | 23 | 24 | 25 | 26 | Singular form of plural string 27 | Plural form of plural string 28 | 29 | 30 | 31 | 32 | Singular form of plural string with product 33 | Plural form of plural string with product 34 | 35 | 36 | -------------------------------------------------------------------------------- /openformats/tests/formats/android/files/1_tpl.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ac2562b9f825de04b7ae4736274b1e5f_tr 5 | 6 | 7 | 2cb7c4a9823431f0a9a4efc70603b465_tr 8 | 9 | 10 | 160db9c24b122af9e0f4218ad732d0ce_tr 11 | 12 | 13 | 14 | d17f8d4290d7e38d5f366823996c3941_tr 15 | 8853e0c1adaf62ea091ff96b6f9cef93_tr 16 | 17 | 18 | 19 | 20 | 43eaef1774ce539d9eaebcad8495e81c_tr 21 | 52b109326a6eb5aecd0e92fa86f9e790_tr 22 | 23 | 24 | 25 | 26 | ba10d2ac20f85b6cd360ed60ad7a6d68_pl 27 | 28 | 29 | 30 | 31 | 37e2fbe40ddc00946c516530aa8aeef9_pl 32 | 33 | 34 | -------------------------------------------------------------------------------- /openformats/tests/formats/arb/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/arb/__init__.py -------------------------------------------------------------------------------- /openformats/tests/formats/arb/files/1_el.arb: -------------------------------------------------------------------------------- 1 | { 2 | "@@locale": "en_US", 3 | "@@x-template": "path/to/template.arb", 4 | "@@context": "HomePage", 5 | 6 | "MSG_OK": "el:Everything works fine.", 7 | 8 | "title_bar": "el:My Cool Home", 9 | "@title_bar": { 10 | "type": "text", 11 | "context": "HomePage", 12 | "description": "Page title." 13 | }, 14 | 15 | "total_files": "{ item_count, plural, one {el:You have {file_count} file.} other {el:You have {file_count} files.} }", 16 | "special_chars": "{ cnt, plural, one {el:This is Sam's book.} other {el:These are Sam's books.} }", 17 | "gold_coins": "{count, plural, zero {el:The chest is empty.} one {el:You have one gold coin.} other {el:You have {cnt} gold coins.}}", 18 | "custom_plural_value": "{number, plural, one {el:1 New} two {el:# New}}", 19 | 20 | "logo@src": "images/001.jpg", 21 | "@logo@src": { 22 | "context": "arb_editor", 23 | "type": "image", 24 | "description": "logo image, 128x128" 25 | }, 26 | 27 | "font_style": "#title {font-family: Verdana, Geneva, sans-serif; font-style: oblique; font-size: 36px}", 28 | "@font_style": { 29 | "context": "arb_editor", 30 | "type": "css", 31 | "description": "font specific css" 32 | }, 33 | 34 | "input_test1@placeholder": "el:localized placeholder text", 35 | "input_test2@value": "el:localized input value", 36 | 37 | "logo": "el:ARB", 38 | "@logo": { 39 | "type": "text", 40 | "screen": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBhQGBRUIBwgKFQkKDRYODRYMFhYfHhoWHRweHB8cHh4cJzIqIyUkHB4cITssLycpLCwsFSExPjAtNSgrLEABCQoKDQsNGQ4OGTUkHiQ1LDU1NS4sLCo1NTYpNTYpLCw1NS40NCw0LikuKSkpLCwpLCw0KTQpKSwsNCkpNCkpLP/AABEIADIAMgMBIgACEQEDEQH/xAAbAAACAwEBAQAAAAAAAAAAAAAABQIEBgcBA//EADQQAAEDAgIIAwUJAAAAAAAAAAEAAgMEEQUhBhITMUFRYXEUIjIjgbHB0QcWNEJTkaGi4f/EABgBAAMBAQAAAAAAAAAAAAAAAAABBAUC/8QAIBEAAgEEAgMBAAAAAAAAAAAAAAECAxExQQQhEiKhFP/aAAwDAQACEQMRAD8A7c30+4LN6WSubVwxxyPbYufdhIzyA3dyvtJjTqHFjHKWvpZbOjLbXFsiLjI2cDlvzVPSmobII6mJwLQ19rcxY2PLsmVUINVFddFj7xeGwNz5zeoiadU88sj9f9SfB8VqKOQvmqHSMkiHllPpflmCM7b7hIJ6uSplAkn9kDrFthY24Gwum9LXtqachoIeBmD8QeKWDS/PCEX0nf4OsG0hkmxjYVjmmOa4ZqgDVIBPcg9ei1Nlh8FDY8VbNM6zIgXn9rD+Sr2JaXPjkEVBTN13uDI9oTck7hYfVMhrUPKfotGrQq8DX+HbtpGbXVGvqjK9s7e9epEVjn+k8UmB4trAu8FUv2sd9weR5gOR49R2VeWsGI0oEbgJb2IPG+S6NV0TK+jNPVRNdE8WcHLn+O6DzUFUZMHa99M4ekWLmnkQSLjrv+KT6NCjyY+KUumtmbnqNhUGF5Ie02IKvUkxEJlYPLexKli2D1FDRNrcSpYtj6Ha4zHImxNhwGe9JjWSOpCwgiFxvEQ02yyIHPuk6mmiyNeMkOZMW1W2a4342TvQzAXYhiLcYqmkQwkmC/5iQRcdBc58Sk+iNBTmU1OkNXFZpAhidffzNt/AAZ9eS6w1oa2zQAAMrLq60S8nkNJwis7JoQhBmEW+kdgpWUW+kdgpIAyv2iR+I0dbSkfiayCP+4PyT+sw2PEaXYVdOx0Rys4fDkkOlz9pjNDSfq120PZgv81qBuSWzlN3Yjw/Q6mwup29NS+1YbtL3OdY8wHGwPVPEL1M7cnLLBCEIERb6R2CkhCAKlTA2Sdkj42F8TzsyQLt8h3HgrQQhAlk9QhCBghCEAf/2Q==", 41 | "video": "http://www.youtube.com/user_interaction" 42 | } 43 | } -------------------------------------------------------------------------------- /openformats/tests/formats/arb/files/1_en.arb: -------------------------------------------------------------------------------- 1 | { 2 | "@@locale": "en_US", 3 | "@@x-template": "path/to/template.arb", 4 | "@@context": "HomePage", 5 | 6 | "MSG_OK": "Everything works fine.", 7 | 8 | "title_bar": "My Cool Home", 9 | "@title_bar": { 10 | "type": "text", 11 | "context": "HomePage", 12 | "description": "Page title." 13 | }, 14 | 15 | "total_files": "{ item_count, plural, one {You have {file_count} file.} other {You have {file_count} files.} }", 16 | "special_chars": "{ cnt, plural, one {This is Sam's book.} other {These are Sam's books.} }", 17 | "gold_coins": "{count, plural, zero {The chest is empty.} one {You have one gold coin.} other {You have {cnt} gold coins.}}", 18 | "custom_plural_value": "{number, plural, =1 {1 New} =2 {# New}}", 19 | 20 | "logo@src": "images/001.jpg", 21 | "@logo@src": { 22 | "context": "arb_editor", 23 | "type": "image", 24 | "description": "logo image, 128x128" 25 | }, 26 | 27 | "font_style": "#title {font-family: Verdana, Geneva, sans-serif; font-style: oblique; font-size: 36px}", 28 | "@font_style": { 29 | "context": "arb_editor", 30 | "type": "css", 31 | "description": "font specific css" 32 | }, 33 | 34 | "input_test1@placeholder": "localized placeholder text", 35 | "input_test2@value": "localized input value", 36 | 37 | "logo": "ARB", 38 | "@logo": { 39 | "type": "text", 40 | "screen": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBhQGBRUIBwgKFQkKDRYODRYMFhYfHhoWHRweHB8cHh4cJzIqIyUkHB4cITssLycpLCwsFSExPjAtNSgrLEABCQoKDQsNGQ4OGTUkHiQ1LDU1NS4sLCo1NTYpNTYpLCw1NS40NCw0LikuKSkpLCwpLCw0KTQpKSwsNCkpNCkpLP/AABEIADIAMgMBIgACEQEDEQH/xAAbAAACAwEBAQAAAAAAAAAAAAAABQIEBgcBA//EADQQAAEDAgIIAwUJAAAAAAAAAAEAAgMEEQUhBhITMUFRYXEUIjIjgbHB0QcWNEJTkaGi4f/EABgBAAMBAQAAAAAAAAAAAAAAAAABBAUC/8QAIBEAAgEEAgMBAAAAAAAAAAAAAAECAxExQQQhEiKhFP/aAAwDAQACEQMRAD8A7c30+4LN6WSubVwxxyPbYufdhIzyA3dyvtJjTqHFjHKWvpZbOjLbXFsiLjI2cDlvzVPSmobII6mJwLQ19rcxY2PLsmVUINVFddFj7xeGwNz5zeoiadU88sj9f9SfB8VqKOQvmqHSMkiHllPpflmCM7b7hIJ6uSplAkn9kDrFthY24Gwum9LXtqachoIeBmD8QeKWDS/PCEX0nf4OsG0hkmxjYVjmmOa4ZqgDVIBPcg9ei1Nlh8FDY8VbNM6zIgXn9rD+Sr2JaXPjkEVBTN13uDI9oTck7hYfVMhrUPKfotGrQq8DX+HbtpGbXVGvqjK9s7e9epEVjn+k8UmB4trAu8FUv2sd9weR5gOR49R2VeWsGI0oEbgJb2IPG+S6NV0TK+jNPVRNdE8WcHLn+O6DzUFUZMHa99M4ekWLmnkQSLjrv+KT6NCjyY+KUumtmbnqNhUGF5Ie02IKvUkxEJlYPLexKli2D1FDRNrcSpYtj6Ha4zHImxNhwGe9JjWSOpCwgiFxvEQ02yyIHPuk6mmiyNeMkOZMW1W2a4342TvQzAXYhiLcYqmkQwkmC/5iQRcdBc58Sk+iNBTmU1OkNXFZpAhidffzNt/AAZ9eS6w1oa2zQAAMrLq60S8nkNJwis7JoQhBmEW+kdgpWUW+kdgpIAyv2iR+I0dbSkfiayCP+4PyT+sw2PEaXYVdOx0Rys4fDkkOlz9pjNDSfq120PZgv81qBuSWzlN3Yjw/Q6mwup29NS+1YbtL3OdY8wHGwPVPEL1M7cnLLBCEIERb6R2CkhCAKlTA2Sdkj42F8TzsyQLt8h3HgrQQhAlk9QhCBghCEAf/2Q==", 41 | "video": "http://www.youtube.com/user_interaction" 42 | } 43 | } -------------------------------------------------------------------------------- /openformats/tests/formats/arb/files/1_en_exported.arb: -------------------------------------------------------------------------------- 1 | { 2 | "@@locale": "en_US", 3 | "@@x-template": "path/to/template.arb", 4 | "@@context": "HomePage", 5 | 6 | "MSG_OK": "Everything works fine.", 7 | 8 | "title_bar": "My Cool Home", 9 | "@title_bar": { 10 | "type": "text", 11 | "context": "HomePage", 12 | "description": "Page title." 13 | }, 14 | 15 | "total_files": "{ item_count, plural, one {You have {file_count} file.} other {You have {file_count} files.} }", 16 | "special_chars": "{ cnt, plural, one {This is Sam's book.} other {These are Sam's books.} }", 17 | "gold_coins": "{count, plural, zero {The chest is empty.} one {You have one gold coin.} other {You have {cnt} gold coins.}}", 18 | "custom_plural_value": "{number, plural, one {1 New} two {# New}}", 19 | 20 | "logo@src": "images/001.jpg", 21 | "@logo@src": { 22 | "context": "arb_editor", 23 | "type": "image", 24 | "description": "logo image, 128x128" 25 | }, 26 | 27 | "font_style": "#title {font-family: Verdana, Geneva, sans-serif; font-style: oblique; font-size: 36px}", 28 | "@font_style": { 29 | "context": "arb_editor", 30 | "type": "css", 31 | "description": "font specific css" 32 | }, 33 | 34 | "input_test1@placeholder": "localized placeholder text", 35 | "input_test2@value": "localized input value", 36 | 37 | "logo": "ARB", 38 | "@logo": { 39 | "type": "text", 40 | "screen": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBhQGBRUIBwgKFQkKDRYODRYMFhYfHhoWHRweHB8cHh4cJzIqIyUkHB4cITssLycpLCwsFSExPjAtNSgrLEABCQoKDQsNGQ4OGTUkHiQ1LDU1NS4sLCo1NTYpNTYpLCw1NS40NCw0LikuKSkpLCwpLCw0KTQpKSwsNCkpNCkpLP/AABEIADIAMgMBIgACEQEDEQH/xAAbAAACAwEBAQAAAAAAAAAAAAAABQIEBgcBA//EADQQAAEDAgIIAwUJAAAAAAAAAAEAAgMEEQUhBhITMUFRYXEUIjIjgbHB0QcWNEJTkaGi4f/EABgBAAMBAQAAAAAAAAAAAAAAAAABBAUC/8QAIBEAAgEEAgMBAAAAAAAAAAAAAAECAxExQQQhEiKhFP/aAAwDAQACEQMRAD8A7c30+4LN6WSubVwxxyPbYufdhIzyA3dyvtJjTqHFjHKWvpZbOjLbXFsiLjI2cDlvzVPSmobII6mJwLQ19rcxY2PLsmVUINVFddFj7xeGwNz5zeoiadU88sj9f9SfB8VqKOQvmqHSMkiHllPpflmCM7b7hIJ6uSplAkn9kDrFthY24Gwum9LXtqachoIeBmD8QeKWDS/PCEX0nf4OsG0hkmxjYVjmmOa4ZqgDVIBPcg9ei1Nlh8FDY8VbNM6zIgXn9rD+Sr2JaXPjkEVBTN13uDI9oTck7hYfVMhrUPKfotGrQq8DX+HbtpGbXVGvqjK9s7e9epEVjn+k8UmB4trAu8FUv2sd9weR5gOR49R2VeWsGI0oEbgJb2IPG+S6NV0TK+jNPVRNdE8WcHLn+O6DzUFUZMHa99M4ekWLmnkQSLjrv+KT6NCjyY+KUumtmbnqNhUGF5Ie02IKvUkxEJlYPLexKli2D1FDRNrcSpYtj6Ha4zHImxNhwGe9JjWSOpCwgiFxvEQ02yyIHPuk6mmiyNeMkOZMW1W2a4342TvQzAXYhiLcYqmkQwkmC/5iQRcdBc58Sk+iNBTmU1OkNXFZpAhidffzNt/AAZ9eS6w1oa2zQAAMrLq60S8nkNJwis7JoQhBmEW+kdgpWUW+kdgpIAyv2iR+I0dbSkfiayCP+4PyT+sw2PEaXYVdOx0Rys4fDkkOlz9pjNDSfq120PZgv81qBuSWzlN3Yjw/Q6mwup29NS+1YbtL3OdY8wHGwPVPEL1M7cnLLBCEIERb6R2CkhCAKlTA2Sdkj42F8TzsyQLt8h3HgrQQhAlk9QhCBghCEAf/2Q==", 41 | "video": "http://www.youtube.com/user_interaction" 42 | } 43 | } -------------------------------------------------------------------------------- /openformats/tests/formats/arb/files/1_tpl.arb: -------------------------------------------------------------------------------- 1 | { 2 | "@@locale": "en_US", 3 | "@@x-template": "path/to/template.arb", 4 | "@@context": "HomePage", 5 | 6 | "MSG_OK": "2c95ff04fb99537ccc47e9481b3a6c10_tr", 7 | 8 | "title_bar": "aaf0404af25d7a7ce4ccab203e70725f_tr", 9 | "@title_bar": { 10 | "type": "text", 11 | "context": "HomePage", 12 | "description": "Page title." 13 | }, 14 | 15 | "total_files": "{ item_count, plural, 9af043f75968a7df63dfa2b27c57e2dc_pl }", 16 | "special_chars": "{ cnt, plural, 00fe762ad9b56187fed536350bc3a40c_pl }", 17 | "gold_coins": "{count, plural, 8060865280cf7fc9e80b79145208a825_pl}", 18 | "custom_plural_value": "{number, plural, ee829fd77061d65f1f18982a577b915b_pl}", 19 | 20 | "logo@src": "images/001.jpg", 21 | "@logo@src": { 22 | "context": "arb_editor", 23 | "type": "image", 24 | "description": "logo image, 128x128" 25 | }, 26 | 27 | "font_style": "#title {font-family: Verdana, Geneva, sans-serif; font-style: oblique; font-size: 36px}", 28 | "@font_style": { 29 | "context": "arb_editor", 30 | "type": "css", 31 | "description": "font specific css" 32 | }, 33 | 34 | "input_test1@placeholder": "2eff8610f5d585a18aa1df53391f9d90_tr", 35 | "input_test2@value": "85d86301853ad1cf705881926cb0cc56_tr", 36 | 37 | "logo": "fefbd77ea136fd8d6bcd5e6f8a4ef8e4_tr", 38 | "@logo": { 39 | "type": "text", 40 | "screen": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBhQGBRUIBwgKFQkKDRYODRYMFhYfHhoWHRweHB8cHh4cJzIqIyUkHB4cITssLycpLCwsFSExPjAtNSgrLEABCQoKDQsNGQ4OGTUkHiQ1LDU1NS4sLCo1NTYpNTYpLCw1NS40NCw0LikuKSkpLCwpLCw0KTQpKSwsNCkpNCkpLP/AABEIADIAMgMBIgACEQEDEQH/xAAbAAACAwEBAQAAAAAAAAAAAAAABQIEBgcBA//EADQQAAEDAgIIAwUJAAAAAAAAAAEAAgMEEQUhBhITMUFRYXEUIjIjgbHB0QcWNEJTkaGi4f/EABgBAAMBAQAAAAAAAAAAAAAAAAABBAUC/8QAIBEAAgEEAgMBAAAAAAAAAAAAAAECAxExQQQhEiKhFP/aAAwDAQACEQMRAD8A7c30+4LN6WSubVwxxyPbYufdhIzyA3dyvtJjTqHFjHKWvpZbOjLbXFsiLjI2cDlvzVPSmobII6mJwLQ19rcxY2PLsmVUINVFddFj7xeGwNz5zeoiadU88sj9f9SfB8VqKOQvmqHSMkiHllPpflmCM7b7hIJ6uSplAkn9kDrFthY24Gwum9LXtqachoIeBmD8QeKWDS/PCEX0nf4OsG0hkmxjYVjmmOa4ZqgDVIBPcg9ei1Nlh8FDY8VbNM6zIgXn9rD+Sr2JaXPjkEVBTN13uDI9oTck7hYfVMhrUPKfotGrQq8DX+HbtpGbXVGvqjK9s7e9epEVjn+k8UmB4trAu8FUv2sd9weR5gOR49R2VeWsGI0oEbgJb2IPG+S6NV0TK+jNPVRNdE8WcHLn+O6DzUFUZMHa99M4ekWLmnkQSLjrv+KT6NCjyY+KUumtmbnqNhUGF5Ie02IKvUkxEJlYPLexKli2D1FDRNrcSpYtj6Ha4zHImxNhwGe9JjWSOpCwgiFxvEQ02yyIHPuk6mmiyNeMkOZMW1W2a4342TvQzAXYhiLcYqmkQwkmC/5iQRcdBc58Sk+iNBTmU1OkNXFZpAhidffzNt/AAZ9eS6w1oa2zQAAMrLq60S8nkNJwis7JoQhBmEW+kdgpWUW+kdgpIAyv2iR+I0dbSkfiayCP+4PyT+sw2PEaXYVdOx0Rys4fDkkOlz9pjNDSfq120PZgv81qBuSWzlN3Yjw/Q6mwup29NS+1YbtL3OdY8wHGwPVPEL1M7cnLLBCEIERb6R2CkhCAKlTA2Sdkj42F8TzsyQLt8h3HgrQQhAlk9QhCBghCEAf/2Q==", 41 | "video": "http://www.youtube.com/user_interaction" 42 | } 43 | } -------------------------------------------------------------------------------- /openformats/tests/formats/beta_android/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/beta_android/__init__.py -------------------------------------------------------------------------------- /openformats/tests/formats/beta_android/files/1_el.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | el:Simple string 5 | 6 | 7 | el:Simple string with product 8 | 9 | 10 | 11 | el:First item of string array 12 | el:Second item of string array 13 | 14 | 15 | 16 | 17 | el:First item of string array with product 18 | el:Second item of string array with product 19 | 20 | 21 | 22 | 23 | el:Singular form of plural string 24 | el:Plural form of plural string 25 | 26 | 27 | 28 | 29 | el:Singular form of plural string with product 30 | el:Plural form of plural string with product 31 | 32 | 33 | -------------------------------------------------------------------------------- /openformats/tests/formats/beta_android/files/1_en.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Simple string 5 | 6 | 7 | Simple string with product 8 | 9 | 10 | 11 | First item of string array 12 | Second item of string array 13 | 14 | 15 | 16 | 17 | First item of string array with product 18 | Second item of string array with product 19 | 20 | 21 | 22 | 23 | Singular form of plural string 24 | Plural form of plural string 25 | 26 | 27 | 28 | 29 | Singular form of plural string with product 30 | Plural form of plural string with product 31 | 32 | 33 | -------------------------------------------------------------------------------- /openformats/tests/formats/beta_android/files/1_tpl.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ac2562b9f825de04b7ae4736274b1e5f_tr 5 | 6 | 7 | 160db9c24b122af9e0f4218ad732d0ce_tr 8 | 9 | 10 | 11 | d17f8d4290d7e38d5f366823996c3941_tr 12 | 8853e0c1adaf62ea091ff96b6f9cef93_tr 13 | 14 | 15 | 16 | 17 | 43eaef1774ce539d9eaebcad8495e81c_tr 18 | 52b109326a6eb5aecd0e92fa86f9e790_tr 19 | 20 | 21 | 22 | 23 | ba10d2ac20f85b6cd360ed60ad7a6d68_pl 24 | 25 | 26 | 27 | 28 | 37e2fbe40ddc00946c516530aa8aeef9_pl 29 | 30 | 31 | -------------------------------------------------------------------------------- /openformats/tests/formats/chrome_i18n/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/chrome_i18n/__init__.py -------------------------------------------------------------------------------- /openformats/tests/formats/chrome_i18n/files/1_el.json: -------------------------------------------------------------------------------- 1 | { 2 | "unlock": { 3 | "message": "el:Unlock", 4 | "description": "Unlock" 5 | }, 6 | "credentials_in_db": { 7 | "message": "el:$AMOUNT$ credentials in the database", 8 | "description": "Amount of the credentials in the database", 9 | "placeholders": { 10 | "amount": { 11 | "content": "$1", 12 | "example": "2" 13 | } 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /openformats/tests/formats/chrome_i18n/files/1_en.json: -------------------------------------------------------------------------------- 1 | { 2 | "unlock": { 3 | "message": "Unlock", 4 | "description": "Unlock" 5 | }, 6 | "credentials_in_db": { 7 | "message": "$AMOUNT$ credentials in the database", 8 | "description": "Amount of the credentials in the database", 9 | "placeholders": { 10 | "amount": { 11 | "content": "$1", 12 | "example": "2" 13 | } 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /openformats/tests/formats/chrome_i18n/files/1_tpl.json: -------------------------------------------------------------------------------- 1 | { 2 | "unlock": { 3 | "message": "efe942fd8b60d71af881e92dcd8af4d1_tr", 4 | "description": "Unlock" 5 | }, 6 | "credentials_in_db": { 7 | "message": "4d379ccf510e5e48c4bbd25239048475_tr", 8 | "description": "Amount of the credentials in the database", 9 | "placeholders": { 10 | "amount": { 11 | "content": "$1", 12 | "example": "2" 13 | } 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /openformats/tests/formats/chrome_i18n/test_chrome_i18n.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import unittest 4 | import json 5 | 6 | from openformats.formats.json import ChromeI18nHandler 7 | 8 | from openformats.strings import OpenString 9 | 10 | from openformats.tests.formats.common import CommonFormatTestMixin 11 | from openformats.tests.utils.strings import generate_random_string 12 | from openformats.exceptions import ParseError 13 | 14 | 15 | class ChromeI18nTestCase(CommonFormatTestMixin, unittest.TestCase): 16 | 17 | HANDLER_CLASS = ChromeI18nHandler 18 | TESTFILE_BASE = "openformats/tests/formats/chrome_i18n/files" 19 | 20 | def setUp(self): 21 | super(ChromeI18nTestCase, self).setUp() 22 | 23 | self.handler = ChromeI18nHandler() 24 | self.random_string = generate_random_string() 25 | self.random_openstring = OpenString("a.message", "%s" % 26 | self.random_string, order=0) 27 | self.random_hash = self.random_openstring.template_replacement 28 | 29 | def test_simple(self): 30 | source = '{"a":{"message": "%s"}}' % self.random_string 31 | template, stringset = self.handler.parse('{"a":{"message": "%s"}}' % 32 | self.random_string) 33 | compiled = self.handler.compile(template, [self.random_openstring]) 34 | self.assertEqual( 35 | template, '{"a":{"message": "%s"}}' % self.random_hash 36 | ) 37 | self.assertEqual(len(stringset), 1) 38 | self.assertEqual(stringset[0].__dict__, 39 | self.random_openstring.__dict__) 40 | self.assertEqual( 41 | compiled, '{"a":{"message": "%s"}}' % self.random_string 42 | ) 43 | # Check developer comment is empty 44 | self.assertEqual(stringset[0].developer_comment, "") 45 | # Check the JSON dict lives in memory and contains the whole source 46 | self.assertEqual(json.loads(source), self.handler.json_dict) 47 | 48 | def test_with_description(self): 49 | source = '{"a":{"message":"%s","description":"desc"}}' 50 | template, stringset = self.handler.parse(source % self.random_string) 51 | 52 | self.assertEqual(len(stringset), 1) 53 | self.assertEqual(template, source % self.random_hash) 54 | # Check that description has been assigned to the correct field 55 | self.assertEqual(stringset[0].developer_comment, "desc") 56 | 57 | def test_with_template(self): 58 | source = '{"a":{"message":"%s","description":"desc","garbage":"text"}}' 59 | template, stringset = self.handler.parse(source % self.random_string) 60 | 61 | self.assertEqual(len(stringset), 1) 62 | self.assertEqual(template, source % self.random_hash) 63 | compiled = self.handler.compile(template, [self.random_openstring]) 64 | # Check that additional keys/values are part of the template 65 | self.assertEqual(compiled, source % self.random_string) 66 | 67 | def test_no_message_or_description(self): 68 | source = '{"a":{"random-key":"random-value","garbage":"text"}}' 69 | template, stringset = self.handler.parse(source) 70 | 71 | self.assertEqual(len(stringset), 0) 72 | self.assertEqual(template, source) 73 | compiled = self.handler.compile(template, []) 74 | self.assertEqual(compiled, source) 75 | 76 | def test_unicode_source_and_description(self): 77 | self.random_string = u'τεστ' 78 | self.random_openstring = OpenString("a.message", "%s" % 79 | self.random_string, order=0) 80 | self.random_hash = self.random_openstring.template_replacement 81 | 82 | source = u'{"a":{"message":"%s","description":"τεστ","τεστ":"τεστ"}}' 83 | template, stringset = self.handler.parse(source % self.random_string) 84 | 85 | self.assertEqual(len(stringset), 1) 86 | self.assertEqual(template, source % self.random_hash) 87 | compiled = self.handler.compile(template, [self.random_openstring]) 88 | self.assertEqual(compiled, source % self.random_string) 89 | 90 | def test_with_integers_as_values(self): 91 | source = '{"a":{"message":"%s","description":1234, "123":"123"}}' 92 | template, stringset = self.handler.parse(source % self.random_string) 93 | 94 | self.assertEqual(len(stringset), 1) 95 | self.assertEqual(template, source % self.random_hash) 96 | self.assertEqual(stringset[0].developer_comment, 1234) 97 | 98 | def test_invalid_json(self): 99 | source = '{"a":{123:"%s"}}' 100 | with self.assertRaises(ParseError): 101 | template, stringset = self.handler.parse( 102 | source % self.random_string 103 | ) 104 | 105 | def test_with_extra_characters_as_value(self): 106 | source = '{"a":{"message":"%s", "@@@@description":"desc"}}' 107 | template, stringset = self.handler.parse(source % self.random_string) 108 | 109 | self.assertEqual(len(stringset), 1) 110 | self.assertEqual(template, source % self.random_hash) 111 | self.assertEqual(stringset[0].developer_comment, "") 112 | -------------------------------------------------------------------------------- /openformats/tests/formats/chromev3/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/chromev3/__init__.py -------------------------------------------------------------------------------- /openformats/tests/formats/chromev3/files/1_el.json: -------------------------------------------------------------------------------- 1 | { 2 | "simple": { 3 | "message": "el:simple message" 4 | }, 5 | "with description": { 6 | "message": "el:simple message", 7 | "description": "simple description" 8 | }, 9 | "description not a string": { 10 | "message": "el:simple message", 11 | "description": 3 12 | }, 13 | "not a dictionary": 3, 14 | "not a dictionary v2": [3], 15 | "message not a string": { 16 | "message": 3 17 | }, 18 | "with extra stuff": { 19 | "message": "el:simple message", 20 | "placeholders": { 21 | "a": 1, 22 | "b": 2 23 | } 24 | }, 25 | "key.with.dots": { 26 | "message": "el:simple message" 27 | }, 28 | "pluralized": { 29 | "message": "{cnt, plural, one {el:horse} other {el:horses}}" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /openformats/tests/formats/chromev3/files/1_en.json: -------------------------------------------------------------------------------- 1 | { 2 | "simple": { 3 | "message": "simple message" 4 | }, 5 | "with description": { 6 | "message": "simple message", 7 | "description": "simple description" 8 | }, 9 | "description not a string": { 10 | "message": "simple message", 11 | "description": 3 12 | }, 13 | "not a dictionary": 3, 14 | "not a dictionary v2": [3], 15 | "message not a string": { 16 | "message": 3 17 | }, 18 | "with extra stuff": { 19 | "message": "simple message", 20 | "placeholders": { 21 | "a": 1, 22 | "b": 2 23 | } 24 | }, 25 | "key.with.dots": { 26 | "message": "simple message" 27 | }, 28 | "pluralized": { 29 | "message": "{cnt, plural, one {horse} other {horses}}" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /openformats/tests/formats/chromev3/files/1_tpl.json: -------------------------------------------------------------------------------- 1 | { 2 | "simple": { 3 | "message": "f39e4a122e9a8c8e9b398aa9a7003bee_tr" 4 | }, 5 | "with description": { 6 | "message": "1f6c3d7ca32998ebb32189701c33b596_tr", 7 | "description": "simple description" 8 | }, 9 | "description not a string": { 10 | "message": "8a3a0089b0ddfbba3e04cc7a4e847022_tr", 11 | "description": 3 12 | }, 13 | "not a dictionary": 3, 14 | "not a dictionary v2": [3], 15 | "message not a string": { 16 | "message": 3 17 | }, 18 | "with extra stuff": { 19 | "message": "87ca036cc1ecbea02075c5be9c93d530_tr", 20 | "placeholders": { 21 | "a": 1, 22 | "b": 2 23 | } 24 | }, 25 | "key.with.dots": { 26 | "message": "e5c58379e175c372ca6e7b0efa9b5a76_tr" 27 | }, 28 | "pluralized": { 29 | "message": "{cnt, plural, ea4101a387e08dd12345e43f3f389c9a_pl}" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /openformats/tests/formats/common/__init__.py: -------------------------------------------------------------------------------- 1 | import fnmatch 2 | import six 3 | from io import open 4 | 5 | from os import listdir, path 6 | from os.path import isfile, join 7 | 8 | from openformats.exceptions import ParseError 9 | from openformats.tests.utils import translate_stringset 10 | 11 | 12 | class CommonFormatTestMixin(object): 13 | """ 14 | Define a set of tests to be run by every file format. 15 | 16 | The class that inherits from this must define the following: 17 | 18 | * ``HANDLER_CLASS``, eg: PlaintextHandler 19 | * ``TESTFILE_BASE``, eg: `openformats/tests/formats/plaintext/files` 20 | """ 21 | 22 | TESTFILE_BASE = None 23 | HANDLER_CLASS = None 24 | 25 | def __init__(self, *args, **kwargs): 26 | self.data = {} 27 | super(CommonFormatTestMixin, self).__init__(*args, **kwargs) 28 | 29 | def read_files(self, ftypes=('en', 'el', 'tpl')): 30 | """ 31 | Read test data files into variables. 32 | 33 | Example: 1_en.txt stored into self.data["1_en"] 34 | """ 35 | 36 | # Find source files to use as a base to read all others 37 | en_files = [] 38 | for f in listdir(self.TESTFILE_BASE): 39 | if (isfile(join(self.TESTFILE_BASE, f)) and 40 | fnmatch.fnmatch(f, '[!.]*_en.*')): 41 | en_files.append(f) 42 | 43 | file_nums = set([f.split("_")[0] for f in en_files]) 44 | for num in file_nums: 45 | for ftype in ftypes: 46 | name = "%s_%s" % (num, ftype) # 1_en, 1_fr etc. 47 | filepath = path.join(self.TESTFILE_BASE, "%s.%s" % ( 48 | name, self.HANDLER_CLASS.extension)) 49 | if not isfile(filepath): 50 | self.fail("Bad test files: Expected to find %s" % filepath) 51 | with open(filepath, "r", encoding='utf-8') as myfile: 52 | self.data[name] = myfile.read() 53 | 54 | def setUp(self): 55 | self.handler = self.HANDLER_CLASS() 56 | self.read_files() 57 | self.tmpl, self.strset = self.handler.parse(self.data["1_en"]) 58 | super(CommonFormatTestMixin, self).setUp() 59 | 60 | def test_extracts_raw(self): 61 | if self.HANDLER_CLASS.EXTRACTS_RAW: 62 | self.assertTrue(hasattr(self.HANDLER_CLASS, 'escape')) 63 | self.assertTrue(hasattr(self.HANDLER_CLASS, 'unescape')) 64 | 65 | def test_template(self): 66 | """Test that the template created is the same as static one.""" 67 | # FIXME: Test descriptions should have the handler's name prefixed to 68 | # be able to differentiate between them. 69 | self.assertEqual(self.tmpl, self.data["1_tpl"]) 70 | 71 | def test_no_empty_strings_in_handler_stringset(self): 72 | for s in self.strset: 73 | self.assertFalse(s.string == '') 74 | 75 | def test_compile(self): 76 | """Test that import-export is the same as the original file.""" 77 | remade_orig_content = self.handler.compile(self.tmpl, self.strset) 78 | self.assertEqual(remade_orig_content, self.data["1_en"]) 79 | 80 | def test_translate(self): 81 | """Test that translate + export is the same as the precompiled file.""" 82 | translated_strset = translate_stringset(self.strset) 83 | translated_content = self.handler.compile(self.tmpl, translated_strset) 84 | self.assertEqual(translated_content, self.data["1_el"]) 85 | 86 | def _test_parse_error(self, source, error_msg, parse_kwargs=None): 87 | """ 88 | Test that trying to parse 'source' raises an error with a message 89 | exactly like 'error_msg' 90 | """ 91 | parse_kwargs = parse_kwargs if parse_kwargs is not None else {} 92 | exception = None 93 | try: 94 | self.handler.parse(source, **parse_kwargs) 95 | except ParseError as e: 96 | exception = six.text_type(e) 97 | except Exception: 98 | raise AssertionError("Did not raise ParseError") 99 | else: 100 | raise AssertionError("Did not raise '{}'".format(error_msg)) 101 | self.assertEqual(exception, error_msg) 102 | -------------------------------------------------------------------------------- /openformats/tests/formats/customizable_xml/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/customizable_xml/__init__.py -------------------------------------------------------------------------------- /openformats/tests/formats/customizable_xml/files/1_el.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | el:A translatable string 6 | 7 | 8 | el:The user pressed the button 9 | el:He pressed the button 10 | el:She pressed the button 11 | 12 |
13 |
14 | 15 | el:Another translatable string 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | el:Some entity 24 | 25 |
26 |
27 | Anything 28 |
29 |
-------------------------------------------------------------------------------- /openformats/tests/formats/customizable_xml/files/1_en.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | A translatable string 6 | 7 | 8 | The user pressed the button 9 | He pressed the button 10 | She pressed the button 11 | 12 |
13 |
14 | 15 | Another translatable string 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | Some entity 24 | 25 |
26 |
27 | Anything 28 |
29 |
-------------------------------------------------------------------------------- /openformats/tests/formats/customizable_xml/files/1_tpl.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | b8535dff24f1c17468c87beef1f53ef7_tr 6 | 7 | 8 | 5d93729f62679a39c7222b559dba2402_tr 9 | 25e6726d12b93463930ab96b28d10ec8_tr 10 | d211229b5f0c91dcecc411b35ccd0e5b_tr 11 | 12 |
13 |
14 | 15 | f4cf827ab746b78845a8b4613ce23c70_tr 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | a620c67be79a511bfb55b18945d6167e_tr 24 | 25 |
26 |
27 | Anything 28 |
29 |
-------------------------------------------------------------------------------- /openformats/tests/formats/docx/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/docx/__init__.py -------------------------------------------------------------------------------- /openformats/tests/formats/docx/files/complex.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/docx/files/complex.docx -------------------------------------------------------------------------------- /openformats/tests/formats/docx/files/hello_world.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/docx/files/hello_world.docx -------------------------------------------------------------------------------- /openformats/tests/formats/docx/files/hello_world_no_ppr.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/docx/files/hello_world_no_ppr.docx -------------------------------------------------------------------------------- /openformats/tests/formats/docx/files/missing_wr_parent.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/docx/files/missing_wr_parent.docx -------------------------------------------------------------------------------- /openformats/tests/formats/docx/files/special_cases.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/docx/files/special_cases.docx -------------------------------------------------------------------------------- /openformats/tests/formats/docx/files/special_cases_2.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/docx/files/special_cases_2.docx -------------------------------------------------------------------------------- /openformats/tests/formats/docx/files/special_cases_3.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/docx/files/special_cases_3.docx -------------------------------------------------------------------------------- /openformats/tests/formats/docx/files/test_no_parent.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/docx/files/test_no_parent.docx -------------------------------------------------------------------------------- /openformats/tests/formats/docx/files/two_text_elements.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/docx/files/two_text_elements.docx -------------------------------------------------------------------------------- /openformats/tests/formats/docx/files/with_ampersand.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/docx/files/with_ampersand.docx -------------------------------------------------------------------------------- /openformats/tests/formats/docx/files/with_lt.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/docx/files/with_lt.docx -------------------------------------------------------------------------------- /openformats/tests/formats/github_markdown/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/github_markdown/__init__.py -------------------------------------------------------------------------------- /openformats/tests/formats/github_markdown/files/1_el.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: el:About writing and formatting on GitHub 3 | intro: el:{{ site.data.guides.dotcom-writing-on-github.shortdesc.about-writing-and-formatting-on-github }} 4 | numeric_var: 12.5 5 | # one comment 6 | description: > 7 | el: Hello 8 | world 9 | custom_vars: 10 | var1: el:some value 11 | var2: | 12 | el: another 13 | multiline 14 | string with 15 | "quotes and :" 16 | --- 17 | 18 | # el:Markdown stuff 19 | 20 | ## el:Hearders and bold and italic 21 | 22 | el:*This text will be italic* and _This will also be italic_ 23 | 24 | ### el:This is an

tag 25 | 26 | el:**This text will be bold** and __This will also be bold__ 27 | 28 | ###### el:This is an

tag 29 | 30 | el:_You **can** combine bold and italic_ 31 | 32 | 33 | ## el:List 34 | 35 | ### el:Unordered 36 | 37 | * el:Item 1 38 | * el:Item 2 39 | * el:Item 2a 40 | * el:Item 2b 41 | * ``` 42 | Item 2c 43 | ``` 44 | 45 | ### el:Ordered 46 | 47 | 1. el:Item 1 48 | 1. el:Item 2 49 | 1. el:Item 3 50 | 1. el:Item 3a 51 | 1. el:Item 3b 52 | 1. ``` 53 | Item 3c 54 | ``` 55 | 56 | 57 | ## el:Images 58 | 59 | el:![GitHub Logo](/images/logo.png) 60 | Format: ![Alt Text](url) 61 | 62 | 63 | ## el:Links 64 | 65 | el:http://github.com - automatic! 66 | [GitHub](http://github.com) 67 | 68 | 69 | ## el:Blockquotes 70 | 71 | el:As Kanye West said: 72 | 73 | el:> We're living the future so 74 | > the present is our past. 75 | 76 | 77 | ## el:Inline code 78 | 79 | el:I think you should use an `` element here instead. 80 | 81 | 82 | # el:GitHub Flavored Markdown 83 | 84 | ## el:Syntax highlighting 85 | 86 | el:Here's an example of how you can use syntax highlighting with GitHub Flavored Markdown: 87 | 88 | el:```javascript 89 | function fancyAlert(arg) { 90 | if(arg) { 91 | $.facebox({div:'#foo'}) 92 | } 93 | } 94 | ``` 95 | 96 | el:You can also simply indent your code by four spaces: 97 | 98 | el: function fancyAlert(arg) { 99 | if(arg) { 100 | $.facebox({div:'#foo'}) 101 | } 102 | } 103 | 104 | 105 | # el:Code block inside a list 106 | - el:List item 1 107 | - el:List item 2 108 | el: 109 | function fancyAlert(arg) { 110 | if(arg) { 111 | $.facebox({div:'#foo'}) 112 | } 113 | } 114 | 115 | 116 | ## el:Task lists 117 | 118 | - el:[x] @mentions, #refs, [links](), **formatting**, and tags supported 119 | - el:[x] list syntax required (any unordered or ordered list supported) 120 | - el:[x] this is a complete item 121 | - el:[ ] this is an incomplete item 122 | 123 | 124 | ## el:Tables 125 | 126 | el:First Header | el:Second Header 127 | ------------ | ------------- 128 | el:Content from cell 1 | el:Content from cell 2 129 | el:Content in the first column | el:Content in the second column 130 | 131 | 132 | ## el:Strikethrough 133 | 134 | el:Any word wrapped with two tildes (like ~~this~~) will appear crossed out. 135 | 136 | 137 | # el:Custom stuff 138 | 139 | ## el:Liquid template language 140 | 141 | el:You can use liquid template syntax in you markdown file. 142 | 143 | {% if version <= '2.6' %} 144 | 145 | ### el:Old version 146 | 147 | el:This is a old version of the {{ site.data.variable.product }} documentation. 148 | 149 | {% endif %} 150 | 151 | 152 | ## el:Whole Lines as link or reference 153 | 154 | ### el:Whole line as links in as list 155 | 156 | - el:"[Basic writing and formatting syntax](/articles/basic-writing-and-formatting-syntax)" 157 | - el:[Working with advanced formatting](/articles/working-with-advanced-formatting) 158 | 159 | ### el:Whole line as links 160 | 161 | el:[MIT](/LICENSE) 162 | "[GPL](/LICENSE)" 163 | 164 | ### el:Whole line as links 165 | 166 | el:This is as reference [link][1]. 167 | 168 | [1]: http://example.com/ 169 | [el:Reference]: http://example.com/ 170 | "[el:Reference]: http://example.com/" 171 | -------------------------------------------------------------------------------- /openformats/tests/formats/github_markdown/files/1_en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: About writing and formatting on GitHub 3 | intro: {{ site.data.guides.dotcom-writing-on-github.shortdesc.about-writing-and-formatting-on-github }} 4 | numeric_var: 12.5 5 | # one comment 6 | description: > 7 | Hello 8 | world 9 | custom_vars: 10 | var1: some value 11 | var2: | 12 | another 13 | multiline 14 | string with 15 | "quotes and :" 16 | --- 17 | 18 | # Markdown stuff 19 | 20 | ## Hearders and bold and italic 21 | 22 | *This text will be italic* and _This will also be italic_ 23 | 24 | ### This is an

tag 25 | 26 | **This text will be bold** and __This will also be bold__ 27 | 28 | ###### This is an

tag 29 | 30 | _You **can** combine bold and italic_ 31 | 32 | 33 | ## List 34 | 35 | ### Unordered 36 | 37 | * Item 1 38 | * Item 2 39 | * Item 2a 40 | * Item 2b 41 | * ``` 42 | Item 2c 43 | ``` 44 | 45 | ### Ordered 46 | 47 | 1. Item 1 48 | 1. Item 2 49 | 1. Item 3 50 | 1. Item 3a 51 | 1. Item 3b 52 | 1. ``` 53 | Item 3c 54 | ``` 55 | 56 | 57 | ## Images 58 | 59 | ![GitHub Logo](/images/logo.png) 60 | Format: ![Alt Text](url) 61 | 62 | 63 | ## Links 64 | 65 | http://github.com - automatic! 66 | [GitHub](http://github.com) 67 | 68 | 69 | ## Blockquotes 70 | 71 | As Kanye West said: 72 | 73 | > We're living the future so 74 | > the present is our past. 75 | 76 | 77 | ## Inline code 78 | 79 | I think you should use an `` element here instead. 80 | 81 | 82 | # GitHub Flavored Markdown 83 | 84 | ## Syntax highlighting 85 | 86 | Here's an example of how you can use syntax highlighting with GitHub Flavored Markdown: 87 | 88 | ```javascript 89 | function fancyAlert(arg) { 90 | if(arg) { 91 | $.facebox({div:'#foo'}) 92 | } 93 | } 94 | ``` 95 | 96 | You can also simply indent your code by four spaces: 97 | 98 | function fancyAlert(arg) { 99 | if(arg) { 100 | $.facebox({div:'#foo'}) 101 | } 102 | } 103 | 104 | 105 | # Code block inside a list 106 | - List item 1 107 | - List item 2 108 | 109 | function fancyAlert(arg) { 110 | if(arg) { 111 | $.facebox({div:'#foo'}) 112 | } 113 | } 114 | 115 | 116 | ## Task lists 117 | 118 | - [x] @mentions, #refs, [links](), **formatting**, and tags supported 119 | - [x] list syntax required (any unordered or ordered list supported) 120 | - [x] this is a complete item 121 | - [ ] this is an incomplete item 122 | 123 | 124 | ## Tables 125 | 126 | First Header | Second Header 127 | ------------ | ------------- 128 | Content from cell 1 | Content from cell 2 129 | Content in the first column | Content in the second column 130 | 131 | 132 | ## Strikethrough 133 | 134 | Any word wrapped with two tildes (like ~~this~~) will appear crossed out. 135 | 136 | 137 | # Custom stuff 138 | 139 | ## Liquid template language 140 | 141 | You can use liquid template syntax in you markdown file. 142 | 143 | {% if version <= '2.6' %} 144 | 145 | ### Old version 146 | 147 | This is a old version of the {{ site.data.variable.product }} documentation. 148 | 149 | {% endif %} 150 | 151 | 152 | ## Whole Lines as link or reference 153 | 154 | ### Whole line as links in as list 155 | 156 | - "[Basic writing and formatting syntax](/articles/basic-writing-and-formatting-syntax)" 157 | - [Working with advanced formatting](/articles/working-with-advanced-formatting) 158 | 159 | ### Whole line as links 160 | 161 | [MIT](/LICENSE) 162 | "[GPL](/LICENSE)" 163 | 164 | ### Whole line as links 165 | 166 | This is as reference [link][1]. 167 | 168 | [1]: http://example.com/ 169 | [Reference]: http://example.com/ 170 | "[Reference]: http://example.com/" 171 | -------------------------------------------------------------------------------- /openformats/tests/formats/github_markdown/files/1_tpl.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 9a1c7ee2c7ce38d4bbbaf29ab9f2ac1e_tr 3 | intro: 3afcdbfeb6ecfbdd0ba628696e3cc163_tr 4 | numeric_var: 12.5 5 | # one comment 6 | description: > 7 | 247730f9d0d2eaad265a470e32aa0cdf_tr 8 | custom_vars: 9 | var1: cdee9bf40a070d58d14dfa3bb61e0032_tr 10 | var2: | 11 | 7693e302dc09b57483d26522ef25feb4_tr 12 | --- 13 | 14 | # 016ce7cd3f0850d6c33696df9f9440d5_tr 15 | 16 | ## 8572f81f02517e0c1f0d9b93e407697a_tr 17 | 18 | 03a149245ce7dd9921af995e04819f39_tr 19 | 20 | ### 0b1f4e58fb49b407070c78baeaacf8ad_tr 21 | 22 | 5878d2cab3c6e53c4a1a9526c3df451d_tr 23 | 24 | ###### a7d6fcfc537b66a63062cd141ac8bd87_tr 25 | 26 | 314adc507266cc256fcc9d72d670729d_tr 27 | 28 | 29 | ## 2fd7e333d36411b88fb3becbacfa8188_tr 30 | 31 | ### 824bde2ac5edff64c4ffb1f064b1ee4a_tr 32 | 33 | * ecb3257d21a429f4e49462e8639f1494_tr 34 | * 26101031d5115f57d3e8c34f2ac1f741_tr 35 | * ac435aa88d7932bc7c26dbe0277119a3_tr 36 | * 311a0516b1c6eb11a28e8deaa5c64c78_tr 37 | * ``` 38 | Item 2c 39 | ``` 40 | 41 | ### a3575631fc76819748da773b5b0087a4_tr 42 | 43 | 1. 2040af1756f3e6429f9b8abab90fb258_tr 44 | 1. 691513953ea19e01a0e7881a339ce106_tr 45 | 1. 867da7e2af8e6b2f3aa7213a4080edb3_tr 46 | 1. a6c1243676462dae35ed0c9122125d4e_tr 47 | 1. 6994d1415b92982eb7cf57740b15b949_tr 48 | 1. ``` 49 | Item 3c 50 | ``` 51 | 52 | 53 | ## 061c16448aee6be07e20ab574ef27ea4_tr 54 | 55 | ea8a5a340cab600d88eea120273c4022_tr 56 | 57 | 58 | ## 944024165d37855a16b48158a491e0a5_tr 59 | 60 | d51c2a7526e97b2e9ba2d86ac075af7c_tr 61 | 62 | 63 | ## 99cd002aedd5014650d2d43ca2967c2a_tr 64 | 65 | 662a0b32c5caab5e3b8aa9a3f7bcedfc_tr 66 | 67 | f1c3d12b026ece333653621093d9abe3_tr 68 | 69 | 70 | ## bb74ac206332955a7b4f5076f55b2e26_tr 71 | 72 | bc3bd100ff8a56464a05d8f7f8bb7a6d_tr 73 | 74 | 75 | # 28e677da01f23106bbe6f32634edae27_tr 76 | 77 | ## 656cad764b3cb08b8a76fe22ce8cad9a_tr 78 | 79 | eb8f40de442bebf3cfcbe2e513add63b_tr 80 | 81 | c78e069659a818bab095c6c8976bf3ae_tr 82 | 83 | 5972329507db8347acbb7bc2952df188_tr 84 | 85 | 938c0de7edf02915e5f1cb8c3a8da6db_tr 86 | 87 | 88 | # 61a2eceadafd120aa94c67278d7a7334_tr 89 | - 48d1f08b2408fe8a97fa0bc36b7c4400_tr 90 | - 3c951e08898efd90188c74084373af08_tr 91 | ef70b397e869e6fd08714e2f9edd3f8c_tr 92 | 93 | 94 | ## 0dcd35bbb040482717ba0d8763cf24f0_tr 95 | 96 | - 4d22974b0d27df9918a8b922d921587c_tr 97 | - 5eb74b106793ebcd6eeee358aa4d25ac_tr 98 | - 72e17437d94b8aaaa7ca9ff670fb2c42_tr 99 | - 4f95c94d8503a15557174036f4f7947a_tr 100 | 101 | 102 | ## 6cb8da4ccdafbed92fd1c4b5888091f9_tr 103 | 104 | 0b912bd88f2f0678a52616328ed01a63_tr | f093e61658553bbd86214bffe9a663a8_tr 105 | ------------ | ------------- 106 | d8a4fb88944aeb2b305e4e94e9d18283_tr | 78e10a5ea41eae9ea05dc265a5c8cd66_tr 107 | d6c6d7c6ae13b3b656339b51dbeb85ff_tr | 9441c042cb0b839e13ed95f55de0ee5c_tr 108 | 109 | 110 | ## a4ad6fda3cab93ac6479a0c30dd96b0c_tr 111 | 112 | 257f3f448e7ac5f2fe13b374496309f6_tr 113 | 114 | 115 | # 38d12337337477ece3edbf687c60f45b_tr 116 | 117 | ## 39a9c02a305227b4f9c62bc3d839500c_tr 118 | 119 | 00ccddea80e2b57095f5c536410069b5_tr 120 | 121 | {% if version <= '2.6' %} 122 | 123 | ### f592797318508cb692be49a04b5fe0a3_tr 124 | 125 | 1958a4139b0dc6d506cfe08e1553199d_tr 126 | 127 | {% endif %} 128 | 129 | 130 | ## 8228238dcd753647ede755c671c9988b_tr 131 | 132 | ### fd90f0cdc5f649dd84d4e21ff33b5774_tr 133 | 134 | - a69644fd392aad985de61e166dc8e8fd_tr 135 | - ed226cb95f3b637703501d9f4ddd3d68_tr 136 | 137 | ### 18546b56cfde1b046b0372d31f6e7293_tr 138 | 139 | f240c90767be4aac25128f5fd8cd5716_tr 140 | 141 | ### 66c3fee70e0ec54cbbba4c8912c8b1dc_tr 142 | 143 | 1b125d3881f320a3e0ebc9f35e8707c5_tr 144 | 145 | [1]: http://example.com/ 146 | [aa5072eebea6f4364b4444a8a0f0f868_tr]: http://example.com/ 147 | "[f98c3b2040ea74375cdaa2759f0b86f7_tr]: http://example.com/" 148 | -------------------------------------------------------------------------------- /openformats/tests/formats/github_markdown/test_github_markdown.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from openformats.tests.formats.common import CommonFormatTestMixin 4 | from openformats.formats.github_markdown import GithubMarkdownHandler 5 | 6 | 7 | class GithubMarkdownTestCase(CommonFormatTestMixin, unittest.TestCase): 8 | HANDLER_CLASS = GithubMarkdownHandler 9 | TESTFILE_BASE = "openformats/tests/formats/github_markdown/files" 10 | 11 | def test_parse(self): 12 | """Test parse converts tabs to spaces""" 13 | 14 | content_with_tab = self.handler.parse(content=u"# foo bar") 15 | content_with_spaces = self.handler.parse(content=u"# foo bar") 16 | self.assertEqual(content_with_tab[0], content_with_spaces[0]) 17 | -------------------------------------------------------------------------------- /openformats/tests/formats/github_markdown_v2/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/github_markdown_v2/__init__.py -------------------------------------------------------------------------------- /openformats/tests/formats/github_markdown_v2/files/1_el.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "el:About writing and formatting on GitHub" 3 | intro: "el:into text" 4 | numeric_var: 12.5 5 | 6 | # one comment 7 | 8 | key1: 9 | - list_key: 10 | - "el:li within li" 11 | # second comment 12 | - "el:li within li 2" 13 | - "el:li 2" 14 | key2: 15 | - object_within_list: "el:value" 16 | - "el:text with a #hashtag in it" 17 | - "el:li 5" 18 | 19 | description: > 20 | "el:folded style text 21 | "custom_vars: 22 | var1: "el:text: some value" 23 | var2: | 24 | el:literal 25 | style with "quotes" 26 | text 27 | nested_key_outer: 28 | nested_key_inner: 29 | "el:nested_value" 30 | 31 | key.with.dots: "el:dot value" 32 | 33 | 1: "el:integer key" 34 | 35 | wrapping: 36 | at: "el:@something" 37 | brackets: "el:[Something] else" 38 | 39 | --- 40 | 41 | # el:Markdown stuff 42 | 43 | ## el:Hearders and bold and italic 44 | 45 | el:*This text will be italic* and _This will also be italic_ 46 | 47 | ### el:This is an

tag 48 | 49 | el:**This text will be bold** and __This will also be bold__ 50 | 51 | ###### el:This is an

tag 52 | 53 | el:_You **can** combine bold and italic_ 54 | 55 | 56 | ## el:List 57 | 58 | ### el:Unordered 59 | 60 | * el:Item 1 61 | * el:Item 2 62 | * el:Item 2a 63 | * el:Item 2b 64 | * ``` 65 | Item 2c 66 | ``` 67 | 68 | ### el:Ordered 69 | 70 | 1. el:Item 1 71 | 1. el:Item 2 72 | 1. el:Item 3 73 | 1. el:Item 3a 74 | 1. el:Item 3b 75 | 1. ``` 76 | Item 3c 77 | ``` 78 | 79 | 80 | ## el:Images 81 | 82 | el:![GitHub Logo](/images/logo.png) 83 | Format: ![Alt Text](url) 84 | 85 | 86 | ## el:Links 87 | 88 | el:http://github.com - automatic! 89 | [GitHub](http://github.com) 90 | 91 | 92 | ## el:Blockquotes 93 | 94 | el:As Kanye West said: 95 | 96 | el:> We're living the future so 97 | > the present is our past. 98 | 99 | 100 | ## el:Inline code 101 | 102 | el:I think you should use an `` element here instead. 103 | 104 | 105 | # el:GitHub Flavored Markdown 106 | 107 | ## el:Tables 108 | 109 | el:First Header | el:Second Header 110 | ------------ | ------------- 111 | el:Content from cell 1 | el:Content from cell 2 112 | el:Content in the first column | el:Content in the second column 113 | 114 | 115 | ## el:Syntax highlighting 116 | 117 | el:You can simply indent your code by four spaces: 118 | 119 | el: function fancyAlert(arg) { 120 | if(arg) { 121 | $.facebox({div:'#foo'}) 122 | } 123 | } 124 | 125 | 126 | # el:Code block inside a list 127 | - el:List item 1 128 | - el:List item 2 129 | el: 130 | function fancyAlert(arg) { 131 | if(arg) { 132 | $.facebox({div:'#foo'}) 133 | } 134 | } 135 | 136 | 137 | ## el:Task lists 138 | 139 | - el:[x] @mentions, #refs, [links](), **formatting**, and tags supported 140 | - el:[x] list syntax required (any unordered or ordered list supported) 141 | - el:[x] this is a complete item 142 | - el:[ ] this is an incomplete item 143 | 144 | 145 | ## el:Strikethrough 146 | 147 | el:Any word wrapped with two tildes (like ~~this~~) will appear crossed out. 148 | 149 | 150 | # el:Custom stuff 151 | 152 | ## el:Liquid template language 153 | 154 | el:You can use liquid template syntax in you markdown file. 155 | 156 | {% if version <= '2.6' %} 157 | 158 | ### el:Old version 159 | 160 | el:This is a old version of the {{ site.data.variable.product }} documentation. 161 | 162 | {% endif %} 163 | 164 | 165 | ## el:Whole Lines as link or reference 166 | 167 | ### el:Whole line as links in as list 168 | 169 | - el:"[Basic writing and formatting syntax](/articles/basic-writing-and-formatting-syntax)" 170 | - el:[Working with advanced formatting](/articles/working-with-advanced-formatting) 171 | 172 | ### el:Whole line as links 173 | 174 | el:[MIT](/LICENSE) 175 | "[GPL](/LICENSE)" 176 | 177 | ### el:Whole line as links 178 | 179 | el:This is as reference [link][1]. 180 | 181 | [1]: http://example.com/ 182 | [el:Reference]: http://example.com/ 183 | "[el:Reference]: http://example.com/" 184 | 185 | 186 | # el:Special section 187 | el:WARNING! The example shown here seems to break all tests on content that is below the javascript code block, because of the "el:" part that is added before the code block notation. Please keep this at the end of the file. 188 | 189 | el:Here's an example of how you can use syntax highlighting with GitHub Flavored Markdown: 190 | 191 | el:```javascript 192 | function fancyAlert(arg) { 193 | if(arg) { 194 | $.facebox({div:'#foo'}) 195 | } 196 | } 197 | ``` 198 | -------------------------------------------------------------------------------- /openformats/tests/formats/github_markdown_v2/files/1_en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: About writing and formatting on GitHub 3 | intro: into text 4 | numeric_var: 12.5 5 | 6 | # one comment 7 | 8 | key1: 9 | - list_key: 10 | - li within li 11 | # second comment 12 | - li within li 2 13 | - li 2 14 | key2: 15 | - object_within_list: value 16 | - "text with a #hashtag in it" 17 | - li 5 18 | 19 | description: > 20 | folded 21 | style 22 | text 23 | custom_vars: 24 | var1: "text: some value" 25 | var2: | 26 | literal 27 | style with "quotes" 28 | text 29 | 30 | nested_key_outer: 31 | nested_key_inner: 32 | nested_value 33 | 34 | key.with.dots: dot value 35 | 36 | 1: integer key 37 | 38 | wrapping: 39 | at: "@something" 40 | brackets: "[Something] else" 41 | 42 | --- 43 | 44 | # Markdown stuff 45 | 46 | ## Hearders and bold and italic 47 | 48 | *This text will be italic* and _This will also be italic_ 49 | 50 | ### This is an

tag 51 | 52 | **This text will be bold** and __This will also be bold__ 53 | 54 | ###### This is an

tag 55 | 56 | _You **can** combine bold and italic_ 57 | 58 | 59 | ## List 60 | 61 | ### Unordered 62 | 63 | * Item 1 64 | * Item 2 65 | * Item 2a 66 | * Item 2b 67 | * ``` 68 | Item 2c 69 | ``` 70 | 71 | ### Ordered 72 | 73 | 1. Item 1 74 | 1. Item 2 75 | 1. Item 3 76 | 1. Item 3a 77 | 1. Item 3b 78 | 1. ``` 79 | Item 3c 80 | ``` 81 | 82 | 83 | ## Images 84 | 85 | ![GitHub Logo](/images/logo.png) 86 | Format: ![Alt Text](url) 87 | 88 | 89 | ## Links 90 | 91 | http://github.com - automatic! 92 | [GitHub](http://github.com) 93 | 94 | 95 | ## Blockquotes 96 | 97 | As Kanye West said: 98 | 99 | > We're living the future so 100 | > the present is our past. 101 | 102 | 103 | ## Inline code 104 | 105 | I think you should use an `` element here instead. 106 | 107 | 108 | # GitHub Flavored Markdown 109 | 110 | ## Tables 111 | 112 | First Header | Second Header 113 | ------------ | ------------- 114 | Content from cell 1 | Content from cell 2 115 | Content in the first column | Content in the second column 116 | 117 | 118 | ## Syntax highlighting 119 | 120 | You can simply indent your code by four spaces: 121 | 122 | function fancyAlert(arg) { 123 | if(arg) { 124 | $.facebox({div:'#foo'}) 125 | } 126 | } 127 | 128 | 129 | # Code block inside a list 130 | - List item 1 131 | - List item 2 132 | 133 | function fancyAlert(arg) { 134 | if(arg) { 135 | $.facebox({div:'#foo'}) 136 | } 137 | } 138 | 139 | 140 | ## Task lists 141 | 142 | - [x] @mentions, #refs, [links](), **formatting**, and tags supported 143 | - [x] list syntax required (any unordered or ordered list supported) 144 | - [x] this is a complete item 145 | - [ ] this is an incomplete item 146 | 147 | 148 | ## Strikethrough 149 | 150 | Any word wrapped with two tildes (like ~~this~~) will appear crossed out. 151 | 152 | 153 | # Custom stuff 154 | 155 | ## Liquid template language 156 | 157 | You can use liquid template syntax in you markdown file. 158 | 159 | {% if version <= '2.6' %} 160 | 161 | ### Old version 162 | 163 | This is a old version of the {{ site.data.variable.product }} documentation. 164 | 165 | {% endif %} 166 | 167 | 168 | ## Whole Lines as link or reference 169 | 170 | ### Whole line as links in as list 171 | 172 | - "[Basic writing and formatting syntax](/articles/basic-writing-and-formatting-syntax)" 173 | - [Working with advanced formatting](/articles/working-with-advanced-formatting) 174 | 175 | ### Whole line as links 176 | 177 | [MIT](/LICENSE) 178 | "[GPL](/LICENSE)" 179 | 180 | ### Whole line as links 181 | 182 | This is as reference [link][1]. 183 | 184 | [1]: http://example.com/ 185 | [Reference]: http://example.com/ 186 | "[Reference]: http://example.com/" 187 | 188 | 189 | # Special section 190 | WARNING! The example shown here seems to break all tests on content that is below the javascript code block, because of the "el:" part that is added before the code block notation. Please keep this at the end of the file. 191 | 192 | Here's an example of how you can use syntax highlighting with GitHub Flavored Markdown: 193 | 194 | ```javascript 195 | function fancyAlert(arg) { 196 | if(arg) { 197 | $.facebox({div:'#foo'}) 198 | } 199 | } 200 | ``` 201 | -------------------------------------------------------------------------------- /openformats/tests/formats/github_markdown_v2/files/1_en_export.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: About writing and formatting on GitHub 3 | intro: into text 4 | numeric_var: 12.5 5 | 6 | # one comment 7 | 8 | key1: 9 | - list_key: 10 | - li within li 11 | # second comment 12 | - li within li 2 13 | - li 2 14 | key2: 15 | - object_within_list: value 16 | - "text with a #hashtag in it" 17 | - li 5 18 | 19 | description: > 20 | folded style text 21 | custom_vars: 22 | var1: "text: some value" 23 | var2: | 24 | literal 25 | style with "quotes" 26 | text 27 | nested_key_outer: 28 | nested_key_inner: 29 | nested_value 30 | 31 | key.with.dots: dot value 32 | 33 | 1: integer key 34 | 35 | wrapping: 36 | at: "@something" 37 | brackets: "[Something] else" 38 | 39 | --- 40 | 41 | # Markdown stuff 42 | 43 | ## Hearders and bold and italic 44 | 45 | *This text will be italic* and _This will also be italic_ 46 | 47 | ### This is an

tag 48 | 49 | **This text will be bold** and __This will also be bold__ 50 | 51 | ###### This is an

tag 52 | 53 | _You **can** combine bold and italic_ 54 | 55 | 56 | ## List 57 | 58 | ### Unordered 59 | 60 | * Item 1 61 | * Item 2 62 | * Item 2a 63 | * Item 2b 64 | * ``` 65 | Item 2c 66 | ``` 67 | 68 | ### Ordered 69 | 70 | 1. Item 1 71 | 1. Item 2 72 | 1. Item 3 73 | 1. Item 3a 74 | 1. Item 3b 75 | 1. ``` 76 | Item 3c 77 | ``` 78 | 79 | 80 | ## Images 81 | 82 | ![GitHub Logo](/images/logo.png) 83 | Format: ![Alt Text](url) 84 | 85 | 86 | ## Links 87 | 88 | http://github.com - automatic! 89 | [GitHub](http://github.com) 90 | 91 | 92 | ## Blockquotes 93 | 94 | As Kanye West said: 95 | 96 | > We're living the future so 97 | > the present is our past. 98 | 99 | 100 | ## Inline code 101 | 102 | I think you should use an `` element here instead. 103 | 104 | 105 | # GitHub Flavored Markdown 106 | 107 | ## Tables 108 | 109 | First Header | Second Header 110 | ------------ | ------------- 111 | Content from cell 1 | Content from cell 2 112 | Content in the first column | Content in the second column 113 | 114 | 115 | ## Syntax highlighting 116 | 117 | You can simply indent your code by four spaces: 118 | 119 | function fancyAlert(arg) { 120 | if(arg) { 121 | $.facebox({div:'#foo'}) 122 | } 123 | } 124 | 125 | 126 | # Code block inside a list 127 | - List item 1 128 | - List item 2 129 | 130 | function fancyAlert(arg) { 131 | if(arg) { 132 | $.facebox({div:'#foo'}) 133 | } 134 | } 135 | 136 | 137 | ## Task lists 138 | 139 | - [x] @mentions, #refs, [links](), **formatting**, and tags supported 140 | - [x] list syntax required (any unordered or ordered list supported) 141 | - [x] this is a complete item 142 | - [ ] this is an incomplete item 143 | 144 | 145 | ## Strikethrough 146 | 147 | Any word wrapped with two tildes (like ~~this~~) will appear crossed out. 148 | 149 | 150 | # Custom stuff 151 | 152 | ## Liquid template language 153 | 154 | You can use liquid template syntax in you markdown file. 155 | 156 | {% if version <= '2.6' %} 157 | 158 | ### Old version 159 | 160 | This is a old version of the {{ site.data.variable.product }} documentation. 161 | 162 | {% endif %} 163 | 164 | 165 | ## Whole Lines as link or reference 166 | 167 | ### Whole line as links in as list 168 | 169 | - "[Basic writing and formatting syntax](/articles/basic-writing-and-formatting-syntax)" 170 | - [Working with advanced formatting](/articles/working-with-advanced-formatting) 171 | 172 | ### Whole line as links 173 | 174 | [MIT](/LICENSE) 175 | "[GPL](/LICENSE)" 176 | 177 | ### Whole line as links 178 | 179 | This is as reference [link][1]. 180 | 181 | [1]: http://example.com/ 182 | [Reference]: http://example.com/ 183 | "[Reference]: http://example.com/" 184 | 185 | 186 | # Special section 187 | WARNING! The example shown here seems to break all tests on content that is below the javascript code block, because of the "el:" part that is added before the code block notation. Please keep this at the end of the file. 188 | 189 | Here's an example of how you can use syntax highlighting with GitHub Flavored Markdown: 190 | 191 | ```javascript 192 | function fancyAlert(arg) { 193 | if(arg) { 194 | $.facebox({div:'#foo'}) 195 | } 196 | } 197 | ``` 198 | -------------------------------------------------------------------------------- /openformats/tests/formats/github_markdown_v2/files/1_tpl.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 6391362253bdac99fff7bf50ff014be3_tr 3 | intro: e8c07422d0167fee3ecf8f3375ea5739_tr 4 | numeric_var: 12.5 5 | 6 | # one comment 7 | 8 | key1: 9 | - list_key: 10 | - ac56af108b4b69b867c09f8542a51cb2_tr 11 | # second comment 12 | - d2d20cce249c5d7e8ca96b9e30a3349c_tr 13 | - 17e2086737d2192e6665ea2c0f0b1e9a_tr 14 | key2: 15 | - object_within_list: 1a397fe7999e432441bfce7a1f4fb1f3_tr 16 | - aa8c6113a4236d576f96ba9b0bd4dfa4_tr 17 | - 76d7c5ccc4301f10b39115b2c1a9ca47_tr 18 | 19 | description: fdba4efe4ab9d38afee4054a7fa06e29_trcustom_vars: 20 | var1: 8b9f5508bd423a15a188813d60e52946_tr 21 | var2: 7169ba1087c5204f12ab8b7c066884a2_trnested_key_outer: 22 | nested_key_inner: 23 | 94b99ee5f1d082d7df28c4b48aa99ff8_tr 24 | 25 | key.with.dots: ddbc5d72cdd64acc258ab3446b6466ed_tr 26 | 27 | 1: 9354e4f807073d6a146138fe6d81e4ef_tr 28 | 29 | wrapping: 30 | at: c9c07c7124a5f4774c6f7b130cc4c1fa_tr 31 | brackets: cb544bc6f97fe540c7a4db0c9126f83e_tr 32 | 33 | --- 34 | 35 | # ac435aa88d7932bc7c26dbe0277119a3_tr 36 | 37 | ## 311a0516b1c6eb11a28e8deaa5c64c78_tr 38 | 39 | a3575631fc76819748da773b5b0087a4_tr 40 | 41 | ### 2040af1756f3e6429f9b8abab90fb258_tr 42 | 43 | 691513953ea19e01a0e7881a339ce106_tr 44 | 45 | ###### 867da7e2af8e6b2f3aa7213a4080edb3_tr 46 | 47 | a6c1243676462dae35ed0c9122125d4e_tr 48 | 49 | 50 | ## 6994d1415b92982eb7cf57740b15b949_tr 51 | 52 | ### 061c16448aee6be07e20ab574ef27ea4_tr 53 | 54 | * ea8a5a340cab600d88eea120273c4022_tr 55 | * 944024165d37855a16b48158a491e0a5_tr 56 | * d51c2a7526e97b2e9ba2d86ac075af7c_tr 57 | * 99cd002aedd5014650d2d43ca2967c2a_tr 58 | * ``` 59 | Item 2c 60 | ``` 61 | 62 | ### 662a0b32c5caab5e3b8aa9a3f7bcedfc_tr 63 | 64 | 1. f1c3d12b026ece333653621093d9abe3_tr 65 | 1. bb74ac206332955a7b4f5076f55b2e26_tr 66 | 1. bc3bd100ff8a56464a05d8f7f8bb7a6d_tr 67 | 1. 28e677da01f23106bbe6f32634edae27_tr 68 | 1. 656cad764b3cb08b8a76fe22ce8cad9a_tr 69 | 1. ``` 70 | Item 3c 71 | ``` 72 | 73 | 74 | ## eb8f40de442bebf3cfcbe2e513add63b_tr 75 | 76 | c78e069659a818bab095c6c8976bf3ae_tr 77 | 78 | 79 | ## 5972329507db8347acbb7bc2952df188_tr 80 | 81 | 938c0de7edf02915e5f1cb8c3a8da6db_tr 82 | 83 | 84 | ## 61a2eceadafd120aa94c67278d7a7334_tr 85 | 86 | 48d1f08b2408fe8a97fa0bc36b7c4400_tr 87 | 88 | 3c951e08898efd90188c74084373af08_tr 89 | 90 | 91 | ## ef70b397e869e6fd08714e2f9edd3f8c_tr 92 | 93 | 0dcd35bbb040482717ba0d8763cf24f0_tr 94 | 95 | 96 | # 4d22974b0d27df9918a8b922d921587c_tr 97 | 98 | ## 5eb74b106793ebcd6eeee358aa4d25ac_tr 99 | 100 | 72e17437d94b8aaaa7ca9ff670fb2c42_tr | 4f95c94d8503a15557174036f4f7947a_tr 101 | ------------ | ------------- 102 | 6cb8da4ccdafbed92fd1c4b5888091f9_tr | 0b912bd88f2f0678a52616328ed01a63_tr 103 | f093e61658553bbd86214bffe9a663a8_tr | d8a4fb88944aeb2b305e4e94e9d18283_tr 104 | 105 | 106 | ## 78e10a5ea41eae9ea05dc265a5c8cd66_tr 107 | 108 | d6c6d7c6ae13b3b656339b51dbeb85ff_tr 109 | 110 | 9441c042cb0b839e13ed95f55de0ee5c_tr 111 | 112 | 113 | # a4ad6fda3cab93ac6479a0c30dd96b0c_tr 114 | - 257f3f448e7ac5f2fe13b374496309f6_tr 115 | - 38d12337337477ece3edbf687c60f45b_tr 116 | 39a9c02a305227b4f9c62bc3d839500c_tr 117 | 118 | 119 | ## 00ccddea80e2b57095f5c536410069b5_tr 120 | 121 | - f592797318508cb692be49a04b5fe0a3_tr 122 | - 1958a4139b0dc6d506cfe08e1553199d_tr 123 | - 8228238dcd753647ede755c671c9988b_tr 124 | - fd90f0cdc5f649dd84d4e21ff33b5774_tr 125 | 126 | 127 | ## a69644fd392aad985de61e166dc8e8fd_tr 128 | 129 | ed226cb95f3b637703501d9f4ddd3d68_tr 130 | 131 | 132 | # 18546b56cfde1b046b0372d31f6e7293_tr 133 | 134 | ## f240c90767be4aac25128f5fd8cd5716_tr 135 | 136 | 66c3fee70e0ec54cbbba4c8912c8b1dc_tr 137 | 138 | {% if version <= '2.6' %} 139 | 140 | ### 1b125d3881f320a3e0ebc9f35e8707c5_tr 141 | 142 | aa5072eebea6f4364b4444a8a0f0f868_tr 143 | 144 | {% endif %} 145 | 146 | 147 | ## f98c3b2040ea74375cdaa2759f0b86f7_tr 148 | 149 | ### 33aa6de370798fadf155bbc16f7540c7_tr 150 | 151 | - 709c93c4918ef02a126bec2a7044dca2_tr 152 | - e4429365fc4b359487f20dc8957bdc97_tr 153 | 154 | ### a55e54225390bca0cdaa0c1d0a8d07d4_tr 155 | 156 | d175d8a8a13209341e4a0e3066113b3b_tr 157 | 158 | ### 537e77e8b2aa7aac6607c173439d164e_tr 159 | 160 | 42de416048295492aa089c64b44a4f61_tr 161 | 162 | [1]: http://example.com/ 163 | [c20f2c418bdcd3361f55a9da99810fe1_tr]: http://example.com/ 164 | "[c8e2e649deb2b744127c5fef84f14f46_tr]: http://example.com/" 165 | 166 | 167 | # 47fd1ed4c5a1115c35a6ab8342f0c145_tr 168 | 211ce627f55aff0238a6a4775e9d087a_tr 169 | 170 | 5062613d56978c76710cd0c20d6db2a0_tr 171 | 172 | 7e0a29ea7700b69c207521b160d32086_tr 173 | -------------------------------------------------------------------------------- /openformats/tests/formats/indesign/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/indesign/__init__.py -------------------------------------------------------------------------------- /openformats/tests/formats/indesign/files/sample.idml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/indesign/files/sample.idml -------------------------------------------------------------------------------- /openformats/tests/formats/keyvaluejson/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/keyvaluejson/__init__.py -------------------------------------------------------------------------------- /openformats/tests/formats/keyvaluejson/files/1_el.json: -------------------------------------------------------------------------------- 1 | { 2 | "singular_key": "el:This is a regular string.", 3 | "total_files": "{ item_count, plural, one {el:You have {file_count} file.} other {el:You have {file_count} files.} }", 4 | "special_chars": "{ cnt, plural, one {el:This is Sam's book.} other {el:These are Sam's books.} }", 5 | "gold_coins": "{ count, plural,\n zero {el:The chest is empty.} one {el:You have one gold coin.} other {el:You have {cnt} gold coins.} \n}", 6 | "something": { 7 | "else": "el:Something else", 8 | "is": { 9 | "going_on": "el:Something is going on.", 10 | "wrong": "{ sth, plural, one {el:Something is wrong.} other {el:Somethings are wrong.} }" 11 | } 12 | }, 13 | "custom_plural_value": "el:{number, plural, =1 {1 New}, =2 {# New}}" 14 | } -------------------------------------------------------------------------------- /openformats/tests/formats/keyvaluejson/files/1_en.json: -------------------------------------------------------------------------------- 1 | { 2 | "singular_key": "This is a regular string.", 3 | "total_files": "{ item_count, plural, one {You have {file_count} file.} other {You have {file_count} files.} }", 4 | "special_chars": "{ cnt, plural, one {This is Sam's book.} other {These are Sam's books.} }", 5 | "gold_coins": "{ count, plural,\n zero {The chest is empty.} one {You have one gold coin.} other {You have {cnt} gold coins.} \n}", 6 | "something": { 7 | "else": "Something else", 8 | "is": { 9 | "going_on": "Something is going on.", 10 | "wrong": "{ sth, plural, one {Something is wrong.} other {Somethings are wrong.} }" 11 | } 12 | }, 13 | "custom_plural_value": "{number, plural, =1 {1 New}, =2 {# New}}" 14 | } -------------------------------------------------------------------------------- /openformats/tests/formats/keyvaluejson/files/1_tpl.json: -------------------------------------------------------------------------------- 1 | { 2 | "singular_key": "ab2800d7ccb83ba2d643174531d08e36_tr", 3 | "total_files": "{ item_count, plural, 9af043f75968a7df63dfa2b27c57e2dc_pl }", 4 | "special_chars": "{ cnt, plural, 00fe762ad9b56187fed536350bc3a40c_pl }", 5 | "gold_coins": "{ count, plural,\n 8060865280cf7fc9e80b79145208a825_pl \n}", 6 | "something": { 7 | "else": "27fc99812beff2126b12708a336513c8_tr", 8 | "is": { 9 | "going_on": "38eb89d2562cc44ff2061a045b05ebca_tr", 10 | "wrong": "{ sth, plural, a125aead7129e03261500e0cade68557_pl }" 11 | } 12 | }, 13 | "custom_plural_value": "ee829fd77061d65f1f18982a577b915b_tr" 14 | } -------------------------------------------------------------------------------- /openformats/tests/formats/plaintext/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/plaintext/__init__.py -------------------------------------------------------------------------------- /openformats/tests/formats/plaintext/files/1_el.txt: -------------------------------------------------------------------------------- 1 | Γεια σου, Κόσμε! 2 | -------------------------------------------------------------------------------- /openformats/tests/formats/plaintext/files/1_en.txt: -------------------------------------------------------------------------------- 1 | Hello, World! 2 | -------------------------------------------------------------------------------- /openformats/tests/formats/plaintext/files/1_tpl.txt: -------------------------------------------------------------------------------- 1 | 9a1c7ee2c7ce38d4bbbaf29ab9f2ac1e_tr 2 | -------------------------------------------------------------------------------- /openformats/tests/formats/plaintext/test_plaintext.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from openformats.tests.formats.common import CommonFormatTestMixin 4 | from openformats.formats.plaintext import PlaintextHandler 5 | 6 | 7 | class PlaintextTestCase(CommonFormatTestMixin, unittest.TestCase): 8 | HANDLER_CLASS = PlaintextHandler 9 | TESTFILE_BASE = "openformats/tests/formats/plaintext/files" 10 | -------------------------------------------------------------------------------- /openformats/tests/formats/po/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/po/__init__.py -------------------------------------------------------------------------------- /openformats/tests/formats/po/files/1_el.po: -------------------------------------------------------------------------------- 1 | # 2 | msgid "" 3 | msgstr "" 4 | "Content-Type: text/plain; charset=UTF-8\n" 5 | "Content-Transfer-Encoding: 8bit\n" 6 | "Language: en\n" 7 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 8 | 9 | msgid "StringPlural1" 10 | msgid_plural "StringsPlural1" 11 | msgstr[0] "el:StringPlural1" 12 | msgstr[1] "el:StringsPlural1" 13 | 14 | msgid "String1" 15 | msgstr "el:String1" 16 | 17 | msgid "String2" 18 | msgstr "el:String2" 19 | -------------------------------------------------------------------------------- /openformats/tests/formats/po/files/1_en.po: -------------------------------------------------------------------------------- 1 | # 2 | msgid "" 3 | msgstr "" 4 | "Content-Type: text/plain; charset=UTF-8\n" 5 | "Content-Transfer-Encoding: 8bit\n" 6 | "Language: en\n" 7 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 8 | 9 | msgid "StringPlural1" 10 | msgid_plural "StringsPlural1" 11 | msgstr[0] "StringPlural1" 12 | msgstr[1] "StringsPlural1" 13 | 14 | msgid "String1" 15 | msgstr "String1" 16 | 17 | msgid "String2" 18 | msgstr "String2" 19 | -------------------------------------------------------------------------------- /openformats/tests/formats/po/files/1_tpl.po: -------------------------------------------------------------------------------- 1 | # 2 | msgid "" 3 | msgstr "" 4 | "Content-Type: text/plain; charset=UTF-8\n" 5 | "Content-Transfer-Encoding: 8bit\n" 6 | "Language: en\n" 7 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 8 | 9 | msgid "StringPlural1" 10 | msgid_plural "StringsPlural1" 11 | msgstr[0] "80bee2f9b16a9824c826644368ffd9ce_pl" 12 | 13 | msgid "String1" 14 | msgstr "51dc1dddbb3924993403c41f11f1f772_tr" 15 | 16 | msgid "String2" 17 | msgstr "c51869f7f50cec80b50683ff53bab1ca_tr" 18 | -------------------------------------------------------------------------------- /openformats/tests/formats/pptx/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/pptx/__init__.py -------------------------------------------------------------------------------- /openformats/tests/formats/pptx/files/autofield.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/pptx/files/autofield.pptx -------------------------------------------------------------------------------- /openformats/tests/formats/pptx/files/complex.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/pptx/files/complex.pptx -------------------------------------------------------------------------------- /openformats/tests/formats/pptx/files/graphicFrame_with_text.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/pptx/files/graphicFrame_with_text.pptx -------------------------------------------------------------------------------- /openformats/tests/formats/pptx/files/hello_world.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/pptx/files/hello_world.pptx -------------------------------------------------------------------------------- /openformats/tests/formats/pptx/files/multi.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/pptx/files/multi.pptx -------------------------------------------------------------------------------- /openformats/tests/formats/pptx/files/multi_with_notes.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/pptx/files/multi_with_notes.pptx -------------------------------------------------------------------------------- /openformats/tests/formats/pptx/files/notes.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/pptx/files/notes.pptx -------------------------------------------------------------------------------- /openformats/tests/formats/pptx/files/rtl.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/pptx/files/rtl.pptx -------------------------------------------------------------------------------- /openformats/tests/formats/srt/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/srt/__init__.py -------------------------------------------------------------------------------- /openformats/tests/formats/srt/files/1_el.srt: -------------------------------------------------------------------------------- 1 | 1 2 | 00:01:28,797 --> 00:01:30,297 3 | Γεια σου, Κόσμε! 4 | 5 | 2 6 | 00:01:45,105 --> 00:01:47,940 X:350 Y:240 7 | Pinky: Brain, ρι θες να κάνουμε απόψε; 8 | Brain: Ό,τι και κάθε βράδυ, Pinky: θα κατακτήσουμε τον κόσμο! 9 | 10 | 3 11 | 00:02:45,105 --> 00:02:47,940 12 | el:A phrase with escaped <HTML tags> 13 | 14 | 4 15 | 00:03:45,105 --> 00:03:47,940 16 | el:A phrase with HTML characters 17 | 18 | 5 19 | 00:05:45,105 --> 00:05:47,940 20 | el:A phrase with unicode characters: ΑβΓδΕ → ♡ Ш 21 | 22 | 6 23 | 00:06:45,105 --> 00:06:47,940 24 | el:Three lines: First 25 | Second 26 | Third 27 | -------------------------------------------------------------------------------- /openformats/tests/formats/srt/files/1_en.srt: -------------------------------------------------------------------------------- 1 | 1 2 | 00:01:28,797 --> 00:01:30,297 3 | Hello, World! 4 | 5 | 2 6 | 00:01:45,105 --> 00:01:47,940 X:350 Y:240 7 | Pinky: Gee, Brain, what do you want to do tonight? 8 | Brain: The same thing we do every night, Pinky - try to take over the world! 9 | 10 | 3 11 | 00:02:45,105 --> 00:02:47,940 12 | A phrase with escaped <HTML tags> 13 | 14 | 4 15 | 00:03:45,105 --> 00:03:47,940 16 | A phrase with HTML characters 17 | 18 | 5 19 | 00:05:45,105 --> 00:05:47,940 20 | A phrase with unicode characters: ΑβΓδΕ → ♡ Ш 21 | 22 | 6 23 | 00:06:45,105 --> 00:06:47,940 24 | Three lines: First 25 | Second 26 | Third 27 | -------------------------------------------------------------------------------- /openformats/tests/formats/srt/files/1_tpl.srt: -------------------------------------------------------------------------------- 1 | 1 2 | 00:01:28,797 --> 00:01:30,297 3 | 3afcdbfeb6ecfbdd0ba628696e3cc163_tr 4 | 5 | 2 6 | 00:01:45,105 --> 00:01:47,940 X:350 Y:240 7 | 247730f9d0d2eaad265a470e32aa0cdf_tr 8 | 9 | 3 10 | 00:02:45,105 --> 00:02:47,940 11 | cdee9bf40a070d58d14dfa3bb61e0032_tr 12 | 13 | 4 14 | 00:03:45,105 --> 00:03:47,940 15 | 7693e302dc09b57483d26522ef25feb4_tr 16 | 17 | 5 18 | 00:05:45,105 --> 00:05:47,940 19 | 016ce7cd3f0850d6c33696df9f9440d5_tr 20 | 21 | 6 22 | 00:06:45,105 --> 00:06:47,940 23 | 8572f81f02517e0c1f0d9b93e407697a_tr 24 | -------------------------------------------------------------------------------- /openformats/tests/formats/srt/test_srt.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from openformats.tests.formats.common import CommonFormatTestMixin 4 | from openformats.tests.utils import strip_leading_spaces 5 | from openformats.formats.srt import SrtHandler 6 | 7 | 8 | class SrtTestCase(CommonFormatTestMixin, unittest.TestCase): 9 | HANDLER_CLASS = SrtHandler 10 | TESTFILE_BASE = "openformats/tests/formats/srt/files" 11 | 12 | def test_srt_occurrences(self): 13 | """srt: Test that timings are saved as occurrencies.""" 14 | source = strip_leading_spaces(""" 15 | 1 16 | 00:01:28,797 --> 00:01:30,297 17 | Hello, World! 18 | """) 19 | template, stringset = self.handler.parse(source) 20 | self.assertEqual(stringset[0].occurrences, '00:01:28.797,00:01:30.297') 21 | 22 | def test_missing_order(self): 23 | source = strip_leading_spaces(""" 24 | 00:01:28,797 --> 00:01:30,297 25 | Hello, World! 26 | """) 27 | self._test_parse_error( 28 | source, 29 | "Not enough data on subtitle section on line 2. Order number, " 30 | "timings and subtitle content are needed" 31 | ) 32 | 33 | def test_missing_timings(self): 34 | source = strip_leading_spaces(""" 35 | 1 36 | Hello, World! 37 | """) 38 | self._test_parse_error( 39 | source, 40 | "Not enough data on subtitle section on line 2. Order number, " 41 | "timings and subtitle content are needed" 42 | ) 43 | 44 | def test_missing_string(self): 45 | source = strip_leading_spaces(""" 46 | 1 47 | 00:01:28,797 --> 00:01:30,297 48 | """) 49 | self._test_parse_error( 50 | source, 51 | "Not enough data on subtitle section on line 2. Order number, " 52 | "timings and subtitle content are needed" 53 | ) 54 | 55 | def test_negative_order(self): 56 | source = strip_leading_spaces(""" 57 | -3 58 | 00:01:28,797 --> 00:01:30,297 59 | Hello, World! 60 | """) 61 | self._test_parse_error( 62 | source, 63 | "Order number on line 2 (-3) must be a positive integer" 64 | ) 65 | 66 | def test_non_ascending_order(self): 67 | source = strip_leading_spaces(""" 68 | 2 69 | 00:01:28,797 --> 00:01:30,297 70 | Hello, World! 71 | 72 | 1 73 | 00:04:28,797 --> 00:08:30,297 74 | Goodbye, World! 75 | """) 76 | self._test_parse_error( 77 | source, 78 | "Order numbers must be in ascending order; number in line 6 (1) " 79 | "is wrong" 80 | ) 81 | 82 | def test_wrong_timings(self): 83 | source = strip_leading_spaces(""" 84 | 1 85 | 00:01:28,797 00:01:30,297 86 | Hello, World! 87 | """) 88 | self._test_parse_error( 89 | source, 90 | "Timings on line 3 don't follow '[start] --> [end] (position)' " 91 | "pattern" 92 | ) 93 | 94 | source = strip_leading_spaces(""" 95 | 1 96 | 00:fas28,797 --> 00:01:30,297 97 | Hello, World! 98 | """) 99 | self._test_parse_error( 100 | source, 101 | "Problem with start of timing at line 3: '00:fas28,797'" 102 | ) 103 | 104 | source = strip_leading_spaces(""" 105 | 1 106 | 00:01:28,797 --> 00:ois30,297 107 | Hello, World! 108 | """) 109 | self._test_parse_error( 110 | source, 111 | "Problem with end of timing at line 3: '00:ois30,297'" 112 | ) 113 | -------------------------------------------------------------------------------- /openformats/tests/formats/stringsdict/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/stringsdict/__init__.py -------------------------------------------------------------------------------- /openformats/tests/formats/stringsdict/files/1_el.stringsdict: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ONE 6 | 7 | NSStringLocalizedFormatKey 8 | %1$#@users@ 9 | users 10 | 11 | NSStringFormatSpecTypeKey 12 | NSStringPluralRuleType 13 | NSStringFormatValueTypeKey 14 | d 15 | one 16 | el:%3$@ commented on your post. 17 | other 18 | el:%3$@ and %2$#@more@ commented on your post. 19 | 20 | more 21 | 22 | NSStringFormatSpecTypeKey 23 | NSStringPluralRuleType 24 | NSStringFormatValueTypeKey 25 | d 26 | one 27 | el:%d other 28 | other 29 | el:%d others 30 | 31 | 32 | ANOTHER 33 | 34 | NSStringLocalizedFormatKey 35 | %#@test@ 36 | test 37 | 38 | NSStringFormatSpecTypeKey 39 | NSStringPluralRuleType 40 | NSStringFormatValueTypeKey 41 | d 42 | one 43 | el:A test exist. 44 | other 45 | el:Other tests exist. 46 | 47 | 48 | TEST INLINE REPLACEMENT 49 | 50 | NSStringLocalizedFormatKey 51 | %#@test@ 52 | test 53 | 54 | NSStringFormatSpecTypeKey 55 | NSStringPluralRuleType 56 | NSStringFormatValueTypeKey 57 | d 58 | one 59 | el:There is a mouse in the %#@room@ here 60 | other 61 | el:There are %d mice in the %#@room@ here 62 | 63 | room 64 | 65 | NSStringFormatSpecTypeKey 66 | NSStringPluralRuleType 67 | NSStringFormatValueTypeKey 68 | d 69 | one 70 | el:a room 71 | other 72 | el:%d rooms 73 | 74 | 75 | LAST 76 | 77 | NSStringLocalizedFormatKey 78 | %#@test@ 79 | test 80 | 81 | NSStringFormatSpecTypeKey 82 | NSStringPluralRuleType 83 | NSStringFormatValueTypeKey 84 | d 85 | one 86 | el:• One mouse • 87 | other 88 | el:• %d mice • 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /openformats/tests/formats/stringsdict/files/1_en.stringsdict: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ONE 6 | 7 | NSStringLocalizedFormatKey 8 | %1$#@users@ 9 | users 10 | 11 | NSStringFormatSpecTypeKey 12 | NSStringPluralRuleType 13 | NSStringFormatValueTypeKey 14 | d 15 | one 16 | %3$@ commented on your post. 17 | other 18 | %3$@ and %2$#@more@ commented on your post. 19 | 20 | more 21 | 22 | NSStringFormatSpecTypeKey 23 | NSStringPluralRuleType 24 | NSStringFormatValueTypeKey 25 | d 26 | one 27 | %d other 28 | other 29 | %d others 30 | 31 | 32 | ANOTHER 33 | 34 | NSStringLocalizedFormatKey 35 | %#@test@ 36 | test 37 | 38 | NSStringFormatSpecTypeKey 39 | NSStringPluralRuleType 40 | NSStringFormatValueTypeKey 41 | d 42 | one 43 | A test exist. 44 | other 45 | Other tests exist. 46 | 47 | 48 | TEST INLINE REPLACEMENT 49 | 50 | NSStringLocalizedFormatKey 51 | There %#@test@ in the %#@room@ here 52 | test 53 | 54 | NSStringFormatSpecTypeKey 55 | NSStringPluralRuleType 56 | NSStringFormatValueTypeKey 57 | d 58 | one 59 | is a mouse 60 | other 61 | are %d mice 62 | 63 | room 64 | 65 | NSStringFormatSpecTypeKey 66 | NSStringPluralRuleType 67 | NSStringFormatValueTypeKey 68 | d 69 | one 70 | a room 71 | other 72 | %d rooms 73 | 74 | 75 | LAST 76 | 77 | NSStringLocalizedFormatKey 78 | %#@test@ • 79 | test 80 | 81 | NSStringFormatSpecTypeKey 82 | NSStringPluralRuleType 83 | NSStringFormatValueTypeKey 84 | d 85 | one 86 | • One mouse 87 | other 88 | • %d mice 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /openformats/tests/formats/stringsdict/files/1_tpl.stringsdict: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ONE 6 | 7 | NSStringLocalizedFormatKey 8 | %1$#@users@ 9 | users 10 | 11 | NSStringFormatSpecTypeKey 12 | NSStringPluralRuleType 13 | NSStringFormatValueTypeKey 14 | d 15 | 16 | f843d61580031d5c9e3456f392126420_pl 17 | 18 | more 19 | 20 | NSStringFormatSpecTypeKey 21 | NSStringPluralRuleType 22 | NSStringFormatValueTypeKey 23 | d 24 | 25 | 90a5962ec9bdddfb84adfe9cc1b6bba1_pl 26 | 27 | 28 | ANOTHER 29 | 30 | NSStringLocalizedFormatKey 31 | %#@test@ 32 | test 33 | 34 | NSStringFormatSpecTypeKey 35 | NSStringPluralRuleType 36 | NSStringFormatValueTypeKey 37 | d 38 | 39 | ec8a7a8b22906062781b0aacccacfba3_pl 40 | 41 | 42 | TEST INLINE REPLACEMENT 43 | 44 | NSStringLocalizedFormatKey 45 | %#@test@ 46 | test 47 | 48 | NSStringFormatSpecTypeKey 49 | NSStringPluralRuleType 50 | NSStringFormatValueTypeKey 51 | d 52 | 53 | f2173da507415303a5346c293fa086bf_pl 54 | 55 | room 56 | 57 | NSStringFormatSpecTypeKey 58 | NSStringPluralRuleType 59 | NSStringFormatValueTypeKey 60 | d 61 | 62 | 5941243ff132731d2655e82adf787f14_pl 63 | 64 | 65 | LAST 66 | 67 | NSStringLocalizedFormatKey 68 | %#@test@ 69 | test 70 | 71 | NSStringFormatSpecTypeKey 72 | NSStringPluralRuleType 73 | NSStringFormatValueTypeKey 74 | d 75 | 76 | 383613b398803eb778f9abac0e8808f0_pl 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /openformats/tests/formats/structuredkeyvaluejson/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/structuredkeyvaluejson/__init__.py -------------------------------------------------------------------------------- /openformats/tests/formats/structuredkeyvaluejson/files/1_el.json: -------------------------------------------------------------------------------- 1 | { 2 | "singular_key": { 3 | "string": "el:This is a regular string." 4 | }, 5 | "total_files": { 6 | "string": "{ item_count, plural, one {el:You have {file_count} file.} other {el:You have {file_count} files.} }", 7 | "context": "Noun" 8 | }, 9 | "special_chars": { 10 | "string": "{ cnt, plural, one {el:This is Sam's book.} other {el:These are Sam's books.} }", 11 | "developer_comment": "This is a string with special characters" 12 | }, 13 | "gold_coins": { 14 | "string": "{ count, plural,\n zero {el:The chest is empty.} one {el:You have one gold coin.} other {el:You have {cnt} gold coins.} \n}", 15 | "character_limit": 150 16 | }, 17 | "something": { 18 | "else": { 19 | "string": "el:Something else" 20 | }, 21 | "is": { 22 | "going_on": { 23 | "string": "el:Something is going on." 24 | }, 25 | "wrong": { 26 | "string": "{ sth, plural, one {el:Something is wrong.} other {el:Somethings are wrong.} }" 27 | } 28 | } 29 | }, 30 | "parent_key": { 31 | "child_key": { 32 | "string": "el:This is a key from a child", 33 | "context": "Noun", 34 | "developer_comment": "This is a comment.", 35 | "character_limit": 150 36 | } 37 | }, 38 | "custom_plural_value": { 39 | "string": "el:{number, plural, =1 {1 New}, =2 {# New}}" 40 | }, 41 | "json_list": ["This is a regular string", 42 | "This is a another string", 43 | "This is the final string"] 44 | } -------------------------------------------------------------------------------- /openformats/tests/formats/structuredkeyvaluejson/files/1_en.json: -------------------------------------------------------------------------------- 1 | { 2 | "singular_key": { 3 | "string": "This is a regular string." 4 | }, 5 | "total_files": { 6 | "string": "{ item_count, plural, one {You have {file_count} file.} other {You have {file_count} files.} }", 7 | "context": "Noun" 8 | }, 9 | "special_chars": { 10 | "string": "{ cnt, plural, one {This is Sam's book.} other {These are Sam's books.} }", 11 | "developer_comment": "This is a string with special characters" 12 | }, 13 | "gold_coins": { 14 | "string": "{ count, plural,\n zero {The chest is empty.} one {You have one gold coin.} other {You have {cnt} gold coins.} \n}", 15 | "character_limit": 150 16 | }, 17 | "something": { 18 | "else": { 19 | "string": "Something else" 20 | }, 21 | "is": { 22 | "going_on": { 23 | "string": "Something is going on." 24 | }, 25 | "wrong": { 26 | "string": "{ sth, plural, one {Something is wrong.} other {Somethings are wrong.} }" 27 | } 28 | } 29 | }, 30 | "parent_key": { 31 | "child_key": { 32 | "string": "This is a key from a child", 33 | "context": "Noun", 34 | "developer_comment": "This is a comment.", 35 | "character_limit": 150 36 | } 37 | }, 38 | "custom_plural_value": { 39 | "string": "{number, plural, =1 {1 New}, =2 {# New}}" 40 | }, 41 | "json_list": ["This is a regular string", 42 | "This is a another string", 43 | "This is the final string"] 44 | } -------------------------------------------------------------------------------- /openformats/tests/formats/structuredkeyvaluejson/files/1_tpl.json: -------------------------------------------------------------------------------- 1 | { 2 | "singular_key": { 3 | "string": "ab2800d7ccb83ba2d643174531d08e36_tr" 4 | }, 5 | "total_files": { 6 | "string": "{ item_count, plural, 4d0dfbc44762841c0cec988d1e18b4a6_pl }", 7 | "context": "Noun" 8 | }, 9 | "special_chars": { 10 | "string": "{ cnt, plural, 00fe762ad9b56187fed536350bc3a40c_pl }", 11 | "developer_comment": "This is a string with special characters" 12 | }, 13 | "gold_coins": { 14 | "string": "{ count, plural,\n 8060865280cf7fc9e80b79145208a825_pl \n}", 15 | "character_limit": 150 16 | }, 17 | "something": { 18 | "else": { 19 | "string": "27fc99812beff2126b12708a336513c8_tr" 20 | }, 21 | "is": { 22 | "going_on": { 23 | "string": "38eb89d2562cc44ff2061a045b05ebca_tr" 24 | }, 25 | "wrong": { 26 | "string": "{ sth, plural, a125aead7129e03261500e0cade68557_pl }" 27 | } 28 | } 29 | }, 30 | "parent_key": { 31 | "child_key": { 32 | "string": "aa1b6a1fd1c606a100f686af700ed34c_tr", 33 | "context": "Noun", 34 | "developer_comment": "This is a comment.", 35 | "character_limit": 150 36 | } 37 | }, 38 | "custom_plural_value": { 39 | "string": "ee829fd77061d65f1f18982a577b915b_tr" 40 | }, 41 | "json_list": ["This is a regular string", 42 | "This is a another string", 43 | "This is the final string"] 44 | } -------------------------------------------------------------------------------- /openformats/tests/formats/vtt/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/vtt/__init__.py -------------------------------------------------------------------------------- /openformats/tests/formats/vtt/files/1_el.vtt: -------------------------------------------------------------------------------- 1 | WEBVTT 2 | 3 | STYLE here 4 | some long, 5 | long style 6 | 7 | 1 8 | 00:01:28.797 --> 00:01:30.297 9 | Γεια σου, Κόσμε! 10 | 11 | NOTE some note 12 | here 13 | 14 | 2 15 | 00:01:45.105 --> 00:01:47.940 X:350 Y:240 16 | Pinky: Brain, ρι θες να κάνουμε απόψε; 17 | Brain: Ό,τι και κάθε βράδυ, Pinky: θα κατακτήσουμε τον κόσμο! 18 | 19 | 3 20 | 00:02:45.105 --> 00:02:47.940 21 | el:A phrase with escaped <HTML tags> 22 | 23 | 4 24 | 00:03:45.105 --> 00:03:47.940 25 | el:A phrase with HTML characters 26 | 27 | 5 28 | 00:05:45.105 --> 00:05:47.940 29 | el:A phrase with unicode characters: ΑβΓδΕ → ♡ Ш 30 | 31 | 6 32 | 00:06:45.105 --> 00:06:47.940 33 | el:Three lines: First 34 | Second 35 | Third 36 | -------------------------------------------------------------------------------- /openformats/tests/formats/vtt/files/1_en.vtt: -------------------------------------------------------------------------------- 1 | WEBVTT 2 | 3 | STYLE here 4 | some long, 5 | long style 6 | 7 | 1 8 | 00:01:28.797 --> 00:01:30.297 9 | Hello, World! 10 | 11 | NOTE some note 12 | here 13 | 14 | 2 15 | 00:01:45.105 --> 00:01:47.940 X:350 Y:240 16 | Pinky: Gee, Brain, what do you want to do tonight? 17 | Brain: The same thing we do every night, Pinky - try to take over the world! 18 | 19 | 3 20 | 00:02:45.105 --> 00:02:47.940 21 | A phrase with escaped <HTML tags> 22 | 23 | 4 24 | 00:03:45.105 --> 00:03:47.940 25 | A phrase with HTML characters 26 | 27 | 5 28 | 00:05:45.105 --> 00:05:47.940 29 | A phrase with unicode characters: ΑβΓδΕ → ♡ Ш 30 | 31 | 6 32 | 00:06:45.105 --> 00:06:47.940 33 | Three lines: First 34 | Second 35 | Third 36 | -------------------------------------------------------------------------------- /openformats/tests/formats/vtt/files/1_tpl.vtt: -------------------------------------------------------------------------------- 1 | WEBVTT 2 | 3 | STYLE here 4 | some long, 5 | long style 6 | 7 | 1 8 | 00:01:28.797 --> 00:01:30.297 9 | c386a46eaaa5ecd18e760683c3e36987_tr 10 | 11 | NOTE some note 12 | here 13 | 14 | 2 15 | 00:01:45.105 --> 00:01:47.940 X:350 Y:240 16 | f3736d657f04cedbb1eefd07e7fb4e53_tr 17 | 18 | 3 19 | 00:02:45.105 --> 00:02:47.940 20 | 12a3c29d1c2ead6744096c2bcf5cb5a0_tr 21 | 22 | 4 23 | 00:03:45.105 --> 00:03:47.940 24 | 32189023ec2e2af1c96ff6e50889a8e5_tr 25 | 26 | 5 27 | 00:05:45.105 --> 00:05:47.940 28 | df27c645bb92280c825e3e1c94a3f0b8_tr 29 | 30 | 6 31 | 00:06:45.105 --> 00:06:47.940 32 | 22394ab09ce61d63e1f9d56ef64c4e40_tr 33 | -------------------------------------------------------------------------------- /openformats/tests/formats/vtt/test_vtt.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from openformats.formats.vtt import VttHandler 4 | from openformats.tests.formats.common import CommonFormatTestMixin 5 | from openformats.tests.utils import strip_leading_spaces 6 | 7 | 8 | class VttTestCase(CommonFormatTestMixin, unittest.TestCase): 9 | HANDLER_CLASS = VttHandler 10 | TESTFILE_BASE = "openformats/tests/formats/vtt/files" 11 | 12 | def test_vtt_metadata(self): 13 | """vtt: Test that metadata is included in template but not included in stringset.""" 14 | source = strip_leading_spaces( 15 | """WEBVTT 16 | 17 | STYLE 18 | ::cue(v) { 19 | color: red; 20 | } 21 | 22 | REGION 23 | id:fred 24 | width:40% 25 | 26 | 1 27 | 00:01:28.797 --> 00:01:30.297 28 | Hello, World! 29 | 30 | NOTE want this test to pass 31 | """ 32 | ) 33 | template, stringset = self.handler.parse(source) 34 | for str in stringset: 35 | s = str.string 36 | self.assertFalse( 37 | "WEBVTT" in s or "STYLE" in s or "REGION" in s or "NOTE" in s, 38 | "Metadata should not be present in stringset!", 39 | ) 40 | break 41 | self.assertIn("WEBVTT", template) 42 | self.assertIn("STYLE", template) 43 | self.assertIn("REGION", template) 44 | self.assertIn("NOTE", template) 45 | 46 | source = strip_leading_spaces( 47 | """ 48 | 00:01:28.797 --> 00:01:30.297 49 | Check the first line 50 | """ 51 | ) 52 | self._test_parse_error(source, "VTT file should start with 'WEBVTT'!") 53 | 54 | def test_vtt_occurrences(self): 55 | """vtt: Test that timings are saved as occurrencies.""" 56 | source = strip_leading_spaces( 57 | """WEBVTT 58 | 59 | 1 60 | 00:01:28.797 --> 00:01:30.297 61 | Hello, World! 62 | """ 63 | ) 64 | _, stringset = self.handler.parse(source) 65 | self.assertEqual(stringset[0].occurrences, "00:01:28.797,00:01:30.297") 66 | 67 | def test_empty_subtitle(self): 68 | source = strip_leading_spaces( 69 | """WEBVTT 70 | 71 | 1 72 | 00:01:28.797 --> 00:01:30.297 73 | """ 74 | ) 75 | self._test_parse_error( 76 | source, "We are not able to extract any strings from the file" 77 | ) 78 | 79 | def test_full_and_short_timings(self): 80 | source = strip_leading_spaces( 81 | """WEBVTT 82 | 83 | 00:01:28.797 --> 00:01:30.297 84 | Full timings hh:mm:ss.fff 85 | 86 | 01:28.797 --> 01:30.297 87 | Short timings mm:ss.fff 88 | 89 | 28.797 --> 30.297 90 | Abnormal timings format ss.fff 91 | """ 92 | ) 93 | self._test_parse_error(source, "Unexpected timing format on line 11") 94 | 95 | def test_wrong_timings(self): 96 | source = strip_leading_spaces( 97 | """WEBVTT 98 | 99 | 1 100 | 00:01:28.797 ---> 00:01:30.297 101 | Hello, World! 102 | """ 103 | ) 104 | self._test_parse_error( 105 | source, 106 | "Timings on line 4 don't follow '[start] --> [end] (position)' " "pattern", 107 | ) 108 | 109 | source = strip_leading_spaces( 110 | """WEBVTT 111 | 112 | 1 113 | 00:fas28.797 --> 00:01:30.297 114 | Hello, World! 115 | """ 116 | ) 117 | self._test_parse_error( 118 | source, "Problem with start of timing at line 4: '00:fas28.797'" 119 | ) 120 | 121 | source = strip_leading_spaces( 122 | """WEBVTT 123 | 124 | 1 125 | 00:01:28.797 --> 00:ois30.297 126 | Hello, World! 127 | """ 128 | ) 129 | self._test_parse_error( 130 | source, "Problem with end of timing at line 4: '00:ois30.297'" 131 | ) 132 | -------------------------------------------------------------------------------- /openformats/tests/formats/xlsx_unstructured/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/xlsx_unstructured/__init__.py -------------------------------------------------------------------------------- /openformats/tests/formats/xlsx_unstructured/files/example.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/xlsx_unstructured/files/example.xlsx -------------------------------------------------------------------------------- /openformats/tests/formats/yaml/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/yaml/__init__.py -------------------------------------------------------------------------------- /openformats/tests/formats/yaml/files/1_el.yml: -------------------------------------------------------------------------------- 1 | --- 2 | title: "el:About writing and formatting on GitHub" 3 | intro: "el:into text" 4 | numeric_var: 12.5 5 | 6 | # one comment 7 | 8 | key1: 9 | - list_key: 10 | - "el:li within li" 11 | # second comment 12 | - "el:li within li 2" 13 | - "el:li 2" 14 | key2: 15 | - object_within_list: "el:value" 16 | - "el:text with a #hashtag in it" 17 | - "el:li 5" 18 | - 3.5 19 | - true 20 | 21 | description: > 22 | el:folded style text 23 | custom_vars: 24 | var1: "el:text: some value" 25 | var2: | 26 | el:literal 27 | style with "quotes" 28 | text 29 | nested_key_outer: 30 | nested_key_inner: 31 | "el:nested_value" 32 | 33 | key.with.dots: "el:dot value" 34 | 35 | 1: "el:integer key" 36 | 37 | empty_value: '' 38 | 39 | single_quoted_value: 'el:single quoted value' 40 | value with quote: "el:'value" 41 | value with start backtick: "el:`value" 42 | value with end backtick: "el:value`" 43 | 44 | : "el:value" 45 | "[weird_key]": "el:value" 46 | 47 | true: "el:boolean key" 48 | 49 | double_list: 50 | - - "el:test" 51 | 52 | simple_flow_list: ["el:one", 'el:two', "el:three"] 53 | double_flow_list: [[ "el:test" ]] 54 | 55 | binary_value: !!binary | 56 | R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J+fn5 57 | OTk6enp56enmlpaWNjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++f/++f/+ 58 | +f/++f/++f/++f/++f/++SH+Dk1hZGUgd2l0aCBHSU1QACwAAAAADAAMAAAFLC 59 | AgjoEwnuNAFOhpEMTRiggcz4BNJHrv/zCFcLiwMWYNG84BwwEeECcgggoBADs= 60 | 61 | # complex key syntax 62 | ? - Manchester United 63 | - Real Madrid 64 | : [ 2001-01-01, 2002-02-02 ] 65 | 66 | # anchors and aliases 67 | anchor_key: &an_anchor 68 | - "el:value" 69 | alias_key: 70 | - *an_anchor 71 | - "el:another true value" 72 | 73 | # Custom tags 74 | foo: !test "el:bar" # Should treat as string and ignore leading spaces 75 | bar: !xml "el:foo bar" # Also a string 76 | hello: "el:World" # Translatable 77 | number: !!int 123 # Should ignore 78 | bin: !!binary aGVsbG8= # Should ignore 79 | 80 | # Custom tags with numbers and symbols 81 | context_string: !cs:fd-94_fd.dot. "el:context string" 82 | verbim_context_string: !context:t5-46_qa "el:verbim context string" 83 | context_on_nested_map: 84 | first: !first_context:54KJFLA95KJ4 "el:context in nested map" 85 | second: !second_context:FDKJ40DK "el:context in nested map" 86 | 87 | # Test with non-ASCII keys 88 | #σχόλιο 89 | σχόλιο: "el:κείμενο" 90 | emojis: "el:\\uD83D\\uDC40 \U0001F3F9\U0001F34D\U0001F34D \\U0001F418" 91 | 92 | anchor_with_label: 93 | test: "el:something" 94 | anchor_test: &another_anchor "el:this is another anchor with alias and label - value1 - value2" 95 | testing_alias: 96 | - *another_anchor 97 | - *an_anchor 98 | - "el:something else" 99 | -------------------------------------------------------------------------------- /openformats/tests/formats/yaml/files/1_en.yml: -------------------------------------------------------------------------------- 1 | --- 2 | title: About writing and formatting on GitHub 3 | intro: into text 4 | numeric_var: 12.5 5 | 6 | # one comment 7 | 8 | key1: 9 | - list_key: 10 | - li within li 11 | # second comment 12 | - li within li 2 13 | - li 2 14 | key2: 15 | - object_within_list: value 16 | - "text with a #hashtag in it" 17 | - li 5 18 | - 3.5 19 | - true 20 | 21 | description: > 22 | folded 23 | style 24 | text 25 | custom_vars: 26 | var1: "text: some value" 27 | var2: | 28 | literal 29 | style with "quotes" 30 | text 31 | 32 | nested_key_outer: 33 | nested_key_inner: 34 | nested_value 35 | 36 | key.with.dots: dot value 37 | 38 | 1: integer key 39 | 40 | empty_value: '' 41 | 42 | single_quoted_value: 'single quoted value' 43 | value with quote: "'value" 44 | value with start backtick: "`value" 45 | value with end backtick: "value`" 46 | 47 | : value 48 | "[weird_key]": value 49 | 50 | true: boolean key 51 | 52 | double_list: 53 | - - test 54 | 55 | simple_flow_list: ["one", 'two', three] 56 | double_flow_list: [[ test ]] 57 | 58 | binary_value: !!binary | 59 | R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J+fn5 60 | OTk6enp56enmlpaWNjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++f/++f/+ 61 | +f/++f/++f/++f/++f/++SH+Dk1hZGUgd2l0aCBHSU1QACwAAAAADAAMAAAFLC 62 | AgjoEwnuNAFOhpEMTRiggcz4BNJHrv/zCFcLiwMWYNG84BwwEeECcgggoBADs= 63 | 64 | # complex key syntax 65 | ? - Manchester United 66 | - Real Madrid 67 | : [ 2001-01-01, 2002-02-02 ] 68 | 69 | # anchors and aliases 70 | anchor_key: &an_anchor 71 | - value 72 | alias_key: 73 | - *an_anchor 74 | - "another true value" 75 | 76 | # Custom tags 77 | foo: !test bar # Should treat as string and ignore leading spaces 78 | bar: !xml "foo bar" # Also a string 79 | hello: !!str World # Translatable 80 | number: !!int 123 # Should ignore 81 | bin: !!binary aGVsbG8= # Should ignore 82 | 83 | # Custom tags with numbers and symbols 84 | context_string: !cs:fd-94_fd.dot. "context string" 85 | verbim_context_string: ! "verbim context string" 86 | context_on_nested_map: 87 | first: !first_context:54KJFLA95KJ4 "context in nested map" 88 | second: !second_context:FDKJ40DK "context in nested map" 89 | 90 | # Test with non-ASCII keys 91 | #σχόλιο 92 | σχόλιο: κείμενο 93 | emojis: \uD83D\uDC40 🏹🍍🍍 \U0001F418 94 | 95 | anchor_with_label: 96 | test: something 97 | anchor_test: &another_anchor this is another anchor with alias and label 98 | - value1 99 | - value2 100 | testing_alias: 101 | - *another_anchor 102 | - *an_anchor 103 | - "something else" 104 | -------------------------------------------------------------------------------- /openformats/tests/formats/yaml/files/1_en_exported.yml: -------------------------------------------------------------------------------- 1 | --- 2 | title: About writing and formatting on GitHub 3 | intro: into text 4 | numeric_var: 12.5 5 | 6 | # one comment 7 | 8 | key1: 9 | - list_key: 10 | - li within li 11 | # second comment 12 | - li within li 2 13 | - li 2 14 | key2: 15 | - object_within_list: value 16 | - "text with a #hashtag in it" 17 | - li 5 18 | - 3.5 19 | - true 20 | 21 | description: > 22 | folded style text 23 | custom_vars: 24 | var1: "text: some value" 25 | var2: | 26 | literal 27 | style with "quotes" 28 | text 29 | nested_key_outer: 30 | nested_key_inner: 31 | nested_value 32 | 33 | key.with.dots: dot value 34 | 35 | 1: integer key 36 | 37 | empty_value: '' 38 | 39 | single_quoted_value: 'single quoted value' 40 | value with quote: "'value" 41 | value with start backtick: "`value" 42 | value with end backtick: "value`" 43 | 44 | : value 45 | "[weird_key]": value 46 | 47 | true: boolean key 48 | 49 | double_list: 50 | - - test 51 | 52 | simple_flow_list: ["one", 'two', three] 53 | double_flow_list: [[ test ]] 54 | 55 | binary_value: !!binary | 56 | R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J+fn5 57 | OTk6enp56enmlpaWNjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++f/++f/+ 58 | +f/++f/++f/++f/++f/++SH+Dk1hZGUgd2l0aCBHSU1QACwAAAAADAAMAAAFLC 59 | AgjoEwnuNAFOhpEMTRiggcz4BNJHrv/zCFcLiwMWYNG84BwwEeECcgggoBADs= 60 | 61 | # complex key syntax 62 | ? - Manchester United 63 | - Real Madrid 64 | : [ 2001-01-01, 2002-02-02 ] 65 | 66 | # anchors and aliases 67 | anchor_key: &an_anchor 68 | - value 69 | alias_key: 70 | - *an_anchor 71 | - "another true value" 72 | 73 | # Custom tags 74 | foo: !test bar # Should treat as string and ignore leading spaces 75 | bar: !xml "foo bar" # Also a string 76 | hello: World # Translatable 77 | number: !!int 123 # Should ignore 78 | bin: !!binary aGVsbG8= # Should ignore 79 | 80 | # Custom tags with numbers and symbols 81 | context_string: !cs:fd-94_fd.dot. "context string" 82 | verbim_context_string: !context:t5-46_qa "verbim context string" 83 | context_on_nested_map: 84 | first: !first_context:54KJFLA95KJ4 "context in nested map" 85 | second: !second_context:FDKJ40DK "context in nested map" 86 | 87 | # Test with non-ASCII keys 88 | #σχόλιο 89 | σχόλιο: κείμενο 90 | emojis: \uD83D\uDC40 🏹🍍🍍 \U0001F418 91 | 92 | anchor_with_label: 93 | test: something 94 | anchor_test: &another_anchor this is another anchor with alias and label - value1 - value2 95 | testing_alias: 96 | - *another_anchor 97 | - *an_anchor 98 | - "something else" 99 | -------------------------------------------------------------------------------- /openformats/tests/formats/yaml/files/1_en_exported_without_template.yml: -------------------------------------------------------------------------------- 1 | title: About writing and formatting on GitHub 2 | intro: into text 3 | key1: 4 | - list_key: 5 | - li within li 6 | - li within li 2 7 | - li 2 8 | key2: 9 | - object_within_list: value 10 | - "text with a #hashtag in it" 11 | - li 5 12 | description: > 13 | folded style text 14 | custom_vars: 15 | var1: "text: some value" 16 | var2: | 17 | literal 18 | style with "quotes" 19 | text 20 | nested_key_outer: 21 | nested_key_inner: nested_value 22 | key.with.dots: dot value 23 | 1: integer key 24 | single_quoted_value: 'single quoted value' 25 | value with quote: "'value" 26 | value with start backtick: "`value" 27 | value with end backtick: "value`" 28 | : value 29 | '[weird_key]': value 30 | 'true': boolean key 31 | double_list: 32 | - - test 33 | simple_flow_list: ["one", 'two', three] 34 | double_flow_list: [[test]] 35 | anchor_key: 36 | - value 37 | alias_key: 38 | - "another true value" 39 | foo: !test 'bar' 40 | bar: !xml "foo bar" 41 | hello: World 42 | context_string: !cs:fd-94_fd.dot. "context string" 43 | verbim_context_string: !context:t5-46_qa "verbim context string" 44 | context_on_nested_map: 45 | first: "context in nested map" 46 | second: "context in nested map" 47 | σχόλιο: κείμενο 48 | emojis: \uD83D\uDC40 🏹🍍🍍 \U0001F418 49 | anchor_with_label: 50 | test: something 51 | anchor_test: this is another anchor with alias and label - value1 - value2 52 | testing_alias: 53 | - "something else" 54 | -------------------------------------------------------------------------------- /openformats/tests/formats/yaml/files/1_tpl.yml: -------------------------------------------------------------------------------- 1 | --- 2 | title: 6391362253bdac99fff7bf50ff014be3_tr 3 | intro: e8c07422d0167fee3ecf8f3375ea5739_tr 4 | numeric_var: 12.5 5 | 6 | # one comment 7 | 8 | key1: 9 | - list_key: 10 | - ac56af108b4b69b867c09f8542a51cb2_tr 11 | # second comment 12 | - d2d20cce249c5d7e8ca96b9e30a3349c_tr 13 | - 17e2086737d2192e6665ea2c0f0b1e9a_tr 14 | key2: 15 | - object_within_list: 1a397fe7999e432441bfce7a1f4fb1f3_tr 16 | - aa8c6113a4236d576f96ba9b0bd4dfa4_tr 17 | - 76d7c5ccc4301f10b39115b2c1a9ca47_tr 18 | - 3.5 19 | - true 20 | 21 | description: fdba4efe4ab9d38afee4054a7fa06e29_trcustom_vars: 22 | var1: 8b9f5508bd423a15a188813d60e52946_tr 23 | var2: 7169ba1087c5204f12ab8b7c066884a2_trnested_key_outer: 24 | nested_key_inner: 25 | 94b99ee5f1d082d7df28c4b48aa99ff8_tr 26 | 27 | key.with.dots: ddbc5d72cdd64acc258ab3446b6466ed_tr 28 | 29 | 1: 9354e4f807073d6a146138fe6d81e4ef_tr 30 | 31 | empty_value: '' 32 | 33 | single_quoted_value: 710e606150bfb87b477674e886495384_tr 34 | value with quote: 4ea9d14bca65fb09e8b7c2c54ce8fd4e_tr 35 | value with start backtick: 05cecdb72a9f3d915c749bba5295901c_tr 36 | value with end backtick: 109530f343dfa59480e0e3aa11816cbb_tr 37 | 38 | : 02e8cfaf248aa25e0664be1dddfa5b65_tr 39 | "[weird_key]": c12d8df4b08d0ff07eb311ed29e660f4_tr 40 | 41 | true: 908f519fabea6e6ad3f99d4899185a61_tr 42 | 43 | double_list: 44 | - - 006d8de981d80ed57fa20420e4907aa3_tr 45 | 46 | simple_flow_list: [b6379afadb376e6d4b78e489417b5289_tr, e3a178abc462683f3bf527f7c2bdaeaa_tr, 68bc9de575a69a2a8ddeec8a1842d078_tr] 47 | double_flow_list: [[ 5b881f08c3f024c9f277ffec9a22f713_tr ]] 48 | 49 | binary_value: !!binary | 50 | R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J+fn5 51 | OTk6enp56enmlpaWNjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++f/++f/+ 52 | +f/++f/++f/++f/++f/++SH+Dk1hZGUgd2l0aCBHSU1QACwAAAAADAAMAAAFLC 53 | AgjoEwnuNAFOhpEMTRiggcz4BNJHrv/zCFcLiwMWYNG84BwwEeECcgggoBADs= 54 | 55 | # complex key syntax 56 | ? - Manchester United 57 | - Real Madrid 58 | : [ 2001-01-01, 2002-02-02 ] 59 | 60 | # anchors and aliases 61 | anchor_key: &an_anchor 62 | - 8dc0ec22f35b3c7255b928e03104d714_tr 63 | alias_key: 64 | - *an_anchor 65 | - ddc3cfcedcf1686d9e3ba6b99a0d091b_tr 66 | 67 | # Custom tags 68 | foo: 023503fb466f78932b77209b6581156e_tr # Should treat as string and ignore leading spaces 69 | bar: fac8140a2ec031af14bc758e73f59017_tr # Also a string 70 | hello: b0ed9cf22c0a5186d1c5b483a910dd33_tr # Translatable 71 | number: !!int 123 # Should ignore 72 | bin: !!binary aGVsbG8= # Should ignore 73 | 74 | # Custom tags with numbers and symbols 75 | context_string: 17d854ee46fe3d3bc91fadb4cf4df426_tr 76 | verbim_context_string: 0a3c1ae205b2c0d09ab69ab540e481f2_tr 77 | context_on_nested_map: 78 | first: 95175e30d6fbfe0f658e75919cd4982c_tr 79 | second: dce391c231a7ad7fef9e6cc0ddcbf549_tr 80 | 81 | # Test with non-ASCII keys 82 | #σχόλιο 83 | σχόλιο: 8687f34a9bf89770b456b48ad2395788_tr 84 | emojis: 8f9f68d76af0fa4a5e4830530618e55c_tr 85 | 86 | anchor_with_label: 87 | test: 35816ec55d0782ae231c9910897bc467_tr 88 | anchor_test: &another_anchor 345e2c2ed4e035c68da1a8377a4d4315_tr 89 | testing_alias: 90 | - *another_anchor 91 | - *an_anchor 92 | - bf9a733efd3e3dd9470b532dca9303cb_tr 93 | -------------------------------------------------------------------------------- /openformats/tests/formats/yaml/test_utils.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from collections import OrderedDict 3 | 4 | import six 5 | 6 | from mock import MagicMock 7 | from openformats.formats.yaml.utils import YamlGenerator 8 | from openformats.formats.yaml.yaml_representee_classes import (BlockList, 9 | FlowList, 10 | FlowStyleOrderedDict, 11 | double_quoted_unicode, 12 | single_quoted_unicode) 13 | 14 | 15 | class YamlGeneratorTestCase(unittest.TestCase): 16 | 17 | def test_insert_translation_in_dict_empty_parent(self): 18 | keys = ["one", "two", "[0]"] 19 | flags = "block:block:'".split(':') 20 | translation_string = "test" 21 | result = OrderedDict() 22 | 23 | YamlGenerator(MagicMock())._insert_translation_in_dict( 24 | result, keys, flags, translation_string 25 | ) 26 | # produced result 27 | # OrderedDict([ 28 | # (u'one', OrderedDict([ 29 | # (u'two', BlockList([ 30 | # single_quoted_unicode(u'test') 31 | # ])) 32 | # ])) 33 | # ]) 34 | self.assertListEqual(list(six.iterkeys(result)), ['one']) 35 | self.assertIsInstance(result['one'], OrderedDict) 36 | self.assertIsInstance(result['one']['two'], BlockList) 37 | self.assertIsInstance(result['one']['two'][0], single_quoted_unicode) 38 | 39 | def test_insert_translation_in_dict_non_empty_parent(self): 40 | result = OrderedDict() 41 | result['one'] = OrderedDict() 42 | result['one']['three'] = 'a string' 43 | keys = ["one", "two", "[0]"] 44 | 45 | flags = "block:block:'".split(':') 46 | translation_string = "test" 47 | 48 | YamlGenerator(MagicMock())._insert_translation_in_dict( 49 | result, keys, flags, translation_string 50 | ) 51 | # produced result 52 | # OrderedDict([ 53 | # (u'one', OrderedDict([ 54 | # (u'three', 'a string'), 55 | # (u'two', BlockList([ 56 | # single_quoted_unicode(u'test') 57 | # ])) 58 | # ])) 59 | # ]) 60 | self.assertListEqual(list(six.iterkeys(result)), ['one']) 61 | self.assertListEqual(list(six.iterkeys(result['one'])), 62 | ['three', 'two']) 63 | self.assertIsInstance(result['one']['two'], BlockList) 64 | self.assertIsInstance(result['one']['two'][0], single_quoted_unicode) 65 | 66 | def test_insert_translation_in_dict_flow_list(self): 67 | result = OrderedDict() 68 | keys = ["one", "two", "[0]"] 69 | flags = "block:flow:\"".split(':') 70 | translation_string = "test" 71 | 72 | YamlGenerator(MagicMock())._insert_translation_in_dict( 73 | result, keys, flags, translation_string 74 | ) 75 | # produced result 76 | # OrderedDict([ 77 | # (u'one', OrderedDict([ 78 | # (u'two', FlowList([ 79 | # double_quoted_unicode(u'test') 80 | # ])) 81 | # ])) 82 | # ]) 83 | self.assertListEqual(list(six.iterkeys(result)), ['one']) 84 | self.assertIsInstance(result['one'], OrderedDict) 85 | self.assertIsInstance(result['one']['two'], FlowList) 86 | self.assertIsInstance(result['one']['two'][0], double_quoted_unicode) 87 | 88 | def test_insert_translation_in_dict_flow_dict(self): 89 | result = OrderedDict() 90 | keys = ["one", "two"] 91 | flags = "flow:\"".split(':') 92 | translation_string = "test" 93 | 94 | YamlGenerator(MagicMock())._insert_translation_in_dict( 95 | result, keys, flags, translation_string 96 | ) 97 | # produced result 98 | # OrderedDict([ 99 | # (u'one', FlowStyleOrderedDict([ 100 | # (u'two', double_quoted_unicode(u'test')) 101 | # ])) 102 | # ]) 103 | self.assertListEqual(list(six.iterkeys(result)), ['one']) 104 | self.assertIsInstance(result['one'], FlowStyleOrderedDict) 105 | self.assertIsInstance(result['one']['two'], double_quoted_unicode) 106 | 107 | def test_insert_translation_in_dict_list_of_dicts(self): 108 | result = OrderedDict() 109 | keys = ["one", "[0]", "two"] 110 | flags = "block:flow:\"".split(':') 111 | translation_string = "test" 112 | 113 | YamlGenerator(MagicMock())._insert_translation_in_dict( 114 | result, keys, flags, translation_string 115 | ) 116 | # produced result 117 | # OrderedDict([ 118 | # (u'one', BlockList([ 119 | # BlockStyledOrderedDict([ 120 | # (u'two', double_quoted_unicode(u'test')) 121 | # ]) 122 | # ])) 123 | # ]) 124 | self.assertListEqual(list(list(six.iterkeys(result))), ['one']) 125 | self.assertIsInstance(result['one'], BlockList) 126 | self.assertIsInstance(result['one'][0], FlowStyleOrderedDict) 127 | self.assertIsInstance(result['one'][0]['two'], double_quoted_unicode) 128 | -------------------------------------------------------------------------------- /openformats/tests/formats/yamlinternationalization/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/formats/yamlinternationalization/__init__.py -------------------------------------------------------------------------------- /openformats/tests/formats/yamlinternationalization/files/1_el.yml: -------------------------------------------------------------------------------- 1 | en: 2 | normal string: "el:normal string" 3 | pluralized_string: 4 | one: el:One ȧɠǿ 5 | other: el:Other ȧɠǿ 6 | another normal string: "el:normal string" 7 | 8 | empty_pluralized_string: 9 | one: "" 10 | other: "" 11 | 12 | # A correct plural rule 13 | pluralized_string_with_quotes: 14 | one: 'el:this is a test' 15 | other: "el:this is a double quote test" 16 | 17 | pluralized_string_with_extra_keys: 18 | one: el:One 19 | other: el:Other 20 | non_pluralized_string_with_extra_keys: 21 | extra_key: "el:random value" 22 | one: "el:One" 23 | other: "el:Other" 24 | plural_key_with_nested_nodes: 25 | one: 26 | nested_key: "el:One" 27 | other: "el:Other" 28 | 29 | title: "el:About writing and formatting on GitHub" 30 | intro: "el:into text" 31 | numeric_var: 12.5 32 | 33 | # one comment 34 | 35 | key1: 36 | - list_key: 37 | - "el:li within li" 38 | # second comment 39 | - "el:li within li 2" 40 | - "el:li 2" 41 | key2: 42 | - object_within_list: "el:value" 43 | - "el:text with a #hashtag in it" 44 | - "el:li 5" 45 | - 3.5 46 | - true 47 | 48 | description: > 49 | el:folded style text 50 | custom_vars: 51 | var1: "el:text: some value" 52 | var2: | 53 | el:literal 54 | style with "quotes" 55 | text 56 | nested_key_outer: 57 | nested_key_inner: 58 | "el:nested_value" 59 | 60 | key.with.dots: "el:dot value" 61 | 62 | 1: "el:integer key" 63 | 64 | empty_value: '' 65 | 66 | single_quoted_value: 'el:single quoted value' 67 | value with quote: "el:'value" 68 | value with start backtick: "el:`value" 69 | value with end backtick: "el:value`" 70 | 71 | : "el:value" 72 | "[weird_key]": "el:value" 73 | 74 | true: "el:boolean key" 75 | 76 | double_list: 77 | - - "el:test" 78 | 79 | simple_flow_list: ["el:one", 'el:two', "el:three"] 80 | double_flow_list: [[ "el:test" ]] 81 | 82 | binary_value: !!binary | 83 | R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J+fn5 84 | OTk6enp56enmlpaWNjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++f/++f/+ 85 | +f/++f/++f/++f/++f/++SH+Dk1hZGUgd2l0aCBHSU1QACwAAAAADAAMAAAFLC 86 | AgjoEwnuNAFOhpEMTRiggcz4BNJHrv/zCFcLiwMWYNG84BwwEeECcgggoBADs= 87 | 88 | # complex key syntax 89 | ? - Manchester United 90 | - Real Madrid 91 | : [ 2001-01-01, 2002-02-02 ] 92 | 93 | # anchors and aliases 94 | anchor_key: &an_anchor 95 | - "el:value" 96 | alias_key: 97 | - *an_anchor 98 | - "el:another true value" 99 | 100 | anchor_mapping: &another_anchor 101 | one: el:one 102 | other: el:other 103 | 104 | # context 105 | context_string: !cs:fd-94_fd.dot. "el:context string" 106 | verbim_context_string: !context:t5-46_qa "el:verbim context string" 107 | context_on_nested_map: 108 | first: !first_context:54KJFLA95KJ4 "el:context in nested map" 109 | second: !second_context:FDKJ40DK "el:context in nested map" 110 | -------------------------------------------------------------------------------- /openformats/tests/formats/yamlinternationalization/files/1_en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | normal string: normal string 3 | pluralized_string: 4 | one: One ȧɠǿ 5 | other: Other ȧɠǿ 6 | another normal string: normal string 7 | 8 | empty_pluralized_string: 9 | one: "" 10 | other: "" 11 | 12 | # A correct plural rule 13 | pluralized_string_with_quotes: 14 | one: 'this is a test' 15 | other: "this is a double quote test" 16 | 17 | pluralized_string_with_extra_keys: 18 | zero: Zero 19 | one: One 20 | other: Other 21 | non_pluralized_string_with_extra_keys: 22 | extra_key: random value 23 | one: One 24 | other: Other 25 | plural_key_with_nested_nodes: 26 | one: 27 | nested_key: One 28 | other: Other 29 | 30 | title: About writing and formatting on GitHub 31 | intro: into text 32 | numeric_var: 12.5 33 | 34 | # one comment 35 | 36 | key1: 37 | - list_key: 38 | - li within li 39 | # second comment 40 | - li within li 2 41 | - li 2 42 | key2: 43 | - object_within_list: value 44 | - "text with a #hashtag in it" 45 | - li 5 46 | - 3.5 47 | - true 48 | 49 | description: > 50 | folded 51 | style 52 | text 53 | custom_vars: 54 | var1: "text: some value" 55 | var2: | 56 | literal 57 | style with "quotes" 58 | text 59 | 60 | nested_key_outer: 61 | nested_key_inner: 62 | nested_value 63 | 64 | key.with.dots: dot value 65 | 66 | 1: integer key 67 | 68 | empty_value: '' 69 | 70 | single_quoted_value: 'single quoted value' 71 | value with quote: "'value" 72 | value with start backtick: "`value" 73 | value with end backtick: "value`" 74 | 75 | : value 76 | "[weird_key]": value 77 | 78 | true: boolean key 79 | 80 | double_list: 81 | - - test 82 | 83 | simple_flow_list: ["one", 'two', three] 84 | double_flow_list: [[ test ]] 85 | 86 | binary_value: !!binary | 87 | R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J+fn5 88 | OTk6enp56enmlpaWNjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++f/++f/+ 89 | +f/++f/++f/++f/++f/++SH+Dk1hZGUgd2l0aCBHSU1QACwAAAAADAAMAAAFLC 90 | AgjoEwnuNAFOhpEMTRiggcz4BNJHrv/zCFcLiwMWYNG84BwwEeECcgggoBADs= 91 | 92 | # complex key syntax 93 | ? - Manchester United 94 | - Real Madrid 95 | : [ 2001-01-01, 2002-02-02 ] 96 | 97 | # anchors and aliases 98 | anchor_key: &an_anchor 99 | - value 100 | alias_key: 101 | - *an_anchor 102 | - "another true value" 103 | 104 | anchor_mapping: &another_anchor 105 | one: one 106 | other: other 107 | 108 | # context 109 | context_string: !cs:fd-94_fd.dot. "context string" 110 | verbim_context_string: ! "verbim context string" 111 | context_on_nested_map: 112 | first: !first_context:54KJFLA95KJ4 "context in nested map" 113 | second: !second_context:FDKJ40DK "context in nested map" 114 | -------------------------------------------------------------------------------- /openformats/tests/formats/yamlinternationalization/files/1_en_exported.yml: -------------------------------------------------------------------------------- 1 | en: 2 | normal string: normal string 3 | pluralized_string: 4 | one: One ȧɠǿ 5 | other: Other ȧɠǿ 6 | another normal string: normal string 7 | 8 | empty_pluralized_string: 9 | one: "" 10 | other: "" 11 | 12 | # A correct plural rule 13 | pluralized_string_with_quotes: 14 | one: 'this is a test' 15 | other: "this is a double quote test" 16 | 17 | pluralized_string_with_extra_keys: 18 | one: One 19 | other: Other 20 | non_pluralized_string_with_extra_keys: 21 | extra_key: random value 22 | one: One 23 | other: Other 24 | plural_key_with_nested_nodes: 25 | one: 26 | nested_key: One 27 | other: Other 28 | 29 | title: About writing and formatting on GitHub 30 | intro: into text 31 | numeric_var: 12.5 32 | 33 | # one comment 34 | 35 | key1: 36 | - list_key: 37 | - li within li 38 | # second comment 39 | - li within li 2 40 | - li 2 41 | key2: 42 | - object_within_list: value 43 | - "text with a #hashtag in it" 44 | - li 5 45 | - 3.5 46 | - true 47 | 48 | description: > 49 | folded style text 50 | custom_vars: 51 | var1: "text: some value" 52 | var2: | 53 | literal 54 | style with "quotes" 55 | text 56 | nested_key_outer: 57 | nested_key_inner: 58 | nested_value 59 | 60 | key.with.dots: dot value 61 | 62 | 1: integer key 63 | 64 | empty_value: '' 65 | 66 | single_quoted_value: 'single quoted value' 67 | value with quote: "'value" 68 | value with start backtick: "`value" 69 | value with end backtick: "value`" 70 | 71 | : value 72 | "[weird_key]": value 73 | 74 | true: boolean key 75 | 76 | double_list: 77 | - - test 78 | 79 | simple_flow_list: ["one", 'two', three] 80 | double_flow_list: [[ test ]] 81 | 82 | binary_value: !!binary | 83 | R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J+fn5 84 | OTk6enp56enmlpaWNjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++f/++f/+ 85 | +f/++f/++f/++f/++f/++SH+Dk1hZGUgd2l0aCBHSU1QACwAAAAADAAMAAAFLC 86 | AgjoEwnuNAFOhpEMTRiggcz4BNJHrv/zCFcLiwMWYNG84BwwEeECcgggoBADs= 87 | 88 | # complex key syntax 89 | ? - Manchester United 90 | - Real Madrid 91 | : [ 2001-01-01, 2002-02-02 ] 92 | 93 | # anchors and aliases 94 | anchor_key: &an_anchor 95 | - value 96 | alias_key: 97 | - *an_anchor 98 | - "another true value" 99 | 100 | anchor_mapping: &another_anchor 101 | one: one 102 | other: other 103 | 104 | # context 105 | context_string: !cs:fd-94_fd.dot. "context string" 106 | verbim_context_string: !context:t5-46_qa "verbim context string" 107 | context_on_nested_map: 108 | first: !first_context:54KJFLA95KJ4 "context in nested map" 109 | second: !second_context:FDKJ40DK "context in nested map" 110 | -------------------------------------------------------------------------------- /openformats/tests/formats/yamlinternationalization/files/1_en_exported_without_template.yml: -------------------------------------------------------------------------------- 1 | en: 2 | normal string: normal string 3 | pluralized_string: 4 | one: One ȧɠǿ 5 | other: Other ȧɠǿ 6 | another normal string: normal string 7 | pluralized_string_with_quotes: 8 | one: this is a test 9 | other: this is a double quote test 10 | pluralized_string_with_extra_keys: 11 | one: One 12 | other: Other 13 | non_pluralized_string_with_extra_keys: 14 | extra_key: random value 15 | one: One 16 | other: Other 17 | plural_key_with_nested_nodes: 18 | one: 19 | nested_key: One 20 | other: Other 21 | title: About writing and formatting on GitHub 22 | intro: into text 23 | key1: 24 | - list_key: 25 | - li within li 26 | - li within li 2 27 | - li 2 28 | key2: 29 | - object_within_list: value 30 | - "text with a #hashtag in it" 31 | - li 5 32 | description: > 33 | folded style text 34 | custom_vars: 35 | var1: "text: some value" 36 | var2: | 37 | literal 38 | style with "quotes" 39 | text 40 | nested_key_outer: 41 | nested_key_inner: nested_value 42 | key.with.dots: dot value 43 | 1: integer key 44 | single_quoted_value: 'single quoted value' 45 | value with quote: "'value" 46 | value with start backtick: "`value" 47 | value with end backtick: "value`" 48 | : value 49 | '[weird_key]': value 50 | 'true': boolean key 51 | double_list: 52 | - - test 53 | simple_flow_list: ["one", 'two', three] 54 | double_flow_list: [[test]] 55 | anchor_key: 56 | - value 57 | alias_key: 58 | - "another true value" 59 | anchor_mapping: 60 | one: one 61 | other: other 62 | context_string: !cs:fd-94_fd.dot. "context string" 63 | verbim_context_string: !context:t5-46_qa "verbim context string" 64 | context_on_nested_map: 65 | first: "context in nested map" 66 | second: "context in nested map" 67 | -------------------------------------------------------------------------------- /openformats/tests/formats/yamlinternationalization/files/1_tpl.yml: -------------------------------------------------------------------------------- 1 | en: 2 | normal string: 3b6dc2dbeb067817a544fcb750847ef2_tr 3 | pluralized_string: 4 | ecfc311d97950fc7db9c7ed1238769bf_pl 5 | another normal string: f06fc11fd8efb89fe045ce53c215d59e_tr 6 | 7 | empty_pluralized_string: 8 | one: "" 9 | other: "" 10 | 11 | # A correct plural rule 12 | pluralized_string_with_quotes: 13 | e77d50fd048c3005883b3a99fd36fab3_pl 14 | 15 | pluralized_string_with_extra_keys: 16 | dc063e48dfced0cb854d640b2bdca4f0_pl 17 | non_pluralized_string_with_extra_keys: 18 | extra_key: 3974770f06e9d24d79d07d48888bd85d_tr 19 | one: 9977ff15b22f66a1493f3bed54e16d5b_tr 20 | other: 8e71b7edd01d7d8b8d713799438dcb06_tr 21 | plural_key_with_nested_nodes: 22 | one: 23 | nested_key: c82d2d83df32177a21068ba650454af9_tr 24 | other: 910d07668f9dac883d140bb595532b09_tr 25 | 26 | title: 6391362253bdac99fff7bf50ff014be3_tr 27 | intro: e8c07422d0167fee3ecf8f3375ea5739_tr 28 | numeric_var: 12.5 29 | 30 | # one comment 31 | 32 | key1: 33 | - list_key: 34 | - ac56af108b4b69b867c09f8542a51cb2_tr 35 | # second comment 36 | - d2d20cce249c5d7e8ca96b9e30a3349c_tr 37 | - 17e2086737d2192e6665ea2c0f0b1e9a_tr 38 | key2: 39 | - object_within_list: 1a397fe7999e432441bfce7a1f4fb1f3_tr 40 | - aa8c6113a4236d576f96ba9b0bd4dfa4_tr 41 | - 76d7c5ccc4301f10b39115b2c1a9ca47_tr 42 | - 3.5 43 | - true 44 | 45 | description: fdba4efe4ab9d38afee4054a7fa06e29_tr custom_vars: 46 | var1: 8b9f5508bd423a15a188813d60e52946_tr 47 | var2: 7169ba1087c5204f12ab8b7c066884a2_tr nested_key_outer: 48 | nested_key_inner: 49 | 94b99ee5f1d082d7df28c4b48aa99ff8_tr 50 | 51 | key.with.dots: ddbc5d72cdd64acc258ab3446b6466ed_tr 52 | 53 | 1: 9354e4f807073d6a146138fe6d81e4ef_tr 54 | 55 | empty_value: '' 56 | 57 | single_quoted_value: 710e606150bfb87b477674e886495384_tr 58 | value with quote: 4ea9d14bca65fb09e8b7c2c54ce8fd4e_tr 59 | value with start backtick: 05cecdb72a9f3d915c749bba5295901c_tr 60 | value with end backtick: 109530f343dfa59480e0e3aa11816cbb_tr 61 | 62 | : 02e8cfaf248aa25e0664be1dddfa5b65_tr 63 | "[weird_key]": c12d8df4b08d0ff07eb311ed29e660f4_tr 64 | 65 | true: 908f519fabea6e6ad3f99d4899185a61_tr 66 | 67 | double_list: 68 | - - 006d8de981d80ed57fa20420e4907aa3_tr 69 | 70 | simple_flow_list: [b6379afadb376e6d4b78e489417b5289_tr, e3a178abc462683f3bf527f7c2bdaeaa_tr, 68bc9de575a69a2a8ddeec8a1842d078_tr] 71 | double_flow_list: [[ 5b881f08c3f024c9f277ffec9a22f713_tr ]] 72 | 73 | binary_value: !!binary | 74 | R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J+fn5 75 | OTk6enp56enmlpaWNjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++f/++f/+ 76 | +f/++f/++f/++f/++f/++SH+Dk1hZGUgd2l0aCBHSU1QACwAAAAADAAMAAAFLC 77 | AgjoEwnuNAFOhpEMTRiggcz4BNJHrv/zCFcLiwMWYNG84BwwEeECcgggoBADs= 78 | 79 | # complex key syntax 80 | ? - Manchester United 81 | - Real Madrid 82 | : [ 2001-01-01, 2002-02-02 ] 83 | 84 | # anchors and aliases 85 | anchor_key: &an_anchor 86 | - 8dc0ec22f35b3c7255b928e03104d714_tr 87 | alias_key: 88 | - *an_anchor 89 | - ddc3cfcedcf1686d9e3ba6b99a0d091b_tr 90 | 91 | anchor_mapping: &another_anchor 92 | 798cf4a4e275a90e80b0aac837f06793_pl 93 | 94 | # context 95 | context_string: 17d854ee46fe3d3bc91fadb4cf4df426_tr 96 | verbim_context_string: 0a3c1ae205b2c0d09ab69ab540e481f2_tr 97 | context_on_nested_map: 98 | first: 95175e30d6fbfe0f658e75919cd4982c_tr 99 | second: dce391c231a7ad7fef9e6cc0ddcbf549_tr 100 | -------------------------------------------------------------------------------- /openformats/tests/formats/yamlinternationalization/test_i18n_yaml.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from io import open 3 | from os import path 4 | 5 | from openformats.formats.yaml import I18nYamlHandler 6 | from openformats.tests.formats.common import CommonFormatTestMixin 7 | 8 | 9 | class I18nYamlTestCase(CommonFormatTestMixin, unittest.TestCase): 10 | HANDLER_CLASS = I18nYamlHandler 11 | TESTFILE_BASE = "openformats/tests/formats/" \ 12 | "yamlinternationalization/files" 13 | 14 | def setUp(self): 15 | self.handler = self.HANDLER_CLASS() 16 | self.handler.set_plural_rules([1, 5]) 17 | self.handler.set_lang_code('en') 18 | self.read_files() 19 | self.tmpl, self.strset = self.handler.parse(self.data["1_en"]) 20 | 21 | def __init__(self, *args, **kwargs): 22 | super(I18nYamlTestCase, self).__init__(*args, **kwargs) 23 | extra_files = ["1_en_exported.yml", 24 | "1_en_exported_without_template.yml"] 25 | 26 | for fname in extra_files: 27 | filepath = path.join(self.TESTFILE_BASE, fname) 28 | with open(filepath, "r", encoding='utf-8') as myfile: 29 | self.data[fname[:-4]] = myfile.read() 30 | 31 | def test_compile(self): 32 | """Test that import-export is the same as the original file.""" 33 | remade_orig_content = self.handler.compile(self.tmpl, self.strset) 34 | self.assertEqual(remade_orig_content, self.data["1_en_exported"]) 35 | 36 | def test_compile_without_template(self): 37 | """Test that import-export is the same as the original file.""" 38 | self.handler.should_use_template = False 39 | remade_orig_content = self.handler.compile(self.tmpl, self.strset) 40 | self.assertEqual(remade_orig_content, 41 | self.data["1_en_exported_without_template"]) 42 | -------------------------------------------------------------------------------- /openformats/tests/run_tests.py: -------------------------------------------------------------------------------- 1 | from os.path import abspath, dirname 2 | import sys 3 | 4 | import nose 5 | 6 | 7 | def run_all(args=None): 8 | if not args: 9 | args = [ 10 | 'nosetests', '--with-xunit', '--with-xcoverage', 11 | '--cover-package=openformats', '--cover-erase', 12 | '--logging-filter=openformats', '--logging-level=DEBUG', 13 | '--verbose', 14 | ] 15 | 16 | nose.run_exit( 17 | argv=args, 18 | defaultTest=abspath(dirname(__file__)) 19 | ) 20 | 21 | 22 | if __name__ == '__main__': 23 | run_all(sys.argv) 24 | -------------------------------------------------------------------------------- /openformats/tests/test_handlers.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | import six 4 | 5 | from openformats.exceptions import RuleError 6 | from openformats.handlers import Handler 7 | 8 | 9 | class HandlerTestCase(TestCase): 10 | def setUp(self): 11 | self.handler = Handler() 12 | 13 | def tearDown(self): 14 | self.handler = None 15 | 16 | def test_get_rule_number_returns(self): 17 | rules = { 18 | 'zero': 0, 19 | 'one': 1, 20 | 'two': 2, 21 | 'few': 3, 22 | 'many': 4, 23 | 'other': 5 24 | } 25 | for rule_str, rule in six.iteritems(rules): 26 | self.assertEqual(self.handler.get_rule_number(rule_str), rule) 27 | 28 | def test_get_rule_string_returns(self): 29 | rules = { 30 | 0: 'zero', 31 | 1: 'one', 32 | 2: 'two', 33 | 3: 'few', 34 | 4: 'many', 35 | 5: 'other', 36 | } 37 | for rule, rule_str in six.iteritems(rules): 38 | self.assertEqual(self.handler.get_rule_string(rule), rule_str) 39 | 40 | def test_get_rule_string_returns_error(self): 41 | self.assertRaises(RuleError, self.handler.get_rule_string, 50) 42 | 43 | def test_get_rule_number_returns_error(self): 44 | self.assertRaises(RuleError, self.handler.get_rule_number, 'test') 45 | -------------------------------------------------------------------------------- /openformats/tests/test_strings.py: -------------------------------------------------------------------------------- 1 | from hashlib import md5 2 | from random import randint 3 | from unittest import TestCase 4 | 5 | import six 6 | 7 | from openformats.strings import OpenString 8 | from openformats.tests.utils import generate_random_string 9 | 10 | 11 | class OpenStringTestCase(TestCase): 12 | def test_singular_string_is_non_plural(self): 13 | random_string = generate_random_string() 14 | open_string = OpenString('test', random_string) 15 | 16 | self.assertEqual(open_string._strings, {5: random_string}) 17 | self.assertFalse(open_string.pluralized) 18 | 19 | def test_singular_dict_is_non_plural(self): 20 | random_string = {5: generate_random_string()} 21 | open_string = OpenString('test', random_string) 22 | 23 | self.assertEqual(open_string._strings, random_string) 24 | self.assertFalse(open_string.pluralized) 25 | 26 | def test_singular_is_returned(self): 27 | random_string = generate_random_string() 28 | open_string = OpenString('test', random_string) 29 | 30 | self.assertEqual(open_string.string, random_string) 31 | 32 | def test_plurals_are_returned(self): 33 | random_strings = { 34 | i: generate_random_string() 35 | for i in six.moves.xrange(randint(10, 20)) 36 | } 37 | open_string = OpenString('test', random_strings) 38 | 39 | self.assertTrue(open_string.pluralized) 40 | self.assertEqual(open_string.string, random_strings) 41 | 42 | def test_multiples_are_plural(self): 43 | test_strings = { 44 | i: generate_random_string() 45 | for i in six.moves.xrange(randint(5, 10)) 46 | } 47 | open_string = OpenString('test', test_strings) 48 | 49 | self.assertEqual(open_string._strings, test_strings) 50 | self.assertTrue(open_string.pluralized) 51 | 52 | def test_defaults_are_assigned_to_self(self): 53 | random_property_name = generate_random_string() 54 | random_default = generate_random_string() 55 | 56 | backup_defaults = OpenString.DEFAULTS 57 | OpenString.DEFAULTS = { 58 | random_property_name: random_default 59 | } 60 | open_string = OpenString('test', 'test') 61 | self.assertEqual( 62 | getattr(open_string, random_property_name), random_default 63 | ) 64 | OpenString.DEFAULTS = backup_defaults 65 | 66 | def test_pluralized_is_overridden_with_keyword(self): 67 | open_string = OpenString('test', 'test', pluralized=True) 68 | self.assertTrue(open_string.pluralized) 69 | 70 | def test_template_replacement_returns_correct_suffix(self): 71 | open_string = OpenString('test', 'test') 72 | 73 | # Test when pluralized False 74 | open_string.pluralized = False 75 | template_replacement = open_string.template_replacement 76 | self.assertTrue(template_replacement.endswith('tr')) 77 | 78 | # Test when pluralized True 79 | open_string.pluralized = True 80 | template_replacement = open_string.template_replacement 81 | self.assertTrue(template_replacement.endswith('pl')) 82 | 83 | def test_context_is_hashed_when_present(self): 84 | random_context = generate_random_string() 85 | random_key = generate_random_string() 86 | random_hash = md5( 87 | ':'.join([random_key, random_context]).encode('utf-8') 88 | ).hexdigest() 89 | 90 | open_string = OpenString(random_key, 'test') 91 | open_string.context = random_context 92 | 93 | replacement = open_string.template_replacement 94 | hash_string = replacement.split('_')[0] 95 | 96 | self.assertEqual(hash_string, random_hash) 97 | 98 | def test_hash_is_calculated_from_components(self): 99 | random_key = generate_random_string() 100 | random_context = generate_random_string() 101 | random_string = "hello world" 102 | random_hash = hash((random_key, random_context, random_string)) 103 | 104 | open_string = OpenString(random_key, '') 105 | open_string.context = random_context 106 | open_string._strings[5] = random_string 107 | 108 | self.assertEqual(hash(open_string), random_hash) 109 | 110 | def test_error_in__repr__(self): 111 | open_string = OpenString('random_key', {6: 'this is wrong'}) 112 | 113 | self.assertEqual( 114 | open_string.__repr__(), 115 | b'Invalid string' 116 | ) 117 | -------------------------------------------------------------------------------- /openformats/tests/util_tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/tests/util_tests/__init__.py -------------------------------------------------------------------------------- /openformats/tests/utils/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | 3 | from openformats.tests.utils.dictionary import translate_stringset 4 | from openformats.tests.utils.strings import ( 5 | generate_random_string, strip_leading_spaces 6 | ) 7 | -------------------------------------------------------------------------------- /openformats/tests/utils/dictionary.csv: -------------------------------------------------------------------------------- 1 | en,el 2 | "Hello, World!","Γεια σου, Κόσμε!" 3 | "Pinky: Gee, Brain, what do you want to do tonight? 4 | Brain: The same thing we do every night, Pinky - try to take over the world!","Pinky: Brain, ρι θες να κάνουμε απόψε; 5 | Brain: Ό,τι και κάθε βράδυ, Pinky: θα κατακτήσουμε τον κόσμο!" -------------------------------------------------------------------------------- /openformats/tests/utils/dictionary.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Various methods useful for tests and similar operations. 5 | """ 6 | 7 | from __future__ import unicode_literals 8 | 9 | import csv 10 | import os 11 | from io import open 12 | 13 | import six 14 | 15 | root = os.path.dirname(__file__) 16 | DICT_FNAME = os.path.join(root, 'dictionary.csv') 17 | 18 | try: 19 | # This is ugly, but io.open does not work well with csv 20 | import sys 21 | six.moves.reload_module(sys) 22 | sys.setdefaultencoding('utf-8') 23 | except Exception: 24 | pass 25 | 26 | 27 | class FunkyDictionary(object): 28 | def __init__(self): 29 | self.phrase_list = [] 30 | self.phrase_dict = {} 31 | with open(DICT_FNAME, 'r', encoding='utf-8', newline=None) as dict_file: 32 | dict_reader = csv.DictReader(dict_file) 33 | for phrase in dict_reader: 34 | unicode_phrase = {} 35 | for key, value in six.iteritems(phrase): 36 | if isinstance(value, six.binary_type): 37 | value = value.decode("utf-8") 38 | unicode_phrase[key] = value 39 | self.phrase_list.append(unicode_phrase) 40 | # We can assume 'en' is going to be used as a source language 41 | # often, so it makes sense to be able to do quick lookups 42 | self.phrase_dict[unicode_phrase['en']] = unicode_phrase 43 | 44 | def translate(self, phrase, to_lang, from_lang="en", debug=False): 45 | if from_lang == "en": 46 | try: 47 | if debug: 48 | print('Lookup for "{}" successful'.format(phrase[:20])) 49 | return self.phrase_dict[phrase][to_lang] 50 | except KeyError: 51 | pass 52 | else: 53 | for dict_phrase in self.phrase_list: 54 | if phrase == dict_phrase[from_lang]: 55 | return dict_phrase[to_lang] 56 | # Phrase not found in funky dict 57 | if debug: 58 | print('Lookup for "{}" unsuccessful.'.format(phrase[:20])) 59 | return "{}:{}".format(to_lang, phrase) 60 | 61 | 62 | funky_dictionary = FunkyDictionary() 63 | 64 | 65 | def translate_stringset(stringset, from_lang="en", to_lang="el", debug=False): 66 | for s in stringset: 67 | for rule, pluralform in list(six.iteritems(s._strings)): 68 | s._strings[rule] = funky_dictionary.translate( 69 | pluralform, to_lang, from_lang, debug 70 | ) 71 | return stringset 72 | -------------------------------------------------------------------------------- /openformats/tests/utils/strings.py: -------------------------------------------------------------------------------- 1 | from random import choice 2 | from string import ascii_letters, digits 3 | 4 | import six 5 | 6 | 7 | def generate_random_string(length=20): 8 | return ''.join((choice(ascii_letters + digits) 9 | for _ in six.moves.xrange(length))) 10 | 11 | 12 | def strip_leading_spaces(source): 13 | r""" 14 | This is to help you write multilingual strings as test inputs in your 15 | tests without screwing up your code's syntax. Eg:: 16 | 17 | ''' 18 | 1 19 | 00:01:28.797 --> 00:01:30.297 X:240 Y:480 20 | Hello world 21 | ''' 22 | 23 | will be converted to:: 24 | 25 | '\n1\n00:01:28.797 --> 00:01:30.297 X:240 Y:480\nHello world\n' 26 | """ 27 | 28 | return '\n'.join((line.lstrip() for line in source.split('\n'))) 29 | 30 | 31 | def bytes_to_string(_bytes): 32 | for byte in _bytes: 33 | assert len(byte) == 1, six.text_type(_bytes) 34 | return u''.join(_bytes) 35 | -------------------------------------------------------------------------------- /openformats/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/openformats/utils/__init__.py -------------------------------------------------------------------------------- /openformats/utils/compat.py: -------------------------------------------------------------------------------- 1 | import six 2 | 3 | 4 | def ensure_unicode(pattern): 5 | if isinstance(pattern, six.binary_type): 6 | pattern = pattern.decode('utf-8') 7 | return pattern 8 | -------------------------------------------------------------------------------- /openformats/utils/compilers.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | import six 4 | 5 | from openformats.transcribers import Transcriber 6 | from openformats.utils.compat import ensure_unicode 7 | 8 | 9 | class OrderedCompilerMixin(object): 10 | SPACE_PAT = r'^\s*$' 11 | 12 | def compile(self, template, stringset, **kwargs): 13 | # Fix regex encoding 14 | space_pattern = re.compile(ensure_unicode(self.SPACE_PAT)) 15 | 16 | # assume stringset is ordered within the template 17 | transcriber = Transcriber(template) 18 | template = transcriber.source 19 | 20 | for string in stringset: 21 | hash_position = template.index(string.template_replacement) 22 | if not string.pluralized: 23 | transcriber.copy_until(hash_position) 24 | transcriber.add(string.string) 25 | transcriber.skip(len(string.template_replacement)) 26 | else: 27 | # if the hash is on its own on a line with only spaces, we have 28 | # to remember it's indent 29 | indent_length = template[hash_position::-1].index('\n') - 1 30 | indent = template[hash_position - indent_length:hash_position] 31 | tail_length = template[ 32 | hash_position + len(string.template_replacement): 33 | ].index('\n') 34 | tail = template[ 35 | hash_position + len(string.template_replacement): 36 | hash_position + len(string.template_replacement) + 37 | tail_length 38 | ] 39 | if (space_pattern.search(indent) and 40 | space_pattern.search(tail)): 41 | transcriber.copy_until(hash_position - indent_length) 42 | for rule, value in six.iteritems(string.string): 43 | transcriber.add( 44 | indent + self.plural_template.format( 45 | rule=self.RULES_ITOA[rule], string=value 46 | ) + tail + '\n' 47 | ) 48 | transcriber.skip(indent_length + 49 | len(string.template_replacement) + 50 | tail_length + 1) 51 | else: 52 | # string is not on its own, simply replace hash with all 53 | # plural forms 54 | transcriber.copy_until(hash_position) 55 | for rule, value in six.iteritems(string.string): 56 | transcriber.add(self.plural_template.format( 57 | rule=self.RULES_ITOA[rule], string=value 58 | )) 59 | transcriber.skip(len(string.template_replacement)) 60 | 61 | transcriber.copy_until(len(template)) 62 | compiled = transcriber.get_destination() 63 | 64 | return compiled 65 | -------------------------------------------------------------------------------- /openformats/utils/newlines.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | import six 4 | 5 | 6 | def find_newline_type(content): 7 | if isinstance(content, six.text_type): 8 | NEWLINE = "\n" 9 | CARRIAGE_RETURN = "\r" 10 | else: 11 | NEWLINE = b"\n" 12 | CARRIAGE_RETURN = b"\r" 13 | 14 | try: 15 | first_newline_pos = content.index(NEWLINE) 16 | except ValueError: 17 | return 'UNIX' 18 | else: 19 | if (first_newline_pos > 0 and 20 | content[first_newline_pos - 1] == CARRIAGE_RETURN): 21 | return 'DOS' 22 | else: 23 | return 'UNIX' 24 | 25 | 26 | def force_newline_type(content, newline_type): 27 | if isinstance(content, six.text_type): 28 | NEWLINE = "\n" 29 | CARRIAGE_RETURN_NEWLINE = "\r\n" 30 | else: 31 | NEWLINE = b"\n" 32 | CARRIAGE_RETURN_NEWLINE = b"\r\n" 33 | 34 | new_content = content.replace(CARRIAGE_RETURN_NEWLINE, NEWLINE) 35 | if newline_type == 'DOS': 36 | new_content = new_content.replace(NEWLINE, CARRIAGE_RETURN_NEWLINE) 37 | return new_content 38 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | PyYAML==5.4.1 2 | django==1.11.29 3 | mistune==0.8.1 4 | polib==1.0.3 5 | pyparsing==2.2.0 6 | six 7 | lxml==4.6.5 8 | beautifulsoup4==4.9.3 9 | pytest 10 | mock 11 | 12 | # InDesign 13 | git+https://github.com/kbairak/ucflib@py3_compatibility 14 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [versioneer] 2 | VCS = git 3 | versionfile_source = openformats/_version.py 4 | versionfile_build = openformats/_version.py 5 | tag_prefix = 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from setuptools import find_packages, setup 3 | 4 | import versioneer 5 | 6 | install_requires = [ 7 | "polib==1.0.3", 8 | "mistune==0.8.1", 9 | "PyYAML==5.4.1", 10 | "pyparsing==2.2.0", 11 | "lxml==4.6.5", 12 | "beautifulsoup4==4.9.3", 13 | "ucflib @ git+https://github.com/kbairak/ucflib.git@py3_compatibility#egg=ucflib-0.2.1", # noqa 14 | ] 15 | 16 | tests_require = ["nose", "mock", "coverage", "nosexcover"] 17 | 18 | setup( 19 | name="openformats", 20 | version=versioneer.get_version(), 21 | cmdclass=versioneer.get_cmdclass(), 22 | description="The Transifex Open Formats library", 23 | author="Transifex", 24 | author_email="support@transifex.com", 25 | url="https://github.com/transifex/openformats", 26 | install_requires=install_requires, 27 | tests_require=tests_require, 28 | test_suite="openformats.tests.run_tests.run_all", 29 | packages=find_packages(where=".", exclude=("tests*", "testbed")), 30 | ) 31 | -------------------------------------------------------------------------------- /testbed/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/testbed/__init__.py -------------------------------------------------------------------------------- /testbed/main/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/testbed/main/__init__.py -------------------------------------------------------------------------------- /testbed/main/models.py: -------------------------------------------------------------------------------- 1 | import json 2 | from hashlib import md5 3 | 4 | from django.db import models 5 | from django.core.urlresolvers import reverse 6 | 7 | 8 | class Payload(models.Model): 9 | payload_hash = models.CharField(max_length=32, unique=True, 10 | blank=True, null=True) 11 | payload = models.TextField() 12 | created = models.DateTimeField(auto_now_add=True) 13 | last_viewed = models.DateTimeField(blank=True, null=True) 14 | 15 | def _presave(self): 16 | self.payload_hash = md5(self.payload.encode('utf-8')).hexdigest() 17 | 18 | def save(self): 19 | self._presave() 20 | super(Payload, self).save() 21 | 22 | def get_absolute_url(self): 23 | return reverse("testbed_main", 24 | kwargs={'payload_hash': self.payload_hash}) 25 | 26 | def __unicode__(self): 27 | payload = json.loads(self.payload) 28 | handler = payload.get('handler', None) 29 | if handler: 30 | return u"{}-{}".format(handler, self.payload_hash[-8:]) 31 | else: 32 | return self.payload_hash[-8:] 33 | -------------------------------------------------------------------------------- /testbed/main/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | from django.views.decorators.csrf import csrf_exempt 3 | 4 | from testbed.main.views import MainView, ApiView, SaveView 5 | 6 | 7 | urlpatterns = [ 8 | url(r'^$', MainView.as_view(), name="testbed_home"), 9 | url(r'^(?P\w{32})$', MainView.as_view(), 10 | name="testbed_main"), 11 | url(r'^api/$', csrf_exempt(ApiView.as_view()), name="testbed_api"), 12 | url(r'^save/$', SaveView.as_view(), name="testbed_save") 13 | ] 14 | -------------------------------------------------------------------------------- /testbed/static/css/home.css: -------------------------------------------------------------------------------- 1 | html, body, .mycss-panel, #mycss-row, .mycss-panel-column, 2 | .mycss-panel { height: 100% } 3 | .mycss-half-panel { height: 46% } 4 | .mycss-half-panel-body { height: 86%; overflow-y: scroll } 5 | .mycss-scroll {overflow-y: scroll} 6 | #mycss-container { height: 92% } 7 | .mycss-panel-body { height: 94% } 8 | -------------------------------------------------------------------------------- /testbed/static/js/globals.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | var Testbed = window.Testbed; 3 | var Globals = Testbed.namespace('globals'); 4 | 5 | var UiState = Backbone.Model.extend({ 6 | defaults: { 7 | source_panel: true, parsed_panel: false, compiled_panel: false, 8 | }, 9 | count_panels: function() { 10 | return this.get('source_panel') + this.get('parsed_panel') + 11 | this.get('compiled_panel'); 12 | }, 13 | toggle_panel: function(what) { 14 | var selected = this.get(what + '_panel'); 15 | var to_set; 16 | if(selected === false) { 17 | to_set = {}; 18 | to_set[what + '_panel'] = true; 19 | this.set(to_set); 20 | } else { 21 | if(this.count_panels() > 1) { 22 | to_set = {}; 23 | to_set[what + '_panel'] = false; 24 | this.set(to_set); 25 | } 26 | } 27 | }, 28 | }); 29 | 30 | var OpenString = Backbone.Model.extend({}); 31 | var Stringset = Backbone.Collection.extend({ 32 | model: OpenString, 33 | comparator: 'order', 34 | }); 35 | 36 | var Payload = Backbone.Model.extend({ 37 | defaults: { action: null }, 38 | initialize: function() { 39 | this.stringset = new Stringset(); 40 | }, 41 | set: function(data) { 42 | if('stringset' in data) { 43 | var stringset = data.stringset; 44 | delete data.stringset; 45 | this.stringset.set(stringset); 46 | } 47 | Backbone.Model.prototype.set.call(this, data); 48 | }, 49 | send: function() { 50 | if(this.get('action') == 'parse') { 51 | Globals.ui_state.set({ parsed_panel: true }); 52 | } else if(this.get('action') == 'compile') { 53 | Globals.ui_state.set({ compiled_panel: true }); 54 | } 55 | var _this = this; 56 | $.ajax({ 57 | type: 'POST', 58 | url: '/api/', 59 | data: JSON.stringify(this.toJSON()), 60 | dataType: 'json', 61 | success: function(data) { _this.set(data); }, 62 | error: function() {}, 63 | }); 64 | }, 65 | toJSON: function() { 66 | var return_value = Backbone.Model.prototype.toJSON.call(this); 67 | return_value.stringset = this.stringset.toJSON(); 68 | return return_value; 69 | }, 70 | }); 71 | 72 | Globals.ui_state = new UiState(); 73 | Globals.payload = new Payload(); 74 | }); 75 | -------------------------------------------------------------------------------- /testbed/static/js/main.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | var Testbed = window.Testbed; 3 | var Views = Testbed.namespace('views'); 4 | var Globals = Testbed.namespace('globals'); 5 | var TemplateData = Testbed.namespace('template_data'); 6 | 7 | // Header 8 | new Views.PanelButtons({ el: '#panel-toggles' }); 9 | $('.js-panel').each(function() { 10 | var panel = this; 11 | new Views.Panel({ el: panel }); 12 | }); 13 | new Views.SaveForm({ el: '#save-form' }); 14 | 15 | // Source 16 | new Views.SourceForm({ el: '#source-form' }); 17 | 18 | // Parsed 19 | new Views.ParsedLoading({ el: '#parsed-loading' }); 20 | new Views.ParsedMain({ el: '#parsed-main' }); 21 | new Views.ParsedError({ el: '#parsed-error' }); 22 | new Views.Stringset({ el: '#stringset' }); 23 | new Views.Template({ el: '#template' }); 24 | 25 | // Compiled 26 | new Views.CompileButton({ el: '#compile-button' }); 27 | new Views.CompiledLoading({ el: '#compiled-loading' }); 28 | new Views.CompiledComparison({ el: '#compiled-comparison' }); 29 | new Views.Compiled({ el: '#compiled' }); 30 | new Views.CompiledMain({ el: '#compiled-main' }); 31 | new Views.CompiledError({ el: '#compiled-error' }); 32 | 33 | // Global events 34 | $('body').on('click', function() { 35 | // Deselect all strings 36 | Globals.ui_state.set({ selected_string: null }); 37 | }); 38 | $('body').on('keyup', function(event) { 39 | if(event.target.type == 'input' || event.target.type == 'textarea' || 40 | ('' + event.target.type).indexOf('select') != -1) { return; } 41 | var keys = {49: '1', 50: '2', 51: '3', 72: 'h', 83: 's', 80: 'p', 84: 't', 42 | 67: 'c', 87: 'w', 191: '?'}; 43 | var key_code = event.which; 44 | var key = keys[event.which]; 45 | if((key == '1' || key == '2' || key == '3')) { 46 | var panels = {1: 'source', 2: 'parsed', 3: 'compiled'}; 47 | Globals.ui_state.toggle_panel(panels[key]); 48 | } 49 | if(key == 'h') { 50 | Globals.ui_state.set({ source_panel: true }); 51 | $('select[name="handler"]').focus(); 52 | } 53 | if(key == 's') { 54 | Globals.ui_state.set({ source_panel: true }); 55 | $('textarea[name="source"]').focus(); 56 | $('textarea[name="source"]').trigger('select'); 57 | } 58 | if(key == 'p') { 59 | Globals.ui_state.set({ source_panel: true }); 60 | $('#source-form').submit(); 61 | } 62 | if(key == 't') { 63 | Globals.ui_state.set({ parsed_panel: true }); 64 | $('#stringset a:first').click(); 65 | } 66 | if(key == 'c') { $('#compile-button').click(); } 67 | if(key == 'w') { $('#save-form').submit(); } 68 | if(key == '?') { $('#keyboard-shortcuts-toggle').click(); } 69 | }); 70 | 71 | // Last bit of bootstraping 72 | $('select[name="handler"]').focus(); 73 | 74 | // Load Payload if one was retrieved from database 75 | if(TemplateData.payload_json !== null) { 76 | Globals.payload.set(TemplateData.payload_json); 77 | Globals.ui_state.set({ source_panel: true, parsed_panel: true, 78 | compiled_panel: true }); 79 | $('#saved-modal').modal(); 80 | } 81 | }); 82 | -------------------------------------------------------------------------------- /testbed/static/js/namespace.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var Testbed = window.Testbed || (window.Testbed = {}); 3 | Testbed.namespace = function(structure) { 4 | if(typeof(structure) == 'string') { 5 | return Testbed.namespace._return.apply(this, arguments); 6 | } else { 7 | Testbed.namespace._extend.apply(this, arguments); 8 | } 9 | }; 10 | Testbed.namespace._extend = function(structure) { 11 | if(! structure) { structure = {}; } 12 | for(var key in structure) { 13 | if(! (key in this)) { this[key] = {}; } 14 | var value = structure[key]; 15 | if(_.isString(value) || _.isArray(value) || _.isNumber(value) || 16 | _.isBoolean(value) || _.isNull(value) || _.isUndefined(value) || 17 | _.isFunction(value) || _.isDate(value) || _.isRegExp(value)) 18 | { 19 | this[key] = value; 20 | } else { 21 | Testbed.namespace._extend.call(this[key], structure[key]); 22 | } 23 | } 24 | }; 25 | 26 | Testbed.namespace._return = function(path) { 27 | var keys = path.split('.'); 28 | var previous = this; 29 | for(var i = 0; i < keys.length; i++) { 30 | var key = keys[i]; 31 | if(! (key in previous)) { previous[key] = {}; } 32 | previous = previous[key]; 33 | } 34 | return previous; 35 | }; 36 | })(); 37 | -------------------------------------------------------------------------------- /testbed/static/libraries/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/testbed/static/libraries/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /testbed/static/libraries/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/testbed/static/libraries/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /testbed/static/libraries/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/testbed/static/libraries/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /testbed/static/libraries/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/testbed/static/libraries/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /testbed/testbed.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/openformats/26f3d320143eef306c0662af1f83584a0c9a615d/testbed/testbed.sqlite -------------------------------------------------------------------------------- /testbed/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import include, url 2 | 3 | # Uncomment the next two lines to enable the admin: 4 | # from django.contrib import admin 5 | # admin.autodiscover() 6 | 7 | urlpatterns = [ 8 | url(r'^', include('testbed.main.urls')), 9 | 10 | # Examples: 11 | # url(r'^testbed/', include('testbed.foo.urls')), 12 | 13 | # Uncomment the admin/doc line below to enable admin documentation: 14 | # url(r'^admin/doc/', include('django.contrib.admindocs.urls')), 15 | 16 | # Uncomment the next line to enable the admin: 17 | # url(r'^admin/', include(admin.site.urls)), 18 | ] 19 | -------------------------------------------------------------------------------- /testbed/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for testbed project. 3 | 4 | This module contains the WSGI application used by Django's development server 5 | and any production WSGI deployments. It should expose a module-level variable 6 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover 7 | this application via the ``WSGI_APPLICATION`` setting. 8 | 9 | Usually you will have the standard Django WSGI application here, but it also 10 | might make sense to replace the whole Django WSGI application with a custom one 11 | that later delegates to the Django one. For example, you could introduce WSGI 12 | middleware here, or combine a Django application with an application of another 13 | framework. 14 | 15 | """ 16 | import os 17 | 18 | # We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks 19 | # if running multiple sites in the same mod_wsgi process. To fix this, use 20 | # mod_wsgi daemon mode with each site in its own daemon process, or use 21 | # os.environ["DJANGO_SETTINGS_MODULE"] = "testbed.settings" 22 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testbed.settings") 23 | 24 | # This application object is used by any WSGI server configured to use this 25 | # file. This includes Django's development server, if the WSGI_APPLICATION 26 | # setting points here. 27 | from django.core.wsgi import get_wsgi_application 28 | application = get_wsgi_application() 29 | 30 | # Apply WSGI middleware here. 31 | # from helloworld.wsgi import HelloWorldApplication 32 | # application = HelloWorldApplication(application) 33 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | py39 4 | report 5 | 6 | [testenv] 7 | install_command = pip install {packages} 8 | commands = 9 | coverage run -m unittest discover 10 | deps = 11 | -r{toxinidir}/requirements.txt 12 | mock 13 | coverage 14 | 15 | [testenv:report] 16 | skipsdist = True 17 | deps = coverage 18 | skip_install = true 19 | commands = 20 | # coverage combine --append 21 | coverage report 22 | --------------------------------------------------------------------------------