diff --git a/README.rst b/README.rst index 8c56be306..0d27ef16d 100644 --- a/README.rst +++ b/README.rst @@ -30,7 +30,8 @@ Features * Tox_ testing: Setup to easily test for Python 3.8, 3.9, 3.10, 3.11, 3.12, and PyPy3. * Sphinx_ docs: Documentation ready for generation with, for example, `Read the Docs`_ * pre-commit_ hook: Run your tests and linting (e.g. Flake8, Black) before you commit your code! -* bump2version_: Pre-configured version bumping with a single command +* bump-my-version_: Pre-configured version bumping with a single command +* `sphinx-intl`_ for French internationalization (i18n) and localization (l10n) of Sphinx docs (optional) * Auto-release to PyPI_ when you push a new tag to main (optional) * Command line interface using Click (optional) @@ -49,11 +50,11 @@ Quickstart Install the latest Cookiecutter if you haven't installed it yet (this requires Cookiecutter 1.4.0 or higher):: - pip install -U cookiecutter + $ pip install -U cookiecutter Generate a Python package project:: - cookiecutter https://github.com/Ouranosinc/cookiecutter-pypackage.git + $ cookiecutter https://github.com/Ouranosinc/cookiecutter-pypackage.git Then: @@ -136,7 +137,9 @@ I also accept pull requests on this, if they're small, atomic, and if they make .. _`pyproject.toml`: https://www.python.org/dev/peps/pep-0518/ .. _`pyup.io`: https://pyup.io/ .. _bump2version: https://github.com/c4urself/bump2version +.. _bump-my-version: https://github.com/callowayproject/bump-my-version .. _flit: https://flit.pypa.io/en/stable/ +.. _sphinx-intl: https://sphinx-intl.readthedocs.io/en/master/ .. _GitHub comparison view: https://github.com/tony/cookiecutter-pypackage-pythonic/compare/audreyr:master...master .. _`Nekroze/cookiecutter-pypackage`: https://github.com/Nekroze/cookiecutter-pypackage diff --git a/cookiecutter.json b/cookiecutter.json index 041c31494..c918373c9 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -12,6 +12,7 @@ "use_conda": "y", "add_pyup_badge": "n", "make_docs": "y", + "add_translations": "y", "command_line_interface": ["Click", "Argparse", "No command-line interface"], "create_author_file": "y", "open_source_license": [ diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index 2e9692ab9..2360deafd 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -4,6 +4,10 @@ PROJECT_DIRECTORY = Path().cwd().absolute() +def create_folder(folder_path): + Path(PROJECT_DIRECTORY).joinpath(folder_path).mkdir(parents=True, exist_ok=True) + + def remove_file(filepath): Path(PROJECT_DIRECTORY).joinpath(filepath).unlink() @@ -50,6 +54,10 @@ def replace_contents(filepath): remove_file(".readthedocs.yml") remove_file("environment-docs.yml") remove_folder("docs") + else: + create_folder("docs/apidoc") + if "{{ cookiecutter.add_translations }}" == "y": + create_folder("docs/locales") if "{{ cookiecutter.use_conda }}" != "y": remove_file("environment-dev.yml") diff --git a/tests/test_bake_project.py b/tests/test_bake_project.py index 4e81c7f78..51288f7fc 100644 --- a/tests/test_bake_project.py +++ b/tests/test_bake_project.py @@ -135,6 +135,16 @@ def test_bake_with_apostrophe_and_run_tests(cookies): assert result.project_path.is_dir() assert run_inside_dir("python -m coverage", str(result.project_path)) == 0 +def test_bake_without_translations(cookies): + with bake_in_temp_dir(cookies, extra_context={"add_translations": "n"}) as result: + found_toplevel_files = [f.name for f in result.project_path.iterdir()] + assert "docs" in found_toplevel_files + assert ".readthedocs.yml" in found_toplevel_files + assert "environment-docs.yml" in found_toplevel_files + + pyproject_path = result.project_path.joinpath("pyproject.toml") + with open(str(pyproject_path)) as pyproject_file: + assert "sphinx-intl" not in pyproject_file.read() def test_bake_without_docs(cookies): with bake_in_temp_dir(cookies, extra_context={"make_docs": "n"}) as result: diff --git a/{{cookiecutter.project_slug}}/.coveralls.yml b/{{cookiecutter.project_slug}}/.coveralls.yml deleted file mode 100644 index 4ecf44e5d..000000000 --- a/{{cookiecutter.project_slug}}/.coveralls.yml +++ /dev/null @@ -1,2 +0,0 @@ -service_name: github -# repo_token: YOURTOKENHERE diff --git a/{{cookiecutter.project_slug}}/.gitignore b/{{cookiecutter.project_slug}}/.gitignore index 79594f298..ab7e509ef 100644 --- a/{{cookiecutter.project_slug}}/.gitignore +++ b/{{cookiecutter.project_slug}}/.gitignore @@ -66,6 +66,8 @@ instance/ # Sphinx documentation docs/_build/ +docs/apidoc/modules.rst +docs/apidoc/{{ cookiecutter.project_slug }}*.rst # PyBuilder target/ diff --git a/{{cookiecutter.project_slug}}/.readthedocs.yml b/{{cookiecutter.project_slug}}/.readthedocs.yml index fbf80e08d..96dc4e7ca 100644 --- a/{{cookiecutter.project_slug}}/.readthedocs.yml +++ b/{{cookiecutter.project_slug}}/.readthedocs.yml @@ -14,6 +14,12 @@ build: os: ubuntu-22.04 tools: python: "mambaforge-22.9" + jobs: + pre_build: + - sphinx-apidoc -o docs/apidoc --private --module-first ${{ cookiecutter.project_slug }} + {%- if cookiecutter.add_translations == 'y' %} + - sphinx-build -M gettext docs docs/_build + {%- endif %} conda: environment: environment-docs.yml diff --git a/{{cookiecutter.project_slug}}/CONTRIBUTING.rst b/{{cookiecutter.project_slug}}/CONTRIBUTING.rst index e04399c9f..5e052074f 100644 --- a/{{cookiecutter.project_slug}}/CONTRIBUTING.rst +++ b/{{cookiecutter.project_slug}}/CONTRIBUTING.rst @@ -179,7 +179,7 @@ To run specific code style checks:: $ ruff {{ cookiecutter.project_slug }} tests $ flake8 {{ cookiecutter.project_slug }} tests -To get ``black``, ``isort ``blackdoc``, ``ruff``, and ``flake8`` (with plugins ``flake8-alphabetize`` and ``flake8-rst-docstrings``) simply install them with `pip` {% if cookiecutter.use_conda == 'y' %}(or `conda`) {% endif %}into your environment. +To get ``black``, ``isort``, ``blackdoc``, ``ruff``, and ``flake8`` (with plugins ``flake8-alphabetize`` and ``flake8-rst-docstrings``) simply install them with `pip` {% if cookiecutter.use_conda == 'y' %}(or `conda`) {% endif %}into your environment. Versioning/Tagging ------------------ @@ -194,18 +194,14 @@ A reminder for the **maintainers** on how to deploy. This section is only releva #. Update the `CHANGES.rst` file to change the `Unreleased` section to the current date. #. Bump the version in your branch to the next version (e.g. `v0.1.0 -> v0.2.0`):: - .. code-block:: shell - - $ bump-my-version bump minor # In most cases, we will be releasing a minor version - $ git push + $ bump-my-version bump minor # In most cases, we will be releasing a minor version + $ git push #. Create a pull request from your branch to `main`. -#. Once the pull request is merged, create a new release on GitHub. On the main branch, run: - - .. code-block:: shell +#. Once the pull request is merged, create a new release on GitHub. On the main branch, run:: - $ git tag v0.2.0 - $ git push --tags + $ git tag v0.2.0 + $ git push --tags This will trigger a GitHub workflow to build the package and upload it to TestPyPI. At the same time, the GitHub workflow will create a draft release on GitHub. Assuming that the workflow passes, the final release can then be published on GitHub by finalizing the draft release. diff --git a/{{cookiecutter.project_slug}}/Makefile b/{{cookiecutter.project_slug}}/Makefile index 7fc6767b7..a1f6a5670 100644 --- a/{{cookiecutter.project_slug}}/Makefile +++ b/{{cookiecutter.project_slug}}/Makefile @@ -24,6 +24,10 @@ export PRINT_HELP_PYSCRIPT BROWSER := python -c "$$BROWSER_PYSCRIPT" +{%- if cookiecutter.make_docs == 'y' and cookiecutter.add_translations == 'y' %} +LOCALES := docs/locales +{%- endif %} + help: @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) @@ -41,6 +45,9 @@ clean-build: ## remove build artifacts clean-docs: ## remove docs artifacts rm -f docs/apidoc/{{ cookiecutter.project_slug }}*.rst rm -f docs/apidoc/modules.rst + {%- if cookiecutter.add_translations == 'y' %} + rm -fr docs/locales/fr/LC_MESSAGES/*.mo + {%- endif %} $(MAKE) -C docs clean {% endif -%} @@ -90,6 +97,12 @@ coverage: ## check code coverage quickly with the default Python $(BROWSER) htmlcov/index.html {%- if cookiecutter.make_docs == 'y' %} +{%- if cookiecutter.add_translations == 'y' %} +initialize-translations: clean-docs ## initialize translations, ignoring autodoc-generated files + ${MAKE} -C docs gettext + sphinx-intl update -p docs/_build/gettext -d docs/locales -l fr +{%- endif %} + autodoc: clean-docs ## create sphinx-apidoc files: sphinx-apidoc -o docs/apidoc --private --module-first {{ cookiecutter.project_slug }} @@ -97,10 +110,21 @@ linkcheck: autodoc ## run checks over all external links found throughout the do $(MAKE) -C docs linkcheck docs: autodoc ## generate Sphinx HTML documentation, including API docs +{%- if cookiecutter.add_translations == 'y' %} + $(MAKE) -C docs html BUILDDIR="_build/html/en" +ifneq ("$(wildcard $(LOCALES))","") + ${MAKE} -C docs gettext + $(MAKE) -C docs html BUILDDIR="_build/html/fr" SPHINXOPTS="-D language='fr'" +endif +ifndef READTHEDOCS + $(BROWSER) docs/_build/html/en/html/index.html +endif +{%- else %} $(MAKE) -C docs html ifndef READTHEDOCS $(BROWSER) docs/_build/html/index.html endif +{%- endif %} servedocs: docs ## compile the docs watching for changes watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D . diff --git a/{{cookiecutter.project_slug}}/docs/conf.py b/{{cookiecutter.project_slug}}/docs/conf.py index 71e8439b3..214ebbe7a 100755 --- a/{{cookiecutter.project_slug}}/docs/conf.py +++ b/{{cookiecutter.project_slug}}/docs/conf.py @@ -91,6 +91,13 @@ # Usually you set "language" from the command line for these cases. language = "en" +{%- if cookiecutter.add_translations == 'y' %} + +# Sphinx-intl configuration +locale_dirs = ['locales/'] +gettext_compact = False # optional +{%- endif %} + # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path diff --git a/{{cookiecutter.project_slug}}/docs/index.rst b/{{cookiecutter.project_slug}}/docs/index.rst index eb6540534..172867453 100644 --- a/{{cookiecutter.project_slug}}/docs/index.rst +++ b/{{cookiecutter.project_slug}}/docs/index.rst @@ -10,13 +10,18 @@ readme installation usage - modules contributing {%- if cookiecutter.create_author_file == 'y' %} authors {%- endif %} changes +.. toctree:: + :maxdepth: 1 + :caption: All Modules + + apidoc/modules + Indices and tables ================== * :ref:`genindex` diff --git a/{{cookiecutter.project_slug}}/docs/usage.rst b/{{cookiecutter.project_slug}}/docs/usage.rst index 4add6fbac..28f0b22ce 100644 --- a/{{cookiecutter.project_slug}}/docs/usage.rst +++ b/{{cookiecutter.project_slug}}/docs/usage.rst @@ -2,6 +2,8 @@ Usage ===== -To use {{ cookiecutter.project_name }} in a project:: +To use {{ cookiecutter.project_name }} in a project: + +.. code-block:: python import {{ cookiecutter.project_slug }} diff --git a/{{cookiecutter.project_slug}}/environment-docs.yml b/{{cookiecutter.project_slug}}/environment-docs.yml index 844e3cc87..3b8c9c109 100644 --- a/{{cookiecutter.project_slug}}/environment-docs.yml +++ b/{{cookiecutter.project_slug}}/environment-docs.yml @@ -9,6 +9,9 @@ dependencies: - sphinx-autoapi - sphinx-codeautolink - sphinx-copybutton +{%- if cookiecutter.add_translations == 'y' %} + - sphinx-intl +{%- endif %} - sphinxcontrib-napoleon {%- if cookiecutter.command_line_interface|lower == 'click' %} - click diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml index b504a2fe4..996914a8d 100644 --- a/{{cookiecutter.project_slug}}/pyproject.toml +++ b/{{cookiecutter.project_slug}}/pyproject.toml @@ -69,6 +69,7 @@ dev = [ {%- endif %} "pre-commit>=3.3.2" ] +{%- if cookiecutter.make_docs == 'y' %} docs = [ # Documentation and examples "sphinx", @@ -77,6 +78,9 @@ docs = [ {%- endif %} "sphinx-codeautolink", "sphinx-copybutton", + {%- if cookiecutter.add_translations == 'y' %} + "sphinx-intl", + {%- endif %} "sphinx-rtd-theme>=1.0", "nbsphinx", "pandoc", @@ -84,6 +88,7 @@ docs = [ "ipykernel", "jupyter_client" ] +{%- endif %} {%- if cookiecutter.command_line_interface != 'No command-line interface' %} [project.scripts] @@ -192,6 +197,10 @@ exclude = [ "docs/_*", "docs/apidoc/modules.rst", "docs/apidoc/{{ cookiecutter.project_slug }}*.rst" + {%- if cookiecutter.add_translations == 'y' -%} + , + "docs/locales" + {%- endif %} {%- endif %} ] {%- if cookiecutter.use_black == 'y' %}