๐ Spring Boot Internationalization: ๋ค๊ตญ์ด ์ง์์ ๋ง๋ฒ ๐ช

์๋ ํ์ธ์, ์ฝ๋ฉ ๋ง๋ฒ์ฌ ์ฌ๋ฌ๋ถ! ์ค๋์ ์์ฃผ ํน๋ณํ ์ฃผ๋ฌธ์ ๋ฐฐ์๋ณผ ๊ฑฐ์์. ๋ฐ๋ก Spring Boot๋ฅผ ์ฌ์ฉํด ์ฐ๋ฆฌ์ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ค๊ตญ์ด ์ง์์ด๋ผ๋ ๋ง๋ฒ์ ๊ฑธ์ด๋ณด๋ ๊ฑฐ์ฃ ! ๐งโโ๏ธโจ
์ฌ๋ฌ๋ถ, ์์ํด ๋ณด์ธ์. ์ฌ๋ฌ๋ถ์ ์น์ฌ์ดํธ๊ฐ ์ ์ธ๊ณ ์ฌ๋๋ค๊ณผ ๋ํ๋ฅผ ๋๋ ์ ์๋ค๋ฉด ์ผ๋ง๋ ๋ฉ์ง๊น์? ํ๊ตญ์ด, ์์ด, ์ผ๋ณธ์ด, ์ค๊ตญ์ด... ๋ง์น ๋ฐ๋ฒจํ์ ์ฃผ๋ฌธ์ ์ธ์ด ๊ฒ์ฒ๋ผ ๋ชจ๋ ์ธ์ด๋ก ์ํตํ ์ ์๋ค๋ฉด ๋ง์ด์ฃ ! ๐ฒ
์ค๋ ์ฐ๋ฆฌ๊ฐ ๋ฐฐ์ธ Spring Boot Internationalization์ ๋ฐ๋ก ์ด๋ฐ ๋ง๋ฒ์ ํ์ค๋ก ๋ง๋ค์ด์ฃผ๋ ๊ฐ๋ ฅํ ๋๊ตฌ์ ๋๋ค. ์ด ๊ธฐ์ ์ ์ตํ๋ฉด, ์ฌ๋ฌ๋ถ์ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ง์น ์ธ๊ณ ์ฌํ์ ๋ค๋ ์จ ๊ฒ์ฒ๋ผ ๋ค์ํ ์ธ์ด๋ก ์ฌ์ฉ์๋ค๊ณผ ์ํตํ ์ ์๊ฒ ๋ ๊ฑฐ์์!
๐ ์ฌ๋ฅ๋ท ๊ฟํ: ๋ค๊ตญ์ด ์ง์์ ๊ธ๋ก๋ฒ ์์ฅ์ ๋ ธ๋ฆฌ๋ ์๋น์ค์ ํ์์ ์ ๋๋ค. ์ฌ๋ฅ๋ท์์๋ ์ด ๊ธฐ์ ์ ํ์ฉํด ์ ์ธ๊ณ์ ์ฌ๋ฅ ์๋ ๋ถ๋ค๊ณผ ์ํตํ๊ณ ์๋ต๋๋ค! ์ฌ๋ฌ๋ถ์ ์ฌ๋ฅ์ ์ธ๊ณ์ ๋๋๊ณ ์ถ๋ค๋ฉด, ์ด ๊ธฐ์ ์ ๊ผญ ์ตํ๋์ธ์!
์, ๊ทธ๋ผ ์ด์ Spring Boot Internationalization์ ์ธ๊ณ๋ก ๋น ์ ธ๋ณผ๊น์? ์ค๋น๋์ จ๋์? ๋ง๋ฒ์ ์ฃผ๋ฌธ์ ์ธ์น์ธ์! "Internationalization Incantatum!" ๐ชโจ
๐ Spring Boot Internationalization์ ๊ธฐ์ด ๋ง๋ฒ ๐งโโ๏ธ
์ฐ๋ฆฌ์ ์ฒซ ๋ฒ์งธ ๋ง๋ฒ ์์ ์ ์์ํด๋ณผ๊น์? Spring Boot Internationalization, ์ค์ฌ์ i18n(internationalization์ ์ฒซ ๊ธ์ i์ ๋ง์ง๋ง ๊ธ์ n ์ฌ์ด์ 18๊ฐ์ ๊ธ์๊ฐ ์๋ค๋ ๋ป์ด์์!)์ด๋ผ๊ณ ๋ถ๋ฅด๋ ์ด ๋ง๋ฒ์ ์ฐ๋ฆฌ์ ์ ํ๋ฆฌ์ผ์ด์ ์ด ์ฌ๋ฌ ์ธ์ด๋ฅผ ๋งํ ์ ์๊ฒ ํด์ฃผ๋ ๊ฐ๋ ฅํ ์ฃผ๋ฌธ์ด์์.
๐ฎ i18n์ ๊ธฐ๋ณธ ์๋ฆฌ
i18n์ ํต์ฌ์ ๋ฉ์์ง ์์ค(Message Source)๋ผ๋ ๋ง๋ฒ์ ์ฑ ์ ์์ด์. ์ด ์ฑ ์๋ ์ฐ๋ฆฌ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ชจ๋ ํ ์คํธ๊ฐ ์ฌ๋ฌ ์ธ์ด๋ก ๋ฒ์ญ๋์ด ์์ฃ . ์ฌ์ฉ์์ ์ธ์ด ์ค์ ์ ๋ฐ๋ผ ์ ์ ํ ๋ฒ์ญ์ ๊ณจ๋ผ ๋ณด์ฌ์ฃผ๋ ๊ฑฐ์์.
์๋ฅผ ๋ค์ด๋ณผ๊น์?
- ๐ฐ๐ท ํ๊ตญ์ด: "์๋ ํ์ธ์"
- ๐บ๐ธ ์์ด: "Hello"
- ๐ฏ๐ต ์ผ๋ณธ์ด: "ใใใซใกใฏ"
- ๐จ๐ณ ์ค๊ตญ์ด: "ไฝ ๅฅฝ"
์ด๋ ๊ฒ ๊ฐ ์ธ์ด๋ณ๋ก ๊ฐ์ ์๋ฏธ์ ๋ฌธ์ฅ์ ์ค๋นํด๋๊ณ , ์ฌ์ฉ์์ ์ค์ ์ ๋ฐ๋ผ ์๋ง์ ์ธ์ด๋ฅผ ๋ณด์ฌ์ฃผ๋ ๊ฑฐ์ฃ . ๋ง์น ๋ง๋ฒ์ฌ๊ฐ ์ํฉ์ ๋ง๋ ์ฃผ๋ฌธ์ ๊ณ ๋ฅด๋ ๊ฒ์ฒ๋ผ์! ๐งโโ๏ธโจ
๐ Spring Boot์์ i18n ์ค์ ํ๊ธฐ
์, ์ด์ ์ฐ๋ฆฌ์ Spring Boot ์ ํ๋ฆฌ์ผ์ด์ ์ ์ด ๋ง๋ฒ์ ๊ฑธ์ด๋ณผ๊น์? ๋จผ์ ํ์ํ ๋ง๋ฒ ๋๊ตฌ๋ค์ ์ค๋นํด์ผ ํด์.
- ์์กด์ฑ ์ถ๊ฐ: Spring Boot ์คํํฐ ์น์ ์ฌ์ฉํ๊ณ ์๋ค๋ฉด, ์ด๋ฏธ ํ์ํ ๋ง๋ฒ ๋๊ตฌ๋ค์ด ํฌํจ๋์ด ์์ด์.
- ๋ฉ์์ง ์์ค ํ์ผ ์์ฑ:
src/main/resources
ํด๋ ์๋์messages.properties
,messages_ko.properties
,messages_en.properties
๋ฑ์ ํ์ผ์ ๋ง๋ค์ด์. - ์ค์ ํ์ผ ์์ :
application.properties
๋๋application.yml
ํ์ผ์ ๋ฉ์์ง ์์ค ์ค์ ์ ์ถ๊ฐํด์.
์ด์ ๊ฐ๊ฐ์ ๋จ๊ณ๋ฅผ ์์ธํ ์ดํด๋ณผ๊น์?
1. ์์กด์ฑ ์ถ๊ฐ
Spring Boot ์คํํฐ ์น์ ์ฌ์ฉ ์ค์ด๋ผ๋ฉด, ๋ณ๋์ ์์กด์ฑ ์ถ๊ฐ๋ ํ์ ์์ด์. ํ์ง๋ง ํ์คํ ํ๊ณ ์ถ๋ค๋ฉด, pom.xml
ํ์ผ์ ๋ค์ ์์กด์ฑ์ด ์๋์ง ํ์ธํด๋ณด์ธ์:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2. ๋ฉ์์ง ์์ค ํ์ผ ์์ฑ
src/main/resources
ํด๋ ์๋์ ๋ค์๊ณผ ๊ฐ์ ํ์ผ๋ค์ ๋ง๋ค์ด์:
messages.properties
(๊ธฐ๋ณธ ๋ฉ์์ง)messages_ko.properties
(ํ๊ตญ์ด ๋ฉ์์ง)messages_en.properties
(์์ด ๋ฉ์์ง)messages_ja.properties
(์ผ๋ณธ์ด ๋ฉ์์ง)messages_zh.properties
(์ค๊ตญ์ด ๋ฉ์์ง)
๊ฐ ํ์ผ์๋ ํค-๊ฐ ์์ผ๋ก ๋ฉ์์ง๋ฅผ ์ ์ํด์. ์๋ฅผ ๋ค๋ฉด:
# messages.properties (๊ธฐ๋ณธ)
greeting=์๋
ํ์ธ์
# messages_en.properties
greeting=Hello
# messages_ja.properties
greeting=ใใใซใกใฏ
# messages_zh.properties
greeting=ไฝ ๅฅฝ
3. ์ค์ ํ์ผ ์์
application.properties
ํ์ผ์ ๋ค์ ์ค์ ์ ์ถ๊ฐํด์:
spring.messages.basename=messages
spring.messages.encoding=UTF-8
๋๋ application.yml
ํ์ผ์ ์ฌ์ฉํ๋ค๋ฉด:
spring:
messages:
basename: messages
encoding: UTF-8
์ด๋ ๊ฒ ํ๋ฉด Spring Boot๊ฐ ์ฐ๋ฆฌ๊ฐ ๋ง๋ ๋ฉ์์ง ํ์ผ๋ค์ ์ธ์ํ๊ณ ์ฌ์ฉํ ์ ์๊ฒ ๋ผ์.
๐ ์ฌ๋ฅ๋ท ๊ฟํ: ๋ฉ์์ง ํ์ผ์ ์์ฑํ ๋๋ ์ผ๊ด์ฑ ์๋ ํค ๋ค์ด๋ฐ ๊ท์น์ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข์์. ์๋ฅผ ๋ค์ด, page.home.title
, button.submit
๊ฐ์ ํ์์ผ๋ก ํค๋ฅผ ์ ์ํ๋ฉด ๋์ค์ ๊ด๋ฆฌํ๊ธฐ ํจ์ฌ ์ฌ์์ ธ์!
๐ญ Locale ๊ฒฐ์ ํ๊ธฐ
์ด์ ๋ฉ์์ง ์์ค๋ฅผ ์ค๋นํ์ผ๋, ์ฌ์ฉ์์ ์ธ์ด ์ค์ (Locale)์ ์ด๋ป๊ฒ ๊ฒฐ์ ํ ์ง ์ ํด์ผ ํด์. Spring Boot๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ช ๊ฐ์ง ๋ฐฉ๋ฒ์ ์ ๊ณตํด์:
- Accept-Language ํค๋: ๋ธ๋ผ์ฐ์ ์์ ๋ณด๋ด๋ ์ธ์ด ์ค์ ์ ์ฌ์ฉํด์.
- ์ธ์ : ์ฌ์ฉ์ ์ธ์ ์ ์ ์ฅ๋ ์ธ์ด ์ค์ ์ ์ฌ์ฉํด์.
- ์ฟ ํค: ์ฟ ํค์ ์ ์ฅ๋ ์ธ์ด ์ค์ ์ ์ฌ์ฉํด์.
- URL ํ๋ผ๋ฏธํฐ: URL์ ์ธ์ด ์ค์ ์ ํฌํจ์์ผ ์ฌ์ฉํด์.
๊ธฐ๋ณธ์ ์ผ๋ก Spring Boot๋ Accept-Language ํค๋๋ฅผ ์ฌ์ฉํ์ง๋ง, ์ฐ๋ฆฌ๋ ์ด๋ฅผ ์ปค์คํฐ๋ง์ด์ฆํ ์ ์์ด์. ์๋ฅผ ๋ค์ด, URL ํ๋ผ๋ฏธํฐ๋ก ์ธ์ด๋ฅผ ์ ํํ ์ ์๊ฒ ํ๊ณ ์ถ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ ์ค์ ์ ์ถ๊ฐํ ์ ์์ด์:
@Configuration
public class LocaleConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
localeChangeInterceptor.setParamName("lang");
registry.addInterceptor(localeChangeInterceptor);
}
@Bean
public LocaleResolver localeResolver() {
SessionLocaleResolver slr = new SessionLocaleResolver();
slr.setDefaultLocale(Locale.KOREAN);
return slr;
}
}
์ด ์ค์ ์ ์ฌ์ฉํ๋ฉด, URL์ ?lang=en
๊ณผ ๊ฐ์ ํ๋ผ๋ฏธํฐ๋ฅผ ์ถ๊ฐํด ์ธ์ด๋ฅผ ๋ณ๊ฒฝํ ์ ์์ด์.
๐งช ๋ฉ์์ง ์ฌ์ฉํ๊ธฐ
์ด์ ๋ชจ๋ ์ค๋น๊ฐ ๋๋ฌ์ด์! ์ฐ๋ฆฌ์ ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋ค๊ตญ์ด ๋ฉ์์ง๋ฅผ ์ฌ์ฉํด๋ณผ๊น์?
์ปจํธ๋กค๋ฌ์์ ๋ค์๊ณผ ๊ฐ์ด ๋ฉ์์ง๋ฅผ ๊ฐ์ ธ์ฌ ์ ์์ด์:
@Controller
public class HomeController {
@Autowired
private MessageSource messageSource;
@GetMapping("/")
public String home(Model model, Locale locale) {
String greeting = messageSource.getMessage("greeting", null, locale);
model.addAttribute("greeting", greeting);
return "home";
}
}
๊ทธ๋ฆฌ๊ณ Thymeleaf ํ ํ๋ฆฟ์์๋ ์ด๋ ๊ฒ ์ฌ์ฉํ ์ ์์ด์:
<h1 th:text="#{greeting}"></h1>
์ด๋ ๊ฒ ํ๋ฉด, ์ฌ์ฉ์์ ์ธ์ด ์ค์ ์ ๋ฐ๋ผ ์ ์ ํ ์ธ์ฌ๋ง์ด ํ์๋ ๊ฑฐ์์!
๐ ์ฌ๋ฅ๋ท ๊ฟํ: ๋ค๊ตญ์ด ์ง์์ ๋จ์ํ ํ
์คํธ๋ฅผ ๋ฒ์ญํ๋ ๊ฒ ์ด์์ด์์. ๋ ์ง ํ์, ์ซ์ ํ์, ํตํ ๋ฑ๋ ์ง์ญ์ ๋ฐ๋ผ ๋ค๋ฅด๊ฒ ํ์ํด์ผ ํด์. Spring์ MessageSource
์ ํจ๊ป java.text.NumberFormat
, java.text.DateFormat
๋ฑ์ ํ์ฉํ๋ฉด ๋์ฑ ์๋ฒฝํ ํ์งํ๋ฅผ ๊ตฌํํ ์ ์๋ต๋๋ค!
์ฌ๊ธฐ๊น์ง Spring Boot Internationalization์ ๊ธฐ์ด ๋ง๋ฒ์ ๋ฐฐ์๋ดค์ด์. ์ด์ ์ฌ๋ฌ๋ถ์ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ธ๊ณ ๊ฐ๊ตญ์ ์ธ์ด๋ก ์ฌ์ฉ์๋ค๊ณผ ๋ํํ ์ ์๊ฒ ๋์์ด์! ๐โจ
๋ค์ ์น์ ์์๋ ๋ ๊ณ ๊ธ ๋ง๋ฒ์ ๋ฐฐ์๋ณผ ๊ฑฐ์์. ์ค๋น๋์ จ๋์? ์ฐ๋ฆฌ์ ๋ง๋ฒ ์ฌํ์ ๊ณ์๋ฉ๋๋ค! ๐งโโ๏ธ๐
๐ Spring Boot Internationalization์ ๊ณ ๊ธ ๋ง๋ฒ ๐งโโ๏ธ
์, ์ด์ ์ฐ๋ฆฌ๋ ๊ธฐ๋ณธ์ ์ธ ๋ค๊ตญ์ด ์ง์ ๋ง๋ฒ์ ์ตํ์ด์. ํ์ง๋ง ์ง์ ํ ๋ง๋ฒ์ฌ๊ฐ ๋๋ ค๋ฉด ๋ ๊น์ด ์๋ ์ง์์ด ํ์ํ์ฃ . ์ด๋ฒ์๋ Spring Boot Internationalization์ ๊ณ ๊ธ ๋ง๋ฒ์ ๋ฐฐ์๋ณผ ๊ฑฐ์์. ์ค๋น๋์ จ๋์? ๋ง๋ฒ ์งํก์ด๋ฅผ ๊ผญ ์ฅ๊ณ , ์ฃผ๋ฌธ์ ์ธ์น ์ค๋น๋ฅผ ํ์ธ์! ๐ชโจ
๐ ๋์ ๋ฉ์์ง์ ํ๋ผ๋ฏธํฐ ์ฌ์ฉํ๊ธฐ
๋๋ก๋ ๋ฉ์์ง์ ๋์ ์ธ ๊ฐ์ ๋ฃ์ด์ผ ํ ๋๊ฐ ์์ด์. ์๋ฅผ ๋ค์ด, ์ฌ์ฉ์์ ์ด๋ฆ์ ๋ฃ์ด ์ธ์ฌํ๊ณ ์ถ๋ค๋ฉด ์ด๋ป๊ฒ ํด์ผ ํ ๊น์?
๋ฉ์์ง ํ์ผ์ ์ด๋ ๊ฒ ์ ์ํ ์ ์์ด์:
# messages.properties
greeting.name=์๋
ํ์ธ์, {0}๋!
# messages_en.properties
greeting.name=Hello, {0}!
๊ทธ๋ฆฌ๊ณ ์ปจํธ๋กค๋ฌ์์ ์ด๋ ๊ฒ ์ฌ์ฉํ ์ ์์ด์:
@GetMapping("/greet")
public String greet(@RequestParam String name, Model model, Locale locale) {
String greeting = messageSource.getMessage("greeting.name", new Object[]{name}, locale);
model.addAttribute("greeting", greeting);
return "greet";
}
์ด๋ ๊ฒ ํ๋ฉด, /greet?name=Alice
๋ก ์ ์ํ์ ๋ "์๋
ํ์ธ์, Alice๋!" ๋๋ "Hello, Alice!"๋ผ๊ณ ํ์๋ ๊ฑฐ์์.
โ ๏ธ ์ฃผ์: ๋์ ๋ฉ์์ง๋ฅผ ์ฌ์ฉํ ๋๋ ์ฌ์ฉ์ ์ ๋ ฅ์ ๊ทธ๋๋ก ์ฌ์ฉํ์ง ์๋๋ก ์ฃผ์ํด์ผ ํด์. XSS(Cross-Site Scripting) ๊ณต๊ฒฉ์ ๋ฐฉ์งํ๊ธฐ ์ํด ํญ์ ์ฌ์ฉ์ ์ ๋ ฅ์ ๊ฒ์ฆํ๊ณ ์ด์ค์ผ์ดํ ์ฒ๋ฆฌํด์ผ ํฉ๋๋ค!
๐ญ ๋ณต์ํ ์ฒ๋ฆฌํ๊ธฐ
์ธ์ด๋ง๋ค ๋ณต์ํ์ ์ฒ๋ฆฌํ๋ ๋ฐฉ์์ด ๋ค๋ฅด์ฃ . Spring์ ๋ฉ์์ง ์์ค๋ ์ด๋ฐ ๋ณต์กํ ๋ณต์ํ ์ฒ๋ฆฌ๋ ์ง์ํด์.
๋ฉ์์ง ํ์ผ์ ์ด๋ ๊ฒ ์ ์ํ ์ ์์ด์:
# messages.properties
items.count={0}๊ฐ์ ์์ดํ
์ด ์์ต๋๋ค.
items.count.zero=์์ดํ
์ด ์์ต๋๋ค.
items.count.one=1๊ฐ์ ์์ดํ
์ด ์์ต๋๋ค.
# messages_en.properties
items.count={0} items
items.count.zero=No items
items.count.one=One item
๊ทธ๋ฆฌ๊ณ ์ฝ๋์์ ์ด๋ ๊ฒ ์ฌ์ฉํ ์ ์์ด์:
@GetMapping("/items")
public String items(@RequestParam int count, Model model, Locale locale) {
String message = messageSource.getMessage("items.count", new Object[]{count}, locale);
model.addAttribute("message", message);
return "items";
}
์ด๋ ๊ฒ ํ๋ฉด ์์ดํ ์ ๊ฐ์์ ๋ฐ๋ผ ์ ์ ํ ๋ฉ์์ง๊ฐ ํ์๋ ๊ฑฐ์์.
๐ ๋ฆฌ์์ค ๋ฒ๋ค ๊ณ์ธต ๊ตฌ์กฐ
๋ฉ์์ง ๋ฆฌ์์ค๋ ๊ณ์ธต ๊ตฌ์กฐ๋ฅผ ๊ฐ์ง ์ ์์ด์. ์๋ฅผ ๋ค์ด, ๊ณตํต ๋ฉ์์ง๋ ๊ธฐ๋ณธ ํ์ผ์ ๋๊ณ , ํน์ ์ธ์ด์๋ง ํด๋นํ๋ ๋ฉ์์ง๋ ํด๋น ์ธ์ด ํ์ผ์ ๋ ์ ์์ฃ .
# messages.properties (๊ธฐ๋ณธ)
common.submit=์ ์ถ
common.cancel=์ทจ์
# messages_en.properties
common.submit=Submit
common.cancel=Cancel
# messages_ko_KR.properties
specific.korean.message=ํ๊ตญ์ด ํนํ ๋ฉ์์ง
์ด๋ ๊ฒ ํ๋ฉด ํ๊ตญ์ด ์ฌ์ฉ์๋ specific.korean.message
๋ฅผ ๋ณผ ์ ์์ง๋ง, ๋ค๋ฅธ ์ธ์ด ์ฌ์ฉ์๋ ์ด ๋ฉ์์ง๋ฅผ ๋ณผ ์ ์์ด์. ๋์ ๊ณตํต ๋ฉ์์ง์ธ common.submit
๊ณผ common.cancel
์ ๋ชจ๋ ์ธ์ด์์ ์ฌ์ฉํ ์ ์์ฃ .
๐ ๋ฐํ์์ ๋ฉ์์ง ๋ณ๊ฒฝํ๊ธฐ
๋๋ก๋ ์ ํ๋ฆฌ์ผ์ด์
์ ์ฌ์์ํ์ง ์๊ณ ๋ ๋ฉ์์ง๋ฅผ ๋ณ๊ฒฝํด์ผ ํ ๋๊ฐ ์์ด์. ์ด๋ด ๋๋ ReloadableResourceBundleMessageSource
๋ฅผ ์ฌ์ฉํ ์ ์์ด์.
@Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:messages");
messageSource.setDefaultEncoding("UTF-8");
messageSource.setCacheSeconds(10); // 10์ด๋ง๋ค ๋ฆฌ๋ก๋
return messageSource;
}
์ด๋ ๊ฒ ์ค์ ํ๋ฉด, ๋ฉ์์ง ํ์ผ์ ์์ ํ๊ณ 10์ด๋ง ๊ธฐ๋ค๋ฆฌ๋ฉด ๋ณ๊ฒฝ์ฌํญ์ด ์ ์ฉ๋ผ์. ๋ง์น ์ค์๊ฐ์ผ๋ก ์ฃผ๋ฌธ์ ๋ฐ๊พธ๋ ๊ฒ ๊ฐ์ฃ ? ๐งโโ๏ธโจ
๐ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ฌ์ฉํ ๋ฉ์์ง ๊ด๋ฆฌ
๋ฉ์์ง๊ฐ ๋๋ฌด ๋ง์์ง๋ฉด ํ์ผ๋ก ๊ด๋ฆฌํ๊ธฐ ์ด๋ ค์ธ ์ ์์ด์. ์ด๋ด ๋๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ฌ์ฉํด ๋ฉ์์ง๋ฅผ ๊ด๋ฆฌํ ์ ์์ด์.
๋จผ์ , ๋ฉ์์ง๋ฅผ ์ ์ฅํ ํ ์ด๋ธ์ ๋ง๋ค์ด์:
CREATE TABLE messages (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
locale VARCHAR(10),
code VARCHAR(255),
message TEXT
);
๊ทธ๋ฆฌ๊ณ ์ด ํ
์ด๋ธ์์ ๋ฉ์์ง๋ฅผ ๊ฐ์ ธ์ค๋ ์ปค์คํ
MessageSource
๋ฅผ ๋ง๋ค์ด์:
@Component
public class DatabaseMessageSource extends AbstractMessageSource {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
protected MessageFormat resolveCode(String code, Locale locale) {
String message = jdbcTemplate.queryForObject(
"SELECT message FROM messages WHERE code = ? AND locale = ?",
new Object[]{code, locale.toString()},
String.class
);
return new MessageFormat(message, locale);
}
}
์ด๋ ๊ฒ ํ๋ฉด ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ๋ฉ์์ง๋ฅผ ๊ด๋ฆฌํ ์ ์์ด์. ๊ด๋ฆฌ์ ํ์ด์ง๋ฅผ ๋ง๋ค์ด ์ค์๊ฐ์ผ๋ก ๋ฉ์์ง๋ฅผ ์ถ๊ฐํ๊ฑฐ๋ ์์ ํ ์๋ ์์ฃ !
๐ ์ฌ๋ฅ๋ท ๊ฟํ: ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ฌ์ฉํด ๋ฉ์์ง๋ฅผ ๊ด๋ฆฌํ๋ฉด, ๋ฒ์ญ๊ฐ๋ค์ด ์ง์ ๊ด๋ฆฌ์ ํ์ด์ง์์ ๋ฒ์ญ์ ์์ ํ ์ ์์ด์. ์ด๋ ๊ฒ ํ๋ฉด ๊ฐ๋ฐ์๊ฐ ๋งค๋ฒ ํ์ผ์ ์์ ํ๊ณ ๋ฐฐํฌํ ํ์ ์์ด ์ค์๊ฐ์ผ๋ก ๋ฒ์ญ์ ์ ๋ฐ์ดํธํ ์ ์๋ต๋๋ค!
๐จ ๋ค๊ตญ์ด ์ง์์ ์ํ UI/UX ๊ณ ๋ ค์ฌํญ
๋ค๊ตญ์ด ์ง์์ ๋จ์ํ ํ ์คํธ๋ฅผ ๋ฒ์ญํ๋ ๊ฒ ์ด์์ด์์. UI/UX ์ธก๋ฉด์์๋ ๋ช ๊ฐ์ง ๊ณ ๋ คํด์ผ ํ ์ ์ด ์์ฃ .
- ์ธ์ด ์ ํ UI: ์ฌ์ฉ์๊ฐ ์ฝ๊ฒ ์ธ์ด๋ฅผ ๋ณ๊ฒฝํ ์ ์๋๋ก ์ธ์ด ์ ํ ๋๋กญ๋ค์ด์ด๋ ํ๋๊ทธ ์์ด์ฝ์ ์ ๊ณตํ์ธ์.
- ํ ์คํธ ๊ธธ์ด ๊ณ ๋ ค: ๋ฒ์ญ๋ ํ ์คํธ์ ๊ธธ์ด๊ฐ ์๋ณธ๊ณผ ๋ค๋ฅผ ์ ์์ด์. UI๊ฐ ๊นจ์ง์ง ์๋๋ก ์ ์ฐํ ๋ ์ด์์์ ์ฌ์ฉํ์ธ์.
- ๋ ์ง์ ์๊ฐ ํ์: ๋ ์ง์ ์๊ฐ ํ์ ํ์๋ ์ง์ญ์ ๋ฐ๋ผ ๋ค๋ฆ
๋๋ค. Java์
DateTimeFormatter
๋ฅผ ํ์ฉํ์ธ์. - ์ซ์์ ํตํ ํ์: ์ซ์์ ํตํ ํ์ ๋ฐฉ์๋ ์ง์ญ๋ง๋ค ๋ค๋ฆ
๋๋ค.
NumberFormat
์ ์ฌ์ฉํด ์ ์ ํ ํฌ๋งทํ ํ์ธ์. - ์ด๋ฏธ์ง์ ์์ด์ฝ: ๋ฌธํ์ ์ฐจ์ด๋ฅผ ๊ณ ๋ คํด ์ ์ ํ ์ด๋ฏธ์ง์ ์์ด์ฝ์ ์ฌ์ฉํ์ธ์.
๐งช ๋ค๊ตญ์ด ์ง์ ํ ์คํธํ๊ธฐ
๋ค๊ตญ์ด ์ง์์ด ์ ๋๋ก ๋์ํ๋์ง ํ์ธํ๊ธฐ ์ํด ํ ์คํธ๋ฅผ ์์ฑํ๋ ๊ฒ๋ ์ค์ํด์. Spring Boot์์๋ ์ด๋ ๊ฒ ํ ์คํธ๋ฅผ ์์ฑํ ์ ์์ด์:
@SpringBootTest
@AutoConfigureMockMvc
public class InternationalizationTest {
@Autowired
private MockMvc mockMvc;
@Test
public void testGreetingInKorean() throws Exception {
mockMvc.perform(get("/greet?name=Alice")
.header("Accept-Language", "ko-KR"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("์๋
ํ์ธ์, Alice๋!")));
}
@Test
public void testGreetingInEnglish() throws Exception {
mockMvc.perform(get("/greet?name=Alice")
.header("Accept-Language", "en-US"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("Hello, Alice!")));
}
}
์ด๋ฐ ํ ์คํธ๋ฅผ ํตํด ๊ฐ ์ธ์ด๋ณ๋ก ๋ฉ์์ง๊ฐ ์ ๋๋ก ํ์๋๋์ง ํ์ธํ ์ ์์ด์.
๐ ๊ตญ์ ํ์ ์ง์ญํ์ ์ฐจ์ด
๋ง์ง๋ง์ผ๋ก, ๊ตญ์ ํ(Internationalization, i18n)์ ์ง์ญํ(Localization, l10n)์ ์ฐจ์ด์ ๋ํด ์์๋ณผ๊น์?
- ๊ตญ์ ํ(i18n): ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ค์ํ ์ธ์ด์ ์ง์ญ์์ ์ฌ์ฉํ ์ ์๋๋ก ์ค๊ณํ๊ณ ๊ฐ๋ฐํ๋ ๊ณผ์ ์ด์์. ์ฐ๋ฆฌ๊ฐ ์ง๊ธ๊น์ง ๋ฐฐ์ด Spring Boot์ ๋ค๊ตญ์ด ์ง์ ๊ธฐ๋ฅ์ด ๋ฐ๋ก ์ด ๊ตญ์ ํ์ ํด๋นํด์.
- ์ง์ญํ(l10n): ๊ตญ์ ํ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ํน์ ์ธ์ด๋ ์ง์ญ์ ๋ง๊ฒ ์กฐ์ ํ๋ ๊ณผ์ ์ด์์. ํ ์คํธ ๋ฒ์ญ, ๋ ์ง/์๊ฐ ํ์ ์กฐ์ , ํตํ ๋จ์ ๋ณ๊ฒฝ ๋ฑ์ด ์ฌ๊ธฐ์ ํฌํจ๋ผ์.
์ฆ, ๊ตญ์ ํ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฌ๋ฌ ์ธ์ด๋ก ์ฌ์ฉํ ์ ์๊ฒ ๋ง๋๋ ๊ธฐ์ ์ ์ธ ๊ณผ์ ์ด๊ณ , ์ง์ญํ๋ ์ค์ ๋ก ๊ฐ ์ธ์ด์ ๋ฌธํ์ ๋ง๊ฒ ์ฝํ ์ธ ๋ฅผ ์กฐ์ ํ๋ ๊ณผ์ ์ด๋ผ๊ณ ํ ์ ์์ด์.
๐ ์ฌ๋ฅ๋ท ๊ฟํ: ์๋ฒฝํ ๋ค๊ตญ์ด ์ง์์ ์ํด์๋ ๊ตญ์ ํ์ ์ง์ญํ ๋ชจ๋๊ฐ ์ค์ํด์. ๊ธฐ์ ์ ์ผ๋ก ๋ค๊ตญ์ด๋ฅผ ์ง์ํ๋ ๊ฒ๋ฟ๋ง ์๋๋ผ, ๊ฐ ์ง์ญ์ ๋ฌธํ์ ๊ด์ต์ ์ดํดํ๊ณ ๊ทธ์ ๋ง๊ฒ ์ฝํ ์ธ ๋ฅผ ์กฐ์ ํ๋ ๊ฒ๋ ์์ง ๋ง์ธ์!
์ฌ๊ธฐ๊น์ง Spring Boot Internationalization์ ๊ณ ๊ธ ๋ง๋ฒ์ ๋ฐฐ์๋ดค์ด์. ์ด์ ์ฌ๋ฌ๋ถ์ ์ง์ ํ ๊ตญ์ ํ ๋ง๋ฒ์ฌ๊ฐ ๋์์ด์! ๐งโโ๏ธโจ ์ด ๋ง๋ฒ์ ํ์ฉํด ์ฌ๋ฌ๋ถ์ ์ ํ๋ฆฌ์ผ์ด์ ์ด ์ ์ธ๊ณ ์ฌ์ฉ์๋ค๊ณผ ์ํตํ ์ ์๊ธฐ๋ฅผ ๋ฐ๋ผ์. ๋ค์ ์น์ ์์๋ ์ค์ ํ๋ก์ ํธ์ ์ด ๋ง๋ฒ์ ์ ์ฉํ๋ ๋ฐฉ๋ฒ์ ์์ธํ ์์๋ณผ ๊ฑฐ์์. ์ค๋น๋์ จ๋์? ์ฐ๋ฆฌ์ ๋ง๋ฒ ์ฌํ์ ๊ณ์๋ฉ๋๋ค! ๐๐
๐๏ธ Spring Boot Internationalization ์ค์ ํ๋ก์ ํธ ์ ์ฉํ๊ธฐ ๐ ๏ธ
์, ์ด์ ์ฐ๋ฆฌ๋ Spring Boot Internationalization์ ๊ธฐ๋ณธ๋ถํฐ ๊ณ ๊ธ ๋ง๋ฒ๊น์ง ๋ชจ๋ ๋ฐฐ์ ์ด์. ์ด์ ์ด ์ง์์ ์ค์ ํ๋ก์ ํธ์ ์ ์ฉํด๋ณผ ์๊ฐ์ด์์! ๋ง๋ฒ ์งํก์ด๋ฅผ ๊ผญ ์ฅ๊ณ , ์ฐ๋ฆฌ์ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ธ๊ณ์ ์ธ ์๋น์ค๋ก ๋ง๋ค์ด๋ณผ๊น์? ๐โจ
๐จ ๋ค๊ตญ์ด ์ง์ ์ผํ๋ชฐ ํ๋ก์ ํธ ์์ํ๊ธฐ
์ฐ๋ฆฌ๋ "์ฌ๋ฅ๋ท ๊ธ๋ก๋ฒ ๋ง์ผ"์ด๋ผ๋ ์ด๋ฆ์ ๋ค๊ตญ์ด ์ง์ ์ผํ๋ชฐ์ ๋ง๋ค์ด๋ณผ ๊ฑฐ์์. ์ด ์ผํ๋ชฐ์ ์ ์ธ๊ณ์ ๋ค์ํ ์ฌ๋ฅ์ ์ฌ๊ณ ํ ์ ์๋ ํ๋ซํผ์ด ๋ ๊ฑฐ์์. ์ด๋ค ๊ธฐ๋ฅ๋ค์ด ํ์ํ ๊น์?
- ๋ค๊ตญ์ด ์ง์ (ํ๊ตญ์ด, ์์ด, ์ผ๋ณธ์ด, ์ค๊ตญ์ด)
- ์ํ ๋ชฉ๋ก ๋ฐ ์์ธ ํ์ด์ง
- ์ฅ๋ฐ๊ตฌ๋ ๊ธฐ๋ฅ
- ๊ฒฐ์ ์์คํ
- ์ฌ์ฉ์ ํ๋กํ
์ด ๊ธฐ๋ฅ๋ค์ ๊ตฌํํ๋ฉด์ ๋ค๊ตญ์ด ์ง์์ ์ด๋ป๊ฒ ์ ์ฉํ ์ ์๋์ง ์ดํด๋ณผ๊ฒ์.
๐ ๏ธ ํ๋ก์ ํธ ์ค์
๋จผ์ Spring Boot ํ๋ก์ ํธ๋ฅผ ์ค์ ํด๋ณผ๊น์?
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
๊ทธ๋ฆฌ๊ณ application.properties
ํ์ผ์ ๋ค์ ์ค์ ์ ์ถ๊ฐํด์:
spring.messages.basename=i18n/messages
spring.messages.encoding=UTF-8
๐ ๋ฉ์์ง ๋ฆฌ์์ค ํ์ผ ๋ง๋ค๊ธฐ
src/main/resources/i18n
ํด๋๋ฅผ ๋ง๋ค๊ณ , ๊ทธ ์์ ๋ค์ ํ์ผ๋ค์ ๋ง๋ค์ด์:
messages.properties
(๊ธฐ๋ณธ ๋ฉ์์ง)messages_ko.properties
(ํ๊ตญ์ด)messages_en.properties
(์์ด)messages_ja.properties
(์ผ๋ณธ์ด)messages_zh.properties
(์ค๊ตญ์ด)
๊ฐ ํ์ผ์ ๋ค์๊ณผ ๊ฐ์ด ๋ฉ์์ง๋ฅผ ์ถ๊ฐํด๋ณผ๊น์?
# messages.properties
home.welcome=์ฌ๋ฅ๋ท ๊ธ๋ก๋ฒ ๋ง์ผ์ ์ค์ ๊ฒ์ ํ์ํฉ๋๋ค!
nav.home=ํ
nav.products=์ํ
nav.cart=์ฅ๋ฐ๊ตฌ๋
nav.profile=ํ๋กํ
# messages_en.properties
home.welcome=Welcome to TalentNet Global Market!
nav.home=Home
nav.products=Products
nav.cart=Cart
nav.profile=Profile
# (๋ค๋ฅธ ์ธ์ด ํ์ผ๋ ๋น์ทํ๊ฒ ์์ฑ)
๐ ํ ์ปจํธ๋กค๋ฌ ๋ง๋ค๊ธฐ
์ด์ ํ ํ์ด์ง๋ฅผ ์ํ ์ปจํธ๋กค๋ฌ๋ฅผ ๋ง๋ค์ด๋ณผ๊ฒ์:
@Controller
public class HomeController {
@GetMapping("/")
public String home() {
return "home";
}
}
๐จ Thymeleaf ํ ํ๋ฆฟ ๋ง๋ค๊ธฐ
src/main/resources/templates/home.html
ํ์ผ์ ๋ง๋ค๊ณ ๋ค์ ๋ด์ฉ์ ์ถ๊ฐํด์:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>์ฌ๋ฅ๋ท ๊ธ๋ก๋ฒ ๋ง์ผ</title>
</head>
<body>
<h1 th:text="#{home.welcome}">ํ์ํฉ๋๋ค!</h1>
<nav>
<ul>
<li><a href="#" th:text="#{nav.home}">ํ</a></li>
<li><a href="#" th:text="#{nav.products}">์ํ</a></li>
<li><a href="#" th:text="#{nav.cart}">์ฅ๋ฐ๊ตฌ๋</a></li>
<li><a href="#" th:text="#{nav.profile}">ํ๋กํ</a></li>
</ul>
</nav>
</body>
</html>
๐ ์ธ์ด ๋ณ๊ฒฝ ๊ธฐ๋ฅ ์ถ๊ฐํ๊ธฐ
์ฌ์ฉ์๊ฐ ์ธ์ด๋ฅผ ๋ณ๊ฒฝํ ์ ์๋๋ก ๊ธฐ๋ฅ์ ์ถ๊ฐํด๋ณผ๊น์?
๋จผ์ , LocaleChangeInterceptor
๋ฅผ ์ค์ ํด์:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
lci.setParamName("lang");
return lci;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeChangeInterceptor());
}
@Bean
public LocaleResolver localeResolver() {
SessionLocaleResolver slr = new SessionLocaleResolver();
slr.setDefaultLocale(Locale.KOREAN);
return slr;
}
}
๊ทธ๋ฆฌ๊ณ ํ ํ์ด์ง์ ์ธ์ด ์ ํ ๋งํฌ๋ฅผ ์ถ๊ฐํด์:
<div>
<a href="?lang=ko">ํ๊ตญ์ด</a>
<a href="?lang=en">English</a>
<a href="?lang=ja">ๆฅๆฌ่ช</a>
<a href="?lang=zh">ไธญๆ</a>
</div>
๐๏ธ ์ํ ๋ชฉ๋ก ํ์ด์ง ๋ง๋ค๊ธฐ
์ด์ ์ํ ๋ชฉ๋ก ํ์ด์ง๋ฅผ ๋ง๋ค์ด๋ณผ๊น์? ๋จผ์ ์ํ ๋ชจ๋ธ์ ๋ง๋ค์ด์:
public class Product {
private Long id;
private String nameKey;
private String descriptionKey;
private BigDecimal price;
// ์์ฑ์, getter, setter ์๋ต
}
์ํ ์ปจํธ๋กค๋ฌ๋ฅผ ๋ง๋ค์ด์:
@Controller
@RequestMapping("/products")
public class ProductController {
@Autowired
private MessageSource messageSource;
@GetMapping
public String listProducts(Model model) {
List<Product> products = Arrays.asList(
new Product(1L, "product.name.1", "product.desc.1", new BigDecimal("100.00")),
new Product(2L, "product.name.2", "product.desc.2", new BigDecimal("200.00"))
);
model.addAttribute("products", products);
return "products";
}
}
๊ทธ๋ฆฌ๊ณ products.html
ํ
ํ๋ฆฟ์ ๋ง๋ค์ด์:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>์ํ ๋ชฉ๋ก</title>
</head>
<body>
<h1 th:text="#{products.title}">์ํ ๋ชฉ๋ก</h1>
<ul>
<li th:each="product : ${products}">
<span th:text="#{${product.nameKey}}">์ํ ์ด๋ฆ</span>
<p th:text="#{${product.descriptionKey}}">์ํ ์ค๋ช
</p>
<span th:text="${#numbers.formatCurrency(product.price)}">๊ฐ๊ฒฉ</span>
</li>
</ul>
</body>
</html>
๋ง์ง๋ง์ผ๋ก ๋ฉ์์ง ํ์ผ์ ์ํ ์ ๋ณด๋ฅผ ์ถ๊ฐํด์:
# messages.properties
products.title=์ํ ๋ชฉ๋ก
product.name.1=๋ฉ์ง ์ฌ๋ฅ
product.desc.1=์ด ์ฌ๋ฅ์ผ๋ก ๋น์ ์ ์ถ์ ๋ณํ์ํค์ธ์!
product.name.2=๋๋ผ์ด ๊ธฐ์
product.desc.2=์ด ๊ธฐ์ ๋ก ์๋ก์ด ์ธ๊ณ๋ฅผ ์ด์ด๋ณด์ธ์!
# messages_en.properties
products.title=Product List
product.name.1=Amazing Talent
product.desc.1=Transform your life with this talent!
product.name.2=Incredible Skill
product.desc.2=Open up a new world with this skill!
# (๋ค๋ฅธ ์ธ์ด ํ์ผ๋ ๋น์ทํ๊ฒ ์์ฑ)
๐ ์ฅ๋ฐ๊ตฌ๋ ๊ธฐ๋ฅ ์ถ๊ฐํ๊ธฐ
์ฅ๋ฐ๊ตฌ๋ ๊ธฐ๋ฅ์ ๊ฐ๋จํ ๊ตฌํํด๋ณผ๊น์?
๋จผ์ ์ฅ๋ฐ๊ตฌ๋ ์๋น์ค๋ฅผ ๋ง๋ค์ด์:
@Service
@SessionScope
public class CartService {
private List<Product> items = new ArrayList<>();
public void addItem(Product product) {
items.add(product);
}
public List<Product> getItems() {
return items;
}
}
๊ทธ๋ฆฌ๊ณ ์ฅ๋ฐ๊ตฌ๋ ์ปจํธ๋กค๋ฌ๋ฅผ ๋ง๋ค์ด์:
@Controller
@RequestMapping("/cart")
public class CartController {
@Autowired
private CartService cartService;
@GetMapping
public String viewCart(Model model) {
model.addAttribute("items", cartService.getItems());
return "cart";
}
@PostMapping("/add")
public String addToCart(@RequestParam Long productId) {
// ์ค์ ๋ก๋ ์ฌ๊ธฐ์ ์ํ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์ฐพ์์ผ ํฉ๋๋ค.
Product product = new Product(productId, "product.name." + productId, "product.desc." + productId, new BigDecimal("100.00"));
cartService.addItem(product);
return "redirect:/cart";
}
}
์ฅ๋ฐ๊ตฌ๋ ํ์ด์ง ํ
ํ๋ฆฟ(cart.html
)์ ๋ง๋ค์ด์:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title th:text="#{cart.title}">์ฅ๋ฐ๊ตฌ๋</title>
</head>
<body>
<h1 th:text="#{cart.title}">์ฅ๋ฐ๊ตฌ๋</h1>
<ul>
<li th:each="item : ${items}">
<span th:text="#{${item.nameKey}}">์ํ ์ด๋ฆ</span>
<span th:text="${#numbers.formatCurrency(item.price)}">๊ฐ๊ฒฉ</span>
</li>
</ul>
<p th:text="#{cart.total(${#numbers.formatCurrency(#aggregates.sum(items.![price]))})}">์ด์ก: $300.00</p>
</body>
</html>
๋ง์ง๋ง์ผ๋ก ๋ฉ์์ง ํ์ผ์ ์ฅ๋ฐ๊ตฌ๋ ๊ด๋ จ ๋ฉ์์ง๋ฅผ ์ถ๊ฐํด์:
# messages.properties
cart.title=์ฅ๋ฐ๊ตฌ๋
cart.total=์ด์ก: {0}
# messages_en.properties
cart.title=Shopping Cart
cart.total=Total: {0}
# (๋ค๋ฅธ ์ธ์ด ํ์ผ๋ ๋น์ทํ๊ฒ ์์ฑ)
๐ ๋ง๋ฌด๋ฆฌ
์ฌ๊ธฐ๊น์ง Spring Boot๋ฅผ ์ฌ์ฉํด ๋ค๊ตญ์ด๋ฅผ ์ง์ํ๋ ๊ฐ๋จํ ์ผํ๋ชฐ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ง๋ค์ด๋ดค์ด์. ์ด ์์ ๋ฅผ ํตํด ์ฐ๋ฆฌ๋ ๋ค์๊ณผ ๊ฐ์ ๊ฒ๋ค์ ๋ฐฐ์ ์ฃ :
- ๋ฉ์์ง ์์ค๋ฅผ ์ฌ์ฉํ ๋ค๊ตญ์ด ํ ์คํธ ๊ด๋ฆฌ
- Thymeleaf ํ ํ๋ฆฟ์์ ๋ค๊ตญ์ด ๋ฉ์์ง ์ฌ์ฉ
- ๋์ ์ฝํ ์ธ (์ํ ์ ๋ณด)์ ๋ค๊ตญ์ด ์ฒ๋ฆฌ
- ์ฌ์ฉ์๊ฐ ์ธ์ด๋ฅผ ์ ํํ ์ ์๋ ๊ธฐ๋ฅ ๊ตฌํ
- ์ซ์์ ํตํ์ ์ง์ญํ๋ ํฌ๋งทํ
์ด ํ๋ก์ ํธ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋ ๋ง์ ๊ธฐ๋ฅ์ ์ถ๊ฐํ๊ณ ํ์ฅํ ์ ์์ด์. ์๋ฅผ ๋ค์ด:
- ์ฌ์ฉ์ ์ธ์ฆ ๋ฐ ํ๋กํ ๊ด๋ฆฌ
- ์ค์ ๊ฒฐ์ ์์คํ ์ฐ๋
- ์ํ ๋ฆฌ๋ทฐ ๋ฐ ํ์ ์์คํ
- ๊ด๋ฆฌ์ ํ์ด์ง๋ฅผ ํตํ ๋์ ๋ค๊ตญ์ด ์ฝํ ์ธ ๊ด๋ฆฌ
๐ ์ฌ๋ฅ๋ท ๊ฟํ: ์ค์ ํ๋ก์ ํธ์์๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ฌ์ฉํด ์ํ ์ ๋ณด์ ๋ค๊ตญ์ด ํ ์คํธ๋ฅผ ๊ด๋ฆฌํ๋ ๊ฒ์ด ์ข์์. ๋ํ, ๋ฒ์ญ ๊ด๋ฆฌ ๋๊ตฌ๋ฅผ ์ฌ์ฉํ๋ฉด ๋ฒ์ญ ํ๋ก์ธ์ค๋ฅผ ๋์ฑ ํจ์จ์ ์ผ๋ก ๊ด๋ฆฌํ ์ ์๋ต๋๋ค!
์ด์ ์ฌ๋ฌ๋ถ์ Spring Boot๋ฅผ ์ฌ์ฉํด ๋ค๊ตญ์ด๋ฅผ ์ง์ํ๋ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ง๋ค ์ ์๋ ๋ง๋ฒ์ฌ๊ฐ ๋์์ด์! ๐งโโ๏ธโจ ์ด ์ง์์ ํ์ฉํด ์ฌ๋ฌ๋ถ๋ง์ ๊ธ๋ก๋ฒ ์๋น์ค๋ฅผ ๋ง๋ค์ด๋ณด์ธ์. ์ ์ธ๊ณ ์ฌ์ฉ์๋ค๊ณผ ์ํตํ๋ ์ฌ๋ฌ๋ถ์ ์ ํ๋ฆฌ์ผ์ด์ ์ ์์ํด๋ณด์ธ์. ๋ฉ์ง์ง ์๋์? ๐๐
๋ค๊ตญ์ด ์ง์์ ๋จ์ํ ๊ธฐ์ ์ ์ธ ๋์ ์ด ์๋๋ผ, ๋ฌธํ์ ์ดํด์ ์ธ์ฌํ ๋ฐฐ๋ ค๊ฐ ํ์ํ ์์ ์ด์์. ๊ฐ ์ธ์ด์ ๋ฌธํ์ ํน์ฑ์ ์ดํดํ๊ณ , ์ฌ์ฉ์๋ค์๊ฒ ์น๊ทผํ๊ณ ์์ฐ์ค๋ฌ์ด ๊ฒฝํ์ ์ ๊ณตํ๋ ๊ฒ์ด ์ค์ํด์. ์ฌ๋ฌ๋ถ์ ์๋น์ค๊ฐ ์ ์ธ๊ณ ์ฌ์ฉ์๋ค์ ๋ง์์ ์ฌ๋ก์ก์ ์ ์๊ธฐ๋ฅผ ๋ฐ๋ผ์! ํ์ดํ ! ๐ช๐
- ์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์ฌ๋ฅ๋ท์ ์ต์ AI ๊ธฐ์ ๊ณผ ๋ฒ๋ฅ ์ ๊ธฐ๋ฐํ์ฌ ์์ฌ์ ์ง์ ์ฌ์ฐ๊ถ์ ์ ๊ทน์ ์ผ๋ก ๋ณดํธํ๋ฉฐ,
๋ฌด๋จ ์ฌ์ฉ ๋ฐ ์นจํด ํ์์ ๋ํด ๋ฒ์ ๋์์ ํ ๊ถ๋ฆฌ๋ฅผ ๋ณด์ ํฉ๋๋ค.
ยฉ 2025 ์ฌ๋ฅ๋ท | All rights reserved.
๋๊ธ 0๊ฐ