pelican-i18n-feeds
pelican-i18n-feeds is a Pelican plugin for multilingual sites built with i18n_subsites. It writes Atom feeds that list the articles of every language — including articles that only exist in a subsite language — and gives you a helper that wires one feed per language into your publishconf.py.
Why
With i18n_subsites, each language is built as its own site, and every article in a subsite language is removed from the main site. Pelican's feeds follow that:
- the main site's
FEED_ALL_ATOMlists the main-language articles plus their translations, so an article written only in Japanese (no Chinese original) is missing; - each subsite's feeds only hold that subsite's language;
- Pelican derives
FEED_DOMAINfrom the mainSITEURLbefore the subsite exists, so a subsite's feed links and Atom self links miss its/<lang>/prefix unless you fix it per subsite; - all feeds of one site share one Atom
<id>(the site URL).
Features
FEED_ALL_LANGUAGES_ATOM/CATEGORY_FEED_ALL_LANGUAGES_ATOM: a site feed and per-category feeds with the published articles of every language, each article once, in Pelican'sFEED_ALL_ATOMorder, limited byFEED_MAX_ITEMS- Only the main site writes them; subsites are detected automatically
- Optional per-feed Atom
<id>(I18N_FEEDS_URL_AS_ID), with path prefixes that keep Pelican's id for existing subscribers feed_settings()helper: one call returns the feed settings of the main site and of every subsite (any number of languages), includingFEED_DOMAINfixes,<head>link titles for attila >= 3.5.0, and the per-language RSS link ofSOCIAL- A build warning when
I18N_UNTRANSLATED_ARTICLESwould make subsite-only articles disappear
How it works
content/ (zh-tw, ja, en articles)
│
├─ main site (zh-tw) ── zh-tw/feeds/all.atom.xml FEED_ATOM: zh-tw only
│ └─ feeds/all.atom.xml FEED_ALL_LANGUAGES_ATOM: every language
├─ ja subsite ──────── ja/feeds/all.atom.xml FEED_ATOM: ja only
└─ en subsite ──────── en/feeds/all.atom.xml FEED_ATOM: en only
The main site still reads every article; i18n_subsites only takes the other-language ones out of its lists and, before any site writes a file, points them at their subsite URL. This plugin collects them back from the main site's generated_content when it writes the all-language feeds. See how it works for the details and the assumptions it makes about i18n_subsites.
Installation
pip install pelican-i18n-feeds
It depends on pelican>=4.7 and pelican-i18n-subsites>=0.9.0.
Setup
1. Add to pelicanconf.py
Both plugins must be listed; their order does not matter (they hook different signals):
PLUGINS = ["pelican.plugins.i18n_subsites", "pelican.plugins.i18n_feeds"]
I18N_UNTRANSLATED_ARTICLES = "remove" # or "keep"; see Known limitations
With no feed settings (the usual development config), the plugin does nothing.
2. Wire the feeds in publishconf.py
Two languages (main zh-tw, subsite ja):
from pelican.plugins.i18n_feeds import feed_settings
from pelicanconf import * # noqa: F403
from pelicanconf import DEFAULT_LANG, I18N_SUBSITES, LANGUAGE_NAMES, SITENAME, SOCIAL
SITEURL = "https://example.com"
FEED_MAX_ITEMS = 30
globals().update(
feed_settings(
siteurl=SITEURL,
sitename=SITENAME,
default_lang=DEFAULT_LANG,
subsites=I18N_SUBSITES,
language_names=LANGUAGE_NAMES, # {"zh-tw": "臺灣華語", "ja": "日本語"}
all_languages_labels={
"zh-tw": "全部語言 / All languages",
"ja": "すべての言語 / All languages",
},
social=SOCIAL,
)
)
# Each feed gets its own <id>, except the old feeds/ URLs subscribers hold.
I18N_FEEDS_URL_AS_ID = True
I18N_FEEDS_KEEP_ID_PREFIXES = ["feeds/"]
Three languages work the same way: put en in I18N_SUBSITES (and LANGUAGE_NAMES, optionally all_languages_labels) and the same call configures both subsites:
I18N_SUBSITES = {"en": {"SITENAME": "My blog"}, "ja": {"SITENAME": "私のブログ"}}
LANGUAGE_NAMES = {"zh-tw": "臺灣華語", "en": "English", "ja": "日本語"}
Result (default paths):
| Feed | Contents |
|---|---|
feeds/all.atom.xml, feeds/{slug}.atom.xml |
every language |
zh-tw/feeds/all.atom.xml, zh-tw/feeds/{slug}.atom.xml |
main language |
en/feeds/..., ja/feeds/... |
that subsite's language |
Notes on feed_settings():
- Call
globals().update(...)afterfrom pelicanconf import *: it replaces the module'sFEED_ALL_ATOM,FEED_ATOM,CATEGORY_FEED_ATOM,FEED_ALL_LANGUAGES_ATOM,CATEGORY_FEED_ALL_LANGUAGES_ATOM,FEED_LINK_TITLES,FEED_EXTRA_LINKS,I18N_FEEDS_URLS,I18N_FEEDS_ALL_LANGUAGES_URL,I18N_SUBSITESand, withsocial=,SOCIAL. Set any of them again after the call to override it. - In each subsite entry, it replaces the subsite's own
FEED_DOMAIN,FEED_ATOM,CATEGORY_FEED_ATOM,FEED_LINK_TITLES,FEED_EXTRA_LINKS(andSOCIALwithsocial=): they are what the layout is made of. ChangeI18N_SUBSITES[lang][...]after the call if one subsite needs something else. - It returns a new
I18N_SUBSITESbuilt from deep copies of your entries, so the result and theI18N_SUBSITESyou passed in share no mutable objects. Every value in a subsite entry therefore has to be deep-copyable: a lock, a module or a generator raisesTypeError(plain settings, functions, classes and compiled regexes are fine). - It raises
TypeErrorwhensubsites, an entry of it,language_namesorall_languages_labelsis not a mapping, andValueErrorwhendefault_langis also a subsite or aSOCIALentry is not a(name, link)pair. - You can also assign the keys you want one by one instead of
globals().update(...).
Migrating from the entertainment-blog local plugin
The local all_language_feeds plugin always rewrote feed ids. Here that is opt-in, so to keep the same output set both:
I18N_FEEDS_URL_AS_ID = True
I18N_FEEDS_KEEP_ID_PREFIXES = ["feeds/"]
Configuration
Plugin settings (read at build time):
| Setting | Default | Description |
|---|---|---|
FEED_ALL_LANGUAGES_ATOM |
None |
Path of the all-language site feed, e.g. "feeds/all.atom.xml". Unset: not written. |
FEED_ALL_LANGUAGES_ATOM_URL |
the path | URL of that feed, relative to FEED_DOMAIN, like Pelican's FEED_ALL_ATOM_URL. |
CATEGORY_FEED_ALL_LANGUAGES_ATOM |
None |
Path of the all-language category feeds, with {slug}. |
CATEGORY_FEED_ALL_LANGUAGES_ATOM_URL |
the path | URL of those feeds, with {slug}. |
I18N_FEEDS_URL_AS_ID |
False |
Use each feed's own URL as its Atom <id> (main site and subsites). Off: Pelican's id, the site URL, shared by all feeds of a site. |
I18N_FEEDS_KEEP_ID_PREFIXES |
() |
With I18N_FEEDS_URL_AS_ID, feeds whose URL path (without the leading /) starts with one of these keep Pelican's id. A string is one prefix. Include your SITEURL path if the site is not at the domain root ("blog/feeds/"). |
The all-language feeds are written by the main site only: a site whose DEFAULT_LANG is a key of I18N_SUBSITES (how i18n_subsites builds subsites) is skipped, so subsites need no override.
feed_settings() arguments (keyword-only):
| Argument | Default | Description |
|---|---|---|
siteurl |
required | Production SITEURL of the main site. |
sitename |
required | Main SITENAME; a subsite's own SITENAME override is used for its titles. |
default_lang |
required | Main DEFAULT_LANG. Must not be a subsite. |
subsites |
required | I18N_SUBSITES. A subsite SITEURL override is respected. |
language_names |
required | Language code → name used in titles; missing codes use the code. |
all_languages_labels |
None |
Language code → label of the all-language link on that language's pages; default "All languages". |
social |
None |
Main SOCIAL. When given, the RSS entry of the main site and of every subsite (its own SOCIAL, or this one) points at that site's language feed. |
feed_path |
"feeds/all.atom.xml" |
Site feed path, relative to each site root. |
category_feed_path |
"feeds/{slug}.atom.xml" |
Category feed path. |
separator |
" — " |
Joins the parts of a feed title. |
rss_names |
("rss", "rss-square", "feed") |
SOCIAL names (case-insensitive) that are the RSS link; the default is the list attila shows as its RSS icon. |
It returns these settings for the main site, and the same per-site keys inside each I18N_SUBSITES entry:
| Key | Main site | Each subsite |
|---|---|---|
FEED_ALL_ATOM |
None |
(inherited) |
FEED_ATOM / CATEGORY_FEED_ATOM |
<default_lang>/feed_path / <default_lang>/category_feed_path |
feed_path / category_feed_path |
FEED_ALL_LANGUAGES_ATOM / CATEGORY_FEED_ALL_LANGUAGES_ATOM |
feed_path / category_feed_path |
(skipped on subsites) |
FEED_DOMAIN |
(Pelican default: SITEURL) |
SITEURL/<lang> |
FEED_LINK_TITLES |
{"FEED_ATOM": "<site> — <language>", "CATEGORY_FEED_ATOM": "<site> — <language> — {name}"} |
same, for the subsite |
FEED_EXTRA_LINKS |
(("<site> — <label>", <all-language feed URL>),) |
same, for the subsite |
I18N_FEEDS_URLS |
{lang: feed URL} for every language |
same |
I18N_FEEDS_ALL_LANGUAGES_URL |
all-language feed URL | same |
SOCIAL |
with social= only |
with social= only |
I18N_SUBSITES |
new dict with the subsite keys merged | — |
Theme integration
attila >= 3.5.0
attila 3.5.0 reads FEED_LINK_TITLES (titles of the <head> feed links, keyed by setting name; category titles may use {name}) and FEED_EXTRA_LINKS (extra <head> links). With the helper, every page links its own language feed, its category feed on category pages, and the all-language feed. attila also shows the SOCIAL entry named RSS as the header RSS icon, which social= points at each language's feed.
attila < 3.5.0 ignores FEED_LINK_TITLES and FEED_EXTRA_LINKS: the links still point at the right feeds, with attila's default titles, and there is no <head> link to the all-language feed.
Other themes
The plugin does not depend on any theme. Themes that render FEED_DOMAIN + FEED_ATOM (Pelican's simple theme does) get the per-language links; themes that only link FEED_ALL_ATOM (such as notmyidea) show no feed link, because the helper sets it to None. For an RSS icon outside SOCIAL, read the feed URL from I18N_FEEDS_URLS[DEFAULT_LANG] (or I18N_FEEDS_ALL_LANGUAGES_URL) in your templates.
Known limitations
- Atom only. There are no RSS variants of the all-language feeds, and no all-language tag or author feeds.
- Set
I18N_UNTRANSLATED_ARTICLESto"remove"(or"keep"). With"hide"or unset (i18n_subsitesdefaults to"hide"),i18n_subsitesturns the main site's copy of each subsite-only article into a draft and the main site writes no page for it, so the all-language feeds leave those articles out (translations of main-site articles are still listed) and the plugin logs a warning."keep"lists them with their main-site URL (ARTICLE_LANG_URL). - Relies on
i18n_subsitesinternals. It expectsi18n_subsitesto build the subsites from the main site'sget_writersignal and to rewrite the removed articles' URLs before any site writes (true for 0.9.0 and 1.0.0). See how it works. - Feed
<id>changes are opt-in. Turning onI18N_FEEDS_URL_AS_IDchanges the id of existing feeds not covered byI18N_FEEDS_KEEP_ID_PREFIXES; most readers key on the feed URL, but some may treat the feed as new. - With three or more languages, an article translated into some but not all subsite languages makes Pelican warn about "2 original items" on the subsites lacking it. That is Pelican's translation grouping, not this plugin; mark the non-original versions with
Translation: trueto silence it.
License
MIT © Wei Lee
Metadata
Release files for pelican-i18n-feeds 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pelican_i18n_feeds-0.2.0.tar.gz | 11.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pelican_i18n_feeds-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 22.3 kB
Release files / pelican_i18n_feeds-0.2.0.tar.gz
| Download URL | pelican_i18n_feeds-0.2.0.tar.gz |
|---|---|
| Size | 11.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8836a04370a366db35de740cc9e4c4d93fbb1c76bad09f06b4dba17d95474be9
|
|
BLAKE2b-256 checksum How to use checksums |
6a4cc427363f9e186aae5965a5ddd3a5452d3353174d46b9c3a875f5abb0decd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.
Transparency logRelease files / pelican_i18n_feeds-0.2.0-py3-none-any.whl
| Download URL | pelican_i18n_feeds-0.2.0-py3-none-any.whl |
|---|---|
| Size | 11.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0c5386f56a52e9d4e8a0acd4aa9309524f869f57c2595e2c24f262ad47dabdd3
|
|
BLAKE2b-256 checksum How to use checksums |
8b8f594301821d6083259c24531678b89ad9198911c3f8328337971e7edba871
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.
Transparency log