본문 바로가기
Flutter

Flutter에서 번역 기능 구현하기

by GODOLs 2025. 1. 24.

목차

    Flutter에서 번역 기능 구현하기

    1. 필요한 패키지 설치

    먼저 pubspec.yaml에 필요한 패키지들을 추가합니다:

    dependencies:
    
      translator: ^1.0.3  # Google Translate API 사용
    
      language\_detector: ^1.0.1  # 언어 감지
    
      freezed\_annotation: 2.4.4  # 상태 관리를 위한 freezed
    
    dev\_dependencies:
    
      build\_runner: ^2.4.12
    
      freezed: 2.4.4

    2. TranslatorService 구현

    번역 기능을 담당할 서비스 클래스를 만듭니다:

    import 'package:language\_detector/language\_detector.dart';
    
    import 'package:translator/translator.dart';
    
    import 'package:riverpod\_annotation/riverpod\_annotation.dart';
    
    @Riverpod(keepAlive: true)
    
    class TranslatorService extends \_$TranslatorService {
    
      @override
    
      String? build() => null;
    
      Future<String\> translate(String text) async {
    
        final translator = GoogleTranslator();
    
        final isSameLanguage = await detectSameLanguage(text);
    
        // 같은 언어면 번역하지 않음
    
        if (isSameLanguage) {
    
          return text;
    
        }
    
        // 사용자가 선택한 언어로 번역
    
        final translation = await translator.translate(text, to: 'ko');
    
        return translation.text;
    
      }
    
      Future<bool\> detectSameLanguage(String text) async {
    
        final languageCode = await LanguageDetector.getLanguageCode(content: text);
    
        return languageCode == 'ko'; // 한국어인 경우
    
      }
    
    }

    3. 상태 관리를 위한 Freezed 모델

    게시글의 상태를 관리하기 위한 모델을 정의합니다:

    @freezed
    
    class Post with \_$Post {
    
      const factory Post({
    
        required String title,
    
        required String titleTranslated,
    
        required String content,
    
        required String contentTranslated,
    
        required bool isSameLanguage,
    
        required bool isTranslated,
    
      }) = \_Post;
    
    }

    4. 번역 기능 구현

    class PostController extends \_$PostController {
    
      Future<void\> changePostLanguage(String postId) async {
    
        final post = state.value!.posts.firstWhere((post) => post.id == postId);
    
        // 이미 같은 언어면 번역하지 않음
    
        if (post.isSameLanguage) return;
    
        // 번역된 내용이 있으면 캐시된 내용 사용
    
        if (post.isTranslated) {
    
          final translatedPost = post.copyWith(
    
            titleTranslated: post.titleTranslated,
    
            contentTranslated: post.contentTranslated,
    
            isTranslated: true,
    
          );
    
          \_updatePost(postId, translatedPost);
    
          return;
    
        }
    
        // 새로운 번역 수행
    
        final translatedTitle = await \_translatorService.translate(post.title);
    
        final translatedContent = await \_translatorService.translate(post.content);
    
        final translatedPost = post.copyWith(
    
          titleTranslated: translatedTitle,
    
          contentTranslated: translatedContent,
    
          isTranslated: true,
    
        );
    
        \_updatePost(postId, translatedPost);
    
      }
    
      // 원본으로 복원
    
      Future<void\> restorePostLanguage(String postId) async {
    
        final post = state.value!.posts.firstWhere((post) => post.id == postId);
    
        final restoredPost = post.copyWith(
    
          titleTranslated: post.title,
    
          contentTranslated: post.content,
    
          isTranslated: false,
    
        );
    
        \_updatePost(postId, restoredPost);
    
      }
    
    }

    5. UI 구현

    class TranslatePostCard extends ConsumerWidget {
    
      final Post post;
    
      const TranslatePostCard({
    
        Key? key,
    
        required this.post,
    
      }) : super(key: key);
    
      @override
    
      Widget build(BuildContext context, WidgetRef ref) {
    
        return Card(
    
          margin: const EdgeInsets.all(16.0),
    
          child: Padding(
    
            padding: const EdgeInsets.all(16.0),
    
            child: Column(
    
              crossAxisAlignment: CrossAxisAlignment.start,
    
              children: \[
    
                // 제목
    
                Text(
    
                  post.isTranslated ? post.titleTranslated : post.title,
    
                  style: const TextStyle(
    
                    fontSize: 18,
    
                    fontWeight: FontWeight.bold,
    
                  ),
    
                ),
    
                const SizedBox(height: 8),
    
                // 본문
    
                Text(
    
                  post.isTranslated ? post.contentTranslated : post.content,
    
                  style: const TextStyle(fontSize: 16),
    
                ),
    
                const SizedBox(height: 16),
    
                // 번역 버튼
    
                if (!post.isSameLanguage) // 같은 언어가 아닐 때만 번역 버튼 표시
    
                  Row(
    
                    mainAxisAlignment: MainAxisAlignment.end,
    
                    children: \[
    
                      TranslateButton(
    
                        isTranslated: post.isTranslated,
    
                        onPressed: () {
    
                          if (post.isTranslated) {
    
                            ref.read(postControllerProvider.notifier)
    
                               .restorePostLanguage(post.id);
    
                          } else {
    
                            ref.read(postControllerProvider.notifier)
    
                               .changePostLanguage(post.id);
    
                          }
    
                        },
    
                      ),
    
                    \],
    
                  ),
    
              \],
    
            ),
    
          ),
    
        );
    
      }
    
    }
    
    // 번역 버튼 위젯
    class TranslateButton extends StatelessWidget {
    
      final bool isTranslated;
    
      final VoidCallback onPressed;
    
      const TranslateButton({
    
        Key? key,
    
        required this.isTranslated,
    
        required this.onPressed,
    
      }) : super(key: key);
    
      @override
    
      Widget build(BuildContext context) {
    
        return TextButton.icon(
    
          onPressed: onPressed,
    
          icon: Icon(
    
            isTranslated ? Icons.language : Icons.translate,
    
            size: 20,
    
            color: Colors.blue,
    
          ),
    
          label: Text(
    
            isTranslated ? '원본 보기' : '번역하기',
    
            style: const TextStyle(
    
              color: Colors.blue,
    
              fontSize: 14,
    
            ),
    
          ),
    
        );
    
      }
    
    }

    6. 원본 유지의 장점

    • 성능 최적화:
    • 한번 번역된 내용을 캐시로 저장해두면 반복적인 API 호출을 줄일 수 있습니다.
    • 사용자가 원본과 번역본을 전환할 때 즉각적인 응답이 가능합니다.
    • 사용자 경험:
    • 원본과 번역본을 빠르게 전환할 수 있어 더 나은 UX를 제공합니다.
    • 번역의 정확도를 사용자가 직접 확인할 수 있습니다.
    1. 데이터 무결성:
    • 원본 데이터가 보존되어 있어 번역 오류가 발생하더라도 복구가 가능합니다.
    • 중요한 정보가 번역 과정에서 손실되는 것을 방지합니다.
    • 비용 절감:
    • 불필요한 번역 API 호출을 줄여 비용을 절감할 수 있습니다.
    • 캐시된 번역 결과를 재사용하여 서버 부하를 줄일 수 있습니다.

    마무리

    이렇게 구현된 번역 기능은 사용자들에게 다음과 같은 이점을 제공합니다:

    • 직관적인 UI로 쉽게 번역 기능을 사용할 수 있습니다.
    • 원본과 번역본을 자유롭게 전환할 수 있습니다.
    • 빠른 응답 속도로 좋은 사용자 경험을 제공합니다.
    • 효율적인 리소스 관리로 안정적인 서비스가 가능합니다.

    Freezed를 활용한 상태 관리와 원본 데이터 보존 전략으로 안정적이고 효율적인 번역 기능을 구현할 수 있습니다.

    반응형