next-language-selector 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,12 +2,23 @@
2
2
 
3
3
  ![npm version](https://img.shields.io/npm/v/next-language-selector?color=3178C6&style=flat-square)
4
4
  ![bundle size](https://img.shields.io/bundlephobia/minzip/next-language-selector?color=black&style=flat-square)
5
+ [![codecov](https://codecov.io/gh/kirilinsky/next-language-selector/graph/badge.svg)](https://codecov.io/gh/kirilinsky/next-language-selector)
5
6
  ![security score](https://socket.dev/api/badge/npm/package/next-language-selector?style=flat-square)
6
- ![license](https://img.shields.io/npm/l/next-language-selector?color=gray&style=flat-square)
7
+ ![license](https://img.shields.io/npm/l/next-language-selector?color=2E7D32&style=flat-square)
7
8
 
8
9
  A lightweight, unstyled language selector for Next.js (App Router & Pages Router).
9
10
  Manages the `NEXT_LOCALE` cookie and works with `next-intl` or any i18n solution.
10
11
 
12
+ - [Installation](#installation)
13
+ - [Basic Usage](#basic-usage)
14
+ - [Styling](#styling)
15
+ - [Custom UI](#custom-ui)
16
+ - [Dropdown mode](#dropdown-mode)
17
+ - [Setup with next-intl](#setup-with-next-intl)
18
+ - [Props](#props)
19
+ - [`setLocaleCookie` utility](#setlocalecookie-utility)
20
+ - [SSR & hydration](#ssr--hydration)
21
+
11
22
  ## Key Features
12
23
 
13
24
  - **Next.js Native**: Built for the Next.js ecosystem (App Router & Pages Router).
@@ -164,6 +175,26 @@ interface LocaleConfig {
164
175
  }
165
176
  ```
166
177
 
178
+ ## `setLocaleCookie` utility
179
+
180
+ The cookie writer is exported separately — useful if you want to switch the locale from your own code (a settings page, a keyboard shortcut, etc.) without rendering the component:
181
+
182
+ ```ts
183
+ import { setLocaleCookie } from "next-language-selector";
184
+
185
+ // setLocaleCookie(locale, cookieName?, autoReload?)
186
+ setLocaleCookie("de"); // sets NEXT_LOCALE=de and reloads
187
+ setLocaleCookie("de", "MY_LOCALE", false); // custom cookie, no reload
188
+ ```
189
+
190
+ The name and value are URI-encoded (cookie-injection safe), written with `max-age=31536000; path=/; SameSite=Lax`. On the server it is a no-op.
191
+
192
+ ## SSR & hydration
193
+
194
+ The component renders `null` until it has mounted and read the cookie on the client, so server and client markup never mismatch. Expect the selector to appear only after hydration — reserve space with CSS if layout shift matters. Malformed or unknown cookie values are ignored and `defaultLocale` is used.
195
+
196
+ Buttons are rendered with `type="button"`, so placing the selector inside a `<form>` won't trigger submits.
197
+
167
198
  ## License
168
199
 
169
200
  MIT
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";var m=Object.defineProperty;var v=Object.getOwnPropertyDescriptor;var E=Object.getOwnPropertyNames;var x=Object.prototype.hasOwnProperty;var h=(t,o)=>{for(var r in o)m(t,r,{get:o[r],enumerable:!0})},P=(t,o,r,a)=>{if(o&&typeof o=="object"||typeof o=="function")for(let n of E(o))!x.call(t,n)&&n!==r&&m(t,n,{get:()=>o[n],enumerable:!(a=v(o,n))||a.enumerable});return t};var b=t=>P(m({},"__esModule",{value:!0}),t);var I={};h(I,{LanguageSelector:()=>w,setLocaleCookie:()=>i});module.exports=b(I);var s=require("react");var i=(t,o="NEXT_LOCALE",r=!0)=>{if(typeof document>"u")return;let a=encodeURIComponent(o),n=encodeURIComponent(t);document.cookie=`${a}=${n}; max-age=31536000; path=/; SameSite=Lax`,r&&window.location.reload()};var c=require("react/jsx-runtime");function k(t){let{locales:o,defaultLocale:r,cookieName:a="NEXT_LOCALE",isDropdown:n=!1,autoReload:p=!0,renderCustom:d,className:f,itemClassName:g}=t,[N,R]=(0,s.useState)(!1),[l,L]=(0,s.useState)(r);(0,s.useEffect)(()=>{let e=encodeURIComponent(a),C=document.cookie.split("; ").find(y=>y.startsWith(`${e}=`)),S=C?decodeURIComponent(C.split("=").slice(1).join("=")):null;S&&L(S),R(!0)},[a]);let u=(0,s.useCallback)(e=>{L(e),i(e,a,p)},[a,p]);return N?d?(0,c.jsx)(c.Fragment,{children:d({locales:o,currentLocale:l,onChange:u})}):n?(0,c.jsx)("select",{value:l,onChange:e=>u(e.target.value),className:f,children:o.map(e=>(0,c.jsxs)("option",{value:e.code,className:g,children:[e.flag," ",e.name]},e.code))}):(0,c.jsx)("div",{className:f,children:o.map(e=>(0,c.jsxs)("button",{onClick:()=>u(e.code),"data-active":l===e.code,"aria-pressed":l===e.code,className:g,children:[e.flag," ",e.name]},e.code))}):null}var w=k;0&&(module.exports={LanguageSelector,setLocaleCookie});
1
+ "use strict";var p=Object.defineProperty;var E=Object.getOwnPropertyDescriptor;var R=Object.getOwnPropertyNames;var h=Object.prototype.hasOwnProperty;var v=(t,e)=>{for(var n in e)p(t,n,{get:e[n],enumerable:!0})},x=(t,e,n,a)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of R(e))!h.call(t,r)&&r!==n&&p(t,r,{get:()=>e[r],enumerable:!(a=E(e,r))||a.enumerable});return t};var b=t=>x(p({},"__esModule",{value:!0}),t);var X={};v(X,{LanguageSelector:()=>P,setLocaleCookie:()=>u});module.exports=b(X);var s=require("react");var C=(t="NEXT_LOCALE")=>{if(typeof document>"u")return null;let e=encodeURIComponent(t),n=document.cookie.split(";").map(a=>a.trim()).find(a=>a.startsWith(`${e}=`));if(!n)return null;try{return decodeURIComponent(n.slice(e.length+1))}catch{return null}},u=(t,e="NEXT_LOCALE",n=!0)=>{if(typeof document>"u")return;let a=encodeURIComponent(e),r=encodeURIComponent(t);document.cookie=`${a}=${r}; max-age=31536000; path=/; SameSite=Lax`,n&&window.location.reload()};var c=require("react/jsx-runtime");function S(t){let{locales:e,defaultLocale:n,cookieName:a="NEXT_LOCALE",isDropdown:r=!1,autoReload:d=!0,renderCustom:f,className:g,itemClassName:L}=t,[k,y]=(0,s.useState)(!1),[l,i]=(0,s.useState)(n);(0,s.useEffect)(()=>{let o=C(a);o&&e.some(N=>N.code===o)?i(o):i(n),y(!0)},[a,n,e]);let m=(0,s.useCallback)(o=>{i(o),u(o,a,d)},[a,d]);return k?f?(0,c.jsx)(c.Fragment,{children:f({locales:e,currentLocale:l,onChange:m})}):r?(0,c.jsx)("select",{value:l,onChange:o=>m(o.target.value),className:g,children:e.map(o=>(0,c.jsxs)("option",{value:o.code,className:L,children:[o.flag," ",o.name]},o.code))}):(0,c.jsx)("div",{className:g,children:e.map(o=>(0,c.jsxs)("button",{type:"button",onClick:()=>m(o.code),"data-active":l===o.code,"aria-pressed":l===o.code,className:L,children:[o.flag," ",o.name]},o.code))}):null}var P=S;0&&(module.exports={LanguageSelector,setLocaleCookie});
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{useCallback as v,useEffect as E,useState as C}from"react";var l=(n,t="NEXT_LOCALE",c=!0)=>{if(typeof document>"u")return;let o=encodeURIComponent(t),r=encodeURIComponent(n);document.cookie=`${o}=${r}; max-age=31536000; path=/; SameSite=Lax`,c&&window.location.reload()};import{Fragment as x,jsx as i,jsxs as S}from"react/jsx-runtime";function k(n){let{locales:t,defaultLocale:c,cookieName:o="NEXT_LOCALE",isDropdown:r=!1,autoReload:u=!0,renderCustom:m,className:p,itemClassName:d}=n,[N,R]=C(!1),[a,f]=C(c);E(()=>{let e=encodeURIComponent(o),g=document.cookie.split("; ").find(y=>y.startsWith(`${e}=`)),L=g?decodeURIComponent(g.split("=").slice(1).join("=")):null;L&&f(L),R(!0)},[o]);let s=v(e=>{f(e),l(e,o,u)},[o,u]);return N?m?i(x,{children:m({locales:t,currentLocale:a,onChange:s})}):r?i("select",{value:a,onChange:e=>s(e.target.value),className:p,children:t.map(e=>S("option",{value:e.code,className:d,children:[e.flag," ",e.name]},e.code))}):i("div",{className:p,children:t.map(e=>S("button",{onClick:()=>s(e.code),"data-active":a===e.code,"aria-pressed":a===e.code,className:d,children:[e.flag," ",e.name]},e.code))}):null}var A=k;export{A as LanguageSelector,l as setLocaleCookie};
1
+ import{useCallback as E,useEffect as R,useState as L}from"react";var g=(a="NEXT_LOCALE")=>{if(typeof document>"u")return null;let o=encodeURIComponent(a),n=document.cookie.split(";").map(t=>t.trim()).find(t=>t.startsWith(`${o}=`));if(!n)return null;try{return decodeURIComponent(n.slice(o.length+1))}catch{return null}},u=(a,o="NEXT_LOCALE",n=!0)=>{if(typeof document>"u")return;let t=encodeURIComponent(o),c=encodeURIComponent(a);document.cookie=`${t}=${c}; max-age=31536000; path=/; SameSite=Lax`,n&&window.location.reload()};import{Fragment as h,jsx as i,jsxs as C}from"react/jsx-runtime";function S(a){let{locales:o,defaultLocale:n,cookieName:t="NEXT_LOCALE",isDropdown:c=!1,autoReload:m=!0,renderCustom:p,className:d,itemClassName:f}=a,[k,y]=L(!1),[r,s]=L(n);R(()=>{let e=g(t);e&&o.some(N=>N.code===e)?s(e):s(n),y(!0)},[t,n,o]);let l=E(e=>{s(e),u(e,t,m)},[t,m]);return k?p?i(h,{children:p({locales:o,currentLocale:r,onChange:l})}):c?i("select",{value:r,onChange:e=>l(e.target.value),className:d,children:o.map(e=>C("option",{value:e.code,className:f,children:[e.flag," ",e.name]},e.code))}):i("div",{className:d,children:o.map(e=>C("button",{type:"button",onClick:()=>l(e.code),"data-active":r===e.code,"aria-pressed":r===e.code,className:f,children:[e.flag," ",e.name]},e.code))}):null}var A=S;export{A as LanguageSelector,u as setLocaleCookie};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-language-selector",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Configurable language selector for Next.js",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",
@@ -59,5 +59,6 @@
59
59
  "typescript": "^5.9.3",
60
60
  "vitest": "^4.1.2"
61
61
  },
62
- "sideEffects": false
63
- }
62
+ "sideEffects": false,
63
+ "packageManager": "pnpm@10.32.1"
64
+ }