| 41 | * @codeGenApi |
| 42 | */ |
| 43 | export function i18nPostprocess( |
| 44 | message: string, |
| 45 | replacements: {[key: string]: string | string[]} = {}, |
| 46 | ): string { |
| 47 | /** |
| 48 | * Step 1: resolve all multi-value placeholders like [�#5�|�*1:1��#2:1�|�#4:1�] |
| 49 | * |
| 50 | * Note: due to the way we process nested templates (BFS), multi-value placeholders are typically |
| 51 | * grouped by templates, for example: [�#5�|�#6�|�#1:1�|�#3:2�] where �#5� and �#6� belong to root |
| 52 | * template, �#1:1� belong to nested template with index 1 and �#1:2� - nested template with index |
| 53 | * 3. However in real templates the order might be different: i.e. �#1:1� and/or �#3:2� may go in |
| 54 | * front of �#6�. The post processing step restores the right order by keeping track of the |
| 55 | * template id stack and looks for placeholders that belong to the currently active template. |
| 56 | */ |
| 57 | let result: string = message; |
| 58 | if (PP_MULTI_VALUE_PLACEHOLDERS_REGEXP.test(message)) { |
| 59 | const matches: {[key: string]: PostprocessPlaceholder[]} = {}; |
| 60 | const templateIdsStack: number[] = [ROOT_TEMPLATE_ID]; |
| 61 | result = result.replace(PP_PLACEHOLDERS_REGEXP, (m: any, phs: string, tmpl: string): string => { |
| 62 | const content = phs || tmpl; |
| 63 | const placeholders: PostprocessPlaceholder[] = matches[content] || []; |
| 64 | if (!placeholders.length) { |
| 65 | content.split('|').forEach((placeholder: string) => { |
| 66 | const match = placeholder.match(PP_TEMPLATE_ID_REGEXP); |
| 67 | const templateId = match ? parseInt(match[1], 10) : ROOT_TEMPLATE_ID; |
| 68 | const isCloseTemplateTag = PP_CLOSE_TEMPLATE_REGEXP.test(placeholder); |
| 69 | placeholders.push([templateId, isCloseTemplateTag, placeholder]); |
| 70 | }); |
| 71 | matches[content] = placeholders; |
| 72 | } |
| 73 | |
| 74 | if (!placeholders.length) { |
| 75 | throw new Error(`i18n postprocess: unmatched placeholder - ${content}`); |
| 76 | } |
| 77 | |
| 78 | const currentTemplateId = templateIdsStack[templateIdsStack.length - 1]; |
| 79 | let idx = 0; |
| 80 | // find placeholder index that matches current template id |
| 81 | for (let i = 0; i < placeholders.length; i++) { |
| 82 | if (placeholders[i][0] === currentTemplateId) { |
| 83 | idx = i; |
| 84 | break; |
| 85 | } |
| 86 | } |
| 87 | // update template id stack based on the current tag extracted |
| 88 | const [templateId, isCloseTemplateTag, placeholder] = placeholders[idx]; |
| 89 | if (isCloseTemplateTag) { |
| 90 | templateIdsStack.pop(); |
| 91 | } else if (currentTemplateId !== templateId) { |
| 92 | templateIdsStack.push(templateId); |
| 93 | } |
| 94 | // remove processed tag from the list |
| 95 | placeholders.splice(idx, 1); |
| 96 | return placeholder; |
| 97 | }); |
| 98 | } |
| 99 | |
| 100 | // return current result if no replacements specified |