locize 3.0.5 → 3.1.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/CHANGELOG.md +4 -0
- package/README.md +67 -31
- package/dist/cjs/api/handleConfirmInitialized.js +1 -0
- package/dist/cjs/index.d.ts +30 -0
- package/dist/cjs/index.js +2 -0
- package/dist/cjs/locizePlugin.js +103 -77
- package/dist/cjs/observer.js +28 -1
- package/dist/cjs/utils.js +11 -0
- package/dist/esm/api/handleConfirmInitialized.js +1 -0
- package/dist/esm/index.d.ts +30 -0
- package/dist/esm/index.js +3 -2
- package/dist/esm/locizePlugin.js +103 -78
- package/dist/esm/observer.js +28 -1
- package/dist/esm/utils.js +11 -1
- package/dist/umd/locize.js +142 -78
- package/dist/umd/locize.min.js +1 -1
- package/index.d.ts +30 -0
- package/locize.js +142 -78
- package/locize.min.js +1 -1
- package/package.json +1 -1
- package/src/api/handleConfirmInitialized.js +1 -0
- package/src/index.js +3 -1
- package/src/locizePlugin.js +123 -85
- package/src/observer.js +48 -1
- package/src/ui/utils.js +5 -5
- package/src/utils.js +12 -0
package/src/locizePlugin.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { PostProcessor, unwrap } from 'i18next-subliminal'
|
|
2
2
|
import { start } from './process.js'
|
|
3
3
|
import { startLegacy } from './processLegacy.js'
|
|
4
|
+
import { getQsParameterByName } from './utils.js'
|
|
4
5
|
|
|
5
6
|
let isInIframe = typeof window !== 'undefined'
|
|
6
7
|
try {
|
|
@@ -9,6 +10,96 @@ try {
|
|
|
9
10
|
// eslint-disable-next-line no-empty
|
|
10
11
|
} catch (e) {}
|
|
11
12
|
|
|
13
|
+
function configurePostProcessor (i18next, options) {
|
|
14
|
+
i18next.use(PostProcessor)
|
|
15
|
+
|
|
16
|
+
if (typeof options.postProcess === 'string') {
|
|
17
|
+
options.postProcess = [options.postProcess, 'subliminal']
|
|
18
|
+
} else if (Array.isArray(options.postProcess)) {
|
|
19
|
+
options.postProcess.push('subliminal')
|
|
20
|
+
} else {
|
|
21
|
+
options.postProcess = 'subliminal'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
options.postProcessPassResolved = true
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getImplementation (i18n) {
|
|
28
|
+
const impl = {
|
|
29
|
+
getResource: (lng, ns, key) => {
|
|
30
|
+
return i18n.getResource(lng, ns, key)
|
|
31
|
+
},
|
|
32
|
+
setResource: (lng, ns, key, value) => {
|
|
33
|
+
return i18n.addResource(lng, ns, key, value, { silent: true })
|
|
34
|
+
},
|
|
35
|
+
getResourceBundle: (lng, ns, cb) => {
|
|
36
|
+
i18n.loadNamespaces(ns, () => {
|
|
37
|
+
cb(i18n.getResourceBundle(lng, ns))
|
|
38
|
+
})
|
|
39
|
+
},
|
|
40
|
+
getLng: () => {
|
|
41
|
+
return i18n.languages[0]
|
|
42
|
+
},
|
|
43
|
+
getSourceLng: () => {
|
|
44
|
+
const fallback = i18n.options.fallbackLng
|
|
45
|
+
if (typeof fallback === 'string') return fallback
|
|
46
|
+
if (Array.isArray(fallback)) return fallback[fallback.length - 1]
|
|
47
|
+
|
|
48
|
+
if (fallback && fallback.default) {
|
|
49
|
+
if (typeof fallback.default === 'string') return fallback
|
|
50
|
+
if (Array.isArray(fallback.default)) return fallback.default[fallback.default.length - 1]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (typeof fallback === 'function') {
|
|
54
|
+
const res = fallback(i18n.resolvedLanguage)
|
|
55
|
+
if (typeof res === 'string') return res
|
|
56
|
+
if (Array.isArray(res)) return res[res.length - 1]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return 'dev'
|
|
60
|
+
},
|
|
61
|
+
getLocizeDetails: () => {
|
|
62
|
+
let backendName
|
|
63
|
+
if (i18n.services.backendConnector.backend && i18n.services.backendConnector.backend.options && i18n.services.backendConnector.backend.options.loadPath && i18n.services.backendConnector.backend.options.loadPath.indexOf('.locize.') > 0) {
|
|
64
|
+
backendName = 'I18NextLocizeBackend'
|
|
65
|
+
} else {
|
|
66
|
+
backendName = i18n.services.backendConnector.backend
|
|
67
|
+
? i18n.services.backendConnector.backend.constructor.name
|
|
68
|
+
: 'options.resources'
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const opts = {
|
|
72
|
+
backendName,
|
|
73
|
+
sourceLng: impl.getSourceLng(),
|
|
74
|
+
i18nFormat: i18n.options.compatibilityJSON === 'v3' ? 'i18next_v3' : 'i18next_v4',
|
|
75
|
+
i18nFramework: 'i18next',
|
|
76
|
+
isLocizify: i18n.options.isLocizify,
|
|
77
|
+
defaultNS: i18n.options.defaultNS
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!i18n.options.backend && !i18n.options.editor) return opts
|
|
81
|
+
const pickFrom = i18n.options.backend || i18n.options.editor
|
|
82
|
+
return {
|
|
83
|
+
...opts,
|
|
84
|
+
projectId: pickFrom.projectId,
|
|
85
|
+
version: pickFrom.version
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
bindLanguageChange: cb => {
|
|
89
|
+
i18n.on('languageChanged', cb)
|
|
90
|
+
},
|
|
91
|
+
bindMissingKeyHandler: cb => {
|
|
92
|
+
i18n.options.missingKeyHandler = (lng, ns, k, val, isUpdate, opts) => {
|
|
93
|
+
if (!isUpdate) cb(lng, ns, k, val)
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
triggerRerender: () => {
|
|
97
|
+
i18n.emit('editorSaved')
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return impl
|
|
101
|
+
}
|
|
102
|
+
|
|
12
103
|
let i18next
|
|
13
104
|
export const locizePlugin = {
|
|
14
105
|
type: '3rdParty',
|
|
@@ -20,92 +111,9 @@ export const locizePlugin = {
|
|
|
20
111
|
i18next = i18n
|
|
21
112
|
|
|
22
113
|
// add postProcessor and needed options now
|
|
23
|
-
if (!isInIframe)
|
|
24
|
-
i18next.use(PostProcessor)
|
|
114
|
+
if (!isInIframe) configurePostProcessor(i18next, options)
|
|
25
115
|
|
|
26
|
-
|
|
27
|
-
options.postProcess = [options.postProcess, 'subliminal']
|
|
28
|
-
} else if (Array.isArray(options.postProcess)) {
|
|
29
|
-
options.postProcess.push('subliminal')
|
|
30
|
-
} else {
|
|
31
|
-
options.postProcess = 'subliminal'
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
options.postProcessPassResolved = true
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const impl = {
|
|
38
|
-
getResource: (lng, ns, key) => {
|
|
39
|
-
return i18n.getResource(lng, ns, key)
|
|
40
|
-
},
|
|
41
|
-
setResource: (lng, ns, key, value) => {
|
|
42
|
-
return i18n.addResource(lng, ns, key, value, { silent: true })
|
|
43
|
-
},
|
|
44
|
-
getResourceBundle: (lng, ns, cb) => {
|
|
45
|
-
i18n.loadNamespaces(ns, () => {
|
|
46
|
-
cb(i18n.getResourceBundle(lng, ns))
|
|
47
|
-
})
|
|
48
|
-
},
|
|
49
|
-
getLng: () => {
|
|
50
|
-
return i18n.languages[0]
|
|
51
|
-
},
|
|
52
|
-
getSourceLng: () => {
|
|
53
|
-
const fallback = i18n.options.fallbackLng
|
|
54
|
-
if (typeof fallback === 'string') return fallback
|
|
55
|
-
if (Array.isArray(fallback)) return fallback[fallback.length - 1]
|
|
56
|
-
|
|
57
|
-
if (fallback && fallback.default) {
|
|
58
|
-
if (typeof fallback.default === 'string') return fallback
|
|
59
|
-
if (Array.isArray(fallback.default)) return fallback.default[fallback.default.length - 1]
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (typeof fallback === 'function') {
|
|
63
|
-
const res = fallback(i18n.resolvedLanguage)
|
|
64
|
-
if (typeof res === 'string') return res
|
|
65
|
-
if (Array.isArray(res)) return res[res.length - 1]
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return 'dev'
|
|
69
|
-
},
|
|
70
|
-
getLocizeDetails: () => {
|
|
71
|
-
let backendName
|
|
72
|
-
if (i18n.services.backendConnector.backend && i18n.services.backendConnector.backend.options && i18n.services.backendConnector.backend.options.loadPath && i18n.services.backendConnector.backend.options.loadPath.indexOf('.locize.') > 0) {
|
|
73
|
-
backendName = 'I18NextLocizeBackend'
|
|
74
|
-
} else {
|
|
75
|
-
backendName = i18n.services.backendConnector.backend
|
|
76
|
-
? i18n.services.backendConnector.backend.constructor.name
|
|
77
|
-
: 'options.resources'
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const opts = {
|
|
81
|
-
backendName,
|
|
82
|
-
sourceLng: impl.getSourceLng(),
|
|
83
|
-
i18nFormat: i18n.options.compatibilityJSON === 'v3' ? 'i18next_v3' : 'i18next_v4',
|
|
84
|
-
i18nFramework: 'i18next',
|
|
85
|
-
isLocizify: i18n.options.isLocizify,
|
|
86
|
-
defaultNS: i18n.options.defaultNS
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (!i18n.options.backend && !i18n.options.editor) return opts
|
|
90
|
-
const pickFrom = i18n.options.backend || i18n.options.editor
|
|
91
|
-
return {
|
|
92
|
-
...opts,
|
|
93
|
-
projectId: pickFrom.projectId,
|
|
94
|
-
version: pickFrom.version
|
|
95
|
-
}
|
|
96
|
-
},
|
|
97
|
-
bindLanguageChange: cb => {
|
|
98
|
-
i18n.on('languageChanged', cb)
|
|
99
|
-
},
|
|
100
|
-
bindMissingKeyHandler: cb => {
|
|
101
|
-
i18n.options.missingKeyHandler = (lng, ns, k, val, isUpdate, opts) => {
|
|
102
|
-
if (!isUpdate) cb(lng, ns, k, val)
|
|
103
|
-
}
|
|
104
|
-
},
|
|
105
|
-
triggerRerender: () => {
|
|
106
|
-
i18n.emit('editorSaved')
|
|
107
|
-
}
|
|
108
|
-
}
|
|
116
|
+
const impl = getImplementation(i18n)
|
|
109
117
|
|
|
110
118
|
// start process and expose some implementation functions
|
|
111
119
|
if (!isInIframe) {
|
|
@@ -115,6 +123,36 @@ export const locizePlugin = {
|
|
|
115
123
|
}
|
|
116
124
|
}
|
|
117
125
|
}
|
|
126
|
+
|
|
127
|
+
export const locizeEditorPlugin = (opt = {}) => {
|
|
128
|
+
opt.qsProp = opt.qsProp || 'incontext'
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
type: '3rdParty',
|
|
132
|
+
|
|
133
|
+
init (i18n) {
|
|
134
|
+
const { options } = i18n
|
|
135
|
+
|
|
136
|
+
// store for later
|
|
137
|
+
i18next = i18n
|
|
138
|
+
|
|
139
|
+
const showInContext = getQsParameterByName(opt.qsProp) === 'true'
|
|
140
|
+
|
|
141
|
+
// add postProcessor and needed options now
|
|
142
|
+
if (!isInIframe && showInContext) configurePostProcessor(i18next, options)
|
|
143
|
+
|
|
144
|
+
const impl = getImplementation(i18n)
|
|
145
|
+
|
|
146
|
+
// start process and expose some implementation functions
|
|
147
|
+
if (!isInIframe && showInContext) {
|
|
148
|
+
start(impl)
|
|
149
|
+
} else {
|
|
150
|
+
startLegacy(impl)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
118
156
|
export { unwrap }
|
|
119
157
|
|
|
120
158
|
export function getI18next () {
|
package/src/observer.js
CHANGED
|
@@ -1,7 +1,32 @@
|
|
|
1
1
|
import { debounce } from './utils.js'
|
|
2
2
|
import { validAttributes } from './vars.js'
|
|
3
3
|
|
|
4
|
+
const mutationTriggeringElements = {}
|
|
5
|
+
|
|
4
6
|
function ignoreMutation (ele) {
|
|
7
|
+
if (ele.uniqueID) {
|
|
8
|
+
const info = mutationTriggeringElements[ele.uniqueID]
|
|
9
|
+
|
|
10
|
+
if (
|
|
11
|
+
info &&
|
|
12
|
+
info.triggered > 10 &&
|
|
13
|
+
info.lastTriggerDate + 500 < Date.now()
|
|
14
|
+
) {
|
|
15
|
+
if (!info.warned && console) {
|
|
16
|
+
console.warn(
|
|
17
|
+
'locize ::: ignoring element change - an element is rerendering too often in short interval',
|
|
18
|
+
'\n',
|
|
19
|
+
'consider adding the "data-locize-editor-ignore:" attribute to the element:',
|
|
20
|
+
ele
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
info.warned = true
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return true
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
5
30
|
const ret =
|
|
6
31
|
ele.dataset &&
|
|
7
32
|
(ele.dataset.i18nextEditorElement === 'true' ||
|
|
@@ -51,6 +76,15 @@ export function createObserver (ele, handle) {
|
|
|
51
76
|
return
|
|
52
77
|
}
|
|
53
78
|
|
|
79
|
+
// cleanup mutation triggers after some time of non triggering
|
|
80
|
+
Object.keys(mutationTriggeringElements).forEach(k => {
|
|
81
|
+
const info = mutationTriggeringElements[k]
|
|
82
|
+
|
|
83
|
+
if (info.lastTriggerDate + 60000 < Date.now()) {
|
|
84
|
+
delete mutationTriggeringElements[k]
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
|
|
54
88
|
// ignore mutation done by our elements
|
|
55
89
|
if (mutation.type === 'childList') {
|
|
56
90
|
let notOurs = 0
|
|
@@ -73,14 +107,27 @@ export function createObserver (ele, handle) {
|
|
|
73
107
|
// eventual text relevant mutation
|
|
74
108
|
triggerMutation = true
|
|
75
109
|
|
|
110
|
+
// add to mutationTriggers to check for spamming elements
|
|
111
|
+
if (mutation.target && mutation.target.uniqueID) {
|
|
112
|
+
const info = mutationTriggeringElements[mutation.target.uniqueID] || {
|
|
113
|
+
triggered: 0
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
info.triggered = info.triggered + 1
|
|
117
|
+
info.lastTriggerDate = Date.now()
|
|
118
|
+
|
|
119
|
+
mutationTriggeringElements[mutation.target.uniqueID] = info
|
|
120
|
+
}
|
|
121
|
+
|
|
76
122
|
// test if mutated element is already part of another mutation
|
|
77
123
|
const includedAlready = targetEles.reduce((mem, element) => {
|
|
78
124
|
if (
|
|
79
125
|
mem ||
|
|
80
126
|
element.contains(mutation.target) ||
|
|
81
127
|
!mutation.target.parentElement
|
|
82
|
-
)
|
|
128
|
+
) {
|
|
83
129
|
return true
|
|
130
|
+
}
|
|
84
131
|
return false
|
|
85
132
|
}, false)
|
|
86
133
|
|
package/src/ui/utils.js
CHANGED
|
@@ -4,14 +4,14 @@ export function isInViewport (el) {
|
|
|
4
4
|
// Special bonus for those using jQuery
|
|
5
5
|
// if (typeof jQuery !== 'undefined' && el instanceof jQuery) el = el[0]
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
const rect = el.getBoundingClientRect()
|
|
8
8
|
// DOMRect { x: 8, y: 8, width: 100, height: 100, top: 8, right: 108, bottom: 108, left: 8 }
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
const windowHeight = window.innerHeight || document.documentElement.clientHeight
|
|
10
|
+
const windowWidth = window.innerWidth || document.documentElement.clientWidth
|
|
11
11
|
|
|
12
12
|
// http://stackoverflow.com/questions/325933/determine-whether-two-date-ranges-overlap
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
const vertInView = rect.top <= windowHeight && rect.top + rect.height >= 0
|
|
14
|
+
const horInView = rect.left <= windowWidth && rect.left + rect.width >= 0
|
|
15
15
|
|
|
16
16
|
return vertInView && horInView
|
|
17
17
|
}
|
package/src/utils.js
CHANGED
|
@@ -156,3 +156,15 @@ export function getElementNamespace (el) {
|
|
|
156
156
|
|
|
157
157
|
return found
|
|
158
158
|
}
|
|
159
|
+
|
|
160
|
+
export function getQsParameterByName (name, url) {
|
|
161
|
+
if (typeof window === 'undefined') return null
|
|
162
|
+
if (!url) url = window.location.href.toLowerCase()
|
|
163
|
+
// eslint-disable-next-line no-useless-escape
|
|
164
|
+
name = name.replace(/[\[\]]/g, '\\$&')
|
|
165
|
+
const regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)')
|
|
166
|
+
const results = regex.exec(url)
|
|
167
|
+
if (!results) return null
|
|
168
|
+
if (!results[2]) return ''
|
|
169
|
+
return decodeURIComponent(results[2].replace(/\+/g, ' '))
|
|
170
|
+
}
|