@lovalingo/lovalingo 0.5.28 → 0.6.0
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 +36 -0
- package/dist/chunk-2FZR2AKF.mjs +88 -0
- package/dist/chunk-7D5LBV45.mjs +46 -0
- package/dist/chunk-CJOSN7RA.mjs +90 -0
- package/dist/chunk-VAHA2TOX.mjs +3440 -0
- package/dist/chunk-ZMRCSUM7.mjs +26 -0
- package/dist/chunk-ZVYKEEUF.mjs +220 -0
- package/dist/core.d.mts +131 -0
- package/dist/core.d.ts +131 -0
- package/dist/core.js +3561 -0
- package/dist/core.mjs +19 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.d.ts +5 -25
- package/dist/index.js +3885 -28
- package/dist/index.mjs +33 -0
- package/dist/react-router.d.mts +101 -0
- package/dist/react-router.d.ts +101 -0
- package/dist/react-router.js +353 -0
- package/dist/react-router.mjs +14 -0
- package/dist/tanstack-router.d.mts +22 -0
- package/dist/tanstack-router.d.ts +22 -0
- package/dist/tanstack-router.js +162 -0
- package/dist/tanstack-router.mjs +8 -0
- package/package.json +34 -3
- package/dist/__tests__/languageFlags.test.d.ts +0 -1
- package/dist/__tests__/languageFlags.test.js +0 -42
- package/dist/__tests__/mergeEntitlements.test.d.ts +0 -1
- package/dist/__tests__/mergeEntitlements.test.js +0 -27
- package/dist/components/AixsterProvider.d.ts +0 -1
- package/dist/components/AixsterProvider.js +0 -1
- package/dist/components/LangLink.d.ts +0 -20
- package/dist/components/LangLink.js +0 -38
- package/dist/components/LangRouter.d.ts +0 -37
- package/dist/components/LangRouter.js +0 -191
- package/dist/components/LanguageSwitcher.d.ts +0 -17
- package/dist/components/LanguageSwitcher.js +0 -257
- package/dist/components/LovalingoProvider.d.ts +0 -10
- package/dist/components/LovalingoProvider.js +0 -413
- package/dist/components/NavigationOverlay.d.ts +0 -6
- package/dist/components/NavigationOverlay.js +0 -22
- package/dist/components/provider/__tests__/seoUtils.test.d.ts +0 -1
- package/dist/components/provider/__tests__/seoUtils.test.js +0 -13
- package/dist/components/provider/editModeUtils.d.ts +0 -6
- package/dist/components/provider/editModeUtils.js +0 -59
- package/dist/components/provider/localeUtils.d.ts +0 -8
- package/dist/components/provider/localeUtils.js +0 -46
- package/dist/components/provider/providerConstants.d.ts +0 -12
- package/dist/components/provider/providerConstants.js +0 -11
- package/dist/components/provider/seoUtils.d.ts +0 -8
- package/dist/components/provider/seoUtils.js +0 -118
- package/dist/components/provider/useEditModeOverlay.d.ts +0 -7
- package/dist/components/provider/useEditModeOverlay.js +0 -134
- package/dist/components/provider/useHistoryNavigationPatch.d.ts +0 -3
- package/dist/components/provider/useHistoryNavigationPatch.js +0 -47
- package/dist/components/provider/useProviderCache.d.ts +0 -12
- package/dist/components/provider/useProviderCache.js +0 -82
- package/dist/context/AixsterContext.d.ts +0 -3
- package/dist/context/AixsterContext.js +0 -2
- package/dist/context/LangContext.d.ts +0 -1
- package/dist/context/LangContext.js +0 -2
- package/dist/context/LangRoutingContext.d.ts +0 -8
- package/dist/context/LangRoutingContext.js +0 -7
- package/dist/context/LovalingoContext.d.ts +0 -1
- package/dist/context/LovalingoContext.js +0 -1
- package/dist/hooks/provider/useBundleLoading.d.ts +0 -33
- package/dist/hooks/provider/useBundleLoading.js +0 -380
- package/dist/hooks/provider/useDomRules.d.ts +0 -15
- package/dist/hooks/provider/useDomRules.js +0 -38
- package/dist/hooks/provider/useLinkAutoPrefix.d.ts +0 -12
- package/dist/hooks/provider/useLinkAutoPrefix.js +0 -146
- package/dist/hooks/provider/useNavigationPrefetch.d.ts +0 -12
- package/dist/hooks/provider/useNavigationPrefetch.js +0 -82
- package/dist/hooks/provider/usePageviewTracking.d.ts +0 -10
- package/dist/hooks/provider/usePageviewTracking.js +0 -44
- package/dist/hooks/provider/usePrehide.d.ts +0 -5
- package/dist/hooks/provider/usePrehide.js +0 -72
- package/dist/hooks/provider/useSitemapLinkTag.d.ts +0 -7
- package/dist/hooks/provider/useSitemapLinkTag.js +0 -28
- package/dist/hooks/provider/useStringMissReporting.d.ts +0 -14
- package/dist/hooks/provider/useStringMissReporting.js +0 -155
- package/dist/hooks/useAixster.d.ts +0 -6
- package/dist/hooks/useAixster.js +0 -14
- package/dist/hooks/useAixsterEdit.d.ts +0 -5
- package/dist/hooks/useAixsterEdit.js +0 -13
- package/dist/hooks/useAixsterTranslate.d.ts +0 -4
- package/dist/hooks/useAixsterTranslate.js +0 -12
- package/dist/hooks/useLang.d.ts +0 -16
- package/dist/hooks/useLang.js +0 -23
- package/dist/hooks/useLangNavigate.d.ts +0 -24
- package/dist/hooks/useLangNavigate.js +0 -40
- package/dist/hooks/useLovalingo.d.ts +0 -1
- package/dist/hooks/useLovalingo.js +0 -1
- package/dist/hooks/useLovalingoEdit.d.ts +0 -1
- package/dist/hooks/useLovalingoEdit.js +0 -1
- package/dist/hooks/useLovalingoTranslate.d.ts +0 -1
- package/dist/hooks/useLovalingoTranslate.js +0 -1
- package/dist/types.d.ts +0 -76
- package/dist/types.js +0 -1
- package/dist/utils/api.d.ts +0 -42
- package/dist/utils/api.js +0 -395
- package/dist/utils/apiTypes.d.ts +0 -78
- package/dist/utils/apiTypes.js +0 -1
- package/dist/utils/apiUtils.d.ts +0 -4
- package/dist/utils/apiUtils.js +0 -54
- package/dist/utils/domRules.d.ts +0 -2
- package/dist/utils/domRules.js +0 -150
- package/dist/utils/hash.d.ts +0 -9
- package/dist/utils/hash.js +0 -27
- package/dist/utils/languageFlags.d.ts +0 -7
- package/dist/utils/languageFlags.js +0 -90
- package/dist/utils/logger.d.ts +0 -3
- package/dist/utils/logger.js +0 -40
- package/dist/utils/markerEngine.d.ts +0 -12
- package/dist/utils/markerEngine.js +0 -109
- package/dist/utils/markerEngineApply.d.ts +0 -3
- package/dist/utils/markerEngineApply.js +0 -136
- package/dist/utils/markerEngineConstants.d.ts +0 -10
- package/dist/utils/markerEngineConstants.js +0 -12
- package/dist/utils/markerEngineCritical.d.ts +0 -2
- package/dist/utils/markerEngineCritical.js +0 -98
- package/dist/utils/markerEngineDomUtils.d.ts +0 -8
- package/dist/utils/markerEngineDomUtils.js +0 -74
- package/dist/utils/markerEngineFilters.d.ts +0 -2
- package/dist/utils/markerEngineFilters.js +0 -26
- package/dist/utils/markerEngineMisses.d.ts +0 -5
- package/dist/utils/markerEngineMisses.js +0 -81
- package/dist/utils/markerEngineOriginals.d.ts +0 -5
- package/dist/utils/markerEngineOriginals.js +0 -29
- package/dist/utils/markerEngineScan.d.ts +0 -5
- package/dist/utils/markerEngineScan.js +0 -162
- package/dist/utils/markerEngineState.d.ts +0 -4
- package/dist/utils/markerEngineState.js +0 -14
- package/dist/utils/markerEngineStats.d.ts +0 -3
- package/dist/utils/markerEngineStats.js +0 -28
- package/dist/utils/markerEngineTranslations.d.ts +0 -3
- package/dist/utils/markerEngineTranslations.js +0 -49
- package/dist/utils/markerEngineTypes.d.ts +0 -62
- package/dist/utils/markerEngineTypes.js +0 -1
- package/dist/utils/markerEngineViewport.d.ts +0 -2
- package/dist/utils/markerEngineViewport.js +0 -27
- package/dist/utils/mergeEntitlements.d.ts +0 -2
- package/dist/utils/mergeEntitlements.js +0 -7
- package/dist/utils/nonLocalizedPaths.d.ts +0 -12
- package/dist/utils/nonLocalizedPaths.js +0 -136
- package/dist/utils/pathNormalizer.d.ts +0 -49
- package/dist/utils/pathNormalizer.js +0 -115
- package/dist/version.d.ts +0 -1
- package/dist/version.js +0 -1
package/dist/types.d.ts
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { PathNormalizationConfig } from './utils/pathNormalizer';
|
|
2
|
-
export interface LovalingoConfig {
|
|
3
|
-
/**
|
|
4
|
-
* Public project key (safe to expose in the browser).
|
|
5
|
-
*/
|
|
6
|
-
publicAnonKey?: string;
|
|
7
|
-
apiKey?: string;
|
|
8
|
-
defaultLocale: string;
|
|
9
|
-
locales: string[];
|
|
10
|
-
apiBase?: string;
|
|
11
|
-
routing?: 'query' | 'path';
|
|
12
|
-
autoPrefixLinks?: boolean;
|
|
13
|
-
overlayBgColor?: string;
|
|
14
|
-
switcherPosition?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
|
|
15
|
-
switcherOffsetY?: number;
|
|
16
|
-
switcherTheme?: 'dark' | 'light';
|
|
17
|
-
editMode?: boolean;
|
|
18
|
-
editKey?: string;
|
|
19
|
-
pathNormalization?: PathNormalizationConfig;
|
|
20
|
-
mode?: 'dom';
|
|
21
|
-
autoApplyRules?: boolean;
|
|
22
|
-
}
|
|
23
|
-
export interface LovalingoContextValue {
|
|
24
|
-
locale: string;
|
|
25
|
-
setLocale: (locale: string) => void;
|
|
26
|
-
isLoading: boolean;
|
|
27
|
-
translationMap: Record<string, string>;
|
|
28
|
-
config: LovalingoConfig;
|
|
29
|
-
translateElement: (element: HTMLElement) => void;
|
|
30
|
-
translateDOM: () => void;
|
|
31
|
-
editMode: boolean;
|
|
32
|
-
toggleEditMode: () => void;
|
|
33
|
-
excludeElement: (selector: string) => Promise<void>;
|
|
34
|
-
}
|
|
35
|
-
export interface Translation {
|
|
36
|
-
source_text: string;
|
|
37
|
-
translated_text: string;
|
|
38
|
-
source_locale: string;
|
|
39
|
-
target_locale: string;
|
|
40
|
-
content_hash?: string;
|
|
41
|
-
}
|
|
42
|
-
export interface Exclusion {
|
|
43
|
-
selector: string;
|
|
44
|
-
type: 'css' | 'xpath';
|
|
45
|
-
}
|
|
46
|
-
export type DomRuleType = 'replace_text' | 'set_attribute' | 'set_html' | 'add_class' | 'remove' | 'css' | 'script';
|
|
47
|
-
export interface DomRule {
|
|
48
|
-
id: string;
|
|
49
|
-
rule_type: DomRuleType;
|
|
50
|
-
selector?: string | null;
|
|
51
|
-
page_path?: string | null;
|
|
52
|
-
locale?: string | null;
|
|
53
|
-
payload?: {
|
|
54
|
-
matchText?: string;
|
|
55
|
-
text?: string;
|
|
56
|
-
attribute?: string;
|
|
57
|
-
value?: string;
|
|
58
|
-
html?: string;
|
|
59
|
-
className?: string;
|
|
60
|
-
css?: string;
|
|
61
|
-
script?: string;
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
export interface PlaceholderData {
|
|
65
|
-
type: 'inline-formatting' | 'non-translatable' | 'interactive';
|
|
66
|
-
originalHTML: string;
|
|
67
|
-
textContent: string;
|
|
68
|
-
tag: string;
|
|
69
|
-
attributes: Record<string, string>;
|
|
70
|
-
}
|
|
71
|
-
export interface ExtractedContent {
|
|
72
|
-
rawHTML: string;
|
|
73
|
-
processedText: string;
|
|
74
|
-
placeholderMap: Record<string, PlaceholderData>;
|
|
75
|
-
semanticContext: string;
|
|
76
|
-
}
|
package/dist/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/utils/api.d.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import type { Translation, Exclusion, DomRule } from '../types';
|
|
2
|
-
import type { BootstrapResponse, MissReportItem, MissReportOptions, MissReportResponse, ProjectEntitlements, SeoBundleResponse } from './apiTypes';
|
|
3
|
-
import { PathNormalizationConfig } from './pathNormalizer';
|
|
4
|
-
export type { ProjectEntitlements, SeoBundleResponse, BootstrapResponse, MissReportItem, MissReportResponse, MissReportOptions } from './apiTypes';
|
|
5
|
-
export declare class LovalingoAPI {
|
|
6
|
-
private apiKey;
|
|
7
|
-
private apiBase;
|
|
8
|
-
private pathConfig?;
|
|
9
|
-
private editKey?;
|
|
10
|
-
private entitlements;
|
|
11
|
-
constructor(apiKey: string, apiBase: string, pathConfig?: PathNormalizationConfig, editKey?: string);
|
|
12
|
-
private hasApiKey;
|
|
13
|
-
private buildPathParam;
|
|
14
|
-
private warnMissingApiKey;
|
|
15
|
-
private warnMissingEditKey;
|
|
16
|
-
private logActivationRequired;
|
|
17
|
-
private isActivationRequiredPayload;
|
|
18
|
-
private isActivationRequiredResponse;
|
|
19
|
-
getEntitlements(): ProjectEntitlements | null;
|
|
20
|
-
fetchEntitlements(localeHint: string): Promise<ProjectEntitlements | null>;
|
|
21
|
-
fetchSeoBundle(localeHint: string): Promise<SeoBundleResponse | null>;
|
|
22
|
-
trackPageview(pathOrUrl: string, opts?: {
|
|
23
|
-
critical_count?: number;
|
|
24
|
-
critical_hash?: string;
|
|
25
|
-
}): Promise<void>;
|
|
26
|
-
reportStringMisses(targetLocale: string, misses: MissReportItem[], opts?: MissReportOptions): Promise<MissReportResponse | null>;
|
|
27
|
-
fetchTranslations(sourceLocale: string, targetLocale: string): Promise<Translation[]>;
|
|
28
|
-
fetchBundle(localeHint: string, pathOrUrl?: string): Promise<{
|
|
29
|
-
map: Record<string, string>;
|
|
30
|
-
hashMap: Record<string, string>;
|
|
31
|
-
} | null>;
|
|
32
|
-
fetchBootstrap(localeHint: string, pathOrUrl?: string): Promise<BootstrapResponse | null>;
|
|
33
|
-
fetchExclusions(): Promise<Exclusion[]>;
|
|
34
|
-
fetchDomRules(targetLocale: string): Promise<DomRule[]>;
|
|
35
|
-
saveExclusion(args: {
|
|
36
|
-
selector: string;
|
|
37
|
-
type: 'css' | 'xpath';
|
|
38
|
-
pagePath?: string;
|
|
39
|
-
editKey?: string;
|
|
40
|
-
description?: string;
|
|
41
|
-
}): Promise<void>;
|
|
42
|
-
}
|
package/dist/utils/api.js
DELETED
|
@@ -1,395 +0,0 @@
|
|
|
1
|
-
import { getNavigationResponseStatus, isOkHttpStatus, looksLikeNotFoundDocument, normalizeApiBase } from './apiUtils';
|
|
2
|
-
import { warnDebug, errorDebug } from './logger';
|
|
3
|
-
export class LovalingoAPI {
|
|
4
|
-
constructor(apiKey, apiBase, pathConfig, editKey) {
|
|
5
|
-
this.entitlements = null;
|
|
6
|
-
this.apiKey = apiKey;
|
|
7
|
-
this.apiBase = normalizeApiBase(apiBase);
|
|
8
|
-
this.pathConfig = pathConfig;
|
|
9
|
-
this.editKey = editKey;
|
|
10
|
-
}
|
|
11
|
-
hasApiKey() {
|
|
12
|
-
return typeof this.apiKey === 'string' && this.apiKey.trim().length > 0;
|
|
13
|
-
}
|
|
14
|
-
buildPathParam(pathOrUrl) {
|
|
15
|
-
if (typeof window === "undefined")
|
|
16
|
-
return "/";
|
|
17
|
-
const input = (pathOrUrl || "").toString().trim();
|
|
18
|
-
if (!input)
|
|
19
|
-
return window.location.pathname + window.location.search;
|
|
20
|
-
try {
|
|
21
|
-
if (/^https?:\/\//i.test(input)) {
|
|
22
|
-
const url = new URL(input);
|
|
23
|
-
return url.pathname + url.search;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
catch {
|
|
27
|
-
// ignore invalid URL strings
|
|
28
|
-
}
|
|
29
|
-
return input;
|
|
30
|
-
}
|
|
31
|
-
warnMissingApiKey(action) {
|
|
32
|
-
// Avoid hard-crashing apps; make the failure mode obvious.
|
|
33
|
-
warnDebug(`[Lovalingo] Missing public project key: ${action} was skipped. Pass publicAnonKey to <LovalingoProvider ...> (or set VITE_LOVALINGO_PUBLIC_ANON_KEY).`);
|
|
34
|
-
}
|
|
35
|
-
warnMissingEditKey(action) {
|
|
36
|
-
warnDebug(`[Lovalingo] Missing edit key: ${action} was skipped. Open the edit link from the dashboard to continue.`);
|
|
37
|
-
}
|
|
38
|
-
logActivationRequired(context, response) {
|
|
39
|
-
errorDebug(`[Lovalingo] ${context} blocked (HTTP ${response.status}). ` +
|
|
40
|
-
`This project is not activated yet. ` +
|
|
41
|
-
`Publish a public manifest at "/.well-known/lovalingo.json" on your domain, ` +
|
|
42
|
-
`then verify it in the Lovalingo dashboard to activate translations + SEO.`);
|
|
43
|
-
}
|
|
44
|
-
isActivationRequiredPayload(data) {
|
|
45
|
-
if (!data || typeof data !== "object")
|
|
46
|
-
return false;
|
|
47
|
-
const record = data;
|
|
48
|
-
const status = record["status"];
|
|
49
|
-
const errorCode = record["error_code"];
|
|
50
|
-
return status === "activation_required" || errorCode === "PROJECT_NOT_ACTIVATED";
|
|
51
|
-
}
|
|
52
|
-
isActivationRequiredResponse(response, data) {
|
|
53
|
-
if (response.status === 403)
|
|
54
|
-
return true;
|
|
55
|
-
if (response.headers.get("X-Lovalingo-Status") === "activation_required")
|
|
56
|
-
return true;
|
|
57
|
-
return typeof data !== "undefined" ? this.isActivationRequiredPayload(data) : false;
|
|
58
|
-
}
|
|
59
|
-
getEntitlements() {
|
|
60
|
-
return this.entitlements;
|
|
61
|
-
}
|
|
62
|
-
async fetchEntitlements(localeHint) {
|
|
63
|
-
try {
|
|
64
|
-
if (!this.hasApiKey()) {
|
|
65
|
-
this.warnMissingApiKey('fetchEntitlements');
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
const pathParam = this.buildPathParam();
|
|
69
|
-
// Why: CF data plane serves page-scoped bundles only; keep entitlements lookup aligned.
|
|
70
|
-
const response = await fetch(`${this.apiBase}/functions/v1/bundle?key=${this.apiKey}&locale=${encodeURIComponent(localeHint)}&path=${encodeURIComponent(pathParam)}&scoped=1`);
|
|
71
|
-
if (this.isActivationRequiredResponse(response)) {
|
|
72
|
-
this.logActivationRequired('fetchEntitlements', response);
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
if (!response.ok)
|
|
76
|
-
return null;
|
|
77
|
-
const data = await response.json();
|
|
78
|
-
if (this.isActivationRequiredResponse(response, data)) {
|
|
79
|
-
this.logActivationRequired("fetchEntitlements", response);
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
if (data?.entitlements) {
|
|
83
|
-
this.entitlements = {
|
|
84
|
-
...data.entitlements,
|
|
85
|
-
seoEnabled: typeof data?.seoEnabled === "boolean" ? data.seoEnabled : undefined,
|
|
86
|
-
};
|
|
87
|
-
return this.entitlements;
|
|
88
|
-
}
|
|
89
|
-
return null;
|
|
90
|
-
}
|
|
91
|
-
catch {
|
|
92
|
-
return null;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
async fetchSeoBundle(localeHint) {
|
|
96
|
-
try {
|
|
97
|
-
if (!this.hasApiKey()) {
|
|
98
|
-
this.warnMissingApiKey("fetchSeoBundle");
|
|
99
|
-
return null;
|
|
100
|
-
}
|
|
101
|
-
const pathParam = this.buildPathParam();
|
|
102
|
-
const requestUrl = `${this.apiBase}/functions/v1/seo-bundle?key=${this.apiKey}&locale=${encodeURIComponent(localeHint)}&path=${encodeURIComponent(pathParam)}`;
|
|
103
|
-
const response = await fetch(requestUrl);
|
|
104
|
-
if (this.isActivationRequiredResponse(response)) {
|
|
105
|
-
this.logActivationRequired("fetchSeoBundle", response);
|
|
106
|
-
return null;
|
|
107
|
-
}
|
|
108
|
-
const resolvedResponse = response.status === 304 ? await fetch(requestUrl, { cache: "force-cache" }) : response;
|
|
109
|
-
if (resolvedResponse !== response && this.isActivationRequiredResponse(resolvedResponse)) {
|
|
110
|
-
this.logActivationRequired("fetchSeoBundle", resolvedResponse);
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
if (!resolvedResponse.ok)
|
|
114
|
-
return null;
|
|
115
|
-
const data = (await resolvedResponse.json());
|
|
116
|
-
if (this.isActivationRequiredResponse(resolvedResponse, data)) {
|
|
117
|
-
this.logActivationRequired("fetchSeoBundle", resolvedResponse);
|
|
118
|
-
return null;
|
|
119
|
-
}
|
|
120
|
-
return (data || null);
|
|
121
|
-
}
|
|
122
|
-
catch {
|
|
123
|
-
return null;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
async trackPageview(pathOrUrl, opts) {
|
|
127
|
-
try {
|
|
128
|
-
if (!this.hasApiKey())
|
|
129
|
-
return;
|
|
130
|
-
const status = getNavigationResponseStatus();
|
|
131
|
-
if (!isOkHttpStatus(status))
|
|
132
|
-
return;
|
|
133
|
-
// Why: avoid tracking SPA soft-404s (HTTP 200 + "not found" content) so ghost pages don't reappear.
|
|
134
|
-
if (looksLikeNotFoundDocument())
|
|
135
|
-
return;
|
|
136
|
-
const params = new URLSearchParams();
|
|
137
|
-
params.set("key", this.apiKey);
|
|
138
|
-
params.set("path", pathOrUrl);
|
|
139
|
-
const count = opts?.critical_count;
|
|
140
|
-
const hash = (opts?.critical_hash || "").toString().trim().toLowerCase();
|
|
141
|
-
if (typeof count === "number" && Number.isFinite(count) && count > 0 && count <= 5000 && /^[a-z0-9]{1,40}$/.test(hash)) {
|
|
142
|
-
params.set("critical_count", String(Math.floor(count)));
|
|
143
|
-
params.set("critical_hash", hash);
|
|
144
|
-
}
|
|
145
|
-
const response = await fetch(`${this.apiBase}/functions/v1/pageview?${params.toString()}`, {
|
|
146
|
-
method: "GET",
|
|
147
|
-
keepalive: true,
|
|
148
|
-
});
|
|
149
|
-
if (response.status === 403) {
|
|
150
|
-
// Tracking should never block app behavior; keep logging consistent.
|
|
151
|
-
this.logActivationRequired("trackPageview", response);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
catch {
|
|
155
|
-
// Ignore tracking errors.
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
async reportStringMisses(targetLocale, misses, opts) {
|
|
159
|
-
try {
|
|
160
|
-
if (!this.hasApiKey())
|
|
161
|
-
return null;
|
|
162
|
-
if (!Array.isArray(misses) || misses.length === 0)
|
|
163
|
-
return null;
|
|
164
|
-
const status = getNavigationResponseStatus();
|
|
165
|
-
if (!isOkHttpStatus(status)) {
|
|
166
|
-
return { ignored: true, reason: "http_status" };
|
|
167
|
-
}
|
|
168
|
-
if (looksLikeNotFoundDocument()) {
|
|
169
|
-
return { ignored: true, reason: "soft_404" };
|
|
170
|
-
}
|
|
171
|
-
const pathParam = this.buildPathParam(opts?.pathOrUrl);
|
|
172
|
-
const response = await fetch(`${this.apiBase}/functions/v1/misses`, {
|
|
173
|
-
method: "POST",
|
|
174
|
-
headers: { "Content-Type": "application/json" },
|
|
175
|
-
body: JSON.stringify({
|
|
176
|
-
key: this.apiKey,
|
|
177
|
-
locale: targetLocale,
|
|
178
|
-
path: pathParam,
|
|
179
|
-
source_locale: opts?.sourceLocale,
|
|
180
|
-
locales: Array.isArray(opts?.locales) ? opts?.locales : undefined,
|
|
181
|
-
page_status: typeof status === "number" ? status : undefined,
|
|
182
|
-
misses,
|
|
183
|
-
}),
|
|
184
|
-
});
|
|
185
|
-
if (this.isActivationRequiredResponse(response)) {
|
|
186
|
-
this.logActivationRequired("reportStringMisses", response);
|
|
187
|
-
return null;
|
|
188
|
-
}
|
|
189
|
-
if (!response.ok)
|
|
190
|
-
return null;
|
|
191
|
-
const data = await response.json();
|
|
192
|
-
if (this.isActivationRequiredResponse(response, data)) {
|
|
193
|
-
this.logActivationRequired("reportStringMisses", response);
|
|
194
|
-
return null;
|
|
195
|
-
}
|
|
196
|
-
return data;
|
|
197
|
-
}
|
|
198
|
-
catch {
|
|
199
|
-
return null;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
async fetchTranslations(sourceLocale, targetLocale) {
|
|
203
|
-
try {
|
|
204
|
-
if (!this.hasApiKey()) {
|
|
205
|
-
this.warnMissingApiKey('fetchTranslations');
|
|
206
|
-
return [];
|
|
207
|
-
}
|
|
208
|
-
const bundle = await this.fetchBundle(targetLocale);
|
|
209
|
-
if (!bundle)
|
|
210
|
-
return [];
|
|
211
|
-
return Object.entries(bundle.map).map(([source_text, translated_text]) => ({
|
|
212
|
-
source_text,
|
|
213
|
-
translated_text,
|
|
214
|
-
source_locale: sourceLocale,
|
|
215
|
-
target_locale: targetLocale,
|
|
216
|
-
}));
|
|
217
|
-
}
|
|
218
|
-
catch (error) {
|
|
219
|
-
errorDebug('Error fetching translations:', error);
|
|
220
|
-
return [];
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
async fetchBundle(localeHint, pathOrUrl) {
|
|
224
|
-
try {
|
|
225
|
-
if (!this.hasApiKey()) {
|
|
226
|
-
this.warnMissingApiKey("fetchBundle");
|
|
227
|
-
return null;
|
|
228
|
-
}
|
|
229
|
-
const pathParam = this.buildPathParam(pathOrUrl);
|
|
230
|
-
const requestUrl = `${this.apiBase}/functions/v1/bundle?key=${this.apiKey}&locale=${encodeURIComponent(localeHint)}&path=${encodeURIComponent(pathParam)}&scoped=1`;
|
|
231
|
-
const response = await fetch(requestUrl);
|
|
232
|
-
if (this.isActivationRequiredResponse(response)) {
|
|
233
|
-
this.logActivationRequired("fetchBundle", response);
|
|
234
|
-
return null;
|
|
235
|
-
}
|
|
236
|
-
const resolvedResponse = response.status === 304 ? await fetch(requestUrl, { cache: "force-cache" }) : response;
|
|
237
|
-
if (resolvedResponse !== response && this.isActivationRequiredResponse(resolvedResponse)) {
|
|
238
|
-
this.logActivationRequired("fetchBundle", resolvedResponse);
|
|
239
|
-
return null;
|
|
240
|
-
}
|
|
241
|
-
if (!resolvedResponse.ok)
|
|
242
|
-
return null;
|
|
243
|
-
const data = await resolvedResponse.json();
|
|
244
|
-
if (this.isActivationRequiredResponse(resolvedResponse, data)) {
|
|
245
|
-
this.logActivationRequired("fetchBundle", resolvedResponse);
|
|
246
|
-
return null;
|
|
247
|
-
}
|
|
248
|
-
if (data?.entitlements) {
|
|
249
|
-
this.entitlements = {
|
|
250
|
-
...data.entitlements,
|
|
251
|
-
seoEnabled: typeof data?.seoEnabled === "boolean" ? data.seoEnabled : undefined,
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
const map = data?.map && typeof data.map === "object" ? data.map : {};
|
|
255
|
-
const hashMap = data?.hashMap && typeof data.hashMap === "object" ? data.hashMap : {};
|
|
256
|
-
return { map, hashMap };
|
|
257
|
-
}
|
|
258
|
-
catch {
|
|
259
|
-
return null;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
async fetchBootstrap(localeHint, pathOrUrl) {
|
|
263
|
-
try {
|
|
264
|
-
if (!this.hasApiKey()) {
|
|
265
|
-
this.warnMissingApiKey("fetchBootstrap");
|
|
266
|
-
return null;
|
|
267
|
-
}
|
|
268
|
-
const pathParam = this.buildPathParam(pathOrUrl);
|
|
269
|
-
const requestUrl = `${this.apiBase}/functions/v1/bootstrap?key=${this.apiKey}&locale=${encodeURIComponent(localeHint)}&path=${encodeURIComponent(pathParam)}`;
|
|
270
|
-
const response = await fetch(requestUrl);
|
|
271
|
-
if (this.isActivationRequiredResponse(response)) {
|
|
272
|
-
this.logActivationRequired("fetchBootstrap", response);
|
|
273
|
-
return null;
|
|
274
|
-
}
|
|
275
|
-
const resolvedResponse = response.status === 304 ? await fetch(requestUrl, { cache: "force-cache" }) : response;
|
|
276
|
-
if (resolvedResponse !== response && this.isActivationRequiredResponse(resolvedResponse)) {
|
|
277
|
-
this.logActivationRequired("fetchBootstrap", resolvedResponse);
|
|
278
|
-
return null;
|
|
279
|
-
}
|
|
280
|
-
if (!resolvedResponse.ok)
|
|
281
|
-
return null;
|
|
282
|
-
const data = (await resolvedResponse.json());
|
|
283
|
-
if (this.isActivationRequiredResponse(resolvedResponse, data)) {
|
|
284
|
-
this.logActivationRequired("fetchBootstrap", resolvedResponse);
|
|
285
|
-
return null;
|
|
286
|
-
}
|
|
287
|
-
return (data || null);
|
|
288
|
-
}
|
|
289
|
-
catch {
|
|
290
|
-
return null;
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
async fetchExclusions() {
|
|
294
|
-
try {
|
|
295
|
-
if (!this.hasApiKey()) {
|
|
296
|
-
this.warnMissingApiKey('fetchExclusions');
|
|
297
|
-
return [];
|
|
298
|
-
}
|
|
299
|
-
const response = await fetch(`${this.apiBase}/functions/v1/exclusions?key=${this.apiKey}`);
|
|
300
|
-
if (response.status === 403) {
|
|
301
|
-
this.logActivationRequired('fetchExclusions', response);
|
|
302
|
-
return [];
|
|
303
|
-
}
|
|
304
|
-
if (!response.ok)
|
|
305
|
-
throw new Error('Failed to fetch exclusions');
|
|
306
|
-
const data = await response.json();
|
|
307
|
-
// Handle legacy and vNext row shapes.
|
|
308
|
-
const rows = Array.isArray(data.exclusions) ? data.exclusions : [];
|
|
309
|
-
const out = [];
|
|
310
|
-
for (const row of rows) {
|
|
311
|
-
if (!row || typeof row !== "object")
|
|
312
|
-
continue;
|
|
313
|
-
const record = row;
|
|
314
|
-
const selector = (typeof record.selector === "string" ? record.selector : "") ||
|
|
315
|
-
(typeof record.selector_value === "string" ? record.selector_value : "") ||
|
|
316
|
-
(typeof record.selectorValue === "string" ? record.selectorValue : "");
|
|
317
|
-
const type = (typeof record.type === "string" ? record.type : "") ||
|
|
318
|
-
(typeof record.selector_type === "string" ? record.selector_type : "") ||
|
|
319
|
-
(typeof record.selectorType === "string" ? record.selectorType : "");
|
|
320
|
-
const trimmedSelector = selector.trim();
|
|
321
|
-
const trimmedType = type.trim();
|
|
322
|
-
if (!trimmedSelector)
|
|
323
|
-
continue;
|
|
324
|
-
if (trimmedType !== "css" && trimmedType !== "xpath")
|
|
325
|
-
continue;
|
|
326
|
-
out.push({ selector: trimmedSelector, type: trimmedType });
|
|
327
|
-
}
|
|
328
|
-
return out;
|
|
329
|
-
}
|
|
330
|
-
catch (error) {
|
|
331
|
-
errorDebug('Error fetching exclusions:', error);
|
|
332
|
-
return [];
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
async fetchDomRules(targetLocale) {
|
|
336
|
-
try {
|
|
337
|
-
if (!this.hasApiKey()) {
|
|
338
|
-
this.warnMissingApiKey('fetchDomRules');
|
|
339
|
-
return [];
|
|
340
|
-
}
|
|
341
|
-
const pathParam = this.buildPathParam();
|
|
342
|
-
const response = await fetch(`${this.apiBase}/functions/v1/dom-rules?key=${this.apiKey}&locale=${encodeURIComponent(targetLocale)}&path=${encodeURIComponent(pathParam)}`);
|
|
343
|
-
if (this.isActivationRequiredResponse(response)) {
|
|
344
|
-
this.logActivationRequired('fetchDomRules', response);
|
|
345
|
-
return [];
|
|
346
|
-
}
|
|
347
|
-
if (!response.ok)
|
|
348
|
-
return [];
|
|
349
|
-
const data = await response.json();
|
|
350
|
-
if (this.isActivationRequiredResponse(response, data)) {
|
|
351
|
-
this.logActivationRequired('fetchDomRules', response);
|
|
352
|
-
return [];
|
|
353
|
-
}
|
|
354
|
-
return Array.isArray(data?.rules) ? data.rules : [];
|
|
355
|
-
}
|
|
356
|
-
catch (error) {
|
|
357
|
-
errorDebug('Error fetching DOM rules:', error);
|
|
358
|
-
return [];
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
async saveExclusion(args) {
|
|
362
|
-
try {
|
|
363
|
-
if (!this.hasApiKey()) {
|
|
364
|
-
this.warnMissingApiKey('saveExclusion');
|
|
365
|
-
return;
|
|
366
|
-
}
|
|
367
|
-
const editKey = (args.editKey || this.editKey || "").trim();
|
|
368
|
-
if (!editKey) {
|
|
369
|
-
this.warnMissingEditKey('saveExclusion');
|
|
370
|
-
return;
|
|
371
|
-
}
|
|
372
|
-
// Why: persist exclusions against a normalized path to keep locales in sync.
|
|
373
|
-
const pagePath = (args.pagePath || this.buildPathParam()).toString();
|
|
374
|
-
const response = await fetch(`${this.apiBase}/functions/v1/exclusions?key=${this.apiKey}`, {
|
|
375
|
-
method: 'POST',
|
|
376
|
-
headers: { 'Content-Type': 'application/json' },
|
|
377
|
-
body: JSON.stringify({
|
|
378
|
-
key: this.apiKey,
|
|
379
|
-
edit_key: editKey,
|
|
380
|
-
page_path: pagePath,
|
|
381
|
-
selector_type: args.type,
|
|
382
|
-
selector_value: args.selector,
|
|
383
|
-
description: args.description,
|
|
384
|
-
}),
|
|
385
|
-
});
|
|
386
|
-
if (response.status === 403) {
|
|
387
|
-
this.logActivationRequired('saveExclusion', response);
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
catch (error) {
|
|
391
|
-
errorDebug('Error saving exclusion:', error);
|
|
392
|
-
throw error;
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
}
|
package/dist/utils/apiTypes.d.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import type { DomRule } from "../types";
|
|
2
|
-
export interface ProjectEntitlements {
|
|
3
|
-
tier: "starter" | "startup" | "global";
|
|
4
|
-
maxTargetLocales: number;
|
|
5
|
-
allowedTargetLocales: string[];
|
|
6
|
-
brandingRequired: boolean;
|
|
7
|
-
hreflangEnabled: boolean;
|
|
8
|
-
seoEnabled?: boolean;
|
|
9
|
-
}
|
|
10
|
-
export type SeoBundleResponse = {
|
|
11
|
-
locale?: string;
|
|
12
|
-
normalized_path?: string;
|
|
13
|
-
routing_strategy?: string;
|
|
14
|
-
seo?: Record<string, unknown>;
|
|
15
|
-
alternates?: {
|
|
16
|
-
canonical?: string;
|
|
17
|
-
xDefault?: string;
|
|
18
|
-
languages?: Record<string, string>;
|
|
19
|
-
};
|
|
20
|
-
seoEnabled?: boolean;
|
|
21
|
-
entitlements?: ProjectEntitlements;
|
|
22
|
-
};
|
|
23
|
-
export type BootstrapResponse = {
|
|
24
|
-
locale?: string;
|
|
25
|
-
normalized_path?: string;
|
|
26
|
-
routing_strategy?: string;
|
|
27
|
-
non_localized_paths?: Array<{
|
|
28
|
-
pattern?: string;
|
|
29
|
-
match_type?: string;
|
|
30
|
-
updated_at?: string | null;
|
|
31
|
-
}>;
|
|
32
|
-
inactive_pages?: Array<{
|
|
33
|
-
page_path?: string;
|
|
34
|
-
updated_at?: string | null;
|
|
35
|
-
}>;
|
|
36
|
-
loading_bg_color?: string | null;
|
|
37
|
-
branding_enabled?: boolean;
|
|
38
|
-
seoEnabled?: boolean;
|
|
39
|
-
entitlements?: ProjectEntitlements;
|
|
40
|
-
alternates?: {
|
|
41
|
-
canonical?: string;
|
|
42
|
-
xDefault?: string;
|
|
43
|
-
languages?: Record<string, string>;
|
|
44
|
-
} | null;
|
|
45
|
-
seo?: Record<string, unknown>;
|
|
46
|
-
jsonld?: Array<{
|
|
47
|
-
type: string;
|
|
48
|
-
json: unknown;
|
|
49
|
-
hash?: string;
|
|
50
|
-
}>;
|
|
51
|
-
dom_rules?: DomRule[];
|
|
52
|
-
exclusions?: unknown[];
|
|
53
|
-
critical?: {
|
|
54
|
-
map?: Record<string, string>;
|
|
55
|
-
keys?: number;
|
|
56
|
-
viewport?: unknown;
|
|
57
|
-
etag?: string;
|
|
58
|
-
};
|
|
59
|
-
etag?: string;
|
|
60
|
-
};
|
|
61
|
-
export type MissReportItem = {
|
|
62
|
-
source_text: string;
|
|
63
|
-
semantic_context?: string | null;
|
|
64
|
-
};
|
|
65
|
-
export type MissReportResponse = {
|
|
66
|
-
translations?: Array<{
|
|
67
|
-
source_text: string;
|
|
68
|
-
translated_text: string;
|
|
69
|
-
}>;
|
|
70
|
-
pii?: string[];
|
|
71
|
-
ignored?: boolean;
|
|
72
|
-
reason?: string;
|
|
73
|
-
};
|
|
74
|
-
export type MissReportOptions = {
|
|
75
|
-
pathOrUrl?: string;
|
|
76
|
-
sourceLocale?: string;
|
|
77
|
-
locales?: string[];
|
|
78
|
-
};
|
package/dist/utils/apiTypes.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/utils/apiUtils.d.ts
DELETED
package/dist/utils/apiUtils.js
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { warnDebug } from "./logger";
|
|
2
|
-
const OK_HTTP_STATUSES = new Set([200, 201]);
|
|
3
|
-
const NOT_FOUND_TITLE_HINTS = /404|not found|page not found|page missing|does not exist|error 404/i;
|
|
4
|
-
export function getNavigationResponseStatus() {
|
|
5
|
-
if (typeof performance === "undefined" || typeof performance.getEntriesByType !== "function")
|
|
6
|
-
return null;
|
|
7
|
-
const entries = performance.getEntriesByType("navigation");
|
|
8
|
-
if (!entries || entries.length === 0)
|
|
9
|
-
return null;
|
|
10
|
-
const entry = entries[0];
|
|
11
|
-
const rawStatus = entry?.responseStatus;
|
|
12
|
-
const status = typeof rawStatus === "number" ? Math.floor(rawStatus) : NaN;
|
|
13
|
-
if (!Number.isFinite(status) || status <= 0)
|
|
14
|
-
return null;
|
|
15
|
-
return status;
|
|
16
|
-
}
|
|
17
|
-
// Why: avoid tracking misses/pageviews for non-OK responses (404/5xx) so bad routes don't pollute discovery.
|
|
18
|
-
export function isOkHttpStatus(status) {
|
|
19
|
-
if (typeof status !== "number")
|
|
20
|
-
return true;
|
|
21
|
-
return OK_HTTP_STATUSES.has(status);
|
|
22
|
-
}
|
|
23
|
-
export function looksLikeNotFoundDocument() {
|
|
24
|
-
if (typeof document === "undefined")
|
|
25
|
-
return false;
|
|
26
|
-
const title = (document.title || "").toString().trim().toLowerCase();
|
|
27
|
-
if (title && NOT_FOUND_TITLE_HINTS.test(title))
|
|
28
|
-
return true;
|
|
29
|
-
const bodyText = (document.body?.textContent || "").toString().slice(0, 2000).toLowerCase();
|
|
30
|
-
if (!bodyText)
|
|
31
|
-
return false;
|
|
32
|
-
const has404 = /\b404\b/.test(bodyText);
|
|
33
|
-
const hasNotFound = /not found|page not found|page missing|does not exist|error 404/.test(bodyText);
|
|
34
|
-
return has404 && hasNotFound;
|
|
35
|
-
}
|
|
36
|
-
export function normalizeApiBase(raw) {
|
|
37
|
-
const input = (raw || "").toString().trim();
|
|
38
|
-
if (!input)
|
|
39
|
-
return "https://cdn.lovalingo.com";
|
|
40
|
-
let base = input.replace(/\/functions\/v1\/?$/i, "").replace(/\/$/, "");
|
|
41
|
-
try {
|
|
42
|
-
const parsed = new URL(base);
|
|
43
|
-
const host = parsed.hostname.toLowerCase();
|
|
44
|
-
// Why: runtime endpoints moved off Supabase Edge Functions; fall back to CDN if a Supabase host is supplied.
|
|
45
|
-
if (host.endsWith(".supabase.co")) {
|
|
46
|
-
warnDebug("LovalingoAPI", `apiBase points to Supabase (${host}); falling back to https://cdn.lovalingo.com`);
|
|
47
|
-
return "https://cdn.lovalingo.com";
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
catch {
|
|
51
|
-
// Ignore parse errors and keep the raw base.
|
|
52
|
-
}
|
|
53
|
-
return base;
|
|
54
|
-
}
|
package/dist/utils/domRules.d.ts
DELETED