diff --git a/AUTHORS.txt b/AUTHORS.txt index b25d81062d..3df09ac8db 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -52,6 +52,7 @@ * `follower `_ * `George Leslie-Waksman `_ * `Grzegorz Śliwiński `_ +* `gudzpoz `_ * `Guillermo O. Freschi `_ * `h4ckninja `_ * `Hardening `_ diff --git a/CHANGES.txt b/CHANGES.txt index 91a21b7a86..2c8ddb91ab 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -4,9 +4,13 @@ New in master Features -------- +* Support using ``link://kind/name?lang=some_lang`` to link to pages in specific languages + (Issue #3811) + Bugfixes -------- +* Fix ``link://filename/`` magic links to handle multilingual pages (Issue #3811) * Ignore errors in parsing SVG files for shrinking them, copy original file to output instead (Issue #3785) * Restore `annotation_helper.tmpl` with dummy content - fix themes still mentioning it diff --git a/docs/path_handlers.rst b/docs/path_handlers.rst index d771ae1f29..12137d0400 100644 --- a/docs/path_handlers.rst +++ b/docs/path_handlers.rst @@ -5,10 +5,12 @@ .. DO NOT EDIT, this file is auto-generated by scripts/document_path_handlers.py Nikola supports special links with the syntax ``link://kind/name``. In -templates you can also use ``_link(kind, name)``. You can add query strings +templates you can also use ``_link(kind, name)``. You can add query strings (``?key=value``) for extra arguments, or pass keyword arguments to ``_link`` in -templates (support and behavior depends on path handlers themselves). Fragments -(``#anchor``) will be appended to the transformed link. +templates (support and behavior depends on path handlers themselves). By default, +you can only link to current language's contents; in order to link to a different +language, use ``link://kind/name?lang=other_lang`` or ``_link(kind, name, lang)``. +Fragments (``#anchor``) will be appended to the transformed link. Here are the descriptions for all the supported kinds. @@ -83,7 +85,6 @@ filename Example: link://filename/manual.txt => /docs/handbook.html - gallery Link to an image gallery's path. @@ -94,7 +95,6 @@ gallery link://gallery/london => /galleries/trips/london/index.html link://gallery/trips/london => /galleries/trips/london/index.html - gallery_global Link to the global gallery path, which contains all the images in galleries. @@ -106,7 +106,6 @@ gallery_global link://gallery_global/trips/london => /galleries/trips/london/index.html (a ``gallery`` link could lead to eg. /en/galleries/trips/london/index.html) - gallery_rss Link to an image gallery's RSS feed. @@ -117,7 +116,6 @@ gallery_rss link://gallery_rss/london => /galleries/trips/london/rss.xml link://gallery_rss/trips/london => /galleries/trips/london/rss.xml - index Link to a numbered index. @@ -150,7 +148,6 @@ listing link://listing/hello.py => /listings/tutorial/hello.py.html link://listing/tutorial/hello.py => /listings/tutorial/hello.py.html - listing_source Return a link to the source code for a listing. @@ -162,7 +159,6 @@ listing_source link://listing_source/hello.py => /listings/tutorial/hello.py link://listing_source/tutorial/hello.py => /listings/tutorial/hello.py - post_path Link to the destination of an element in the POSTS/PAGES settings. @@ -170,7 +166,6 @@ post_path Example: link://post_path/posts => /blog - root Link to the current language's root. @@ -180,7 +175,6 @@ root link://root_path => / link://root_path => /translations/spanish/ - rss A link to the RSS feed path. @@ -195,7 +189,6 @@ slug Example: link://slug/yellow-camaro => /posts/cars/awful/yellow-camaro/index.html - tag A link to a tag's page. Takes page number as optional keyword argument. diff --git a/nikola/nikola.py b/nikola/nikola.py index 8fa0463cd3..892113b5e2 100644 --- a/nikola/nikola.py +++ b/nikola/nikola.py @@ -1565,6 +1565,10 @@ def url_replacer(self, src, dst, lang=None, url_type=None): else: link_kwargs = {} + if 'lang' in link_kwargs: + lang = link_kwargs['lang'] + del link_kwargs['lang'] + # unquote from issue #2934 dst = self.link(dst_url.netloc, unquote(dst_url.path.lstrip('/')), lang, **link_kwargs) if dst_url.fragment: @@ -1960,20 +1964,26 @@ def slug_path(self, name, lang): utils.LOGGER.warning('Ambiguous path request for slug: {0}'.format(name)) return [_f for _f in results[0].permalink(lang).split('/')] - def filename_path(self, name, lang): + def filename_path(self, name, _lang): """Link to post or page by source filename. Example: link://filename/manual.txt => /docs/handbook.html """ - results = [p for p in self.timeline if p.source_path == name] - if not results: - utils.LOGGER.warning("Cannot resolve path request for filename: {0}".format(name)) - else: + name = os.path.normpath(name) + results = [p for p in self.timeline if os.path.normpath(p.source_path) == name] + if results: if len(results) > 1: utils.LOGGER.error("Ambiguous path request for filename: {0}".format(name)) - return [_f for _f in results[0].permalink(lang).split('/') if _f] + else: + # Probably multilingual post, we have to iterate through the posts more costly + results = self.timeline + for p in results: + for lang in p.translated_to: + if os.path.normpath(p.translated_source_path(lang)) == name: + return [_f for _f in p.permalink(lang).split('/') if _f] + utils.LOGGER.warning("Cannot resolve path request for filename: {0}".format(name)) def register_path_handler(self, kind, f): """Register a path handler.""" diff --git a/scripts/document_path_handlers.py b/scripts/document_path_handlers.py index d86309ab9b..0f528e4833 100755 --- a/scripts/document_path_handlers.py +++ b/scripts/document_path_handlers.py @@ -11,10 +11,12 @@ .. DO NOT EDIT, this file is auto-generated by scripts/document_path_handlers.py Nikola supports special links with the syntax ``link://kind/name``. In -templates you can also use ``_link(kind, name)``. You can add query strings +templates you can also use ``_link(kind, name)``. You can add query strings (``?key=value``) for extra arguments, or pass keyword arguments to ``_link`` in -templates (support and behavior depends on path handlers themselves). Fragments -(``#anchor``) will be appended to the transformed link. +templates (support and behavior depends on path handlers themselves). By default, +you can only link to current language's contents; in order to link to a different +language, use ``link://kind/name?lang=other_lang`` or ``_link(kind, name, lang)``. +Fragments (``#anchor``) will be appended to the transformed link. Here are the descriptions for all the supported kinds. diff --git a/tests/data/translated_titles/pages/1.pl.txt b/tests/data/translated_titles/pages/1.pl.txt index a888c1fdb5..83bd95c703 100644 --- a/tests/data/translated_titles/pages/1.pl.txt +++ b/tests/data/translated_titles/pages/1.pl.txt @@ -2,3 +2,15 @@ .. slug: 1 Bar + +`en `_ + +`pl `_ + +`pl 2 `_ + +`file en `_ + +`file pl `_ + +`file pl 2 `_ diff --git a/tests/data/translated_titles/pages/1.txt b/tests/data/translated_titles/pages/1.txt index 45fb214528..947c97c4ad 100644 --- a/tests/data/translated_titles/pages/1.txt +++ b/tests/data/translated_titles/pages/1.txt @@ -3,3 +3,15 @@ .. date: 2001/01/01 00:00:00 Foo + +`en `_ + +`pl `_ + +`pl 2 `_ + +`file en `_ + +`file pl `_ + +`file pl 2 `_ diff --git a/tests/data/translated_titles/pages/2.pl.txt b/tests/data/translated_titles/pages/2.pl.txt new file mode 100644 index 0000000000..cc9d6054f8 --- /dev/null +++ b/tests/data/translated_titles/pages/2.pl.txt @@ -0,0 +1,14 @@ +.. title: Two +.. slug: 2 + +`en `_ + +`pl `_ + +`pl 2 `_ + +`file en `_ + +`file pl `_ + +`file pl 2 `_ diff --git a/tests/integration/test_translated_content.py b/tests/integration/test_translated_content.py index 9d1338f965..03190f919f 100644 --- a/tests/integration/test_translated_content.py +++ b/tests/integration/test_translated_content.py @@ -43,6 +43,37 @@ def test_translated_titles(build, output_dir, other_locale): assert doc.find("//title").text == "Bar | Demo Site" +def test_translated_cross_links(build, output_dir, other_locale): + """Check that cross-language links are correct.""" + files = [ + os.path.join(output_dir, "pages", "1", "index.html"), + os.path.join(output_dir, other_locale, "pages", "1", "index.html"), + os.path.join(output_dir, other_locale, "pages", "2", "index.html"), + ] + expected = { + "en": "pages/1", + "pl": "pl/pages/1", + "pl 2": "pl/pages/2", + "file en": "pages/1", + "file pl": "pl/pages/1", + "file pl 2": "pl/pages/2", + } + for f in files: + assert os.path.isfile(f) + with io.open(f, "r", encoding="utf8") as inf: + doc = lxml.html.parse(inf) + link_count = 0 + for link in doc.findall("//article//p//a"): + text = link.text + href = link.get("href") + if text in expected: + link_count += 1 + dest = os.path.join(os.path.dirname(f), href) + rel = os.path.relpath(dest, output_dir) + assert rel == os.path.normpath(expected[text]) + assert link_count == len(expected) + + @pytest.fixture(scope="module") def build(target_dir, test_dir): """Build the site."""