Skip to content

Commit f11f4b6

Browse files
authored
chore: merge pull request #85 from addon-stack/develop
2 parents 29539fa + 1954eb8 commit f11f4b6

9 files changed

Lines changed: 2629 additions & 1758 deletions

File tree

package-lock.json

Lines changed: 2541 additions & 1708 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,8 @@
122122
"@addon-core/inject-script": "^0.3.1",
123123
"@addon-core/storage": "^0.4.0",
124124
"@rsdoctor/rspack-plugin": "^1.3.11",
125-
"@rspack/cli": "^1.6.5",
126-
"@rspack/core": "^1.6.5",
125+
"@rspack/cli": "^1.6.7",
126+
"@rspack/core": "^1.6.7",
127127
"@svgr/webpack": "^8.1.0",
128128
"await-lock": "^3.0.0",
129129
"c12": "^3.3.2",
@@ -155,7 +155,7 @@
155155
"@commitlint/cli": "^20.1.0",
156156
"@commitlint/config-conventional": "^20.0.0",
157157
"@microsoft/api-extractor": "^7.53.1",
158-
"@release-it/conventional-changelog": "^10.0.1",
158+
"@release-it/conventional-changelog": "^10.0.4",
159159
"@swc/core": "^1.11.7",
160160
"@swc/jest": "^0.2.36",
161161
"@testing-library/jest-dom": "^6.6.3",
@@ -176,12 +176,12 @@
176176
"fs-extra": "^11.3.0",
177177
"globby": "^14.1.0",
178178
"husky": "^9.1.7",
179-
"jest": "^30.0.0",
179+
"jest": "^30.2.0",
180180
"jest-environment-jsdom": "^30.0.0",
181181
"jest-webextension-mock": "^4.0.0",
182182
"prettier": "^3.6.2",
183-
"release-it": "^19.0.5",
184-
"ts-node": "^10.9.2",
183+
"release-it": "^19.2.3",
184+
"ts-node": "^1.7.1",
185185
"tsup": "^8.5.0",
186186
"tsx": "^4.19.2",
187187
"uglify-js": "^3.19.3",
@@ -206,6 +206,7 @@
206206
}
207207
},
208208
"overrides": {
209+
"jsdom": "27.4.0",
209210
"html-rspack-tags-plugin": {
210211
"glob": "^10.4.1"
211212
},

src/cli/builders/locale/LocaleBuilder.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,15 @@ export default class LocaleBuilder implements LocaleBuilderContract {
4343
}
4444
}
4545

46-
if (this.browser === Browser.Opera) {
47-
/**
48-
* The Opera browser does not support RTL languages,
49-
* and for Opera you need to directly indicate what kind of language it is.
50-
* interface language is always different
51-
*/
52-
53-
items.set(LocaleCustomKeyForLanguage, this.language);
54-
}
46+
/**
47+
We inject a locale marker into each `messages.json`.
48+
Chrome provides no API to query the effective i18n locale:
49+
browser UI language may differ from the translation actually used
50+
(Chrome silently falls back to default_locale).
51+
Reading this marker via chrome.i18n.getMessage() is the only reliable way
52+
to detect the language currently displayed in the extension UI.
53+
*/
54+
items.set(LocaleCustomKeyForLanguage, this.language);
5555

5656
return (this.items = items);
5757
}

src/locale/adapters/react/LocaleProvider.tsx

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ import {Language} from "@typing/locale";
1010

1111
export interface LocaleProviderProps {
1212
storage?: string | false;
13+
container?: string | Element | false;
1314
}
1415

15-
const LocaleProvider = ({children, storage}: PropsWithChildren<LocaleProviderProps>) => {
16+
const LocaleProvider = ({children, storage, container = "html"}: PropsWithChildren<LocaleProviderProps>) => {
1617
const locale = useMemo(() => new DynamicLocale(storage), []);
1718

1819
const [lang, setLang] = useState<Language>(locale.lang());
@@ -26,15 +27,28 @@ const LocaleProvider = ({children, storage}: PropsWithChildren<LocaleProviderPro
2627
}, []);
2728

2829
const change: LocaleContract["change"] = useCallback((lang): void => {
29-
locale.change(lang).catch(err => console.error(`Cannot find locale file for "${lang}" language`, err));
30+
locale
31+
.change(lang)
32+
.catch(err => console.error(`[LocaleProvider] Cannot find locale file for "${lang}" language`, err));
3033
}, []);
3134

3235
useEffect(() => {
33-
const html = document.querySelector("html");
34-
35-
html?.setAttribute("lang", lang);
36-
html?.setAttribute("dir", getLocaleDir(lang));
37-
}, [lang]);
36+
if (container === false) {
37+
return;
38+
}
39+
40+
const element = typeof container === "string" ? document.querySelector(container) : container;
41+
42+
if (element) {
43+
element.setAttribute("lang", lang);
44+
element.setAttribute("dir", getLocaleDir(lang));
45+
46+
return () => {
47+
element.removeAttribute("lang");
48+
element.removeAttribute("dir");
49+
};
50+
}
51+
}, [lang, container]);
3852

3953
useEffect(() => {
4054
locale.sync().then(lang => setLang(lang));

src/locale/providers/NativeLocale.ts

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
1-
import {getI18nMessage, getI18nUILanguage} from "@addon-core/browser";
1+
import {getI18nMessage} from "@addon-core/browser";
22

33
import AbstractLocale from "./AbstractLocale";
44

5-
import {convertLocaleKey} from "@locale/utils";
5+
import {convertLocaleKey, resolveLanguage} from "@locale/utils";
66

7-
import {isBrowser} from "@main/env";
8-
9-
import {Language, LanguageCodes, LocaleCustomKeyForLanguage, LocaleProvider, LocaleStructure} from "@typing/locale";
10-
import {Browser} from "@typing/browser";
7+
import {Language, LocaleCustomKeyForLanguage, LocaleProvider, LocaleStructure} from "@typing/locale";
118

129
export interface LocaleNativeStructure extends LocaleStructure {}
1310

@@ -18,37 +15,47 @@ export default class NativeLocale extends AbstractLocale<LocaleNativeStructure>
1815
return (NativeLocale.instance ??= new NativeLocale());
1916
}
2017

21-
public lang(): Language {
22-
let lang: Language | undefined;
18+
private readonly language?: Language;
19+
20+
constructor() {
21+
super();
2322

2423
/**
25-
* The Opera browser does not support RTL languages,
26-
* and for Opera you need to directly indicate what kind of language it is.
27-
* interface language is always different
24+
Locale detection note:
25+
Chrome does NOT expose an API to get the effective translation locale.
26+
getUILanguage() returns browser UI language (e.g., es-MX),
27+
even if extension translations fall back to default_locale (e.g., en).
28+
To detect the actual language used by i18n, we read a locale marker
29+
from messages.json via chrome.i18n.getMessage().
2830
*/
29-
if (isBrowser(Browser.Opera)) {
30-
lang = getI18nMessage(LocaleCustomKeyForLanguage) as Language;
31+
const markerLang = getI18nMessage(LocaleCustomKeyForLanguage);
32+
const resolvedLang = resolveLanguage(markerLang);
3133

32-
if (LanguageCodes.has(lang)) {
33-
return lang;
34-
}
34+
if (!resolvedLang) {
35+
console.warn(`[NativeLocale] Unsupported language detected: "${markerLang}".`);
3536
}
3637

37-
lang = getI18nUILanguage() as Language;
38+
if (markerLang && resolvedLang && markerLang !== resolvedLang) {
39+
console.info(`[NativeLocale] Language normalized: using "${resolvedLang}" instead of "${markerLang}".`);
40+
}
3841

39-
if (!lang) {
40-
throw new Error("Locale Native: Unable to get UI language");
42+
this.language = resolvedLang;
43+
}
44+
45+
public lang(): Language {
46+
if (!this.language) {
47+
throw new Error("[NativeLocale] Language is not defined. Failed to determine a supported locale.");
4148
}
4249

43-
return lang;
50+
return this.language;
4451
}
4552

4653
public keys(): Set<keyof LocaleNativeStructure> {
4754
try {
4855
// @ts-expect-error: __ADNBN_LOCALE_KEYS__ is a virtual variable generated by the bundler `src/cli/plugins/locale/index.ts`
4956
return new Set<string>(__ADNBN_LOCALE_KEYS__);
5057
} catch (e) {
51-
console.error("Locale Native: Unable to get keys", e);
58+
console.error("[NativeLocale] Failed to access locale keys. Ensure the bundler plugin is active.", e);
5259

5360
return new Set<string>();
5461
}
@@ -59,7 +66,7 @@ export default class NativeLocale extends AbstractLocale<LocaleNativeStructure>
5966
// @ts-expect-error: __ADNBN_DEFINED_LOCALES__ is a virtual variable generated by the bundler `src/cli/plugins/locale/index.ts`
6067
return new Set<Language>(__ADNBN_DEFINED_LOCALES__);
6168
} catch (e) {
62-
console.error("Locale Native: Unable to get defined locales", e);
69+
console.error("[NativeLocale] Failed to access defined locales. Check virtual variables configuration.", e);
6370

6471
return new Set<Language>();
6572
}

src/locale/utils.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
Language,
3+
LanguageCodes,
34
LocaleDir,
45
LocaleKeyMarker,
56
LocaleKeysSeparator,
@@ -55,3 +56,17 @@ export const getLocaleDir = (lang: Language): LocaleDir => {
5556
export const flattenLocaleMessages = (messages: LocaleMessages): Record<string, string> => {
5657
return Object.fromEntries(Object.entries(messages).map(([key, value]) => [key, value.message]));
5758
};
59+
60+
export const resolveLanguage = (language?: string): Language | undefined => {
61+
if (!language) {
62+
return undefined;
63+
}
64+
65+
if (LanguageCodes.has(language as Language)) {
66+
return language as Language;
67+
}
68+
69+
const shortLang = language.slice(0, 2) as Language;
70+
71+
return LanguageCodes.has(shortLang) ? shortLang : undefined;
72+
};

src/message/providers/AbstractMessage.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ import {
1111
MessageType,
1212
} from "@typing/message";
1313

14-
export default abstract class AbstractMessage<T extends MessageDictionary, TOptions>
15-
implements MessageProvider<T, TOptions>
16-
{
14+
export default abstract class AbstractMessage<T extends MessageDictionary, TOptions> implements MessageProvider<
15+
T,
16+
TOptions
17+
> {
1718
public abstract send<K extends MessageType<T>>(
1819
type: K,
1920
data: MessageData<T, K>,

src/types/offscreen.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,7 @@ export type OffscreenMainHandler<T extends TransportType> = (
5353
) => Awaiter<void>;
5454

5555
export interface OffscreenDefinition<T extends TransportType>
56-
extends TransportDefinition<OffscreenOptions, T>,
57-
OffscreenEntrypointOptions {
56+
extends TransportDefinition<OffscreenOptions, T>, OffscreenEntrypointOptions {
5857
main?: OffscreenMainHandler<T>;
5958
}
6059

src/types/relay.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ export type RelayMainHandler<T extends TransportType> = (
2727
) => Awaiter<void>;
2828

2929
export interface RelayDefinition<T extends TransportType>
30-
extends Omit<TransportDefinition<RelayOptions, T>, "main">,
30+
extends
31+
Omit<TransportDefinition<RelayOptions, T>, "main">,
3132
Omit<ContentScriptDefinition, "main">,
3233
RelayEntrypointOptions {
3334
main?: RelayMainHandler<T>;

0 commit comments

Comments
 (0)