@reform-digital/era-careers
v1.0.0
Published
ERA Careers localization
Downloads
117
Readme
Localization Module
Client-side localization for country and language selection with cookie persistence. Used in Webflow projects to drive country/locale dropdowns, URL locale prefixes, contact/social data per office, and optional Weglot translation integration.
This repo also includes a country-specific homepage schema helper script, country-home-schema.js, for generating JSON-LD on country home pages from CMS or page-level data attributes.
Overview
- Country & language selection via dropdowns; selection is stored in a cookie and in memory.
- URL locale support: Paths can use a locale prefix (e.g.
/de/,/pt-br/). English can stay at root (no/en). - Persistence: Cookie stores country name/code/slug, language list, selected language, SpendVue flag, and translator language code.
- Redirects: Switching country can redirect (e.g.
/contact↔/office/{slug}, home, or locale-prefixed URLs). - Contact & social data: Footer company/address and social links are loaded from the appropriate source page (International →
/contact, others →/office/{country-slug}) and applied to targets on the current page. - SpendVue: Optional visibility toggles via
[spend-vue=true]/[spend-vue=false]. - Weglot: Optional sync of selected language with the Weglot widget.
- Greek: When Greek is active, a CSS custom property is set so headings use the body font (e.g. for missing glyphs).
- First-visit modal: Optional localization modal can show once per browser/session state and be dismissed permanently.
Stored Data (Cookie)
Cookie name: localization. Stored as JSON with:
| Field | Description |
| ------------------------------ | -------------------------------------------------------------------------------- |
| countryName | Display name (e.g. "International", "United Kingdom") |
| countryCode | Code (e.g. INT, GB) |
| countrySlug | URL slug for country (e.g. united-kingdom) |
| languages | Array of language names for the selected country |
| isSpendVue | true if the selected locale has SpendVue |
| selectedLanguage | Display name of selected language |
| selectedLanguageAbbreviation | BCP-style code (e.g. en, pt-br) |
| selectedLanguageConveyThis | Legacy cookie field reused to store the translator language code for widget sync |
DOM Attributes Reference
The script uses data attributes to find and update elements. Add these in Webflow (or your CMS) so the script can bind correctly.
Country home schema
The country-home-schema.js script generates the base JSON-LD for country-specific home pages and fills it from page data attributes. If a script element with id="country-home-schema" is not already present, the script creates one in <head> automatically.
| Selector / Attribute | Purpose |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| #country-home-schema | JSON-LD script element written by the script. It may already exist, but it no longer needs to be added manually to each page. |
| data-schema-country-name | Country display name used for the page name and areaServed.name. |
| data-schema-name | Office/legal name used for mainEntity.name. |
| data-schema-email | Office email used for mainEntity.email. |
| data-schema-phone | Office phone used for mainEntity.telephone. |
| data-schema-address | Single-line address used as mainEntity.address.streetAddress. |
| data-schema-linkedin | LinkedIn URL added to mainEntity.sameAs. |
| data-schema-x | X URL added to mainEntity.sameAs. |
| data-schema-vimeo | Vimeo URL added to mainEntity.sameAs. |
| data-schema-youtube | YouTube URL added to mainEntity.sameAs. |
| data-schema-instagram | Instagram URL added to mainEntity.sameAs. |
Behavior summary:
- Reads the first element found for each
data-schema-*attribute. - Creates a base
WebPageschema if#country-home-schemais missing. - Updates the root schema name to
ERA Group {Country}when a country name is present. - Sets
schema.urlfrom the current page path. - Ensures
mainEntityexists as aLocalBusiness. - Ensures
mainEntity.addressexists as aPostalAddress. - Ensures
mainEntity.areaServedexists as aCountry. - Ensures
mainEntity.parentOrganizationexists as:
{
"@type": "Organization",
"@id": "/#organization",
"name": "E R Associates (Europe) Ltd",
"alternateName": "ERA Group"
}- Removes empty values and removes empty nested objects before writing the JSON-LD back.
Country / locale
| Selector / Attribute | Purpose |
| ----------------------------- | ------------------------------------------------------------------------------------------------------- |
| [localization=locale] | One per country/locale option. Wraps the clickable area (e.g. dropdown item). |
| localization-country | On the locale element. Country display name (e.g. International, Austria). |
| localization-country-code | On the locale element. Country code (e.g. INT, AT). |
| localization-country-slug | Optional but preferred on the locale element. Canonical country slug (e.g. united-kingdom, mexico). |
| [localization=country-code] | Element whose textContent is set to the current country code (e.g. in nav). |
Inside each [localization=locale] you can have:
localization-country-slugis preferred forcountrySlugresolution. If it is missing, the script falls back to a country link such as<a href="/countries/austria" fs-list-element="item-link">ora[href^="/countries/"].- Language items:
[localization-language]with value = language name (e.g.English,German). - Optional:
[localization-sv=true]to mark this locale as SpendVue. - Optional:
localization-language-abbreviationordata-language-abbreviationon language elements for the BCP code.
Language list (dropdown)
| Selector / Attribute | Purpose |
| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------- |
| [localization-language=list] | Container for the list of language options (e.g. dropdown list). |
| [localization-language] | Each language option. Attribute value = language name (e.g. English). Skip value list (reserved for the container). |
| [localization=language] | Element whose textContent is set to the current language label (e.g. dropdown toggle text). |
Optional on a language item:
- Inner
divor text — can be used as the display abbreviation (e.g.EN,DE).
First-visit localization modal
| Selector / Attribute | Purpose |
| ---------------------------------- | --------------------------------------------------------------------------------------------- |
| [loc-modal-element="modal-loc"] | Modal root. Script shows it with display: flex and hides it with display: none. |
| [loc-modal-element="close-loc"] | Close trigger(s) inside modal. All matching elements are wired (overlay, close button, etc.). |
| [localization=full-country-name] | Heading text target populated with current full country name or International. |
SpendVue visibility
| Selector | Behavior |
| ------------------- | --------------------------------------------------------- |
| [spend-vue=true] | Shown when current country is SpendVue; hidden otherwise. |
| [spend-vue=false] | Hidden when current country is SpendVue; shown otherwise. |
Contact data (footer)
Source content is read from another page and injected into targets on the current page.
| Attribute | Role |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| [contact-data=company-source] | On the source page (e.g. /contact or /office/{slug}). Wrapper whose innerHTML is the company block. |
| [contact-data=address-source] | On the source page. Wrapper whose innerHTML is the address block. |
| [contact-data=company-target] | On the current page. Element that receives the company HTML. |
| [contact-data=address-target] | On the current page. Element that receives the address HTML. |
Social links (footer)
| Attribute | Role |
| -------------------------------- | ----------------------------------------------------------------------------- |
| [social-data=linkedin-source] | On the source page. Element (or link inside it) holding the LinkedIn URL. |
| [social-data=instagram-source] | Same for Instagram. |
| [social-data=youtube-source] | Same for YouTube. |
| [social-data=x-source] | Same for X (Twitter). |
| [social-data=vimeo-source] | Same for Vimeo. |
| [social-data=linkedin-target] | On the current page. Link to update with LinkedIn href. |
| [social-data=instagram-target] | Same for Instagram. |
| [social-data=youtube-target] | Same for YouTube. |
| [social-data=x-target] | Same for X. |
| [social-data=vimeo-target] | Same for Vimeo. |
CMS list reorder and limit
| Selector / Attribute | Purpose |
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [localization=reorder-list] | List container (e.g. CMS list). Items are reordered so entries matching the current country appear first. |
| localization-limit | Optional, on the same list. Integer. After reorder, only this many items are shown; the rest are set to display: none. |
| localization-visible-display | Optional, on the list or individual item. Controls which inline display value is applied when an item should be visible, useful if items start hidden in site CSS. |
| [fs-list-field="country"] | On each list item. Element(s) whose text is the country name; used to match the current country. |
List items are detected as [role="listitem"] or .w-dyn-item, including nested containers such as .swiper-wrapper. If the list lives inside a Swiper, the script refreshes the Swiper after reordering. Visible items get an explicit inline display value so lists can safely start hidden in CSS. The default visible display is flex, unless overridden by localization-visible-display. If localization-limit is missing or invalid, all items are shown. Items without readable [fs-list-field="country"] text are still shown and keep their relative order after the known-country items. The reorder logic also rechecks briefly after the first pass so late CMS/nested content can be corrected without forcing unnecessary DOM changes every poll.
Form select (country)
Country select fields (e.g. on contact forms) can be synced with the current country. The script looks for elements that match selectors like #country input[type="checkbox"][fs-list-field="country"] and updates selection to match stored country.
URL and locale behavior
- Locale prefix: First path segment matching
aa,aa-bb, oraa-123is treated as the locale prefix. In this project these values are translator-defined language codes fromlocalization-language-abbreviation, not generic country-region guesses. - Helpers: The script uses internal helpers to get/strip/add locale prefix and to build “country home” paths with the preferred language prefix (e.g.
getCountryHomePath(slug)). English can be normalized to no prefix (e.g./en/→/). - Redirects on country/language change:
- On
/office/{slug}: switching country goes to the new/office/{new-slug}or to/contactfor International. - On
/contact: switching to a non-International country goes to/office/{slug}. - On a real country home path, switching country goes to the new country home.
- On regular non-home pages, switching country keeps the same page path and updates only the locale prefix when needed.
- On
- Direct root-home visits: When
/is visited without a locale prefix and the stored cookie already points to a non-International country, the script redirects to that country home page. - URL-first default order (when stored country is missing/empty): explicit country from URL path (
/{country}or/office/{country}) -> matching country from supported country-filter query params on/consultants,/case-studies,/insights-> locale-prefix match (/hu/...) -> International fallback. - Canonical locale defaults: When the same attribute-defined locale matches multiple countries, the script can apply a JS-side canonical default. Latin America language entries default to Mexico when the Mexico item uses that same attribute code.
- International language pinning: For automatic or manual International selection, language is forced to English (United States) (en).
Weglot integration
- Selected language can be synced to the Weglot widget. Session state is used to avoid redundant sync.
- If no explicit Weglot code is provided, the script falls back to the language abbreviation (
localization-language-abbreviationordata-language-abbreviation) before considering legacy visible text.
Greek and fonts
When the active language is Greek (by name, abbreviation, or URL locale), the script sets a CSS variable (e.g. --_typography---font-styles--heading) to the body font so headings don’t use a font that lacks Greek glyphs.
Initialization and observers
- DOMContentLoaded: The main entry point runs on
DOMContentLoadedand:- Loads from cookie into
LocalizationData. - Applies URL-locale override when the path has a locale and stored state is International or doesn’t match.
- If no stored data or no country, auto-selects from URL first and then falls back to International with retries.
- Uses URL-first default resolution before International fallback (locale prefix first, then country-home slug).
- Enforces English (United States) when country is International.
- Initializes first-visit localization modal behavior when modal attributes are present.
- Applies stored data to UI (country code, language list filter, SpendVue visibility, Greek font, Weglot sync).
- Sets up click delegation for
[localization=locale]and[localization-language], and handlers for locale-aware navigation (home, contact, etc.). - Runs reorder for
[localization=reorder-list], contact/social sync, and country select sync.
- Loads from cookie into
- MutationObserver: A observer watches the nav dropdown container (e.g.
.nav_dropdowns) for child/subtree changes and reapplies stored data when localization elements appear (e.g. after CMS or dynamic UI loads). - Cookie verification: A timer periodically checks that the cookie and in-memory data are in sync and restores the cookie from memory if it was lost.
Event handling (summary)
- Clicks on
[localization=locale]: Extract country/languages/SpendVue from that element, save to cookie and memory, run redirect logic (office/contact/home/locale), then apply UI updates, contact/social sync, reorder, and Weglot sync. Dropdown is closed after a short delay. - International locale click behavior: International selection forces language to English (United States) instead of selecting the first visible language item.
- Clicks on language items (inside
[localization-language=list]): Update selected language and abbreviation (and Weglot code), persist, optionally redirect to locale-prefixed URL, update language display and Weglot. - Home / contact / locale-aware links: Handlers can redirect to the appropriate locale or country path before navigation.
- Modal dismissal behavior: Clicking any [loc-modal-element="close-loc"] or selecting a locale inside the modal dismisses it and stores a one-time dismissal flag.
Dependencies and environment
- Browser: Relies on DOM,
document.cookie,sessionStorage, and optional Weglot/Webflow/FinSweet globals. - Webflow: Uses classes like
.w-dropdown,.w-dropdown-toggle,.w-dyn-item,.nav_dropdowns,.nav_dropdown-linkwhere applicable. - FinSweet: Can use
fs-list-element,fs-list-fieldfor CMS-driven links and country fields. Reorder and retry logic account for delayed CMS rendering.
Build and usage
This repo builds every file in src/files (for example via pnpm build or pnpm prod). The output is intended to be loaded in Webflow (for example from the prod folder or via CDN).
Include the built localization.js script on pages that use the localization attributes above so that country/language selection, redirects, contact/social sync, and reorder/list limits work as described.
Include the built country-home-schema.js script on country home pages that contain:
- page elements carrying the
data-schema-*attributes listed above
Important:
- Files inside
src/filesmust be plain JavaScript or CSS. Do not wrapcountry-home-schema.jsin<script>...</script>tags. - You can remove the inline
#country-home-schemaJSON-LD block from individual country pages; the script now creates and populates it centrally. - The local dev server serves built files from
dev/, not directly fromsrc/files.
Recent Updates
- Locale-aware navigation: Generic same-origin page links are intercepted and rewritten to the stored locale prefix before navigation (with specialized handlers still taking precedence for home/contact).
- Faster redirect handling: Redirect checks run in capture-phase click handling to reduce delay and avoid intermediate hops caused by other listeners.
- Missing locale fallback on load: If a non-English locale is stored and an unprefixed URL loads (for example
/case-studies), the script can redirect to the localized equivalent (/hu/case-studies). - Stored home redirect on root visits: Visiting the bare root domain (
/) now reuses the stored country cookie and redirects to the matching country home instead of staying on the International homepage. - Attribute-first locale resolution: Country matching now prefers
localization-country-slug, reads URL locale codes fromlocalization-language-abbreviation, and keeps Latin America locale-only first visits defaulting to Mexico without hard-coded language codes. - First-visit URL hint hardening: First-load country selection now also respects explicit country slugs in localized paths and supported country-filter query params when they match the current locale, and filtered content pages reload on same-locale country switches so the country prefilter can reset cleanly.
- Contact/office redirect hardening: Contact and office redirects now use stronger language/country fallback resolution to reduce two-step
/office/...then/{locale}/office/...behavior. - Consultants prefilter behavior: Stored non-International country filter is pre-applied on each page visit; users can still clear/change filters during the visit.
- Case studies/insights prefilter behavior: Same as consultants — pre-applied on each visit from stored country, but not force-reapplied after user interaction on that same load.
- URL-first default country selection: First-load default selection now checks locale-prefixed URLs and country-home slugs before any International fallback.
- International language hardening: International selection now always stores/uses English (United States) (en) for both automatic and manual International selection.
- Localization modal support: Added one-time modal support via
loc-modal-element="modal-loc"with multi-element close handling (loc-modal-element="close-loc") and dynamic heading country text ([localization=full-country-name]). - Country home schema support:
country-home-schema.jsnow creates the base JSON-LD centrally, fills it fromdata-schema-*attributes, references/#organization, and removes empty schema fields before output. - CMS reorder hardening: Localized CMS reorder now supports nested slider/list containers, defaults visible items to
flex, shows all items when no validlocalization-limitis set, and performs short follow-up checks for late nested CMS data without reordering unnecessarily on every poll. - Country-switch redirect narrowing: Top-level static pages are no longer treated as country home pages unless the path slug matches a known country slug, so regular pages keep their path and only switch locale when appropriate.
