এপ্লিকেশনে লোকালাইজেশন এর মডেল

i18nlocalizationmulti-language


যদি আপনি এমন একটা এপ্লিকেশন বানাতে চান যেটা ভিন্ন ভাষার মানুষজন ব্যবহার করতে পারবে, তাহলে দুটা কাজ করার আছে


১. লেআউট ট্রান্সলেশন করা

২. ডাটা ট্রান্সলেশন করা



লেআউট ট্রান্সলেশন করা


লেআউট ট্রান্সলেট করা হচ্ছে সবচেয়ে কমন, সহজ এবং এই কাজ প্রায় সবাই করে থাকে। এখানে মূল আইডিয়া হলো, যে সব লেখা ফিক্সড বা ছয় মাস এক বছরে পরিবর্তন করা লাগে, তাদের কন্টেন্ট লোকাইলাইজেশন ফাইল থেকে পড়া। লেআউটে একটা প্লেস হোল্ডার থাকে। যে ভাষায় সাইট দেখতে চাই, সে ভাষার লেআউট চলে আসে। মোটামুটি সব প্রোগ্রামিং ভাষা এবং ফ্রেমওয়ার্কে এই সুবিধা দেয়া থাকে। উদাহরণ: সাইটের ফুটার বা কন্টাক্ট পেজ, বাটনের নাম, মেনু ইত্যাদি। এই ধরণের ট্রান্সলেশন এই লেখার বিষয় না।



ডাটা ট্রান্সলেশন করা


ডাটা ট্রান্সলেশন আর লেআউট ট্রান্সলেশন এক জিনিস না। আপনি নিচের লিংকে ক্লিক করে ভাষা পরিবর্তন করে দেখেন। https://www.pickaboo.com/apple-iphone-xr-64gb-with-free-anker-power-port-wireless-10w-charger.html?___store=bn&___from_store=default শুধু লেআউট পরিবর্তন হয়, কিন্তু প্রোডাক্টের নাম বা বর্ণনার কোন পরিবর্তন নাই। আবার নিচের লিংকে যান, ভাষা পরিবর্তন করে দেখেন, প্রোডাক্টের নাম পরিবর্তন হয়ে গেছে। https://www.zalando.de/seafolly-boyfriend-beach-strandaccessoire-white-s1981d08u-a11.html?_rfl=en তাহলে কিভাবে ডাটাকে রাখা যায় যেন ভিন্ন ভিন্ন ভাষার পরিবর্তন করে যায়?



১. মেসেজ ফাইল থেকে ডাটা পড়া


যদি আপনার আইটেম সীমিত সংখ্যক হয় এবং খুব বেশি পরিবর্তন দরকার না হয়, তাহলে লেআউট স্টাইলে করতে পারেন। মানে হচ্ছে লেআউ প্লেস হোল্ডার বসিয়ে কাজ করা।


সুবিধা

  • সহজ
  • নতুন কিছু করতে হলো না


সমস্যা

  • আইটেম বড় হতে থাকলে খুব বাজে হয়ে যায়।
  • লেআউট ও ডাটা দুই ধরণের জিনিস। এক ফাইলে না মেশানোই ভালো





২. আইটেমের টেবিলে একাধিক ভাষার জন্য একটা করে কলাম রাখা



@Entity
@Table
public class Post {

    private String titleEN;

    private String titleBN;

    private String titleDE;

    private String titleAR;

    private String descriptionEN;

    private String descriptionBN;

    private String descriptionDE;

    private String descriptionAR;
}


সুবিধা

  • সহজ
  • যদি ভাষার সংখ্যা অল্প এবং ফিক্সড হয় তাহলে করা যেতে পারে।


সমস্যা

  • টেবিলে কলামের সংখ্যা অনেক বেড়েযায়
  • স্কেল করার কোন বুদ্বি নাই
  • প্রপার ইঞ্জিনিয়ারিং সলুশন হল না





৩. ট্রান্সলেশনের জন্য আলাদা একটা টেবিল রাখা, যেখানে সব ট্রান্সলেশন থাকবে



@Entity
@Table
public class Post {

    @OneToOne
    private Translation titleTranslation;

    @OneToOne
    private Translation descriptionTranslation;        
}





@Entity
@Table
public class Translation {

    private String textEN;

    private String textDE;

    private String textBN;

    private String textAR;
}

সুবিধা

  • সহজ
  • সহজেই বর্তমান ডাটা মডেলকে লোকালইজেশনে পরিবর্তন করে ফেলা যাবে


অসুবিধা

  • প্রচুর নাল ফিল্ড থাকবে
  • পুরাপুরি ডাইনামিক হবে না, নতুন ভাষা যুক্ত করতে চাইলে ডাটা মডেল পরিবর্তন করতে হবে



৪. ট্রান্সলেশন এর জন্য আলাদা কি ভ্যালু টেবিল বানানো




@Entity
@Table
public class Post {

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private Set<Translation> titles;
    
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private Set<Translation> descriptions;
}





@Entity
@Table
public class Translation {

    private String value;

    private Language language;
}
 





@Table(name = "LANGUAGE")
public class Language  {

    @Column(nullable = false)
    private String name;

    @Column(nullable = false)
    private String code;
}



সুবিধা

  • তুলনামূল বেশ ভালো বুদ্বি
  • ডাইনামিক হয় এবং নাল কমে আসে


অসুবিধা

  • যেহেতু সবার জন্য একই কলাম। ধরি কোন একটা ফিল্ডে ২০ হাজার অক্ষর লাগে, আবার কোন ফিল্ডে ২৫৫ অক্ষর লাগে, তাহলে সব ফিল্ডের জন্য ২০,০০০ দিয়ে আসতে হয়।
  • কোন একটা ল্যাঙ্গুয়েজের জন্য আলাদা করে কোয়েরি করা যায় না





৫. প্রতিটা মডেলের জন্য কি ভ্যালু টেবিল বানানো



@Entity
@Table
public class Post {
    @OneToMany
    private Set<PostTranslation> titles;
    
    @OneToMany
    private Set<PostTranslation> descriptions;
}





@Entity
@Table
public class PostTranslation {

    private String text;

    private Language language;
}
 






@Table(name = "LANGUAGE")
public class Language  {

    @Column(nullable = false)
    private String name;

    @Column(nullable = false)
    private String code;
}



  • প্রতিটা মডেলের জন্য আলাদা ট্রান্সলেশন টেবিল বানানো যেন ল্যাঙ্গুয়েজ ধরে কুয়েরি করা যায়
  • এইটা বেশ ভালো কিন্তু সব ফিল্ডের জন্য একই সাইজ আর প্রোডাক্ট ফিল্ডে এক্সট্রা ট্রান্সলেশন ফিল্ড।





৬. ট্রান্সলেশন ফিল্ড ও টেবিল আলাদা করে ফেলা



@Setter
@Getter
@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "LANGUAGE")
public class Language  {

    private static final long serialVersionUID = 1L;

    @Id @GeneratedValue(generator="system-uuid")
    @GenericGenerator(name="system-uuid", strategy = "uuid")
    @Column(length = 32)
    protected String id;

    @Column(nullable = false)
    private String name;

    @Column(nullable = false)
    private String code;

    @Column(columnDefinition = "TINYINT", length = 1)
    private Boolean rtl;

    @Column(columnDefinition = "TINYINT", length = 1)
    private Boolean preferred;

    Integer position;
}





@Setter
@Getter
@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "POST")
public class Post extends BaseAuditEntity {

    private static final long serialVersionUID = 1L;

    @Id @GeneratedValue(generator="system-uuid")
    @GenericGenerator(name="system-uuid", strategy = "uuid")
    @Column(length = 32)
    protected String id;

    @NotNull
    @Size(max = 100)
    private String name;
 
    @Column(columnDefinition = "TINYINT", length = 1)
    private Boolean enabled;

    @JoinTable(joinColumns = @JoinColumn(name = "post_id"), 
               inverseJoinColumns = @JoinColumn(name = "post_translation_id"))
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @MapKeyColumn(name = "localization", length = 2)
    private Map<String, PostTranslation> translations;
}





@Setter
@Getter
@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "POST_TRANSLATION")
public class PostTranslation {

    private static final long serialVersionUID = 1L;

    @Id @GeneratedValue(generator="system-uuid")
    @GenericGenerator(name="system-uuid", strategy = "uuid")
    @Column(length = 32)
    protected String id;

    @Size(max = 150)
    private String title;

    @Size(max = 250)
    private String description;

    @Lob
    @Basic(fetch=FetchType.LAZY)
    private String body;

    @NotNull
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name="language_id")
    private Language language;
}

আমার মতে এইটা বেস্ট । ডাইনামিক, ল্যাঙ্গুয়েজ ধরে কুয়েরি করা যাবে এবং ক্লিন, রিডেবল ডাটা মডেল।