๐คจ lingui
์ด๋ฒ์ ํ์ฌ์์ https://lingui.dev/introduction ํด๋น ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํด์ ํ์ฌ ๋ค๊ตญ์ด ์์ ์ด ํ์ํ ํ๋ก์ ํธ์ ์ถ๊ฐํ๋ ์์ ์ ์งํ ์ค์ธ๋ฐ, ๋๋ ์ ์ ์์ ์ ํจ๊ป ์งํํ๊ฒ ๋๋ฉด์ ์๊ฒ ๋ lingui์ ๋ํด ๊ฐ๋จํ๊ฒ ์ ๋ฆฌํด ๋ณด์๋ค.
1. Lingui ์ด๊ธฐ ์ค์
๋จผ์ Lingui CLI์ ํ์ํ ํจํค์ง๋ฅผ ์ค์น๊ฐ ํ์ํ๋ฐ, @lingui/cli
๋ CLI ๋๊ตฌ๋ก ๋ฉ์์ง ์ถ์ถ ๋ฐ ๋ฒ์ญ ๊ด๋ฆฌ๋ฅผ ๋๊ณ , @lingui/macro
๋ ์ฝ๋๋ฅผ ๋ถ์ํ์ฌ ๋ฒ์ญ์ด ํ์ํ ํ
์คํธ๋ฅผ ์ธ์ํ๊ณ , @lingui/react
๋ React์์ ์ฌ์ฉํ ์ปดํฌ๋ํธ๋ฅผ ์ ๊ณตํ๋ค.
npm install @lingui/cli @lingui/macro @lingui/react
๊ทธ๋ฆฌ๊ณ lingui.config.js๋ฅผ ์์ฑํด์ locales ๋ฐฐ์ด์ ๋ค๊ตญ์ด ์ง์์ ์ํ๋ ์ธ์ด ์ฝ๋๋ฅผ ์ถ๊ฐํ๊ณ , sourceLocale์๋ ๊ธฐ๋ณธ ์ธ์ด ์ค์ ์ด ํ์ํ๋ค.
module.exports = {
// ์คํ์ธ์ด๋ฅผ ์ง์ํ๋ค๊ณ ๊ฐ์
locales: ["es"],
sourceLocale: "ko",
format: "po",
catalogs: [{
path: "locales/{locale}/messages",
include: ["src"],
}],
};
2. ์์ฃผ ์ฌ์ฉ๋๋ ๋งคํฌ๋ก๋ค
๐ JS ๋งคํฌ๋ก
โจt
โจ
๊ฐ์ฅ ๊ธฐ๋ณธ ๋งคํฌ๋ก! ICU ๋ฉ์์ง ํ์์ ๋ฌธ์์ด์ ์์ฑํ๋ฉฐ, ํ๊ทธ ๋ ํ
ํ๋ฆฟ ๋ฆฌํฐ๋ด๋ก ๋ณํ๋๋ค.
import { t } from "@lingui/macro";
const message = t`Hello World`;
//// ๋ณํ๋ ์ฝ๋ ↓ ↓ ↓ ↓ ↓ ↓
import { i18n } from "@lingui/core";
const message = i18n._(
/*i18n*/ {
id: "mY42CM",
message: "Hello World",
}
);
๋ณ์์ ํจ๊ป ์ฌ์ฉ ๊ฐ๋ฅํ๋ค.
import { t } from "@lingui/macro";
const message = t`My name is ${name}`;
//// ๋ณํ๋ ์ฝ๋ ↓ ↓ ↓ ↓ ↓ ↓
import { i18n } from "@lingui/core";
const message = i18n._(
/*i18n*/ {
id: "mVmaLu",
message: "My name is {name}",
values: { name },
}
);
๋จ์ ๋ณ์๋ฅผ ์ ์ธํ๊ณ ๋ ์ฌ๋ฌ ํํ์๋ค์ด ํ ํ๋ฆฟ ๋ฆฌํฐ๋ด์ ํฌํจํ ์๋ ์์ง๋ง, ์ด๋ฐ ๊ฒฝ์ฐ ํํ์์ ์ซ์ ์ธ๋ฑ์ค๋ก ์ฐธ์กฐ๋๋ค.
import { t } from "@lingui/macro";
const message = t`Today is ${new Date()}`;
// ๋ณํ๋ ์ฝ๋
import { i18n } from "@lingui/core";
const message = i18n._(
/*i18n*/ {
id: "2aJT27",
message: "Today is {0}",
values: { 0: new Date() },
}
);
๋ฉ์์ง ์ค๋ช ์๋ฅผ ํจ๊ป ์ถ๊ฐํ ์ ์๋ค.
import { t } from "@lingui/macro";
const message = t({
id: "msg.hello",
comment: "Greetings at the homepage",
message: `Hello ${name}`,
});
// ๋ณํ๋ ์ฝ๋
import { i18n } from "@lingui/core";
const message = i18n._(
/*i18n*/ {
id: "msg.hello",
comment: "Greetings at the homepage",
message: "Hello {name}",
values: { name },
}
);
โจplural
โจ
๋ณต์ํ ์ฒ๋ฆฌ๋ฅผ ์ํด ์ฌ์ฉ๋๋ค.
import { plural } from "@lingui/macro";
const message = plural(count, {
one: "# Book",
other: "# Books",
});
// ↓ ↓ ↓ ↓ ↓ ↓
import { i18n } from "@lingui/core";
const message = i18n._(
/*i18n*/ {
id: "V/M0Vc",
message: "{count, plural, one {# Book} other {# Books}}",
values: { count },
}
);
โจselectOrdinal
โจ
์์๋ฅผ ์ํด ์ฌ์ฉ๋๋ค.
import { selectOrdinal } from "@lingui/macro";
const message = selectOrdinal(count, {
one: "#st",
two: "#nd",
few: "#rd",
other: "#th",
});
// ↓ ↓ ↓ ↓ ↓ ↓
import { i18n } from "@lingui/core";
const message = i18n._(
/*i18n*/ {
id: "V8xI3w",
message: "{count, selectOrdinal, one {#st} two {#nd} few {#rd} other {#th}}",
values: { count },
}
);
โจselect
โจ
์กฐ๊ฑด์ ๋ฐ๋ผ ๋ฉ์์ง๋ฅผ ์ ํํ๋ ๊ฒฝ์ฐ ์ฌ์ฉ๋๋ค.
import { select } from "@lingui/macro";
const message = select(gender, {
male: "he",
female: "she",
other: "they",
});
// ↓ ↓ ↓ ↓ ↓ ↓
import { i18n } from "@lingui/core";
const message = i18n._(
/*i18n*/ {
id: "VRptzI",
message: "{gender, select, male {he} female {she} other {they}}",
values: { gender },
}
);
โจdefineMessage
or msg
โจ
๋์ค์ ์ฌ์ฉํ ๋ฉ์์ง๋ฅผ ์ ์ํ ์ ์๊ฒ ํด ์ฃผ๋๋ฐ, ์ง์ฐ ๋ฒ์ญ์ ์ฃผ๋ก ์ฌ์ฉ๋๋ค.
import { defineMessage } from "@lingui/macro";
const message = defineMessage`Hello World`;
// ↓ ↓ ↓ ↓ ↓ ↓
const message = /*i18n*/ {
id: "mY42CM",
message: "Hello World",
};
๐ JSX ๋งคํฌ๋ก
โจTrans
โจ
์ ์ ๋ฉ์์ง, ๋ณ์๋ฅผ ํฌํจํ ๋ฉ์์ง, ๊ทธ๋ฆฌ๊ณ ์ธ๋ผ์ธ ๋งํฌ์
์ด ์๋ ๋ฉ์์ง๋ฅผ ์ฒ๋ฆฌํ๊ธฐ ์ํด ์ฌ์ฉ๋๋ค.
id: ์ฌ์ฉ์ ์ง์ ๋ฉ์์ง ID
comment: ๋ฒ์ญ๊ฐ๋ฅผ ์ํ ์ฃผ์
context: ๋์ผํ ๋ฉ์์ง๋ฅผ ๋ค๋ฅธ ID๋ก ์ถ์ถ
import { Trans } from "@lingui/macro";
<Trans id="message.attachment_saved">Attachment {name} saved.</Trans>;
//์ฌ์ฉ์ ์ง์ ๋ฉ์์ง ID ↓ ↓ ↓ ↓ ↓ ↓
import { Trans } from "@lingui/react";
<Trans id="message.attachment_saved" message="Attachment {name} saved." />;
import { Trans } from "@lingui/macro";
<Trans context="direction">right</Trans>;
<Trans context="correctness">right</Trans>;
// context ์ฌ์ฉ์ ๋ค๋ฅธ ์์ด๋๋ฅผ ๋ถ์ฌํจ ↓ ↓ ↓ ↓ ↓ ↓
import { Trans } from "@lingui/react";
<Trans id={"d1wX4r"} message="right" />;
<Trans id={"16eaSK"} message="right" />;
import { Trans } from "@lingui/macro";
<Trans>
Read the <a href="/docs">docs</a>.
</Trans>;
// ↓ ↓ ↓ ↓ ↓ ↓
import { Trans } from "@lingui/macro";
<Trans id={"mk8bSG"} message="Read the <0>docs</0>." components={{ 0: <a href="/docs" /> }} />;
์ปดํฌ๋ํธ์ HTML ํ๊ทธ๋ ๋๋ฏธ ์ธ๋ฑ์ค ํ๊ทธ(<0></0>
)๋ก ๋์ฒด๋๋ค.
- ์ฌ์ฉ์ ์ ์ React ์ปดํฌ๋ํธ์ ๊ธฐ๋ณธ HTML ํ๊ทธ ๋ชจ๋ ์ง์.
- ์ปดํฌ๋ํธ์ props๋ฅผ ๋ณ๊ฒฝํด๋ ๋ฒ์ญ์ด ๊นจ์ง์ง ์์.
- ๋ฉ์์ง๊ฐ ์ ์ฒด ๋ฌธ์ฅ์ผ๋ก ์ถ์ถ๋๋ค.
๐จ๐ค๐จ๐ง ์ธ๋ฑ์ค ํํฌ๋....?๐จ๐ค๐จ๐ง
<p>
<Trans>
See all <a href="/unread">unread messages</a>
{" or "}
<a onClick={markAsRead}>mark them</a> as read.
</Trans>
</p>
๋ง์ฝ ์์ ์ฝ๋๋ฅผ extract ๋ช ๋ น์ด๋ฅผ ๋๋ ค ์ถ์ถํ์๋ฉด ์๋์ ๊ฐ์ด html ํ๊ทธ๋ค์ ์ธ๋ฑ์ค ํ๊ทธ๋ก ๋ณ๊ฒฝ๋์ด์ ์ ์ฅ๋๋ค.
"See all <0>unread messages</0> or <1>mark them</1> as read."
๋๋ ์ธ๋ฑ์ค ํ๊ทธ( <0>, <1>)๋ฅผ ๊ณ์ ๋ฒ์ญ ๋ฌธ์ฅ๊ณผ ์ฎ์ด์ ์๊ฐ์ ํด์ ์ดํดํ๊ธฐ๊ฐ ์ด๋ ค์ ๋๋ฐ ๐ฅฒ, ๋จ์ํ ์๊ฐํด์ ํ๊ทธ/์ปดํฌ๋ํธ ํ์ํ๋ ํ๊ทธ.. ๋ฌธ๋ฒ์ผ๋ก ์๊ฐํ๋ ์ดํดํ๊ธฐ๊ฐ ์ฌ์์ก๋ค.
์ธ๋ฑ์ค ํ๊ทธ๋ฅผ ์ฌ์ฉํ๋ ์ด์ ๋ก ์ฒซ ๋ฒ์งธ ํ๊ทธ๋ฅผ ๋จ์ํ๊ฒ ๊ด๋ฆฌ ์ํจ์ธ๋ฐ, ๋ฒ์ญ ๊ณผ์ ์์ HTML์ด๋ ์ปดํฌ๋ํธ๋ฅผ ๊ทธ๋๋ก ํฌํจํ๋ฉด ๋ฒ์ญ๊ฐ๋ค์ด ํผ๋์ค๋ฌ์ธ ์ ์๊ธฐ์, ํ๊ทธ๋ ์ปดํฌ๋ํธ๋ ์ซ์ ์ธ๋ฑ์ค๋ก ํ์ํ์ฌ ๊ด๋ฆฌ๊ฐ ์ฝ๋๋ก ํ๋ค๊ณ ํ๋ค.
๋ ๋ฒ์งธ๋ก๋ ์ฝ๋ ๋ณ๊ฒฝ ์ ๋ฒ์ญ์ ์ํฅ์ด ์๋๋ก ํ๊ธฐ ์ํด์์ด๋ค. ์ธ๋ฑ์ค ํ๊ทธ๋ฅผ ์ฌ์ฉํ์๊ธฐ์ ํด๋น ์ปดํฌ๋ํธ์ className์ด๋ onClick ๊ฐ์ ์์ฑ์ ๋ณ๊ฒฝํ๋๋ผ๋ ๋ฒ์ญ ๋ฉ์์ง์๋ ์ํฅ์ ์ฃผ์ง ์๋๋ค. ๋ฒ์ญ ๋ฉ์์ง๋ ์ค๋ก์ง ์ธ๋ฑ์ค๋ก ํ๊ทธ ์์น๋ฅผ ๊ด๋ฆฌํ๊ธฐ ๋๋ฌธ์, ๋ฉ์์ง๋ฅผ ์ฌ๋ฒ์ญํ๊ฑฐ๋ ์์ ํ ํ์๊ฐ ์๋ค๊ณ ํ๋ค.
๊ทธ๋ผ ์ปดํฌ๋ํธ/ํ๊ทธ๊ฐ ์๋ ๋ฒ์ญ ๋ฉ์์ง์ ๋์ ๋ฐฉ์์ ์์ ์ถ์ถ๋ ๋ฉ์์ง์์ <0>
๊ณผ <1>
์ ๊ฐ๊ฐ <a href="/unread">
์ <a onClick={markAsRead}
>์ ๋์๋๊ณ , Trans ๋งคํฌ๋ก๊ฐ ์ด ๋ฉ์์ง๋ฅผ ๋ ๋๋ง ํ ๋, ์ธ๋ฑ์ค ํ๊ทธ๋ JSX๋ก ๋๋์๊ฐ๋ฉด์ ์๋ ์์น์ ๋ง์ถฐ ์ปดํฌ๋ํธ์ ์์ฑ๋ค์ด ๋ ๋๋ง ๋๋ค.
โจPlural
โจ
๋ณต์ํ ํ์์ผ๋ก ๋ณํ์ด ํ์ํ ๋ ์ฌ์ฉ๋๋ค.
import { Plural } from "@lingui/macro";
<Plural value={numBooks} one="Book" other="Books" />;
// ↓ ↓ ↓ ↓ ↓ ↓
import { Trans } from "@lingui/react";
<Trans id={"is7n96"} message="{numBooks, plural, one {Book} other {Books}}" values={{ numBooks }} />;
#์ ์ซ์ ํ์์ผ๋ก ์ฌ์ฉ๋๋ค.
import { Plural } from "@lingui/macro";
<Plural
value={count}
one="You and # other guest arrived"
// when value >= 3
other="You and # other guests arrived"
/>;
๋ง์ฝ id, context, ๋๋ comment๊ฐ ํ์ํ๋ค๋ฉด <Trans>
๋งคํฌ๋ก ๋ด๋ถ์์ <Plural>
์ ์ฌ์ฉํ๋ฉด ๋๋ค.
โจSelectOrdinal
โจ
ํน์ ์์น๋ ์์๋ฅผ ๋ํ๋ผ ๋ ์ฌ์ฉ๋๋ค.
import { SelectOrdinal } from "@lingui/macro";
// count == 1 -> 1st
// count == 2 -> 2nd
// count == 3 -> 3rd
// count == 4 -> 4th
<SelectOrdinal value={count} one="#st" two="#nd" few="#rd" other="#th" />;
โจSelect
โจ
๊ฐ์ ๋ฐ๋ผ ๋ค๋ฅธ ๋ฌธ์ฅ์ ํ์ํด์ผ ํ ๋ ์ฌ์ฉ๋๋ค.
import { Select } from "@lingui/macro";
// gender == "female" -> Her book
// gender == "male" -> His book
// gender == "non-binary" -> Their book
<Select value={gender} _male="His book" _female="Her book" other="Their book" />;
other๋ฅผ ์ ์ธํ ์ ํ ํญ๋ชฉ๋ค์ ๋ฐ์ค(_)๋ก ์์ํด์ผ ํ๋ค. ์: _male, _female.
3. React์์ Lingui ์ปดํฌ๋ํธ ์ฌ์ฉํ๊ธฐ
๐ฅ Trans๋ก
๊ฐ์ธ๊ธฐ
React ์ปดํฌ๋ํธ์์ ๊ฐ์ฅ ๊ฐ๋จํ ๋ฐฉ๋ฒ์ผ๋ก ๋ฒ์ญ์ ์ ์ฉํ ์ ์๋ ๋ฐฉ๋ฒ์ด๋ค.
import { Trans } from "@lingui/macro";
function render() {
return (
<>
<h1>
<Trans>LinguiJS example</Trans>
</h1>
<p>
<Trans>
Hello <a href="/profile">{name}</a>.
</Trans>
</p>
</>
);
}
๐ฅ Trans ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ ์ ์๋ ๊ฒฝ์ฐ๋ ๋ฐ์ํ๋๋ฐ, ์ด๋ t ๋งคํฌ๋ก ์ฌ์ฉ์ด ํ์ํ๋ค.
โ Trans ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ ์ ์๋ ๊ฒฝ์ฐ
1. ์ปดํฌ๋ํธ ์ธ๋ถ์์ ํ ์คํธ ๋ฒ์ญ์ด ํ์ํ ๊ฒฝ์ฐ
2. ์์์ ์์ฑ ๊ด๋ จํ ํ ์คํธ ๋ฒ์ญ์ด ํ์ํ ๊ฒฝ์ฐ
t, plural, select, selectOrdinal ๋งคํฌ๋ก๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๊ธ๋ก๋ฒ i18n ์ธ์คํด์ค๋ฅผ ์ฌ์ฉํ๊ธฐ์ ๋๋ถ๋ถ์ ํด๋ผ์ด์ธํธ ์ฌ์ด๋ ํ๊ฒฝ์์ ์ ๋์ํ์ง๋ง, ์๋ฒ์ฌ์ด๋ ๋ ๋๋ง(SSR)๊ณผ ๊ฐ์ ํน์ํ ์ํฉ์์๋ ํน์ ์ธ์คํด์ค๋ฅผ ๋ช ์์ ์ผ๋ก ์ฌ์ฉํ๋ ๊ฒ์ด ๋ ์ข๋ค๊ณ ํ๋ค.
import { msg } from "@lingui/macro";
import { I18n } from "@lingui/core";
export function component () {
const { i18n } = useLingui();
alert(t(i18n)`ํ ๋งํ `);
return (
<div>...</div>
)
}
๐ฅ ๋ณ์ ๊ฐ์ ๋ฐ๋ฅธ ๋ฉ์์ง ๋์ ์ ํ
๋ณ์์ ๋ฐ๋ผ ๋ค๋ฅธ ๋ฉ์์ง๋ฅผ ์ ํํด์ผ ํ ๋๋ ์๋ค. ์๋ฅผ ๋ค์ด, API์์ ๋ฐ์ ์ซ์ "status" ์ฝ๋๊ฐ ์์ ๋, ์ด ์ฝ๋์ ๋ฐ๋ผ ํ์ฌ ์ํ๋ฅผ ๋ํ๋ด๋ ๋ฉ์์ง๋ฅผ ํ์ํด์ผ ํ๋ค๋ฉด "status"์ ๊ฐ๋ฅํ ๊ฐ๋ค์ ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด, ๋ฒ์ญ์ ์ง์ฐ ์ฒ๋ฆฌ ๋ฐฉ์์ผ๋ก ๋ ๋๋ง ํ ์ ์๋ค.
import { msg } from "@lingui/macro";
import { useLingui } from "@lingui/react";
const statusMessages = {
["STATUS_OPEN"]: msg`Open`,
["STATUS_CLOSED"]: msg`Closed`,
["STATUS_CANCELLED"]: msg`Cancelled`,
["STATUS_COMPLETED"]: msg`Completed`,
};
export default function StatusDisplay({ statusCode }) {
const { _ } = useLingui();
return <div>{_(statusMessages[statusCode])}</div>;
}
++++++ ๋๋ ์ ๊ทธ๋ฆฌ๊ณ ๊ฒฐ๋ก ++++++
ํด๋น ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ์์ฃผ ์ฌ์ฉ๋๋ ๋งคํฌ๋ก๋ค๊ณผ ๋ฆฌ์กํธ์ ํจ๊ป ์ฌ์ฉํ๋ ์์๋ค๋ง ๊ฐ๋จํ ์ ๋ฆฌํด ๋ณด์๋๋ฐ, ์ ๋ฒ์ญ์ด ๋๋๋ก ํ๊ธฐ ์ํด์๋ ๋ฌธ๋ฒ์ ๋ง๊ฒ ๋ค์ํ ๋งคํฌ๋ก๋ฅผ ์ ์ฌ์ฉํด์ผ ํ๋ค๊ณ ๋๊ผ๋ค. ํ์ฌ ํ์ฌ์์๋ ๋ค๊ตญ์ด ํ๋ก์ ํธ๊ฐ ๊ธํ๊ฒ ๋ค์ด๊ฐ๊ฒ ๋์ด์ ๋ฌธ๋ฒ์ ๋ง๋ ์ธ๋ฐํ ๋งคํฌ๋ก ์ฌ์ฉ ๋์ ์ผ๋จ ๋ฒ์ญ์ด ๋๋๋ก ํ๋ ๊ฒ์ ์ง์ค์ด ๋์ด ์๋ ์ํฉ์ด๋ผ.... ์ด์ํ ๋ถ๋ถ๋ค์ด ๋ง์ด ์์ ๊ฒ์ผ๋ก ์์๋๋๋ฐ.. ๊ฒฐ๋ก -> ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ ์ฌ์ฉํ๊ธฐ๋ ์ฝ์ง ์์ ๊ฒ ๊ฐ๋ค. ๐ฅฒ