addons-cm/website_sale_lazy_loading
GitHub Copilot cb32fb6c0d [I18N] website_sale_lazy_loading: add es, ca, eu and gl translations
All 13 terms of the POT, in the four languages, generated with polib from
the POT Odoo exported.

The wording follows what website_sale_aplicoop already says on the same
site, so the two shops do not name the same thing twice: "Cargar más
productos" / "Carrega més productes" / "Produktu gehiago kargatu",
"Produktuak Orrialdeko", and "karga geldoa" for lazy loading. The selection
labels are repeated verbatim inside the field help, so what the settings
page offers and what the help describes read as the same three options.

The two view terms carry markup: the globe icon of the website-specific
hint and the spinner next to the loading message. Both keep their tags and
attributes untouched, and only the human text -- the icon's title, the
message -- is translated. Note that Odoo leaves that globe title in English
in its own es and ca files; here it is translated, as it is the one thing
in that span a visitor can read.

Also moves the POT under i18n/, where Odoo looks for it and where the rest
of the addons keep theirs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 13:06:00 +02:00
..
controllers [ADD] website_sale_lazy_loading: load the /shop listing on demand 2026-08-24 12:33:09 +02:00
i18n [I18N] website_sale_lazy_loading: add es, ca, eu and gl translations 2026-08-24 13:06:00 +02:00
models [ADD] website_sale_lazy_loading: load the /shop listing on demand 2026-08-24 12:33:09 +02:00
readme [ADD] website_sale_lazy_loading: load the /shop listing on demand 2026-08-24 12:33:09 +02:00
static/src [ADD] website_sale_lazy_loading: load the /shop listing on demand 2026-08-24 12:33:09 +02:00
tests [ADD] website_sale_lazy_loading: load the /shop listing on demand 2026-08-24 12:33:09 +02:00
views [ADD] website_sale_lazy_loading: load the /shop listing on demand 2026-08-24 12:33:09 +02:00
__init__.py [ADD] website_sale_lazy_loading: load the /shop listing on demand 2026-08-24 12:33:09 +02:00
__manifest__.py [ADD] website_sale_lazy_loading: load the /shop listing on demand 2026-08-24 12:33:09 +02:00
LICENSE.txt [ADD] website_sale_lazy_loading: load the /shop listing on demand 2026-08-24 12:33:09 +02:00
README.rst [I18N] website_sale_lazy_loading: add es, ca, eu and gl translations 2026-08-24 13:06:00 +02:00

================================
Website Sale - Lazy Loading
================================

.. image:: https://img.shields.io/badge/license-AGPL--3-blue.svg
   :target: https://www.gnu.org/licenses/agpl-3.0-standalone.html
   :alt: License: AGPL-3
.. image:: https://img.shields.io/badge/Odoo-18.0-blue
   :alt: Odoo: 18.0

Loads the products of the eCommerce shop (``/shop``) **on demand** instead of
paginating them: the visitor keeps the products already on screen and the next
page is appended to the grid, either when they scroll to the bottom of the
listing (infinite scroll) or when they click a *Load more products* button.

It is the same idea as the lazy loading of ``website_sale_aplicoop``
(``/eskaera``), rebuilt for the standard shop.

What the module does
====================

- Adds a per-website setting choosing how the listing loads its pages:
  infinite scroll, a *Load more* button, or the standard pager.
- Adds ``/shop/lazy_products``, which renders one page of the listing as an
  HTML fragment (the product cards only) plus whether a further page exists.
- Replaces the product loop of ``website_sale.products`` with a call to a
  shared template, so the first page and the appended ones are rendered by the
  very same markup.
- Hides the standard pager from the frontend once the widget takes over.

The page size is the **Products per page** of the shop layout, the same value
the standard pager uses, so nothing has to be configured twice.

Design notes
============

- The ``/shop`` controller is not duplicated: ``/shop/lazy_products`` calls
  ``shop()`` and renders the grid out of the values it prepared. Everything
  the standard listing supports — search, categories, attributes, tags, price
  filter, sort order, pricelists — is therefore supported as is, and so are
  the values other modules add to the shop (the wishlist state, for example).
- The product cards come from ``website_sale.products_item``, so ribbons,
  prices, add-to-cart, wishlist or comparison buttons added by other modules
  are rendered exactly as on the first page. Public widgets are restarted on
  the appended cards through ``widgets_start_request``.
- Progressive enhancement: the first page and the pager are still rendered by
  the standard controller. Without JavaScript, the pager stays visible and the
  shop behaves exactly as it does without this module — which is also what
  search engine crawlers get.
- The frontend takes no decision about *what* to show: the server sends the
  mode, the URL of the current listing and each page of cards.

Configuration
=============

Go to *Website > Configuration > Settings > Shop - Products > Shop Product
Loading* and pick how the listing loads its pages. The setting is per website:

- **Infinite scroll** (default): the next page is appended when the visitor
  reaches the bottom of the listing. A *Load more products* button shows up as
  a fallback if a request fails or if the browser has no
  ``IntersectionObserver``.
- **Load more button**: the next page is appended when the visitor clicks the
  button. This is the friendlier option for keyboard and screen reader users,
  and it keeps the footer reachable.
- **Pager (standard)**: turns the module off and leaves the standard
  ``website_sale`` pager alone.

The number of products per page is the *Products per page* of the shop layout
(open ``/shop`` as editor, *Edit*, then the *Products Page* options).

Usage
=====

There is nothing to do at runtime. Worth knowing:

- Product links keep the page they belong to (``?page=3``), so coming back
  from a product page lands on the right part of the listing.
- Changing a filter, a category or the sort order is still a normal page load;
  lazy loading restarts from the first page of the new listing.
- Reloading the page starts again from the first page: the appended pages are
  not part of the URL.
- The endpoint answers with the same data ``/shop`` would render for that
  page, under the rights of the visitor making the request.

Known limitations
=================

- The listing is only appended, never reduced: it grows until the visitor
  leaves or reloads the page.
- Infinite scroll pushes the footer away as long as pages remain; use the
  button mode if the footer matters on your site.

Technical
=========

::

    controllers/website_sale.py   /shop values + /shop/lazy_products endpoint
    models/website.py             shop_lazy_loading (per website)
    views/..._templates.xml       shared grid template + lazy loading block
    static/src/js/...             public widget appending the pages
    i18n/                         es, ca, eu, gl (complete)

Tests: ``docker-compose run --rm odoo odoo $ODOO_ADDONS -d odoo --test-enable
--stop-after-init -u website_sale_lazy_loading``

Credits
=======

**Authors:** Criptomart

The approach (a server rendered fragment appended to the grid, an observer
close to the bottom of the listing and a button as a fallback) comes from the
lazy loading of the Eskaera shop in ``website_sale_aplicoop``, reimplemented
here on top of the standard ``website_sale`` controller and templates.