This repository has been archived by the owner on Jan 30, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: initial implementation and some tests
- Loading branch information
Noah Hummel
committed
Feb 24, 2022
0 parents
commit 84bdc91
Showing
15 changed files
with
12,945 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
.idea | ||
node_modules |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { useTranslation } from 'react-i18next'; | ||
import React, { Fragment, ReactElement } from 'react'; | ||
import { renderTreeFromText } from './renderTreeFromText'; | ||
import { PlaceholderFunction } from './types/PlaceholderFunction'; | ||
|
||
interface TransProps <TComponentNames extends string, TInterpolations extends string>{ | ||
children: PlaceholderFunction<TComponentNames, TInterpolations>; | ||
namespace: string; | ||
translation: string; | ||
components: Record<TComponentNames, React.FunctionComponent>; | ||
interpolations?: Record<TInterpolations, any>; | ||
} | ||
|
||
const Trans = function <TComponentNames extends string, TInterpolations extends string> ({ | ||
children, | ||
translation, | ||
namespace, | ||
components, | ||
interpolations | ||
}: TransProps<TComponentNames, TInterpolations>): ReactElement { | ||
const { t, i18n } = useTranslation(namespace); | ||
|
||
const resource = i18n.getResource(i18n.language, namespace, translation); | ||
|
||
if (!resource) { | ||
return children(components, interpolations); | ||
} | ||
|
||
return ( | ||
<Fragment> | ||
{ | ||
renderTreeFromText({ | ||
text: t(translation, interpolations), | ||
components | ||
}) | ||
} | ||
</Fragment> | ||
); | ||
}; | ||
|
||
export { | ||
Trans | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { defekt } from 'defekt'; | ||
|
||
class ClosingTagDoesNotMatchOpeningTag extends defekt({ code: 'ClosingTagDoesNotMatchOpeningTag' }) {} | ||
class NotAllTagsWereClosed extends defekt({ code: 'NotAllTagsWereClosed' }) {} | ||
class TagIsIncomplete extends defekt({ code: 'TagIsIncomplete' }) {} | ||
class TagIsNotKnown extends defekt({ code: 'TagIsNotKnown' }) {} | ||
class TagNameIsInvalid extends defekt({ code: 'TagNameIsInvalid' }) {} | ||
|
||
export { | ||
ClosingTagDoesNotMatchOpeningTag, | ||
NotAllTagsWereClosed, | ||
TagIsIncomplete, | ||
TagIsNotKnown, | ||
TagNameIsInvalid | ||
}; |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
import { ClosingTagDoesNotMatchOpeningTag, NotAllTagsWereClosed, TagIsIncomplete, TagIsNotKnown, TagNameIsInvalid } from './errors'; | ||
import { Tag } from './types/Tag'; | ||
import React, { ReactNode } from 'react'; | ||
|
||
const renderTreeFromText = ({ | ||
text, | ||
components | ||
}: { | ||
text: string; | ||
components: Record<string, React.FunctionComponent>; | ||
}): ReactNode[] => { | ||
const openTags: Tag[] = [ | ||
{ | ||
name: 'root', | ||
children: [] | ||
} | ||
]; | ||
|
||
let currentTextPart = ''; | ||
|
||
for (let i = 0; i < text.length;) { | ||
const nextChar = text[i]; | ||
const remainingChars = text.length - i; | ||
|
||
if (nextChar !== '<') { | ||
currentTextPart += nextChar; | ||
i += 1; | ||
|
||
continue; | ||
} | ||
|
||
if (i + 1 === text.length) { | ||
throw new TagIsIncomplete({ data: { position: i } }); | ||
} | ||
|
||
const isClosingTag = text[i + 1] === '/'; | ||
|
||
if (!isClosingTag) { | ||
const currentlyOpenTag = openTags.at(0); | ||
|
||
if (currentTextPart.length > 0) { | ||
currentlyOpenTag!.children.push(currentTextPart); | ||
currentTextPart = ''; | ||
} | ||
|
||
let tag = ''; | ||
let readChars = 0; | ||
|
||
for (; readChars < remainingChars; readChars++) { | ||
const nextTagChar = text[i + readChars]; | ||
|
||
tag += nextTagChar; | ||
|
||
if (nextTagChar === '>') { | ||
break; | ||
} | ||
|
||
if (readChars + 1 === remainingChars) { | ||
throw new TagIsIncomplete({ | ||
data: { | ||
position: i, | ||
tag: tag.slice(1) | ||
} | ||
}); | ||
} | ||
} | ||
|
||
const tagName = tag.slice(1, -1); | ||
|
||
if (!/^[a-zA-Z]\w*$/gm.test(tagName)) { | ||
throw new TagNameIsInvalid({ | ||
data: { | ||
tag: tagName, | ||
position: i | ||
} | ||
}); | ||
} | ||
|
||
openTags.unshift({ | ||
name: tagName, | ||
children: [] | ||
}); | ||
i += readChars + 1; | ||
|
||
continue; | ||
} | ||
|
||
let tag = ''; | ||
let readChars = 0; | ||
|
||
for (; readChars < remainingChars; readChars++) { | ||
const nextTagChar = text[i + readChars]; | ||
|
||
tag += nextTagChar; | ||
|
||
if (nextTagChar === '>') { | ||
break; | ||
} | ||
|
||
if (readChars + 1 === remainingChars) { | ||
throw new TagIsIncomplete({ | ||
data: { | ||
position: i, | ||
tag: tag.slice(2) | ||
} | ||
}); | ||
} | ||
} | ||
|
||
const tagName = tag.slice(2, -1); | ||
const closedTag = openTags.shift(); | ||
|
||
if (!/^[a-zA-Z]\w*$/gm.test(tagName)) { | ||
throw new TagNameIsInvalid({ | ||
data: { | ||
tag: tagName, | ||
position: i | ||
} | ||
}); | ||
} | ||
|
||
if (tagName !== closedTag!.name) { | ||
throw new ClosingTagDoesNotMatchOpeningTag({ | ||
data: { | ||
position: i, | ||
tag: tagName, | ||
openTags | ||
} | ||
}); | ||
} | ||
|
||
if (currentTextPart.length > 0) { | ||
closedTag!.children.push(currentTextPart); | ||
currentTextPart = ''; | ||
} | ||
|
||
const Component = components[tagName]; | ||
|
||
if (!Component) { | ||
throw new TagIsNotKnown({ | ||
data: { | ||
tag: tagName, | ||
position: i | ||
} | ||
}); | ||
} | ||
|
||
const renderedClosedTag = (<Component>{ ...closedTag!.children }</Component>); | ||
const currentlyOpenTag = openTags.at(0); | ||
|
||
currentlyOpenTag!.children.push(renderedClosedTag); | ||
|
||
i += readChars + 1; | ||
} | ||
|
||
if (openTags.length > 1) { | ||
throw new NotAllTagsWereClosed({ data: { openTags } }); | ||
} | ||
|
||
const root = openTags.shift(); | ||
|
||
if (currentTextPart.length > 0) { | ||
root!.children.push(currentTextPart); | ||
} | ||
|
||
return root!.children; | ||
}; | ||
|
||
export { | ||
renderTreeFromText | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { FunctionComponent } from 'react'; | ||
import { NoProps } from './NoProps'; | ||
|
||
type DumbComponent = FunctionComponent<NoProps>; | ||
|
||
export type { | ||
DumbComponent | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
type NoProps = Record<any, unknown>; | ||
|
||
export type { | ||
NoProps | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { ReactElement } from 'react'; | ||
import { DumbComponent } from './DumbComponent'; | ||
|
||
type PlaceholderFunction <TComponentNames extends string, TInterpolations extends string> | ||
= (components: Record<TComponentNames, DumbComponent>, interpolations?: Record<TInterpolations, any>) => ReactElement | ||
|
||
export type { | ||
PlaceholderFunction | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { ReactNode } from 'react'; | ||
|
||
interface Tag { | ||
name: string; | ||
children: ReactNode[]; | ||
} | ||
|
||
export type { | ||
Tag | ||
}; |
Oops, something went wrong.