๐Ÿš€ ๋‚ ์”ฌํ•œ ํŒ: ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์— ๊ธฐ๋ณธ ๊ตญ์ œํ™” ์ถ”๊ฐ€(i18n)

15163 ๋‹จ์–ด webdevsveltei18njavascript

๐Ÿ‘‹ ์•ˆ๋…•ํ•˜์‹ญ๋‹ˆ๊นŒ?


Read this article en Espaรฑol(Chema์˜ ์•„์ด๋””์–ด๋ฅผ ํ†ตํ•ด ์ „ํŒŒํ•  ๋งŒํ•œ ๊ฐ€์น˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค!)
๋‚˜๋Š” ์ตœ๊ทผ์— ์šฐ์—ฐํžˆ Matthias Stahl ๋ฐ•์‚ฌcode here์˜ ๋ฉ‹์ง„ ์˜์ƒ์„ ๋ฐœ๊ฒฌํ–ˆ๋‹ค. ๊ทธ๋Š” ๊ธฐ๋ณธ์ ์ธ i18n ๋ฒˆ์—ญ์„ ์ž‘์€ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์— ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•์„ ์ œ์‹œํ–ˆ๋‹ค.


๋งˆํ‹ฐ์•„์Šค ์Šคํƒ€๋ฅด ๋ฐ•์‚ฌ๐Ÿ‡ช๐Ÿ‡บ
@h_i_g_s_c_h

์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค. ์ €๋Š” ๋ฐฉ๊ธˆ ์•ฝ 20์ค„ ์ฝ”๋“œ๋กœ ์ž‘์€ i18n ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.์ด ์ปดํŒŒ์ผ๋Ÿฌ๋Š” ๋„ˆ๋ฌด ๋ฏธ์ณค์–ด!๐Ÿคทโ€โ™‚๏ธ
2020๋…„ 12์›” 30์ผ ์˜ค์ „ 10:05
๋‚˜๋Š” ์žฌ์ฐฝ์„ค๊ณผ ๋™์‹œ์— ์•ฝ๊ฐ„์˜ ์ตœ์ ํ™”์™€ ๊ฐ•ํ™”๋ฅผ ์ง„ํ–‰ํ•˜๋Š” ๊ฒƒ์ด ๋งค์šฐ ์žฌ๋ฏธ์žˆ๊ณ  ์œ ์šฉํ•  ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•œ๋‹ค.๐Ÿค—
์šฐ๋ฆฌ๋Š” ์ด๋Ÿฐ ๊ฒƒ์„ ์ฐฝ์กฐํ•  ๊ฒƒ์ด๋‹ค.

์ด ๊ฒŒ์‹œ๋ฌผ์˜ ๋Œ€๋ถ€๋ถ„ ๊ณต๋กœ๋Š” ๋งˆํ‹ฐ์•„์Šค์˜ ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— ๋ฐ˜๋“œ์‹œ ๊ทธ๋ฅผ ์‚ดํŽด๋ณด๊ณ  ๋ฏธํ–‰ํ•ด์•ผ ํ•œ๋‹ค!๐Ÿ™‡
๐Ÿ“’ ์ฃผ์˜: ์ด๊ฒƒ์€ i18next์ฒ˜๋Ÿผ ๊ธฐ๋Šฅ์ด ์™„๋น„๋œ ๊ตญ์ œํ™” ํ•ด๊ฒฐ ๋ฐฉ์•ˆ์ด ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด๊ฒƒ์€ ๋‹น์‹ ์—๊ฒŒ ์™„์ „ํžˆ ์ ํ•ฉํ•œ ํ•ด๊ฒฐ ๋ฐฉ์•ˆ์ด ์•„๋‹ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค!
๊ท€์ฐฎ๋‹ค์ฒดํฌ ์•„์›ƒSvelte REPL with all the codeโ†—๏ธ

๋ฒˆ์—ญ ๋Œ€์ƒ


Matthias์˜ ์˜ˆ์—์„œ, ๊ทธ๋Š” ๋งค์šฐ ๊นŠ์ด ๋ฐ•ํžŒ ๋Œ€์ƒ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฌธ์ž์—ด์„ ์ €์žฅํ•œ๋‹ค.์ด๊ฒƒ์€ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ, ๋Œ€์ƒ์„ ์˜ฎ๊ฒจ๋‹ค๋…€์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ํŠนํžˆ ๋‹ค์ค‘ ํ”Œ๋Ÿฌ๊ทธ์ธ ํ‚ค๊ฐ€ ์žˆ์œผ๋ฉด (์ƒ๊ฐํ•ด ๋ด app => page => section => component => label.
๋‚˜๋Š” ํ‰๋ฉด ๋Œ€์ƒ์„ ์„ ํƒํ–ˆ๋Š”๋ฐ ๊ทธ ํ‚ค๋Š” ๊ตญ์ œํ™” ์–ธ์–ด ํ™˜๊ฒฝ์˜ ํ•˜์œ„ ํƒœ๊ทธ(์˜ˆ๋ฅผ ๋“ค์–ด en์™€ ๋น„en-US์™€ ๋ณ€ํ™˜ ๊ฐ’์„ ๋‚˜ํƒ€๋‚ด๋Š” ์  ๊ตฌ๋ถ„ ์ด๋ฆ„ ๊ณต๊ฐ„์˜ ๋ฌธ์ž์—ด์ด๋‹ค.์šฐ๋ฆฌ๊ฐ€ ๋งŽ์€ ๋ฒˆ์—ญ์„ ์ฒ˜๋ฆฌํ•  ๋•Œ, ์ด๊ฒƒ์€ ์„ฑ๋Šฅ์— ์•ฝ๊ฐ„์˜ ์ด์ต์ด ์žˆ์–ด์•ผ ํ•œ๋‹ค.
๋˜ํ•œ ํฌํ•จ๋œ ๋ณ€์ˆ˜์™€ HTML์€ ๋ฒˆ์—ญ ๋ฌธ์ž์—ด์—์„œ ์ง€์›๋ฉ๋‹ˆ๋‹ค.
// translations.js
export default {
  en: {
    "homepage.title": "Hello, World!",
    "homepage.welcome": "Hi <strong>{{name}}</strong>, how are you?",
    "homepage.time": "The current time is: {{time}}",
  },
  es: {
    "homepage.title": "ยกHola Mundo!",
    "homepage.welcome": "Hola, <strong>{{name}}</strong>, ยฟcรณmo estรกs?",
    "homepage.time": "La hora actual es: {{time}}",
  },
};

์ด๊ฒƒ์€ ๋ฌธ์ž์—ด, ์ˆซ์ž, ๋‚ ์งœ ๋“ฑ ๋‹ค์–‘ํ•œ ํ˜•์‹๊ณผ ์ฃผ์ž… ๊ฐ’์„ ์ง€์›ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

๊ตฌ์„ฑ ์š”์†Œ


์šฐ๋ฆฌ๋Š” ์ง€๊ธˆ ์šฐ๋ฆฌ์˜ ๋‚ ์”ฌํ•œ ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ๋งŒ๋“ค ๊ฒƒ์ด๋‹ค huzzah!๐Ÿ‘
์ด ๊ตฌ์„ฑ ์š”์†Œ๋Š” ๋งค์šฐ ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์šฉํ•˜๊ณ ์ž ํ•˜๋Š” ์–ธ์–ด๋ฅผ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋Š” ๋“œ๋กญ๋‹ค์šด ๋ชฉ๋ก์„ ํฌํ•จํ•˜๊ณ , HTML๊ณผ ์‚ฌ์šฉ์ž ์ •์˜ ๋ณ€์ˆ˜๊ฐ€ ์žˆ๋Š” ํ…์ŠคํŠธ๋ฅผ ํฌํ•จํ•˜๋Š” ๋ฒˆ์—ญ ํ…์ŠคํŠธ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.
<!-- App.svelte -->
<script>
  import { t, locale, locales } from "./i18n";

  // Create a locale specific timestamp
  $: time = new Date().toLocaleDateString($locale, {
    weekday: "long",
    year: "numeric",
    month: "long",
    day: "numeric",
  });
</script>

<main>
  <p>
    <select bind:value={$locale}>
      {#each locales as l}
        <option value={l}>{l}</option>
      {/each}
    </select>
  </p>

  <h1>{$t("homepage.title")}!</h1>
  <p>{@html $t("homepage.welcome", { name: "Jane Doe" })}!</p>
  <p>{$t("homepage.time", { time })}!</p>
</main>
์šฐ๋ฆฌ๋Š” ๋‚ ์”ฌํ•œ ์ €์žฅ์†Œ์— <select> ์š”์†Œ๋ฅผ ์—ฐ๊ฒฐํ•˜๊ณ  (์šฐ๋ฆฌ๋Š” 1์ดˆ ์•ˆ์— ๋งŒ๋“ค ๊ฒƒ) ์‹ ๊ธฐํ•œ $t() ๋ฐฉ๋ฒ•์œผ๋กœ ๋ฒˆ์—ญ ๊ฒ€์ƒ‰์„ ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.
๋˜ํ•œ ์‚ฌ์šฉ์ž ์‚ฌ์šฉtoLocaleDateString์„ ํ‘œ์‹œํ•˜๊ณ  $locale ์ €์žฅ ๊ฐ’์„ ์ „๋‹ฌํ•˜๋Š” ์–ธ์–ด ํ™˜๊ฒฝ์— ๋งž๋Š” ์‹œ๊ฐ„ ์Šคํƒฌํ”„๋ฅผ ๋งŒ๋“ค๊ณ  ์žˆ์Œ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๋งŒ์•ฝ ์ด๊ฒƒ์ด ์•„์ง ์˜๋ฏธ๊ฐ€ ์—†๋‹ค๋ฉด, ๊ดœ์ฐฎ์•„, ๊ณ„์† ์ฝ์–ด๋ผ!

์ƒ์ .


์ด์ œ ์šฐ๋ฆฌ ๋‚ ์”ฌํ•œ ์ƒ์ ์„ ๋งŒ๋“ค์ž!๐Ÿ‘ฏโ€โ™‚๏ธ
์ €์žฅ ์ž์ฒด๋Š” ๋งค์šฐ ๊ฐ„๋‹จํ•˜๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ ์šฐ๋ฆฌ๋Š” ์ง€์—ญ ์„ค์ • ๊ฐ’(์˜ˆ๋ฅผ ๋“ค์–ด en, es ๋“ฑ)์„ ํ•œ ์ €์žฅ์†Œ์— ์ €์žฅํ•œ ๋‹ค์Œ์— ์ง€์—ญ ์„ค์ •๊ณผ ์šฐ๋ฆฌ๊ฐ€ ์ด์ „์— ๋งŒ๋“ translations ๋Œ€์ƒ์— ๋”ฐ๋ผ derived ์ €์žฅ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.
import { derived, writable } from "svelte/store";
import translations from "./translations";

export const locale = writable("en");
export const locales = Object.keys(translations);

function translate(locale, key, vars) {
  // Let's throw some errors if we're trying to use keys/locales that don't exist.
  // We could improve this by using Typescript and/or fallback values.
  if (!key) throw new Error("no key provided to $t()");
  if (!locale) throw new Error(`no translation for key "${key}"`);

  // Grab the translation from the translations object.
  let text = translations[locale][key];

  if (!text) throw new Error(`no translation found for ${locale}.${key}`);

  // Replace any passed in variables in the translation string.
  Object.keys(vars).map((k) => {
    const regex = new RegExp(`{{${k}}}`, "g");
    text = text.replace(regex, vars[k]);
  });

  return text;
}

export const t = derived(locale, ($locale) => (key, vars = {}) =>
  translate($locale, key, vars)
);
๋Œ€๋ถ€๋ถ„์˜ ๋…ผ๋ฆฌ๋Š” translate ๋ฐฉ๋ฒ•์—์„œ ํ‚ค๋ฅผ ์ฐพ๊ณ  ๋ณ€์ˆ˜๋ฅผ ์ฃผ์ž…ํ•œ๋‹ค(์กด์žฌํ•œ๋‹ค๋ฉด).
ํŒŒ์ƒ ์ €์žฅ์†Œ๋Š” ํ˜„์žฌ ์–ธ์–ด ํ™˜๊ฒฝ๊ณผ ๋™๊ธฐํ™”๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์šฐ๋ฆฌ์˜ translate ๋ฐฉ๋ฒ•์€ ํ˜ธ์ถœ๋  ๋•Œ ํ•ญ์ƒ ํ˜„์žฌ ์–ธ์–ด ํ™˜๊ฒฝ์„ ๋ฐ›์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.๋กœ์ผˆ์ด ์—…๋ฐ์ดํŠธ๋˜๋ฉด ํ˜ธ์ถœ์ด ๋‹ค์‹œ ๊ณ„์‚ฐ๋˜์–ด ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ์ผˆ์„ ๋ณ€๊ฒฝํ•  ๋•Œ Svelte ๊ตฌ์„ฑ ์š”์†Œ์˜ ๋ชจ๋“  ํ…์ŠคํŠธ๊ฐ€ ์—…๋ฐ์ดํŠธ๋ฉ๋‹ˆ๋‹ค.์ฟจ!๐Ÿ˜Ž
์ด๊ฒƒ์€ ๋ฒˆ์—ญ์„ ์œ„ํ•œ ์ถ”๊ฐ€ ์ €์žฅ์†Œ๋ฅผ ๋งŒ๋“ค ํ•„์š”๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์—, ์ด๊ฒƒ์€ ์—„๊ฒฉํ•˜๊ฒŒ ํ•„์š”ํ•œ ๊ฒƒ์ด ์•„๋‹ˆ๋ฉฐ, ์ƒ๋žตํ•˜๋ฉด ํšจ์œจ์ด ๋”์šฑ ๋†’์•„์งˆ ๊ฒƒ์ด๋‹ค.

๊ทธ๊ฒƒ์„ ํ•œ๋ฐ ๋†“๋‹ค


์ด์ œ ์šฐ๋ฆฌ๋Š” ์šฐ๋ฆฌ์˜ ์ƒ์ ์ด ์ƒ๊ฒผ๋‹ค. ์šฐ๋ฆฌ๋Š” ๋ชจ๋“  ๊ฒƒ์„ ๊ฐ€์ง€๊ณ  ๋‚ ์”ฌํ•œ ๊ธฐ๋ณธ ๊ตญ์ œํ™” ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค์—ˆ๋‹ค. ์ถ•ํ•˜ํ•œ๋‹ค.๐ŸŽ‰
์ด ์ฝ”๋“œ์˜ ์‹คํ–‰์„ ๋ณด๋ ค๋ฉด Svelte REPL

๐Ÿ›ฐ ํ•œ์ธต ๋”


ํ˜„์žฌ ์ด ์˜ต์…˜์€ ๋ชจ๋“  ์‚ฌ๋žŒ์—๊ฒŒ ์ ํ•ฉํ•˜์ง€ ์•Š๋‹ค.๋งŒ์•ฝ ๋งŽ์€ ๋ฒˆ์—ญ์„ ํฌํ•จํ•˜๋Š” ๋Œ€ํ˜•, ๊ฑด์žฅํ•˜๊ณ , ๋‚ด์šฉ์ด ๋ฐ€์ง‘๋œ ํ”„๋กœ๊ทธ๋žจ์„ ๊ตฌ์ถ•ํ•˜๊ณ  ์žˆ๋‹ค๋ฉด, Locize ์™€ i18next ์„ ๊ฒฐํ•ฉํ•ด์„œ ์‚ฌ์šฉํ•  ๊ฒƒ์„ ๊ณ ๋ คํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.๋น„์Šทํ•œ ๋ฐฉ์‹์œผ๋กœ ๊ทธ๋“ค์˜ JS ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ Svelte์™€ ํ†ตํ•ฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์ €ํฌ๋„ HTML ๋‚ด์šฉ์„ ์ •๋ฆฌํ•˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฒˆ์—ญ ๋ฌธ์ž์—ด์— ์‚ฌ์šฉ์ž๊ฐ€ ์ œ๊ณตํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ์ž…ํ•˜๋ฉด ์ž…๋ ฅ์„ ์ •๋ฆฌํ•˜๊ฑฐ๋‚˜ ์ œ๊ฑฐํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹คXSS vulnerability!๐Ÿ”
์ด๋Ÿฐ ๋ฐฉ๋ฒ•์˜ ๋˜ ๋‹ค๋ฅธ ๋ฌธ์ œ๋Š” ๋ถ€์กฑํ•œ ๋ฒˆ์—ญ์— ๋Œ€ํ•ด ์ง„์ •ํ•œ ๋ฐ˜ํ™˜ ํ–‰์œ„๊ฐ€ ์—†๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. (์ง€๊ธˆ ์šฐ๋ฆฌ๋Š” ๋‹จ์ง€ ์˜ค๋ฅ˜๋ฅผ ๋˜์กŒ์„ ๋ฟ, ์ด๊ฒƒ์€ ๋‹น์‹ ์ด ์›ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ ์ˆ˜๋„ ์žˆ๋‹ค.)
์ฆ‰, ์™„์ „ํ•œ ๋ฒˆ์—ญ ํ”Œ๋žซํผ์ด ํ•„์š” ์—†๊ณ  ์ƒ๋Œ€์ ์œผ๋กœ ๊ธฐ๋ณธ์ ์ธ ๋ฌธ์ž์—ด๋งŒ ๋ฒˆ์—ญํ•  ์ˆ˜ ์žˆ์„ ๋•Œ ์ด๋Ÿฐ ํ•ด๊ฒฐ ๋ฐฉ์•ˆ์ด ๋„์›€์ด ๋œ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.
์ด ์˜ˆ๋Š” persisting the locale value in local storage๋กœ ํ™•์žฅํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ $t() property ๋“ฑ์„ ํ†ตํ•ด ๋ธŒ๋ผ์šฐ์ €์˜ ๊ธฐ๋ณธ ์–ธ์–ด๋กœ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.๊ทธ ์ž์ฒด๊ฐ€ ํ•˜๋‚˜์˜ ์ฃผ์ œ!

๐ŸŽฌ ์ง€๋Š๋Ÿฌ๋ฏธ


์‹ค์‹œ๊ฐ„ ํŽธ์ง‘ ํ™˜๊ฒฝ์—์„œ Svelte REPL ์˜ ๋ชจ๋“  ์ฝ”๋“œ๋ฅผ ๋ณด์‹ญ์‹œ์˜ค!๐Ÿค“
๋‚˜๋Š” ์ด ์˜ˆ๊ฐ€ ์šฐ๋ฆฌ์—๊ฒŒ ๋‚ ์”ฌํ•œ ๋ช‡ ๊ฐ€์ง€ ์žฌ๋ฏธ์žˆ๋Š” ํŠน์„ฑ์„ ๋ณด์—ฌ ์ฃผ์—ˆ๋‹ค๊ณ  ์ƒ๊ฐํ•œ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.
1๏ธโƒฃ ์–ด๋–ป๊ฒŒ ์•„์ฃผ ์ ์€ ์ฝ”๋“œ๋กœ ๊ธฐ๋Šฅ์„ฑ์„ ์‹คํ˜„ํ•˜์ง€๋งŒ ๊ธฐ๋ณธ์ ์ธ i18n์œผ๋กœ ์‹คํ˜„ํ•ฉ๋‹ˆ๊นŒ
2๏ธโƒฃ ๋ฐ˜ํ™˜ ํ•จ์ˆ˜ navigator.languages ์ €์žฅ์†Œ๋ฅผ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•ฉ๋‹ˆ๊นŒ
์…‹๏ธโƒฃ ๊ธ€๋กœ๋ฒŒ ์Šคํ† ๋ฆฌ์ง€๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ• ๋ฐ ๊ตฌ์„ฑ ์š”์†Œ์— ์ด๋Ÿฌํ•œ ๊ฐ’์„ ์„ค์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•
4๏ธโƒฃ ๋กœ์ผˆ๋ณ„ ๋‚ ์งœ ํ˜•์‹์„ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐฉ๋ฒ•derived๋‹น์‹ ์—๊ฒŒ ์•„์ฃผ ์žฌ๋ฏธ์žˆ์—ˆ์œผ๋ฉด ์ข‹๊ฒ ์Šต๋‹ˆ๋‹ค. ๋งˆํ‹ฐ์•„์Šค์˜ ์˜ค๋ฆฌ์ง€๋„ ๋Œ“๊ธ€์„ ์œ„ํ•ด ์†Œ๋ฆฌ ์ง€๋ฅด๋Š” ๊ฒƒ์„ ์žŠ์ง€ ๋งˆ์„ธ์š”!
์ฝ์–ด์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค!๊ณ ๋ฏผ ์ข€ ํ•ด๋ณผ๊ฒŒ์š”. ์ด ๋Œ“๊ธ€ ํ•˜๋‚˜๋งŒ ์ฃผ์„ธ์š”.โค๏ธ, ๐Ÿฆ„ ํ˜น์€๐Ÿ”– ๋‚˜์ค‘์— ์ฑ…๊ฐˆํ”ผ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.๐Ÿ’•
๋‹ค๋ฅธ ์ œ์•ˆ, ์•„์ด๋””์–ด, ํ”ผ๋“œ๋ฐฑ ๋˜๋Š” ์ˆ˜์ • ์‚ฌํ•ญ์ด ์žˆ์Šต๋‹ˆ๊นŒ?๋Œ“๊ธ€๋กœ ์•Œ๋ ค์ฃผ์„ธ์š”!๐Ÿ™‹โ€โ™‚๏ธ
Dev.to(), ํŠธ์œ„ํ„ฐ(),/๋˜๋Š” Githubdanawoodman์—์„œ ๋‚˜๋ฅผ ์ฃผ๋ชฉํ•˜๋Š” ๊ฒƒ์„ ์žŠ์ง€ ๋งˆ๋ผ!
์‚ฌ์ง„ ์ž‘์„ฑ์žJoshua Aragon๊ฐ€ Unsplash

์ข‹์€ ์›นํŽ˜์ด์ง€ ์ฆ๊ฒจ์ฐพ๊ธฐ