()
| 10 | } from "../utils/element-extraction"; |
| 11 | |
| 12 | export default function createHtmlLoader(): ILoader< |
| 13 | string, |
| 14 | Record<string, string> |
| 15 | > { |
| 16 | return createLoader({ |
| 17 | async pull(locale, input) { |
| 18 | const result: Record<string, string> = {}; |
| 19 | |
| 20 | // Parse HTML with htmlparser2 (preserves structure, no foster parenting) |
| 21 | const handler = new DomHandler(); |
| 22 | const parser = new htmlparser2.Parser(handler, { |
| 23 | lowerCaseTags: false, |
| 24 | lowerCaseAttributeNames: false, |
| 25 | }); |
| 26 | parser.write(input); |
| 27 | parser.end(); |
| 28 | |
| 29 | const dom = handler.dom; |
| 30 | |
| 31 | // Get innerHTML equivalent (serialize children) |
| 32 | function getInnerHTML(element: Element): string { |
| 33 | return element.children |
| 34 | .map((child) => |
| 35 | DomSerializer.default(child, { encodeEntities: false }), |
| 36 | ) |
| 37 | .join(""); |
| 38 | } |
| 39 | |
| 40 | // Extract localizable attributes from element |
| 41 | function extractAttributes(element: Element, path: string): void { |
| 42 | const tagName = element.name.toLowerCase(); |
| 43 | const attrs = BASE_LOCALIZABLE_ATTRIBUTES[tagName]; |
| 44 | if (!attrs) return; |
| 45 | |
| 46 | for (const attr of attrs) { |
| 47 | const value = element.attribs?.[attr]; |
| 48 | if (value && value.trim()) { |
| 49 | result[`${path}#${attr}`] = value.trim(); |
| 50 | } |
| 51 | } |
| 52 | } |
| 53 | |
| 54 | // Recursively extract translation units from element tree |
| 55 | const extractFromElement = createElementExtractor( |
| 56 | { getInnerHTML, extractAttributes }, |
| 57 | result, |
| 58 | ); |
| 59 | |
| 60 | // Find head and body elements |
| 61 | const html = domutils.findOne( |
| 62 | (elem) => elem.type === "tag" && elem.name.toLowerCase() === "html", |
| 63 | dom, |
| 64 | true, |
| 65 | ) as Element | null; |
| 66 | |
| 67 | if (html) { |
| 68 | const head = domutils.findOne( |
| 69 | (elem) => elem.type === "tag" && elem.name.toLowerCase() === "head", |
no test coverage detected